GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/smtpd/smtpctl/../enqueue.c Lines: 0 413 0.0 %
Date: 2017-11-13 Branches: 0 481 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: enqueue.c,v 1.113 2016/07/03 14:30:33 gilles Exp $	*/
2
3
/*
4
 * Copyright (c) 2005 Henning Brauer <henning@bulabula.org>
5
 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6
 * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
7
 *
8
 * Permission to use, copy, modify, and distribute this software for any
9
 * purpose with or without fee is hereby granted, provided that the above
10
 * copyright notice and this permission notice appear in all copies.
11
 *
12
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
17
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
18
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
 */
20
21
#include <sys/types.h>
22
#include <sys/queue.h>
23
#include <sys/socket.h>
24
#include <sys/tree.h>
25
#include <sys/stat.h>
26
27
#include <ctype.h>
28
#include <err.h>
29
#include <errno.h>
30
#include <event.h>
31
#include <grp.h>
32
#include <imsg.h>
33
#include <inttypes.h>
34
#include <pwd.h>
35
#include <stdarg.h>
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <time.h>
40
#include <unistd.h>
41
#include <limits.h>
42
43
#include "smtpd.h"
44
45
extern struct imsgbuf	*ibuf;
46
47
void usage(void);
48
static void build_from(char *, struct passwd *);
49
static int parse_message(FILE *, int, int, FILE *);
50
static void parse_addr(char *, size_t, int);
51
static void parse_addr_terminal(int);
52
static char *qualify_addr(char *);
53
static void rcpt_add(char *);
54
static int open_connection(void);
55
static int get_responses(FILE *, int);
56
static int send_line(FILE *, int, char *, ...);
57
static int enqueue_offline(int, char *[], FILE *, FILE *);
58
static int savedeadletter(struct passwd *, FILE *);
59
60
extern int srv_connected(void);
61
62
enum headerfields {
63
	HDR_NONE,
64
	HDR_FROM,
65
	HDR_TO,
66
	HDR_CC,
67
	HDR_BCC,
68
	HDR_SUBJECT,
69
	HDR_DATE,
70
	HDR_MSGID,
71
	HDR_MIME_VERSION,
72
	HDR_CONTENT_TYPE,
73
	HDR_CONTENT_DISPOSITION,
74
	HDR_CONTENT_TRANSFER_ENCODING,
75
	HDR_USER_AGENT
76
};
77
78
struct {
79
	char			*word;
80
	enum headerfields	 type;
81
} keywords[] = {
82
	{ "From:",			HDR_FROM },
83
	{ "To:",			HDR_TO },
84
	{ "Cc:",			HDR_CC },
85
	{ "Bcc:",			HDR_BCC },
86
	{ "Subject:",			HDR_SUBJECT },
87
	{ "Date:",			HDR_DATE },
88
	{ "Message-Id:",		HDR_MSGID },
89
	{ "MIME-Version:",		HDR_MIME_VERSION },
90
	{ "Content-Type:",		HDR_CONTENT_TYPE },
91
	{ "Content-Disposition:",	HDR_CONTENT_DISPOSITION },
92
	{ "Content-Transfer-Encoding:",	HDR_CONTENT_TRANSFER_ENCODING },
93
	{ "User-Agent:",		HDR_USER_AGENT },
94
};
95
96
#define	LINESPLIT		990
97
#define	SMTP_LINELEN		1000
98
#define	TIMEOUTMSG		"Timeout\n"
99
100
#define WSP(c)			(c == ' ' || c == '\t')
101
102
int		 verbose = 0;
103
static char	 host[HOST_NAME_MAX+1];
104
char		*user = NULL;
105
time_t		 timestamp;
106
107
struct {
108
	int	  fd;
109
	char	 *from;
110
	char	 *fromname;
111
	char	**rcpts;
112
	char	 *dsn_notify;
113
	char	 *dsn_ret;
114
	char	 *dsn_envid;
115
	int	  rcpt_cnt;
116
	int	  need_linesplit;
117
	int	  saw_date;
118
	int	  saw_msgid;
119
	int	  saw_from;
120
	int	  saw_mime_version;
121
	int	  saw_content_type;
122
	int	  saw_content_disposition;
123
	int	  saw_content_transfer_encoding;
124
	int	  saw_user_agent;
125
	int	  noheader;
126
} msg;
127
128
struct {
129
	uint		quote;
130
	uint		comment;
131
	uint		esc;
132
	uint		brackets;
133
	size_t		wpos;
134
	char		buf[SMTP_LINELEN];
135
} pstate;
136
137
static void
138
qp_encoded_write(FILE *fp, char *buf, size_t len)
139
{
140
	while (len) {
141
		if (*buf == '=')
142
			fprintf(fp, "=3D");
143
		else if (*buf == ' ' || *buf == '\t') {
144
			char *p = buf;
145
146
			while (*p != '\n') {
147
				if (*p != ' ' && *p != '\t')
148
					break;
149
				p++;
150
			}
151
			if (*p == '\n')
152
				fprintf(fp, "=%2X", *buf & 0xff);
153
			else
154
				fprintf(fp, "%c", *buf & 0xff);
155
		}
156
		else if (!isprint((unsigned char)*buf) && *buf != '\n')
157
			fprintf(fp, "=%2X", *buf & 0xff);
158
		else
159
			fprintf(fp, "%c", *buf);
160
		buf++;
161
		len--;
162
	}
163
}
164
165
int
166
enqueue(int argc, char *argv[], FILE *ofp)
167
{
168
	int			 i, ch, tflag = 0;
169
	char			*fake_from = NULL, *buf = NULL;
170
	struct passwd		*pw;
171
	FILE			*fp = NULL, *fout;
172
	size_t			 sz = 0, envid_sz = 0;
173
	ssize_t			 len;
174
	int			 fd;
175
	char			 sfn[] = "/tmp/smtpd.XXXXXXXXXX";
176
	char			*line;
177
	int			 dotted;
178
	int			 inheaders = 1;
179
	int			 save_argc;
180
	char			**save_argv;
181
	int			 no_getlogin = 0;
182
183
	memset(&msg, 0, sizeof(msg));
184
	time(&timestamp);
185
186
	save_argc = argc;
187
	save_argv = argv;
188
189
	while ((ch = getopt(argc, argv,
190
	    "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) {
191
		switch (ch) {
192
		case 'f':
193
			fake_from = optarg;
194
			break;
195
		case 'F':
196
			msg.fromname = optarg;
197
			break;
198
		case 'N':
199
			msg.dsn_notify = optarg;
200
			break;
201
		case 'r':
202
			fake_from = optarg;
203
			break;
204
		case 'R':
205
			msg.dsn_ret = optarg;
206
			break;
207
		case 'S':
208
			no_getlogin = 1;
209
			break;
210
		case 't':
211
			tflag = 1;
212
			break;
213
		case 'v':
214
			verbose = 1;
215
			break;
216
		case 'V':
217
			msg.dsn_envid = optarg;
218
			break;
219
		/* all remaining: ignored, sendmail compat */
220
		case 'A':
221
		case 'B':
222
		case 'b':
223
		case 'E':
224
		case 'e':
225
		case 'i':
226
		case 'L':
227
		case 'm':
228
		case 'o':
229
		case 'p':
230
		case 'x':
231
			break;
232
		case 'q':
233
			/* XXX: implement "process all now" */
234
			return (EX_SOFTWARE);
235
		default:
236
			usage();
237
		}
238
	}
239
240
	argc -= optind;
241
	argv += optind;
242
243
	if (getmailname(host, sizeof(host)) == -1)
244
		errx(EX_NOHOST, "getmailname");
245
	if (no_getlogin) {
246
		if ((pw = getpwuid(getuid())) == NULL)
247
			user = "anonymous";
248
		if (pw != NULL)
249
			user = xstrdup(pw->pw_name, "enqueue");
250
	}
251
	else {
252
		uid_t ruid = getuid();
253
254
		if ((user = getlogin()) != NULL && *user != '\0') {
255
			if ((pw = getpwnam(user)) == NULL ||
256
			    (ruid != 0 && ruid != pw->pw_uid))
257
				pw = getpwuid(ruid);
258
		} else if ((pw = getpwuid(ruid)) == NULL) {
259
			user = "anonymous";
260
		}
261
		user = xstrdup(pw ? pw->pw_name : user, "enqueue");
262
	}
263
264
	build_from(fake_from, pw);
265
266
	while (argc > 0) {
267
		rcpt_add(argv[0]);
268
		argv++;
269
		argc--;
270
	}
271
272
	if ((fd = mkstemp(sfn)) == -1 ||
273
	    (fp = fdopen(fd, "w+")) == NULL) {
274
		int saved_errno = errno;
275
		if (fd != -1) {
276
			unlink(sfn);
277
			close(fd);
278
		}
279
		errc(EX_UNAVAILABLE, saved_errno, "mkstemp");
280
	}
281
	unlink(sfn);
282
	msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp);
283
284
	if (msg.rcpt_cnt == 0)
285
		errx(EX_SOFTWARE, "no recipients");
286
287
	/* init session */
288
	rewind(fp);
289
290
	/* check if working in offline mode */
291
	/* If the server is not running, enqueue the message offline */
292
293
	if (!srv_connected()) {
294
		if (pledge("stdio flock rpath cpath wpath", NULL) == -1)
295
			err(1, "pledge");
296
297
		return (enqueue_offline(save_argc, save_argv, fp, ofp));
298
	}
299
300
	if ((msg.fd = open_connection()) == -1)
301
		errx(EX_UNAVAILABLE, "server too busy");
302
303
	if (pledge("stdio wpath cpath flock rpath", NULL) == -1)
304
		err(1, "pledge");
305
306
	fout = fdopen(msg.fd, "a+");
307
	if (fout == NULL)
308
		err(EX_UNAVAILABLE, "fdopen");
309
310
	/*
311
	 * We need to call get_responses after every command because we don't
312
	 * support PIPELINING on the server-side yet.
313
	 */
314
315
	/* banner */
316
	if (!get_responses(fout, 1))
317
		goto fail;
318
319
	if (!send_line(fout, verbose, "EHLO localhost\n"))
320
		goto fail;
321
	if (!get_responses(fout, 1))
322
		goto fail;
323
324
	if (msg.dsn_envid != NULL)
325
		envid_sz = strlen(msg.dsn_envid);
326
327
	if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\n",
328
	    msg.from,
329
	    msg.dsn_ret ? "RET=" : "",
330
	    msg.dsn_ret ? msg.dsn_ret : "",
331
	    envid_sz ? "ENVID=" : "",
332
	    envid_sz ? msg.dsn_envid : ""))
333
		goto fail;
334
	if (!get_responses(fout, 1))
335
		goto fail;
336
337
	for (i = 0; i < msg.rcpt_cnt; i++) {
338
		if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\n",
339
		    msg.rcpts[i],
340
		    msg.dsn_notify ? "NOTIFY=" : "",
341
		    msg.dsn_notify ? msg.dsn_notify : ""))
342
			goto fail;
343
		if (!get_responses(fout, 1))
344
			goto fail;
345
	}
346
347
	if (!send_line(fout, verbose, "DATA\n"))
348
		goto fail;
349
	if (!get_responses(fout, 1))
350
		goto fail;
351
352
	/* add From */
353
	if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\n",
354
	    msg.fromname ? msg.fromname : "", msg.fromname ? " " : "",
355
	    msg.from))
356
		goto fail;
357
358
	/* add Date */
359
	if (!msg.saw_date && !send_line(fout, 0, "Date: %s\n",
360
	    time_to_text(timestamp)))
361
		goto fail;
362
363
	if (msg.need_linesplit) {
364
		/* we will always need to mime encode for long lines */
365
		if (!msg.saw_mime_version && !send_line(fout, 0,
366
		    "MIME-Version: 1.0\n"))
367
			goto fail;
368
		if (!msg.saw_content_type && !send_line(fout, 0,
369
		    "Content-Type: text/plain; charset=unknown-8bit\n"))
370
			goto fail;
371
		if (!msg.saw_content_disposition && !send_line(fout, 0,
372
		    "Content-Disposition: inline\n"))
373
			goto fail;
374
		if (!msg.saw_content_transfer_encoding && !send_line(fout, 0,
375
		    "Content-Transfer-Encoding: quoted-printable\n"))
376
			goto fail;
377
	}
378
379
	/* add separating newline */
380
	if (msg.noheader) {
381
		if (!send_line(fout, 0, "\n"))
382
			goto fail;
383
		inheaders = 0;
384
	}
385
386
	for (;;) {
387
		if ((len = getline(&buf, &sz, fp)) == -1) {
388
			if (feof(fp))
389
				break;
390
			else
391
				err(EX_UNAVAILABLE, "getline");
392
		}
393
394
		/* newlines have been normalized on first parsing */
395
		if (buf[len-1] != '\n')
396
			errx(EX_SOFTWARE, "expect EOL");
397
398
		dotted = 0;
399
		if (buf[0] == '.') {
400
			if (fputc('.', fout) == EOF)
401
				goto fail;
402
			dotted = 1;
403
		}
404
405
		line = buf;
406
407
		if (inheaders) {
408
			if (strncasecmp("from ", line, 5) == 0)
409
				continue;
410
			if (strncasecmp("return-path: ", line, 13) == 0)
411
				continue;
412
		}
413
414
		if (msg.saw_content_transfer_encoding || msg.noheader ||
415
		    inheaders || !msg.need_linesplit) {
416
			if (!send_line(fout, 0, "%.*s", (int)len, line))
417
				goto fail;
418
			if (inheaders && buf[0] == '\n')
419
				inheaders = 0;
420
			continue;
421
		}
422
423
		/* we don't have a content transfer encoding, use our default */
424
		do {
425
			if (len < LINESPLIT) {
426
				qp_encoded_write(fout, line, len);
427
				break;
428
			}
429
			else {
430
				qp_encoded_write(fout, line,
431
				    LINESPLIT - 2 - dotted);
432
				if (!send_line(fout, 0, "=\n"))
433
					goto fail;
434
				line += LINESPLIT - 2 - dotted;
435
				len -= LINESPLIT - 2 - dotted;
436
			}
437
		} while (len);
438
	}
439
	free(buf);
440
	if (!send_line(fout, verbose, ".\n"))
441
		goto fail;
442
	if (!get_responses(fout, 1))
443
		goto fail;
444
445
	if (!send_line(fout, verbose, "QUIT\n"))
446
		goto fail;
447
	if (!get_responses(fout, 1))
448
		goto fail;
449
450
	fclose(fp);
451
	fclose(fout);
452
453
	exit(EX_OK);
454
455
fail:
456
	if (pw)
457
		savedeadletter(pw, fp);
458
	exit(EX_SOFTWARE);
459
}
460
461
static int
462
get_responses(FILE *fin, int n)
463
{
464
	char	*buf = NULL;
465
	size_t	 sz = 0;
466
	ssize_t	 len;
467
	int	 e, ret = 0;
468
469
	fflush(fin);
470
	if ((e = ferror(fin))) {
471
		warnx("ferror: %d", e);
472
		goto err;
473
	}
474
475
	while (n) {
476
		if ((len = getline(&buf, &sz, fin)) == -1) {
477
			if (ferror(fin)) {
478
				warn("getline");
479
				goto err;
480
			} else if (feof(fin))
481
				break;
482
			else
483
				err(EX_UNAVAILABLE, "getline");
484
		}
485
486
		/* account for \r\n linebreaks */
487
		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
488
			buf[--len - 1] = '\n';
489
490
		if (len < 4) {
491
			warnx("bad response");
492
			goto err;
493
		}
494
495
		if (verbose)
496
			printf("<<< %.*s", (int)len, buf);
497
498
		if (buf[3] == '-')
499
			continue;
500
		if (buf[0] != '2' && buf[0] != '3') {
501
			warnx("command failed: %.*s", (int)len, buf);
502
			goto err;
503
		}
504
		n--;
505
	}
506
507
	ret = 1;
508
err:
509
	free(buf);
510
	return ret;
511
}
512
513
static int
514
send_line(FILE *fp, int v, char *fmt, ...)
515
{
516
	int ret = 0;
517
	va_list ap;
518
519
	va_start(ap, fmt);
520
	if (vfprintf(fp, fmt, ap) >= 0)
521
	    ret = 1;
522
	va_end(ap);
523
524
	if (ret && v) {
525
		printf(">>> ");
526
		va_start(ap, fmt);
527
		vprintf(fmt, ap);
528
		va_end(ap);
529
	}
530
531
	return (ret);
532
}
533
534
static void
535
build_from(char *fake_from, struct passwd *pw)
536
{
537
	char	*p;
538
539
	if (fake_from == NULL)
540
		msg.from = qualify_addr(user);
541
	else {
542
		if (fake_from[0] == '<') {
543
			if (fake_from[strlen(fake_from) - 1] != '>')
544
				errx(1, "leading < but no trailing >");
545
			fake_from[strlen(fake_from) - 1] = 0;
546
			p = xstrdup(fake_from + 1, "build_from");
547
548
			msg.from = qualify_addr(p);
549
			free(p);
550
		} else
551
			msg.from = qualify_addr(fake_from);
552
	}
553
554
	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
555
		int	 len, apos;
556
557
		len = strcspn(pw->pw_gecos, ",");
558
		if ((p = memchr(pw->pw_gecos, '&', len))) {
559
			apos = p - pw->pw_gecos;
560
			if (asprintf(&msg.fromname, "%.*s%s%.*s",
561
			    apos, pw->pw_gecos,
562
			    pw->pw_name,
563
			    len - apos - 1, p + 1) == -1)
564
				err(1, NULL);
565
			msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]);
566
		} else {
567
			if (asprintf(&msg.fromname, "%.*s", len,
568
			    pw->pw_gecos) == -1)
569
				err(1, NULL);
570
		}
571
	}
572
}
573
574
static int
575
parse_message(FILE *fin, int get_from, int tflag, FILE *fout)
576
{
577
	char	*buf = NULL;
578
	size_t	 sz = 0;
579
	ssize_t	 len;
580
	uint	 i, cur = HDR_NONE;
581
	uint	 header_seen = 0, header_done = 0;
582
583
	memset(&pstate, 0, sizeof(pstate));
584
	for (;;) {
585
		if ((len = getline(&buf, &sz, fin)) == -1) {
586
			if (feof(fin))
587
				break;
588
			else
589
				err(EX_UNAVAILABLE, "getline");
590
		}
591
592
		/* account for \r\n linebreaks */
593
		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
594
			buf[--len - 1] = '\n';
595
596
		if (len == 1 && buf[0] == '\n')		/* end of header */
597
			header_done = 1;
598
599
		if (!WSP(buf[0])) {	/* whitespace -> continuation */
600
			if (cur == HDR_FROM)
601
				parse_addr_terminal(1);
602
			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
603
				parse_addr_terminal(0);
604
			cur = HDR_NONE;
605
		}
606
607
		/* not really exact, if we are still in headers */
608
		if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT)
609
			msg.need_linesplit = 1;
610
611
		for (i = 0; !header_done && cur == HDR_NONE &&
612
		    i < nitems(keywords); i++)
613
			if ((size_t)len > strlen(keywords[i].word) &&
614
			    !strncasecmp(buf, keywords[i].word,
615
			    strlen(keywords[i].word)))
616
				cur = keywords[i].type;
617
618
		if (cur != HDR_NONE)
619
			header_seen = 1;
620
621
		if (cur != HDR_BCC) {
622
			if (!send_line(fout, 0, "%.*s", (int)len, buf))
623
				err(1, "write error");
624
			if (buf[len - 1] != '\n') {
625
				if (fputc('\n', fout) == EOF)
626
					err(1, "write error");
627
			}
628
		}
629
630
		/*
631
		 * using From: as envelope sender is not sendmail compatible,
632
		 * but I really want it that way - maybe needs a knob
633
		 */
634
		if (cur == HDR_FROM) {
635
			msg.saw_from++;
636
			if (get_from)
637
				parse_addr(buf, len, 1);
638
		}
639
640
		if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
641
			parse_addr(buf, len, 0);
642
643
		if (cur == HDR_DATE)
644
			msg.saw_date++;
645
		if (cur == HDR_MSGID)
646
			msg.saw_msgid++;
647
		if (cur == HDR_MIME_VERSION)
648
			msg.saw_mime_version = 1;
649
		if (cur == HDR_CONTENT_TYPE)
650
			msg.saw_content_type = 1;
651
		if (cur == HDR_CONTENT_DISPOSITION)
652
			msg.saw_content_disposition = 1;
653
		if (cur == HDR_CONTENT_TRANSFER_ENCODING)
654
			msg.saw_content_transfer_encoding = 1;
655
		if (cur == HDR_USER_AGENT)
656
			msg.saw_user_agent = 1;
657
	}
658
659
	free(buf);
660
	return (!header_seen);
661
}
662
663
static void
664
parse_addr(char *s, size_t len, int is_from)
665
{
666
	size_t	 pos = 0;
667
	int	 terminal = 0;
668
669
	/* unless this is a continuation... */
670
	if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
671
		/* ... skip over everything before the ':' */
672
		for (; pos < len && s[pos] != ':'; pos++)
673
			;	/* nothing */
674
		/* ... and check & reset parser state */
675
		parse_addr_terminal(is_from);
676
	}
677
678
	/* skip over ':' ',' ';' and whitespace */
679
	for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
680
	    s[pos] == ',' || s[pos] == ';'); pos++)
681
		;	/* nothing */
682
683
	for (; pos < len; pos++) {
684
		if (!pstate.esc && !pstate.quote && s[pos] == '(')
685
			pstate.comment++;
686
		if (!pstate.comment && !pstate.esc && s[pos] == '"')
687
			pstate.quote = !pstate.quote;
688
689
		if (!pstate.comment && !pstate.quote && !pstate.esc) {
690
			if (s[pos] == ':') {	/* group */
691
				for (pos++; pos < len && WSP(s[pos]); pos++)
692
					;	/* nothing */
693
				pstate.wpos = 0;
694
			}
695
			if (s[pos] == '\n' || s[pos] == '\r')
696
				break;
697
			if (s[pos] == ',' || s[pos] == ';') {
698
				terminal = 1;
699
				break;
700
			}
701
			if (s[pos] == '<') {
702
				pstate.brackets = 1;
703
				pstate.wpos = 0;
704
			}
705
			if (pstate.brackets && s[pos] == '>')
706
				terminal = 1;
707
		}
708
709
		if (!pstate.comment && !terminal && (!(!(pstate.quote ||
710
		    pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
711
			if (pstate.wpos >= sizeof(pstate.buf))
712
				errx(1, "address exceeds buffer size");
713
			pstate.buf[pstate.wpos++] = s[pos];
714
		}
715
716
		if (!pstate.quote && pstate.comment && s[pos] == ')')
717
			pstate.comment--;
718
719
		if (!pstate.esc && !pstate.comment && !pstate.quote &&
720
		    s[pos] == '\\')
721
			pstate.esc = 1;
722
		else
723
			pstate.esc = 0;
724
	}
725
726
	if (terminal)
727
		parse_addr_terminal(is_from);
728
729
	for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
730
		;	/* nothing */
731
732
	if (pos < len)
733
		parse_addr(s + pos, len - pos, is_from);
734
}
735
736
static void
737
parse_addr_terminal(int is_from)
738
{
739
	if (pstate.comment || pstate.quote || pstate.esc)
740
		errx(1, "syntax error in address");
741
	if (pstate.wpos) {
742
		if (pstate.wpos >= sizeof(pstate.buf))
743
			errx(1, "address exceeds buffer size");
744
		pstate.buf[pstate.wpos] = '\0';
745
		if (is_from)
746
			msg.from = qualify_addr(pstate.buf);
747
		else
748
			rcpt_add(pstate.buf);
749
		pstate.wpos = 0;
750
	}
751
}
752
753
static char *
754
qualify_addr(char *in)
755
{
756
	char	*out;
757
758
	if (strlen(in) > 0 && strchr(in, '@') == NULL) {
759
		if (asprintf(&out, "%s@%s", in, host) == -1)
760
			err(1, "qualify asprintf");
761
	} else
762
		out = xstrdup(in, "qualify_addr");
763
764
	return (out);
765
}
766
767
static void
768
rcpt_add(char *addr)
769
{
770
	void	*nrcpts;
771
	char	*p;
772
	int	n;
773
774
	n = 1;
775
	p = addr;
776
	while ((p = strchr(p, ',')) != NULL) {
777
		n++;
778
		p++;
779
	}
780
781
	if ((nrcpts = reallocarray(msg.rcpts,
782
	    msg.rcpt_cnt + n, sizeof(char *))) == NULL)
783
		err(1, "rcpt_add realloc");
784
	msg.rcpts = nrcpts;
785
786
	while (n--) {
787
		if ((p = strchr(addr, ',')) != NULL)
788
			*p++ = '\0';
789
		msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
790
		if (p == NULL)
791
			break;
792
		addr = p;
793
	}
794
}
795
796
static int
797
open_connection(void)
798
{
799
	struct imsg	imsg;
800
	int		fd;
801
	int		n;
802
803
	imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0);
804
805
	while (ibuf->w.queued)
806
		if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
807
			err(1, "write error");
808
809
	while (1) {
810
		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
811
			errx(1, "imsg_read error");
812
		if (n == 0)
813
			errx(1, "pipe closed");
814
815
		if ((n = imsg_get(ibuf, &imsg)) == -1)
816
			errx(1, "imsg_get error");
817
		if (n == 0)
818
			continue;
819
820
		switch (imsg.hdr.type) {
821
		case IMSG_CTL_OK:
822
			break;
823
		case IMSG_CTL_FAIL:
824
			errx(1, "server disallowed submission request");
825
		default:
826
			errx(1, "unexpected imsg reply type");
827
		}
828
829
		fd = imsg.fd;
830
		imsg_free(&imsg);
831
832
		break;
833
	}
834
835
	return fd;
836
}
837
838
static int
839
enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile)
840
{
841
	int	i, ch;
842
843
	for (i = 1; i < argc; i++) {
844
		if (strchr(argv[i], '|') != NULL) {
845
			warnx("%s contains illegal character", argv[i]);
846
			ftruncate(fileno(ofile), 0);
847
			exit(EX_SOFTWARE);
848
		}
849
		if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0)
850
			goto write_error;
851
	}
852
853
	if (fputc('\n', ofile) == EOF)
854
		goto write_error;
855
856
	while ((ch = fgetc(ifile)) != EOF) {
857
		if (fputc(ch, ofile) == EOF)
858
			goto write_error;
859
	}
860
861
	if (ferror(ifile)) {
862
		warn("read error");
863
		ftruncate(fileno(ofile), 0);
864
		exit(EX_UNAVAILABLE);
865
	}
866
867
	if (fclose(ofile) == EOF)
868
		goto write_error;
869
870
	return (EX_TEMPFAIL);
871
write_error:
872
	warn("write error");
873
	ftruncate(fileno(ofile), 0);
874
	exit(EX_UNAVAILABLE);
875
}
876
877
static int
878
savedeadletter(struct passwd *pw, FILE *in)
879
{
880
	char	 buffer[PATH_MAX];
881
	FILE	*fp;
882
	char	*buf = NULL;
883
	size_t	 sz = 0;
884
	ssize_t	 len;
885
886
	(void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir);
887
888
	if (fseek(in, 0, SEEK_SET) != 0)
889
		return 0;
890
891
	if ((fp = fopen(buffer, "w")) == NULL)
892
		return 0;
893
894
	/* add From */
895
	if (!msg.saw_from)
896
		fprintf(fp, "From: %s%s<%s>\n",
897
		    msg.fromname ? msg.fromname : "",
898
		    msg.fromname ? " " : "",
899
		    msg.from);
900
901
	/* add Date */
902
	if (!msg.saw_date)
903
		fprintf(fp, "Date: %s\n", time_to_text(timestamp));
904
905
	if (msg.need_linesplit) {
906
		/* we will always need to mime encode for long lines */
907
		if (!msg.saw_mime_version)
908
			fprintf(fp, "MIME-Version: 1.0\n");
909
		if (!msg.saw_content_type)
910
			fprintf(fp, "Content-Type: text/plain; "
911
			    "charset=unknown-8bit\n");
912
		if (!msg.saw_content_disposition)
913
			fprintf(fp, "Content-Disposition: inline\n");
914
		if (!msg.saw_content_transfer_encoding)
915
			fprintf(fp, "Content-Transfer-Encoding: "
916
			    "quoted-printable\n");
917
	}
918
919
	/* add separating newline */
920
	if (msg.noheader)
921
		fprintf(fp, "\n");
922
923
	while ((len = getline(&buf, &sz, in)) != -1) {
924
		if (buf[len - 1] == '\n')
925
			buf[len - 1] = '\0';
926
		fprintf(fp, "%s\n", buf);
927
	}
928
929
	free(buf);
930
	fprintf(fp, "\n");
931
	fclose(fp);
932
	return 1;
933
}