Make the base64 encoder endian-agnostic, and remove the function
[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 msgid[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 (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
906                 (int) getpid(), (long) clock, LocalName(1));
907         partno = 0;
908         msgfmt = getcpy(msgid);
909     }
910     snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
911     ct->c_id = getcpy (msgid);
912 }
913
914
915 static char ebcdicsafe[0x100] = {
916     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
917     0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
918     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
919     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
920     0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
921     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
922     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
923     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
924     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
925     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
926     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
927     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
928     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
929     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
930     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
931     0x01, 0x01, 0x01, 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     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
948 };
949
950
951 /*
952  * Fill out, or expand the various contents in the composition
953  * draft.  Read-in any necessary files.  Parse and execute any
954  * commands specified by profile composition strings.
955  */
956
957 static int
958 compose_content (CT ct)
959 {
960     CE ce = ct->c_cefile;
961
962     switch (ct->c_type) {
963     case CT_MULTIPART:
964     {
965         int partnum;
966         char *pp;
967         char partnam[BUFSIZ];
968         struct multipart *m = (struct multipart *) ct->c_ctparams;
969         struct part *part;
970
971         if (ct->c_partno) {
972             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
973             pp = partnam + strlen (partnam);
974         } else {
975             pp = partnam;
976         }
977
978         /* first, we call compose_content on all the subparts */
979         for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
980             CT p = part->mp_part;
981
982             sprintf (pp, "%d", partnum);
983             p->c_partno = add (partnam, NULL);
984             if (compose_content (p) == NOTOK)
985                 return NOTOK;
986         }
987
988         /*
989          * If the -rfc934mode switch is given, then check all
990          * the subparts of a multipart/digest.  If they are all
991          * message/rfc822, then mark this content and all
992          * subparts with the rfc934 compatibility mode flag.
993          */
994         if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
995             int is934 = 1;
996
997             for (part = m->mp_parts; part; part = part->mp_next) {
998                 CT p = part->mp_part;
999
1000                 if (p->c_subtype != MESSAGE_RFC822) {
1001                     is934 = 0;
1002                     break;
1003                 }
1004             }
1005             ct->c_rfc934 = is934;
1006             for (part = m->mp_parts; part; part = part->mp_next) {
1007                 CT p = part->mp_part;
1008
1009                 if ((p->c_rfc934 = is934))
1010                     p->c_end++;
1011             }
1012         }
1013
1014         if (listsw) {
1015             ct->c_end = (partnum = strlen (prefix) + 2) + 2;
1016             if (ct->c_rfc934)
1017                 ct->c_end += 1;
1018
1019             for (part = m->mp_parts; part; part = part->mp_next)
1020                 ct->c_end += part->mp_part->c_end + partnum;
1021         }
1022     }
1023     break;
1024
1025     case CT_MESSAGE:
1026         /* Nothing to do for type message */
1027         break;
1028
1029     /*
1030      * Discrete types (text/application/audio/image/video)
1031      */
1032     default:
1033         if (!ce->ce_file) {
1034             pid_t child_id;
1035             int i, xstdout, len, buflen;
1036             char *bp, **ap, *cp;
1037             char *vec[4], buffer[BUFSIZ];
1038             FILE *out;
1039             CI ci = &ct->c_ctinfo;
1040             char *tfile = NULL;
1041
1042             if (!(cp = ci->ci_magic))
1043                 adios (NULL, "internal error(5)");
1044
1045             tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1046             if (tfile == NULL) {
1047                 adios("mhbuildsbr", "unable to create temporary file");
1048             }
1049             ce->ce_file = add (tfile, NULL);
1050             ce->ce_unlink = 1;
1051
1052             xstdout = 0;
1053
1054             /* Get buffer ready to go */
1055             bp = buffer;
1056             bp[0] = '\0';
1057             buflen = sizeof(buffer);
1058
1059             /*
1060              * Parse composition string into buffer
1061              */
1062             for ( ; *cp; cp++) {
1063                 if (*cp == '%') {
1064                     switch (*++cp) {
1065                     case 'a':
1066                     {
1067                         /* insert parameters from directive */
1068                         char **ep;
1069                         char *s = "";
1070
1071                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1072                             snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1073                             len = strlen (bp);
1074                             bp += len;
1075                             buflen -= len;
1076                             s = " ";
1077                         }
1078                     }
1079                     break;
1080
1081                     case 'F':
1082                         /* %f, and stdout is not-redirected */
1083                         xstdout = 1;
1084                         /* and fall... */
1085
1086                     case 'f':
1087                         /*
1088                          * insert temporary filename where
1089                          * content should be written
1090                          */
1091                         snprintf (bp, buflen, "%s", ce->ce_file);
1092                         break;
1093
1094                     case 's':
1095                         /* insert content subtype */
1096                         strncpy (bp, ci->ci_subtype, buflen);
1097                         break;
1098
1099                     case '%':
1100                         /* insert character % */
1101                         goto raw;
1102
1103                     default:
1104                         *bp++ = *--cp;
1105                         *bp = '\0';
1106                         buflen--;
1107                         continue;
1108                     }
1109                     len = strlen (bp);
1110                     bp += len;
1111                     buflen -= len;
1112                 } else {
1113 raw:
1114                 *bp++ = *cp;
1115                 *bp = '\0';
1116                 buflen--;
1117                 }
1118             }
1119
1120             if (verbosw)
1121                 printf ("composing content %s/%s from command\n\t%s\n",
1122                         ci->ci_type, ci->ci_subtype, buffer);
1123
1124             fflush (stdout);    /* not sure if need for -noverbose */
1125
1126             vec[0] = "/bin/sh";
1127             vec[1] = "-c";
1128             vec[2] = buffer;
1129             vec[3] = NULL;
1130
1131             if ((out = fopen (ce->ce_file, "w")) == NULL)
1132                 adios (ce->ce_file, "unable to open for writing");
1133
1134             for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1135                 sleep (5);
1136             switch (child_id) {
1137             case NOTOK:
1138                 adios ("fork", "unable to fork");
1139                 /* NOTREACHED */
1140
1141             case OK:
1142                 if (!xstdout)
1143                     dup2 (fileno (out), 1);
1144                 close (fileno (out));
1145                 execvp ("/bin/sh", vec);
1146                 fprintf (stderr, "unable to exec ");
1147                 perror ("/bin/sh");
1148                 _exit (-1);
1149                 /* NOTREACHED */
1150
1151             default:
1152                 fclose (out);
1153                 if (pidXwait(child_id, NULL))
1154                     done (1);
1155                 break;
1156             }
1157         }
1158
1159         /* Check size of file */
1160         if (listsw && ct->c_end == 0L) {
1161             struct stat st;
1162
1163             if (stat (ce->ce_file, &st) != NOTOK)
1164                 ct->c_end = (long) st.st_size;
1165         }
1166         break;
1167     }
1168
1169     return OK;
1170 }
1171
1172
1173 /*
1174  * Scan the content.
1175  *
1176  *    1) choose a transfer encoding.
1177  *    2) check for clashes with multipart boundary string.
1178  *    3) for text content, figure out which character set is being used.
1179  *
1180  * If there is a clash with one of the contents and the multipart boundary,
1181  * this function will exit with NOTOK.  This will cause the scanning process
1182  * to be repeated with a different multipart boundary.  It is possible
1183  * (although highly unlikely) that this scan will be repeated multiple times.
1184  */
1185
1186 static int
1187 scan_content (CT ct)
1188 {
1189     int len;
1190     int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data                */
1191     int checklinelen = 0, linelen = 0;    /* check for long lines                       */
1192     int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1193     int checklinespace = 0, linespace = 0;  /* check if any line ends with space          */
1194     int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1195     unsigned char *cp = NULL, buffer[BUFSIZ];
1196     struct text *t = NULL;
1197     FILE *in = NULL;
1198     CE ce = ct->c_cefile;
1199
1200     /*
1201      * handle multipart by scanning all subparts
1202      * and then checking their encoding.
1203      */
1204     if (ct->c_type == CT_MULTIPART) {
1205         struct multipart *m = (struct multipart *) ct->c_ctparams;
1206         struct part *part;
1207
1208         /* initially mark the domain of enclosing multipart as 7bit */
1209         ct->c_encoding = CE_7BIT;
1210
1211         for (part = m->mp_parts; part; part = part->mp_next) {
1212             CT p = part->mp_part;
1213
1214             if (scan_content (p) == NOTOK)      /* choose encoding for subpart */
1215                 return NOTOK;
1216
1217             /* if necessary, enlarge encoding for enclosing multipart */
1218             if (p->c_encoding == CE_BINARY)
1219                 ct->c_encoding = CE_BINARY;
1220             if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1221                 ct->c_encoding = CE_8BIT;
1222         }
1223
1224         return OK;
1225     }
1226
1227     /*
1228      * Decide what to check while scanning this content.
1229      */
1230     switch (ct->c_type) {
1231     case CT_TEXT:
1232         check8bit = 1;
1233         checkboundary = 1;
1234         if (ct->c_subtype == TEXT_PLAIN) {
1235             checkebcdic = 0;
1236             checklinelen = 0;
1237             checklinespace = 0;
1238         } else {
1239             checkebcdic = ebcdicsw;
1240             checklinelen = 1;
1241             checklinespace = 1;
1242         }
1243         break;
1244
1245     case CT_APPLICATION:
1246         check8bit = 1;
1247         checkebcdic = ebcdicsw;
1248         checklinelen = 1;
1249         checklinespace = 1;
1250         checkboundary = 1;
1251         break;
1252
1253     case CT_MESSAGE:
1254         check8bit = 0;
1255         checkebcdic = 0;
1256         checklinelen = 0;
1257         checklinespace = 0;
1258
1259         /* don't check anything for message/external */
1260         if (ct->c_subtype == MESSAGE_EXTERNAL)
1261             checkboundary = 0;
1262         else
1263             checkboundary = 1;
1264         break;
1265
1266     case CT_AUDIO:
1267     case CT_IMAGE:
1268     case CT_VIDEO:
1269         /*
1270          * Don't check anything for these types,
1271          * since we are forcing use of base64.
1272          */
1273         check8bit = 0;
1274         checkebcdic = 0;
1275         checklinelen = 0;
1276         checklinespace = 0;
1277         checkboundary = 0;
1278         break;
1279     }
1280
1281     /*
1282      * Scan the unencoded content
1283      */
1284     if (check8bit || checklinelen || checklinespace || checkboundary) {
1285         if ((in = fopen (ce->ce_file, "r")) == NULL)
1286             adios (ce->ce_file, "unable to open for reading");
1287         len = strlen (prefix);
1288
1289         while (fgets (buffer, sizeof(buffer) - 1, in)) {
1290             /*
1291              * Check for 8bit data.
1292              */
1293             if (check8bit) {
1294                 for (cp = buffer; *cp; cp++) {
1295                     if (!isascii (*cp)) {
1296                         contains8bit = 1;
1297                         check8bit = 0;  /* no need to keep checking */
1298                     }
1299                     /*
1300                      * Check if character is ebcdic-safe.  We only check
1301                      * this if also checking for 8bit data.
1302                      */
1303                     if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1304                         ebcdicunsafe = 1;
1305                         checkebcdic = 0; /* no need to keep checking */
1306                     }
1307                 }
1308             }
1309
1310             /*
1311              * Check line length.
1312              */
1313             if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1314                 linelen = 1;
1315                 checklinelen = 0;       /* no need to keep checking */
1316             }
1317
1318             /*
1319              * Check if line ends with a space.
1320              */
1321             if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1322                 linespace = 1;
1323                 checklinespace = 0;     /* no need to keep checking */
1324             }
1325
1326             /*
1327              * Check if content contains a line that clashes
1328              * with our standard boundary for multipart messages.
1329              */
1330             if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1331                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1332                     if (!isspace (*cp))
1333                         break;
1334                 *++cp = '\0';
1335                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1336                     boundaryclash = 1;
1337                     checkboundary = 0;  /* no need to keep checking */
1338                 }
1339             }
1340         }
1341         fclose (in);
1342     }
1343
1344     /*
1345      * Decide which transfer encoding to use.
1346      */
1347     switch (ct->c_type) {
1348     case CT_TEXT:
1349         /*
1350          * If the text content didn't specify a character
1351          * set, we need to figure out which one was used.
1352          */
1353         t = (struct text *) ct->c_ctparams;
1354         if (t->tx_charset == CHARSET_UNSPECIFIED) {
1355             CI ci = &ct->c_ctinfo;
1356             char **ap, **ep;
1357
1358             for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1359                 continue;
1360
1361             if (contains8bit) {
1362                 t->tx_charset = CHARSET_UNKNOWN;
1363                 *ap = concat ("charset=", write_charset_8bit(), NULL);
1364             } else {
1365                 t->tx_charset = CHARSET_USASCII;
1366                 *ap = add ("charset=us-ascii", NULL);
1367             }
1368
1369             cp = strchr(*ap++, '=');
1370             *ap = NULL;
1371             *cp++ = '\0';
1372             *ep = cp;
1373         }
1374
1375         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1376             ct->c_encoding = CE_QUOTED;
1377         else
1378             ct->c_encoding = CE_7BIT;
1379         break;
1380
1381     case CT_APPLICATION:
1382         /* For application type, use base64, except when postscript */
1383         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1384             ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1385                 ? CE_QUOTED : CE_BASE64;
1386         else
1387             ct->c_encoding = CE_7BIT;
1388         break;
1389
1390     case CT_MESSAGE:
1391         ct->c_encoding = CE_7BIT;
1392         break;
1393
1394     case CT_AUDIO:
1395     case CT_IMAGE:
1396     case CT_VIDEO:
1397         /* For audio, image, and video contents, just use base64 */
1398         ct->c_encoding = CE_BASE64;
1399         break;
1400     }
1401
1402     return (boundaryclash ? NOTOK : OK);
1403 }
1404
1405
1406 /*
1407  * Scan the content structures, and build header
1408  * fields that will need to be output into the
1409  * message.
1410  */
1411
1412 static int
1413 build_headers (CT ct)
1414 {
1415     int cc, mailbody, len;
1416     char **ap, **ep;
1417     char *np, *vp, buffer[BUFSIZ];
1418     CI ci = &ct->c_ctinfo;
1419
1420     /*
1421      * If message is type multipart, then add the multipart
1422      * boundary to the list of attribute/value pairs.
1423      */
1424     if (ct->c_type == CT_MULTIPART) {
1425         char *cp;
1426         static int level = 0;   /* store nesting level */
1427
1428         ap = ci->ci_attrs;
1429         ep = ci->ci_values;
1430         snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1431         cp = strchr(*ap++ = add (buffer, NULL), '=');
1432         *ap = NULL;
1433         *cp++ = '\0';
1434         *ep = cp;
1435     }
1436
1437     /*
1438      * Skip the output of Content-Type, parameters, content
1439      * description and disposition, and Content-ID if the
1440      * content is of type "message" and the rfc934 compatibility
1441      * flag is set (which means we are inside multipart/digest
1442      * and the switch -rfc934mode was given).
1443      */
1444     if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1445         goto skip_headers;
1446
1447     /*
1448      * output the content type and subtype
1449      */
1450     np = add (TYPE_FIELD, NULL);
1451     vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1452
1453     /* keep track of length of line */
1454     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1455                 + strlen (ci->ci_subtype) + 3;
1456
1457     mailbody = ct->c_type == CT_MESSAGE
1458         && ct->c_subtype == MESSAGE_EXTERNAL
1459         && ((struct exbody *) ct->c_ctparams)->eb_body;
1460
1461     /*
1462      * Append the attribute/value pairs to
1463      * the end of the Content-Type line.
1464      */
1465     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1466         if (mailbody && !mh_strcasecmp (*ap, "body"))
1467             continue;
1468
1469         vp = add (";", vp);
1470         len++;
1471
1472         snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1473         if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1474             vp = add ("\n\t", vp);
1475             len = 8;
1476         } else {
1477             vp = add (" ", vp);
1478             len++;
1479         }
1480         vp = add (buffer, vp);
1481         len += cc;
1482     }
1483
1484     /*
1485      * Append any RFC-822 comment to the end of
1486      * the Content-Type line.
1487      */
1488     if (ci->ci_comment) {
1489         snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1490         if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1491             vp = add ("\n\t", vp);
1492             len = 8;
1493         } else {
1494             vp = add (" ", vp);
1495             len++;
1496         }
1497         vp = add (buffer, vp);
1498         len += cc;
1499     }
1500     vp = add ("\n", vp);
1501     add_header (ct, np, vp);
1502
1503     /*
1504      * output the Content-ID, unless disabled by -nocontentid
1505      */
1506     if (contentidsw && ct->c_id) {
1507         np = add (ID_FIELD, NULL);
1508         vp = concat (" ", ct->c_id, NULL);
1509         add_header (ct, np, vp);
1510     }
1511
1512     /*
1513      * output the Content-Description
1514      */
1515     if (ct->c_descr) {
1516         np = add (DESCR_FIELD, NULL);
1517         vp = concat (" ", ct->c_descr, NULL);
1518         add_header (ct, np, vp);
1519     }
1520
1521     /*
1522      * output the Content-Disposition
1523      */
1524     if (ct->c_dispo) {
1525         np = add (DISPO_FIELD, NULL);
1526         vp = concat (" ", ct->c_dispo, NULL);
1527         add_header (ct, np, vp);
1528     }
1529
1530 skip_headers:
1531     /*
1532      * If this is the internal content structure for a
1533      * "message/external", then we are done with the
1534      * headers (since it has no body).
1535      */
1536     if (ct->c_ctexbody)
1537         return OK;
1538
1539     /*
1540      * output the Content-MD5
1541      */
1542     if (checksw) {
1543         np = add (MD5_FIELD, NULL);
1544         vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1545         add_header (ct, np, vp);
1546     }
1547
1548     /*
1549      * output the Content-Transfer-Encoding
1550      */
1551     switch (ct->c_encoding) {
1552     case CE_7BIT:
1553         /* Nothing to output */
1554 #if 0
1555         np = add (ENCODING_FIELD, NULL);
1556         vp = concat (" ", "7bit", "\n", NULL);
1557         add_header (ct, np, vp);
1558 #endif
1559         break;
1560
1561     case CE_8BIT:
1562         if (ct->c_type == CT_MESSAGE)
1563             adios (NULL, "internal error, invalid encoding");
1564
1565         np = add (ENCODING_FIELD, NULL);
1566         vp = concat (" ", "8bit", "\n", NULL);
1567         add_header (ct, np, vp);
1568         break;
1569
1570     case CE_QUOTED:
1571         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1572             adios (NULL, "internal error, invalid encoding");
1573
1574         np = add (ENCODING_FIELD, NULL);
1575         vp = concat (" ", "quoted-printable", "\n", NULL);
1576         add_header (ct, np, vp);
1577         break;
1578
1579     case CE_BASE64:
1580         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1581             adios (NULL, "internal error, invalid encoding");
1582
1583         np = add (ENCODING_FIELD, NULL);
1584         vp = concat (" ", "base64", "\n", NULL);
1585         add_header (ct, np, vp);
1586         break;
1587
1588     case CE_BINARY:
1589         if (ct->c_type == CT_MESSAGE)
1590             adios (NULL, "internal error, invalid encoding");
1591
1592         np = add (ENCODING_FIELD, NULL);
1593         vp = concat (" ", "binary", "\n", NULL);
1594         add_header (ct, np, vp);
1595         break;
1596
1597     default:
1598         adios (NULL, "unknown transfer encoding in content");
1599         break;
1600     }
1601
1602     /*
1603      * Additional content specific header processing
1604      */
1605     switch (ct->c_type) {
1606     case CT_MULTIPART:
1607     {
1608         struct multipart *m;
1609         struct part *part;
1610
1611         m = (struct multipart *) ct->c_ctparams;
1612         for (part = m->mp_parts; part; part = part->mp_next) {
1613             CT p;
1614
1615             p = part->mp_part;
1616             build_headers (p);
1617         }
1618     }
1619         break;
1620
1621     case CT_MESSAGE:
1622         if (ct->c_subtype == MESSAGE_EXTERNAL) {
1623             struct exbody *e;
1624
1625             e = (struct exbody *) ct->c_ctparams;
1626             build_headers (e->eb_content);
1627         }
1628         break;
1629
1630     default:
1631         /* Nothing to do */
1632         break;
1633     }
1634
1635     return OK;
1636 }
1637
1638
1639 static char nib2b64[0x40+1] =
1640         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1641
1642 static char *
1643 calculate_digest (CT ct, int asciiP)
1644 {
1645     int cc;
1646     char buffer[BUFSIZ], *vp, *op;
1647     unsigned char *dp;
1648     unsigned char digest[16];
1649     unsigned char outbuf[25];
1650     MD5_CTX mdContext;
1651     CE ce = ct->c_cefile;
1652     char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1653     FILE *in;
1654
1655     /* open content */
1656     if ((in = fopen (infilename, "r")) == NULL)
1657         adios (infilename, "unable to open for reading");
1658
1659     /* Initialize md5 context */
1660     MD5Init (&mdContext);
1661
1662     /* calculate md5 message digest */
1663     if (asciiP) {
1664         while (fgets (buffer, sizeof(buffer) - 1, in)) {
1665             char c, *cp;
1666
1667             cp = buffer + strlen (buffer) - 1;
1668             if ((c = *cp) == '\n')
1669                 *cp = '\0';
1670
1671             MD5Update (&mdContext, (unsigned char *) buffer,
1672                        (unsigned int) strlen (buffer));
1673
1674             if (c == '\n')
1675                 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1676         }
1677     } else {
1678         while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1679             MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1680     }
1681
1682     /* md5 finalization.  Write digest and zero md5 context */
1683     MD5Final (digest, &mdContext);
1684
1685     /* close content */
1686     fclose (in);
1687
1688     /* print debugging info */
1689     if (debugsw) {
1690         unsigned char *ep;
1691
1692         fprintf (stderr, "MD5 digest=");
1693         for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1694                  dp < ep; dp++)
1695             fprintf (stderr, "%02x", *dp & 0xff);
1696         fprintf (stderr, "\n");
1697     }
1698
1699     /* encode the digest using base64 */
1700     for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1701                 cc > 0; cc -= 3, op += 4) {
1702         unsigned long bits;
1703         char *bp;
1704
1705         bits = (*dp++ & 0xff) << 16;
1706         if (cc > 1) {
1707             bits |= (*dp++ & 0xff) << 8;
1708             if (cc > 2)
1709                 bits |= *dp++ & 0xff;
1710         }
1711
1712         for (bp = op + 4; bp > op; bits >>= 6)
1713             *--bp = nib2b64[bits & 0x3f];
1714         if (cc < 3) {
1715             *(op + 3) = '=';
1716             if (cc < 2)
1717                 *(op + 2) = '=';
1718         }
1719     }
1720
1721     /* null terminate string */
1722     outbuf[24] = '\0';
1723
1724     /* now make copy and return string */
1725     vp = concat (" ", outbuf, "\n", NULL);
1726     return vp;
1727 }