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