From e4b4e8f55455bd9a4c38fc159aca3df37cc1f404 Mon Sep 17 00:00:00 2001 From: Nathan Whitehorn Date: Mon, 31 Jan 2005 23:15:51 +0000 Subject: [PATCH] Included Oliver Tappe's DIGEST-MD5 support and fixes to the CRAM-MD5 mechanism. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@11176 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../outbound_protocols/smtp/smtp.cpp | 200 ++++++++++++++++-- .../outbound_protocols/smtp/smtp.h | 1 + 2 files changed, 186 insertions(+), 15 deletions(-) diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp index 1464c1765e..8e3709db16 100644 --- a/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,8 @@ #include #include +#include + #include "smtp.h" #ifndef USESSL #include "md5.h" @@ -127,10 +130,96 @@ MD5HexHmac(char *hexdigest, { unsigned char digest[16]; int i; + unsigned char c; MD5Hmac(digest, text, text_len, key, key_len); - for (i = 0; i < 16; i++) - sprintf(hexdigest + 2 * i, "%02x", digest[i]); + for (i = 0; i < 16; i++) { + c = digest[i]; + *hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4); + *hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F); + } + *hexdigest = '\0'; +} + + +/* +** Function: MD5Sum +** generates an MD5-sum from the given string +*/ +void +MD5Sum (char* sum, unsigned char *text, int text_len) { + MD5_CTX context; + MD5_Init(&context); + MD5_Update(&context, text, text_len); + MD5_Final((unsigned char*)sum, &context); + sum[16] = '\0'; +} + +/* +** Function: MD5Digest +** generates an MD5-digest from the given string +*/ +void MD5Digest (char* hexdigest, unsigned char *text, int text_len) { + int i; + unsigned char digest[17]; + unsigned char c; + + MD5Sum((char*)digest, text, text_len); + + for (i = 0; i < 16; i++) { + c = digest[i]; + *hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4); + *hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F); + } + *hexdigest = '\0'; +} + +/* +** Function: SplitChallengeIntoMap +** splits a challenge-string into the given map (see RFC-2831) +*/ +// : +static bool +SplitChallengeIntoMap(BString str, map& m) +{ + m.clear(); + const char* key; + const char* val; + char* s = (char*)str.String(); + while(*s != 0) { + while(isspace(*s)) + s++; + key = s; + while(isalpha(*s)) + s++; + if (*s != '=') + return false; + *s++ = '\0'; + while(isspace(*s)) + s++; + if (*s=='"') { + val = ++s; + while(*s!='"') { + if (*s == 0) + return false; + s++; + } + *s++ = '\0'; + } else { + val = s; + while(*s!=0 && *s!=',' && !isspace(*s)) + s++; + if (*s != 0) + *s++ = '\0'; + } + m[key] = val; + while(isspace(*s)) + s++; + if (*s != ',') + return false; + s++; + } + return true; } @@ -372,8 +461,10 @@ SMTPProtocol::Open(const char *address, int port, bool esmtp) fAuthType |= PLAIN; if(::strstr(p, "CRAM-MD5")) fAuthType |= CRAM_MD5; - if(::strstr(p, "DIGEST-MD5")) + if(::strstr(p, "DIGEST-MD5")) { fAuthType |= DIGEST_MD5; + fServerName = address; + } } } return B_OK; @@ -465,16 +556,92 @@ SMTPProtocol::Login(const char *_login, const char *password) int32 loginlen = ::strlen(login); int32 passlen = ::strlen(password); + if (fAuthType & DIGEST_MD5) { + //******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) + // this implements only the subpart of DIGEST-MD5 which is + // required for authentication to SMTP-servers. Integrity- + // and confidentiality-protection are not implemented, as + // they are provided by the use of OpenSSL. + SendCommand("AUTH DIGEST-MD5"CRLF); + const char *res = fLog.String(); + + if (strncmp(res, "334", 3) != 0) + return B_ERROR; + int32 baselen = ::strlen(&res[4]); + char *base = new char[baselen+1]; + baselen = ::decode_base64(base, &res[4], baselen); + base[baselen] = '\0'; + + D(bug("base: %s\n", base)); + + map challengeMap; + SplitChallengeIntoMap(base, challengeMap); + + BString rawResponse = BString("username=") << '"' << login << '"'; + rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"'; + rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"'; + rawResponse << ",nc=00000001"; + char temp[33]; + for( int i=0; i<32; ++i) + temp[i] = 1+(rand()%254); + temp[33] = '\0'; + BString rawCnonce(temp); + BString cnonce; + char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2); + baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */); + cnoncePtr[baselen] = '\0'; + cnonce.UnlockBuffer(baselen); + rawResponse << ",cnonce=" << '"' << cnonce << '"'; + rawResponse << ",qop=auth"; + BString digestUriValue = BString("smtp/") << fServerName; + rawResponse << ",digest-uri=" << '"' << digestUriValue << '"'; + char sum[17], hex_digest2[33]; + BString a1,a2,kd; + BString t1 = BString(login) << ":" + << challengeMap["realm"] << ":" + << password; + MD5Sum(sum, (unsigned char*)t1.String(), t1.Length()); + a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce; + MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length()); + a2 << "AUTHENTICATE:" << digestUriValue; + MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length()); + kd << hex_digest << ':' << challengeMap["nonce"] + << ":" << "00000001" << ':' << cnonce << ':' << "auth" + << ':' << hex_digest2; + MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length()); + + rawResponse << ",response=" << hex_digest; + rawResponse << ",charset=utf-8"; + BString postResponse; + char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10); + baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */); + resp[baselen] = 0; + postResponse.UnlockBuffer(); + postResponse.Append(CRLF); + + SendCommand(postResponse.String()); + + res = fLog.String(); + if (atol(res) >= 500) + return B_ERROR; + // actually, we are supposed to check the rspauth sent back + // by the SMTP-server, but that isn't strictly required, + // so we skip that for now. + SendCommand(CRLF); // finish off authentication + res = fLog.String(); + if (atol(res) < 500) + return B_OK; + } if (fAuthType & CRAM_MD5) { - //******* CRAM-MD5 Authentication ( not tested yet.) + //******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) SendCommand("AUTH CRAM-MD5"CRLF); const char *res = fLog.String(); if (strncmp(res, "334", 3) != 0) return B_ERROR; - char *base = new char[::strlen(&res[4])+1]; - int32 baselen = ::strlen(base); - baselen = ::decode_base64(base, base, baselen); + int32 baselen = ::strlen(&res[4]); + char *base = new char[baselen+1]; + baselen = ::decode_base64(base, &res[4], baselen); base[baselen] = '\0'; D(bug("base: %s\n", base)); @@ -501,10 +668,6 @@ SMTPProtocol::Login(const char *_login, const char *password) if (atol(res) < 500) return B_OK; } - if (fAuthType & DIGEST_MD5) { - //******* DIGEST-MD5 Authentication ( not written yet..) - fLog = "DIGEST-MD5 Authentication is not supported"; - } if (fAuthType & LOGIN) { //******* LOGIN Authentication ( tested. works fine) ssize_t encodedsize; // required by our base64 implementation @@ -540,13 +703,20 @@ SMTPProtocol::Login(const char *_login, const char *password) return B_OK; } if (fAuthType & PLAIN) { - //******* PLAIN Authentication ( not tested yet.) + //******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] ) + // format is: + // authenticateID + \0 + username + \0 + password + // (where authenticateID is always empty !?!) BString preResponse, postResponse; char *stringPntr; ssize_t encodedLength; - stringPntr = preResponse.LockBuffer(loginlen * 2 + passlen + 3); - sprintf (stringPntr, "%s%c%s%c%s", login, 0, login, 0, password); - preResponse.UnlockBuffer(loginlen * 2 + passlen + 3); + stringPntr = preResponse.LockBuffer(loginlen + passlen + 3); + // +3 to make room for the two \0-chars between the tokens and + // the final delimiter added by sprintf(). + sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password); + preResponse.UnlockBuffer(loginlen + passlen + 2); + // +2 in order to leave out the final delimiter (which is not part + // of the string). stringPntr = postResponse.LockBuffer(preResponse.Length() * 3); encodedLength = ::encode_base64(stringPntr, preResponse.String(), preResponse.Length(), true /* headerMode */); diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h index 9566633460..b72afc7243 100644 --- a/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h @@ -51,6 +51,7 @@ class SMTPProtocol : public BMailFilter { #endif status_t fStatus; + BString fServerName; // required for DIGEST-MD5 }; #endif /* ZOIDBERG_SMTP_H */