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