]> 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

1  2 
crypto.c
dma.h
net.c

diff --combined crypto.c
index 440c882880b50be8896cd53cbb4f29fb1d181673,8048f204e877963754521de7df17285da82b6933..0ebcf78c86d8babcd89d71c00f4cd959e6f7c2b7
+++ b/crypto.c
@@@ -77,7 -77,7 +77,7 @@@ init_cert_file(SSL_CTX *ctx, const cha
  }
  
  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)
        SSL_library_init();
        SSL_load_error_strings();
  
 -      meth = TLSv1_client_method();
 +      // Allow any possible version
 +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
 +      meth = TLS_client_method();
 +#else
 +      meth = SSLv23_client_method();
 +#endif
  
        ctx = SSL_CTX_new(meth);
        if (ctx == NULL) {
                /* 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) {
                                }
                        }
                }
                /* End of TLS init phase, enable SSL_write/read */
                config.features &= ~NOSSL;
        }
diff --combined dma.h
index 593417617d3d84dbf64333679523759b161ba004,ee749d8b7ee975d7f35f410c8b0bd3e7181bc3ae..ed0d0fc8cbd09ebb321a321691eacc237874bc10
--- 1/dma.h
--- 2/dma.h
+++ b/dma.h
@@@ -49,8 -49,9 +49,9 @@@
  #define VERSION       "DragonFly Mail Agent " DMA_VERSION
  
  #define BUF_SIZE      2048
 -#define ERRMSG_SIZE   200
 +#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 +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 +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);
  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 --combined net.c
index 47ee928494215be68c5dec48ee04cb2ce95da9a2,33ff8f52ed89370925aecb4a7492639165620de2..a1cc3e3bfd7970dfa37d68fbfbefcc3da831fda2
--- 1/net.c
--- 2/net.c
+++ b/net.c
@@@ -247,64 -247,70 +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);
                }
+       }
+       // 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) {
+                       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 +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;
                       host->host, host->addr, c, neterr); \
                snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \
                         host->host, host->addr, c, neterr); \
 -              return (-1); \
 +              error = -1; \
 +              goto out; \
        } else if (res != exp) { \
                syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \
                       host->host, host->addr, c, neterr); \
 -              return (1); \
 +              error = 1; \
 +              goto out; \
        }
  
        /* Check first reply from remote 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
                        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
                 * 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");
                        snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host);
 -                      return (-1);
 +                      error = -1;
 +                      goto out;
                }
                /* SMTP login is not available, so try without */
                else if (error > 0) {