]> git.ipfire.org Git - people/ms/dma.git/commitdiff
Merge pull request #34 from mtremer/better-authentication master
authorSimon Schubert <2@0x2c.org>
Mon, 12 Feb 2018 17:49:29 +0000 (18:49 +0100)
committerGitHub <noreply@github.com>
Mon, 12 Feb 2018 17:49:29 +0000 (18:49 +0100)
Parse EHLO response and use for authentication

crypto.c
dma.h
net.c

index 440c882880b50be8896cd53cbb4f29fb1d181673..0ebcf78c86d8babcd89d71c00f4cd959e6f7c2b7 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)
@@ -123,8 +123,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) {
@@ -136,6 +135,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 593417617d3d84dbf64333679523759b161ba004..ed0d0fc8cbd09ebb321a321691eacc237874bc10 100644 (file)
--- a/dma.h
+++ b/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 */
@@ -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 47ee928494215be68c5dec48ee04cb2ce95da9a2..a1cc3e3bfd7970dfa37d68fbfbefcc3da831fda2 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;
@@ -391,7 +502,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
@@ -401,10 +512,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
@@ -423,7 +536,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");