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