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