git: 23cfde968815 - stable/12 - dma: import snapshot 2021-07-10

From: Ed Maste <emaste_at_FreeBSD.org>
Date: Sun, 30 Jan 2022 17:56:10 UTC
The branch stable/12 has been updated by emaste:

URL: https://cgit.FreeBSD.org/src/commit/?id=23cfde968815a0d200ea4cf6d17617db852f60d1

commit 23cfde968815a0d200ea4cf6d17617db852f60d1
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:55:47 +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 ***