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