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