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