3ed31f98fbc9ec2021c3f2d051d1056e16252f6d
[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                         int i;
1062
1063                         ap = brkstring(ci->ci_magic, " ", "\n");
1064                         for (i=0; ap[i] && i<MAXARGS-1; i++) {
1065                                 arguments[i] = ap[i];
1066                         }
1067                         arguments[i] = NULL;
1068
1069                 } else {
1070                         arguments[0] = seq_cur;
1071                         arguments[1] = NULL;
1072                 }
1073                 folder = NULL;
1074
1075                 /* search the arguments for a folder name */
1076                 for (ap = arguments; *ap; ap++) {
1077                         cp = *ap;
1078                         if (*cp == '+' || *cp == '@') {
1079                                 if (folder)
1080                                         adios(NULL, "only one folder per #forw directive");
1081                                 else
1082                                         folder = getcpy(expandfol(cp));
1083                         }
1084                 }
1085
1086                 /* else, use the current folder */
1087                 if (!folder)
1088                         folder = getcpy(getcurfol());
1089
1090                 if (!(mp = folder_read(folder)))
1091                         adios(NULL, "unable to read folder %s", folder);
1092                 for (ap = arguments; *ap; ap++) {
1093                         cp = *ap;
1094                         if (*cp != '+' && *cp != '@')
1095                                 if (!m_convert(mp, cp))
1096                                         done(1);
1097                 }
1098                 free(folder);
1099                 free_ctinfo(ct);
1100
1101                 /*
1102                 ** If there is more than one message to include, make this
1103                 ** a content of type "multipart/digest" and insert each message
1104                 ** as a subpart.  If there is only one message, then make this
1105                 ** a content of type "message/rfc822".
1106                 */
1107                 if (mp->numsel > 1) {
1108                         /* we are forwarding multiple messages */
1109                         if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
1110                                 done(1);
1111                         ct->c_type = CT_MULTIPART;
1112                         ct->c_subtype = MULTI_DIGEST;
1113
1114                         if ((m = (struct multipart *)
1115                                         calloc(1, sizeof(*m))) == NULL)
1116                                 adios(NULL, "out of memory");
1117                         ct->c_ctparams = (void *) m;
1118                         pp = &m->mp_parts;
1119
1120                         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1121                                 if (is_selected(mp, msgnum)) {
1122                                         struct part *part;
1123                                         CT p;
1124                                         CE pe;
1125
1126                                         if ((p = (CT) calloc(1, sizeof(*p)))
1127                                                         == NULL)
1128                                                 adios(NULL, "out of memory");
1129                                         init_decoded_content(p);
1130                                         pe = p->c_cefile;
1131                                         if (get_ctinfo("message/rfc822", p, 0)
1132                                                         == NOTOK)
1133                                                 done(1);
1134                                         p->c_type = CT_MESSAGE;
1135                                         p->c_subtype = MESSAGE_RFC822;
1136
1137                                         snprintf(buffer, sizeof(buffer),
1138                                                         "%s/%d", mp->foldpath,
1139                                                         msgnum);
1140                                         pe->ce_file = getcpy(buffer);
1141                                         if (listsw && stat(pe->ce_file, &st)
1142                                                         != NOTOK)
1143                                                 p->c_end = (long) st.st_size;
1144
1145                                         if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1146                                                 adios(NULL, "out of memory");
1147                                         *pp = part;
1148                                         pp = &part->mp_next;
1149                                         part->mp_part = p;
1150                                 }
1151                         }
1152                 } else {
1153                         /* we are forwarding one message */
1154                         if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1155                                 done(1);
1156                         ct->c_type = CT_MESSAGE;
1157                         ct->c_subtype = MESSAGE_RFC822;
1158
1159                         msgnum = mp->lowsel;
1160                         snprintf(buffer, sizeof(buffer), "%s/%d",
1161                                         mp->foldpath, msgnum);
1162                         ce->ce_file = getcpy(buffer);
1163                         if (listsw && stat(ce->ce_file, &st) != NOTOK)
1164                                 ct->c_end = (long) st.st_size;
1165                 }
1166
1167                 folder_free(mp);  /* free folder/message structure */
1168                 return OK;
1169         }
1170
1171         /*
1172         ** #end
1173         */
1174         if (!mh_strcasecmp(ci->ci_type, "end")) {
1175                 free_content(ct);
1176                 *ctp = NULL;
1177                 return DONE;
1178         }
1179
1180         /*
1181         ** #begin [ alternative | parallel ]
1182         */
1183         if (!mh_strcasecmp(ci->ci_type, "begin")) {
1184                 if (!ci->ci_magic) {
1185                         vrsn = MULTI_MIXED;
1186                         cp = SubMultiPart[vrsn - 1].kv_key;
1187                 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1188                         vrsn = MULTI_ALTERNATE;
1189                         cp = SubMultiPart[vrsn - 1].kv_key;
1190                 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1191                         vrsn = MULTI_PARALLEL;
1192                         cp = SubMultiPart[vrsn - 1].kv_key;
1193                 } else if (uprf(ci->ci_magic, "digest")) {
1194                         goto use_forw;
1195                 } else {
1196                         vrsn = MULTI_UNKNOWN;
1197                         cp = ci->ci_magic;
1198                 }
1199
1200                 free_ctinfo(ct);
1201                 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1202                 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1203                         done(1);
1204                 ct->c_type = CT_MULTIPART;
1205                 ct->c_subtype = vrsn;
1206
1207                 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1208                         adios(NULL, "out of memory");
1209                 ct->c_ctparams = (void *) m;
1210
1211                 pp = &m->mp_parts;
1212                 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1213                         struct part *part;
1214                         CT p;
1215
1216                         if (user_content(in, file, buffer, &p) == DONE) {
1217                                 if (!m->mp_parts)
1218                                         adios(NULL, "empty \"#begin ... #end\" sequence");
1219                                 return OK;
1220                         }
1221                         if (!p)
1222                                 continue;
1223
1224                         if ((part = (struct part *)
1225                                         calloc(1, sizeof(*part))) == NULL)
1226                                 adios(NULL, "out of memory");
1227                         *pp = part;
1228                         pp = &part->mp_next;
1229                         part->mp_part = p;
1230                 }
1231                 admonish(NULL, "premature end-of-file, missing #end");
1232                 return OK;
1233         }
1234
1235         /*
1236         ** Unknown directive
1237         */
1238         adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1239         return NOTOK;  /* NOT REACHED */
1240 }
1241
1242
1243 static void
1244 set_id(CT ct, int top)
1245 {
1246         char msgid[BUFSIZ];
1247         static int partno;
1248         static time_t clock = 0;
1249         static char *msgfmt;
1250
1251         if (clock == 0) {
1252                 time(&clock);
1253                 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1254                                 (int) getpid(), (long) clock, LocalName());
1255                 partno = 0;
1256                 msgfmt = getcpy(msgid);
1257         }
1258         snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1259         ct->c_id = getcpy(msgid);
1260 }
1261
1262
1263 static char ebcdicsafe[0x100] = {
1264         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1265         0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1266         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1267         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1268         0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
1269         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1270         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1271         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1272         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1273         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1274         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1275         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
1276         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1277         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1278         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1279         0x01, 0x01, 0x01, 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         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1291         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1292         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1293         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1294         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1295         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1296 };
1297
1298
1299 /*
1300 ** Fill out, or expand the various contents in the composition
1301 ** draft.  Read-in any necessary files.  Parse and execute any
1302 ** commands specified by profile composition strings.
1303 */
1304
1305 static int
1306 compose_content(CT ct)
1307 {
1308         CE ce = ct->c_cefile;
1309
1310         switch (ct->c_type) {
1311         case CT_MULTIPART:
1312         {
1313                 int partnum;
1314                 char *pp;
1315                 char partnam[BUFSIZ];
1316                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1317                 struct part *part;
1318
1319                 if (ct->c_partno) {
1320                         snprintf(partnam, sizeof(partnam), "%s.",
1321                                         ct->c_partno);
1322                         pp = partnam + strlen(partnam);
1323                 } else {
1324                         pp = partnam;
1325                 }
1326
1327                 /* first, we call compose_content on all the subparts */
1328                 for (part = m->mp_parts, partnum = 1; part;
1329                                 part = part->mp_next, partnum++) {
1330                         CT p = part->mp_part;
1331
1332                         sprintf(pp, "%d", partnum);
1333                         p->c_partno = getcpy(partnam);
1334                         if (compose_content(p) == NOTOK)
1335                                 return NOTOK;
1336                 }
1337
1338                 /*
1339                 ** If the -rfc934mode switch is given, then check all
1340                 ** the subparts of a multipart/digest.  If they are all
1341                 ** message/rfc822, then mark this content and all
1342                 ** subparts with the rfc934 compatibility mode flag.
1343                 */
1344                 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1345                         int is934 = 1;
1346
1347                         for (part = m->mp_parts; part; part = part->mp_next) {
1348                                 CT p = part->mp_part;
1349
1350                                 if (p->c_subtype != MESSAGE_RFC822) {
1351                                         is934 = 0;
1352                                         break;
1353                                 }
1354                         }
1355                         ct->c_rfc934 = is934;
1356                         for (part = m->mp_parts; part; part = part->mp_next) {
1357                                 CT p = part->mp_part;
1358
1359                                 if ((p->c_rfc934 = is934))
1360                                         p->c_end++;
1361                         }
1362                 }
1363
1364                 if (listsw) {
1365                         ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1366                         if (ct->c_rfc934)
1367                                 ct->c_end += 1;
1368
1369                         for (part = m->mp_parts; part; part = part->mp_next)
1370                                 ct->c_end += part->mp_part->c_end + partnum;
1371                 }
1372         }
1373         break;
1374
1375         case CT_MESSAGE:
1376                 /* Nothing to do for type message */
1377                 break;
1378
1379         /*
1380         ** Discrete types (text/application/audio/image/video)
1381         */
1382         default:
1383                 if (!ce->ce_file) {
1384                         pid_t child_id;
1385                         int xstdout, len, buflen;
1386                         char *bp, **ap, *cp;
1387                         char *vec[4], buffer[BUFSIZ];
1388                         FILE *out;
1389                         CI ci = &ct->c_ctinfo;
1390                         char *tfile = NULL;
1391
1392                         if (!(cp = ci->ci_magic))
1393                                 adios(NULL, "internal error(5)");
1394
1395                         tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1396                         if (tfile == NULL) {
1397                                 adios("mhbuild", "unable to create temporary file");
1398                         }
1399                         ce->ce_file = getcpy(tfile);
1400                         ce->ce_unlink = 1;
1401
1402                         xstdout = 0;
1403
1404                         /* Get buffer ready to go */
1405                         bp = buffer;
1406                         bp[0] = '\0';
1407                         buflen = sizeof(buffer);
1408
1409                         /*
1410                         ** Parse composition string into buffer
1411                         */
1412                         for ( ; *cp; cp++) {
1413                                 if (*cp == '%') {
1414                                         switch (*++cp) {
1415                                         case 'a':
1416                                         {
1417                                                 /*
1418                                                 ** insert parameters from
1419                                                 ** directive
1420                                                 */
1421                                                 char **ep;
1422                                                 char *s = "";
1423
1424                                                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1425                                                         snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1426                                                         len = strlen(bp);
1427                                                         bp += len;
1428                                                         buflen -= len;
1429                                                         s = " ";
1430                                                 }
1431                                         }
1432                                         break;
1433
1434                                         case 'F':
1435                                                 /* %f, and stdout is not-redirected */
1436                                                 xstdout = 1;
1437                                                 /* and fall... */
1438
1439                                         case 'f':
1440                                                 /*
1441                                                 ** insert temporary filename
1442                                                 ** where content should be
1443                                                 ** written
1444                                                 */
1445                                                 snprintf(bp, buflen, "%s", ce->ce_file);
1446                                                 break;
1447
1448                                         case 's':
1449                                                 /* insert content subtype */
1450                                                 strncpy(bp, ci->ci_subtype, buflen);
1451                                                 break;
1452
1453                                         case '%':
1454                                                 /* insert character % */
1455                                                 goto raw;
1456
1457                                         default:
1458                                                 *bp++ = *--cp;
1459                                                 *bp = '\0';
1460                                                 buflen--;
1461                                                 continue;
1462                                         }
1463                                         len = strlen(bp);
1464                                         bp += len;
1465                                         buflen -= len;
1466                                 } else {
1467 raw:
1468                                         *bp++ = *cp;
1469                                         *bp = '\0';
1470                                         buflen--;
1471                                 }
1472                         }
1473
1474                         if (verbosw)
1475                                 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1476
1477                         fflush(stdout);  /* not sure if need for -noverbose */
1478
1479                         vec[0] = "/bin/sh";
1480                         vec[1] = "-c";
1481                         vec[2] = buffer;
1482                         vec[3] = NULL;
1483
1484                         if ((out = fopen(ce->ce_file, "w")) == NULL)
1485                                 adios(ce->ce_file, "unable to open for writing");
1486
1487                         switch (child_id = fork()) {
1488                         case NOTOK:
1489                                 adios("fork", "unable to fork");
1490                                 /* NOTREACHED */
1491
1492                         case OK:
1493                                 if (!xstdout)
1494                                         dup2(fileno(out), 1);
1495                                 close(fileno(out));
1496                                 execvp("/bin/sh", vec);
1497                                 fprintf(stderr, "unable to exec ");
1498                                 perror("/bin/sh");
1499                                 _exit(-1);
1500                                 /* NOTREACHED */
1501
1502                         default:
1503                                 fclose(out);
1504                                 if (pidXwait(child_id, NULL))
1505                                         done(1);
1506                                 break;
1507                         }
1508                 }
1509
1510                 /* Check size of file */
1511                 if (listsw && ct->c_end == 0L) {
1512                         struct stat st;
1513
1514                         if (stat(ce->ce_file, &st) != NOTOK)
1515                                 ct->c_end = (long) st.st_size;
1516                 }
1517                 break;
1518         }
1519
1520         return OK;
1521 }
1522
1523
1524 /*
1525 ** Scan the content.
1526 **
1527 **    1) choose a transfer encoding.
1528 **    2) check for clashes with multipart boundary string.
1529 **    3) for text content, figure out which character set is being used.
1530 **
1531 ** If there is a clash with one of the contents and the multipart boundary,
1532 ** this function will exit with NOTOK.  This will cause the scanning process
1533 ** to be repeated with a different multipart boundary.  It is possible
1534 ** (although highly unlikely) that this scan will be repeated multiple times.
1535 */
1536
1537 static int
1538 scan_content(CT ct)
1539 {
1540         int len;
1541         int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data */
1542         int checklinelen = 0, linelen = 0;  /* check for long lines */
1543         int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1544         int checklinespace = 0, linespace = 0;  /* check if any line ends with space */
1545         int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1546         unsigned char *cp = NULL, buffer[BUFSIZ];
1547         struct text *t = NULL;
1548         FILE *in = NULL;
1549         CE ce = ct->c_cefile;
1550
1551         /*
1552         ** handle multipart by scanning all subparts
1553         ** and then checking their encoding.
1554         */
1555         if (ct->c_type == CT_MULTIPART) {
1556                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1557                 struct part *part;
1558
1559                 /* initially mark the domain of enclosing multipart as 7bit */
1560                 ct->c_encoding = CE_7BIT;
1561
1562                 for (part = m->mp_parts; part; part = part->mp_next) {
1563                         CT p = part->mp_part;
1564
1565                         if (scan_content(p) == NOTOK) {
1566                                 /* choose encoding for subpart */
1567                                 return NOTOK;
1568                         }
1569
1570                         /*
1571                         ** if necessary, enlarge encoding for enclosing
1572                         ** multipart
1573                         */
1574                         if (p->c_encoding == CE_BINARY)
1575                                 ct->c_encoding = CE_BINARY;
1576                         if (p->c_encoding == CE_8BIT &&
1577                                         ct->c_encoding != CE_BINARY)
1578                                 ct->c_encoding = CE_8BIT;
1579                 }
1580
1581                 return OK;
1582         }
1583
1584         /*
1585         ** Decide what to check while scanning this content.
1586         */
1587         switch (ct->c_type) {
1588         case CT_TEXT:
1589                 check8bit = 1;
1590                 checkboundary = 1;
1591                 if (ct->c_subtype == TEXT_PLAIN) {
1592                         checkebcdic = 0;
1593                         checklinelen = 0;
1594                         checklinespace = 0;
1595                 } else {
1596                         checkebcdic = ebcdicsw;
1597                         checklinelen = 1;
1598                         checklinespace = 1;
1599                 }
1600                 break;
1601
1602         case CT_APPLICATION:
1603                 check8bit = 1;
1604                 checkebcdic = ebcdicsw;
1605                 checklinelen = 1;
1606                 checklinespace = 1;
1607                 checkboundary = 1;
1608                 break;
1609
1610         case CT_MESSAGE:
1611                 check8bit = 0;
1612                 checkebcdic = 0;
1613                 checklinelen = 0;
1614                 checklinespace = 0;
1615
1616                 /* don't check anything for message/external */
1617                 if (ct->c_subtype == MESSAGE_EXTERNAL)
1618                         checkboundary = 0;
1619                 else
1620                         checkboundary = 1;
1621                 break;
1622
1623         case CT_AUDIO:
1624         case CT_IMAGE:
1625         case CT_VIDEO:
1626                 /*
1627                 ** Don't check anything for these types,
1628                 ** since we are forcing use of base64.
1629                 */
1630                 check8bit = 0;
1631                 checkebcdic = 0;
1632                 checklinelen = 0;
1633                 checklinespace = 0;
1634                 checkboundary = 0;
1635                 break;
1636         }
1637
1638         /*
1639         ** Scan the unencoded content
1640         */
1641         if (check8bit || checklinelen || checklinespace || checkboundary) {
1642                 if ((in = fopen(ce->ce_file, "r")) == NULL)
1643                         adios(ce->ce_file, "unable to open for reading");
1644                 len = strlen(prefix);
1645
1646                 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1647                         /*
1648                         ** Check for 8bit data.
1649                         */
1650                         if (check8bit) {
1651                                 for (cp = buffer; *cp; cp++) {
1652                                         if (!isascii(*cp)) {
1653                                                 contains8bit = 1;
1654                                                 /* no need to keep checking */
1655                                                 check8bit = 0;
1656                                         }
1657                                         /*
1658                                         ** Check if character is ebcdic-safe.
1659                                         ** We only check this if also checking
1660                                         ** for 8bit data.
1661                                         */
1662                                         if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1663                                                 ebcdicunsafe = 1;
1664                                                 /* no need to keep checking */
1665                                                 checkebcdic = 0;
1666                                         }
1667                                 }
1668                         }
1669
1670                         /*
1671                         ** Check line length.
1672                         */
1673                         if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1674                                 linelen = 1;
1675                                 checklinelen = 0;  /* no need to keep checking */
1676                         }
1677
1678                         /*
1679                         ** Check if line ends with a space.
1680                         */
1681                         if (checklinespace &&
1682                                         (cp = buffer + strlen(buffer) - 2) >
1683                                         buffer && isspace(*cp)) {
1684                                 linespace = 1;
1685                                 /* no need to keep checking */
1686                                 checklinespace = 0;
1687                         }
1688
1689                         /*
1690                         ** Check if content contains a line that clashes
1691                         ** with our standard boundary for multipart messages.
1692                         */
1693                         if (checkboundary && buffer[0] == '-' &&
1694                                         buffer[1] == '-') {
1695                                 for (cp = buffer + strlen(buffer) - 1;
1696                                                 cp >= buffer; cp--)
1697                                         if (!isspace(*cp))
1698                                                 break;
1699                                 *++cp = '\0';
1700                                 if (strncmp(buffer + 2, prefix, len)==0 &&
1701                                                 isdigit(buffer[2 + len])) {
1702                                         boundaryclash = 1;
1703                                         /* no need to keep checking */
1704                                         checkboundary = 0;
1705                                 }
1706                         }
1707                 }
1708                 fclose(in);
1709         }
1710
1711         /*
1712         ** Decide which transfer encoding to use.
1713         */
1714         switch (ct->c_type) {
1715         case CT_TEXT:
1716                 /*
1717                 ** If the text content didn't specify a character
1718                 ** set, we need to figure out which one was used.
1719                 */
1720                 t = (struct text *) ct->c_ctparams;
1721                 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1722                         CI ci = &ct->c_ctinfo;
1723                         char **ap, **ep;
1724
1725                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1726                                 continue;
1727
1728                         if (contains8bit) {
1729                                 t->tx_charset = CHARSET_UNKNOWN;
1730                                 *ap = concat("charset=", write_charset_8bit(),
1731                                                 NULL);
1732                         } else {
1733                                 t->tx_charset = CHARSET_USASCII;
1734                                 *ap = getcpy("charset=us-ascii");
1735                         }
1736
1737                         cp = strchr(*ap++, '=');
1738                         *ap = NULL;
1739                         *cp++ = '\0';
1740                         *ep = cp;
1741                 }
1742
1743                 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1744                                 checksw)
1745                         ct->c_encoding = CE_QUOTED;
1746                 else
1747                         ct->c_encoding = CE_7BIT;
1748                 break;
1749
1750         case CT_APPLICATION:
1751                 /* For application type, use base64, except when postscript */
1752                 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1753                                 checksw)
1754                         ct->c_encoding = (ct->c_subtype ==
1755                                         APPLICATION_POSTSCRIPT) ?
1756                                         CE_QUOTED : CE_BASE64;
1757                 else
1758                         ct->c_encoding = CE_7BIT;
1759                 break;
1760
1761         case CT_MESSAGE:
1762                 ct->c_encoding = CE_7BIT;
1763                 break;
1764
1765         case CT_AUDIO:
1766         case CT_IMAGE:
1767         case CT_VIDEO:
1768                 /* For audio, image, and video contents, just use base64 */
1769                 ct->c_encoding = CE_BASE64;
1770                 break;
1771         }
1772
1773         return (boundaryclash ? NOTOK : OK);
1774 }
1775
1776
1777 /*
1778 ** Scan the content structures, and build header
1779 ** fields that will need to be output into the
1780 ** message.
1781 */
1782
1783 static int
1784 build_headers(CT ct)
1785 {
1786         int cc, mailbody, len;
1787         char **ap, **ep;
1788         char *np, *vp, buffer[BUFSIZ];
1789         CI ci = &ct->c_ctinfo;
1790
1791         /*
1792         ** If message is type multipart, then add the multipart
1793         ** boundary to the list of attribute/value pairs.
1794         */
1795         if (ct->c_type == CT_MULTIPART) {
1796                 char *cp;
1797                 static int level = 0;  /* store nesting level */
1798
1799                 ap = ci->ci_attrs;
1800                 ep = ci->ci_values;
1801                 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1802                                 prefix, level++);
1803                 cp = strchr(*ap++ = getcpy(buffer), '=');
1804                 *ap = NULL;
1805                 *cp++ = '\0';
1806                 *ep = cp;
1807         }
1808
1809         /*
1810         ** Skip the output of Content-Type, parameters, content
1811         ** description and disposition, and Content-ID if the
1812         ** content is of type "message" and the rfc934 compatibility
1813         ** flag is set (which means we are inside multipart/digest
1814         ** and the switch -rfc934mode was given).
1815         */
1816         if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1817                 goto skip_headers;
1818
1819         /*
1820         ** output the content type and subtype
1821         */
1822         np = getcpy(TYPE_FIELD);
1823         vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1824
1825         /* keep track of length of line */
1826         len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1827                         strlen(ci->ci_subtype) + 3;
1828
1829         mailbody = ct->c_type == CT_MESSAGE &&
1830                         ct->c_subtype == MESSAGE_EXTERNAL &&
1831                         ((struct exbody *) ct->c_ctparams)->eb_body;
1832
1833         /*
1834         ** Append the attribute/value pairs to
1835         ** the end of the Content-Type line.
1836         */
1837         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1838                 if (mailbody && !mh_strcasecmp(*ap, "body"))
1839                         continue;
1840
1841                 vp = add(";", vp);
1842                 len++;
1843
1844                 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1845                 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1846                         vp = add("\n\t", vp);
1847                         len = 8;
1848                 } else {
1849                         vp = add(" ", vp);
1850                         len++;
1851                 }
1852                 vp = add(buffer, vp);
1853                 len += cc;
1854         }
1855
1856         /*
1857         ** Append any RFC-822 comment to the end of
1858         ** the Content-Type line.
1859         */
1860         if (ci->ci_comment) {
1861                 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1862                 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1863                         vp = add("\n\t", vp);
1864                         len = 8;
1865                 } else {
1866                         vp = add(" ", vp);
1867                         len++;
1868                 }
1869                 vp = add(buffer, vp);
1870                 len += cc;
1871         }
1872         vp = add("\n", vp);
1873         add_header(ct, np, vp);
1874
1875         /*
1876         ** output the Content-ID, unless disabled by -nocontentid
1877         */
1878         if (contentidsw && ct->c_id) {
1879                 np = getcpy(ID_FIELD);
1880                 vp = concat(" ", ct->c_id, NULL);
1881                 add_header(ct, np, vp);
1882         }
1883
1884         /*
1885         ** output the Content-Description
1886         */
1887         if (ct->c_descr) {
1888                 np = getcpy(DESCR_FIELD);
1889                 vp = concat(" ", ct->c_descr, NULL);
1890                 add_header(ct, np, vp);
1891         }
1892
1893         /*
1894         ** output the Content-Disposition
1895         */
1896         if (ct->c_dispo) {
1897                 np = getcpy(DISPO_FIELD);
1898                 vp = concat(" ", ct->c_dispo, NULL);
1899                 add_header(ct, np, vp);
1900         }
1901
1902 skip_headers:
1903         /*
1904         ** If this is the internal content structure for a
1905         ** "message/external", then we are done with the
1906         ** headers (since it has no body).
1907         */
1908         if (ct->c_ctexbody)
1909                 return OK;
1910
1911         /*
1912         ** output the Content-MD5
1913         */
1914         if (checksw) {
1915                 np = getcpy(MD5_FIELD);
1916                 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1917                                 1 : 0);
1918                 add_header(ct, np, vp);
1919         }
1920
1921         /*
1922         ** output the Content-Transfer-Encoding
1923         */
1924         switch (ct->c_encoding) {
1925         case CE_7BIT:
1926                 /* Nothing to output */
1927                 break;
1928
1929         case CE_8BIT:
1930                 if (ct->c_type == CT_MESSAGE)
1931                         adios(NULL, "internal error, invalid encoding");
1932
1933                 np = getcpy(ENCODING_FIELD);
1934                 vp = concat(" ", "8bit", "\n", NULL);
1935                 add_header(ct, np, vp);
1936                 break;
1937
1938         case CE_QUOTED:
1939                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1940                         adios(NULL, "internal error, invalid encoding");
1941
1942                 np = getcpy(ENCODING_FIELD);
1943                 vp = concat(" ", "quoted-printable", "\n", NULL);
1944                 add_header(ct, np, vp);
1945                 break;
1946
1947         case CE_BASE64:
1948                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1949                         adios(NULL, "internal error, invalid encoding");
1950
1951                 np = getcpy(ENCODING_FIELD);
1952                 vp = concat(" ", "base64", "\n", NULL);
1953                 add_header(ct, np, vp);
1954                 break;
1955
1956         case CE_BINARY:
1957                 if (ct->c_type == CT_MESSAGE)
1958                         adios(NULL, "internal error, invalid encoding");
1959
1960                 np = getcpy(ENCODING_FIELD);
1961                 vp = concat(" ", "binary", "\n", NULL);
1962                 add_header(ct, np, vp);
1963                 break;
1964
1965         default:
1966                 adios(NULL, "unknown transfer encoding in content");
1967                 break;
1968         }
1969
1970         /*
1971         ** Additional content specific header processing
1972         */
1973         switch (ct->c_type) {
1974         case CT_MULTIPART:
1975         {
1976                 struct multipart *m;
1977                 struct part *part;
1978
1979                 m = (struct multipart *) ct->c_ctparams;
1980                 for (part = m->mp_parts; part; part = part->mp_next) {
1981                         CT p;
1982
1983                         p = part->mp_part;
1984                         build_headers(p);
1985                 }
1986         }
1987                 break;
1988
1989         case CT_MESSAGE:
1990                 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1991                         struct exbody *e;
1992
1993                         e = (struct exbody *) ct->c_ctparams;
1994                         build_headers(e->eb_content);
1995                 }
1996                 break;
1997
1998         default:
1999                 /* Nothing to do */
2000                 break;
2001         }
2002
2003         return OK;
2004 }
2005
2006
2007 static char nib2b64[0x40+1] =
2008         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2009
2010 static char *
2011 calculate_digest(CT ct, int asciiP)
2012 {
2013         int cc;
2014         char buffer[BUFSIZ], *vp, *op;
2015         unsigned char *dp;
2016         unsigned char digest[16];
2017         unsigned char outbuf[25];
2018         FILE *in;
2019         MD5_CTX mdContext;
2020         CE ce = ct->c_cefile;
2021
2022         /* open content */
2023         if ((in = fopen(ce->ce_file, "r")) == NULL)
2024                 adios(ce->ce_file, "unable to open for reading");
2025
2026         /* Initialize md5 context */
2027         MD5Init(&mdContext);
2028
2029         /* calculate md5 message digest */
2030         if (asciiP) {
2031                 while (fgets(buffer, sizeof(buffer) - 1, in)) {
2032                         char c, *cp;
2033
2034                         cp = buffer + strlen(buffer) - 1;
2035                         if ((c = *cp) == '\n')
2036                                 *cp = '\0';
2037
2038                         MD5Update(&mdContext, (unsigned char *) buffer,
2039                                         (unsigned int) strlen(buffer));
2040
2041                         if (c == '\n')
2042                                 MD5Update(&mdContext, (unsigned char *) "\r\n",
2043                                                  2);
2044                 }
2045         } else {
2046                 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
2047                                 in)) > 0)
2048                         MD5Update(&mdContext, (unsigned char *) buffer,
2049                                         (unsigned int) cc);
2050         }
2051
2052         /* md5 finalization.  Write digest and zero md5 context */
2053         MD5Final(digest, &mdContext);
2054
2055         /* close content */
2056         fclose(in);
2057
2058         /* print debugging info */
2059         if (debugsw) {
2060                 unsigned char *ep;
2061
2062                 fprintf(stderr, "MD5 digest=");
2063                 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
2064                         dp < ep; dp++)
2065                         fprintf(stderr, "%02x", *dp & 0xff);
2066                 fprintf(stderr, "\n");
2067         }
2068
2069         /* encode the digest using base64 */
2070         for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
2071                 cc > 0; cc -= 3, op += 4) {
2072                 unsigned long bits;
2073                 char *bp;
2074
2075                 bits = (*dp++ & 0xff) << 16;
2076                 if (cc > 1) {
2077                         bits |= (*dp++ & 0xff) << 8;
2078                         if (cc > 2)
2079                                 bits |= *dp++ & 0xff;
2080                 }
2081
2082                 for (bp = op + 4; bp > op; bits >>= 6)
2083                         *--bp = nib2b64[bits & 0x3f];
2084                 if (cc < 3) {
2085                         *(op + 3) = '=';
2086                         if (cc < 2)
2087                                 *(op + 2) = '=';
2088                 }
2089         }
2090
2091         /* null terminate string */
2092         outbuf[24] = '\0';
2093
2094         /* now make copy and return string */
2095         vp = concat(" ", outbuf, "\n", NULL);
2096         return vp;
2097 }