041a14296df4e5481ca82203ae014979b8cf3d31
[mmh] / uip / mhbuildsbr.c
1
2 /*
3  * mhbuildsbr.c -- routines to expand/translate MIME composition files
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 /*
13  * This code was originally part of mhn.c.  I split it into
14  * a separate program (mhbuild.c) and then later split it
15  * again (mhbuildsbr.c).  But the code still has some of
16  * the mhn.c code in it.  This program needs additional
17  * streamlining and removal of unneeded code.
18  */
19
20 #include <h/mh.h>
21 #include <fcntl.h>
22 #include <h/signals.h>
23 #include <h/md5.h>
24 #include <errno.h>
25 #include <signal.h>
26 #include <h/mts.h>
27 #include <h/tws.h>
28 #include <h/mime.h>
29 #include <h/mhparse.h>
30 #include <h/utils.h>
31
32 #ifdef TIME_WITH_SYS_TIME
33 # include <sys/time.h>
34 # include <time.h>
35 #else
36 # ifdef TM_IN_SYS_TIME
37 #  include <sys/time.h>
38 # else
39 #  include <time.h>
40 # endif
41 #endif
42
43 #ifdef HAVE_SYS_WAIT_H
44 # include <sys/wait.h>
45 #endif
46
47
48 extern int debugsw;
49 extern int verbosw;
50
51 extern int ebcdicsw;
52 extern int listsw;
53 extern int rfc934sw;
54 extern int contentidsw;
55
56 extern int endian;      /* mhmisc.c */
57
58 /* cache policies */
59 extern int rcachesw;    /* mhcachesbr.c */
60 extern int wcachesw;    /* mhcachesbr.c */
61
62 int checksw = 0;        /* Add Content-MD5 field */
63
64 /*
65  * Directory to place tmp files.  This must
66  * be set before these routines are called.
67  */
68 char *tmp;
69
70 pid_t xpid = 0;
71
72 static char prefix[] = "----- =_aaaaaaaaaa";
73
74 /*
75  * Structure for mapping types to their internal flags
76  */
77 struct k2v {
78     char *kv_key;
79     int   kv_value;
80 };
81
82 /*
83  * Structures for TEXT messages
84  */
85 static struct k2v SubText[] = {
86     { "plain",    TEXT_PLAIN },
87     { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
88     { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
89     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
90 };
91
92 static struct k2v Charset[] = {
93     { "us-ascii",   CHARSET_USASCII },
94     { "iso-8859-1", CHARSET_LATIN },
95     { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
96 };
97
98 /*
99  * Structures for MULTIPART messages
100  */
101 static struct k2v SubMultiPart[] = {
102     { "mixed",       MULTI_MIXED },
103     { "alternative", MULTI_ALTERNATE },
104     { "digest",      MULTI_DIGEST },
105     { "parallel",    MULTI_PARALLEL },
106     { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
107 };
108
109 /*
110  * Structures for MESSAGE messages
111  */
112 static struct k2v SubMessage[] = {
113     { "rfc822",        MESSAGE_RFC822 },
114     { "partial",       MESSAGE_PARTIAL },
115     { "external-body", MESSAGE_EXTERNAL },
116     { NULL,            MESSAGE_UNKNOWN }        /* this one must be last! */
117 };
118
119 /*
120  * Structure for APPLICATION messages
121  */
122 static struct k2v SubApplication[] = {
123     { "octet-stream", APPLICATION_OCTETS },
124     { "postscript",   APPLICATION_POSTSCRIPT },
125     { NULL,           APPLICATION_UNKNOWN }     /* this one must be last! */
126 };
127
128
129 /* mhmisc.c */
130 int make_intermediates (char *);
131 void content_error (char *, CT, char *, ...);
132
133 /* mhcachesbr.c */
134 int find_cache (CT, int, int *, char *, char *, int);
135
136 /* ftpsbr.c */
137 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
138
139 /* mhfree.c */
140 void free_content (CT);
141 void free_ctinfo (CT);
142 void free_encoding (CT, int);
143
144 /*
145  * prototypes
146  */
147 CT build_mime (char *);
148 int pidcheck (int);
149
150 /*
151  * static prototypes
152  */
153 static CT get_content (FILE *, char *, int);
154 static int add_header (CT, char *, char *);
155 static int get_ctinfo (unsigned char *, CT, int);
156 static int get_comment (CT, unsigned char **, int);
157 static int InitGeneric (CT);
158 static int InitText (CT);
159 static int InitMultiPart (CT);
160 static void reverse_parts (CT);
161 static int InitMessage (CT);
162 static int params_external (CT, int);
163 static int InitApplication (CT);
164 static int init_decoded_content (CT);
165 static int init_encoding (CT, OpenCEFunc);
166 static void close_encoding (CT);
167 static unsigned long size_encoding (CT);
168 static int InitBase64 (CT);
169 static int openBase64 (CT, char **);
170 static int InitQuoted (CT);
171 static int openQuoted (CT, char **);
172 static int Init7Bit (CT);
173 static int open7Bit (CT, char **);
174 static int openExternal (CT, CT, CE, char **, int *);
175 static int InitFile (CT);
176 static int openFile (CT, char **);
177 static int InitFTP (CT);
178 static int openFTP (CT, char **);
179 static int InitMail (CT);
180 static int openMail (CT, char **);
181 static char *fgetstr (char *, int, FILE *);
182 static int user_content (FILE *, char *, char *, CT *);
183 static void set_id (CT, int);
184 static int compose_content (CT);
185 static int scan_content (CT);
186 static int build_headers (CT);
187 static char *calculate_digest (CT, int);
188 static int readDigest (CT, char *);
189 static char *incl_name_value (unsigned char *, char *, char *);
190 static char *extract_name_value (char *, char *);
191
192 /*
193  * Structures for mapping (content) types to
194  * the functions to handle them.
195  */
196 struct str2init {
197     char *si_key;
198     int   si_val;
199     InitFunc si_init;
200 };
201
202 static struct str2init str2cts[] = {
203     { "application", CT_APPLICATION, InitApplication },
204     { "audio",       CT_AUDIO,       InitGeneric },
205     { "image",       CT_IMAGE,       InitGeneric },
206     { "message",     CT_MESSAGE,     InitMessage },
207     { "multipart",   CT_MULTIPART,   InitMultiPart },
208     { "text",        CT_TEXT,        InitText },
209     { "video",       CT_VIDEO,       InitGeneric },
210     { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
211     { NULL,          CT_UNKNOWN,     NULL },
212 };
213
214 static struct str2init str2ces[] = {
215     { "base64",           CE_BASE64,    InitBase64 },
216     { "quoted-printable", CE_QUOTED,    InitQuoted },
217     { "8bit",             CE_8BIT,      Init7Bit },
218     { "7bit",             CE_7BIT,      Init7Bit },
219     { "binary",           CE_BINARY,    NULL },
220     { NULL,               CE_EXTENSION, NULL },  /* these two must be last! */
221     { NULL,               CE_UNKNOWN,   NULL },
222 };
223
224 /*
225  * NOTE WELL: si_key MUST NOT have value of NOTOK
226  *
227  * si_key is 1 if access method is anonymous.
228  */
229 static struct str2init str2methods[] = {
230     { "afs",         1, InitFile },
231     { "anon-ftp",    1, InitFTP },
232     { "ftp",         0, InitFTP },
233     { "local-file",  0, InitFile },
234     { "mail-server", 0, InitMail },
235     { NULL,          0, NULL }
236 };
237
238
239 int
240 pidcheck (int status)
241 {
242     if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
243         return status;
244
245     fflush (stdout);
246     fflush (stderr);
247     done (1);
248     return 1;
249 }
250
251
252 /*
253  * Main routine for translating composition file
254  * into valid MIME message.  It translates the draft
255  * into a content structure (actually a tree of content
256  * structures).  This message then can be manipulated
257  * in various ways, including being output via
258  * output_message().
259  */
260
261 CT
262 build_mime (char *infile)
263 {
264     int compnum, state;
265     char buf[BUFSIZ], name[NAMESZ];
266     char *cp, *np, *vp;
267     struct multipart *m;
268     struct part **pp;
269     CT ct;
270     FILE *in;
271
272     umask (~m_gmprot ());
273
274     /* open the composition draft */
275     if ((in = fopen (infile, "r")) == NULL)
276         adios (infile, "unable to open for reading");
277
278     /*
279      * Allocate space for primary (outside) content
280      */
281     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
282         adios (NULL, "out of memory");
283
284     /*
285      * Allocate structure for handling decoded content
286      * for this part.  We don't really need this, but
287      * allocate it to remain consistent.
288      */
289     init_decoded_content (ct);
290
291     /*
292      * Parse some of the header fields in the composition
293      * draft into the linked list of header fields for
294      * the new MIME message.
295      */
296     for (compnum = 1, state = FLD;;) {
297         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
298         case FLD:
299         case FLDPLUS:
300         case FLDEOF:
301             compnum++;
302
303             /* abort if draft has Mime-Version header field */
304             if (!mh_strcasecmp (name, VRSN_FIELD))
305                 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
306
307             /* abort if draft has Content-Transfer-Encoding header field */
308             if (!mh_strcasecmp (name, ENCODING_FIELD))
309                 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
310
311             /* ignore any Content-Type fields in the header */
312             if (!mh_strcasecmp (name, TYPE_FIELD)) {
313                 while (state == FLDPLUS)
314                     state = m_getfld (state, name, buf, sizeof(buf), in);
315                 goto finish_field;
316             }
317
318             /* get copies of the buffers */
319             np = add (name, NULL);
320             vp = add (buf, NULL);
321
322             /* if necessary, get rest of field */
323             while (state == FLDPLUS) {
324                 state = m_getfld (state, name, buf, sizeof(buf), in);
325                 vp = add (buf, vp);     /* add to previous value */
326             }
327
328             /* Now add the header data to the list */
329             add_header (ct, np, vp);
330
331 finish_field:
332             /* if this wasn't the last header field, then continue */
333             if (state != FLDEOF)
334                 continue;
335             /* else fall... */
336
337         case FILEEOF:
338             adios (NULL, "draft has empty body -- no directives!");
339             /* NOTREACHED */
340
341         case BODY:
342         case BODYEOF:
343             fseek (in, (long) (-strlen (buf)), SEEK_CUR);
344             break;
345
346         case LENERR:
347         case FMTERR:
348             adios (NULL, "message format error in component #%d", compnum);
349
350         default:
351             adios (NULL, "getfld() returned %d", state);
352         }
353         break;
354     }
355
356     /*
357      * Now add the MIME-Version header field
358      * to the list of header fields.
359      */
360     np = add (VRSN_FIELD, NULL);
361     vp = concat (" ", VRSN_VALUE, "\n", NULL);
362     add_header (ct, np, vp);
363
364     /*
365      * We initally assume we will find multiple contents in the
366      * draft.  So create a multipart/mixed content to hold everything.
367      * We can remove this later, if it is not needed.
368      */
369     if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
370         done (1);
371     ct->c_type = CT_MULTIPART;
372     ct->c_subtype = MULTI_MIXED;
373     ct->c_file = add (infile, NULL);
374
375     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
376         adios (NULL, "out of memory");
377     ct->c_ctparams = (void *) m;
378     pp = &m->mp_parts;
379
380     /*
381      * read and parse the composition file
382      * and the directives it contains.
383      */
384     while (fgetstr (buf, sizeof(buf) - 1, in)) {
385         struct part *part;
386         CT p;
387
388         if (user_content (in, infile, buf, &p) == DONE) {
389             admonish (NULL, "ignoring spurious #end");
390             continue;
391         }
392         if (!p)
393             continue;
394
395         if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
396             adios (NULL, "out of memory");
397         *pp = part;
398         pp = &part->mp_next;
399         part->mp_part = p;
400     }
401
402     /*
403      * close the composition draft since
404      * it's not needed any longer.
405      */
406     fclose (in);
407
408     /* check if any contents were found */
409     if (!m->mp_parts)
410         adios (NULL, "no content directives found");
411
412     /*
413      * If only one content was found, then remove and
414      * free the outer multipart content.
415      */
416     if (!m->mp_parts->mp_next) {
417         CT p;
418
419         p = m->mp_parts->mp_part;
420         m->mp_parts->mp_part = NULL;
421
422         /* move header fields */
423         p->c_first_hf = ct->c_first_hf;
424         p->c_last_hf = ct->c_last_hf;
425         ct->c_first_hf = NULL;
426         ct->c_last_hf = NULL;
427
428         free_content (ct);
429         ct = p;
430     } else {
431         set_id (ct, 1);
432     }
433
434     /*
435      * Fill out, or expand directives.  Parse and execute
436      * commands specified by profile composition strings.
437      */
438     compose_content (ct);
439
440     if ((cp = strchr(prefix, 'a')) == NULL)
441         adios (NULL, "internal error(4)");
442
443     /*
444      * Scan the contents.  Choose a transfer encoding, and
445      * check if prefix for multipart boundary clashes with
446      * any of the contents.
447      */
448     while (scan_content (ct) == NOTOK) {
449         if (*cp < 'z') {
450             (*cp)++;
451         } else {
452             if (*++cp == 0)
453                 adios (NULL, "giving up trying to find a unique delimiter string");
454             else
455                 (*cp)++;
456         }
457     }
458
459     /* Build the rest of the header field structures */
460     build_headers (ct);
461
462     return ct;
463 }
464
465
466 /*
467  * Main routine for reading/parsing the headers
468  * of a message content.
469  *
470  * toplevel =  1   # we are at the top level of the message
471  * toplevel =  0   # we are inside message type or multipart type
472  *                 # other than multipart/digest
473  * toplevel = -1   # we are inside multipart/digest
474  */
475
476 static CT
477 get_content (FILE *in, char *file, int toplevel)
478 {
479     int compnum, state;
480     char buf[BUFSIZ], name[NAMESZ];
481     CT ct;
482
483     if (!(ct = (CT) calloc (1, sizeof(*ct))))
484         adios (NULL, "out of memory");
485
486     ct->c_fp = in;
487     ct->c_file = add (file, NULL);
488     ct->c_begin = ftell (ct->c_fp) + 1;
489
490     /*
491      * Read the content headers
492      */
493     for (compnum = 1, state = FLD;;) {
494         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
495         case FLD:
496         case FLDPLUS:
497         case FLDEOF:
498             compnum++;
499
500             /* Get MIME-Version field */
501             if (!mh_strcasecmp (name, VRSN_FIELD)) {
502                 int ucmp;
503                 char c;
504                 unsigned char *cp, *dp;
505
506                 cp = add (buf, NULL);
507                 while (state == FLDPLUS) {
508                     state = m_getfld (state, name, buf, sizeof(buf), in);
509                     cp = add (buf, cp);
510                 }
511
512                 if (ct->c_vrsn) {
513                     advise (NULL, "message %s has multiple %s: fields (%s)",
514                             ct->c_file, VRSN_FIELD, dp = trimcpy (cp));
515                     free (dp);
516                     free (cp);
517                     goto out;
518                 }
519
520                 ct->c_vrsn = cp;
521                 while (isspace (*cp))
522                     cp++;
523                 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
524                     *dp++ = ' ';
525                 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
526                     if (!isspace (*dp))
527                         break;
528                 *++dp = '\0';
529                 if (debugsw)
530                     fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
531
532                 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
533                     goto out;
534
535                 for (dp = cp; istoken (*dp); dp++)
536                     continue;
537                 c = *dp, *dp = '\0';
538                 ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
539                 *dp = c;
540                 if (!ucmp)
541                     admonish (NULL,
542                               "message %s has unknown value for %s: field (%s)",
543                               ct->c_file, VRSN_FIELD, cp);
544                 goto got_header;
545             }
546
547             /* Get Content-Type field */
548             if (!mh_strcasecmp (name, TYPE_FIELD)) {
549                 char *cp;
550                 struct str2init *s2i;
551                 CI ci = &ct->c_ctinfo;
552
553                 cp = add (buf, NULL);
554                 while (state == FLDPLUS) {
555                     state = m_getfld (state, name, buf, sizeof(buf), in);
556                     cp = add (buf, cp);
557                 }
558
559                 /* Check if we've already seen a Content-Type header */
560                 if (ct->c_ctline) {
561                     char *dp = trimcpy (cp);
562
563                     advise (NULL, "message %s has multiple %s: fields (%s)",
564                             ct->c_file, TYPE_FIELD, dp);
565                     free (dp);
566                     free (cp);
567                     goto out;
568                 }
569
570                 /* Parse the Content-Type field */
571                 if (get_ctinfo (cp, ct, 0) == NOTOK)
572                     goto out;
573
574                 /*
575                  * Set the Init function and the internal
576                  * flag for this content type.
577                  */
578                 for (s2i = str2cts; s2i->si_key; s2i++)
579                     if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
580                         break;
581                 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
582                     s2i++;
583                 ct->c_type = s2i->si_val;
584                 ct->c_ctinitfnx = s2i->si_init;
585                 goto got_header;
586             }
587
588             /* Get Content-Transfer-Encoding field */
589             if (!mh_strcasecmp (name, ENCODING_FIELD)) {
590                 unsigned char *cp, *dp;
591                 char c;
592                 struct str2init *s2i;
593
594                 cp = add (buf, NULL);
595                 while (state == FLDPLUS) {
596                     state = m_getfld (state, name, buf, sizeof(buf), in);
597                     cp = add (buf, cp);
598                 }
599
600                 /*
601                  * Check if we've already seen the
602                  * Content-Transfer-Encoding field
603                  */
604                 if (ct->c_celine) {
605                     advise (NULL, "message %s has multiple %s: fields (%s)",
606                             ct->c_file, ENCODING_FIELD, dp = trimcpy (cp));
607                     free (dp);
608                     free (cp);
609                     goto out;
610                 }
611
612                 ct->c_celine = cp;      /* Save copy of this field */
613                 while (isspace (*cp))
614                     cp++;
615                 for (dp = cp; istoken (*dp); dp++)
616                     continue;
617                 c = *dp;
618                 *dp = '\0';
619
620                 /*
621                  * Find the internal flag and Init function
622                  * for this transfer encoding.
623                  */
624                 for (s2i = str2ces; s2i->si_key; s2i++)
625                     if (!mh_strcasecmp (cp, s2i->si_key))
626                         break;
627                 if (!s2i->si_key && !uprf (cp, "X-"))
628                     s2i++;
629                 *dp = c;
630                 ct->c_encoding = s2i->si_val;
631
632                 /* Call the Init function for this encoding */
633                 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
634                     goto out;
635                 goto got_header;
636             }
637
638             /* Get Content-ID field */
639             if (!mh_strcasecmp (name, ID_FIELD)) {
640                 ct->c_id = add (buf, ct->c_id);
641                 while (state == FLDPLUS) {
642                     state = m_getfld (state, name, buf, sizeof(buf), in);
643                     ct->c_id = add (buf, ct->c_id);
644                 }
645                 goto got_header;
646             }
647
648             /* Get Content-Description field */
649             if (!mh_strcasecmp (name, DESCR_FIELD)) {
650                 ct->c_descr = add (buf, ct->c_descr);
651                 while (state == FLDPLUS) {
652                     state = m_getfld (state, name, buf, sizeof(buf), in);
653                     ct->c_descr = add (buf, ct->c_descr);
654                 }
655                 goto got_header;
656             }
657
658             /* Get Content-Disposition field */
659             if (!mh_strcasecmp (name, DISPO_FIELD)) {
660                 ct->c_dispo = add (buf, ct->c_dispo);
661                 while (state == FLDPLUS) {
662                     state = m_getfld (state, name, buf, sizeof(buf), in);
663                     ct->c_dispo = add (buf, ct->c_dispo);
664                 }
665                 goto got_header;
666             }
667
668             /* Get Content-MD5 field */
669             if (!mh_strcasecmp (name, MD5_FIELD)) {
670                 unsigned char *cp, *dp;
671                 char *ep;
672
673                 cp = add (buf, NULL);
674                 while (state == FLDPLUS) {
675                     state = m_getfld (state, name, buf, sizeof(buf), in);
676                     cp = add (buf, cp);
677                 }
678
679                 if (!checksw) {
680                     free (cp);
681                     goto got_header;
682                 }
683
684                 if (ct->c_digested) {
685                     advise (NULL, "message %s has multiple %s: fields (%s)",
686                             ct->c_file, MD5_FIELD, dp = trimcpy (cp));
687                     free (dp);
688                     free (cp);
689                     goto out;
690                 }
691
692                 ep = cp;
693                 while (isspace (*cp))
694                     cp++;
695                 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
696                     *dp++ = ' ';
697                 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
698                     if (!isspace (*dp))
699                         break;
700                 *++dp = '\0';
701                 if (debugsw)
702                     fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
703
704                 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
705                     free (ep);
706                     goto out;
707                 }
708
709                 for (dp = cp; *dp && !isspace (*dp); dp++)
710                     continue;
711                 *dp = '\0';
712
713                 readDigest (ct, cp);
714                 free (ep);
715                 ct->c_digested++;
716                 goto got_header;
717             }
718
719 #if 0
720             if (uprf (name, XXX_FIELD_PRF))
721                 advise (NULL, "unknown field (%s) in message %s",
722                         name, ct->c_file);
723             /* and fall... */
724 #endif
725
726             while (state == FLDPLUS)
727                 state = m_getfld (state, name, buf, sizeof(buf), in);
728
729 got_header:
730             if (state != FLDEOF) {
731                 ct->c_begin = ftell (in) + 1;
732                 continue;
733             }
734             /* else fall... */
735
736         case BODY:
737         case BODYEOF:
738             ct->c_begin = ftell (in) - strlen (buf);
739             break;
740
741         case FILEEOF:
742             ct->c_begin = ftell (in);
743             break;
744
745         case LENERR:
746         case FMTERR:
747             adios (NULL, "message format error in component #%d", compnum);
748
749         default:
750             adios (NULL, "getfld() returned %d", state);
751         }
752         break;
753     }
754
755     /*
756      * Check if we saw a Content-Type field.
757      * If not, then assign a default value for
758      * it, and the Init function.
759      */
760     if (!ct->c_ctline) {
761         /*
762          * If we are inside a multipart/digest message,
763          * so default type is message/rfc822
764          */
765         if (toplevel < 0) {
766             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
767                 goto out;
768             ct->c_type = CT_MESSAGE;
769             ct->c_ctinitfnx = InitMessage;
770         } else {
771             /*
772              * Else default type is text/plain
773              */
774             if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
775                 goto out;
776             ct->c_type = CT_TEXT;
777             ct->c_ctinitfnx = InitText;
778         }
779     }
780
781     /* Use default Transfer-Encoding, if necessary */
782     if (!ct->c_celine) {
783         ct->c_encoding = CE_7BIT;
784         Init7Bit (ct);
785     }
786
787     return ct;
788
789 out:
790     free_content (ct);
791     return NULL;
792 }
793
794
795 /*
796  * small routine to add header field to list
797  */
798
799 static int
800 add_header (CT ct, char *name, char *value)
801 {
802     HF hp;
803
804     /* allocate header field structure */
805     hp = mh_xmalloc (sizeof(*hp));
806
807     /* link data into header structure */
808     hp->name = name;
809     hp->value = value;
810     hp->next = NULL;
811
812     /* link header structure into the list */
813     if (ct->c_first_hf == NULL) {
814         ct->c_first_hf = hp;            /* this is the first */
815         ct->c_last_hf = hp;
816     } else {
817         ct->c_last_hf->next = hp;       /* add it to the end */
818         ct->c_last_hf = hp;
819     }
820
821     return 0;
822 }
823
824
825 /*
826  * Used to parse both:
827  *   1) Content-Type line
828  *   2) composition directives
829  *
830  * and fills in the information of the CTinfo structure.
831  */
832
833 static int
834 get_ctinfo (unsigned char *cp, CT ct, int magic)
835 {
836     int i;
837     unsigned char *dp;
838     char **ap, **ep;
839     char c;
840     CI ci;
841
842     ci = &ct->c_ctinfo;
843     i = strlen (invo_name) + 2;
844
845     /* store copy of Content-Type line */
846     cp = ct->c_ctline = add (cp, NULL);
847
848     while (isspace (*cp))       /* trim leading spaces */
849         cp++;
850
851     /* change newlines to spaces */
852     for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
853         *dp++ = ' ';
854
855     /* trim trailing spaces */
856     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
857         if (!isspace (*dp))
858             break;
859     *++dp = '\0';
860
861     if (debugsw)
862         fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
863
864     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
865         return NOTOK;
866
867     for (dp = cp; istoken (*dp); dp++)
868         continue;
869     c = *dp, *dp = '\0';
870     ci->ci_type = add (cp, NULL);       /* store content type */
871     *dp = c, cp = dp;
872
873     if (!*ci->ci_type) {
874         advise (NULL, "invalid %s: field in message %s (empty type)", 
875                 TYPE_FIELD, ct->c_file);
876         return NOTOK;
877     }
878
879     /* down case the content type string */
880     for (dp = ci->ci_type; *dp; dp++)
881         if (isalpha(*dp) && isupper (*dp))
882             *dp = tolower (*dp);
883
884     while (isspace (*cp))
885         cp++;
886
887     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
888         return NOTOK;
889
890     if (*cp != '/') {
891         if (!magic)
892             ci->ci_subtype = add ("", NULL);
893         goto magic_skip;
894     }
895
896     cp++;
897     while (isspace (*cp))
898         cp++;
899
900     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
901         return NOTOK;
902
903     for (dp = cp; istoken (*dp); dp++)
904         continue;
905     c = *dp, *dp = '\0';
906     ci->ci_subtype = add (cp, NULL);    /* store the content subtype */
907     *dp = c, cp = dp;
908
909     if (!*ci->ci_subtype) {
910         advise (NULL,
911                 "invalid %s: field in message %s (empty subtype for \"%s\")",
912                 TYPE_FIELD, ct->c_file, ci->ci_type);
913         return NOTOK;
914     }
915
916     /* down case the content subtype string */
917     for (dp = ci->ci_subtype; *dp; dp++)
918         if (isalpha(*dp) && isupper (*dp))
919             *dp = tolower (*dp);
920
921 magic_skip:
922     while (isspace (*cp))
923         cp++;
924
925     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
926         return NOTOK;
927
928     /*
929      * Parse attribute/value pairs given with Content-Type
930      */
931     ep = (ap = ci->ci_attrs) + NPARMS;
932     while (*cp == ';') {
933         char *vp;
934         unsigned char *up;
935
936         if (ap >= ep) {
937             advise (NULL,
938                     "too many parameters in message %s's %s: field (%d max)",
939                     ct->c_file, TYPE_FIELD, NPARMS);
940             return NOTOK;
941         }
942
943         cp++;
944         while (isspace (*cp))
945             cp++;
946
947         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
948             return NOTOK;
949
950         if (*cp == 0) {
951             advise (NULL,
952                     "extraneous trailing ';' in message %s's %s: parameter list",
953                     ct->c_file, TYPE_FIELD);
954             return OK;
955         }
956
957         /* down case the attribute name */
958         for (dp = cp; istoken (*dp); dp++)
959             if (isalpha(*dp) && isupper (*dp))
960                 *dp = tolower (*dp);
961
962         for (up = dp; isspace (*dp); )
963             dp++;
964         if (dp == cp || *dp != '=') {
965             advise (NULL,
966                     "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
967                     ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
968             return NOTOK;
969         }
970
971         vp = (*ap = add (cp, NULL)) + (up - cp);
972         *vp = '\0';
973         for (dp++; isspace (*dp); )
974             dp++;
975
976         /* now add the attribute value */
977         ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
978
979         if (*dp == '"') {
980             for (cp = ++dp, dp = vp;;) {
981                 switch (c = *cp++) {
982                     case '\0':
983 bad_quote:
984                         advise (NULL,
985                                 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
986                                 ct->c_file, TYPE_FIELD, i, i, "", *ap);
987                         return NOTOK;
988
989                     case '\\':
990                         *dp++ = c;
991                         if ((c = *cp++) == '\0')
992                             goto bad_quote;
993                         /* else fall... */
994
995                     default:
996                         *dp++ = c;
997                         continue;
998
999                     case '"':
1000                         *dp = '\0';
1001                         break;
1002                 }
1003                 break;
1004             }
1005         } else {
1006             for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
1007                 continue;
1008             *dp = '\0';
1009         }
1010         if (!*vp) {
1011             advise (NULL,
1012                     "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
1013                     ct->c_file, TYPE_FIELD, i, i, "", *ap);
1014             return NOTOK;
1015         }
1016         ap++;
1017
1018         while (isspace (*cp))
1019             cp++;
1020
1021         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
1022             return NOTOK;
1023     }
1024
1025     /*
1026      * Get any <Content-Id> given in buffer
1027      */
1028     if (magic && *cp == '<') {
1029         if (ct->c_id) {
1030             free (ct->c_id);
1031             ct->c_id = NULL;
1032         }
1033         if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
1034             advise (NULL, "invalid ID in message %s", ct->c_file);
1035             return NOTOK;
1036         }
1037         c = *dp;
1038         *dp = '\0';
1039         if (*ct->c_id)
1040             ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
1041         else
1042             ct->c_id = NULL;
1043         *dp++ = c;
1044         cp = dp;
1045
1046         while (isspace (*cp))
1047             cp++;
1048     }
1049
1050     /*
1051      * Get any [Content-Description] given in buffer.
1052      */
1053     if (magic && *cp == '[') {
1054         ct->c_descr = ++cp;
1055         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1056             if (*dp == ']')
1057                 break;
1058         if (dp < cp) {
1059             advise (NULL, "invalid description in message %s", ct->c_file);
1060             ct->c_descr = NULL;
1061             return NOTOK;
1062         }
1063         
1064         c = *dp;
1065         *dp = '\0';
1066         if (*ct->c_descr)
1067             ct->c_descr = concat (ct->c_descr, "\n", NULL);
1068         else
1069             ct->c_descr = NULL;
1070         *dp++ = c;
1071         cp = dp;
1072
1073         while (isspace (*cp))
1074             cp++;
1075     }
1076
1077     /*
1078      * Get any {Content-Disposition} given in buffer.
1079      */
1080     if (magic && *cp == '{') {
1081         ct->c_dispo = ++cp;
1082         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1083             if (*dp == '}')
1084                 break;
1085         if (dp < cp) {
1086             advise (NULL, "invalid disposition in message %s", ct->c_file);
1087             ct->c_dispo = NULL;
1088             return NOTOK;
1089         }
1090         
1091         c = *dp;
1092         *dp = '\0';
1093         if (*ct->c_dispo)
1094             ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
1095         else
1096             ct->c_dispo = NULL;
1097         *dp++ = c;
1098         cp = dp;
1099
1100         while (isspace (*cp))
1101             cp++;
1102     }
1103
1104     /*
1105      * Check if anything is left over
1106      */
1107     if (*cp) {
1108         if (magic) {
1109             ci->ci_magic = add (cp, NULL);
1110
1111             /* If there is a Content-Disposition header and it doesn't
1112                have a *filename=, extract it from the magic contents.
1113                The r1bindex call skips any leading directory
1114                components. */
1115             if (ct->c_dispo)
1116                 ct->c_dispo =
1117                     incl_name_value (ct->c_dispo,
1118                                      "filename",
1119                                      r1bindex (extract_name_value ("name",
1120                                                                    ci->
1121                                                                    ci_magic),
1122                                                '/'));
1123         }
1124         else
1125             advise (NULL,
1126                     "extraneous information in message %s's %s: field\n%*.*s(%s)",
1127                     ct->c_file, TYPE_FIELD, i, i, "", cp);
1128     }
1129
1130     return OK;
1131 }
1132
1133
1134 static int
1135 get_comment (CT ct, unsigned char **ap, int istype)
1136 {
1137     int i;
1138     char *bp;
1139     unsigned char *cp;
1140     char c, buffer[BUFSIZ], *dp;
1141     CI ci;
1142
1143     ci = &ct->c_ctinfo;
1144     cp = *ap;
1145     bp = buffer;
1146     cp++;
1147
1148     for (i = 0;;) {
1149         switch (c = *cp++) {
1150         case '\0':
1151 invalid:
1152         advise (NULL, "invalid comment in message %s's %s: field",
1153                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
1154         return NOTOK;
1155
1156         case '\\':
1157             *bp++ = c;
1158             if ((c = *cp++) == '\0')
1159                 goto invalid;
1160             *bp++ = c;
1161             continue;
1162
1163         case '(':
1164             i++;
1165             /* and fall... */
1166         default:
1167             *bp++ = c;
1168             continue;
1169
1170         case ')':
1171             if (--i < 0)
1172                 break;
1173             *bp++ = c;
1174             continue;
1175         }
1176         break;
1177     }
1178     *bp = '\0';
1179
1180     if (istype) {
1181         if ((dp = ci->ci_comment)) {
1182             ci->ci_comment = concat (dp, " ", buffer, NULL);
1183             free (dp);
1184         } else {
1185             ci->ci_comment = add (buffer, NULL);
1186         }
1187     }
1188
1189     while (isspace (*cp))
1190         cp++;
1191
1192     *ap = cp;
1193     return OK;
1194 }
1195
1196
1197 /*
1198  * CONTENTS
1199  *
1200  * Handles content types audio, image, and video.
1201  * There's not much to do right here.
1202  */
1203
1204 static int
1205 InitGeneric (CT ct)
1206 {
1207     return OK;          /* not much to do here */
1208 }
1209
1210
1211 /*
1212  * TEXT
1213  */
1214
1215 static int
1216 InitText (CT ct)
1217 {
1218     char **ap, **ep;
1219     struct k2v *kv;
1220     struct text *t;
1221     CI ci = &ct->c_ctinfo;
1222
1223     /* check for missing subtype */
1224     if (!*ci->ci_subtype)
1225         ci->ci_subtype = add ("plain", ci->ci_subtype);
1226
1227     /* match subtype */
1228     for (kv = SubText; kv->kv_key; kv++)
1229         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1230             break;
1231     ct->c_subtype = kv->kv_value;
1232
1233     /* allocate text character set structure */
1234     if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1235         adios (NULL, "out of memory");
1236     ct->c_ctparams = (void *) t;
1237
1238     /* initially mark character set as unspecified */
1239     t->tx_charset = CHARSET_UNSPECIFIED;
1240
1241     /* scan for charset parameter */
1242     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1243         if (!mh_strcasecmp (*ap, "charset"))
1244             break;
1245
1246     /* check if content specified a character set */
1247     if (*ap) {
1248         /* match character set or set to CHARSET_UNKNOWN */
1249         for (kv = Charset; kv->kv_key; kv++)
1250             if (!mh_strcasecmp (*ep, kv->kv_key))
1251                 break;
1252         t->tx_charset = kv->kv_value;
1253     }
1254
1255     return OK;
1256 }
1257
1258
1259 /*
1260  * MULTIPART
1261  */
1262
1263 static int
1264 InitMultiPart (CT ct)
1265 {
1266     int inout;
1267     long last, pos;
1268     unsigned char *cp, *dp;
1269     char **ap, **ep;
1270     char *bp, buffer[BUFSIZ];
1271     struct multipart *m;
1272     struct k2v *kv;
1273     struct part *part, **next;
1274     CI ci = &ct->c_ctinfo;
1275     CT p;
1276     FILE *fp;
1277
1278     /*
1279      * The encoding for multipart messages must be either
1280      * 7bit, 8bit, or binary (per RFC2045).
1281      */
1282     if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1283         && ct->c_encoding != CE_BINARY) {
1284         admonish (NULL,
1285                   "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1286                   ci->ci_type, ci->ci_subtype, ct->c_file);
1287         return NOTOK;
1288     }
1289
1290     /* match subtype */
1291     for (kv = SubMultiPart; kv->kv_key; kv++)
1292         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1293             break;
1294     ct->c_subtype = kv->kv_value;
1295
1296     /*
1297      * Check for "boundary" parameter, which is
1298      * required for multipart messages.
1299      */
1300     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1301         if (!mh_strcasecmp (*ap, "boundary")) {
1302             bp = *ep;
1303             break;
1304         }
1305     }
1306
1307     /* complain if boundary parameter is missing */
1308     if (!*ap) {
1309         advise (NULL,
1310                 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1311                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1312         return NOTOK;
1313     }
1314
1315     /* allocate primary structure for multipart info */
1316     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1317         adios (NULL, "out of memory");
1318     ct->c_ctparams = (void *) m;
1319
1320     /* check if boundary parameter contains only whitespace characters */
1321     for (cp = bp; isspace (*cp); cp++)
1322         continue;
1323     if (!*cp) {
1324         advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1325                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1326         return NOTOK;
1327     }
1328
1329     /* remove trailing whitespace from boundary parameter */
1330     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1331         if (!isspace (*dp))
1332             break;
1333     *++dp = '\0';
1334
1335     /* record boundary separators */
1336     m->mp_start = concat (bp, "\n", NULL);
1337     m->mp_stop = concat (bp, "--\n", NULL);
1338
1339     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1340         advise (ct->c_file, "unable to open for reading");
1341         return NOTOK;
1342     }
1343
1344     fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1345     last = ct->c_end;
1346     next = &m->mp_parts;
1347     part = NULL;
1348     inout = 1;
1349
1350     while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1351         if (pos > last)
1352             break;
1353
1354         pos += strlen (buffer);
1355         if (buffer[0] != '-' || buffer[1] != '-')
1356             continue;
1357         if (inout) {
1358             if (strcmp (buffer + 2, m->mp_start))
1359                 continue;
1360 next_part:
1361             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1362                 adios (NULL, "out of memory");
1363             *next = part;
1364             next = &part->mp_next;
1365
1366             if (!(p = get_content (fp, ct->c_file,
1367                 rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1368                 fclose (ct->c_fp);
1369                 ct->c_fp = NULL;
1370                 return NOTOK;
1371             }
1372             p->c_fp = NULL;
1373             part->mp_part = p;
1374             pos = p->c_begin;
1375             fseek (fp, pos, SEEK_SET);
1376             inout = 0;
1377         } else {
1378             if (strcmp (buffer + 2, m->mp_start) == 0) {
1379                 inout = 1;
1380 end_part:
1381                 p = part->mp_part;
1382                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1383                 if (p->c_end < p->c_begin)
1384                     p->c_begin = p->c_end;
1385                 if (inout)
1386                     goto next_part;
1387                 goto last_part;
1388             } else {
1389                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1390                     goto end_part;
1391             }
1392         }
1393     }
1394
1395     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1396     if (!inout && part) {
1397         p = part->mp_part;
1398         p->c_end = ct->c_end;
1399
1400         if (p->c_begin >= p->c_end) {
1401             for (next = &m->mp_parts; *next != part;
1402                      next = &((*next)->mp_next))
1403                 continue;
1404             *next = NULL;
1405             free_content (p);
1406             free ((char *) part);
1407         }
1408     }
1409
1410 last_part:
1411     /* reverse the order of the parts for multipart/alternative */
1412     if (ct->c_subtype == MULTI_ALTERNATE)
1413         reverse_parts (ct);
1414
1415     /*
1416      * label all subparts with part number, and
1417      * then initialize the content of the subpart.
1418      */
1419     {
1420         int partnum;
1421         char *pp;
1422         char partnam[BUFSIZ];
1423
1424         if (ct->c_partno) {
1425             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1426             pp = partnam + strlen (partnam);
1427         } else {
1428             pp = partnam;
1429         }
1430
1431         for (part = m->mp_parts, partnum = 1; part;
1432                  part = part->mp_next, partnum++) {
1433             p = part->mp_part;
1434
1435             sprintf (pp, "%d", partnum);
1436             p->c_partno = add (partnam, NULL);
1437
1438             /* initialize the content of the subparts */
1439             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1440                 fclose (ct->c_fp);
1441                 ct->c_fp = NULL;
1442                 return NOTOK;
1443             }
1444         }
1445     }
1446
1447     fclose (ct->c_fp);
1448     ct->c_fp = NULL;
1449     return OK;
1450 }
1451
1452
1453 /*
1454  * reverse the order of the parts of a multipart
1455  */
1456
1457 static void
1458 reverse_parts (CT ct)
1459 {
1460     int i;
1461     struct multipart *m;
1462     struct part **base, **bmp, **next, *part;
1463
1464     m = (struct multipart *) ct->c_ctparams;
1465
1466     /* if only one part, just return */
1467     if (!m->mp_parts || !m->mp_parts->mp_next)
1468         return;
1469
1470     /* count number of parts */
1471     i = 0;
1472     for (part = m->mp_parts; part; part = part->mp_next)
1473         i++;
1474
1475     /* allocate array of pointers to the parts */
1476     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1477         adios (NULL, "out of memory");
1478     bmp = base;
1479
1480     /* point at all the parts */
1481     for (part = m->mp_parts; part; part = part->mp_next)
1482         *bmp++ = part;
1483     *bmp = NULL;
1484
1485     /* reverse the order of the parts */
1486     next = &m->mp_parts;
1487     for (bmp--; bmp >= base; bmp--) {
1488         part = *bmp;
1489         *next = part;
1490         next = &part->mp_next;
1491     }
1492     *next = NULL;
1493
1494     /* free array of pointers */
1495     free ((char *) base);
1496 }
1497
1498
1499 /*
1500  * MESSAGE
1501  */
1502
1503 static int
1504 InitMessage (CT ct)
1505 {
1506     struct k2v *kv;
1507     CI ci = &ct->c_ctinfo;
1508
1509     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1510         admonish (NULL,
1511                   "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1512                   ci->ci_type, ci->ci_subtype, ct->c_file);
1513         return NOTOK;
1514     }
1515
1516     /* check for missing subtype */
1517     if (!*ci->ci_subtype)
1518         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1519
1520     /* match subtype */
1521     for (kv = SubMessage; kv->kv_key; kv++)
1522         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1523             break;
1524     ct->c_subtype = kv->kv_value;
1525
1526     switch (ct->c_subtype) {
1527         case MESSAGE_RFC822:
1528             break;
1529
1530         case MESSAGE_PARTIAL:
1531             {
1532                 char **ap, **ep;
1533                 struct partial *p;
1534
1535                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1536                     adios (NULL, "out of memory");
1537                 ct->c_ctparams = (void *) p;
1538
1539                 /* scan for parameters "id", "number", and "total" */
1540                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1541                     if (!mh_strcasecmp (*ap, "id")) {
1542                         p->pm_partid = add (*ep, NULL);
1543                         continue;
1544                     }
1545                     if (!mh_strcasecmp (*ap, "number")) {
1546                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1547                                 || p->pm_partno < 1) {
1548 invalid_param:
1549                             advise (NULL,
1550                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1551                                     *ap, ci->ci_type, ci->ci_subtype,
1552                                     ct->c_file, TYPE_FIELD);
1553                             return NOTOK;
1554                         }
1555                         continue;
1556                     }
1557                     if (!mh_strcasecmp (*ap, "total")) {
1558                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1559                                 || p->pm_maxno < 1)
1560                             goto invalid_param;
1561                         continue;
1562                     }
1563                 }
1564
1565                 if (!p->pm_partid
1566                         || !p->pm_partno
1567                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1568                     advise (NULL,
1569                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1570                             ci->ci_type, ci->ci_subtype,
1571                             ct->c_file, TYPE_FIELD);
1572                     return NOTOK;
1573                 }
1574             }
1575             break;
1576
1577         case MESSAGE_EXTERNAL:
1578             {
1579                 int exresult;
1580                 struct exbody *e;
1581                 CT p;
1582                 FILE *fp;
1583
1584                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1585                     adios (NULL, "out of memory");
1586                 ct->c_ctparams = (void *) e;
1587
1588                 if (!ct->c_fp
1589                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1590                     advise (ct->c_file, "unable to open for reading");
1591                     return NOTOK;
1592                 }
1593
1594                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1595
1596                 if (!(p = get_content (fp, ct->c_file, 0))) {
1597                     fclose (ct->c_fp);
1598                     ct->c_fp = NULL;
1599                     return NOTOK;
1600                 }
1601
1602                 e->eb_parent = ct;
1603                 e->eb_content = p;
1604                 p->c_ctexbody = e;
1605                 if ((exresult = params_external (ct, 0)) != NOTOK
1606                         && p->c_ceopenfnx == openMail) {
1607                     int cc, size;
1608                     char *bp;
1609                     
1610                     if ((size = ct->c_end - p->c_begin) <= 0) {
1611                         if (!e->eb_subject)
1612                             content_error (NULL, ct,
1613                                            "empty body for access-type=mail-server");
1614                         goto no_body;
1615                     }
1616                     
1617                     e->eb_body = bp = mh_xmalloc ((unsigned) size);
1618                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1619                     while (size > 0)
1620                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1621                             case NOTOK:
1622                                 adios ("failed", "fread");
1623
1624                             case OK:
1625                                 adios (NULL, "unexpected EOF from fread");
1626
1627                             default:
1628                                 bp += cc, size -= cc;
1629                                 break;
1630                         }
1631                     *bp = 0;
1632                 }
1633 no_body:
1634                 p->c_fp = NULL;
1635                 p->c_end = p->c_begin;
1636
1637                 fclose (ct->c_fp);
1638                 ct->c_fp = NULL;
1639
1640                 if (exresult == NOTOK)
1641                     return NOTOK;
1642                 if (e->eb_flags == NOTOK)
1643                     return OK;
1644
1645                 switch (p->c_type) {
1646                     case CT_MULTIPART:
1647                         break;
1648
1649                     case CT_MESSAGE:
1650                         if (p->c_subtype != MESSAGE_RFC822)
1651                             break;
1652                         /* else fall... */
1653                     default:
1654                         e->eb_partno = ct->c_partno;
1655                         if (p->c_ctinitfnx)
1656                             (*p->c_ctinitfnx) (p);
1657                         break;
1658                 }
1659             }
1660             break;
1661
1662         default:
1663             break;
1664     }
1665
1666     return OK;
1667 }
1668
1669
1670 static int
1671 params_external (CT ct, int composing)
1672 {
1673     char **ap, **ep;
1674     struct exbody *e = (struct exbody *) ct->c_ctparams;
1675     CI ci = &ct->c_ctinfo;
1676
1677     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1678         if (!mh_strcasecmp (*ap, "access-type")) {
1679             struct str2init *s2i;
1680             CT p = e->eb_content;
1681
1682             for (s2i = str2methods; s2i->si_key; s2i++)
1683                 if (!mh_strcasecmp (*ep, s2i->si_key))
1684                     break;
1685
1686             if (!s2i->si_key) {
1687                 e->eb_access = *ep;
1688                 e->eb_flags = NOTOK;
1689                 p->c_encoding = CE_EXTERNAL;
1690                 continue;
1691             }
1692             e->eb_access = s2i->si_key;
1693             e->eb_flags = s2i->si_val;
1694             p->c_encoding = CE_EXTERNAL;
1695
1696             /* Call the Init function for this external type */
1697             if ((*s2i->si_init)(p) == NOTOK)
1698                 return NOTOK;
1699             continue;
1700         }
1701         if (!mh_strcasecmp (*ap, "name")) {
1702             e->eb_name = *ep;
1703             continue;
1704         }
1705         if (!mh_strcasecmp (*ap, "permission")) {
1706             e->eb_permission = *ep;
1707             continue;
1708         }
1709         if (!mh_strcasecmp (*ap, "site")) {
1710             e->eb_site = *ep;
1711             continue;
1712         }
1713         if (!mh_strcasecmp (*ap, "directory")) {
1714             e->eb_dir = *ep;
1715             continue;
1716         }
1717         if (!mh_strcasecmp (*ap, "mode")) {
1718             e->eb_mode = *ep;
1719             continue;
1720         }
1721         if (!mh_strcasecmp (*ap, "size")) {
1722             sscanf (*ep, "%lu", &e->eb_size);
1723             continue;
1724         }
1725         if (!mh_strcasecmp (*ap, "server")) {
1726             e->eb_server = *ep;
1727             continue;
1728         }
1729         if (!mh_strcasecmp (*ap, "subject")) {
1730             e->eb_subject = *ep;
1731             continue;
1732         }
1733         if (composing && !mh_strcasecmp (*ap, "body")) {
1734             e->eb_body = getcpy (*ep);
1735             continue;
1736         }
1737     }
1738
1739     if (!e->eb_access) {
1740         advise (NULL,
1741                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1742                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1743         return NOTOK;
1744     }
1745
1746     return OK;
1747 }
1748
1749
1750 /*
1751  * APPLICATION
1752  */
1753
1754 static int
1755 InitApplication (CT ct)
1756 {
1757     struct k2v *kv;
1758     CI ci = &ct->c_ctinfo;
1759
1760     /* match subtype */
1761     for (kv = SubApplication; kv->kv_key; kv++)
1762         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1763             break;
1764     ct->c_subtype = kv->kv_value;
1765
1766     return OK;
1767 }
1768
1769
1770 /*
1771  * Set up structures for placing unencoded
1772  * content when building parts.
1773  */
1774
1775 static int
1776 init_decoded_content (CT ct)
1777 {
1778     CE ce;
1779
1780     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1781         adios (NULL, "out of memory");
1782
1783     ct->c_cefile     = ce;
1784     ct->c_ceopenfnx  = open7Bit;        /* since unencoded */
1785     ct->c_ceclosefnx = close_encoding;
1786     ct->c_cesizefnx  = NULL;            /* since unencoded */
1787
1788     return OK;
1789 }
1790
1791
1792 /*
1793  * TRANSFER ENCODINGS
1794  */
1795
1796 static int
1797 init_encoding (CT ct, OpenCEFunc openfnx)
1798 {
1799     CE ce;
1800
1801     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1802         adios (NULL, "out of memory");
1803
1804     ct->c_cefile     = ce;
1805     ct->c_ceopenfnx  = openfnx;
1806     ct->c_ceclosefnx = close_encoding;
1807     ct->c_cesizefnx  = size_encoding;
1808
1809     return OK;
1810 }
1811
1812
1813 static void
1814 close_encoding (CT ct)
1815 {
1816     CE ce;
1817
1818     if (!(ce = ct->c_cefile))
1819         return;
1820
1821     if (ce->ce_fp) {
1822         fclose (ce->ce_fp);
1823         ce->ce_fp = NULL;
1824     }
1825 }
1826
1827
1828 static unsigned long
1829 size_encoding (CT ct)
1830 {
1831     int fd;
1832     unsigned long size;
1833     char *file;
1834     CE ce;
1835     struct stat st;
1836
1837     if (!(ce = ct->c_cefile))
1838         return (ct->c_end - ct->c_begin);
1839
1840     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1841         return (long) st.st_size;
1842
1843     if (ce->ce_file) {
1844         if (stat (ce->ce_file, &st) != NOTOK)
1845             return (long) st.st_size;
1846         else
1847             return 0L;
1848     }
1849
1850     if (ct->c_encoding == CE_EXTERNAL)
1851         return (ct->c_end - ct->c_begin);       
1852
1853     file = NULL;
1854     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1855         return (ct->c_end - ct->c_begin);
1856
1857     if (fstat (fd, &st) != NOTOK)
1858         size = (long) st.st_size;
1859     else
1860         size = 0L;
1861
1862     (*ct->c_ceclosefnx) (ct);
1863     return size;
1864 }
1865
1866
1867 /*
1868  * BASE64
1869  */
1870
1871 static unsigned char b642nib[0x80] = {
1872     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1873     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1874     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1875     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1876     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1877     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1878     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1879     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1880     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1881     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1882     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1883     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1884     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1885     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1886     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1887     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1888 };
1889
1890
1891 static int
1892 InitBase64 (CT ct)
1893 {
1894     return init_encoding (ct, openBase64);
1895 }
1896
1897
1898 static int
1899 openBase64 (CT ct, char **file)
1900 {
1901     int bitno, cc, digested;
1902     int fd, len, skip;
1903     unsigned long bits;
1904     unsigned char value, *b, *b1, *b2, *b3;
1905     unsigned char *cp, *ep;
1906     char buffer[BUFSIZ];
1907     CE ce;
1908     MD5_CTX mdContext;
1909
1910     b  = (unsigned char *) &bits;
1911     b1 = &b[endian > 0 ? 1 : 2];
1912     b2 = &b[endian > 0 ? 2 : 1];
1913     b3 = &b[endian > 0 ? 3 : 0];
1914
1915     ce = ct->c_cefile;
1916     if (ce->ce_fp) {
1917         fseek (ce->ce_fp, 0L, SEEK_SET);
1918         goto ready_to_go;
1919     }
1920
1921     if (ce->ce_file) {
1922         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1923             content_error (ce->ce_file, ct, "unable to fopen for reading");
1924             return NOTOK;
1925         }
1926         goto ready_to_go;
1927     }
1928
1929     if (*file == NULL) {
1930         ce->ce_file = add (m_scratch ("", tmp), NULL);
1931         ce->ce_unlink = 1;
1932     } else {
1933         ce->ce_file = add (*file, NULL);
1934         ce->ce_unlink = 0;
1935     }
1936
1937     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1938         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1939         return NOTOK;
1940     }
1941
1942     if ((len = ct->c_end - ct->c_begin) < 0)
1943         adios (NULL, "internal error(1)");
1944
1945     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1946         content_error (ct->c_file, ct, "unable to open for reading");
1947         return NOTOK;
1948     }
1949     
1950     if ((digested = ct->c_digested))
1951         MD5Init (&mdContext);
1952
1953     bitno = 18;
1954     bits = 0L;
1955     skip = 0;
1956
1957     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1958     while (len > 0) {
1959         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1960         case NOTOK:
1961             content_error (ct->c_file, ct, "error reading from");
1962             goto clean_up;
1963
1964         case OK:
1965             content_error (NULL, ct, "premature eof");
1966             goto clean_up;
1967
1968         default:
1969             if (cc > len)
1970                 cc = len;
1971             len -= cc;
1972
1973             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1974                 switch (*cp) {
1975                 default:
1976                     if (isspace (*cp))
1977                         break;
1978                     if (skip || (*cp & 0x80)
1979                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1980                         if (debugsw) {
1981                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1982                                 *cp,
1983                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1984                                 skip);
1985                         }
1986                         content_error (NULL, ct,
1987                                        "invalid BASE64 encoding -- continuing");
1988                         continue;
1989                     }
1990
1991                     bits |= value << bitno;
1992 test_end:
1993                     if ((bitno -= 6) < 0) {
1994                         putc ((char) *b1, ce->ce_fp);
1995                         if (digested)
1996                             MD5Update (&mdContext, b1, 1);
1997                         if (skip < 2) {
1998                             putc ((char) *b2, ce->ce_fp);
1999                             if (digested)
2000                                 MD5Update (&mdContext, b2, 1);
2001                             if (skip < 1) {
2002                                 putc ((char) *b3, ce->ce_fp);
2003                                 if (digested)
2004                                     MD5Update (&mdContext, b3, 1);
2005                             }
2006                         }
2007
2008                         if (ferror (ce->ce_fp)) {
2009                             content_error (ce->ce_file, ct,
2010                                            "error writing to");
2011                             goto clean_up;
2012                         }
2013                         bitno = 18, bits = 0L, skip = 0;
2014                     }
2015                     break;
2016
2017                 case '=':
2018                     if (++skip > 3)
2019                         goto self_delimiting;
2020                     goto test_end;
2021                 }
2022             }
2023         }
2024     }
2025
2026     if (bitno != 18) {
2027         if (debugsw)
2028             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2029
2030         content_error (NULL, ct, "invalid BASE64 encoding");
2031         goto clean_up;
2032     }
2033
2034 self_delimiting:
2035     fseek (ct->c_fp, 0L, SEEK_SET);
2036
2037     if (fflush (ce->ce_fp)) {
2038         content_error (ce->ce_file, ct, "error writing to");
2039         goto clean_up;
2040     }
2041
2042     if (digested) {
2043         unsigned char digest[16];
2044
2045         MD5Final (digest, &mdContext);
2046         if (memcmp((char *) digest, (char *) ct->c_digest,
2047                    sizeof(digest) / sizeof(digest[0])))
2048             content_error (NULL, ct,
2049                            "content integrity suspect (digest mismatch) -- continuing");
2050         else
2051             if (debugsw)
2052                 fprintf (stderr, "content integrity confirmed\n");
2053     }
2054
2055     fseek (ce->ce_fp, 0L, SEEK_SET);
2056
2057 ready_to_go:
2058     *file = ce->ce_file;
2059     return fileno (ce->ce_fp);
2060
2061 clean_up:
2062     free_encoding (ct, 0);
2063     return NOTOK;
2064 }
2065
2066
2067 /*
2068  * QUOTED PRINTABLE
2069  */
2070
2071 static char hex2nib[0x80] = {
2072     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2073     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2074     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2075     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2076     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2077     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2078     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2079     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2080     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
2081     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2082     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2083     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2084     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
2085     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2086     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2087     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
2088 };
2089
2090
2091 static int 
2092 InitQuoted (CT ct)
2093 {
2094     return init_encoding (ct, openQuoted);
2095 }
2096
2097
2098 static int
2099 openQuoted (CT ct, char **file)
2100 {
2101     int cc, digested, len, quoted;
2102     unsigned char *cp, *ep;
2103     char buffer[BUFSIZ];
2104     unsigned char mask;
2105     CE ce;
2106     MD5_CTX mdContext;
2107
2108     ce = ct->c_cefile;
2109     if (ce->ce_fp) {
2110         fseek (ce->ce_fp, 0L, SEEK_SET);
2111         goto ready_to_go;
2112     }
2113
2114     if (ce->ce_file) {
2115         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2116             content_error (ce->ce_file, ct, "unable to fopen for reading");
2117             return NOTOK;
2118         }
2119         goto ready_to_go;
2120     }
2121
2122     if (*file == NULL) {
2123         ce->ce_file = add (m_scratch ("", tmp), NULL);
2124         ce->ce_unlink = 1;
2125     } else {
2126         ce->ce_file = add (*file, NULL);
2127         ce->ce_unlink = 0;
2128     }
2129
2130     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2131         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2132         return NOTOK;
2133     }
2134
2135     if ((len = ct->c_end - ct->c_begin) < 0)
2136         adios (NULL, "internal error(2)");
2137
2138     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2139         content_error (ct->c_file, ct, "unable to open for reading");
2140         return NOTOK;
2141     }
2142
2143     if ((digested = ct->c_digested))
2144         MD5Init (&mdContext);
2145
2146     quoted = 0;
2147 #ifdef lint
2148     mask = 0;
2149 #endif
2150
2151     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2152     while (len > 0) {
2153         char *dp;
2154
2155         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2156             content_error (NULL, ct, "premature eof");
2157             goto clean_up;
2158         }
2159
2160         if ((cc = strlen (buffer)) > len)
2161             cc = len;
2162         len -= cc;
2163
2164         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2165             if (!isspace (*ep))
2166                 break;
2167         *++ep = '\n', ep++;
2168
2169         for (; cp < ep; cp++) {
2170             if (quoted) {
2171                 if (quoted > 1) {
2172                     if (!isxdigit (*cp)) {
2173 invalid_hex:
2174                         dp = "expecting hexidecimal-digit";
2175                         goto invalid_encoding;
2176                     }
2177                     mask <<= 4;
2178                     mask |= hex2nib[*cp & 0x7f];
2179                     putc (mask, ce->ce_fp);
2180                     if (digested)
2181                         MD5Update (&mdContext, &mask, 1);
2182                 } else {
2183                     switch (*cp) {
2184                     case ':':
2185                         putc (*cp, ce->ce_fp);
2186                         if (digested)
2187                             MD5Update (&mdContext, (unsigned char *) ":", 1);
2188                         break;
2189
2190                     default:
2191                         if (!isxdigit (*cp))
2192                             goto invalid_hex;
2193                         mask = hex2nib[*cp & 0x7f];
2194                         quoted = 2;
2195                         continue;
2196                     }
2197                 }
2198
2199                 if (ferror (ce->ce_fp)) {
2200                     content_error (ce->ce_file, ct, "error writing to");
2201                     goto clean_up;
2202                 }
2203                 quoted = 0;
2204                 continue;
2205             }
2206
2207             switch (*cp) {
2208             default:
2209                 if (*cp < '!' || *cp > '~') {
2210                     int i;
2211                     dp = "expecting character in range [!..~]";
2212
2213 invalid_encoding:
2214                     i = strlen (invo_name) + 2;
2215                     content_error (NULL, ct,
2216                                    "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
2217                                    dp, i, i, "", *cp);
2218                     goto clean_up;
2219                 }
2220                 /* and fall...*/
2221             case ' ':
2222             case '\t':
2223             case '\n':
2224                 putc (*cp, ce->ce_fp);
2225                 if (digested) {
2226                     if (*cp == '\n')
2227                         MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2228                     else
2229                         MD5Update (&mdContext, (unsigned char *) cp, 1);
2230                 }
2231                 if (ferror (ce->ce_fp)) {
2232                     content_error (ce->ce_file, ct, "error writing to");
2233                     goto clean_up;
2234                 }
2235                 break;
2236
2237             case '=':
2238                 if (*++cp != '\n') {
2239                     quoted = 1;
2240                     cp--;
2241                 }
2242                 break;
2243             }
2244         }
2245     }
2246     if (quoted) {
2247         content_error (NULL, ct,
2248                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2249         goto clean_up;
2250     }
2251
2252     fseek (ct->c_fp, 0L, SEEK_SET);
2253
2254     if (fflush (ce->ce_fp)) {
2255         content_error (ce->ce_file, ct, "error writing to");
2256         goto clean_up;
2257     }
2258
2259     if (digested) {
2260         unsigned char digest[16];
2261
2262         MD5Final (digest, &mdContext);
2263         if (memcmp((char *) digest, (char *) ct->c_digest,
2264                    sizeof(digest) / sizeof(digest[0])))
2265             content_error (NULL, ct,
2266                            "content integrity suspect (digest mismatch) -- continuing");
2267         else
2268             if (debugsw)
2269                 fprintf (stderr, "content integrity confirmed\n");
2270     }
2271
2272     fseek (ce->ce_fp, 0L, SEEK_SET);
2273
2274 ready_to_go:
2275     *file = ce->ce_file;
2276     return fileno (ce->ce_fp);
2277
2278 clean_up:
2279     free_encoding (ct, 0);
2280     return NOTOK;
2281 }
2282
2283
2284 /*
2285  * 7BIT
2286  */
2287
2288 static int
2289 Init7Bit (CT ct)
2290 {
2291     if (init_encoding (ct, open7Bit) == NOTOK)
2292         return NOTOK;
2293
2294     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2295     return OK;
2296 }
2297
2298
2299 static int
2300 open7Bit (CT ct, char **file)
2301 {
2302     int cc, fd, len;
2303     char buffer[BUFSIZ];
2304     CE ce;
2305
2306     ce = ct->c_cefile;
2307     if (ce->ce_fp) {
2308         fseek (ce->ce_fp, 0L, SEEK_SET);
2309         goto ready_to_go;
2310     }
2311
2312     if (ce->ce_file) {
2313         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2314             content_error (ce->ce_file, ct, "unable to fopen for reading");
2315             return NOTOK;
2316         }
2317         goto ready_to_go;
2318     }
2319
2320     if (*file == NULL) {
2321         ce->ce_file = add (m_scratch ("", tmp), NULL);
2322         ce->ce_unlink = 1;
2323     } else {
2324         ce->ce_file = add (*file, NULL);
2325         ce->ce_unlink = 0;
2326     }
2327
2328     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2329         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2330         return NOTOK;
2331     }
2332
2333     if (ct->c_type == CT_MULTIPART) {
2334         char **ap, **ep;
2335         CI ci = &ct->c_ctinfo;
2336
2337         len = 0;
2338         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2339         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2340             + 1 + strlen (ci->ci_subtype);
2341         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2342             putc (';', ce->ce_fp);
2343             len++;
2344
2345             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2346
2347             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2348                 fputs ("\n\t", ce->ce_fp);
2349                 len = 8;
2350             } else {
2351                 putc (' ', ce->ce_fp);
2352                 len++;
2353             }
2354             fprintf (ce->ce_fp, "%s", buffer);
2355             len += cc;
2356         }
2357
2358         if (ci->ci_comment) {
2359             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2360                 fputs ("\n\t", ce->ce_fp);
2361                 len = 8;
2362             }
2363             else {
2364                 putc (' ', ce->ce_fp);
2365                 len++;
2366             }
2367             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2368             len += cc;
2369         }
2370         fprintf (ce->ce_fp, "\n");
2371         if (ct->c_id)
2372             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2373         if (ct->c_descr)
2374             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2375         if (ct->c_dispo)
2376             fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2377         fprintf (ce->ce_fp, "\n");
2378     }
2379
2380     if ((len = ct->c_end - ct->c_begin) < 0)
2381         adios (NULL, "internal error(3)");
2382
2383     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2384         content_error (ct->c_file, ct, "unable to open for reading");
2385         return NOTOK;
2386     }
2387
2388     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2389     while (len > 0)
2390         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2391         case NOTOK:
2392             content_error (ct->c_file, ct, "error reading from");
2393             goto clean_up;
2394
2395         case OK:
2396             content_error (NULL, ct, "premature eof");
2397             goto clean_up;
2398
2399         default:
2400             if (cc > len)
2401                 cc = len;
2402             len -= cc;
2403
2404             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2405             if (ferror (ce->ce_fp)) {
2406                 content_error (ce->ce_file, ct, "error writing to");
2407                 goto clean_up;
2408             }
2409         }
2410
2411     fseek (ct->c_fp, 0L, SEEK_SET);
2412
2413     if (fflush (ce->ce_fp)) {
2414         content_error (ce->ce_file, ct, "error writing to");
2415         goto clean_up;
2416     }
2417
2418     fseek (ce->ce_fp, 0L, SEEK_SET);
2419
2420 ready_to_go:
2421     *file = ce->ce_file;
2422     return fileno (ce->ce_fp);
2423
2424 clean_up:
2425     free_encoding (ct, 0);
2426     return NOTOK;
2427 }
2428
2429
2430 /*
2431  * External
2432  */
2433
2434 static int
2435 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2436 {
2437     char cachefile[BUFSIZ];
2438
2439     if (ce->ce_fp) {
2440         fseek (ce->ce_fp, 0L, SEEK_SET);
2441         goto ready_already;
2442     }
2443
2444     if (ce->ce_file) {
2445         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2446             content_error (ce->ce_file, ct, "unable to fopen for reading");
2447             return NOTOK;
2448         }
2449         goto ready_already;
2450     }
2451
2452     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2453                 cachefile, sizeof(cachefile)) != NOTOK) {
2454         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2455             ce->ce_file = getcpy (cachefile);
2456             ce->ce_unlink = 0;
2457             goto ready_already;
2458         } else {
2459             admonish (cachefile, "unable to fopen for reading");
2460         }
2461     }
2462
2463     return OK;
2464
2465 ready_already:
2466     *file = ce->ce_file;
2467     *fd = fileno (ce->ce_fp);
2468     return DONE;
2469 }
2470
2471 /*
2472  * File
2473  */
2474
2475 static int
2476 InitFile (CT ct)
2477 {
2478     return init_encoding (ct, openFile);
2479 }
2480
2481
2482 static int
2483 openFile (CT ct, char **file)
2484 {
2485     int fd, cachetype;
2486     char cachefile[BUFSIZ];
2487     struct exbody *e = ct->c_ctexbody;
2488     CE ce = ct->c_cefile;
2489
2490     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2491         case NOTOK:
2492             return NOTOK;
2493
2494         case OK:
2495             break;
2496
2497         case DONE:
2498             return fd;
2499     }
2500
2501     if (!e->eb_name) {
2502         content_error (NULL, ct, "missing name parameter");
2503         return NOTOK;
2504     }
2505
2506     ce->ce_file = getcpy (e->eb_name);
2507     ce->ce_unlink = 0;
2508
2509     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2510         content_error (ce->ce_file, ct, "unable to fopen for reading");
2511         return NOTOK;
2512     }
2513
2514     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2515             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2516                 cachefile, sizeof(cachefile)) != NOTOK) {
2517         int mask;
2518         FILE *fp;
2519
2520         mask = umask (cachetype ? ~m_gmprot () : 0222);
2521         if ((fp = fopen (cachefile, "w"))) {
2522             int cc;
2523             char buffer[BUFSIZ];
2524             FILE *gp = ce->ce_fp;
2525
2526             fseek (gp, 0L, SEEK_SET);
2527
2528             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2529                        > 0)
2530                 fwrite (buffer, sizeof(*buffer), cc, fp);
2531             fflush (fp);
2532
2533             if (ferror (gp)) {
2534                 admonish (ce->ce_file, "error reading");
2535                 unlink (cachefile);
2536             }
2537             else
2538                 if (ferror (fp)) {
2539                     admonish (cachefile, "error writing");
2540                     unlink (cachefile);
2541                 }
2542             fclose (fp);
2543         }
2544         umask (mask);
2545     }
2546
2547     fseek (ce->ce_fp, 0L, SEEK_SET);
2548     *file = ce->ce_file;
2549     return fileno (ce->ce_fp);
2550 }
2551
2552 /*
2553  * FTP
2554  */
2555
2556 static int
2557 InitFTP (CT ct)
2558 {
2559     return init_encoding (ct, openFTP);
2560 }
2561
2562
2563 static int
2564 openFTP (CT ct, char **file)
2565 {
2566     int cachetype, caching, fd;
2567     int len, buflen;
2568     char *bp, *ftp, *user, *pass;
2569     char buffer[BUFSIZ], cachefile[BUFSIZ];
2570     struct exbody *e;
2571     CE ce;
2572     static char *username = NULL;
2573     static char *password = NULL;
2574
2575     e  = ct->c_ctexbody;
2576     ce = ct->c_cefile;
2577
2578     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2579         ftp = NULL;
2580
2581 #ifndef BUILTIN_FTP
2582     if (!ftp)
2583         return NOTOK;
2584 #endif
2585
2586     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2587         case NOTOK:
2588             return NOTOK;
2589
2590         case OK:
2591             break;
2592
2593         case DONE:
2594             return fd;
2595     }
2596
2597     if (!e->eb_name || !e->eb_site) {
2598         content_error (NULL, ct, "missing %s parameter",
2599                        e->eb_name ? "site": "name");
2600         return NOTOK;
2601     }
2602
2603     if (xpid) {
2604         if (xpid < 0)
2605             xpid = -xpid;
2606         pidcheck (pidwait (xpid, NOTOK));
2607         xpid = 0;
2608     }
2609
2610     /* Get the buffer ready to go */
2611     bp = buffer;
2612     buflen = sizeof(buffer);
2613
2614     /*
2615      * Construct the query message for user
2616      */
2617     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2618     len = strlen (bp);
2619     bp += len;
2620     buflen -= len;
2621
2622     if (e->eb_partno) {
2623         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2624         len = strlen (bp);
2625         bp += len;
2626         buflen -= len;
2627     }
2628
2629     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2630                     e->eb_flags ? "anonymous " : "", e->eb_site);
2631     len = strlen (bp);
2632     bp += len;
2633     buflen -= len;
2634
2635     if (e->eb_size > 0) {
2636         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2637         len = strlen (bp);
2638         bp += len;
2639         buflen -= len;
2640     }
2641     snprintf (bp, buflen, "? ");
2642
2643     /*
2644      * Now, check the answer
2645      */
2646     if (!getanswer (buffer))
2647         return NOTOK;
2648
2649     if (e->eb_flags) {
2650         user = "anonymous";
2651         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2652         pass = buffer;
2653     } else {
2654         ruserpass (e->eb_site, &username, &password);
2655         user = username;
2656         pass = password;
2657     }
2658
2659     ce->ce_unlink = (*file == NULL);
2660     caching = 0;
2661     cachefile[0] = '\0';
2662     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2663             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2664                 cachefile, sizeof(cachefile)) != NOTOK) {
2665         if (*file == NULL) {
2666             ce->ce_unlink = 0;
2667             caching = 1;
2668         }
2669     }
2670
2671     if (*file)
2672         ce->ce_file = add (*file, NULL);
2673     else if (caching)
2674         ce->ce_file = add (cachefile, NULL);
2675     else
2676         ce->ce_file = add (m_scratch ("", tmp), NULL);
2677
2678     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2679         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2680         return NOTOK;
2681     }
2682
2683 #ifdef BUILTIN_FTP
2684     if (ftp)
2685 #endif
2686     {
2687         int child_id, i, vecp;
2688         char *vec[9];
2689
2690         vecp = 0;
2691         vec[vecp++] = r1bindex (ftp, '/');
2692         vec[vecp++] = e->eb_site;
2693         vec[vecp++] = user;
2694         vec[vecp++] = pass;
2695         vec[vecp++] = e->eb_dir;
2696         vec[vecp++] = e->eb_name;
2697         vec[vecp++] = ce->ce_file,
2698         vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2699                         ? "ascii" : "binary";
2700         vec[vecp] = NULL;
2701
2702         fflush (stdout);
2703
2704         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2705             sleep (5);
2706         switch (child_id) {
2707             case NOTOK:
2708                 adios ("fork", "unable to");
2709                 /* NOTREACHED */
2710
2711             case OK:
2712                 close (fileno (ce->ce_fp));
2713                 execvp (ftp, vec);
2714                 fprintf (stderr, "unable to exec ");
2715                 perror (ftp);
2716                 _exit (-1);
2717                 /* NOTREACHED */
2718
2719             default:
2720                 if (pidXwait (child_id, NULL)) {
2721 #ifdef BUILTIN_FTP
2722 losing_ftp:
2723 #endif
2724                     username = password = NULL;
2725                     ce->ce_unlink = 1;
2726                     return NOTOK;
2727                 }
2728                 break;
2729         }
2730     }
2731 #ifdef BUILTIN_FTP
2732     else
2733         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2734                      ce->ce_file,
2735                      e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
2736                 == NOTOK)
2737             goto losing_ftp;
2738 #endif
2739
2740     if (cachefile[0]) {
2741         if (caching)
2742             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2743         else {
2744             int mask;
2745             FILE *fp;
2746
2747             mask = umask (cachetype ? ~m_gmprot () : 0222);
2748             if ((fp = fopen (cachefile, "w"))) {
2749                 int cc;
2750                 FILE *gp = ce->ce_fp;
2751
2752                 fseek (gp, 0L, SEEK_SET);
2753
2754                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2755                            > 0)
2756                     fwrite (buffer, sizeof(*buffer), cc, fp);
2757                 fflush (fp);
2758
2759                 if (ferror (gp)) {
2760                     admonish (ce->ce_file, "error reading");
2761                     unlink (cachefile);
2762                 }
2763                 else
2764                     if (ferror (fp)) {
2765                         admonish (cachefile, "error writing");
2766                         unlink (cachefile);
2767                     }
2768                 fclose (fp);
2769             }
2770             umask (mask);
2771         }
2772     }
2773
2774     fseek (ce->ce_fp, 0L, SEEK_SET);
2775     *file = ce->ce_file;
2776     return fileno (ce->ce_fp);
2777 }
2778
2779
2780 /*
2781  * Mail
2782  */
2783
2784 static int
2785 InitMail (CT ct)
2786 {
2787     return init_encoding (ct, openMail);
2788 }
2789
2790
2791 static int
2792 openMail (CT ct, char **file)
2793 {
2794     int child_id, fd, i, vecp;
2795     int len, buflen;
2796     char *bp, buffer[BUFSIZ], *vec[7];
2797     struct exbody *e = ct->c_ctexbody;
2798     CE ce = ct->c_cefile;
2799
2800     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2801         case NOTOK:
2802             return NOTOK;
2803
2804         case OK:
2805             break;
2806
2807         case DONE:
2808             return fd;
2809     }
2810
2811     if (!e->eb_server) {
2812         content_error (NULL, ct, "missing server parameter");
2813         return NOTOK;
2814     }
2815
2816     if (xpid) {
2817         if (xpid < 0)
2818             xpid = -xpid;
2819         pidcheck (pidwait (xpid, NOTOK));
2820         xpid = 0;
2821     }
2822
2823     /* Get buffer ready to go */
2824     bp = buffer;
2825     buflen = sizeof(buffer);
2826
2827     /* Now construct query message */
2828     snprintf (bp, buflen, "Retrieve content");
2829     len = strlen (bp);
2830     bp += len;
2831     buflen -= len;
2832
2833     if (e->eb_partno) {
2834         snprintf (bp, buflen, " %s", e->eb_partno);
2835         len = strlen (bp);
2836         bp += len;
2837         buflen -= len;
2838     }
2839
2840     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2841                     e->eb_server,
2842                     e->eb_subject ? e->eb_subject : e->eb_body);
2843
2844     /* Now, check answer */
2845     if (!getanswer (buffer))
2846         return NOTOK;
2847
2848     vecp = 0;
2849     vec[vecp++] = r1bindex (mailproc, '/');
2850     vec[vecp++] = e->eb_server;
2851     vec[vecp++] = "-subject";
2852     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2853     vec[vecp++] = "-body";
2854     vec[vecp++] = e->eb_body;
2855     vec[vecp] = NULL;
2856
2857     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2858         sleep (5);
2859     switch (child_id) {
2860         case NOTOK:
2861             advise ("fork", "unable to");
2862             return NOTOK;
2863
2864         case OK:
2865             execvp (mailproc, vec);
2866             fprintf (stderr, "unable to exec ");
2867             perror (mailproc);
2868             _exit (-1);
2869             /* NOTREACHED */
2870
2871         default:
2872             if (pidXwait (child_id, NULL) == OK)
2873                 advise (NULL, "request sent");
2874             break;
2875     }
2876
2877     if (*file == NULL) {
2878         ce->ce_file = add (m_scratch ("", tmp), NULL);
2879         ce->ce_unlink = 1;
2880     } else {
2881         ce->ce_file = add (*file, NULL);
2882         ce->ce_unlink = 0;
2883     }
2884
2885     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2886         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2887         return NOTOK;
2888     }
2889
2890     fseek (ce->ce_fp, 0L, SEEK_SET);
2891     *file = ce->ce_file;
2892     return fileno (ce->ce_fp);
2893 }
2894
2895
2896 static char *
2897 fgetstr (char *s, int n, FILE *stream)
2898 {
2899     char *cp, *ep;
2900
2901     for (ep = (cp = s) + n; cp < ep; ) {
2902         int i;
2903
2904         if (!fgets (cp, n, stream))
2905             return (cp != s ? s : NULL);
2906         if (cp == s && *cp != '#')
2907             return s;
2908
2909         cp += (i = strlen (cp)) - 1;
2910         if (i <= 1 || *cp-- != '\n' || *cp != '\\')
2911             break;
2912         *cp = '\0';
2913         n -= (i - 2);
2914     }
2915
2916     return s;
2917 }
2918
2919
2920 /*
2921  * Parse the composition draft for text and directives.
2922  * Do initial setup of Content structure.
2923  */
2924
2925 static int
2926 user_content (FILE *in, char *file, char *buf, CT *ctp)
2927 {
2928     int extrnal, vrsn;
2929     unsigned char *cp;
2930     char **ap;
2931     char buffer[BUFSIZ];
2932     struct multipart *m;
2933     struct part **pp;
2934     struct stat st;
2935     struct str2init *s2i;
2936     CI ci;
2937     CT ct;
2938     CE ce;
2939
2940     if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
2941         *ctp = NULL;
2942         return OK;
2943     }
2944
2945     /* allocate basic Content structure */
2946     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
2947         adios (NULL, "out of memory");
2948     *ctp = ct;
2949
2950     /* allocate basic structure for handling decoded content */
2951     init_decoded_content (ct);
2952     ce = ct->c_cefile;
2953
2954     ci = &ct->c_ctinfo;
2955     set_id (ct, 0);
2956
2957     /*
2958      * Handle inline text.  Check if line
2959      * is one of the following forms:
2960      *
2961      * 1) doesn't begin with '#'        (implicit directive)
2962      * 2) begins with "##"              (implicit directive)
2963      * 3) begins with "#<"
2964      */
2965     if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
2966         int headers;
2967         int inlineD;
2968         long pos;
2969         char content[BUFSIZ];
2970         FILE *out;
2971
2972         /* use a temp file to collect the plain text lines */
2973         ce->ce_file = add (m_tmpfil (invo_name), NULL);
2974         ce->ce_unlink = 1;
2975
2976         if ((out = fopen (ce->ce_file, "w")) == NULL)
2977             adios (ce->ce_file, "unable to open for writing");
2978
2979         if (buf[0] == '#' && buf[1] == '<') {
2980             strncpy (content, buf + 2, sizeof(content));
2981             inlineD = 1;
2982             goto rock_and_roll;
2983         } else {
2984             inlineD = 0;
2985         }
2986
2987         /* the directive is implicit */
2988         strncpy (content, "text/plain", sizeof(content));
2989         headers = 0;
2990         strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
2991         for (;;) {
2992             int i;
2993
2994             if (headers >= 0 && uprf (buffer, DESCR_FIELD)
2995                 && buffer[i = strlen (DESCR_FIELD)] == ':') {
2996                 headers = 1;
2997
2998 again_descr:
2999                 ct->c_descr = add (buffer + i + 1, ct->c_descr);
3000                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
3001                     adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
3002                 switch (buffer[0]) {
3003                 case ' ':
3004                 case '\t':
3005                     i = -1;
3006                     goto again_descr;
3007
3008                 case '#':
3009                     adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
3010                     /* NOTREACHED */
3011
3012                 default:
3013                     break;
3014                 }
3015             }
3016
3017             if (headers >= 0 && uprf (buffer, DISPO_FIELD)
3018                 && buffer[i = strlen (DISPO_FIELD)] == ':') {
3019                 headers = 1;
3020
3021 again_dispo:
3022                 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
3023                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
3024                     adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
3025                 switch (buffer[0]) {
3026                 case ' ':
3027                 case '\t':
3028                     i = -1;
3029                     goto again_dispo;
3030
3031                 case '#':
3032                     adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
3033                     /* NOTREACHED */
3034
3035                 default:
3036                     break;
3037                 }
3038             }
3039
3040             if (headers != 1 || buffer[0] != '\n')
3041                 fputs (buffer, out);
3042
3043 rock_and_roll:
3044             headers = -1;
3045             pos = ftell (in);
3046             if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
3047                 break;
3048             if (buffer[0] == '#') {
3049                 char *bp;
3050
3051                 if (buffer[1] != '#')
3052                     break;
3053                 for (cp = (bp = buffer) + 1; *cp; cp++)
3054                     *bp++ = *cp;
3055                 *bp = '\0';
3056             }
3057         }
3058
3059         if (listsw)
3060             ct->c_end = ftell (out);
3061         fclose (out);
3062
3063         /* parse content type */
3064         if (get_ctinfo (content, ct, inlineD) == NOTOK)
3065             done (1);
3066
3067         for (s2i = str2cts; s2i->si_key; s2i++)
3068             if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
3069                 break;
3070         if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
3071             s2i++;
3072
3073         /*
3074          * check type specified (possibly implicitly)
3075          */
3076         switch (ct->c_type = s2i->si_val) {
3077         case CT_MESSAGE:
3078             if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
3079                 ct->c_encoding = CE_7BIT;
3080                 goto call_init;
3081             }
3082             /* else fall... */
3083         case CT_MULTIPART:
3084             adios (NULL, "it doesn't make sense to define an in-line %s content",
3085                    ct->c_type == CT_MESSAGE ? "message" : "multipart");
3086             /* NOTREACHED */
3087
3088         default:
3089 call_init:
3090             if ((ct->c_ctinitfnx = s2i->si_init))
3091                 (*ct->c_ctinitfnx) (ct);
3092             break;
3093         }
3094
3095         if (cp)
3096             fseek (in, pos, SEEK_SET);
3097         return OK;
3098     }
3099
3100     /*
3101      * If we've reached this point, the next line
3102      * must be some type of explicit directive.
3103      */
3104
3105     /* check if directive is external-type */
3106     extrnal = (buf[1] == '@');
3107
3108     /* parse directive */
3109     if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
3110         done (1);
3111
3112     /* check directive against the list of MIME types */
3113     for (s2i = str2cts; s2i->si_key; s2i++)
3114         if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
3115             break;
3116
3117     /*
3118      * Check if the directive specified a valid type.
3119      * This will happen if it was one of the following forms:
3120      *
3121      *    #type/subtype  (or)
3122      *    #@type/subtype
3123      */
3124     if (s2i->si_key) {
3125         if (!ci->ci_subtype)
3126             adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
3127
3128         switch (ct->c_type = s2i->si_val) {
3129         case CT_MULTIPART:
3130             adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
3131                    ci->ci_type, ci->ci_subtype);
3132             /* NOTREACHED */
3133
3134         case CT_MESSAGE:
3135             if (!mh_strcasecmp (ci->ci_subtype, "partial"))
3136                 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
3137                        ci->ci_type, ci->ci_subtype);
3138             if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
3139                 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
3140                        ci->ci_type, ci->ci_subtype);
3141 use_forw:
3142             adios (NULL,
3143                    "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
3144                    ci->ci_type, ci->ci_subtype);
3145             /* NOTREACHED */
3146
3147         default:
3148             if ((ct->c_ctinitfnx = s2i->si_init))
3149                 (*ct->c_ctinitfnx) (ct);
3150             break;
3151         }
3152
3153         /*
3154          * #@type/subtype (external types directive)
3155          */
3156         if (extrnal) {
3157             struct exbody *e;
3158             CT p;
3159
3160             if (!ci->ci_magic)
3161                 adios (NULL, "need external information for \"#@%s/%s\"",
3162                        ci->ci_type, ci->ci_subtype);
3163             p = ct;
3164
3165             snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
3166             free (ci->ci_magic);
3167             ci->ci_magic = NULL;
3168
3169             /*
3170              * Since we are using the current Content structure to
3171              * hold information about the type of the external
3172              * reference, we need to create another Content structure
3173              * for the message/external-body to wrap it in.
3174              */
3175             if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
3176                 adios (NULL, "out of memory");
3177             *ctp = ct;
3178             ci = &ct->c_ctinfo;
3179             if (get_ctinfo (buffer, ct, 0) == NOTOK)
3180                 done (1);
3181             ct->c_type = CT_MESSAGE;
3182             ct->c_subtype = MESSAGE_EXTERNAL;
3183
3184             if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
3185                 adios (NULL, "out of memory");
3186             ct->c_ctparams = (void *) e;
3187
3188             e->eb_parent = ct;
3189             e->eb_content = p;
3190             p->c_ctexbody = e;
3191
3192             if (params_external (ct, 1) == NOTOK)
3193                 done (1);
3194
3195             return OK;
3196         }
3197
3198         /* Handle [file] argument */
3199         if (ci->ci_magic) {
3200             /* check if specifies command to execute */
3201             if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
3202                 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
3203                     continue;
3204                 if (!*cp)
3205                     adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
3206                 cp = add (cp, NULL);
3207                 free (ci->ci_magic);
3208                 ci->ci_magic = cp;
3209             } else {
3210                 /* record filename of decoded contents */
3211                 ce->ce_file = ci->ci_magic;
3212                 if (access (ce->ce_file, R_OK) == NOTOK)
3213                     adios ("reading", "unable to access %s for", ce->ce_file);
3214                 if (listsw && stat (ce->ce_file, &st) != NOTOK)
3215                     ct->c_end = (long) st.st_size;
3216                 ci->ci_magic = NULL;
3217             }
3218             return OK;
3219         }
3220
3221         /*
3222          * No [file] argument, so check profile for
3223          * method to compose content.
3224          */
3225         snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
3226                 invo_name, ci->ci_type, ci->ci_subtype);
3227         if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3228             snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
3229             if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3230                 content_error (NULL, ct, "don't know how to compose content");
3231                 done (1);
3232             }
3233         }
3234         ci->ci_magic = add (cp, NULL);
3235         return OK;
3236     }
3237
3238     if (extrnal)
3239         adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
3240
3241     /*
3242      * Message directive
3243      * #forw [+folder] [msgs]
3244      */
3245     if (!mh_strcasecmp (ci->ci_type, "forw")) {
3246         int msgnum;
3247         char *folder, *arguments[MAXARGS];
3248         struct msgs *mp;
3249
3250         if (ci->ci_magic) {
3251             ap = brkstring (ci->ci_magic, " ", "\n");
3252             copyip (ap, arguments, MAXARGS);
3253         } else {
3254             arguments[0] = "cur";
3255             arguments[1] = NULL;
3256         }
3257         folder = NULL;
3258
3259         /* search the arguments for a folder name */
3260         for (ap = arguments; *ap; ap++) {
3261             cp = *ap;
3262             if (*cp == '+' || *cp == '@') {
3263                 if (folder)
3264                     adios (NULL, "only one folder per #forw directive");
3265                 else
3266                     folder = pluspath (cp);
3267             }
3268         }
3269
3270         /* else, use the current folder */
3271         if (!folder)
3272             folder = add (getfolder (1), NULL);
3273
3274         if (!(mp = folder_read (folder)))
3275             adios (NULL, "unable to read folder %s", folder);
3276         for (ap = arguments; *ap; ap++) {
3277             cp = *ap;
3278             if (*cp != '+' && *cp != '@')
3279                 if (!m_convert (mp, cp))
3280                     done (1);
3281         }
3282         free (folder);
3283         free_ctinfo (ct);
3284
3285         /*
3286          * If there is more than one message to include, make this
3287          * a content of type "multipart/digest" and insert each message
3288          * as a subpart.  If there is only one message, then make this
3289          * a content of type "message/rfc822".
3290          */
3291         if (mp->numsel > 1) {
3292             /* we are forwarding multiple messages */
3293             if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
3294                 done (1);
3295             ct->c_type = CT_MULTIPART;
3296             ct->c_subtype = MULTI_DIGEST;
3297
3298             if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3299                 adios (NULL, "out of memory");
3300             ct->c_ctparams = (void *) m;
3301             pp = &m->mp_parts;
3302
3303             for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
3304                 if (is_selected(mp, msgnum)) {
3305                     struct part *part;
3306                     CT p;
3307                     CE pe;
3308
3309                     if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
3310                         adios (NULL, "out of memory");
3311                     init_decoded_content (p);
3312                     pe = p->c_cefile;
3313                     if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
3314                         done (1);
3315                     p->c_type = CT_MESSAGE;
3316                     p->c_subtype = MESSAGE_RFC822;
3317
3318                     snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3319                     pe->ce_file = add (buffer, NULL);
3320                     if (listsw && stat (pe->ce_file, &st) != NOTOK)
3321                         p->c_end = (long) st.st_size;
3322
3323                     if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3324                         adios (NULL, "out of memory");
3325                     *pp = part;
3326                     pp = &part->mp_next;
3327                     part->mp_part = p;
3328                 }
3329             }
3330         } else {
3331             /* we are forwarding one message */
3332             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
3333                 done (1);
3334             ct->c_type = CT_MESSAGE;
3335             ct->c_subtype = MESSAGE_RFC822;
3336
3337             msgnum = mp->lowsel;
3338             snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3339             ce->ce_file = add (buffer, NULL);
3340             if (listsw && stat (ce->ce_file, &st) != NOTOK)
3341                 ct->c_end = (long) st.st_size;
3342         }
3343
3344         folder_free (mp);       /* free folder/message structure */
3345         return OK;
3346     }
3347
3348     /*
3349      * #end
3350      */
3351     if (!mh_strcasecmp (ci->ci_type, "end")) {
3352         free_content (ct);
3353         *ctp = NULL;
3354         return DONE;
3355     }
3356
3357     /*
3358      * #begin [ alternative | parallel ]
3359      */
3360     if (!mh_strcasecmp (ci->ci_type, "begin")) {
3361         if (!ci->ci_magic) {
3362             vrsn = MULTI_MIXED;
3363             cp = SubMultiPart[vrsn - 1].kv_key;
3364         } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
3365             vrsn = MULTI_ALTERNATE;
3366             cp = SubMultiPart[vrsn - 1].kv_key;
3367         } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
3368             vrsn = MULTI_PARALLEL;
3369             cp = SubMultiPart[vrsn - 1].kv_key;
3370         } else if (uprf (ci->ci_magic, "digest")) {
3371             goto use_forw;
3372         } else {
3373             vrsn = MULTI_UNKNOWN;
3374             cp = ci->ci_magic;
3375         }
3376
3377         free_ctinfo (ct);
3378         snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
3379         if (get_ctinfo (buffer, ct, 0) == NOTOK)
3380             done (1);
3381         ct->c_type = CT_MULTIPART;
3382         ct->c_subtype = vrsn;
3383
3384         if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3385             adios (NULL, "out of memory");
3386         ct->c_ctparams = (void *) m;
3387
3388         pp = &m->mp_parts;
3389         while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
3390             struct part *part;
3391             CT p;
3392
3393             if (user_content (in, file, buffer, &p) == DONE) {
3394                 if (!m->mp_parts)
3395                     adios (NULL, "empty \"#begin ... #end\" sequence");
3396                 return OK;
3397             }
3398             if (!p)
3399                 continue;
3400
3401             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3402                 adios (NULL, "out of memory");
3403             *pp = part;
3404             pp = &part->mp_next;
3405             part->mp_part = p;
3406         }
3407         admonish (NULL, "premature end-of-file, missing #end");
3408         return OK;
3409     }
3410
3411     /*
3412      * Unknown directive
3413      */
3414     adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
3415     return NOTOK;       /* NOT REACHED */
3416 }
3417
3418
3419 static void
3420 set_id (CT ct, int top)
3421 {
3422     char msgid[BUFSIZ];
3423     static int partno;
3424     static time_t clock = 0;
3425     static char *msgfmt;
3426
3427     if (clock == 0) {
3428         time (&clock);
3429         snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
3430                 (int) getpid(), (long) clock, LocalName());
3431         partno = 0;
3432         msgfmt = getcpy(msgid);
3433     }
3434     snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
3435     ct->c_id = getcpy (msgid);
3436 }
3437
3438
3439 static char ebcdicsafe[0x100] = {
3440     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3441     0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3442     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3443     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3444     0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
3445     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3446     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3447     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3448     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3449     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3450     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3451     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
3452     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3453     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3454     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3455     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3456     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3457     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3458     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3459     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3460     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3461     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3462     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3463     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3464     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3465     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3466     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3467     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3468     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3469     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3470     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3471     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
3472 };
3473
3474
3475 /*
3476  * Fill out, or expand the various contents in the composition
3477  * draft.  Read-in any necessary files.  Parse and execute any
3478  * commands specified by profile composition strings.
3479  */
3480
3481 static int
3482 compose_content (CT ct)
3483 {
3484     CE ce = ct->c_cefile;
3485
3486     switch (ct->c_type) {
3487     case CT_MULTIPART:
3488     {
3489         int partnum;
3490         char *pp;
3491         char partnam[BUFSIZ];
3492         struct multipart *m = (struct multipart *) ct->c_ctparams;
3493         struct part *part;
3494
3495         if (ct->c_partno) {
3496             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
3497             pp = partnam + strlen (partnam);
3498         } else {
3499             pp = partnam;
3500         }
3501
3502         /* first, we call compose_content on all the subparts */
3503         for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
3504             CT p = part->mp_part;
3505
3506             sprintf (pp, "%d", partnum);
3507             p->c_partno = add (partnam, NULL);
3508             if (compose_content (p) == NOTOK)
3509                 return NOTOK;
3510         }
3511
3512         /*
3513          * If the -rfc934mode switch is given, then check all
3514          * the subparts of a multipart/digest.  If they are all
3515          * message/rfc822, then mark this content and all
3516          * subparts with the rfc934 compatibility mode flag.
3517          */
3518         if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
3519             int is934 = 1;
3520
3521             for (part = m->mp_parts; part; part = part->mp_next) {
3522                 CT p = part->mp_part;
3523
3524                 if (p->c_subtype != MESSAGE_RFC822) {
3525                     is934 = 0;
3526                     break;
3527                 }
3528             }
3529             ct->c_rfc934 = is934;
3530             for (part = m->mp_parts; part; part = part->mp_next) {
3531                 CT p = part->mp_part;
3532
3533                 if ((p->c_rfc934 = is934))
3534                     p->c_end++;
3535             }
3536         }
3537
3538         if (listsw) {
3539             ct->c_end = (partnum = strlen (prefix) + 2) + 2;
3540             if (ct->c_rfc934)
3541                 ct->c_end += 1;
3542
3543             for (part = m->mp_parts; part; part = part->mp_next)
3544                 ct->c_end += part->mp_part->c_end + partnum;
3545         }
3546     }
3547     break;
3548
3549     case CT_MESSAGE:
3550         /* Nothing to do for type message */
3551         break;
3552
3553     /*
3554      * Discrete types (text/application/audio/image/video)
3555      */
3556     default:
3557         if (!ce->ce_file) {
3558             pid_t child_id;
3559             int i, xstdout, len, buflen;
3560             char *bp, **ap, *cp;
3561             char *vec[4], buffer[BUFSIZ];
3562             FILE *out;
3563             CI ci = &ct->c_ctinfo;
3564
3565             if (!(cp = ci->ci_magic))
3566                 adios (NULL, "internal error(5)");
3567
3568             ce->ce_file = add (m_tmpfil (invo_name), NULL);
3569             ce->ce_unlink = 1;
3570
3571             xstdout = 0;
3572
3573             /* Get buffer ready to go */
3574             bp = buffer;
3575             bp[0] = '\0';
3576             buflen = sizeof(buffer);
3577
3578             /*
3579              * Parse composition string into buffer
3580              */
3581             for ( ; *cp; cp++) {
3582                 if (*cp == '%') {
3583                     switch (*++cp) {
3584                     case 'a':
3585                     {
3586                         /* insert parameters from directive */
3587                         char **ep;
3588                         char *s = "";
3589
3590                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3591                             snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
3592                             len = strlen (bp);
3593                             bp += len;
3594                             buflen -= len;
3595                             s = " ";
3596                         }
3597                     }
3598                     break;
3599
3600                     case 'F':
3601                         /* %f, and stdout is not-redirected */
3602                         xstdout = 1;
3603                         /* and fall... */
3604
3605                     case 'f':
3606                         /*
3607                          * insert temporary filename where
3608                          * content should be written
3609                          */
3610                         snprintf (bp, buflen, "%s", ce->ce_file);
3611                         break;
3612
3613                     case 's':
3614                         /* insert content subtype */
3615                         strncpy (bp, ci->ci_subtype, buflen);
3616                         break;
3617
3618                     case '%':
3619                         /* insert character % */
3620                         goto raw;
3621
3622                     default:
3623                         *bp++ = *--cp;
3624                         *bp = '\0';
3625                         buflen--;
3626                         continue;
3627                     }
3628                     len = strlen (bp);
3629                     bp += len;
3630                     buflen -= len;
3631                 } else {
3632 raw:
3633                 *bp++ = *cp;
3634                 *bp = '\0';
3635                 buflen--;
3636                 }
3637             }
3638
3639             if (verbosw)
3640                 printf ("composing content %s/%s from command\n\t%s\n",
3641                         ci->ci_type, ci->ci_subtype, buffer);
3642
3643             fflush (stdout);    /* not sure if need for -noverbose */
3644
3645             vec[0] = "/bin/sh";
3646             vec[1] = "-c";
3647             vec[2] = buffer;
3648             vec[3] = NULL;
3649
3650             if ((out = fopen (ce->ce_file, "w")) == NULL)
3651                 adios (ce->ce_file, "unable to open for writing");
3652
3653             for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
3654                 sleep (5);
3655             switch (child_id) {
3656             case NOTOK:
3657                 adios ("fork", "unable to fork");
3658                 /* NOTREACHED */
3659
3660             case OK:
3661                 if (!xstdout)
3662                     dup2 (fileno (out), 1);
3663                 close (fileno (out));
3664                 execvp ("/bin/sh", vec);
3665                 fprintf (stderr, "unable to exec ");
3666                 perror ("/bin/sh");
3667                 _exit (-1);
3668                 /* NOTREACHED */
3669
3670             default:
3671                 fclose (out);
3672                 if (pidXwait(child_id, NULL))
3673                     done (1);
3674                 break;
3675             }
3676         }
3677
3678         /* Check size of file */
3679         if (listsw && ct->c_end == 0L) {
3680             struct stat st;
3681
3682             if (stat (ce->ce_file, &st) != NOTOK)
3683                 ct->c_end = (long) st.st_size;
3684         }
3685         break;
3686     }
3687
3688     return OK;
3689 }
3690
3691
3692 /*
3693  * Scan the content.
3694  *
3695  *    1) choose a transfer encoding.
3696  *    2) check for clashes with multipart boundary string.
3697  *    3) for text content, figure out which character set is being used.
3698  *
3699  * If there is a clash with one of the contents and the multipart boundary,
3700  * this function will exit with NOTOK.  This will cause the scanning process
3701  * to be repeated with a different multipart boundary.  It is possible
3702  * (although highly unlikely) that this scan will be repeated multiple times.
3703  */
3704
3705 static int
3706 scan_content (CT ct)
3707 {
3708     int len;
3709     int check8bit, contains8bit = 0;      /* check if contains 8bit data                */
3710     int checklinelen, linelen = 0;        /* check for long lines                       */
3711     int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary   */
3712     int checklinespace, linespace = 0;    /* check if any line ends with space          */
3713     int checkebcdic, ebcdicunsafe = 0;    /* check if contains ebcdic unsafe characters */
3714     unsigned char *cp, buffer[BUFSIZ];
3715     struct text *t;
3716     FILE *in;
3717     CE ce = ct->c_cefile;
3718
3719     /*
3720      * handle multipart by scanning all subparts
3721      * and then checking their encoding.
3722      */
3723     if (ct->c_type == CT_MULTIPART) {
3724         struct multipart *m = (struct multipart *) ct->c_ctparams;
3725         struct part *part;
3726
3727         /* initially mark the domain of enclosing multipart as 7bit */
3728         ct->c_encoding = CE_7BIT;
3729
3730         for (part = m->mp_parts; part; part = part->mp_next) {
3731             CT p = part->mp_part;
3732
3733             if (scan_content (p) == NOTOK)      /* choose encoding for subpart */
3734                 return NOTOK;
3735
3736             /* if necessary, enlarge encoding for enclosing multipart */
3737             if (p->c_encoding == CE_BINARY)
3738                 ct->c_encoding = CE_BINARY;
3739             if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
3740                 ct->c_encoding = CE_8BIT;
3741         }
3742
3743         return OK;
3744     }
3745
3746     /*
3747      * Decide what to check while scanning this content.
3748      */
3749     switch (ct->c_type) {
3750     case CT_TEXT:
3751         check8bit = 1;
3752         checkboundary = 1;
3753         if (ct->c_subtype == TEXT_PLAIN) {
3754             checkebcdic = 0;
3755             checklinelen = 0;
3756             checklinespace = 0;
3757         } else {
3758             checkebcdic = ebcdicsw;
3759             checklinelen = 1;
3760             checklinespace = 1;
3761         }
3762         break;
3763
3764     case CT_APPLICATION:
3765         check8bit = 1;
3766         checkebcdic = ebcdicsw;
3767         checklinelen = 1;
3768         checklinespace = 1;
3769         checkboundary = 1;
3770         break;
3771
3772     case CT_MESSAGE:
3773         check8bit = 0;
3774         checkebcdic = 0;
3775         checklinelen = 0;
3776         checklinespace = 0;
3777
3778         /* don't check anything for message/external */
3779         if (ct->c_subtype == MESSAGE_EXTERNAL)
3780             checkboundary = 0;
3781         else
3782             checkboundary = 1;
3783         break;
3784
3785     case CT_AUDIO:
3786     case CT_IMAGE:
3787     case CT_VIDEO:
3788         /*
3789          * Don't check anything for these types,
3790          * since we are forcing use of base64.
3791          */
3792         check8bit = 0;
3793         checkebcdic = 0;
3794         checklinelen = 0;
3795         checklinespace = 0;
3796         checkboundary = 0;
3797         break;
3798     }
3799
3800     /*
3801      * Scan the unencoded content
3802      */
3803     if (check8bit || checklinelen || checklinespace || checkboundary) {
3804         if ((in = fopen (ce->ce_file, "r")) == NULL)
3805             adios (ce->ce_file, "unable to open for reading");
3806         len = strlen (prefix);
3807
3808         while (fgets (buffer, sizeof(buffer) - 1, in)) {
3809             /*
3810              * Check for 8bit data.
3811              */
3812             if (check8bit) {
3813                 for (cp = buffer; *cp; cp++) {
3814                     if (!isascii (*cp)) {
3815                         contains8bit = 1;
3816                         check8bit = 0;  /* no need to keep checking */
3817                     }
3818                     /*
3819                      * Check if character is ebcdic-safe.  We only check
3820                      * this if also checking for 8bit data.
3821                      */
3822                     if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
3823                         ebcdicunsafe = 1;
3824                         checkebcdic = 0; /* no need to keep checking */
3825                     }
3826                 }
3827             }
3828
3829             /*
3830              * Check line length.
3831              */
3832             if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
3833                 linelen = 1;
3834                 checklinelen = 0;       /* no need to keep checking */
3835             }
3836
3837             /*
3838              * Check if line ends with a space.
3839              */
3840             if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
3841                 linespace = 1;
3842                 checklinespace = 0;     /* no need to keep checking */
3843             }
3844
3845             /*
3846              * Check if content contains a line that clashes
3847              * with our standard boundary for multipart messages.
3848              */
3849             if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
3850                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
3851                     if (!isspace (*cp))
3852                         break;
3853                 *++cp = '\0';
3854                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
3855                     boundaryclash = 1;
3856                     checkboundary = 0;  /* no need to keep checking */
3857                 }
3858             }
3859         }
3860         fclose (in);
3861     }
3862
3863     /*
3864      * Decide which transfer encoding to use.
3865      */
3866     switch (ct->c_type) {
3867     case CT_TEXT:
3868         /*
3869          * If the text content didn't specify a character
3870          * set, we need to figure out which one was used.
3871          */
3872         t = (struct text *) ct->c_ctparams;
3873         if (t->tx_charset == CHARSET_UNSPECIFIED) {
3874             CI ci = &ct->c_ctinfo;
3875             char **ap, **ep;
3876
3877             for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
3878                 continue;
3879
3880             if (contains8bit) {
3881                 t->tx_charset = CHARSET_UNKNOWN;
3882                 *ap = concat ("charset=", write_charset_8bit(), NULL);
3883             } else {
3884                 t->tx_charset = CHARSET_USASCII;
3885                 *ap = add ("charset=us-ascii", NULL);
3886             }
3887
3888             cp = strchr(*ap++, '=');
3889             *ap = NULL;
3890             *cp++ = '\0';
3891             *ep = cp;
3892         }
3893
3894         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3895             ct->c_encoding = CE_QUOTED;
3896         else
3897             ct->c_encoding = CE_7BIT;
3898         break;
3899
3900     case CT_APPLICATION:
3901         /* For application type, use base64, except when postscript */
3902         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3903             ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
3904                 ? CE_QUOTED : CE_BASE64;
3905         else
3906             ct->c_encoding = CE_7BIT;
3907         break;
3908
3909     case CT_MESSAGE:
3910         ct->c_encoding = CE_7BIT;
3911         break;
3912
3913     case CT_AUDIO:
3914     case CT_IMAGE:
3915     case CT_VIDEO:
3916         /* For audio, image, and video contents, just use base64 */
3917         ct->c_encoding = CE_BASE64;
3918         break;
3919     }
3920
3921     return (boundaryclash ? NOTOK : OK);
3922 }
3923
3924
3925 /*
3926  * Scan the content structures, and build header
3927  * fields that will need to be output into the
3928  * message.
3929  */
3930
3931 static int
3932 build_headers (CT ct)
3933 {
3934     int cc, mailbody, len;
3935     char **ap, **ep;
3936     char *np, *vp, buffer[BUFSIZ];
3937     CI ci = &ct->c_ctinfo;
3938
3939     /*
3940      * If message is type multipart, then add the multipart
3941      * boundary to the list of attribute/value pairs.
3942      */
3943     if (ct->c_type == CT_MULTIPART) {
3944         char *cp;
3945         static int level = 0;   /* store nesting level */
3946
3947         ap = ci->ci_attrs;
3948         ep = ci->ci_values;
3949         snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
3950         cp = strchr(*ap++ = add (buffer, NULL), '=');
3951         *ap = NULL;
3952         *cp++ = '\0';
3953         *ep = cp;
3954     }
3955
3956     /*
3957      * Skip the output of Content-Type, parameters, content
3958      * description and disposition, and Content-ID if the
3959      * content is of type "message" and the rfc934 compatibility
3960      * flag is set (which means we are inside multipart/digest
3961      * and the switch -rfc934mode was given).
3962      */
3963     if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
3964         goto skip_headers;
3965
3966     /*
3967      * output the content type and subtype
3968      */
3969     np = add (TYPE_FIELD, NULL);
3970     vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
3971
3972     /* keep track of length of line */
3973     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
3974                 + strlen (ci->ci_subtype) + 3;
3975
3976     mailbody = ct->c_type == CT_MESSAGE
3977         && ct->c_subtype == MESSAGE_EXTERNAL
3978         && ((struct exbody *) ct->c_ctparams)->eb_body;
3979
3980     /*
3981      * Append the attribute/value pairs to
3982      * the end of the Content-Type line.
3983      */
3984     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3985         if (mailbody && !mh_strcasecmp (*ap, "body"))
3986             continue;
3987
3988         vp = add (";", vp);
3989         len++;
3990
3991         snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
3992         if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
3993             vp = add ("\n\t", vp);
3994             len = 8;
3995         } else {
3996             vp = add (" ", vp);
3997             len++;
3998         }
3999         vp = add (buffer, vp);
4000         len += cc;
4001     }
4002
4003     /*
4004      * Append any RFC-822 comment to the end of
4005      * the Content-Type line.
4006      */
4007     if (ci->ci_comment) {
4008         snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
4009         if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
4010             vp = add ("\n\t", vp);
4011             len = 8;
4012         } else {
4013             vp = add (" ", vp);
4014             len++;
4015         }
4016         vp = add (buffer, vp);
4017         len += cc;
4018     }
4019     vp = add ("\n", vp);
4020     add_header (ct, np, vp);
4021
4022     /*
4023      * output the Content-ID, unless disabled by -nocontentid
4024      */
4025     if (contentidsw && ct->c_id) {
4026         np = add (ID_FIELD, NULL);
4027         vp = concat (" ", ct->c_id, NULL);
4028         add_header (ct, np, vp);
4029     }
4030
4031     /*
4032      * output the Content-Description
4033      */
4034     if (ct->c_descr) {
4035         np = add (DESCR_FIELD, NULL);
4036         vp = concat (" ", ct->c_descr, NULL);
4037         add_header (ct, np, vp);
4038     }
4039
4040     /*
4041      * output the Content-Disposition
4042      */
4043     if (ct->c_dispo) {
4044         np = add (DISPO_FIELD, NULL);
4045         vp = concat (" ", ct->c_dispo, NULL);
4046         add_header (ct, np, vp);
4047     }
4048
4049 skip_headers:
4050     /*
4051      * If this is the internal content structure for a
4052      * "message/external", then we are done with the
4053      * headers (since it has no body).
4054      */
4055     if (ct->c_ctexbody)
4056         return OK;
4057
4058     /*
4059      * output the Content-MD5
4060      */
4061     if (checksw) {
4062         np = add (MD5_FIELD, NULL);
4063         vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
4064         add_header (ct, np, vp);
4065     }
4066
4067     /*
4068      * output the Content-Transfer-Encoding
4069      */
4070     switch (ct->c_encoding) {
4071     case CE_7BIT:
4072         /* Nothing to output */
4073 #if 0
4074         np = add (ENCODING_FIELD, NULL);
4075         vp = concat (" ", "7bit", "\n", NULL);
4076         add_header (ct, np, vp);
4077 #endif
4078         break;
4079
4080     case CE_8BIT:
4081         if (ct->c_type == CT_MESSAGE)
4082             adios (NULL, "internal error, invalid encoding");
4083
4084         np = add (ENCODING_FIELD, NULL);
4085         vp = concat (" ", "8bit", "\n", NULL);
4086         add_header (ct, np, vp);
4087         break;
4088
4089     case CE_QUOTED:
4090         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
4091             adios (NULL, "internal error, invalid encoding");
4092
4093         np = add (ENCODING_FIELD, NULL);
4094         vp = concat (" ", "quoted-printable", "\n", NULL);
4095         add_header (ct, np, vp);
4096         break;
4097
4098     case CE_BASE64:
4099         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
4100             adios (NULL, "internal error, invalid encoding");
4101
4102         np = add (ENCODING_FIELD, NULL);
4103         vp = concat (" ", "base64", "\n", NULL);
4104         add_header (ct, np, vp);
4105         break;
4106
4107     case CE_BINARY:
4108         if (ct->c_type == CT_MESSAGE)
4109             adios (NULL, "internal error, invalid encoding");
4110
4111         np = add (ENCODING_FIELD, NULL);
4112         vp = concat (" ", "binary", "\n", NULL);
4113         add_header (ct, np, vp);
4114         break;
4115
4116     default:
4117         adios (NULL, "unknown transfer encoding in content");
4118         break;
4119     }
4120
4121     /*
4122      * Additional content specific header processing
4123      */
4124     switch (ct->c_type) {
4125     case CT_MULTIPART:
4126     {
4127         struct multipart *m;
4128         struct part *part;
4129
4130         m = (struct multipart *) ct->c_ctparams;
4131         for (part = m->mp_parts; part; part = part->mp_next) {
4132             CT p;
4133
4134             p = part->mp_part;
4135             build_headers (p);
4136         }
4137     }
4138         break;
4139
4140     case CT_MESSAGE:
4141         if (ct->c_subtype == MESSAGE_EXTERNAL) {
4142             struct exbody *e;
4143
4144             e = (struct exbody *) ct->c_ctparams;
4145             build_headers (e->eb_content);
4146         }
4147         break;
4148
4149     default:
4150         /* Nothing to do */
4151         break;
4152     }
4153
4154     return OK;
4155 }
4156
4157
4158 static char nib2b64[0x40+1] =
4159         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4160
4161 static char *
4162 calculate_digest (CT ct, int asciiP)
4163 {
4164     int cc;
4165     char buffer[BUFSIZ], *vp, *op;
4166     unsigned char *dp;
4167     unsigned char digest[16];
4168     unsigned char outbuf[25];
4169     FILE *in;
4170     MD5_CTX mdContext;
4171     CE ce = ct->c_cefile;
4172
4173     /* open content */
4174     if ((in = fopen (ce->ce_file, "r")) == NULL)
4175         adios (ce->ce_file, "unable to open for reading");
4176
4177     /* Initialize md5 context */
4178     MD5Init (&mdContext);
4179
4180     /* calculate md5 message digest */
4181     if (asciiP) {
4182         while (fgets (buffer, sizeof(buffer) - 1, in)) {
4183             char c, *cp;
4184
4185             cp = buffer + strlen (buffer) - 1;
4186             if ((c = *cp) == '\n')
4187                 *cp = '\0';
4188
4189             MD5Update (&mdContext, (unsigned char *) buffer,
4190                        (unsigned int) strlen (buffer));
4191
4192             if (c == '\n')
4193                 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
4194         }
4195     } else {
4196         while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
4197             MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
4198     }
4199
4200     /* md5 finalization.  Write digest and zero md5 context */
4201     MD5Final (digest, &mdContext);
4202
4203     /* close content */
4204     fclose (in);
4205
4206     /* print debugging info */
4207     if (debugsw) {
4208         unsigned char *ep;
4209
4210         fprintf (stderr, "MD5 digest=");
4211         for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
4212                  dp < ep; dp++)
4213             fprintf (stderr, "%02x", *dp & 0xff);
4214         fprintf (stderr, "\n");
4215     }
4216
4217     /* encode the digest using base64 */
4218     for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
4219                 cc > 0; cc -= 3, op += 4) {
4220         unsigned long bits;
4221         char *bp;
4222
4223         bits = (*dp++ & 0xff) << 16;
4224         if (cc > 1) {
4225             bits |= (*dp++ & 0xff) << 8;
4226             if (cc > 2)
4227                 bits |= *dp++ & 0xff;
4228         }
4229
4230         for (bp = op + 4; bp > op; bits >>= 6)
4231             *--bp = nib2b64[bits & 0x3f];
4232         if (cc < 3) {
4233             *(op + 3) = '=';
4234             if (cc < 2)
4235                 *(op + 2) = '=';
4236         }
4237     }
4238
4239     /* null terminate string */
4240     outbuf[24] = '\0';
4241
4242     /* now make copy and return string */
4243     vp = concat (" ", outbuf, "\n", NULL);
4244     return vp;
4245 }
4246
4247
4248 static int
4249 readDigest (CT ct, char *cp)
4250 {
4251     int bitno, skip;
4252     unsigned long bits;
4253     char *bp = cp;
4254     unsigned char *dp, value, *ep;
4255     unsigned char *b, *b1, *b2, *b3;
4256
4257     b  = (unsigned char *) &bits,
4258     b1 = &b[endian > 0 ? 1 : 2],
4259     b2 = &b[endian > 0 ? 2 : 1],
4260     b3 = &b[endian > 0 ? 3 : 0];
4261     bitno = 18;
4262     bits = 0L;
4263     skip = 0;
4264
4265     for (ep = (dp = ct->c_digest)
4266                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
4267         switch (*cp) {
4268             default:
4269                 if (skip
4270                         || (*cp & 0x80)
4271                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
4272                     if (debugsw)
4273                         fprintf (stderr, "invalid BASE64 encoding\n");
4274                     return NOTOK;
4275                 }
4276
4277                 bits |= value << bitno;
4278 test_end:
4279                 if ((bitno -= 6) < 0) {
4280                     if (dp + (3 - skip) > ep)
4281                         goto invalid_digest;
4282                     *dp++ = *b1;
4283                     if (skip < 2) {
4284                         *dp++ = *b2;
4285                         if (skip < 1)
4286                             *dp++ = *b3;
4287                     }
4288                     bitno = 18;
4289                     bits = 0L;
4290                     skip = 0;
4291                 }
4292                 break;
4293
4294             case '=':
4295                 if (++skip > 3)
4296                     goto self_delimiting;
4297                 goto test_end;
4298         }
4299     if (bitno != 18) {
4300         if (debugsw)
4301             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
4302
4303         return NOTOK;
4304     }
4305 self_delimiting:
4306     if (dp != ep) {
4307 invalid_digest:
4308         if (debugsw) {
4309             while (*cp)
4310                 cp++;
4311             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
4312                      cp - bp);
4313         }
4314
4315         return NOTOK;
4316     }
4317
4318     if (debugsw) {
4319         fprintf (stderr, "MD5 digest=");
4320         for (dp = ct->c_digest; dp < ep; dp++)
4321             fprintf (stderr, "%02x", *dp & 0xff);
4322         fprintf (stderr, "\n");
4323     }
4324
4325     return OK;
4326 }
4327
4328
4329 /* Make sure that buf contains at least one appearance of name,
4330    followed by =.  If not, insert both name and value, just after
4331    first semicolon, if any.  Note that name should not contain a
4332    trailing =.  And quotes will be added around the value.  Typical
4333    usage:  make sure that a Content-Disposition header contains
4334    filename="foo".  If it doesn't and value does, use value from
4335    that. */
4336 static char *
4337 incl_name_value (unsigned char *buf, char *name, char *value) {
4338     char *newbuf = buf;
4339
4340     /* Assume that name is non-null. */
4341     if (buf && value) {
4342         char *name_plus_equal = concat (name, "=", NULL);
4343
4344         if (! strstr (buf, name_plus_equal)) {
4345             char *insertion;
4346             unsigned char *cp;
4347             char *prefix, *suffix;
4348
4349             /* Trim trailing space, esp. newline. */
4350             for (cp = &buf[strlen (buf) - 1];
4351                  cp >= buf && isspace (*cp);
4352                  --cp) {
4353                 *cp = '\0';
4354             }
4355
4356             insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
4357
4358             /* Insert at first semicolon, if any.  If none, append to
4359                end. */
4360             prefix = add (buf, NULL);
4361             if ((cp = strchr (prefix, ';'))) {
4362                 suffix = concat (cp, NULL);
4363                 *cp = '\0';
4364                 newbuf = concat (prefix, insertion, suffix, "\n", NULL);
4365                 free (suffix);
4366             } else {
4367                 /* Append to end. */
4368                 newbuf = concat (buf, insertion, "\n", NULL);
4369             }
4370
4371             free (prefix);
4372             free (insertion);
4373             free (buf);
4374         }
4375
4376         free (name_plus_equal);
4377     }
4378
4379     return newbuf;
4380 }
4381
4382
4383 /* Extract just name_suffix="foo", if any, from value.  If there isn't
4384    one, return the entire value.  Note that, for example, a name_suffix
4385    of name will match filename="foo", and return foo. */
4386 static char *
4387 extract_name_value (char *name_suffix, char *value) {
4388     char *extracted_name_value = value;
4389     char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
4390     char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
4391     char *cp;
4392
4393     free (name_suffix_plus_quote);
4394     if (name_suffix_equals) {
4395         char *name_suffix_begin;
4396
4397         /* Find first \". */
4398         for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
4399         name_suffix_begin = ++cp;
4400         /* Find second \". */
4401         for (; *cp != '"'; ++cp) /* empty */;
4402
4403         extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
4404         memcpy (extracted_name_value,
4405                 name_suffix_begin,
4406                 cp - name_suffix_begin);
4407         extracted_name_value[cp - name_suffix_begin] = '\0';
4408     }
4409
4410     return extracted_name_value;
4411 }