xref: /openbsd-src/usr.sbin/smtpd/enqueue.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: enqueue.c,v 1.14 2009/04/21 18:12:05 jacekm Exp $	*/
2 
3 /*
4  * Copyright (c) 2005 Henning Brauer <henning@bulabula.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/tree.h>
23 #include <sys/types.h>
24 
25 #include <ctype.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <event.h>
29 #include <netdb.h>
30 #include <pwd.h>
31 #include <signal.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "smtpd.h"
39 
40 extern struct imsgbuf	*ibuf;
41 
42 void	 usage(void);
43 void	 sighdlr(int);
44 int	 main(int, char *[]);
45 void	 femail_write(const void *, size_t);
46 void	 femail_put(const char *, ...);
47 void	 send_cmd(const char *);
48 void	 build_from(char *, struct passwd *);
49 int	 parse_message(FILE *, int, int);
50 void	 parse_addr(char *, size_t, int);
51 void	 parse_addr_terminal(int);
52 char	*qualify_addr(char *);
53 void	 rcpt_add(char *);
54 void	 received(void);
55 int	 open_connection(void);
56 int	 read_reply(void);
57 void	 greeting(int);
58 void	 mailfrom(char *);
59 void	 rcptto(char *);
60 void	 start_data(void);
61 void	 send_message(int);
62 void	 end_data(void);
63 
64 enum headerfields {
65 	HDR_NONE,
66 	HDR_FROM,
67 	HDR_TO,
68 	HDR_CC,
69 	HDR_BCC,
70 	HDR_SUBJECT,
71 	HDR_DATE,
72 	HDR_MSGID
73 };
74 
75 struct {
76 	char			*word;
77 	enum headerfields	 type;
78 } keywords[] = {
79 	{ "From:",		HDR_FROM },
80 	{ "To:",		HDR_TO },
81 	{ "Cc:",		HDR_CC },
82 	{ "Bcc:",		HDR_BCC },
83 	{ "Subject:",		HDR_SUBJECT },
84 	{ "Date:",		HDR_DATE },
85 	{ "Message-Id:",	HDR_MSGID }
86 };
87 
88 #define	STATUS_GREETING		220
89 #define	STATUS_HELO		250
90 #define	STATUS_MAILFROM		250
91 #define	STATUS_RCPTTO		250
92 #define	STATUS_DATA		354
93 #define	STATUS_QUEUED		250
94 #define	STATUS_QUIT		221
95 #define	SMTP_LINELEN		1000
96 #define	SMTP_TIMEOUT		120
97 #define	TIMEOUTMSG		"Timeout\n"
98 
99 #define WSP(c)			(c == ' ' || c == '\t')
100 
101 int	  verbose = 0;
102 char	  host[MAXHOSTNAMELEN];
103 char	 *user = NULL;
104 time_t	  timestamp;
105 
106 struct {
107 	int	  fd;
108 	char	 *from;
109 	char	 *fromname;
110 	char	**rcpts;
111 	int	  rcpt_cnt;
112 	char	 *data;
113 	size_t	  len;
114 	int	  saw_date;
115 	int	  saw_msgid;
116 	int	  saw_from;
117 } msg;
118 
119 struct {
120 	u_int		quote;
121 	u_int		comment;
122 	u_int		esc;
123 	u_int		brackets;
124 	size_t		wpos;
125 	char		buf[SMTP_LINELEN];
126 } pstate;
127 
128 void
129 sighdlr(int sig)
130 {
131 	if (sig == SIGALRM) {
132 		write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG));
133 		_exit (2);
134 	}
135 }
136 
137 int
138 enqueue(int argc, char *argv[])
139 {
140 	int		 i, ch, tflag = 0, status, noheader;
141 	char		*fake_from = NULL;
142 	struct passwd	*pw;
143 
144 	bzero(&msg, sizeof(msg));
145 	time(&timestamp);
146 
147 	while ((ch = getopt(argc, argv, "46B:b:E::e:F:f:iJ::mo:p:tvx")) != -1) {
148 		switch (ch) {
149 		case 'f':
150 			fake_from = optarg;
151 			break;
152 		case 'F':
153 			msg.fromname = optarg;
154 			break;
155 		case 't':
156 			tflag = 1;
157 			break;
158 		case 'v':
159 			verbose = 1;
160 			break;
161 		/* all remaining: ignored, sendmail compat */
162 		case 'B':
163 		case 'b':
164 		case 'E':
165 		case 'e':
166 		case 'i':
167 		case 'm':
168 		case 'o':
169 		case 'p':
170 		case 'x':
171 			break;
172 		default:
173 			usage();
174 		}
175 	}
176 
177 	argc -= optind;
178 	argv += optind;
179 
180 	if (gethostname(host, sizeof(host)) == -1)
181 		err(1, "gethostname");
182 	if ((pw = getpwuid(getuid())) == NULL)
183 		user = "anonymous";
184 	if (pw != NULL && (user = strdup(pw->pw_name)) == NULL)
185 		err(1, "strdup");
186 
187 	build_from(fake_from, pw);
188 
189 	while(argc > 0) {
190 		rcpt_add(argv[0]);
191 		argv++;
192 		argc--;
193 	}
194 
195 	noheader = parse_message(stdin, fake_from == NULL, tflag);
196 
197 	if (msg.rcpt_cnt == 0)
198 		errx(1, "no recipients");
199 
200 	signal(SIGALRM, sighdlr);
201 	alarm(SMTP_TIMEOUT);
202 
203 	msg.fd = open_connection();
204 	if ((status = read_reply()) != STATUS_GREETING)
205 		errx(1, "server greets us with status %d", status);
206 	greeting(1);
207 	mailfrom(msg.from);
208 	for (i = 0; i < msg.rcpt_cnt; i++)
209 		rcptto(msg.rcpts[i]);
210 	start_data();
211 	send_message(noheader);
212 	end_data();
213 
214 	close(msg.fd);
215 	exit (0);
216 }
217 
218 void
219 femail_write(const void *buf, size_t nbytes)
220 {
221 	ssize_t	n;
222 
223 	do {
224 		n = write(msg.fd, buf, nbytes);
225 	} while (n == -1 && errno == EINTR);
226 
227 	if (n == 0)
228 		errx(1, "write: connection closed");
229 	if (n == -1)
230 		err(1, "write");
231 	if ((size_t)n < nbytes)
232 		errx(1, "short write: %ld of %lu bytes written",
233 		    (long)n, (u_long)nbytes);
234 }
235 
236 void
237 femail_put(const char *fmt, ...)
238 {
239 	va_list	ap;
240 	char	buf[SMTP_LINELEN];
241 
242 	va_start(ap, fmt);
243 	if (vsnprintf(buf, sizeof(buf), fmt, ap) >= (int)sizeof(buf))
244 		errx(1, "line length exceeded");
245 	va_end(ap);
246 
247 	femail_write(buf, strlen(buf));
248 }
249 
250 void
251 send_cmd(const char *cmd)
252 {
253 	if (verbose)
254 		printf(">>> %s\n", cmd);
255 
256 	femail_put("%s\r\n", cmd);
257 }
258 
259 void
260 build_from(char *fake_from, struct passwd *pw)
261 {
262 	char	*p;
263 
264 	if (fake_from == NULL)
265 		msg.from = qualify_addr(user);
266 	else {
267 		if (fake_from[0] == '<') {
268 			if (fake_from[strlen(fake_from) - 1] != '>')
269 				errx(1, "leading < but no trailing >");
270 			fake_from[strlen(fake_from) - 1] = 0;
271 			if ((p = malloc(strlen(fake_from))) == NULL)
272 				err(1, "malloc");
273 			strlcpy(p, fake_from + 1, strlen(fake_from));
274 
275 			msg.from = qualify_addr(p);
276 			free(p);
277 		} else
278 			msg.from = qualify_addr(fake_from);
279 	}
280 
281 	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
282 		size_t		 len;
283 
284 		len = strcspn(pw->pw_gecos, ",");
285 		len++;	/* null termination */
286 		if ((msg.fromname = malloc(len)) == NULL)
287 			err(1, NULL);
288 		strlcpy(msg.fromname, pw->pw_gecos, len);
289 	}
290 }
291 
292 int
293 parse_message(FILE *fin, int get_from, int tflag)
294 {
295 	char	*buf, *twodots = "..";
296 	size_t	 len, new_len;
297 	void	*newp;
298 	u_int	 i, cur = HDR_NONE, dotonly;
299 	u_int	 header_seen = 0, header_done = 0;
300 
301 	bzero(&pstate, sizeof(pstate));
302 	for (;;) {
303 		buf = fgetln(fin, &len);
304 		if (buf == NULL && ferror(fin))
305 			err(1, "fgetln");
306 		if (buf == NULL && feof(fin))
307 			break;
308 
309 		/* account for \r\n linebreaks */
310 		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
311 			buf[--len - 1] = '\n';
312 
313 		if (len == 1 && buf[0] == '\n')		/* end of header */
314 			header_done = 1;
315 
316 		if (buf == NULL || len < 1)
317 			err(1, "fgetln weird");
318 
319 		if (!WSP(buf[0])) {	/* whitespace -> continuation */
320 			if (cur == HDR_FROM)
321 				parse_addr_terminal(1);
322 			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
323 				parse_addr_terminal(0);
324 			cur = HDR_NONE;
325 		}
326 
327 		for (i = 0; !header_done && cur == HDR_NONE &&
328 		    i < (sizeof(keywords) / sizeof(keywords[0])); i++)
329 			if (len > strlen(keywords[i].word) &&
330 			    !strncasecmp(buf, keywords[i].word,
331 			    strlen(keywords[i].word)))
332 				cur = keywords[i].type;
333 
334 		if (cur != HDR_NONE)
335 			header_seen = 1;
336 
337 		if (cur != HDR_BCC) {
338 			/* save data, \n -> \r\n, . -> .. */
339 			if (buf[len - 1] == '\n')
340 				new_len = msg.len + len + 1;
341 			else
342 				new_len = msg.len + len + 2;
343 
344 			if ((len == 1 && buf[0] == '.') ||
345 			    (len > 1 && buf[0] == '.' && buf[1] == '\n')) {
346 				dotonly = 1;
347 				new_len++;
348 			} else
349 				dotonly = 0;
350 
351 			if ((newp = realloc(msg.data, new_len)) == NULL)
352 				err(1, "realloc header");
353 			msg.data = newp;
354 			if (dotonly)
355 				memcpy(msg.data + msg.len, twodots, 2);
356 			else
357 				memcpy(msg.data + msg.len, buf, len);
358 			msg.len = new_len;
359 			msg.data[msg.len - 2] = '\r';
360 			msg.data[msg.len - 1] = '\n';
361 		}
362 
363 		/*
364 		 * using From: as envelope sender is not sendmail compatible,
365 		 * but I really want it that way - maybe needs a knob
366 		 */
367 		if (cur == HDR_FROM) {
368 			msg.saw_from++;
369 			if (get_from)
370 				parse_addr(buf, len, 1);
371 		}
372 
373 		if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
374 			parse_addr(buf, len, 0);
375 
376 		if (cur == HDR_DATE)
377 			msg.saw_date++;
378 		if (cur == HDR_MSGID)
379 			msg.saw_msgid++;
380 	}
381 
382 	return (!header_seen);
383 }
384 
385 void
386 parse_addr(char *s, size_t len, int is_from)
387 {
388 	size_t	 pos = 0;
389 	int	 terminal = 0;
390 
391 	/* unless this is a continuation... */
392 	if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
393 		/* ... skip over everything before the ':' */
394 		for (; pos < len && s[pos] != ':'; pos++)
395 			;	/* nothing */
396 		/* ... and check & reset parser state */
397 		parse_addr_terminal(is_from);
398 	}
399 
400 	/* skip over ':' ',' ';' and whitespace */
401 	for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
402 	    s[pos] == ',' || s[pos] == ';'); pos++)
403 		;	/* nothing */
404 
405 	for (; pos < len; pos++) {
406 		if (!pstate.esc && !pstate.quote && s[pos] == '(')
407 			pstate.comment++;
408 		if (!pstate.comment && !pstate.esc && s[pos] == '"')
409 			pstate.quote = !pstate.quote;
410 
411 		if (!pstate.comment && !pstate.quote && !pstate.esc) {
412 			if (s[pos] == ':') {	/* group */
413 				for(pos++; pos < len && WSP(s[pos]); pos++)
414 					;	/* nothing */
415 				pstate.wpos = 0;
416 			}
417 			if (s[pos] == '\n' || s[pos] == '\r')
418 				break;
419 			if (s[pos] == ',' || s[pos] == ';') {
420 				terminal = 1;
421 				break;
422 			}
423 			if (s[pos] == '<') {
424 				pstate.brackets = 1;
425 				pstate.wpos = 0;
426 			}
427 			if (pstate.brackets && s[pos] == '>')
428 				terminal = 1;
429 		}
430 
431 		if (!pstate.comment && !terminal && (!(!(pstate.quote ||
432 		    pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
433 			if (pstate.wpos >= sizeof(pstate.buf))
434 				errx(1, "address exceeds buffer size");
435 			pstate.buf[pstate.wpos++] = s[pos];
436 		}
437 
438 		if (!pstate.quote && pstate.comment && s[pos] == ')')
439 			pstate.comment--;
440 
441 		if (!pstate.esc && !pstate.comment && !pstate.quote &&
442 		    s[pos] == '\\')
443 			pstate.esc = 1;
444 		else
445 			pstate.esc = 0;
446 	}
447 
448 	if (terminal)
449 		parse_addr_terminal(is_from);
450 
451 	for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
452 		;	/* nothing */
453 
454 	if (pos < len)
455 		parse_addr(s + pos, len - pos, is_from);
456 }
457 
458 void
459 parse_addr_terminal(int is_from)
460 {
461 	if (pstate.comment || pstate.quote || pstate.esc)
462 		errx(1, "syntax error in address");
463 	if (pstate.wpos) {
464 		if (pstate.wpos >= sizeof(pstate.buf))
465 			errx(1, "address exceeds buffer size");
466 		pstate.buf[pstate.wpos] = '\0';
467 		if (is_from)
468 			msg.from = qualify_addr(pstate.buf);
469 		else
470 			rcpt_add(pstate.buf);
471 		pstate.wpos = 0;
472 	}
473 }
474 
475 char *
476 qualify_addr(char *in)
477 {
478 	char	*out;
479 
480 	if (strchr(in, '@') == NULL) {
481 		if (asprintf(&out, "%s@%s", in, host) == -1)
482 			err(1, "qualify asprintf");
483 	} else
484 		if ((out = strdup(in)) == NULL)
485 			err(1, "qualify strdup");
486 
487 	return (out);
488 }
489 
490 void
491 rcpt_add(char *addr)
492 {
493 	void	*nrcpts;
494 
495 	if ((nrcpts = realloc(msg.rcpts,
496 	    sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL)
497 		err(1, "rcpt_add realloc");
498 	msg.rcpts = nrcpts;
499 	msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
500 }
501 
502 void
503 received(void)
504 {
505 	femail_put(
506 	    "Received: (from %s@%s, uid %lu)\r\n\tby %s\r\n\t%s\r\n",
507 	    user, "localhost", (u_long)getuid(), host, time_to_text(timestamp));
508 }
509 
510 int
511 open_connection(void)
512 {
513 	struct imsg	imsg;
514 	int		fd;
515 	int		n;
516 
517 	imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0);
518 
519 	while (ibuf->w.queued)
520 		if (msgbuf_write(&ibuf->w) < 0)
521 			err(1, "write error");
522 
523 	while (1) {
524 		if ((n = imsg_read(ibuf)) == -1)
525 			errx(1, "imsg_read error");
526 		if (n == 0)
527 			errx(1, "pipe closed");
528 
529 		if ((n = imsg_get(ibuf, &imsg)) == -1)
530 			errx(1, "imsg_get error");
531 		if (n == 0)
532 			continue;
533 
534 		fd = imsg_get_fd(ibuf, &imsg);
535 		imsg_free(&imsg);
536 
537 		break;
538 	}
539 
540 	return fd;
541 }
542 
543 int
544 read_reply(void)
545 {
546 	char		*lbuf = NULL;
547 	size_t		 len, pos, spos;
548 	long		 status = 0;
549 	char		 buf[BUFSIZ];
550 	ssize_t		 rlen;
551 	int		 done = 0;
552 
553 	for (len = pos = spos = 0; !done;) {
554 		if (pos == 0 ||
555 		    (pos > 0 && memchr(buf + pos, '\n', len - pos) == NULL)) {
556 			memmove(buf, buf + pos, len - pos);
557 			len -= pos;
558 			pos = 0;
559 			if ((rlen = read(msg.fd, buf + len,
560 			    sizeof(buf) - len)) == -1)
561 				err(1, "read");
562 			len += rlen;
563 		}
564 		spos = pos;
565 
566 		/* status code */
567 		for (; pos < len && buf[pos] >= '0' && buf[pos] <= '9'; pos++)
568 			;	/* nothing */
569 
570 		if (pos == len)
571 			return (0);
572 
573 		if (buf[pos] == ' ')
574 			done = 1;
575 		else if (buf[pos] != '-')
576 			errx(1, "invalid syntax in reply from server");
577 
578 		/* skip up to \n */
579 		for (; pos < len && buf[pos - 1] != '\n'; pos++)
580 			;	/* nothing */
581 
582 		if (verbose) {
583 			size_t	clen;
584 
585 			clen = pos - spos + 1;	/* + 1 for trailing \0 */
586 			if (buf[pos - 1] == '\n')
587 				clen--;
588 			if (buf[pos - 2] == '\r')
589 				clen--;
590 			if ((lbuf = malloc(clen)) == NULL)
591 				err(1, NULL);
592 			strlcpy(lbuf, buf + spos, clen);
593 			printf("<<< %s\n", lbuf);
594 			free(lbuf);
595 		}
596 	}
597 
598 	status = strtol(buf, NULL, 10);
599 	if (status < 100 || status > 999)
600 		errx(1, "error reading status: out of range");
601 
602 	return (status);
603 }
604 
605 void
606 greeting(int use_ehlo)
607 {
608 	int	 status;
609 	char	*cmd, *how;
610 
611 	if (use_ehlo)
612 		how = "EHLO";
613 	else
614 		how = "HELO";
615 
616 	if (asprintf(&cmd, "%s %s", how, host) == -1)
617 		err(1, "asprintf");
618 	send_cmd(cmd);
619 	free(cmd);
620 
621 	if ((status = read_reply()) != STATUS_HELO) {
622 		if (use_ehlo)
623 			greeting(0);
624 		else
625 			errx(1, "remote host refuses our greeting");
626 	}
627 }
628 
629 void
630 mailfrom(char *addr)
631 {
632 	int	 status;
633 	char	*cmd;
634 
635 	if (asprintf(&cmd, "MAIL FROM:<%s>", addr) == -1)
636 		err(1, "asprintf");
637 	send_cmd(cmd);
638 	free(cmd);
639 
640 	if ((status = read_reply()) != STATUS_MAILFROM)
641 		errx(1, "mail from %s refused by server", addr);
642 }
643 
644 void
645 rcptto(char *addr)
646 {
647 	int	 status;
648 	char	*cmd;
649 
650 	if (asprintf(&cmd, "RCPT TO:<%s>", addr) == -1)
651 		err(1, "asprintf");
652 	send_cmd(cmd);
653 	free(cmd);
654 
655 	if ((status = read_reply()) != STATUS_RCPTTO)
656 		errx(1, "rcpt to %s refused by server", addr);
657 }
658 
659 void
660 start_data(void)
661 {
662 	int	 status;
663 
664 	send_cmd("DATA");
665 
666 	if ((status = read_reply()) != STATUS_DATA)
667 		errx(1, "server sends error after DATA");
668 }
669 
670 void
671 send_message(int noheader)
672 {
673 	/* our own headers */
674 	received();
675 
676 	if (!msg.saw_from) {
677 		if (msg.fromname != NULL)
678 			femail_put("From: %s <%s>\r\n", msg.fromname, msg.from);
679 		else
680 			femail_put("From: %s\r\n", msg.from);
681 	}
682 
683 	if (!msg.saw_date)
684 		femail_put("Date: %s\r\n", time_to_text(timestamp));
685 
686 	if (!msg.saw_msgid)
687 		femail_put("Message-Id: <%llu.enqueue@%s>\r\n",
688 		    queue_generate_id(), host);
689 
690 	if (noheader)
691 		femail_write("\r\n", 2);
692 
693 	if (msg.data != NULL)
694 		femail_write(msg.data, msg.len);
695 }
696 
697 void
698 end_data(void)
699 {
700 	int	status;
701 
702 	femail_write(".\r\n", 3);
703 
704 	if ((status = read_reply()) != STATUS_QUEUED)
705 		errx(1, "error after sending mail, got status %d", status);
706 
707 	send_cmd("QUIT");
708 
709 	if ((status = read_reply()) != STATUS_QUIT)
710 		errx(1, "server sends error after QUIT");
711 }
712 
713 int
714 enqueue_offline(int argc, char *argv[])
715 {
716 	char	 path[MAXPATHLEN];
717 	FILE	*fp;
718 	int	 i, fd, ch;
719 
720 	if (! bsnprintf(path, sizeof(path), "%s%s/%d,XXXXXXXXXX", PATH_SPOOL,
721 		PATH_OFFLINE, time(NULL)))
722 		err(1, "snprintf");
723 
724 	if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
725 		warn("cannot create temporary file %s", path);
726 		if (fd != -1)
727 			unlink(path);
728 		exit(1);
729 	}
730 
731 	for (i = 1; i < argc; i++) {
732 		if (strchr(argv[i], '|') != NULL) {
733 			warnx("%s contains illegal character", argv[i]);
734 			unlink(path);
735 			exit(1);
736 		}
737 		fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]);
738 	}
739 
740 	fprintf(fp, "\n");
741 
742 	while ((ch = fgetc(stdin)) != EOF)
743 		if (fputc(ch, fp) == EOF) {
744 			warn("write error");
745 			unlink(path);
746 			exit(1);
747 		}
748 
749 	if (ferror(stdin)) {
750 		warn("read error");
751 		unlink(path);
752 		exit(1);
753 	}
754 
755 	fclose(fp);
756 
757 	return (0);
758 }
759