Reformated comments and long lines
[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 = add (name, NULL);
175                         vp = add (buf, NULL);
176
177                         /* if necessary, get rest of field */
178                         while (state == FLDPLUS) {
179                                 state = m_getfld (state, name, buf, 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 = add (VRSN_FIELD, NULL);
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 = add (infile, NULL);
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 = add (cp, NULL);
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)) == NULL)
497                                 break;
498                         if (buffer[0] == '#') {
499                                 char *bp;
500
501                                 if (buffer[1] != '#')
502                                         break;
503                                 for (cp = (bp = buffer) + 1; *cp; cp++)
504                                         *bp++ = *cp;
505                                 *bp = '\0';
506                         }
507                 }
508
509                 if (listsw)
510                         ct->c_end = ftell (out);
511                 fclose (out);
512
513                 /* parse content type */
514                 if (get_ctinfo (content, ct, inlineD) == NOTOK)
515                         done (1);
516
517                 for (s2i = str2cts; s2i->si_key; s2i++)
518                         if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
519                                 break;
520                 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
521                         s2i++;
522
523                 /*
524                 ** check type specified (possibly implicitly)
525                 */
526                 switch (ct->c_type = s2i->si_val) {
527                 case CT_MESSAGE:
528                         if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
529                                 ct->c_encoding = CE_7BIT;
530                                 goto call_init;
531                         }
532                         /* else fall... */
533                 case CT_MULTIPART:
534                         adios (NULL, "it doesn't make sense to define an in-line %s content",
535                                         ct->c_type == CT_MESSAGE ? "message" :
536                                         "multipart");
537                         /* NOTREACHED */
538
539                 default:
540 call_init:
541                         if ((ct->c_ctinitfnx = s2i->si_init))
542                                 (*ct->c_ctinitfnx) (ct);
543                         break;
544                 }
545
546                 if (cp)
547                         fseek (in, pos, SEEK_SET);
548                 return OK;
549         }
550
551         /*
552         ** If we've reached this point, the next line
553         ** must be some type of explicit directive.
554         */
555
556         /* check if directive is external-type */
557         extrnal = (buf[1] == '@');
558
559         /* parse directive */
560         if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
561                 done (1);
562
563         /* check directive against the list of MIME types */
564         for (s2i = str2cts; s2i->si_key; s2i++)
565                 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
566                         break;
567
568         /*
569         ** Check if the directive specified a valid type.
570         ** This will happen if it was one of the following forms:
571         **
572         **    #type/subtype  (or)
573         **    #@type/subtype
574         */
575         if (s2i->si_key) {
576                 if (!ci->ci_subtype)
577                         adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
578
579                 switch (ct->c_type = s2i->si_val) {
580                 case CT_MULTIPART:
581                         adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
582                                    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",
588                                            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\"",
591                                            ci->ci_type, ci->ci_subtype);
592 use_forw:
593                         adios (NULL,
594                                    "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
595                                    ci->ci_type, ci->ci_subtype);
596                         /* NOTREACHED */
597
598                 default:
599                         if ((ct->c_ctinitfnx = s2i->si_init))
600                                 (*ct->c_ctinitfnx) (ct);
601                         break;
602                 }
603
604                 /*
605                 ** #@type/subtype (external types directive)
606                 */
607                 if (extrnal) {
608                         struct exbody *e;
609                         CT p;
610
611                         if (!ci->ci_magic)
612                                 adios (NULL, "need external information for \"#@%s/%s\"",
613                                            ci->ci_type, ci->ci_subtype);
614                         p = ct;
615
616                         snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
617                         free (ci->ci_magic);
618                         ci->ci_magic = NULL;
619
620                         /*
621                         ** Since we are using the current Content structure to
622                         ** hold information about the type of the external
623                         ** reference, we need to create another Content
624                         ** structure for the message/external-body to wrap
625                         ** it in.
626                         */
627                         if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
628                                 adios (NULL, "out of memory");
629                         *ctp = ct;
630                         ci = &ct->c_ctinfo;
631                         if (get_ctinfo (buffer, ct, 0) == NOTOK)
632                                 done (1);
633                         ct->c_type = CT_MESSAGE;
634                         ct->c_subtype = MESSAGE_EXTERNAL;
635
636                         if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
637                                 adios (NULL, "out of memory");
638                         ct->c_ctparams = (void *) e;
639
640                         e->eb_parent = ct;
641                         e->eb_content = p;
642                         p->c_ctexbody = e;
643
644                         if (params_external (ct, 1) == NOTOK)
645                                 done (1);
646
647                         return OK;
648                 }
649
650                 /* Handle [file] argument */
651                 if (ci->ci_magic) {
652                         /* check if specifies command to execute */
653                         if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
654                                 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
655                                         continue;
656                                 if (!*cp)
657                                         adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
658                                 cp = add (cp, NULL);
659                                 free (ci->ci_magic);
660                                 ci->ci_magic = cp;
661                         } else {
662                                 /* record filename of decoded contents */
663                                 ce->ce_file = ci->ci_magic;
664                                 if (access (ce->ce_file, R_OK) == NOTOK)
665                                         adios ("reading", "unable to access %s for", ce->ce_file);
666                                 if (listsw && stat (ce->ce_file, &st) != NOTOK)
667                                         ct->c_end = (long) st.st_size;
668                                 ci->ci_magic = NULL;
669                         }
670                         return OK;
671                 }
672
673                 /*
674                 ** No [file] argument, so check profile for
675                 ** method to compose content.
676                 */
677                 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
678                                 invo_name, ci->ci_type, ci->ci_subtype);
679                 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
680                         snprintf (buffer, sizeof(buffer), "%s-compose-%s",
681                                         invo_name, ci->ci_type);
682                         if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
683                                 content_error (NULL, ct, "don't know how to compose content");
684                                 done (1);
685                         }
686                 }
687                 ci->ci_magic = add (cp, NULL);
688                 return OK;
689         }
690
691         if (extrnal)
692                 adios (NULL, "external definition not allowed for \"#%s\"",
693                                 ci->ci_type);
694
695         /*
696         ** Message directive
697         ** #forw [+folder] [msgs]
698         */
699         if (!mh_strcasecmp (ci->ci_type, "forw")) {
700                 int msgnum;
701                 char *folder, *arguments[MAXARGS];
702                 struct msgs *mp;
703
704                 if (ci->ci_magic) {
705                         ap = brkstring (ci->ci_magic, " ", "\n");
706                         copyip (ap, arguments, MAXARGS);
707                 } else {
708                         arguments[0] = "cur";
709                         arguments[1] = NULL;
710                 }
711                 folder = NULL;
712
713                 /* search the arguments for a folder name */
714                 for (ap = arguments; *ap; ap++) {
715                         cp = *ap;
716                         if (*cp == '+' || *cp == '@') {
717                                 if (folder)
718                                         adios (NULL, "only one folder per #forw directive");
719                                 else
720                                         folder = pluspath (cp);
721                         }
722                 }
723
724                 /* else, use the current folder */
725                 if (!folder)
726                         folder = add (getfolder (1), NULL);
727
728                 if (!(mp = folder_read (folder)))
729                         adios (NULL, "unable to read folder %s", folder);
730                 for (ap = arguments; *ap; ap++) {
731                         cp = *ap;
732                         if (*cp != '+' && *cp != '@')
733                                 if (!m_convert (mp, cp))
734                                         done (1);
735                 }
736                 free (folder);
737                 free_ctinfo (ct);
738
739                 /*
740                 ** If there is more than one message to include, make this
741                 ** a content of type "multipart/digest" and insert each message
742                 ** as a subpart.  If there is only one message, then make this
743                 ** a content of type "message/rfc822".
744                 */
745                 if (mp->numsel > 1) {
746                         /* we are forwarding multiple messages */
747                         if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
748                                 done (1);
749                         ct->c_type = CT_MULTIPART;
750                         ct->c_subtype = MULTI_DIGEST;
751
752                         if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
753                                 adios (NULL, "out of memory");
754                         ct->c_ctparams = (void *) m;
755                         pp = &m->mp_parts;
756
757                         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
758                                 if (is_selected(mp, msgnum)) {
759                                         struct part *part;
760                                         CT p;
761                                         CE pe;
762
763                                         if ((p = (CT) calloc (1, sizeof(*p))) == 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) == NOTOK)
768                                                 done (1);
769                                         p->c_type = CT_MESSAGE;
770                                         p->c_subtype = MESSAGE_RFC822;
771
772                                         snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
773                                         pe->ce_file = add (buffer, NULL);
774                                         if (listsw && stat (pe->ce_file, &st) != NOTOK)
775                                                 p->c_end = (long) st.st_size;
776
777                                         if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
778                                                 adios (NULL, "out of memory");
779                                         *pp = part;
780                                         pp = &part->mp_next;
781                                         part->mp_part = p;
782                                 }
783                         }
784                 } else {
785                         /* we are forwarding one message */
786                         if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
787                                 done (1);
788                         ct->c_type = CT_MESSAGE;
789                         ct->c_subtype = MESSAGE_RFC822;
790
791                         msgnum = mp->lowsel;
792                         snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
793                         ce->ce_file = add (buffer, NULL);
794                         if (listsw && stat (ce->ce_file, &st) != NOTOK)
795                                 ct->c_end = (long) st.st_size;
796                 }
797
798                 folder_free (mp);  /* free folder/message structure */
799                 return OK;
800         }
801
802         /*
803         ** #end
804         */
805         if (!mh_strcasecmp (ci->ci_type, "end")) {
806                 free_content (ct);
807                 *ctp = NULL;
808                 return DONE;
809         }
810
811         /*
812         ** #begin [ alternative | parallel ]
813         */
814         if (!mh_strcasecmp (ci->ci_type, "begin")) {
815                 if (!ci->ci_magic) {
816                         vrsn = MULTI_MIXED;
817                         cp = SubMultiPart[vrsn - 1].kv_key;
818                 } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
819                         vrsn = MULTI_ALTERNATE;
820                         cp = SubMultiPart[vrsn - 1].kv_key;
821                 } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
822                         vrsn = MULTI_PARALLEL;
823                         cp = SubMultiPart[vrsn - 1].kv_key;
824                 } else if (uprf (ci->ci_magic, "digest")) {
825                         goto use_forw;
826                 } else {
827                         vrsn = MULTI_UNKNOWN;
828                         cp = ci->ci_magic;
829                 }
830
831                 free_ctinfo (ct);
832                 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
833                 if (get_ctinfo (buffer, ct, 0) == NOTOK)
834                         done (1);
835                 ct->c_type = CT_MULTIPART;
836                 ct->c_subtype = vrsn;
837
838                 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
839                         adios (NULL, "out of memory");
840                 ct->c_ctparams = (void *) m;
841
842                 pp = &m->mp_parts;
843                 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
844                         struct part *part;
845                         CT p;
846
847                         if (user_content (in, file, buffer, &p) == DONE) {
848                                 if (!m->mp_parts)
849                                         adios (NULL, "empty \"#begin ... #end\" sequence");
850                                 return OK;
851                         }
852                         if (!p)
853                                 continue;
854
855                         if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
856                                 adios (NULL, "out of memory");
857                         *pp = part;
858                         pp = &part->mp_next;
859                         part->mp_part = p;
860                 }
861                 admonish (NULL, "premature end-of-file, missing #end");
862                 return OK;
863         }
864
865         /*
866         ** Unknown directive
867         */
868         adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
869         return NOTOK;  /* NOT REACHED */
870 }
871
872
873 static void
874 set_id (CT ct, int top)
875 {
876         char msgid[BUFSIZ];
877         static int partno;
878         static time_t clock = 0;
879         static char *msgfmt;
880
881         if (clock == 0) {
882                 time (&clock);
883                 snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
884                                 (int) getpid(), (long) clock, LocalName());
885                 partno = 0;
886                 msgfmt = getcpy(msgid);
887         }
888         snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
889         ct->c_id = getcpy (msgid);
890 }
891
892
893 static char ebcdicsafe[0x100] = {
894         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
895         0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
896         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
897         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
898         0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
899         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
900         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
901         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
902         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
903         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
904         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
905         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
906         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
907         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
908         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
909         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
910         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
911         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
912         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
913         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
914         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
915         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
916         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
917         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 };
927
928
929 /*
930 ** Fill out, or expand the various contents in the composition
931 ** draft.  Read-in any necessary files.  Parse and execute any
932 ** commands specified by profile composition strings.
933 */
934
935 static int
936 compose_content (CT ct)
937 {
938         CE ce = ct->c_cefile;
939
940         switch (ct->c_type) {
941         case CT_MULTIPART:
942         {
943                 int partnum;
944                 char *pp;
945                 char partnam[BUFSIZ];
946                 struct multipart *m = (struct multipart *) ct->c_ctparams;
947                 struct part *part;
948
949                 if (ct->c_partno) {
950                         snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
951                         pp = partnam + strlen (partnam);
952                 } else {
953                         pp = partnam;
954                 }
955
956                 /* first, we call compose_content on all the subparts */
957                 for (part = m->mp_parts, partnum = 1; part;
958                                 part = part->mp_next, partnum++) {
959                         CT p = part->mp_part;
960
961                         sprintf (pp, "%d", partnum);
962                         p->c_partno = add (partnam, NULL);
963                         if (compose_content (p) == NOTOK)
964                                 return NOTOK;
965                 }
966
967                 /*
968                 ** If the -rfc934mode switch is given, then check all
969                 ** the subparts of a multipart/digest.  If they are all
970                 ** message/rfc822, then mark this content and all
971                 ** subparts with the rfc934 compatibility mode flag.
972                 */
973                 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
974                         int is934 = 1;
975
976                         for (part = m->mp_parts; part; part = part->mp_next) {
977                                 CT p = part->mp_part;
978
979                                 if (p->c_subtype != MESSAGE_RFC822) {
980                                         is934 = 0;
981                                         break;
982                                 }
983                         }
984                         ct->c_rfc934 = is934;
985                         for (part = m->mp_parts; part; part = part->mp_next) {
986                                 CT p = part->mp_part;
987
988                                 if ((p->c_rfc934 = is934))
989                                         p->c_end++;
990                         }
991                 }
992
993                 if (listsw) {
994                         ct->c_end = (partnum = strlen (prefix) + 2) + 2;
995                         if (ct->c_rfc934)
996                                 ct->c_end += 1;
997
998                         for (part = m->mp_parts; part; part = part->mp_next)
999                                 ct->c_end += part->mp_part->c_end + partnum;
1000                 }
1001         }
1002         break;
1003
1004         case CT_MESSAGE:
1005                 /* Nothing to do for type message */
1006                 break;
1007
1008         /*
1009         ** Discrete types (text/application/audio/image/video)
1010         */
1011         default:
1012                 if (!ce->ce_file) {
1013                         pid_t child_id;
1014                         int i, xstdout, len, buflen;
1015                         char *bp, **ap, *cp;
1016                         char *vec[4], buffer[BUFSIZ];
1017                         FILE *out;
1018                         CI ci = &ct->c_ctinfo;
1019                         char *tfile = NULL;
1020
1021                         if (!(cp = ci->ci_magic))
1022                                 adios (NULL, "internal error(5)");
1023
1024                         tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1025                         if (tfile == NULL) {
1026                                 adios("mhbuildsbr", "unable to create temporary file");
1027                         }
1028                         ce->ce_file = add (tfile, NULL);
1029                         ce->ce_unlink = 1;
1030
1031                         xstdout = 0;
1032
1033                         /* Get buffer ready to go */
1034                         bp = buffer;
1035                         bp[0] = '\0';
1036                         buflen = sizeof(buffer);
1037
1038                         /*
1039                         ** Parse composition string into buffer
1040                         */
1041                         for ( ; *cp; cp++) {
1042                                 if (*cp == '%') {
1043                                         switch (*++cp) {
1044                                         case 'a':
1045                                         {
1046                                                 /*
1047                                                 ** insert parameters from
1048                                                 ** directive
1049                                                 */
1050                                                 char **ep;
1051                                                 char *s = "";
1052
1053                                                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1054                                                         snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1055                                                         len = strlen (bp);
1056                                                         bp += len;
1057                                                         buflen -= len;
1058                                                         s = " ";
1059                                                 }
1060                                         }
1061                                         break;
1062
1063                                         case 'F':
1064                                                 /* %f, and stdout is not-redirected */
1065                                                 xstdout = 1;
1066                                                 /* and fall... */
1067
1068                                         case 'f':
1069                                                 /*
1070                                                 ** insert temporary filename
1071                                                 ** where content should be
1072                                                 ** written
1073                                                 */
1074                                                 snprintf (bp, buflen, "%s", ce->ce_file);
1075                                                 break;
1076
1077                                         case 's':
1078                                                 /* insert content subtype */
1079                                                 strncpy (bp, ci->ci_subtype, buflen);
1080                                                 break;
1081
1082                                         case '%':
1083                                                 /* insert character % */
1084                                                 goto raw;
1085
1086                                         default:
1087                                                 *bp++ = *--cp;
1088                                                 *bp = '\0';
1089                                                 buflen--;
1090                                                 continue;
1091                                         }
1092                                         len = strlen (bp);
1093                                         bp += len;
1094                                         buflen -= len;
1095                                 } else {
1096 raw:
1097                                         *bp++ = *cp;
1098                                         *bp = '\0';
1099                                         buflen--;
1100                                 }
1101                         }
1102
1103                         if (verbosw)
1104                                 printf ("composing content %s/%s from command\n\t%s\n",
1105                                                 ci->ci_type, ci->ci_subtype, buffer);
1106
1107                         fflush (stdout);  /* not sure if need for -noverbose */
1108
1109                         vec[0] = "/bin/sh";
1110                         vec[1] = "-c";
1111                         vec[2] = buffer;
1112                         vec[3] = NULL;
1113
1114                         if ((out = fopen (ce->ce_file, "w")) == NULL)
1115                                 adios (ce->ce_file, "unable to open for writing");
1116
1117                         for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1118                                 sleep (5);
1119                         switch (child_id) {
1120                         case NOTOK:
1121                                 adios ("fork", "unable to fork");
1122                                 /* NOTREACHED */
1123
1124                         case OK:
1125                                 if (!xstdout)
1126                                         dup2 (fileno (out), 1);
1127                                 close (fileno (out));
1128                                 execvp ("/bin/sh", vec);
1129                                 fprintf (stderr, "unable to exec ");
1130                                 perror ("/bin/sh");
1131                                 _exit (-1);
1132                                 /* NOTREACHED */
1133
1134                         default:
1135                                 fclose (out);
1136                                 if (pidXwait(child_id, NULL))
1137                                         done (1);
1138                                 break;
1139                         }
1140                 }
1141
1142                 /* Check size of file */
1143                 if (listsw && ct->c_end == 0L) {
1144                         struct stat st;
1145
1146                         if (stat (ce->ce_file, &st) != NOTOK)
1147                                 ct->c_end = (long) st.st_size;
1148                 }
1149                 break;
1150         }
1151
1152         return OK;
1153 }
1154
1155
1156 /*
1157 ** Scan the content.
1158 **
1159 **    1) choose a transfer encoding.
1160 **    2) check for clashes with multipart boundary string.
1161 **    3) for text content, figure out which character set is being used.
1162 **
1163 ** If there is a clash with one of the contents and the multipart boundary,
1164 ** this function will exit with NOTOK.  This will cause the scanning process
1165 ** to be repeated with a different multipart boundary.  It is possible
1166 ** (although highly unlikely) that this scan will be repeated multiple times.
1167 */
1168
1169 static int
1170 scan_content (CT ct)
1171 {
1172         int len;
1173         int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data */
1174         int checklinelen = 0, linelen = 0;  /* check for long lines */
1175         int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1176         int checklinespace = 0, linespace = 0;  /* check if any line ends with space */
1177         int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1178         unsigned char *cp = NULL, buffer[BUFSIZ];
1179         struct text *t = NULL;
1180         FILE *in = NULL;
1181         CE ce = ct->c_cefile;
1182
1183         /*
1184         ** handle multipart by scanning all subparts
1185         ** and then checking their encoding.
1186         */
1187         if (ct->c_type == CT_MULTIPART) {
1188                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1189                 struct part *part;
1190
1191                 /* initially mark the domain of enclosing multipart as 7bit */
1192                 ct->c_encoding = CE_7BIT;
1193
1194                 for (part = m->mp_parts; part; part = part->mp_next) {
1195                         CT p = part->mp_part;
1196
1197                         if (scan_content (p) == NOTOK)  /* choose encoding for subpart */
1198                                 return NOTOK;
1199
1200                         /*
1201                         ** if necessary, enlarge encoding for enclosing
1202                         ** multipart
1203                         */
1204                         if (p->c_encoding == CE_BINARY)
1205                                 ct->c_encoding = CE_BINARY;
1206                         if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1207                                 ct->c_encoding = CE_8BIT;
1208                 }
1209
1210                 return OK;
1211         }
1212
1213         /*
1214         ** Decide what to check while scanning this content.
1215         */
1216         switch (ct->c_type) {
1217         case CT_TEXT:
1218                 check8bit = 1;
1219                 checkboundary = 1;
1220                 if (ct->c_subtype == TEXT_PLAIN) {
1221                         checkebcdic = 0;
1222                         checklinelen = 0;
1223                         checklinespace = 0;
1224                 } else {
1225                         checkebcdic = ebcdicsw;
1226                         checklinelen = 1;
1227                         checklinespace = 1;
1228                 }
1229                 break;
1230
1231         case CT_APPLICATION:
1232                 check8bit = 1;
1233                 checkebcdic = ebcdicsw;
1234                 checklinelen = 1;
1235                 checklinespace = 1;
1236                 checkboundary = 1;
1237                 break;
1238
1239         case CT_MESSAGE:
1240                 check8bit = 0;
1241                 checkebcdic = 0;
1242                 checklinelen = 0;
1243                 checklinespace = 0;
1244
1245                 /* don't check anything for message/external */
1246                 if (ct->c_subtype == MESSAGE_EXTERNAL)
1247                         checkboundary = 0;
1248                 else
1249                         checkboundary = 1;
1250                 break;
1251
1252         case CT_AUDIO:
1253         case CT_IMAGE:
1254         case CT_VIDEO:
1255                 /*
1256                 ** Don't check anything for these types,
1257                 ** since we are forcing use of base64.
1258                 */
1259                 check8bit = 0;
1260                 checkebcdic = 0;
1261                 checklinelen = 0;
1262                 checklinespace = 0;
1263                 checkboundary = 0;
1264                 break;
1265         }
1266
1267         /*
1268         ** Scan the unencoded content
1269         */
1270         if (check8bit || checklinelen || checklinespace || checkboundary) {
1271                 if ((in = fopen (ce->ce_file, "r")) == NULL)
1272                         adios (ce->ce_file, "unable to open for reading");
1273                 len = strlen (prefix);
1274
1275                 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1276                         /*
1277                         ** Check for 8bit data.
1278                         */
1279                         if (check8bit) {
1280                                 for (cp = buffer; *cp; cp++) {
1281                                         if (!isascii (*cp)) {
1282                                                 contains8bit = 1;
1283                                                 /* no need to keep checking */
1284                                                 check8bit = 0;
1285                                         }
1286                                         /*
1287                                         ** Check if character is ebcdic-safe.
1288                                         ** We only check this if also checking
1289                                         ** for 8bit data.
1290                                         */
1291                                         if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1292                                                 ebcdicunsafe = 1;
1293                                                 /* no need to keep checking */
1294                                                 checkebcdic = 0;
1295                                         }
1296                                 }
1297                         }
1298
1299                         /*
1300                         ** Check line length.
1301                         */
1302                         if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1303                                 linelen = 1;
1304                                 checklinelen = 0;  /* no need to keep checking */
1305                         }
1306
1307                         /*
1308                         ** Check if line ends with a space.
1309                         */
1310                         if (checklinespace &&
1311                                         (cp = buffer + strlen (buffer) - 2) >
1312                                         buffer && isspace (*cp)) {
1313                                 linespace = 1;
1314                                 /* no need to keep checking */
1315                                 checklinespace = 0;
1316                         }
1317
1318                         /*
1319                         ** Check if content contains a line that clashes
1320                         ** with our standard boundary for multipart messages.
1321                         */
1322                         if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1323                                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1324                                         if (!isspace (*cp))
1325                                                 break;
1326                                 *++cp = '\0';
1327                                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1328                                         boundaryclash = 1;
1329                                         /* no need to keep checking */
1330                                         checkboundary = 0;
1331                                 }
1332                         }
1333                 }
1334                 fclose (in);
1335         }
1336
1337         /*
1338         ** Decide which transfer encoding to use.
1339         */
1340         switch (ct->c_type) {
1341         case CT_TEXT:
1342                 /*
1343                 ** If the text content didn't specify a character
1344                 ** set, we need to figure out which one was used.
1345                 */
1346                 t = (struct text *) ct->c_ctparams;
1347                 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1348                         CI ci = &ct->c_ctinfo;
1349                         char **ap, **ep;
1350
1351                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1352                                 continue;
1353
1354                         if (contains8bit) {
1355                                 t->tx_charset = CHARSET_UNKNOWN;
1356                                 *ap = concat ("charset=", write_charset_8bit(), NULL);
1357                         } else {
1358                                 t->tx_charset = CHARSET_USASCII;
1359                                 *ap = add ("charset=us-ascii", NULL);
1360                         }
1361
1362                         cp = strchr(*ap++, '=');
1363                         *ap = NULL;
1364                         *cp++ = '\0';
1365                         *ep = cp;
1366                 }
1367
1368                 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1369                         ct->c_encoding = CE_QUOTED;
1370                 else
1371                         ct->c_encoding = CE_7BIT;
1372                 break;
1373
1374         case CT_APPLICATION:
1375                 /* For application type, use base64, except when postscript */
1376                 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1377                         ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1378                                 ? CE_QUOTED : CE_BASE64;
1379                 else
1380                         ct->c_encoding = CE_7BIT;
1381                 break;
1382
1383         case CT_MESSAGE:
1384                 ct->c_encoding = CE_7BIT;
1385                 break;
1386
1387         case CT_AUDIO:
1388         case CT_IMAGE:
1389         case CT_VIDEO:
1390                 /* For audio, image, and video contents, just use base64 */
1391                 ct->c_encoding = CE_BASE64;
1392                 break;
1393         }
1394
1395         return (boundaryclash ? NOTOK : OK);
1396 }
1397
1398
1399 /*
1400 ** Scan the content structures, and build header
1401 ** fields that will need to be output into the
1402 ** message.
1403 */
1404
1405 static int
1406 build_headers (CT ct)
1407 {
1408         int cc, mailbody, len;
1409         char **ap, **ep;
1410         char *np, *vp, buffer[BUFSIZ];
1411         CI ci = &ct->c_ctinfo;
1412
1413         /*
1414         ** If message is type multipart, then add the multipart
1415         ** boundary to the list of attribute/value pairs.
1416         */
1417         if (ct->c_type == CT_MULTIPART) {
1418                 char *cp;
1419                 static int level = 0;  /* store nesting level */
1420
1421                 ap = ci->ci_attrs;
1422                 ep = ci->ci_values;
1423                 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1424                 cp = strchr(*ap++ = add (buffer, NULL), '=');
1425                 *ap = NULL;
1426                 *cp++ = '\0';
1427                 *ep = cp;
1428         }
1429
1430         /*
1431         ** Skip the output of Content-Type, parameters, content
1432         ** description and disposition, and Content-ID if the
1433         ** content is of type "message" and the rfc934 compatibility
1434         ** flag is set (which means we are inside multipart/digest
1435         ** and the switch -rfc934mode was given).
1436         */
1437         if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1438                 goto skip_headers;
1439
1440         /*
1441         ** output the content type and subtype
1442         */
1443         np = add (TYPE_FIELD, NULL);
1444         vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1445
1446         /* keep track of length of line */
1447         len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1448                 + strlen (ci->ci_subtype) + 3;
1449
1450         mailbody = ct->c_type == CT_MESSAGE
1451                 && ct->c_subtype == MESSAGE_EXTERNAL
1452                 && ((struct exbody *) ct->c_ctparams)->eb_body;
1453
1454         /*
1455         ** Append the attribute/value pairs to
1456         ** the end of the Content-Type line.
1457         */
1458         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1459                 if (mailbody && !mh_strcasecmp (*ap, "body"))
1460                         continue;
1461
1462                 vp = add (";", vp);
1463                 len++;
1464
1465                 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1466                 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1467                         vp = add ("\n\t", vp);
1468                         len = 8;
1469                 } else {
1470                         vp = add (" ", vp);
1471                         len++;
1472                 }
1473                 vp = add (buffer, vp);
1474                 len += cc;
1475         }
1476
1477         /*
1478         ** Append any RFC-822 comment to the end of
1479         ** the Content-Type line.
1480         */
1481         if (ci->ci_comment) {
1482                 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1483                 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= 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         vp = add ("\n", vp);
1494         add_header (ct, np, vp);
1495
1496         /*
1497         ** output the Content-ID, unless disabled by -nocontentid
1498         */
1499         if (contentidsw && ct->c_id) {
1500                 np = add (ID_FIELD, NULL);
1501                 vp = concat (" ", ct->c_id, NULL);
1502                 add_header (ct, np, vp);
1503         }
1504
1505         /*
1506         ** output the Content-Description
1507         */
1508         if (ct->c_descr) {
1509                 np = add (DESCR_FIELD, NULL);
1510                 vp = concat (" ", ct->c_descr, NULL);
1511                 add_header (ct, np, vp);
1512         }
1513
1514         /*
1515         ** output the Content-Disposition
1516         */
1517         if (ct->c_dispo) {
1518                 np = add (DISPO_FIELD, NULL);
1519                 vp = concat (" ", ct->c_dispo, NULL);
1520                 add_header (ct, np, vp);
1521         }
1522
1523 skip_headers:
1524         /*
1525         ** If this is the internal content structure for a
1526         ** "message/external", then we are done with the
1527         ** headers (since it has no body).
1528         */
1529         if (ct->c_ctexbody)
1530                 return OK;
1531
1532         /*
1533         ** output the Content-MD5
1534         */
1535         if (checksw) {
1536                 np = add (MD5_FIELD, NULL);
1537                 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1538                 add_header (ct, np, vp);
1539         }
1540
1541         /*
1542         ** output the Content-Transfer-Encoding
1543         */
1544         switch (ct->c_encoding) {
1545         case CE_7BIT:
1546                 /* Nothing to output */
1547 #if 0
1548                 np = add (ENCODING_FIELD, NULL);
1549                 vp = concat (" ", "7bit", "\n", NULL);
1550                 add_header (ct, np, vp);
1551 #endif
1552                 break;
1553
1554         case CE_8BIT:
1555                 if (ct->c_type == CT_MESSAGE)
1556                         adios (NULL, "internal error, invalid encoding");
1557
1558                 np = add (ENCODING_FIELD, NULL);
1559                 vp = concat (" ", "8bit", "\n", NULL);
1560                 add_header (ct, np, vp);
1561                 break;
1562
1563         case CE_QUOTED:
1564                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1565                         adios (NULL, "internal error, invalid encoding");
1566
1567                 np = add (ENCODING_FIELD, NULL);
1568                 vp = concat (" ", "quoted-printable", "\n", NULL);
1569                 add_header (ct, np, vp);
1570                 break;
1571
1572         case CE_BASE64:
1573                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1574                         adios (NULL, "internal error, invalid encoding");
1575
1576                 np = add (ENCODING_FIELD, NULL);
1577                 vp = concat (" ", "base64", "\n", NULL);
1578                 add_header (ct, np, vp);
1579                 break;
1580
1581         case CE_BINARY:
1582                 if (ct->c_type == CT_MESSAGE)
1583                         adios (NULL, "internal error, invalid encoding");
1584
1585                 np = add (ENCODING_FIELD, NULL);
1586                 vp = concat (" ", "binary", "\n", NULL);
1587                 add_header (ct, np, vp);
1588                 break;
1589
1590         default:
1591                 adios (NULL, "unknown transfer encoding in content");
1592                 break;
1593         }
1594
1595         /*
1596         ** Additional content specific header processing
1597         */
1598         switch (ct->c_type) {
1599         case CT_MULTIPART:
1600         {
1601                 struct multipart *m;
1602                 struct part *part;
1603
1604                 m = (struct multipart *) ct->c_ctparams;
1605                 for (part = m->mp_parts; part; part = part->mp_next) {
1606                         CT p;
1607
1608                         p = part->mp_part;
1609                         build_headers (p);
1610                 }
1611         }
1612                 break;
1613
1614         case CT_MESSAGE:
1615                 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1616                         struct exbody *e;
1617
1618                         e = (struct exbody *) ct->c_ctparams;
1619                         build_headers (e->eb_content);
1620                 }
1621                 break;
1622
1623         default:
1624                 /* Nothing to do */
1625                 break;
1626         }
1627
1628         return OK;
1629 }
1630
1631
1632 static char nib2b64[0x40+1] =
1633         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1634
1635 static char *
1636 calculate_digest (CT ct, int asciiP)
1637 {
1638         int cc;
1639         char buffer[BUFSIZ], *vp, *op;
1640         unsigned char *dp;
1641         unsigned char digest[16];
1642         unsigned char outbuf[25];
1643         FILE *in;
1644         MD5_CTX mdContext;
1645         CE ce = ct->c_cefile;
1646
1647         /* open content */
1648         if ((in = fopen (ce->ce_file, "r")) == NULL)
1649                 adios (ce->ce_file, "unable to open for reading");
1650
1651         /* Initialize md5 context */
1652         MD5Init (&mdContext);
1653
1654         /* calculate md5 message digest */
1655         if (asciiP) {
1656                 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1657                         char c, *cp;
1658
1659                         cp = buffer + strlen (buffer) - 1;
1660                         if ((c = *cp) == '\n')
1661                                 *cp = '\0';
1662
1663                         MD5Update (&mdContext, (unsigned char *) buffer,
1664                                            (unsigned int) strlen (buffer));
1665
1666                         if (c == '\n')
1667                                 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1668                 }
1669         } else {
1670                 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1671                         MD5Update (&mdContext, (unsigned char *) buffer,
1672                                         (unsigned int) cc);
1673         }
1674
1675         /* md5 finalization.  Write digest and zero md5 context */
1676         MD5Final (digest, &mdContext);
1677
1678         /* close content */
1679         fclose (in);
1680
1681         /* print debugging info */
1682         if (debugsw) {
1683                 unsigned char *ep;
1684
1685                 fprintf (stderr, "MD5 digest=");
1686                 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1687                         dp < ep; dp++)
1688                         fprintf (stderr, "%02x", *dp & 0xff);
1689                 fprintf (stderr, "\n");
1690         }
1691
1692         /* encode the digest using base64 */
1693         for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1694                 cc > 0; cc -= 3, op += 4) {
1695                 unsigned long bits;
1696                 char *bp;
1697
1698                 bits = (*dp++ & 0xff) << 16;
1699                 if (cc > 1) {
1700                         bits |= (*dp++ & 0xff) << 8;
1701                         if (cc > 2)
1702                                 bits |= *dp++ & 0xff;
1703                 }
1704
1705                 for (bp = op + 4; bp > op; bits >>= 6)
1706                         *--bp = nib2b64[bits & 0x3f];
1707                 if (cc < 3) {
1708                         *(op + 3) = '=';
1709                         if (cc < 2)
1710                                 *(op + 2) = '=';
1711                 }
1712         }
1713
1714         /* null terminate string */
1715         outbuf[24] = '\0';
1716
1717         /* now make copy and return string */
1718         vp = concat (" ", outbuf, "\n", NULL);
1719         return vp;
1720 }