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