/* * smtp.c * * This file is part of msmtp, an SMTP client. * * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007 * Martin Lambers * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #ifdef HAVE_LIBGSASL # include #else # include "base64.h" # include "hmac.h" #endif #include "gettext.h" #include "xalloc.h" #include "xvasprintf.h" #include "list.h" #include "net.h" #include "smtp.h" #include "stream.h" #ifdef HAVE_TLS #include "tls.h" #endif /* HAVE_TLS */ /* This defines the maximum number of lines in a multiline server reply. * This limit exists to prevent an extremely long reply from eating * all the memory. * The longest server reply we have to expect is the repsonse to the EHLO * command. We should have enough lines for every SMTP extension known today * plus more lines for future extensions. */ #define SMTP_MAXLINES 50 /* This defines the length of the *input* buffer for SMTP messages. * According to RFC 2821, SMTP commands and SMTP messages may contain * at most 512 characters including CRLF, thus the *minimum* size is * 512 characters. */ #define SMTP_BUFSIZE 1024 /* This defines the length of the *output* buffer for SMTP commands, without * CRLF. According to RFC 2821, SMTP commands may contain at most 512 * characters including CRLF, thus the maximum size should be 510 characters. * But this is not sufficient for some authentication mechanisms, notably * GSSAPI, so this value must be higher. */ #define SMTP_MAXCMDLEN 1022 /* The maximum length of the SMTP command pipeline (the maximum number of * commands that are sent at once, before starting to read the replies). This * number should be large enough to keep the benefits of pipelining (saved round * trips), and small enough to avoid problems (exceeding the TCP window size). * A value of 1 disables pipelining. */ #define SMTP_PIPELINE_LIMIT 100 /* This is the buffer length for copying the mail to the SMTP server. * According to RFC 2822, a line in a mail can contain at most 998 * characters + CRLF. Plus one character for '\0' = 1001 characters. * All lines should fit in a buffer of this size. * However, this length is not a limit; smtp_send_mail() will accept * arbitrary long lines. */ #define MAIL_BUFSIZE 1024 /* * smtp_new() * * see smtp.h */ smtp_server_t smtp_new(FILE *debug, int protocol) { smtp_server_t srv; srv.fd = -1; net_readbuf_init(&(srv.readbuf)); #ifdef HAVE_TLS tls_clear(&srv.tls); #endif /* HAVE_TLS */ srv.protocol = protocol; srv.cap.flags = 0; srv.cap.size = 0; srv.debug = debug; return srv; } /* * smtp_connect() * * see smtp.h */ int smtp_connect(smtp_server_t *srv, const char *host, int port, int timeout, char **server_canonical_name, char **server_address, char **errstr) { return net_open_socket(host, port, timeout, &srv->fd, server_canonical_name, server_address, errstr); } /* * smtp_get_msg() * * This function gets a message from the SMTP server 'srv'. * In case of success, 'msg' will contain a pointer to a newly created list, * and each member of this list will contain one line of the message as an * allocated string. The list will contain at least one line. Each line will * be at least 4 characters long: The three digit status code plus a ' ' or '-'. * Each line will be at most SMTP_BUFSIZE characters long, including '\0'. * The return code will be EOK. * In case of failure, 'msg' will be NULL, and one of the following error codes * will be returned: SMTP_EIO, SMTP_EPROTO */ int smtp_get_msg(smtp_server_t *srv, list_t **msg, char **errstr) { list_t *l; list_t *lp; char line[SMTP_BUFSIZE]; int counter; size_t len; *msg = NULL; l = list_new(); lp = l; counter = 0; do { #ifdef HAVE_TLS if (tls_is_active(&srv->tls)) { if (tls_gets(&srv->tls, line, SMTP_BUFSIZE, &len, errstr) != TLS_EOK) { list_xfree(l, free); return SMTP_EIO; } } else { #endif /* HAVE_TLS */ if (net_gets(srv->fd, &(srv->readbuf), line, SMTP_BUFSIZE, &len, errstr) != NET_EOK) { list_xfree(l, free); return SMTP_EIO; } #ifdef HAVE_TLS } #endif /* HAVE_TLS */ if (len < 4 || !(isdigit((unsigned char)line[0]) && isdigit((unsigned char)line[1]) && isdigit((unsigned char)line[2]) && (line[3] == ' ' || line[3] == '-')) || line[len - 1] != '\n') { list_xfree(l, free); /* The string is not necessarily a reply (it may be the initial OK * message), but this is the term used in the RFCs. * An empty reply is a special case of an invalid reply - this * differentiation may help the user. */ if (counter == 0 && len == 0) { *errstr = xasprintf(_("the server sent an empty reply")); } else { *errstr = xasprintf(_("the server sent an invalid reply")); } return SMTP_EPROTO; } if (srv->debug) { fputs("<-- ", srv->debug); fwrite(line, sizeof(char), len, srv->debug); } /* kill CRLF */ line[--len] = '\0'; if (line[len - 1] == '\r') { line[--len] = '\0'; } list_insert(lp, xstrdup(line)); counter++; lp = lp->next; } while (line[3] == '-' && counter <= SMTP_MAXLINES); if (counter > SMTP_MAXLINES) { list_xfree(l, free); *errstr = xasprintf(_("Rejecting server reply that is longer " "than %d lines. Increase SMTP_MAXLINES."), SMTP_MAXLINES); return SMTP_EPROTO; } *msg = l; return SMTP_EOK; } /* * smtp_msg_status() * * see smtp.h */ int smtp_msg_status(list_t *msg) { /* we know that *msg is valid; there's no need to check for errors */ return atoi(msg->next->data); } /* * smtp_put() * * This function writes 'len' characters from 's' to the SMTP server 'srv'. * Used error codes: SMTP_EIO */ int smtp_put(smtp_server_t *srv, const char *s, size_t len, char **errstr) { int e = 0; #ifdef HAVE_TLS if (tls_is_active(&srv->tls)) { e = (tls_puts(&srv->tls, s, len, errstr) != TLS_EOK); } else { #endif /* HAVE_TLS */ e = (net_puts(srv->fd, s, len, errstr) != NET_EOK); #ifdef HAVE_TLS } #endif /* HAVE_TLS */ if (e) { return SMTP_EIO; } if (srv->debug) { fputs("--> ", srv->debug); fwrite(s, sizeof(char), len, srv->debug); } return SMTP_EOK; } /* * smtp_send_cmd() * * This function writes a string to the SMTP server 'srv'. The string may not * be longer than SMTP_MAXCMDLEN characters (see above). TCP CRLF ('\r\n') will * be appended to the string. Use this function to send SMTP commands (not mail * data) to the SMTP server. * Used error codes: SMTP_EIO, SMTP_EINVAL */ /* make gcc print format warnings for this function */ #ifdef __GNUC__ int smtp_send_cmd(smtp_server_t *srv, char **errstr, const char *format, ...) __attribute__ ((format (printf, 3, 4))); #endif int smtp_send_cmd(smtp_server_t *srv, char **errstr, const char *format, ...) { char line[SMTP_MAXCMDLEN + 3]; int count; va_list args; va_start(args, format); count = vsnprintf(line, SMTP_MAXCMDLEN + 1, format, args); va_end(args); if (count >= SMTP_MAXCMDLEN + 1) { *errstr = xasprintf(_("Cannot send command because it is " "longer than %d characters. Increase SMTP_MAXCMDLEN."), SMTP_MAXCMDLEN); return SMTP_EINVAL; } line[count++] = '\r'; line[count++] = '\n'; line[count] = '\0'; return smtp_put(srv, line, (size_t)count, errstr); } /* * smtp_get_greeting() * * see smtp.h */ int smtp_get_greeting(smtp_server_t *srv, list_t **errmsg, char **buf, char **errstr) { int e; list_t *msg; *errmsg = NULL; if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 220) { *errmsg = msg; *errstr = xasprintf(_("cannot get initial OK message from server")); return SMTP_EPROTO; } if (buf) { *buf = xmalloc( (strlen((char *)msg->next->data + 4) + 1) * sizeof(char)); strcpy(*buf, (char *)(msg->next->data) + 4); } list_xfree(msg, free); return SMTP_EOK; } /* * smtp_init() * * see smtp.h */ int smtp_init(smtp_server_t *srv, const char *ehlo_domain, list_t **errmsg, char **errstr) { int e; list_t *ehlo_response; list_t *lp; char *s; char *p; size_t len; int i; srv->cap.flags = 0; *errmsg = NULL; if (srv->protocol == SMTP_PROTO_SMTP) { if ((e = smtp_send_cmd(srv, errstr, "EHLO %s", ehlo_domain)) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &ehlo_response, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(ehlo_response) != 250) { /* fall back to HELO, for very old SMTP servers */ list_xfree(ehlo_response, free); if ((e = smtp_send_cmd(srv, errstr, "HELO %s", ehlo_domain)) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &ehlo_response, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(ehlo_response) != 250) { *errmsg = ehlo_response; *errstr = xasprintf(_("SMTP server does not accept " "EHLO or HELO commands")); return SMTP_EPROTO; } list_xfree(ehlo_response, free); /* srv->cap.flags is 0 */ return SMTP_EOK; } } else /* protocol is LMTP */ { if ((e = smtp_send_cmd(srv, errstr, "LHLO %s", ehlo_domain)) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &ehlo_response, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(ehlo_response) != 250) { *errmsg = ehlo_response; *errstr = xasprintf(_("command %s failed"), "LHLO"); return SMTP_EPROTO; } } lp = ehlo_response; while (!list_is_empty(lp)) { lp = lp->next; s = lp->data; len = strlen(s); /* we know that len is >= 4 */ /* make line uppercase */ for (i = 4; (size_t)i < len; i++) { s[i] = toupper((unsigned char)s[i]); } /* search capabilities */ if (strncmp(s + 4, "STARTTLS", 8) == 0) { srv->cap.flags |= SMTP_CAP_STARTTLS; } else if (strncmp(s + 4, "DSN", 3) == 0) { srv->cap.flags |= SMTP_CAP_DSN; } else if (strncmp(s + 4, "PIPELINING", 10) == 0) { srv->cap.flags |= SMTP_CAP_PIPELINING; } else if (strncmp(s + 4, "SIZE", 4) == 0) { /* If there's no number after the SIZE keyword, the server does not * tell us about a maximum message size. Treat that as if the SIZE * keyword was not seen. Also treat invalid numbers the same way. * The value 0 means there is no maximum. * See RFC 1653. */ errno = 0; srv->cap.size = strtol(s + 8, &p, 10); if (!(*(s + 8) == '\0' || *p != '\0' || srv->cap.size < 0 || (srv->cap.size == LONG_MAX && errno == ERANGE))) { srv->cap.flags |= SMTP_CAP_SIZE; } } /* Accept "AUTH " as well as "AUTH=". There are still some broken * servers that use "AUTH=". */ else if (strncmp(s + 4, "AUTH", 4) == 0 && (*(s + 8) == ' ' || *(s + 8) == '=')) { srv->cap.flags |= SMTP_CAP_AUTH; if (strstr(s + 9, "PLAIN")) { srv->cap.flags |= SMTP_CAP_AUTH_PLAIN; } if (strstr(s + 9, "CRAM-MD5")) { srv->cap.flags |= SMTP_CAP_AUTH_CRAM_MD5; } if (strstr(s + 9, "DIGEST-MD5")) { srv->cap.flags |= SMTP_CAP_AUTH_DIGEST_MD5; } if (strstr(s + 9, "GSSAPI")) { srv->cap.flags |= SMTP_CAP_AUTH_GSSAPI; } if (strstr(s + 9, "EXTERNAL")) { srv->cap.flags |= SMTP_CAP_AUTH_EXTERNAL; } if (strstr(s + 9, "LOGIN")) { srv->cap.flags |= SMTP_CAP_AUTH_LOGIN; } if (strstr(s + 9, "NTLM")) { srv->cap.flags |= SMTP_CAP_AUTH_NTLM; } } else if (strncmp(s + 4, "ETRN", 4) == 0) { srv->cap.flags |= SMTP_CAP_ETRN; } } list_xfree(ehlo_response, free); return SMTP_EOK; } /* * smtp_tls_init() * * see smtp.h */ #ifdef HAVE_TLS int smtp_tls_init(smtp_server_t *srv, const char *tls_key_file, const char *tls_ca_file, const char *tls_trust_file, int force_sslv3, char **errstr) { return tls_init(&srv->tls, tls_key_file, tls_ca_file, tls_trust_file, force_sslv3, errstr); } #endif /* HAVE_TLS */ /* * smtp_tls_starttls() * * see smtp.h */ #ifdef HAVE_TLS int smtp_tls_starttls(smtp_server_t *srv, list_t **error_msg, char **errstr) { int e; list_t *msg; *error_msg = NULL; if ((e = smtp_send_cmd(srv, errstr, "STARTTLS")) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 220) { *error_msg = msg; *errstr = xasprintf(_("command %s failed"), "STARTTLS"); return SMTP_EPROTO; } list_xfree(msg, free); return SMTP_EOK; } #endif /* HAVE_TLS */ /* * smtp_tls() * * see smtp.h */ #ifdef HAVE_TLS int smtp_tls(smtp_server_t *srv, const char *hostname, int tls_nocertcheck, tls_cert_info_t *tci, char **errstr) { return tls_start(&srv->tls, srv->fd, hostname, tls_nocertcheck, tci, errstr); } #endif /* HAVE_TLS */ /* * smtp_auth_plain() * * Do SMTP authentication via AUTH PLAIN. * The SMTP server must support SMTP_CAP_AUTH_PLAIN * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL */ #ifndef HAVE_LIBGSASL int smtp_auth_plain(smtp_server_t *srv, const char *user, const char *password, list_t **error_msg, char **errstr) { char *s; char *b64; size_t u_len; size_t p_len; size_t b64_len; list_t *msg; int e; int status; *error_msg = NULL; u_len = strlen(user); p_len = strlen(password); s = xmalloc((u_len + p_len + 3) * sizeof(char)); s[0] = '\0'; strcpy(s + 1, user); strcpy(s + u_len + 2, password); b64_len = BASE64_LENGTH(u_len + p_len + 2); b64 = xmalloc(b64_len + 1); base64_encode(s, u_len + p_len + 2, b64, b64_len + 1); free(s); if ((e = smtp_send_cmd(srv, errstr, "AUTH PLAIN %s", b64)) != SMTP_EOK) { free(b64); return e; } free(b64); if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if ((status = smtp_msg_status(msg)) != 235) { *error_msg = msg; if (status == 504) { *errstr = xasprintf(_("command %s failed"), "AUTH PLAIN"); return SMTP_EPROTO; } else { *errstr = xasprintf(_("authentication failed (method %s)"), "PLAIN"); return SMTP_EAUTHFAIL; } } list_xfree(msg, free); return SMTP_EOK; } #endif /* !HAVE_LIBGSASL */ /* * smtp_auth_login() * * Do SMTP authentication via AUTH LOGIN. * The SMTP server must support SMTP_CAP_AUTH_LOGIN * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL */ #ifndef HAVE_LIBGSASL int smtp_auth_login(smtp_server_t *srv, const char *user, const char *password, list_t **error_msg, char **errstr) { int e; list_t *msg; char *b64; size_t b64_len; size_t u_len; size_t p_len; *error_msg = NULL; if ((e = smtp_send_cmd(srv, errstr, "AUTH LOGIN")) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 334) { *error_msg = msg; *errstr = xasprintf(_("command %s failed"), "AUTH LOGIN"); return SMTP_EPROTO; } list_xfree(msg, free); u_len = strlen(user); b64_len = BASE64_LENGTH(u_len); b64 = xmalloc(b64_len + 1); base64_encode(user, u_len, b64, b64_len + 1); if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK) { free(b64); return e; } free(b64); if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 334) { *error_msg = msg; *errstr = xasprintf(_("authentication failed (method %s)"), "LOGIN"); return SMTP_EAUTHFAIL; } list_xfree(msg, free); p_len = strlen(password); b64_len = BASE64_LENGTH(p_len); b64 = xmalloc(b64_len + 1); base64_encode(password, p_len, b64, b64_len + 1); if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK) { free(b64); return e; } free(b64); if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 235) { *error_msg = msg; *errstr = xasprintf(_("authentication failed (method %s)"), "LOGIN"); return SMTP_EAUTHFAIL; } list_xfree(msg, free); return SMTP_EOK; } #endif /* !HAVE_LIBGSASL */ /* * smtp_auth_cram_md5() * * Do SMTP authentication via AUTH CRAM-MD5. * The SMTP server must support SMTP_CAP_AUTH_CRAM_MD5 * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL */ #ifndef HAVE_LIBGSASL int smtp_auth_cram_md5(smtp_server_t *srv, const char *user, const char *password, list_t **error_msg, char **errstr) { unsigned char digest[16]; char hex[] = "0123456789abcdef"; char *challenge; size_t challenge_len; char *b64; size_t b64_len; char *buf; char *p; size_t len; int i; list_t *msg; int e; *error_msg = NULL; if ((e = smtp_send_cmd(srv, errstr, "AUTH CRAM-MD5")) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 334) { *error_msg = msg; *errstr = xasprintf(_("command %s failed"), "AUTH CRAM-MD5"); return SMTP_EPROTO; } /* we know the line is at least 4 characters long */ challenge = (char *)(msg->next->data) + 4; challenge_len = strlen(challenge); len = 3 * (challenge_len / 4) + 2; b64 = xmalloc(len); if (!base64_decode(challenge, challenge_len, b64, &len)) { list_xfree(msg, free); *errstr = xasprintf(_("authentication method CRAM-MD5: " "server sent invalid challenge")); return SMTP_EPROTO; } list_xfree(msg, free); hmac_md5(password, strlen(password), b64, len, digest); free(b64); /* construct username + ' ' + digest_in_hex */ len = strlen(user); buf = xmalloc((len + 1 + 32 + 1) * sizeof(char)); strcpy(buf, user); p = buf + len; *p++ = ' '; for (i = 0; i < 16; i++) { p[2 * i] = hex[(digest[i] & 0xf0) >> 4]; p[2 * i + 1] = hex[digest[i] & 0x0f]; } p[32] = '\0'; b64_len = BASE64_LENGTH(len + 33); b64 = xmalloc(b64_len + 1); base64_encode(buf, len + 33, b64, b64_len + 1); free(buf); if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK) { free(b64); return e; } free(b64); if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 235) { *error_msg = msg; *errstr = xasprintf(_("authentication failed (method %s)"), "CRAM-MD5"); return SMTP_EAUTHFAIL; } list_xfree(msg, free); return SMTP_EOK; } #endif /* !HAVE_LIBGSASL */ /* * smtp_auth_external() * * Do SMTP authentication via AUTH EXTERNAL. * This means the actual authentication is done via TLS; we just send the user * name to ther server. * The SMTP server must support SMTP_CAP_AUTH_EXTERNAL * Used error codes: SMTP_EIO, SMTP_EPROTO, SMTP_EAUTHFAIL, SMTP_EINVAL */ #ifndef HAVE_LIBGSASL int smtp_auth_external(smtp_server_t *srv, const char *user, list_t **error_msg, char **errstr) { size_t u_len; size_t b64_len; char *b64; list_t *msg; int e; *error_msg = NULL; if ((e = smtp_send_cmd(srv, errstr, "AUTH EXTERNAL")) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 334) { *error_msg = msg; *errstr = xasprintf(_("command %s failed"), "AUTH EXTERNAL"); return SMTP_EPROTO; } list_xfree(msg, free); u_len = strlen(user); b64_len = BASE64_LENGTH(u_len); b64 = xmalloc(b64_len + 1); base64_encode(user, u_len, b64, b64_len + 1); if ((e = smtp_send_cmd(srv, errstr, "%s", b64)) != SMTP_EOK) { free(b64); return e; } free(b64); if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 235) { *error_msg = msg; *errstr = xasprintf(_("authentication failed (method %s)"), "EXTERNAL"); return SMTP_EAUTHFAIL; } list_xfree(msg, free); return SMTP_EOK; } #endif /* !HAVE_LIBGSASL */ /* * smtp_server_supports_authmech() * * see smtp.h */ int smtp_server_supports_authmech(smtp_server_t *srv, const char *mech) { return (((srv->cap.flags & SMTP_CAP_AUTH_PLAIN) && strcmp(mech, "PLAIN") == 0) || ((srv->cap.flags & SMTP_CAP_AUTH_CRAM_MD5) && strcmp(mech, "CRAM-MD5") == 0) || ((srv->cap.flags & SMTP_CAP_AUTH_DIGEST_MD5) && strcmp(mech, "DIGEST-MD5") == 0) || ((srv->cap.flags & SMTP_CAP_AUTH_EXTERNAL) && strcmp(mech, "EXTERNAL") == 0) || ((srv->cap.flags & SMTP_CAP_AUTH_GSSAPI) && strcmp(mech, "GSSAPI") == 0) || ((srv->cap.flags & SMTP_CAP_AUTH_LOGIN) && strcmp(mech, "LOGIN") == 0) || ((srv->cap.flags & SMTP_CAP_AUTH_NTLM) && strcmp(mech, "NTLM") == 0)); } /* * smtp_client_supports_authmech() * * see smtp.h */ int smtp_client_supports_authmech(const char *mech) { #ifdef HAVE_LIBGSASL int supported = 0; Gsasl *ctx; if (gsasl_init(&ctx) != GSASL_OK) { return 0; } supported = gsasl_client_support_p(ctx, mech); gsasl_done(ctx); return supported; #else /* not HAVE_LIBGSASL */ return (strcmp(mech, "CRAM-MD5") == 0 || strcmp(mech, "PLAIN") == 0 || strcmp(mech, "EXTERNAL") == 0 || strcmp(mech, "LOGIN") == 0); #endif /* not HAVE_LIBGSASL */ } /* * smtp_auth() * * see smtp.h */ int smtp_auth(smtp_server_t *srv, const char *hostname, const char *user, const char *password, #ifdef HAVE_LIBGSASL const char *ntlmdomain, #else const char *ntlmdomain UNUSED, #endif const char *auth_mech, char *(*password_callback)(const char *hostname, const char *user), list_t **error_msg, char **errstr) { #ifdef HAVE_LIBGSASL int e; list_t *msg; Gsasl *ctx; Gsasl_session *sctx; char *input; char inbuf[SMTP_BUFSIZE]; char *outbuf; int error_code; int auth_plain_special; char *callback_password = NULL; *error_msg = NULL; if (strcmp(auth_mech, "") != 0 && !smtp_server_supports_authmech(srv, auth_mech)) { *errstr = xasprintf( _("the server does not support authentication method %s"), auth_mech); return SMTP_EUNAVAIL; } if ((error_code = gsasl_init(&ctx)) != GSASL_OK) { *errstr = xasprintf(_("GNU SASL: %s"), gsasl_strerror(error_code)); return SMTP_ELIBFAILED; } if (strcmp(auth_mech, "") != 0 && !gsasl_client_support_p(ctx, auth_mech)) { gsasl_done(ctx); *errstr = xasprintf( _("GNU SASL: authentication method %s not supported"), auth_mech); return SMTP_ELIBFAILED; } if (strcmp(auth_mech, "") == 0) { /* Choose "best" authentication mechanism. */ /* TODO: use gsasl_client_suggest_mechanism()? */ if (gsasl_client_support_p(ctx, "GSSAPI") && (srv->cap.flags & SMTP_CAP_AUTH_GSSAPI)) { auth_mech = "GSSAPI"; } else if (gsasl_client_support_p(ctx, "DIGEST-MD5") && (srv->cap.flags & SMTP_CAP_AUTH_DIGEST_MD5)) { auth_mech = "DIGEST-MD5"; } else if (gsasl_client_support_p(ctx, "CRAM-MD5") && (srv->cap.flags & SMTP_CAP_AUTH_CRAM_MD5)) { auth_mech = "CRAM-MD5"; } #ifdef HAVE_TLS else if (tls_is_active(&srv->tls)) { if (gsasl_client_support_p(ctx, "PLAIN") && (srv->cap.flags & SMTP_CAP_AUTH_PLAIN)) { auth_mech = "PLAIN"; } else if (gsasl_client_support_p(ctx, "LOGIN") && (srv->cap.flags & SMTP_CAP_AUTH_LOGIN)) { auth_mech = "LOGIN"; } else if (gsasl_client_support_p(ctx, "NTLM") && (srv->cap.flags & SMTP_CAP_AUTH_NTLM)) { auth_mech = "NTLM"; } } #endif /* HAVE_TLS */ } if (strcmp(auth_mech, "") == 0) { gsasl_done(ctx); #ifdef HAVE_TLS if (!tls_is_active(&srv->tls)) { #endif /* HAVE_TLS */ *errstr = xasprintf(_("cannot use a secure authentication method")); #ifdef HAVE_TLS } else { *errstr = xasprintf( _("cannot find a usable authentication method")); } #endif /* not HAVE_TLS */ return SMTP_EUNAVAIL; } /* Check availability of required authentication data */ if (strcmp(auth_mech, "EXTERNAL") != 0) { /* GSSAPI, DIGEST-MD5, CRAM-MD5, PLAIN, LOGIN, NTLM all need a user * name */ if (!user) { gsasl_done(ctx); *errstr = xasprintf(_("authentication method %s needs a user name"), auth_mech); return SMTP_EUNAVAIL; } /* DIGEST-MD5, CRAM-MD5, PLAIN, LOGIN, NTLM all need a password */ if (strcmp(auth_mech, "GSSAPI") != 0 && !password) { if (!password_callback || !(callback_password = password_callback(hostname, user))) { gsasl_done(ctx); *errstr = xasprintf( _("authentication method %s needs a password"), auth_mech); return SMTP_EUNAVAIL; } password = callback_password; } } if ((error_code = gsasl_client_start(ctx, auth_mech, &sctx)) != GSASL_OK) { gsasl_done(ctx); *errstr = xasprintf(_("GNU SASL: %s"), gsasl_strerror(error_code)); return SMTP_ELIBFAILED; } /* Set the authentication properties */ if (user) { gsasl_property_set(sctx, GSASL_AUTHID, user); /* GSASL_AUTHZID must not be set for DIGEST-MD5, because otherwise * authentication may fail (tested with postfix). Set it only for * EXTERNAL. */ if (strcmp(auth_mech, "EXTERNAL") == 0) { gsasl_property_set(sctx, GSASL_AUTHZID, user); } } if (password) { gsasl_property_set(sctx, GSASL_PASSWORD, password); } free(callback_password); /* For DIGEST-MD5 and GSSAPI */ gsasl_property_set(sctx, GSASL_SERVICE, "smtp"); if (hostname) { gsasl_property_set(sctx, GSASL_HOSTNAME, hostname); } /* For NTLM. Postfix does not care, MS IIS needs an arbitrary non-empty * string. */ if (ntlmdomain) { gsasl_property_set(sctx, GSASL_REALM, ntlmdomain); } /* Bigg authentication loop */ input = NULL; do { error_code = gsasl_step64(sctx, input, &outbuf); if (error_code != GSASL_OK && error_code != GSASL_NEEDS_MORE) { gsasl_finish(sctx); gsasl_done(ctx); *errstr = xasprintf(_("GNU SASL: %s"), gsasl_strerror(error_code)); return SMTP_ELIBFAILED; } if (!input) { if (strcmp(auth_mech, "PLAIN") == 0 && outbuf[0]) { /* AUTH PLAIN needs special treatment because it needs to send * the authentication data together with the AUTH PLAIN command. * At least smtp.web.de requires this, and I happen to use this * server :) */ auth_plain_special = 1; if ((e = smtp_send_cmd(srv, errstr, "AUTH PLAIN %s", outbuf)) != SMTP_EOK) { gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); return e; } } else { auth_plain_special = 0; if ((e = smtp_send_cmd(srv, errstr, "AUTH %s", auth_mech)) != SMTP_EOK) { gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); return e; } } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); return e; } if (smtp_msg_status(msg) != 334 && smtp_msg_status(msg) != 235) { *error_msg = msg; gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); *errstr = xasprintf(_("authentication failed (method %s)"), auth_mech); return SMTP_EAUTHFAIL; } /* msg->next->data cannot be longer than SMTP_BUFSIZE-1 */ strcpy(inbuf, msg->next->data); list_xfree(msg, free); input = inbuf + 4; if (auth_plain_special) { free(outbuf); continue; } } /* For all mechanisms except GSSAPI, testing for (outbuf[0]) works. * GSSAPI needs an additional step with empty output. */ if (outbuf[0] || (GSASL_NEEDS_MORE && (strcmp(auth_mech, "GSSAPI") == 0))) { if ((e = smtp_send_cmd(srv, errstr, "%s", outbuf)) != SMTP_EOK) { gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); return e; } if (smtp_msg_status(msg) != 334 && smtp_msg_status(msg) != 235) { *error_msg = msg; gsasl_finish(sctx); gsasl_done(ctx); free(outbuf); *errstr = xasprintf(_("authentication failed (method %s)"), auth_mech); return SMTP_EAUTHFAIL; } /* msg->next->data cannot be longer than SMTP_BUFSIZE-1 */ strcpy(inbuf, msg->next->data); list_xfree(msg, free); input = inbuf + 4; } free(outbuf); } while (error_code == GSASL_NEEDS_MORE); if (error_code != GSASL_OK) { gsasl_finish(sctx); gsasl_done(ctx); *errstr = xasprintf(_("authentication failed: %s (method %s)"), gsasl_strerror(error_code), auth_mech); return SMTP_EAUTHFAIL; } gsasl_finish(sctx); gsasl_done(ctx); /* For DIGEST-MD5, we need to send an empty answer to the last 334 * response before we get 235. */ if (strncmp(inbuf, "235 ", 4) != 0) { if ((e = smtp_send_cmd(srv, errstr, "")) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 235) { *error_msg = msg; *errstr = xasprintf(_("authentication failed (method %s)"), auth_mech); return SMTP_EAUTHFAIL; } list_xfree(msg, free); } return SMTP_EOK; #else /* not HAVE_LIBGSASL */ char *callback_password = NULL; int e; *error_msg = NULL; if (strcmp(auth_mech, "") != 0 && !smtp_server_supports_authmech(srv, auth_mech)) { *errstr = xasprintf( _("the server does not support authentication method %s"), auth_mech); return SMTP_EUNAVAIL; } if (strcmp(auth_mech, "") == 0) { /* Choose "best" authentication mechanism. */ if (srv->cap.flags & SMTP_CAP_AUTH_CRAM_MD5) { auth_mech = "CRAM-MD5"; } #ifdef HAVE_TLS else if (tls_is_active(&srv->tls)) { if (srv->cap.flags & SMTP_CAP_AUTH_PLAIN) { auth_mech = "PLAIN"; } else if (srv->cap.flags & SMTP_CAP_AUTH_LOGIN) { auth_mech = "LOGIN"; } } #endif /* HAVE_TLS */ } if (strcmp(auth_mech, "") == 0) { #ifdef HAVE_TLS if (!tls_is_active(&srv->tls)) { #endif /* HAVE_TLS */ *errstr = xasprintf(_("cannot use a secure authentication method")); #ifdef HAVE_TLS } else { *errstr = xasprintf( _("cannot find a usable authentication method")); } #endif /* not HAVE_TLS */ return SMTP_EUNAVAIL; } if (strcmp(auth_mech, "EXTERNAL") != 0) { /* CRAMD-MD5, PLAIN, LOGIN all need a user name and a password */ if (!user) { *errstr = xasprintf(_("authentication method %s needs a user name"), auth_mech); return SMTP_EUNAVAIL; } if (!password) { if (!password_callback || !(callback_password = password_callback(hostname, user))) { *errstr = xasprintf( _("authentication method %s needs a password"), auth_mech); return SMTP_EUNAVAIL; } password = callback_password; } } if (strcmp(auth_mech, "CRAM-MD5") == 0) { e = smtp_auth_cram_md5(srv, user, password, error_msg, errstr); } else if (strcmp(auth_mech, "PLAIN") == 0) { e = smtp_auth_plain(srv, user, password, error_msg, errstr); } else if (strcmp(auth_mech, "EXTERNAL") == 0) { e = smtp_auth_external(srv, user ? user : "", error_msg, errstr); } else if (strcmp(auth_mech, "LOGIN") == 0) { e = smtp_auth_login(srv, user, password, error_msg, errstr); } else { *errstr = xasprintf(_("authentication method %s not supported"), auth_mech); e = SMTP_ELIBFAILED; } free(callback_password); return e; #endif /* not HAVE_LIBGSASL */ } /* * smtp_send_envelope() * * see smtp.h */ int smtp_send_envelope(smtp_server_t *srv, const char *envelope_from, list_t *recipients, const char *dsn_notify, const char *dsn_return, list_t **error_msg, char **errstr) { int e; list_t *msg; int mailfrom_cmd_was_sent = 0; int mailfrom_reply_was_rcvd = 0; list_t *rcpt_send = recipients; list_t *rcpt_recv = recipients; int data_cmd_was_sent = 0; int data_reply_was_rcvd = 0; int pipeline_limit = 1; int piped_commands = 0; *error_msg = NULL; if (srv->cap.flags & SMTP_CAP_PIPELINING) { pipeline_limit = SMTP_PIPELINE_LIMIT; } /* Send the MAIL FROM, RCPT TO and DATA commands using pipelining. The * number of pipelined commands will never be greater than pipeline_limit * to avoid problems with the TCP window size (exceeding it can lead to * deadlocks). pipeline_limit == 1 disables pipelining. */ while (!data_reply_was_rcvd) { while (!data_cmd_was_sent && piped_commands < pipeline_limit) { /* send */ if (!mailfrom_cmd_was_sent) { if (dsn_return) { e = smtp_send_cmd(srv, errstr, "MAIL FROM:<%s> RET=%s", strcasecmp(envelope_from, "MAILER-DAEMON") == 0 ? "" : envelope_from, dsn_return); } else { e = smtp_send_cmd(srv, errstr, "MAIL FROM:<%s>", strcasecmp(envelope_from, "MAILER-DAEMON") == 0 ? "" : envelope_from); } if (e != SMTP_EOK) { return e; } mailfrom_cmd_was_sent = 1; } else if (!list_is_empty(rcpt_send)) { rcpt_send = rcpt_send->next; if (dsn_notify) { e = smtp_send_cmd(srv, errstr, "RCPT TO:<%s> NOTIFY=%s", (char *)(rcpt_send->data), dsn_notify); } else { e = smtp_send_cmd(srv, errstr, "RCPT TO:<%s>", (char *)(rcpt_send->data)); } if (e != SMTP_EOK) { return e; } } else { if ((e = smtp_send_cmd(srv, errstr, "DATA")) != SMTP_EOK) { return e; } data_cmd_was_sent = 1; } piped_commands++; } while (piped_commands > 0) { /* receive */ if (!mailfrom_reply_was_rcvd) { if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 250) { *error_msg = msg; *errstr = xasprintf(_("envelope from address %s not " "accepted by the server"), envelope_from); return SMTP_EINVAL; } list_xfree(msg, free); mailfrom_reply_was_rcvd = 1; } else if (!list_is_empty(rcpt_recv)) { rcpt_recv = rcpt_recv->next; if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 250) { *error_msg = msg; *errstr = xasprintf(_("recipient address %s not " "accepted by the server"), (char *)(rcpt_recv->data)); return SMTP_EINVAL; } list_xfree(msg, free); } else { if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 354) { *error_msg = msg; *errstr = xasprintf( _("the server does not accept mail data")); return SMTP_EUNAVAIL; } list_xfree(msg, free); data_reply_was_rcvd = 1; } piped_commands--; } } return SMTP_EOK; } /* * smtp_send_mail() * * see smtp.h */ int smtp_send_mail(smtp_server_t *srv, FILE *mailf, int keep_bcc, long *mailsize, char **errstr) { char bigbuffer[MAIL_BUFSIZE + 3]; /* buffer + leading dot + ending CRLF */ char *buffer; size_t len; char *send_buf; size_t send_len; int in_header; int in_bcc; int line_starts; int line_continues; int e; bigbuffer[0] = '.'; buffer = bigbuffer + 1; in_header = 1; in_bcc = 0; line_continues = 0; e = SMTP_EOK; for (;;) { if (stream_gets(mailf, buffer, MAIL_BUFSIZE, &len, errstr) != STREAM_EOK) { return SMTP_EIO; } if (len == 0) { break; } line_starts = !line_continues; if (len > 0 && buffer[len - 1] == '\n') { /* first case: we have a line end */ buffer[--len] = '\0'; if (len > 0 && buffer[len - 1] == '\r') { buffer[--len] = '\0'; } line_continues = 0; } else if (len == MAIL_BUFSIZE - 1) { /* second case: the line continues */ if (buffer[len - 1] == '\r') { /* We have CRLF that is divided by the buffer boundary. Since CR * may not appear alone in a mail according to RFC2822, we * know that the next buffer will be "\n\0", so it's safe to * just delete the CR. */ buffer[--len] = '\0'; } line_continues = 1; } else { /* third case: this is the last line, and it lacks a newline * character */ line_continues = 0; } if (!keep_bcc) { if (line_starts && in_header && buffer[0] == '\0') { in_header = 0; } if (in_header) { if (line_starts) { if (!in_bcc) { if (strncasecmp(buffer, "Bcc:", 4) == 0) { in_bcc = 1; /* remove Bcc header by ignoring this line */ continue; } } else { /* continued header lines begin with "horizontal * whitespace" (RFC 2822, section 2.2.3) */ if (buffer[0] == '\t' || buffer[0] == ' ') { /* remove Bcc header by ignoring this line */ continue; } else { in_bcc = 0; } } } else { if (in_bcc) { /* remove Bcc header by ignoring this line */ continue; } } } } send_buf = buffer; send_len = len; if (line_starts && buffer[0] == '.') { /* Quote the leading dot with another dot */ send_buf = bigbuffer; send_len = len + 1; } if (!line_continues) { /* Append CRLF */ buffer[len] = '\r'; buffer[len + 1] = '\n'; buffer[len + 2] = '\0'; send_len += 2; } /* Update mailsize. Do not count a quote dot. Count CRLF as one * character. */ *mailsize += (long)len + (line_continues ? 0 : 1); if ((e = smtp_put(srv, send_buf, send_len, errstr)) != SMTP_EOK) { return e; } } return SMTP_EOK; } /* * smtp_end_mail() * * see smtp.h */ int smtp_end_mail(smtp_server_t *srv, list_t **error_msg, char **errstr) { int e; list_t *msg; *error_msg = NULL; if ((e = smtp_send_cmd(srv, errstr, ".")) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } if (smtp_msg_status(msg) != 250) { *error_msg = msg; *errstr = xasprintf(_("the server did not accept the mail")); return SMTP_EUNAVAIL; } list_xfree(msg, free); return SMTP_EOK; } /* * smtp_end_mail_lmtp() * * see smtp.h * */ void _smtp_free_list_of_lists(void *l) { list_xfree((list_t *)l, free); } int smtp_end_mail_lmtp(smtp_server_t *srv, list_t *recipients, list_t **errstrs, list_t **error_msgs, char **errstr) { int e; list_t *msg; list_t *lp_recipients; list_t *lp_errstrs; list_t *lp_error_msgs; int all_recipients_accepted; char *tmp; if ((e = smtp_send_cmd(srv, errstr, ".")) != SMTP_EOK) { *errstrs = NULL; *error_msgs = NULL; return e; } *errstrs = list_new(); *error_msgs = list_new(); lp_errstrs = *errstrs; lp_error_msgs = *error_msgs; lp_recipients = recipients; all_recipients_accepted = 1; while (!list_is_empty(lp_recipients)) { lp_recipients = lp_recipients->next; if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { list_xfree(*errstrs, free); *errstrs = NULL; list_xfree(*error_msgs, _smtp_free_list_of_lists); *error_msgs = NULL; return e; } if (smtp_msg_status(msg) != 250) { all_recipients_accepted = 0; tmp = xasprintf(_("the server refuses to send the mail to %s"), (char *)(lp_recipients->data)); list_insert(lp_errstrs, tmp); list_insert(lp_error_msgs, msg); } else { list_xfree(msg, free); list_insert(lp_errstrs, NULL); list_insert(lp_error_msgs, NULL); } lp_errstrs = lp_errstrs->next; lp_error_msgs = lp_error_msgs->next; } if (all_recipients_accepted) { /* we can use list_free() here since all list entries are just NULL */ list_free(*errstrs); *errstrs = NULL; list_free(*error_msgs); *error_msgs = NULL; return SMTP_EOK; } else { return SMTP_EUNAVAIL; } } /* * smtp_etrn() * * see smtp.h */ int smtp_etrn(smtp_server_t *srv, const char *etrn_argument, list_t **error_msg, char **errstr) { int e; list_t *msg; *error_msg = NULL; if ((e = smtp_send_cmd(srv, errstr, "ETRN %s", etrn_argument)) != SMTP_EOK) { return e; } if ((e = smtp_get_msg(srv, &msg, errstr)) != SMTP_EOK) { return e; } switch (smtp_msg_status(msg)) { case 250: /* OK, queuing for node started */ case 251: /* OK, no messages waiting for node */ case 252: /* OK, pending messages for node started */ case 253: /* OK, pending messages for node started */ break; case 458: /* Unable to queue messages for node */ case 459: /* Node not allowed: */ *error_msg = msg; *errstr = xasprintf( _("the server is unable to fulfill the request")); return SMTP_EUNAVAIL; break; case 500: /* Syntax Error */ case 501: /* 501 Syntax Error in Parameters */ *error_msg = msg; *errstr = xasprintf( _("invalid argument for Remote Message Queue Starting")); return SMTP_EINVAL; break; default: *error_msg = msg; *errstr = xasprintf(_("command %s failed"), "ETRN"); return SMTP_EPROTO; break; } list_xfree(msg, free); return SMTP_EOK; } /* * smtp_quit() * * see smtp.h */ int smtp_quit(smtp_server_t *srv, char **errstr) { int e; list_t *msg; if ((e = smtp_send_cmd(srv, errstr, "QUIT")) == SMTP_EOK) { e = smtp_get_msg(srv, &msg, errstr); } if (msg) { list_xfree(msg, free); } return e; } /* * smtp_close() * * see smtp.h */ void smtp_close(smtp_server_t *srv) { #ifdef HAVE_TLS if (tls_is_active(&srv->tls)) { tls_close(&srv->tls); } #endif /* HAVE_TLS */ net_close_socket(srv->fd); }