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