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