]> git.ipfire.org Git - people/ms/dma.git/commitdiff
Implement better authentication better-authentication
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 9 Nov 2015 21:52:08 +0000 (21:52 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 10 Nov 2015 22:42:46 +0000 (22:42 +0000)
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
dma.h
net.c

index 897b55bfdcfcc94814ba8482056c72e3d4a31f35..8048f204e877963754521de7df17285da82b6933 100644 (file)
--- 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 acf5e44d07bcced8faf1420a05304323b4986fab..ee749d8b7ee975d7f35f410c8b0bd3e7181bc3ae 100644 (file)
--- 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 26935a8feec8306bf6b5f7be7daa563e0c9cabd6..33ff8f52ed89370925aecb4a7492639165620de2 100644 (file)
--- a/net.c
+++ b/net.c
@@ -247,64 +247,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 & 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");