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