/// <summary>
        /// Raises <b>GetUserInfo</b> event.
        /// </summary>
        /// <param name="userName">User name.</param>
        /// <returns>Returns specified user info.</returns>
        private AUTH_e_UserInfo OnGetUserInfo(string userName)
        {
            AUTH_e_UserInfo retVal = new AUTH_e_UserInfo(userName);

            if (this.GetUserInfo != null)
            {
                this.GetUserInfo(this, retVal);
            }

            return(retVal);
        }
        /// <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>
        /// 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 2195 2. Challenge-Response Authentication Mechanism.
             *  The authentication type associated with CRAM is "CRAM-MD5".
             *
             *  The data encoded in the first ready response contains an
             *  presumptively arbitrary string of random digits, a timestamp, and the
             *  fully-qualified primary host name of the server.  The syntax of the
             *  unencoded form must correspond to that of an RFC 822 'msg-id'
             *  [RFC822] as described in [POP3].
             *
             *  The client makes note of the data and then responds with a string
             *  consisting of the user name, a space, and a 'digest'.  The latter is
             *  computed by applying the keyed MD5 algorithm from [KEYED-MD5] where
             *  the key is a shared secret and the digested text is the timestamp
             *  (including angle-brackets).
             *
             *  This shared secret is a string known only to the client and server.
             *  The `digest' parameter itself is a 16-octet value which is sent in
             *  hexadecimal format, using lower-case ASCII characters.
             *
             *  When the server receives this client response, it verifies the digest
             *  provided.  If the digest is correct, the server should consider the
             *  client authenticated and respond appropriately.
             *
             *  Example:
             *      The examples in this document show the use of the CRAM mechanism with
             *      the IMAP4 AUTHENTICATE command [IMAP-AUTH].  The base64 encoding of
             *      the challenges and responses is part of the IMAP4 AUTHENTICATE
             *      command, not part of the CRAM specification itself.
             *
             *      S: * OK IMAP4 Server
             *      C: A0001 AUTHENTICATE CRAM-MD5
             *      S: + PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+
             *      C: dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
             *      S: A0001 OK CRAM authentication successful
             *
             *      In this example, the shared secret is the string
             *      'tanstaaftanstaaf'.  Hence, the Keyed MD5 digest is produced by
             *      calculating
             *
             *      MD5((tanstaaftanstaaf XOR opad),
             *          MD5((tanstaaftanstaaf XOR ipad),
             *          <*****@*****.**>))
             *
             *      where ipad and opad are as defined in the keyed-MD5 Work in
             *      Progress [KEYED-MD5] and the string shown in the challenge is the
             *      base64 encoding of <*****@*****.**>. The
             *      shared secret is null-padded to a length of 64 bytes. If the
             *      shared secret is longer than 64 bytes, the MD5 digest of the
             *      shared secret is used as a 16 byte input to the keyed MD5
             *      calculation.
             *
             *      This produces a digest value (in hexadecimal) of
             *
             *          b913a602c7eda7a495b4e6e7334d3890
             *
             *      The user name is then prepended to it, forming
             *
             *          tim b913a602c7eda7a495b4e6e7334d3890
             *
             *      Which is then base64 encoded to meet the requirements of the IMAP4
             *      AUTHENTICATE command (or the similar POP3 AUTH command), yielding
             *
             *      dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
             */

            if (m_State == 0)
            {
                m_State++;
                m_Key = "<" + Guid.NewGuid().ToString() + "@host" + ">";

                return(Encoding.UTF8.GetBytes(m_Key));
            }
            else
            {
                // Parse client response. response = userName SP hash.
                string[] user_hash = Encoding.UTF8.GetString(clientResponse).Split(' ');
                if (user_hash.Length == 2 && !string.IsNullOrEmpty(user_hash[0]))
                {
                    m_UserName = user_hash[0];
                    AUTH_e_UserInfo result = OnGetUserInfo(user_hash[0]);
                    if (result.UserExists)
                    {
                        // hash = Hex(HmacMd5(hashKey,password))
                        string hash = Net_Utils.ToHex(HmacMd5(m_Key, result.Password));
                        if (hash == user_hash[1])
                        {
                            m_IsAuthenticated = true;
                        }
                    }
                }

                m_IsCompleted = true;
            }

            return(null);
        }