* Bug #15213, #18635: The use of the insecure m_scratch() and
[mmh] / uip / mhparse.c
1
2 /*
3  * mhparse.c -- routines to parse the contents of MIME messages
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 #include <h/mh.h>
13 #include <fcntl.h>
14 #include <h/signals.h>
15 #include <h/md5.h>
16 #include <errno.h>
17 #include <setjmp.h>
18 #include <signal.h>
19 #include <h/mts.h>
20 #include <h/tws.h>
21 #include <h/mime.h>
22 #include <h/mhparse.h>
23 #include <h/utils.h>
24
25 #ifdef HAVE_SYS_WAIT_H
26 # include <sys/wait.h>
27 #endif
28
29
30 extern int debugsw;
31
32 extern int endian;      /* mhmisc.c     */
33
34 extern pid_t xpid;      /* mhshowsbr.c  */
35
36 /* cache policies */
37 extern int rcachesw;    /* mhcachesbr.c */
38 extern int wcachesw;    /* mhcachesbr.c */
39
40 int checksw = 0;        /* check Content-MD5 field */
41
42 /*
43  * Directory to place temp files.  This must
44  * be set before these routines are called.
45  */
46 char *tmp;
47
48 /*
49  * Structures for TEXT messages
50  */
51 struct k2v SubText[] = {
52     { "plain",    TEXT_PLAIN },
53     { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
54     { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
55     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
56 };
57
58 struct k2v Charset[] = {
59     { "us-ascii",   CHARSET_USASCII },
60     { "iso-8859-1", CHARSET_LATIN },
61     { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
62 };
63
64 /*
65  * Structures for MULTIPART messages
66  */
67 struct k2v SubMultiPart[] = {
68     { "mixed",       MULTI_MIXED },
69     { "alternative", MULTI_ALTERNATE },
70     { "digest",      MULTI_DIGEST },
71     { "parallel",    MULTI_PARALLEL },
72     { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
73 };
74
75 /*
76  * Structures for MESSAGE messages
77  */
78 struct k2v SubMessage[] = {
79     { "rfc822",        MESSAGE_RFC822 },
80     { "partial",       MESSAGE_PARTIAL },
81     { "external-body", MESSAGE_EXTERNAL },
82     { NULL,            MESSAGE_UNKNOWN }        /* this one must be last! */
83 };
84
85 /*
86  * Structure for APPLICATION messages
87  */
88 struct k2v SubApplication[] = {
89     { "octet-stream", APPLICATION_OCTETS },
90     { "postscript",   APPLICATION_POSTSCRIPT },
91     { NULL,           APPLICATION_UNKNOWN }     /* this one must be last! */
92 };
93
94
95 /* ftpsbr.c */
96 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
97
98 /* mhcachesbr.c */
99 int find_cache (CT, int, int *, char *, char *, int);
100
101 /* mhmisc.c */
102 int part_ok (CT, int);
103 int type_ok (CT, int);
104 int make_intermediates (char *);
105 void content_error (char *, CT, char *, ...);
106
107 /* mhfree.c */
108 void free_content (CT);
109 void free_encoding (CT, int);
110
111 /*
112  * static prototypes
113  */
114 static CT get_content (FILE *, char *, int);
115 static int get_comment (CT, unsigned char **, int);
116
117 static int InitGeneric (CT);
118 static int InitText (CT);
119 static int InitMultiPart (CT);
120 static void reverse_parts (CT);
121 static int InitMessage (CT);
122 static int InitApplication (CT);
123 static int init_encoding (CT, OpenCEFunc);
124 static unsigned long size_encoding (CT);
125 static int InitBase64 (CT);
126 static int openBase64 (CT, char **);
127 static int InitQuoted (CT);
128 static int openQuoted (CT, char **);
129 static int Init7Bit (CT);
130 static int openExternal (CT, CT, CE, char **, int *);
131 static int InitFile (CT);
132 static int openFile (CT, char **);
133 static int InitFTP (CT);
134 static int openFTP (CT, char **);
135 static int InitMail (CT);
136 static int openMail (CT, char **);
137 static int readDigest (CT, char *);
138
139 struct str2init str2cts[] = {
140     { "application", CT_APPLICATION, InitApplication },
141     { "audio",       CT_AUDIO,       InitGeneric },
142     { "image",       CT_IMAGE,       InitGeneric },
143     { "message",     CT_MESSAGE,     InitMessage },
144     { "multipart",   CT_MULTIPART,   InitMultiPart },
145     { "text",        CT_TEXT,        InitText },
146     { "video",       CT_VIDEO,       InitGeneric },
147     { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
148     { NULL,          CT_UNKNOWN,     NULL },
149 };
150
151 struct str2init str2ces[] = {
152     { "base64",           CE_BASE64,    InitBase64 },
153     { "quoted-printable", CE_QUOTED,    InitQuoted },
154     { "8bit",             CE_8BIT,      Init7Bit },
155     { "7bit",             CE_7BIT,      Init7Bit },
156     { "binary",           CE_BINARY,    Init7Bit },
157     { NULL,               CE_EXTENSION, NULL },  /* these two must be last! */
158     { NULL,               CE_UNKNOWN,   NULL },
159 };
160
161 /*
162  * NOTE WELL: si_key MUST NOT have value of NOTOK
163  *
164  * si_key is 1 if access method is anonymous.
165  */
166 struct str2init str2methods[] = {
167     { "afs",         1, InitFile },
168     { "anon-ftp",    1, InitFTP },
169     { "ftp",         0, InitFTP },
170     { "local-file",  0, InitFile },
171     { "mail-server", 0, InitMail },
172     { NULL,          0, NULL }
173 };
174
175
176 int
177 pidcheck (int status)
178 {
179     if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
180         return status;
181
182     fflush (stdout);
183     fflush (stderr);
184     done (1);
185     return 1;
186 }
187
188
189 /*
190  * Main entry point for parsing a MIME message or file.
191  * It returns the Content structure for the top level
192  * entity in the file.
193  */
194
195 CT
196 parse_mime (char *file)
197 {
198     int is_stdin;
199     char buffer[BUFSIZ];
200     FILE *fp;
201     CT ct;
202
203     /*
204      * Check if file is actually standard input
205      */
206     if ((is_stdin = !(strcmp (file, "-")))) {
207         char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
208         if (tfile == NULL) {
209             advise("mhparse", "unable to create temporary file");
210             return NULL;
211         }
212         file = add (tfile, NULL);
213         chmod (file, 0600);
214
215         while (fgets (buffer, sizeof(buffer), stdin))
216             fputs (buffer, fp);
217         fflush (fp);
218
219         if (ferror (stdin)) {
220             unlink (file);
221             advise ("stdin", "error reading");
222             return NULL;
223         }
224         if (ferror (fp)) {
225             unlink (file);
226             advise (file, "error writing");
227             return NULL;
228         }
229         fseek (fp, 0L, SEEK_SET);
230     } else if ((fp = fopen (file, "r")) == NULL) {
231         advise (file, "unable to read");
232         return NULL;
233     }
234
235     if (!(ct = get_content (fp, file, 1))) {
236         if (is_stdin)
237             unlink (file);
238         advise (NULL, "unable to decode %s", file);
239         return NULL;
240     }
241
242     if (is_stdin)
243         ct->c_unlink = 1;       /* temp file to remove */
244
245     ct->c_fp = NULL;
246
247     if (ct->c_end == 0L) {
248         fseek (fp, 0L, SEEK_END);
249         ct->c_end = ftell (fp);
250     }
251
252     if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
253         fclose (fp);
254         free_content (ct);
255         return NULL;
256     }
257
258     fclose (fp);
259     return ct;
260 }
261
262
263 /*
264  * Main routine for reading/parsing the headers
265  * of a message content.
266  *
267  * toplevel =  1   # we are at the top level of the message
268  * toplevel =  0   # we are inside message type or multipart type
269  *                 # other than multipart/digest
270  * toplevel = -1   # we are inside multipart/digest
271  * NB: on failure we will fclose(in)!
272  */
273
274 static CT
275 get_content (FILE *in, char *file, int toplevel)
276 {
277     int compnum, state;
278     char buf[BUFSIZ], name[NAMESZ];
279     char *np, *vp;
280     CT ct;
281     HF hp;
282
283     /* allocate the content structure */
284     if (!(ct = (CT) calloc (1, sizeof(*ct))))
285         adios (NULL, "out of memory");
286
287     ct->c_fp = in;
288     ct->c_file = add (file, NULL);
289     ct->c_begin = ftell (ct->c_fp) + 1;
290
291     /*
292      * Parse the header fields for this
293      * content into a linked list.
294      */
295     for (compnum = 1, state = FLD;;) {
296         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
297         case FLD:
298         case FLDPLUS:
299         case FLDEOF:
300             compnum++;
301
302             /* get copies of the buffers */
303             np = add (name, NULL);
304             vp = add (buf, NULL);
305
306             /* if necessary, get rest of field */
307             while (state == FLDPLUS) {
308                 state = m_getfld (state, name, buf, sizeof(buf), in);
309                 vp = add (buf, vp);     /* add to previous value */
310             }
311
312             /* Now add the header data to the list */
313             add_header (ct, np, vp);
314
315             /* continue, if this isn't the last header field */
316             if (state != FLDEOF) {
317                 ct->c_begin = ftell (in) + 1;
318                 continue;
319             }
320             /* else fall... */
321
322         case BODY:
323         case BODYEOF:
324             ct->c_begin = ftell (in) - strlen (buf);
325             break;
326
327         case FILEEOF:
328             ct->c_begin = ftell (in);
329             break;
330
331         case LENERR:
332         case FMTERR:
333             adios (NULL, "message format error in component #%d", compnum);
334
335         default:
336             adios (NULL, "getfld() returned %d", state);
337         }
338
339         /* break out of the loop */
340         break;
341     }
342
343     /*
344      * Read the content headers.  We will parse the
345      * MIME related header fields into their various
346      * structures and set internal flags related to
347      * content type/subtype, etc.
348      */
349
350     hp = ct->c_first_hf;        /* start at first header field */
351     while (hp) {
352         /* Get MIME-Version field */
353         if (!mh_strcasecmp (hp->name, VRSN_FIELD)) {
354             int ucmp;
355             char c;
356             unsigned char *cp, *dp;
357
358             if (ct->c_vrsn) {
359                 advise (NULL, "message %s has multiple %s: fields",
360                         ct->c_file, VRSN_FIELD);
361                 goto next_header;
362             }
363             ct->c_vrsn = add (hp->value, NULL);
364
365             /* Now, cleanup this field */
366             cp = ct->c_vrsn;
367
368             while (isspace (*cp))
369                 cp++;
370             for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
371                 *dp++ = ' ';
372             for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
373                 if (!isspace (*dp))
374                     break;
375             *++dp = '\0';
376             if (debugsw)
377                 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
378
379             if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
380                 goto out;
381
382             for (dp = cp; istoken (*dp); dp++)
383                 continue;
384             c = *dp;
385             *dp = '\0';
386             ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
387             *dp = c;
388             if (!ucmp) {
389                 admonish (NULL, "message %s has unknown value for %s: field (%s)",
390                 ct->c_file, VRSN_FIELD, cp);
391             }
392         }
393         else if (!mh_strcasecmp (hp->name, TYPE_FIELD)) {
394         /* Get Content-Type field */
395             struct str2init *s2i;
396             CI ci = &ct->c_ctinfo;
397
398             /* Check if we've already seen a Content-Type header */
399             if (ct->c_ctline) {
400                 advise (NULL, "message %s has multiple %s: fields",
401                         ct->c_file, TYPE_FIELD);
402                 goto next_header;
403             }
404
405             /* Parse the Content-Type field */
406             if (get_ctinfo (hp->value, ct, 0) == NOTOK)
407                 goto out;
408
409             /*
410              * Set the Init function and the internal
411              * flag for this content type.
412              */
413             for (s2i = str2cts; s2i->si_key; s2i++)
414                 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
415                     break;
416             if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
417                 s2i++;
418             ct->c_type = s2i->si_val;
419             ct->c_ctinitfnx = s2i->si_init;
420         }
421         else if (!mh_strcasecmp (hp->name, ENCODING_FIELD)) {
422         /* Get Content-Transfer-Encoding field */
423             char c;
424             unsigned char *cp, *dp;
425             struct str2init *s2i;
426
427             /*
428              * Check if we've already seen the
429              * Content-Transfer-Encoding field
430              */
431             if (ct->c_celine) {
432                 advise (NULL, "message %s has multiple %s: fields",
433                         ct->c_file, ENCODING_FIELD);
434                 goto next_header;
435             }
436
437             /* get copy of this field */
438             ct->c_celine = cp = add (hp->value, NULL);
439
440             while (isspace (*cp))
441                 cp++;
442             for (dp = cp; istoken (*dp); dp++)
443                 continue;
444             c = *dp;
445             *dp = '\0';
446
447             /*
448              * Find the internal flag and Init function
449              * for this transfer encoding.
450              */
451             for (s2i = str2ces; s2i->si_key; s2i++)
452                 if (!mh_strcasecmp (cp, s2i->si_key))
453                     break;
454             if (!s2i->si_key && !uprf (cp, "X-"))
455                 s2i++;
456             *dp = c;
457             ct->c_encoding = s2i->si_val;
458
459             /* Call the Init function for this encoding */
460             if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
461                 goto out;
462         }
463         else if (!mh_strcasecmp (hp->name, MD5_FIELD)) {
464         /* Get Content-MD5 field */
465             unsigned char *cp, *dp;
466             char *ep;
467
468             if (!checksw)
469                 goto next_header;
470
471             if (ct->c_digested) {
472                 advise (NULL, "message %s has multiple %s: fields",
473                         ct->c_file, MD5_FIELD);
474                 goto next_header;
475             }
476
477             ep = cp = add (hp->value, NULL);    /* get a copy */
478
479             while (isspace (*cp))
480                 cp++;
481             for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
482                 *dp++ = ' ';
483             for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
484                 if (!isspace (*dp))
485                     break;
486             *++dp = '\0';
487             if (debugsw)
488                 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
489
490             if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
491                 free (ep);
492                 goto out;
493             }
494
495             for (dp = cp; *dp && !isspace (*dp); dp++)
496                 continue;
497             *dp = '\0';
498
499             readDigest (ct, cp);
500             free (ep);
501             ct->c_digested++;
502         }
503         else if (!mh_strcasecmp (hp->name, ID_FIELD)) {
504         /* Get Content-ID field */
505             ct->c_id = add (hp->value, ct->c_id);
506         }
507         else if (!mh_strcasecmp (hp->name, DESCR_FIELD)) {
508         /* Get Content-Description field */
509             ct->c_descr = add (hp->value, ct->c_descr);
510         }
511         else if (!mh_strcasecmp (hp->name, DISPO_FIELD)) {
512         /* Get Content-Disposition field */
513             ct->c_dispo = add (hp->value, ct->c_dispo);
514         }
515
516 next_header:
517         hp = hp->next;  /* next header field */
518     }
519
520     /*
521      * Check if we saw a Content-Type field.
522      * If not, then assign a default value for
523      * it, and the Init function.
524      */
525     if (!ct->c_ctline) {
526         /*
527          * If we are inside a multipart/digest message,
528          * so default type is message/rfc822
529          */
530         if (toplevel < 0) {
531             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
532                 goto out;
533             ct->c_type = CT_MESSAGE;
534             ct->c_ctinitfnx = InitMessage;
535         } else {
536             /*
537              * Else default type is text/plain
538              */
539             if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
540                 goto out;
541             ct->c_type = CT_TEXT;
542             ct->c_ctinitfnx = InitText;
543         }
544     }
545
546     /* Use default Transfer-Encoding, if necessary */
547     if (!ct->c_celine) {
548         ct->c_encoding = CE_7BIT;
549         Init7Bit (ct);
550     }
551
552     return ct;
553
554 out:
555     free_content (ct);
556     return NULL;
557 }
558
559
560 /*
561  * small routine to add header field to list
562  */
563
564 int
565 add_header (CT ct, char *name, char *value)
566 {
567     HF hp;
568
569     /* allocate header field structure */
570     hp = mh_xmalloc (sizeof(*hp));
571
572     /* link data into header structure */
573     hp->name = name;
574     hp->value = value;
575     hp->next = NULL;
576
577     /* link header structure into the list */
578     if (ct->c_first_hf == NULL) {
579         ct->c_first_hf = hp;            /* this is the first */
580         ct->c_last_hf = hp;
581     } else {
582         ct->c_last_hf->next = hp;       /* add it to the end */
583         ct->c_last_hf = hp;
584     }
585
586     return 0;
587 }
588
589
590 /* Make sure that buf contains at least one appearance of name,
591    followed by =.  If not, insert both name and value, just after
592    first semicolon, if any.  Note that name should not contain a
593    trailing =.  And quotes will be added around the value.  Typical
594    usage:  make sure that a Content-Disposition header contains
595    filename="foo".  If it doesn't and value does, use value from
596    that. */
597 static char *
598 incl_name_value (unsigned char *buf, char *name, char *value) {
599     char *newbuf = buf;
600
601     /* Assume that name is non-null. */
602     if (buf && value) {
603         char *name_plus_equal = concat (name, "=", NULL);
604
605         if (! strstr (buf, name_plus_equal)) {
606             char *insertion;
607             unsigned char *cp;
608             char *prefix, *suffix;
609
610             /* Trim trailing space, esp. newline. */
611             for (cp = &buf[strlen (buf) - 1];
612                  cp >= buf && isspace (*cp);
613                  --cp) {
614                 *cp = '\0';
615             }
616
617             insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
618
619             /* Insert at first semicolon, if any.  If none, append to
620                end. */
621             prefix = add (buf, NULL);
622             if ((cp = strchr (prefix, ';'))) {
623                 suffix = concat (cp, NULL);
624                 *cp = '\0';
625                 newbuf = concat (prefix, insertion, suffix, "\n", NULL);
626                 free (suffix);
627             } else {
628                 /* Append to end. */
629                 newbuf = concat (buf, insertion, "\n", NULL);
630             }
631
632             free (prefix);
633             free (insertion);
634             free (buf);
635         }
636
637         free (name_plus_equal);
638     }
639
640     return newbuf;
641 }
642
643 /* Extract just name_suffix="foo", if any, from value.  If there isn't
644    one, return the entire value.  Note that, for example, a name_suffix
645    of name will match filename="foo", and return foo. */
646 static char *
647 extract_name_value (char *name_suffix, char *value) {
648     char *extracted_name_value = value;
649     char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
650     char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
651     char *cp;
652
653     free (name_suffix_plus_quote);
654     if (name_suffix_equals) {
655         char *name_suffix_begin;
656
657         /* Find first \". */
658         for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
659         name_suffix_begin = ++cp;
660         /* Find second \". */
661         for (; *cp != '"'; ++cp) /* empty */;
662
663         extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
664         memcpy (extracted_name_value,
665                 name_suffix_begin,
666                 cp - name_suffix_begin);
667         extracted_name_value[cp - name_suffix_begin] = '\0';
668     }
669
670     return extracted_name_value;
671 }
672
673 /*
674  * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
675  * directives.  Fills in the information of the CTinfo structure.
676  */
677 int
678 get_ctinfo (unsigned char *cp, CT ct, int magic)
679 {
680     int i;
681     unsigned char *dp;
682     char **ap, **ep;
683     char c;
684     CI ci;
685
686     ci = &ct->c_ctinfo;
687     i = strlen (invo_name) + 2;
688
689     /* store copy of Content-Type line */
690     cp = ct->c_ctline = add (cp, NULL);
691
692     while (isspace (*cp))       /* trim leading spaces */
693         cp++;
694
695     /* change newlines to spaces */
696     for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
697         *dp++ = ' ';
698
699     /* trim trailing spaces */
700     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
701         if (!isspace (*dp))
702             break;
703     *++dp = '\0';
704
705     if (debugsw)
706         fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
707
708     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
709         return NOTOK;
710
711     for (dp = cp; istoken (*dp); dp++)
712         continue;
713     c = *dp, *dp = '\0';
714     ci->ci_type = add (cp, NULL);       /* store content type */
715     *dp = c, cp = dp;
716
717     if (!*ci->ci_type) {
718         advise (NULL, "invalid %s: field in message %s (empty type)", 
719                 TYPE_FIELD, ct->c_file);
720         return NOTOK;
721     }
722
723     /* down case the content type string */
724     for (dp = ci->ci_type; *dp; dp++)
725         if (isalpha(*dp) && isupper (*dp))
726             *dp = tolower (*dp);
727
728     while (isspace (*cp))
729         cp++;
730
731     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
732         return NOTOK;
733
734     if (*cp != '/') {
735         if (!magic)
736             ci->ci_subtype = add ("", NULL);
737         goto magic_skip;
738     }
739
740     cp++;
741     while (isspace (*cp))
742         cp++;
743
744     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
745         return NOTOK;
746
747     for (dp = cp; istoken (*dp); dp++)
748         continue;
749     c = *dp, *dp = '\0';
750     ci->ci_subtype = add (cp, NULL);    /* store the content subtype */
751     *dp = c, cp = dp;
752
753     if (!*ci->ci_subtype) {
754         advise (NULL,
755                 "invalid %s: field in message %s (empty subtype for \"%s\")",
756                 TYPE_FIELD, ct->c_file, ci->ci_type);
757         return NOTOK;
758     }
759
760     /* down case the content subtype string */
761     for (dp = ci->ci_subtype; *dp; dp++)
762         if (isalpha(*dp) && isupper (*dp))
763             *dp = tolower (*dp);
764
765 magic_skip:
766     while (isspace (*cp))
767         cp++;
768
769     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
770         return NOTOK;
771
772     /*
773      * Parse attribute/value pairs given with Content-Type
774      */
775     ep = (ap = ci->ci_attrs) + NPARMS;
776     while (*cp == ';') {
777         char *vp;
778         unsigned char *up;
779
780         if (ap >= ep) {
781             advise (NULL,
782                     "too many parameters in message %s's %s: field (%d max)",
783                     ct->c_file, TYPE_FIELD, NPARMS);
784             return NOTOK;
785         }
786
787         cp++;
788         while (isspace (*cp))
789             cp++;
790
791         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
792             return NOTOK;
793
794         if (*cp == 0) {
795             advise (NULL,
796                     "extraneous trailing ';' in message %s's %s: parameter list",
797                     ct->c_file, TYPE_FIELD);
798             return OK;
799         }
800
801         /* down case the attribute name */
802         for (dp = cp; istoken (*dp); dp++)
803             if (isalpha(*dp) && isupper (*dp))
804                 *dp = tolower (*dp);
805
806         for (up = dp; isspace (*dp);)
807             dp++;
808         if (dp == cp || *dp != '=') {
809             advise (NULL,
810                     "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
811                     ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
812             return NOTOK;
813         }
814
815         vp = (*ap = add (cp, NULL)) + (up - cp);
816         *vp = '\0';
817         for (dp++; isspace (*dp);)
818             dp++;
819
820         /* now add the attribute value */
821         ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
822
823         if (*dp == '"') {
824             for (cp = ++dp, dp = vp;;) {
825                 switch (c = *cp++) {
826                     case '\0':
827 bad_quote:
828                         advise (NULL,
829                                 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
830                                 ct->c_file, TYPE_FIELD, i, i, "", *ap);
831                         return NOTOK;
832
833                     case '\\':
834                         *dp++ = c;
835                         if ((c = *cp++) == '\0')
836                             goto bad_quote;
837                         /* else fall... */
838
839                     default:
840                         *dp++ = c;
841                         continue;
842
843                     case '"':
844                         *dp = '\0';
845                         break;
846                 }
847                 break;
848             }
849         } else {
850             for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
851                 continue;
852             *dp = '\0';
853         }
854         if (!*vp) {
855             advise (NULL,
856                     "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
857                     ct->c_file, TYPE_FIELD, i, i, "", *ap);
858             return NOTOK;
859         }
860         ap++;
861
862         while (isspace (*cp))
863             cp++;
864
865         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
866             return NOTOK;
867     }
868
869     /*
870      * Get any <Content-Id> given in buffer
871      */
872     if (magic && *cp == '<') {
873         if (ct->c_id) {
874             free (ct->c_id);
875             ct->c_id = NULL;
876         }
877         if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
878             advise (NULL, "invalid ID in message %s", ct->c_file);
879             return NOTOK;
880         }
881         c = *dp;
882         *dp = '\0';
883         if (*ct->c_id)
884             ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
885         else
886             ct->c_id = NULL;
887         *dp++ = c;
888         cp = dp;
889
890         while (isspace (*cp))
891             cp++;
892     }
893
894     /*
895      * Get any [Content-Description] given in buffer.
896      */
897     if (magic && *cp == '[') {
898         ct->c_descr = ++cp;
899         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
900             if (*dp == ']')
901                 break;
902         if (dp < cp) {
903             advise (NULL, "invalid description in message %s", ct->c_file);
904             ct->c_descr = NULL;
905             return NOTOK;
906         }
907         
908         c = *dp;
909         *dp = '\0';
910         if (*ct->c_descr)
911             ct->c_descr = concat (ct->c_descr, "\n", NULL);
912         else
913             ct->c_descr = NULL;
914         *dp++ = c;
915         cp = dp;
916
917         while (isspace (*cp))
918             cp++;
919     }
920
921     /*
922      * Get any {Content-Disposition} given in buffer.
923      */
924     if (magic && *cp == '{') {
925         ct->c_dispo = ++cp;
926         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
927             if (*dp == '}')
928                 break;
929         if (dp < cp) {
930             advise (NULL, "invalid disposition in message %s", ct->c_file);
931             ct->c_dispo = NULL;
932             return NOTOK;
933         }
934         
935         c = *dp;
936         *dp = '\0';
937         if (*ct->c_dispo)
938             ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
939         else
940             ct->c_dispo = NULL;
941         *dp++ = c;
942         cp = dp;
943
944         while (isspace (*cp))
945             cp++;
946     }
947
948     /*
949      * Check if anything is left over
950      */
951     if (*cp) {
952         if (magic) {
953             ci->ci_magic = add (cp, NULL);
954
955             /* If there is a Content-Disposition header and it doesn't
956                have a *filename=, extract it from the magic contents.
957                The r1bindex call skips any leading directory
958                components. */
959             if (ct->c_dispo)
960                 ct->c_dispo =
961                     incl_name_value (ct->c_dispo,
962                                      "filename",
963                                      r1bindex (extract_name_value ("name",
964                                                                    ci->
965                                                                    ci_magic),
966                                                '/'));
967         }
968         else
969             advise (NULL,
970                     "extraneous information in message %s's %s: field\n%*.*s(%s)",
971                     ct->c_file, TYPE_FIELD, i, i, "", cp);
972     }
973
974     return OK;
975 }
976
977
978 static int
979 get_comment (CT ct, unsigned char **ap, int istype)
980 {
981     int i;
982     char *bp;
983     unsigned char *cp;
984     char c, buffer[BUFSIZ], *dp;
985     CI ci;
986
987     ci = &ct->c_ctinfo;
988     cp = *ap;
989     bp = buffer;
990     cp++;
991
992     for (i = 0;;) {
993         switch (c = *cp++) {
994         case '\0':
995 invalid:
996         advise (NULL, "invalid comment in message %s's %s: field",
997                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
998         return NOTOK;
999
1000         case '\\':
1001             *bp++ = c;
1002             if ((c = *cp++) == '\0')
1003                 goto invalid;
1004             *bp++ = c;
1005             continue;
1006
1007         case '(':
1008             i++;
1009             /* and fall... */
1010         default:
1011             *bp++ = c;
1012             continue;
1013
1014         case ')':
1015             if (--i < 0)
1016                 break;
1017             *bp++ = c;
1018             continue;
1019         }
1020         break;
1021     }
1022     *bp = '\0';
1023
1024     if (istype) {
1025         if ((dp = ci->ci_comment)) {
1026             ci->ci_comment = concat (dp, " ", buffer, NULL);
1027             free (dp);
1028         } else {
1029             ci->ci_comment = add (buffer, NULL);
1030         }
1031     }
1032
1033     while (isspace (*cp))
1034         cp++;
1035
1036     *ap = cp;
1037     return OK;
1038 }
1039
1040
1041 /*
1042  * CONTENTS
1043  *
1044  * Handles content types audio, image, and video.
1045  * There's not much to do right here.
1046  */
1047
1048 static int
1049 InitGeneric (CT ct)
1050 {
1051     return OK;          /* not much to do here */
1052 }
1053
1054
1055 /*
1056  * TEXT
1057  */
1058
1059 static int
1060 InitText (CT ct)
1061 {
1062     char buffer[BUFSIZ];
1063     char *chset = NULL;
1064     char **ap, **ep, *cp;
1065     struct k2v *kv;
1066     struct text *t;
1067     CI ci = &ct->c_ctinfo;
1068
1069     /* check for missing subtype */
1070     if (!*ci->ci_subtype)
1071         ci->ci_subtype = add ("plain", ci->ci_subtype);
1072
1073     /* match subtype */
1074     for (kv = SubText; kv->kv_key; kv++)
1075         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1076             break;
1077     ct->c_subtype = kv->kv_value;
1078
1079     /* allocate text character set structure */
1080     if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1081         adios (NULL, "out of memory");
1082     ct->c_ctparams = (void *) t;
1083
1084     /* scan for charset parameter */
1085     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1086         if (!mh_strcasecmp (*ap, "charset"))
1087             break;
1088
1089     /* check if content specified a character set */
1090     if (*ap) {
1091         /* match character set or set to CHARSET_UNKNOWN */
1092         for (kv = Charset; kv->kv_key; kv++) {
1093             if (!mh_strcasecmp (*ep, kv->kv_key)) {
1094                 chset = *ep;
1095                 break;
1096             }
1097         }
1098         t->tx_charset = kv->kv_value;
1099     } else {
1100         t->tx_charset = CHARSET_UNSPECIFIED;
1101     }
1102
1103     /*
1104      * If we can not handle character set natively,
1105      * then check profile for string to modify the
1106      * terminal or display method.
1107      *
1108      * termproc is for mhshow, though mhlist -debug prints it, too.
1109      */
1110     if (chset != NULL && !check_charset (chset, strlen (chset))) {
1111         snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1112         if ((cp = context_find (buffer)))
1113             ct->c_termproc = getcpy (cp);
1114     }
1115
1116     return OK;
1117 }
1118
1119
1120 /*
1121  * MULTIPART
1122  */
1123
1124 static int
1125 InitMultiPart (CT ct)
1126 {
1127     int inout;
1128     long last, pos;
1129     unsigned char *cp, *dp;
1130     char **ap, **ep;
1131     char *bp, buffer[BUFSIZ];
1132     struct multipart *m;
1133     struct k2v *kv;
1134     struct part *part, **next;
1135     CI ci = &ct->c_ctinfo;
1136     CT p;
1137     FILE *fp;
1138
1139     /*
1140      * The encoding for multipart messages must be either
1141      * 7bit, 8bit, or binary (per RFC2045).
1142      */
1143     if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1144         && ct->c_encoding != CE_BINARY) {
1145         admonish (NULL,
1146                   "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1147                   ci->ci_type, ci->ci_subtype, ct->c_file);
1148         return NOTOK;
1149     }
1150
1151     /* match subtype */
1152     for (kv = SubMultiPart; kv->kv_key; kv++)
1153         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1154             break;
1155     ct->c_subtype = kv->kv_value;
1156
1157     /*
1158      * Check for "boundary" parameter, which is
1159      * required for multipart messages.
1160      */
1161     bp = 0;
1162     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1163         if (!mh_strcasecmp (*ap, "boundary")) {
1164             bp = *ep;
1165             break;
1166         }
1167     }
1168
1169     /* complain if boundary parameter is missing */
1170     if (!*ap) {
1171         advise (NULL,
1172                 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1173                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1174         return NOTOK;
1175     }
1176
1177     /* allocate primary structure for multipart info */
1178     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1179         adios (NULL, "out of memory");
1180     ct->c_ctparams = (void *) m;
1181
1182     /* check if boundary parameter contains only whitespace characters */
1183     for (cp = bp; isspace (*cp); cp++)
1184         continue;
1185     if (!*cp) {
1186         advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1187                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1188         return NOTOK;
1189     }
1190
1191     /* remove trailing whitespace from boundary parameter */
1192     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1193         if (!isspace (*dp))
1194             break;
1195     *++dp = '\0';
1196
1197     /* record boundary separators */
1198     m->mp_start = concat (bp, "\n", NULL);
1199     m->mp_stop = concat (bp, "--\n", NULL);
1200
1201     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1202         advise (ct->c_file, "unable to open for reading");
1203         return NOTOK;
1204     }
1205
1206     fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1207     last = ct->c_end;
1208     next = &m->mp_parts;
1209     part = NULL;
1210     inout = 1;
1211
1212     while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1213         if (pos > last)
1214             break;
1215
1216         pos += strlen (buffer);
1217         if (buffer[0] != '-' || buffer[1] != '-')
1218             continue;
1219         if (inout) {
1220             if (strcmp (buffer + 2, m->mp_start))
1221                 continue;
1222 next_part:
1223             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1224                 adios (NULL, "out of memory");
1225             *next = part;
1226             next = &part->mp_next;
1227
1228             if (!(p = get_content (fp, ct->c_file,
1229                         ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1230                 ct->c_fp = NULL;
1231                 return NOTOK;
1232             }
1233             p->c_fp = NULL;
1234             part->mp_part = p;
1235             pos = p->c_begin;
1236             fseek (fp, pos, SEEK_SET);
1237             inout = 0;
1238         } else {
1239             if (strcmp (buffer + 2, m->mp_start) == 0) {
1240                 inout = 1;
1241 end_part:
1242                 p = part->mp_part;
1243                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1244                 if (p->c_end < p->c_begin)
1245                     p->c_begin = p->c_end;
1246                 if (inout)
1247                     goto next_part;
1248                 goto last_part;
1249             } else {
1250                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1251                     goto end_part;
1252             }
1253         }
1254     }
1255
1256     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1257     if (!inout && part) {
1258         p = part->mp_part;
1259         p->c_end = ct->c_end;
1260
1261         if (p->c_begin >= p->c_end) {
1262             for (next = &m->mp_parts; *next != part;
1263                      next = &((*next)->mp_next))
1264                 continue;
1265             *next = NULL;
1266             free_content (p);
1267             free ((char *) part);
1268         }
1269     }
1270
1271 last_part:
1272     /* reverse the order of the parts for multipart/alternative */
1273     if (ct->c_subtype == MULTI_ALTERNATE)
1274         reverse_parts (ct);
1275
1276     /*
1277      * label all subparts with part number, and
1278      * then initialize the content of the subpart.
1279      */
1280     {
1281         int partnum;
1282         char *pp;
1283         char partnam[BUFSIZ];
1284
1285         if (ct->c_partno) {
1286             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1287             pp = partnam + strlen (partnam);
1288         } else {
1289             pp = partnam;
1290         }
1291
1292         for (part = m->mp_parts, partnum = 1; part;
1293                  part = part->mp_next, partnum++) {
1294             p = part->mp_part;
1295
1296             sprintf (pp, "%d", partnum);
1297             p->c_partno = add (partnam, NULL);
1298
1299             /* initialize the content of the subparts */
1300             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1301                 fclose (ct->c_fp);
1302                 ct->c_fp = NULL;
1303                 return NOTOK;
1304             }
1305         }
1306     }
1307
1308     fclose (ct->c_fp);
1309     ct->c_fp = NULL;
1310     return OK;
1311 }
1312
1313
1314 /*
1315  * reverse the order of the parts of a multipart
1316  */
1317
1318 static void
1319 reverse_parts (CT ct)
1320 {
1321     int i;
1322     struct multipart *m;
1323     struct part **base, **bmp, **next, *part;
1324
1325     m = (struct multipart *) ct->c_ctparams;
1326
1327     /* if only one part, just return */
1328     if (!m->mp_parts || !m->mp_parts->mp_next)
1329         return;
1330
1331     /* count number of parts */
1332     i = 0;
1333     for (part = m->mp_parts; part; part = part->mp_next)
1334         i++;
1335
1336     /* allocate array of pointers to the parts */
1337     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1338         adios (NULL, "out of memory");
1339     bmp = base;
1340
1341     /* point at all the parts */
1342     for (part = m->mp_parts; part; part = part->mp_next)
1343         *bmp++ = part;
1344     *bmp = NULL;
1345
1346     /* reverse the order of the parts */
1347     next = &m->mp_parts;
1348     for (bmp--; bmp >= base; bmp--) {
1349         part = *bmp;
1350         *next = part;
1351         next = &part->mp_next;
1352     }
1353     *next = NULL;
1354
1355     /* free array of pointers */
1356     free ((char *) base);
1357 }
1358
1359
1360 /*
1361  * MESSAGE
1362  */
1363
1364 static int
1365 InitMessage (CT ct)
1366 {
1367     struct k2v *kv;
1368     CI ci = &ct->c_ctinfo;
1369
1370     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1371         admonish (NULL,
1372                   "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1373                   ci->ci_type, ci->ci_subtype, ct->c_file);
1374         return NOTOK;
1375     }
1376
1377     /* check for missing subtype */
1378     if (!*ci->ci_subtype)
1379         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1380
1381     /* match subtype */
1382     for (kv = SubMessage; kv->kv_key; kv++)
1383         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1384             break;
1385     ct->c_subtype = kv->kv_value;
1386
1387     switch (ct->c_subtype) {
1388         case MESSAGE_RFC822:
1389             break;
1390
1391         case MESSAGE_PARTIAL:
1392             {
1393                 char **ap, **ep;
1394                 struct partial *p;
1395
1396                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1397                     adios (NULL, "out of memory");
1398                 ct->c_ctparams = (void *) p;
1399
1400                 /* scan for parameters "id", "number", and "total" */
1401                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1402                     if (!mh_strcasecmp (*ap, "id")) {
1403                         p->pm_partid = add (*ep, NULL);
1404                         continue;
1405                     }
1406                     if (!mh_strcasecmp (*ap, "number")) {
1407                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1408                                 || p->pm_partno < 1) {
1409 invalid_param:
1410                             advise (NULL,
1411                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1412                                     *ap, ci->ci_type, ci->ci_subtype,
1413                                     ct->c_file, TYPE_FIELD);
1414                             return NOTOK;
1415                         }
1416                         continue;
1417                     }
1418                     if (!mh_strcasecmp (*ap, "total")) {
1419                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1420                                 || p->pm_maxno < 1)
1421                             goto invalid_param;
1422                         continue;
1423                     }
1424                 }
1425
1426                 if (!p->pm_partid
1427                         || !p->pm_partno
1428                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1429                     advise (NULL,
1430                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1431                             ci->ci_type, ci->ci_subtype,
1432                             ct->c_file, TYPE_FIELD);
1433                     return NOTOK;
1434                 }
1435             }
1436             break;
1437
1438         case MESSAGE_EXTERNAL:
1439             {
1440                 int exresult;
1441                 struct exbody *e;
1442                 CT p;
1443                 FILE *fp;
1444
1445                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1446                     adios (NULL, "out of memory");
1447                 ct->c_ctparams = (void *) e;
1448
1449                 if (!ct->c_fp
1450                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1451                     advise (ct->c_file, "unable to open for reading");
1452                     return NOTOK;
1453                 }
1454
1455                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1456
1457                 if (!(p = get_content (fp, ct->c_file, 0))) {
1458                     ct->c_fp = NULL;
1459                     return NOTOK;
1460                 }
1461
1462                 e->eb_parent = ct;
1463                 e->eb_content = p;
1464                 p->c_ctexbody = e;
1465                 if ((exresult = params_external (ct, 0)) != NOTOK
1466                         && p->c_ceopenfnx == openMail) {
1467                     int cc, size;
1468                     char *bp;
1469                     
1470                     if ((size = ct->c_end - p->c_begin) <= 0) {
1471                         if (!e->eb_subject)
1472                             content_error (NULL, ct,
1473                                            "empty body for access-type=mail-server");
1474                         goto no_body;
1475                     }
1476                     
1477                     e->eb_body = bp = mh_xmalloc ((unsigned) size);
1478                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1479                     while (size > 0)
1480                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1481                             case NOTOK:
1482                                 adios ("failed", "fread");
1483
1484                             case OK:
1485                                 adios (NULL, "unexpected EOF from fread");
1486
1487                             default:
1488                                 bp += cc, size -= cc;
1489                                 break;
1490                         }
1491                     *bp = 0;
1492                 }
1493 no_body:
1494                 p->c_fp = NULL;
1495                 p->c_end = p->c_begin;
1496
1497                 fclose (ct->c_fp);
1498                 ct->c_fp = NULL;
1499
1500                 if (exresult == NOTOK)
1501                     return NOTOK;
1502                 if (e->eb_flags == NOTOK)
1503                     return OK;
1504
1505                 switch (p->c_type) {
1506                     case CT_MULTIPART:
1507                         break;
1508
1509                     case CT_MESSAGE:
1510                         if (p->c_subtype != MESSAGE_RFC822)
1511                             break;
1512                         /* else fall... */
1513                     default:
1514                         e->eb_partno = ct->c_partno;
1515                         if (p->c_ctinitfnx)
1516                             (*p->c_ctinitfnx) (p);
1517                         break;
1518                 }
1519             }
1520             break;
1521
1522         default:
1523             break;
1524     }
1525
1526     return OK;
1527 }
1528
1529
1530 int
1531 params_external (CT ct, int composing)
1532 {
1533     char **ap, **ep;
1534     struct exbody *e = (struct exbody *) ct->c_ctparams;
1535     CI ci = &ct->c_ctinfo;
1536
1537     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1538         if (!mh_strcasecmp (*ap, "access-type")) {
1539             struct str2init *s2i;
1540             CT p = e->eb_content;
1541
1542             for (s2i = str2methods; s2i->si_key; s2i++)
1543                 if (!mh_strcasecmp (*ep, s2i->si_key))
1544                     break;
1545             if (!s2i->si_key) {
1546                 e->eb_access = *ep;
1547                 e->eb_flags = NOTOK;
1548                 p->c_encoding = CE_EXTERNAL;
1549                 continue;
1550             }
1551             e->eb_access = s2i->si_key;
1552             e->eb_flags = s2i->si_val;
1553             p->c_encoding = CE_EXTERNAL;
1554
1555             /* Call the Init function for this external type */
1556             if ((*s2i->si_init)(p) == NOTOK)
1557                 return NOTOK;
1558             continue;
1559         }
1560         if (!mh_strcasecmp (*ap, "name")) {
1561             e->eb_name = *ep;
1562             continue;
1563         }
1564         if (!mh_strcasecmp (*ap, "permission")) {
1565             e->eb_permission = *ep;
1566             continue;
1567         }
1568         if (!mh_strcasecmp (*ap, "site")) {
1569             e->eb_site = *ep;
1570             continue;
1571         }
1572         if (!mh_strcasecmp (*ap, "directory")) {
1573             e->eb_dir = *ep;
1574             continue;
1575         }
1576         if (!mh_strcasecmp (*ap, "mode")) {
1577             e->eb_mode = *ep;
1578             continue;
1579         }
1580         if (!mh_strcasecmp (*ap, "size")) {
1581             sscanf (*ep, "%lu", &e->eb_size);
1582             continue;
1583         }
1584         if (!mh_strcasecmp (*ap, "server")) {
1585             e->eb_server = *ep;
1586             continue;
1587         }
1588         if (!mh_strcasecmp (*ap, "subject")) {
1589             e->eb_subject = *ep;
1590             continue;
1591         }
1592         if (composing && !mh_strcasecmp (*ap, "body")) {
1593             e->eb_body = getcpy (*ep);
1594             continue;
1595         }
1596     }
1597
1598     if (!e->eb_access) {
1599         advise (NULL,
1600                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1601                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1602         return NOTOK;
1603     }
1604
1605     return OK;
1606 }
1607
1608
1609 /*
1610  * APPLICATION
1611  */
1612
1613 static int
1614 InitApplication (CT ct)
1615 {
1616     struct k2v *kv;
1617     CI ci = &ct->c_ctinfo;
1618
1619     /* match subtype */
1620     for (kv = SubApplication; kv->kv_key; kv++)
1621         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1622             break;
1623     ct->c_subtype = kv->kv_value;
1624
1625     return OK;
1626 }
1627
1628
1629 /*
1630  * TRANSFER ENCODINGS
1631  */
1632
1633 static int
1634 init_encoding (CT ct, OpenCEFunc openfnx)
1635 {
1636     CE ce;
1637
1638     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1639         adios (NULL, "out of memory");
1640
1641     ct->c_cefile     = ce;
1642     ct->c_ceopenfnx  = openfnx;
1643     ct->c_ceclosefnx = close_encoding;
1644     ct->c_cesizefnx  = size_encoding;
1645
1646     return OK;
1647 }
1648
1649
1650 void
1651 close_encoding (CT ct)
1652 {
1653     CE ce;
1654
1655     if (!(ce = ct->c_cefile))
1656         return;
1657
1658     if (ce->ce_fp) {
1659         fclose (ce->ce_fp);
1660         ce->ce_fp = NULL;
1661     }
1662 }
1663
1664
1665 static unsigned long
1666 size_encoding (CT ct)
1667 {
1668     int fd;
1669     unsigned long size;
1670     char *file;
1671     CE ce;
1672     struct stat st;
1673
1674     if (!(ce = ct->c_cefile))
1675         return (ct->c_end - ct->c_begin);
1676
1677     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1678         return (long) st.st_size;
1679
1680     if (ce->ce_file) {
1681         if (stat (ce->ce_file, &st) != NOTOK)
1682             return (long) st.st_size;
1683         else
1684             return 0L;
1685     }
1686
1687     if (ct->c_encoding == CE_EXTERNAL)
1688         return (ct->c_end - ct->c_begin);       
1689
1690     file = NULL;
1691     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1692         return (ct->c_end - ct->c_begin);
1693
1694     if (fstat (fd, &st) != NOTOK)
1695         size = (long) st.st_size;
1696     else
1697         size = 0L;
1698
1699     (*ct->c_ceclosefnx) (ct);
1700     return size;
1701 }
1702
1703
1704 /*
1705  * BASE64
1706  */
1707
1708 static unsigned char b642nib[0x80] = {
1709     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1710     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1711     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1712     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1713     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1714     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1715     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1716     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1717     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1718     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1719     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1720     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1721     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1722     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1723     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1724     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1725 };
1726
1727
1728 static int
1729 InitBase64 (CT ct)
1730 {
1731     return init_encoding (ct, openBase64);
1732 }
1733
1734
1735 static int
1736 openBase64 (CT ct, char **file)
1737 {
1738     int bitno, cc, digested;
1739     int fd, len, skip;
1740     unsigned long bits;
1741     unsigned char value, *b, *b1, *b2, *b3;
1742     unsigned char *cp, *ep;
1743     char buffer[BUFSIZ];
1744     /* sbeck -- handle suffixes */
1745     CI ci;
1746     CE ce;
1747     MD5_CTX mdContext;
1748
1749     b  = (unsigned char *) &bits;
1750     b1 = &b[endian > 0 ? 1 : 2];
1751     b2 = &b[endian > 0 ? 2 : 1];
1752     b3 = &b[endian > 0 ? 3 : 0];
1753
1754     ce = ct->c_cefile;
1755     if (ce->ce_fp) {
1756         fseek (ce->ce_fp, 0L, SEEK_SET);
1757         goto ready_to_go;
1758     }
1759
1760     if (ce->ce_file) {
1761         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1762             content_error (ce->ce_file, ct, "unable to fopen for reading");
1763             return NOTOK;
1764         }
1765         goto ready_to_go;
1766     }
1767
1768     if (*file == NULL) {
1769         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1770         ce->ce_unlink = 1;
1771     } else {
1772         ce->ce_file = add (*file, NULL);
1773         ce->ce_unlink = 0;
1774     }
1775
1776     /* sbeck@cise.ufl.edu -- handle suffixes */
1777     ci = &ct->c_ctinfo;
1778     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1779               invo_name, ci->ci_type, ci->ci_subtype);
1780     cp = context_find (buffer);
1781     if (cp == NULL || *cp == '\0') {
1782         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1783                   ci->ci_type);
1784         cp = context_find (buffer);
1785     }
1786     if (cp != NULL && *cp != '\0') {
1787         if (ce->ce_unlink) {
1788             // Temporary file already exists, so we rename to
1789             // version with extension.
1790             char *file_org = strdup(ce->ce_file);
1791             ce->ce_file = add (cp, ce->ce_file);
1792             if (rename(file_org, ce->ce_file)) {
1793                 adios (ce->ce_file, "unable to rename %s to ", file_org);
1794             }
1795             free(file_org);
1796
1797         } else {
1798             ce->ce_file = add (cp, ce->ce_file);
1799         }
1800     }
1801
1802     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1803         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1804         return NOTOK;
1805     }
1806
1807     if ((len = ct->c_end - ct->c_begin) < 0)
1808         adios (NULL, "internal error(1)");
1809
1810     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1811         content_error (ct->c_file, ct, "unable to open for reading");
1812         return NOTOK;
1813     }
1814     
1815     if ((digested = ct->c_digested))
1816         MD5Init (&mdContext);
1817
1818     bitno = 18;
1819     bits = 0L;
1820     skip = 0;
1821
1822     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1823     while (len > 0) {
1824         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1825         case NOTOK:
1826             content_error (ct->c_file, ct, "error reading from");
1827             goto clean_up;
1828
1829         case OK:
1830             content_error (NULL, ct, "premature eof");
1831             goto clean_up;
1832
1833         default:
1834             if (cc > len)
1835                 cc = len;
1836             len -= cc;
1837
1838             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1839                 switch (*cp) {
1840                 default:
1841                     if (isspace (*cp))
1842                         break;
1843                     if (skip || (*cp & 0x80)
1844                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1845                         if (debugsw) {
1846                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1847                                 *cp,
1848                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1849                                 skip);
1850                         }
1851                         content_error (NULL, ct,
1852                                        "invalid BASE64 encoding -- continuing");
1853                         continue;
1854                     }
1855
1856                     bits |= value << bitno;
1857 test_end:
1858                     if ((bitno -= 6) < 0) {
1859                         putc ((char) *b1, ce->ce_fp);
1860                         if (digested)
1861                             MD5Update (&mdContext, b1, 1);
1862                         if (skip < 2) {
1863                             putc ((char) *b2, ce->ce_fp);
1864                             if (digested)
1865                                 MD5Update (&mdContext, b2, 1);
1866                             if (skip < 1) {
1867                                 putc ((char) *b3, ce->ce_fp);
1868                                 if (digested)
1869                                     MD5Update (&mdContext, b3, 1);
1870                             }
1871                         }
1872
1873                         if (ferror (ce->ce_fp)) {
1874                             content_error (ce->ce_file, ct,
1875                                            "error writing to");
1876                             goto clean_up;
1877                         }
1878                         bitno = 18, bits = 0L, skip = 0;
1879                     }
1880                     break;
1881
1882                 case '=':
1883                     if (++skip > 3)
1884                         goto self_delimiting;
1885                     goto test_end;
1886                 }
1887             }
1888         }
1889     }
1890
1891     if (bitno != 18) {
1892         if (debugsw)
1893             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1894
1895         content_error (NULL, ct, "invalid BASE64 encoding");
1896         goto clean_up;
1897     }
1898
1899 self_delimiting:
1900     fseek (ct->c_fp, 0L, SEEK_SET);
1901
1902     if (fflush (ce->ce_fp)) {
1903         content_error (ce->ce_file, ct, "error writing to");
1904         goto clean_up;
1905     }
1906
1907     if (digested) {
1908         unsigned char digest[16];
1909
1910         MD5Final (digest, &mdContext);
1911         if (memcmp((char *) digest, (char *) ct->c_digest,
1912                    sizeof(digest) / sizeof(digest[0])))
1913             content_error (NULL, ct,
1914                            "content integrity suspect (digest mismatch) -- continuing");
1915         else
1916             if (debugsw)
1917                 fprintf (stderr, "content integrity confirmed\n");
1918     }
1919
1920     fseek (ce->ce_fp, 0L, SEEK_SET);
1921
1922 ready_to_go:
1923     *file = ce->ce_file;
1924     return fileno (ce->ce_fp);
1925
1926 clean_up:
1927     free_encoding (ct, 0);
1928     return NOTOK;
1929 }
1930
1931
1932 /*
1933  * QUOTED PRINTABLE
1934  */
1935
1936 static char hex2nib[0x80] = {
1937     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1938     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1939     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1940     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1941     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1942     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1943     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1944     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1945     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
1946     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1947     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1948     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1949     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
1950     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1951     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1952     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1953 };
1954
1955
1956 static int 
1957 InitQuoted (CT ct)
1958 {
1959     return init_encoding (ct, openQuoted);
1960 }
1961
1962
1963 static int
1964 openQuoted (CT ct, char **file)
1965 {
1966     int cc, digested, len, quoted;
1967     unsigned char *cp, *ep;
1968     char buffer[BUFSIZ];
1969     unsigned char mask;
1970     CE ce;
1971     /* sbeck -- handle suffixes */
1972     CI ci;
1973     MD5_CTX mdContext;
1974
1975     ce = ct->c_cefile;
1976     if (ce->ce_fp) {
1977         fseek (ce->ce_fp, 0L, SEEK_SET);
1978         goto ready_to_go;
1979     }
1980
1981     if (ce->ce_file) {
1982         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1983             content_error (ce->ce_file, ct, "unable to fopen for reading");
1984             return NOTOK;
1985         }
1986         goto ready_to_go;
1987     }
1988
1989     if (*file == NULL) {
1990         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1991         ce->ce_unlink = 1;
1992     } else {
1993         ce->ce_file = add (*file, NULL);
1994         ce->ce_unlink = 0;
1995     }
1996
1997     /* sbeck@cise.ufl.edu -- handle suffixes */
1998     ci = &ct->c_ctinfo;
1999     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2000               invo_name, ci->ci_type, ci->ci_subtype);
2001     cp = context_find (buffer);
2002     if (cp == NULL || *cp == '\0') {
2003         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2004                   ci->ci_type);
2005         cp = context_find (buffer);
2006     }
2007     if (cp != NULL && *cp != '\0') {
2008         if (ce->ce_unlink) {
2009             // Temporary file already exists, so we rename to
2010             // version with extension.
2011             char *file_org = strdup(ce->ce_file);
2012             ce->ce_file = add (cp, ce->ce_file);
2013             if (rename(file_org, ce->ce_file)) {
2014                 adios (ce->ce_file, "unable to rename %s to ", file_org);
2015             }
2016             free(file_org);
2017
2018         } else {
2019             ce->ce_file = add (cp, ce->ce_file);
2020         }
2021     }
2022
2023     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2024         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2025         return NOTOK;
2026     }
2027
2028     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2029         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2030         return NOTOK;
2031     }
2032
2033     if ((len = ct->c_end - ct->c_begin) < 0)
2034         adios (NULL, "internal error(2)");
2035
2036     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2037         content_error (ct->c_file, ct, "unable to open for reading");
2038         return NOTOK;
2039     }
2040
2041     if ((digested = ct->c_digested))
2042         MD5Init (&mdContext);
2043
2044     quoted = 0;
2045 #ifdef lint
2046     mask = 0;
2047 #endif
2048
2049     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2050     while (len > 0) {
2051         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2052             content_error (NULL, ct, "premature eof");
2053             goto clean_up;
2054         }
2055
2056         if ((cc = strlen (buffer)) > len)
2057             cc = len;
2058         len -= cc;
2059
2060         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2061             if (!isspace (*ep))
2062                 break;
2063         *++ep = '\n', ep++;
2064
2065         for (; cp < ep; cp++) {
2066             if (quoted > 0) {
2067                 /* in an escape sequence */
2068                 if (quoted == 1) {
2069                     /* at byte 1 of an escape sequence */
2070                     mask = hex2nib[*cp & 0x7f];
2071                     /* next is byte 2 */
2072                     quoted = 2;
2073                 } else {
2074                     /* at byte 2 of an escape sequence */
2075                     mask <<= 4;
2076                     mask |= hex2nib[*cp & 0x7f];
2077                     putc (mask, ce->ce_fp);
2078                     if (digested)
2079                         MD5Update (&mdContext, &mask, 1);
2080                     if (ferror (ce->ce_fp)) {
2081                         content_error (ce->ce_file, ct, "error writing to");
2082                         goto clean_up;
2083                     }
2084                     /* finished escape sequence; next may be literal or a new
2085                      * escape sequence */
2086                     quoted = 0;
2087                 }
2088                 /* on to next byte */
2089                 continue;
2090             }
2091
2092             /* not in an escape sequence */
2093             if (*cp == '=') {
2094                 /* starting an escape sequence, or invalid '='? */
2095                 if (cp + 1 < ep && cp[1] == '\n') {
2096                     /* "=\n" soft line break, eat the \n */
2097                     cp++;
2098                     continue;
2099                 }
2100                 if (cp + 1 >= ep || cp + 2 >= ep) {
2101                     /* We don't have 2 bytes left, so this is an invalid
2102                      * escape sequence; just show the raw bytes (below). */
2103                 } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
2104                     /* Next 2 bytes are hex digits, making this a valid escape
2105                      * sequence; let's decode it (above). */
2106                     quoted = 1;
2107                     continue;
2108                 } else {
2109                     /* One or both of the next 2 is out of range, making this
2110                      * an invalid escape sequence; just show the raw bytes
2111                      * (below). */
2112                 }
2113             }
2114
2115             /* Just show the raw byte. */
2116             putc (*cp, ce->ce_fp);
2117             if (digested) {
2118                 if (*cp == '\n') {
2119                     MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2120                 } else {
2121                     MD5Update (&mdContext, (unsigned char *) cp, 1);
2122                 }
2123             }
2124             if (ferror (ce->ce_fp)) {
2125                 content_error (ce->ce_file, ct, "error writing to");
2126                 goto clean_up;
2127             }
2128         }
2129     }
2130     if (quoted) {
2131         content_error (NULL, ct,
2132                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2133         goto clean_up;
2134     }
2135
2136     fseek (ct->c_fp, 0L, SEEK_SET);
2137
2138     if (fflush (ce->ce_fp)) {
2139         content_error (ce->ce_file, ct, "error writing to");
2140         goto clean_up;
2141     }
2142
2143     if (digested) {
2144         unsigned char digest[16];
2145
2146         MD5Final (digest, &mdContext);
2147         if (memcmp((char *) digest, (char *) ct->c_digest,
2148                    sizeof(digest) / sizeof(digest[0])))
2149             content_error (NULL, ct,
2150                            "content integrity suspect (digest mismatch) -- continuing");
2151         else
2152             if (debugsw)
2153                 fprintf (stderr, "content integrity confirmed\n");
2154     }
2155
2156     fseek (ce->ce_fp, 0L, SEEK_SET);
2157
2158 ready_to_go:
2159     *file = ce->ce_file;
2160     return fileno (ce->ce_fp);
2161
2162 clean_up:
2163     free_encoding (ct, 0);
2164     return NOTOK;
2165 }
2166
2167
2168 /*
2169  * 7BIT
2170  */
2171
2172 static int
2173 Init7Bit (CT ct)
2174 {
2175     if (init_encoding (ct, open7Bit) == NOTOK)
2176         return NOTOK;
2177
2178     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2179     return OK;
2180 }
2181
2182
2183 int
2184 open7Bit (CT ct, char **file)
2185 {
2186     int cc, fd, len;
2187     char buffer[BUFSIZ];
2188     /* sbeck -- handle suffixes */
2189     char *cp;
2190     CI ci;
2191     CE ce;
2192
2193     ce = ct->c_cefile;
2194     if (ce->ce_fp) {
2195         fseek (ce->ce_fp, 0L, SEEK_SET);
2196         goto ready_to_go;
2197     }
2198
2199     if (ce->ce_file) {
2200         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2201             content_error (ce->ce_file, ct, "unable to fopen for reading");
2202             return NOTOK;
2203         }
2204         goto ready_to_go;
2205     }
2206
2207     if (*file == NULL) {
2208         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2209         ce->ce_unlink = 1;
2210     } else {
2211         ce->ce_file = add (*file, NULL);
2212         ce->ce_unlink = 0;
2213     }
2214
2215     /* sbeck@cise.ufl.edu -- handle suffixes */
2216     ci = &ct->c_ctinfo;
2217     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2218               invo_name, ci->ci_type, ci->ci_subtype);
2219     cp = context_find (buffer);
2220     if (cp == NULL || *cp == '\0') {
2221         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2222                   ci->ci_type);
2223         cp = context_find (buffer);
2224     }
2225     if (cp != NULL && *cp != '\0') {
2226         if (ce->ce_unlink) {
2227             // Temporary file already exists, so we rename to
2228             // version with extension.
2229             char *file_org = strdup(ce->ce_file);
2230             ce->ce_file = add (cp, ce->ce_file);
2231             if (rename(file_org, ce->ce_file)) {
2232                 adios (ce->ce_file, "unable to rename %s to ", file_org);
2233             }
2234             free(file_org);
2235
2236         } else {
2237             ce->ce_file = add (cp, ce->ce_file);
2238         }
2239     }
2240
2241     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2242         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2243         return NOTOK;
2244     }
2245
2246     if (ct->c_type == CT_MULTIPART) {
2247         char **ap, **ep;
2248         CI ci = &ct->c_ctinfo;
2249
2250         len = 0;
2251         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2252         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2253             + 1 + strlen (ci->ci_subtype);
2254         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2255             putc (';', ce->ce_fp);
2256             len++;
2257
2258             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2259
2260             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2261                 fputs ("\n\t", ce->ce_fp);
2262                 len = 8;
2263             } else {
2264                 putc (' ', ce->ce_fp);
2265                 len++;
2266             }
2267             fprintf (ce->ce_fp, "%s", buffer);
2268             len += cc;
2269         }
2270
2271         if (ci->ci_comment) {
2272             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2273                 fputs ("\n\t", ce->ce_fp);
2274                 len = 8;
2275             }
2276             else {
2277                 putc (' ', ce->ce_fp);
2278                 len++;
2279             }
2280             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2281             len += cc;
2282         }
2283         fprintf (ce->ce_fp, "\n");
2284         if (ct->c_id)
2285             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2286         if (ct->c_descr)
2287             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2288         if (ct->c_dispo)
2289             fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2290         fprintf (ce->ce_fp, "\n");
2291     }
2292
2293     if ((len = ct->c_end - ct->c_begin) < 0)
2294         adios (NULL, "internal error(3)");
2295
2296     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2297         content_error (ct->c_file, ct, "unable to open for reading");
2298         return NOTOK;
2299     }
2300
2301     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2302     while (len > 0)
2303         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2304         case NOTOK:
2305             content_error (ct->c_file, ct, "error reading from");
2306             goto clean_up;
2307
2308         case OK:
2309             content_error (NULL, ct, "premature eof");
2310             goto clean_up;
2311
2312         default:
2313             if (cc > len)
2314                 cc = len;
2315             len -= cc;
2316
2317             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2318             if (ferror (ce->ce_fp)) {
2319                 content_error (ce->ce_file, ct, "error writing to");
2320                 goto clean_up;
2321             }
2322         }
2323
2324     fseek (ct->c_fp, 0L, SEEK_SET);
2325
2326     if (fflush (ce->ce_fp)) {
2327         content_error (ce->ce_file, ct, "error writing to");
2328         goto clean_up;
2329     }
2330
2331     fseek (ce->ce_fp, 0L, SEEK_SET);
2332
2333 ready_to_go:
2334     *file = ce->ce_file;
2335     return fileno (ce->ce_fp);
2336
2337 clean_up:
2338     free_encoding (ct, 0);
2339     return NOTOK;
2340 }
2341
2342
2343 /*
2344  * External
2345  */
2346
2347 static int
2348 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2349 {
2350     char cachefile[BUFSIZ];
2351
2352     if (ce->ce_fp) {
2353         fseek (ce->ce_fp, 0L, SEEK_SET);
2354         goto ready_already;
2355     }
2356
2357     if (ce->ce_file) {
2358         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2359             content_error (ce->ce_file, ct, "unable to fopen for reading");
2360             return NOTOK;
2361         }
2362         goto ready_already;
2363     }
2364
2365     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2366                 cachefile, sizeof(cachefile)) != NOTOK) {
2367         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2368             ce->ce_file = getcpy (cachefile);
2369             ce->ce_unlink = 0;
2370             goto ready_already;
2371         } else {
2372             admonish (cachefile, "unable to fopen for reading");
2373         }
2374     }
2375
2376     return OK;
2377
2378 ready_already:
2379     *file = ce->ce_file;
2380     *fd = fileno (ce->ce_fp);
2381     return DONE;
2382 }
2383
2384 /*
2385  * File
2386  */
2387
2388 static int
2389 InitFile (CT ct)
2390 {
2391     return init_encoding (ct, openFile);
2392 }
2393
2394
2395 static int
2396 openFile (CT ct, char **file)
2397 {
2398     int fd, cachetype;
2399     char cachefile[BUFSIZ];
2400     struct exbody *e = ct->c_ctexbody;
2401     CE ce = ct->c_cefile;
2402
2403     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2404         case NOTOK:
2405             return NOTOK;
2406
2407         case OK:
2408             break;
2409
2410         case DONE:
2411             return fd;
2412     }
2413
2414     if (!e->eb_name) {
2415         content_error (NULL, ct, "missing name parameter");
2416         return NOTOK;
2417     }
2418
2419     ce->ce_file = getcpy (e->eb_name);
2420     ce->ce_unlink = 0;
2421
2422     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2423         content_error (ce->ce_file, ct, "unable to fopen for reading");
2424         return NOTOK;
2425     }
2426
2427     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2428             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2429                 cachefile, sizeof(cachefile)) != NOTOK) {
2430         int mask;
2431         FILE *fp;
2432
2433         mask = umask (cachetype ? ~m_gmprot () : 0222);
2434         if ((fp = fopen (cachefile, "w"))) {
2435             int cc;
2436             char buffer[BUFSIZ];
2437             FILE *gp = ce->ce_fp;
2438
2439             fseek (gp, 0L, SEEK_SET);
2440
2441             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2442                        > 0)
2443                 fwrite (buffer, sizeof(*buffer), cc, fp);
2444             fflush (fp);
2445
2446             if (ferror (gp)) {
2447                 admonish (ce->ce_file, "error reading");
2448                 unlink (cachefile);
2449             }
2450             else
2451                 if (ferror (fp)) {
2452                     admonish (cachefile, "error writing");
2453                     unlink (cachefile);
2454                 }
2455             fclose (fp);
2456         }
2457         umask (mask);
2458     }
2459
2460     fseek (ce->ce_fp, 0L, SEEK_SET);
2461     *file = ce->ce_file;
2462     return fileno (ce->ce_fp);
2463 }
2464
2465 /*
2466  * FTP
2467  */
2468
2469 static int
2470 InitFTP (CT ct)
2471 {
2472     return init_encoding (ct, openFTP);
2473 }
2474
2475
2476 static int
2477 openFTP (CT ct, char **file)
2478 {
2479     int cachetype, caching, fd;
2480     int len, buflen;
2481     char *bp, *ftp, *user, *pass;
2482     char buffer[BUFSIZ], cachefile[BUFSIZ];
2483     struct exbody *e;
2484     CE ce;
2485     static char *username = NULL;
2486     static char *password = NULL;
2487
2488     e  = ct->c_ctexbody;
2489     ce = ct->c_cefile;
2490
2491     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2492         ftp = NULL;
2493
2494 #ifndef BUILTIN_FTP
2495     if (!ftp)
2496         return NOTOK;
2497 #endif
2498
2499     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2500         case NOTOK:
2501             return NOTOK;
2502
2503         case OK:
2504             break;
2505
2506         case DONE:
2507             return fd;
2508     }
2509
2510     if (!e->eb_name || !e->eb_site) {
2511         content_error (NULL, ct, "missing %s parameter",
2512                        e->eb_name ? "site": "name");
2513         return NOTOK;
2514     }
2515
2516     if (xpid) {
2517         if (xpid < 0)
2518             xpid = -xpid;
2519         pidcheck (pidwait (xpid, NOTOK));
2520         xpid = 0;
2521     }
2522
2523     /* Get the buffer ready to go */
2524     bp = buffer;
2525     buflen = sizeof(buffer);
2526
2527     /*
2528      * Construct the query message for user
2529      */
2530     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2531     len = strlen (bp);
2532     bp += len;
2533     buflen -= len;
2534
2535     if (e->eb_partno) {
2536         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2537         len = strlen (bp);
2538         bp += len;
2539         buflen -= len;
2540     }
2541
2542     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2543                     e->eb_flags ? "anonymous " : "", e->eb_site);
2544     len = strlen (bp);
2545     bp += len;
2546     buflen -= len;
2547
2548     if (e->eb_size > 0) {
2549         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2550         len = strlen (bp);
2551         bp += len;
2552         buflen -= len;
2553     }
2554     snprintf (bp, buflen, "? ");
2555
2556     /*
2557      * Now, check the answer
2558      */
2559     if (!getanswer (buffer))
2560         return NOTOK;
2561
2562     if (e->eb_flags) {
2563         user = "anonymous";
2564         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2565         pass = buffer;
2566     } else {
2567         ruserpass (e->eb_site, &username, &password);
2568         user = username;
2569         pass = password;
2570     }
2571
2572     ce->ce_unlink = (*file == NULL);
2573     caching = 0;
2574     cachefile[0] = '\0';
2575     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2576             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2577                 cachefile, sizeof(cachefile)) != NOTOK) {
2578         if (*file == NULL) {
2579             ce->ce_unlink = 0;
2580             caching = 1;
2581         }
2582     }
2583
2584     if (*file)
2585         ce->ce_file = add (*file, NULL);
2586     else if (caching)
2587         ce->ce_file = add (cachefile, NULL);
2588     else
2589         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2590
2591     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2592         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2593         return NOTOK;
2594     }
2595
2596 #ifdef BUILTIN_FTP
2597     if (ftp)
2598 #endif
2599     {
2600         int child_id, i, vecp;
2601         char *vec[9];
2602
2603         vecp = 0;
2604         vec[vecp++] = r1bindex (ftp, '/');
2605         vec[vecp++] = e->eb_site;
2606         vec[vecp++] = user;
2607         vec[vecp++] = pass;
2608         vec[vecp++] = e->eb_dir;
2609         vec[vecp++] = e->eb_name;
2610         vec[vecp++] = ce->ce_file,
2611         vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2612                         ? "ascii" : "binary";
2613         vec[vecp] = NULL;
2614
2615         fflush (stdout);
2616
2617         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2618             sleep (5);
2619         switch (child_id) {
2620             case NOTOK:
2621                 adios ("fork", "unable to");
2622                 /* NOTREACHED */
2623
2624             case OK:
2625                 close (fileno (ce->ce_fp));
2626                 execvp (ftp, vec);
2627                 fprintf (stderr, "unable to exec ");
2628                 perror (ftp);
2629                 _exit (-1);
2630                 /* NOTREACHED */
2631
2632             default:
2633                 if (pidXwait (child_id, NULL)) {
2634 #ifdef BUILTIN_FTP
2635 losing_ftp:
2636 #endif
2637                     username = password = NULL;
2638                     ce->ce_unlink = 1;
2639                     return NOTOK;
2640                 }
2641                 break;
2642         }
2643     }
2644 #ifdef BUILTIN_FTP
2645     else
2646         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2647                      ce->ce_file,
2648                      e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
2649                 == NOTOK)
2650             goto losing_ftp;
2651 #endif
2652
2653     if (cachefile[0]) {
2654         if (caching)
2655             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2656         else {
2657             int mask;
2658             FILE *fp;
2659
2660             mask = umask (cachetype ? ~m_gmprot () : 0222);
2661             if ((fp = fopen (cachefile, "w"))) {
2662                 int cc;
2663                 FILE *gp = ce->ce_fp;
2664
2665                 fseek (gp, 0L, SEEK_SET);
2666
2667                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2668                            > 0)
2669                     fwrite (buffer, sizeof(*buffer), cc, fp);
2670                 fflush (fp);
2671
2672                 if (ferror (gp)) {
2673                     admonish (ce->ce_file, "error reading");
2674                     unlink (cachefile);
2675                 }
2676                 else
2677                     if (ferror (fp)) {
2678                         admonish (cachefile, "error writing");
2679                         unlink (cachefile);
2680                     }
2681                 fclose (fp);
2682             }
2683             umask (mask);
2684         }
2685     }
2686
2687     fseek (ce->ce_fp, 0L, SEEK_SET);
2688     *file = ce->ce_file;
2689     return fileno (ce->ce_fp);
2690 }
2691
2692
2693 /*
2694  * Mail
2695  */
2696
2697 static int
2698 InitMail (CT ct)
2699 {
2700     return init_encoding (ct, openMail);
2701 }
2702
2703
2704 static int
2705 openMail (CT ct, char **file)
2706 {
2707     int child_id, fd, i, vecp;
2708     int len, buflen;
2709     char *bp, buffer[BUFSIZ], *vec[7];
2710     struct exbody *e = ct->c_ctexbody;
2711     CE ce = ct->c_cefile;
2712
2713     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2714         case NOTOK:
2715             return NOTOK;
2716
2717         case OK:
2718             break;
2719
2720         case DONE:
2721             return fd;
2722     }
2723
2724     if (!e->eb_server) {
2725         content_error (NULL, ct, "missing server parameter");
2726         return NOTOK;
2727     }
2728
2729     if (xpid) {
2730         if (xpid < 0)
2731             xpid = -xpid;
2732         pidcheck (pidwait (xpid, NOTOK));
2733         xpid = 0;
2734     }
2735
2736     /* Get buffer ready to go */
2737     bp = buffer;
2738     buflen = sizeof(buffer);
2739
2740     /* Now, construct query message */
2741     snprintf (bp, buflen, "Retrieve content");
2742     len = strlen (bp);
2743     bp += len;
2744     buflen -= len;
2745
2746     if (e->eb_partno) {
2747         snprintf (bp, buflen, " %s", e->eb_partno);
2748         len = strlen (bp);
2749         bp += len;
2750         buflen -= len;
2751     }
2752
2753     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2754                     e->eb_server,
2755                     e->eb_subject ? e->eb_subject : e->eb_body);
2756
2757     /* Now, check answer */
2758     if (!getanswer (buffer))
2759         return NOTOK;
2760
2761     vecp = 0;
2762     vec[vecp++] = r1bindex (mailproc, '/');
2763     vec[vecp++] = e->eb_server;
2764     vec[vecp++] = "-subject";
2765     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2766     vec[vecp++] = "-body";
2767     vec[vecp++] = e->eb_body;
2768     vec[vecp] = NULL;
2769
2770     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2771         sleep (5);
2772     switch (child_id) {
2773         case NOTOK:
2774             advise ("fork", "unable to");
2775             return NOTOK;
2776
2777         case OK:
2778             execvp (mailproc, vec);
2779             fprintf (stderr, "unable to exec ");
2780             perror (mailproc);
2781             _exit (-1);
2782             /* NOTREACHED */
2783
2784         default:
2785             if (pidXwait (child_id, NULL) == OK)
2786                 advise (NULL, "request sent");
2787             break;
2788     }
2789
2790     if (*file == NULL) {
2791         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2792         ce->ce_unlink = 1;
2793     } else {
2794         ce->ce_file = add (*file, NULL);
2795         ce->ce_unlink = 0;
2796     }
2797
2798     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2799         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2800         return NOTOK;
2801     }
2802
2803     /* showproc is for mhshow and mhstore, though mhlist -debug
2804      * prints it, too. */
2805     if (ct->c_showproc)
2806         free (ct->c_showproc);
2807     ct->c_showproc = add ("true", NULL);
2808
2809     fseek (ce->ce_fp, 0L, SEEK_SET);
2810     *file = ce->ce_file;
2811     return fileno (ce->ce_fp);
2812 }
2813
2814
2815 static int
2816 readDigest (CT ct, char *cp)
2817 {
2818     int bitno, skip;
2819     unsigned long bits;
2820     char *bp = cp;
2821     unsigned char *dp, value, *ep;
2822     unsigned char *b, *b1, *b2, *b3;
2823
2824     b  = (unsigned char *) &bits,
2825     b1 = &b[endian > 0 ? 1 : 2],
2826     b2 = &b[endian > 0 ? 2 : 1],
2827     b3 = &b[endian > 0 ? 3 : 0];
2828     bitno = 18;
2829     bits = 0L;
2830     skip = 0;
2831
2832     for (ep = (dp = ct->c_digest)
2833                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2834         switch (*cp) {
2835             default:
2836                 if (skip
2837                         || (*cp & 0x80)
2838                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2839                     if (debugsw)
2840                         fprintf (stderr, "invalid BASE64 encoding\n");
2841                     return NOTOK;
2842                 }
2843
2844                 bits |= value << bitno;
2845 test_end:
2846                 if ((bitno -= 6) < 0) {
2847                     if (dp + (3 - skip) > ep)
2848                         goto invalid_digest;
2849                     *dp++ = *b1;
2850                     if (skip < 2) {
2851                         *dp++ = *b2;
2852                         if (skip < 1)
2853                             *dp++ = *b3;
2854                     }
2855                     bitno = 18;
2856                     bits = 0L;
2857                     skip = 0;
2858                 }
2859                 break;
2860
2861             case '=':
2862                 if (++skip > 3)
2863                     goto self_delimiting;
2864                 goto test_end;
2865         }
2866     if (bitno != 18) {
2867         if (debugsw)
2868             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2869
2870         return NOTOK;
2871     }
2872 self_delimiting:
2873     if (dp != ep) {
2874 invalid_digest:
2875         if (debugsw) {
2876             while (*cp)
2877                 cp++;
2878             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2879                      (int)(cp - bp));
2880         }
2881
2882         return NOTOK;
2883     }
2884
2885     if (debugsw) {
2886         fprintf (stderr, "MD5 digest=");
2887         for (dp = ct->c_digest; dp < ep; dp++)
2888             fprintf (stderr, "%02x", *dp & 0xff);
2889         fprintf (stderr, "\n");
2890     }
2891
2892     return OK;
2893 }