+++ /dev/null
-From 1fa7a882dd22d5f619b3645c6597a419034e9b4e Mon Sep 17 00:00:00 2001
-From: Michael Tremer <michael.tremer@ipfire.org>
-Date: Mon, 9 Nov 2015 21:52:08 +0000
-Subject: [PATCH] Implement better authentication
-
-DMA tries to authenticate by simply trying various authentication
-mechanisms. This is obviously not conforming to RFC and some mail
-providers detect this is spam and reject all emails.
-
-This patch parses the EHLO response and reads various keywords
-from it that can then later in the program be used to jump into
-certain code paths.
-
-Currently this is used to only authenticate with CRAM-MD5 and/or
-LOGIN if the server supports one or both of these. The
-implementation can be easily be extended though.
-
-Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
----
- crypto.c | 6 +-
- dma.h | 13 +++-
- net.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++----------------
- 3 files changed, 181 insertions(+), 57 deletions(-)
-
-diff --git a/crypto.c b/crypto.c
-index 897b55b..8048f20 100644
---- a/crypto.c
-+++ b/crypto.c
-@@ -77,7 +77,7 @@ init_cert_file(SSL_CTX *ctx, const char *path)
- }
-
- 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)
-@@ -118,8 +118,7 @@ smtp_init_crypto(int fd, int feature)
- /* 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) {
-@@ -131,6 +130,7 @@ smtp_init_crypto(int fd, int feature)
- }
- }
- }
-+
- /* End of TLS init phase, enable SSL_write/read */
- config.features &= ~NOSSL;
- }
-diff --git a/dma.h b/dma.h
-index acf5e44..ee749d8 100644
---- a/dma.h
-+++ b/dma.h
-@@ -51,6 +51,7 @@
- #define BUF_SIZE 2048
- #define ERRMSG_SIZE 200
- #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 */
-@@ -160,6 +161,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 +197,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 +206,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 */
-diff --git a/net.c b/net.c
-index 26935a8..33ff8f5 100644
---- a/net.c
-+++ b/net.c
-@@ -247,64 +247,70 @@ read_remote(int fd, int extbufsize, char *extbuf)
- * 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 & 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);
-+ 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);
-@@ -348,10 +354,115 @@ 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;
-+ struct smtp_features features;
- char line[1000];
- size_t linelen;
- int fd, error = 0, do_auth = 0, res = 0;
-@@ -389,7 +500,7 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host)
- }
-
- if ((config.features & SECURETRANS) != 0) {
-- error = smtp_init_crypto(fd, config.features);
-+ error = smtp_init_crypto(fd, config.features, &features);
- if (error == 0)
- syslog(LOG_DEBUG, "SSL initialization successful");
- else
-@@ -399,10 +510,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);
-+ return -1;
-+ }
-
- /*
- * Use SMTP authentication if the user defined an entry for the remote
-@@ -421,7 +534,7 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host)
- * encryption.
- */
- syslog(LOG_INFO, "using SMTP authentication for user %s", a->login);
-- error = smtp_login(fd, a->login, a->password);
-+ error = smtp_login(fd, a->login, a->password, &features);
- if (error < 0) {
- syslog(LOG_ERR, "remote delivery failed:"
- " SMTP login failed: %m");