]>
Commit | Line | Data |
---|---|---|
b1372c3b MT |
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 | |
5 | ||
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. | |
9 | ||
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 | |
12 | certain code paths. | |
13 | ||
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. | |
17 | ||
18 | Signed-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 | ||
25 | diff --git a/crypto.c b/crypto.c | |
26 | index 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 | } | |
56 | diff --git a/dma.h b/dma.h | |
57 | index 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 */ | |
101 | diff --git a/net.c b/net.c | |
102 | index 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"); |