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