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