git: 74c7755fe972 - stable/13 - dma: import snapshot 2021-07-10
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 30 Jan 2022 17:55:08 UTC
The branch stable/13 has been updated by emaste: URL: https://cgit.FreeBSD.org/src/commit/?id=74c7755fe9726c7ebdfc9b04f9caee5fbf93246b commit 74c7755fe9726c7ebdfc9b04f9caee5fbf93246b Author: Baptiste Daroussin <bapt@FreeBSD.org> AuthorDate: 2021-09-22 09:09:27 +0000 Commit: Ed Maste <emaste@FreeBSD.org> CommitDate: 2022-01-30 17:54:56 +0000 dma: import snapshot 2021-07-10 (cherry picked from commit fbe95b885f3431b1d8003545b32e8ffa88f2d16b) --- contrib/dma/Makefile | 2 +- contrib/dma/VERSION | 2 +- contrib/dma/conf.c | 20 +++- contrib/dma/crypto.c | 43 ++++++- contrib/dma/dfcompat.c | 6 +- contrib/dma/dma.8 | 17 ++- contrib/dma/dma.c | 38 +++--- contrib/dma/dma.conf | 6 +- contrib/dma/dma.h | 17 ++- contrib/dma/dns.c | 5 - contrib/dma/get-version.sh | 0 contrib/dma/local.c | 5 +- contrib/dma/mail.c | 40 +++++-- contrib/dma/net.c | 284 ++++++++++++++++++++++++++++++++------------- contrib/dma/spool.c | 2 + contrib/dma/util.c | 20 ++++ 16 files changed, 378 insertions(+), 129 deletions(-) diff --git a/contrib/dma/Makefile b/contrib/dma/Makefile index aed2ef7246cf..8cae5b28f98b 100644 --- a/contrib/dma/Makefile +++ b/contrib/dma/Makefile @@ -17,7 +17,7 @@ CC?= gcc CFLAGS?= -O -pipe LDADD?= -lssl -lcrypto -lresolv -CFLAGS+= -Wall -DDMA_VERSION='"${version}"' -DLIBEXEC_PATH='"${LIBEXEC}"' -DCONF_PATH='"${CONFDIR}"' +CFLAGS+= -Wall -Wno-format-truncation -DDMA_VERSION='"${version}"' -DLIBEXEC_PATH='"${LIBEXEC}"' -DCONF_PATH='"${CONFDIR}"' INSTALL?= install -p CHGRP?= chgrp diff --git a/contrib/dma/VERSION b/contrib/dma/VERSION index 5416288bc5ef..3c58828758cd 100644 --- a/contrib/dma/VERSION +++ b/contrib/dma/VERSION @@ -1 +1 @@ -v0.11 +v0.13 diff --git a/contrib/dma/conf.c b/contrib/dma/conf.c index b8a6a2e2cbd7..13cfac7a6de4 100644 --- a/contrib/dma/conf.c +++ b/contrib/dma/conf.c @@ -218,10 +218,26 @@ parse_conf(const char *config_path) config.masquerade_user = user; } else if (strcmp(word, "STARTTLS") == 0 && data == NULL) config.features |= STARTTLS; - else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) + else if (strcmp(word, "FINGERPRINT") == 0) { + if (strlen(data) != SHA256_DIGEST_LENGTH * 2) { + errlogx(EX_CONFIG, "invalid sha256 fingerprint length"); + } + unsigned char *fingerprint = malloc(SHA256_DIGEST_LENGTH); + if (fingerprint == NULL) { + errlogx(EX_CONFIG, "fingerprint allocation failed"); + } + unsigned int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + if(sscanf(data + 2 * i, "%02hhx", &fingerprint[i]) != 1) { + errlogx(EX_CONFIG, "failed to read fingerprint"); + } + } + free(data); + config.fingerprint = fingerprint; + } else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) config.features |= TLS_OPP; else if (strcmp(word, "SECURETRANSFER") == 0 && data == NULL) - config.features |= SECURETRANS; + config.features |= SECURETRANSFER; else if (strcmp(word, "DEFER") == 0 && data == NULL) config.features |= DEFER; else if (strcmp(word, "INSECURE") == 0 && data == NULL) diff --git a/contrib/dma/crypto.c b/contrib/dma/crypto.c index 37b255c18310..368238b9d632 100644 --- a/contrib/dma/crypto.c +++ b/contrib/dma/crypto.c @@ -40,6 +40,7 @@ #include <openssl/pem.h> #include <openssl/rand.h> +#include <strings.h> #include <string.h> #include <syslog.h> @@ -77,8 +78,31 @@ init_cert_file(SSL_CTX *ctx, const char *path) return (0); } +static int +verify_server_fingerprint(const X509 *cert) +{ + unsigned char fingerprint[EVP_MAX_MD_SIZE] = {0}; + unsigned int fingerprint_len = 0; + if(!X509_digest(cert, EVP_sha256(), fingerprint, &fingerprint_len)) { + syslog(LOG_WARNING, "failed to load fingerprint of server certicate: %s", + ssl_errstr()); + return (1); + } + if(fingerprint_len != SHA256_DIGEST_LENGTH) { + syslog(LOG_WARNING, "sha256 fingerprint has unexpected length of %d bytes", + fingerprint_len); + return (1); + } + if(memcmp(fingerprint, config.fingerprint, SHA256_DIGEST_LENGTH) != 0) { + syslog(LOG_WARNING, "fingerprints do not match"); + return (1); + } + syslog(LOG_DEBUG, "successfully verified server certificate fingerprint"); + return (0); +} + int -smtp_init_crypto(int fd, int feature) +smtp_init_crypto(int fd, int feature, struct smtp_features* features) { SSL_CTX *ctx = NULL; #if (OPENSSL_VERSION_NUMBER >= 0x00909000L) @@ -119,13 +143,12 @@ smtp_init_crypto(int fd, int feature) /* * If the user wants STARTTLS, we have to send EHLO here */ - if (((feature & SECURETRANS) != 0) && + if (((feature & SECURETRANSFER) != 0) && (feature & STARTTLS) != 0) { /* TLS init phase, disable SSL_write */ config.features |= NOSSL; - send_remote_command(fd, "EHLO %s", hostname()); - if (read_remote(fd, 0, NULL) == 2) { + if (perform_server_greeting(fd, features) == 0) { send_remote_command(fd, "STARTTLS"); if (read_remote(fd, 0, NULL) != 2) { if ((feature & TLS_OPP) == 0) { @@ -136,7 +159,12 @@ smtp_init_crypto(int fd, int feature) return (0); } } + } else { + syslog(LOG_ERR, "remote delivery deferred: could not perform server greeting: %s", + neterr); + return (1); } + /* End of TLS init phase, enable SSL_write/read */ config.features &= ~NOSSL; } @@ -161,7 +189,7 @@ smtp_init_crypto(int fd, int feature) /* Open SSL connection */ error = SSL_connect(config.ssl); - if (error < 0) { + if (error != 1) { syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s", ssl_errstr()); return (1); @@ -172,6 +200,11 @@ smtp_init_crypto(int fd, int feature) if (cert == NULL) { syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s", ssl_errstr()); + return (1); + } + if(config.fingerprint != NULL && verify_server_fingerprint(cert)) { + X509_free(cert); + return (1); } X509_free(cert); diff --git a/contrib/dma/dfcompat.c b/contrib/dma/dfcompat.c index 014fa88b8d49..d4ecc1d74ae9 100644 --- a/contrib/dma/dfcompat.c +++ b/contrib/dma/dfcompat.c @@ -16,7 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ - * $FreeBSD$ + * $FreeBSD: src/lib/libc/string/strlcpy.c,v 1.10 2008/10/19 10:11:35 delphij Exp $ * $DragonFly: src/lib/libc/string/strlcpy.c,v 1.4 2005/09/18 16:32:34 asmodai Exp $ */ @@ -85,7 +85,7 @@ strlcpy(char *dst, const char *src, size_t siz) * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD$ + * $FreeBSD: src/lib/libc/stdlib/reallocf.c,v 1.3 1999/08/28 00:01:37 peter Exp $ * $DragonFly: src/lib/libc/stdlib/reallocf.c,v 1.2 2003/06/17 04:26:46 dillon Exp $ */ #include <stdlib.h> @@ -96,7 +96,7 @@ reallocf(void *ptr, size_t size) void *nptr; nptr = realloc(ptr, size); - if (!nptr && ptr) + if (!nptr && ptr && size != 0) free(ptr); return (nptr); } diff --git a/contrib/dma/dma.8 b/contrib/dma/dma.8 index cadf899e50fc..e0f5e79ff47d 100644 --- a/contrib/dma/dma.8 +++ b/contrib/dma/dma.8 @@ -89,8 +89,11 @@ Useful for debugging. .It Fl f Ar sender Set sender address (envelope-from) to .Ar sender . -This overrides the value of the environment variable -.Ev EMAIL . +This overrides the value of the +.Ev EMAIL +environment variable, but is overridden by the +.Sq MASQUERADE +config file setting. .It Fl i Ignore dots alone on lines by themselves in incoming messages. This should be set if you are reading data from a file. @@ -223,6 +226,11 @@ Uncomment if you want TLS/SSL secured transfer. Uncomment if you want to use STARTTLS. Only useful together with .Sq SECURETRANSFER . +.It Ic FINGERPRINT Xo +(string, default=empty) +.Xc +Pin the server certificate by specifying its SHA256 fingerprint. +Only makes sense if you use a smarthost. .It Ic OPPORTUNISTIC_TLS Xo (boolean, default=commented) .Xc @@ -283,7 +291,7 @@ as the hostname. Masquerade the envelope-from addresses with this address/hostname. Use this setting if mails are not accepted by destination mail servers because your sender domain is invalid. -This setting is overridden by the +This setting overrides the .Fl f flag and the .Ev EMAIL @@ -309,6 +317,7 @@ will send all mails as .Ql Va username @percolator . .Sm on .It Ic NULLCLIENT Xo +(boolean, default=commented) .Xc Bypass aliases and local delivery, and instead forward all mails to the defined @@ -329,6 +338,8 @@ Used to set the sender address (envelope-from). Use a plain address, in the form of .Li user@example.com . This value will be overridden when the +.Sq MASQUERADE +config file setting or the .Fl f flag is used. .El diff --git a/contrib/dma/dma.c b/contrib/dma/dma.c index b553c0fa0eef..72115ae2b55e 100644 --- a/contrib/dma/dma.c +++ b/contrib/dma/dma.c @@ -85,6 +85,7 @@ struct config config = { .mailname = NULL, .masquerade_host = NULL, .masquerade_user = NULL, + .fingerprint = NULL, }; @@ -100,15 +101,14 @@ set_from(struct queue *queue, const char *osender) const char *addr; char *sender; - if (osender) { + if (config.masquerade_user) { + addr = config.masquerade_user; + } else if (osender) { addr = osender; } else if (getenv("EMAIL") != NULL) { addr = getenv("EMAIL"); } else { - if (config.masquerade_user) - addr = config.masquerade_user; - else - addr = username; + addr = username; } if (!strchr(addr, '@')) { @@ -422,9 +422,10 @@ main(int argc, char **argv) { struct sigaction act; char *sender = NULL; + char *own_name = NULL; struct queue queue; int i, ch; - int nodot = 0, showq = 0, queue_only = 0; + int nodot = 0, showq = 0, queue_only = 0, newaliases = 0; int recp_from_header = 0; set_username(); @@ -458,19 +459,17 @@ main(int argc, char **argv) bzero(&queue, sizeof(queue)); LIST_INIT(&queue.queue); - if (strcmp(basename(argv[0]), "mailq") == 0) { + own_name = basename(argv[0]); + + if (strcmp(own_name, "mailq") == 0) { argv++; argc--; showq = 1; if (argc != 0) errx(EX_USAGE, "invalid arguments"); goto skipopts; - } else if (strcmp(argv[0], "newaliases") == 0) { - logident_base = "dma"; - setlogident("%s", logident_base); - - if (read_aliases() != 0) - errx(EX_SOFTWARE, "could not parse aliases file `%s'", config.aliases); - exit(EX_OK); + } else if (strcmp(own_name, "newaliases") == 0) { + newaliases = 1; + goto skipopts; } opterr = 0; @@ -481,7 +480,7 @@ main(int argc, char **argv) if (optarg[0] == 'c' || optarg[0] == 'm') { break; } - /* else FALLTRHOUGH */ + /* Else FALLTHROUGH */ case 'b': /* -bX is being ignored, except for -bp */ if (optarg[0] == 'p') { @@ -491,7 +490,7 @@ main(int argc, char **argv) queue_only = 1; break; } - /* else FALLTRHOUGH */ + /* Else FALLTHROUGH */ case 'D': daemonize = 0; break; @@ -511,7 +510,7 @@ main(int argc, char **argv) /* -oX is being ignored, except for -oi */ if (optarg[0] != 'i') break; - /* else FALLTRHOUGH */ + /* Else FALLTHROUGH */ case 'O': break; case 'i': @@ -545,7 +544,7 @@ main(int argc, char **argv) doqueue = 1; break; } - /* FALLTHROUGH */ + /* Else FALLTHROUGH */ default: fprintf(stderr, "invalid argument: `-%c'\n", optopt); @@ -596,6 +595,9 @@ skipopts: if (read_aliases() != 0) errlog(EX_SOFTWARE, "could not parse aliases file `%s'", config.aliases); + if (newaliases) + return(0); + if ((sender = set_from(&queue, sender)) == NULL) errlog(EX_SOFTWARE, "set_from()"); diff --git a/contrib/dma/dma.conf b/contrib/dma/dma.conf index 1cc2bf5bc843..fa95fc1a0c22 100644 --- a/contrib/dma/dma.conf +++ b/contrib/dma/dma.conf @@ -18,13 +18,17 @@ # SMTP authentication #AUTHPATH /etc/dma/auth.conf -# Uncomment if yout want TLS/SSL support +# Uncomment if you want TLS/SSL support #SECURETRANSFER # Uncomment if you want STARTTLS support (only used in combination with # SECURETRANSFER) #STARTTLS +# Pin the server certificate by specifying its SHA256 fingerprint. +# Only makes sense if you use a smarthost. +#FINGERPRINT 1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF + # Uncomment if you have specified STARTTLS above and it should be allowed # to fail ("opportunistic TLS", use an encrypted connection when available # but allow an unencrypted one to servers that do not support it) diff --git a/contrib/dma/dma.h b/contrib/dma/dma.h index 593417617d3d..9e7f6cd2c431 100644 --- a/contrib/dma/dma.h +++ b/contrib/dma/dma.h @@ -51,6 +51,7 @@ #define BUF_SIZE 2048 #define ERRMSG_SIZE 1024 #define USERNAME_SIZE 50 +#define EHLO_RESPONSE_SIZE BUF_SIZE #define MIN_RETRY 300 /* 5 minutes */ #define MAX_RETRY (3*60*60) /* retry at least every 3 hours */ #define MAX_TIMEOUT (5*24*60*60) /* give up after 5 days */ @@ -62,7 +63,7 @@ #define CON_TIMEOUT (5*60) /* Connection timeout per RFC5321 */ #define STARTTLS 0x002 /* StartTLS support */ -#define SECURETRANS 0x004 /* SSL/TLS in general */ +#define SECURETRANSFER 0x004 /* SSL/TLS in general */ #define NOSSL 0x008 /* Do not use SSL */ #define DEFER 0x010 /* Defer mails */ #define INSECURE 0x020 /* Allow plain login w/o encryption */ @@ -137,6 +138,7 @@ struct config { const char *mailname; const char *masquerade_host; const char *masquerade_user; + const unsigned char *fingerprint; /* XXX does not belong into config */ SSL *ssl; @@ -160,6 +162,15 @@ struct mx_hostentry { struct sockaddr_storage sa; }; +struct smtp_auth_mechanisms { + int cram_md5; + int login; +}; + +struct smtp_features { + struct smtp_auth_mechanisms auth; + int starttls; +}; /* global variables */ extern struct aliases aliases; @@ -187,7 +198,7 @@ void parse_authfile(const char *); /* crypto.c */ void hmac_md5(unsigned char *, int, unsigned char *, int, unsigned char *); int smtp_auth_md5(int, char *, char *); -int smtp_init_crypto(int, int); +int smtp_init_crypto(int, int, struct smtp_features*); /* dns.c */ int dns_get_mx_list(const char *, int, struct mx_hostentry **, int); @@ -196,6 +207,7 @@ int dns_get_mx_list(const char *, int, struct mx_hostentry **, int); char *ssl_errstr(void); int read_remote(int, int, char *); ssize_t send_remote_command(int, const char*, ...) __attribute__((__nonnull__(2), __format__ (__printf__, 2, 3))); +int perform_server_greeting(int, struct smtp_features*); int deliver_remote(struct qitem *); /* base64.c */ @@ -227,6 +239,7 @@ int readmail(struct queue *, int, int); /* util.c */ const char *hostname(void); +const char *systemhostname(void); void setlogident(const char *, ...) __attribute__((__format__ (__printf__, 1, 2))); void errlog(int, const char *, ...) __attribute__((__format__ (__printf__, 2, 3))); void errlogx(int, const char *, ...) __attribute__((__format__ (__printf__, 2, 3))); diff --git a/contrib/dma/dns.c b/contrib/dma/dns.c index bd28c4db724c..449e6b463caa 100644 --- a/contrib/dma/dns.c +++ b/contrib/dma/dns.c @@ -271,11 +271,6 @@ err: *he = hosts; return (err); - - free(ans); - if (hosts != NULL) - free(hosts); - return (err); } #if defined(TESTING) diff --git a/contrib/dma/get-version.sh b/contrib/dma/get-version.sh old mode 100755 new mode 100644 diff --git a/contrib/dma/local.c b/contrib/dma/local.c index b6c4180fc5dc..2c3483ea0380 100644 --- a/contrib/dma/local.c +++ b/contrib/dma/local.c @@ -44,6 +44,7 @@ #include <signal.h> #include <stdint.h> #include <stdio.h> +#include <strings.h> #include <string.h> #include <syslog.h> #include <unistd.h> @@ -81,7 +82,7 @@ create_mbox(const char *name) for (i = 3; i <= maxfd; ++i) close(i); - execl(LIBEXEC_PATH "/dma-mbox-create", "dma-mbox-create", name, NULL); + execl(LIBEXEC_PATH "/dma-mbox-create", "dma-mbox-create", name, (char *)NULL); syslog(LOG_ERR, "cannot execute "LIBEXEC_PATH"/dma-mbox-create: %m"); exit(EX_SOFTWARE); @@ -219,7 +220,7 @@ retry: /* * mboxro processing: * - escape lines that start with "From " with a > sign. - * - be reversable by escaping lines that contain an arbitrary + * - be reversible by escaping lines that contain an arbitrary * number of > signs, followed by "From ", i.e. />*From / in regexp. * - strict mbox processing only requires escaping after empty lines, * yet most MUAs seem to relax this requirement and will treat any diff --git a/contrib/dma/mail.c b/contrib/dma/mail.c index ad928272e1a1..48c8ee6d4dd2 100644 --- a/contrib/dma/mail.c +++ b/contrib/dma/mail.c @@ -36,6 +36,7 @@ #include <errno.h> #include <inttypes.h> #include <signal.h> +#include <strings.h> #include <string.h> #include <syslog.h> #include <unistd.h> @@ -73,7 +74,7 @@ bounce(struct qitem *it, const char *reason) error = fprintf(bounceq.mailf, "Received: from MAILER-DAEMON\n" "\tid %s\n" - "\tby %s (%s);\n" + "\tby %s (%s on %s);\n" "\t%s\n" "X-Original-To: <%s>\n" "From: MAILER-DAEMON <>\n" @@ -91,7 +92,7 @@ bounce(struct qitem *it, const char *reason) "%s\n" "\n", bounceq.id, - hostname(), VERSION, + hostname(), VERSION, systemhostname(), rfc822date(), it->addr, it->sender, @@ -191,8 +192,7 @@ again: switch (*s) { case ' ': case '\t': - s++; - /* continue */ + ps->state = MAIN; break; default: @@ -201,6 +201,7 @@ again: goto newaddr; return (0); } + break; case QUIT: return (0); @@ -383,6 +384,8 @@ readmail(struct queue *queue, int nodot, int recp_from_header) int had_from = 0; int had_messagid = 0; int had_date = 0; + int had_first_line = 0; + int had_last_line = 0; int nocopy = 0; int ret = -1; @@ -392,12 +395,12 @@ readmail(struct queue *queue, int nodot, int recp_from_header) "Received: from %s (uid %d)\n" "\t(envelope-from %s)\n" "\tid %s\n" - "\tby %s (%s);\n" + "\tby %s (%s on %s);\n" "\t%s\n", username, useruid, queue->sender, queue->id, - hostname(), VERSION, + hostname(), VERSION, systemhostname(), rfc822date()); if ((ssize_t)error < 0) return (-1); @@ -406,7 +409,30 @@ readmail(struct queue *queue, int nodot, int recp_from_header) newline[0] = '\0'; if ((linelen = getline(&line, &linecap, stdin)) <= 0) break; - + if (had_last_line) + errlogx(EX_DATAERR, "bad mail input format:" + " from %s (uid %d) (envelope-from %s)", + username, useruid, queue->sender); + linelen = strlen(line); + if (linelen == 0 || line[linelen - 1] != '\n') { + /* + * This line did not end with a newline character. + * If we fix it, it better be the last line of + * the file. + */ + line[linelen] = '\n'; + line[linelen + 1] = 0; + had_last_line = 1; + } + if (!had_first_line) { + /* + * Ignore a leading RFC-976 From_ or >From_ line mistakenly + * inserted by some programs. + */ + if (strprefixcmp(line, "From ") == 0 || strprefixcmp(line, ">From ") == 0) + continue; + had_first_line = 1; + } if (!had_headers) { if (linelen > MAX_LINE_RFC822) { /* XXX also split headers */ diff --git a/contrib/dma/net.c b/contrib/dma/net.c index 7953370500d8..e8e2634a9386 100644 --- a/contrib/dma/net.c +++ b/contrib/dma/net.c @@ -53,6 +53,7 @@ #include <netdb.h> #include <setjmp.h> #include <signal.h> +#include <strings.h> #include <string.h> #include <syslog.h> #include <unistd.h> @@ -94,13 +95,13 @@ send_remote_command(int fd, const char* fmt, ...) strcat(cmd, "\r\n"); len = strlen(cmd); - if (((config.features & SECURETRANS) != 0) && + if (((config.features & SECURETRANSFER) != 0) && ((config.features & NOSSL) == 0)) { while ((s = SSL_write(config.ssl, (const char*)cmd, len)) <= 0) { s = SSL_get_error(config.ssl, s); if (s != SSL_ERROR_WANT_READ && s != SSL_ERROR_WANT_WRITE) { - strncpy(neterr, ssl_errstr(), sizeof(neterr)); + strlcpy(neterr, ssl_errstr(), sizeof(neterr)); return (-1); } } @@ -147,15 +148,15 @@ read_remote(int fd, int extbufsize, char *extbuf) memmove(buff, buff + pos, len - pos); len -= pos; pos = 0; - if (((config.features & SECURETRANS) != 0) && + if (((config.features & SECURETRANSFER) != 0) && (config.features & NOSSL) == 0) { if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) { - strncpy(neterr, ssl_errstr(), sizeof(neterr)); + strlcpy(neterr, ssl_errstr(), sizeof(neterr)); goto error; } } else { if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) { - strncpy(neterr, strerror(errno), sizeof(neterr)); + strlcpy(neterr, strerror(errno), sizeof(neterr)); goto error; } } @@ -248,64 +249,70 @@ error: * Handle SMTP authentication */ static int -smtp_login(int fd, char *login, char* password) +smtp_login(int fd, char *login, char* password, const struct smtp_features* features) { char *temp; int len, res = 0; - res = smtp_auth_md5(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); - } - - if ((config.features & INSECURE) != 0 || - (config.features & SECURETRANS) != 0) { - /* Send AUTH command according to RFC 2554 */ - send_remote_command(fd, "AUTH LOGIN"); - if (read_remote(fd, 0, NULL) != 3) { - syslog(LOG_NOTICE, "remote delivery deferred:" - " AUTH login not available: %s", - neterr); + // CRAM-MD5 + if (features->auth.cram_md5) { + res = smtp_auth_md5(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); } + } - len = base64_encode(login, strlen(login), &temp); - if (len < 0) { + // LOGIN + if (features->auth.login) { + if ((config.features & INSECURE) != 0 || + (config.features & SECURETRANSFER) != 0) { + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH LOGIN"); + if (read_remote(fd, 0, NULL) != 3) { + syslog(LOG_NOTICE, "remote delivery deferred:" + " AUTH login not available: %s", + neterr); + return (1); + } + + len = base64_encode(login, strlen(login), &temp); + if (len < 0) { encerr: - syslog(LOG_ERR, "can not encode auth reply: %m"); - return (1); - } + syslog(LOG_ERR, "can not encode auth reply: %m"); + return (1); + } - send_remote_command(fd, "%s", temp); - free(temp); - res = read_remote(fd, 0, NULL); - if (res != 3) { - syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s", - res == 5 ? "failed" : "deferred", neterr); - return (res == 5 ? -1 : 1); - } + send_remote_command(fd, "%s", temp); + free(temp); + res = read_remote(fd, 0, NULL); + if (res != 3) { + syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s", + res == 5 ? "failed" : "deferred", neterr); + return (res == 5 ? -1 : 1); + } - len = base64_encode(password, strlen(password), &temp); - if (len < 0) - goto encerr; - - send_remote_command(fd, "%s", temp); - free(temp); - res = read_remote(fd, 0, NULL); - if (res != 2) { - syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s", - res == 5 ? "failed" : "deferred", neterr); - return (res == 5 ? -1 : 1); + len = base64_encode(password, strlen(password), &temp); + if (len < 0) + goto encerr; + + send_remote_command(fd, "%s", temp); + free(temp); + res = read_remote(fd, 0, NULL); + if (res != 2) { + syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s", + res == 5 ? "failed" : "deferred", neterr); + return (res == 5 ? -1 : 1); + } + } else { + syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. "); + return (1); } - } else { - syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. "); - return (1); } return (0); @@ -340,7 +347,7 @@ static void close_connection(int fd) { if (config.ssl != NULL) { - if (((config.features & SECURETRANS) != 0) && + if (((config.features & SECURETRANSFER) != 0) && ((config.features & NOSSL) == 0)) SSL_shutdown(config.ssl); SSL_free(config.ssl); @@ -349,11 +356,116 @@ close_connection(int fd) close(fd); } +static void parse_auth_line(char* line, struct smtp_auth_mechanisms* auth) { + // Skip the auth prefix + line += strlen("AUTH "); + + char* method = strtok(line, " "); + while (method) { + if (strcmp(method, "CRAM-MD5") == 0) + auth->cram_md5 = 1; + + else if (strcmp(method, "LOGIN") == 0) + auth->login = 1; + + method = strtok(NULL, " "); + } +} + +int perform_server_greeting(int fd, struct smtp_features* features) { + /* + Send EHLO + XXX allow HELO fallback + */ + send_remote_command(fd, "EHLO %s", hostname()); + + char buffer[EHLO_RESPONSE_SIZE]; + memset(buffer, 0, sizeof(buffer)); + + int res = read_remote(fd, sizeof(buffer) - 1, buffer); + + // Got an unexpected response + if (res != 2) + return -1; + + // Reset all features + memset(features, 0, sizeof(*features)); + + // Run through the buffer line by line + char linebuffer[EHLO_RESPONSE_SIZE]; + char* p = buffer; + + while (*p) { + char* line = linebuffer; + while (*p && *p != '\n') { + *line++ = *p++; + } + + // p should never point to NULL after the loop + // above unless we reached the end of the buffer. + // In that case we will raise an error. + if (!*p) { + return -1; + } + + // Otherwise p points to the newline character which + // we will skip. + p++; + + // Terminte the string (and remove the carriage-return character) + *--line = '\0'; + line = linebuffer; + + // End main loop for empty lines + if (*line == '\0') + break; + + // Process the line + // - Must start with 250, followed by dash or space + // - We won't check for the correct usage of space and dash because + // that is already done in read_remote(). + if ((strncmp(line, "250-", 4) != 0) && (strncmp(line, "250 ", 4) != 0)) { + syslog(LOG_ERR, "Invalid line: %s\n", line); + return -1; + } + + // Skip the prefix + line += 4; + + // Check for STARTTLS + if (strcmp(line, "STARTTLS") == 0) + features->starttls = 1; + + // Parse authentication mechanisms + else if (strncmp(line, "AUTH ", 5) == 0) + parse_auth_line(line, &features->auth); + } + + syslog(LOG_DEBUG, "Server greeting successfully completed"); + + // STARTTLS + if (features->starttls) + syslog(LOG_DEBUG, " Server supports STARTTLS"); + else + syslog(LOG_DEBUG, " Server does not support STARTTLS"); + + // Authentication + if (features->auth.cram_md5) { + syslog(LOG_DEBUG, " Server supports CRAM-MD5 authentication"); + } + if (features->auth.login) { + syslog(LOG_DEBUG, " Server supports LOGIN authentication"); + } + + return 0; +} + static int deliver_to_host(struct qitem *it, struct mx_hostentry *host) { struct authuser *a; - char line[1000]; + struct smtp_features features; + char line[1000], *addrtmp = NULL, *to_addr; size_t linelen; int fd, error = 0, do_auth = 0, res = 0; @@ -366,24 +478,26 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host) if (fd < 0) return (1); -#define READ_REMOTE_CHECK(c, exp) \ - res = read_remote(fd, 0, NULL); \ - if (res == 5) { \ - syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \ - host->host, host->addr, c, neterr); \ - snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \ - host->host, host->addr, c, neterr); \ - error = -1; \ - goto out; \ - } else if (res != exp) { \ - syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ - host->host, host->addr, c, neterr); \ - error = 1; \ - goto out; \ - } +#define READ_REMOTE_CHECK(c, exp) \ + do { \ + res = read_remote(fd, 0, NULL); \ + if (res == 5) { \ + syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \ + host->host, host->addr, c, neterr); \ + error = -1; \ + goto out; \ + } else if (res != exp) { \ + syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + error = 1; \ + goto out; \ + } \ + } while (0) /* Check first reply from remote host */ - if ((config.features & SECURETRANS) == 0 || + if ((config.features & SECURETRANSFER) == 0 || (config.features & STARTTLS) != 0) { config.features |= NOSSL; READ_REMOTE_CHECK("connect", 2); @@ -391,8 +505,8 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host) config.features &= ~NOSSL; } - if ((config.features & SECURETRANS) != 0) { - error = smtp_init_crypto(fd, config.features); + if ((config.features & SECURETRANSFER) != 0) { + error = smtp_init_crypto(fd, config.features, &features); if (error == 0) syslog(LOG_DEBUG, "SSL initialization successful"); else @@ -402,10 +516,12 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host) READ_REMOTE_CHECK("connect", 2); } - /* XXX allow HELO fallback */ - /* XXX record ESMTP keywords */ - send_remote_command(fd, "EHLO %s", hostname()); - READ_REMOTE_CHECK("EHLO", 2); + // Say EHLO + if (perform_server_greeting(fd, &features) != 0) { + syslog(LOG_ERR, "Could not perform server greeting at %s [%s]: %s", + host->host, host->addr, neterr); *** 100 LINES SKIPPED ***