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