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