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