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