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