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