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