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