Well the wrap of vfork() calls didn't work: its semantics don't allow that. So...
[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, xstdout, len, buflen;
992             char *bp, **ap, *cp;
993             char *vec[4], buffer[BUFSIZ];
994             FILE *out;
995             CI ci = &ct->c_ctinfo;
996             char *tfile = NULL;
997
998             if (!(cp = ci->ci_magic))
999                 adios (NULL, "internal error(5)");
1000
1001             tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1002             if (tfile == NULL) {
1003                 adios("mhbuildsbr", "unable to create temporary file");
1004             }
1005             ce->ce_file = add (tfile, NULL);
1006             ce->ce_unlink = 1;
1007
1008             xstdout = 0;
1009
1010             /* Get buffer ready to go */
1011             bp = buffer;
1012             bp[0] = '\0';
1013             buflen = sizeof(buffer);
1014
1015             /*
1016              * Parse composition string into buffer
1017              */
1018             for ( ; *cp; cp++) {
1019                 if (*cp == '%') {
1020                     switch (*++cp) {
1021                     case 'a':
1022                     {
1023                         /* insert parameters from directive */
1024                         char **ep;
1025                         char *s = "";
1026
1027                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1028                             snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1029                             len = strlen (bp);
1030                             bp += len;
1031                             buflen -= len;
1032                             s = " ";
1033                         }
1034                     }
1035                     break;
1036
1037                     case 'F':
1038                         /* %f, and stdout is not-redirected */
1039                         xstdout = 1;
1040                         /* and fall... */
1041
1042                     case 'f':
1043                         /*
1044                          * insert temporary filename where
1045                          * content should be written
1046                          */
1047                         snprintf (bp, buflen, "%s", ce->ce_file);
1048                         break;
1049
1050                     case 's':
1051                         /* insert content subtype */
1052                         strncpy (bp, ci->ci_subtype, buflen);
1053                         break;
1054
1055                     case '%':
1056                         /* insert character % */
1057                         goto raw;
1058
1059                     default:
1060                         *bp++ = *--cp;
1061                         *bp = '\0';
1062                         buflen--;
1063                         continue;
1064                     }
1065                     len = strlen (bp);
1066                     bp += len;
1067                     buflen -= len;
1068                 } else {
1069 raw:
1070                 *bp++ = *cp;
1071                 *bp = '\0';
1072                 buflen--;
1073                 }
1074             }
1075
1076             if (verbosw)
1077                 printf ("composing content %s/%s from command\n\t%s\n",
1078                         ci->ci_type, ci->ci_subtype, buffer);
1079
1080             fflush (stdout);    /* not sure if need for -noverbose */
1081
1082             vec[0] = "/bin/sh";
1083             vec[1] = "-c";
1084             vec[2] = buffer;
1085             vec[3] = NULL;
1086
1087             if ((out = fopen (ce->ce_file, "w")) == NULL)
1088                 adios (ce->ce_file, "unable to open for writing");
1089
1090             for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1091                 sleep (5);
1092             switch (child_id) {
1093             case NOTOK:
1094                 adios ("fork", "unable to fork");
1095                 /* NOTREACHED */
1096
1097             case OK:
1098                 if (!xstdout)
1099                     dup2 (fileno (out), 1);
1100                 close (fileno (out));
1101                 execvp ("/bin/sh", vec);
1102                 fprintf (stderr, "unable to exec ");
1103                 perror ("/bin/sh");
1104                 _exit (-1);
1105                 /* NOTREACHED */
1106
1107             default:
1108                 fclose (out);
1109                 if (pidXwait(child_id, NULL))
1110                     done (1);
1111                 break;
1112             }
1113         }
1114
1115         /* Check size of file */
1116         if (listsw && ct->c_end == 0L) {
1117             struct stat st;
1118
1119             if (stat (ce->ce_file, &st) != NOTOK)
1120                 ct->c_end = (long) st.st_size;
1121         }
1122         break;
1123     }
1124
1125     return OK;
1126 }
1127
1128
1129 /*
1130  * Scan the content.
1131  *
1132  *    1) choose a transfer encoding.
1133  *    2) check for clashes with multipart boundary string.
1134  *    3) for text content, figure out which character set is being used.
1135  *
1136  * If there is a clash with one of the contents and the multipart boundary,
1137  * this function will exit with NOTOK.  This will cause the scanning process
1138  * to be repeated with a different multipart boundary.  It is possible
1139  * (although highly unlikely) that this scan will be repeated multiple times.
1140  */
1141
1142 static int
1143 scan_content (CT ct)
1144 {
1145     int len;
1146     int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data                */
1147     int checklinelen = 0, linelen = 0;    /* check for long lines                       */
1148     int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1149     int checklinespace = 0, linespace = 0;  /* check if any line ends with space          */
1150     int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1151     unsigned char *cp = NULL, buffer[BUFSIZ];
1152     struct text *t = NULL;
1153     FILE *in = NULL;
1154     CE ce = ct->c_cefile;
1155
1156     /*
1157      * handle multipart by scanning all subparts
1158      * and then checking their encoding.
1159      */
1160     if (ct->c_type == CT_MULTIPART) {
1161         struct multipart *m = (struct multipart *) ct->c_ctparams;
1162         struct part *part;
1163
1164         /* initially mark the domain of enclosing multipart as 7bit */
1165         ct->c_encoding = CE_7BIT;
1166
1167         for (part = m->mp_parts; part; part = part->mp_next) {
1168             CT p = part->mp_part;
1169
1170             if (scan_content (p) == NOTOK)      /* choose encoding for subpart */
1171                 return NOTOK;
1172
1173             /* if necessary, enlarge encoding for enclosing multipart */
1174             if (p->c_encoding == CE_BINARY)
1175                 ct->c_encoding = CE_BINARY;
1176             if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1177                 ct->c_encoding = CE_8BIT;
1178         }
1179
1180         return OK;
1181     }
1182
1183     /*
1184      * Decide what to check while scanning this content.
1185      */
1186     switch (ct->c_type) {
1187     case CT_TEXT:
1188         check8bit = 1;
1189         checkboundary = 1;
1190         if (ct->c_subtype == TEXT_PLAIN) {
1191             checkebcdic = 0;
1192             checklinelen = 0;
1193             checklinespace = 0;
1194         } else {
1195             checkebcdic = ebcdicsw;
1196             checklinelen = 1;
1197             checklinespace = 1;
1198         }
1199         break;
1200
1201     case CT_APPLICATION:
1202         check8bit = 1;
1203         checkebcdic = ebcdicsw;
1204         checklinelen = 1;
1205         checklinespace = 1;
1206         checkboundary = 1;
1207         break;
1208
1209     case CT_MESSAGE:
1210         check8bit = 0;
1211         checkebcdic = 0;
1212         checklinelen = 0;
1213         checklinespace = 0;
1214
1215         /* don't check anything for message/external */
1216         if (ct->c_subtype == MESSAGE_EXTERNAL)
1217             checkboundary = 0;
1218         else
1219             checkboundary = 1;
1220         break;
1221
1222     case CT_AUDIO:
1223     case CT_IMAGE:
1224     case CT_VIDEO:
1225         /*
1226          * Don't check anything for these types,
1227          * since we are forcing use of base64.
1228          */
1229         check8bit = 0;
1230         checkebcdic = 0;
1231         checklinelen = 0;
1232         checklinespace = 0;
1233         checkboundary = 0;
1234         break;
1235     }
1236
1237     /*
1238      * Scan the unencoded content
1239      */
1240     if (check8bit || checklinelen || checklinespace || checkboundary) {
1241         if ((in = fopen (ce->ce_file, "r")) == NULL)
1242             adios (ce->ce_file, "unable to open for reading");
1243         len = strlen (prefix);
1244
1245         while (fgets (buffer, sizeof(buffer) - 1, in)) {
1246             /*
1247              * Check for 8bit data.
1248              */
1249             if (check8bit) {
1250                 for (cp = buffer; *cp; cp++) {
1251                     if (!isascii (*cp)) {
1252                         contains8bit = 1;
1253                         check8bit = 0;  /* no need to keep checking */
1254                     }
1255                     /*
1256                      * Check if character is ebcdic-safe.  We only check
1257                      * this if also checking for 8bit data.
1258                      */
1259                     if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1260                         ebcdicunsafe = 1;
1261                         checkebcdic = 0; /* no need to keep checking */
1262                     }
1263                 }
1264             }
1265
1266             /*
1267              * Check line length.
1268              */
1269             if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1270                 linelen = 1;
1271                 checklinelen = 0;       /* no need to keep checking */
1272             }
1273
1274             /*
1275              * Check if line ends with a space.
1276              */
1277             if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1278                 linespace = 1;
1279                 checklinespace = 0;     /* no need to keep checking */
1280             }
1281
1282             /*
1283              * Check if content contains a line that clashes
1284              * with our standard boundary for multipart messages.
1285              */
1286             if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1287                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1288                     if (!isspace (*cp))
1289                         break;
1290                 *++cp = '\0';
1291                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1292                     boundaryclash = 1;
1293                     checkboundary = 0;  /* no need to keep checking */
1294                 }
1295             }
1296         }
1297         fclose (in);
1298     }
1299
1300     /*
1301      * Decide which transfer encoding to use.
1302      */
1303     switch (ct->c_type) {
1304     case CT_TEXT:
1305         /*
1306          * If the text content didn't specify a character
1307          * set, we need to figure out which one was used.
1308          */
1309         t = (struct text *) ct->c_ctparams;
1310         if (t->tx_charset == CHARSET_UNSPECIFIED) {
1311             CI ci = &ct->c_ctinfo;
1312             char **ap, **ep;
1313
1314             for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1315                 continue;
1316
1317             if (contains8bit) {
1318                 t->tx_charset = CHARSET_UNKNOWN;
1319                 *ap = concat ("charset=", write_charset_8bit(), NULL);
1320             } else {
1321                 t->tx_charset = CHARSET_USASCII;
1322                 *ap = add ("charset=us-ascii", NULL);
1323             }
1324
1325             cp = strchr(*ap++, '=');
1326             *ap = NULL;
1327             *cp++ = '\0';
1328             *ep = cp;
1329         }
1330
1331         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1332             ct->c_encoding = CE_QUOTED;
1333         else
1334             ct->c_encoding = CE_7BIT;
1335         break;
1336
1337     case CT_APPLICATION:
1338         /* For application type, use base64, except when postscript */
1339         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1340             ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1341                 ? CE_QUOTED : CE_BASE64;
1342         else
1343             ct->c_encoding = CE_7BIT;
1344         break;
1345
1346     case CT_MESSAGE:
1347         ct->c_encoding = CE_7BIT;
1348         break;
1349
1350     case CT_AUDIO:
1351     case CT_IMAGE:
1352     case CT_VIDEO:
1353         /* For audio, image, and video contents, just use base64 */
1354         ct->c_encoding = CE_BASE64;
1355         break;
1356     }
1357
1358     return (boundaryclash ? NOTOK : OK);
1359 }
1360
1361
1362 /*
1363  * Scan the content structures, and build header
1364  * fields that will need to be output into the
1365  * message.
1366  */
1367
1368 static int
1369 build_headers (CT ct)
1370 {
1371     int cc, mailbody, len;
1372     char **ap, **ep;
1373     char *np, *vp, buffer[BUFSIZ];
1374     CI ci = &ct->c_ctinfo;
1375
1376     /*
1377      * If message is type multipart, then add the multipart
1378      * boundary to the list of attribute/value pairs.
1379      */
1380     if (ct->c_type == CT_MULTIPART) {
1381         char *cp;
1382         static int level = 0;   /* store nesting level */
1383
1384         ap = ci->ci_attrs;
1385         ep = ci->ci_values;
1386         snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1387         cp = strchr(*ap++ = add (buffer, NULL), '=');
1388         *ap = NULL;
1389         *cp++ = '\0';
1390         *ep = cp;
1391     }
1392
1393     /*
1394      * Skip the output of Content-Type, parameters, content
1395      * description and disposition, and Content-ID if the
1396      * content is of type "message" and the rfc934 compatibility
1397      * flag is set (which means we are inside multipart/digest
1398      * and the switch -rfc934mode was given).
1399      */
1400     if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1401         goto skip_headers;
1402
1403     /*
1404      * output the content type and subtype
1405      */
1406     np = add (TYPE_FIELD, NULL);
1407     vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1408
1409     /* keep track of length of line */
1410     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1411                 + strlen (ci->ci_subtype) + 3;
1412
1413     mailbody = ct->c_type == CT_MESSAGE
1414         && ct->c_subtype == MESSAGE_EXTERNAL
1415         && ((struct exbody *) ct->c_ctparams)->eb_body;
1416
1417     /*
1418      * Append the attribute/value pairs to
1419      * the end of the Content-Type line.
1420      */
1421     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1422         if (mailbody && !mh_strcasecmp (*ap, "body"))
1423             continue;
1424
1425         vp = add (";", vp);
1426         len++;
1427
1428         snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1429         if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1430             vp = add ("\n\t", vp);
1431             len = 8;
1432         } else {
1433             vp = add (" ", vp);
1434             len++;
1435         }
1436         vp = add (buffer, vp);
1437         len += cc;
1438     }
1439
1440     /*
1441      * Append any RFC-822 comment to the end of
1442      * the Content-Type line.
1443      */
1444     if (ci->ci_comment) {
1445         snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1446         if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1447             vp = add ("\n\t", vp);
1448             len = 8;
1449         } else {
1450             vp = add (" ", vp);
1451             len++;
1452         }
1453         vp = add (buffer, vp);
1454         len += cc;
1455     }
1456     vp = add ("\n", vp);
1457     add_header (ct, np, vp);
1458
1459     /*
1460      * output the Content-ID, unless disabled by -nocontentid
1461      */
1462     if (contentidsw && ct->c_id) {
1463         np = add (ID_FIELD, NULL);
1464         vp = concat (" ", ct->c_id, NULL);
1465         add_header (ct, np, vp);
1466     }
1467
1468     /*
1469      * output the Content-Description
1470      */
1471     if (ct->c_descr) {
1472         np = add (DESCR_FIELD, NULL);
1473         vp = concat (" ", ct->c_descr, NULL);
1474         add_header (ct, np, vp);
1475     }
1476
1477     /*
1478      * output the Content-Disposition
1479      */
1480     if (ct->c_dispo) {
1481         np = add (DISPO_FIELD, NULL);
1482         vp = concat (" ", ct->c_dispo, NULL);
1483         add_header (ct, np, vp);
1484     }
1485
1486 skip_headers:
1487     /*
1488      * If this is the internal content structure for a
1489      * "message/external", then we are done with the
1490      * headers (since it has no body).
1491      */
1492     if (ct->c_ctexbody)
1493         return OK;
1494
1495     /*
1496      * output the Content-MD5
1497      */
1498     if (checksw) {
1499         np = add (MD5_FIELD, NULL);
1500         vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1501         add_header (ct, np, vp);
1502     }
1503
1504     /*
1505      * output the Content-Transfer-Encoding
1506      */
1507     switch (ct->c_encoding) {
1508     case CE_7BIT:
1509         /* Nothing to output */
1510 #if 0
1511         np = add (ENCODING_FIELD, NULL);
1512         vp = concat (" ", "7bit", "\n", NULL);
1513         add_header (ct, np, vp);
1514 #endif
1515         break;
1516
1517     case CE_8BIT:
1518         if (ct->c_type == CT_MESSAGE)
1519             adios (NULL, "internal error, invalid encoding");
1520
1521         np = add (ENCODING_FIELD, NULL);
1522         vp = concat (" ", "8bit", "\n", NULL);
1523         add_header (ct, np, vp);
1524         break;
1525
1526     case CE_QUOTED:
1527         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1528             adios (NULL, "internal error, invalid encoding");
1529
1530         np = add (ENCODING_FIELD, NULL);
1531         vp = concat (" ", "quoted-printable", "\n", NULL);
1532         add_header (ct, np, vp);
1533         break;
1534
1535     case CE_BASE64:
1536         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1537             adios (NULL, "internal error, invalid encoding");
1538
1539         np = add (ENCODING_FIELD, NULL);
1540         vp = concat (" ", "base64", "\n", NULL);
1541         add_header (ct, np, vp);
1542         break;
1543
1544     case CE_BINARY:
1545         if (ct->c_type == CT_MESSAGE)
1546             adios (NULL, "internal error, invalid encoding");
1547
1548         np = add (ENCODING_FIELD, NULL);
1549         vp = concat (" ", "binary", "\n", NULL);
1550         add_header (ct, np, vp);
1551         break;
1552
1553     default:
1554         adios (NULL, "unknown transfer encoding in content");
1555         break;
1556     }
1557
1558     /*
1559      * Additional content specific header processing
1560      */
1561     switch (ct->c_type) {
1562     case CT_MULTIPART:
1563     {
1564         struct multipart *m;
1565         struct part *part;
1566
1567         m = (struct multipart *) ct->c_ctparams;
1568         for (part = m->mp_parts; part; part = part->mp_next) {
1569             CT p;
1570
1571             p = part->mp_part;
1572             build_headers (p);
1573         }
1574     }
1575         break;
1576
1577     case CT_MESSAGE:
1578         if (ct->c_subtype == MESSAGE_EXTERNAL) {
1579             struct exbody *e;
1580
1581             e = (struct exbody *) ct->c_ctparams;
1582             build_headers (e->eb_content);
1583         }
1584         break;
1585
1586     default:
1587         /* Nothing to do */
1588         break;
1589     }
1590
1591     return OK;
1592 }
1593
1594
1595 static char nib2b64[0x40+1] =
1596         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1597
1598 static char *
1599 calculate_digest (CT ct, int asciiP)
1600 {
1601     int cc;
1602     char buffer[BUFSIZ], *vp, *op;
1603     unsigned char *dp;
1604     unsigned char digest[16];
1605     unsigned char outbuf[25];
1606     FILE *in;
1607     MD5_CTX mdContext;
1608     CE ce = ct->c_cefile;
1609
1610     /* open content */
1611     if ((in = fopen (ce->ce_file, "r")) == NULL)
1612         adios (ce->ce_file, "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 }