f9674a3861d219505529ba89f8fff7a0b0aed1df
[mmh] / uip / mhbuild.c
1 /*
2 ** mhbuild.c -- expand/translate MIME composition files
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 /*
10 ** This code was originally part of mhn.c.  I split it into
11 ** a separate program (mhbuild.c). But the code still has some of
12 ** the mhn.c code in it.  This program needs additional
13 ** streamlining and removal of unneeded code.
14 */
15
16 #include <h/mh.h>
17 #include <fcntl.h>
18 #include <errno.h>
19 #include <h/tws.h>
20 #include <h/mime.h>
21 #include <h/mhparse.h>
22 #include <h/utils.h>
23 #include <unistd.h>
24 #include <ctype.h>
25 #include <sys/stat.h>
26 #include <locale.h>
27 #include <sysexits.h>
28
29 #ifdef HAVE_SYS_TIME_H
30 # include <sys/time.h>
31 #endif
32 #include <time.h>
33
34 static struct swit switches[] = {
35 #define VERBSW  0
36         { "verbose", 0 },
37 #define NVERBSW  1
38         { "noverbose", 2 },
39 #define VERSIONSW  2
40         { "Version", 0 },
41 #define HELPSW  3
42         { "help", 0 },
43 #define DEBUGSW  4
44         { "debug", -5 },
45         { NULL, 0 }
46 };
47
48 char *version=VERSION;
49
50 /*
51 ** Directory to place tmp files.  This must
52 ** be set before these routines are called.
53 */
54 extern char *tmp;
55
56 pid_t xpid = 0;
57
58 static char prefix[] = "----- =_aaaaaaaaaa";
59
60
61 /*
62 ** prototypes
63 */
64
65 /* mhmisc.c */
66 int make_intermediates(char *);
67 void content_error(char *, CT, char *, ...);
68
69 /* mhfree.c */
70 void free_content(CT);
71 void free_ctinfo(CT);
72 void free_encoding(CT, int);
73
74 /*
75 ** static prototypes
76 */
77 static int init_decoded_content(CT);
78 static char *fgetstr(char *, int, FILE *);
79 static int user_content(FILE *, char *, char *, CT *);
80 static void set_id(CT, int);
81 static int compose_content(CT);
82 static int scan_content(CT);
83 static int build_headers(CT);
84 static CT build_mime(char *);
85
86
87 int debugsw = 0;
88 int verbosw = 0;
89
90 /*
91 ** Temporary files
92 */
93 static char infile[BUFSIZ];
94 static int unlink_infile  = 0;
95
96 static char outfile[BUFSIZ];
97 static int unlink_outfile = 0;
98
99 void unlink_done();
100
101 /* mhoutsbr.c */
102 int output_message(CT, char *);
103 int output_message_fp(CT, FILE *, char*);
104
105 /* mhmisc.c */
106 void set_endian(void);
107
108 /* mhfree.c */
109 void free_content(CT);
110
111
112 int
113 main(int argc, char **argv)
114 {
115         char *cp, buf[BUFSIZ];
116         char buffer[BUFSIZ], *compfile = NULL;
117         char **argp, **arguments;
118         CT ct;
119         FILE *fp = NULL;
120         FILE *fp_out = NULL;
121
122         if (atexit(unlink_done) != 0) {
123                 adios(EX_OSERR, NULL, "atexit failed");
124         }
125
126         setlocale(LC_ALL, "");
127         invo_name = mhbasename(argv[0]);
128
129         /* read user profile/context */
130         context_read();
131
132         arguments = getarguments(invo_name, argc, argv, 1);
133         argp = arguments;
134
135         while ((cp = *argp++)) {
136                 if (cp[0] == '-' && cp[1] == '\0') {
137                         if (compfile)
138                                 adios(EX_USAGE, NULL, "cannot specify both standard input and a file");
139                         else
140                                 compfile = cp;
141                         verbosw = 0;  /* turn off -verbose listings */
142                         break;
143                 }
144                 if (*cp == '-') {
145                         switch (smatch(++cp, switches)) {
146                         case AMBIGSW:
147                                 ambigsw(cp, switches);
148                                 exit(EX_USAGE);
149                         case UNKWNSW:
150                                 adios(EX_USAGE, NULL, "-%s unknown", cp);
151
152                         case HELPSW:
153                                 snprintf(buf, sizeof(buf), "%s [switches] file", invo_name);
154                                 print_help(buf, switches, 1);
155                                 exit(argc == 2 ? EX_OK : EX_USAGE);
156                         case VERSIONSW:
157                                 print_version(invo_name);
158                                 exit(argc == 2 ? EX_OK : EX_USAGE);
159
160                         case VERBSW:
161                                 verbosw++;
162                                 continue;
163                         case NVERBSW:
164                                 verbosw = 0;
165                                 continue;
166                         case DEBUGSW:
167                                 debugsw = 1;
168                                 continue;
169                         }
170                 }
171                 if (compfile)
172                         adios(EX_USAGE, NULL, "only one composition file allowed");
173                 else
174                         compfile = cp;
175         }
176
177         set_endian();
178
179         /*
180         ** Check if we've specified an additional profile
181         */
182         if ((cp = getenv("MHBUILD"))) {
183                 if ((fp = fopen(cp, "r"))) {
184                         readconfig((struct node **) 0, fp, cp, 0);
185                         fclose(fp);
186                 } else {
187                         admonish("", "unable to read $MHBUILD profile (%s)",
188                                         cp);
189                 }
190         }
191
192         /*
193         ** Read the standard profile setup
194         */
195         if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
196                 readconfig((struct node **) 0, fp, cp, 0);
197                 fclose(fp);
198         }
199
200         /*
201         ** Check for storage directory.  If defined, we
202         ** will store temporary files there.  Else we
203         ** store them in standard nmh directory.
204         */
205         if ((cp = context_find(nmhstorage)) && *cp)
206                 tmp = concat(cp, "/", invo_name, NULL);
207         else
208                 tmp = mh_xstrdup(toabsdir(invo_name));
209
210         /* Check if we have a file to process */
211         if (!compfile)
212                 adios(EX_USAGE, NULL, "need to specify a %s composition file",
213                                 invo_name);
214
215         /*
216         ** Process the composition file from standard input.
217         */
218         if (compfile[0] == '-' && compfile[1] == '\0') {
219                 /* copy standard input to temporary file */
220                 strncpy(infile, m_mktemp(invo_name, NULL, &fp),
221                                 sizeof(infile));
222                 while (fgets(buffer, BUFSIZ, stdin))
223                         fputs(buffer, fp);
224                 fclose(fp);
225                 unlink_infile = 1;
226
227                 /* build the content structures for MIME message */
228                 ct = build_mime(infile);
229
230                 /* output MIME message to this temporary file */
231                 strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out),
232                                 sizeof(outfile));
233                 unlink_outfile = 1;
234
235                 /* output the message */
236                 output_message_fp(ct, fp_out, outfile);
237                 fclose(fp_out);
238
239                 /* output the temp file to standard output */
240                 if ((fp = fopen(outfile, "r")) == NULL)
241                         adios(EX_IOERR, outfile, "unable to open");
242                 while (fgets(buffer, BUFSIZ, fp))
243                         fputs(buffer, stdout);
244                 fclose(fp);
245
246                 unlink(infile);
247                 unlink_infile = 0;
248
249                 unlink(outfile);
250                 unlink_outfile = 0;
251
252                 free_content(ct);
253                 exit(EX_OK);
254         }
255
256         /*
257         ** Process the composition file from a file.
258         */
259
260         /* build the content structures for MIME message */
261         ct = build_mime(compfile);
262
263         /* output MIME message to this temporary file */
264         strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out),
265                         sizeof(outfile));
266         unlink_outfile = 1;
267
268         /* output the message */
269         output_message_fp(ct, fp_out, outfile);
270         fclose(fp_out);
271
272         /* Rename composition draft */
273         snprintf(buffer, sizeof(buffer), "%s.orig", compfile);
274         if (rename(compfile, buffer) == NOTOK) {
275                 adios(EX_IOERR, buffer, "unable to rename draft %s to", compfile);
276         }
277
278         /* Rename output file to take its place */
279         if (rename(outfile, compfile) == NOTOK) {
280                 advise(compfile, "unable to rename output %s to", outfile);
281                 rename(buffer, compfile);
282                 exit(EX_IOERR);
283         }
284         unlink_outfile = 0;
285
286         free_content(ct);
287         return 0;
288 }
289
290
291 void
292 unlink_done()
293 {
294         /*
295         ** Check if we need to remove stray temporary files.
296         */
297         if (unlink_infile) {
298                 unlink(infile);
299         }
300         if (unlink_outfile) {
301                 unlink(outfile);
302         }
303 }
304
305 /*
306 ** Main routine for translating composition file
307 ** into valid MIME message.  It translates the draft
308 ** into a content structure (actually a tree of content
309 ** structures).  This message then can be manipulated
310 ** in various ways, including being output via
311 ** output_message().
312 */
313 static CT
314 build_mime(char *infile)
315 {
316         enum state state;
317         struct field f = {{0}};
318         int compnum;
319         char buf[BUFSIZ];
320         char *cp, *np, *vp;
321         struct multipart *m;
322         struct part **pp;
323         CT ct;
324         FILE *in;
325         HF hp;
326
327         umask(~m_gmprot());
328
329         /* open the composition draft */
330         if ((in = fopen(infile, "r")) == NULL) {
331                 adios(EX_IOERR, infile, "unable to open for reading");
332         }
333
334         /*
335         ** Allocate space for primary (outside) content
336         */
337         ct = mh_xcalloc(1, sizeof(*ct));
338
339         /*
340         ** Allocate structure for handling decoded content
341         ** for this part.  We don't really need this, but
342         ** allocate it to remain consistent.
343         */
344         init_decoded_content(ct);
345
346         /*
347         ** Parse some of the header fields in the composition
348         ** draft into the linked list of header fields for
349         ** the new MIME message.
350         */
351         for (compnum = 1, state = FLD2;;) {
352                 switch (state = m_getfld2(state, &f, in)) {
353                 case LENERR2:
354                         state = FLD2;
355                 case FLD2:
356                         compnum++;
357
358                         /* abort if draft has Mime-Version header field */
359                         if (!mh_strcasecmp(f.name, VRSN_FIELD)) {
360                                 adios(EX_CONFIG, NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
361                         }
362
363                         /*
364                         ** abort if draft has Content-Transfer-Encoding
365                         ** header field
366                         */
367                         if (!mh_strcasecmp(f.name, ENCODING_FIELD)) {
368                                 adios(EX_CONFIG, NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
369                         }
370
371                         /* ignore any Content-Type fields in the header */
372                         if (!mh_strcasecmp(f.name, TYPE_FIELD)) {
373                                 continue;
374                         }
375
376                         /* add the header data to the list */
377                         add_header(ct, mh_xstrdup(f.name), mh_xstrdup(f.value));
378
379                         continue;
380
381                 case BODY2:
382                         fseek(in, (long) (-strlen(f.value)), SEEK_CUR);
383                         break;
384
385                 case FILEEOF2:
386                         adios(EX_CONFIG, NULL, "draft has empty body -- no directives!");
387                         /* NOTREACHED */
388
389                 case FMTERR2:
390                 case IOERR2:
391                         adios(EX_CONFIG, NULL, "message format error in component #%d",
392                                         compnum);
393
394                 default:
395                         adios(EX_SOFTWARE, NULL, "getfld() returned %d", state);
396                 }
397                 break;
398         }
399
400         /*
401         ** Iterate through the list of headers and call the function to
402         ** MIME-ify them if required.
403         */
404         for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
405                 if (encode_rfc2047(hp->name, &hp->value, NULL)) {
406                         adios(EX_DATAERR, NULL, "Unable to encode header \"%s\"", hp->name);
407                 }
408         }
409
410         /*
411         ** Now add the MIME-Version header field
412         ** to the list of header fields.
413         */
414         np = mh_xstrdup(VRSN_FIELD);
415         vp = concat(" ", VRSN_VALUE, "\n", NULL);
416         add_header(ct, np, vp);
417
418         /*
419         ** We initially assume we will find multiple contents in the
420         ** draft.  So create a multipart/mixed content to hold everything.
421         ** We can remove this later, if it is not needed.
422         */
423         if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK) {
424                 exit(EX_DATAERR);
425         }
426         ct->c_type = CT_MULTIPART;
427         ct->c_subtype = MULTI_MIXED;
428         ct->c_file = mh_xstrdup(infile);
429
430         m = (struct multipart *) mh_xcalloc(1, sizeof(*m));
431         ct->c_ctparams = (void *) m;
432         pp = &m->mp_parts;
433
434         /*
435         ** read and parse the composition file
436         ** and the directives it contains.
437         */
438         while (fgetstr(buf, sizeof(buf) - 1, in)) {
439                 struct part *part;
440                 CT p;
441
442                 if (user_content(in, infile, buf, &p) == DONE) {
443                         admonish(NULL, "ignoring spurious #end");
444                         continue;
445                 }
446                 if (!p)
447                         continue;
448
449                 part = mh_xcalloc(1, sizeof(*part));
450                 *pp = part;
451                 pp = &part->mp_next;
452                 part->mp_part = p;
453         }
454
455         /*
456         ** close the composition draft since
457         ** it's not needed any longer.
458         */
459         fclose(in);
460
461         /* check if any contents were found */
462         if (!m->mp_parts)
463                 adios(EX_OSERR, NULL, "no content directives found");
464
465         /*
466         ** If only one content was found, then remove and
467         ** free the outer multipart content.
468         */
469         if (!m->mp_parts->mp_next) {
470                 CT p;
471
472                 p = m->mp_parts->mp_part;
473                 m->mp_parts->mp_part = NULL;
474
475                 /* move header fields */
476                 p->c_first_hf = ct->c_first_hf;
477                 p->c_last_hf = ct->c_last_hf;
478                 ct->c_first_hf = NULL;
479                 ct->c_last_hf = NULL;
480
481                 free_content(ct);
482                 ct = p;
483         } else {
484                 set_id(ct, 1);
485         }
486
487         /*
488         ** Fill out, or expand directives.  Parse and execute
489         ** commands specified by profile composition strings.
490         */
491         compose_content(ct);
492
493         if ((cp = strchr(prefix, 'a')) == NULL)
494                 adios(EX_SOFTWARE, NULL, "internal error(4)");
495
496         /*
497         ** Scan the contents.  Choose a transfer encoding, and
498         ** check if prefix for multipart boundary clashes with
499         ** any of the contents.
500         */
501         while (scan_content(ct) == NOTOK) {
502                 if (*cp < 'z') {
503                         (*cp)++;
504                 } else {
505                         if (*++cp == 0)
506                                 adios(EX_SOFTWARE, NULL, "giving up trying to find a unique delimiter string");
507                                 else
508                                 (*cp)++;
509                 }
510         }
511
512         /* Build the rest of the header field structures */
513         build_headers(ct);
514
515         return ct;
516 }
517
518
519 /*
520 ** Set up structures for placing unencoded
521 ** content when building parts.
522 */
523
524 static int
525 init_decoded_content(CT ct)
526 {
527         CE ce;
528
529         ce = mh_xcalloc(1, sizeof(*ce));
530
531         ct->c_cefile     = ce;
532         ct->c_ceopenfnx  = open7Bit;  /* since unencoded */
533         ct->c_ceclosefnx = close_encoding;
534         ct->c_cesizefnx  = NULL;  /* since unencoded */
535
536         return OK;
537 }
538
539
540 static char *
541 fgetstr(char *s, int n, FILE *stream)
542 {
543         char *cp, *ep;
544
545         for (ep = (cp = s) + n; cp < ep; ) {
546                 int i;
547
548                 if (!fgets(cp, n, stream))
549                         return (cp != s ? s : NULL);
550                 if (cp == s && *cp != '#')
551                         return s;
552
553                 cp += (i = strlen(cp)) - 1;
554                 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
555                         break;
556                 *cp = '\0';
557                 n -= (i - 2);
558         }
559
560         return s;
561 }
562
563
564 /*
565 ** Parse the composition draft for text and directives.
566 ** Do initial setup of Content structure.
567 */
568
569 static int
570 user_content(FILE *in, char *file, char *buf, CT *ctp)
571 {
572         int vrsn;
573         unsigned char *cp;
574         char **ap;
575         char buffer[BUFSIZ];
576         struct multipart *m;
577         struct part **pp;
578         struct str2init *s2i;
579         CI ci;
580         CT ct;
581         CE ce;
582
583         if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
584                 *ctp = NULL;
585                 return OK;
586         }
587
588         /* allocate basic Content structure */
589         ct = mh_xcalloc(1, sizeof(*ct));
590         *ctp = ct;
591
592         /* allocate basic structure for handling decoded content */
593         init_decoded_content(ct);
594         ce = ct->c_cefile;
595
596         ci = &ct->c_ctinfo;
597         set_id(ct, 0);
598
599         /*
600         ** Handle inline text.  Check if line
601         ** is one of the following forms:
602         **
603         ** 1) doesn't begin with '#'  (implicit directive)
604         ** 2) begins with "##"        (implicit directive)
605         ** 3) begins with "#<"
606         */
607         if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
608                 int headers;
609                 int inlineD;
610                 long pos;
611                 char content[BUFSIZ];
612                 FILE *out;
613                 char *cp;
614
615                 cp = m_mktemp2(NULL, invo_name, NULL, &out);
616                 if (cp == NULL)
617                         adios(EX_CANTCREAT, "mhbuild", "unable to create temporary file");
618
619                 /* use a temp file to collect the plain text lines */
620                 ce->ce_file = mh_xstrdup(cp);
621                 ce->ce_unlink = 1;
622
623                 if (buf[0] == '#' && buf[1] == '<') {
624                         strncpy(content, buf + 2, sizeof(content));
625                         inlineD = 1;
626                         goto rock_and_roll;
627                 } else {
628                         inlineD = 0;
629                 }
630
631                 /* the directive is implicit */
632                 strncpy(content, "text/plain", sizeof(content));
633                 headers = 0;
634                 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
635                 for (;;) {
636                         int i;
637
638                         if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
639                                         buffer[i=strlen(DESCR_FIELD)] == ':') {
640                                 headers = 1;
641
642 again_descr:
643                                 ct->c_descr = add(buffer + i + 1, ct->c_descr);
644                                 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
645                                         adios(EX_DATAERR, NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
646                                 switch (buffer[0]) {
647                                 case ' ':
648                                 case '\t':
649                                         i = -1;
650                                         goto again_descr;
651
652                                 case '#':
653                                         adios(EX_DATAERR, NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
654                                         /* NOTREACHED */
655
656                                 default:
657                                         break;
658                                 }
659                         }
660
661                         if (headers >= 0 && uprf(buffer, DISPO_FIELD)
662                                 && buffer[i = strlen(DISPO_FIELD)] == ':') {
663                                 headers = 1;
664
665 again_dispo:
666                                 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
667                                 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
668                                         adios(EX_DATAERR, NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
669                                 switch (buffer[0]) {
670                                 case ' ':
671                                 case '\t':
672                                         i = -1;
673                                         goto again_dispo;
674
675                                 case '#':
676                                         adios(EX_DATAERR, NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
677                                         /* NOTREACHED */
678
679                                 default:
680                                         break;
681                                 }
682                         }
683
684                         if (headers != 1 || buffer[0] != '\n')
685                                 fputs(buffer, out);
686
687 rock_and_roll:
688                         headers = -1;
689                         pos = ftell(in);
690                         if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
691                                         == NULL)
692                                 break;
693                         if (buffer[0] == '#') {
694                                 char *bp;
695
696                                 if (buffer[1] != '#')
697                                         break;
698                                 for (cp = (bp = buffer) + 1; *cp; cp++)
699                                         *bp++ = *cp;
700                                 *bp = '\0';
701                         }
702                 }
703
704                 fclose(out);
705
706                 /* parse content type */
707                 if (get_ctinfo(content, ct, inlineD) == NOTOK)
708                         exit(EX_DATAERR);
709
710                 for (s2i = str2cts; s2i->si_key; s2i++)
711                         if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
712                                 break;
713                 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
714                         s2i++;
715
716                 /*
717                 ** check type specified (possibly implicitly)
718                 */
719                 switch (ct->c_type = s2i->si_val) {
720                 case CT_MESSAGE:
721                         if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
722                                 ct->c_encoding = CE_7BIT;
723                                 goto call_init;
724                         }
725                         /* else fall... */
726                 case CT_MULTIPART:
727                         adios(EX_DATAERR, NULL, "it doesn't make sense to define an in-line %s content",
728                                         ct->c_type == CT_MESSAGE ? "message" :
729                                         "multipart");
730                         /* NOTREACHED */
731
732                 default:
733 call_init:
734                         if ((ct->c_ctinitfnx = s2i->si_init))
735                                 (*ct->c_ctinitfnx) (ct);
736                         break;
737                 }
738
739                 if (cp)
740                         fseek(in, pos, SEEK_SET);
741                 return OK;
742         }
743
744         /*
745         ** If we've reached this point, the next line
746         ** must be some type of explicit directive.
747         */
748
749         if (buf[1] == '@') {
750                 adios(EX_DATAERR, NULL, "The #@ directive i.e. message/external-body "
751                                 "is not supported anymore.");
752         }
753
754         /* parse directive */
755         if (get_ctinfo(buf+1, ct, 1) == NOTOK)
756                 exit(EX_DATAERR);
757
758         /* check directive against the list of MIME types */
759         for (s2i = str2cts; s2i->si_key; s2i++)
760                 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
761                         break;
762
763         /*
764         ** Check if the directive specified a valid type.
765         ** This will happen if it was one of the following forms:
766         **
767         **    #type/subtype
768         */
769         if (s2i->si_key) {
770                 if (!ci->ci_subtype)
771                         adios(EX_DATAERR, NULL, "missing subtype in \"#%s\"", ci->ci_type);
772
773                 switch (ct->c_type = s2i->si_val) {
774                 case CT_MULTIPART:
775                         adios(EX_DATAERR, NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
776                         /* NOTREACHED */
777
778                 case CT_MESSAGE:
779                         if (!mh_strcasecmp(ci->ci_subtype, "partial") ||
780                                         !mh_strcasecmp(ci->ci_subtype,
781                                         "external-body")) {
782                                 adios(EX_DATAERR, NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
783                         }
784 use_forw:
785                         admonish(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
786                         /* FALL */
787
788                 default:
789                         if ((ct->c_ctinitfnx = s2i->si_init))
790                                 (*ct->c_ctinitfnx) (ct);
791                         break;
792                 }
793
794                 /* Handle [file] argument */
795                 if (ci->ci_magic) {
796                         /* check if specifies command to execute */
797                         if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
798                                 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
799                                         continue;
800                                 if (!*cp)
801                                         adios(EX_DATAERR, NULL, "empty pipe command for #%s directive", ci->ci_type);
802                                 cp = mh_xstrdup(cp);
803                                 mh_free0(&(ci->ci_magic));
804                                 ci->ci_magic = cp;
805                         } else {
806                                 /* record filename of decoded contents */
807                                 ce->ce_file = ci->ci_magic;
808                                 if (access(ce->ce_file, R_OK) == NOTOK)
809                                         adios(EX_IOERR, "reading", "unable to access %s for", ce->ce_file);
810                                 ci->ci_magic = NULL;
811                         }
812                         return OK;
813                 }
814
815                 /*
816                 ** No [file] argument, so check profile for
817                 ** method to compose content.
818                 */
819                 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
820                                 invo_name, ci->ci_type, ci->ci_subtype);
821                 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
822                         snprintf(buffer, sizeof(buffer), "%s-compose-%s",
823                                         invo_name, ci->ci_type);
824                         if ((cp = context_find(buffer)) == NULL ||
825                                         *cp == '\0') {
826                                 content_error(NULL, ct, "don't know how to compose content");
827                                 exit(EX_CONFIG);
828                         }
829                 }
830                 ci->ci_magic = mh_xstrdup(cp);
831                 return OK;
832         }
833
834         /*
835         ** Message directive
836         ** #forw [+folder] [msgs]
837         */
838         if (!mh_strcasecmp(ci->ci_type, "forw")) {
839                 int msgnum;
840                 char *folder, *arguments[MAXARGS];
841                 struct msgs *mp;
842
843                 if (ci->ci_magic) {
844                         int i;
845
846                         ap = brkstring(ci->ci_magic, " ", "\n");
847                         for (i=0; ap[i] && i<MAXARGS-1; i++) {
848                                 arguments[i] = ap[i];
849                         }
850                         arguments[i] = NULL;
851
852                 } else {
853                         arguments[0] = seq_cur;
854                         arguments[1] = NULL;
855                 }
856                 folder = NULL;
857
858                 /* search the arguments for a folder name */
859                 for (ap = arguments; *ap; ap++) {
860                         cp = *ap;
861                         if (*cp == '+' || *cp == '@') {
862                                 if (folder)
863                                         adios(EX_USAGE, NULL, "only one folder per #forw directive");
864                                 else
865                                         folder = mh_xstrdup(expandfol(cp));
866                         }
867                 }
868
869                 /* else, use the current folder */
870                 if (!folder)
871                         folder = mh_xstrdup(getcurfol());
872
873                 if (!(mp = folder_read(folder)))
874                         adios(EX_IOERR, NULL, "unable to read folder %s", folder);
875                 for (ap = arguments; *ap; ap++) {
876                         cp = *ap;
877                         if (*cp != '+' && *cp != '@')
878                                 if (!m_convert(mp, cp))
879                                         exit(EX_USAGE);
880                 }
881                 mh_free0(&folder);
882                 free_ctinfo(ct);
883
884                 /*
885                 ** If there is more than one message to include, make this
886                 ** a content of type "multipart/digest" and insert each message
887                 ** as a subpart.  If there is only one message, then make this
888                 ** a content of type "message/rfc822".
889                 */
890                 if (mp->numsel > 1) {
891                         /* we are forwarding multiple messages */
892                         if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
893                                 exit(EX_DATAERR);
894                         ct->c_type = CT_MULTIPART;
895                         ct->c_subtype = MULTI_DIGEST;
896
897                         m = mh_xcalloc(1, sizeof(*m));
898                         ct->c_ctparams = (void *) m;
899                         pp = &m->mp_parts;
900
901                         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
902                                 if (is_selected(mp, msgnum)) {
903                                         struct part *part;
904                                         CT p;
905                                         CE pe;
906
907                                         p = mh_xcalloc(1, sizeof(*p));
908                                         init_decoded_content(p);
909                                         pe = p->c_cefile;
910                                         if (get_ctinfo("message/rfc822", p, 0)
911                                                         == NOTOK)
912                                                 exit(EX_DATAERR);
913                                         p->c_type = CT_MESSAGE;
914                                         p->c_subtype = MESSAGE_RFC822;
915
916                                         snprintf(buffer, sizeof(buffer),
917                                                         "%s/%d", mp->foldpath,
918                                                         msgnum);
919                                         pe->ce_file = mh_xstrdup(buffer);
920
921                                         part = mh_xcalloc(1, sizeof(*part));
922                                         *pp = part;
923                                         pp = &part->mp_next;
924                                         part->mp_part = p;
925                                 }
926                         }
927                 } else {
928                         /* we are forwarding one message */
929                         if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
930                                 exit(EX_DATAERR);
931                         ct->c_type = CT_MESSAGE;
932                         ct->c_subtype = MESSAGE_RFC822;
933
934                         msgnum = mp->lowsel;
935                         snprintf(buffer, sizeof(buffer), "%s/%d",
936                                         mp->foldpath, msgnum);
937                         ce->ce_file = mh_xstrdup(buffer);
938                 }
939
940                 folder_free(mp);  /* free folder/message structure */
941                 return OK;
942         }
943
944         /*
945         ** #end
946         */
947         if (!mh_strcasecmp(ci->ci_type, "end")) {
948                 free_content(ct);
949                 *ctp = NULL;
950                 return DONE;
951         }
952
953         /*
954         ** #begin [ alternative | parallel ]
955         */
956         if (!mh_strcasecmp(ci->ci_type, "begin")) {
957                 if (!ci->ci_magic) {
958                         vrsn = MULTI_MIXED;
959                         cp = SubMultiPart[vrsn - 1].kv_key;
960                 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
961                         vrsn = MULTI_ALTERNATE;
962                         cp = SubMultiPart[vrsn - 1].kv_key;
963                 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
964                         vrsn = MULTI_PARALLEL;
965                         cp = SubMultiPart[vrsn - 1].kv_key;
966                 } else if (uprf(ci->ci_magic, "digest")) {
967                         goto use_forw;
968                 } else {
969                         vrsn = MULTI_UNKNOWN;
970                         cp = ci->ci_magic;
971                 }
972
973                 free_ctinfo(ct);
974                 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
975                 if (get_ctinfo(buffer, ct, 0) == NOTOK)
976                         exit(EX_DATAERR);
977                 ct->c_type = CT_MULTIPART;
978                 ct->c_subtype = vrsn;
979
980                 m = mh_xcalloc(1, sizeof(*m));
981                 ct->c_ctparams = (void *) m;
982
983                 pp = &m->mp_parts;
984                 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
985                         struct part *part;
986                         CT p;
987
988                         if (user_content(in, file, buffer, &p) == DONE) {
989                                 if (!m->mp_parts)
990                                         adios(EX_DATAERR, NULL, "empty \"#begin ... #end\" sequence");
991                                 return OK;
992                         }
993                         if (!p)
994                                 continue;
995
996                         part = mh_xcalloc(1, sizeof(*part));
997                         *pp = part;
998                         pp = &part->mp_next;
999                         part->mp_part = p;
1000                 }
1001                 admonish(NULL, "premature end-of-file, missing #end");
1002                 return OK;
1003         }
1004
1005         /*
1006         ** Unknown directive
1007         */
1008         adios(EX_DATAERR, NULL, "unknown directive \"#%s\"", ci->ci_type);
1009         return NOTOK;  /* NOT REACHED */
1010 }
1011
1012
1013 static void
1014 set_id(CT ct, int top)
1015 {
1016         char msgid[BUFSIZ];
1017         static int partno;
1018         static time_t clock = 0;
1019         static char *msgfmt;
1020
1021         if (clock == 0) {
1022                 time(&clock);
1023                 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1024                                 (int) getpid(), (long) clock, LocalName());
1025                 partno = 0;
1026                 msgfmt = mh_xstrdup(msgid);
1027         }
1028         snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1029         ct->c_id = mh_xstrdup(msgid);
1030 }
1031
1032
1033 /*
1034 ** Fill out, or expand the various contents in the composition
1035 ** draft.  Read-in any necessary files.  Parse and execute any
1036 ** commands specified by profile composition strings.
1037 */
1038
1039 static int
1040 compose_content(CT ct)
1041 {
1042         CE ce = ct->c_cefile;
1043
1044         switch (ct->c_type) {
1045         case CT_MULTIPART:
1046         {
1047                 int partnum;
1048                 char *pp;
1049                 char partnam[BUFSIZ];
1050                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1051                 struct part *part;
1052
1053                 if (ct->c_partno) {
1054                         snprintf(partnam, sizeof(partnam), "%s.",
1055                                         ct->c_partno);
1056                         pp = partnam + strlen(partnam);
1057                 } else {
1058                         pp = partnam;
1059                 }
1060
1061                 /* first, we call compose_content on all the subparts */
1062                 for (part = m->mp_parts, partnum = 1; part;
1063                                 part = part->mp_next, partnum++) {
1064                         CT p = part->mp_part;
1065
1066                         sprintf(pp, "%d", partnum);
1067                         p->c_partno = mh_xstrdup(partnam);
1068                         if (compose_content(p) == NOTOK)
1069                                 return NOTOK;
1070                 }
1071         }
1072         break;
1073
1074         case CT_MESSAGE:
1075                 /* Nothing to do for type message */
1076                 break;
1077
1078         /*
1079         ** Discrete types (text/application/audio/image/video)
1080         */
1081         default:
1082                 if (!ce->ce_file) {
1083                         pid_t child_id;
1084                         int xstdout, len, buflen;
1085                         char *bp, **ap, *cp;
1086                         char *vec[4], buffer[BUFSIZ];
1087                         FILE *out;
1088                         CI ci = &ct->c_ctinfo;
1089                         char *tfile = NULL;
1090
1091                         if (!(cp = ci->ci_magic))
1092                                 adios(EX_SOFTWARE, NULL, "internal error(5)");
1093
1094                         tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1095                         if (tfile == NULL) {
1096                                 adios(EX_CANTCREAT, "mhbuild", "unable to create temporary file");
1097                         }
1098                         ce->ce_file = mh_xstrdup(tfile);
1099                         ce->ce_unlink = 1;
1100
1101                         xstdout = 0;
1102
1103                         /* Get buffer ready to go */
1104                         bp = buffer;
1105                         bp[0] = '\0';
1106                         buflen = sizeof(buffer);
1107
1108                         /*
1109                         ** Parse composition string into buffer
1110                         */
1111                         for ( ; *cp; cp++) {
1112                                 if (*cp == '%') {
1113                                         switch (*++cp) {
1114                                         case 'a':
1115                                         {
1116                                                 /*
1117                                                 ** insert parameters from
1118                                                 ** directive
1119                                                 */
1120                                                 char **ep;
1121                                                 char *s = "";
1122
1123                                                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1124                                                         snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1125                                                         len = strlen(bp);
1126                                                         bp += len;
1127                                                         buflen -= len;
1128                                                         s = " ";
1129                                                 }
1130                                         }
1131                                         break;
1132
1133                                         case 'F':
1134                                                 /* %f, and stdout is not-redirected */
1135                                                 xstdout = 1;
1136                                                 /* and fall... */
1137
1138                                         case 'f':
1139                                                 /*
1140                                                 ** insert temporary filename
1141                                                 ** where content should be
1142                                                 ** written
1143                                                 */
1144                                                 snprintf(bp, buflen, "%s", ce->ce_file);
1145                                                 break;
1146
1147                                         case 's':
1148                                                 /* insert content subtype */
1149                                                 strncpy(bp, ci->ci_subtype, buflen);
1150                                                 break;
1151
1152                                         case '%':
1153                                                 /* insert character % */
1154                                                 goto raw;
1155
1156                                         default:
1157                                                 *bp++ = *--cp;
1158                                                 *bp = '\0';
1159                                                 buflen--;
1160                                                 continue;
1161                                         }
1162                                         len = strlen(bp);
1163                                         bp += len;
1164                                         buflen -= len;
1165                                 } else {
1166 raw:
1167                                         *bp++ = *cp;
1168                                         *bp = '\0';
1169                                         buflen--;
1170                                 }
1171                         }
1172
1173                         if (verbosw)
1174                                 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1175
1176                         fflush(stdout);  /* not sure if need for -noverbose */
1177
1178                         vec[0] = "/bin/sh";
1179                         vec[1] = "-c";
1180                         vec[2] = buffer;
1181                         vec[3] = NULL;
1182
1183                         if ((out = fopen(ce->ce_file, "w")) == NULL)
1184                                 adios(EX_IOERR, ce->ce_file, "unable to open for writing");
1185
1186                         switch (child_id = fork()) {
1187                         case NOTOK:
1188                                 adios(EX_OSERR, "fork", "unable to fork");
1189                                 /* NOTREACHED */
1190
1191                         case OK:
1192                                 if (!xstdout)
1193                                         dup2(fileno(out), 1);
1194                                 close(fileno(out));
1195                                 execvp("/bin/sh", vec);
1196                                 fprintf(stderr, "unable to exec ");
1197                                 perror("/bin/sh");
1198                                 _exit(EX_OSERR);
1199                                 /* NOTREACHED */
1200
1201                         default:
1202                                 fclose(out);
1203                                 if (pidXwait(child_id, NULL))
1204                                         exit(EX_SOFTWARE);
1205                                 break;
1206                         }
1207                 }
1208                 break;
1209         }
1210
1211         return OK;
1212 }
1213
1214
1215 /*
1216 ** Scan the content.
1217 **
1218 **    1) choose a transfer encoding.
1219 **    2) check for clashes with multipart boundary string.
1220 **    3) for text content, figure out which character set is being used.
1221 **
1222 ** If there is a clash with one of the contents and the multipart boundary,
1223 ** this function will exit with NOTOK.  This will cause the scanning process
1224 ** to be repeated with a different multipart boundary.  It is possible
1225 ** (although highly unlikely) that this scan will be repeated multiple times.
1226 */
1227
1228 static int
1229 scan_content(CT ct)
1230 {
1231         int len;
1232         int check8bit = 0, contains8bit = 0;
1233         int checklinelen = 0, linelen = 0;
1234         int checkboundary = 0, boundaryclash = 0;
1235         int checklinespace = 0, linespace = 0;  /* trailing whitespace */
1236         unsigned char *cp = NULL, buffer[BUFSIZ];
1237         struct text *t = NULL;
1238         FILE *in = NULL;
1239         CE ce = ct->c_cefile;
1240
1241         /*
1242         ** handle multipart by scanning all subparts
1243         ** and then checking their encoding.
1244         */
1245         if (ct->c_type == CT_MULTIPART) {
1246                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1247                 struct part *part;
1248
1249                 /* initially mark the domain of enclosing multipart as 7bit */
1250                 ct->c_encoding = CE_7BIT;
1251
1252                 for (part = m->mp_parts; part; part = part->mp_next) {
1253                         CT p = part->mp_part;
1254
1255                         if (scan_content(p) == NOTOK) {
1256                                 /* choose encoding for subpart */
1257                                 return NOTOK;
1258                         }
1259
1260                         /*
1261                         ** if necessary, enlarge encoding for enclosing
1262                         ** multipart
1263                         */
1264                         if (p->c_encoding == CE_BINARY)
1265                                 ct->c_encoding = CE_BINARY;
1266                         if (p->c_encoding == CE_8BIT &&
1267                                         ct->c_encoding != CE_BINARY)
1268                                 ct->c_encoding = CE_8BIT;
1269                 }
1270
1271                 return OK;
1272         }
1273
1274         /*
1275         ** Decide what to check while scanning this content.
1276         */
1277         switch (ct->c_type) {
1278         case CT_TEXT:
1279                 check8bit = 1;
1280                 checkboundary = 1;
1281                 checklinelen = 1;
1282                 checklinespace = 1;
1283                 break;
1284
1285         case CT_MESSAGE:
1286                 check8bit = 0;
1287                 checklinelen = 0;
1288                 checklinespace = 0;
1289                 checkboundary = 1;
1290                 break;
1291
1292         case CT_APPLICATION:
1293         case CT_AUDIO:
1294         case CT_IMAGE:
1295         case CT_VIDEO:
1296                 check8bit = 0;
1297                 checklinelen = 0;
1298                 checklinespace = 0;
1299                 checkboundary = 0;
1300                 break;
1301         }
1302
1303         /*
1304         ** Scan the unencoded content
1305         */
1306         if (check8bit || checklinelen || checklinespace || checkboundary) {
1307                 if ((in = fopen(ce->ce_file, "r")) == NULL)
1308                         adios(EX_IOERR, ce->ce_file, "unable to open for reading");
1309                 len = strlen(prefix);
1310
1311                 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1312                         if (check8bit) {
1313                                 for (cp = buffer; *cp; cp++) {
1314                                         if (!isascii(*cp)) {
1315                                                 contains8bit = 1;
1316                                                 /* no need to keep checking */
1317                                                 check8bit = 0;
1318                                         }
1319                                 }
1320                         }
1321
1322                         if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1323                                 linelen = 1;
1324                                 checklinelen = 0;  /* no need to keep checking */
1325                         }
1326
1327                         if (checklinespace &&
1328                                         (cp = buffer + strlen(buffer) - 2) >
1329                                         buffer && isspace(*cp)) {
1330                                 linespace = 1;
1331                                 /* no need to keep checking */
1332                                 checklinespace = 0;
1333                         }
1334
1335                         /*
1336                         ** Check if content contains a line that clashes
1337                         ** with our standard boundary for multipart messages.
1338                         */
1339                         if (checkboundary && buffer[0] == '-' &&
1340                                         buffer[1] == '-') {
1341                                 for (cp = buffer + strlen(buffer) - 1;
1342                                                 cp >= buffer; cp--)
1343                                         if (!isspace(*cp))
1344                                                 break;
1345                                 *++cp = '\0';
1346                                 if (strncmp(buffer + 2, prefix, len)==0 &&
1347                                                 isdigit(buffer[2 + len])) {
1348                                         boundaryclash = 1;
1349                                         /* no need to keep checking */
1350                                         checkboundary = 0;
1351                                 }
1352                         }
1353                 }
1354                 fclose(in);
1355         }
1356
1357         /*
1358         ** Decide which transfer encoding to use.
1359         */
1360         switch (ct->c_type) {
1361         case CT_TEXT:
1362                 /*
1363                 ** If the text content didn't specify a character
1364                 ** set, we need to figure out which one was used.
1365                 */
1366                 t = (struct text *) ct->c_ctparams;
1367                 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1368                         CI ci = &ct->c_ctinfo;
1369                         char **ap, **ep;
1370
1371                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1372                                 continue;
1373
1374                         if (contains8bit) {
1375                                 t->tx_charset = CHARSET_UNKNOWN;
1376                                 *ap = concat("charset=", write_charset_8bit(),
1377                                                 NULL);
1378                         } else {
1379                                 t->tx_charset = CHARSET_USASCII;
1380                                 *ap = mh_xstrdup("charset=us-ascii");
1381                         }
1382
1383                         cp = strchr(*ap++, '=');
1384                         *ap = NULL;
1385                         *cp++ = '\0';
1386                         *ep = cp;
1387                 }
1388
1389                 if (contains8bit || linelen || linespace)
1390                         ct->c_encoding = CE_QUOTED;
1391                 else
1392                         ct->c_encoding = CE_7BIT;
1393                 break;
1394
1395         case CT_MESSAGE:
1396                 ct->c_encoding = CE_7BIT;
1397                 break;
1398
1399         case CT_APPLICATION:
1400         case CT_AUDIO:
1401         case CT_IMAGE:
1402         case CT_VIDEO:
1403                 /*
1404                 ** Forcing use of base64, because these types likely
1405                 ** contain binary data and NUL bytes. Don't care about
1406                 ** files that would be clean.
1407                 */
1408                 ct->c_encoding = CE_BASE64;
1409                 break;
1410         }
1411
1412         return (boundaryclash ? NOTOK : OK);
1413 }
1414
1415
1416 /*
1417 ** Scan the content structures, and build header
1418 ** fields that will need to be output into the
1419 ** message.
1420 */
1421
1422 static int
1423 build_headers(CT ct)
1424 {
1425         int cc, len;
1426         char **ap, **ep;
1427         char *np, *vp, buffer[BUFSIZ];
1428         CI ci = &ct->c_ctinfo;
1429
1430         /*
1431         ** If message is type multipart, then add the multipart
1432         ** boundary to the list of attribute/value pairs.
1433         */
1434         if (ct->c_type == CT_MULTIPART) {
1435                 char *cp;
1436                 static int level = 0;  /* store nesting level */
1437
1438                 ap = ci->ci_attrs;
1439                 ep = ci->ci_values;
1440                 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1441                                 prefix, level++);
1442                 cp = strchr(*ap++ = mh_xstrdup(buffer), '=');
1443                 *ap = NULL;
1444                 *cp++ = '\0';
1445                 *ep = cp;
1446         }
1447
1448         /*
1449         ** output the content type and subtype
1450         */
1451         np = mh_xstrdup(TYPE_FIELD);
1452         vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1453
1454         /* keep track of length of line */
1455         len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1456                         strlen(ci->ci_subtype) + 3;
1457
1458         /*
1459         ** Append the attribute/value pairs to
1460         ** the end of the Content-Type line.
1461         */
1462         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1463                 vp = add(";", vp);
1464                 len++;
1465
1466                 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1467                 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1468                         vp = add("\n\t", vp);
1469                         len = 8;
1470                 } else {
1471                         vp = add(" ", vp);
1472                         len++;
1473                 }
1474                 vp = add(buffer, vp);
1475                 len += cc;
1476         }
1477
1478         /*
1479         ** Append any RFC-822 comment to the end of
1480         ** the Content-Type line.
1481         */
1482         if (ci->ci_comment) {
1483                 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1484                 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1485                         vp = add("\n\t", vp);
1486                         len = 8;
1487                 } else {
1488                         vp = add(" ", vp);
1489                         len++;
1490                 }
1491                 vp = add(buffer, vp);
1492                 len += cc;
1493         }
1494         vp = add("\n", vp);
1495         add_header(ct, np, vp);
1496
1497         /*
1498         ** output the Content-ID
1499         */
1500         if (ct->c_id) {
1501                 np = mh_xstrdup(ID_FIELD);
1502                 vp = concat(" ", ct->c_id, NULL);
1503                 add_header(ct, np, vp);
1504         }
1505
1506         /*
1507         ** output the Content-Description
1508         */
1509         if (ct->c_descr) {
1510                 np = mh_xstrdup(DESCR_FIELD);
1511                 vp = concat(" ", ct->c_descr, NULL);
1512                 if (encode_rfc2047(DESCR_FIELD, &vp, NULL)) {
1513                         adios(EX_DATAERR, NULL, "Unable to encode %s header", DESCR_FIELD);
1514                 }
1515                 add_header(ct, np, vp);
1516         }
1517
1518         /*
1519         ** output the Content-Disposition
1520         */
1521         if (ct->c_dispo) {
1522                 np = mh_xstrdup(DISPO_FIELD);
1523                 vp = concat(" ", ct->c_dispo, NULL);
1524                 add_header(ct, np, vp);
1525         }
1526
1527         /*
1528         ** output the Content-Transfer-Encoding
1529         */
1530         switch (ct->c_encoding) {
1531         case CE_7BIT:
1532                 /* Nothing to output */
1533                 break;
1534
1535         case CE_8BIT:
1536                 if (ct->c_type == CT_MESSAGE)
1537                         adios(EX_DATAERR, NULL, "internal error, invalid encoding");
1538
1539                 np = mh_xstrdup(ENCODING_FIELD);
1540                 vp = concat(" ", "8bit", "\n", NULL);
1541                 add_header(ct, np, vp);
1542                 break;
1543
1544         case CE_QUOTED:
1545                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1546                         adios(EX_DATAERR, NULL, "internal error, invalid encoding");
1547
1548                 np = mh_xstrdup(ENCODING_FIELD);
1549                 vp = concat(" ", "quoted-printable", "\n", NULL);
1550                 add_header(ct, np, vp);
1551                 break;
1552
1553         case CE_BASE64:
1554                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1555                         adios(EX_DATAERR, NULL, "internal error, invalid encoding");
1556
1557                 np = mh_xstrdup(ENCODING_FIELD);
1558                 vp = concat(" ", "base64", "\n", NULL);
1559                 add_header(ct, np, vp);
1560                 break;
1561
1562         case CE_BINARY:
1563                 if (ct->c_type == CT_MESSAGE)
1564                         adios(EX_DATAERR, NULL, "internal error, invalid encoding");
1565
1566                 np = mh_xstrdup(ENCODING_FIELD);
1567                 vp = concat(" ", "binary", "\n", NULL);
1568                 add_header(ct, np, vp);
1569                 break;
1570
1571         default:
1572                 adios(EX_DATAERR, NULL, "unknown transfer encoding in content");
1573                 break;
1574         }
1575
1576         /*
1577         ** Additional content specific header processing
1578         */
1579         switch (ct->c_type) {
1580         case CT_MULTIPART:
1581         {
1582                 struct multipart *m;
1583                 struct part *part;
1584
1585                 m = (struct multipart *) ct->c_ctparams;
1586                 for (part = m->mp_parts; part; part = part->mp_next) {
1587                         CT p;
1588
1589                         p = part->mp_part;
1590                         build_headers(p);
1591                 }
1592         }
1593                 break;
1594
1595         default:
1596                 /* Nothing to do */
1597                 break;
1598         }
1599
1600         return OK;
1601 }