Exemplo n.º 1
0
        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
        }
Exemplo n.º 2
0
        /// <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.");
            }
        }
Exemplo n.º 3
0
        /// <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 byte[] Continue(byte[] 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++;

                AUTH_SASL_DigestMD5_Challenge callenge = new AUTH_SASL_DigestMD5_Challenge(new string[] { m_Realm }, m_Nonce, new string[] { "auth" }, false);

                return(Encoding.UTF8.GetBytes(callenge.ToChallenge()));
            }
            else if (m_State == 1)
            {
                m_State++;

                try{
                    AUTH_SASL_DigestMD5_Response response = AUTH_SASL_DigestMD5_Response.Parse(Encoding.UTF8.GetString(clientResponse));

                    // Check realm and nonce value.
                    if (m_Realm != response.Realm || m_Nonce != response.Nonce)
                    {
                        return(Encoding.UTF8.GetBytes("rspauth=\"\""));
                    }

                    m_UserName = response.UserName;
                    AUTH_e_UserInfo result = OnGetUserInfo(response.UserName);
                    if (result.UserExists)
                    {
                        if (response.Authenticate(result.UserName, result.Password))
                        {
                            m_IsAuthenticated = true;

                            return(Encoding.UTF8.GetBytes(response.ToRspauthResponse(result.UserName, result.Password)));
                        }
                    }
                }
                catch {
                    // Authentication failed, just reject request.
                }

                return(Encoding.UTF8.GetBytes("rspauth=\"\""));
            }
            else
            {
                m_IsCompleted = true;
            }

            return(null);
        }
        /// <summary>
        /// Parses DIGEST-MD5 response from response-string.
        /// </summary>
        /// <param name="digestResponse">Response string.</param>
        /// <returns>Returns DIGEST-MD5 response.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>digestResponse</b> isnull reference.</exception>
        /// <exception cref="ParseException">Is raised when response parsing + validation fails.</exception>
        public static AUTH_SASL_DigestMD5_Response Parse(string digestResponse)
        {
            if (digestResponse == null)
            {
                throw new ArgumentNullException(digestResponse);
            }

            /* RFC 2831 2.1.2.
             *  The client makes note of the "digest-challenge" and then responds
             *  with a string formatted and computed according to the rules for a
             *  "digest-response" defined as follows:
             *
             *  digest-response  = 1#( username | realm | nonce | cnonce |
             *                         nonce-count | qop | digest-uri | response |
             *                         maxbuf | charset | cipher | authzid |
             *                         auth-param )
             *
             *  username         = "******" "=" <"> username-value <">
             *  username-value   = qdstr-val
             *  cnonce           = "cnonce" "=" <"> cnonce-value <">
             *  cnonce-value     = qdstr-val
             *  nonce-count      = "nc" "=" nc-value
             *  nc-value         = 8LHEX
             *  qop              = "qop" "=" qop-value
             *  digest-uri       = "digest-uri" "=" <"> digest-uri-value <">
             *  digest-uri-value  = serv-type "/" host [ "/" serv-name ]
             *  serv-type        = 1*ALPHA
             *  host             = 1*( ALPHA | DIGIT | "-" | "." )
             *  serv-name        = host
             *  response         = "response" "=" response-value
             *  response-value   = 32LHEX
             *  LHEX             = "0" | "1" | "2" | "3" |
             *                     "4" | "5" | "6" | "7" |
             *                     "8" | "9" | "a" | "b" |
             *                     "c" | "d" | "e" | "f"
             *  cipher           = "cipher" "=" cipher-value
             *  authzid          = "authzid" "=" <"> authzid-value <">
             *  authzid-value    = qdstr-val
             */

            AUTH_SASL_DigestMD5_Response retVal = new AUTH_SASL_DigestMD5_Response();

            // Set default values.
            retVal.m_Realm = "";

            string[] parameters = TextUtils.SplitQuotedString(digestResponse, ',');
            foreach (string parameter in parameters)
            {
                string[] name_value = parameter.Split(new char[] { '=' }, 2);
                string   name       = name_value[0].Trim();

                if (name_value.Length == 2)
                {
                    if (name.ToLower() == "username")
                    {
                        retVal.m_UserName = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "realm")
                    {
                        retVal.m_Realm = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "nonce")
                    {
                        retVal.m_Nonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "cnonce")
                    {
                        retVal.m_Cnonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "nc")
                    {
                        retVal.m_NonceCount = Int32.Parse(TextUtils.UnQuoteString(name_value[1]), System.Globalization.NumberStyles.HexNumber);
                    }
                    else if (name.ToLower() == "qop")
                    {
                        retVal.m_Qop = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "digest-uri")
                    {
                        retVal.m_DigestUri = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "response")
                    {
                        retVal.m_Response = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "charset")
                    {
                        retVal.m_Charset = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "cipher")
                    {
                        retVal.m_Cipher = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "authzid")
                    {
                        retVal.m_Authzid = TextUtils.UnQuoteString(name_value[1]);
                    }
                }
            }

            /* Validate required fields.
             *  Per RFC 2831 2.1.2. Only [username nonce cnonce nc response] parameters are required.
             */
            if (string.IsNullOrEmpty(retVal.UserName))
            {
                throw new ParseException("The response-string doesn't contain required parameter 'username' value.");
            }
            if (string.IsNullOrEmpty(retVal.Nonce))
            {
                throw new ParseException("The response-string doesn't contain required parameter 'nonce' value.");
            }
            if (string.IsNullOrEmpty(retVal.Cnonce))
            {
                throw new ParseException("The response-string doesn't contain required parameter 'cnonce' value.");
            }
            if (retVal.NonceCount < 1)
            {
                throw new ParseException("The response-string doesn't contain required parameter 'nc' value.");
            }
            if (string.IsNullOrEmpty(retVal.Response))
            {
                throw new ParseException("The response-string doesn't contain required parameter 'response' value.");
            }

            return(retVal);
        }
        /// <summary>
        /// Parses DIGEST-MD5 response from response-string.
        /// </summary>
        /// <param name="digestResponse">Response string.</param>
        /// <returns>Returns DIGEST-MD5 response.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>digestResponse</b> isnull reference.</exception>
        /// <exception cref="ParseException">Is raised when response parsing + validation fails.</exception>
        public static AUTH_SASL_DigestMD5_Response Parse(string digestResponse)
        {
            if(digestResponse == null){
                throw new ArgumentNullException(digestResponse);
            }

            /* RFC 2831 2.1.2.
                The client makes note of the "digest-challenge" and then responds
                with a string formatted and computed according to the rules for a
                "digest-response" defined as follows:

                digest-response  = 1#( username | realm | nonce | cnonce |
                                       nonce-count | qop | digest-uri | response |
                                       maxbuf | charset | cipher | authzid |
                                       auth-param )

                username         = "******" "=" <"> username-value <">
                username-value   = qdstr-val
                cnonce           = "cnonce" "=" <"> cnonce-value <">
                cnonce-value     = qdstr-val
                nonce-count      = "nc" "=" nc-value
                nc-value         = 8LHEX
                qop              = "qop" "=" qop-value
                digest-uri       = "digest-uri" "=" <"> digest-uri-value <">
                digest-uri-value  = serv-type "/" host [ "/" serv-name ]
                serv-type        = 1*ALPHA
                host             = 1*( ALPHA | DIGIT | "-" | "." )
                serv-name        = host
                response         = "response" "=" response-value
                response-value   = 32LHEX
                LHEX             = "0" | "1" | "2" | "3" |
                                   "4" | "5" | "6" | "7" |
                                   "8" | "9" | "a" | "b" |
                                   "c" | "d" | "e" | "f"
                cipher           = "cipher" "=" cipher-value
                authzid          = "authzid" "=" <"> authzid-value <">
                authzid-value    = qdstr-val
            */

            AUTH_SASL_DigestMD5_Response retVal = new AUTH_SASL_DigestMD5_Response();

            // Set default values.
            retVal.m_Realm = "";

            string[] parameters = TextUtils.SplitQuotedString(digestResponse,',');
            foreach(string parameter in parameters){
                string[] name_value = parameter.Split(new char[]{'='},2);
                string   name       = name_value[0].Trim();

                if(name_value.Length == 2){
                    if(name.ToLower() == "username"){
                        retVal.m_UserName = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "realm"){
                        retVal.m_Realm = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "nonce"){
                        retVal.m_Nonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "cnonce"){
                        retVal.m_Cnonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "nc"){
                        retVal.m_NonceCount = Int32.Parse(TextUtils.UnQuoteString(name_value[1]),System.Globalization.NumberStyles.HexNumber);
                    }
                    else if(name.ToLower() == "qop"){
                        retVal.m_Qop = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "digest-uri"){
                        retVal.m_DigestUri = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "response"){
                        retVal.m_Response = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "charset"){
                        retVal.m_Charset = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "cipher"){
                        retVal.m_Cipher = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "authzid"){
                        retVal.m_Authzid = TextUtils.UnQuoteString(name_value[1]);
                    }
                }
            }

            /* Validate required fields.
                Per RFC 2831 2.1.2. Only [username nonce cnonce nc response] parameters are required.
            */
            if(string.IsNullOrEmpty(retVal.UserName)){
                throw new ParseException("The response-string doesn't contain required parameter 'username' value.");
            }
            if(string.IsNullOrEmpty(retVal.Nonce)){
                throw new ParseException("The response-string doesn't contain required parameter 'nonce' value.");
            }
            if(string.IsNullOrEmpty(retVal.Cnonce)){
                throw new ParseException("The response-string doesn't contain required parameter 'cnonce' value.");
            }
            if(retVal.NonceCount < 1){
                throw new ParseException("The response-string doesn't contain required parameter 'nc' value.");
            }
            if(string.IsNullOrEmpty(retVal.Response)){
                throw new ParseException("The response-string doesn't contain required parameter 'response' value.");
            }

            return retVal;
        }
        /// <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.");
            }
        }