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