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