From: Matthias Schmidt Date: Tue, 2 Sep 2008 15:11:49 +0000 (+0000) Subject: Add CRAM-MD5 authentication support for the DragonFly Mail Agent. This is the X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bf28fcc61561dedeafd841d15d8fe53bbc1eb587;p=people%2Fms%2Fdma.git Add CRAM-MD5 authentication support for the DragonFly Mail Agent. This is the first piece of Max's work for the Google Summer of Code 2008. All other new features will follow after evaluation/review :) Besides the CRAM code there is new code within base64.c (BSD licensed and from the University of Stockholm) and within crypto.c derived from RFC 2104. Note: This code is tested and works. If you find a bug, please report back to the bugs@ list. Thanks a lot for the good work Max. Submitted-by: Max Lindner Sponsored-by: Google Summer of Code 2008 --- diff --git a/base64.c b/base64.c index 610aa3c..f140027 100644 --- a/base64.c +++ b/base64.c @@ -30,7 +30,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $DragonFly: src/libexec/dma/base64.c,v 1.1 2008/02/02 18:20:51 matthias Exp $ + * $DragonFly: src/libexec/dma/base64.c,v 1.2 2008/09/02 15:11:49 matthias Exp $ */ #include @@ -41,6 +41,17 @@ int base64_encode(const void *data, int size, char **str); static char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static int +pos(char c) +{ + char *p; + for (p = base64_chars; *p; p++) + if (*p == c) + return p - base64_chars; + return -1; +} + + int base64_encode(const void *data, int size, char **str) { @@ -79,3 +90,48 @@ base64_encode(const void *data, int size, char **str) return strlen(s); } +#define DECODE_ERROR 0xffffffff + +static unsigned int +token_decode(const char *token) +{ + int i; + unsigned int val = 0; + int marker = 0; + if (strlen(token) < 4) + return DECODE_ERROR; + for (i = 0; i < 4; i++) { + val *= 64; + if (token[i] == '=') + marker++; + else if (marker > 0) + return DECODE_ERROR; + else + val += pos(token[i]); + } + if (marker > 2) + return DECODE_ERROR; + return (marker << 24) | val; +} + +int +base64_decode(const char *str, void *data) +{ + const char *p; + unsigned char *q; + + q = data; + for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { + unsigned int val = token_decode(p); + unsigned int marker = (val >> 24) & 0xff; + if (val == DECODE_ERROR) + return -1; + *q++ = (val >> 16) & 0xff; + if (marker < 2) + *q++ = (val >> 8) & 0xff; + if (marker < 1) + *q++ = val & 0xff; + } + return q - (unsigned char *) data; +} + diff --git a/crypto.c b/crypto.c index 57cada8..9182137 100644 --- a/crypto.c +++ b/crypto.c @@ -32,12 +32,13 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $DragonFly: src/libexec/dma/crypto.c,v 1.2 2008/03/04 11:36:08 matthias Exp $ + * $DragonFly: src/libexec/dma/crypto.c,v 1.3 2008/09/02 15:11:49 matthias Exp $ */ #ifdef HAVE_CRYPTO #include +#include #include #include #include @@ -118,9 +119,9 @@ smtp_init_crypto(struct qitem *it, int fd, int feature) config->features |= NOSSL; send_remote_command(fd, "EHLO %s", hostname()); - if (read_remote(fd) == 2) { + if (read_remote(fd, 0, NULL) == 2) { send_remote_command(fd, "STARTTLS"); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery failed:" " STARTTLS not available: %m", it->queueid); config->features &= ~NOSSL; @@ -169,16 +170,137 @@ smtp_init_crypto(struct qitem *it, int fd, int feature) return (0); } -#if 0 +/* + * hmac_md5() taken out of RFC 2104. This RFC was written by H. Krawczyk, + * M. Bellare and R. Canetti. + */ +void +hmac_md5(text, text_len, key, key_len, digest) +unsigned char* text; /* pointer to data stream */ +int text_len; /* length of data stream */ +unsigned char* key; /* pointer to authentication key */ +int key_len; /* length of authentication key */ +caddr_t digest; /* caller digest to be filled in */ + +{ + MD5_CTX context; + unsigned char k_ipad[65]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[65]; /* outer padding - + * key XORd with opad + */ + unsigned char tk[16]; + int i; + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + + MD5_CTX tctx; + + MD5_Init(&tctx); + MD5_Update(&tctx, key, key_len); + MD5_Final(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + bzero( k_ipad, sizeof k_ipad); + bzero( k_opad, sizeof k_opad); + bcopy( key, k_ipad, key_len); + bcopy( key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + MD5_Init(&context); /* init context for 1st + * pass */ + MD5_Update(&context, k_ipad, 64); /* start with inner pad */ + MD5_Update(&context, text, text_len); /* then text of datagram */ + MD5_Final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + MD5_Init(&context); /* init context for 2nd + * pass */ + MD5_Update(&context, k_opad, 64); /* start with outer pad */ + MD5_Update(&context, digest, 16); /* then results of 1st + * hash */ + MD5_Final(digest, &context); /* finish up 2nd pass */ +} + /* * CRAM-MD5 authentication - * - * XXX TODO implement me, I don't have a mail server with CRAM-MD5 available */ int -smtp_auth_md5(int fd, char *login, char *password) +smtp_auth_md5(struct qitem *it, int fd, char *login, char *password) { + unsigned char buffer[BUF_SIZE], digest[BUF_SIZE], ascii_digest[33]; + char *temp; + int len, i; + static char hextab[] = "0123456789abcdef"; + + temp = calloc(BUF_SIZE, 1); + memset(buffer, 0, sizeof(buffer)); + memset(digest, 0, sizeof(digest)); + memset(ascii_digest, 0, sizeof(ascii_digest)); + + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH CRAM-MD5"); + if (read_remote(fd, sizeof(buffer), buffer) != 3) { + syslog(LOG_ERR, "%s: smarthost authentification:" + " AUTH cram-md5 not available: %m", it->queueid); + /* if cram-md5 is not available */ + return (-1); + } + + /* skip 3 char status + 1 char space */ + base64_decode(buffer + 4, temp); + hmac_md5(temp, strlen(temp), password, strlen(password), digest); + + ascii_digest[32] = 0; + for (i = 0; i < 16; i++) { + ascii_digest[2*i] = hextab[digest[i] >> 4]; + ascii_digest[2*i+1] = hextab[digest[i] & 15]; + } + + /* prepare answer */ + snprintf(buffer, BUF_SIZE, "%s %s", login, ascii_digest); + + /* temp will be allocated inside base64_encode again */ + free(temp); + /* encode answer */ + len = base64_encode(buffer, strlen(buffer), &temp); + if (len <= 0) + return (-1); + + /* send answer */ + send_remote_command(fd, "%s", temp); + if (read_remote(fd, 0, NULL) != 2) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " AUTH cram-md5 failed: %m", it->queueid); + return (-2); + } + + return (0); } -#endif /* 0 */ #endif /* HAVE_CRYPTO */ diff --git a/dma.h b/dma.h index c364f08..b8bc21b 100644 --- a/dma.h +++ b/dma.h @@ -32,7 +32,7 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $DragonFly: src/libexec/dma/dma.h,v 1.5 2008/03/04 11:36:09 matthias Exp $ + * $DragonFly: src/libexec/dma/dma.h,v 1.6 2008/09/02 15:11:49 matthias Exp $ */ #ifndef DMA_H @@ -146,12 +146,13 @@ extern int smtp_init_crypto(struct qitem *, int, int); #endif /* HAVE_CRYPTO */ /* net.c */ -extern int read_remote(int); +extern int read_remote(int, int, char *); extern ssize_t send_remote_command(int, const char*, ...); extern int deliver_remote(struct qitem *, const char **); /* base64.c */ extern int base64_encode(const void *, int, char **); +extern int base64_decode(const char *, void *); /* dma.c */ extern char * hostname(void); diff --git a/net.c b/net.c index 111f333..fbde18e 100644 --- a/net.c +++ b/net.c @@ -32,7 +32,7 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $DragonFly: src/libexec/dma/net.c,v 1.6 2008/04/20 13:44:24 swildner Exp $ + * $DragonFly: src/libexec/dma/net.c,v 1.7 2008/09/02 15:11:49 matthias Exp $ */ #include @@ -90,12 +90,12 @@ send_remote_command(int fd, const char* fmt, ...) } int -read_remote(int fd) +read_remote(int fd, int extbufsize, char *extbuf) { ssize_t rlen = 0; size_t pos, len; char buff[BUF_SIZE]; - int done = 0, status = 0; + int done = 0, status = 0, extbufpos = 0; if (signal(SIGALRM, sig_alarm) == SIG_ERR) { syslog(LOG_ERR, "SIGALRM error: %m"); @@ -111,6 +111,7 @@ read_remote(int fd) * OpenBSD and released under a BSD style license. */ for (len = pos = 0; !done; ) { + rlen = 0; if (pos == 0 || (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) { memmove(buff, buff + pos, len - pos); @@ -128,6 +129,21 @@ read_remote(int fd) } len += rlen; } + /* + * If there is an external buffer with a size bigger than zero + * and as long as there is space in the external buffer and + * there are new characters read from the mailserver + * copy them to the external buffer + */ + if (extbufpos <= (extbufsize - 1) && rlen && extbufsize > 0 + && extbuf != NULL) { + /* do not write over the bounds of the buffer */ + if(extbufpos + rlen > (extbufsize - 1)) { + rlen = extbufsize - extbufpos; + } + memcpy(extbuf + extbufpos, buff + len - rlen, rlen); + extbufpos += rlen; + } for (; pos < len && buff[pos] >= '0' && buff[pos] <= '9'; pos++) ; /* Do nothing */ @@ -152,8 +168,6 @@ read_remote(int fd) /* * Handle SMTP authentication - * - * XXX TODO: give me AUTH CRAM-MD5 */ static int smtp_login(struct qitem *it, int fd, char *login, char* password) @@ -161,39 +175,58 @@ smtp_login(struct qitem *it, int fd, char *login, char* password) char *temp; int len, res = 0; - /* Send AUTH command according to RFC 2554 */ - send_remote_command(fd, "AUTH LOGIN"); - if (read_remote(fd) != 3) { - syslog(LOG_ERR, "%s: remote delivery deferred:" - " AUTH login not available: %m", it->queueid); - return (1); +#ifdef HAVE_CRYPTO + res = smtp_auth_md5(it, fd, login, password); + if (res == 0) { + return (0); + } else if (res == -2) { + /* + * If the return code is -2, then then the login attempt failed, + * do not try other login mechanisms + */ + return (-1); } +#endif /* HAVE_CRYPTO */ - len = base64_encode(login, strlen(login), &temp); - if (len <= 0) - return (-1); + if ((config->features & INSECURE) != 0) { + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH LOGIN"); + if (read_remote(fd, 0, NULL) != 3) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " AUTH login not available: %m", it->queueid); + return (1); + } - send_remote_command(fd, "%s", temp); - if (read_remote(fd) != 3) { - syslog(LOG_ERR, "%s: remote delivery deferred:" - " AUTH login failed: %m", it->queueid); - return (-1); - } + len = base64_encode(login, strlen(login), &temp); + if (len <= 0) + return (-1); - len = base64_encode(password, strlen(password), &temp); - if (len <= 0) - return (-1); + send_remote_command(fd, "%s", temp); + if (read_remote(fd, 0, NULL) != 3) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " AUTH login failed: %m", it->queueid); + return (-1); + } - send_remote_command(fd, "%s", temp); - res = read_remote(fd); - if (res == 5) { - syslog(LOG_ERR, "%s: remote delivery failed:" - " Authentication failed: %m", it->queueid); - return (-1); - } else if (res != 2) { - syslog(LOG_ERR, "%s: remote delivery failed:" - " AUTH password failed: %m", it->queueid); - return (-1); + len = base64_encode(password, strlen(password), &temp); + if (len <= 0) + return (-1); + + send_remote_command(fd, "%s", temp); + res = read_remote(fd, 0, NULL); + if (res == 5) { + syslog(LOG_ERR, "%s: remote delivery failed:" + " Authentication failed: %m", it->queueid); + return (-1); + } else if (res != 2) { + syslog(LOG_ERR, "%s: remote delivery failed:" + " AUTH password failed: %m", it->queueid); + return (-1); + } + } else { + syslog(LOG_ERR, "%s: non-encrypted SMTP login is disabled in config, so skipping it. ", + it->queueid); + return (1); } return (0); @@ -212,6 +245,7 @@ open_connection(struct qitem *it, const char *host) else port = SMTP_PORT; + /* FIXME get MX record of host */ /* Shamelessly taken from getaddrinfo(3) */ memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; @@ -279,7 +313,7 @@ deliver_remote(struct qitem *it, const char **errmsg) /* Check first reply from remote host */ config->features |= NOSSL; - res = read_remote(fd); + res = read_remote(fd, 0, NULL); if (res != 2) { syslog(LOG_INFO, "%s: Invalid initial response: %i", it->queueid, res); @@ -304,7 +338,7 @@ deliver_remote(struct qitem *it, const char **errmsg) if (((config->features & STARTTLS) == 0) && ((config->features & SECURETRANS) != 0)) { send_remote_command(fd, "EHLO %s", hostname()); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery deferred: " " EHLO failed: %m", it->queueid); return (-1); @@ -313,7 +347,7 @@ deliver_remote(struct qitem *it, const char **errmsg) #endif /* HAVE_CRYPTO */ if (((config->features & SECURETRANS) == 0)) { send_remote_command(fd, "EHLO %s", hostname()); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery deferred: " " EHLO failed: %m", it->queueid); return (-1); @@ -336,44 +370,36 @@ deliver_remote(struct qitem *it, const char **errmsg) * Check if the user wants plain text login without using * encryption. */ - if ((config->features & INSECURE) != 0) { - syslog(LOG_INFO, "%s: Use SMTP authentication", + syslog(LOG_INFO, "%s: Use SMTP authentication", it->queueid); - error = smtp_login(it, fd, a->login, a->password); - if (error < 0) { - syslog(LOG_ERR, "%s: remote delivery failed:" + error = smtp_login(it, fd, a->login, a->password); + if (error < 0) { + syslog(LOG_ERR, "%s: remote delivery failed:" " SMTP login failed: %m", it->queueid); - return (-1); - } - /* SMTP login is not available, so try without */ - else if (error > 0) - syslog(LOG_ERR, "%s: SMTP login not available." - " Try without", it->queueid); - } else { - syslog(LOG_ERR, "%s: Skip SMTP login. ", - it->queueid); + return (-1); } + /* SMTP login is not available, so try without */ + else if (error > 0) + syslog(LOG_ERR, "%s: SMTP login not available." + " Try without", it->queueid); } send_remote_command(fd, "MAIL FROM:<%s>", it->sender); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery deferred:" " MAIL FROM failed: %m", it->queueid); return (1); } - /* XXX TODO: - * Iterate over all recepients and open only one connection - */ send_remote_command(fd, "RCPT TO:<%s>", it->addr); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery deferred:" - " RCPT TO failed: %m", it->queueid); + " RCPT TO failed: %m", it->queueid); return (1); } send_remote_command(fd, "DATA"); - if (read_remote(fd) != 3) { + if (read_remote(fd, 0, NULL) != 3) { syslog(LOG_ERR, "%s: remote delivery deferred:" " DATA failed: %m", it->queueid); return (1); @@ -416,14 +442,14 @@ deliver_remote(struct qitem *it, const char **errmsg) } send_remote_command(fd, "."); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery deferred: %m", it->queueid); return (1); } send_remote_command(fd, "QUIT"); - if (read_remote(fd) != 2) { + if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_ERR, "%s: remote delivery deferred: " "QUIT failed: %m", it->queueid); return (1);