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