xref: /openbsd/usr.sbin/smtpd/mail.lmtp.c (revision d3140113)
16c4a8d13Sgilles /*
26c4a8d13Sgilles  * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org>
36c4a8d13Sgilles  *
46c4a8d13Sgilles  * Permission to use, copy, modify, and distribute this software for any
56c4a8d13Sgilles  * purpose with or without fee is hereby granted, provided that the above
66c4a8d13Sgilles  * copyright notice and this permission notice appear in all copies.
76c4a8d13Sgilles  *
86c4a8d13Sgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
96c4a8d13Sgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
106c4a8d13Sgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
116c4a8d13Sgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
126c4a8d13Sgilles  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
136c4a8d13Sgilles  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
146c4a8d13Sgilles  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
156c4a8d13Sgilles  */
166c4a8d13Sgilles 
176c4a8d13Sgilles #include <sys/socket.h>
186c4a8d13Sgilles #include <sys/un.h>
196c4a8d13Sgilles 
206c4a8d13Sgilles #include <ctype.h>
216c4a8d13Sgilles #include <err.h>
226c4a8d13Sgilles #include <errno.h>
236c4a8d13Sgilles #include <netdb.h>
246c4a8d13Sgilles #include <stdio.h>
256c4a8d13Sgilles #include <stdlib.h>
266c4a8d13Sgilles #include <string.h>
27460f7b42Sgilles #include <sysexits.h>
286c4a8d13Sgilles #include <unistd.h>
296c4a8d13Sgilles 
306c4a8d13Sgilles enum phase {
316c4a8d13Sgilles 	PHASE_BANNER,
326c4a8d13Sgilles 	PHASE_HELO,
336c4a8d13Sgilles 	PHASE_MAILFROM,
346c4a8d13Sgilles 	PHASE_RCPTTO,
356c4a8d13Sgilles 	PHASE_DATA,
366c4a8d13Sgilles 	PHASE_EOM,
376c4a8d13Sgilles 	PHASE_QUIT
386c4a8d13Sgilles };
396c4a8d13Sgilles 
406c4a8d13Sgilles struct session {
416c4a8d13Sgilles 	const char	*lhlo;
426c4a8d13Sgilles 	const char	*mailfrom;
4343304138Sgilles 	char		*rcptto;
446c4a8d13Sgilles 
456c4a8d13Sgilles 	char		**rcpts;
466c4a8d13Sgilles 	int		n_rcpts;
476c4a8d13Sgilles };
486c4a8d13Sgilles 
499d327827Sgilles static int lmtp_connect(const char *);
509d327827Sgilles static void lmtp_engine(int, struct session *);
516c4a8d13Sgilles static void stream_file(FILE *);
526c4a8d13Sgilles 
536c4a8d13Sgilles int
main(int argc,char * argv[])546c4a8d13Sgilles main(int argc, char *argv[])
556c4a8d13Sgilles {
566c4a8d13Sgilles 	int ch;
579d327827Sgilles 	int conn;
58909acb05Sgilles 	const char *destination = "localhost";
596c4a8d13Sgilles 	struct session	session;
606c4a8d13Sgilles 
61aa0e0035Sgilles 	if (! geteuid())
62776f254eStim 		errx(EX_TEMPFAIL, "mail.lmtp: may not be executed as root");
63aa0e0035Sgilles 
646c4a8d13Sgilles 	session.lhlo = "localhost";
6543304138Sgilles 	session.mailfrom = getenv("SENDER");
667d506d68Smillert 	session.rcptto = NULL;
676c4a8d13Sgilles 
6843304138Sgilles 	while ((ch = getopt(argc, argv, "d:l:f:ru")) != -1) {
696c4a8d13Sgilles 		switch (ch) {
706c4a8d13Sgilles 		case 'd':
716c4a8d13Sgilles 			destination = optarg;
726c4a8d13Sgilles 			break;
736c4a8d13Sgilles 		case 'l':
746c4a8d13Sgilles 			session.lhlo = optarg;
756c4a8d13Sgilles 			break;
766c4a8d13Sgilles 		case 'f':
776c4a8d13Sgilles 			session.mailfrom = optarg;
786c4a8d13Sgilles 			break;
7943304138Sgilles 
8043304138Sgilles 		case 'r':
8143304138Sgilles 			session.rcptto = getenv("RECIPIENT");
8243304138Sgilles 			break;
8343304138Sgilles 
8443304138Sgilles 		case 'u':
8543304138Sgilles 			session.rcptto = getenv("USER");
8643304138Sgilles 			break;
8743304138Sgilles 
886c4a8d13Sgilles 		default:
896c4a8d13Sgilles 			break;
906c4a8d13Sgilles 		}
916c4a8d13Sgilles 	}
926c4a8d13Sgilles 	argc -= optind;
936c4a8d13Sgilles 	argv += optind;
946c4a8d13Sgilles 
956c4a8d13Sgilles 	if (session.mailfrom == NULL)
96776f254eStim 		errx(EX_TEMPFAIL, "sender must be specified with -f");
976c4a8d13Sgilles 
9843304138Sgilles 	if (argc == 0 && session.rcptto == NULL)
99776f254eStim 		errx(EX_TEMPFAIL, "no recipient was specified");
1006c4a8d13Sgilles 
10143304138Sgilles 	if (session.rcptto) {
10243304138Sgilles 		session.rcpts = &session.rcptto;
10343304138Sgilles 		session.n_rcpts = 1;
10443304138Sgilles 	}
10543304138Sgilles 	else {
1066c4a8d13Sgilles 		session.rcpts = argv;
1076c4a8d13Sgilles 		session.n_rcpts = argc;
10843304138Sgilles 	}
1096c4a8d13Sgilles 
1106c4a8d13Sgilles 	conn = lmtp_connect(destination);
1116c4a8d13Sgilles 	lmtp_engine(conn, &session);
1126c4a8d13Sgilles 
1136c4a8d13Sgilles 	return (0);
1146c4a8d13Sgilles }
1156c4a8d13Sgilles 
1169d327827Sgilles static int
lmtp_connect_inet(const char * destination)1176c4a8d13Sgilles lmtp_connect_inet(const char *destination)
1186c4a8d13Sgilles {
1196c4a8d13Sgilles 	struct addrinfo hints, *res, *res0;
1206c4a8d13Sgilles 	char *destcopy = NULL;
1216c4a8d13Sgilles 	const char *hostname = NULL;
1226c4a8d13Sgilles 	const char *servname = NULL;
1236c4a8d13Sgilles 	const char *cause = NULL;
1246c4a8d13Sgilles 	char *p;
1256c4a8d13Sgilles 	int n, s = -1, save_errno;
1266c4a8d13Sgilles 
1276c4a8d13Sgilles 	if ((destcopy = strdup(destination)) == NULL)
128776f254eStim 		err(EX_TEMPFAIL, NULL);
1296c4a8d13Sgilles 
1306c4a8d13Sgilles 	servname = "25";
1316c4a8d13Sgilles 	hostname = destcopy;
1326c4a8d13Sgilles 	p = destcopy;
1336c4a8d13Sgilles 	if (*p == '[') {
1346c4a8d13Sgilles 		if ((p = strchr(destcopy, ']')) == NULL)
135776f254eStim 			errx(EX_TEMPFAIL, "inet: invalid address syntax");
1366c4a8d13Sgilles 
1376c4a8d13Sgilles 		/* remove [ and ] */
1386c4a8d13Sgilles 		*p = '\0';
1396c4a8d13Sgilles 		hostname++;
1406c4a8d13Sgilles 		if (strncasecmp(hostname, "IPv6:", 5) == 0)
1416c4a8d13Sgilles 			hostname += 5;
1426c4a8d13Sgilles 
1436c4a8d13Sgilles 		/* extract port if any */
1446c4a8d13Sgilles 		switch (*(p+1)) {
1456c4a8d13Sgilles 		case ':':
1466c4a8d13Sgilles 			servname = p+2;
1476c4a8d13Sgilles 			break;
1486c4a8d13Sgilles 		case '\0':
1496c4a8d13Sgilles 			break;
1506c4a8d13Sgilles 		default:
151776f254eStim 			errx(EX_TEMPFAIL, "inet: invalid address syntax");
1526c4a8d13Sgilles 		}
1536c4a8d13Sgilles 	}
1546c4a8d13Sgilles 	else if ((p = strchr(destcopy, ':')) != NULL) {
1556c4a8d13Sgilles 		*p++ = '\0';
1566c4a8d13Sgilles 		servname = p;
1576c4a8d13Sgilles 	}
1586c4a8d13Sgilles 
1596c4a8d13Sgilles 	memset(&hints, 0, sizeof hints);
1606c4a8d13Sgilles 	hints.ai_family = PF_UNSPEC;
1616c4a8d13Sgilles 	hints.ai_socktype = SOCK_STREAM;
1626c4a8d13Sgilles 	hints.ai_flags = AI_NUMERICSERV;
1636c4a8d13Sgilles 	n = getaddrinfo(hostname, servname, &hints, &res0);
1646c4a8d13Sgilles 	if (n)
165776f254eStim 		errx(EX_TEMPFAIL, "inet: %s", gai_strerror(n));
1666c4a8d13Sgilles 
1676c4a8d13Sgilles 	for (res = res0; res; res = res->ai_next) {
1686c4a8d13Sgilles 		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
1696c4a8d13Sgilles 		if (s == -1) {
1706c4a8d13Sgilles 			cause = "socket";
1716c4a8d13Sgilles 			continue;
1726c4a8d13Sgilles 		}
1736c4a8d13Sgilles 
1746c4a8d13Sgilles 		if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
1756c4a8d13Sgilles 			cause = "connect";
1766c4a8d13Sgilles 			save_errno = errno;
1776c4a8d13Sgilles 			close(s);
1786c4a8d13Sgilles 			errno = save_errno;
1796c4a8d13Sgilles 			s = -1;
1806c4a8d13Sgilles 			continue;
1816c4a8d13Sgilles 		}
1826c4a8d13Sgilles 		break;
1836c4a8d13Sgilles 	}
1846c4a8d13Sgilles 
1856c4a8d13Sgilles 	freeaddrinfo(res0);
1866c4a8d13Sgilles 	if (s == -1)
187460f7b42Sgilles 		errx(EX_TEMPFAIL, "%s", cause);
1886c4a8d13Sgilles 
1893131edd7Sgilles 	free(destcopy);
1909d327827Sgilles 	return s;
1916c4a8d13Sgilles }
1926c4a8d13Sgilles 
1939d327827Sgilles static int
lmtp_connect_unix(const char * destination)1946c4a8d13Sgilles lmtp_connect_unix(const char *destination)
1956c4a8d13Sgilles {
1966c4a8d13Sgilles 	struct sockaddr_un addr;
1976c4a8d13Sgilles 	int s;
1986c4a8d13Sgilles 
1996c4a8d13Sgilles 	if (*destination != '/')
200776f254eStim 		errx(EX_TEMPFAIL, "unix: path must be absolute");
2016c4a8d13Sgilles 
2026c4a8d13Sgilles 	if ((s = socket(PF_LOCAL, SOCK_STREAM, 0)) == -1)
203776f254eStim 		err(EX_TEMPFAIL, NULL);
2046c4a8d13Sgilles 
2056c4a8d13Sgilles 	memset(&addr, 0, sizeof addr);
2066c4a8d13Sgilles 	addr.sun_family = AF_UNIX;
2076c4a8d13Sgilles 	if (strlcpy(addr.sun_path, destination, sizeof addr.sun_path)
2086c4a8d13Sgilles 	    >= sizeof addr.sun_path)
209776f254eStim 		errx(EX_TEMPFAIL, "unix: socket path is too long");
2106c4a8d13Sgilles 
2116c4a8d13Sgilles 	if (connect(s, (struct sockaddr *)&addr, sizeof addr) == -1)
212460f7b42Sgilles 		err(EX_TEMPFAIL, "connect");
2136c4a8d13Sgilles 
2149d327827Sgilles 	return s;
2156c4a8d13Sgilles }
2166c4a8d13Sgilles 
2179d327827Sgilles static int
lmtp_connect(const char * destination)2186c4a8d13Sgilles lmtp_connect(const char *destination)
2196c4a8d13Sgilles {
220909acb05Sgilles 	if (destination[0] == '/')
221909acb05Sgilles 		return lmtp_connect_unix(destination);
222909acb05Sgilles 	return lmtp_connect_inet(destination);
2236c4a8d13Sgilles }
2246c4a8d13Sgilles 
2256c4a8d13Sgilles static void
lmtp_engine(int fd_read,struct session * session)2269d327827Sgilles lmtp_engine(int fd_read, struct session *session)
2276c4a8d13Sgilles {
2289d327827Sgilles 	int fd_write = 0;
2299d327827Sgilles 	FILE *file_read = 0;
2309d327827Sgilles 	FILE *file_write = 0;
2316c4a8d13Sgilles 	char *line = NULL;
2326c4a8d13Sgilles 	size_t linesize = 0;
2336c4a8d13Sgilles 	ssize_t linelen;
2346c4a8d13Sgilles 	enum phase phase = PHASE_BANNER;
2356c4a8d13Sgilles 
2369d327827Sgilles 	if ((fd_write = dup(fd_read)) == -1)
2379d327827Sgilles 		err(EX_TEMPFAIL, "dup");
2389d327827Sgilles 
2399d327827Sgilles 	if ((file_read = fdopen(fd_read, "r")) == NULL)
2409d327827Sgilles 		err(EX_TEMPFAIL, "fdopen");
2419d327827Sgilles 
2429d327827Sgilles 	if ((file_write = fdopen(fd_write, "w")) == NULL)
2439d327827Sgilles 		err(EX_TEMPFAIL, "fdopen");
2449d327827Sgilles 
2456c4a8d13Sgilles 	do {
2469d327827Sgilles 		fflush(file_write);
2479d327827Sgilles 
2489d327827Sgilles 		if ((linelen = getline(&line, &linesize, file_read)) == -1) {
2499d327827Sgilles 			if (ferror(file_read))
250776f254eStim 				err(EX_TEMPFAIL, "getline");
251776f254eStim 			else
252776f254eStim 				errx(EX_TEMPFAIL, "unexpected EOF from LMTP server");
253776f254eStim 		}
2546c4a8d13Sgilles 		line[strcspn(line, "\n")] = '\0';
2553520deb8Sgilles 		line[strcspn(line, "\r")] = '\0';
2566c4a8d13Sgilles 
2576c4a8d13Sgilles 		if (linelen < 4 ||
2583d02c75dSmillert 		    !isdigit((unsigned char)line[0]) ||
2593d02c75dSmillert 		    !isdigit((unsigned char)line[1]) ||
2603d02c75dSmillert 		    !isdigit((unsigned char)line[2]) ||
2616c4a8d13Sgilles 		    (line[3] != ' ' && line[3] != '-'))
262776f254eStim 			errx(EX_TEMPFAIL, "LMTP server sent an invalid line");
2636c4a8d13Sgilles 
2646c4a8d13Sgilles 		if (line[0] != (phase == PHASE_DATA ? '3' : '2'))
265776f254eStim 			errx(EX_TEMPFAIL, "LMTP server error: %s", line);
2666c4a8d13Sgilles 
2676c4a8d13Sgilles 		if (line[3] == '-')
2686c4a8d13Sgilles 			continue;
2696c4a8d13Sgilles 
2706c4a8d13Sgilles 		switch (phase) {
2716c4a8d13Sgilles 
2726c4a8d13Sgilles 		case PHASE_BANNER:
2739d327827Sgilles 			fprintf(file_write, "LHLO %s\r\n", session->lhlo);
2746c4a8d13Sgilles 			phase++;
2756c4a8d13Sgilles 			break;
2766c4a8d13Sgilles 
2776c4a8d13Sgilles 		case PHASE_HELO:
2789d327827Sgilles 			fprintf(file_write, "MAIL FROM:<%s>\r\n", session->mailfrom);
2796c4a8d13Sgilles 			phase++;
2806c4a8d13Sgilles 			break;
2816c4a8d13Sgilles 
2826c4a8d13Sgilles 		case PHASE_MAILFROM:
2839d327827Sgilles 			fprintf(file_write, "RCPT TO:<%s>\r\n", session->rcpts[session->n_rcpts - 1]);
2846c4a8d13Sgilles 			if (session->n_rcpts - 1 == 0) {
2856c4a8d13Sgilles 				phase++;
2866c4a8d13Sgilles 				break;
2876c4a8d13Sgilles 			}
2886c4a8d13Sgilles 			session->n_rcpts--;
2896c4a8d13Sgilles 			break;
2906c4a8d13Sgilles 
2916c4a8d13Sgilles 		case PHASE_RCPTTO:
2929d327827Sgilles 			fprintf(file_write, "DATA\r\n");
2936c4a8d13Sgilles 			phase++;
2946c4a8d13Sgilles 			break;
2956c4a8d13Sgilles 
2966c4a8d13Sgilles 		case PHASE_DATA:
2979d327827Sgilles 			stream_file(file_write);
2989d327827Sgilles 			fprintf(file_write, ".\r\n");
2996c4a8d13Sgilles 			phase++;
3006c4a8d13Sgilles 			break;
3016c4a8d13Sgilles 
3026c4a8d13Sgilles 		case PHASE_EOM:
3039d327827Sgilles 			fprintf(file_write, "QUIT\r\n");
3046c4a8d13Sgilles 			phase++;
3056c4a8d13Sgilles 			break;
3066c4a8d13Sgilles 
3076c4a8d13Sgilles 		case PHASE_QUIT:
3086c4a8d13Sgilles 			exit(0);
3096c4a8d13Sgilles 		}
3106c4a8d13Sgilles 	} while (1);
3116c4a8d13Sgilles }
3126c4a8d13Sgilles 
3136c4a8d13Sgilles static void
stream_file(FILE * conn)3146c4a8d13Sgilles stream_file(FILE *conn)
3156c4a8d13Sgilles {
3166c4a8d13Sgilles 	char *line = NULL;
3176c4a8d13Sgilles 	size_t linesize = 0;
3186c4a8d13Sgilles 
319*1997d66fSrob 	while (getline(&line, &linesize, stdin) != -1) {
3206c4a8d13Sgilles 		line[strcspn(line, "\n")] = '\0';
321ccc94729Stim 		if (line[0] == '.')
3226c4a8d13Sgilles 			fprintf(conn, ".");
3236c4a8d13Sgilles 		fprintf(conn, "%s\r\n", line);
3246c4a8d13Sgilles 	}
3256c4a8d13Sgilles 	free(line);
3266c4a8d13Sgilles 	if (ferror(stdin))
327776f254eStim 		err(EX_TEMPFAIL, "getline");
3286c4a8d13Sgilles }
329