]> git.ipfire.org Git - ipfire-2.x.git/blame - src/patches/dma-0.10-better-authentication.patch
Merge branch 'ipsec' into next
[ipfire-2.x.git] / src / patches / dma-0.10-better-authentication.patch
CommitLineData
b1372c3b
MT
1From 1fa7a882dd22d5f619b3645c6597a419034e9b4e Mon Sep 17 00:00:00 2001
2From: Michael Tremer <michael.tremer@ipfire.org>
3Date: Mon, 9 Nov 2015 21:52:08 +0000
4Subject: [PATCH] Implement better authentication
5
6DMA tries to authenticate by simply trying various authentication
7mechanisms. This is obviously not conforming to RFC and some mail
8providers detect this is spam and reject all emails.
9
10This patch parses the EHLO response and reads various keywords
11from it that can then later in the program be used to jump into
12certain code paths.
13
14Currently this is used to only authenticate with CRAM-MD5 and/or
15LOGIN if the server supports one or both of these. The
16implementation can be easily be extended though.
17
18Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
19---
20 crypto.c | 6 +-
21 dma.h | 13 +++-
22 net.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++----------------
23 3 files changed, 181 insertions(+), 57 deletions(-)
24
25diff --git a/crypto.c b/crypto.c
26index 897b55b..8048f20 100644
27--- a/crypto.c
28+++ b/crypto.c
29@@ -77,7 +77,7 @@ init_cert_file(SSL_CTX *ctx, const char *path)
30 }
31
32 int
33-smtp_init_crypto(int fd, int feature)
34+smtp_init_crypto(int fd, int feature, struct smtp_features* features)
35 {
36 SSL_CTX *ctx = NULL;
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;
41
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)
49 }
50 }
51 }
52+
53 /* End of TLS init phase, enable SSL_write/read */
54 config.features &= ~NOSSL;
55 }
56diff --git a/dma.h b/dma.h
57index acf5e44..ee749d8 100644
58--- a/dma.h
59+++ b/dma.h
60@@ -51,6 +51,7 @@
61 #define BUF_SIZE 2048
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;
70 };
71
72+struct smtp_auth_mechanisms {
73+ int cram_md5;
74+ int login;
75+};
76+
77+struct smtp_features {
78+ struct smtp_auth_mechanisms auth;
79+ int starttls;
80+};
81
82 /* global variables */
83 extern struct aliases aliases;
84@@ -187,7 +197,7 @@ void parse_authfile(const char *);
85 /* crypto.c */
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*);
90
91 /* dns.c */
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 *);
99
100 /* base64.c */
101diff --git a/net.c b/net.c
102index 26935a8..33ff8f5 100644
103--- a/net.c
104+++ b/net.c
105@@ -247,64 +247,70 @@ read_remote(int fd, int extbufsize, char *extbuf)
106 * Handle SMTP authentication
107 */
108 static int
109-smtp_login(int fd, char *login, char* password)
110+smtp_login(int fd, char *login, char* password, const struct smtp_features* features)
111 {
112 char *temp;
113 int len, res = 0;
114
115- res = smtp_auth_md5(fd, login, password);
116- if (res == 0) {
117- return (0);
118- } else if (res == -2) {
119- /*
120- * If the return code is -2, then then the login attempt failed,
121- * do not try other login mechanisms
122- */
123- return (1);
124- }
125-
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",
133- neterr);
134+ // CRAM-MD5
135+ if (features->auth.cram_md5) {
136+ res = smtp_auth_md5(fd, login, password);
137+ if (res == 0) {
138+ return (0);
139+ } else if (res == -2) {
140+ /*
141+ * If the return code is -2, then then the login attempt failed,
142+ * do not try other login mechanisms
143+ */
144 return (1);
145 }
146+ }
147
148- len = base64_encode(login, strlen(login), &temp);
149- if (len < 0) {
150+ // LOGIN
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",
159+ neterr);
160+ return (1);
161+ }
162+
163+ len = base64_encode(login, strlen(login), &temp);
164+ if (len < 0) {
165 encerr:
166- syslog(LOG_ERR, "can not encode auth reply: %m");
167- return (1);
168- }
169+ syslog(LOG_ERR, "can not encode auth reply: %m");
170+ return (1);
171+ }
172
173- send_remote_command(fd, "%s", temp);
174- free(temp);
175- res = read_remote(fd, 0, NULL);
176- if (res != 3) {
177- syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
178- res == 5 ? "failed" : "deferred", neterr);
179- return (res == 5 ? -1 : 1);
180- }
181+ send_remote_command(fd, "%s", temp);
182+ free(temp);
183+ res = read_remote(fd, 0, NULL);
184+ if (res != 3) {
185+ syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
186+ res == 5 ? "failed" : "deferred", neterr);
187+ return (res == 5 ? -1 : 1);
188+ }
189
190- len = base64_encode(password, strlen(password), &temp);
191- if (len < 0)
192- goto encerr;
193-
194- send_remote_command(fd, "%s", temp);
195- free(temp);
196- res = read_remote(fd, 0, NULL);
197- if (res != 2) {
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);
202+ if (len < 0)
203+ goto encerr;
204+
205+ send_remote_command(fd, "%s", temp);
206+ free(temp);
207+ res = read_remote(fd, 0, NULL);
208+ if (res != 2) {
209+ syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s",
210+ res == 5 ? "failed" : "deferred", neterr);
211+ return (res == 5 ? -1 : 1);
212+ }
213+ } else {
214+ syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
215+ return (1);
216 }
217- } else {
218- syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
219- return (1);
220 }
221
222 return (0);
223@@ -348,10 +354,115 @@ close_connection(int fd)
224 close(fd);
225 }
226
227+static void parse_auth_line(char* line, struct smtp_auth_mechanisms* auth) {
228+ // Skip the auth prefix
229+ line += strlen("AUTH ");
230+
231+ char* method = strtok(line, " ");
232+ while (method) {
233+ if (strcmp(method, "CRAM-MD5") == 0)
234+ auth->cram_md5 = 1;
235+
236+ else if (strcmp(method, "LOGIN") == 0)
237+ auth->login = 1;
238+
239+ method = strtok(NULL, " ");
240+ }
241+}
242+
243+int perform_server_greeting(int fd, struct smtp_features* features) {
244+ /*
245+ Send EHLO
246+ XXX allow HELO fallback
247+ */
248+ send_remote_command(fd, "EHLO %s", hostname());
249+
250+ char buffer[EHLO_RESPONSE_SIZE];
251+ memset(buffer, 0, sizeof(buffer));
252+
253+ int res = read_remote(fd, sizeof(buffer) - 1, buffer);
254+
255+ // Got an unexpected response
256+ if (res != 2)
257+ return -1;
258+
259+ // Reset all features
260+ memset(features, 0, sizeof(*features));
261+
262+ // Run through the buffer line by line
263+ char linebuffer[EHLO_RESPONSE_SIZE];
264+ char* p = buffer;
265+
266+ while (*p) {
267+ char* line = linebuffer;
268+ while (*p && *p != '\n') {
269+ *line++ = *p++;
270+ }
271+
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.
275+ if (!*p) {
276+ return -1;
277+ }
278+
279+ // Otherwise p points to the newline character which
280+ // we will skip.
281+ p++;
282+
283+ // Terminte the string (and remove the carriage-return character)
284+ *--line = '\0';
285+ line = linebuffer;
286+
287+ // End main loop for empty lines
288+ if (*line == '\0')
289+ break;
290+
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);
297+ return -1;
298+ }
299+
300+ // Skip the prefix
301+ line += 4;
302+
303+ // Check for STARTTLS
304+ if (strcmp(line, "STARTTLS") == 0)
305+ features->starttls = 1;
306+
307+ // Parse authentication mechanisms
308+ else if (strncmp(line, "AUTH ", 5) == 0)
309+ parse_auth_line(line, &features->auth);
310+ }
311+
312+ syslog(LOG_DEBUG, "Server greeting successfully completed");
313+
314+ // STARTTLS
315+ if (features->starttls)
316+ syslog(LOG_DEBUG, " Server supports STARTTLS");
317+ else
318+ syslog(LOG_DEBUG, " Server does not support STARTTLS");
319+
320+ // Authentication
321+ if (features->auth.cram_md5) {
322+ syslog(LOG_DEBUG, " Server supports CRAM-MD5 authentication");
323+ }
324+ if (features->auth.login) {
325+ syslog(LOG_DEBUG, " Server supports LOGIN authentication");
326+ }
327+
328+ return 0;
329+}
330+
331 static int
332 deliver_to_host(struct qitem *it, struct mx_hostentry *host)
333 {
334 struct authuser *a;
335+ struct smtp_features features;
336 char line[1000];
337 size_t linelen;
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)
340 }
341
342 if ((config.features & SECURETRANS) != 0) {
343- error = smtp_init_crypto(fd, config.features);
344+ error = smtp_init_crypto(fd, config.features, &features);
345 if (error == 0)
346 syslog(LOG_DEBUG, "SSL initialization successful");
347 else
348@@ -399,10 +510,12 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host)
349 READ_REMOTE_CHECK("connect", 2);
350 }
351
352- /* XXX allow HELO fallback */
353- /* XXX record ESMTP keywords */
354- send_remote_command(fd, "EHLO %s", hostname());
355- READ_REMOTE_CHECK("EHLO", 2);
356+ // Say EHLO
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);
360+ return -1;
361+ }
362
363 /*
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)
366 * encryption.
367 */
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);
371 if (error < 0) {
372 syslog(LOG_ERR, "remote delivery failed:"
373 " SMTP login failed: %m");