/// <summary> /// Continues authentication process. /// </summary> /// <param name="serverResponse">Server sent SASL response.</param> /// <returns>Returns challange request what must be sent to server or null if authentication has completed.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>serverResponse</b> is null reference.</exception> /// <exception cref="InvalidOperationException">Is raised when this method is called when authentication is completed.</exception> public override byte[] Continue(byte[] serverResponse) { if (serverResponse == null) { throw new ArgumentNullException("serverResponse"); } if (m_IsCompleted) { throw new InvalidOperationException("Authentication is completed."); } /* 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++; // Parse server challenge. AUTH_SASL_DigestMD5_Challenge challenge = AUTH_SASL_DigestMD5_Challenge.Parse(Encoding.UTF8.GetString(serverResponse)); // Construct our response to server challenge. m_pResponse = new AUTH_SASL_DigestMD5_Response( challenge, challenge.Realm[0], m_UserName, m_Password, Guid.NewGuid().ToString().Replace("-", ""), 1, challenge.QopOptions[0], m_Protocol + "/" + m_ServerName ); return(Encoding.UTF8.GetBytes(m_pResponse.ToResponse())); } else if (m_State == 1) { m_State++; m_IsCompleted = true; // Check rspauth value. if (!String2.Equals(Encoding.UTF8.GetString(serverResponse), m_pResponse.ToRspauthResponse(m_UserName, m_Password), StringComparison2.InvariantCultureIgnoreCase)) { throw new Exception("Server server 'rspauth' value mismatch with local 'rspauth' value."); } return(new byte[0]); } else { throw new InvalidOperationException("Authentication is completed."); } }
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(digestResponse)<CRLF> S: 334 base64(serverDigestRpAuth)<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); } // Parse server challenge. AUTH_SASL_DigestMD5_Challenge challenge = AUTH_SASL_DigestMD5_Challenge.Parse(Encoding.Default.GetString(Convert.FromBase64String(line.Split(' ')[1]))); // Construct our response to server challenge. AUTH_SASL_DigestMD5_Response response = new AUTH_SASL_DigestMD5_Response( challenge, challenge.Realm[0], userName, password,Guid.NewGuid().ToString().Replace("-",""), 1, challenge.QopOptions[0], "smtp/" + this.RemoteEndPoint.Address.ToString() ); // Send authentication info to server. WriteLine(Convert.ToBase64String(Encoding.Default.GetBytes(response.ToResponse()))); // 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); } // Check rspauth value. if(!string.Equals(Encoding.Default.GetString(Convert.FromBase64String(line.Split(' ')[1])),response.ToRspauthResponse(userName,password),StringComparison.InvariantCultureIgnoreCase)){ throw new Exception("SMTP server 'rspauth' value mismatch."); } // 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> /// Continues authentication process. /// </summary> /// <param name="serverResponse">Server sent SASL response.</param> /// <returns>Returns challange request what must be sent to server or null if authentication has completed.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>serverResponse</b> is null reference.</exception> /// <exception cref="InvalidOperationException">Is raised when this method is called when authentication is completed.</exception> public override byte[] Continue(byte[] serverResponse) { if(serverResponse == null){ throw new ArgumentNullException("serverResponse"); } if(m_IsCompleted){ throw new InvalidOperationException("Authentication is completed."); } /* 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++; // Parse server challenge. AUTH_SASL_DigestMD5_Challenge challenge = AUTH_SASL_DigestMD5_Challenge.Parse(Encoding.UTF8.GetString(serverResponse)); // Construct our response to server challenge. m_pResponse = new AUTH_SASL_DigestMD5_Response( challenge, challenge.Realm[0], m_UserName, m_Password, Guid.NewGuid().ToString().Replace("-",""), 1, challenge.QopOptions[0], m_Protocol + "/" + m_ServerName ); return Encoding.UTF8.GetBytes(m_pResponse.ToResponse()); } else if(m_State == 1){ m_State++; m_IsCompleted = true; // Check rspauth value. if(!string.Equals(Encoding.UTF8.GetString(serverResponse),m_pResponse.ToRspauthResponse(m_UserName,m_Password),StringComparison.InvariantCultureIgnoreCase)){ throw new Exception("Server server 'rspauth' value mismatch with local 'rspauth' value."); } return new byte[0]; } else{ throw new InvalidOperationException("Authentication is completed."); } }