1 /* $OpenBSD: enqueue.c,v 1.118 2020/03/18 20:17:14 eric 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 "includes.h"
22
23 #include <sys/types.h>
24 #include <sys/queue.h>
25 #include <sys/socket.h>
26 #include <sys/tree.h>
27 #include <sys/stat.h>
28
29 #include <ctype.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <event.h>
33 #include <grp.h>
34 #include <imsg.h>
35 #include <inttypes.h>
36 #include <pwd.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 #include <unistd.h>
43 #include <limits.h>
44
45 #include "smtpd.h"
46
47 extern struct imsgbuf *ibuf;
48
49 void usage(void);
50 static void build_from(char *, struct passwd *);
51 static int parse_message(FILE *, int, int, FILE *);
52 static void parse_addr(char *, size_t, int);
53 static void parse_addr_terminal(int);
54 static char *qualify_addr(char *);
55 static void rcpt_add(char *);
56 static int open_connection(void);
57 static int get_responses(FILE *, int);
58 static int send_line(FILE *, int, char *, ...);
59 static int enqueue_offline(int, char *[], FILE *, FILE *);
60 static int savedeadletter(struct passwd *, FILE *);
61
62 extern int srv_connected(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 HDR_MIME_VERSION,
74 HDR_CONTENT_TYPE,
75 HDR_CONTENT_DISPOSITION,
76 HDR_CONTENT_TRANSFER_ENCODING,
77 HDR_USER_AGENT
78 };
79
80 struct {
81 char *word;
82 enum headerfields type;
83 } keywords[] = {
84 { "From:", HDR_FROM },
85 { "To:", HDR_TO },
86 { "Cc:", HDR_CC },
87 { "Bcc:", HDR_BCC },
88 { "Subject:", HDR_SUBJECT },
89 { "Date:", HDR_DATE },
90 { "Message-Id:", HDR_MSGID },
91 { "MIME-Version:", HDR_MIME_VERSION },
92 { "Content-Type:", HDR_CONTENT_TYPE },
93 { "Content-Disposition:", HDR_CONTENT_DISPOSITION },
94 { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING },
95 { "User-Agent:", HDR_USER_AGENT },
96 };
97
98 #define LINESPLIT 990
99 #define SMTP_LINELEN 1000
100 #define TIMEOUTMSG "Timeout\n"
101
102 #define WSP(c) (c == ' ' || c == '\t')
103
104 int verbose = 0;
105 static char host[HOST_NAME_MAX+1];
106 char *user = NULL;
107 time_t timestamp;
108
109 struct {
110 int fd;
111 char *from;
112 char *fromname;
113 char **rcpts;
114 char *dsn_notify;
115 char *dsn_ret;
116 char *dsn_envid;
117 int rcpt_cnt;
118 int need_linesplit;
119 int saw_date;
120 int saw_msgid;
121 int saw_from;
122 int saw_mime_version;
123 int saw_content_type;
124 int saw_content_disposition;
125 int saw_content_transfer_encoding;
126 int saw_user_agent;
127 int noheader;
128 } msg;
129
130 struct {
131 uint quote;
132 uint comment;
133 uint esc;
134 uint brackets;
135 size_t wpos;
136 char buf[SMTP_LINELEN];
137 } pstate;
138
139 #define QP_TEST_WRAP(fp, buf, linelen, size) do { \
140 if (((linelen) += (size)) + 1 > 76) { \
141 fprintf((fp), "=\r\n"); \
142 if (buf[0] == '.') \
143 fprintf((fp), "."); \
144 (linelen) = (size); \
145 } \
146 } while (0)
147
148 /* RFC 2045 section 6.7 */
149 static void
qp_encoded_write(FILE * fp,char * buf)150 qp_encoded_write(FILE *fp, char *buf)
151 {
152 size_t linelen = 0;
153
154 for (;buf[0] != '\0' && buf[0] != '\n'; buf++) {
155 /*
156 * Point 3: Any TAB (HT) or SPACE characters on an encoded line
157 * MUST thus be followed on that line by a printable character.
158 *
159 * Ergo, only encode if the next character is EOL.
160 */
161 if (buf[0] == ' ' || buf[0] == '\t') {
162 if (buf[1] == '\n') {
163 QP_TEST_WRAP(fp, buf, linelen, 3);
164 fprintf(fp, "=%2X", *buf & 0xff);
165 } else {
166 QP_TEST_WRAP(fp, buf, linelen, 1);
167 fprintf(fp, "%c", *buf & 0xff);
168 }
169 /*
170 * Point 1, with exclusion of point 2, skip EBCDIC NOTE.
171 * Do this after whitespace check, else they would match here.
172 */
173 } else if (!((buf[0] >= 33 && buf[0] <= 60) ||
174 (buf[0] >= 62 && buf[0] <= 126))) {
175 QP_TEST_WRAP(fp, buf, linelen, 3);
176 fprintf(fp, "=%2X", *buf & 0xff);
177 /* Point 2: 33 through 60 inclusive, and 62 through 126 */
178 } else {
179 QP_TEST_WRAP(fp, buf, linelen, 1);
180 fprintf(fp, "%c", *buf);
181 }
182 }
183 fprintf(fp, "\r\n");
184 }
185
186 int
enqueue(int argc,char * argv[],FILE * ofp)187 enqueue(int argc, char *argv[], FILE *ofp)
188 {
189 int i, ch, tflag = 0;
190 char *fake_from = NULL, *buf = NULL;
191 struct passwd *pw;
192 FILE *fp = NULL, *fout;
193 size_t sz = 0, envid_sz = 0;
194 ssize_t len;
195 char *line;
196 int inheaders = 1;
197 int save_argc;
198 char **save_argv;
199 int no_getlogin = 0;
200
201 memset(&msg, 0, sizeof(msg));
202 time(×tamp);
203
204 save_argc = argc;
205 save_argv = argv;
206
207 while ((ch = getopt(argc, argv,
208 "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) {
209 switch (ch) {
210 case 'f':
211 fake_from = optarg;
212 break;
213 case 'F':
214 msg.fromname = optarg;
215 break;
216 case 'N':
217 msg.dsn_notify = optarg;
218 break;
219 case 'r':
220 fake_from = optarg;
221 break;
222 case 'R':
223 msg.dsn_ret = optarg;
224 break;
225 case 'S':
226 no_getlogin = 1;
227 break;
228 case 't':
229 tflag = 1;
230 break;
231 case 'v':
232 verbose = 1;
233 break;
234 case 'V':
235 msg.dsn_envid = optarg;
236 break;
237 /* all remaining: ignored, sendmail compat */
238 case 'A':
239 case 'B':
240 case 'b':
241 case 'E':
242 case 'e':
243 case 'i':
244 case 'L':
245 case 'm':
246 case 'o':
247 case 'p':
248 case 'x':
249 break;
250 case 'q':
251 /* XXX: implement "process all now" */
252 return (EX_SOFTWARE);
253 default:
254 usage();
255 }
256 }
257
258 argc -= optind;
259 argv += optind;
260
261 if (getmailname(host, sizeof(host)) == -1)
262 errx(EX_NOHOST, "getmailname");
263 if (no_getlogin) {
264 if ((pw = getpwuid(getuid())) == NULL)
265 user = "anonymous";
266 if (pw != NULL)
267 user = xstrdup(pw->pw_name);
268 }
269 else {
270 uid_t ruid = getuid();
271
272 if ((user = getlogin()) != NULL && *user != '\0') {
273 if ((pw = getpwnam(user)) == NULL ||
274 (ruid != 0 && ruid != pw->pw_uid))
275 pw = getpwuid(ruid);
276 } else if ((pw = getpwuid(ruid)) == NULL) {
277 user = "anonymous";
278 }
279 user = xstrdup(pw ? pw->pw_name : user);
280 }
281
282 build_from(fake_from, pw);
283
284 while (argc > 0) {
285 rcpt_add(argv[0]);
286 argv++;
287 argc--;
288 }
289
290 if ((fp = tmpfile()) == NULL)
291 err(EX_UNAVAILABLE, "tmpfile");
292
293 msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp);
294
295 if (msg.rcpt_cnt == 0)
296 errx(EX_SOFTWARE, "no recipients");
297
298 /* init session */
299 rewind(fp);
300
301 /* check if working in offline mode */
302 /* If the server is not running, enqueue the message offline */
303
304 if (!srv_connected()) {
305 #if HAVE_PLEDGE
306 if (pledge("stdio", NULL) == -1)
307 err(1, "pledge");
308 #endif
309 return (enqueue_offline(save_argc, save_argv, fp, ofp));
310 }
311
312 if ((msg.fd = open_connection()) == -1)
313 errx(EX_UNAVAILABLE, "server too busy");
314
315 #if HAVE_PLEDGE
316 if (pledge("stdio wpath cpath", NULL) == -1)
317 err(1, "pledge");
318 #endif
319
320 fout = fdopen(msg.fd, "a+");
321 if (fout == NULL)
322 err(EX_UNAVAILABLE, "fdopen");
323
324 /*
325 * We need to call get_responses after every command because we don't
326 * support PIPELINING on the server-side yet.
327 */
328
329 /* banner */
330 if (!get_responses(fout, 1))
331 goto fail;
332
333 if (!send_line(fout, verbose, "EHLO localhost\r\n"))
334 goto fail;
335 if (!get_responses(fout, 1))
336 goto fail;
337
338 if (msg.dsn_envid != NULL)
339 envid_sz = strlen(msg.dsn_envid);
340
341 if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n",
342 msg.from,
343 msg.dsn_ret ? "RET=" : "",
344 msg.dsn_ret ? msg.dsn_ret : "",
345 envid_sz ? "ENVID=" : "",
346 envid_sz ? msg.dsn_envid : ""))
347 goto fail;
348 if (!get_responses(fout, 1))
349 goto fail;
350
351 for (i = 0; i < msg.rcpt_cnt; i++) {
352 if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n",
353 msg.rcpts[i],
354 msg.dsn_notify ? "NOTIFY=" : "",
355 msg.dsn_notify ? msg.dsn_notify : ""))
356 goto fail;
357 if (!get_responses(fout, 1))
358 goto fail;
359 }
360
361 if (!send_line(fout, verbose, "DATA\r\n"))
362 goto fail;
363 if (!get_responses(fout, 1))
364 goto fail;
365
366 /* add From */
367 if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n",
368 msg.fromname ? msg.fromname : "", msg.fromname ? " " : "",
369 msg.from))
370 goto fail;
371
372 /* add Date */
373 if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n",
374 time_to_text(timestamp)))
375 goto fail;
376
377 if (msg.need_linesplit) {
378 /* we will always need to mime encode for long lines */
379 if (!msg.saw_mime_version && !send_line(fout, 0,
380 "MIME-Version: 1.0\r\n"))
381 goto fail;
382 if (!msg.saw_content_type && !send_line(fout, 0,
383 "Content-Type: text/plain; charset=unknown-8bit\r\n"))
384 goto fail;
385 if (!msg.saw_content_disposition && !send_line(fout, 0,
386 "Content-Disposition: inline\r\n"))
387 goto fail;
388 if (!msg.saw_content_transfer_encoding && !send_line(fout, 0,
389 "Content-Transfer-Encoding: quoted-printable\r\n"))
390 goto fail;
391 }
392
393 /* add separating newline */
394 if (msg.noheader) {
395 if (!send_line(fout, 0, "\r\n"))
396 goto fail;
397 inheaders = 0;
398 }
399
400 for (;;) {
401 if ((len = getline(&buf, &sz, fp)) == -1) {
402 if (feof(fp))
403 break;
404 else
405 err(EX_UNAVAILABLE, "getline");
406 }
407
408 /* newlines have been normalized on first parsing */
409 if (buf[len-1] != '\n')
410 errx(EX_SOFTWARE, "expect EOL");
411 len--;
412
413 if (buf[0] == '.') {
414 if (fputc('.', fout) == EOF)
415 goto fail;
416 }
417
418 line = buf;
419
420 if (inheaders) {
421 if (strncasecmp("from ", line, 5) == 0)
422 continue;
423 if (strncasecmp("return-path: ", line, 13) == 0)
424 continue;
425 }
426
427 if (msg.saw_content_transfer_encoding || msg.noheader ||
428 inheaders || !msg.need_linesplit) {
429 if (!send_line(fout, 0, "%.*s\r\n", (int)len, line))
430 goto fail;
431 if (inheaders && buf[0] == '\n')
432 inheaders = 0;
433 continue;
434 }
435
436 /* we don't have a content transfer encoding, use our default */
437 qp_encoded_write(fout, line);
438 }
439 free(buf);
440 if (!send_line(fout, verbose, ".\r\n"))
441 goto fail;
442 if (!get_responses(fout, 1))
443 goto fail;
444
445 if (!send_line(fout, verbose, "QUIT\r\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
get_responses(FILE * fin,int n)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
send_line(FILE * fp,int v,char * fmt,...)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
build_from(char * fake_from,struct passwd * pw)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);
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, "asprintf");
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, "asprintf");
570 }
571 }
572 }
573
574 static int
parse_message(FILE * fin,int get_from,int tflag,FILE * fout)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
parse_addr(char * s,size_t len,int is_from)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 && s[pos] == '\\')
720 pstate.esc = 1;
721 else
722 pstate.esc = 0;
723 }
724
725 if (terminal)
726 parse_addr_terminal(is_from);
727
728 for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
729 ; /* nothing */
730
731 if (pos < len)
732 parse_addr(s + pos, len - pos, is_from);
733 }
734
735 static void
parse_addr_terminal(int is_from)736 parse_addr_terminal(int is_from)
737 {
738 if (pstate.comment || pstate.quote || pstate.esc)
739 errx(1, "syntax error in address");
740 if (pstate.wpos) {
741 if (pstate.wpos >= sizeof(pstate.buf))
742 errx(1, "address exceeds buffer size");
743 pstate.buf[pstate.wpos] = '\0';
744 if (is_from)
745 msg.from = qualify_addr(pstate.buf);
746 else
747 rcpt_add(pstate.buf);
748 pstate.wpos = 0;
749 }
750 }
751
752 static char *
qualify_addr(char * in)753 qualify_addr(char *in)
754 {
755 char *out;
756
757 if (strlen(in) > 0 && strchr(in, '@') == NULL) {
758 if (asprintf(&out, "%s@%s", in, host) == -1)
759 err(1, "qualify asprintf");
760 } else
761 out = xstrdup(in);
762
763 return (out);
764 }
765
766 static void
rcpt_add(char * addr)767 rcpt_add(char *addr)
768 {
769 void *nrcpts;
770 char *p;
771 int n;
772
773 n = 1;
774 p = addr;
775 while ((p = strchr(p, ',')) != NULL) {
776 n++;
777 p++;
778 }
779
780 if ((nrcpts = reallocarray(msg.rcpts,
781 msg.rcpt_cnt + n, sizeof(char *))) == NULL)
782 err(1, "rcpt_add realloc");
783 msg.rcpts = nrcpts;
784
785 while (n--) {
786 if ((p = strchr(addr, ',')) != NULL)
787 *p++ = '\0';
788 msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
789 if (p == NULL)
790 break;
791 addr = p;
792 }
793 }
794
795 static int
open_connection(void)796 open_connection(void)
797 {
798 struct imsg imsg;
799 int fd;
800 int n;
801
802 imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0);
803
804 while (ibuf->w.queued)
805 if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
806 err(1, "write error");
807
808 while (1) {
809 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
810 errx(1, "imsg_read error");
811 if (n == 0)
812 errx(1, "pipe closed");
813
814 if ((n = imsg_get(ibuf, &imsg)) == -1)
815 errx(1, "imsg_get error");
816 if (n == 0)
817 continue;
818
819 switch (imsg.hdr.type) {
820 case IMSG_CTL_OK:
821 break;
822 case IMSG_CTL_FAIL:
823 errx(1, "server disallowed submission request");
824 default:
825 errx(1, "unexpected imsg reply type");
826 }
827
828 fd = imsg.fd;
829 imsg_free(&imsg);
830
831 break;
832 }
833
834 return fd;
835 }
836
837 static int
enqueue_offline(int argc,char * argv[],FILE * ifile,FILE * ofile)838 enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile)
839 {
840 int i, ch;
841
842 for (i = 1; i < argc; i++) {
843 if (strchr(argv[i], '|') != NULL) {
844 warnx("%s contains illegal character", argv[i]);
845 ftruncate(fileno(ofile), 0);
846 exit(EX_SOFTWARE);
847 }
848 if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0)
849 goto write_error;
850 }
851
852 if (fputc('\n', ofile) == EOF)
853 goto write_error;
854
855 while ((ch = fgetc(ifile)) != EOF) {
856 if (fputc(ch, ofile) == EOF)
857 goto write_error;
858 }
859
860 if (ferror(ifile)) {
861 warn("read error");
862 ftruncate(fileno(ofile), 0);
863 exit(EX_UNAVAILABLE);
864 }
865
866 if (fclose(ofile) == EOF)
867 goto write_error;
868
869 return (EX_TEMPFAIL);
870 write_error:
871 warn("write error");
872 ftruncate(fileno(ofile), 0);
873 exit(EX_UNAVAILABLE);
874 }
875
876 static int
savedeadletter(struct passwd * pw,FILE * in)877 savedeadletter(struct passwd *pw, FILE *in)
878 {
879 char buffer[PATH_MAX];
880 FILE *fp;
881 char *buf = NULL;
882 size_t sz = 0;
883 ssize_t len;
884
885 (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir);
886
887 if (fseek(in, 0, SEEK_SET) != 0)
888 return 0;
889
890 if ((fp = fopen(buffer, "w")) == NULL)
891 return 0;
892
893 /* add From */
894 if (!msg.saw_from)
895 fprintf(fp, "From: %s%s<%s>\n",
896 msg.fromname ? msg.fromname : "",
897 msg.fromname ? " " : "",
898 msg.from);
899
900 /* add Date */
901 if (!msg.saw_date)
902 fprintf(fp, "Date: %s\n", time_to_text(timestamp));
903
904 if (msg.need_linesplit) {
905 /* we will always need to mime encode for long lines */
906 if (!msg.saw_mime_version)
907 fprintf(fp, "MIME-Version: 1.0\n");
908 if (!msg.saw_content_type)
909 fprintf(fp, "Content-Type: text/plain; "
910 "charset=unknown-8bit\n");
911 if (!msg.saw_content_disposition)
912 fprintf(fp, "Content-Disposition: inline\n");
913 if (!msg.saw_content_transfer_encoding)
914 fprintf(fp, "Content-Transfer-Encoding: "
915 "quoted-printable\n");
916 }
917
918 /* add separating newline */
919 if (msg.noheader)
920 fprintf(fp, "\n");
921
922 while ((len = getline(&buf, &sz, in)) != -1) {
923 if (buf[len - 1] == '\n')
924 buf[len - 1] = '\0';
925 fprintf(fp, "%s\n", buf);
926 }
927
928 free(buf);
929 fprintf(fp, "\n");
930 fclose(fp);
931 return 1;
932 }
933