Clean up process wait to use POSIX waitpid() interface.
[mmh] / uip / mhbuildsbr.c
1
2 /*
3  * mhbuildsbr.c -- routines to expand/translate MIME composition files
4  *
5  * This code is Copyright (c) 2002, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 /*
11  * This code was originally part of mhn.c.  I split it into
12  * a separate program (mhbuild.c) and then later split it
13  * again (mhbuildsbr.c).  But the code still has some of
14  * the mhn.c code in it.  This program needs additional
15  * streamlining and removal of unneeded code.
16  */
17
18 #include <h/mh.h>
19 #include <fcntl.h>
20 #include <h/signals.h>
21 #include <h/md5.h>
22 #include <errno.h>
23 #include <signal.h>
24 #include <h/mts.h>
25 #include <h/tws.h>
26 #include <h/mime.h>
27 #include <h/mhparse.h>
28 #include <h/utils.h>
29
30 #ifdef TIME_WITH_SYS_TIME
31 # include <sys/time.h>
32 # include <time.h>
33 #else
34 # ifdef TM_IN_SYS_TIME
35 #  include <sys/time.h>
36 # else
37 #  include <time.h>
38 # endif
39 #endif
40
41
42 extern int debugsw;
43 extern int verbosw;
44
45 extern int ebcdicsw;
46 extern int listsw;
47 extern int rfc934sw;
48 extern int contentidsw;
49
50 extern int endian;      /* mhmisc.c */
51
52 /* cache policies */
53 extern int rcachesw;    /* mhcachesbr.c */
54 extern int wcachesw;    /* mhcachesbr.c */
55
56 /*
57  * Directory to place tmp files.  This must
58  * be set before these routines are called.
59  */
60 char *tmp;
61
62 pid_t xpid = 0;
63
64 static char prefix[] = "----- =_aaaaaaaaaa";
65
66
67 /* mhmisc.c */
68 int make_intermediates (char *);
69 void content_error (char *, CT, char *, ...);
70
71 /* mhcachesbr.c */
72 int find_cache (CT, int, int *, char *, char *, int);
73
74 /* ftpsbr.c */
75 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
76
77 /* mhfree.c */
78 void free_content (CT);
79 void free_ctinfo (CT);
80 void free_encoding (CT, int);
81
82 /*
83  * prototypes
84  */
85 CT build_mime (char *);
86
87 /*
88  * static prototypes
89  */
90 static int init_decoded_content (CT);
91 static char *fgetstr (char *, int, FILE *);
92 static int user_content (FILE *, char *, char *, CT *);
93 static void set_id (CT, int);
94 static int compose_content (CT);
95 static int scan_content (CT);
96 static int build_headers (CT);
97 static char *calculate_digest (CT, int);
98
99
100 /*
101  * Main routine for translating composition file
102  * into valid MIME message.  It translates the draft
103  * into a content structure (actually a tree of content
104  * structures).  This message then can be manipulated
105  * in various ways, including being output via
106  * output_message().
107  */
108
109 CT
110 build_mime (char *infile)
111 {
112     int compnum, state;
113     char buf[BUFSIZ], name[NAMESZ];
114     char *cp, *np, *vp;
115     struct multipart *m;
116     struct part **pp;
117     CT ct;
118     FILE *in;
119
120     umask (~m_gmprot ());
121
122     /* open the composition draft */
123     if ((in = fopen (infile, "r")) == NULL)
124         adios (infile, "unable to open for reading");
125
126     /*
127      * Allocate space for primary (outside) content
128      */
129     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
130         adios (NULL, "out of memory");
131
132     /*
133      * Allocate structure for handling decoded content
134      * for this part.  We don't really need this, but
135      * allocate it to remain consistent.
136      */
137     init_decoded_content (ct);
138
139     /*
140      * Parse some of the header fields in the composition
141      * draft into the linked list of header fields for
142      * the new MIME message.
143      */
144     for (compnum = 1, state = FLD;;) {
145         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
146         case FLD:
147         case FLDPLUS:
148         case FLDEOF:
149             compnum++;
150
151             /* abort if draft has Mime-Version header field */
152             if (!mh_strcasecmp (name, VRSN_FIELD))
153                 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
154
155             /* abort if draft has Content-Transfer-Encoding header field */
156             if (!mh_strcasecmp (name, ENCODING_FIELD))
157                 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
158
159             /* ignore any Content-Type fields in the header */
160             if (!mh_strcasecmp (name, TYPE_FIELD)) {
161                 while (state == FLDPLUS)
162                     state = m_getfld (state, name, buf, sizeof(buf), in);
163                 goto finish_field;
164             }
165
166             /* get copies of the buffers */
167             np = add (name, NULL);
168             vp = add (buf, NULL);
169
170             /* if necessary, get rest of field */
171             while (state == FLDPLUS) {
172                 state = m_getfld (state, name, buf, sizeof(buf), in);
173                 vp = add (buf, vp);     /* add to previous value */
174             }
175
176             /* Now add the header data to the list */
177             add_header (ct, np, vp);
178
179 finish_field:
180             /* if this wasn't the last header field, then continue */
181             if (state != FLDEOF)
182                 continue;
183             /* else fall... */
184
185         case FILEEOF:
186             adios (NULL, "draft has empty body -- no directives!");
187             /* NOTREACHED */
188
189         case BODY:
190         case BODYEOF:
191             fseek (in, (long) (-strlen (buf)), SEEK_CUR);
192             break;
193
194         case LENERR:
195         case FMTERR:
196             adios (NULL, "message format error in component #%d", compnum);
197
198         default:
199             adios (NULL, "getfld() returned %d", state);
200         }
201         break;
202     }
203
204     /*
205      * Now add the MIME-Version header field
206      * to the list of header fields.
207      */
208     np = add (VRSN_FIELD, NULL);
209     vp = concat (" ", VRSN_VALUE, "\n", NULL);
210     add_header (ct, np, vp);
211
212     /*
213      * We initally assume we will find multiple contents in the
214      * draft.  So create a multipart/mixed content to hold everything.
215      * We can remove this later, if it is not needed.
216      */
217     if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
218         done (1);
219     ct->c_type = CT_MULTIPART;
220     ct->c_subtype = MULTI_MIXED;
221     ct->c_file = add (infile, NULL);
222
223     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
224         adios (NULL, "out of memory");
225     ct->c_ctparams = (void *) m;
226     pp = &m->mp_parts;
227
228     /*
229      * read and parse the composition file
230      * and the directives it contains.
231      */
232     while (fgetstr (buf, sizeof(buf) - 1, in)) {
233         struct part *part;
234         CT p;
235
236         if (user_content (in, infile, buf, &p) == DONE) {
237             admonish (NULL, "ignoring spurious #end");
238             continue;
239         }
240         if (!p)
241             continue;
242
243         if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
244             adios (NULL, "out of memory");
245         *pp = part;
246         pp = &part->mp_next;
247         part->mp_part = p;
248     }
249
250     /*
251      * close the composition draft since
252      * it's not needed any longer.
253      */
254     fclose (in);
255
256     /* check if any contents were found */
257     if (!m->mp_parts)
258         adios (NULL, "no content directives found");
259
260     /*
261      * If only one content was found, then remove and
262      * free the outer multipart content.
263      */
264     if (!m->mp_parts->mp_next) {
265         CT p;
266
267         p = m->mp_parts->mp_part;
268         m->mp_parts->mp_part = NULL;
269
270         /* move header fields */
271         p->c_first_hf = ct->c_first_hf;
272         p->c_last_hf = ct->c_last_hf;
273         ct->c_first_hf = NULL;
274         ct->c_last_hf = NULL;
275
276         free_content (ct);
277         ct = p;
278     } else {
279         set_id (ct, 1);
280     }
281
282     /*
283      * Fill out, or expand directives.  Parse and execute
284      * commands specified by profile composition strings.
285      */
286     compose_content (ct);
287
288     if ((cp = strchr(prefix, 'a')) == NULL)
289         adios (NULL, "internal error(4)");
290
291     /*
292      * Scan the contents.  Choose a transfer encoding, and
293      * check if prefix for multipart boundary clashes with
294      * any of the contents.
295      */
296     while (scan_content (ct) == NOTOK) {
297         if (*cp < 'z') {
298             (*cp)++;
299         } else {
300             if (*++cp == 0)
301                 adios (NULL, "giving up trying to find a unique delimiter string");
302             else
303                 (*cp)++;
304         }
305     }
306
307     /* Build the rest of the header field structures */
308     build_headers (ct);
309
310     return ct;
311 }
312
313
314 /*
315  * Set up structures for placing unencoded
316  * content when building parts.
317  */
318
319 static int
320 init_decoded_content (CT ct)
321 {
322     CE ce;
323
324     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
325         adios (NULL, "out of memory");
326
327     ct->c_cefile     = ce;
328     ct->c_ceopenfnx  = open7Bit;        /* since unencoded */
329     ct->c_ceclosefnx = close_encoding;
330     ct->c_cesizefnx  = NULL;            /* since unencoded */
331
332     return OK;
333 }
334
335
336 static char *
337 fgetstr (char *s, int n, FILE *stream)
338 {
339     char *cp, *ep;
340
341     for (ep = (cp = s) + n; cp < ep; ) {
342         int i;
343
344         if (!fgets (cp, n, stream))
345             return (cp != s ? s : NULL);
346         if (cp == s && *cp != '#')
347             return s;
348
349         cp += (i = strlen (cp)) - 1;
350         if (i <= 1 || *cp-- != '\n' || *cp != '\\')
351             break;
352         *cp = '\0';
353         n -= (i - 2);
354     }
355
356     return s;
357 }
358
359
360 /*
361  * Parse the composition draft for text and directives.
362  * Do initial setup of Content structure.
363  */
364
365 static int
366 user_content (FILE *in, char *file, char *buf, CT *ctp)
367 {
368     int extrnal, vrsn;
369     unsigned char *cp;
370     char **ap;
371     char buffer[BUFSIZ];
372     struct multipart *m;
373     struct part **pp;
374     struct stat st;
375     struct str2init *s2i;
376     CI ci;
377     CT ct;
378     CE ce;
379
380     if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
381         *ctp = NULL;
382         return OK;
383     }
384
385     /* allocate basic Content structure */
386     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
387         adios (NULL, "out of memory");
388     *ctp = ct;
389
390     /* allocate basic structure for handling decoded content */
391     init_decoded_content (ct);
392     ce = ct->c_cefile;
393
394     ci = &ct->c_ctinfo;
395     set_id (ct, 0);
396
397     /*
398      * Handle inline text.  Check if line
399      * is one of the following forms:
400      *
401      * 1) doesn't begin with '#'        (implicit directive)
402      * 2) begins with "##"              (implicit directive)
403      * 3) begins with "#<"
404      */
405     if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
406         int headers;
407         int inlineD;
408         long pos;
409         char content[BUFSIZ];
410         FILE *out;
411         char *cp;
412
413         cp = m_mktemp2(NULL, invo_name, NULL, &out);
414         if (cp == NULL) adios("mhbuildsbr", "unable to create temporary file");
415
416         /* use a temp file to collect the plain text lines */
417         ce->ce_file = add (cp, NULL);
418         ce->ce_unlink = 1;
419
420         if (buf[0] == '#' && buf[1] == '<') {
421             strncpy (content, buf + 2, sizeof(content));
422             inlineD = 1;
423             goto rock_and_roll;
424         } else {
425             inlineD = 0;
426         }
427
428         /* the directive is implicit */
429         strncpy (content, "text/plain", sizeof(content));
430         headers = 0;
431         strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
432         for (;;) {
433             int i;
434
435             if (headers >= 0 && uprf (buffer, DESCR_FIELD)
436                 && buffer[i = strlen (DESCR_FIELD)] == ':') {
437                 headers = 1;
438
439 again_descr:
440                 ct->c_descr = add (buffer + i + 1, ct->c_descr);
441                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
442                     adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
443                 switch (buffer[0]) {
444                 case ' ':
445                 case '\t':
446                     i = -1;
447                     goto again_descr;
448
449                 case '#':
450                     adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
451                     /* NOTREACHED */
452
453                 default:
454                     break;
455                 }
456             }
457
458             if (headers >= 0 && uprf (buffer, DISPO_FIELD)
459                 && buffer[i = strlen (DISPO_FIELD)] == ':') {
460                 headers = 1;
461
462 again_dispo:
463                 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
464                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
465                     adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
466                 switch (buffer[0]) {
467                 case ' ':
468                 case '\t':
469                     i = -1;
470                     goto again_dispo;
471
472                 case '#':
473                     adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
474                     /* NOTREACHED */
475
476                 default:
477                     break;
478                 }
479             }
480
481             if (headers != 1 || buffer[0] != '\n')
482                 fputs (buffer, out);
483
484 rock_and_roll:
485             headers = -1;
486             pos = ftell (in);
487             if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
488                 break;
489             if (buffer[0] == '#') {
490                 char *bp;
491
492                 if (buffer[1] != '#')
493                     break;
494                 for (cp = (bp = buffer) + 1; *cp; cp++)
495                     *bp++ = *cp;
496                 *bp = '\0';
497             }
498         }
499
500         if (listsw)
501             ct->c_end = ftell (out);
502         fclose (out);
503
504         /* parse content type */
505         if (get_ctinfo (content, ct, inlineD) == NOTOK)
506             done (1);
507
508         for (s2i = str2cts; s2i->si_key; s2i++)
509             if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
510                 break;
511         if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
512             s2i++;
513
514         /*
515          * check type specified (possibly implicitly)
516          */
517         switch (ct->c_type = s2i->si_val) {
518         case CT_MESSAGE:
519             if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
520                 ct->c_encoding = CE_7BIT;
521                 goto call_init;
522             }
523             /* else fall... */
524         case CT_MULTIPART:
525             adios (NULL, "it doesn't make sense to define an in-line %s content",
526                    ct->c_type == CT_MESSAGE ? "message" : "multipart");
527             /* NOTREACHED */
528
529         default:
530 call_init:
531             if ((ct->c_ctinitfnx = s2i->si_init))
532                 (*ct->c_ctinitfnx) (ct);
533             break;
534         }
535
536         if (cp)
537             fseek (in, pos, SEEK_SET);
538         return OK;
539     }
540
541     /*
542      * If we've reached this point, the next line
543      * must be some type of explicit directive.
544      */
545
546     /* check if directive is external-type */
547     extrnal = (buf[1] == '@');
548
549     /* parse directive */
550     if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
551         done (1);
552
553     /* check directive against the list of MIME types */
554     for (s2i = str2cts; s2i->si_key; s2i++)
555         if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
556             break;
557
558     /*
559      * Check if the directive specified a valid type.
560      * This will happen if it was one of the following forms:
561      *
562      *    #type/subtype  (or)
563      *    #@type/subtype
564      */
565     if (s2i->si_key) {
566         if (!ci->ci_subtype)
567             adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
568
569         switch (ct->c_type = s2i->si_val) {
570         case CT_MULTIPART:
571             adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
572                    ci->ci_type, ci->ci_subtype);
573             /* NOTREACHED */
574
575         case CT_MESSAGE:
576             if (!mh_strcasecmp (ci->ci_subtype, "partial"))
577                 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
578                        ci->ci_type, ci->ci_subtype);
579             if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
580                 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
581                        ci->ci_type, ci->ci_subtype);
582 use_forw:
583             adios (NULL,
584                    "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
585                    ci->ci_type, ci->ci_subtype);
586             /* NOTREACHED */
587
588         default:
589             if ((ct->c_ctinitfnx = s2i->si_init))
590                 (*ct->c_ctinitfnx) (ct);
591             break;
592         }
593
594         /*
595          * #@type/subtype (external types directive)
596          */
597         if (extrnal) {
598             struct exbody *e;
599             CT p;
600
601             if (!ci->ci_magic)
602                 adios (NULL, "need external information for \"#@%s/%s\"",
603                        ci->ci_type, ci->ci_subtype);
604             p = ct;
605
606             snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
607             free (ci->ci_magic);
608             ci->ci_magic = NULL;
609
610             /*
611              * Since we are using the current Content structure to
612              * hold information about the type of the external
613              * reference, we need to create another Content structure
614              * for the message/external-body to wrap it in.
615              */
616             if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
617                 adios (NULL, "out of memory");
618             *ctp = ct;
619             ci = &ct->c_ctinfo;
620             if (get_ctinfo (buffer, ct, 0) == NOTOK)
621                 done (1);
622             ct->c_type = CT_MESSAGE;
623             ct->c_subtype = MESSAGE_EXTERNAL;
624
625             if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
626                 adios (NULL, "out of memory");
627             ct->c_ctparams = (void *) e;
628
629             e->eb_parent = ct;
630             e->eb_content = p;
631             p->c_ctexbody = e;
632
633             if (params_external (ct, 1) == NOTOK)
634                 done (1);
635
636             return OK;
637         }
638
639         /* Handle [file] argument */
640         if (ci->ci_magic) {
641             /* check if specifies command to execute */
642             if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
643                 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
644                     continue;
645                 if (!*cp)
646                     adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
647                 cp = add (cp, NULL);
648                 free (ci->ci_magic);
649                 ci->ci_magic = cp;
650             } else {
651                 /* record filename of decoded contents */
652                 ce->ce_file = ci->ci_magic;
653                 if (access (ce->ce_file, R_OK) == NOTOK)
654                     adios ("reading", "unable to access %s for", ce->ce_file);
655                 if (listsw && stat (ce->ce_file, &st) != NOTOK)
656                     ct->c_end = (long) st.st_size;
657                 ci->ci_magic = NULL;
658             }
659             return OK;
660         }
661
662         /*
663          * No [file] argument, so check profile for
664          * method to compose content.
665          */
666         snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
667                 invo_name, ci->ci_type, ci->ci_subtype);
668         if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
669             snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
670             if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
671                 content_error (NULL, ct, "don't know how to compose content");
672                 done (1);
673             }
674         }
675         ci->ci_magic = add (cp, NULL);
676         return OK;
677     }
678
679     if (extrnal)
680         adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
681
682     /*
683      * Message directive
684      * #forw [+folder] [msgs]
685      */
686     if (!mh_strcasecmp (ci->ci_type, "forw")) {
687         int msgnum;
688         char *folder, *arguments[MAXARGS];
689         struct msgs *mp;
690
691         if (ci->ci_magic) {
692             ap = brkstring (ci->ci_magic, " ", "\n");
693             copyip (ap, arguments, MAXARGS);
694         } else {
695             arguments[0] = "cur";
696             arguments[1] = NULL;
697         }
698         folder = NULL;
699
700         /* search the arguments for a folder name */
701         for (ap = arguments; *ap; ap++) {
702             cp = *ap;
703             if (*cp == '+' || *cp == '@') {
704                 if (folder)
705                     adios (NULL, "only one folder per #forw directive");
706                 else
707                     folder = pluspath (cp);
708             }
709         }
710
711         /* else, use the current folder */
712         if (!folder)
713             folder = add (getfolder (1), NULL);
714
715         if (!(mp = folder_read (folder)))
716             adios (NULL, "unable to read folder %s", folder);
717         for (ap = arguments; *ap; ap++) {
718             cp = *ap;
719             if (*cp != '+' && *cp != '@')
720                 if (!m_convert (mp, cp))
721                     done (1);
722         }
723         free (folder);
724         free_ctinfo (ct);
725
726         /*
727          * If there is more than one message to include, make this
728          * a content of type "multipart/digest" and insert each message
729          * as a subpart.  If there is only one message, then make this
730          * a content of type "message/rfc822".
731          */
732         if (mp->numsel > 1) {
733             /* we are forwarding multiple messages */
734             if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
735                 done (1);
736             ct->c_type = CT_MULTIPART;
737             ct->c_subtype = MULTI_DIGEST;
738
739             if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
740                 adios (NULL, "out of memory");
741             ct->c_ctparams = (void *) m;
742             pp = &m->mp_parts;
743
744             for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
745                 if (is_selected(mp, msgnum)) {
746                     struct part *part;
747                     CT p;
748                     CE pe;
749
750                     if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
751                         adios (NULL, "out of memory");
752                     init_decoded_content (p);
753                     pe = p->c_cefile;
754                     if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
755                         done (1);
756                     p->c_type = CT_MESSAGE;
757                     p->c_subtype = MESSAGE_RFC822;
758
759                     snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
760                     pe->ce_file = add (buffer, NULL);
761                     if (listsw && stat (pe->ce_file, &st) != NOTOK)
762                         p->c_end = (long) st.st_size;
763
764                     if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
765                         adios (NULL, "out of memory");
766                     *pp = part;
767                     pp = &part->mp_next;
768                     part->mp_part = p;
769                 }
770             }
771         } else {
772             /* we are forwarding one message */
773             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
774                 done (1);
775             ct->c_type = CT_MESSAGE;
776             ct->c_subtype = MESSAGE_RFC822;
777
778             msgnum = mp->lowsel;
779             snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
780             ce->ce_file = add (buffer, NULL);
781             if (listsw && stat (ce->ce_file, &st) != NOTOK)
782                 ct->c_end = (long) st.st_size;
783         }
784
785         folder_free (mp);       /* free folder/message structure */
786         return OK;
787     }
788
789     /*
790      * #end
791      */
792     if (!mh_strcasecmp (ci->ci_type, "end")) {
793         free_content (ct);
794         *ctp = NULL;
795         return DONE;
796     }
797
798     /*
799      * #begin [ alternative | parallel ]
800      */
801     if (!mh_strcasecmp (ci->ci_type, "begin")) {
802         if (!ci->ci_magic) {
803             vrsn = MULTI_MIXED;
804             cp = SubMultiPart[vrsn - 1].kv_key;
805         } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
806             vrsn = MULTI_ALTERNATE;
807             cp = SubMultiPart[vrsn - 1].kv_key;
808         } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
809             vrsn = MULTI_PARALLEL;
810             cp = SubMultiPart[vrsn - 1].kv_key;
811         } else if (uprf (ci->ci_magic, "digest")) {
812             goto use_forw;
813         } else {
814             vrsn = MULTI_UNKNOWN;
815             cp = ci->ci_magic;
816         }
817
818         free_ctinfo (ct);
819         snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
820         if (get_ctinfo (buffer, ct, 0) == NOTOK)
821             done (1);
822         ct->c_type = CT_MULTIPART;
823         ct->c_subtype = vrsn;
824
825         if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
826             adios (NULL, "out of memory");
827         ct->c_ctparams = (void *) m;
828
829         pp = &m->mp_parts;
830         while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
831             struct part *part;
832             CT p;
833
834             if (user_content (in, file, buffer, &p) == DONE) {
835                 if (!m->mp_parts)
836                     adios (NULL, "empty \"#begin ... #end\" sequence");
837                 return OK;
838             }
839             if (!p)
840                 continue;
841
842             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
843                 adios (NULL, "out of memory");
844             *pp = part;
845             pp = &part->mp_next;
846             part->mp_part = p;
847         }
848         admonish (NULL, "premature end-of-file, missing #end");
849         return OK;
850     }
851
852     /*
853      * Unknown directive
854      */
855     adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
856     return NOTOK;       /* NOT REACHED */
857 }
858
859
860 static void
861 set_id (CT ct, int top)
862 {
863     char msgid[BUFSIZ];
864     static int partno;
865     static time_t clock = 0;
866     static char *msgfmt;
867
868     if (clock == 0) {
869         time (&clock);
870         snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
871                 (int) getpid(), (long) clock, LocalName());
872         partno = 0;
873         msgfmt = getcpy(msgid);
874     }
875     snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
876     ct->c_id = getcpy (msgid);
877 }
878
879
880 static char ebcdicsafe[0x100] = {
881     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
882     0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
883     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
884     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
885     0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
886     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
887     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
888     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
889     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
890     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
891     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
892     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
893     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
894     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
895     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
896     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
897     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
898     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
899     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
900     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
901     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
902     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
903     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
904     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 };
914
915
916 /*
917  * Fill out, or expand the various contents in the composition
918  * draft.  Read-in any necessary files.  Parse and execute any
919  * commands specified by profile composition strings.
920  */
921
922 static int
923 compose_content (CT ct)
924 {
925     CE ce = ct->c_cefile;
926
927     switch (ct->c_type) {
928     case CT_MULTIPART:
929     {
930         int partnum;
931         char *pp;
932         char partnam[BUFSIZ];
933         struct multipart *m = (struct multipart *) ct->c_ctparams;
934         struct part *part;
935
936         if (ct->c_partno) {
937             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
938             pp = partnam + strlen (partnam);
939         } else {
940             pp = partnam;
941         }
942
943         /* first, we call compose_content on all the subparts */
944         for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
945             CT p = part->mp_part;
946
947             sprintf (pp, "%d", partnum);
948             p->c_partno = add (partnam, NULL);
949             if (compose_content (p) == NOTOK)
950                 return NOTOK;
951         }
952
953         /*
954          * If the -rfc934mode switch is given, then check all
955          * the subparts of a multipart/digest.  If they are all
956          * message/rfc822, then mark this content and all
957          * subparts with the rfc934 compatibility mode flag.
958          */
959         if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
960             int is934 = 1;
961
962             for (part = m->mp_parts; part; part = part->mp_next) {
963                 CT p = part->mp_part;
964
965                 if (p->c_subtype != MESSAGE_RFC822) {
966                     is934 = 0;
967                     break;
968                 }
969             }
970             ct->c_rfc934 = is934;
971             for (part = m->mp_parts; part; part = part->mp_next) {
972                 CT p = part->mp_part;
973
974                 if ((p->c_rfc934 = is934))
975                     p->c_end++;
976             }
977         }
978
979         if (listsw) {
980             ct->c_end = (partnum = strlen (prefix) + 2) + 2;
981             if (ct->c_rfc934)
982                 ct->c_end += 1;
983
984             for (part = m->mp_parts; part; part = part->mp_next)
985                 ct->c_end += part->mp_part->c_end + partnum;
986         }
987     }
988     break;
989
990     case CT_MESSAGE:
991         /* Nothing to do for type message */
992         break;
993
994     /*
995      * Discrete types (text/application/audio/image/video)
996      */
997     default:
998         if (!ce->ce_file) {
999             pid_t child_id;
1000             int i, xstdout, len, buflen;
1001             char *bp, **ap, *cp;
1002             char *vec[4], buffer[BUFSIZ];
1003             FILE *out;
1004             CI ci = &ct->c_ctinfo;
1005             char *tfile = NULL;
1006
1007             if (!(cp = ci->ci_magic))
1008                 adios (NULL, "internal error(5)");
1009
1010             tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1011             if (tfile == NULL) {
1012                 adios("mhbuildsbr", "unable to create temporary file");
1013             }
1014             ce->ce_file = add (tfile, 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 = 0, contains8bit = 0;  /* check if contains 8bit data                */
1156     int checklinelen = 0, linelen = 0;    /* check for long lines                       */
1157     int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1158     int checklinespace = 0, linespace = 0;  /* check if any line ends with space          */
1159     int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1160     unsigned char *cp = NULL, buffer[BUFSIZ];
1161     struct text *t = NULL;
1162     FILE *in = NULL;
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 }