Don't fclose() the file twice in error-exit paths from get_content()
[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         advise (NULL, "unable to decode %s", file);
263         return NULL;
264     }
265
266     if (is_stdin)
267         ct->c_unlink = 1;       /* temp file to remove */
268
269     ct->c_fp = NULL;
270
271     if (ct->c_end == 0L) {
272         fseek (fp, 0L, SEEK_END);
273         ct->c_end = ftell (fp);
274     }
275
276     if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
277         fclose (fp);
278         free_content (ct);
279         return NULL;
280     }
281
282     fclose (fp);
283     return ct;
284 }
285
286
287 /*
288  * Main routine for reading/parsing the headers
289  * of a message content.
290  *
291  * toplevel =  1   # we are at the top level of the message
292  * toplevel =  0   # we are inside message type or multipart type
293  *                 # other than multipart/digest
294  * toplevel = -1   # we are inside multipart/digest
295  * NB: on failure we will fclose(in)!
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                 ct->c_fp = NULL;
1060                 return NOTOK;
1061             }
1062             p->c_fp = NULL;
1063             part->mp_part = p;
1064             pos = p->c_begin;
1065             fseek (fp, pos, SEEK_SET);
1066             inout = 0;
1067         } else {
1068             if (strcmp (buffer + 2, m->mp_start) == 0) {
1069                 inout = 1;
1070 end_part:
1071                 p = part->mp_part;
1072                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1073                 if (p->c_end < p->c_begin)
1074                     p->c_begin = p->c_end;
1075                 if (inout)
1076                     goto next_part;
1077                 goto last_part;
1078             } else {
1079                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1080                     goto end_part;
1081             }
1082         }
1083     }
1084
1085     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1086     if (!inout && part) {
1087         p = part->mp_part;
1088         p->c_end = ct->c_end;
1089
1090         if (p->c_begin >= p->c_end) {
1091             for (next = &m->mp_parts; *next != part;
1092                      next = &((*next)->mp_next))
1093                 continue;
1094             *next = NULL;
1095             free_content (p);
1096             free ((char *) part);
1097         }
1098     }
1099
1100 last_part:
1101     /* reverse the order of the parts for multipart/alternative */
1102     if (ct->c_subtype == MULTI_ALTERNATE)
1103         reverse_parts (ct);
1104
1105     /*
1106      * label all subparts with part number, and
1107      * then initialize the content of the subpart.
1108      */
1109     {
1110         int partnum;
1111         char *pp;
1112         char partnam[BUFSIZ];
1113
1114         if (ct->c_partno) {
1115             snprintf (partnam, sizeof(partnum), "%s.", ct->c_partno);
1116             pp = partnam + strlen (partnam);
1117         } else {
1118             pp = partnam;
1119         }
1120
1121         for (part = m->mp_parts, partnum = 1; part;
1122                  part = part->mp_next, partnum++) {
1123             p = part->mp_part;
1124
1125             sprintf (pp, "%d", partnum);
1126             p->c_partno = add (partnam, NULL);
1127
1128             /* initialize the content of the subparts */
1129             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1130                 fclose (ct->c_fp);
1131                 ct->c_fp = NULL;
1132                 return NOTOK;
1133             }
1134         }
1135     }
1136
1137     fclose (ct->c_fp);
1138     ct->c_fp = NULL;
1139     return OK;
1140 }
1141
1142
1143 /*
1144  * reverse the order of the parts of a multipart
1145  */
1146
1147 static void
1148 reverse_parts (CT ct)
1149 {
1150     int i;
1151     struct multipart *m;
1152     struct part **base, **bmp, **next, *part;
1153
1154     m = (struct multipart *) ct->c_ctparams;
1155
1156     /* if only one part, just return */
1157     if (!m->mp_parts || !m->mp_parts->mp_next)
1158         return;
1159
1160     /* count number of parts */
1161     i = 0;
1162     for (part = m->mp_parts; part; part = part->mp_next)
1163         i++;
1164
1165     /* allocate array of pointers to the parts */
1166     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1167         adios (NULL, "out of memory");
1168     bmp = base;
1169
1170     /* point at all the parts */
1171     for (part = m->mp_parts; part; part = part->mp_next)
1172         *bmp++ = part;
1173     *bmp = NULL;
1174
1175     /* reverse the order of the parts */
1176     next = &m->mp_parts;
1177     for (bmp--; bmp >= base; bmp--) {
1178         part = *bmp;
1179         *next = part;
1180         next = &part->mp_next;
1181     }
1182     *next = NULL;
1183
1184     /* free array of pointers */
1185     free ((char *) base);
1186 }
1187
1188
1189 /*
1190  * MESSAGE
1191  */
1192
1193 static int
1194 InitMessage (CT ct)
1195 {
1196     struct k2v *kv;
1197     CI ci = &ct->c_ctinfo;
1198
1199     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1200         admonish (NULL,
1201                   "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1202                   ci->ci_type, ci->ci_subtype, ct->c_file);
1203         return NOTOK;
1204     }
1205
1206     /* check for missing subtype */
1207     if (!*ci->ci_subtype)
1208         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1209
1210     /* match subtype */
1211     for (kv = SubMessage; kv->kv_key; kv++)
1212         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1213             break;
1214     ct->c_subtype = kv->kv_value;
1215
1216     switch (ct->c_subtype) {
1217         case MESSAGE_RFC822:
1218             break;
1219
1220         case MESSAGE_PARTIAL:
1221             {
1222                 char **ap, **ep;
1223                 struct partial *p;
1224
1225                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1226                     adios (NULL, "out of memory");
1227                 ct->c_ctparams = (void *) p;
1228
1229                 /* scan for parameters "id", "number", and "total" */
1230                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1231                     if (!strcasecmp (*ap, "id")) {
1232                         p->pm_partid = add (*ep, NULL);
1233                         continue;
1234                     }
1235                     if (!strcasecmp (*ap, "number")) {
1236                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1237                                 || p->pm_partno < 1) {
1238 invalid_param:
1239                             advise (NULL,
1240                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1241                                     *ap, ci->ci_type, ci->ci_subtype,
1242                                     ct->c_file, TYPE_FIELD);
1243                             return NOTOK;
1244                         }
1245                         continue;
1246                     }
1247                     if (!strcasecmp (*ap, "total")) {
1248                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1249                                 || p->pm_maxno < 1)
1250                             goto invalid_param;
1251                         continue;
1252                     }
1253                 }
1254
1255                 if (!p->pm_partid
1256                         || !p->pm_partno
1257                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1258                     advise (NULL,
1259                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1260                             ci->ci_type, ci->ci_subtype,
1261                             ct->c_file, TYPE_FIELD);
1262                     return NOTOK;
1263                 }
1264             }
1265             break;
1266
1267         case MESSAGE_EXTERNAL:
1268             {
1269                 int exresult;
1270                 struct exbody *e;
1271                 CT p;
1272                 FILE *fp;
1273
1274                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1275                     adios (NULL, "out of memory");
1276                 ct->c_ctparams = (void *) e;
1277
1278                 if (!ct->c_fp
1279                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1280                     advise (ct->c_file, "unable to open for reading");
1281                     return NOTOK;
1282                 }
1283
1284                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1285
1286                 if (!(p = get_content (fp, ct->c_file, 0))) {
1287                     ct->c_fp = NULL;
1288                     return NOTOK;
1289                 }
1290
1291                 e->eb_parent = ct;
1292                 e->eb_content = p;
1293                 p->c_ctexbody = e;
1294                 if ((exresult = params_external (ct, 0)) != NOTOK
1295                         && p->c_ceopenfnx == openMail) {
1296                     int cc, size;
1297                     char *bp;
1298                     
1299                     if ((size = ct->c_end - p->c_begin) <= 0) {
1300                         if (!e->eb_subject)
1301                             content_error (NULL, ct,
1302                                            "empty body for access-type=mail-server");
1303                         goto no_body;
1304                     }
1305                     
1306                     if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL)
1307                         adios (NULL, "out of memory");
1308                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1309                     while (size > 0)
1310                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1311                             case NOTOK:
1312                                 adios ("failed", "fread");
1313
1314                             case OK:
1315                                 adios (NULL, "unexpected EOF from fread");
1316
1317                             default:
1318                                 bp += cc, size -= cc;
1319                                 break;
1320                         }
1321                     *bp = 0;
1322                 }
1323 no_body:
1324                 p->c_fp = NULL;
1325                 p->c_end = p->c_begin;
1326
1327                 fclose (ct->c_fp);
1328                 ct->c_fp = NULL;
1329
1330                 if (exresult == NOTOK)
1331                     return NOTOK;
1332                 if (e->eb_flags == NOTOK)
1333                     return OK;
1334
1335                 switch (p->c_type) {
1336                     case CT_MULTIPART:
1337                         break;
1338
1339                     case CT_MESSAGE:
1340                         if (p->c_subtype != MESSAGE_RFC822)
1341                             break;
1342                         /* else fall... */
1343                     default:
1344                         e->eb_partno = ct->c_partno;
1345                         if (p->c_ctinitfnx)
1346                             (*p->c_ctinitfnx) (p);
1347                         break;
1348                 }
1349             }
1350             break;
1351
1352         default:
1353             break;
1354     }
1355
1356     return OK;
1357 }
1358
1359
1360 static int
1361 params_external (CT ct, int composing)
1362 {
1363     char **ap, **ep;
1364     struct exbody *e = (struct exbody *) ct->c_ctparams;
1365     CI ci = &ct->c_ctinfo;
1366
1367     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1368         if (!strcasecmp (*ap, "access-type")) {
1369             struct str2init *s2i;
1370             CT p = e->eb_content;
1371
1372             for (s2i = str2methods; s2i->si_key; s2i++)
1373                 if (!strcasecmp (*ep, s2i->si_key))
1374                     break;
1375             if (!s2i->si_key) {
1376                 e->eb_access = *ep;
1377                 e->eb_flags = NOTOK;
1378                 p->c_encoding = CE_EXTERNAL;
1379                 continue;
1380             }
1381             e->eb_access = s2i->si_key;
1382             e->eb_flags = s2i->si_val;
1383             p->c_encoding = CE_EXTERNAL;
1384
1385             /* Call the Init function for this external type */
1386             if ((*s2i->si_init)(p) == NOTOK)
1387                 return NOTOK;
1388             continue;
1389         }
1390         if (!strcasecmp (*ap, "name")) {
1391             e->eb_name = *ep;
1392             continue;
1393         }
1394         if (!strcasecmp (*ap, "permission")) {
1395             e->eb_permission = *ep;
1396             continue;
1397         }
1398         if (!strcasecmp (*ap, "site")) {
1399             e->eb_site = *ep;
1400             continue;
1401         }
1402         if (!strcasecmp (*ap, "directory")) {
1403             e->eb_dir = *ep;
1404             continue;
1405         }
1406         if (!strcasecmp (*ap, "mode")) {
1407             e->eb_mode = *ep;
1408             continue;
1409         }
1410         if (!strcasecmp (*ap, "size")) {
1411             sscanf (*ep, "%lu", &e->eb_size);
1412             continue;
1413         }
1414         if (!strcasecmp (*ap, "server")) {
1415             e->eb_server = *ep;
1416             continue;
1417         }
1418         if (!strcasecmp (*ap, "subject")) {
1419             e->eb_subject = *ep;
1420             continue;
1421         }
1422         if (composing && !strcasecmp (*ap, "body")) {
1423             e->eb_body = getcpy (*ep);
1424             continue;
1425         }
1426     }
1427
1428     if (!e->eb_access) {
1429         advise (NULL,
1430                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1431                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1432         return NOTOK;
1433     }
1434
1435     return OK;
1436 }
1437
1438
1439 /*
1440  * APPLICATION
1441  */
1442
1443 static int
1444 InitApplication (CT ct)
1445 {
1446     struct k2v *kv;
1447     CI ci = &ct->c_ctinfo;
1448
1449     /* match subtype */
1450     for (kv = SubApplication; kv->kv_key; kv++)
1451         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1452             break;
1453     ct->c_subtype = kv->kv_value;
1454
1455     return OK;
1456 }
1457
1458
1459 /*
1460  * TRANSFER ENCODINGS
1461  */
1462
1463 static int
1464 init_encoding (CT ct, OpenCEFunc openfnx)
1465 {
1466     CE ce;
1467
1468     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1469         adios (NULL, "out of memory");
1470
1471     ct->c_cefile     = ce;
1472     ct->c_ceopenfnx  = openfnx;
1473     ct->c_ceclosefnx = close_encoding;
1474     ct->c_cesizefnx  = size_encoding;
1475
1476     return OK;
1477 }
1478
1479
1480 static void
1481 close_encoding (CT ct)
1482 {
1483     CE ce;
1484
1485     if (!(ce = ct->c_cefile))
1486         return;
1487
1488     if (ce->ce_fp) {
1489         fclose (ce->ce_fp);
1490         ce->ce_fp = NULL;
1491     }
1492 }
1493
1494
1495 static unsigned long
1496 size_encoding (CT ct)
1497 {
1498     int fd;
1499     unsigned long size;
1500     char *file;
1501     CE ce;
1502     struct stat st;
1503
1504     if (!(ce = ct->c_cefile))
1505         return (ct->c_end - ct->c_begin);
1506
1507     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1508         return (long) st.st_size;
1509
1510     if (ce->ce_file) {
1511         if (stat (ce->ce_file, &st) != NOTOK)
1512             return (long) st.st_size;
1513         else
1514             return 0L;
1515     }
1516
1517     if (ct->c_encoding == CE_EXTERNAL)
1518         return (ct->c_end - ct->c_begin);       
1519
1520     file = NULL;
1521     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1522         return (ct->c_end - ct->c_begin);
1523
1524     if (fstat (fd, &st) != NOTOK)
1525         size = (long) st.st_size;
1526     else
1527         size = 0L;
1528
1529     (*ct->c_ceclosefnx) (ct);
1530     return size;
1531 }
1532
1533
1534 /*
1535  * BASE64
1536  */
1537
1538 static unsigned char b642nib[0x80] = {
1539     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1540     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1541     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1542     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1543     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1544     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1545     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1546     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1547     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1548     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1549     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1550     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1551     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1552     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1553     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1554     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1555 };
1556
1557
1558 static int
1559 InitBase64 (CT ct)
1560 {
1561     return init_encoding (ct, openBase64);
1562 }
1563
1564
1565 static int
1566 openBase64 (CT ct, char **file)
1567 {
1568     int bitno, cc, digested;
1569     int fd, len, skip;
1570     unsigned long bits;
1571     unsigned char value, *b, *b1, *b2, *b3;
1572     char *cp, *ep, buffer[BUFSIZ];
1573     /* sbeck -- handle prefixes */
1574     CI ci;
1575     CE ce;
1576     MD5_CTX mdContext;
1577
1578     b  = (unsigned char *) &bits;
1579     b1 = &b[endian > 0 ? 1 : 2];
1580     b2 = &b[endian > 0 ? 2 : 1];
1581     b3 = &b[endian > 0 ? 3 : 0];
1582
1583     ce = ct->c_cefile;
1584     if (ce->ce_fp) {
1585         fseek (ce->ce_fp, 0L, SEEK_SET);
1586         goto ready_to_go;
1587     }
1588
1589     if (ce->ce_file) {
1590         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1591             content_error (ce->ce_file, ct, "unable to fopen for reading");
1592             return NOTOK;
1593         }
1594         goto ready_to_go;
1595     }
1596
1597     if (*file == NULL) {
1598         ce->ce_file = add (m_scratch ("", tmp), NULL);
1599         ce->ce_unlink = 1;
1600     } else {
1601         ce->ce_file = add (*file, NULL);
1602         ce->ce_unlink = 0;
1603     }
1604
1605     /* sbeck@cise.ufl.edu -- handle suffixes */
1606     ci = &ct->c_ctinfo;
1607     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1608               invo_name, ci->ci_type, ci->ci_subtype);
1609     cp = context_find (buffer);
1610     if (cp == NULL || *cp == '\0') {
1611         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1612                   ci->ci_type);
1613         cp = context_find (buffer);
1614     }
1615     if (cp != NULL && *cp != '\0')
1616         ce->ce_file = add (cp, ce->ce_file);
1617
1618     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1619         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1620         return NOTOK;
1621     }
1622
1623     if ((len = ct->c_end - ct->c_begin) < 0)
1624         adios (NULL, "internal error(1)");
1625
1626     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1627         content_error (ct->c_file, ct, "unable to open for reading");
1628         return NOTOK;
1629     }
1630     
1631     if ((digested = ct->c_digested))
1632         MD5Init (&mdContext);
1633
1634     bitno = 18;
1635     bits = 0L;
1636     skip = 0;
1637
1638     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1639     while (len > 0) {
1640         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1641         case NOTOK:
1642             content_error (ct->c_file, ct, "error reading from");
1643             goto clean_up;
1644
1645         case OK:
1646             content_error (NULL, ct, "premature eof");
1647             goto clean_up;
1648
1649         default:
1650             if (cc > len)
1651                 cc = len;
1652             len -= cc;
1653
1654             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1655                 switch (*cp) {
1656                 default:
1657                     if (isspace (*cp))
1658                         break;
1659                     if (skip || (*cp & 0x80)
1660                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1661                         if (debugsw) {
1662                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1663                                 *cp,
1664                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1665                                 skip);
1666                         }
1667                         content_error (NULL, ct,
1668                                        "invalid BASE64 encoding -- continuing");
1669                         continue;
1670                     }
1671
1672                     bits |= value << bitno;
1673 test_end:
1674                     if ((bitno -= 6) < 0) {
1675                         putc ((char) *b1, ce->ce_fp);
1676                         if (digested)
1677                             MD5Update (&mdContext, b1, 1);
1678                         if (skip < 2) {
1679                             putc ((char) *b2, ce->ce_fp);
1680                             if (digested)
1681                                 MD5Update (&mdContext, b2, 1);
1682                             if (skip < 1) {
1683                                 putc ((char) *b3, ce->ce_fp);
1684                                 if (digested)
1685                                     MD5Update (&mdContext, b3, 1);
1686                             }
1687                         }
1688
1689                         if (ferror (ce->ce_fp)) {
1690                             content_error (ce->ce_file, ct,
1691                                            "error writing to");
1692                             goto clean_up;
1693                         }
1694                         bitno = 18, bits = 0L, skip = 0;
1695                     }
1696                     break;
1697
1698                 case '=':
1699                     if (++skip > 3)
1700                         goto self_delimiting;
1701                     goto test_end;
1702                 }
1703             }
1704         }
1705     }
1706
1707     if (bitno != 18) {
1708         if (debugsw)
1709             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1710
1711         content_error (NULL, ct, "invalid BASE64 encoding");
1712         goto clean_up;
1713     }
1714
1715 self_delimiting:
1716     fseek (ct->c_fp, 0L, SEEK_SET);
1717
1718     if (fflush (ce->ce_fp)) {
1719         content_error (ce->ce_file, ct, "error writing to");
1720         goto clean_up;
1721     }
1722
1723     if (digested) {
1724         unsigned char digest[16];
1725
1726         MD5Final (digest, &mdContext);
1727         if (memcmp((char *) digest, (char *) ct->c_digest,
1728                    sizeof(digest) / sizeof(digest[0])))
1729             content_error (NULL, ct,
1730                            "content integrity suspect (digest mismatch) -- continuing");
1731         else
1732             if (debugsw)
1733                 fprintf (stderr, "content integrity confirmed\n");
1734     }
1735
1736     fseek (ce->ce_fp, 0L, SEEK_SET);
1737
1738 ready_to_go:
1739     *file = ce->ce_file;
1740     return fileno (ce->ce_fp);
1741
1742 clean_up:
1743     free_encoding (ct, 0);
1744     return NOTOK;
1745 }
1746
1747
1748 /*
1749  * QUOTED PRINTABLE
1750  */
1751
1752 static char hex2nib[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1758     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1759     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1760     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1761     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
1762     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1763     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1764     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1765     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
1766     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1767     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1768     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1769 };
1770
1771
1772 static int 
1773 InitQuoted (CT ct)
1774 {
1775     return init_encoding (ct, openQuoted);
1776 }
1777
1778
1779 static int
1780 openQuoted (CT ct, char **file)
1781 {
1782     int cc, digested, len, quoted;
1783     char *cp, *ep;
1784     char buffer[BUFSIZ];
1785     unsigned char mask;
1786     CE ce;
1787     /* sbeck -- handle prefixes */
1788     CI ci;
1789     MD5_CTX mdContext;
1790
1791     ce = ct->c_cefile;
1792     if (ce->ce_fp) {
1793         fseek (ce->ce_fp, 0L, SEEK_SET);
1794         goto ready_to_go;
1795     }
1796
1797     if (ce->ce_file) {
1798         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1799             content_error (ce->ce_file, ct, "unable to fopen for reading");
1800             return NOTOK;
1801         }
1802         goto ready_to_go;
1803     }
1804
1805     if (*file == NULL) {
1806         ce->ce_file = add (m_scratch ("", tmp), NULL);
1807         ce->ce_unlink = 1;
1808     } else {
1809         ce->ce_file = add (*file, NULL);
1810         ce->ce_unlink = 0;
1811     }
1812
1813     /* sbeck@cise.ufl.edu -- handle suffixes */
1814     ci = &ct->c_ctinfo;
1815     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1816               invo_name, ci->ci_type, ci->ci_subtype);
1817     cp = context_find (buffer);
1818     if (cp == NULL || *cp == '\0') {
1819         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1820                   ci->ci_type);
1821         cp = context_find (buffer);
1822     }
1823     if (cp != NULL && *cp != '\0')
1824         ce->ce_file = add (cp, ce->ce_file);
1825
1826     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1827         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1828         return NOTOK;
1829     }
1830
1831     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1832         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1833         return NOTOK;
1834     }
1835
1836     if ((len = ct->c_end - ct->c_begin) < 0)
1837         adios (NULL, "internal error(2)");
1838
1839     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1840         content_error (ct->c_file, ct, "unable to open for reading");
1841         return NOTOK;
1842     }
1843
1844     if ((digested = ct->c_digested))
1845         MD5Init (&mdContext);
1846
1847     quoted = 0;
1848 #ifdef lint
1849     mask = 0;
1850 #endif
1851
1852     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
1853     while (len > 0) {
1854         char *dp;
1855
1856         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
1857             content_error (NULL, ct, "premature eof");
1858             goto clean_up;
1859         }
1860
1861         if ((cc = strlen (buffer)) > len)
1862             cc = len;
1863         len -= cc;
1864
1865         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
1866             if (!isspace (*ep))
1867                 break;
1868         *++ep = '\n', ep++;
1869
1870         for (; cp < ep; cp++) {
1871             if (quoted) {
1872                 if (quoted > 1) {
1873                     if (!isxdigit (*cp)) {
1874 invalid_hex:
1875                         dp = "expecting hexidecimal-digit";
1876                         goto invalid_encoding;
1877                     }
1878                     mask <<= 4;
1879                     mask |= hex2nib[*cp & 0x7f];
1880                     putc (mask, ce->ce_fp);
1881                     if (digested)
1882                         MD5Update (&mdContext, &mask, 1);
1883                 } else {
1884                     switch (*cp) {
1885                     case ':':
1886                         putc (*cp, ce->ce_fp);
1887                         if (digested)
1888                             MD5Update (&mdContext, (unsigned char *) ":", 1);
1889                         break;
1890
1891                     default:
1892                         if (!isxdigit (*cp))
1893                             goto invalid_hex;
1894                         mask = hex2nib[*cp & 0x7f];
1895                         quoted = 2;
1896                         continue;
1897                     }
1898                 }
1899
1900                 if (ferror (ce->ce_fp)) {
1901                     content_error (ce->ce_file, ct, "error writing to");
1902                     goto clean_up;
1903                 }
1904                 quoted = 0;
1905                 continue;
1906             }
1907
1908             switch (*cp) {
1909             default:
1910                 if (*cp < '!' || *cp > '~') {
1911                     int i;
1912                     dp = "expecting character in range [!..~]";
1913
1914 invalid_encoding:
1915                     i = strlen (invo_name) + 2;
1916                     content_error (NULL, ct,
1917                                    "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
1918                                    dp, i, i, "", *cp);
1919                     goto clean_up;
1920                 }
1921                 /* and fall...*/
1922             case ' ':
1923             case '\t':
1924             case '\n':
1925                 putc (*cp, ce->ce_fp);
1926                 if (digested) {
1927                     if (*cp == '\n')
1928                         MD5Update (&mdContext, (unsigned char *) "\r\n",2);
1929                     else
1930                         MD5Update (&mdContext, (unsigned char *) cp, 1);
1931                 }
1932                 if (ferror (ce->ce_fp)) {
1933                     content_error (ce->ce_file, ct, "error writing to");
1934                     goto clean_up;
1935                 }
1936                 break;
1937
1938             case '=':
1939                 if (*++cp != '\n') {
1940                     quoted = 1;
1941                     cp--;
1942                 }
1943                 break;
1944             }
1945         }
1946     }
1947     if (quoted) {
1948         content_error (NULL, ct,
1949                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
1950         goto clean_up;
1951     }
1952
1953     fseek (ct->c_fp, 0L, SEEK_SET);
1954
1955     if (fflush (ce->ce_fp)) {
1956         content_error (ce->ce_file, ct, "error writing to");
1957         goto clean_up;
1958     }
1959
1960     if (digested) {
1961         unsigned char digest[16];
1962
1963         MD5Final (digest, &mdContext);
1964         if (memcmp((char *) digest, (char *) ct->c_digest,
1965                    sizeof(digest) / sizeof(digest[0])))
1966             content_error (NULL, ct,
1967                            "content integrity suspect (digest mismatch) -- continuing");
1968         else
1969             if (debugsw)
1970                 fprintf (stderr, "content integrity confirmed\n");
1971     }
1972
1973     fseek (ce->ce_fp, 0L, SEEK_SET);
1974
1975 ready_to_go:
1976     *file = ce->ce_file;
1977     return fileno (ce->ce_fp);
1978
1979 clean_up:
1980     free_encoding (ct, 0);
1981     return NOTOK;
1982 }
1983
1984
1985 /*
1986  * 7BIT
1987  */
1988
1989 static int
1990 Init7Bit (CT ct)
1991 {
1992     if (init_encoding (ct, open7Bit) == NOTOK)
1993         return NOTOK;
1994
1995     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
1996     return OK;
1997 }
1998
1999
2000 static int
2001 open7Bit (CT ct, char **file)
2002 {
2003     int cc, fd, len;
2004     char buffer[BUFSIZ];
2005     /* sbeck -- handle prefixes */
2006     char *cp;
2007     CI ci;
2008     CE ce;
2009
2010     ce = ct->c_cefile;
2011     if (ce->ce_fp) {
2012         fseek (ce->ce_fp, 0L, SEEK_SET);
2013         goto ready_to_go;
2014     }
2015
2016     if (ce->ce_file) {
2017         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2018             content_error (ce->ce_file, ct, "unable to fopen for reading");
2019             return NOTOK;
2020         }
2021         goto ready_to_go;
2022     }
2023
2024     if (*file == NULL) {
2025         ce->ce_file = add (m_scratch ("", tmp), NULL);
2026         ce->ce_unlink = 1;
2027     } else {
2028         ce->ce_file = add (*file, NULL);
2029         ce->ce_unlink = 0;
2030     }
2031
2032     /* sbeck@cise.ufl.edu -- handle suffixes */
2033     ci = &ct->c_ctinfo;
2034     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2035               invo_name, ci->ci_type, ci->ci_subtype);
2036     cp = context_find (buffer);
2037     if (cp == NULL || *cp == '\0') {
2038         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2039                   ci->ci_type);
2040         cp = context_find (buffer);
2041     }
2042     if (cp != NULL && *cp != '\0')
2043         ce->ce_file = add (cp, ce->ce_file);
2044
2045     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2046         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2047         return NOTOK;
2048     }
2049
2050     if (ct->c_type == CT_MULTIPART) {
2051         char **ap, **ep;
2052         CI ci = &ct->c_ctinfo;
2053
2054         len = 0;
2055         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2056         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2057             + 1 + strlen (ci->ci_subtype);
2058         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2059             putc (';', ce->ce_fp);
2060             len++;
2061
2062             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2063
2064             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2065                 fputs ("\n\t", ce->ce_fp);
2066                 len = 8;
2067             } else {
2068                 putc (' ', ce->ce_fp);
2069                 len++;
2070             }
2071             fprintf (ce->ce_fp, "%s", buffer);
2072             len += cc;
2073         }
2074
2075         if (ci->ci_comment) {
2076             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2077                 fputs ("\n\t", ce->ce_fp);
2078                 len = 8;
2079             }
2080             else {
2081                 putc (' ', ce->ce_fp);
2082                 len++;
2083             }
2084             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2085             len += cc;
2086         }
2087         fprintf (ce->ce_fp, "\n");
2088         if (ct->c_id)
2089             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2090         if (ct->c_descr)
2091             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2092         fprintf (ce->ce_fp, "\n");
2093     }
2094
2095     if ((len = ct->c_end - ct->c_begin) < 0)
2096         adios (NULL, "internal error(3)");
2097
2098     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2099         content_error (ct->c_file, ct, "unable to open for reading");
2100         return NOTOK;
2101     }
2102
2103     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2104     while (len > 0)
2105         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2106         case NOTOK:
2107             content_error (ct->c_file, ct, "error reading from");
2108             goto clean_up;
2109
2110         case OK:
2111             content_error (NULL, ct, "premature eof");
2112             goto clean_up;
2113
2114         default:
2115             if (cc > len)
2116                 cc = len;
2117             len -= cc;
2118
2119             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2120             if (ferror (ce->ce_fp)) {
2121                 content_error (ce->ce_file, ct, "error writing to");
2122                 goto clean_up;
2123             }
2124         }
2125
2126     fseek (ct->c_fp, 0L, SEEK_SET);
2127
2128     if (fflush (ce->ce_fp)) {
2129         content_error (ce->ce_file, ct, "error writing to");
2130         goto clean_up;
2131     }
2132
2133     fseek (ce->ce_fp, 0L, SEEK_SET);
2134
2135 ready_to_go:
2136     *file = ce->ce_file;
2137     return fileno (ce->ce_fp);
2138
2139 clean_up:
2140     free_encoding (ct, 0);
2141     return NOTOK;
2142 }
2143
2144
2145 /*
2146  * External
2147  */
2148
2149 static int
2150 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2151 {
2152     char cachefile[BUFSIZ];
2153
2154     if (ce->ce_fp) {
2155         fseek (ce->ce_fp, 0L, SEEK_SET);
2156         goto ready_already;
2157     }
2158
2159     if (ce->ce_file) {
2160         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2161             content_error (ce->ce_file, ct, "unable to fopen for reading");
2162             return NOTOK;
2163         }
2164         goto ready_already;
2165     }
2166
2167     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2168                 cachefile, sizeof(cachefile)) != NOTOK) {
2169         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2170             ce->ce_file = getcpy (cachefile);
2171             ce->ce_unlink = 0;
2172             goto ready_already;
2173         } else {
2174             admonish (cachefile, "unable to fopen for reading");
2175         }
2176     }
2177
2178     return OK;
2179
2180 ready_already:
2181     *file = ce->ce_file;
2182     *fd = fileno (ce->ce_fp);
2183     return DONE;
2184 }
2185
2186 /*
2187  * File
2188  */
2189
2190 static int
2191 InitFile (CT ct)
2192 {
2193     return init_encoding (ct, openFile);
2194 }
2195
2196
2197 static int
2198 openFile (CT ct, char **file)
2199 {
2200     int fd, cachetype;
2201     char cachefile[BUFSIZ];
2202     struct exbody *e = ct->c_ctexbody;
2203     CE ce = ct->c_cefile;
2204
2205     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2206         case NOTOK:
2207             return NOTOK;
2208
2209         case OK:
2210             break;
2211
2212         case DONE:
2213             return fd;
2214     }
2215
2216     if (!e->eb_name) {
2217         content_error (NULL, ct, "missing name parameter");
2218         return NOTOK;
2219     }
2220
2221     ce->ce_file = getcpy (e->eb_name);
2222     ce->ce_unlink = 0;
2223
2224     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2225         content_error (ce->ce_file, ct, "unable to fopen for reading");
2226         return NOTOK;
2227     }
2228
2229     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2230             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2231                 cachefile, sizeof(cachefile)) != NOTOK) {
2232         int mask;
2233         FILE *fp;
2234
2235         mask = umask (cachetype ? ~m_gmprot () : 0222);
2236         if ((fp = fopen (cachefile, "w"))) {
2237             int cc;
2238             char buffer[BUFSIZ];
2239             FILE *gp = ce->ce_fp;
2240
2241             fseek (gp, 0L, SEEK_SET);
2242
2243             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2244                        > 0)
2245                 fwrite (buffer, sizeof(*buffer), cc, fp);
2246             fflush (fp);
2247
2248             if (ferror (gp)) {
2249                 admonish (ce->ce_file, "error reading");
2250                 unlink (cachefile);
2251             }
2252             else
2253                 if (ferror (fp)) {
2254                     admonish (cachefile, "error writing");
2255                     unlink (cachefile);
2256                 }
2257             fclose (fp);
2258         }
2259         umask (mask);
2260     }
2261
2262     fseek (ce->ce_fp, 0L, SEEK_SET);
2263     *file = ce->ce_file;
2264     return fileno (ce->ce_fp);
2265 }
2266
2267 /*
2268  * FTP
2269  */
2270
2271 static int
2272 InitFTP (CT ct)
2273 {
2274     return init_encoding (ct, openFTP);
2275 }
2276
2277
2278 static int
2279 openFTP (CT ct, char **file)
2280 {
2281     int cachetype, caching, fd;
2282     int len, buflen;
2283     char *bp, *ftp, *user, *pass;
2284     char buffer[BUFSIZ], cachefile[BUFSIZ];
2285     struct exbody *e;
2286     CE ce;
2287     static char *username = NULL;
2288     static char *password = NULL;
2289
2290     e  = ct->c_ctexbody;
2291     ce = ct->c_cefile;
2292
2293     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2294         ftp = NULL;
2295
2296 #ifndef BUILTIN_FTP
2297     if (!ftp)
2298         return NOTOK;
2299 #endif
2300
2301     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2302         case NOTOK:
2303             return NOTOK;
2304
2305         case OK:
2306             break;
2307
2308         case DONE:
2309             return fd;
2310     }
2311
2312     if (!e->eb_name || !e->eb_site) {
2313         content_error (NULL, ct, "missing %s parameter",
2314                        e->eb_name ? "site": "name");
2315         return NOTOK;
2316     }
2317
2318     if (xpid) {
2319         if (xpid < 0)
2320             xpid = -xpid;
2321         pidcheck (pidwait (xpid, NOTOK));
2322         xpid = 0;
2323     }
2324
2325     /* Get the buffer ready to go */
2326     bp = buffer;
2327     buflen = sizeof(buffer);
2328
2329     /*
2330      * Construct the query message for user
2331      */
2332     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2333     len = strlen (bp);
2334     bp += len;
2335     buflen -= len;
2336
2337     if (e->eb_partno) {
2338         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2339         len = strlen (bp);
2340         bp += len;
2341         buflen -= len;
2342     }
2343
2344     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2345                     e->eb_flags ? "anonymous " : "", e->eb_site);
2346     len = strlen (bp);
2347     bp += len;
2348     buflen -= len;
2349
2350     if (e->eb_size > 0) {
2351         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2352         len = strlen (bp);
2353         bp += len;
2354         buflen -= len;
2355     }
2356     snprintf (bp, buflen, "? ");
2357
2358     /*
2359      * Now, check the answer
2360      */
2361     if (!getanswer (buffer))
2362         return NOTOK;
2363
2364     if (e->eb_flags) {
2365         user = "anonymous";
2366         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2367         pass = buffer;
2368     } else {
2369         ruserpass (e->eb_site, &username, &password);
2370         user = username;
2371         pass = password;
2372     }
2373
2374     ce->ce_unlink = (*file == NULL);
2375     caching = 0;
2376     cachefile[0] = '\0';
2377     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2378             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2379                 cachefile, sizeof(cachefile)) != NOTOK) {
2380         if (*file == NULL) {
2381             ce->ce_unlink = 0;
2382             caching = 1;
2383         }
2384     }
2385
2386     if (*file)
2387         ce->ce_file = add (*file, NULL);
2388     else if (caching)
2389         ce->ce_file = add (cachefile, NULL);
2390     else
2391         ce->ce_file = add (m_scratch ("", tmp), NULL);
2392
2393     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2394         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2395         return NOTOK;
2396     }
2397
2398 #ifdef BUILTIN_FTP
2399     if (ftp)
2400 #endif
2401     {
2402         int child_id, i, vecp;
2403         char *vec[9];
2404
2405         vecp = 0;
2406         vec[vecp++] = r1bindex (ftp, '/');
2407         vec[vecp++] = e->eb_site;
2408         vec[vecp++] = user;
2409         vec[vecp++] = pass;
2410         vec[vecp++] = e->eb_dir;
2411         vec[vecp++] = e->eb_name;
2412         vec[vecp++] = ce->ce_file,
2413         vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2414                         ? "ascii" : "binary";
2415         vec[vecp] = NULL;
2416
2417         fflush (stdout);
2418
2419         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2420             sleep (5);
2421         switch (child_id) {
2422             case NOTOK:
2423                 adios ("fork", "unable to");
2424                 /* NOTREACHED */
2425
2426             case OK:
2427                 close (fileno (ce->ce_fp));
2428                 execvp (ftp, vec);
2429                 fprintf (stderr, "unable to exec ");
2430                 perror (ftp);
2431                 _exit (-1);
2432                 /* NOTREACHED */
2433
2434             default:
2435                 if (pidXwait (child_id, NULL)) {
2436 #ifdef BUILTIN_FTP
2437 losing_ftp:
2438 #endif
2439                     username = password = NULL;
2440                     ce->ce_unlink = 1;
2441                     return NOTOK;
2442                 }
2443                 break;
2444         }
2445     }
2446 #ifdef BUILTIN_FTP
2447     else
2448         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2449                      ce->ce_file,
2450                      e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0)
2451                 == NOTOK)
2452             goto losing_ftp;
2453 #endif
2454
2455     if (cachefile[0]) {
2456         if (caching)
2457             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2458         else {
2459             int mask;
2460             FILE *fp;
2461
2462             mask = umask (cachetype ? ~m_gmprot () : 0222);
2463             if ((fp = fopen (cachefile, "w"))) {
2464                 int cc;
2465                 FILE *gp = ce->ce_fp;
2466
2467                 fseek (gp, 0L, SEEK_SET);
2468
2469                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2470                            > 0)
2471                     fwrite (buffer, sizeof(*buffer), cc, fp);
2472                 fflush (fp);
2473
2474                 if (ferror (gp)) {
2475                     admonish (ce->ce_file, "error reading");
2476                     unlink (cachefile);
2477                 }
2478                 else
2479                     if (ferror (fp)) {
2480                         admonish (cachefile, "error writing");
2481                         unlink (cachefile);
2482                     }
2483                 fclose (fp);
2484             }
2485             umask (mask);
2486         }
2487     }
2488
2489     fseek (ce->ce_fp, 0L, SEEK_SET);
2490     *file = ce->ce_file;
2491     return fileno (ce->ce_fp);
2492 }
2493
2494
2495 /*
2496  * Mail
2497  */
2498
2499 static int
2500 InitMail (CT ct)
2501 {
2502     return init_encoding (ct, openMail);
2503 }
2504
2505
2506 static int
2507 openMail (CT ct, char **file)
2508 {
2509     int child_id, fd, i, vecp;
2510     int len, buflen;
2511     char *bp, buffer[BUFSIZ], *vec[7];
2512     struct exbody *e = ct->c_ctexbody;
2513     CE ce = ct->c_cefile;
2514
2515     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2516         case NOTOK:
2517             return NOTOK;
2518
2519         case OK:
2520             break;
2521
2522         case DONE:
2523             return fd;
2524     }
2525
2526     if (!e->eb_server) {
2527         content_error (NULL, ct, "missing server parameter");
2528         return NOTOK;
2529     }
2530
2531     if (xpid) {
2532         if (xpid < 0)
2533             xpid = -xpid;
2534         pidcheck (pidwait (xpid, NOTOK));
2535         xpid = 0;
2536     }
2537
2538     /* Get buffer ready to go */
2539     bp = buffer;
2540     buflen = sizeof(buffer);
2541
2542     /* Now, construct query message */
2543     snprintf (bp, buflen, "Retrieve content");
2544     len = strlen (bp);
2545     bp += len;
2546     buflen -= len;
2547
2548     if (e->eb_partno) {
2549         snprintf (bp, buflen, " %s", e->eb_partno);
2550         len = strlen (bp);
2551         bp += len;
2552         buflen -= len;
2553     }
2554
2555     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2556                     e->eb_server,
2557                     e->eb_subject ? e->eb_subject : e->eb_body);
2558
2559     /* Now, check answer */
2560     if (!getanswer (buffer))
2561         return NOTOK;
2562
2563     vecp = 0;
2564     vec[vecp++] = r1bindex (mailproc, '/');
2565     vec[vecp++] = e->eb_server;
2566     vec[vecp++] = "-subject";
2567     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2568     vec[vecp++] = "-body";
2569     vec[vecp++] = e->eb_body;
2570     vec[vecp] = NULL;
2571
2572     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2573         sleep (5);
2574     switch (child_id) {
2575         case NOTOK:
2576             advise ("fork", "unable to");
2577             return NOTOK;
2578
2579         case OK:
2580             execvp (mailproc, vec);
2581             fprintf (stderr, "unable to exec ");
2582             perror (mailproc);
2583             _exit (-1);
2584             /* NOTREACHED */
2585
2586         default:
2587             if (pidXwait (child_id, NULL) == OK)
2588                 advise (NULL, "request sent");
2589             break;
2590     }
2591
2592     if (*file == NULL) {
2593         ce->ce_file = add (m_scratch ("", tmp), NULL);
2594         ce->ce_unlink = 1;
2595     } else {
2596         ce->ce_file = add (*file, NULL);
2597         ce->ce_unlink = 0;
2598     }
2599
2600     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2601         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2602         return NOTOK;
2603     }
2604
2605     if (ct->c_showproc)
2606         free (ct->c_showproc);
2607     ct->c_showproc = add ("true", NULL);
2608
2609     fseek (ce->ce_fp, 0L, SEEK_SET);
2610     *file = ce->ce_file;
2611     return fileno (ce->ce_fp);
2612 }
2613
2614
2615 static int
2616 readDigest (CT ct, char *cp)
2617 {
2618     int bitno, skip;
2619     unsigned long bits;
2620     char *bp = cp;
2621     unsigned char *dp, value, *ep;
2622     unsigned char *b, *b1, *b2, *b3;
2623
2624     b  = (unsigned char *) &bits,
2625     b1 = &b[endian > 0 ? 1 : 2],
2626     b2 = &b[endian > 0 ? 2 : 1],
2627     b3 = &b[endian > 0 ? 3 : 0];
2628     bitno = 18;
2629     bits = 0L;
2630     skip = 0;
2631
2632     for (ep = (dp = ct->c_digest)
2633                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2634         switch (*cp) {
2635             default:
2636                 if (skip
2637                         || (*cp & 0x80)
2638                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2639                     if (debugsw)
2640                         fprintf (stderr, "invalid BASE64 encoding\n");
2641                     return NOTOK;
2642                 }
2643
2644                 bits |= value << bitno;
2645 test_end:
2646                 if ((bitno -= 6) < 0) {
2647                     if (dp + (3 - skip) > ep)
2648                         goto invalid_digest;
2649                     *dp++ = *b1;
2650                     if (skip < 2) {
2651                         *dp++ = *b2;
2652                         if (skip < 1)
2653                             *dp++ = *b3;
2654                     }
2655                     bitno = 18;
2656                     bits = 0L;
2657                     skip = 0;
2658                 }
2659                 break;
2660
2661             case '=':
2662                 if (++skip > 3)
2663                     goto self_delimiting;
2664                 goto test_end;
2665         }
2666     if (bitno != 18) {
2667         if (debugsw)
2668             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2669
2670         return NOTOK;
2671     }
2672 self_delimiting:
2673     if (dp != ep) {
2674 invalid_digest:
2675         if (debugsw) {
2676             while (*cp)
2677                 cp++;
2678             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2679                      cp - bp);
2680         }
2681
2682         return NOTOK;
2683     }
2684
2685     if (debugsw) {
2686         fprintf (stderr, "MD5 digest=");
2687         for (dp = ct->c_digest; dp < ep; dp++)
2688             fprintf (stderr, "%02x", *dp & 0xff);
2689         fprintf (stderr, "\n");
2690     }
2691
2692     return OK;
2693 }