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