]> git.ipfire.org Git - people/ms/dma.git/blobdiff - net.c
Merge pull request #34 from mtremer/better-authentication
[people/ms/dma.git] / net.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");