Click here to Skip to main content
15,893,668 members
Articles / Programming Languages / C#

STUN Client

Rate me:
Please Sign up or sign in to vote.
4.83/5 (36 votes)
20 Apr 2007CPOL 328.5K   14.9K   85  
STUN client C# implementation with sample application
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Timers;

using LumiSoft.Net.AUTH;
using LumiSoft.Net.SIP.Message;
using LumiSoft.Net.SIP.Stack;

namespace LumiSoft.Net.SIP.Proxy
{
    #region Delegates

    /// <summary>
    /// Represents the method that will handle the SIP_Stack.SIP_Stack.IsLocalUri event.
    /// </summary>
    /// <param name="uri">Request URI.</param>
    /// <returns>Returns true if server local URI, otherwise false.</returns>
    public delegate bool IsLocalUriEventHandler(string uri);

    #endregion

    /// <summary>
    /// Implements SIP registrar,statefull and stateless proxy.
    /// </summary>
    public class SIP_ProxyCore : SIP_Core
    {
        private SIP_Stack                  m_pSipStack      = null;
        private SIP_ProxyMode              m_ProxyMode      = SIP_ProxyMode.Registrar | SIP_ProxyMode.Statefull;
        private SIP_RegistrationCollection m_pRegistrations = null;
        private SIP_ForkingMode            m_ForkingMode    = SIP_ForkingMode.Parallel;
        private Timer                      m_pTimer         = null;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="sipStack">Reference to SIP stack.</param>
        public SIP_ProxyCore(SIP_Stack sipStack)
        {
            m_pSipStack = sipStack;

            m_pRegistrations = new SIP_RegistrationCollection();

            m_pTimer = new Timer(15000);
            m_pTimer.Elapsed += new ElapsedEventHandler(m_pTimer_Elapsed);
            m_pTimer.Enabled = true;
        }


        #region method m_pTimer_Elapsed

        private void m_pTimer_Elapsed(object sender,ElapsedEventArgs e)
        {
            m_pRegistrations.RemoveExpired();
        }

        #endregion
                                        
        
        #region method OnRequestReceived

        /// <summary>
        /// This method is called when new request is received.
        /// </summary>
        /// <param name="e">Request event arguments.</param>
        public override void OnRequestReceived(SIP_RequestReceivedEventArgs e)
        {
            // TODO: See if Request-URI is meant for our proxy (is local).
            // If local, handle request in this proxy
            // If not local, forward request to destination URI.

            /* RFC 3261 16.12. ????????? Forward does all thse steps.
                1. The proxy will inspect the Request-URI.  If it indicates a
                   resource owned by this proxy, the proxy will replace it with
                   the results of running a location service.  Otherwise, the
                   proxy will not change the Request-URI.

                2. The proxy will inspect the URI in the topmost Route header
                   field value.  If it indicates this proxy, the proxy removes it
                   from the Route header field (this route node has been reached).

                3. The proxy will forward the request to the resource indicated
                   by the URI in the topmost Route header field value or in the
                   Request-URI if no Route header field is present.  The proxy
                   determines the address, port and transport to use when
                   forwarding the request by applying the procedures in [4] to that URI.
            */

            try{
                SIP_Request request = e.Request;

                #region Registrar

                // Registrar
                if((m_ProxyMode & SIP_ProxyMode.Registrar) != 0 && request.Method == "REGISTER"){
                    Register(e);
                }

                #endregion

                #region Presence
/*
                // Presence
                else if((m_ProxyMode & SIP_ProxyMode.Presence) != 0 && (request.Method == "SUBSCRIBE" || request.Method == "NOTIFY")){

                }
*/
                #endregion

                #region Statefull

                // Statefull
                else if((m_ProxyMode & SIP_ProxyMode.Statefull) != 0){
                    // Statefull proxy is transaction statefull proxy only, 
                    // what don't create dialogs and keep dialog state.

                    // ACK never creates transaction, it's always passed directly to transport layer.
                    if(e.Request.Method == SIP_Methods.ACK){
                        ForwardRequest(false,e);
                    }
                    else{
                        ForwardRequest(true,e);
                    }
                }

                #endregion
                
                // NOT IMPLEMENTED
                #region CallStatefull
                /*
                // CallStatefull
                else if((m_ProxyMode & SIP_ProxyMode.CallStatefull) != 0){
                    if(request.Method == "BYE"){
                        // Statefull proxy never should get BYE, that means we don't have matching dialog.                    
                        e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.Call_Transaction_Does_Not_Exist));                     
                    }
                    else if(request.Method == "CANCEL"){
                        // We never should get CANCEL here, because we handle CANCEL in server transaction.
                        // (This is not RFC compilant, but why the heck we can't do so ?, if somebody knows let me know)

                        e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.Call_Transaction_Does_Not_Exist));
                    }
                    else if(request.Method == "ACK"){
                        // We never should get ACK here, because we handle ACK in server transaction.
                        // (This is not RFC compilant, but why the heck we can't do so ?, if somebody knows let me know)
                    }
                    else{
                        ForwardRequest(true,e);
                    }
                }*/

                #endregion

                #region Stateless

                // Stateless
                else if((m_ProxyMode & SIP_ProxyMode.Stateless) != 0){
                    // Stateless proxy don't do transaction, just forwards all.
                    ForwardRequest(false,e);
                }

                #endregion

                #region Proxy won't accept command
                
                else{
                    e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented));
                }

                #endregion

            }
            catch(Exception x){   
                m_pSipStack.TransportLayer.SendResponse(null,e.Request.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error));

                // Don't raise OnError for transport errors.
                if((x is SIP_TransportException)){
                    m_pSipStack.OnError(x);
                }
            }            
        }

        #endregion

        #region method OnResponseReceived

        /// <summary>
        /// This method is called when new response is received.
        /// </summary>
        /// <param name="e">Response event arguments.</param>
        public override void OnResponseReceived(SIP_ResponseReceivedEventArgs e)
        {
            /* This method is called when stateless proxy gets response or statefull proxy
               has no matching server transaction.
            */
                  
            /* RFC 3261 16.11.
                When a response arrives at a stateless
                proxy, the proxy MUST inspect the sent-by value in the first
                (topmost) Via header field value.  If that address matches the proxy,
                (it equals a value this proxy has inserted into previous requests)
                the proxy MUST remove that header field value from the response and
                forward the result to the location indicated in the next Via header
                field value.
            */
            // Just remove topmost Via:, sent-by check is done in transport layer.
            e.Response.Via.RemoveTopMostValue();

            if((m_ProxyMode & SIP_ProxyMode.Statefull) != 0){
                // We should not reach here. This happens when no matching client transaction found.
                // RFC 3161 18.1.2 orders to forward them statelessly.
                m_pSipStack.TransportLayer.SendResponse(null,e.Response);
            }
            else if((m_ProxyMode & SIP_ProxyMode.Stateless) != 0){
                m_pSipStack.TransportLayer.SendResponse(null,e.Response);
            }
        }

        #endregion

// FIX
        #region mehtod Register

        /// <summary>
        /// Handles REGISTER method.
        /// </summary>
        /// <param name="e">Request event arguments.</param>
        private void Register(SIP_RequestReceivedEventArgs e)
        {
            /* RFC 3261 10.3 Processing REGISTER Requests.
                1. The registrar inspects the Request-URI to determine whether it
                    has access to bindings for the domain identified in the Request-URI.
                      
                2. To guarantee that the registrar supports any necessary extensions, 
                   the registrar MUST process the Require header field.
                     
                3. A registrar SHOULD authenticate the UAC.
                     
                4. The registrar SHOULD determine if the authenticated user is
                   authorized to modify registrations for this address-of-record.
                     
                5. The registrar extracts the address-of-record from the To header
                   field of the request.  If the address-of-record is not valid
                   for the domain in the Request-URI, the registrar MUST send a
                   404 (Not Found) response and skip the remaining steps.
                     
                6. The registrar checks whether the request contains the Contact
                   header field.  If not, it skips to the last step.  If the
                   Contact header field is present, the registrar checks if there
                   is one Contact field value that contains the special value "*"
                   and an Expires field.  If the request has additional Contact
                   fields or an expiration time other than zero, the request is
                   invalid, and the server MUST return a 400 (Invalid Request).
                     
                7. The registrar now processes each contact address in the Contact
                   eader field in turn.
                     
                   If Expire paremeter specified, check that it isnt smaller than server minimum
                   allowed expire value. If smaller return error 423 (Interval Too Brief) and add 
                   header field Min-Expires. If no expire parameter, user server default value.                          
                     
                8. The registrar returns a 200 (OK) response.  The response MUST contain Contact 
                   header field values enumerating all current bindings. Each Contact value MUST 
                   feature an "expires" parameter indicating its expiration interval chosen by the
                   registrar. The response SHOULD include a Date header field.

            */

            SIP_Request request = e.Request;
            SocketEx socket = request.Socket;

            // 3. Authenticate request
            string userName = null;
            if(!AuthenticateRequest(e,out userName)){
                return;
            }
                        
            // 5. Get To:.
            // We have not SIP URI, To: may be SIP URI only.
            if(!(request.To.Address.IsSipUri || request.To.Address.IsSecureSipUri)){
                e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x400_Bad_Request + ": To: value must be SIP URI."));
                return;
            }
            SIP_Uri to = SIP_Uri.Parse(request.To.Address.Uri);

            // 4. Check if user can register specified address.
            // User is not allowed to register specified address.
            if(!OnCanRegister(userName,to.Address)){
                e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x403_Forbidden));
                return;
            }
                                        
            //--- 6. And 7. --------------------------------------------------------
            SIP_t_ContactParam[] contacts = request.Contact.GetAllValues();
            if(request.Header.Contains("Contact:")){                
                SIP_t_ContactParam starContact = null;
                foreach(SIP_t_ContactParam contact in contacts){
                    // We have STAR contact, store it.
                    if(starContact == null && contact.IsStarContact){
                        starContact = contact;                               
                    }

                    /* RFC 3261 10.2.2.
                        Expire value 0 in Expire header or contact expire parameter, means that specified
                        contact must be removed. 
                        If STAR contact then expires must be always 0.
                    */

                    //--- Handle minimum expires time --------------------------------------------
                    // Get contact expires time, if not specified, get header expires time, if not specified,
                    // use server minimum value.
                    int expires = contact.Expires;
                    if(expires == -1){
                        expires = request.Expires;
                    }
                    if(expires == -1){
                        expires = m_pSipStack.MinimumExpireTime;
                    }
                    // We don't check that for STAR contact and if contact expires parameter = 0.
                    if(!contact.IsStarContact && expires != 0 && expires < m_pSipStack.MinimumExpireTime){                        
                        SIP_Response sipExpiresResponse = request.CreateResponse(SIP_ResponseCodes.x423_Interval_Too_Brief);

                        // RFC 3261 20.23 must add Min-Expires for "Interval Too Brief".
                        sipExpiresResponse.MinExpires = m_pSipStack.MinimumExpireTime;

                        // The response SHOULD include a Date header field.
                        sipExpiresResponse.Date = DateTime.Now;
                                               
                        // Send response
                        e.ServerTransaction.SendResponse(sipExpiresResponse);
                        return;
                    }
                    //---------------------------------------------------------------------------
                }

                // We have STAR contact. Check that STAR contact meets all RFC rules.
                if(starContact != null){
                    // RFC 3261 10.3-6. We may have only 1 STAR Contact and Expires: header field must be 0.
                    if(contacts.Length > 1 || request.Expires != 0){                        
                        e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x400_Bad_Request + ". Invalid STAR Contact: combination or parameter. For more info see RFC 3261 10.3.6."));
                        return;
                    }
                    // We have valid STAR Contact:.
                    //else{
                    //}
                }
            }
                    
            // Add or update SIP registration
            SIP_Registration registration = null;
            lock(m_pRegistrations){
                if(!m_pRegistrations.Contains(to.Address)){
                    // Add SIP registration.
                    registration = new SIP_Registration(userName,to.Address);
                    m_pRegistrations.Add(registration);
                }
                // Update SIP registration contacts
                else{
                    registration = m_pRegistrations[to.Address];
                }
            }

            // Update registration contacts.
            registration.UpdateContacts(contacts,request.Expires,m_pSipStack.MinimumExpireTime);
            //--------------------------------------------------------------------

            // 8. --- Make and send SIP respone ----------------------------------
            SIP_Response sipResponse = request.CreateResponse(SIP_ResponseCodes.x200_Ok);
            
            // The response SHOULD include a Date header field.
            sipResponse.Date = DateTime.Now;

            // List Registered Contacts. We also need to return expires parameter with remaining time.
            sipResponse.Header.RemoveAll("Contact:");
            foreach(SIP_RegistrationContact contact in registration.Contacts){
                // Don't list expired contacts what wait to be disposed.
                if(contact.Expires > 1){
                    sipResponse.Header.Add("Contact:",contact.ToStringValue());
                }
            }

            // Add Authentication-Info:, then client knows next nonce.
            sipResponse.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pSipStack.DigestNonceManager.CreateNonce() + "\"");
                                               
            // Send response
            e.ServerTransaction.SendResponse(sipResponse);
            //-----------------------------------------------------------------------
        }

        #endregion

        #region method ForwardRequest

        /// <summary>
        /// Forwards specified request to destination recipient.
        /// </summary>
        /// <param name="statefull">Specifies if request is sent statefully or statelessly.</param>
        /// <param name="e">Request event arguments.</param>
        private void ForwardRequest(bool statefull,SIP_RequestReceivedEventArgs e)
        {
            /* RFC 3261 16.6. Request Forwarding
                A stateful proxy must have a mechanism to maintain the target set as
                responses are received and associate the responses to each forwarded
                request with the original request.  For the purposes of this model,
                this mechanism is a "response context" created by the proxy layer
                before forwarding the first request.
                          
                1.  Make a copy of the received request
                2.  Update the Request-URI
                3.  Update the Max-Forwards header field (-1)
                4.  Optionally add a Record-route header field value
                5.  Optionally add additional header fields
                6.  Postprocess routing information
                7.  Determine the next-hop address, port, and transport
                8.  Add a Via header field value
                9.  Add a Content-Length header field if necessary
                10. Forward the new request
                11. Set timer C
            */
                        
            // 1. Make a copy of the received request
            SIP_Request forwardRequest = SIP_Request.Parse(e.Request.ToByteData());

            // 2. Update the Request-URI
            //    Is that what we do in 7. route handling ? If someone knows let me know.

            // 3. Update the Max-Forwards header field.   
            //    MaxForwards not specified, default is 70.
            if(forwardRequest.MaxForwards != -1){
                forwardRequest.MaxForwards = 70;
            }
            // MaxForwards hop limit reached.
            else if(forwardRequest.MaxForwards < 1){
                e.ServerTransaction.ProcessResponse(forwardRequest.CreateResponse(SIP_ResponseCodes.x483_Too_Many_Hops));
                return;
            }
            forwardRequest.MaxForwards = forwardRequest.MaxForwards - 1;

            // 4. Optionally add a Record-route header field value.
            //    We need to do it always, "host name" specified or positive ACK never sent through server,
            //    then all NAT types won't work.
            if(!string.IsNullOrEmpty(m_pSipStack.HostName)){
                forwardRequest.RecordRoute.Add("<sip:" + m_pSipStack.HostName + ";lr>");
            }
                                                
            /* 7. Determine the next-hop address, port, and transport.
                1) If local address, forward request to registrar contact address(es).
                2) If there is Route header field, we always need send request to specified uri.
                3) Send request to To request-URI.
            */

            /* We currently allow only SIP URIs. Do we need others or how then to handle them ?
            */            
            SIP_Uri requestUri = null;
            try{
                requestUri = SIP_Uri.Parse(e.Request.Uri);
            }
            catch{
                // Not valid SIP URI
                e.ServerTransaction.ProcessResponse(e.Request.CreateResponse(SIP_ResponseCodes.x400_Bad_Request + ": Not supported Request-URI."));
                return;
            }

            List<SIP_Uri> remoteURIs = new List<SIP_Uri>();
            
            // TODO: Is it more exacter to check To: instead request-URI, because request-URI can be IP address ?
            
            string uri = requestUri.Host;
            try{
                uri = SIP_Uri.Parse(forwardRequest.To.Address.Uri).Host;
            }
            catch{
                // Non SIP URI. If request-URI is SIP, the we let it through, otherwise we don't know
                // what todo with it.
                // TODO:
            }
      
            // Local address
            if(this.OnIsLocalUri(uri)){
            //if(this.OnIsLocalUri(requestUri.Host)){        
                //SIP_Registration registration = m_pRegistrations[requestUri.Address];
                SIP_Registration registration = m_pRegistrations[SIP_Uri.Parse(forwardRequest.To.Address.Uri).Address];
                // No registration or no contact(s) available now.
                if(registration == null || registration.SipContacts.Length == 0){
                    // User just not available now.
                    if(this.OnAddressExists(requestUri.Address)){
                        e.ServerTransaction.ProcessResponse(e.Request.CreateResponse(SIP_ResponseCodes.x480_Temporarily_Unavailable));
                    }
                    // User not found.
                    else{
                        e.ServerTransaction.ProcessResponse(e.Request.CreateResponse(SIP_ResponseCodes.x404_Not_Found));
                    }                    
                    return;
                }
                // Add all contacts to list.
                remoteURIs.AddRange(registration.SipContacts);
            }
            // Remote address
            else{
                // Authenticate request. We may not require for authentication ACK !
                if(forwardRequest.Method != SIP_Methods.ACK && !AuthenticateRequest(e)){
                    return;
                }

                SIP_t_AddressParam route = forwardRequest.Route.GetTopMostValue();
                // There is route. We accept SIP URIs in route only. If non SIP URI, skip route.
                if(route != null && (route.Address.IsSipUri || route.Address.IsSecureSipUri)){                    
                    // Loose route.  Loose route don't change route header, just send request to topmost value.
                    if(route.Parameters["lr"] != null){
                        remoteURIs.Add(SIP_Uri.Parse(route.Address.Uri));
                    }
                    /* Strict route. 
                        1) Append current Request-URI to Route: header.
                        2) Put route value to Request-URI. NOTE: We need to remove not allowed Request-URI prameters !
                        3) Remove added route from Route: header.                 
                    */
                    else{
                        forwardRequest.Route.Add(forwardRequest.Uri);
                        forwardRequest.Uri = SIP_Utils.UriToRequestUri(route.Address.Uri);
                        forwardRequest.Route.RemoveTopMostValue();
                        remoteURIs.Add(SIP_Uri.Parse(route.Address.Uri));
                    }                
                }
                // Use Request-URI
                else{
                    remoteURIs.Add(requestUri);
                }
            }
                                    
            // 8. Add a Via header field value.
            //    We need to add it only for stateless proxy, for statefull client transaction adds it.
            //    We do it in 10.

            // 9. Add a Content-Length header field if necessary.
            //    Don't need, our SIP_Message class is smart and do it when ever needed.
            
            // 10. Forward request
            if(statefull){                                
                /*  RFC 3841 9.1.
                    The Request-Disposition header field specifies caller preferences for
                    how a server should process a request.
                */
                SIP_ForkingMode forkingMode = m_ForkingMode;
                bool noCancel  = false;
                bool noRecurse = false;
                foreach(SIP_t_Directive directive in forwardRequest.RequestDisposition.GetAllValues()){
                    if(directive.Directive == SIP_t_Directive.DirectiveType.NoFork){
                        forkingMode = SIP_ForkingMode.None;
                    }
                    else if(directive.Directive == SIP_t_Directive.DirectiveType.Parallel){
                        forkingMode = SIP_ForkingMode.Parallel;
                    }
                    else if(directive.Directive == SIP_t_Directive.DirectiveType.Sequential){
                        forkingMode = SIP_ForkingMode.Sequential;
                    }                    
                    else if(directive.Directive == SIP_t_Directive.DirectiveType.NoCancel){
                        noCancel = true;
                    }                    
                    else if(directive.Directive == SIP_t_Directive.DirectiveType.NoRecurse){
                        noRecurse = true;
                    }
                }
                
                // Create proxy context that will be responsible for forwarding request.
                SIP_ProxyContext proxyContext = new SIP_ProxyContext(
                    m_pSipStack,
                    forwardRequest,
                    forkingMode,
                    noCancel,
                    noRecurse,
                    remoteURIs.ToArray()
                );
                proxyContext.Start();
            }
            else{
                /* RFC 3261 16.11
                    However, a stateless proxy cannot simply use a random number generator to compute
                    the first component of the branch ID, as described in Section 16.6 bullet 8.
                    This is because retransmissions of a request need to have the same value, and 
                    a stateless proxy cannot tell a retransmission from the original request.
                
                    We just use: "z9hG4bK-" + md5(topmost branch)                
                */   
                             
                string transport = forwardRequest.Via.GetTopMostValue().ProtocolTransport;
                // ACK never may adds Via: !!!
                if(forwardRequest.Method != SIP_Methods.ACK){
                    forwardRequest.Via.AddToTop("SIP/2.0/" + transport + " tl-will-assign-it;branch=z9hG4bK-" + Core.ComputeMd5(forwardRequest.Via.GetTopMostValue().Branch));
                }

                m_pSipStack.TransportLayer.SendRequest(forwardRequest,remoteURIs[0].HostPort,transport);
            }
        }

        #endregion

        #region method AuthenticateRequest

        /// <summary>
        /// Authenticates SIP request. This method also sends all needed replys to request sender.
        /// </summary>
        /// <param name="e">Request event arguments.</param>
        /// <returns>Returns true if request was authenticated.</returns>
        private bool AuthenticateRequest(SIP_RequestReceivedEventArgs e)
        {
            string userName = null;
            return AuthenticateRequest(e,out userName);
        }

        /// <summary>
        /// Authenticates SIP request. This method also sends all needed replys to request sender.
        /// </summary>
        /// <param name="e">Request event arguments.</param>
        /// <param name="userName">If authentication sucessful, then authenticated user name is stored to this variable.</param>
        /// <returns>Returns true if request was authenticated.</returns>
        private bool AuthenticateRequest(SIP_RequestReceivedEventArgs e,out string userName)
        {
            // TODO: If multiple auth headers, what then ???
            
            userName = null;
  
            // User didn't supplied credentials. 
            if(e.Request.Authorization.Count == 0){
                SIP_Response notAuthenticatedResponse = e.Request.CreateResponse(SIP_ResponseCodes.x401_Unauthorized);
                notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"" + m_pSipStack.HostName + "\",qop=\"auth\",nonce=\"" + m_pSipStack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");
                    
                e.ServerTransaction.ProcessResponse(notAuthenticatedResponse);
                return false;
            }
 
            // Check that client supplied supported authentication method.
            // digest
            if(e.Request.Authorization.GetFirst().ValueX.Method.ToLower() == "digest"){
            }
            // Not supported authentication.
            else{
                e.ServerTransaction.ProcessResponse(e.Request.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented + " authentication method"));
                return false;
            }
                                     
            Auth_HttpDigest auth = new Auth_HttpDigest(e.Request.Authorization.GetFirst().ValueX.AuthData,e.Request.Method);        
            // Check nonce validity
            if(!m_pSipStack.DigestNonceManager.NonceExists(auth.Nonce)){
                SIP_Response notAuthenticatedResponse = e.Request.CreateResponse(SIP_ResponseCodes.x401_Unauthorized);
                notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"" + m_pSipStack.HostName + "\",qop=\"auth\",nonce=\"" + m_pSipStack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");

                // Send response
                e.ServerTransaction.ProcessResponse(notAuthenticatedResponse);
                return false;
            }
            // Valid nonce, consume it so that nonce can't be used any more. 
            else{
                m_pSipStack.DigestNonceManager.RemoveNonce(auth.Nonce);
            }

            AuthenticateEventArgs eArgs = this.OnAuthenticate(auth);
            // Authenticate failed.
            if(!eArgs.Authenticated){
                SIP_Response notAuthenticatedResponse = e.Request.CreateResponse(SIP_ResponseCodes.x401_Unauthorized);
                notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"" + m_pSipStack.HostName + "\",qop=\"auth\",nonce=\"" + m_pSipStack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");
                    
                // Send response
                e.ServerTransaction.ProcessResponse(notAuthenticatedResponse);
                return false;
            }

            userName = auth.UserName;

            return true;
        }

        #endregion


        #region method SetRegistration

        /// <summary>
        /// Add or updates specified SIP registration info.
        /// </summary>
        /// <param name="addressOfRecord">Registration address of record.</param>
        /// <param name="contacts">Registration address of record contacts to update.</param>
        public void SetRegistration(string addressOfRecord,SIP_t_ContactParam[] contacts)
        {
            lock(m_pRegistrations){
                SIP_Registration registration = m_pRegistrations[addressOfRecord];
                if(registration == null){
                    registration = new SIP_Registration("system",addressOfRecord);
                    m_pRegistrations.Add(registration);
                }

                registration.UpdateContacts(contacts,180,m_pSipStack.MinimumExpireTime);
            }
        }

        #endregion

        #region method DeleteRegistration

        /// <summary>
        /// Deletes specified registration and all it's contacts.
        /// </summary>
        /// <param name="addressOfRecord">Registration address of record what to remove.</param>
        public void DeleteRegistration(string addressOfRecord)
        {
            m_pRegistrations.Remove(addressOfRecord);
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets owner SIP stack.
        /// </summary>
        public SIP_Stack Stack
        {
            get{ return m_pSipStack; }
        }

        /// <summary>
        /// Gets or sets proxy mode.
        /// </summary>
        public SIP_ProxyMode ProxyMode
        {
            get{ return m_ProxyMode; }

            set{
                // Check for invalid mode ()
                if((value & SIP_ProxyMode.Statefull) != 0 && (value & SIP_ProxyMode.Stateless) != 0){
                    throw new ArgumentException("Proxy can't be at Statefull and Stateless at same time !");
                }

                m_ProxyMode = value;
            }
        }

        /// <summary>
        /// Gets or sets how proxy handle forking. This property applies for statefull proxy only.
        /// </summary>
        public SIP_ForkingMode ForkingMode
        {
            get{ return m_ForkingMode; }

            set{ m_ForkingMode = value; }
        }

        /// <summary>
        /// Gets current SIP registrations.
        /// </summary>
        public SIP_Registration[] Registrations
        {
            get{
                lock(m_pRegistrations){
                    SIP_Registration[] retVal = new SIP_Registration[m_pRegistrations.Count];
                    m_pRegistrations.Values.CopyTo(retVal,0);

                    return retVal;
                }
            }
        }

        #endregion

        #region Events Implementation

        /// <summary>
        /// This class provides data for SIP_Stack.Authenticate event.
        /// </summary>
        public class AuthenticateEventArgs
        {
            private Auth_HttpDigest m_pAuth         = null;
            private bool            m_Authenticated = false;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="auth">Authentication context.</param>
            public AuthenticateEventArgs(Auth_HttpDigest auth)
            {
                m_pAuth = auth;
            }


            #region Properties Implementation

            /// <summary>
            /// Gets authentication context.
            /// </summary>
            public Auth_HttpDigest AuthContext
            {
                get{ return m_pAuth; }
            }

            /// <summary>
            /// Gets or sets if specified request is authenticated.
            /// </summary>
            public bool Authenticated
            {
                get{ return m_Authenticated; }

                set{ m_Authenticated = value; }
            }

            #endregion

        }
                
        /// <summary>
        /// This event is raised when SIP proxy needs to know if specified request URI is local URI or remote URI.
        /// </summary>
        public event IsLocalUriEventHandler IsLocalUri = null;

        #region mehtod OnIsLocalUri

        /// <summary>
        /// Raises 'IsLocalUri' event.
        /// </summary>
        /// <param name="uri">Request URI.</param>
        /// <returns>Returns true if server local URI, otherwise false.</returns>
        internal bool OnIsLocalUri(string uri)
        {
            if(this.IsLocalUri != null){
                return this.IsLocalUri(uri);
            }

            return true;
        }

        #endregion

        /// <summary>
        /// Represents the method that will handle the SIP_Stack.Authenticate event.
        /// </summary>
        public delegate void AuthenticateEventHandler(AuthenticateEventArgs e);

        /// <summary>
        /// This event is raised when SIP proxy or registrar server needs to authenticate user.
        /// </summary>
        public event AuthenticateEventHandler Authenticate = null;

        /// <summary>
        /// Represents the method that will handle the SIP_Stack.AddressExists event.
        /// </summary>
        /// <param name="address">SIP address to check.</param>
        /// <returns>Returns true if specified address exists, otherwise false.</returns>
        public delegate bool AddressExistsEventHandler(string address);

        /// <summary>
        /// This event is raised when SIP proxy needs to know if specified local server address exists.
        /// </summary>
        public event AddressExistsEventHandler AddressExists = null;

        /// <summary>
        /// Represents the method that will handle the SIP_Stack.CanRegister event.
        /// </summary>
        /// <param name="userName">Authenticated user name.</param>
        /// <param name="address">Address to be registered.</param>
        /// <returns>Returns true if specified user can register specified address, otherwise false.</returns>
        public delegate bool CanRegisterEventHandler(string userName,string address);

        /// <summary>
        /// This event is raised when SIP registrar need to check if specified user can register specified address.
        /// </summary>
        public event CanRegisterEventHandler CanRegister = null;

                
        #region method OnAuthenticate

        /// <summary>
        /// Is called by SIP proxy or registrar server when it needs to authenticate user.
        /// </summary>
        /// <param name="auth">Authentication context.</param>
        /// <returns></returns>
        internal AuthenticateEventArgs OnAuthenticate(Auth_HttpDigest auth)
        {
            AuthenticateEventArgs eArgs = new AuthenticateEventArgs(auth);
            if(this.Authenticate != null){
                this.Authenticate(eArgs);
            }

            return eArgs;
        }

        #endregion

        #region method OnAddressExists

        /// <summary>
        /// Is called by SIP proxy if it needs to check if specified address exists.
        /// </summary>
        /// <param name="address">SIP address to check.</param>
        /// <returns>Returns true if specified address exists, otherwise false.</returns>
        internal bool OnAddressExists(string address)
        {            
            if(this.AddressExists != null){
                return this.AddressExists(address);
            }
                        
            return false;
        }

        #endregion

        #region method OnCanRegister

        /// <summary>
        /// Is called by SIP registrar if it needs to check if specified user can register specified address.
        /// </summary>
        /// <param name="userName">Authenticated user name.</param>
        /// <param name="address">Address to be registered.</param>
        /// <returns>Returns true if specified user can register specified address, otherwise false.</returns>
        internal bool OnCanRegister(string userName,string address)
        {
            if(this.CanRegister != null){
                return this.CanRegister(userName,address);
            }

            return false;
        }

        #endregion


        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions