Merge branch 'master' of git.sv.nongnu.org:/srv/git/nmh
[mmh] / uip / spost.c
1
2 /*
3  * spost.c -- feed messages to sendmail
4  *
5  * This is a simpler, faster, replacement for "post" for use
6  * when "sendmail" is the transport system.
7  *
8  * This code is Copyright (c) 2002, by the authors of nmh.  See the
9  * COPYRIGHT file in the root directory of the nmh distribution for
10  * complete copyright information.
11  */
12
13 #include <h/mh.h>
14 #include <signal.h>
15 #include <h/addrsbr.h>
16 #include <h/aliasbr.h>
17 #include <h/dropsbr.h>
18 #include <h/tws.h>
19 #include <h/mts.h>
20 #include <h/utils.h>
21
22 #define MAX_SM_FIELD    1476    /* < largest hdr field sendmail will accept */
23 #define FCCS            10      /* max number of fccs allowed */
24
25 struct swit switches[] = {
26 #define FILTSW               0
27     { "filter filterfile", 0 },
28 #define NFILTSW              1
29     { "nofilter", 0 },
30 #define REMVSW               2
31     { "remove", 0 },
32 #define NREMVSW              3
33     { "noremove", 0 },
34 #define VERBSW               4
35     { "verbose", 0 },
36 #define NVERBSW              5
37     { "noverbose", 0 },
38 #define WATCSW               6
39     { "watch", 0 },
40 #define NWATCSW              7
41     { "nowatch", 0 },
42 #define BACKSW               8
43     { "backup", 0 },
44 #define NBACKSW              9
45     { "nobackup", 0 },
46 #define ALIASW              10
47     { "alias aliasfile", 0 },
48 #define NALIASW             11
49     { "noalias", 0 },
50 #define WIDTHSW             12
51     { "width columns", 0 },
52 #define VERSIONSW           13
53     { "version", 0 },
54 #define HELPSW              14
55     { "help", 0 },
56 #define DEBUGSW             15
57     { "debug", -5 },
58 #define DISTSW              16
59     { "dist", -4 },             /* interface from dist */
60 #define WHOMSW              17
61     { "whom", -4 },             /* interface from whom */
62 #define PUSHSW              18  /* fork to sendmail then exit */
63     { "push", -4 },
64 #define NPUSHSW             19  /* exec sendmail */
65     { "nopush", -6 },
66 #define LIBSW               20
67     { "library directory", -7 },
68 #define ANNOSW              21
69     { "idanno number", -6 },
70     { NULL, 0 }
71 };
72
73
74 /* flags for headers->flags */
75 #define HNOP    0x0000          /* just used to keep .set around */
76 #define HBAD    0x0001          /* bad header - don't let it through */
77 #define HADR    0x0002          /* header has an address field */
78 #define HSUB    0x0004          /* Subject: header */
79 #define HTRY    0x0008          /* try to send to addrs on header */
80 #define HBCC    0x0010          /* don't output this header */
81 /* #define      HMNG    0x0020  */      /* munge this header */
82 #define HNGR    0x0040          /* no groups allowed in this header */
83 #define HFCC    0x0080          /* FCC: type header */
84 #define HNIL    0x0100          /* okay for this header not to have addrs */
85 #define HIGN    0x0200          /* ignore this header */
86
87 /* flags for headers->set */
88 #define MFRM    0x0001          /* we've seen a From: */
89 #define MDAT    0x0002          /* we've seen a Date: */
90 #define MRFM    0x0004          /* we've seen a Resent-From: */
91 #define MVIS    0x0008          /* we've seen sighted addrs */
92 #define MINV    0x0010          /* we've seen blind addrs */
93 #define MRDT    0x0020          /* we've seen a Resent-Date: */
94
95 struct headers {
96     char *value;
97     unsigned int flags;
98     unsigned int set;
99 };
100
101
102 static struct headers NHeaders[] = {
103     { "Return-Path", HBAD,                0 },
104     { "Received",    HBAD,                0 },
105     { "Reply-To",    HADR|HNGR,           0 },
106     { "From",        HADR|HNGR,           MFRM },
107     { "Sender",      HADR|HBAD,           0 },
108     { "Date",        HNOP,                MDAT },
109     { "Subject",     HSUB,                0 },
110     { "To",          HADR|HTRY,           MVIS },
111     { "cc",          HADR|HTRY,           MVIS },
112     { "Bcc",         HADR|HTRY|HBCC|HNIL, MINV },
113     { "Message-Id",  HBAD,                0 },
114     { "Fcc",         HFCC,                0 },
115     { NULL,          0,                   0 }
116 };
117
118 static struct headers RHeaders[] = {
119     { "Resent-Reply-To",   HADR|HNGR,      0 },
120     { "Resent-From",       HADR|HNGR,      MRFM },
121     { "Resent-Sender",     HADR|HBAD,      0 },
122     { "Resent-Date",       HNOP,           MRDT },
123     { "Resent-Subject",    HSUB,           0 },
124     { "Resent-To",         HADR|HTRY,      MVIS },
125     { "Resent-cc",         HADR|HTRY,      MVIS },
126     { "Resent-Bcc",        HADR|HTRY|HBCC, MINV },
127     { "Resent-Message-Id", HBAD,           0 },
128     { "Resent-Fcc",        HFCC,           0 },
129     { "Reply-To",          HADR,           0 },
130     { "Fcc",               HIGN,           0 },
131     { NULL,                0,              0 }
132 };
133
134
135 static short fccind = 0;        /* index into fccfold[] */
136
137 static int badmsg = 0;          /* message has bad semantics            */
138 static int verbose = 0;         /* spell it out                         */
139 static int debug = 0;           /* debugging post                       */
140 static int rmflg = 1;           /* remove temporary file when done      */
141 static int watch = 0;           /* watch the delivery process           */
142 static int backflg = 0;         /* rename input file as *.bak when done */
143 static int whomflg = 0;         /* if just checking addresses           */
144 static int pushflg = 0;         /* if going to fork to sendmail         */
145 static int aliasflg = -1;       /* if going to process aliases          */
146 static int outputlinelen=72;
147
148 static unsigned msgflags = 0;   /* what we've seen */
149
150 static enum {
151     normal, resent
152 } msgstate = normal;
153
154 static char tmpfil[] = "/tmp/pstXXXXXX";
155
156 static char from[BUFSIZ];       /* my network address */
157 static char signature[BUFSIZ];  /* my signature */
158 static char *filter = NULL;     /* the filter for BCC'ing */
159 static char *subject = NULL;    /* the subject field for BCC'ing */
160 static char *fccfold[FCCS];     /* foldernames for FCC'ing */
161
162 static struct headers *hdrtab;  /* table for the message we're doing */
163 static FILE *out;               /* output (temp) file */
164
165 extern char *sendmail;
166
167 /*
168  * external prototypes
169  */
170 extern char *getfullname (void);
171 extern char *getusername (void);
172
173 extern boolean  draft_from_masquerading;  /* defined in mts.c */
174
175 /*
176  * static prototypes
177  */
178 static void putfmt (char *, char *, FILE *);
179 static void start_headers (void);
180 static void finish_headers (FILE *);
181 static int get_header (char *, struct headers *);
182 static void putadr (char *, struct mailname *);
183 static int putone (char *, int, int);
184 static void insert_fcc (struct headers *, unsigned char *);
185 static void file (char *);
186 static void fcc (char *, char *);
187
188 #if 0
189 static void die (char *, char *, ...);
190 static void make_bcc_file (void);
191 #endif
192
193
194 int
195 main (int argc, char **argv)
196 {
197     int state, i, pid, compnum;
198     char *cp, *msg = NULL, **argp, **arguments;
199     char *sargv[16], buf[BUFSIZ], name[NAMESZ];
200     FILE *in;
201
202 #ifdef LOCALE
203     setlocale(LC_ALL, "");
204 #endif
205     invo_name = r1bindex (argv[0], '/');
206
207     /* foil search of user profile/context */
208     if (context_foil (NULL) == -1)
209         done (1);
210
211     mts_init (invo_name);
212     arguments = getarguments (invo_name, argc, argv, 0);
213     argp = arguments;
214
215     while ((cp = *argp++)) {
216         if (*cp == '-') {
217             switch (smatch (++cp, switches)) {
218                 case AMBIGSW: 
219                     ambigsw (cp, switches);
220                     done (1);
221                 case UNKWNSW: 
222                     adios (NULL, "-%s unknown", cp);
223
224                 case HELPSW: 
225                     snprintf (buf, sizeof(buf), "%s [switches] file", invo_name);
226                     print_help (buf, switches, 1);
227                     done (1);
228                 case VERSIONSW:
229                     print_version(invo_name);
230                     done (1);
231
232                 case DEBUGSW: 
233                     debug++;
234                     continue;
235
236                 case DISTSW:
237                     msgstate = resent;
238                     continue;
239
240                 case WHOMSW:
241                     whomflg++;
242                     continue;
243
244                 case FILTSW:
245                     if (!(filter = *argp++) || *filter == '-')
246                         adios (NULL, "missing argument to %s", argp[-2]);
247                     continue;
248                 case NFILTSW:
249                     filter = NULL;
250                     continue;
251                 
252                 case REMVSW: 
253                     rmflg++;
254                     continue;
255                 case NREMVSW: 
256                     rmflg = 0;
257                     continue;
258
259                 case BACKSW: 
260                     backflg++;
261                     continue;
262                 case NBACKSW: 
263                     backflg = 0;
264                     continue;
265
266                 case VERBSW: 
267                     verbose++;
268                     continue;
269                 case NVERBSW: 
270                     verbose = 0;
271                     continue;
272
273                 case WATCSW: 
274                     watch++;
275                     continue;
276                 case NWATCSW: 
277                     watch = 0;
278                     continue;
279                 
280                 case PUSHSW:
281                     pushflg++;
282                     continue;
283                 case NPUSHSW:
284                     pushflg = 0;
285                     continue;
286
287                 case ALIASW:
288                     if (!(cp = *argp++) || *cp == '-')
289                         adios (NULL, "missing argument to %s", argp[-2]);
290                     if (aliasflg < 0)
291                         alias (AliasFile);/* load default aka's */
292                     aliasflg = 1;
293                     if ((state = alias(cp)) != AK_OK)
294                         adios (NULL, "aliasing error in file %s - %s",
295                                cp, akerror(state) );
296                     continue;
297                 case NALIASW:
298                     aliasflg = 0;
299                     continue;
300
301                 case WIDTHSW:
302                     if (!(cp = *argp++) || *cp == '-')
303                         adios (NULL, "missing argument to %s", argp[-2]);
304                     outputlinelen = atoi (cp);
305                     if (outputlinelen <= 10)
306                         outputlinelen = 72;
307                     continue;
308
309                 case LIBSW:
310                     if (!(cp = *argp++) || *cp == '-')
311                         adios (NULL, "missing argument to %s", argp[-2]);
312                     /* create a minimal context */
313                     if (context_foil (cp) == -1)
314                         done(1);
315                     continue;
316
317                 case ANNOSW:
318                     /* -idanno switch ignored */
319                     if (!(cp = *argp++) || *cp == '-')
320                         adios (NULL, "missing argument to %s", argp[-2]);
321                     continue;
322             }
323         }
324         if (msg)
325             adios (NULL, "only one message at a time!");
326         else
327             msg = cp;
328     }
329
330     if (aliasflg < 0)
331         alias (AliasFile);      /* load default aka's */
332
333     if (!msg)
334         adios (NULL, "usage: %s [switches] file", invo_name);
335
336     if ((in = fopen (msg, "r")) == NULL)
337         adios (msg, "unable to open");
338
339     start_headers ();
340     if (debug) {
341         verbose++;
342         out = stdout;
343     }
344     else {
345             if ((out = fdopen( mkstemp (tmpfil), "w" )) == NULL )
346                 adios (tmpfil, "unable to create");
347         }
348
349     hdrtab = (msgstate == normal) ? NHeaders : RHeaders;
350
351     for (compnum = 1, state = FLD;;) {
352         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
353             case FLD: 
354                 compnum++;
355                 putfmt (name, buf, out);
356                 continue;
357
358             case FLDPLUS: 
359                 compnum++;
360                 cp = add (buf, cp);
361                 while (state == FLDPLUS) {
362                     state = m_getfld (state, name, buf, sizeof(buf), in);
363                     cp = add (buf, cp);
364                 }
365                 putfmt (name, cp, out);
366                 free (cp);
367                 continue;
368
369             case BODY: 
370                 finish_headers (out);
371                 fprintf (out, "\n%s", buf);
372                 if(whomflg == 0)
373                     while (state == BODY) {
374                         state = m_getfld (state, name, buf, sizeof(buf), in);
375                         fputs (buf, out);
376                     }
377                 break;
378
379             case FILEEOF: 
380                 finish_headers (out);
381                 break;
382
383             case LENERR: 
384             case FMTERR: 
385                 adios (NULL, "message format error in component #%d",
386                         compnum);
387
388             default: 
389                 adios (NULL, "getfld() returned %d", state);
390         }
391         break;
392     }
393
394     fclose (in);
395     if (backflg && !whomflg) {
396         strncpy (buf, m_backup (msg), sizeof(buf));
397         if (rename (msg, buf) == NOTOK)
398             advise (buf, "unable to rename %s to", msg);
399     }
400
401     if (debug) {
402         done (0);
403     }
404     else
405         fclose (out);
406
407     file (tmpfil);
408
409     /*
410      * re-open the temp file, unlink it and exec sendmail, giving it
411      * the msg temp file as std in.
412      */
413     if ( freopen( tmpfil, "r", stdin) == NULL)
414         adios (tmpfil, "can't reopen for sendmail");
415     if (rmflg)
416         unlink (tmpfil);
417
418     argp = sargv;
419     *argp++ = "send-mail";
420     *argp++ = "-m";     /* send to me too */
421     *argp++ = "-t";     /* read msg for recipients */
422     *argp++ = "-i";     /* don't stop on "." */
423     if (whomflg)
424         *argp++ = "-bv";
425     if (watch || verbose)
426         *argp++ = "-v";
427     *argp = NULL;
428
429     if (pushflg && !(watch || verbose)) {
430         /* fork to a child to run sendmail */
431         for (i=0; (pid = vfork()) == NOTOK && i < 5; i++)
432             sleep(5);
433         switch (pid) {
434             case NOTOK:
435                 fprintf (verbose ? stdout : stderr, "%s: can't fork to %s\n",
436                          invo_name, sendmail);
437                 exit(-1);
438             case OK:
439                 /* we're the child .. */
440                 break;
441             default:
442                 exit(0);
443         }
444     }
445     execv ( sendmail, sargv);
446     adios ( sendmail, "can't exec");
447     return 0;  /* dead code to satisfy the compiler */
448 }
449
450 /* DRAFT GENERATION */
451
452 static void
453 putfmt (char *name, char *str, FILE *out)
454 {
455     int i;
456     char *cp, *pp;
457     struct headers *hdr;
458
459     while (*str == ' ' || *str == '\t')
460         str++;
461
462     if ((i = get_header (name, hdrtab)) == NOTOK) {
463         fprintf (out, "%s: %s", name, str);
464         return;
465     }
466
467     hdr = &hdrtab[i];
468     if (hdr->flags & HIGN)
469         return;
470     if (hdr->flags & HBAD) {
471         advise (NULL, "illegal header line -- %s:", name);
472         badmsg++;
473         return;
474     }
475     msgflags |= hdr->set;
476
477     if (hdr->flags & HSUB)
478         subject = subject ? add (str, add ("\t", subject)) : getcpy (str);
479
480     if (hdr->flags & HFCC) {
481         if ((cp = strrchr(str, '\n')))
482             *cp = 0;
483         for (cp = pp = str; (cp = strchr(pp, ',')); pp = cp) {
484             *cp++ = 0;
485             insert_fcc (hdr, pp);
486         }
487         insert_fcc (hdr, pp);
488         return;
489     }
490
491 #ifdef notdef
492     if (hdr->flags & HBCC) {
493         insert_bcc(str);
494         return;
495     }
496 #endif  /* notdef */
497
498     if (*str != '\n' && *str != '\0') {
499         if (aliasflg && hdr->flags & HTRY) {
500             /* this header contains address(es) that we have to do
501              * alias expansion on.  Because of the saved state in
502              * getname we have to put all the addresses into a list.
503              * We then let putadr munch on that list, possibly
504              * expanding aliases.
505              */
506             register struct mailname *f = 0;
507             register struct mailname *mp = 0;
508
509             while ((cp = getname(str))) {
510                 mp = getm( cp, NULL, 0, AD_HOST, NULL);
511                 if (f == 0) {
512                     f = mp;
513                     mp->m_next = mp;
514                 } else {
515                     mp->m_next = f->m_next;
516                     f->m_next = mp;
517                     f = mp;
518                 }
519             }
520             f = mp->m_next; mp->m_next = 0;
521             putadr( name, f );
522         } else {
523             /* The author(s) of spost decided that alias substitution wasn't
524                necessary for the non-HTRY headers.  Unfortunately, one of those
525                headers is "From:", and having alias substitution work on that is
526                extremely useful for someone with a lot of POP3 email accounts or
527                aliases.  post supports aliasing of "From:"...
528
529                Since "From:"-processing is incompletely implemented in this
530                unsupported and undocumented spost backend, I'm not going to take
531                the time to implement my new draft-From:-based email address
532                masquerading.  If I do ever implement it here, I'd almost
533                certainly want to implement "From:" line alias processing as
534                well.  -- Dan Harkless <dan-nmh@dilvish.speed.net> */
535             fprintf (out, "%s: %s", name, str );
536         }
537     }
538 }
539
540
541 static void
542 start_headers (void)
543 {
544     char *cp;
545     char sigbuf[BUFSIZ];
546
547     strncpy(from, getusername(), sizeof(from));
548
549     if ((cp = getfullname ()) && *cp) {
550         strncpy (sigbuf, cp, sizeof(sigbuf));
551         snprintf (signature, sizeof(signature), "%s <%s>", sigbuf,  from);
552     }
553     else
554         snprintf (signature, sizeof(signature), "%s",  from);
555 }
556
557
558 static void
559 finish_headers (FILE *out)
560 {
561     switch (msgstate) {
562         case normal: 
563             if (!(msgflags & MDAT))
564                 fprintf (out, "Date: %s\n", dtimenow (0));
565             
566             if (msgflags & MFRM) {
567                 /* There was already a From: in the draft.  Don't add one. */
568                 if (!draft_from_masquerading)
569                     /* mts.conf didn't contain "masquerade:[...]draft_from[...]"
570                        so we'll reveal the user's actual account@thismachine
571                        address in a Sender: header (and use it as the envelope
572                        From: later). */
573                     fprintf (out, "Sender: %s\n", from);
574             }
575             else
576                 fprintf (out, "From: %s\n", signature);
577            
578 #ifdef notdef
579             if (!(msgflags & MVIS))
580                 fprintf (out, "Bcc: Blind Distribution List: ;\n");
581 #endif  /* notdef */
582             break;
583
584         case resent: 
585             if (!(msgflags & MRDT))
586                 fprintf (out, "Resent-Date: %s\n", dtimenow(0));
587             if (msgflags & MRFM) {
588                 /* There was already a Resent-From: in draft.  Don't add one. */
589                 if (!draft_from_masquerading)
590                     /* mts.conf didn't contain "masquerade:[...]draft_from[...]"
591                        so we'll reveal the user's actual account@thismachine
592                        address in a Sender: header (and use it as the envelope
593                        From: later). */
594                     fprintf (out, "Resent-Sender: %s\n", from);
595             }
596             else
597                 /* Construct a Resent-From: header. */
598                 fprintf (out, "Resent-From: %s\n", signature);
599 #ifdef notdef
600             if (!(msgflags & MVIS))
601                 fprintf (out, "Resent-Bcc: Blind Re-Distribution List: ;\n");
602 #endif  /* notdef */
603             break;
604     }
605
606     if (badmsg)
607         adios (NULL, "re-format message and try again");
608 }
609
610
611 static int
612 get_header (char *header, struct headers *table)
613 {
614     struct headers *h;
615
616     for (h = table; h->value; h++)
617         if (!mh_strcasecmp (header, h->value))
618             return (h - table);
619
620     return NOTOK;
621 }
622
623
624 /*
625  * output the address list for header "name".  The address list
626  * is a linked list of mailname structs.  "nl" points to the head
627  * of the list.  Alias substitution should be done on nl.
628  */
629 static void
630 putadr (char *name, struct mailname *nl)
631 {
632     register struct mailname *mp, *mp2;
633     register int linepos;
634     register char *cp;
635     int namelen;
636
637     fprintf (out, "%s: ", name);
638     namelen = strlen(name) + 2;
639     linepos = namelen;
640
641     for (mp = nl; mp; ) {
642         if (linepos > MAX_SM_FIELD) {
643                 fprintf (out, "\n%s: ", name);
644                 linepos = namelen;
645         }
646         if (mp->m_nohost) {
647             /* a local name - see if it's an alias */
648             cp = akvalue(mp->m_mbox);
649             if (cp == mp->m_mbox)
650                 /* wasn't an alias - use what the user typed */
651                 linepos = putone( mp->m_text, linepos, namelen );
652             else
653                 /* an alias - expand it */
654                 while ((cp = getname(cp))) {
655                     if (linepos > MAX_SM_FIELD) {
656                             fprintf (out, "\n%s: ", name);
657                             linepos = namelen;
658                     }
659                     mp2 = getm( cp, NULL, 0, AD_HOST, NULL);
660                     if (akvisible()) {
661                         mp2->m_pers = getcpy(mp->m_mbox);
662                         linepos = putone( adrformat(mp2), linepos, namelen );
663                     } else {
664                         linepos = putone( mp2->m_text, linepos, namelen );
665                     }
666                     mnfree( mp2 );
667                 }
668         } else {
669             /* not a local name - use what the user typed */
670             linepos = putone( mp->m_text, linepos, namelen );
671         }
672         mp2 = mp;
673         mp = mp->m_next;
674         mnfree( mp2 );
675     }
676     putc( '\n', out );
677 }
678
679 static int
680 putone (char *adr, int pos, int indent)
681 {
682     register int len;
683     static int linepos;
684
685     len = strlen( adr );
686     if (pos == indent)
687         linepos = pos;
688     else if ( linepos+len > outputlinelen ) {
689         fprintf ( out, ",\n%*s", indent, "");
690         linepos = indent;
691         pos += indent + 2;
692     }
693     else {
694         fputs( ", ", out );
695         linepos += 2;
696         pos += 2;
697     }
698     fputs( adr, out );
699
700     linepos += len;
701     return (pos+len);
702 }
703
704
705 static void
706 insert_fcc (struct headers *hdr, unsigned char *pp)
707 {
708     unsigned char   *cp;
709
710     for (cp = pp; isspace (*cp); cp++)
711         continue;
712     for (pp += strlen (pp) - 1; pp > cp && isspace (*pp); pp--)
713         continue;
714     if (pp >= cp)
715         *++pp = 0;
716     if (*cp == 0)
717         return;
718
719     if (fccind >= FCCS)
720         adios (NULL, "too many %ss", hdr->value);
721     fccfold[fccind++] = getcpy (cp);
722 }
723
724 #if 0
725 /* BCC GENERATION */
726
727 static void
728 make_bcc_file (void)
729 {
730     pid_t child_id;
731     int fd, i, status;
732     char *vec[6];
733     FILE * in, *out;
734
735     fd = mkstemp(bccfil);
736     if (fd == -1 || (out = fdopen(fd, "w")) == NULL)
737         adios (bccfil, "unable to create");
738     chmod (bccfil, 0600);
739
740     fprintf (out, "Date: %s\n", dtimenow (0));
741     if (msgflags & MFRM) {
742       /* There was already a From: in the draft.  Don't add one. */
743       if (!draft_from_masquerading)
744         /* mts.conf didn't contain "masquerade:[...]draft_from[...]"
745            so we'll reveal the user's actual account@thismachine
746            address in a Sender: header (and use it as the envelope
747            From: later). */
748         fprintf (out, "Sender: %s\n", from);
749     }
750     else
751       /* Construct a From: header. */
752       fprintf (out, "From: %s\n", signature);
753     if (subject)
754         fprintf (out, "Subject: %s", subject);
755     fprintf (out, "BCC:\n\n------- Blind-Carbon-Copy\n\n");
756     fflush (out);
757
758     if (filter == NULL) {
759         if ((fd = open (tmpfil, O_RDONLY)) == NOTOK)
760             adios (NULL, "unable to re-open");
761         cpydgst (fd, fileno (out), tmpfil, bccfil);
762         close (fd);
763     }
764     else {
765         vec[0] = r1bindex (mhlproc, '/');
766
767         for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
768             sleep (5);
769         switch (child_id) {
770             case NOTOK: 
771                 adios ("vfork", "unable to");
772
773             case OK: 
774                 dup2 (fileno (out), 1);
775
776                 i = 1;
777                 vec[i++] = "-forward";
778                 vec[i++] = "-form";
779                 vec[i++] = filter;
780                 vec[i++] = tmpfil;
781                 vec[i] = NULL;
782
783                 execvp (mhlproc, vec);
784                 adios (mhlproc, "unable to exec");
785
786             default: 
787                 if (status = pidwait(child_id, OK))
788                     admonish (NULL, "%s lost (status=0%o)", vec[0], status);
789                 break;
790         }
791     }
792
793     fseek (out, 0L, SEEK_END);
794     fprintf (out, "\n------- End of Blind-Carbon-Copy\n");
795     fclose (out);
796 }
797 #endif  /* if 0 */
798
799 /* FCC INTERACTION */
800
801 static void
802 file (char *path)
803 {
804     int i;
805
806     if (fccind == 0)
807         return;
808
809     for (i = 0; i < fccind; i++)
810         if (whomflg)
811             printf ("Fcc: %s\n", fccfold[i]);
812         else
813             fcc (path, fccfold[i]);
814 }
815
816
817 static void
818 fcc (char *file, char *folder)
819 {
820     pid_t child_id;
821     int i, status;
822     char fold[BUFSIZ];
823
824     if (verbose)
825         printf ("%sFcc: %s\n", msgstate == resent ? "Resent-" : "", folder);
826     fflush (stdout);
827
828     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
829         sleep (5);
830     switch (child_id) {
831         case NOTOK: 
832             if (!verbose)
833                 fprintf (stderr, "  %sFcc %s: ",
834                         msgstate == resent ? "Resent-" : "", folder);
835             fprintf (verbose ? stdout : stderr, "no forks, so not ok\n");
836             break;
837
838         case OK: 
839             snprintf (fold, sizeof(fold), "%s%s",
840                     *folder == '+' || *folder == '@' ? "" : "+", folder);
841             execlp (fileproc, r1bindex (fileproc, '/'),
842                     "-link", "-file", file, fold, NULL);
843             _exit (-1);
844
845         default: 
846             if ((status = pidwait(child_id, OK))) {
847                 if (!verbose)
848                     fprintf (stderr, "  %sFcc %s: ",
849                             msgstate == resent ? "Resent-" : "", folder);
850                 fprintf (verbose ? stdout : stderr,
851                         " errored (0%o)\n", status);
852             }
853     }
854
855     fflush (stdout);
856 }
857
858
859 #if 0
860
861 /*
862  * TERMINATION
863  */
864
865 static void
866 die (char *what, char *fmt, ...)
867 {
868     va_list ap;
869
870     va_start(ap, fmt);
871     advertise (what, NULL, fmt, ap);
872     va_end(ap);
873
874     done (1);
875 }
876 #endif