Applied Sullivan N. Beck <sbeck@cise.ufl.edu>'s mhshow-suffix patch:
[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     /* sbeck -- handle prefixes */
1572     CI ci;
1573     CE ce;
1574     MD5_CTX mdContext;
1575
1576     b  = (unsigned char *) &bits;
1577     b1 = &b[endian > 0 ? 1 : 2];
1578     b2 = &b[endian > 0 ? 2 : 1];
1579     b3 = &b[endian > 0 ? 3 : 0];
1580
1581     ce = ct->c_cefile;
1582     if (ce->ce_fp) {
1583         fseek (ce->ce_fp, 0L, SEEK_SET);
1584         goto ready_to_go;
1585     }
1586
1587     if (ce->ce_file) {
1588         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1589             content_error (ce->ce_file, ct, "unable to fopen for reading");
1590             return NOTOK;
1591         }
1592         goto ready_to_go;
1593     }
1594
1595     if (*file == NULL) {
1596         ce->ce_file = add (m_scratch ("", tmp), NULL);
1597         ce->ce_unlink = 1;
1598     } else {
1599         ce->ce_file = add (*file, NULL);
1600         ce->ce_unlink = 0;
1601     }
1602
1603     /* sbeck@cise.ufl.edu -- handle suffixes */
1604     ci = &ct->c_ctinfo;
1605     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1606               invo_name, ci->ci_type, ci->ci_subtype);
1607     cp = context_find (buffer);
1608     if (cp == NULL || *cp == '\0') {
1609         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1610                   ci->ci_type);
1611         cp = context_find (buffer);
1612     }
1613     if (cp != NULL && *cp != '\0')
1614         ce->ce_file = add (cp, ce->ce_file);
1615
1616     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1617         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1618         return NOTOK;
1619     }
1620
1621     if ((len = ct->c_end - ct->c_begin) < 0)
1622         adios (NULL, "internal error(1)");
1623
1624     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1625         content_error (ct->c_file, ct, "unable to open for reading");
1626         return NOTOK;
1627     }
1628     
1629     if ((digested = ct->c_digested))
1630         MD5Init (&mdContext);
1631
1632     bitno = 18;
1633     bits = 0L;
1634     skip = 0;
1635
1636     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1637     while (len > 0) {
1638         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1639         case NOTOK:
1640             content_error (ct->c_file, ct, "error reading from");
1641             goto clean_up;
1642
1643         case OK:
1644             content_error (NULL, ct, "premature eof");
1645             goto clean_up;
1646
1647         default:
1648             if (cc > len)
1649                 cc = len;
1650             len -= cc;
1651
1652             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1653                 switch (*cp) {
1654                 default:
1655                     if (isspace (*cp))
1656                         break;
1657                     if (skip || (*cp & 0x80)
1658                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1659                         if (debugsw) {
1660                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1661                                 *cp,
1662                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1663                                 skip);
1664                         }
1665                         content_error (NULL, ct,
1666                                        "invalid BASE64 encoding -- continuing");
1667                         continue;
1668                     }
1669
1670                     bits |= value << bitno;
1671 test_end:
1672                     if ((bitno -= 6) < 0) {
1673                         putc ((char) *b1, ce->ce_fp);
1674                         if (digested)
1675                             MD5Update (&mdContext, b1, 1);
1676                         if (skip < 2) {
1677                             putc ((char) *b2, ce->ce_fp);
1678                             if (digested)
1679                                 MD5Update (&mdContext, b2, 1);
1680                             if (skip < 1) {
1681                                 putc ((char) *b3, ce->ce_fp);
1682                                 if (digested)
1683                                     MD5Update (&mdContext, b3, 1);
1684                             }
1685                         }
1686
1687                         if (ferror (ce->ce_fp)) {
1688                             content_error (ce->ce_file, ct,
1689                                            "error writing to");
1690                             goto clean_up;
1691                         }
1692                         bitno = 18, bits = 0L, skip = 0;
1693                     }
1694                     break;
1695
1696                 case '=':
1697                     if (++skip > 3)
1698                         goto self_delimiting;
1699                     goto test_end;
1700                 }
1701             }
1702         }
1703     }
1704
1705     if (bitno != 18) {
1706         if (debugsw)
1707             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1708
1709         content_error (NULL, ct, "invalid BASE64 encoding");
1710         goto clean_up;
1711     }
1712
1713 self_delimiting:
1714     fseek (ct->c_fp, 0L, SEEK_SET);
1715
1716     if (fflush (ce->ce_fp)) {
1717         content_error (ce->ce_file, ct, "error writing to");
1718         goto clean_up;
1719     }
1720
1721     if (digested) {
1722         unsigned char digest[16];
1723
1724         MD5Final (digest, &mdContext);
1725         if (memcmp((char *) digest, (char *) ct->c_digest,
1726                    sizeof(digest) / sizeof(digest[0])))
1727             content_error (NULL, ct,
1728                            "content integrity suspect (digest mismatch) -- continuing");
1729         else
1730             if (debugsw)
1731                 fprintf (stderr, "content integrity confirmed\n");
1732     }
1733
1734     fseek (ce->ce_fp, 0L, SEEK_SET);
1735
1736 ready_to_go:
1737     *file = ce->ce_file;
1738     return fileno (ce->ce_fp);
1739
1740 clean_up:
1741     free_encoding (ct, 0);
1742     return NOTOK;
1743 }
1744
1745
1746 /*
1747  * QUOTED PRINTABLE
1748  */
1749
1750 static char hex2nib[0x80] = {
1751     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1752     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1753     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1754     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1755     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1756     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1757     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1758     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1759     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
1760     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1761     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1762     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1763     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
1764     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1765     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1766     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1767 };
1768
1769
1770 static int 
1771 InitQuoted (CT ct)
1772 {
1773     return init_encoding (ct, openQuoted);
1774 }
1775
1776
1777 static int
1778 openQuoted (CT ct, char **file)
1779 {
1780     int cc, digested, len, quoted;
1781     char *cp, *ep;
1782     char buffer[BUFSIZ];
1783     unsigned char mask;
1784     CE ce;
1785     /* sbeck -- handle prefixes */
1786     CI ci;
1787     MD5_CTX mdContext;
1788
1789     ce = ct->c_cefile;
1790     if (ce->ce_fp) {
1791         fseek (ce->ce_fp, 0L, SEEK_SET);
1792         goto ready_to_go;
1793     }
1794
1795     if (ce->ce_file) {
1796         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1797             content_error (ce->ce_file, ct, "unable to fopen for reading");
1798             return NOTOK;
1799         }
1800         goto ready_to_go;
1801     }
1802
1803     if (*file == NULL) {
1804         ce->ce_file = add (m_scratch ("", tmp), NULL);
1805         ce->ce_unlink = 1;
1806     } else {
1807         ce->ce_file = add (*file, NULL);
1808         ce->ce_unlink = 0;
1809     }
1810
1811     /* sbeck@cise.ufl.edu -- handle suffixes */
1812     ci = &ct->c_ctinfo;
1813     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1814               invo_name, ci->ci_type, ci->ci_subtype);
1815     cp = context_find (buffer);
1816     if (cp == NULL || *cp == '\0') {
1817         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1818                   ci->ci_type);
1819         cp = context_find (buffer);
1820     }
1821     if (cp != NULL && *cp != '\0')
1822         ce->ce_file = add (cp, ce->ce_file);
1823
1824     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1825         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1826         return NOTOK;
1827     }
1828
1829     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1830         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1831         return NOTOK;
1832     }
1833
1834     if ((len = ct->c_end - ct->c_begin) < 0)
1835         adios (NULL, "internal error(2)");
1836
1837     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1838         content_error (ct->c_file, ct, "unable to open for reading");
1839         return NOTOK;
1840     }
1841
1842     if ((digested = ct->c_digested))
1843         MD5Init (&mdContext);
1844
1845     quoted = 0;
1846 #ifdef lint
1847     mask = 0;
1848 #endif
1849
1850     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
1851     while (len > 0) {
1852         char *dp;
1853
1854         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
1855             content_error (NULL, ct, "premature eof");
1856             goto clean_up;
1857         }
1858
1859         if ((cc = strlen (buffer)) > len)
1860             cc = len;
1861         len -= cc;
1862
1863         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
1864             if (!isspace (*ep))
1865                 break;
1866         *++ep = '\n', ep++;
1867
1868         for (; cp < ep; cp++) {
1869             if (quoted) {
1870                 if (quoted > 1) {
1871                     if (!isxdigit (*cp)) {
1872 invalid_hex:
1873                         dp = "expecting hexidecimal-digit";
1874                         goto invalid_encoding;
1875                     }
1876                     mask <<= 4;
1877                     mask |= hex2nib[*cp & 0x7f];
1878                     putc (mask, ce->ce_fp);
1879                     if (digested)
1880                         MD5Update (&mdContext, &mask, 1);
1881                 } else {
1882                     switch (*cp) {
1883                     case ':':
1884                         putc (*cp, ce->ce_fp);
1885                         if (digested)
1886                             MD5Update (&mdContext, (unsigned char *) ":", 1);
1887                         break;
1888
1889                     default:
1890                         if (!isxdigit (*cp))
1891                             goto invalid_hex;
1892                         mask = hex2nib[*cp & 0x7f];
1893                         quoted = 2;
1894                         continue;
1895                     }
1896                 }
1897
1898                 if (ferror (ce->ce_fp)) {
1899                     content_error (ce->ce_file, ct, "error writing to");
1900                     goto clean_up;
1901                 }
1902                 quoted = 0;
1903                 continue;
1904             }
1905
1906             switch (*cp) {
1907             default:
1908                 if (*cp < '!' || *cp > '~') {
1909                     int i;
1910                     dp = "expecting character in range [!..~]";
1911
1912 invalid_encoding:
1913                     i = strlen (invo_name) + 2;
1914                     content_error (NULL, ct,
1915                                    "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
1916                                    dp, i, i, "", *cp);
1917                     goto clean_up;
1918                 }
1919                 /* and fall...*/
1920             case ' ':
1921             case '\t':
1922             case '\n':
1923                 putc (*cp, ce->ce_fp);
1924                 if (digested) {
1925                     if (*cp == '\n')
1926                         MD5Update (&mdContext, (unsigned char *) "\r\n",2);
1927                     else
1928                         MD5Update (&mdContext, (unsigned char *) cp, 1);
1929                 }
1930                 if (ferror (ce->ce_fp)) {
1931                     content_error (ce->ce_file, ct, "error writing to");
1932                     goto clean_up;
1933                 }
1934                 break;
1935
1936             case '=':
1937                 if (*++cp != '\n') {
1938                     quoted = 1;
1939                     cp--;
1940                 }
1941                 break;
1942             }
1943         }
1944     }
1945     if (quoted) {
1946         content_error (NULL, ct,
1947                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
1948         goto clean_up;
1949     }
1950
1951     fseek (ct->c_fp, 0L, SEEK_SET);
1952
1953     if (fflush (ce->ce_fp)) {
1954         content_error (ce->ce_file, ct, "error writing to");
1955         goto clean_up;
1956     }
1957
1958     if (digested) {
1959         unsigned char digest[16];
1960
1961         MD5Final (digest, &mdContext);
1962         if (memcmp((char *) digest, (char *) ct->c_digest,
1963                    sizeof(digest) / sizeof(digest[0])))
1964             content_error (NULL, ct,
1965                            "content integrity suspect (digest mismatch) -- continuing");
1966         else
1967             if (debugsw)
1968                 fprintf (stderr, "content integrity confirmed\n");
1969     }
1970
1971     fseek (ce->ce_fp, 0L, SEEK_SET);
1972
1973 ready_to_go:
1974     *file = ce->ce_file;
1975     return fileno (ce->ce_fp);
1976
1977 clean_up:
1978     free_encoding (ct, 0);
1979     return NOTOK;
1980 }
1981
1982
1983 /*
1984  * 7BIT
1985  */
1986
1987 static int
1988 Init7Bit (CT ct)
1989 {
1990     if (init_encoding (ct, open7Bit) == NOTOK)
1991         return NOTOK;
1992
1993     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
1994     return OK;
1995 }
1996
1997
1998 static int
1999 open7Bit (CT ct, char **file)
2000 {
2001     int cc, fd, len;
2002     char buffer[BUFSIZ];
2003     /* sbeck -- handle prefixes */
2004     char *cp;
2005     CI ci;
2006     CE ce;
2007
2008     ce = ct->c_cefile;
2009     if (ce->ce_fp) {
2010         fseek (ce->ce_fp, 0L, SEEK_SET);
2011         goto ready_to_go;
2012     }
2013
2014     if (ce->ce_file) {
2015         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2016             content_error (ce->ce_file, ct, "unable to fopen for reading");
2017             return NOTOK;
2018         }
2019         goto ready_to_go;
2020     }
2021
2022     if (*file == NULL) {
2023         ce->ce_file = add (m_scratch ("", tmp), NULL);
2024         ce->ce_unlink = 1;
2025     } else {
2026         ce->ce_file = add (*file, NULL);
2027         ce->ce_unlink = 0;
2028     }
2029
2030     /* sbeck@cise.ufl.edu -- handle suffixes */
2031     ci = &ct->c_ctinfo;
2032     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2033               invo_name, ci->ci_type, ci->ci_subtype);
2034     cp = context_find (buffer);
2035     if (cp == NULL || *cp == '\0') {
2036         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2037                   ci->ci_type);
2038         cp = context_find (buffer);
2039     }
2040     if (cp != NULL && *cp != '\0')
2041         ce->ce_file = add (cp, ce->ce_file);
2042
2043     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2044         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2045         return NOTOK;
2046     }
2047
2048     if (ct->c_type == CT_MULTIPART) {
2049         char **ap, **ep;
2050         CI ci = &ct->c_ctinfo;
2051
2052         len = 0;
2053         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2054         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2055             + 1 + strlen (ci->ci_subtype);
2056         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2057             putc (';', ce->ce_fp);
2058             len++;
2059
2060             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2061
2062             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2063                 fputs ("\n\t", ce->ce_fp);
2064                 len = 8;
2065             } else {
2066                 putc (' ', ce->ce_fp);
2067                 len++;
2068             }
2069             fprintf (ce->ce_fp, "%s", buffer);
2070             len += cc;
2071         }
2072
2073         if (ci->ci_comment) {
2074             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2075                 fputs ("\n\t", ce->ce_fp);
2076                 len = 8;
2077             }
2078             else {
2079                 putc (' ', ce->ce_fp);
2080                 len++;
2081             }
2082             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2083             len += cc;
2084         }
2085         fprintf (ce->ce_fp, "\n");
2086         if (ct->c_id)
2087             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2088         if (ct->c_descr)
2089             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2090         fprintf (ce->ce_fp, "\n");
2091     }
2092
2093     if ((len = ct->c_end - ct->c_begin) < 0)
2094         adios (NULL, "internal error(3)");
2095
2096     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2097         content_error (ct->c_file, ct, "unable to open for reading");
2098         return NOTOK;
2099     }
2100
2101     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2102     while (len > 0)
2103         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2104         case NOTOK:
2105             content_error (ct->c_file, ct, "error reading from");
2106             goto clean_up;
2107
2108         case OK:
2109             content_error (NULL, ct, "premature eof");
2110             goto clean_up;
2111
2112         default:
2113             if (cc > len)
2114                 cc = len;
2115             len -= cc;
2116
2117             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2118             if (ferror (ce->ce_fp)) {
2119                 content_error (ce->ce_file, ct, "error writing to");
2120                 goto clean_up;
2121             }
2122         }
2123
2124     fseek (ct->c_fp, 0L, SEEK_SET);
2125
2126     if (fflush (ce->ce_fp)) {
2127         content_error (ce->ce_file, ct, "error writing to");
2128         goto clean_up;
2129     }
2130
2131     fseek (ce->ce_fp, 0L, SEEK_SET);
2132
2133 ready_to_go:
2134     *file = ce->ce_file;
2135     return fileno (ce->ce_fp);
2136
2137 clean_up:
2138     free_encoding (ct, 0);
2139     return NOTOK;
2140 }
2141
2142
2143 /*
2144  * External
2145  */
2146
2147 static int
2148 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2149 {
2150     char cachefile[BUFSIZ];
2151
2152     if (ce->ce_fp) {
2153         fseek (ce->ce_fp, 0L, SEEK_SET);
2154         goto ready_already;
2155     }
2156
2157     if (ce->ce_file) {
2158         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2159             content_error (ce->ce_file, ct, "unable to fopen for reading");
2160             return NOTOK;
2161         }
2162         goto ready_already;
2163     }
2164
2165     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2166                 cachefile, sizeof(cachefile)) != NOTOK) {
2167         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2168             ce->ce_file = getcpy (cachefile);
2169             ce->ce_unlink = 0;
2170             goto ready_already;
2171         } else {
2172             admonish (cachefile, "unable to fopen for reading");
2173         }
2174     }
2175
2176     return OK;
2177
2178 ready_already:
2179     *file = ce->ce_file;
2180     *fd = fileno (ce->ce_fp);
2181     return DONE;
2182 }
2183
2184 /*
2185  * File
2186  */
2187
2188 static int
2189 InitFile (CT ct)
2190 {
2191     return init_encoding (ct, openFile);
2192 }
2193
2194
2195 static int
2196 openFile (CT ct, char **file)
2197 {
2198     int fd, cachetype;
2199     char cachefile[BUFSIZ];
2200     struct exbody *e = ct->c_ctexbody;
2201     CE ce = ct->c_cefile;
2202
2203     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2204         case NOTOK:
2205             return NOTOK;
2206
2207         case OK:
2208             break;
2209
2210         case DONE:
2211             return fd;
2212     }
2213
2214     if (!e->eb_name) {
2215         content_error (NULL, ct, "missing name parameter");
2216         return NOTOK;
2217     }
2218
2219     ce->ce_file = getcpy (e->eb_name);
2220     ce->ce_unlink = 0;
2221
2222     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2223         content_error (ce->ce_file, ct, "unable to fopen for reading");
2224         return NOTOK;
2225     }
2226
2227     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2228             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2229                 cachefile, sizeof(cachefile)) != NOTOK) {
2230         int mask;
2231         FILE *fp;
2232
2233         mask = umask (cachetype ? ~m_gmprot () : 0222);
2234         if ((fp = fopen (cachefile, "w"))) {
2235             int cc;
2236             char buffer[BUFSIZ];
2237             FILE *gp = ce->ce_fp;
2238
2239             fseek (gp, 0L, SEEK_SET);
2240
2241             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2242                        > 0)
2243                 fwrite (buffer, sizeof(*buffer), cc, fp);
2244             fflush (fp);
2245
2246             if (ferror (gp)) {
2247                 admonish (ce->ce_file, "error reading");
2248                 unlink (cachefile);
2249             }
2250             else
2251                 if (ferror (fp)) {
2252                     admonish (cachefile, "error writing");
2253                     unlink (cachefile);
2254                 }
2255             fclose (fp);
2256         }
2257         umask (mask);
2258     }
2259
2260     fseek (ce->ce_fp, 0L, SEEK_SET);
2261     *file = ce->ce_file;
2262     return fileno (ce->ce_fp);
2263 }
2264
2265 /*
2266  * FTP
2267  */
2268
2269 static int
2270 InitFTP (CT ct)
2271 {
2272     return init_encoding (ct, openFTP);
2273 }
2274
2275
2276 static int
2277 openFTP (CT ct, char **file)
2278 {
2279     int cachetype, caching, fd;
2280     int len, buflen;
2281     char *bp, *ftp, *user, *pass;
2282     char buffer[BUFSIZ], cachefile[BUFSIZ];
2283     struct exbody *e;
2284     CE ce;
2285     static char *username = NULL;
2286     static char *password = NULL;
2287
2288     e  = ct->c_ctexbody;
2289     ce = ct->c_cefile;
2290
2291     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2292         ftp = NULL;
2293
2294 #ifndef BUILTIN_FTP
2295     if (!ftp)
2296         return NOTOK;
2297 #endif
2298
2299     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2300         case NOTOK:
2301             return NOTOK;
2302
2303         case OK:
2304             break;
2305
2306         case DONE:
2307             return fd;
2308     }
2309
2310     if (!e->eb_name || !e->eb_site) {
2311         content_error (NULL, ct, "missing %s parameter",
2312                        e->eb_name ? "site": "name");
2313         return NOTOK;
2314     }
2315
2316     if (xpid) {
2317         if (xpid < 0)
2318             xpid = -xpid;
2319         pidcheck (pidwait (xpid, NOTOK));
2320         xpid = 0;
2321     }
2322
2323     /* Get the buffer ready to go */
2324     bp = buffer;
2325     buflen = sizeof(buffer);
2326
2327     /*
2328      * Construct the query message for user
2329      */
2330     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2331     len = strlen (bp);
2332     bp += len;
2333     buflen -= len;
2334
2335     if (e->eb_partno) {
2336         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2337         len = strlen (bp);
2338         bp += len;
2339         buflen -= len;
2340     }
2341
2342     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2343                     e->eb_flags ? "anonymous " : "", e->eb_site);
2344     len = strlen (bp);
2345     bp += len;
2346     buflen -= len;
2347
2348     if (e->eb_size > 0) {
2349         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2350         len = strlen (bp);
2351         bp += len;
2352         buflen -= len;
2353     }
2354     snprintf (bp, buflen, "? ");
2355
2356     /*
2357      * Now, check the answer
2358      */
2359     if (!getanswer (buffer))
2360         return NOTOK;
2361
2362     if (e->eb_flags) {
2363         user = "anonymous";
2364         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2365         pass = buffer;
2366     } else {
2367         ruserpass (e->eb_site, &username, &password);
2368         user = username;
2369         pass = password;
2370     }
2371
2372     ce->ce_unlink = (*file == NULL);
2373     caching = 0;
2374     cachefile[0] = '\0';
2375     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2376             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2377                 cachefile, sizeof(cachefile)) != NOTOK) {
2378         if (*file == NULL) {
2379             ce->ce_unlink = 0;
2380             caching = 1;
2381         }
2382     }
2383
2384     if (*file)
2385         ce->ce_file = add (*file, NULL);
2386     else if (caching)
2387         ce->ce_file = add (cachefile, NULL);
2388     else
2389         ce->ce_file = add (m_scratch ("", tmp), NULL);
2390
2391     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2392         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2393         return NOTOK;
2394     }
2395
2396 #ifdef BUILTIN_FTP
2397     if (ftp)
2398 #endif
2399     {
2400         int child_id, i, vecp;
2401         char *vec[9];
2402
2403         vecp = 0;
2404         vec[vecp++] = r1bindex (ftp, '/');
2405         vec[vecp++] = e->eb_site;
2406         vec[vecp++] = user;
2407         vec[vecp++] = pass;
2408         vec[vecp++] = e->eb_dir;
2409         vec[vecp++] = e->eb_name;
2410         vec[vecp++] = ce->ce_file,
2411         vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2412                         ? "ascii" : "binary";
2413         vec[vecp] = NULL;
2414
2415         fflush (stdout);
2416
2417         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2418             sleep (5);
2419         switch (child_id) {
2420             case NOTOK:
2421                 adios ("fork", "unable to");
2422                 /* NOTREACHED */
2423
2424             case OK:
2425                 close (fileno (ce->ce_fp));
2426                 execvp (ftp, vec);
2427                 fprintf (stderr, "unable to exec ");
2428                 perror (ftp);
2429                 _exit (-1);
2430                 /* NOTREACHED */
2431
2432             default:
2433                 if (pidXwait (child_id, NULL)) {
2434 #ifdef BUILTIN_FTP
2435 losing_ftp:
2436 #endif
2437                     username = password = NULL;
2438                     ce->ce_unlink = 1;
2439                     return NOTOK;
2440                 }
2441                 break;
2442         }
2443     }
2444 #ifdef BUILTIN_FTP
2445     else
2446         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2447                      ce->ce_file,
2448                      e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0)
2449                 == NOTOK)
2450             goto losing_ftp;
2451 #endif
2452
2453     if (cachefile[0]) {
2454         if (caching)
2455             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2456         else {
2457             int mask;
2458             FILE *fp;
2459
2460             mask = umask (cachetype ? ~m_gmprot () : 0222);
2461             if ((fp = fopen (cachefile, "w"))) {
2462                 int cc;
2463                 FILE *gp = ce->ce_fp;
2464
2465                 fseek (gp, 0L, SEEK_SET);
2466
2467                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2468                            > 0)
2469                     fwrite (buffer, sizeof(*buffer), cc, fp);
2470                 fflush (fp);
2471
2472                 if (ferror (gp)) {
2473                     admonish (ce->ce_file, "error reading");
2474                     unlink (cachefile);
2475                 }
2476                 else
2477                     if (ferror (fp)) {
2478                         admonish (cachefile, "error writing");
2479                         unlink (cachefile);
2480                     }
2481                 fclose (fp);
2482             }
2483             umask (mask);
2484         }
2485     }
2486
2487     fseek (ce->ce_fp, 0L, SEEK_SET);
2488     *file = ce->ce_file;
2489     return fileno (ce->ce_fp);
2490 }
2491
2492
2493 /*
2494  * Mail
2495  */
2496
2497 static int
2498 InitMail (CT ct)
2499 {
2500     return init_encoding (ct, openMail);
2501 }
2502
2503
2504 static int
2505 openMail (CT ct, char **file)
2506 {
2507     int child_id, fd, i, vecp;
2508     int len, buflen;
2509     char *bp, buffer[BUFSIZ], *vec[7];
2510     struct exbody *e = ct->c_ctexbody;
2511     CE ce = ct->c_cefile;
2512
2513     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2514         case NOTOK:
2515             return NOTOK;
2516
2517         case OK:
2518             break;
2519
2520         case DONE:
2521             return fd;
2522     }
2523
2524     if (!e->eb_server) {
2525         content_error (NULL, ct, "missing server parameter");
2526         return NOTOK;
2527     }
2528
2529     if (xpid) {
2530         if (xpid < 0)
2531             xpid = -xpid;
2532         pidcheck (pidwait (xpid, NOTOK));
2533         xpid = 0;
2534     }
2535
2536     /* Get buffer ready to go */
2537     bp = buffer;
2538     buflen = sizeof(buffer);
2539
2540     /* Now, construct query message */
2541     snprintf (bp, buflen, "Retrieve content");
2542     len = strlen (bp);
2543     bp += len;
2544     buflen -= len;
2545
2546     if (e->eb_partno) {
2547         snprintf (bp, buflen, " %s", e->eb_partno);
2548         len = strlen (bp);
2549         bp += len;
2550         buflen -= len;
2551     }
2552
2553     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2554                     e->eb_server,
2555                     e->eb_subject ? e->eb_subject : e->eb_body);
2556
2557     /* Now, check answer */
2558     if (!getanswer (buffer))
2559         return NOTOK;
2560
2561     vecp = 0;
2562     vec[vecp++] = r1bindex (mailproc, '/');
2563     vec[vecp++] = e->eb_server;
2564     vec[vecp++] = "-subject";
2565     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2566     vec[vecp++] = "-body";
2567     vec[vecp++] = e->eb_body;
2568     vec[vecp] = NULL;
2569
2570     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2571         sleep (5);
2572     switch (child_id) {
2573         case NOTOK:
2574             advise ("fork", "unable to");
2575             return NOTOK;
2576
2577         case OK:
2578             execvp (mailproc, vec);
2579             fprintf (stderr, "unable to exec ");
2580             perror (mailproc);
2581             _exit (-1);
2582             /* NOTREACHED */
2583
2584         default:
2585             if (pidXwait (child_id, NULL) == OK)
2586                 advise (NULL, "request sent");
2587             break;
2588     }
2589
2590     if (*file == NULL) {
2591         ce->ce_file = add (m_scratch ("", tmp), NULL);
2592         ce->ce_unlink = 1;
2593     } else {
2594         ce->ce_file = add (*file, NULL);
2595         ce->ce_unlink = 0;
2596     }
2597
2598     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2599         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2600         return NOTOK;
2601     }
2602
2603     if (ct->c_showproc)
2604         free (ct->c_showproc);
2605     ct->c_showproc = add ("true", NULL);
2606
2607     fseek (ce->ce_fp, 0L, SEEK_SET);
2608     *file = ce->ce_file;
2609     return fileno (ce->ce_fp);
2610 }
2611
2612
2613 static int
2614 readDigest (CT ct, char *cp)
2615 {
2616     int bitno, skip;
2617     unsigned long bits;
2618     char *bp = cp;
2619     unsigned char *dp, value, *ep;
2620     unsigned char *b, *b1, *b2, *b3;
2621
2622     b  = (unsigned char *) &bits,
2623     b1 = &b[endian > 0 ? 1 : 2],
2624     b2 = &b[endian > 0 ? 2 : 1],
2625     b3 = &b[endian > 0 ? 3 : 0];
2626     bitno = 18;
2627     bits = 0L;
2628     skip = 0;
2629
2630     for (ep = (dp = ct->c_digest)
2631                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2632         switch (*cp) {
2633             default:
2634                 if (skip
2635                         || (*cp & 0x80)
2636                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2637                     if (debugsw)
2638                         fprintf (stderr, "invalid BASE64 encoding\n");
2639                     return NOTOK;
2640                 }
2641
2642                 bits |= value << bitno;
2643 test_end:
2644                 if ((bitno -= 6) < 0) {
2645                     if (dp + (3 - skip) > ep)
2646                         goto invalid_digest;
2647                     *dp++ = *b1;
2648                     if (skip < 2) {
2649                         *dp++ = *b2;
2650                         if (skip < 1)
2651                             *dp++ = *b3;
2652                     }
2653                     bitno = 18;
2654                     bits = 0L;
2655                     skip = 0;
2656                 }
2657                 break;
2658
2659             case '=':
2660                 if (++skip > 3)
2661                     goto self_delimiting;
2662                 goto test_end;
2663         }
2664     if (bitno != 18) {
2665         if (debugsw)
2666             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2667
2668         return NOTOK;
2669     }
2670 self_delimiting:
2671     if (dp != ep) {
2672 invalid_digest:
2673         if (debugsw) {
2674             while (*cp)
2675                 cp++;
2676             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2677                      cp - bp);
2678         }
2679
2680         return NOTOK;
2681     }
2682
2683     if (debugsw) {
2684         fprintf (stderr, "MD5 digest=");
2685         for (dp = ct->c_digest; dp < ep; dp++)
2686             fprintf (stderr, "%02x", *dp & 0xff);
2687         fprintf (stderr, "\n");
2688     }
2689
2690     return OK;
2691 }