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