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