From b1372c3befd4ba4541fad1a90200ae7c1628ff00 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Thu, 10 Dec 2015 16:35:09 +0000 Subject: [PATCH] dma: Import patch for better authentication Signed-off-by: Michael Tremer --- config/rootfiles/core/96/filelists/dma | 1 + lfs/dma | 1 + .../dma-0.10-better-authentication.patch | 373 ++++++++++++++++++ 3 files changed, 375 insertions(+) create mode 120000 config/rootfiles/core/96/filelists/dma create mode 100644 src/patches/dma-0.10-better-authentication.patch diff --git a/config/rootfiles/core/96/filelists/dma b/config/rootfiles/core/96/filelists/dma new file mode 120000 index 0000000000..60f4682da8 --- /dev/null +++ b/config/rootfiles/core/96/filelists/dma @@ -0,0 +1 @@ +../../../common/dma \ No newline at end of file diff --git a/lfs/dma b/lfs/dma index cf264eac11..64ed944527 100644 --- a/lfs/dma +++ b/lfs/dma @@ -73,6 +73,7 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) @rm -rf $(DIR_APP) && cd $(DIR_SRC) && tar zxf $(DIR_DL)/$(DL_FILE) mkdir -pv /var/ipfire/dma touch /var/ipfire/dma/mail.conf + cd $(DIR_APP) && patch -Np1 < $(DIR_SRC)/src/patches/dma-0.10-better-authentication.patch cd $(DIR_APP) && sed -i '/PREFIX/s/usr\/local/usr/g' Makefile cd $(DIR_APP) && sed -i '/CONFDIR/s/etc\/dma/var\/ipfire\/dma/g' Makefile cd $(DIR_APP) && make diff --git a/src/patches/dma-0.10-better-authentication.patch b/src/patches/dma-0.10-better-authentication.patch new file mode 100644 index 0000000000..596168d2ad --- /dev/null +++ b/src/patches/dma-0.10-better-authentication.patch @@ -0,0 +1,373 @@ +From 1fa7a882dd22d5f619b3645c6597a419034e9b4e Mon Sep 17 00:00:00 2001 +From: Michael Tremer +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 +--- + 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"); -- 2.39.2