1 From 1fa7a882dd22d5f619b3645c6597a419034e9b4e Mon Sep 17 00:00:00 2001
2 From: Michael Tremer <michael.tremer@ipfire.org>
3 Date: Mon, 9 Nov 2015 21:52:08 +0000
4 Subject: [PATCH] Implement better authentication
6 DMA tries to authenticate by simply trying various authentication
7 mechanisms. This is obviously not conforming to RFC and some mail
8 providers detect this is spam and reject all emails.
10 This patch parses the EHLO response and reads various keywords
11 from it that can then later in the program be used to jump into
14 Currently this is used to only authenticate with CRAM-MD5 and/or
15 LOGIN if the server supports one or both of these. The
16 implementation can be easily be extended though.
18 Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
22 net.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++----------------
23 3 files changed, 181 insertions(+), 57 deletions(-)
25 diff --git a/crypto.c b/crypto.c
26 index 897b55b..8048f20 100644
29 @@ -77,7 +77,7 @@ init_cert_file(SSL_CTX *ctx, const char *path)
33 -smtp_init_crypto(int fd, int feature)
34 +smtp_init_crypto(int fd, int feature, struct smtp_features* features)
37 #if (OPENSSL_VERSION_NUMBER >= 0x00909000L)
38 @@ -118,8 +118,7 @@ smtp_init_crypto(int fd, int feature)
39 /* TLS init phase, disable SSL_write */
40 config.features |= NOSSL;
42 - send_remote_command(fd, "EHLO %s", hostname());
43 - if (read_remote(fd, 0, NULL) == 2) {
44 + if (perform_server_greeting(fd, features) == 0) {
45 send_remote_command(fd, "STARTTLS");
46 if (read_remote(fd, 0, NULL) != 2) {
47 if ((feature & TLS_OPP) == 0) {
48 @@ -131,6 +130,7 @@ smtp_init_crypto(int fd, int feature)
53 /* End of TLS init phase, enable SSL_write/read */
54 config.features &= ~NOSSL;
56 diff --git a/dma.h b/dma.h
57 index acf5e44..ee749d8 100644
62 #define ERRMSG_SIZE 200
63 #define USERNAME_SIZE 50
64 +#define EHLO_RESPONSE_SIZE BUF_SIZE
65 #define MIN_RETRY 300 /* 5 minutes */
66 #define MAX_RETRY (3*60*60) /* retry at least every 3 hours */
67 #define MAX_TIMEOUT (5*24*60*60) /* give up after 5 days */
68 @@ -160,6 +161,15 @@ struct mx_hostentry {
69 struct sockaddr_storage sa;
72 +struct smtp_auth_mechanisms {
77 +struct smtp_features {
78 + struct smtp_auth_mechanisms auth;
82 /* global variables */
83 extern struct aliases aliases;
84 @@ -187,7 +197,7 @@ void parse_authfile(const char *);
86 void hmac_md5(unsigned char *, int, unsigned char *, int, unsigned char *);
87 int smtp_auth_md5(int, char *, char *);
88 -int smtp_init_crypto(int, int);
89 +int smtp_init_crypto(int, int, struct smtp_features*);
92 int dns_get_mx_list(const char *, int, struct mx_hostentry **, int);
93 @@ -196,6 +206,7 @@ int dns_get_mx_list(const char *, int, struct mx_hostentry **, int);
94 char *ssl_errstr(void);
95 int read_remote(int, int, char *);
96 ssize_t send_remote_command(int, const char*, ...) __attribute__((__nonnull__(2), __format__ (__printf__, 2, 3)));
97 +int perform_server_greeting(int, struct smtp_features*);
98 int deliver_remote(struct qitem *);
101 diff --git a/net.c b/net.c
102 index 26935a8..33ff8f5 100644
105 @@ -247,64 +247,70 @@ read_remote(int fd, int extbufsize, char *extbuf)
106 * Handle SMTP authentication
109 -smtp_login(int fd, char *login, char* password)
110 +smtp_login(int fd, char *login, char* password, const struct smtp_features* features)
115 - res = smtp_auth_md5(fd, login, password);
118 - } else if (res == -2) {
120 - * If the return code is -2, then then the login attempt failed,
121 - * do not try other login mechanisms
126 - if ((config.features & INSECURE) != 0 ||
127 - (config.features & SECURETRANS) != 0) {
128 - /* Send AUTH command according to RFC 2554 */
129 - send_remote_command(fd, "AUTH LOGIN");
130 - if (read_remote(fd, 0, NULL) != 3) {
131 - syslog(LOG_NOTICE, "remote delivery deferred:"
132 - " AUTH login not available: %s",
135 + if (features->auth.cram_md5) {
136 + res = smtp_auth_md5(fd, login, password);
139 + } else if (res == -2) {
141 + * If the return code is -2, then then the login attempt failed,
142 + * do not try other login mechanisms
148 - len = base64_encode(login, strlen(login), &temp);
151 + if (features->auth.login) {
152 + if ((config.features & INSECURE) != 0 ||
153 + (config.features & SECURETRANS) != 0) {
154 + /* Send AUTH command according to RFC 2554 */
155 + send_remote_command(fd, "AUTH LOGIN");
156 + if (read_remote(fd, 0, NULL) != 3) {
157 + syslog(LOG_NOTICE, "remote delivery deferred:"
158 + " AUTH login not available: %s",
163 + len = base64_encode(login, strlen(login), &temp);
166 - syslog(LOG_ERR, "can not encode auth reply: %m");
169 + syslog(LOG_ERR, "can not encode auth reply: %m");
173 - send_remote_command(fd, "%s", temp);
175 - res = read_remote(fd, 0, NULL);
177 - syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
178 - res == 5 ? "failed" : "deferred", neterr);
179 - return (res == 5 ? -1 : 1);
181 + send_remote_command(fd, "%s", temp);
183 + res = read_remote(fd, 0, NULL);
185 + syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
186 + res == 5 ? "failed" : "deferred", neterr);
187 + return (res == 5 ? -1 : 1);
190 - len = base64_encode(password, strlen(password), &temp);
194 - send_remote_command(fd, "%s", temp);
196 - res = read_remote(fd, 0, NULL);
198 - syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s",
199 - res == 5 ? "failed" : "deferred", neterr);
200 - return (res == 5 ? -1 : 1);
201 + len = base64_encode(password, strlen(password), &temp);
205 + send_remote_command(fd, "%s", temp);
207 + res = read_remote(fd, 0, NULL);
209 + syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s",
210 + res == 5 ? "failed" : "deferred", neterr);
211 + return (res == 5 ? -1 : 1);
214 + syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
218 - syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
223 @@ -348,10 +354,115 @@ close_connection(int fd)
227 +static void parse_auth_line(char* line, struct smtp_auth_mechanisms* auth) {
228 + // Skip the auth prefix
229 + line += strlen("AUTH ");
231 + char* method = strtok(line, " ");
233 + if (strcmp(method, "CRAM-MD5") == 0)
234 + auth->cram_md5 = 1;
236 + else if (strcmp(method, "LOGIN") == 0)
239 + method = strtok(NULL, " ");
243 +int perform_server_greeting(int fd, struct smtp_features* features) {
246 + XXX allow HELO fallback
248 + send_remote_command(fd, "EHLO %s", hostname());
250 + char buffer[EHLO_RESPONSE_SIZE];
251 + memset(buffer, 0, sizeof(buffer));
253 + int res = read_remote(fd, sizeof(buffer) - 1, buffer);
255 + // Got an unexpected response
259 + // Reset all features
260 + memset(features, 0, sizeof(*features));
262 + // Run through the buffer line by line
263 + char linebuffer[EHLO_RESPONSE_SIZE];
267 + char* line = linebuffer;
268 + while (*p && *p != '\n') {
272 + // p should never point to NULL after the loop
273 + // above unless we reached the end of the buffer.
274 + // In that case we will raise an error.
279 + // Otherwise p points to the newline character which
283 + // Terminte the string (and remove the carriage-return character)
287 + // End main loop for empty lines
291 + // Process the line
292 + // - Must start with 250, followed by dash or space
293 + // - We won't check for the correct usage of space and dash because
294 + // that is already done in read_remote().
295 + if ((strncmp(line, "250-", 4) != 0) && (strncmp(line, "250 ", 4) != 0)) {
296 + syslog(LOG_ERR, "Invalid line: %s\n", line);
303 + // Check for STARTTLS
304 + if (strcmp(line, "STARTTLS") == 0)
305 + features->starttls = 1;
307 + // Parse authentication mechanisms
308 + else if (strncmp(line, "AUTH ", 5) == 0)
309 + parse_auth_line(line, &features->auth);
312 + syslog(LOG_DEBUG, "Server greeting successfully completed");
315 + if (features->starttls)
316 + syslog(LOG_DEBUG, " Server supports STARTTLS");
318 + syslog(LOG_DEBUG, " Server does not support STARTTLS");
321 + if (features->auth.cram_md5) {
322 + syslog(LOG_DEBUG, " Server supports CRAM-MD5 authentication");
324 + if (features->auth.login) {
325 + syslog(LOG_DEBUG, " Server supports LOGIN authentication");
332 deliver_to_host(struct qitem *it, struct mx_hostentry *host)
335 + struct smtp_features features;
338 int fd, error = 0, do_auth = 0, res = 0;
339 @@ -389,7 +500,7 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host)
342 if ((config.features & SECURETRANS) != 0) {
343 - error = smtp_init_crypto(fd, config.features);
344 + error = smtp_init_crypto(fd, config.features, &features);
346 syslog(LOG_DEBUG, "SSL initialization successful");
348 @@ -399,10 +510,12 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host)
349 READ_REMOTE_CHECK("connect", 2);
352 - /* XXX allow HELO fallback */
353 - /* XXX record ESMTP keywords */
354 - send_remote_command(fd, "EHLO %s", hostname());
355 - READ_REMOTE_CHECK("EHLO", 2);
357 + if (perform_server_greeting(fd, &features) != 0) {
358 + syslog(LOG_ERR, "Could not perform server greeting at %s [%s]: %s",
359 + host->host, host->addr, neterr);
364 * Use SMTP authentication if the user defined an entry for the remote
365 @@ -421,7 +534,7 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host)
368 syslog(LOG_INFO, "using SMTP authentication for user %s", a->login);
369 - error = smtp_login(fd, a->login, a->password);
370 + error = smtp_login(fd, a->login, a->password, &features);
372 syslog(LOG_ERR, "remote delivery failed:"
373 " SMTP login failed: %m");