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 <string.h>
57#include <syslog.h>
58#include <unistd.h>
59
60#include "dma.h"
61
62char neterr[ERRMSG_SIZE];
63
64char *
65ssl_errstr(void)
66{
67	long oerr, nerr;
68
69	oerr = 0;
70	while ((nerr = ERR_get_error()) != 0)
71		oerr = nerr;
72
73	return (ERR_error_string(oerr, NULL));
74}
75
76ssize_t
77send_remote_command(int fd, const char* fmt, ...)
78{
79	va_list va;
80	char cmd[4096];
81	size_t len, pos;
82	int s;
83	ssize_t n;
84
85	va_start(va, fmt);
86	s = vsnprintf(cmd, sizeof(cmd) - 2, fmt, va);
87	va_end(va);
88	if (s == sizeof(cmd) - 2 || s < 0) {
89		strcpy(neterr, "Internal error: oversized command string");
90		return (-1);
91	}
92
93	/* We *know* there are at least two more bytes available */
94	strcat(cmd, "\r\n");
95	len = strlen(cmd);
96
97	if (((config.features & SECURETRANS) != 0) &&
98	    ((config.features & NOSSL) == 0)) {
99		while ((s = SSL_write(config.ssl, (const char*)cmd, len)) <= 0) {
100			s = SSL_get_error(config.ssl, s);
101			if (s != SSL_ERROR_WANT_READ &&
102			    s != SSL_ERROR_WANT_WRITE) {
103				strncpy(neterr, ssl_errstr(), sizeof(neterr));
104				return (-1);
105			}
106		}
107	}
108	else {
109		pos = 0;
110		while (pos < len) {
111			n = write(fd, cmd + pos, len - pos);
112			if (n < 0)
113				return (-1);
114			pos += n;
115		}
116	}
117
118	return (len);
119}
120
121int
122read_remote(int fd, int extbufsize, char *extbuf)
123{
124	ssize_t rlen = 0;
125	size_t pos, len, copysize;
126	char buff[BUF_SIZE];
127	int done = 0, status = 0, status_running = 0, extbufpos = 0;
128	enum { parse_status, parse_spacedash, parse_rest } parsestate;
129
130	if (do_timeout(CON_TIMEOUT, 1) != 0) {
131		snprintf(neterr, sizeof(neterr), "Timeout reached");
132		return (-1);
133	}
134
135	/*
136	 * Remote reading code from femail.c written by Henning Brauer of
137	 * OpenBSD and released under a BSD style license.
138	 */
139	len = 0;
140	pos = 0;
141	parsestate = parse_status;
142	neterr[0] = 0;
143	while (!(done && parsestate == parse_status)) {
144		rlen = 0;
145		if (pos == 0 ||
146		    (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) {
147			memmove(buff, buff + pos, len - pos);
148			len -= pos;
149			pos = 0;
150			if (((config.features & SECURETRANS) != 0) &&
151			    (config.features & NOSSL) == 0) {
152				if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) {
153					strncpy(neterr, ssl_errstr(), sizeof(neterr));
154					goto error;
155				}
156			} else {
157				if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) {
158					strncpy(neterr, strerror(errno), sizeof(neterr));
159					goto error;
160				}
161			}
162			len += rlen;
163
164			copysize = sizeof(neterr) - strlen(neterr) - 1;
165			if (copysize > len)
166				copysize = len;
167			strncat(neterr, buff, copysize);
168		}
169		/*
170		 * If there is an external buffer with a size bigger than zero
171		 * and as long as there is space in the external buffer and
172		 * there are new characters read from the mailserver
173		 * copy them to the external buffer
174		 */
175		if (extbufpos <= (extbufsize - 1) && rlen > 0 && extbufsize > 0 && extbuf != NULL) {
176			/* do not write over the bounds of the buffer */
177			if(extbufpos + rlen > (extbufsize - 1)) {
178				rlen = extbufsize - extbufpos;
179			}
180			memcpy(extbuf + extbufpos, buff + len - rlen, rlen);
181			extbufpos += rlen;
182		}
183
184		if (pos == len)
185			continue;
186
187		switch (parsestate) {
188		case parse_status:
189			for (; pos < len; pos++) {
190				if (isdigit(buff[pos])) {
191					status_running = status_running * 10 + (buff[pos] - '0');
192				} else {
193					status = status_running;
194					status_running = 0;
195					parsestate = parse_spacedash;
196					break;
197				}
198			}
199			continue;
200
201		case parse_spacedash:
202			switch (buff[pos]) {
203			case ' ':
204				done = 1;
205				break;
206
207			case '-':
208				/* ignore */
209				/* XXX read capabilities */
210				break;
211
212			default:
213				strcpy(neterr, "invalid syntax in reply from server");
214				goto error;
215			}
216
217			pos++;
218			parsestate = parse_rest;
219			continue;
220
221		case parse_rest:
222			/* skip up to \n */
223			for (; pos < len; pos++) {
224				if (buff[pos] == '\n') {
225					pos++;
226					parsestate = parse_status;
227					break;
228				}
229			}
230		}
231
232	}
233
234	do_timeout(0, 0);
235
236	/* chop off trailing newlines */
237	while (neterr[0] != 0 && strchr("\r\n", neterr[strlen(neterr) - 1]) != 0)
238		neterr[strlen(neterr) - 1] = 0;
239
240	return (status/100);
241
242error:
243	do_timeout(0, 0);
244	return (-1);
245}
246
247/*
248 * Handle SMTP authentication
249 */
250static int
251smtp_login(int fd, char *login, char* password)
252{
253	char *temp;
254	int len, res = 0;
255
256	res = smtp_auth_md5(fd, login, password);
257	if (res == 0) {
258		return (0);
259	} else if (res == -2) {
260	/*
261	 * If the return code is -2, then then the login attempt failed,
262	 * do not try other login mechanisms
263	 */
264		return (1);
265	}
266
267	if ((config.features & INSECURE) != 0 ||
268	    (config.features & SECURETRANS) != 0) {
269		/* Send AUTH command according to RFC 2554 */
270		send_remote_command(fd, "AUTH LOGIN");
271		if (read_remote(fd, 0, NULL) != 3) {
272			syslog(LOG_NOTICE, "remote delivery deferred:"
273					" AUTH login not available: %s",
274					neterr);
275			return (1);
276		}
277
278		len = base64_encode(login, strlen(login), &temp);
279		if (len < 0) {
280encerr:
281			syslog(LOG_ERR, "can not encode auth reply: %m");
282			return (1);
283		}
284
285		send_remote_command(fd, "%s", temp);
286		free(temp);
287		res = read_remote(fd, 0, NULL);
288		if (res != 3) {
289			syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
290			       res == 5 ? "failed" : "deferred", neterr);
291			return (res == 5 ? -1 : 1);
292		}
293
294		len = base64_encode(password, strlen(password), &temp);
295		if (len < 0)
296			goto encerr;
297
298		send_remote_command(fd, "%s", temp);
299		free(temp);
300		res = read_remote(fd, 0, NULL);
301		if (res != 2) {
302			syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s",
303					res == 5 ? "failed" : "deferred", neterr);
304			return (res == 5 ? -1 : 1);
305		}
306	} else {
307		syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
308		return (1);
309	}
310
311	return (0);
312}
313
314static int
315open_connection(struct mx_hostentry *h)
316{
317	int fd;
318
319	syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d",
320	       h->host, h->addr, h->pref);
321
322	fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol);
323	if (fd < 0) {
324		syslog(LOG_INFO, "socket for %s [%s] failed: %m",
325		       h->host, h->addr);
326		return (-1);
327	}
328
329	if (connect(fd, (struct sockaddr *)&h->sa, h->ai.ai_addrlen) < 0) {
330		syslog(LOG_INFO, "connect to %s [%s] failed: %m",
331		       h->host, h->addr);
332		close(fd);
333		return (-1);
334	}
335
336	return (fd);
337}
338
339static void
340close_connection(int fd)
341{
342	if (config.ssl != NULL) {
343		if (((config.features & SECURETRANS) != 0) &&
344		    ((config.features & NOSSL) == 0))
345			SSL_shutdown(config.ssl);
346		SSL_free(config.ssl);
347	}
348
349	close(fd);
350}
351
352static int
353deliver_to_host(struct qitem *it, struct mx_hostentry *host)
354{
355	struct authuser *a;
356	char line[1000];
357	size_t linelen;
358	int fd, error = 0, do_auth = 0, res = 0;
359
360	if (fseek(it->mailf, 0, SEEK_SET) != 0) {
361		snprintf(errmsg, sizeof(errmsg), "can not seek: %s", strerror(errno));
362		return (-1);
363	}
364
365	fd = open_connection(host);
366	if (fd < 0)
367		return (1);
368
369#define READ_REMOTE_CHECK(c, exp)	\
370	res = read_remote(fd, 0, NULL); \
371	if (res == 5) { \
372		syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \
373		       host->host, host->addr, c, neterr); \
374		snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \
375			 host->host, host->addr, c, neterr); \
376		error = -1; \
377		goto out; \
378	} else if (res != exp) { \
379		syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \
380		       host->host, host->addr, c, neterr); \
381		error = 1; \
382		goto out; \
383	}
384
385	/* Check first reply from remote host */
386	if ((config.features & SECURETRANS) == 0 ||
387	    (config.features & STARTTLS) != 0) {
388		config.features |= NOSSL;
389		READ_REMOTE_CHECK("connect", 2);
390
391		config.features &= ~NOSSL;
392	}
393
394	if ((config.features & SECURETRANS) != 0) {
395		error = smtp_init_crypto(fd, config.features);
396		if (error == 0)
397			syslog(LOG_DEBUG, "SSL initialization successful");
398		else
399			goto out;
400
401		if ((config.features & STARTTLS) == 0)
402			READ_REMOTE_CHECK("connect", 2);
403	}
404
405	/* XXX allow HELO fallback */
406	/* XXX record ESMTP keywords */
407	send_remote_command(fd, "EHLO %s", hostname());
408	READ_REMOTE_CHECK("EHLO", 2);
409
410	/*
411	 * Use SMTP authentication if the user defined an entry for the remote
412	 * or smarthost
413	 */
414	SLIST_FOREACH(a, &authusers, next) {
415		if (strcmp(a->host, host->host) == 0) {
416			do_auth = 1;
417			break;
418		}
419	}
420
421	if (do_auth == 1) {
422		/*
423		 * Check if the user wants plain text login without using
424		 * encryption.
425		 */
426		syslog(LOG_INFO, "using SMTP authentication for user %s", a->login);
427		error = smtp_login(fd, a->login, a->password);
428		if (error < 0) {
429			syslog(LOG_ERR, "remote delivery failed:"
430					" SMTP login failed: %m");
431			snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host);
432			error = -1;
433			goto out;
434		}
435		/* SMTP login is not available, so try without */
436		else if (error > 0) {
437			syslog(LOG_WARNING, "SMTP login not available. Trying without.");
438		}
439	}
440
441	/* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */
442	send_remote_command(fd, "MAIL FROM:<%s>", it->sender);
443	READ_REMOTE_CHECK("MAIL FROM", 2);
444
445	/* XXX send ESMTP ORCPT */
446	send_remote_command(fd, "RCPT TO:<%s>", it->addr);
447	READ_REMOTE_CHECK("RCPT TO", 2);
448
449	send_remote_command(fd, "DATA");
450	READ_REMOTE_CHECK("DATA", 3);
451
452	error = 0;
453	while (!feof(it->mailf)) {
454		if (fgets(line, sizeof(line), it->mailf) == NULL)
455			break;
456		linelen = strlen(line);
457		if (linelen == 0 || line[linelen - 1] != '\n') {
458			syslog(LOG_CRIT, "remote delivery failed: corrupted queue file");
459			snprintf(errmsg, sizeof(errmsg), "corrupted queue file");
460			error = -1;
461			goto out;
462		}
463
464		/* Remove trailing \n's and escape leading dots */
465		trim_line(line);
466
467		/*
468		 * If the first character is a dot, we escape it so the line
469		 * length increases
470		*/
471		if (line[0] == '.')
472			linelen++;
473
474		if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) {
475			syslog(LOG_NOTICE, "remote delivery deferred: write error");
476			error = 1;
477			goto out;
478		}
479	}
480
481	send_remote_command(fd, ".");
482	READ_REMOTE_CHECK("final DATA", 2);
483
484	send_remote_command(fd, "QUIT");
485	if (read_remote(fd, 0, NULL) != 2)
486		syslog(LOG_INFO, "remote delivery succeeded but QUIT failed: %s", neterr);
487out:
488
489	close_connection(fd);
490	return (error);
491}
492
493int
494deliver_remote(struct qitem *it)
495{
496	struct mx_hostentry *hosts, *h;
497	const char *host;
498	int port;
499	int error = 1, smarthost = 0;
500
501	port = SMTP_PORT;
502
503	/* Smarthost support? */
504	if (config.smarthost != NULL) {
505		host = config.smarthost;
506		port = config.port;
507		syslog(LOG_INFO, "using smarthost (%s:%i)", host, port);
508		smarthost = 1;
509	} else {
510		host = strrchr(it->addr, '@');
511		/* Should not happen */
512		if (host == NULL) {
513			snprintf(errmsg, sizeof(errmsg), "Internal error: badly formed address %s",
514				 it->addr);
515			return(-1);
516		} else {
517			/* Step over the @ */
518			host++;
519		}
520	}
521
522	error = dns_get_mx_list(host, port, &hosts, smarthost);
523	if (error) {
524		snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host);
525		syslog(LOG_NOTICE, "remote delivery %s: DNS lookup failure: host %s not found",
526		       error < 0 ? "failed" : "deferred",
527		       host);
528		return (error);
529	}
530
531	for (h = hosts; *h->host != 0; h++) {
532		switch (deliver_to_host(it, h)) {
533		case 0:
534			/* success */
535			error = 0;
536			goto out;
537		case 1:
538			/* temp failure */
539			error = 1;
540			break;
541		default:
542			/* perm failure */
543			error = -1;
544			goto out;
545		}
546	}
547out:
548	free(hosts);
549
550	return (error);
551}
552