/// <summary> /// Gets specified realm SIP proxy credentials. Returns null if none exists for specified realm. /// </summary> /// <param name="request">SIP reques.</param> /// <param name="realm">Realm(domain).</param> /// <returns>Returns specified realm credentials or null if none.</returns> public static SIP_t_Credentials GetCredentials(SIP_Request request,string realm) { foreach(SIP_SingleValueHF<SIP_t_Credentials> authorization in request.ProxyAuthorization.HeaderFields){ if(authorization.ValueX.Method.ToLower() == "digest"){ Auth_HttpDigest authDigest = new Auth_HttpDigest(authorization.ValueX.AuthData,request.RequestLine.Method); if(authDigest.Realm.ToLower() == realm.ToLower()){ return authorization.ValueX; } } } return null; }
/// <summary> /// Authenticates user. Authenticate method chooses strongest possible authentication method supported by server, /// preference order DIGEST-MD5 -> CRAM-MD5 -> LOGIN. /// </summary> /// <param name="userName">User login name.</param> /// <param name="password">Password.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when SMTP client is not connected or is already authenticated.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>userName</b> is null.</exception> /// <exception cref="SMTP_ClientException">Is raised when SMTP server returns error.</exception> public void Authenticate(string userName,string password) { if(this.IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(!this.IsConnected){ throw new InvalidOperationException("You must connect first."); } if(this.IsAuthenticated){ throw new InvalidOperationException("Session is already authenticated."); } if(string.IsNullOrEmpty(userName)){ throw new ArgumentNullException("userName"); } if(password == null){ password = ""; } // Choose authentication method, we consider LOGIN as default. string authMethod = "LOGIN"; List<string> authMethods = new List<string>(this.SaslAuthMethods); if(authMethods.Contains("DIGEST-MD5")){ authMethod = "DIGEST-MD5"; } else if(authMethods.Contains("CRAM-MD5")){ authMethod = "CRAM-MD5"; } #region AUTH LOGIN if(authMethod == "LOGIN"){ /* LOGIN Example: C: AUTH LOGIN<CRLF> S: 334 VXNlcm5hbWU6<CRLF> VXNlcm5hbWU6 = base64("USERNAME") C: base64(username)<CRLF> S: 334 UGFzc3dvcmQ6<CRLF> UGFzc3dvcmQ6 = base64("PASSWORD") C: base64(password)<CRLF> S: 235 Ok<CRLF> */ WriteLine("AUTH LOGIN"); // Read server response. string line = ReadLine(); // Response line must start with 334 or otherwise it's error response. if(!line.StartsWith("334")){ throw new SMTP_ClientException(line); } // Send user name to server. WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(userName))); // Read server response. line = ReadLine(); // Response line must start with 334 or otherwise it's error response. if(!line.StartsWith("334")){ throw new SMTP_ClientException(line); } // Send password to server. WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(password))); // Read server response. line = ReadLine(); // Response line must start with 334 or otherwise it's error response. if(!line.StartsWith("235")){ throw new SMTP_ClientException(line); } m_pAuthdUserIdentity = new GenericIdentity(userName,"LOGIN"); } #endregion #region AUTH CRAM-MD5 else if(authMethod == "CRAM-MD5"){ /* CRAM-M5 Description: HMACMD5 key is "password". Example: C: AUTH CRAM-MD5<CRLF> S: 334 base64(md5_calculation_hash)<CRLF> C: base64(username password_hash)<CRLF> S: 235 Ok<CRLF> */ WriteLine("AUTH CRAM-MD5"); // Read server response. string line = ReadLine(); // Response line must start with 334 or otherwise it's error response. if(!line.StartsWith("334")){ throw new SMTP_ClientException(line); } HMACMD5 kMd5 = new HMACMD5(Encoding.ASCII.GetBytes(password)); string passwordHash = Net_Utils.ToHex(kMd5.ComputeHash(Convert.FromBase64String(line.Split(' ')[1]))).ToLower(); // Send authentication info to server. WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + " " + passwordHash))); // Read server response. line = ReadLine(); // Response line must start with 235 or otherwise it's error response if(!line.StartsWith("235")){ throw new SMTP_ClientException(line); } m_pAuthdUserIdentity = new GenericIdentity(userName,"CRAM-MD5"); } #endregion #region AUTH DIGEST-MD5 else if(authMethod == "DIGEST-MD5"){ /* Example: C: AUTH DIGEST-MD5<CRLF> S: 334 base64(digestChallange)<CRLF> C: base64(digestAuthorization)<CRLF> S: 334 base64(serverResponse)<CRLF> C: <CRLF> S: 235 Ok<CRLF> */ WriteLine("AUTH DIGEST-MD5"); // Read server response. string line = ReadLine(); // Response line must start with 334 or otherwise it's error response. if(!line.StartsWith("334")){ throw new SMTP_ClientException(line); } Auth_HttpDigest digestmd5 = new Auth_HttpDigest(Encoding.Default.GetString(Convert.FromBase64String(line.Split(' ')[1])),"AUTHENTICATE"); digestmd5.CNonce = Auth_HttpDigest.CreateNonce(); digestmd5.Uri = "smtp/" + digestmd5.Realm; digestmd5.UserName = userName; digestmd5.Password = password; // Send authentication info to server. WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(digestmd5.ToAuthorization(false)))); // Read server response. line = ReadLine(); // Response line must start with 334 or otherwise it's error response. if(!line.StartsWith("334")){ throw new SMTP_ClientException(line); } // Send empty line. WriteLine(""); // Read server response. line = ReadLine(); // Response line must start with 235 or otherwise it's error response. if(!line.StartsWith("235")){ throw new SMTP_ClientException(line); } m_pAuthdUserIdentity = new GenericIdentity(userName,"DIGEST-MD5"); } #endregion }
private void AUTH(string argsText) { /* Rfc 2554 AUTH --------------------------------------------------// Restrictions: After an AUTH command has successfully completed, no more AUTH commands may be issued in the same session. After a successful AUTH command completes, a server MUST reject any further AUTH commands with a 503 reply. Remarks: If an AUTH command fails, the server MUST behave the same as if the client had not issued the AUTH command. */ if(this.Authenticated){ this.Socket.WriteLine("503 already authenticated"); return; } try{ //------ Parse parameters -------------------------------------// string userName = ""; string password = ""; AuthUser_EventArgs aArgs = null; string[] param = argsText.Split(new char[]{' '}); switch(param[0].ToUpper()) { case "PLAIN": this.Socket.WriteLine("504 Unrecognized authentication type."); break; case "LOGIN": #region LOGIN authentication //---- AUTH = LOGIN ------------------------------ /* Login C: AUTH LOGIN S: 334 VXNlcm5hbWU6 C: username_in_base64 S: 334 UGFzc3dvcmQ6 C: password_in_base64 or (initial-response argument included to avoid one 334 server response) C: AUTH LOGIN username_in_base64 S: 334 UGFzc3dvcmQ6 C: password_in_base64 VXNlcm5hbWU6 base64_decoded= USERNAME UGFzc3dvcmQ6 base64_decoded= PASSWORD */ // Note: all strings are base64 strings eg. VXNlcm5hbWU6 = UserName. // No user name included (initial-response argument) if(param.Length == 1){ // Query UserName this.Socket.WriteLine("334 VXNlcm5hbWU6"); string userNameLine = this.Socket.ReadLine(); // Encode username from base64 if(userNameLine.Length > 0){ userName = System.Text.Encoding.Default.GetString(Convert.FromBase64String(userNameLine)); } } // User name included, use it else{ userName = System.Text.Encoding.Default.GetString(Convert.FromBase64String(param[1])); } // Query Password this.Socket.WriteLine("334 UGFzc3dvcmQ6"); string passwordLine = this.Socket.ReadLine(); // Encode password from base64 if(passwordLine.Length > 0){ password = System.Text.Encoding.Default.GetString(Convert.FromBase64String(passwordLine)); } aArgs = m_pServer.OnAuthUser(this,userName,password,"",AuthType.Plain); if(aArgs.Validated){ this.Socket.WriteLine("235 Authentication successful."); this.SetUserName(userName); } else{ this.Socket.WriteLine("535 Authentication failed"); } #endregion break; case "CRAM-MD5": #region CRAM-MD5 authentication /* Cram-M5 C: AUTH CRAM-MD5 S: 334 <md5_calculation_hash_in_base64> C: base64(username password_hash) */ string md5Hash = "<" + Guid.NewGuid().ToString().ToLower() + ">"; this.Socket.WriteLine("334 " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(md5Hash))); string reply = this.Socket.ReadLine(); reply = System.Text.Encoding.Default.GetString(Convert.FromBase64String(reply)); string[] replyArgs = reply.Split(' '); userName = replyArgs[0]; aArgs = m_pServer.OnAuthUser(this,userName,replyArgs[1],md5Hash,AuthType.CRAM_MD5); if(aArgs.Validated){ this.Socket.WriteLine("235 Authentication successful."); this.SetUserName(userName); } else{ this.Socket.WriteLine("535 Authentication failed"); } #endregion break; case "DIGEST-MD5": #region DIGEST-MD5 authentication /* RFC 2831 AUTH DIGEST-MD5 * * Example: * * C: AUTH DIGEST-MD5 * S: 334 base64(realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",algorithm=md5-sess) * C: base64(username="******",realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh", * nc=00000001,cnonce="OA6MHXh6VqTrRk",digest-uri="smtp/elwood.innosoft.com", * response=d388dad90d4bbd760a152321f2143af7,qop=auth) * S: 334 base64(rspauth=ea40f60335c427b5527b84dbabcdfffd) * C: * S: 235 Authentication successful. */ string nonce = Auth_HttpDigest.CreateNonce(); string opaque = Auth_HttpDigest.CreateOpaque(); Auth_HttpDigest digest = new Auth_HttpDigest(this.BindInfo.HostName,nonce,opaque); digest.Algorithm = "md5-sess"; this.Socket.WriteLine("334 " + AuthHelper.Base64en(digest.ToChallange(false))); string clientResponse = AuthHelper.Base64de(this.Socket.ReadLine()); digest = new Auth_HttpDigest(clientResponse,"AUTHENTICATE"); // Check that realm,nonce and opaque in client response are same as we specified. if(this.BindInfo.HostName != digest.Realm){ this.Socket.WriteLine("535 Authentication failed, 'realm' won't match."); return; } if(nonce != digest.Nonce){ this.Socket.WriteLine("535 Authentication failed, 'nonce' won't match."); return; } if(opaque != digest.Opaque){ this.Socket.WriteLine("535 Authentication failed, 'opaque' won't match."); return; } userName = digest.UserName; aArgs = m_pServer.OnAuthUser(this,userName,digest.Response,clientResponse,AuthType.DIGEST_MD5); if(aArgs.Validated){ // Send server computed password hash this.Socket.WriteLine("334 " + AuthHelper.Base64en("rspauth=" + aArgs.ReturnData)); // We must got empty line here clientResponse = this.Socket.ReadLine(); if(clientResponse == ""){ this.Socket.WriteLine("235 Authentication successful."); this.SetUserName(userName); } else{ this.Socket.WriteLine("535 Authentication failed, unexpected client response."); } } else{ this.Socket.WriteLine("535 Authentication failed."); } #endregion break; default: this.Socket.WriteLine("504 Unrecognized authentication type."); break; } //-----------------------------------------------------------------// } catch{ this.Socket.WriteLine("535 Authentication failed."); } }
/// <summary> /// Default constructor. /// </summary> /// <param name="requireSSL">Specifies if this mechanism is available to SSL connections only.</param> public AUTH_SASL_ServerMechanism_DigestMd5(bool requireSSL) { m_RequireSSL = requireSSL; m_Nonce = Auth_HttpDigest.CreateNonce(); }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> internal void OnRequestReceived(SIP_RequestReceivedEventArgs e) { SIP_Request request = e.Request; if(request.RequestLine.Method == SIP_Methods.CANCEL){ /* RFC 3261 9.2. If the UAS did not find a matching transaction for the CANCEL according to the procedure above, it SHOULD respond to the CANCEL with a 481 (Call Leg/Transaction Does Not Exist). Regardless of the method of the original request, as long as the CANCEL matched an existing transaction, the UAS answers the CANCEL request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pProxy.Stack.TransactionLayer.MatchCancelToTransaction(e.Request); if(trToCancel != null){ trToCancel.Cancel(); //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x200_Ok)); } else{ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } } // We never should ge BYE here, because transport layer must match it to dialog. else if(request.RequestLine.Method == SIP_Methods.BYE){ /* RFC 3261 15.1.2. If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should ge ACK here, because transport layer must match it to dialog. else if(request.RequestLine.Method == SIP_Methods.ACK){ // ACK is response less request, so we may not return error to it. } // B2BUA must respond to OPTIONS request, not to forward it. else if(request.RequestLine.Method == SIP_Methods.OPTIONS){ /* SIP_Response response = e.Request.CreateResponse(SIP_ResponseCodes.x200_Ok); // Add Allow to non ACK response. if(e.Request.RequestLine.Method != SIP_Methods.ACK){ response.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK,MESSAGE,UPDATE"); } // Add Supported to 2xx non ACK response. if(response.StatusCodeType == SIP_StatusCodeType.Success && e.Request.RequestLine.Method != SIP_Methods.ACK){ response.Supported.Add("100rel,timer"); } e.ServerTransaction.SendResponse(response);*/ } // We never should get PRACK here, because transport layer must match it to dialog. else if(request.RequestLine.Method == SIP_Methods.PRACK){ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should get UPDATE here, because transport layer must match it to dialog. else if(request.RequestLine.Method == SIP_Methods.UPDATE){ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } else{ /* draft-marjou-sipping-b2bua-00 4.1.3. When the UAS of the B2BUA receives an upstream SIP request, its associated UAC generates a new downstream SIP request with its new Via, Max-Forwards, Call-Id, CSeq, and Contact header fields. Route header fields of the upstream request are copied in the downstream request, except the first Route header if it is under the responsibility of the B2BUA. Record-Route header fields of the upstream request are not copied in the new downstream request, as Record-Route is only meaningful for the upstream dialog. The UAC SHOULD copy other header fields and body from the upstream request into this downstream request before sending it. */ SIP_Request b2buaRequest = e.Request.Copy(); b2buaRequest.Via.RemoveAll(); b2buaRequest.MaxForwards = 70; b2buaRequest.CallID = SIP_t_CallID.CreateCallID().CallID; b2buaRequest.CSeq.SequenceNumber = 1; b2buaRequest.Contact.RemoveAll(); // b2buaRequest.Contact.Add(m_pProxy.CreateContact(b2buaRequest.To.Address).ToStringValue()); if(b2buaRequest.Route.Count > 0 && m_pProxy.IsLocalRoute(SIP_Uri.Parse(b2buaRequest.Route.GetTopMostValue().Address.Uri.ToString()))){ b2buaRequest.Route.RemoveTopMostValue(); } b2buaRequest.RecordRoute.RemoveAll(); // Remove our Authorization header if it's there. foreach(SIP_SingleValueHF<SIP_t_Credentials> header in b2buaRequest.ProxyAuthorization.HeaderFields){ try{ Auth_HttpDigest digest = new Auth_HttpDigest(header.ValueX.AuthData,b2buaRequest.RequestLine.Method); if(m_pProxy.Stack.Realm == digest.Realm){ b2buaRequest.ProxyAuthorization.Remove(header); } } catch{ // We don't care errors here. This can happen if remote server xxx auth method here and // we don't know how to parse it, so we leave it as is. } } //--- Add/replace default fields. ------------------------------------------ b2buaRequest.Allow.RemoveAll(); b2buaRequest.Supported.RemoveAll(); // Accept to non ACK,BYE request. if(request.RequestLine.Method != SIP_Methods.ACK && request.RequestLine.Method != SIP_Methods.BYE){ b2buaRequest.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK"); } // Supported to non ACK request. if(request.RequestLine.Method != SIP_Methods.ACK){ b2buaRequest.Supported.Add("100rel,timer"); } // Remove Require: header. b2buaRequest.Require.RemoveAll(); // RFC 4028 7.4. For re-INVITE and UPDATE we need to add Session-Expires and Min-SE: headers. if(request.RequestLine.Method == SIP_Methods.INVITE || request.RequestLine.Method == SIP_Methods.UPDATE){ b2buaRequest.SessionExpires = new SIP_t_SessionExpires(m_pProxy.Stack.SessionExpries,"uac"); b2buaRequest.MinSE = new SIP_t_MinSE(m_pProxy.Stack.MinimumSessionExpries); } // Forward request. //m_pProxy.ForwardRequest(true,e,b2buaRequest,false); } }
/// <summary> /// Default constructor. /// </summary> /// <param name="auth">Authentication context.</param> public SIP_AuthenticateEventArgs(Auth_HttpDigest auth) { m_pAuth = auth; }
public DataSet AuthUser(string userName, string passwData, string authData, AuthType authType) { this.m_UpdSync.AddMethod(); DataSet dataSet = new DataSet(); DataTable dataTable = dataSet.Tables.Add("Result"); dataTable.Columns.Add("Result"); dataTable.Columns.Add("ReturnData"); DataRow dataRow = dataTable.NewRow(); dataRow["Result"] = "false"; dataRow["ReturnData"] = ""; dataTable.Rows.Add(dataRow); DataSet result; try { foreach (DataRow dataRow2 in this.dsUsers.Tables["Users"].Rows) { if (Convert.ToBoolean(dataRow2["Enabled"]) && dataRow2["USERNAME"].ToString().ToLower() == userName.ToLower()) { string text = dataRow2["PASSWORD"].ToString().ToLower(); if (authType != AuthType.Plain) { if (authType == AuthType.DIGEST_MD5) { Auth_HttpDigest auth_HttpDigest = new Auth_HttpDigest(authData, "AUTHENTICATE"); if (auth_HttpDigest.Authenticate(userName, text)) { dataRow["Result"] = "true"; dataRow["ReturnData"] = auth_HttpDigest.CalculateResponse(userName, text); result = dataSet; return result; } } } else { if (text == passwData.ToLower()) { dataRow["Result"] = "true"; result = dataSet; return result; } } result = dataSet; return result; } } result = dataSet; } catch (Exception ex) { throw ex; } finally { this.m_UpdSync.RemoveMethod(); } return result; }
/// <summary> /// Authenticates user. /// </summary> /// <param name="userName">User name.</param> /// <param name="passwData">Password data.</param> /// <param name="authData">Authentication specific data(as tag).</param> /// <param name="authType">Authentication type.</param> /// <returns></returns> public DataSet AuthUser(string userName,string passwData,string authData,AuthType authType) { DataSet retVal = new DataSet(); DataTable dt = retVal.Tables.Add("Result"); dt.Columns.Add("Result"); dt.Columns.Add("ReturnData"); DataRow drx = dt.NewRow(); drx["Result"] = "false"; drx["ReturnData"] = ""; dt.Rows.Add(drx); using(WSqlCommand sqlCmd = new WSqlCommand(m_ConStr,"lspr_GetUserProperties")){ sqlCmd.AddParameter("_userName",NpgsqlDbType.Varchar,userName); DataSet ds = sqlCmd.Execute(); ds.Tables[0].TableName = "Users"; if(ds.Tables["Users"].Rows.Count > 0){ string password = ds.Tables["Users"].Rows[0]["PASSWORD"].ToString().ToLower(); switch(authType) { case AuthType.APOP: if(AuthHelper.Apop(password,authData) == passwData){ drx["Result"] = "true"; return retVal; } break; case AuthType.CRAM_MD5: if(AuthHelper.Cram_Md5(password,authData) == passwData){ drx["Result"] = "true"; return retVal; } break; case AuthType.DIGEST_MD5: Auth_HttpDigest digest = new Auth_HttpDigest(authData,"AUTHENTICATE"); if(digest.Authenticate(userName,password)){ drx["Result"] = "true"; drx["ReturnData"] = digest.CalculateResponse(userName,password); return retVal; } break; case AuthType.Plain: if(password == passwData.ToLower()){ drx["Result"] = "true"; return retVal; } break; } } } return retVal; }
// REMOVE ME: #region method AuthUser /// <summary> /// Authenticates user. /// </summary> /// <param name="userName">User name.</param> /// <param name="passwData">Password data.</param> /// <param name="authData">Authentication specific data(as tag).</param> /// <param name="authType">Authentication type.</param> /// <returns></returns> public DataSet AuthUser(string userName,string passwData,string authData,AuthType authType) { m_UpdSync.AddMethod(); DataSet retVal = new DataSet(); DataTable dt = retVal.Tables.Add("Result"); dt.Columns.Add("Result"); dt.Columns.Add("ReturnData"); DataRow drx = dt.NewRow(); drx["Result"] = "false"; drx["ReturnData"] = ""; dt.Rows.Add(drx); try{ // See if user with specified name exists foreach(DataRow dr in dsUsers.Tables["Users"].Rows){ if(Convert.ToBoolean(dr["Enabled"]) && dr["USERNAME"].ToString().ToLower() == userName.ToLower()){ string password = dr["PASSWORD"].ToString().ToLower(); switch(authType) {/* case AuthType.APOP: if(AuthHelper.Apop(password,authData) == passwData){ drx["Result"] = "true"; return retVal; } break; case AuthType.CRAM_MD5: if(AuthHelper.Cram_Md5(password,authData) == passwData){ drx["Result"] = "true"; return retVal; } break;*/ case AuthType.DIGEST_MD5: Auth_HttpDigest digest = new Auth_HttpDigest(authData,"AUTHENTICATE"); if(digest.Authenticate(userName,password)){ drx["Result"] = "true"; drx["ReturnData"] = digest.CalculateResponse(userName,password); return retVal; } break; case AuthType.Plain: if(password == passwData.ToLower()){ drx["Result"] = "true"; return retVal; } break; } return retVal; } } return retVal; } catch(Exception x){ throw x; } finally{ m_UpdSync.RemoveMethod(); } }
/// <summary> /// Continues authentication process. /// </summary> /// <param name="clientResponse">Client sent SASL response.</param> /// <returns>Retunrns challange response what must be sent to client or null if authentication has completed.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>clientResponse</b> is null reference.</exception> public override string Continue(string clientResponse) { if (clientResponse == null) { throw new ArgumentNullException("clientResponse"); } /* RFC 2831. * The base64-decoded version of the SASL exchange is: * * S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth", * algorithm=md5-sess,charset=utf-8 * C: charset=utf-8,username="******",realm="elwood.innosoft.com", * nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk", * digest-uri="imap/elwood.innosoft.com", * response=d388dad90d4bbd760a152321f2143af7,qop=auth * S: rspauth=ea40f60335c427b5527b84dbabcdfffd * C: * S: ok * * The password in this example was "secret". */ if (m_State == 0) { m_State++; return("realm=\"" + m_Realm + "\",nonce=\"" + m_Nonce + "\",qop=\"auth\",algorithm=md5-sess,charset=utf-8"); } else if (m_State == 1) { m_State++; Auth_HttpDigest auth = new Auth_HttpDigest(clientResponse, "AUTHENTICATE"); auth.Qop = "auth"; auth.Algorithm = "md5-sess"; // Check realm and nonce value. if (m_Realm != auth.Realm || m_Nonce != auth.Nonce) { return("rspauth=\"\""); } m_UserName = auth.UserName; AUTH_e_UserInfo result = OnGetUserInfo(auth.UserName); if (result.UserExists) { if (auth.Authenticate(result.UserName, result.Password)) { m_IsAuthenticated = true; return("rspauth=" + auth.CalculateRspAuth(result.UserName, result.Password)); } } return("rspauth=\"\""); } else { m_IsCompleted = true; } return(null); }
/// <summary> /// Handles REGISTER method. /// </summary> /// <param name="request">SIP REGISTER request.</param> public void Register(SIP_Request request) { /* 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. */ SocketEx socket = request.Socket; // 3. ------------------------------------------------------------------------- // User didn't supplied credentials. if(request.Authorization.Count == 0){ SIP_Response notAuthenticatedResponse = request.CreateResponse(SIP_ResponseCodes.Unauthorized); notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"\",qop=\"auth\",nonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""); // Send response m_pProxy.Stack.TransportLayer.SendResponse(socket,notAuthenticatedResponse); return; } // Check that client supplied supported authentication method. string authenticationData = ""; // digest if(request.Authorization.GetFirst().ValueX.Method.ToLower() == "digest"){ authenticationData = request.Authorization.GetFirst().ValueX.AuthData; } // Not supported authentication. else{ m_pProxy.Stack.TransportLayer.SendResponse(socket,request.CreateResponse(SIP_ResponseCodes.Not_Implemented + " authentication method")); return; } Auth_HttpDigest auth = new Auth_HttpDigest(authenticationData,request.Method); // Check nonce validity if(!m_pProxy.Stack.DigestNonceManager.NonceExists(auth.Nonce)){ SIP_Response notAuthenticatedResponse = request.CreateResponse(SIP_ResponseCodes.Unauthorized); notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"\",qop=\"auth\",nonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""); // Send response m_pProxy.Stack.TransportLayer.SendResponse(socket,notAuthenticatedResponse); return; } // Valid nonce, consume it so that nonce can't be used any more. else{ m_pProxy.Stack.DigestNonceManager.RemoveNonce(auth.Nonce); } SIP_ServerProxyCore.AuthenticateEventArgs eArgs = m_pProxy.OnAuthenticate(auth); // Authenticate failed. if(!eArgs.Authenticated){ SIP_Response notAuthenticatedResponse = request.CreateResponse(SIP_ResponseCodes.Unauthorized); notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"\",qop=\"auth\",nonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""); // Send response m_pProxy.Stack.TransportLayer.SendResponse(socket,notAuthenticatedResponse); return; } //---------------------------------------------------------------------------- // 5. Get TO:. string registrationName = SipUtils.ParseAddress(request.To.ToStringValue()).ToLower(); // 4. Check if user can register specified address. // User is not allowed to register specified address. if(!m_pProxy.OnCanRegister(auth.UserName,registrationName)){ m_pProxy.Stack.TransportLayer.SendResponse(socket,request.CreateResponse(SIP_ResponseCodes.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; } //--- Handle minimum expires time -------------------------------------------- // Get contact expires time, if not specified, get header expires time. int expires = contact.Expires; if(expires < 1){ expires = request.Expires; } // We don't check that for STAR contact and if contact expires parameter = 0. if(!contact.IsStarContact && contact.Expires != 0 && expires < m_pProxy.Stack.MinimumExpireTime){ // RFC 3261 20.23 must add Min-Expires SIP_Response sipExpiresResponse = request.CreateResponse(SIP_ResponseCodes.Interval_Too_Brief); // The response SHOULD include a Date header field. sipExpiresResponse.Header.Add("Date:",DateTime.Now.ToString("r")); // Send response m_pProxy.Stack.TransportLayer.SendResponse(socket,sipExpiresResponse); return; } //--------------------------------------------------------------------------- } // We have STAR contact. Check that STAR contact meets all RFC rules. if(starContact != null){ // We may have only 1 STAR Contact and expires must be 0. if(contacts.Length > 1 || starContact.Expires != 0){ m_pProxy.Stack.TransportLayer.SendResponse(socket,request.CreateResponse(SIP_ResponseCodes.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 get SIP registration SIP_Registration registration = null; lock(m_pRegistrations){ if(!m_pRegistrations.Contains(registrationName)){ // Add SIP registration. registration = new SIP_Registration(auth.UserName,registrationName); m_pRegistrations.Add(registration); } // Update SIP registration contacts else{ registration = m_pRegistrations[registrationName]; } } // Update registration contacts registration.UpdateContacts(contacts,request.Expires); //-------------------------------------------------------------------- // 8. --- Make and send SIP respone ---------------------------------- SIP_Response sipResponse = request.CreateResponse(SIP_ResponseCodes.Ok); // The response SHOULD include a Date header field. sipResponse.Date = DateTime.Now; // List Registered Contacts sipResponse.Header.RemoveAll("Contact:"); foreach(SIP_RegistrationContact contact in registration.Contacts){ sipResponse.Header.Add("Contact:",contact.Contact.ToStringValue()); } // Add Authentication-Info:, then client knows next nonce. sipResponse.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\""); // Send response m_pProxy.Stack.TransportLayer.SendResponse(socket,sipResponse); //----------------------------------------------------------------------- }
/// <summary> /// Creates authorization for each challange in <b>response</b>. /// </summary> /// <param name="request">SIP request where to add authorization values.</param> /// <param name="response">SIP response which challanges to authorize.</param> /// <param name="credentials">Credentials for authorization.</param> /// <returns>Returns true if all challanges were authorized. If any of the challanges was not authorized, returns false.</returns> private bool Authorize(SIP_Request request,SIP_Response response,NetworkCredential[] credentials) { if(request == null){ throw new ArgumentNullException("request"); } if(response == null){ throw new ArgumentNullException("response"); } if(credentials == null){ throw new ArgumentNullException("credentials"); } bool allAuthorized = true; #region WWWAuthenticate foreach(SIP_t_Challenge challange in response.WWWAuthenticate.GetAllValues()){ Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData,request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach(NetworkCredential c in credentials){ if(c.Domain.ToLower() == authDigest.Realm.ToLower()){ credential = c; break; } } // We don't have credential for this challange. if(credential == null){ allAuthorized = false; } // Authorize challange. else{ authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.Authorization.Add(authDigest.ToAuthorization()); } } #endregion #region ProxyAuthenticate foreach(SIP_t_Challenge challange in response.ProxyAuthenticate.GetAllValues()){ Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData,request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach(NetworkCredential c in credentials){ if(c.Domain.ToLower() == authDigest.Realm.ToLower()){ credential = c; break; } } // We don't have credential for this challange. if(credential == null){ allAuthorized = false; } // Authorize challange. else{ authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.ProxyAuthorization.Add(authDigest.ToAuthorization()); } } #endregion return allAuthorized; }
/// <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> internal bool AuthenticateRequest(SIP_RequestReceivedEventArgs e,out string userName) { userName = null; SIP_t_Credentials credentials = SIP_Utils.GetCredentials(e.Request,m_pStack.Realm); // No credentials for our realm. if(credentials == null){ SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse(SIP_ResponseCodes.x407_Proxy_Authentication_Required,e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add(new Auth_HttpDigest(m_pStack.Realm,m_pStack.DigestNonceManager.CreateNonce(),m_Opaque).ToChallange()); e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } Auth_HttpDigest auth = new Auth_HttpDigest(credentials.AuthData,e.Request.RequestLine.Method); // Check opaque validity. if(auth.Opaque != m_Opaque){ SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse(SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Opaque value won't match !",e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add(new Auth_HttpDigest(m_pStack.Realm,m_pStack.DigestNonceManager.CreateNonce(),m_Opaque).ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } // Check nonce validity. if(!m_pStack.DigestNonceManager.NonceExists(auth.Nonce)){ SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse(SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Invalid nonce value !",e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add(new Auth_HttpDigest(m_pStack.Realm,m_pStack.DigestNonceManager.CreateNonce(),m_Opaque).ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } // Valid nonce, consume it so that nonce can't be used any more. else{ m_pStack.DigestNonceManager.RemoveNonce(auth.Nonce); } SIP_AuthenticateEventArgs eArgs = this.OnAuthenticate(auth); // Authenticate failed. if(!eArgs.Authenticated){ SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse(SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Authentication failed.",e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add(new Auth_HttpDigest(m_pStack.Realm,m_pStack.DigestNonceManager.CreateNonce(),m_Opaque).ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } userName = auth.UserName; return true; }
/// <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 SIP_AuthenticateEventArgs OnAuthenticate(Auth_HttpDigest auth) { SIP_AuthenticateEventArgs eArgs = new SIP_AuthenticateEventArgs(auth); if(this.Authenticate != null){ this.Authenticate(eArgs); } return eArgs; }