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