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