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