diff options
author | Wojciech Polak <polak@gnu.org> | 2009-08-08 00:33:39 +0200 |
---|---|---|
committer | Wojciech Polak <polak@gnu.org> | 2009-08-08 00:33:39 +0200 |
commit | 6eb89fe34d47130e40d121fefeac2bf3c03f3a72 (patch) | |
tree | 0e295196cab636641ee1115e16074eae1823d04c | |
parent | 134c0cc6398077a17ff692475fbd6aebfd4e6509 (diff) | |
download | mailutils-6eb89fe34d47130e40d121fefeac2bf3c03f3a72.tar.gz mailutils-6eb89fe34d47130e40d121fefeac2bf3c03f3a72.tar.bz2 |
Add ESMTP AUTH support.
* libproto/mailer/smtp.c (smtp_auth, cram_md5): New function.
-rw-r--r-- | libproto/mailer/smtp.c | 278 |
1 files changed, 273 insertions, 5 deletions
diff --git a/libproto/mailer/smtp.c b/libproto/mailer/smtp.c index 22f37d94f..1557cc492 100644 --- a/libproto/mailer/smtp.c +++ b/libproto/mailer/smtp.c @@ -42,12 +42,14 @@ #include <mailutils/header.h> #include <mailutils/body.h> #include <mailutils/message.h> +#include <mailutils/mime.h> #include <mailutils/mutil.h> #include <mailutils/observer.h> #include <mailutils/property.h> #include <mailutils/stream.h> #include <mailutils/url.h> #include <mailutils/tls.h> +#include <mailutils/md5.h> #include <mailutils/cctype.h> #include <mailutils/cstr.h> @@ -115,7 +117,7 @@ struct _smtp SMTP_HELO, SMTP_HELO_ACK, SMTP_QUIT, SMTP_QUIT_ACK, SMTP_ENV_FROM, SMTP_ENV_RCPT, SMTP_MAIL_FROM, SMTP_MAIL_FROM_ACK, SMTP_RCPT_TO, SMTP_RCPT_TO_ACK, SMTP_DATA, SMTP_DATA_ACK, SMTP_SEND, SMTP_SEND_ACK, - SMTP_SEND_DOT, SMTP_STARTTLS, SMTP_STARTTLS_ACK + SMTP_SEND_DOT, SMTP_STARTTLS, SMTP_STARTTLS_ACK, SMTP_AUTH, SMTP_AUTH_ACK, } state; @@ -123,6 +125,7 @@ struct _smtp unsigned long capa; /* Server capabilities */ size_t max_size; /* Maximum message size the server is willing to accept */ + unsigned long auth_mechs; /* Available ESMTP AUTH mechanisms */ const char *mail_from; mu_address_t rcpt_to; /* Destroy this if not the same as argto below. */ @@ -148,17 +151,43 @@ typedef struct _smtp *smtp_t; #define CAPA_STARTTLS 0x00000001 #define CAPA_8BITMIME 0x00000002 #define CAPA_SIZE 0x00000004 +#define CAPA_AUTH 0x00000008 + +/* ESMTP AUTH mechanisms */ +#define AUTH_LOGIN 0x00000001 +#define AUTH_PLAIN 0x00000002 +#define AUTH_CRAM_MD5 0x00000004 +#define AUTH_DIGEST_MD5 0x00000008 +#define AUTH_GSSAPI 0x00000010 +#define AUTH_EXTERNAL 0x00000020 + +struct auth_mech_record { + unsigned long id; + char *name; +}; + +static struct auth_mech_record auth_mech_list[] = { + { AUTH_LOGIN, "login" }, + { AUTH_PLAIN, "plain" }, + { AUTH_CRAM_MD5, "cram-md5" }, + { AUTH_DIGEST_MD5, "digest-md5" }, + { AUTH_GSSAPI, "gssapi" }, + { AUTH_EXTERNAL, "external" }, + { 0, NULL }, +}; static void smtp_destroy (mu_mailer_t); static int smtp_open (mu_mailer_t, int); static int smtp_close (mu_mailer_t); -static int smtp_send_message (mu_mailer_t, mu_message_t, mu_address_t, mu_address_t); +static int smtp_send_message (mu_mailer_t, mu_message_t, mu_address_t, + mu_address_t); static int smtp_writeline (smtp_t smtp, const char *format, ...); static int smtp_readline (smtp_t); static int smtp_read_ack (smtp_t); static int smtp_parse_ehlo_ack (smtp_t); static int smtp_write (smtp_t); static int smtp_starttls (smtp_t); +static int smtp_auth (smtp_t); static int _smtp_set_rcpt (smtp_t, mu_message_t, mu_address_t); @@ -414,6 +443,8 @@ smtp_open (mu_mailer_t mailer, int flags) mu_stream_close (mailer->stream); return EACCES; } + +ehlo: status = smtp_writeline (smtp, "EHLO %s\r\n", smtp->localhost); CHECK_ERROR (smtp, status); @@ -442,14 +473,26 @@ smtp_open (mu_mailer_t mailer, int flags) if (smtp->capa & CAPA_STARTTLS) smtp->state = SMTP_STARTTLS; + else if (smtp->capa & CAPA_AUTH && mailer->url->user) { + smtp->state = SMTP_AUTH; + } else break; } case SMTP_STARTTLS: case SMTP_STARTTLS_ACK: - smtp_starttls (smtp); - break; + if (smtp->capa & CAPA_STARTTLS) { + smtp_starttls (smtp); + goto ehlo; + } + + case SMTP_AUTH: + case SMTP_AUTH_ACK: + if (smtp->capa & CAPA_AUTH) { + smtp_auth (smtp); + break; + } case SMTP_HELO: if (!smtp->extended) /* FIXME: this will always be false! */ @@ -557,12 +600,13 @@ smtp_starttls (smtp_t smtp) #ifdef WITH_TLS int status; mu_mailer_t mailer = smtp->mailer; - char *keywords[] = { "STARTTLS", "EHLO", NULL }; + char *keywords[] = { "STARTTLS", NULL }; if (!mu_tls_enable || !(smtp->capa & CAPA_STARTTLS)) return -1; smtp->capa = 0; + smtp->auth_mechs = 0; status = mu_tls_begin (smtp, smtp_reader, smtp_writer, smtp_stream_ctl, keywords); @@ -575,6 +619,209 @@ smtp_starttls (smtp_t smtp) #endif /* WITH_TLS */ } +static void +cram_md5 (char *secret, char *challenge, unsigned char *digest) +{ + struct mu_md5_ctx context; + unsigned char ipad[64]; + unsigned char opad[64]; + int secret_len; + int challenge_len; + int i; + + if (secret == 0 || challenge == 0) + return; + + secret_len = strlen (secret); + challenge_len = strlen (challenge); + memset (ipad, 0, sizeof (ipad)); + memset (opad, 0, sizeof (opad)); + + if (secret_len > 64) + { + mu_md5_init_ctx (&context); + mu_md5_process_bytes ((unsigned char *)secret, secret_len, &context); + mu_md5_finish_ctx (&context, ipad); + mu_md5_finish_ctx (&context, opad); + } + else + { + memcpy (ipad, secret, secret_len); + memcpy (opad, secret, secret_len); + } + + for (i = 0; i < 64; i++) + { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + mu_md5_init_ctx (&context); + mu_md5_process_bytes (ipad, sizeof (ipad), &context); + mu_md5_process_bytes ((unsigned char *)challenge, challenge_len, &context); + mu_md5_finish_ctx (&context, digest); + + mu_md5_init_ctx (&context); + mu_md5_process_bytes (opad, sizeof (opad), &context); + mu_md5_process_bytes (digest, 16, &context); + mu_md5_finish_ctx (&context, digest); +} + +static int +smtp_auth (smtp_t smtp) +{ + int status; + mu_mailer_t mailer = smtp->mailer; + struct auth_mech_record *mechs = auth_mech_list; + const char *chosen_mech_name = NULL; + int chosen_mech_id = 0; + + status = mu_url_sget_auth (mailer->url, &chosen_mech_name); + if (status != MU_ERR_NOENT) + { + for (; mechs->name; mechs++) + { + if (!mu_c_strcasecmp (mechs->name, chosen_mech_name)) + { + chosen_mech_id = mechs->id; + break; + } + } + } + if (chosen_mech_id) + { + if (smtp->auth_mechs & chosen_mech_id) + { + smtp->auth_mechs = 0; + smtp->auth_mechs |= chosen_mech_id; + } + else + { + MU_DEBUG1 (mailer->debug, MU_DEBUG_ERROR, + "mailer does not support AUTH '%s' mechanism\n", + chosen_mech_name); + return -1; + } + } + +#if 0 && defined(WITH_GSASL) + + /* FIXME: Add GNU SASL support. */ + +#else + + /* Provide basic AUTH mechanisms when GSASL is not enabled. */ + + if (smtp->auth_mechs & AUTH_CRAM_MD5) + { + int i; + char *p, *buf = NULL; + const char *user = NULL; + mu_secret_t secret; + unsigned char *chl; + size_t chlen, buflen = 0, b64buflen = 0; + unsigned char *b64buf = NULL; + unsigned char digest[16]; + static char ascii_digest[33]; + memset (digest, 0, 16); + + status = mu_url_sget_user (mailer->url, &user); + if (status == MU_ERR_NOENT) + return -1; + + status = mu_url_get_secret (mailer->url, &secret); + if (status == MU_ERR_NOENT) + { + MU_DEBUG (mailer->debug, MU_DEBUG_ERROR, + "AUTH CRAM-MD5 mechanism requires giving a password\n"); + return -1; + } + + status = smtp_writeline (smtp, "AUTH CRAM-MD5\r\n"); + CHECK_ERROR (smtp, status); + status = smtp_write (smtp); + CHECK_EAGAIN (smtp, status); + status = smtp_read_ack (smtp); + CHECK_EAGAIN (smtp, status); + + if (strncmp (smtp->buffer, "334 ", 4)) + { + MU_DEBUG (mailer->debug, MU_DEBUG_ERROR, + "mailer rejected the AUTH CRAM-MD5 command\n"); + return -1; + } + + p = strchr (smtp->buffer, ' ') + 1; + mu_rtrim_cset (p, "\r\n"); + mu_base64_decode (p, strlen (p), &chl, &chlen); + + cram_md5 ((char *)mu_secret_password (secret), chl, digest); + mu_secret_password_unref (secret); + free (chl); + + for (i = 0; i < 16; i++) + sprintf (ascii_digest + 2 * i, "%02x", digest[i]); + + mu_asnprintf (&buf, &buflen, "%s %s", user, ascii_digest); + buflen = strlen (buf); + mu_base64_encode (buf, buflen, &b64buf, &b64buflen); + b64buf[b64buflen] = '\0'; + free (buf); + + status = smtp_writeline (smtp, "%s\r\n", b64buf); + CHECK_ERROR (smtp, status); + status = smtp_write (smtp); + CHECK_EAGAIN (smtp, status); + status = smtp_read_ack (smtp); + CHECK_EAGAIN (smtp, status); + } + + else if (smtp->auth_mechs & AUTH_PLAIN) + { + int c; + char *buf = NULL; + unsigned char *b64buf = NULL; + size_t buflen = 0, b64buflen = 0; + const char *user = NULL; + mu_secret_t secret; + + status = mu_url_sget_user (mailer->url, &user); + if (status == MU_ERR_NOENT) + return -1; + + status = mu_url_get_secret (mailer->url, &secret); + if (status == MU_ERR_NOENT) + { + MU_DEBUG (mailer->debug, MU_DEBUG_ERROR, + "AUTH PLAIN mechanism requires giving a password\n"); + return -1; + } + + mu_asnprintf (&buf, &buflen, "^%s^%s", + user, mu_secret_password (secret)); + mu_secret_password_unref (secret); + buflen = strlen (buf); + for (c = buflen - 1; c >= 0; c--) + { + if (buf[c] == '^') + buf[c] = '\0'; + } + mu_base64_encode (buf, buflen, &b64buf, &b64buflen); + b64buf[b64buflen] = '\0'; + free (buf); + + status = smtp_writeline (smtp, "AUTH PLAIN %s\r\n", b64buf); + CHECK_ERROR (smtp, status); + status = smtp_write (smtp); + CHECK_EAGAIN (smtp, status); + status = smtp_read_ack (smtp); + CHECK_EAGAIN (smtp, status); + } + +#endif /* not WITH_GSASL */ + return 0; +} + static int message_set_header_value (mu_message_t msg, const char *field, const char *value) { @@ -1157,6 +1404,27 @@ smtp_parse_ehlo_ack (smtp_t smtp) smtp->max_size = n; } } + else if (!mu_c_strncasecmp (smtp->buffer, "250-AUTH", 8)) + { + char *name, *s; + smtp->capa |= CAPA_AUTH; + + for (name = strtok_r (smtp->buffer + 8, " ", &s); name; + name = strtok_r (NULL, " ", &s)) + { + struct auth_mech_record *mechs = auth_mech_list; + for (; mechs->name; mechs++) + { + mu_rtrim_cset (name, "\r\n"); + if (!mu_c_strcasecmp (mechs->name, name)) + { + smtp->auth_mechs |= mechs->id; + break; + } + } + } + } + } } while (multi && status == 0); |