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