67717890cb31595feaf961b5c98b663948b53177
[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         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
1863             content_error (NULL, ct, "premature eof");
1864             goto clean_up;
1865         }
1866
1867         if ((cc = strlen (buffer)) > len)
1868             cc = len;
1869         len -= cc;
1870
1871         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
1872             if (!isspace (*ep))
1873                 break;
1874         *++ep = '\n', ep++;
1875
1876         for (; cp < ep; cp++) {
1877             if (quoted > 0) {
1878                 /* in an escape sequence */
1879                 if (quoted == 1) {
1880                     /* at byte 1 of an escape sequence */
1881                     mask = hex2nib[*cp & 0x7f];
1882                     /* next is byte 2 */
1883                     quoted = 2;
1884                 } else {
1885                     /* at byte 2 of an escape sequence */
1886                     mask <<= 4;
1887                     mask |= hex2nib[*cp & 0x7f];
1888                     putc (mask, ce->ce_fp);
1889                     if (digested)
1890                         MD5Update (&mdContext, &mask, 1);
1891                     if (ferror (ce->ce_fp)) {
1892                         content_error (ce->ce_file, ct, "error writing to");
1893                         goto clean_up;
1894                     }
1895                     /* finished escape sequence; next may be literal or a new
1896                      * escape sequence */
1897                     quoted = 0;
1898                 }
1899                 /* on to next byte */
1900                 continue;
1901             }
1902
1903             /* not in an escape sequence */
1904             if (*cp == '=') {
1905                 /* starting an escape sequence, or invalid '='? */
1906                 if (cp + 1 < ep && cp[1] == '\n') {
1907                     /* "=\n" soft line break, eat the \n */
1908                     cp++;
1909                     continue;
1910                 }
1911                 if (cp + 1 >= ep || cp + 2 >= ep) {
1912                     /* We don't have 2 bytes left, so this is an invalid
1913                      * escape sequence; just show the raw bytes (below). */
1914                 } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
1915                     /* Next 2 bytes are hex digits, making this a valid escape
1916                      * sequence; let's decode it (above). */
1917                     quoted = 1;
1918                     continue;
1919                 } else {
1920                     /* One or both of the next 2 is out of range, making this
1921                      * an invalid escape sequence; just show the raw bytes
1922                      * (below). */
1923                 }
1924             }
1925
1926             /* Just show the raw byte. */
1927             putc (*cp, ce->ce_fp);
1928             if (digested) {
1929                 if (*cp == '\n') {
1930                     MD5Update (&mdContext, (unsigned char *) "\r\n",2);
1931                 } else {
1932                     MD5Update (&mdContext, (unsigned char *) cp, 1);
1933                 }
1934             }
1935             if (ferror (ce->ce_fp)) {
1936                 content_error (ce->ce_file, ct, "error writing to");
1937                 goto clean_up;
1938             }
1939         }
1940     }
1941     if (quoted) {
1942         content_error (NULL, ct,
1943                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
1944         goto clean_up;
1945     }
1946
1947     fseek (ct->c_fp, 0L, SEEK_SET);
1948
1949     if (fflush (ce->ce_fp)) {
1950         content_error (ce->ce_file, ct, "error writing to");
1951         goto clean_up;
1952     }
1953
1954     if (digested) {
1955         unsigned char digest[16];
1956
1957         MD5Final (digest, &mdContext);
1958         if (memcmp((char *) digest, (char *) ct->c_digest,
1959                    sizeof(digest) / sizeof(digest[0])))
1960             content_error (NULL, ct,
1961                            "content integrity suspect (digest mismatch) -- continuing");
1962         else
1963             if (debugsw)
1964                 fprintf (stderr, "content integrity confirmed\n");
1965     }
1966
1967     fseek (ce->ce_fp, 0L, SEEK_SET);
1968
1969 ready_to_go:
1970     *file = ce->ce_file;
1971     return fileno (ce->ce_fp);
1972
1973 clean_up:
1974     free_encoding (ct, 0);
1975     return NOTOK;
1976 }
1977
1978
1979 /*
1980  * 7BIT
1981  */
1982
1983 static int
1984 Init7Bit (CT ct)
1985 {
1986     if (init_encoding (ct, open7Bit) == NOTOK)
1987         return NOTOK;
1988
1989     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
1990     return OK;
1991 }
1992
1993
1994 static int
1995 open7Bit (CT ct, char **file)
1996 {
1997     int cc, fd, len;
1998     char buffer[BUFSIZ];
1999     /* sbeck -- handle prefixes */
2000     char *cp;
2001     CI ci;
2002     CE ce;
2003
2004     ce = ct->c_cefile;
2005     if (ce->ce_fp) {
2006         fseek (ce->ce_fp, 0L, SEEK_SET);
2007         goto ready_to_go;
2008     }
2009
2010     if (ce->ce_file) {
2011         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2012             content_error (ce->ce_file, ct, "unable to fopen for reading");
2013             return NOTOK;
2014         }
2015         goto ready_to_go;
2016     }
2017
2018     if (*file == NULL) {
2019         ce->ce_file = add (m_scratch ("", tmp), NULL);
2020         ce->ce_unlink = 1;
2021     } else {
2022         ce->ce_file = add (*file, NULL);
2023         ce->ce_unlink = 0;
2024     }
2025
2026     /* sbeck@cise.ufl.edu -- handle suffixes */
2027     ci = &ct->c_ctinfo;
2028     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2029               invo_name, ci->ci_type, ci->ci_subtype);
2030     cp = context_find (buffer);
2031     if (cp == NULL || *cp == '\0') {
2032         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2033                   ci->ci_type);
2034         cp = context_find (buffer);
2035     }
2036     if (cp != NULL && *cp != '\0')
2037         ce->ce_file = add (cp, ce->ce_file);
2038
2039     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2040         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2041         return NOTOK;
2042     }
2043
2044     if (ct->c_type == CT_MULTIPART) {
2045         char **ap, **ep;
2046         CI ci = &ct->c_ctinfo;
2047
2048         len = 0;
2049         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2050         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2051             + 1 + strlen (ci->ci_subtype);
2052         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2053             putc (';', ce->ce_fp);
2054             len++;
2055
2056             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2057
2058             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2059                 fputs ("\n\t", ce->ce_fp);
2060                 len = 8;
2061             } else {
2062                 putc (' ', ce->ce_fp);
2063                 len++;
2064             }
2065             fprintf (ce->ce_fp, "%s", buffer);
2066             len += cc;
2067         }
2068
2069         if (ci->ci_comment) {
2070             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2071                 fputs ("\n\t", ce->ce_fp);
2072                 len = 8;
2073             }
2074             else {
2075                 putc (' ', ce->ce_fp);
2076                 len++;
2077             }
2078             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2079             len += cc;
2080         }
2081         fprintf (ce->ce_fp, "\n");
2082         if (ct->c_id)
2083             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2084         if (ct->c_descr)
2085             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2086         fprintf (ce->ce_fp, "\n");
2087     }
2088
2089     if ((len = ct->c_end - ct->c_begin) < 0)
2090         adios (NULL, "internal error(3)");
2091
2092     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2093         content_error (ct->c_file, ct, "unable to open for reading");
2094         return NOTOK;
2095     }
2096
2097     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2098     while (len > 0)
2099         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2100         case NOTOK:
2101             content_error (ct->c_file, ct, "error reading from");
2102             goto clean_up;
2103
2104         case OK:
2105             content_error (NULL, ct, "premature eof");
2106             goto clean_up;
2107
2108         default:
2109             if (cc > len)
2110                 cc = len;
2111             len -= cc;
2112
2113             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2114             if (ferror (ce->ce_fp)) {
2115                 content_error (ce->ce_file, ct, "error writing to");
2116                 goto clean_up;
2117             }
2118         }
2119
2120     fseek (ct->c_fp, 0L, SEEK_SET);
2121
2122     if (fflush (ce->ce_fp)) {
2123         content_error (ce->ce_file, ct, "error writing to");
2124         goto clean_up;
2125     }
2126
2127     fseek (ce->ce_fp, 0L, SEEK_SET);
2128
2129 ready_to_go:
2130     *file = ce->ce_file;
2131     return fileno (ce->ce_fp);
2132
2133 clean_up:
2134     free_encoding (ct, 0);
2135     return NOTOK;
2136 }
2137
2138
2139 /*
2140  * External
2141  */
2142
2143 static int
2144 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2145 {
2146     char cachefile[BUFSIZ];
2147
2148     if (ce->ce_fp) {
2149         fseek (ce->ce_fp, 0L, SEEK_SET);
2150         goto ready_already;
2151     }
2152
2153     if (ce->ce_file) {
2154         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2155             content_error (ce->ce_file, ct, "unable to fopen for reading");
2156             return NOTOK;
2157         }
2158         goto ready_already;
2159     }
2160
2161     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2162                 cachefile, sizeof(cachefile)) != NOTOK) {
2163         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2164             ce->ce_file = getcpy (cachefile);
2165             ce->ce_unlink = 0;
2166             goto ready_already;
2167         } else {
2168             admonish (cachefile, "unable to fopen for reading");
2169         }
2170     }
2171
2172     return OK;
2173
2174 ready_already:
2175     *file = ce->ce_file;
2176     *fd = fileno (ce->ce_fp);
2177     return DONE;
2178 }
2179
2180 /*
2181  * File
2182  */
2183
2184 static int
2185 InitFile (CT ct)
2186 {
2187     return init_encoding (ct, openFile);
2188 }
2189
2190
2191 static int
2192 openFile (CT ct, char **file)
2193 {
2194     int fd, cachetype;
2195     char cachefile[BUFSIZ];
2196     struct exbody *e = ct->c_ctexbody;
2197     CE ce = ct->c_cefile;
2198
2199     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2200         case NOTOK:
2201             return NOTOK;
2202
2203         case OK:
2204             break;
2205
2206         case DONE:
2207             return fd;
2208     }
2209
2210     if (!e->eb_name) {
2211         content_error (NULL, ct, "missing name parameter");
2212         return NOTOK;
2213     }
2214
2215     ce->ce_file = getcpy (e->eb_name);
2216     ce->ce_unlink = 0;
2217
2218     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2219         content_error (ce->ce_file, ct, "unable to fopen for reading");
2220         return NOTOK;
2221     }
2222
2223     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2224             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2225                 cachefile, sizeof(cachefile)) != NOTOK) {
2226         int mask;
2227         FILE *fp;
2228
2229         mask = umask (cachetype ? ~m_gmprot () : 0222);
2230         if ((fp = fopen (cachefile, "w"))) {
2231             int cc;
2232             char buffer[BUFSIZ];
2233             FILE *gp = ce->ce_fp;
2234
2235             fseek (gp, 0L, SEEK_SET);
2236
2237             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2238                        > 0)
2239                 fwrite (buffer, sizeof(*buffer), cc, fp);
2240             fflush (fp);
2241
2242             if (ferror (gp)) {
2243                 admonish (ce->ce_file, "error reading");
2244                 unlink (cachefile);
2245             }
2246             else
2247                 if (ferror (fp)) {
2248                     admonish (cachefile, "error writing");
2249                     unlink (cachefile);
2250                 }
2251             fclose (fp);
2252         }
2253         umask (mask);
2254     }
2255
2256     fseek (ce->ce_fp, 0L, SEEK_SET);
2257     *file = ce->ce_file;
2258     return fileno (ce->ce_fp);
2259 }
2260
2261 /*
2262  * FTP
2263  */
2264
2265 static int
2266 InitFTP (CT ct)
2267 {
2268     return init_encoding (ct, openFTP);
2269 }
2270
2271
2272 static int
2273 openFTP (CT ct, char **file)
2274 {
2275     int cachetype, caching, fd;
2276     int len, buflen;
2277     char *bp, *ftp, *user, *pass;
2278     char buffer[BUFSIZ], cachefile[BUFSIZ];
2279     struct exbody *e;
2280     CE ce;
2281     static char *username = NULL;
2282     static char *password = NULL;
2283
2284     e  = ct->c_ctexbody;
2285     ce = ct->c_cefile;
2286
2287     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2288         ftp = NULL;
2289
2290 #ifndef BUILTIN_FTP
2291     if (!ftp)
2292         return NOTOK;
2293 #endif
2294
2295     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2296         case NOTOK:
2297             return NOTOK;
2298
2299         case OK:
2300             break;
2301
2302         case DONE:
2303             return fd;
2304     }
2305
2306     if (!e->eb_name || !e->eb_site) {
2307         content_error (NULL, ct, "missing %s parameter",
2308                        e->eb_name ? "site": "name");
2309         return NOTOK;
2310     }
2311
2312     if (xpid) {
2313         if (xpid < 0)
2314             xpid = -xpid;
2315         pidcheck (pidwait (xpid, NOTOK));
2316         xpid = 0;
2317     }
2318
2319     /* Get the buffer ready to go */
2320     bp = buffer;
2321     buflen = sizeof(buffer);
2322
2323     /*
2324      * Construct the query message for user
2325      */
2326     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2327     len = strlen (bp);
2328     bp += len;
2329     buflen -= len;
2330
2331     if (e->eb_partno) {
2332         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2333         len = strlen (bp);
2334         bp += len;
2335         buflen -= len;
2336     }
2337
2338     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2339                     e->eb_flags ? "anonymous " : "", e->eb_site);
2340     len = strlen (bp);
2341     bp += len;
2342     buflen -= len;
2343
2344     if (e->eb_size > 0) {
2345         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2346         len = strlen (bp);
2347         bp += len;
2348         buflen -= len;
2349     }
2350     snprintf (bp, buflen, "? ");
2351
2352     /*
2353      * Now, check the answer
2354      */
2355     if (!getanswer (buffer))
2356         return NOTOK;
2357
2358     if (e->eb_flags) {
2359         user = "anonymous";
2360         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2361         pass = buffer;
2362     } else {
2363         ruserpass (e->eb_site, &username, &password);
2364         user = username;
2365         pass = password;
2366     }
2367
2368     ce->ce_unlink = (*file == NULL);
2369     caching = 0;
2370     cachefile[0] = '\0';
2371     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2372             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2373                 cachefile, sizeof(cachefile)) != NOTOK) {
2374         if (*file == NULL) {
2375             ce->ce_unlink = 0;
2376             caching = 1;
2377         }
2378     }
2379
2380     if (*file)
2381         ce->ce_file = add (*file, NULL);
2382     else if (caching)
2383         ce->ce_file = add (cachefile, NULL);
2384     else
2385         ce->ce_file = add (m_scratch ("", tmp), NULL);
2386
2387     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2388         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2389         return NOTOK;
2390     }
2391
2392 #ifdef BUILTIN_FTP
2393     if (ftp)
2394 #endif
2395     {
2396         int child_id, i, vecp;
2397         char *vec[9];
2398
2399         vecp = 0;
2400         vec[vecp++] = r1bindex (ftp, '/');
2401         vec[vecp++] = e->eb_site;
2402         vec[vecp++] = user;
2403         vec[vecp++] = pass;
2404         vec[vecp++] = e->eb_dir;
2405         vec[vecp++] = e->eb_name;
2406         vec[vecp++] = ce->ce_file,
2407         vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2408                         ? "ascii" : "binary";
2409         vec[vecp] = NULL;
2410
2411         fflush (stdout);
2412
2413         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2414             sleep (5);
2415         switch (child_id) {
2416             case NOTOK:
2417                 adios ("fork", "unable to");
2418                 /* NOTREACHED */
2419
2420             case OK:
2421                 close (fileno (ce->ce_fp));
2422                 execvp (ftp, vec);
2423                 fprintf (stderr, "unable to exec ");
2424                 perror (ftp);
2425                 _exit (-1);
2426                 /* NOTREACHED */
2427
2428             default:
2429                 if (pidXwait (child_id, NULL)) {
2430 #ifdef BUILTIN_FTP
2431 losing_ftp:
2432 #endif
2433                     username = password = NULL;
2434                     ce->ce_unlink = 1;
2435                     return NOTOK;
2436                 }
2437                 break;
2438         }
2439     }
2440 #ifdef BUILTIN_FTP
2441     else
2442         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2443                      ce->ce_file,
2444                      e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
2445                 == NOTOK)
2446             goto losing_ftp;
2447 #endif
2448
2449     if (cachefile[0]) {
2450         if (caching)
2451             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2452         else {
2453             int mask;
2454             FILE *fp;
2455
2456             mask = umask (cachetype ? ~m_gmprot () : 0222);
2457             if ((fp = fopen (cachefile, "w"))) {
2458                 int cc;
2459                 FILE *gp = ce->ce_fp;
2460
2461                 fseek (gp, 0L, SEEK_SET);
2462
2463                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2464                            > 0)
2465                     fwrite (buffer, sizeof(*buffer), cc, fp);
2466                 fflush (fp);
2467
2468                 if (ferror (gp)) {
2469                     admonish (ce->ce_file, "error reading");
2470                     unlink (cachefile);
2471                 }
2472                 else
2473                     if (ferror (fp)) {
2474                         admonish (cachefile, "error writing");
2475                         unlink (cachefile);
2476                     }
2477                 fclose (fp);
2478             }
2479             umask (mask);
2480         }
2481     }
2482
2483     fseek (ce->ce_fp, 0L, SEEK_SET);
2484     *file = ce->ce_file;
2485     return fileno (ce->ce_fp);
2486 }
2487
2488
2489 /*
2490  * Mail
2491  */
2492
2493 static int
2494 InitMail (CT ct)
2495 {
2496     return init_encoding (ct, openMail);
2497 }
2498
2499
2500 static int
2501 openMail (CT ct, char **file)
2502 {
2503     int child_id, fd, i, vecp;
2504     int len, buflen;
2505     char *bp, buffer[BUFSIZ], *vec[7];
2506     struct exbody *e = ct->c_ctexbody;
2507     CE ce = ct->c_cefile;
2508
2509     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2510         case NOTOK:
2511             return NOTOK;
2512
2513         case OK:
2514             break;
2515
2516         case DONE:
2517             return fd;
2518     }
2519
2520     if (!e->eb_server) {
2521         content_error (NULL, ct, "missing server parameter");
2522         return NOTOK;
2523     }
2524
2525     if (xpid) {
2526         if (xpid < 0)
2527             xpid = -xpid;
2528         pidcheck (pidwait (xpid, NOTOK));
2529         xpid = 0;
2530     }
2531
2532     /* Get buffer ready to go */
2533     bp = buffer;
2534     buflen = sizeof(buffer);
2535
2536     /* Now, construct query message */
2537     snprintf (bp, buflen, "Retrieve content");
2538     len = strlen (bp);
2539     bp += len;
2540     buflen -= len;
2541
2542     if (e->eb_partno) {
2543         snprintf (bp, buflen, " %s", e->eb_partno);
2544         len = strlen (bp);
2545         bp += len;
2546         buflen -= len;
2547     }
2548
2549     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2550                     e->eb_server,
2551                     e->eb_subject ? e->eb_subject : e->eb_body);
2552
2553     /* Now, check answer */
2554     if (!getanswer (buffer))
2555         return NOTOK;
2556
2557     vecp = 0;
2558     vec[vecp++] = r1bindex (mailproc, '/');
2559     vec[vecp++] = e->eb_server;
2560     vec[vecp++] = "-subject";
2561     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2562     vec[vecp++] = "-body";
2563     vec[vecp++] = e->eb_body;
2564     vec[vecp] = NULL;
2565
2566     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2567         sleep (5);
2568     switch (child_id) {
2569         case NOTOK:
2570             advise ("fork", "unable to");
2571             return NOTOK;
2572
2573         case OK:
2574             execvp (mailproc, vec);
2575             fprintf (stderr, "unable to exec ");
2576             perror (mailproc);
2577             _exit (-1);
2578             /* NOTREACHED */
2579
2580         default:
2581             if (pidXwait (child_id, NULL) == OK)
2582                 advise (NULL, "request sent");
2583             break;
2584     }
2585
2586     if (*file == NULL) {
2587         ce->ce_file = add (m_scratch ("", tmp), NULL);
2588         ce->ce_unlink = 1;
2589     } else {
2590         ce->ce_file = add (*file, NULL);
2591         ce->ce_unlink = 0;
2592     }
2593
2594     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2595         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2596         return NOTOK;
2597     }
2598
2599     if (ct->c_showproc)
2600         free (ct->c_showproc);
2601     ct->c_showproc = add ("true", NULL);
2602
2603     fseek (ce->ce_fp, 0L, SEEK_SET);
2604     *file = ce->ce_file;
2605     return fileno (ce->ce_fp);
2606 }
2607
2608
2609 static int
2610 readDigest (CT ct, char *cp)
2611 {
2612     int bitno, skip;
2613     unsigned long bits;
2614     char *bp = cp;
2615     unsigned char *dp, value, *ep;
2616     unsigned char *b, *b1, *b2, *b3;
2617
2618     b  = (unsigned char *) &bits,
2619     b1 = &b[endian > 0 ? 1 : 2],
2620     b2 = &b[endian > 0 ? 2 : 1],
2621     b3 = &b[endian > 0 ? 3 : 0];
2622     bitno = 18;
2623     bits = 0L;
2624     skip = 0;
2625
2626     for (ep = (dp = ct->c_digest)
2627                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2628         switch (*cp) {
2629             default:
2630                 if (skip
2631                         || (*cp & 0x80)
2632                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2633                     if (debugsw)
2634                         fprintf (stderr, "invalid BASE64 encoding\n");
2635                     return NOTOK;
2636                 }
2637
2638                 bits |= value << bitno;
2639 test_end:
2640                 if ((bitno -= 6) < 0) {
2641                     if (dp + (3 - skip) > ep)
2642                         goto invalid_digest;
2643                     *dp++ = *b1;
2644                     if (skip < 2) {
2645                         *dp++ = *b2;
2646                         if (skip < 1)
2647                             *dp++ = *b3;
2648                     }
2649                     bitno = 18;
2650                     bits = 0L;
2651                     skip = 0;
2652                 }
2653                 break;
2654
2655             case '=':
2656                 if (++skip > 3)
2657                     goto self_delimiting;
2658                 goto test_end;
2659         }
2660     if (bitno != 18) {
2661         if (debugsw)
2662             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2663
2664         return NOTOK;
2665     }
2666 self_delimiting:
2667     if (dp != ep) {
2668 invalid_digest:
2669         if (debugsw) {
2670             while (*cp)
2671                 cp++;
2672             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2673                      (int)(cp - bp));
2674         }
2675
2676         return NOTOK;
2677     }
2678
2679     if (debugsw) {
2680         fprintf (stderr, "MD5 digest=");
2681         for (dp = ct->c_digest; dp < ep; dp++)
2682             fprintf (stderr, "%02x", *dp & 0xff);
2683         fprintf (stderr, "\n");
2684     }
2685
2686     return OK;
2687 }