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