1/*
2 * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
3 * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
4 *
5 * This code is derived from software contributed to The DragonFly Project
6 * by Matthias Schmidt <matthias@dragonflybsd.org>, University of Marburg,
7 * Germany.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in
17 *    the documentation and/or other materials provided with the
18 *    distribution.
19 * 3. Neither the name of The DragonFly Project nor the names of its
20 *    contributors may be used to endorse or promote products derived
21 *    from this software without specific, prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
27 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include "dfcompat.h"
38
39#include <sys/param.h>
40#include <sys/queue.h>
41#include <sys/stat.h>
42#include <sys/types.h>
43#include <sys/socket.h>
44#include <netinet/in.h>
45#include <arpa/inet.h>
46
47#include <openssl/ssl.h>
48#include <openssl/err.h>
49
50#include <ctype.h>
51#include <err.h>
52#include <errno.h>
53#include <netdb.h>
54#include <setjmp.h>
55#include <signal.h>
56#include <strings.h>
57#include <string.h>
58#include <syslog.h>
59#include <unistd.h>
60
61#include "dma.h"
62
63char neterr[ERRMSG_SIZE];
64
65char *
66ssl_errstr(void)
67{
68	long oerr, nerr;
69
70	oerr = 0;
71	while ((nerr = ERR_get_error()) != 0)
72		oerr = nerr;
73
74	return (ERR_error_string(oerr, NULL));
75}
76
77ssize_t
78send_remote_command(int fd, const char* fmt, ...)
79{
80	va_list va;
81	char cmd[4096];
82	size_t len, pos;
83	int s;
84	ssize_t n;
85
86	va_start(va, fmt);
87	s = vsnprintf(cmd, sizeof(cmd) - 2, fmt, va);
88	va_end(va);
89	if (s == sizeof(cmd) - 2 || s < 0) {
90		strcpy(neterr, "Internal error: oversized command string");
91		return (-1);
92	}
93
94	/* We *know* there are at least two more bytes available */
95	strcat(cmd, "\r\n");
96	len = strlen(cmd);
97
98	pos = 0;
99	while (pos < len) {
100		if (((config.features & SECURETRANSFER) != 0) &&
101		    ((config.features & NOSSL) == 0)) {
102			if ((n = SSL_write(config.ssl, (const char*)(cmd + pos), len - pos)) <= 0) {
103				s = SSL_get_error(config.ssl, n);
104				if (s == SSL_ERROR_ZERO_RETURN ||
105				    s == SSL_ERROR_SYSCALL ||
106				    s == SSL_ERROR_SSL) {
107					strlcpy(neterr, ssl_errstr(), sizeof(neterr));
108					return (-1);
109				}
110				n = 0;
111			}
112		} else {
113			n = write(fd, cmd + pos, len - pos);
114			if (n < 0) {
115				if ((errno != EAGAIN) && (errno != EINTR))
116					return (-1);
117				n = 0;
118			}
119		}
120		pos += n;
121	}
122
123	return (len);
124}
125
126int
127read_remote(int fd, int extbufsize, char *extbuf)
128{
129	ssize_t rlen = 0;
130	size_t pos, len, copysize;
131	char buff[BUF_SIZE];
132	int done = 0, status = 0, status_running = 0, extbufpos = 0;
133	enum { parse_status, parse_spacedash, parse_rest } parsestate;
134
135	if (do_timeout(CON_TIMEOUT, 1) != 0) {
136		snprintf(neterr, sizeof(neterr), "Timeout reached");
137		return (-1);
138	}
139
140	/*
141	 * Remote reading code from femail.c written by Henning Brauer of
142	 * OpenBSD and released under a BSD style license.
143	 */
144	len = 0;
145	pos = 0;
146	parsestate = parse_status;
147	neterr[0] = 0;
148	while (!(done && parsestate == parse_status)) {
149		rlen = 0;
150		if (pos == 0 ||
151		    (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) {
152			memmove(buff, buff + pos, len - pos);
153			len -= pos;
154			pos = 0;
155			if (((config.features & SECURETRANSFER) != 0) &&
156			    (config.features & NOSSL) == 0) {
157				if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) <= 0) {
158					switch (SSL_get_error(config.ssl, rlen)) {
159					case SSL_ERROR_ZERO_RETURN:
160					case SSL_ERROR_SYSCALL:
161					case SSL_ERROR_SSL:
162						strlcpy(neterr, ssl_errstr(), sizeof(neterr));
163						goto error;
164					default:
165						/* in case of recoverable error, retry after short sleep */
166						usleep(10000);
167						continue;
168					}
169				}
170			} else {
171				if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) {
172					strlcpy(neterr, strerror(errno), sizeof(neterr));
173					goto error;
174				}
175			}
176			len += rlen;
177
178			copysize = sizeof(neterr) - strlen(neterr) - 1;
179			if (copysize > len)
180				copysize = len;
181			strncat(neterr, buff, copysize);
182		}
183		/*
184		 * If there is an external buffer with a size bigger than zero
185		 * and as long as there is space in the external buffer and
186		 * there are new characters read from the mailserver
187		 * copy them to the external buffer
188		 */
189		if (extbufpos <= (extbufsize - 1) && rlen > 0 && extbufsize > 0 && extbuf != NULL) {
190			/* do not write over the bounds of the buffer */
191			if(extbufpos + rlen > (extbufsize - 1)) {
192				rlen = extbufsize - extbufpos;
193			}
194			memcpy(extbuf + extbufpos, buff + len - rlen, rlen);
195			extbufpos += rlen;
196		}
197
198		if (pos == len)
199			continue;
200
201		switch (parsestate) {
202		case parse_status:
203			for (; pos < len; pos++) {
204				if (isdigit(buff[pos])) {
205					status_running = status_running * 10 + (buff[pos] - '0');
206				} else {
207					status = status_running;
208					status_running = 0;
209					parsestate = parse_spacedash;
210					break;
211				}
212			}
213			continue;
214
215		case parse_spacedash:
216			switch (buff[pos]) {
217			case ' ':
218				done = 1;
219				break;
220
221			case '-':
222				/* ignore */
223				/* XXX read capabilities */
224				break;
225
226			default:
227				strcpy(neterr, "invalid syntax in reply from server");
228				goto error;
229			}
230
231			pos++;
232			parsestate = parse_rest;
233			continue;
234
235		case parse_rest:
236			/* skip up to \n */
237			for (; pos < len; pos++) {
238				if (buff[pos] == '\n') {
239					pos++;
240					parsestate = parse_status;
241					break;
242				}
243			}
244		}
245
246	}
247
248	do_timeout(0, 0);
249
250	/* chop off trailing newlines */
251	while (neterr[0] != 0 && strchr("\r\n", neterr[strlen(neterr) - 1]) != 0)
252		neterr[strlen(neterr) - 1] = 0;
253
254	return (status/100);
255
256error:
257	do_timeout(0, 0);
258	return (-1);
259}
260
261/*
262 * Handle SMTP authentication
263 */
264static int
265smtp_login(int fd, char *login, char* password, const struct smtp_features* features)
266{
267	char *temp;
268	int len, res = 0;
269
270	// CRAM-MD5
271	if (features->auth.cram_md5) {
272		res = smtp_auth_md5(fd, login, password);
273		if (res == 0) {
274			return (0);
275		} else if (res == -2) {
276		/*
277		 * If the return code is -2, then then the login attempt failed,
278		 * do not try other login mechanisms
279		 */
280			return (1);
281		}
282	}
283
284	// LOGIN
285	if (features->auth.login) {
286		if ((config.features & INSECURE) != 0 ||
287		    (config.features & SECURETRANSFER) != 0) {
288			/* Send AUTH command according to RFC 2554 */
289			send_remote_command(fd, "AUTH LOGIN");
290			if (read_remote(fd, 0, NULL) != 3) {
291				syslog(LOG_NOTICE, "remote delivery deferred:"
292						" AUTH login not available: %s",
293						neterr);
294				return (1);
295			}
296
297			len = base64_encode(login, strlen(login), &temp);
298			if (len < 0) {
299encerr:
300				syslog(LOG_ERR, "can not encode auth reply: %m");
301				return (1);
302			}
303
304			send_remote_command(fd, "%s", temp);
305			free(temp);
306			res = read_remote(fd, 0, NULL);
307			if (res != 3) {
308				syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
309				       res == 5 ? "failed" : "deferred", neterr);
310				return (res == 5 ? -1 : 1);
311			}
312
313			len = base64_encode(password, strlen(password), &temp);
314			if (len < 0)
315				goto encerr;
316
317			send_remote_command(fd, "%s", temp);
318			free(temp);
319			res = read_remote(fd, 0, NULL);
320			if (res != 2) {
321				syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s",
322						res == 5 ? "failed" : "deferred", neterr);
323				return (res == 5 ? -1 : 1);
324			}
325		} else {
326			syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
327			return (1);
328		}
329	}
330
331	return (0);
332}
333
334static int
335open_connection(struct mx_hostentry *h)
336{
337	int fd;
338
339	syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d",
340	       h->host, h->addr, h->pref);
341
342	fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol);
343	if (fd < 0) {
344		syslog(LOG_INFO, "socket for %s [%s] failed: %m",
345		       h->host, h->addr);
346		return (-1);
347	}
348
349	if (connect(fd, (struct sockaddr *)&h->sa, h->ai.ai_addrlen) < 0) {
350		syslog(LOG_INFO, "connect to %s [%s] failed: %m",
351		       h->host, h->addr);
352		close(fd);
353		return (-1);
354	}
355
356	return (fd);
357}
358
359static void
360close_connection(int fd)
361{
362	if (config.ssl != NULL) {
363		if (((config.features & SECURETRANSFER) != 0) &&
364		    ((config.features & NOSSL) == 0))
365			SSL_shutdown(config.ssl);
366		SSL_free(config.ssl);
367	}
368
369	close(fd);
370}
371
372static void parse_auth_line(char* line, struct smtp_auth_mechanisms* auth) {
373	// Skip the auth prefix
374	line += strlen("AUTH ");
375
376	char* method = strtok(line, " ");
377	while (method) {
378		if (strcmp(method, "CRAM-MD5") == 0)
379			auth->cram_md5 = 1;
380
381		else if (strcmp(method, "LOGIN") == 0)
382			auth->login = 1;
383
384		method = strtok(NULL, " ");
385	}
386}
387
388int perform_server_greeting(int fd, struct smtp_features* features) {
389	/*
390		Send EHLO
391		XXX allow HELO fallback
392	*/
393	send_remote_command(fd, "EHLO %s", hostname());
394
395	char buffer[EHLO_RESPONSE_SIZE];
396	memset(buffer, 0, sizeof(buffer));
397
398	int res = read_remote(fd, sizeof(buffer) - 1, buffer);
399
400	// Got an unexpected response
401	if (res != 2)
402		return -1;
403
404	// Reset all features
405	memset(features, 0, sizeof(*features));
406
407	// Run through the buffer line by line
408	char linebuffer[EHLO_RESPONSE_SIZE];
409	char* p = buffer;
410
411	while (*p) {
412		char* line = linebuffer;
413		while (*p && *p != '\n') {
414			*line++ = *p++;
415		}
416
417		// p should never point to NULL after the loop
418		// above unless we reached the end of the buffer.
419		// In that case we will raise an error.
420		if (!*p) {
421			return -1;
422		}
423
424		// Otherwise p points to the newline character which
425		// we will skip.
426		p++;
427
428		// Terminte the string (and remove the carriage-return character)
429		*--line = '\0';
430		line = linebuffer;
431
432		// End main loop for empty lines
433		if (*line == '\0')
434			break;
435
436		// Process the line
437		// - Must start with 250, followed by dash or space
438		// - We won't check for the correct usage of space and dash because
439		//    that is already done in read_remote().
440		if ((strncmp(line, "250-", 4) != 0) && (strncmp(line, "250 ", 4) != 0)) {
441			syslog(LOG_ERR, "Invalid line: %s\n", line);
442			return -1;
443		}
444
445		// Skip the prefix
446		line += 4;
447
448		// Check for STARTTLS
449		if (strcmp(line, "STARTTLS") == 0)
450			features->starttls = 1;
451
452		// Parse authentication mechanisms
453		else if (strncmp(line, "AUTH ", 5) == 0)
454			parse_auth_line(line, &features->auth);
455	}
456
457	syslog(LOG_DEBUG, "Server greeting successfully completed");
458
459	// STARTTLS
460	if (features->starttls)
461		syslog(LOG_DEBUG, "  Server supports STARTTLS");
462	else
463		syslog(LOG_DEBUG, "  Server does not support STARTTLS");
464
465	// Authentication
466	if (features->auth.cram_md5) {
467		syslog(LOG_DEBUG, "  Server supports CRAM-MD5 authentication");
468	}
469	if (features->auth.login) {
470		syslog(LOG_DEBUG, "  Server supports LOGIN authentication");
471	}
472
473	return 0;
474}
475
476static int
477deliver_to_host(struct qitem *it, struct mx_hostentry *host)
478{
479	struct authuser *a;
480	struct smtp_features features;
481	char line[1000], *addrtmp = NULL, *to_addr;
482	size_t linelen;
483	int fd, error = 0, do_auth = 0, res = 0;
484
485	if (fseek(it->mailf, 0, SEEK_SET) != 0) {
486		snprintf(errmsg, sizeof(errmsg), "can not seek: %s", strerror(errno));
487		return (-1);
488	}
489
490	fd = open_connection(host);
491	if (fd < 0)
492		return (1);
493
494#define READ_REMOTE_CHECK(c, exp)                                       \
495        do {                                                            \
496                res = read_remote(fd, 0, NULL);                         \
497                if (res == 5) {                                         \
498                        syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \
499                               host->host, host->addr, c, neterr);      \
500                        snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \
501                                 host->host, host->addr, c, neterr);    \
502                        error = -1;                                     \
503                        goto out;                                       \
504                } else if (res != exp) {                                \
505                        syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \
506                               host->host, host->addr, c, neterr);      \
507                        error = 1;                                      \
508                        goto out;                                       \
509                }                                                       \
510        } while (0)
511
512	/* Check first reply from remote host */
513	if ((config.features & SECURETRANSFER) == 0 ||
514	    (config.features & STARTTLS) != 0) {
515		config.features |= NOSSL;
516		READ_REMOTE_CHECK("connect", 2);
517
518		config.features &= ~NOSSL;
519	}
520
521	if ((config.features & SECURETRANSFER) != 0) {
522		error = smtp_init_crypto(fd, config.features, &features);
523		if (error == 0)
524			syslog(LOG_DEBUG, "SSL initialization successful");
525		else
526			goto out;
527
528		if ((config.features & STARTTLS) == 0)
529			READ_REMOTE_CHECK("connect", 2);
530	}
531
532	// Say EHLO
533	if (perform_server_greeting(fd, &features) != 0) {
534		syslog(LOG_ERR, "Could not perform server greeting at %s [%s]: %s",
535			host->host, host->addr, neterr);
536		return -1;
537	}
538
539	/*
540	 * Use SMTP authentication if the user defined an entry for the remote
541	 * or smarthost
542	 */
543	SLIST_FOREACH(a, &authusers, next) {
544		if (strcmp(a->host, host->host) == 0) {
545			do_auth = 1;
546			break;
547		}
548	}
549
550	if (do_auth == 1) {
551		/*
552		 * Check if the user wants plain text login without using
553		 * encryption.
554		 */
555		syslog(LOG_INFO, "using SMTP authentication for user %s", a->login);
556		error = smtp_login(fd, a->login, a->password, &features);
557		if (error < 0) {
558			syslog(LOG_ERR, "remote delivery failed:"
559					" SMTP login failed: %m");
560			snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host);
561			error = -1;
562			goto out;
563		}
564		/* SMTP login is not available, so try without */
565		else if (error > 0) {
566			syslog(LOG_WARNING, "SMTP login not available. Trying without.");
567		}
568	}
569
570	/* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */
571	send_remote_command(fd, "MAIL FROM:<%s>", it->sender);
572	READ_REMOTE_CHECK("MAIL FROM", 2);
573
574	/* XXX send ESMTP ORCPT */
575	if ((addrtmp = strdup(it->addr)) == NULL) {
576		syslog(LOG_CRIT, "remote delivery deferred: unable to allocate memory");
577		error = 1;
578		goto out;
579	}
580	to_addr = strtok(addrtmp, ",");
581	while (to_addr != NULL) {
582		send_remote_command(fd, "RCPT TO:<%s>", to_addr);
583		READ_REMOTE_CHECK("RCPT TO", 2);
584		to_addr = strtok(NULL, ",");
585	}
586
587	send_remote_command(fd, "DATA");
588	READ_REMOTE_CHECK("DATA", 3);
589
590	error = 0;
591	while (!feof(it->mailf)) {
592		if (fgets(line, sizeof(line), it->mailf) == NULL)
593			break;
594		linelen = strlen(line);
595		if (linelen == 0 || line[linelen - 1] != '\n') {
596			syslog(LOG_CRIT, "remote delivery failed: corrupted queue file");
597			snprintf(errmsg, sizeof(errmsg), "corrupted queue file");
598			error = -1;
599			goto out;
600		}
601
602		/* Remove trailing \n's and escape leading dots */
603		trim_line(line);
604
605		/*
606		 * If the first character is a dot, we escape it so the line
607		 * length increases
608		*/
609		if (line[0] == '.')
610			linelen++;
611
612		if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) {
613			syslog(LOG_NOTICE, "remote delivery deferred: write error");
614			error = 1;
615			goto out;
616		}
617	}
618
619	send_remote_command(fd, ".");
620	READ_REMOTE_CHECK("final DATA", 2);
621
622	send_remote_command(fd, "QUIT");
623	if (read_remote(fd, 0, NULL) != 2)
624		syslog(LOG_INFO, "remote delivery succeeded but QUIT failed: %s", neterr);
625out:
626
627	free(addrtmp);
628	close_connection(fd);
629	return (error);
630}
631
632int
633deliver_remote(struct qitem *it)
634{
635	struct mx_hostentry *hosts, *h;
636	const char *host;
637	int port;
638	int error = 1, smarthost = 0;
639
640	port = SMTP_PORT;
641
642	/* Smarthost support? */
643	if (config.smarthost != NULL) {
644		host = config.smarthost;
645		port = config.port;
646		syslog(LOG_INFO, "using smarthost (%s:%i)", host, port);
647		smarthost = 1;
648	} else {
649		host = strrchr(it->addr, '@');
650		/* Should not happen */
651		if (host == NULL) {
652			snprintf(errmsg, sizeof(errmsg), "Internal error: badly formed address %s",
653				 it->addr);
654			return(-1);
655		} else {
656			/* Step over the @ */
657			host++;
658		}
659	}
660
661	error = dns_get_mx_list(host, port, &hosts, smarthost);
662	if (error) {
663		snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host);
664		syslog(LOG_NOTICE, "remote delivery %s: DNS lookup failure: host %s not found",
665		       error < 0 ? "failed" : "deferred",
666		       host);
667		return (error);
668	}
669
670	for (h = hosts; *h->host != 0; h++) {
671		switch (deliver_to_host(it, h)) {
672		case 0:
673			/* success */
674			error = 0;
675			goto out;
676		case 1:
677			/* temp failure */
678			error = 1;
679			break;
680		default:
681			/* perm failure */
682			error = -1;
683			goto out;
684		}
685	}
686out:
687	free(hosts);
688
689	return (error);
690}
691