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