/// <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 }
/// <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; }