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