xref: /openbsd-src/usr.sbin/smtpd/enqueue.c (revision 5054e3e78af0749a9bb00ba9a024b3ee2d90290f)
1 /*	$OpenBSD: enqueue.c,v 1.26 2009/11/13 20:34:51 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 <errno.h>
29 #include <event.h>
30 #include <netdb.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38 #include <unistd.h>
39 
40 #include "smtpd.h"
41 #include "client.h"
42 
43 extern struct imsgbuf	*ibuf;
44 
45 void	 usage(void);
46 void	 sighdlr(int);
47 int	 main(int, char *[]);
48 void	 build_from(char *, struct passwd *);
49 int	 parse_message(FILE *, int, int, struct buf *);
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 int	 open_connection(void);
55 
56 enum headerfields {
57 	HDR_NONE,
58 	HDR_FROM,
59 	HDR_TO,
60 	HDR_CC,
61 	HDR_BCC,
62 	HDR_SUBJECT,
63 	HDR_DATE,
64 	HDR_MSGID
65 };
66 
67 struct {
68 	char			*word;
69 	enum headerfields	 type;
70 } keywords[] = {
71 	{ "From:",		HDR_FROM },
72 	{ "To:",		HDR_TO },
73 	{ "Cc:",		HDR_CC },
74 	{ "Bcc:",		HDR_BCC },
75 	{ "Subject:",		HDR_SUBJECT },
76 	{ "Date:",		HDR_DATE },
77 	{ "Message-Id:",	HDR_MSGID }
78 };
79 
80 #define	SMTP_LINELEN		1000
81 #define	SMTP_TIMEOUT		120
82 #define	TIMEOUTMSG		"Timeout\n"
83 
84 #define WSP(c)			(c == ' ' || c == '\t')
85 
86 int	  verbose = 0;
87 char	  host[MAXHOSTNAMELEN];
88 char	 *user = NULL;
89 time_t	  timestamp;
90 
91 struct {
92 	int	  fd;
93 	char	 *from;
94 	char	 *fromname;
95 	char	**rcpts;
96 	int	  rcpt_cnt;
97 	char	 *data;
98 	size_t	  len;
99 	int	  saw_date;
100 	int	  saw_msgid;
101 	int	  saw_from;
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 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, ret;
126 	char			*fake_from = NULL;
127 	struct passwd		*pw;
128 	struct smtp_client	*sp;
129 	struct buf		*body;
130 
131 	bzero(&msg, sizeof(msg));
132 	time(&timestamp);
133 
134 	while ((ch = getopt(argc, argv,
135 	    "A:B:b:E::e:F:f:iJ::L:mo:p:qtvx")) != -1) {
136 		switch (ch) {
137 		case 'f':
138 			fake_from = optarg;
139 			break;
140 		case 'F':
141 			msg.fromname = optarg;
142 			break;
143 		case 't':
144 			tflag = 1;
145 			break;
146 		case 'v':
147 			verbose = 1;
148 			break;
149 		/* all remaining: ignored, sendmail compat */
150 		case 'A':
151 		case 'B':
152 		case 'b':
153 		case 'E':
154 		case 'e':
155 		case 'i':
156 		case 'L':
157 		case 'm':
158 		case 'o':
159 		case 'p':
160 		case 'x':
161 			break;
162 		case 'q':
163 			/* XXX: implement "process all now" */
164 			return (0);
165 		default:
166 			usage();
167 		}
168 	}
169 
170 	argc -= optind;
171 	argv += optind;
172 
173 	if (gethostname(host, sizeof(host)) == -1)
174 		err(1, "gethostname");
175 	if ((pw = getpwuid(getuid())) == NULL)
176 		user = "anonymous";
177 	if (pw != NULL && (user = strdup(pw->pw_name)) == NULL)
178 		err(1, "strdup");
179 
180 	build_from(fake_from, pw);
181 
182 	while(argc > 0) {
183 		rcpt_add(argv[0]);
184 		argv++;
185 		argc--;
186 	}
187 
188 	signal(SIGALRM, sighdlr);
189 	alarm(SMTP_TIMEOUT);
190 
191 	msg.fd = open_connection();
192 
193 	/* init session */
194 	if ((sp = client_init(msg.fd, "localhost")) == NULL)
195 		err(1, "client_init failed");
196 	if (verbose)
197 		client_verbose(sp, stdout);
198 
199 	/* parse message */
200 	if ((body = buf_dynamic(0, SIZE_T_MAX)) < 0)
201 		err(1, "buf_dynamic failed");
202 	noheader = parse_message(stdin, fake_from == NULL, tflag, body);
203 
204 	/* set envelope from */
205 	if (client_sender(sp, "%s", msg.from) < 0)
206 		err(1, "client_sender failed");
207 
208 	/* add recipients */
209 	if (msg.rcpt_cnt == 0)
210 		errx(1, "no recipients");
211 	for (i = 0; i < msg.rcpt_cnt; i++)
212 		if (client_rcpt(sp, "%s", msg.rcpts[i]) < 0)
213 			err(1, "client_rcpt failed");
214 
215 	/* add From */
216 	if (!msg.saw_from) {
217 		if (msg.fromname != NULL) {
218 			if (client_data_printf(sp,
219 			    "From: %s <%s>\n", msg.fromname, msg.from) < 0)
220 				err(1, "client_data_printf failed");
221 		} else
222 			if (client_data_printf(sp,
223 			    "From: %s\n", msg.from) < 0)
224 				err(1, "client_data_printf failed");
225 	}
226 
227 	/* add Date */
228 	if (!msg.saw_date)
229 		if (client_data_printf(sp,
230 		    "Date: %s\n", time_to_text(timestamp)) < 0)
231 			err(1, "client_data_printf failed");
232 
233 	/* add Message-Id */
234 	if (!msg.saw_msgid)
235 		if (client_data_printf(sp,
236 		    "Message-Id: <%llu.enqueue@%s>\n",
237 			generate_uid(), host) < 0)
238 			err(1, "client_data_printf failed");
239 
240 	/* add separating newline */
241 	if (noheader)
242 		if (client_data_printf(sp, "\n") < 0)
243 			err(1, "client_data_printf failed");
244 
245 	if (client_data_printf(sp, "%.*s", buf_size(body), body->buf) < 0)
246 		err(1, "client_data_printf failed");
247 	buf_free(body);
248 
249 	/* run the protocol engine */
250 	for (;;) {
251 		while ((ret = client_read(sp)) == CLIENT_WANT_READ)
252 			;
253 		if (ret == CLIENT_ERROR)
254 			errx(1, "read error: %s", client_strerror(sp));
255 		if (ret == CLIENT_RCPT_FAIL)
256 			errx(1, "recipient refused: %s", client_reply(sp));
257 		if (ret == CLIENT_DONE)
258 			break;
259 		while ((ret = client_write(sp)) == CLIENT_WANT_WRITE)
260 			;
261 		if (ret == CLIENT_ERROR)
262 			errx(1, "write error: %s", client_strerror(sp));
263 	}
264 
265 	client_close(sp);
266 
267 	close(msg.fd);
268 	exit (0);
269 }
270 
271 void
272 build_from(char *fake_from, struct passwd *pw)
273 {
274 	char	*p;
275 
276 	if (fake_from == NULL)
277 		msg.from = qualify_addr(user);
278 	else {
279 		if (fake_from[0] == '<') {
280 			if (fake_from[strlen(fake_from) - 1] != '>')
281 				errx(1, "leading < but no trailing >");
282 			fake_from[strlen(fake_from) - 1] = 0;
283 			if ((p = malloc(strlen(fake_from))) == NULL)
284 				err(1, "malloc");
285 			strlcpy(p, fake_from + 1, strlen(fake_from));
286 
287 			msg.from = qualify_addr(p);
288 			free(p);
289 		} else
290 			msg.from = qualify_addr(fake_from);
291 	}
292 
293 	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
294 		int	 len, apos;
295 
296 		len = strcspn(pw->pw_gecos, ",");
297 		if ((p = memchr(pw->pw_gecos, '&', len))) {
298 			apos = p - pw->pw_gecos;
299 			if (asprintf(&msg.fromname, "%.*s%s%.*s",
300 			    apos, pw->pw_gecos,
301 			    pw->pw_name,
302 			    len - apos - 1, p + 1) == -1)
303 				err(1, NULL);
304 			msg.fromname[apos] = toupper(msg.fromname[apos]);
305 		} else {
306 			if (asprintf(&msg.fromname, "%.*s", len,
307 			    pw->pw_gecos) == -1)
308 				err(1, NULL);
309 		}
310 	}
311 }
312 
313 int
314 parse_message(FILE *fin, int get_from, int tflag, struct buf *body)
315 {
316 	char	*buf;
317 	size_t	 len;
318 	u_int	 i, cur = HDR_NONE;
319 	u_int	 header_seen = 0, header_done = 0;
320 
321 	bzero(&pstate, sizeof(pstate));
322 	for (;;) {
323 		buf = fgetln(fin, &len);
324 		if (buf == NULL && ferror(fin))
325 			err(1, "fgetln");
326 		if (buf == NULL && feof(fin))
327 			break;
328 
329 		/* account for \r\n linebreaks */
330 		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
331 			buf[--len - 1] = '\n';
332 
333 		if (len == 1 && buf[0] == '\n')		/* end of header */
334 			header_done = 1;
335 
336 		if (buf == NULL || len < 1)
337 			err(1, "fgetln weird");
338 
339 		if (!WSP(buf[0])) {	/* whitespace -> continuation */
340 			if (cur == HDR_FROM)
341 				parse_addr_terminal(1);
342 			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
343 				parse_addr_terminal(0);
344 			cur = HDR_NONE;
345 		}
346 
347 		for (i = 0; !header_done && cur == HDR_NONE &&
348 		    i < nitems(keywords); i++)
349 			if (len > strlen(keywords[i].word) &&
350 			    !strncasecmp(buf, keywords[i].word,
351 			    strlen(keywords[i].word)))
352 				cur = keywords[i].type;
353 
354 		if (cur != HDR_NONE)
355 			header_seen = 1;
356 
357 		if (cur != HDR_BCC) {
358 			if (buf_add(body, buf, len) < 0)
359 				err(1, "buf_add failed");
360 			if (buf[len - 1] != '\n' && buf_add(body, "\n", 1) < 0)
361 				err(1, "buf_add failed");
362 		}
363 
364 		/*
365 		 * using From: as envelope sender is not sendmail compatible,
366 		 * but I really want it that way - maybe needs a knob
367 		 */
368 		if (cur == HDR_FROM) {
369 			msg.saw_from++;
370 			if (get_from)
371 				parse_addr(buf, len, 1);
372 		}
373 
374 		if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
375 			parse_addr(buf, len, 0);
376 
377 		if (cur == HDR_DATE)
378 			msg.saw_date++;
379 		if (cur == HDR_MSGID)
380 			msg.saw_msgid++;
381 	}
382 
383 	return (!header_seen);
384 }
385 
386 void
387 parse_addr(char *s, size_t len, int is_from)
388 {
389 	size_t	 pos = 0;
390 	int	 terminal = 0;
391 
392 	/* unless this is a continuation... */
393 	if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
394 		/* ... skip over everything before the ':' */
395 		for (; pos < len && s[pos] != ':'; pos++)
396 			;	/* nothing */
397 		/* ... and check & reset parser state */
398 		parse_addr_terminal(is_from);
399 	}
400 
401 	/* skip over ':' ',' ';' and whitespace */
402 	for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
403 	    s[pos] == ',' || s[pos] == ';'); pos++)
404 		;	/* nothing */
405 
406 	for (; pos < len; pos++) {
407 		if (!pstate.esc && !pstate.quote && s[pos] == '(')
408 			pstate.comment++;
409 		if (!pstate.comment && !pstate.esc && s[pos] == '"')
410 			pstate.quote = !pstate.quote;
411 
412 		if (!pstate.comment && !pstate.quote && !pstate.esc) {
413 			if (s[pos] == ':') {	/* group */
414 				for(pos++; pos < len && WSP(s[pos]); pos++)
415 					;	/* nothing */
416 				pstate.wpos = 0;
417 			}
418 			if (s[pos] == '\n' || s[pos] == '\r')
419 				break;
420 			if (s[pos] == ',' || s[pos] == ';') {
421 				terminal = 1;
422 				break;
423 			}
424 			if (s[pos] == '<') {
425 				pstate.brackets = 1;
426 				pstate.wpos = 0;
427 			}
428 			if (pstate.brackets && s[pos] == '>')
429 				terminal = 1;
430 		}
431 
432 		if (!pstate.comment && !terminal && (!(!(pstate.quote ||
433 		    pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
434 			if (pstate.wpos >= sizeof(pstate.buf))
435 				errx(1, "address exceeds buffer size");
436 			pstate.buf[pstate.wpos++] = s[pos];
437 		}
438 
439 		if (!pstate.quote && pstate.comment && s[pos] == ')')
440 			pstate.comment--;
441 
442 		if (!pstate.esc && !pstate.comment && !pstate.quote &&
443 		    s[pos] == '\\')
444 			pstate.esc = 1;
445 		else
446 			pstate.esc = 0;
447 	}
448 
449 	if (terminal)
450 		parse_addr_terminal(is_from);
451 
452 	for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
453 		;	/* nothing */
454 
455 	if (pos < len)
456 		parse_addr(s + pos, len - pos, is_from);
457 }
458 
459 void
460 parse_addr_terminal(int is_from)
461 {
462 	if (pstate.comment || pstate.quote || pstate.esc)
463 		errx(1, "syntax error in address");
464 	if (pstate.wpos) {
465 		if (pstate.wpos >= sizeof(pstate.buf))
466 			errx(1, "address exceeds buffer size");
467 		pstate.buf[pstate.wpos] = '\0';
468 		if (is_from)
469 			msg.from = qualify_addr(pstate.buf);
470 		else
471 			rcpt_add(pstate.buf);
472 		pstate.wpos = 0;
473 	}
474 }
475 
476 char *
477 qualify_addr(char *in)
478 {
479 	char	*out;
480 
481 	if (strchr(in, '@') == NULL) {
482 		if (asprintf(&out, "%s@%s", in, host) == -1)
483 			err(1, "qualify asprintf");
484 	} else
485 		if ((out = strdup(in)) == NULL)
486 			err(1, "qualify strdup");
487 
488 	return (out);
489 }
490 
491 void
492 rcpt_add(char *addr)
493 {
494 	void	*nrcpts;
495 
496 	if ((nrcpts = realloc(msg.rcpts,
497 	    sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL)
498 		err(1, "rcpt_add realloc");
499 	msg.rcpts = nrcpts;
500 	msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
501 }
502 
503 int
504 open_connection(void)
505 {
506 	struct imsg	imsg;
507 	int		fd;
508 	int		n;
509 
510 	imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0);
511 
512 	while (ibuf->w.queued)
513 		if (msgbuf_write(&ibuf->w) < 0)
514 			err(1, "write error");
515 
516 	while (1) {
517 		if ((n = imsg_read(ibuf)) == -1)
518 			errx(1, "imsg_read error");
519 		if (n == 0)
520 			errx(1, "pipe closed");
521 
522 		if ((n = imsg_get(ibuf, &imsg)) == -1)
523 			errx(1, "imsg_get error");
524 		if (n == 0)
525 			continue;
526 
527 		switch (imsg.hdr.type) {
528 		case IMSG_CTL_OK:
529 			break;
530 		case IMSG_CTL_FAIL:
531 			errx(1, "server disallowed submission request");
532 		default:
533 			errx(1, "unexpected imsg reply type");
534 		}
535 
536 		fd = imsg.fd;
537 		imsg_free(&imsg);
538 
539 		break;
540 	}
541 
542 	return fd;
543 }
544 
545 int
546 enqueue_offline(int argc, char *argv[])
547 {
548 	char	 path[MAXPATHLEN];
549 	FILE	*fp;
550 	int	 i, fd, ch;
551 
552 	if (! bsnprintf(path, sizeof(path), "%s%s/%d.XXXXXXXXXX", PATH_SPOOL,
553 		PATH_OFFLINE, time(NULL)))
554 		err(1, "snprintf");
555 
556 	if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
557 		warn("cannot create temporary file %s", path);
558 		if (fd != -1)
559 			unlink(path);
560 		exit(1);
561 	}
562 
563 	for (i = 1; i < argc; i++) {
564 		if (strchr(argv[i], '|') != NULL) {
565 			warnx("%s contains illegal character", argv[i]);
566 			unlink(path);
567 			exit(1);
568 		}
569 		fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]);
570 	}
571 
572 	fprintf(fp, "\n");
573 
574 	while ((ch = fgetc(stdin)) != EOF)
575 		if (fputc(ch, fp) == EOF) {
576 			warn("write error");
577 			unlink(path);
578 			exit(1);
579 		}
580 
581 	if (ferror(stdin)) {
582 		warn("read error");
583 		unlink(path);
584 		exit(1);
585 	}
586 
587 	fclose(fp);
588 
589 	return (0);
590 }
591