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