redesign version string
[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 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
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) {
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
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
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
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
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
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
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
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
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
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 }