xref: /openbsd-src/usr.sbin/smtpd/enqueue.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: enqueue.c,v 1.47 2011/08/29 21:43:08 chl Exp $	*/
2 
3 /*
4  * Copyright (c) 2005 Henning Brauer <henning@bulabula.org>
5  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/param.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
23 #include <sys/tree.h>
24 #include <sys/types.h>
25 
26 #include <ctype.h>
27 #include <err.h>
28 #include <event.h>
29 #include <imsg.h>
30 #include <pwd.h>
31 #include <signal.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 
38 #include "smtpd.h"
39 #include "client.h"
40 
41 extern struct imsgbuf	*ibuf;
42 
43 void usage(void);
44 static void sighdlr(int);
45 static void build_from(char *, struct passwd *);
46 static int parse_message(FILE *, int, int, FILE *);
47 static void parse_addr(char *, size_t, int);
48 static void parse_addr_terminal(int);
49 static char *qualify_addr(char *);
50 static void rcpt_add(char *);
51 static int open_connection(void);
52 static void enqueue_event(int, short, void *);
53 
54 enum headerfields {
55 	HDR_NONE,
56 	HDR_FROM,
57 	HDR_TO,
58 	HDR_CC,
59 	HDR_BCC,
60 	HDR_SUBJECT,
61 	HDR_DATE,
62 	HDR_MSGID
63 };
64 
65 struct {
66 	char			*word;
67 	enum headerfields	 type;
68 } keywords[] = {
69 	{ "From:",		HDR_FROM },
70 	{ "To:",		HDR_TO },
71 	{ "Cc:",		HDR_CC },
72 	{ "Bcc:",		HDR_BCC },
73 	{ "Subject:",		HDR_SUBJECT },
74 	{ "Date:",		HDR_DATE },
75 	{ "Message-Id:",	HDR_MSGID }
76 };
77 
78 #define	SMTP_LINELEN		1000
79 #define	TIMEOUTMSG		"Timeout\n"
80 
81 #define WSP(c)			(c == ' ' || c == '\t')
82 
83 int	  verbose = 0;
84 char	  host[MAXHOSTNAMELEN];
85 char	 *user = NULL;
86 time_t	  timestamp;
87 
88 struct {
89 	int	  fd;
90 	char	 *from;
91 	char	 *fromname;
92 	char	**rcpts;
93 	int	  rcpt_cnt;
94 	char	 *data;
95 	size_t	  len;
96 	int	  saw_date;
97 	int	  saw_msgid;
98 	int	  saw_from;
99 
100 	struct smtp_client	*pcb;
101 	struct event		 ev;
102 } msg;
103 
104 struct {
105 	u_int		quote;
106 	u_int		comment;
107 	u_int		esc;
108 	u_int		brackets;
109 	size_t		wpos;
110 	char		buf[SMTP_LINELEN];
111 } pstate;
112 
113 static void
114 sighdlr(int sig)
115 {
116 	if (sig == SIGALRM) {
117 		write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG));
118 		_exit(2);
119 	}
120 }
121 
122 int
123 enqueue(int argc, char *argv[])
124 {
125 	int			 i, ch, tflag = 0, noheader;
126 	char			*fake_from = NULL;
127 	struct passwd		*pw;
128 	FILE			*fp;
129 
130 	bzero(&msg, sizeof(msg));
131 	time(&timestamp);
132 
133 	while ((ch = getopt(argc, argv,
134 	    "A:B:b:E::e:F:f:iJ::L:mo:p:qtvx")) != -1) {
135 		switch (ch) {
136 		case 'f':
137 			fake_from = optarg;
138 			break;
139 		case 'F':
140 			msg.fromname = optarg;
141 			break;
142 		case 't':
143 			tflag = 1;
144 			break;
145 		case 'v':
146 			verbose = 1;
147 			break;
148 		/* all remaining: ignored, sendmail compat */
149 		case 'A':
150 		case 'B':
151 		case 'b':
152 		case 'E':
153 		case 'e':
154 		case 'i':
155 		case 'L':
156 		case 'm':
157 		case 'o':
158 		case 'p':
159 		case 'x':
160 			break;
161 		case 'q':
162 			/* XXX: implement "process all now" */
163 			return (0);
164 		default:
165 			usage();
166 		}
167 	}
168 
169 	argc -= optind;
170 	argv += optind;
171 
172 	if (gethostname(host, sizeof(host)) == -1)
173 		err(1, "gethostname");
174 	if ((pw = getpwuid(getuid())) == NULL)
175 		user = "anonymous";
176 	if (pw != NULL && (user = strdup(pw->pw_name)) == NULL)
177 		err(1, "strdup");
178 
179 	build_from(fake_from, pw);
180 
181 	while(argc > 0) {
182 		rcpt_add(argv[0]);
183 		argv++;
184 		argc--;
185 	}
186 
187 	signal(SIGALRM, sighdlr);
188 	alarm(300);
189 
190 	fp = tmpfile();
191 	if (fp == NULL)
192 		err(1, "tmpfile");
193 	noheader = parse_message(stdin, fake_from == NULL, tflag, fp);
194 
195 	if ((msg.fd = open_connection()) == -1)
196 		errx(1, "server too busy");
197 
198 	/* init session */
199 	rewind(fp);
200 	msg.pcb = client_init(msg.fd, fp, "localhost", verbose);
201 
202 	/* set envelope from */
203 	client_sender(msg.pcb, "%s", msg.from);
204 
205 	/* add recipients */
206 	if (msg.rcpt_cnt == 0)
207 		errx(1, "no recipients");
208 	for (i = 0; i < msg.rcpt_cnt; i++)
209 		client_rcpt(msg.pcb, "%s", msg.rcpts[i]);
210 
211 	/* add From */
212 	if (!msg.saw_from)
213 		client_printf(msg.pcb, "From: %s%s<%s>\n",
214 		    msg.fromname ? msg.fromname : "",
215 		    msg.fromname ? " " : "",
216 		    msg.from);
217 
218 	/* add Date */
219 	if (!msg.saw_date)
220 		client_printf(msg.pcb, "Date: %s\n", time_to_text(timestamp));
221 
222 	/* add Message-Id */
223 	if (!msg.saw_msgid)
224 		client_printf(msg.pcb, "Message-Id: <%llu.enqueue@%s>\n",
225 		    generate_uid(), host);
226 
227 	/* add separating newline */
228 	if (noheader)
229 		client_printf(msg.pcb, "\n");
230 
231 	alarm(0);
232 	event_init();
233 	session_socket_blockmode(msg.fd, BM_NONBLOCK);
234 	event_set(&msg.ev, msg.fd, EV_READ|EV_WRITE, enqueue_event, NULL);
235 	event_add(&msg.ev, &msg.pcb->timeout);
236 
237 	if (event_dispatch() < 0)
238 		err(1, "event_dispatch");
239 
240 	client_close(msg.pcb);
241 	fclose(fp);
242 	exit(0);
243 }
244 
245 static void
246 enqueue_event(int fd, short event, void *p)
247 {
248 	if (event & EV_TIMEOUT)
249 		errx(1, "timeout");
250 
251 	switch (client_talk(msg.pcb, event & EV_WRITE)) {
252 	case CLIENT_WANT_WRITE:
253 		goto rw;
254 	case CLIENT_STOP_WRITE:
255 		goto ro;
256 	case CLIENT_RCPT_FAIL:
257 		errx(1, "%s", msg.pcb->reply);
258 	case CLIENT_DONE:
259 		break;
260 	default:
261 		errx(1, "enqueue_event: unexpected code");
262 	}
263 
264 	if (msg.pcb->status[0] != '2')
265 		errx(1, "%s", msg.pcb->status);
266 
267 	event_loopexit(NULL);
268 	return;
269 
270 ro:
271 	event_set(&msg.ev, msg.fd, EV_READ, enqueue_event, NULL);
272 	event_add(&msg.ev, &msg.pcb->timeout);
273 	return;
274 
275 rw:
276 	event_set(&msg.ev, msg.fd, EV_READ|EV_WRITE, enqueue_event, NULL);
277 	event_add(&msg.ev, &msg.pcb->timeout);
278 }
279 
280 static void
281 build_from(char *fake_from, struct passwd *pw)
282 {
283 	char	*p;
284 
285 	if (fake_from == NULL)
286 		msg.from = qualify_addr(user);
287 	else {
288 		if (fake_from[0] == '<') {
289 			if (fake_from[strlen(fake_from) - 1] != '>')
290 				errx(1, "leading < but no trailing >");
291 			fake_from[strlen(fake_from) - 1] = 0;
292 			if ((p = malloc(strlen(fake_from))) == NULL)
293 				err(1, "malloc");
294 			strlcpy(p, fake_from + 1, strlen(fake_from));
295 
296 			msg.from = qualify_addr(p);
297 			free(p);
298 		} else
299 			msg.from = qualify_addr(fake_from);
300 	}
301 
302 	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
303 		int	 len, apos;
304 
305 		len = strcspn(pw->pw_gecos, ",");
306 		if ((p = memchr(pw->pw_gecos, '&', len))) {
307 			apos = p - pw->pw_gecos;
308 			if (asprintf(&msg.fromname, "%.*s%s%.*s",
309 			    apos, pw->pw_gecos,
310 			    pw->pw_name,
311 			    len - apos - 1, p + 1) == -1)
312 				err(1, NULL);
313 			msg.fromname[apos] = toupper(msg.fromname[apos]);
314 		} else {
315 			if (asprintf(&msg.fromname, "%.*s", len,
316 			    pw->pw_gecos) == -1)
317 				err(1, NULL);
318 		}
319 	}
320 }
321 
322 static int
323 parse_message(FILE *fin, int get_from, int tflag, FILE *fout)
324 {
325 	char	*buf;
326 	size_t	 len;
327 	u_int	 i, cur = HDR_NONE;
328 	u_int	 header_seen = 0, header_done = 0;
329 
330 	bzero(&pstate, sizeof(pstate));
331 	for (;;) {
332 		buf = fgetln(fin, &len);
333 		if (buf == NULL && ferror(fin))
334 			err(1, "fgetln");
335 		if (buf == NULL && feof(fin))
336 			break;
337 		if (buf == NULL || len < 1)
338 			err(1, "fgetln weird");
339 
340 		/* account for \r\n linebreaks */
341 		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
342 			buf[--len - 1] = '\n';
343 
344 		if (len == 1 && buf[0] == '\n')		/* end of header */
345 			header_done = 1;
346 
347 		if (!WSP(buf[0])) {	/* whitespace -> continuation */
348 			if (cur == HDR_FROM)
349 				parse_addr_terminal(1);
350 			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
351 				parse_addr_terminal(0);
352 			cur = HDR_NONE;
353 		}
354 
355 		for (i = 0; !header_done && cur == HDR_NONE &&
356 		    i < nitems(keywords); i++)
357 			if (len > strlen(keywords[i].word) &&
358 			    !strncasecmp(buf, keywords[i].word,
359 			    strlen(keywords[i].word)))
360 				cur = keywords[i].type;
361 
362 		if (cur != HDR_NONE)
363 			header_seen = 1;
364 
365 		if (cur != HDR_BCC) {
366 			fprintf(fout, "%.*s", (int)len, buf);
367 			if (buf[len - 1] != '\n')
368 				fputc('\n', fout);
369 			if (ferror(fout))
370 				err(1, "write error");
371 		}
372 
373 		/*
374 		 * using From: as envelope sender is not sendmail compatible,
375 		 * but I really want it that way - maybe needs a knob
376 		 */
377 		if (cur == HDR_FROM) {
378 			msg.saw_from++;
379 			if (get_from)
380 				parse_addr(buf, len, 1);
381 		}
382 
383 		if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
384 			parse_addr(buf, len, 0);
385 
386 		if (cur == HDR_DATE)
387 			msg.saw_date++;
388 		if (cur == HDR_MSGID)
389 			msg.saw_msgid++;
390 	}
391 
392 	return (!header_seen);
393 }
394 
395 static void
396 parse_addr(char *s, size_t len, int is_from)
397 {
398 	size_t	 pos = 0;
399 	int	 terminal = 0;
400 
401 	/* unless this is a continuation... */
402 	if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
403 		/* ... skip over everything before the ':' */
404 		for (; pos < len && s[pos] != ':'; pos++)
405 			;	/* nothing */
406 		/* ... and check & reset parser state */
407 		parse_addr_terminal(is_from);
408 	}
409 
410 	/* skip over ':' ',' ';' and whitespace */
411 	for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
412 	    s[pos] == ',' || s[pos] == ';'); pos++)
413 		;	/* nothing */
414 
415 	for (; pos < len; pos++) {
416 		if (!pstate.esc && !pstate.quote && s[pos] == '(')
417 			pstate.comment++;
418 		if (!pstate.comment && !pstate.esc && s[pos] == '"')
419 			pstate.quote = !pstate.quote;
420 
421 		if (!pstate.comment && !pstate.quote && !pstate.esc) {
422 			if (s[pos] == ':') {	/* group */
423 				for(pos++; pos < len && WSP(s[pos]); pos++)
424 					;	/* nothing */
425 				pstate.wpos = 0;
426 			}
427 			if (s[pos] == '\n' || s[pos] == '\r')
428 				break;
429 			if (s[pos] == ',' || s[pos] == ';') {
430 				terminal = 1;
431 				break;
432 			}
433 			if (s[pos] == '<') {
434 				pstate.brackets = 1;
435 				pstate.wpos = 0;
436 			}
437 			if (pstate.brackets && s[pos] == '>')
438 				terminal = 1;
439 		}
440 
441 		if (!pstate.comment && !terminal && (!(!(pstate.quote ||
442 		    pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
443 			if (pstate.wpos >= sizeof(pstate.buf))
444 				errx(1, "address exceeds buffer size");
445 			pstate.buf[pstate.wpos++] = s[pos];
446 		}
447 
448 		if (!pstate.quote && pstate.comment && s[pos] == ')')
449 			pstate.comment--;
450 
451 		if (!pstate.esc && !pstate.comment && !pstate.quote &&
452 		    s[pos] == '\\')
453 			pstate.esc = 1;
454 		else
455 			pstate.esc = 0;
456 	}
457 
458 	if (terminal)
459 		parse_addr_terminal(is_from);
460 
461 	for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
462 		;	/* nothing */
463 
464 	if (pos < len)
465 		parse_addr(s + pos, len - pos, is_from);
466 }
467 
468 static void
469 parse_addr_terminal(int is_from)
470 {
471 	if (pstate.comment || pstate.quote || pstate.esc)
472 		errx(1, "syntax error in address");
473 	if (pstate.wpos) {
474 		if (pstate.wpos >= sizeof(pstate.buf))
475 			errx(1, "address exceeds buffer size");
476 		pstate.buf[pstate.wpos] = '\0';
477 		if (is_from)
478 			msg.from = qualify_addr(pstate.buf);
479 		else
480 			rcpt_add(pstate.buf);
481 		pstate.wpos = 0;
482 	}
483 }
484 
485 static char *
486 qualify_addr(char *in)
487 {
488 	char	*out;
489 
490 	if (strlen(in) > 0 && strchr(in, '@') == NULL) {
491 		if (asprintf(&out, "%s@%s", in, host) == -1)
492 			err(1, "qualify asprintf");
493 	} else
494 		if ((out = strdup(in)) == NULL)
495 			err(1, "qualify strdup");
496 
497 	return (out);
498 }
499 
500 static void
501 rcpt_add(char *addr)
502 {
503 	void	*nrcpts;
504 
505 	if ((nrcpts = realloc(msg.rcpts,
506 	    sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL)
507 		err(1, "rcpt_add realloc");
508 	msg.rcpts = nrcpts;
509 	msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
510 }
511 
512 static int
513 open_connection(void)
514 {
515 	struct imsg	imsg;
516 	int		fd;
517 	int		n;
518 
519 	imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0);
520 
521 	while (ibuf->w.queued)
522 		if (msgbuf_write(&ibuf->w) < 0)
523 			err(1, "write error");
524 
525 	while (1) {
526 		if ((n = imsg_read(ibuf)) == -1)
527 			errx(1, "imsg_read error");
528 		if (n == 0)
529 			errx(1, "pipe closed");
530 
531 		if ((n = imsg_get(ibuf, &imsg)) == -1)
532 			errx(1, "imsg_get error");
533 		if (n == 0)
534 			continue;
535 
536 		switch (imsg.hdr.type) {
537 		case IMSG_CTL_OK:
538 			break;
539 		case IMSG_CTL_FAIL:
540 			errx(1, "server disallowed submission request");
541 		default:
542 			errx(1, "unexpected imsg reply type");
543 		}
544 
545 		fd = imsg.fd;
546 		imsg_free(&imsg);
547 
548 		break;
549 	}
550 
551 	return fd;
552 }
553 
554 int
555 enqueue_offline(int argc, char *argv[])
556 {
557 	char	 path[MAXPATHLEN];
558 	FILE	*fp;
559 	int	 i, fd, ch;
560 
561 	if (! bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL,
562 		PATH_OFFLINE, (long long int) time(NULL)))
563 		err(1, "snprintf");
564 
565 	if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
566 		warn("cannot create temporary file %s", path);
567 		if (fd != -1)
568 			unlink(path);
569 		exit(1);
570 	}
571 
572 	for (i = 1; i < argc; i++) {
573 		if (strchr(argv[i], '|') != NULL) {
574 			warnx("%s contains illegal character", argv[i]);
575 			unlink(path);
576 			exit(1);
577 		}
578 		fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]);
579 	}
580 
581 	fprintf(fp, "\n");
582 
583 	while ((ch = fgetc(stdin)) != EOF)
584 		if (fputc(ch, fp) == EOF) {
585 			warn("write error");
586 			unlink(path);
587 			exit(1);
588 		}
589 
590 	if (ferror(stdin)) {
591 		warn("read error");
592 		unlink(path);
593 		exit(1);
594 	}
595 
596 	fclose(fp);
597 
598 	return (0);
599 }
600