]> git.ipfire.org Git - ipfire-2.x.git/commitdiff
dma: Import patch for better authentication
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 10 Dec 2015 16:35:09 +0000 (16:35 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 10 Dec 2015 16:35:09 +0000 (16:35 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
config/rootfiles/core/96/filelists/dma [new symlink]
lfs/dma
src/patches/dma-0.10-better-authentication.patch [new file with mode: 0644]

diff --git a/config/rootfiles/core/96/filelists/dma b/config/rootfiles/core/96/filelists/dma
new file mode 120000 (symlink)
index 0000000..60f4682
--- /dev/null
@@ -0,0 +1 @@
+../../../common/dma
\ No newline at end of file
diff --git a/lfs/dma b/lfs/dma
index cf264eac1101357823503f0e5ac74042ec7e116e..64ed94452780b5c15a7c53ee325c59a18687a237 100644 (file)
--- 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 (file)
index 0000000..596168d
--- /dev/null
@@ -0,0 +1,373 @@
+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");