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