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