069ea85f2f87dc44d815f8ec5b695530edd1f8c4
[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 i, 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                         for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1123                                 sleep(5);
1124                         switch (child_id) {
1125                         case NOTOK:
1126                                 adios("fork", "unable to fork");
1127                                 /* NOTREACHED */
1128
1129                         case OK:
1130                                 if (!xstdout)
1131                                         dup2(fileno(out), 1);
1132                                 close(fileno(out));
1133                                 execvp("/bin/sh", vec);
1134                                 fprintf(stderr, "unable to exec ");
1135                                 perror("/bin/sh");
1136                                 _exit(-1);
1137                                 /* NOTREACHED */
1138
1139                         default:
1140                                 fclose(out);
1141                                 if (pidXwait(child_id, NULL))
1142                                         done(1);
1143                                 break;
1144                         }
1145                 }
1146
1147                 /* Check size of file */
1148                 if (listsw && ct->c_end == 0L) {
1149                         struct stat st;
1150
1151                         if (stat(ce->ce_file, &st) != NOTOK)
1152                                 ct->c_end = (long) st.st_size;
1153                 }
1154                 break;
1155         }
1156
1157         return OK;
1158 }
1159
1160
1161 /*
1162 ** Scan the content.
1163 **
1164 **    1) choose a transfer encoding.
1165 **    2) check for clashes with multipart boundary string.
1166 **    3) for text content, figure out which character set is being used.
1167 **
1168 ** If there is a clash with one of the contents and the multipart boundary,
1169 ** this function will exit with NOTOK.  This will cause the scanning process
1170 ** to be repeated with a different multipart boundary.  It is possible
1171 ** (although highly unlikely) that this scan will be repeated multiple times.
1172 */
1173
1174 static int
1175 scan_content(CT ct)
1176 {
1177         int len;
1178         int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data */
1179         int checklinelen = 0, linelen = 0;  /* check for long lines */
1180         int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1181         int checklinespace = 0, linespace = 0;  /* check if any line ends with space */
1182         int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1183         unsigned char *cp = NULL, buffer[BUFSIZ];
1184         struct text *t = NULL;
1185         FILE *in = NULL;
1186         CE ce = ct->c_cefile;
1187
1188         /*
1189         ** handle multipart by scanning all subparts
1190         ** and then checking their encoding.
1191         */
1192         if (ct->c_type == CT_MULTIPART) {
1193                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1194                 struct part *part;
1195
1196                 /* initially mark the domain of enclosing multipart as 7bit */
1197                 ct->c_encoding = CE_7BIT;
1198
1199                 for (part = m->mp_parts; part; part = part->mp_next) {
1200                         CT p = part->mp_part;
1201
1202                         if (scan_content(p) == NOTOK) {
1203                                 /* choose encoding for subpart */
1204                                 return NOTOK;
1205                         }
1206
1207                         /*
1208                         ** if necessary, enlarge encoding for enclosing
1209                         ** multipart
1210                         */
1211                         if (p->c_encoding == CE_BINARY)
1212                                 ct->c_encoding = CE_BINARY;
1213                         if (p->c_encoding == CE_8BIT &&
1214                                         ct->c_encoding != CE_BINARY)
1215                                 ct->c_encoding = CE_8BIT;
1216                 }
1217
1218                 return OK;
1219         }
1220
1221         /*
1222         ** Decide what to check while scanning this content.
1223         */
1224         switch (ct->c_type) {
1225         case CT_TEXT:
1226                 check8bit = 1;
1227                 checkboundary = 1;
1228                 if (ct->c_subtype == TEXT_PLAIN) {
1229                         checkebcdic = 0;
1230                         checklinelen = 0;
1231                         checklinespace = 0;
1232                 } else {
1233                         checkebcdic = ebcdicsw;
1234                         checklinelen = 1;
1235                         checklinespace = 1;
1236                 }
1237                 break;
1238
1239         case CT_APPLICATION:
1240                 check8bit = 1;
1241                 checkebcdic = ebcdicsw;
1242                 checklinelen = 1;
1243                 checklinespace = 1;
1244                 checkboundary = 1;
1245                 break;
1246
1247         case CT_MESSAGE:
1248                 check8bit = 0;
1249                 checkebcdic = 0;
1250                 checklinelen = 0;
1251                 checklinespace = 0;
1252
1253                 /* don't check anything for message/external */
1254                 if (ct->c_subtype == MESSAGE_EXTERNAL)
1255                         checkboundary = 0;
1256                 else
1257                         checkboundary = 1;
1258                 break;
1259
1260         case CT_AUDIO:
1261         case CT_IMAGE:
1262         case CT_VIDEO:
1263                 /*
1264                 ** Don't check anything for these types,
1265                 ** since we are forcing use of base64.
1266                 */
1267                 check8bit = 0;
1268                 checkebcdic = 0;
1269                 checklinelen = 0;
1270                 checklinespace = 0;
1271                 checkboundary = 0;
1272                 break;
1273         }
1274
1275         /*
1276         ** Scan the unencoded content
1277         */
1278         if (check8bit || checklinelen || checklinespace || checkboundary) {
1279                 if ((in = fopen(ce->ce_file, "r")) == NULL)
1280                         adios(ce->ce_file, "unable to open for reading");
1281                 len = strlen(prefix);
1282
1283                 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1284                         /*
1285                         ** Check for 8bit data.
1286                         */
1287                         if (check8bit) {
1288                                 for (cp = buffer; *cp; cp++) {
1289                                         if (!isascii(*cp)) {
1290                                                 contains8bit = 1;
1291                                                 /* no need to keep checking */
1292                                                 check8bit = 0;
1293                                         }
1294                                         /*
1295                                         ** Check if character is ebcdic-safe.
1296                                         ** We only check this if also checking
1297                                         ** for 8bit data.
1298                                         */
1299                                         if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1300                                                 ebcdicunsafe = 1;
1301                                                 /* no need to keep checking */
1302                                                 checkebcdic = 0;
1303                                         }
1304                                 }
1305                         }
1306
1307                         /*
1308                         ** Check line length.
1309                         */
1310                         if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1311                                 linelen = 1;
1312                                 checklinelen = 0;  /* no need to keep checking */
1313                         }
1314
1315                         /*
1316                         ** Check if line ends with a space.
1317                         */
1318                         if (checklinespace &&
1319                                         (cp = buffer + strlen(buffer) - 2) >
1320                                         buffer && isspace(*cp)) {
1321                                 linespace = 1;
1322                                 /* no need to keep checking */
1323                                 checklinespace = 0;
1324                         }
1325
1326                         /*
1327                         ** Check if content contains a line that clashes
1328                         ** with our standard boundary for multipart messages.
1329                         */
1330                         if (checkboundary && buffer[0] == '-' &&
1331                                         buffer[1] == '-') {
1332                                 for (cp = buffer + strlen(buffer) - 1;
1333                                                 cp >= buffer; cp--)
1334                                         if (!isspace(*cp))
1335                                                 break;
1336                                 *++cp = '\0';
1337                                 if (strncmp(buffer + 2, prefix, len)==0 &&
1338                                                 isdigit(buffer[2 + len])) {
1339                                         boundaryclash = 1;
1340                                         /* no need to keep checking */
1341                                         checkboundary = 0;
1342                                 }
1343                         }
1344                 }
1345                 fclose(in);
1346         }
1347
1348         /*
1349         ** Decide which transfer encoding to use.
1350         */
1351         switch (ct->c_type) {
1352         case CT_TEXT:
1353                 /*
1354                 ** If the text content didn't specify a character
1355                 ** set, we need to figure out which one was used.
1356                 */
1357                 t = (struct text *) ct->c_ctparams;
1358                 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1359                         CI ci = &ct->c_ctinfo;
1360                         char **ap, **ep;
1361
1362                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1363                                 continue;
1364
1365                         if (contains8bit) {
1366                                 t->tx_charset = CHARSET_UNKNOWN;
1367                                 *ap = concat("charset=", write_charset_8bit(),
1368                                                 NULL);
1369                         } else {
1370                                 t->tx_charset = CHARSET_USASCII;
1371                                 *ap = getcpy("charset=us-ascii");
1372                         }
1373
1374                         cp = strchr(*ap++, '=');
1375                         *ap = NULL;
1376                         *cp++ = '\0';
1377                         *ep = cp;
1378                 }
1379
1380                 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1381                                 checksw)
1382                         ct->c_encoding = CE_QUOTED;
1383                 else
1384                         ct->c_encoding = CE_7BIT;
1385                 break;
1386
1387         case CT_APPLICATION:
1388                 /* For application type, use base64, except when postscript */
1389                 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1390                                 checksw)
1391                         ct->c_encoding = (ct->c_subtype ==
1392                                         APPLICATION_POSTSCRIPT) ?
1393                                         CE_QUOTED : CE_BASE64;
1394                 else
1395                         ct->c_encoding = CE_7BIT;
1396                 break;
1397
1398         case CT_MESSAGE:
1399                 ct->c_encoding = CE_7BIT;
1400                 break;
1401
1402         case CT_AUDIO:
1403         case CT_IMAGE:
1404         case CT_VIDEO:
1405                 /* For audio, image, and video contents, just use base64 */
1406                 ct->c_encoding = CE_BASE64;
1407                 break;
1408         }
1409
1410         return (boundaryclash ? NOTOK : OK);
1411 }
1412
1413
1414 /*
1415 ** Scan the content structures, and build header
1416 ** fields that will need to be output into the
1417 ** message.
1418 */
1419
1420 static int
1421 build_headers(CT ct)
1422 {
1423         int cc, mailbody, len;
1424         char **ap, **ep;
1425         char *np, *vp, buffer[BUFSIZ];
1426         CI ci = &ct->c_ctinfo;
1427
1428         /*
1429         ** If message is type multipart, then add the multipart
1430         ** boundary to the list of attribute/value pairs.
1431         */
1432         if (ct->c_type == CT_MULTIPART) {
1433                 char *cp;
1434                 static int level = 0;  /* store nesting level */
1435
1436                 ap = ci->ci_attrs;
1437                 ep = ci->ci_values;
1438                 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1439                                 prefix, level++);
1440                 cp = strchr(*ap++ = getcpy(buffer), '=');
1441                 *ap = NULL;
1442                 *cp++ = '\0';
1443                 *ep = cp;
1444         }
1445
1446         /*
1447         ** Skip the output of Content-Type, parameters, content
1448         ** description and disposition, and Content-ID if the
1449         ** content is of type "message" and the rfc934 compatibility
1450         ** flag is set (which means we are inside multipart/digest
1451         ** and the switch -rfc934mode was given).
1452         */
1453         if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1454                 goto skip_headers;
1455
1456         /*
1457         ** output the content type and subtype
1458         */
1459         np = getcpy(TYPE_FIELD);
1460         vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1461
1462         /* keep track of length of line */
1463         len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1464                         strlen(ci->ci_subtype) + 3;
1465
1466         mailbody = ct->c_type == CT_MESSAGE &&
1467                         ct->c_subtype == MESSAGE_EXTERNAL &&
1468                         ((struct exbody *) ct->c_ctparams)->eb_body;
1469
1470         /*
1471         ** Append the attribute/value pairs to
1472         ** the end of the Content-Type line.
1473         */
1474         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1475                 if (mailbody && !mh_strcasecmp(*ap, "body"))
1476                         continue;
1477
1478                 vp = add(";", vp);
1479                 len++;
1480
1481                 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1482                 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1483                         vp = add("\n\t", vp);
1484                         len = 8;
1485                 } else {
1486                         vp = add(" ", vp);
1487                         len++;
1488                 }
1489                 vp = add(buffer, vp);
1490                 len += cc;
1491         }
1492
1493         /*
1494         ** Append any RFC-822 comment to the end of
1495         ** the Content-Type line.
1496         */
1497         if (ci->ci_comment) {
1498                 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1499                 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1500                         vp = add("\n\t", vp);
1501                         len = 8;
1502                 } else {
1503                         vp = add(" ", vp);
1504                         len++;
1505                 }
1506                 vp = add(buffer, vp);
1507                 len += cc;
1508         }
1509         vp = add("\n", vp);
1510         add_header(ct, np, vp);
1511
1512         /*
1513         ** output the Content-ID, unless disabled by -nocontentid
1514         */
1515         if (contentidsw && ct->c_id) {
1516                 np = getcpy(ID_FIELD);
1517                 vp = concat(" ", ct->c_id, NULL);
1518                 add_header(ct, np, vp);
1519         }
1520
1521         /*
1522         ** output the Content-Description
1523         */
1524         if (ct->c_descr) {
1525                 np = getcpy(DESCR_FIELD);
1526                 vp = concat(" ", ct->c_descr, NULL);
1527                 add_header(ct, np, vp);
1528         }
1529
1530         /*
1531         ** output the Content-Disposition
1532         */
1533         if (ct->c_dispo) {
1534                 np = getcpy(DISPO_FIELD);
1535                 vp = concat(" ", ct->c_dispo, NULL);
1536                 add_header(ct, np, vp);
1537         }
1538
1539 skip_headers:
1540         /*
1541         ** If this is the internal content structure for a
1542         ** "message/external", then we are done with the
1543         ** headers (since it has no body).
1544         */
1545         if (ct->c_ctexbody)
1546                 return OK;
1547
1548         /*
1549         ** output the Content-MD5
1550         */
1551         if (checksw) {
1552                 np = getcpy(MD5_FIELD);
1553                 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1554                                 1 : 0);
1555                 add_header(ct, np, vp);
1556         }
1557
1558         /*
1559         ** output the Content-Transfer-Encoding
1560         */
1561         switch (ct->c_encoding) {
1562         case CE_7BIT:
1563                 /* Nothing to output */
1564                 break;
1565
1566         case CE_8BIT:
1567                 if (ct->c_type == CT_MESSAGE)
1568                         adios(NULL, "internal error, invalid encoding");
1569
1570                 np = getcpy(ENCODING_FIELD);
1571                 vp = concat(" ", "8bit", "\n", NULL);
1572                 add_header(ct, np, vp);
1573                 break;
1574
1575         case CE_QUOTED:
1576                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1577                         adios(NULL, "internal error, invalid encoding");
1578
1579                 np = getcpy(ENCODING_FIELD);
1580                 vp = concat(" ", "quoted-printable", "\n", NULL);
1581                 add_header(ct, np, vp);
1582                 break;
1583
1584         case CE_BASE64:
1585                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1586                         adios(NULL, "internal error, invalid encoding");
1587
1588                 np = getcpy(ENCODING_FIELD);
1589                 vp = concat(" ", "base64", "\n", NULL);
1590                 add_header(ct, np, vp);
1591                 break;
1592
1593         case CE_BINARY:
1594                 if (ct->c_type == CT_MESSAGE)
1595                         adios(NULL, "internal error, invalid encoding");
1596
1597                 np = getcpy(ENCODING_FIELD);
1598                 vp = concat(" ", "binary", "\n", NULL);
1599                 add_header(ct, np, vp);
1600                 break;
1601
1602         default:
1603                 adios(NULL, "unknown transfer encoding in content");
1604                 break;
1605         }
1606
1607         /*
1608         ** Additional content specific header processing
1609         */
1610         switch (ct->c_type) {
1611         case CT_MULTIPART:
1612         {
1613                 struct multipart *m;
1614                 struct part *part;
1615
1616                 m = (struct multipart *) ct->c_ctparams;
1617                 for (part = m->mp_parts; part; part = part->mp_next) {
1618                         CT p;
1619
1620                         p = part->mp_part;
1621                         build_headers(p);
1622                 }
1623         }
1624                 break;
1625
1626         case CT_MESSAGE:
1627                 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1628                         struct exbody *e;
1629
1630                         e = (struct exbody *) ct->c_ctparams;
1631                         build_headers(e->eb_content);
1632                 }
1633                 break;
1634
1635         default:
1636                 /* Nothing to do */
1637                 break;
1638         }
1639
1640         return OK;
1641 }
1642
1643
1644 static char nib2b64[0x40+1] =
1645         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1646
1647 static char *
1648 calculate_digest(CT ct, int asciiP)
1649 {
1650         int cc;
1651         char buffer[BUFSIZ], *vp, *op;
1652         unsigned char *dp;
1653         unsigned char digest[16];
1654         unsigned char outbuf[25];
1655         FILE *in;
1656         MD5_CTX mdContext;
1657         CE ce = ct->c_cefile;
1658
1659         /* open content */
1660         if ((in = fopen(ce->ce_file, "r")) == NULL)
1661                 adios(ce->ce_file, "unable to open for reading");
1662
1663         /* Initialize md5 context */
1664         MD5Init(&mdContext);
1665
1666         /* calculate md5 message digest */
1667         if (asciiP) {
1668                 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1669                         char c, *cp;
1670
1671                         cp = buffer + strlen(buffer) - 1;
1672                         if ((c = *cp) == '\n')
1673                                 *cp = '\0';
1674
1675                         MD5Update(&mdContext, (unsigned char *) buffer,
1676                                         (unsigned int) strlen(buffer));
1677
1678                         if (c == '\n')
1679                                 MD5Update(&mdContext, (unsigned char *) "\r\n",
1680                                                  2);
1681                 }
1682         } else {
1683                 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
1684                                 in)) > 0)
1685                         MD5Update(&mdContext, (unsigned char *) buffer,
1686                                         (unsigned int) cc);
1687         }
1688
1689         /* md5 finalization.  Write digest and zero md5 context */
1690         MD5Final(digest, &mdContext);
1691
1692         /* close content */
1693         fclose(in);
1694
1695         /* print debugging info */
1696         if (debugsw) {
1697                 unsigned char *ep;
1698
1699                 fprintf(stderr, "MD5 digest=");
1700                 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1701                         dp < ep; dp++)
1702                         fprintf(stderr, "%02x", *dp & 0xff);
1703                 fprintf(stderr, "\n");
1704         }
1705
1706         /* encode the digest using base64 */
1707         for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1708                 cc > 0; cc -= 3, op += 4) {
1709                 unsigned long bits;
1710                 char *bp;
1711
1712                 bits = (*dp++ & 0xff) << 16;
1713                 if (cc > 1) {
1714                         bits |= (*dp++ & 0xff) << 8;
1715                         if (cc > 2)
1716                                 bits |= *dp++ & 0xff;
1717                 }
1718
1719                 for (bp = op + 4; bp > op; bits >>= 6)
1720                         *--bp = nib2b64[bits & 0x3f];
1721                 if (cc < 3) {
1722                         *(op + 3) = '=';
1723                         if (cc < 2)
1724                                 *(op + 2) = '=';
1725                 }
1726         }
1727
1728         /* null terminate string */
1729         outbuf[24] = '\0';
1730
1731         /* now make copy and return string */
1732         vp = concat(" ", outbuf, "\n", NULL);
1733         return vp;
1734 }