xref: /openbsd-src/usr.sbin/smtpd/enqueue.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /*	$OpenBSD: enqueue.c,v 1.24 2009/09/21 20:35:26 jacekm 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 <unistd.h>
38 
39 #include "smtpd.h"
40 #include "client.h"
41 
42 extern struct imsgbuf	*ibuf;
43 
44 void	 usage(void);
45 void	 sighdlr(int);
46 int	 main(int, char *[]);
47 void	 build_from(char *, struct passwd *);
48 int	 parse_message(FILE *, int, int, struct buf *);
49 void	 parse_addr(char *, size_t, int);
50 void	 parse_addr_terminal(int);
51 char	*qualify_addr(char *);
52 void	 rcpt_add(char *);
53 int	 open_connection(void);
54 
55 enum headerfields {
56 	HDR_NONE,
57 	HDR_FROM,
58 	HDR_TO,
59 	HDR_CC,
60 	HDR_BCC,
61 	HDR_SUBJECT,
62 	HDR_DATE,
63 	HDR_MSGID
64 };
65 
66 struct {
67 	char			*word;
68 	enum headerfields	 type;
69 } keywords[] = {
70 	{ "From:",		HDR_FROM },
71 	{ "To:",		HDR_TO },
72 	{ "Cc:",		HDR_CC },
73 	{ "Bcc:",		HDR_BCC },
74 	{ "Subject:",		HDR_SUBJECT },
75 	{ "Date:",		HDR_DATE },
76 	{ "Message-Id:",	HDR_MSGID }
77 };
78 
79 #define	SMTP_LINELEN		1000
80 #define	SMTP_TIMEOUT		120
81 #define	TIMEOUTMSG		"Timeout\n"
82 
83 #define WSP(c)			(c == ' ' || c == '\t')
84 
85 int	  verbose = 0;
86 char	  host[MAXHOSTNAMELEN];
87 char	 *user = NULL;
88 time_t	  timestamp;
89 
90 struct {
91 	int	  fd;
92 	char	 *from;
93 	char	 *fromname;
94 	char	**rcpts;
95 	int	  rcpt_cnt;
96 	char	 *data;
97 	size_t	  len;
98 	int	  saw_date;
99 	int	  saw_msgid;
100 	int	  saw_from;
101 } msg;
102 
103 struct {
104 	u_int		quote;
105 	u_int		comment;
106 	u_int		esc;
107 	u_int		brackets;
108 	size_t		wpos;
109 	char		buf[SMTP_LINELEN];
110 } pstate;
111 
112 void
113 sighdlr(int sig)
114 {
115 	if (sig == SIGALRM) {
116 		write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG));
117 		_exit (2);
118 	}
119 }
120 
121 int
122 enqueue(int argc, char *argv[])
123 {
124 	int			 i, ch, tflag = 0, noheader, ret;
125 	char			*fake_from = NULL;
126 	struct passwd		*pw;
127 	struct smtp_client	*sp;
128 	struct buf		*body;
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(SMTP_TIMEOUT);
189 
190 	msg.fd = open_connection();
191 
192 	/* init session */
193 	if ((sp = client_init(msg.fd, "localhost")) == NULL)
194 		err(1, "client_init failed");
195 	if (verbose)
196 		client_verbose(sp, stdout);
197 
198 	/* parse message */
199 	if ((body = buf_dynamic(0, SIZE_T_MAX)) < 0)
200 		err(1, "buf_dynamic failed");
201 	noheader = parse_message(stdin, fake_from == NULL, tflag, body);
202 
203 	/* set envelope from */
204 	if (client_sender(sp, "%s", msg.from) < 0)
205 		err(1, "client_sender failed");
206 
207 	/* add recipients */
208 	if (msg.rcpt_cnt == 0)
209 		errx(1, "no recipients");
210 	for (i = 0; i < msg.rcpt_cnt; i++)
211 		if (client_rcpt(sp, "%s", msg.rcpts[i]) < 0)
212 			err(1, "client_rcpt failed");
213 
214 	/* add From */
215 	if (!msg.saw_from) {
216 		if (msg.fromname != NULL) {
217 			if (client_data_printf(sp,
218 			    "From: %s <%s>\n", msg.fromname, msg.from) < 0)
219 				err(1, "client_data_printf failed");
220 		} else
221 			if (client_data_printf(sp,
222 			    "From: %s\n", msg.from) < 0)
223 				err(1, "client_data_printf failed");
224 	}
225 
226 	/* add Date */
227 	if (!msg.saw_date)
228 		if (client_data_printf(sp,
229 		    "Date: %s\n", time_to_text(timestamp)) < 0)
230 			err(1, "client_data_printf failed");
231 
232 	/* add Message-Id */
233 	if (!msg.saw_msgid)
234 		if (client_data_printf(sp,
235 		    "Message-Id: <%llu.enqueue@%s>\n",
236 		    queue_generate_id(), host) < 0)
237 			err(1, "client_data_printf failed");
238 
239 	/* add separating newline */
240 	if (noheader)
241 		if (client_data_printf(sp, "\n") < 0)
242 			err(1, "client_data_printf failed");
243 
244 	if (client_data_printf(sp, "%.*s", buf_size(body), body->buf) < 0)
245 		err(1, "client_data_printf failed");
246 	buf_free(body);
247 
248 	/* run the protocol engine */
249 	for (;;) {
250 		while ((ret = client_read(sp)) == CLIENT_WANT_READ)
251 			;
252 		if (ret == CLIENT_ERROR)
253 			errx(1, "read error: %s", client_strerror(sp));
254 		if (ret == CLIENT_RCPT_FAIL)
255 			errx(1, "recipient refused: %s", client_reply(sp));
256 		if (ret == CLIENT_DONE)
257 			break;
258 		while ((ret = client_write(sp)) == CLIENT_WANT_WRITE)
259 			;
260 		if (ret == CLIENT_ERROR)
261 			errx(1, "write error: %s", client_strerror(sp));
262 	}
263 
264 	client_close(sp);
265 
266 	close(msg.fd);
267 	exit (0);
268 }
269 
270 void
271 build_from(char *fake_from, struct passwd *pw)
272 {
273 	char	*p;
274 
275 	if (fake_from == NULL)
276 		msg.from = qualify_addr(user);
277 	else {
278 		if (fake_from[0] == '<') {
279 			if (fake_from[strlen(fake_from) - 1] != '>')
280 				errx(1, "leading < but no trailing >");
281 			fake_from[strlen(fake_from) - 1] = 0;
282 			if ((p = malloc(strlen(fake_from))) == NULL)
283 				err(1, "malloc");
284 			strlcpy(p, fake_from + 1, strlen(fake_from));
285 
286 			msg.from = qualify_addr(p);
287 			free(p);
288 		} else
289 			msg.from = qualify_addr(fake_from);
290 	}
291 
292 	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
293 		int	 len, apos;
294 
295 		len = strcspn(pw->pw_gecos, ",");
296 		if ((p = memchr(pw->pw_gecos, '&', len))) {
297 			apos = p - pw->pw_gecos;
298 			if (asprintf(&msg.fromname, "%.*s%s%.*s",
299 			    apos, pw->pw_gecos,
300 			    pw->pw_name,
301 			    len - apos - 1, p + 1) == -1)
302 				err(1, NULL);
303 			msg.fromname[apos] = toupper(msg.fromname[apos]);
304 		} else {
305 			if (asprintf(&msg.fromname, "%.*s", len,
306 			    pw->pw_gecos) == -1)
307 				err(1, NULL);
308 		}
309 	}
310 }
311 
312 int
313 parse_message(FILE *fin, int get_from, int tflag, struct buf *body)
314 {
315 	char	*buf;
316 	size_t	 len;
317 	u_int	 i, cur = HDR_NONE;
318 	u_int	 header_seen = 0, header_done = 0;
319 
320 	bzero(&pstate, sizeof(pstate));
321 	for (;;) {
322 		buf = fgetln(fin, &len);
323 		if (buf == NULL && ferror(fin))
324 			err(1, "fgetln");
325 		if (buf == NULL && feof(fin))
326 			break;
327 
328 		/* account for \r\n linebreaks */
329 		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
330 			buf[--len - 1] = '\n';
331 
332 		if (len == 1 && buf[0] == '\n')		/* end of header */
333 			header_done = 1;
334 
335 		if (buf == NULL || len < 1)
336 			err(1, "fgetln weird");
337 
338 		if (!WSP(buf[0])) {	/* whitespace -> continuation */
339 			if (cur == HDR_FROM)
340 				parse_addr_terminal(1);
341 			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
342 				parse_addr_terminal(0);
343 			cur = HDR_NONE;
344 		}
345 
346 		for (i = 0; !header_done && cur == HDR_NONE &&
347 		    i < nitems(keywords); i++)
348 			if (len > strlen(keywords[i].word) &&
349 			    !strncasecmp(buf, keywords[i].word,
350 			    strlen(keywords[i].word)))
351 				cur = keywords[i].type;
352 
353 		if (cur != HDR_NONE)
354 			header_seen = 1;
355 
356 		if (cur != HDR_BCC) {
357 			if (buf_add(body, buf, len) < 0)
358 				err(1, "buf_add failed");
359 			if (buf[len - 1] != '\n' && buf_add(body, "\n", 1) < 0)
360 				err(1, "buf_add failed");
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 int
503 open_connection(void)
504 {
505 	struct imsg	imsg;
506 	int		fd;
507 	int		n;
508 
509 	imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0);
510 
511 	while (ibuf->w.queued)
512 		if (msgbuf_write(&ibuf->w) < 0)
513 			err(1, "write error");
514 
515 	while (1) {
516 		if ((n = imsg_read(ibuf)) == -1)
517 			errx(1, "imsg_read error");
518 		if (n == 0)
519 			errx(1, "pipe closed");
520 
521 		if ((n = imsg_get(ibuf, &imsg)) == -1)
522 			errx(1, "imsg_get error");
523 		if (n == 0)
524 			continue;
525 
526 		switch (imsg.hdr.type) {
527 		case IMSG_CTL_OK:
528 			break;
529 		case IMSG_CTL_FAIL:
530 			errx(1, "server disallowed submission request");
531 		default:
532 			errx(1, "unexpected imsg reply type");
533 		}
534 
535 		fd = imsg.fd;
536 		imsg_free(&imsg);
537 
538 		break;
539 	}
540 
541 	return fd;
542 }
543 
544 int
545 enqueue_offline(int argc, char *argv[])
546 {
547 	char	 path[MAXPATHLEN];
548 	FILE	*fp;
549 	int	 i, fd, ch;
550 
551 	if (! bsnprintf(path, sizeof(path), "%s%s/%d.XXXXXXXXXX", PATH_SPOOL,
552 		PATH_OFFLINE, time(NULL)))
553 		err(1, "snprintf");
554 
555 	if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
556 		warn("cannot create temporary file %s", path);
557 		if (fd != -1)
558 			unlink(path);
559 		exit(1);
560 	}
561 
562 	for (i = 1; i < argc; i++) {
563 		if (strchr(argv[i], '|') != NULL) {
564 			warnx("%s contains illegal character", argv[i]);
565 			unlink(path);
566 			exit(1);
567 		}
568 		fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]);
569 	}
570 
571 	fprintf(fp, "\n");
572 
573 	while ((ch = fgetc(stdin)) != EOF)
574 		if (fputc(ch, fp) == EOF) {
575 			warn("write error");
576 			unlink(path);
577 			exit(1);
578 		}
579 
580 	if (ferror(stdin)) {
581 		warn("read error");
582 		unlink(path);
583 		exit(1);
584 	}
585 
586 	fclose(fp);
587 
588 	return (0);
589 }
590