1 /*
2 ** spost.c -- feed messages to sendmail
3 **
4 ** This is a simpler, faster, replacement for "post" for use
5 ** when "sendmail" is the transport system.
6 **
7 ** This code is Copyright (c) 2002, by the authors of nmh. See the
8 ** COPYRIGHT file in the root directory of the nmh distribution for
9 ** complete copyright information.
10 */
11
12 #include <h/mh.h>
13 #include <signal.h>
14 #include <h/addrsbr.h>
15 #include <h/aliasbr.h>
16 #include <h/dropsbr.h>
17 #include <h/tws.h>
18 #include <h/utils.h>
19 #include <unistd.h>
20 #include <locale.h>
21 #include <sysexits.h>
22 #include <errno.h>
23
24 #define MAX_SM_FIELD 1476 /* < largest hdr field sendmail will accept */
25
26 static struct swit switches[] = {
27 #define VERBSW 0
28 { "verbose", 0 },
29 #define NVERBSW 1
30 { "noverbose", 2 },
31 #define VERSIONSW 2
32 { "Version", 0 },
33 #define HELPSW 3
34 { "help", 0 },
35 #define DEBUGSW 4
36 { "debug", -5 },
37 #define DISTSW 5
38 { "dist", -4 }, /* interface from dist */
39 { NULL, 0 }
40 };
41
42 char *version=VERSION;
43
44 /* flags for headers->flags */
45 #define HNOP 0x0000 /* just used to keep .set around */
46 #define HBAD 0x0001 /* bad header - don't let it through */
47 #define HADR 0x0002 /* header has an address field */
48 #define HSUB 0x0004 /* Subject: header */
49 #define HTRY 0x0008 /* try to send to addrs on header */
50 #define HBCC 0x0010 /* don't output this header */
51 #define HFCC 0x0020 /* FCC: type header */
52 #define HIGN 0x0040 /* ignore this header */
53 #define HDCC 0x0080 /* DCC: type header */
54
55 /* flags for headers->set */
56 #define MFRM 0x0001 /* we've seen a From: */
57 #define MDAT 0x0002 /* we've seen a Date: */
58 #define MRFM 0x0004 /* we've seen a Resent-From: */
59 #define MVIS 0x0008 /* we've seen sighted addrs */
60 #define MINV 0x0010 /* we've seen blind addrs */
61 #define MRDT 0x0020 /* we've seen a Resent-Date: */
62 #define MFMM 0x0040 /* The Mail is From a Alternative-Mailbox Addresse */
63
64 struct headers {
65 char *value;
66 unsigned int flags;
67 unsigned int set;
68 };
69
70 static struct headers NHeaders[] = {
71 { "Return-Path", HBAD, 0 },
72 { "Received", HBAD, 0 },
73 { "Reply-To", HADR, 0 },
74 { "From", HADR, MFRM },
75 { "Sender", HADR|HBAD, 0 },
76 { "Date", HNOP, MDAT },
77 { "Subject", HSUB, 0 },
78 { "To", HADR|HTRY, MVIS },
79 { "Cc", HADR|HTRY, MVIS },
80 { "Dcc", HADR|HTRY|HDCC, MINV },
81 { "Bcc", HADR|HTRY|HBCC, MINV },
82 { "Message-Id", HBAD, 0 },
83 { "Fcc", HFCC, 0 },
84 { "Envelope-From", HIGN, 0 },
85 { NULL, 0, 0 }
86 };
87
88 static struct headers RHeaders[] = {
89 { "Resent-Reply-To", HADR, 0 },
90 { "Resent-From", HADR, MRFM },
91 { "Resent-Sender", HADR|HBAD, 0 },
92 { "Resent-Date", HNOP, MRDT },
93 { "Resent-Subject", HSUB, 0 },
94 { "Resent-To", HADR|HTRY, MVIS },
95 { "Resent-Cc", HADR|HTRY, MVIS },
96 { "Resent-Dcc", HADR|HTRY|HDCC, MINV },
97 { "Resent-Bcc", HADR|HTRY|HBCC, MINV },
98 { "Resent-Message-Id", HBAD, 0 },
99 { "Resent-Fcc", HFCC, 0 },
100 { "Reply-To", HADR, 0 },
101 { "Fcc", HIGN, 0 },
102 { "Envelope-From", HIGN, 0 },
103 { NULL, 0, 0 }
104 };
105
106
107 static int badmsg = 0;
108 static int verbose = 0;
109 static int debug = 0;
110 static int aliasflg = 0; /* if going to process aliases */
111
112 static unsigned msgflags = 0; /* what we've seen */
113
114 static enum {
115 normal, resent
116 } msgstate = normal;
117
118 static char *tmpfil;
119
120 static char *subject = NULL; /* the subject field for BCC'ing */
121 static struct mailname *from = NULL; /* the from field for BCC'ing */
122 static char fccs[BUFSIZ] = "";
123 struct mailname *bccs = NULL; /* list of the bcc recipients */
124 struct mailname *recipients = NULL; /* list of the recipients */
125 size_t recipientsc = 0;
126 struct mailname *sender = NULL;
127
128 static struct headers *hdrtab; /* table for the message we're doing */
129 static FILE *out; /* output (temp) file */
130
131 /*
132 ** static prototypes
133 */
134 static void putfmt(char *, char *, FILE *);
135 static void finish_headers(FILE *);
136 static int get_header(char *, struct headers *);
137 static void putadr(char *, struct mailname *);
138 static int putone(char *, int, int);
139 static void process_fcc(char *);
140 static void fcc(char *, char *);
141 static void process_bccs(char *);
142 static size_t do_aliasing(struct mailname *, struct mailname **);
143
144
145 int
main(int argc,char ** argv)146 main(int argc, char **argv)
147 {
148 enum state state;
149 struct field f = {{0}};
150 int compnum;
151 char *cp, *msg = NULL, **argp, **arguments;
152 char **sargv, buf[BUFSIZ];
153 FILE *in;
154
155 setlocale(LC_ALL, "");
156 invo_name = mhbasename(argv[0]);
157
158 context_read();
159
160 arguments = getarguments(invo_name, argc, argv, 0);
161 argp = arguments;
162
163 while ((cp = *argp++)) {
164 if (*cp == '-') {
165 switch (smatch(++cp, switches)) {
166 case AMBIGSW:
167 ambigsw(cp, switches);
168 exit(EX_USAGE);
169 case UNKWNSW:
170 adios(EX_USAGE, NULL, "-%s unknown", cp);
171
172 case HELPSW:
173 snprintf(buf, sizeof(buf),
174 "%s [switches] file",
175 invo_name);
176 print_help(buf, switches, 1);
177 exit(argc == 2 ? EX_OK : EX_USAGE);
178 case VERSIONSW:
179 print_version(invo_name);
180 exit(argc == 2 ? EX_OK : EX_USAGE);
181
182 case DEBUGSW:
183 debug++;
184 continue;
185
186 case DISTSW:
187 msgstate = resent;
188 continue;
189
190 case VERBSW:
191 verbose++;
192 continue;
193 case NVERBSW:
194 verbose = 0;
195 continue;
196 }
197 }
198 if (msg) {
199 adios(EX_USAGE, NULL, "only one message at a time!");
200 } else {
201 msg = cp;
202 }
203 }
204
205 if (!msg) {
206 adios(EX_USAGE, NULL, "usage: %s [switches] file", invo_name);
207 }
208
209 if ((in = fopen(msg, "r")) == NULL) {
210 adios(EX_IOERR, msg, "unable to open");
211 }
212
213 if (debug) {
214 verbose++;
215 out = stdout;
216 } else {
217 tmpfil = mh_xstrdup(m_mktemp2("/tmp/", invo_name, NULL, &out));
218 }
219
220 /* check for "Aliasfile:" profile entry */
221 if ((cp = context_find("Aliasfile"))) {
222 char *dp, **ap;
223
224 aliasflg = 1;
225 for (ap=brkstring(dp=mh_xstrdup(cp), " ", "\n"); ap && *ap;
226 ap++) {
227 if ((state = alias(etcpath(*ap))) != AK_OK) {
228 adios(EX_IOERR, NULL, "aliasing error in file %s: %s",
229 *ap, akerror(state));
230 }
231 }
232 }
233
234
235 hdrtab = (msgstate == normal) ? NHeaders : RHeaders;
236
237 for (compnum = 1, state = FLD2;;) {
238 switch (state = m_getfld2(state, &f, in)) {
239 case FLD2:
240 compnum++;
241 putfmt(f.name, f.value, out);
242 continue;
243
244 case BODY2:
245 finish_headers(out);
246 fprintf(out, "\n%s", f.value);
247 while ((state = m_getfld2(state, &f, in)) == BODY2) {
248 if (f.valuelen > NAMESZ+1 || (!f.crlf && f.valuelen > NAMESZ)) {
249 adios(EX_DATAERR, NULL, "Body contains a to long line");
250 }
251 fputs(f.value, out);
252 }
253 break;
254
255 case FILEEOF2:
256 finish_headers(out);
257 break;
258
259 case LENERR2:
260 case FMTERR2:
261 case IOERR2:
262 adios(EX_DATAERR, NULL, "message format error in component #%d",
263 compnum);
264
265 default:
266 adios(EX_SOFTWARE, NULL, "getfld() returned %d", state);
267 }
268 break;
269 }
270 fclose(in);
271
272 if (state != FILEEOF2) {
273 adios(EX_IOERR, "m_getfld2", "Error while reading body");
274 }
275
276 if (debug) {
277 struct mailname *i = recipients;
278 /* stop here */
279 puts("----EOM----");
280 while (i) {
281 fputs(i->m_mbox, stdout);
282 if (i->m_host) {
283 fputs("@", stdout);
284 fputs(i->m_host, stdout);
285 }
286 fputs("\n", stdout);
287 i = i->m_next;
288 mnfree(recipients);
289 recipients = i;
290 }
291 exit(EX_OK);
292 }
293
294 fclose(out);
295
296 /* process Fcc */
297 if (*fccs) {
298 fcc(tmpfil, fccs);
299 }
300
301 if (bccs) {
302 process_bccs(tmpfil);
303 if (!(msgflags & MVIS)) {
304 /* only Bcc rcpts: we're finished here */
305 unlink(tmpfil);
306 exit(EX_OK);
307 }
308 }
309
310 /*
311 ** re-open the temp file, unlink it and exec sendmail, giving it
312 ** the msg temp file as std in.
313 */
314 if (!freopen(tmpfil, "r", stdin)) {
315 adios(EX_IOERR, tmpfil, "can't reopen for sendmail");
316 }
317 unlink(tmpfil);
318
319 if (recipientsc == 0) {
320 adios(EX_DATAERR, NULL, "message has no recipients");
321 }
322
323 sargv = mh_xcalloc(recipientsc + 4, sizeof(char **));
324
325 argp = sargv;
326 *argp++ = "send-mail";
327 *argp++ = "-i"; /* don't stop on "." */
328 if (verbose) {
329 *argp++ = "-v";
330 }
331
332 while (recipients != NULL) {
333 cp = mh_xstrdup(recipients->m_mbox);
334 if (recipients->m_host) {
335 cp = add("@", cp);
336 cp = add(recipients->m_host, cp);
337 }
338 *argp++ = cp;
339 cp = NULL;
340 recipients = recipients->m_next;
341 }
342 *argp = NULL;
343 execvp(sendmail, sargv);
344
345 if (errno == E2BIG) {
346 adios(EX_DATAERR, sendmail, "too much arguments, probably to much recipients");
347 }
348
349 adios(EX_OSERR, sendmail, "can't exec");
350 return -1;
351 }
352
353
354 /* DRAFT GENERATION */
355
356 static void
putfmt(char * name,char * str,FILE * out)357 putfmt(char *name, char *str, FILE *out)
358 {
359 int i;
360 struct headers *hdr;
361 struct mailname addr_start, *addr_end;
362 size_t addrc = 0;
363 ssize_t ret;
364
365 addr_end = &addr_start;
366 addr_end->m_next = NULL;
367
368 /* remove leading whitespace */
369 while (*str==' ' || *str=='\t') {
370 str++;
371 }
372
373 if ((i = get_header(name, hdrtab)) == NOTOK) {
374 /* no header we would care for */
375 if (mh_strcasecmp(name, attach_hdr)==0) {
376 return;
377 }
378 if (mh_strcasecmp(name, sign_hdr)==0) {
379 return;
380 }
381 if (mh_strcasecmp(name, enc_hdr)==0) {
382 return;
383 }
384 /* push it through */
385 fprintf(out, "%s: %s", name, str);
386 return;
387 }
388 /* it's one of the interesting headers */
389 hdr = &hdrtab[i];
390
391 if (hdr->flags & HIGN || strcmp(str, "\n")==0) {
392 return;
393 }
394
395 if (hdr->flags & HBAD) {
396 advise(NULL, "illegal header line -- %s:", name);
397 badmsg++;
398 return;
399 }
400
401 msgflags |= hdr->set;
402
403 if (hdr->flags & HFCC) {
404 process_fcc(str);
405 return;
406 }
407
408 if (hdr->flags & HSUB) {
409 subject = mh_xstrdup(str);
410 }
411
412 if (!(hdr->flags & HADR)) {
413 fprintf(out, "%s: %s", name, str);
414 return;
415 }
416
417 if ((ret = getmboxes(str, &addr_end)) < 0) {
418 adios(EX_DATAERR, NULL, "can't parse address: %s", str);
419 }
420
421 addrc += ret;
422
423 if (aliasflg) {
424 addrc += do_aliasing(&addr_start, &addr_end);
425 }
426
427 if (hdr->flags & HBCC) {
428 addr_end->m_next = bccs;
429 bccs = addr_start.m_next;
430 return;
431 }
432
433 if (hdr->set & MFRM) {
434 struct mailname *mp = NULL;
435 struct mailname *my = NULL;
436
437 /* needed because the address parser holds global state */
438 ismymbox(NULL);
439
440 for (mp = addr_start.m_next; mp; mp = mp->m_next) {
441 if (ismymbox(mp)) {
442 msgflags |= MFMM;
443 if (my == NULL) {
444 from = my = mp;
445 }
446 }
447 }
448
449 if (addrc > 1) {
450 sender = my;
451 }
452 }
453
454 if (!(hdr->flags & HDCC)) {
455 putadr(name, addr_start.m_next);
456 }
457
458 if (hdr->flags & HTRY) {
459 addr_end->m_next = recipients;
460 recipients = addr_start.m_next;
461 recipientsc += i;
462 }
463 }
464
465
466 /*
467 ** Add yet missing headers.
468 */
469 static void
finish_headers(FILE * out)470 finish_headers(FILE *out)
471 {
472 char *cp;
473 char from[BUFSIZ]; /* my network address */
474 char signature[BUFSIZ]; /* my signature */
475 char *resentstr = (msgstate == resent) ? "Resent-" : "";
476
477 if (!(msgflags & MDAT)) {
478 fprintf(out, "%sDate: %s\n", resentstr, dtimenow());
479 }
480
481 if (sender != NULL) {
482 snprintf(signature, sizeof(signature), "%s", sender->m_text);
483 } else if ((cp = context_find("Default-From")) != NULL) {
484 snprintf(signature, sizeof(signature), "%s", cp);
485 } else {
486 snprintf(from, sizeof(from), "%s@%s", getusername(), LocalName());
487 if ((cp = getfullname()) && *cp) {
488 snprintf(signature, sizeof(signature), "%s <%s>", cp, from);
489 } else {
490 snprintf(signature, sizeof(signature), "%s", from);
491 }
492 }
493 if (!(msgflags & MFRM)) {
494 fprintf(out, "%sFrom: %s\n", resentstr, signature);
495 } else {
496 /*
497 ** Add a Sender: header because the From: header could
498 ** be fake or contain multiple addresses.
499 */
500 if (!(msgflags & MFMM) || sender != NULL) {
501 fprintf(out, "%sSender: %s\n", resentstr, signature);
502 }
503 }
504 if (!(msgflags & MVIS)) {
505 fprintf(out, "%sBcc: undisclosed-recipients:;\n", resentstr);
506 }
507 if (badmsg) {
508 unlink(tmpfil);
509 adios(EX_DATAERR, NULL, "re-format message and try again");
510 }
511 }
512
513
514 /*
515 ** Return index of the requested header in the table, or NOTOK if missing.
516 */
517 static int
get_header(char * header,struct headers * table)518 get_header(char *header, struct headers *table)
519 {
520 struct headers *h;
521
522 for (h=table; h->value; h++) {
523 if (mh_strcasecmp(header, h->value)==0) {
524 return (h - table);
525 }
526 }
527 return NOTOK;
528 }
529
530
531 /*
532 ** output the address list for header "name". The address list
533 ** is a linked list of mailname structs. "nl" points to the head
534 ** of the list. Alias substitution should be done on nl.
535 */
536 static void
putadr(char * name,struct mailname * nl)537 putadr(char *name, struct mailname *nl)
538 {
539 struct mailname *mp;
540 char *cp;
541 int linepos;
542 int namelen;
543
544 fprintf(out, "%s: ", name);
545 namelen = strlen(name) + 2;
546 linepos = namelen;
547
548 for (mp = nl; mp; ) {
549 if (linepos > MAX_SM_FIELD) {
550 fprintf(out, "\n%s: ", name);
551 linepos = namelen;
552 }
553 if (mp->m_ingrp) {
554 if (mp->m_gname != NULL) {
555 cp = mh_xstrdup(mp->m_gname);
556 cp = add(";", cp);
557 linepos = putone(cp, linepos, namelen);
558 mh_free0(&cp);
559 cp = NULL;
560 }
561 } else {
562 linepos = putone(mp->m_text, linepos, namelen);
563 }
564 mp = mp->m_next;
565 }
566 putc('\n', out);
567 }
568
569 static int
putone(char * adr,int pos,int indent)570 putone(char *adr, int pos, int indent)
571 {
572 int len;
573 static int linepos;
574
575 len = strlen(adr);
576 if (pos == indent) {
577 linepos = pos;
578 } else if (linepos+len > OUTPUTLINELEN) {
579 fprintf(out, ",\n%*s", indent, "");
580 linepos = indent;
581 pos += indent + 2;
582 } else {
583 fputs(", ", out);
584 linepos += 2;
585 pos += 2;
586 }
587 fputs(adr, out);
588
589 linepos += len;
590 return (pos+len);
591 }
592
593
594 static void
process_fcc(char * str)595 process_fcc(char *str)
596 {
597 char *cp, *pp;
598 int state = 0;
599
600 if (strlen(str)+strlen(fccs) > sizeof fccs /2) {
601 adios(EX_DATAERR, NULL, "Too much Fcc data");
602 }
603 /* todo: better have three states: SEPARATOR, PLUS, WORD */
604 for (cp=pp=str; *cp; cp++) {
605 switch (*cp) {
606 case ' ':
607 case '\t':
608 case '\n':
609 case ',':
610 if (state != 0) {
611 state = 0;
612 *cp = '\0';
613 if (*pp=='+' || *pp=='@') {
614 strcat(fccs, " ");
615 } else {
616 strcat(fccs, " +");
617 }
618 strcat(fccs, pp);
619 }
620 break;
621 default:
622 if (state == 0) {
623 state = 1;
624 pp = cp;
625 }
626 break;
627 }
628 }
629 }
630
631
632 static void
fcc(char * file,char * folders)633 fcc(char *file, char *folders)
634 {
635 int status;
636 char cmd[BUFSIZ];
637
638 if (verbose) {
639 printf("%sFcc: %s\n", msgstate == resent ? "Resent-" : "",
640 folders);
641 fflush(stdout);
642 }
643 if (100+strlen(file)+strlen(folders) > sizeof cmd) {
644 adios(EX_DATAERR, NULL, "Too much Fcc data");
645 }
646 /* hack: read from /dev/null and refile(1) won't question us */
647 snprintf(cmd, sizeof cmd, "</dev/null refile -link -file '%s' %s",
648 file, folders);
649 status = system(cmd);
650 if (status == -1) {
651 fprintf(stderr, "Skipped %sFcc %s: unable to system().\n",
652 msgstate == resent ? "Resent-" : "", folders);
653 } else if (status != 0) {
654 fprintf(stderr, "%sFcc %s: Problems occurred.\n",
655 msgstate == resent ? "Resent-" : "", folders);
656 }
657 }
658
659
660 /* BCC GENERATION */
661
662 static void
process_bccs(char * origmsg)663 process_bccs(char *origmsg)
664 {
665 char *bccdraft = NULL;
666 struct mailname *mp = NULL;
667 FILE *out = NULL;
668
669 for (mp=bccs; mp; mp=mp->m_next) {
670 bccdraft = mh_xstrdup(m_mktemp2("/tmp/", invo_name, NULL, &out));
671 fprintf(out, "To: %s\n", mp->m_text);
672 if (from) {
673 fprintf(out, "From: %s\n", from->m_text);
674 }
675 fprintf(out, "Subject: [BCC] %s", subject ? subject : "");
676 fprintf(out, "%s: %s\n", attach_hdr, origmsg);
677 fprintf(out, "------------\n");
678 fclose(out);
679
680 if (execprogl("send", "send", bccdraft, (char *)NULL) != 0) {
681 admonish(invo_name, "Problems to send Bcc to %s",
682 mp->m_text);
683 unlink(bccdraft);
684 }
685 }
686 }
687
688 /*
689 * Do aliasing on a mailname linked list
690 * Begin at start->m_next
691 * End if m_next == NULL
692 * **end is set to the new end.
693 * Return the number of new mainames in the list
694 */
695
696 static size_t
do_aliasing(struct mailname * start,struct mailname ** end)697 do_aliasing(struct mailname *start, struct mailname **end)
698 {
699 struct mailname *prev, *cur;
700 char *cp;
701 size_t i = 0;
702 ssize_t e;
703
704 prev = start;
705 cur = prev->m_next;
706
707 while (cur != NULL) {
708 if (cur->m_nohost) {
709 cp = akvalue(cur->m_mbox);
710 if (strcmp(cp, cur->m_mbox) != 0) {
711 prev->m_next = cur->m_next;
712 if ((e = getmboxes(cp, &prev)) < 0) {
713 goto error;
714 }
715 i += e;
716 i -= 1;
717 mnfree(cur);
718 } else {
719 prev = cur;
720 }
721 } else {
722 prev = cur;
723 }
724 cur = prev->m_next;
725 }
726 *end = prev;
727 return i;
728 error:
729 adios(EX_CONFIG, NULL, "can't parse alias %s: %s", cur->m_mbox, cp);
730 return 0; /* not reached */
731 }
732