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