* Bug #15213, #18635: The use of the insecure m_scratch() and
[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         char *cp;
418
419         cp = m_mktemp2(NULL, invo_name, NULL, &out);
420         if (cp == NULL) adios("mhbuildsbr", "unable to create temporary file");
421
422         /* use a temp file to collect the plain text lines */
423         ce->ce_file = add (cp, NULL);
424         ce->ce_unlink = 1;
425
426         if (buf[0] == '#' && buf[1] == '<') {
427             strncpy (content, buf + 2, sizeof(content));
428             inlineD = 1;
429             goto rock_and_roll;
430         } else {
431             inlineD = 0;
432         }
433
434         /* the directive is implicit */
435         strncpy (content, "text/plain", sizeof(content));
436         headers = 0;
437         strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
438         for (;;) {
439             int i;
440
441             if (headers >= 0 && uprf (buffer, DESCR_FIELD)
442                 && buffer[i = strlen (DESCR_FIELD)] == ':') {
443                 headers = 1;
444
445 again_descr:
446                 ct->c_descr = add (buffer + i + 1, ct->c_descr);
447                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
448                     adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
449                 switch (buffer[0]) {
450                 case ' ':
451                 case '\t':
452                     i = -1;
453                     goto again_descr;
454
455                 case '#':
456                     adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
457                     /* NOTREACHED */
458
459                 default:
460                     break;
461                 }
462             }
463
464             if (headers >= 0 && uprf (buffer, DISPO_FIELD)
465                 && buffer[i = strlen (DISPO_FIELD)] == ':') {
466                 headers = 1;
467
468 again_dispo:
469                 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
470                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
471                     adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
472                 switch (buffer[0]) {
473                 case ' ':
474                 case '\t':
475                     i = -1;
476                     goto again_dispo;
477
478                 case '#':
479                     adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
480                     /* NOTREACHED */
481
482                 default:
483                     break;
484                 }
485             }
486
487             if (headers != 1 || buffer[0] != '\n')
488                 fputs (buffer, out);
489
490 rock_and_roll:
491             headers = -1;
492             pos = ftell (in);
493             if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
494                 break;
495             if (buffer[0] == '#') {
496                 char *bp;
497
498                 if (buffer[1] != '#')
499                     break;
500                 for (cp = (bp = buffer) + 1; *cp; cp++)
501                     *bp++ = *cp;
502                 *bp = '\0';
503             }
504         }
505
506         if (listsw)
507             ct->c_end = ftell (out);
508         fclose (out);
509
510         /* parse content type */
511         if (get_ctinfo (content, ct, inlineD) == NOTOK)
512             done (1);
513
514         for (s2i = str2cts; s2i->si_key; s2i++)
515             if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
516                 break;
517         if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
518             s2i++;
519
520         /*
521          * check type specified (possibly implicitly)
522          */
523         switch (ct->c_type = s2i->si_val) {
524         case CT_MESSAGE:
525             if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
526                 ct->c_encoding = CE_7BIT;
527                 goto call_init;
528             }
529             /* else fall... */
530         case CT_MULTIPART:
531             adios (NULL, "it doesn't make sense to define an in-line %s content",
532                    ct->c_type == CT_MESSAGE ? "message" : "multipart");
533             /* NOTREACHED */
534
535         default:
536 call_init:
537             if ((ct->c_ctinitfnx = s2i->si_init))
538                 (*ct->c_ctinitfnx) (ct);
539             break;
540         }
541
542         if (cp)
543             fseek (in, pos, SEEK_SET);
544         return OK;
545     }
546
547     /*
548      * If we've reached this point, the next line
549      * must be some type of explicit directive.
550      */
551
552     /* check if directive is external-type */
553     extrnal = (buf[1] == '@');
554
555     /* parse directive */
556     if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
557         done (1);
558
559     /* check directive against the list of MIME types */
560     for (s2i = str2cts; s2i->si_key; s2i++)
561         if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
562             break;
563
564     /*
565      * Check if the directive specified a valid type.
566      * This will happen if it was one of the following forms:
567      *
568      *    #type/subtype  (or)
569      *    #@type/subtype
570      */
571     if (s2i->si_key) {
572         if (!ci->ci_subtype)
573             adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
574
575         switch (ct->c_type = s2i->si_val) {
576         case CT_MULTIPART:
577             adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
578                    ci->ci_type, ci->ci_subtype);
579             /* NOTREACHED */
580
581         case CT_MESSAGE:
582             if (!mh_strcasecmp (ci->ci_subtype, "partial"))
583                 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
584                        ci->ci_type, ci->ci_subtype);
585             if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
586                 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
587                        ci->ci_type, ci->ci_subtype);
588 use_forw:
589             adios (NULL,
590                    "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
591                    ci->ci_type, ci->ci_subtype);
592             /* NOTREACHED */
593
594         default:
595             if ((ct->c_ctinitfnx = s2i->si_init))
596                 (*ct->c_ctinitfnx) (ct);
597             break;
598         }
599
600         /*
601          * #@type/subtype (external types directive)
602          */
603         if (extrnal) {
604             struct exbody *e;
605             CT p;
606
607             if (!ci->ci_magic)
608                 adios (NULL, "need external information for \"#@%s/%s\"",
609                        ci->ci_type, ci->ci_subtype);
610             p = ct;
611
612             snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
613             free (ci->ci_magic);
614             ci->ci_magic = NULL;
615
616             /*
617              * Since we are using the current Content structure to
618              * hold information about the type of the external
619              * reference, we need to create another Content structure
620              * for the message/external-body to wrap it in.
621              */
622             if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
623                 adios (NULL, "out of memory");
624             *ctp = ct;
625             ci = &ct->c_ctinfo;
626             if (get_ctinfo (buffer, ct, 0) == NOTOK)
627                 done (1);
628             ct->c_type = CT_MESSAGE;
629             ct->c_subtype = MESSAGE_EXTERNAL;
630
631             if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
632                 adios (NULL, "out of memory");
633             ct->c_ctparams = (void *) e;
634
635             e->eb_parent = ct;
636             e->eb_content = p;
637             p->c_ctexbody = e;
638
639             if (params_external (ct, 1) == NOTOK)
640                 done (1);
641
642             return OK;
643         }
644
645         /* Handle [file] argument */
646         if (ci->ci_magic) {
647             /* check if specifies command to execute */
648             if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
649                 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
650                     continue;
651                 if (!*cp)
652                     adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
653                 cp = add (cp, NULL);
654                 free (ci->ci_magic);
655                 ci->ci_magic = cp;
656             } else {
657                 /* record filename of decoded contents */
658                 ce->ce_file = ci->ci_magic;
659                 if (access (ce->ce_file, R_OK) == NOTOK)
660                     adios ("reading", "unable to access %s for", ce->ce_file);
661                 if (listsw && stat (ce->ce_file, &st) != NOTOK)
662                     ct->c_end = (long) st.st_size;
663                 ci->ci_magic = NULL;
664             }
665             return OK;
666         }
667
668         /*
669          * No [file] argument, so check profile for
670          * method to compose content.
671          */
672         snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
673                 invo_name, ci->ci_type, ci->ci_subtype);
674         if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
675             snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
676             if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
677                 content_error (NULL, ct, "don't know how to compose content");
678                 done (1);
679             }
680         }
681         ci->ci_magic = add (cp, NULL);
682         return OK;
683     }
684
685     if (extrnal)
686         adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
687
688     /*
689      * Message directive
690      * #forw [+folder] [msgs]
691      */
692     if (!mh_strcasecmp (ci->ci_type, "forw")) {
693         int msgnum;
694         char *folder, *arguments[MAXARGS];
695         struct msgs *mp;
696
697         if (ci->ci_magic) {
698             ap = brkstring (ci->ci_magic, " ", "\n");
699             copyip (ap, arguments, MAXARGS);
700         } else {
701             arguments[0] = "cur";
702             arguments[1] = NULL;
703         }
704         folder = NULL;
705
706         /* search the arguments for a folder name */
707         for (ap = arguments; *ap; ap++) {
708             cp = *ap;
709             if (*cp == '+' || *cp == '@') {
710                 if (folder)
711                     adios (NULL, "only one folder per #forw directive");
712                 else
713                     folder = pluspath (cp);
714             }
715         }
716
717         /* else, use the current folder */
718         if (!folder)
719             folder = add (getfolder (1), NULL);
720
721         if (!(mp = folder_read (folder)))
722             adios (NULL, "unable to read folder %s", folder);
723         for (ap = arguments; *ap; ap++) {
724             cp = *ap;
725             if (*cp != '+' && *cp != '@')
726                 if (!m_convert (mp, cp))
727                     done (1);
728         }
729         free (folder);
730         free_ctinfo (ct);
731
732         /*
733          * If there is more than one message to include, make this
734          * a content of type "multipart/digest" and insert each message
735          * as a subpart.  If there is only one message, then make this
736          * a content of type "message/rfc822".
737          */
738         if (mp->numsel > 1) {
739             /* we are forwarding multiple messages */
740             if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
741                 done (1);
742             ct->c_type = CT_MULTIPART;
743             ct->c_subtype = MULTI_DIGEST;
744
745             if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
746                 adios (NULL, "out of memory");
747             ct->c_ctparams = (void *) m;
748             pp = &m->mp_parts;
749
750             for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
751                 if (is_selected(mp, msgnum)) {
752                     struct part *part;
753                     CT p;
754                     CE pe;
755
756                     if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
757                         adios (NULL, "out of memory");
758                     init_decoded_content (p);
759                     pe = p->c_cefile;
760                     if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
761                         done (1);
762                     p->c_type = CT_MESSAGE;
763                     p->c_subtype = MESSAGE_RFC822;
764
765                     snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
766                     pe->ce_file = add (buffer, NULL);
767                     if (listsw && stat (pe->ce_file, &st) != NOTOK)
768                         p->c_end = (long) st.st_size;
769
770                     if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
771                         adios (NULL, "out of memory");
772                     *pp = part;
773                     pp = &part->mp_next;
774                     part->mp_part = p;
775                 }
776             }
777         } else {
778             /* we are forwarding one message */
779             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
780                 done (1);
781             ct->c_type = CT_MESSAGE;
782             ct->c_subtype = MESSAGE_RFC822;
783
784             msgnum = mp->lowsel;
785             snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
786             ce->ce_file = add (buffer, NULL);
787             if (listsw && stat (ce->ce_file, &st) != NOTOK)
788                 ct->c_end = (long) st.st_size;
789         }
790
791         folder_free (mp);       /* free folder/message structure */
792         return OK;
793     }
794
795     /*
796      * #end
797      */
798     if (!mh_strcasecmp (ci->ci_type, "end")) {
799         free_content (ct);
800         *ctp = NULL;
801         return DONE;
802     }
803
804     /*
805      * #begin [ alternative | parallel ]
806      */
807     if (!mh_strcasecmp (ci->ci_type, "begin")) {
808         if (!ci->ci_magic) {
809             vrsn = MULTI_MIXED;
810             cp = SubMultiPart[vrsn - 1].kv_key;
811         } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
812             vrsn = MULTI_ALTERNATE;
813             cp = SubMultiPart[vrsn - 1].kv_key;
814         } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
815             vrsn = MULTI_PARALLEL;
816             cp = SubMultiPart[vrsn - 1].kv_key;
817         } else if (uprf (ci->ci_magic, "digest")) {
818             goto use_forw;
819         } else {
820             vrsn = MULTI_UNKNOWN;
821             cp = ci->ci_magic;
822         }
823
824         free_ctinfo (ct);
825         snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
826         if (get_ctinfo (buffer, ct, 0) == NOTOK)
827             done (1);
828         ct->c_type = CT_MULTIPART;
829         ct->c_subtype = vrsn;
830
831         if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
832             adios (NULL, "out of memory");
833         ct->c_ctparams = (void *) m;
834
835         pp = &m->mp_parts;
836         while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
837             struct part *part;
838             CT p;
839
840             if (user_content (in, file, buffer, &p) == DONE) {
841                 if (!m->mp_parts)
842                     adios (NULL, "empty \"#begin ... #end\" sequence");
843                 return OK;
844             }
845             if (!p)
846                 continue;
847
848             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
849                 adios (NULL, "out of memory");
850             *pp = part;
851             pp = &part->mp_next;
852             part->mp_part = p;
853         }
854         admonish (NULL, "premature end-of-file, missing #end");
855         return OK;
856     }
857
858     /*
859      * Unknown directive
860      */
861     adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
862     return NOTOK;       /* NOT REACHED */
863 }
864
865
866 static void
867 set_id (CT ct, int top)
868 {
869     char msgid[BUFSIZ];
870     static int partno;
871     static time_t clock = 0;
872     static char *msgfmt;
873
874     if (clock == 0) {
875         time (&clock);
876         snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
877                 (int) getpid(), (long) clock, LocalName());
878         partno = 0;
879         msgfmt = getcpy(msgid);
880     }
881     snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
882     ct->c_id = getcpy (msgid);
883 }
884
885
886 static char ebcdicsafe[0x100] = {
887     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
888     0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
889     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
890     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
891     0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
892     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
893     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
894     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
895     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
896     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
897     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
898     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
899     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
900     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
901     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
902     0x01, 0x01, 0x01, 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     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
919 };
920
921
922 /*
923  * Fill out, or expand the various contents in the composition
924  * draft.  Read-in any necessary files.  Parse and execute any
925  * commands specified by profile composition strings.
926  */
927
928 static int
929 compose_content (CT ct)
930 {
931     CE ce = ct->c_cefile;
932
933     switch (ct->c_type) {
934     case CT_MULTIPART:
935     {
936         int partnum;
937         char *pp;
938         char partnam[BUFSIZ];
939         struct multipart *m = (struct multipart *) ct->c_ctparams;
940         struct part *part;
941
942         if (ct->c_partno) {
943             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
944             pp = partnam + strlen (partnam);
945         } else {
946             pp = partnam;
947         }
948
949         /* first, we call compose_content on all the subparts */
950         for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
951             CT p = part->mp_part;
952
953             sprintf (pp, "%d", partnum);
954             p->c_partno = add (partnam, NULL);
955             if (compose_content (p) == NOTOK)
956                 return NOTOK;
957         }
958
959         /*
960          * If the -rfc934mode switch is given, then check all
961          * the subparts of a multipart/digest.  If they are all
962          * message/rfc822, then mark this content and all
963          * subparts with the rfc934 compatibility mode flag.
964          */
965         if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
966             int is934 = 1;
967
968             for (part = m->mp_parts; part; part = part->mp_next) {
969                 CT p = part->mp_part;
970
971                 if (p->c_subtype != MESSAGE_RFC822) {
972                     is934 = 0;
973                     break;
974                 }
975             }
976             ct->c_rfc934 = is934;
977             for (part = m->mp_parts; part; part = part->mp_next) {
978                 CT p = part->mp_part;
979
980                 if ((p->c_rfc934 = is934))
981                     p->c_end++;
982             }
983         }
984
985         if (listsw) {
986             ct->c_end = (partnum = strlen (prefix) + 2) + 2;
987             if (ct->c_rfc934)
988                 ct->c_end += 1;
989
990             for (part = m->mp_parts; part; part = part->mp_next)
991                 ct->c_end += part->mp_part->c_end + partnum;
992         }
993     }
994     break;
995
996     case CT_MESSAGE:
997         /* Nothing to do for type message */
998         break;
999
1000     /*
1001      * Discrete types (text/application/audio/image/video)
1002      */
1003     default:
1004         if (!ce->ce_file) {
1005             pid_t child_id;
1006             int i, xstdout, len, buflen;
1007             char *bp, **ap, *cp;
1008             char *vec[4], buffer[BUFSIZ];
1009             FILE *out;
1010             CI ci = &ct->c_ctinfo;
1011             char *tfile = NULL;
1012
1013             if (!(cp = ci->ci_magic))
1014                 adios (NULL, "internal error(5)");
1015
1016             tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1017             if (tfile == NULL) {
1018                 adios("mhbuildsbr", "unable to create temporary file");
1019             }
1020             ce->ce_file = add (tfile, NULL);
1021             ce->ce_unlink = 1;
1022
1023             xstdout = 0;
1024
1025             /* Get buffer ready to go */
1026             bp = buffer;
1027             bp[0] = '\0';
1028             buflen = sizeof(buffer);
1029
1030             /*
1031              * Parse composition string into buffer
1032              */
1033             for ( ; *cp; cp++) {
1034                 if (*cp == '%') {
1035                     switch (*++cp) {
1036                     case 'a':
1037                     {
1038                         /* insert parameters from directive */
1039                         char **ep;
1040                         char *s = "";
1041
1042                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1043                             snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1044                             len = strlen (bp);
1045                             bp += len;
1046                             buflen -= len;
1047                             s = " ";
1048                         }
1049                     }
1050                     break;
1051
1052                     case 'F':
1053                         /* %f, and stdout is not-redirected */
1054                         xstdout = 1;
1055                         /* and fall... */
1056
1057                     case 'f':
1058                         /*
1059                          * insert temporary filename where
1060                          * content should be written
1061                          */
1062                         snprintf (bp, buflen, "%s", ce->ce_file);
1063                         break;
1064
1065                     case 's':
1066                         /* insert content subtype */
1067                         strncpy (bp, ci->ci_subtype, buflen);
1068                         break;
1069
1070                     case '%':
1071                         /* insert character % */
1072                         goto raw;
1073
1074                     default:
1075                         *bp++ = *--cp;
1076                         *bp = '\0';
1077                         buflen--;
1078                         continue;
1079                     }
1080                     len = strlen (bp);
1081                     bp += len;
1082                     buflen -= len;
1083                 } else {
1084 raw:
1085                 *bp++ = *cp;
1086                 *bp = '\0';
1087                 buflen--;
1088                 }
1089             }
1090
1091             if (verbosw)
1092                 printf ("composing content %s/%s from command\n\t%s\n",
1093                         ci->ci_type, ci->ci_subtype, buffer);
1094
1095             fflush (stdout);    /* not sure if need for -noverbose */
1096
1097             vec[0] = "/bin/sh";
1098             vec[1] = "-c";
1099             vec[2] = buffer;
1100             vec[3] = NULL;
1101
1102             if ((out = fopen (ce->ce_file, "w")) == NULL)
1103                 adios (ce->ce_file, "unable to open for writing");
1104
1105             for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1106                 sleep (5);
1107             switch (child_id) {
1108             case NOTOK:
1109                 adios ("fork", "unable to fork");
1110                 /* NOTREACHED */
1111
1112             case OK:
1113                 if (!xstdout)
1114                     dup2 (fileno (out), 1);
1115                 close (fileno (out));
1116                 execvp ("/bin/sh", vec);
1117                 fprintf (stderr, "unable to exec ");
1118                 perror ("/bin/sh");
1119                 _exit (-1);
1120                 /* NOTREACHED */
1121
1122             default:
1123                 fclose (out);
1124                 if (pidXwait(child_id, NULL))
1125                     done (1);
1126                 break;
1127             }
1128         }
1129
1130         /* Check size of file */
1131         if (listsw && ct->c_end == 0L) {
1132             struct stat st;
1133
1134             if (stat (ce->ce_file, &st) != NOTOK)
1135                 ct->c_end = (long) st.st_size;
1136         }
1137         break;
1138     }
1139
1140     return OK;
1141 }
1142
1143
1144 /*
1145  * Scan the content.
1146  *
1147  *    1) choose a transfer encoding.
1148  *    2) check for clashes with multipart boundary string.
1149  *    3) for text content, figure out which character set is being used.
1150  *
1151  * If there is a clash with one of the contents and the multipart boundary,
1152  * this function will exit with NOTOK.  This will cause the scanning process
1153  * to be repeated with a different multipart boundary.  It is possible
1154  * (although highly unlikely) that this scan will be repeated multiple times.
1155  */
1156
1157 static int
1158 scan_content (CT ct)
1159 {
1160     int len;
1161     int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data                */
1162     int checklinelen = 0, linelen = 0;    /* check for long lines                       */
1163     int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1164     int checklinespace = 0, linespace = 0;  /* check if any line ends with space          */
1165     int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1166     unsigned char *cp = NULL, buffer[BUFSIZ];
1167     struct text *t = NULL;
1168     FILE *in = NULL;
1169     CE ce = ct->c_cefile;
1170
1171     /*
1172      * handle multipart by scanning all subparts
1173      * and then checking their encoding.
1174      */
1175     if (ct->c_type == CT_MULTIPART) {
1176         struct multipart *m = (struct multipart *) ct->c_ctparams;
1177         struct part *part;
1178
1179         /* initially mark the domain of enclosing multipart as 7bit */
1180         ct->c_encoding = CE_7BIT;
1181
1182         for (part = m->mp_parts; part; part = part->mp_next) {
1183             CT p = part->mp_part;
1184
1185             if (scan_content (p) == NOTOK)      /* choose encoding for subpart */
1186                 return NOTOK;
1187
1188             /* if necessary, enlarge encoding for enclosing multipart */
1189             if (p->c_encoding == CE_BINARY)
1190                 ct->c_encoding = CE_BINARY;
1191             if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1192                 ct->c_encoding = CE_8BIT;
1193         }
1194
1195         return OK;
1196     }
1197
1198     /*
1199      * Decide what to check while scanning this content.
1200      */
1201     switch (ct->c_type) {
1202     case CT_TEXT:
1203         check8bit = 1;
1204         checkboundary = 1;
1205         if (ct->c_subtype == TEXT_PLAIN) {
1206             checkebcdic = 0;
1207             checklinelen = 0;
1208             checklinespace = 0;
1209         } else {
1210             checkebcdic = ebcdicsw;
1211             checklinelen = 1;
1212             checklinespace = 1;
1213         }
1214         break;
1215
1216     case CT_APPLICATION:
1217         check8bit = 1;
1218         checkebcdic = ebcdicsw;
1219         checklinelen = 1;
1220         checklinespace = 1;
1221         checkboundary = 1;
1222         break;
1223
1224     case CT_MESSAGE:
1225         check8bit = 0;
1226         checkebcdic = 0;
1227         checklinelen = 0;
1228         checklinespace = 0;
1229
1230         /* don't check anything for message/external */
1231         if (ct->c_subtype == MESSAGE_EXTERNAL)
1232             checkboundary = 0;
1233         else
1234             checkboundary = 1;
1235         break;
1236
1237     case CT_AUDIO:
1238     case CT_IMAGE:
1239     case CT_VIDEO:
1240         /*
1241          * Don't check anything for these types,
1242          * since we are forcing use of base64.
1243          */
1244         check8bit = 0;
1245         checkebcdic = 0;
1246         checklinelen = 0;
1247         checklinespace = 0;
1248         checkboundary = 0;
1249         break;
1250     }
1251
1252     /*
1253      * Scan the unencoded content
1254      */
1255     if (check8bit || checklinelen || checklinespace || checkboundary) {
1256         if ((in = fopen (ce->ce_file, "r")) == NULL)
1257             adios (ce->ce_file, "unable to open for reading");
1258         len = strlen (prefix);
1259
1260         while (fgets (buffer, sizeof(buffer) - 1, in)) {
1261             /*
1262              * Check for 8bit data.
1263              */
1264             if (check8bit) {
1265                 for (cp = buffer; *cp; cp++) {
1266                     if (!isascii (*cp)) {
1267                         contains8bit = 1;
1268                         check8bit = 0;  /* no need to keep checking */
1269                     }
1270                     /*
1271                      * Check if character is ebcdic-safe.  We only check
1272                      * this if also checking for 8bit data.
1273                      */
1274                     if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1275                         ebcdicunsafe = 1;
1276                         checkebcdic = 0; /* no need to keep checking */
1277                     }
1278                 }
1279             }
1280
1281             /*
1282              * Check line length.
1283              */
1284             if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1285                 linelen = 1;
1286                 checklinelen = 0;       /* no need to keep checking */
1287             }
1288
1289             /*
1290              * Check if line ends with a space.
1291              */
1292             if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1293                 linespace = 1;
1294                 checklinespace = 0;     /* no need to keep checking */
1295             }
1296
1297             /*
1298              * Check if content contains a line that clashes
1299              * with our standard boundary for multipart messages.
1300              */
1301             if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1302                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1303                     if (!isspace (*cp))
1304                         break;
1305                 *++cp = '\0';
1306                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1307                     boundaryclash = 1;
1308                     checkboundary = 0;  /* no need to keep checking */
1309                 }
1310             }
1311         }
1312         fclose (in);
1313     }
1314
1315     /*
1316      * Decide which transfer encoding to use.
1317      */
1318     switch (ct->c_type) {
1319     case CT_TEXT:
1320         /*
1321          * If the text content didn't specify a character
1322          * set, we need to figure out which one was used.
1323          */
1324         t = (struct text *) ct->c_ctparams;
1325         if (t->tx_charset == CHARSET_UNSPECIFIED) {
1326             CI ci = &ct->c_ctinfo;
1327             char **ap, **ep;
1328
1329             for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1330                 continue;
1331
1332             if (contains8bit) {
1333                 t->tx_charset = CHARSET_UNKNOWN;
1334                 *ap = concat ("charset=", write_charset_8bit(), NULL);
1335             } else {
1336                 t->tx_charset = CHARSET_USASCII;
1337                 *ap = add ("charset=us-ascii", NULL);
1338             }
1339
1340             cp = strchr(*ap++, '=');
1341             *ap = NULL;
1342             *cp++ = '\0';
1343             *ep = cp;
1344         }
1345
1346         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1347             ct->c_encoding = CE_QUOTED;
1348         else
1349             ct->c_encoding = CE_7BIT;
1350         break;
1351
1352     case CT_APPLICATION:
1353         /* For application type, use base64, except when postscript */
1354         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1355             ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1356                 ? CE_QUOTED : CE_BASE64;
1357         else
1358             ct->c_encoding = CE_7BIT;
1359         break;
1360
1361     case CT_MESSAGE:
1362         ct->c_encoding = CE_7BIT;
1363         break;
1364
1365     case CT_AUDIO:
1366     case CT_IMAGE:
1367     case CT_VIDEO:
1368         /* For audio, image, and video contents, just use base64 */
1369         ct->c_encoding = CE_BASE64;
1370         break;
1371     }
1372
1373     return (boundaryclash ? NOTOK : OK);
1374 }
1375
1376
1377 /*
1378  * Scan the content structures, and build header
1379  * fields that will need to be output into the
1380  * message.
1381  */
1382
1383 static int
1384 build_headers (CT ct)
1385 {
1386     int cc, mailbody, len;
1387     char **ap, **ep;
1388     char *np, *vp, buffer[BUFSIZ];
1389     CI ci = &ct->c_ctinfo;
1390
1391     /*
1392      * If message is type multipart, then add the multipart
1393      * boundary to the list of attribute/value pairs.
1394      */
1395     if (ct->c_type == CT_MULTIPART) {
1396         char *cp;
1397         static int level = 0;   /* store nesting level */
1398
1399         ap = ci->ci_attrs;
1400         ep = ci->ci_values;
1401         snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1402         cp = strchr(*ap++ = add (buffer, NULL), '=');
1403         *ap = NULL;
1404         *cp++ = '\0';
1405         *ep = cp;
1406     }
1407
1408     /*
1409      * Skip the output of Content-Type, parameters, content
1410      * description and disposition, and Content-ID if the
1411      * content is of type "message" and the rfc934 compatibility
1412      * flag is set (which means we are inside multipart/digest
1413      * and the switch -rfc934mode was given).
1414      */
1415     if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1416         goto skip_headers;
1417
1418     /*
1419      * output the content type and subtype
1420      */
1421     np = add (TYPE_FIELD, NULL);
1422     vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1423
1424     /* keep track of length of line */
1425     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1426                 + strlen (ci->ci_subtype) + 3;
1427
1428     mailbody = ct->c_type == CT_MESSAGE
1429         && ct->c_subtype == MESSAGE_EXTERNAL
1430         && ((struct exbody *) ct->c_ctparams)->eb_body;
1431
1432     /*
1433      * Append the attribute/value pairs to
1434      * the end of the Content-Type line.
1435      */
1436     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1437         if (mailbody && !mh_strcasecmp (*ap, "body"))
1438             continue;
1439
1440         vp = add (";", vp);
1441         len++;
1442
1443         snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1444         if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1445             vp = add ("\n\t", vp);
1446             len = 8;
1447         } else {
1448             vp = add (" ", vp);
1449             len++;
1450         }
1451         vp = add (buffer, vp);
1452         len += cc;
1453     }
1454
1455     /*
1456      * Append any RFC-822 comment to the end of
1457      * the Content-Type line.
1458      */
1459     if (ci->ci_comment) {
1460         snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1461         if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1462             vp = add ("\n\t", vp);
1463             len = 8;
1464         } else {
1465             vp = add (" ", vp);
1466             len++;
1467         }
1468         vp = add (buffer, vp);
1469         len += cc;
1470     }
1471     vp = add ("\n", vp);
1472     add_header (ct, np, vp);
1473
1474     /*
1475      * output the Content-ID, unless disabled by -nocontentid
1476      */
1477     if (contentidsw && ct->c_id) {
1478         np = add (ID_FIELD, NULL);
1479         vp = concat (" ", ct->c_id, NULL);
1480         add_header (ct, np, vp);
1481     }
1482
1483     /*
1484      * output the Content-Description
1485      */
1486     if (ct->c_descr) {
1487         np = add (DESCR_FIELD, NULL);
1488         vp = concat (" ", ct->c_descr, NULL);
1489         add_header (ct, np, vp);
1490     }
1491
1492     /*
1493      * output the Content-Disposition
1494      */
1495     if (ct->c_dispo) {
1496         np = add (DISPO_FIELD, NULL);
1497         vp = concat (" ", ct->c_dispo, NULL);
1498         add_header (ct, np, vp);
1499     }
1500
1501 skip_headers:
1502     /*
1503      * If this is the internal content structure for a
1504      * "message/external", then we are done with the
1505      * headers (since it has no body).
1506      */
1507     if (ct->c_ctexbody)
1508         return OK;
1509
1510     /*
1511      * output the Content-MD5
1512      */
1513     if (checksw) {
1514         np = add (MD5_FIELD, NULL);
1515         vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1516         add_header (ct, np, vp);
1517     }
1518
1519     /*
1520      * output the Content-Transfer-Encoding
1521      */
1522     switch (ct->c_encoding) {
1523     case CE_7BIT:
1524         /* Nothing to output */
1525 #if 0
1526         np = add (ENCODING_FIELD, NULL);
1527         vp = concat (" ", "7bit", "\n", NULL);
1528         add_header (ct, np, vp);
1529 #endif
1530         break;
1531
1532     case CE_8BIT:
1533         if (ct->c_type == CT_MESSAGE)
1534             adios (NULL, "internal error, invalid encoding");
1535
1536         np = add (ENCODING_FIELD, NULL);
1537         vp = concat (" ", "8bit", "\n", NULL);
1538         add_header (ct, np, vp);
1539         break;
1540
1541     case CE_QUOTED:
1542         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1543             adios (NULL, "internal error, invalid encoding");
1544
1545         np = add (ENCODING_FIELD, NULL);
1546         vp = concat (" ", "quoted-printable", "\n", NULL);
1547         add_header (ct, np, vp);
1548         break;
1549
1550     case CE_BASE64:
1551         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1552             adios (NULL, "internal error, invalid encoding");
1553
1554         np = add (ENCODING_FIELD, NULL);
1555         vp = concat (" ", "base64", "\n", NULL);
1556         add_header (ct, np, vp);
1557         break;
1558
1559     case CE_BINARY:
1560         if (ct->c_type == CT_MESSAGE)
1561             adios (NULL, "internal error, invalid encoding");
1562
1563         np = add (ENCODING_FIELD, NULL);
1564         vp = concat (" ", "binary", "\n", NULL);
1565         add_header (ct, np, vp);
1566         break;
1567
1568     default:
1569         adios (NULL, "unknown transfer encoding in content");
1570         break;
1571     }
1572
1573     /*
1574      * Additional content specific header processing
1575      */
1576     switch (ct->c_type) {
1577     case CT_MULTIPART:
1578     {
1579         struct multipart *m;
1580         struct part *part;
1581
1582         m = (struct multipart *) ct->c_ctparams;
1583         for (part = m->mp_parts; part; part = part->mp_next) {
1584             CT p;
1585
1586             p = part->mp_part;
1587             build_headers (p);
1588         }
1589     }
1590         break;
1591
1592     case CT_MESSAGE:
1593         if (ct->c_subtype == MESSAGE_EXTERNAL) {
1594             struct exbody *e;
1595
1596             e = (struct exbody *) ct->c_ctparams;
1597             build_headers (e->eb_content);
1598         }
1599         break;
1600
1601     default:
1602         /* Nothing to do */
1603         break;
1604     }
1605
1606     return OK;
1607 }
1608
1609
1610 static char nib2b64[0x40+1] =
1611         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1612
1613 static char *
1614 calculate_digest (CT ct, int asciiP)
1615 {
1616     int cc;
1617     char buffer[BUFSIZ], *vp, *op;
1618     unsigned char *dp;
1619     unsigned char digest[16];
1620     unsigned char outbuf[25];
1621     FILE *in;
1622     MD5_CTX mdContext;
1623     CE ce = ct->c_cefile;
1624
1625     /* open content */
1626     if ((in = fopen (ce->ce_file, "r")) == NULL)
1627         adios (ce->ce_file, "unable to open for reading");
1628
1629     /* Initialize md5 context */
1630     MD5Init (&mdContext);
1631
1632     /* calculate md5 message digest */
1633     if (asciiP) {
1634         while (fgets (buffer, sizeof(buffer) - 1, in)) {
1635             char c, *cp;
1636
1637             cp = buffer + strlen (buffer) - 1;
1638             if ((c = *cp) == '\n')
1639                 *cp = '\0';
1640
1641             MD5Update (&mdContext, (unsigned char *) buffer,
1642                        (unsigned int) strlen (buffer));
1643
1644             if (c == '\n')
1645                 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1646         }
1647     } else {
1648         while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1649             MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1650     }
1651
1652     /* md5 finalization.  Write digest and zero md5 context */
1653     MD5Final (digest, &mdContext);
1654
1655     /* close content */
1656     fclose (in);
1657
1658     /* print debugging info */
1659     if (debugsw) {
1660         unsigned char *ep;
1661
1662         fprintf (stderr, "MD5 digest=");
1663         for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1664                  dp < ep; dp++)
1665             fprintf (stderr, "%02x", *dp & 0xff);
1666         fprintf (stderr, "\n");
1667     }
1668
1669     /* encode the digest using base64 */
1670     for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1671                 cc > 0; cc -= 3, op += 4) {
1672         unsigned long bits;
1673         char *bp;
1674
1675         bits = (*dp++ & 0xff) << 16;
1676         if (cc > 1) {
1677             bits |= (*dp++ & 0xff) << 8;
1678             if (cc > 2)
1679                 bits |= *dp++ & 0xff;
1680         }
1681
1682         for (bp = op + 4; bp > op; bits >>= 6)
1683             *--bp = nib2b64[bits & 0x3f];
1684         if (cc < 3) {
1685             *(op + 3) = '=';
1686             if (cc < 2)
1687                 *(op + 2) = '=';
1688         }
1689     }
1690
1691     /* null terminate string */
1692     outbuf[24] = '\0';
1693
1694     /* now make copy and return string */
1695     vp = concat (" ", outbuf, "\n", NULL);
1696     return vp;
1697 }