Initial revision
[mmh] / uip / mhbuildsbr.c
1
2 /*
3  * mhbuildsbr.c -- routines to expand/translate MIME composition files
4  *
5  * $Id$
6  */
7
8 /*
9  * This code was originally part of mhn.c.  I split it into
10  * a separate program (mhbuild.c) and then later split it
11  * again (mhbuildsbr.c).  But the code still has some of
12  * the mhn.c code in it.  This program needs additional
13  * streamlining and removal of unneeded code.
14  */
15
16 #include <h/mh.h>
17 #include <fcntl.h>
18 #include <h/signals.h>
19 #include <h/md5.h>
20 #include <errno.h>
21 #include <signal.h>
22 #include <zotnet/mts/mts.h>
23 #include <zotnet/tws/tws.h>
24 #include <h/mime.h>
25 #include <h/mhparse.h>
26
27 #ifdef HAVE_SYS_WAIT_H
28 # include <sys/wait.h>
29 #endif
30
31
32 extern int errno;
33
34 extern int debugsw;
35 extern int verbosw;
36
37 extern int ebcdicsw;
38 extern int listsw;
39 extern int rfc934sw;
40
41 extern int endian;      /* mhmisc.c */
42
43 /* cache policies */
44 extern int rcachesw;    /* mhcachesbr.c */
45 extern int wcachesw;    /* mhcachesbr.c */
46
47 int checksw = 0;        /* Add Content-MD5 field */
48
49 /*
50  * Directory to place tmp files.  This must
51  * be set before these routines are called.
52  */
53 char *tmp;
54
55 pid_t xpid = 0;
56
57 static char prefix[] = "----- =_aaaaaaaaaa";
58
59 /*
60  * Structure for mapping types to their internal flags
61  */
62 struct k2v {
63     char *kv_key;
64     int   kv_value;
65 };
66
67 /*
68  * Structures for TEXT messages
69  */
70 static struct k2v SubText[] = {
71     { "plain",    TEXT_PLAIN },
72     { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
73     { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
74     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
75 };
76
77 static struct k2v Charset[] = {
78     { "us-ascii",   CHARSET_USASCII },
79     { "iso-8859-1", CHARSET_LATIN },
80     { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
81 };
82
83 /*
84  * Structures for MULTIPART messages
85  */
86 static struct k2v SubMultiPart[] = {
87     { "mixed",       MULTI_MIXED },
88     { "alternative", MULTI_ALTERNATE },
89     { "digest",      MULTI_DIGEST },
90     { "parallel",    MULTI_PARALLEL },
91     { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
92 };
93
94 /*
95  * Structures for MESSAGE messages
96  */
97 static struct k2v SubMessage[] = {
98     { "rfc822",        MESSAGE_RFC822 },
99     { "partial",       MESSAGE_PARTIAL },
100     { "external-body", MESSAGE_EXTERNAL },
101     { NULL,            MESSAGE_UNKNOWN }        /* this one must be last! */
102 };
103
104 /*
105  * Structure for APPLICATION messages
106  */
107 static struct k2v SubApplication[] = {
108     { "octet-stream", APPLICATION_OCTETS },
109     { "postscript",   APPLICATION_POSTSCRIPT },
110     { NULL,           APPLICATION_UNKNOWN }     /* this one must be last! */
111 };
112
113
114 /* mhmisc.c */
115 int make_intermediates (char *);
116 void content_error (char *, CT, char *, ...);
117
118 /* mhcachesbr.c */
119 int find_cache (CT, int, int *, char *, char *, int);
120
121 /* ftpsbr.c */
122 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
123
124 /* mhfree.c */
125 void free_content (CT);
126 void free_ctinfo (CT);
127 void free_encoding (CT, int);
128
129 /*
130  * prototypes
131  */
132 CT build_mime (char *);
133 int pidcheck (int);
134
135 /*
136  * static prototypes
137  */
138 static CT get_content (FILE *, char *, int);
139 static int add_header (CT, char *, char *);
140 static int get_ctinfo (char *, CT, int);
141 static int get_comment (CT, char **, int);
142 static int InitGeneric (CT);
143 static int InitText (CT);
144 static int InitMultiPart (CT);
145 static void reverse_parts (CT);
146 static int InitMessage (CT);
147 static int params_external (CT, int);
148 static int InitApplication (CT);
149 static int init_decoded_content (CT);
150 static int init_encoding (CT, OpenCEFunc);
151 static void close_encoding (CT);
152 static unsigned long size_encoding (CT);
153 static int InitBase64 (CT);
154 static int openBase64 (CT, char **);
155 static int InitQuoted (CT);
156 static int openQuoted (CT, char **);
157 static int Init7Bit (CT);
158 static int open7Bit (CT, char **);
159 static int openExternal (CT, CT, CE, char **, int *);
160 static int InitFile (CT);
161 static int openFile (CT, char **);
162 static int InitFTP (CT);
163 static int openFTP (CT, char **);
164 static int InitMail (CT);
165 static int openMail (CT, char **);
166 static char *fgetstr (char *, int, FILE *);
167 static int user_content (FILE *, char *, char *, CT *);
168 static void set_id (CT, int);
169 static int compose_content (CT);
170 static int scan_content (CT);
171 static int build_headers (CT);
172 static char *calculate_digest (CT, int);
173 static int readDigest (CT, char *);
174
175 /*
176  * Structures for mapping (content) types to
177  * the functions to handle them.
178  */
179 struct str2init {
180     char *si_key;
181     int   si_val;
182     InitFunc si_init;
183 };
184
185 static struct str2init str2cts[] = {
186     { "application", CT_APPLICATION, InitApplication },
187     { "audio",       CT_AUDIO,       InitGeneric },
188     { "image",       CT_IMAGE,       InitGeneric },
189     { "message",     CT_MESSAGE,     InitMessage },
190     { "multipart",   CT_MULTIPART,   InitMultiPart },
191     { "text",        CT_TEXT,        InitText },
192     { "video",       CT_VIDEO,       InitGeneric },
193     { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
194     { NULL,          CT_UNKNOWN,     NULL },
195 };
196
197 static struct str2init str2ces[] = {
198     { "base64",           CE_BASE64,    InitBase64 },
199     { "quoted-printable", CE_QUOTED,    InitQuoted },
200     { "8bit",             CE_8BIT,      Init7Bit },
201     { "7bit",             CE_7BIT,      Init7Bit },
202     { "binary",           CE_BINARY,    NULL },
203     { NULL,               CE_EXTENSION, NULL },  /* these two must be last! */
204     { NULL,               CE_UNKNOWN,   NULL },
205 };
206
207 /*
208  * NOTE WELL: si_key MUST NOT have value of NOTOK
209  *
210  * si_key is 1 if access method is anonymous.
211  */
212 static struct str2init str2methods[] = {
213     { "afs",         1, InitFile },
214     { "anon-ftp",    1, InitFTP },
215     { "ftp",         0, InitFTP },
216     { "local-file",  0, InitFile },
217     { "mail-server", 0, InitMail },
218     { NULL,          0, NULL }
219 };
220
221
222 int
223 pidcheck (int status)
224 {
225     if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
226         return status;
227
228     fflush (stdout);
229     fflush (stderr);
230     done (1);
231     /* NOTREACHED */
232 }
233
234
235 /*
236  * Main routine for translating composition file
237  * into valid MIME message.  It translates the draft
238  * into a content structure (actually a tree of content
239  * structures).  This message then can be manipulated
240  * in various ways, including being output via
241  * output_message().
242  */
243
244 CT
245 build_mime (char *infile)
246 {
247     int compnum, state;
248     char buf[BUFSIZ], name[NAMESZ];
249     char *cp, *np, *vp;
250     struct multipart *m;
251     struct part **pp;
252     CT ct;
253     FILE *in;
254
255     umask (~m_gmprot ());
256
257     /* open the composition draft */
258     if ((in = fopen (infile, "r")) == NULL)
259         adios (infile, "unable to open for reading");
260
261     /*
262      * Allocate space for primary (outside) content
263      */
264     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
265         adios (NULL, "out of memory");
266
267     /*
268      * Allocate structure for handling decoded content
269      * for this part.  We don't really need this, but
270      * allocate it to remain consistent.
271      */
272     init_decoded_content (ct);
273
274     /*
275      * Parse some of the header fields in the composition
276      * draft into the linked list of header fields for
277      * the new MIME message.
278      */
279     for (compnum = 1, state = FLD;;) {
280         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
281         case FLD:
282         case FLDPLUS:
283         case FLDEOF:
284             compnum++;
285
286             /* abort if draft has Mime-Version header field */
287             if (!strcasecmp (name, VRSN_FIELD))
288                 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
289
290             /* abort if draft has Content-Transfer-Encoding header field */
291             if (!strcasecmp (name, ENCODING_FIELD))
292                 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
293
294             /* ignore any Content-Type fields in the header */
295             if (!strcasecmp (name, TYPE_FIELD)) {
296                 while (state == FLDPLUS)
297                     state = m_getfld (state, name, buf, sizeof(buf), in);
298                 goto finish_field;
299             }
300
301             /* get copies of the buffers */
302             np = add (name, NULL);
303             vp = add (buf, NULL);
304
305             /* if necessary, get rest of field */
306             while (state == FLDPLUS) {
307                 state = m_getfld (state, name, buf, sizeof(buf), in);
308                 vp = add (buf, vp);     /* add to previous value */
309             }
310
311             /* Now add the header data to the list */
312             add_header (ct, np, vp);
313
314 finish_field:
315             /* if this wasn't the last header field, then continue */
316             if (state != FLDEOF)
317                 continue;
318             /* else fall... */
319
320         case FILEEOF:
321             adios (NULL, "draft has empty body -- no directives!");
322             /* NOTREACHED */
323
324         case BODY:
325         case BODYEOF:
326             fseek (in, (long) (-strlen (buf)), SEEK_CUR);
327             break;
328
329         case LENERR:
330         case FMTERR:
331             adios (NULL, "message format error in component #%d", compnum);
332
333         default:
334             adios (NULL, "getfld() returned %d", state);
335         }
336         break;
337     }
338
339     /*
340      * Now add the MIME-Version header field
341      * to the list of header fields.
342      */
343     np = add (VRSN_FIELD, NULL);
344     vp = concat (" ", VRSN_VALUE, "\n", NULL);
345     add_header (ct, np, vp);
346
347     /*
348      * We initally assume we will find multiple contents in the
349      * draft.  So create a multipart/mixed content to hold everything.
350      * We can remove this later, if it is not needed.
351      */
352     if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
353         done (1);
354     ct->c_type = CT_MULTIPART;
355     ct->c_subtype = MULTI_MIXED;
356     ct->c_file = add (infile, NULL);
357
358     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
359         adios (NULL, "out of memory");
360     ct->c_ctparams = (void *) m;
361     pp = &m->mp_parts;
362
363     /*
364      * read and parse the composition file
365      * and the directives it contains.
366      */
367     while (fgetstr (buf, sizeof(buf) - 1, in)) {
368         struct part *part;
369         CT p;
370
371         if (user_content (in, infile, buf, &p) == DONE) {
372             admonish (NULL, "ignoring spurious #end");
373             continue;
374         }
375         if (!p)
376             continue;
377
378         if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
379             adios (NULL, "out of memory");
380         *pp = part;
381         pp = &part->mp_next;
382         part->mp_part = p;
383     }
384
385     /*
386      * close the composition draft since
387      * it's not needed any longer.
388      */
389     fclose (in);
390
391     /* check if any contents were found */
392     if (!m->mp_parts)
393         adios (NULL, "no content directives found");
394
395     /*
396      * If only one content was found, then remove and
397      * free the outer multipart content.
398      */
399     if (!m->mp_parts->mp_next) {
400         CT p;
401
402         p = m->mp_parts->mp_part;
403         m->mp_parts->mp_part = NULL;
404
405         /* move header fields */
406         p->c_first_hf = ct->c_first_hf;
407         p->c_last_hf = ct->c_last_hf;
408         ct->c_first_hf = NULL;
409         ct->c_last_hf = NULL;
410
411         free_content (ct);
412         ct = p;
413     } else {
414         set_id (ct, 1);
415     }
416
417     /*
418      * Fill out, or expand directives.  Parse and execute
419      * commands specified by profile composition strings.
420      */
421     compose_content (ct);
422
423     if ((cp = strchr(prefix, 'a')) == NULL)
424         adios (NULL, "internal error(4)");
425
426     /*
427      * Scan the contents.  Choose a transfer encoding, and
428      * check if prefix for multipart boundary clashes with
429      * any of the contents.
430      */
431     while (scan_content (ct) == NOTOK) {
432         if (*cp < 'z') {
433             (*cp)++;
434         } else {
435             if (*++cp == 0)
436                 adios (NULL, "giving up trying to find a unique delimiter string");
437             else
438                 (*cp)++;
439         }
440     }
441
442     /* Build the rest of the header field structures */
443     build_headers (ct);
444
445     return ct;
446 }
447
448
449 /*
450  * Main routine for reading/parsing the headers
451  * of a message content.
452  *
453  * toplevel =  1   # we are at the top level of the message
454  * toplevel =  0   # we are inside message type or multipart type
455  *                 # other than multipart/digest
456  * toplevel = -1   # we are inside multipart/digest
457  */
458
459 static CT
460 get_content (FILE *in, char *file, int toplevel)
461 {
462     int compnum, state;
463     char buf[BUFSIZ], name[NAMESZ];
464     CT ct;
465
466     if (!(ct = (CT) calloc (1, sizeof(*ct))))
467         adios (NULL, "out of memory");
468
469     ct->c_fp = in;
470     ct->c_file = add (file, NULL);
471     ct->c_begin = ftell (ct->c_fp) + 1;
472
473     /*
474      * Read the content headers
475      */
476     for (compnum = 1, state = FLD;;) {
477         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
478         case FLD:
479         case FLDPLUS:
480         case FLDEOF:
481             compnum++;
482
483             /* Get MIME-Version field */
484             if (!strcasecmp (name, VRSN_FIELD)) {
485                 int ucmp;
486                 char c, *cp, *dp;
487
488                 cp = add (buf, NULL);
489                 while (state == FLDPLUS) {
490                     state = m_getfld (state, name, buf, sizeof(buf), in);
491                     cp = add (buf, cp);
492                 }
493
494                 if (ct->c_vrsn) {
495                     advise (NULL, "message %s has multiple %s: fields (%s)",
496                             ct->c_file, VRSN_FIELD, dp = trimcpy (cp));
497                     free (dp);
498                     free (cp);
499                     goto out;
500                 }
501
502                 ct->c_vrsn = cp;
503                 while (isspace (*cp))
504                     cp++;
505                 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
506                     *dp++ = ' ';
507                 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
508                     if (!isspace (*dp))
509                         break;
510                 *++dp = '\0';
511                 if (debugsw)
512                     fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
513
514                 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
515                     goto out;
516
517                 for (dp = cp; istoken (*dp); dp++)
518                     continue;
519                 c = *dp, *dp = '\0';
520                 ucmp = !strcasecmp (cp, VRSN_VALUE);
521                 *dp = c;
522                 if (!ucmp)
523                     admonish (NULL,
524                               "message %s has unknown value for %s: field (%s)",
525                               ct->c_file, VRSN_FIELD, cp);
526                 goto got_header;
527             }
528
529             /* Get Content-Type field */
530             if (!strcasecmp (name, TYPE_FIELD)) {
531                 char *cp;
532                 struct str2init *s2i;
533                 CI ci = &ct->c_ctinfo;
534
535                 cp = add (buf, NULL);
536                 while (state == FLDPLUS) {
537                     state = m_getfld (state, name, buf, sizeof(buf), in);
538                     cp = add (buf, cp);
539                 }
540
541                 /* Check if we've already seen a Content-Type header */
542                 if (ct->c_ctline) {
543                     char *dp = trimcpy (cp);
544
545                     advise (NULL, "message %s has multiple %s: fields (%s)",
546                             ct->c_file, TYPE_FIELD, dp);
547                     free (dp);
548                     free (cp);
549                     goto out;
550                 }
551
552                 /* Parse the Content-Type field */
553                 if (get_ctinfo (cp, ct, 0) == NOTOK)
554                     goto out;
555
556                 /*
557                  * Set the Init function and the internal
558                  * flag for this content type.
559                  */
560                 for (s2i = str2cts; s2i->si_key; s2i++)
561                     if (!strcasecmp (ci->ci_type, s2i->si_key))
562                         break;
563                 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
564                     s2i++;
565                 ct->c_type = s2i->si_val;
566                 ct->c_ctinitfnx = s2i->si_init;
567                 goto got_header;
568             }
569
570             /* Get Content-Transfer-Encoding field */
571             if (!strcasecmp (name, ENCODING_FIELD)) {
572                 char *cp, *dp;
573                 char c;
574                 struct str2init *s2i;
575
576                 cp = add (buf, NULL);
577                 while (state == FLDPLUS) {
578                     state = m_getfld (state, name, buf, sizeof(buf), in);
579                     cp = add (buf, cp);
580                 }
581
582                 /*
583                  * Check if we've already seen the
584                  * Content-Transfer-Encoding field
585                  */
586                 if (ct->c_celine) {
587                     advise (NULL, "message %s has multiple %s: fields (%s)",
588                             ct->c_file, ENCODING_FIELD, dp = trimcpy (cp));
589                     free (dp);
590                     free (cp);
591                     goto out;
592                 }
593
594                 ct->c_celine = cp;      /* Save copy of this field */
595                 while (isspace (*cp))
596                     cp++;
597                 for (dp = cp; istoken (*dp); dp++)
598                     continue;
599                 c = *dp;
600                 *dp = '\0';
601
602                 /*
603                  * Find the internal flag and Init function
604                  * for this transfer encoding.
605                  */
606                 for (s2i = str2ces; s2i->si_key; s2i++)
607                     if (!strcasecmp (cp, s2i->si_key))
608                         break;
609                 if (!s2i->si_key && !uprf (cp, "X-"))
610                     s2i++;
611                 *dp = c;
612                 ct->c_encoding = s2i->si_val;
613
614                 /* Call the Init function for this encoding */
615                 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
616                     goto out;
617                 goto got_header;
618             }
619
620             /* Get Content-ID field */
621             if (!strcasecmp (name, ID_FIELD)) {
622                 ct->c_id = add (buf, ct->c_id);
623                 while (state == FLDPLUS) {
624                     state = m_getfld (state, name, buf, sizeof(buf), in);
625                     ct->c_id = add (buf, ct->c_id);
626                 }
627                 goto got_header;
628             }
629
630             /* Get Content-Description field */
631             if (!strcasecmp (name, DESCR_FIELD)) {
632                 ct->c_descr = add (buf, ct->c_descr);
633                 while (state == FLDPLUS) {
634                     state = m_getfld (state, name, buf, sizeof(buf), in);
635                     ct->c_descr = add (buf, ct->c_descr);
636                 }
637                 goto got_header;
638             }
639
640             /* Get Content-MD5 field */
641             if (!strcasecmp (name, MD5_FIELD)) {
642                 char *cp, *dp, *ep;
643
644                 cp = add (buf, NULL);
645                 while (state == FLDPLUS) {
646                     state = m_getfld (state, name, buf, sizeof(buf), in);
647                     cp = add (buf, cp);
648                 }
649
650                 if (!checksw) {
651                     free (cp);
652                     goto got_header;
653                 }
654
655                 if (ct->c_digested) {
656                     advise (NULL, "message %s has multiple %s: fields (%s)",
657                             ct->c_file, MD5_FIELD, dp = trimcpy (cp));
658                     free (dp);
659                     free (cp);
660                     goto out;
661                 }
662
663                 ep = cp;
664                 while (isspace (*cp))
665                     cp++;
666                 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
667                     *dp++ = ' ';
668                 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
669                     if (!isspace (*dp))
670                         break;
671                 *++dp = '\0';
672                 if (debugsw)
673                     fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
674
675                 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
676                     free (ep);
677                     goto out;
678                 }
679
680                 for (dp = cp; *dp && !isspace (*dp); dp++)
681                     continue;
682                 *dp = '\0';
683
684                 readDigest (ct, cp);
685                 free (ep);
686                 ct->c_digested++;
687                 goto got_header;
688             }
689
690 #if 0
691             if (uprf (name, XXX_FIELD_PRF))
692                 advise (NULL, "unknown field (%s) in message %s",
693                         name, ct->c_file);
694             /* and fall... */
695 #endif
696
697             while (state == FLDPLUS)
698                 state = m_getfld (state, name, buf, sizeof(buf), in);
699
700 got_header:
701             if (state != FLDEOF) {
702                 ct->c_begin = ftell (in) + 1;
703                 continue;
704             }
705             /* else fall... */
706
707         case BODY:
708         case BODYEOF:
709             ct->c_begin = ftell (in) - strlen (buf);
710             break;
711
712         case FILEEOF:
713             ct->c_begin = ftell (in);
714             break;
715
716         case LENERR:
717         case FMTERR:
718             adios (NULL, "message format error in component #%d", compnum);
719
720         default:
721             adios (NULL, "getfld() returned %d", state);
722         }
723         break;
724     }
725
726     /*
727      * Check if we saw a Content-Type field.
728      * If not, then assign a default value for
729      * it, and the Init function.
730      */
731     if (!ct->c_ctline) {
732         /*
733          * If we are inside a multipart/digest message,
734          * so default type is message/rfc822
735          */
736         if (toplevel < 0) {
737             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
738                 goto out;
739             ct->c_type = CT_MESSAGE;
740             ct->c_ctinitfnx = InitMessage;
741         } else {
742             /*
743              * Else default type is text/plain
744              */
745             if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
746                 goto out;
747             ct->c_type = CT_TEXT;
748             ct->c_ctinitfnx = InitText;
749         }
750     }
751
752     /* Use default Transfer-Encoding, if necessary */
753     if (!ct->c_celine) {
754         ct->c_encoding = CE_7BIT;
755         Init7Bit (ct);
756     }
757
758     return ct;
759
760 out:
761     free_content (ct);
762     return NULL;
763 }
764
765
766 /*
767  * small routine to add header field to list
768  */
769
770 static int
771 add_header (CT ct, char *name, char *value)
772 {
773     HF hp;
774
775     /* allocate header field structure */
776     if (!(hp = malloc (sizeof(*hp))))
777         adios (NULL, "out of memory");
778
779     /* link data into header structure */
780     hp->name = name;
781     hp->value = value;
782     hp->next = NULL;
783
784     /* link header structure into the list */
785     if (ct->c_first_hf == NULL) {
786         ct->c_first_hf = hp;            /* this is the first */
787         ct->c_last_hf = hp;
788     } else {
789         ct->c_last_hf->next = hp;       /* add it to the end */
790         ct->c_last_hf = hp;
791     }
792
793     return 0;
794 }
795
796
797 /*
798  * Used to parse both:
799  *   1) Content-Type line
800  *   2) composition directives
801  *
802  * and fills in the information of the CTinfo structure.
803  */
804
805 static int
806 get_ctinfo (char *cp, CT ct, int magic)
807 {
808     int i;
809     char *dp, **ap, **ep;
810     char c;
811     CI ci;
812
813     ci = &ct->c_ctinfo;
814     i = strlen (invo_name) + 2;
815
816     /* store copy of Content-Type line */
817     cp = ct->c_ctline = add (cp, NULL);
818
819     while (isspace (*cp))       /* trim leading spaces */
820         cp++;
821
822     /* change newlines to spaces */
823     for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
824         *dp++ = ' ';
825
826     /* trim trailing spaces */
827     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
828         if (!isspace (*dp))
829             break;
830     *++dp = '\0';
831
832     if (debugsw)
833         fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
834
835     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
836         return NOTOK;
837
838     for (dp = cp; istoken (*dp); dp++)
839         continue;
840     c = *dp, *dp = '\0';
841     ci->ci_type = add (cp, NULL);       /* store content type */
842     *dp = c, cp = dp;
843
844     if (!*ci->ci_type) {
845         advise (NULL, "invalid %s: field in message %s (empty type)", 
846                 TYPE_FIELD, ct->c_file);
847         return NOTOK;
848     }
849
850     /* down case the content type string */
851     for (dp = ci->ci_type; *dp; dp++)
852         if (isalpha(*dp) && isupper (*dp))
853             *dp = tolower (*dp);
854
855     while (isspace (*cp))
856         cp++;
857
858     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
859         return NOTOK;
860
861     if (*cp != '/') {
862         if (!magic)
863             ci->ci_subtype = add ("", NULL);
864         goto magic_skip;
865     }
866
867     cp++;
868     while (isspace (*cp))
869         cp++;
870
871     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
872         return NOTOK;
873
874     for (dp = cp; istoken (*dp); dp++)
875         continue;
876     c = *dp, *dp = '\0';
877     ci->ci_subtype = add (cp, NULL);    /* store the content subtype */
878     *dp = c, cp = dp;
879
880     if (!*ci->ci_subtype) {
881         advise (NULL,
882                 "invalid %s: field in message %s (empty subtype for \"%s\")",
883                 TYPE_FIELD, ct->c_file, ci->ci_type);
884         return NOTOK;
885     }
886
887     /* down case the content subtype string */
888     for (dp = ci->ci_subtype; *dp; dp++)
889         if (isalpha(*dp) && isupper (*dp))
890             *dp = tolower (*dp);
891
892 magic_skip:
893     while (isspace (*cp))
894         cp++;
895
896     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
897         return NOTOK;
898
899     /*
900      * Parse attribute/value pairs given with Content-Type
901      */
902     ep = (ap = ci->ci_attrs) + NPARMS;
903     while (*cp == ';') {
904         char *vp, *up;
905
906         if (ap >= ep) {
907             advise (NULL,
908                     "too many parameters in message %s's %s: field (%d max)",
909                     ct->c_file, TYPE_FIELD, NPARMS);
910             return NOTOK;
911         }
912
913         cp++;
914         while (isspace (*cp))
915             cp++;
916
917         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
918             return NOTOK;
919
920         if (*cp == 0) {
921             advise (NULL,
922                     "extraneous trailing ';' in message %s's %s: parameter list",
923                     ct->c_file, TYPE_FIELD);
924             return OK;
925         }
926
927         /* down case the attribute name */
928         for (dp = cp; istoken (*dp); dp++)
929             if (isalpha(*dp) && isupper (*dp))
930                 *dp = tolower (*dp);
931
932         for (up = dp; isspace (*dp); )
933             dp++;
934         if (dp == cp || *dp != '=') {
935             advise (NULL,
936                     "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
937                     ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
938             return NOTOK;
939         }
940
941         vp = (*ap = add (cp, NULL)) + (up - cp);
942         *vp = '\0';
943         for (dp++; isspace (*dp); )
944             dp++;
945
946         /* now add the attribute value */
947         ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
948
949         if (*dp == '"') {
950             for (cp = ++dp, dp = vp;;) {
951                 switch (c = *cp++) {
952                     case '\0':
953 bad_quote:
954                         advise (NULL,
955                                 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
956                                 ct->c_file, TYPE_FIELD, i, i, "", *ap);
957                         return NOTOK;
958
959                     case '\\':
960                         *dp++ = c;
961                         if ((c = *cp++) == '\0')
962                             goto bad_quote;
963                         /* else fall... */
964
965                     default:
966                         *dp++ = c;
967                         continue;
968
969                     case '"':
970                         *dp = '\0';
971                         break;
972                 }
973                 break;
974             }
975         } else {
976             for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
977                 continue;
978             *dp = '\0';
979         }
980         if (!*vp) {
981             advise (NULL,
982                     "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
983                     ct->c_file, TYPE_FIELD, i, i, "", *ap);
984             return NOTOK;
985         }
986         ap++;
987
988         while (isspace (*cp))
989             cp++;
990
991         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
992             return NOTOK;
993     }
994
995     /*
996      * Get any <Content-Id> given in buffer
997      */
998     if (magic && *cp == '<') {
999         if (ct->c_id) {
1000             free (ct->c_id);
1001             ct->c_id = NULL;
1002         }
1003         if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
1004             advise (NULL, "invalid ID in message %s", ct->c_file);
1005             return NOTOK;
1006         }
1007         c = *dp;
1008         *dp = '\0';
1009         if (*ct->c_id)
1010             ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
1011         else
1012             ct->c_id = NULL;
1013         *dp++ = c;
1014         cp = dp;
1015
1016         while (isspace (*cp))
1017             cp++;
1018     }
1019
1020     /*
1021      * Get any [Content-Description] given in buffer.
1022      */
1023     if (magic && *cp == '[') {
1024         ct->c_descr = ++cp;
1025         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1026             if (*dp == ']')
1027                 break;
1028         if (dp < cp) {
1029             advise (NULL, "invalid description in message %s", ct->c_file);
1030             ct->c_descr = NULL;
1031             return NOTOK;
1032         }
1033         
1034         c = *dp;
1035         *dp = '\0';
1036         if (*ct->c_descr)
1037             ct->c_descr = concat (ct->c_descr, "\n", NULL);
1038         else
1039             ct->c_descr = NULL;
1040         *dp++ = c;
1041         cp = dp;
1042
1043         while (isspace (*cp))
1044             cp++;
1045     }
1046
1047     /*
1048      * Check if anything is left over
1049      */
1050     if (*cp) {
1051         if (magic)
1052             ci->ci_magic = add (cp, NULL);
1053         else
1054             advise (NULL,
1055                     "extraneous information in message %s's %s: field\n%*.*s(%s)",
1056                 ct->c_file, TYPE_FIELD, i, i, "", cp);
1057     }
1058
1059     return OK;
1060 }
1061
1062
1063 static int
1064 get_comment (CT ct, char **ap, int istype)
1065 {
1066     int i;
1067     char *bp, *cp;
1068     char c, buffer[BUFSIZ], *dp;
1069     CI ci;
1070
1071     ci = &ct->c_ctinfo;
1072     cp = *ap;
1073     bp = buffer;
1074     cp++;
1075
1076     for (i = 0;;) {
1077         switch (c = *cp++) {
1078         case '\0':
1079 invalid:
1080         advise (NULL, "invalid comment in message %s's %s: field",
1081                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
1082         return NOTOK;
1083
1084         case '\\':
1085             *bp++ = c;
1086             if ((c = *cp++) == '\0')
1087                 goto invalid;
1088             *bp++ = c;
1089             continue;
1090
1091         case '(':
1092             i++;
1093             /* and fall... */
1094         default:
1095             *bp++ = c;
1096             continue;
1097
1098         case ')':
1099             if (--i < 0)
1100                 break;
1101             *bp++ = c;
1102             continue;
1103         }
1104         break;
1105     }
1106     *bp = '\0';
1107
1108     if (istype) {
1109         if ((dp = ci->ci_comment)) {
1110             ci->ci_comment = concat (dp, " ", buffer, NULL);
1111             free (dp);
1112         } else {
1113             ci->ci_comment = add (buffer, NULL);
1114         }
1115     }
1116
1117     while (isspace (*cp))
1118         cp++;
1119
1120     *ap = cp;
1121     return OK;
1122 }
1123
1124
1125 /*
1126  * CONTENTS
1127  *
1128  * Handles content types audio, image, and video.
1129  * There's not much to do right here.
1130  */
1131
1132 static int
1133 InitGeneric (CT ct)
1134 {
1135     return OK;          /* not much to do here */
1136 }
1137
1138
1139 /*
1140  * TEXT
1141  */
1142
1143 static int
1144 InitText (CT ct)
1145 {
1146     char **ap, **ep;
1147     struct k2v *kv;
1148     struct text *t;
1149     CI ci = &ct->c_ctinfo;
1150
1151     /* check for missing subtype */
1152     if (!*ci->ci_subtype)
1153         ci->ci_subtype = add ("plain", ci->ci_subtype);
1154
1155     /* match subtype */
1156     for (kv = SubText; kv->kv_key; kv++)
1157         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1158             break;
1159     ct->c_subtype = kv->kv_value;
1160
1161     /* allocate text character set structure */
1162     if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1163         adios (NULL, "out of memory");
1164     ct->c_ctparams = (void *) t;
1165
1166     /* initially mark character set as unspecified */
1167     t->tx_charset = CHARSET_UNSPECIFIED;
1168
1169     /* scan for charset parameter */
1170     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1171         if (!strcasecmp (*ap, "charset"))
1172             break;
1173
1174     /* check if content specified a character set */
1175     if (*ap) {
1176         /* match character set or set to CHARSET_UNKNOWN */
1177         for (kv = Charset; kv->kv_key; kv++)
1178             if (!strcasecmp (*ep, kv->kv_key))
1179                 break;
1180         t->tx_charset = kv->kv_value;
1181     }
1182
1183     return OK;
1184 }
1185
1186
1187 /*
1188  * MULTIPART
1189  */
1190
1191 static int
1192 InitMultiPart (CT ct)
1193 {
1194     int inout;
1195     long last, pos;
1196     char *cp, *dp, **ap, **ep;
1197     char *bp, buffer[BUFSIZ];
1198     struct multipart *m;
1199     struct k2v *kv;
1200     struct part *part, **next;
1201     CI ci = &ct->c_ctinfo;
1202     CT p;
1203     FILE *fp;
1204
1205     /*
1206      * The encoding for multipart messages must be either
1207      * 7bit, 8bit, or binary (per RFC2045).
1208      */
1209     if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1210         && ct->c_encoding != CE_BINARY) {
1211         admonish (NULL,
1212                   "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1213                   ci->ci_type, ci->ci_subtype, ct->c_file);
1214         return NOTOK;
1215     }
1216
1217     /* match subtype */
1218     for (kv = SubMultiPart; kv->kv_key; kv++)
1219         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1220             break;
1221     ct->c_subtype = kv->kv_value;
1222
1223     /*
1224      * Check for "boundary" parameter, which is
1225      * required for multipart messages.
1226      */
1227     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1228         if (!strcasecmp (*ap, "boundary")) {
1229             bp = *ep;
1230             break;
1231         }
1232     }
1233
1234     /* complain if boundary parameter is missing */
1235     if (!*ap) {
1236         advise (NULL,
1237                 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1238                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1239         return NOTOK;
1240     }
1241
1242     /* allocate primary structure for multipart info */
1243     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1244         adios (NULL, "out of memory");
1245     ct->c_ctparams = (void *) m;
1246
1247     /* check if boundary parameter contains only whitespace characters */
1248     for (cp = bp; isspace (*cp); cp++)
1249         continue;
1250     if (!*cp) {
1251         advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1252                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1253         return NOTOK;
1254     }
1255
1256     /* remove trailing whitespace from boundary parameter */
1257     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1258         if (!isspace (*dp))
1259             break;
1260     *++dp = '\0';
1261
1262     /* record boundary separators */
1263     m->mp_start = concat (bp, "\n", NULL);
1264     m->mp_stop = concat (bp, "--\n", NULL);
1265
1266     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1267         advise (ct->c_file, "unable to open for reading");
1268         return NOTOK;
1269     }
1270
1271     fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1272     last = ct->c_end;
1273     next = &m->mp_parts;
1274     part = NULL;
1275     inout = 1;
1276
1277     while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1278         if (pos > last)
1279             break;
1280
1281         pos += strlen (buffer);
1282         if (buffer[0] != '-' || buffer[1] != '-')
1283             continue;
1284         if (inout) {
1285             if (strcmp (buffer + 2, m->mp_start))
1286                 continue;
1287 next_part:
1288             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1289                 adios (NULL, "out of memory");
1290             *next = part;
1291             next = &part->mp_next;
1292
1293             if (!(p = get_content (fp, ct->c_file,
1294                 rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1295                 fclose (ct->c_fp);
1296                 ct->c_fp = NULL;
1297                 return NOTOK;
1298             }
1299             p->c_fp = NULL;
1300             part->mp_part = p;
1301             pos = p->c_begin;
1302             fseek (fp, pos, SEEK_SET);
1303             inout = 0;
1304         } else {
1305             if (strcmp (buffer + 2, m->mp_start) == 0) {
1306                 inout = 1;
1307 end_part:
1308                 p = part->mp_part;
1309                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1310                 if (p->c_end < p->c_begin)
1311                     p->c_begin = p->c_end;
1312                 if (inout)
1313                     goto next_part;
1314                 goto last_part;
1315             } else {
1316                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1317                     goto end_part;
1318             }
1319         }
1320     }
1321
1322     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1323     if (!inout && part) {
1324         p = part->mp_part;
1325         p->c_end = ct->c_end;
1326
1327         if (p->c_begin >= p->c_end) {
1328             for (next = &m->mp_parts; *next != part;
1329                      next = &((*next)->mp_next))
1330                 continue;
1331             *next = NULL;
1332             free_content (p);
1333             free ((char *) part);
1334         }
1335     }
1336
1337 last_part:
1338     /* reverse the order of the parts for multipart/alternative */
1339     if (ct->c_subtype == MULTI_ALTERNATE)
1340         reverse_parts (ct);
1341
1342     /*
1343      * label all subparts with part number, and
1344      * then initialize the content of the subpart.
1345      */
1346     {
1347         int partnum;
1348         char *pp;
1349         char partnam[BUFSIZ];
1350
1351         if (ct->c_partno) {
1352             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1353             pp = partnam + strlen (partnam);
1354         } else {
1355             pp = partnam;
1356         }
1357
1358         for (part = m->mp_parts, partnum = 1; part;
1359                  part = part->mp_next, partnum++) {
1360             p = part->mp_part;
1361
1362             sprintf (pp, "%d", partnum);
1363             p->c_partno = add (partnam, NULL);
1364
1365             /* initialize the content of the subparts */
1366             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1367                 fclose (ct->c_fp);
1368                 ct->c_fp = NULL;
1369                 return NOTOK;
1370             }
1371         }
1372     }
1373
1374     fclose (ct->c_fp);
1375     ct->c_fp = NULL;
1376     return OK;
1377 }
1378
1379
1380 /*
1381  * reverse the order of the parts of a multipart
1382  */
1383
1384 static void
1385 reverse_parts (CT ct)
1386 {
1387     int i;
1388     struct multipart *m;
1389     struct part **base, **bmp, **next, *part;
1390
1391     m = (struct multipart *) ct->c_ctparams;
1392
1393     /* if only one part, just return */
1394     if (!m->mp_parts || !m->mp_parts->mp_next)
1395         return;
1396
1397     /* count number of parts */
1398     i = 0;
1399     for (part = m->mp_parts; part; part = part->mp_next)
1400         i++;
1401
1402     /* allocate array of pointers to the parts */
1403     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1404         adios (NULL, "out of memory");
1405     bmp = base;
1406
1407     /* point at all the parts */
1408     for (part = m->mp_parts; part; part = part->mp_next)
1409         *bmp++ = part;
1410     *bmp = NULL;
1411
1412     /* reverse the order of the parts */
1413     next = &m->mp_parts;
1414     for (bmp--; bmp >= base; bmp--) {
1415         part = *bmp;
1416         *next = part;
1417         next = &part->mp_next;
1418     }
1419     *next = NULL;
1420
1421     /* free array of pointers */
1422     free ((char *) base);
1423 }
1424
1425
1426 /*
1427  * MESSAGE
1428  */
1429
1430 static int
1431 InitMessage (CT ct)
1432 {
1433     struct k2v *kv;
1434     CI ci = &ct->c_ctinfo;
1435
1436     if (ct->c_encoding != CE_7BIT) {
1437         admonish (NULL,
1438                   "\"%s/%s\" type in message %s should be encoded in 7bit",
1439                   ci->ci_type, ci->ci_subtype, ct->c_file);
1440         return NOTOK;
1441     }
1442
1443     /* check for missing subtype */
1444     if (!*ci->ci_subtype)
1445         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1446
1447     /* match subtype */
1448     for (kv = SubMessage; kv->kv_key; kv++)
1449         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1450             break;
1451     ct->c_subtype = kv->kv_value;
1452
1453     switch (ct->c_subtype) {
1454         case MESSAGE_RFC822:
1455             break;
1456
1457         case MESSAGE_PARTIAL:
1458             {
1459                 char **ap, **ep;
1460                 struct partial *p;
1461
1462                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1463                     adios (NULL, "out of memory");
1464                 ct->c_ctparams = (void *) p;
1465
1466                 /* scan for parameters "id", "number", and "total" */
1467                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1468                     if (!strcasecmp (*ap, "id")) {
1469                         p->pm_partid = add (*ep, NULL);
1470                         continue;
1471                     }
1472                     if (!strcasecmp (*ap, "number")) {
1473                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1474                                 || p->pm_partno < 1) {
1475 invalid_param:
1476                             advise (NULL,
1477                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1478                                     *ap, ci->ci_type, ci->ci_subtype,
1479                                     ct->c_file, TYPE_FIELD);
1480                             return NOTOK;
1481                         }
1482                         continue;
1483                     }
1484                     if (!strcasecmp (*ap, "total")) {
1485                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1486                                 || p->pm_maxno < 1)
1487                             goto invalid_param;
1488                         continue;
1489                     }
1490                 }
1491
1492                 if (!p->pm_partid
1493                         || !p->pm_partno
1494                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1495                     advise (NULL,
1496                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1497                             ci->ci_type, ci->ci_subtype,
1498                             ct->c_file, TYPE_FIELD);
1499                     return NOTOK;
1500                 }
1501             }
1502             break;
1503
1504         case MESSAGE_EXTERNAL:
1505             {
1506                 int exresult;
1507                 struct exbody *e;
1508                 CT p;
1509                 FILE *fp;
1510
1511                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1512                     adios (NULL, "out of memory");
1513                 ct->c_ctparams = (void *) e;
1514
1515                 if (!ct->c_fp
1516                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1517                     advise (ct->c_file, "unable to open for reading");
1518                     return NOTOK;
1519                 }
1520
1521                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1522
1523                 if (!(p = get_content (fp, ct->c_file, 0))) {
1524                     fclose (ct->c_fp);
1525                     ct->c_fp = NULL;
1526                     return NOTOK;
1527                 }
1528
1529                 e->eb_parent = ct;
1530                 e->eb_content = p;
1531                 p->c_ctexbody = e;
1532                 if ((exresult = params_external (ct, 0)) != NOTOK
1533                         && p->c_ceopenfnx == openMail) {
1534                     int cc, size;
1535                     char *bp;
1536                     
1537                     if ((size = ct->c_end - p->c_begin) <= 0) {
1538                         if (!e->eb_subject)
1539                             content_error (NULL, ct,
1540                                            "empty body for access-type=mail-server");
1541                         goto no_body;
1542                     }
1543                     
1544                     if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL)
1545                         adios (NULL, "out of memory");
1546                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1547                     while (size > 0)
1548                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1549                             case NOTOK:
1550                                 adios ("failed", "fread");
1551
1552                             case OK:
1553                                 adios (NULL, "unexpected EOF from fread");
1554
1555                             default:
1556                                 bp += cc, size -= cc;
1557                                 break;
1558                         }
1559                     *bp = 0;
1560                 }
1561 no_body:
1562                 p->c_fp = NULL;
1563                 p->c_end = p->c_begin;
1564
1565                 fclose (ct->c_fp);
1566                 ct->c_fp = NULL;
1567
1568                 if (exresult == NOTOK)
1569                     return NOTOK;
1570                 if (e->eb_flags == NOTOK)
1571                     return OK;
1572
1573                 switch (p->c_type) {
1574                     case CT_MULTIPART:
1575                         break;
1576
1577                     case CT_MESSAGE:
1578                         if (p->c_subtype != MESSAGE_RFC822)
1579                             break;
1580                         /* else fall... */
1581                     default:
1582                         e->eb_partno = ct->c_partno;
1583                         if (p->c_ctinitfnx)
1584                             (*p->c_ctinitfnx) (p);
1585                         break;
1586                 }
1587             }
1588             break;
1589
1590         default:
1591             break;
1592     }
1593
1594     return OK;
1595 }
1596
1597
1598 static int
1599 params_external (CT ct, int composing)
1600 {
1601     char **ap, **ep;
1602     struct exbody *e = (struct exbody *) ct->c_ctparams;
1603     CI ci = &ct->c_ctinfo;
1604
1605     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1606         if (!strcasecmp (*ap, "access-type")) {
1607             struct str2init *s2i;
1608             CT p = e->eb_content;
1609
1610             for (s2i = str2methods; s2i->si_key; s2i++)
1611                 if (!strcasecmp (*ep, s2i->si_key))
1612                     break;
1613
1614             if (!s2i->si_key) {
1615                 e->eb_access = *ep;
1616                 e->eb_flags = NOTOK;
1617                 p->c_encoding = CE_EXTERNAL;
1618                 continue;
1619             }
1620             e->eb_access = s2i->si_key;
1621             e->eb_flags = s2i->si_val;
1622             p->c_encoding = CE_EXTERNAL;
1623
1624             /* Call the Init function for this external type */
1625             if ((*s2i->si_init)(p) == NOTOK)
1626                 return NOTOK;
1627             continue;
1628         }
1629         if (!strcasecmp (*ap, "name")) {
1630             e->eb_name = *ep;
1631             continue;
1632         }
1633         if (!strcasecmp (*ap, "permission")) {
1634             e->eb_permission = *ep;
1635             continue;
1636         }
1637         if (!strcasecmp (*ap, "site")) {
1638             e->eb_site = *ep;
1639             continue;
1640         }
1641         if (!strcasecmp (*ap, "directory")) {
1642             e->eb_dir = *ep;
1643             continue;
1644         }
1645         if (!strcasecmp (*ap, "mode")) {
1646             e->eb_mode = *ep;
1647             continue;
1648         }
1649         if (!strcasecmp (*ap, "size")) {
1650             sscanf (*ep, "%lu", &e->eb_size);
1651             continue;
1652         }
1653         if (!strcasecmp (*ap, "server")) {
1654             e->eb_server = *ep;
1655             continue;
1656         }
1657         if (!strcasecmp (*ap, "subject")) {
1658             e->eb_subject = *ep;
1659             continue;
1660         }
1661         if (composing && !strcasecmp (*ap, "body")) {
1662             e->eb_body = getcpy (*ep);
1663             continue;
1664         }
1665     }
1666
1667     if (!e->eb_access) {
1668         advise (NULL,
1669                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1670                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1671         return NOTOK;
1672     }
1673
1674     return OK;
1675 }
1676
1677
1678 /*
1679  * APPLICATION
1680  */
1681
1682 static int
1683 InitApplication (CT ct)
1684 {
1685     struct k2v *kv;
1686     CI ci = &ct->c_ctinfo;
1687
1688     /* match subtype */
1689     for (kv = SubApplication; kv->kv_key; kv++)
1690         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1691             break;
1692     ct->c_subtype = kv->kv_value;
1693
1694     return OK;
1695 }
1696
1697
1698 /*
1699  * Set up structures for placing unencoded
1700  * content when building parts.
1701  */
1702
1703 static int
1704 init_decoded_content (CT ct)
1705 {
1706     CE ce;
1707
1708     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1709         adios (NULL, "out of memory");
1710
1711     ct->c_cefile     = ce;
1712     ct->c_ceopenfnx  = open7Bit;        /* since unencoded */
1713     ct->c_ceclosefnx = close_encoding;
1714     ct->c_cesizefnx  = NULL;            /* since unencoded */
1715
1716     return OK;
1717 }
1718
1719
1720 /*
1721  * TRANSFER ENCODINGS
1722  */
1723
1724 static int
1725 init_encoding (CT ct, OpenCEFunc openfnx)
1726 {
1727     CE ce;
1728
1729     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1730         adios (NULL, "out of memory");
1731
1732     ct->c_cefile     = ce;
1733     ct->c_ceopenfnx  = openfnx;
1734     ct->c_ceclosefnx = close_encoding;
1735     ct->c_cesizefnx  = size_encoding;
1736
1737     return OK;
1738 }
1739
1740
1741 static void
1742 close_encoding (CT ct)
1743 {
1744     CE ce;
1745
1746     if (!(ce = ct->c_cefile))
1747         return;
1748
1749     if (ce->ce_fp) {
1750         fclose (ce->ce_fp);
1751         ce->ce_fp = NULL;
1752     }
1753 }
1754
1755
1756 static unsigned long
1757 size_encoding (CT ct)
1758 {
1759     int fd;
1760     unsigned long size;
1761     char *file;
1762     CE ce;
1763     struct stat st;
1764
1765     if (!(ce = ct->c_cefile))
1766         return (ct->c_end - ct->c_begin);
1767
1768     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1769         return (long) st.st_size;
1770
1771     if (ce->ce_file) {
1772         if (stat (ce->ce_file, &st) != NOTOK)
1773             return (long) st.st_size;
1774         else
1775             return 0L;
1776     }
1777
1778     if (ct->c_encoding == CE_EXTERNAL)
1779         return (ct->c_end - ct->c_begin);       
1780
1781     file = NULL;
1782     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1783         return (ct->c_end - ct->c_begin);
1784
1785     if (fstat (fd, &st) != NOTOK)
1786         size = (long) st.st_size;
1787     else
1788         size = 0L;
1789
1790     (*ct->c_ceclosefnx) (ct);
1791     return size;
1792 }
1793
1794
1795 /*
1796  * BASE64
1797  */
1798
1799 static unsigned char b642nib[0x80] = {
1800     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1801     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1802     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1803     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1804     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1805     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1806     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1807     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1808     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1809     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1810     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1811     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1812     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1813     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1814     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1815     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1816 };
1817
1818
1819 static int
1820 InitBase64 (CT ct)
1821 {
1822     return init_encoding (ct, openBase64);
1823 }
1824
1825
1826 static int
1827 openBase64 (CT ct, char **file)
1828 {
1829     int bitno, cc, digested;
1830     int fd, len, skip;
1831     unsigned long bits;
1832     unsigned char value, *b, *b1, *b2, *b3;
1833     char *cp, *ep, buffer[BUFSIZ];
1834     CE ce;
1835     MD5_CTX mdContext;
1836
1837     b  = (unsigned char *) &bits;
1838     b1 = &b[endian > 0 ? 1 : 2];
1839     b2 = &b[endian > 0 ? 2 : 1];
1840     b3 = &b[endian > 0 ? 3 : 0];
1841
1842     ce = ct->c_cefile;
1843     if (ce->ce_fp) {
1844         fseek (ce->ce_fp, 0L, SEEK_SET);
1845         goto ready_to_go;
1846     }
1847
1848     if (ce->ce_file) {
1849         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1850             content_error (ce->ce_file, ct, "unable to fopen for reading");
1851             return NOTOK;
1852         }
1853         goto ready_to_go;
1854     }
1855
1856     if (*file == NULL) {
1857         ce->ce_file = add (m_scratch ("", tmp), NULL);
1858         ce->ce_unlink = 1;
1859     } else {
1860         ce->ce_file = add (*file, NULL);
1861         ce->ce_unlink = 0;
1862     }
1863
1864     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1865         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1866         return NOTOK;
1867     }
1868
1869     if ((len = ct->c_end - ct->c_begin) < 0)
1870         adios (NULL, "internal error(1)");
1871
1872     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1873         content_error (ct->c_file, ct, "unable to open for reading");
1874         return NOTOK;
1875     }
1876     
1877     if ((digested = ct->c_digested))
1878         MD5Init (&mdContext);
1879
1880     bitno = 18;
1881     bits = 0L;
1882     skip = 0;
1883
1884     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1885     while (len > 0) {
1886         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1887         case NOTOK:
1888             content_error (ct->c_file, ct, "error reading from");
1889             goto clean_up;
1890
1891         case OK:
1892             content_error (NULL, ct, "premature eof");
1893             goto clean_up;
1894
1895         default:
1896             if (cc > len)
1897                 cc = len;
1898             len -= cc;
1899
1900             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1901                 switch (*cp) {
1902                 default:
1903                     if (isspace (*cp))
1904                         break;
1905                     if (skip || (*cp & 0x80)
1906                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1907                         if (debugsw) {
1908                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1909                                 *cp,
1910                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1911                                 skip);
1912                         }
1913                         content_error (NULL, ct,
1914                                        "invalid BASE64 encoding -- continuing");
1915                         continue;
1916                     }
1917
1918                     bits |= value << bitno;
1919 test_end:
1920                     if ((bitno -= 6) < 0) {
1921                         putc ((char) *b1, ce->ce_fp);
1922                         if (digested)
1923                             MD5Update (&mdContext, b1, 1);
1924                         if (skip < 2) {
1925                             putc ((char) *b2, ce->ce_fp);
1926                             if (digested)
1927                                 MD5Update (&mdContext, b2, 1);
1928                             if (skip < 1) {
1929                                 putc ((char) *b3, ce->ce_fp);
1930                                 if (digested)
1931                                     MD5Update (&mdContext, b3, 1);
1932                             }
1933                         }
1934
1935                         if (ferror (ce->ce_fp)) {
1936                             content_error (ce->ce_file, ct,
1937                                            "error writing to");
1938                             goto clean_up;
1939                         }
1940                         bitno = 18, bits = 0L, skip = 0;
1941                     }
1942                     break;
1943
1944                 case '=':
1945                     if (++skip > 3)
1946                         goto self_delimiting;
1947                     goto test_end;
1948                 }
1949             }
1950         }
1951     }
1952
1953     if (bitno != 18) {
1954         if (debugsw)
1955             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1956
1957         content_error (NULL, ct, "invalid BASE64 encoding");
1958         goto clean_up;
1959     }
1960
1961 self_delimiting:
1962     fseek (ct->c_fp, 0L, SEEK_SET);
1963
1964     if (fflush (ce->ce_fp)) {
1965         content_error (ce->ce_file, ct, "error writing to");
1966         goto clean_up;
1967     }
1968
1969     if (digested) {
1970         unsigned char digest[16];
1971
1972         MD5Final (digest, &mdContext);
1973         if (memcmp((char *) digest, (char *) ct->c_digest,
1974                    sizeof(digest) / sizeof(digest[0])))
1975             content_error (NULL, ct,
1976                            "content integrity suspect (digest mismatch) -- continuing");
1977         else
1978             if (debugsw)
1979                 fprintf (stderr, "content integrity confirmed\n");
1980     }
1981
1982     fseek (ce->ce_fp, 0L, SEEK_SET);
1983
1984 ready_to_go:
1985     *file = ce->ce_file;
1986     return fileno (ce->ce_fp);
1987
1988 clean_up:
1989     free_encoding (ct, 0);
1990     return NOTOK;
1991 }
1992
1993
1994 /*
1995  * QUOTED PRINTABLE
1996  */
1997
1998 static char hex2nib[0x80] = {
1999     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2000     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2001     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2002     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2003     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2004     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2005     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2006     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2007     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
2008     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2009     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2010     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2011     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
2012     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2013     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2014     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
2015 };
2016
2017
2018 static int 
2019 InitQuoted (CT ct)
2020 {
2021     return init_encoding (ct, openQuoted);
2022 }
2023
2024
2025 static int
2026 openQuoted (CT ct, char **file)
2027 {
2028     int cc, digested, len, quoted;
2029     char *cp, *ep;
2030     char buffer[BUFSIZ];
2031     unsigned char mask;
2032     CE ce;
2033     MD5_CTX mdContext;
2034
2035     ce = ct->c_cefile;
2036     if (ce->ce_fp) {
2037         fseek (ce->ce_fp, 0L, SEEK_SET);
2038         goto ready_to_go;
2039     }
2040
2041     if (ce->ce_file) {
2042         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2043             content_error (ce->ce_file, ct, "unable to fopen for reading");
2044             return NOTOK;
2045         }
2046         goto ready_to_go;
2047     }
2048
2049     if (*file == NULL) {
2050         ce->ce_file = add (m_scratch ("", tmp), NULL);
2051         ce->ce_unlink = 1;
2052     } else {
2053         ce->ce_file = add (*file, NULL);
2054         ce->ce_unlink = 0;
2055     }
2056
2057     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2058         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2059         return NOTOK;
2060     }
2061
2062     if ((len = ct->c_end - ct->c_begin) < 0)
2063         adios (NULL, "internal error(2)");
2064
2065     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2066         content_error (ct->c_file, ct, "unable to open for reading");
2067         return NOTOK;
2068     }
2069
2070     if ((digested = ct->c_digested))
2071         MD5Init (&mdContext);
2072
2073     quoted = 0;
2074 #ifdef lint
2075     mask = 0;
2076 #endif
2077
2078     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2079     while (len > 0) {
2080         char *dp;
2081
2082         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2083             content_error (NULL, ct, "premature eof");
2084             goto clean_up;
2085         }
2086
2087         if ((cc = strlen (buffer)) > len)
2088             cc = len;
2089         len -= cc;
2090
2091         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2092             if (!isspace (*ep))
2093                 break;
2094         *++ep = '\n', ep++;
2095
2096         for (; cp < ep; cp++) {
2097             if (quoted) {
2098                 if (quoted > 1) {
2099                     if (!isxdigit (*cp)) {
2100 invalid_hex:
2101                         dp = "expecting hexidecimal-digit";
2102                         goto invalid_encoding;
2103                     }
2104                     mask <<= 4;
2105                     mask |= hex2nib[*cp & 0x7f];
2106                     putc (mask, ce->ce_fp);
2107                     if (digested)
2108                         MD5Update (&mdContext, &mask, 1);
2109                 } else {
2110                     switch (*cp) {
2111                     case ':':
2112                         putc (*cp, ce->ce_fp);
2113                         if (digested)
2114                             MD5Update (&mdContext, (unsigned char *) ":", 1);
2115                         break;
2116
2117                     default:
2118                         if (!isxdigit (*cp))
2119                             goto invalid_hex;
2120                         mask = hex2nib[*cp & 0x7f];
2121                         quoted = 2;
2122                         continue;
2123                     }
2124                 }
2125
2126                 if (ferror (ce->ce_fp)) {
2127                     content_error (ce->ce_file, ct, "error writing to");
2128                     goto clean_up;
2129                 }
2130                 quoted = 0;
2131                 continue;
2132             }
2133
2134             switch (*cp) {
2135             default:
2136                 if (*cp < '!' || *cp > '~') {
2137                     int i;
2138                     dp = "expecting character in range [!..~]";
2139
2140 invalid_encoding:
2141                     i = strlen (invo_name) + 2;
2142                     content_error (NULL, ct,
2143                                    "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
2144                                    dp, i, i, "", *cp);
2145                     goto clean_up;
2146                 }
2147                 /* and fall...*/
2148             case ' ':
2149             case '\t':
2150             case '\n':
2151                 putc (*cp, ce->ce_fp);
2152                 if (digested) {
2153                     if (*cp == '\n')
2154                         MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2155                     else
2156                         MD5Update (&mdContext, (unsigned char *) cp, 1);
2157                 }
2158                 if (ferror (ce->ce_fp)) {
2159                     content_error (ce->ce_file, ct, "error writing to");
2160                     goto clean_up;
2161                 }
2162                 break;
2163
2164             case '=':
2165                 if (*++cp != '\n') {
2166                     quoted = 1;
2167                     cp--;
2168                 }
2169                 break;
2170             }
2171         }
2172     }
2173     if (quoted) {
2174         content_error (NULL, ct,
2175                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2176         goto clean_up;
2177     }
2178
2179     fseek (ct->c_fp, 0L, SEEK_SET);
2180
2181     if (fflush (ce->ce_fp)) {
2182         content_error (ce->ce_file, ct, "error writing to");
2183         goto clean_up;
2184     }
2185
2186     if (digested) {
2187         unsigned char digest[16];
2188
2189         MD5Final (digest, &mdContext);
2190         if (memcmp((char *) digest, (char *) ct->c_digest,
2191                    sizeof(digest) / sizeof(digest[0])))
2192             content_error (NULL, ct,
2193                            "content integrity suspect (digest mismatch) -- continuing");
2194         else
2195             if (debugsw)
2196                 fprintf (stderr, "content integrity confirmed\n");
2197     }
2198
2199     fseek (ce->ce_fp, 0L, SEEK_SET);
2200
2201 ready_to_go:
2202     *file = ce->ce_file;
2203     return fileno (ce->ce_fp);
2204
2205 clean_up:
2206     free_encoding (ct, 0);
2207     return NOTOK;
2208 }
2209
2210
2211 /*
2212  * 7BIT
2213  */
2214
2215 static int
2216 Init7Bit (CT ct)
2217 {
2218     if (init_encoding (ct, open7Bit) == NOTOK)
2219         return NOTOK;
2220
2221     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2222     return OK;
2223 }
2224
2225
2226 static int
2227 open7Bit (CT ct, char **file)
2228 {
2229     int cc, fd, len;
2230     char buffer[BUFSIZ];
2231     CE ce;
2232
2233     ce = ct->c_cefile;
2234     if (ce->ce_fp) {
2235         fseek (ce->ce_fp, 0L, SEEK_SET);
2236         goto ready_to_go;
2237     }
2238
2239     if (ce->ce_file) {
2240         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2241             content_error (ce->ce_file, ct, "unable to fopen for reading");
2242             return NOTOK;
2243         }
2244         goto ready_to_go;
2245     }
2246
2247     if (*file == NULL) {
2248         ce->ce_file = add (m_scratch ("", tmp), NULL);
2249         ce->ce_unlink = 1;
2250     } else {
2251         ce->ce_file = add (*file, NULL);
2252         ce->ce_unlink = 0;
2253     }
2254
2255     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2256         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2257         return NOTOK;
2258     }
2259
2260     if (ct->c_type == CT_MULTIPART) {
2261         char **ap, **ep;
2262         CI ci = &ct->c_ctinfo;
2263
2264         len = 0;
2265         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2266         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2267             + 1 + strlen (ci->ci_subtype);
2268         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2269             putc (';', ce->ce_fp);
2270             len++;
2271
2272             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2273
2274             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2275                 fputs ("\n\t", ce->ce_fp);
2276                 len = 8;
2277             } else {
2278                 putc (' ', ce->ce_fp);
2279                 len++;
2280             }
2281             fprintf (ce->ce_fp, "%s", buffer);
2282             len += cc;
2283         }
2284
2285         if (ci->ci_comment) {
2286             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2287                 fputs ("\n\t", ce->ce_fp);
2288                 len = 8;
2289             }
2290             else {
2291                 putc (' ', ce->ce_fp);
2292                 len++;
2293             }
2294             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2295             len += cc;
2296         }
2297         fprintf (ce->ce_fp, "\n");
2298         if (ct->c_id)
2299             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2300         if (ct->c_descr)
2301             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2302         fprintf (ce->ce_fp, "\n");
2303     }
2304
2305     if ((len = ct->c_end - ct->c_begin) < 0)
2306         adios (NULL, "internal error(3)");
2307
2308     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2309         content_error (ct->c_file, ct, "unable to open for reading");
2310         return NOTOK;
2311     }
2312
2313     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2314     while (len > 0)
2315         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2316         case NOTOK:
2317             content_error (ct->c_file, ct, "error reading from");
2318             goto clean_up;
2319
2320         case OK:
2321             content_error (NULL, ct, "premature eof");
2322             goto clean_up;
2323
2324         default:
2325             if (cc > len)
2326                 cc = len;
2327             len -= cc;
2328
2329             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2330             if (ferror (ce->ce_fp)) {
2331                 content_error (ce->ce_file, ct, "error writing to");
2332                 goto clean_up;
2333             }
2334         }
2335
2336     fseek (ct->c_fp, 0L, SEEK_SET);
2337
2338     if (fflush (ce->ce_fp)) {
2339         content_error (ce->ce_file, ct, "error writing to");
2340         goto clean_up;
2341     }
2342
2343     fseek (ce->ce_fp, 0L, SEEK_SET);
2344
2345 ready_to_go:
2346     *file = ce->ce_file;
2347     return fileno (ce->ce_fp);
2348
2349 clean_up:
2350     free_encoding (ct, 0);
2351     return NOTOK;
2352 }
2353
2354
2355 /*
2356  * External
2357  */
2358
2359 static int
2360 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2361 {
2362     char cachefile[BUFSIZ];
2363
2364     if (ce->ce_fp) {
2365         fseek (ce->ce_fp, 0L, SEEK_SET);
2366         goto ready_already;
2367     }
2368
2369     if (ce->ce_file) {
2370         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2371             content_error (ce->ce_file, ct, "unable to fopen for reading");
2372             return NOTOK;
2373         }
2374         goto ready_already;
2375     }
2376
2377     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2378                 cachefile, sizeof(cachefile)) != NOTOK) {
2379         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2380             ce->ce_file = getcpy (cachefile);
2381             ce->ce_unlink = 0;
2382             goto ready_already;
2383         } else {
2384             admonish (cachefile, "unable to fopen for reading");
2385         }
2386     }
2387
2388     return OK;
2389
2390 ready_already:
2391     *file = ce->ce_file;
2392     *fd = fileno (ce->ce_fp);
2393     return DONE;
2394 }
2395
2396 /*
2397  * File
2398  */
2399
2400 static int
2401 InitFile (CT ct)
2402 {
2403     return init_encoding (ct, openFile);
2404 }
2405
2406
2407 static int
2408 openFile (CT ct, char **file)
2409 {
2410     int fd, cachetype;
2411     char cachefile[BUFSIZ];
2412     struct exbody *e = ct->c_ctexbody;
2413     CE ce = ct->c_cefile;
2414
2415     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2416         case NOTOK:
2417             return NOTOK;
2418
2419         case OK:
2420             break;
2421
2422         case DONE:
2423             return fd;
2424     }
2425
2426     if (!e->eb_name) {
2427         content_error (NULL, ct, "missing name parameter");
2428         return NOTOK;
2429     }
2430
2431     ce->ce_file = getcpy (e->eb_name);
2432     ce->ce_unlink = 0;
2433
2434     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2435         content_error (ce->ce_file, ct, "unable to fopen for reading");
2436         return NOTOK;
2437     }
2438
2439     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2440             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2441                 cachefile, sizeof(cachefile)) != NOTOK) {
2442         int mask;
2443         FILE *fp;
2444
2445         mask = umask (cachetype ? ~m_gmprot () : 0222);
2446         if ((fp = fopen (cachefile, "w"))) {
2447             int cc;
2448             char buffer[BUFSIZ];
2449             FILE *gp = ce->ce_fp;
2450
2451             fseek (gp, 0L, SEEK_SET);
2452
2453             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2454                        > 0)
2455                 fwrite (buffer, sizeof(*buffer), cc, fp);
2456             fflush (fp);
2457
2458             if (ferror (gp)) {
2459                 admonish (ce->ce_file, "error reading");
2460                 unlink (cachefile);
2461             }
2462             else
2463                 if (ferror (fp)) {
2464                     admonish (cachefile, "error writing");
2465                     unlink (cachefile);
2466                 }
2467             fclose (fp);
2468         }
2469         umask (mask);
2470     }
2471
2472     fseek (ce->ce_fp, 0L, SEEK_SET);
2473     *file = ce->ce_file;
2474     return fileno (ce->ce_fp);
2475 }
2476
2477 /*
2478  * FTP
2479  */
2480
2481 static int
2482 InitFTP (CT ct)
2483 {
2484     return init_encoding (ct, openFTP);
2485 }
2486
2487
2488 static int
2489 openFTP (CT ct, char **file)
2490 {
2491     int cachetype, caching, fd;
2492     int len, buflen;
2493     char *bp, *ftp, *user, *pass;
2494     char buffer[BUFSIZ], cachefile[BUFSIZ];
2495     struct exbody *e;
2496     CE ce;
2497     static char *username = NULL;
2498     static char *password = NULL;
2499
2500     e  = ct->c_ctexbody;
2501     ce = ct->c_cefile;
2502
2503     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2504         ftp = NULL;
2505
2506 #ifndef BUILTIN_FTP
2507     if (!ftp)
2508         return NOTOK;
2509 #endif
2510
2511     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2512         case NOTOK:
2513             return NOTOK;
2514
2515         case OK:
2516             break;
2517
2518         case DONE:
2519             return fd;
2520     }
2521
2522     if (!e->eb_name || !e->eb_site) {
2523         content_error (NULL, ct, "missing %s parameter",
2524                        e->eb_name ? "site": "name");
2525         return NOTOK;
2526     }
2527
2528     if (xpid) {
2529         if (xpid < 0)
2530             xpid = -xpid;
2531         pidcheck (pidwait (xpid, NOTOK));
2532         xpid = 0;
2533     }
2534
2535     /* Get the buffer ready to go */
2536     bp = buffer;
2537     buflen = sizeof(buffer);
2538
2539     /*
2540      * Construct the query message for user
2541      */
2542     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2543     len = strlen (bp);
2544     bp += len;
2545     buflen -= len;
2546
2547     if (e->eb_partno) {
2548         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2549         len = strlen (bp);
2550         bp += len;
2551         buflen -= len;
2552     }
2553
2554     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2555                     e->eb_flags ? "anonymous " : "", e->eb_site);
2556     len = strlen (bp);
2557     bp += len;
2558     buflen -= len;
2559
2560     if (e->eb_size > 0) {
2561         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2562         len = strlen (bp);
2563         bp += len;
2564         buflen -= len;
2565     }
2566     snprintf (bp, buflen, "? ");
2567
2568     /*
2569      * Now, check the answer
2570      */
2571     if (!getanswer (buffer))
2572         return NOTOK;
2573
2574     if (e->eb_flags) {
2575         user = "anonymous";
2576         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2577         pass = buffer;
2578     } else {
2579         ruserpass (e->eb_site, &username, &password);
2580         user = username;
2581         pass = password;
2582     }
2583
2584     ce->ce_unlink = (*file == NULL);
2585     caching = 0;
2586     cachefile[0] = '\0';
2587     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2588             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2589                 cachefile, sizeof(cachefile)) != NOTOK) {
2590         if (*file == NULL) {
2591             ce->ce_unlink = 0;
2592             caching = 1;
2593         }
2594     }
2595
2596     if (*file)
2597         ce->ce_file = add (*file, NULL);
2598     else if (caching)
2599         ce->ce_file = add (cachefile, NULL);
2600     else
2601         ce->ce_file = add (m_scratch ("", tmp), NULL);
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 #ifdef BUILTIN_FTP
2609     if (ftp)
2610 #endif
2611     {
2612         int child_id, i, vecp;
2613         char *vec[9];
2614
2615         vecp = 0;
2616         vec[vecp++] = r1bindex (ftp, '/');
2617         vec[vecp++] = e->eb_site;
2618         vec[vecp++] = user;
2619         vec[vecp++] = pass;
2620         vec[vecp++] = e->eb_dir;
2621         vec[vecp++] = e->eb_name;
2622         vec[vecp++] = ce->ce_file,
2623         vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2624                         ? "ascii" : "binary";
2625         vec[vecp] = NULL;
2626
2627         fflush (stdout);
2628
2629         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2630             sleep (5);
2631         switch (child_id) {
2632             case NOTOK:
2633                 adios ("fork", "unable to");
2634                 /* NOTREACHED */
2635
2636             case OK:
2637                 close (fileno (ce->ce_fp));
2638                 execvp (ftp, vec);
2639                 fprintf (stderr, "unable to exec ");
2640                 perror (ftp);
2641                 _exit (-1);
2642                 /* NOTREACHED */
2643
2644             default:
2645                 if (pidXwait (child_id, NULL)) {
2646 #ifdef BUILTIN_FTP
2647 losing_ftp:
2648 #endif
2649                     username = password = NULL;
2650                     ce->ce_unlink = 1;
2651                     return NOTOK;
2652                 }
2653                 break;
2654         }
2655     }
2656 #ifdef BUILTIN_FTP
2657     else
2658         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2659                      ce->ce_file,
2660                      e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0)
2661                 == NOTOK)
2662             goto losing_ftp;
2663 #endif
2664
2665     if (cachefile[0])
2666         if (caching)
2667             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2668         else {
2669             int mask;
2670             FILE *fp;
2671
2672             mask = umask (cachetype ? ~m_gmprot () : 0222);
2673             if ((fp = fopen (cachefile, "w"))) {
2674                 int cc;
2675                 FILE *gp = ce->ce_fp;
2676
2677                 fseek (gp, 0L, SEEK_SET);
2678
2679                 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2680                            > 0)
2681                     fwrite (buffer, sizeof(*buffer), cc, fp);
2682                 fflush (fp);
2683
2684                 if (ferror (gp)) {
2685                     admonish (ce->ce_file, "error reading");
2686                     unlink (cachefile);
2687                 }
2688                 else
2689                     if (ferror (fp)) {
2690                         admonish (cachefile, "error writing");
2691                         unlink (cachefile);
2692                     }
2693                 fclose (fp);
2694             }
2695             umask (mask);
2696         }
2697
2698     fseek (ce->ce_fp, 0L, SEEK_SET);
2699     *file = ce->ce_file;
2700     return fileno (ce->ce_fp);
2701 }
2702
2703
2704 /*
2705  * Mail
2706  */
2707
2708 static int
2709 InitMail (CT ct)
2710 {
2711     return init_encoding (ct, openMail);
2712 }
2713
2714
2715 static int
2716 openMail (CT ct, char **file)
2717 {
2718     int child_id, fd, i, vecp;
2719     int len, buflen;
2720     char *bp, buffer[BUFSIZ], *vec[7];
2721     struct exbody *e = ct->c_ctexbody;
2722     CE ce = ct->c_cefile;
2723
2724     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2725         case NOTOK:
2726             return NOTOK;
2727
2728         case OK:
2729             break;
2730
2731         case DONE:
2732             return fd;
2733     }
2734
2735     if (!e->eb_server) {
2736         content_error (NULL, ct, "missing server parameter");
2737         return NOTOK;
2738     }
2739
2740     if (xpid) {
2741         if (xpid < 0)
2742             xpid = -xpid;
2743         pidcheck (pidwait (xpid, NOTOK));
2744         xpid = 0;
2745     }
2746
2747     /* Get buffer ready to go */
2748     bp = buffer;
2749     buflen = sizeof(buffer);
2750
2751     /* Now construct query message */
2752     snprintf (bp, buflen, "Retrieve content");
2753     len = strlen (bp);
2754     bp += len;
2755     buflen -= len;
2756
2757     if (e->eb_partno) {
2758         snprintf (bp, buflen, " %s", e->eb_partno);
2759         len = strlen (bp);
2760         bp += len;
2761         buflen -= len;
2762     }
2763
2764     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2765                     e->eb_server,
2766                     e->eb_subject ? e->eb_subject : e->eb_body);
2767
2768     /* Now, check answer */
2769     if (!getanswer (buffer))
2770         return NOTOK;
2771
2772     vecp = 0;
2773     vec[vecp++] = r1bindex (mailproc, '/');
2774     vec[vecp++] = e->eb_server;
2775     vec[vecp++] = "-subject";
2776     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2777     vec[vecp++] = "-body";
2778     vec[vecp++] = e->eb_body;
2779     vec[vecp] = NULL;
2780
2781     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2782         sleep (5);
2783     switch (child_id) {
2784         case NOTOK:
2785             advise ("fork", "unable to");
2786             return NOTOK;
2787
2788         case OK:
2789             execvp (mailproc, vec);
2790             fprintf (stderr, "unable to exec ");
2791             perror (mailproc);
2792             _exit (-1);
2793             /* NOTREACHED */
2794
2795         default:
2796             if (pidXwait (child_id, NULL) == OK)
2797                 advise (NULL, "request sent");
2798             break;
2799     }
2800
2801     if (*file == NULL) {
2802         ce->ce_file = add (m_scratch ("", tmp), NULL);
2803         ce->ce_unlink = 1;
2804     } else {
2805         ce->ce_file = add (*file, NULL);
2806         ce->ce_unlink = 0;
2807     }
2808
2809     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2810         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2811         return NOTOK;
2812     }
2813
2814     fseek (ce->ce_fp, 0L, SEEK_SET);
2815     *file = ce->ce_file;
2816     return fileno (ce->ce_fp);
2817 }
2818
2819
2820 static char *
2821 fgetstr (char *s, int n, FILE *stream)
2822 {
2823     char *cp, *ep;
2824
2825     for (ep = (cp = s) + n; cp < ep; ) {
2826         int i;
2827
2828         if (!fgets (cp, n, stream))
2829             return (cp != s ? s : NULL);
2830         if (cp == s && *cp != '#')
2831             return s;
2832
2833         cp += (i = strlen (cp)) - 1;
2834         if (i <= 1 || *cp-- != '\n' || *cp != '\\')
2835             break;
2836         *cp = '\0';
2837         n -= (i - 2);
2838     }
2839
2840     return s;
2841 }
2842
2843
2844 /*
2845  * Parse the composition draft for text and directives.
2846  * Do initial setup of Content structure.
2847  */
2848
2849 static int
2850 user_content (FILE *in, char *file, char *buf, CT *ctp)
2851 {
2852     int extrnal, vrsn;
2853     char *cp, **ap;
2854     char buffer[BUFSIZ];
2855     struct multipart *m;
2856     struct part **pp;
2857     struct stat st;
2858     struct str2init *s2i;
2859     CI ci;
2860     CT ct;
2861     CE ce;
2862
2863     if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
2864         *ctp = NULL;
2865         return OK;
2866     }
2867
2868     /* allocate basic Content structure */
2869     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
2870         adios (NULL, "out of memory");
2871     *ctp = ct;
2872
2873     /* allocate basic structure for handling decoded content */
2874     init_decoded_content (ct);
2875     ce = ct->c_cefile;
2876
2877     ci = &ct->c_ctinfo;
2878     set_id (ct, 0);
2879
2880     /*
2881      * Handle inline text.  Check if line
2882      * is one of the following forms:
2883      *
2884      * 1) doesn't begin with '#'        (implicit directive)
2885      * 2) begins with "##"              (implicit directive)
2886      * 3) begins with "#<"
2887      */
2888     if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
2889         int headers;
2890         int inlineD;
2891         long pos;
2892         char content[BUFSIZ];
2893         FILE *out;
2894
2895         /* use a temp file to collect the plain text lines */
2896         ce->ce_file = add (m_tmpfil (invo_name), NULL);
2897         ce->ce_unlink = 1;
2898
2899         if ((out = fopen (ce->ce_file, "w")) == NULL)
2900             adios (ce->ce_file, "unable to open for writing");
2901
2902         if (buf[0] == '#' && buf[1] == '<') {
2903             strncpy (content, buf + 2, sizeof(content));
2904             inlineD = 1;
2905             goto rock_and_roll;
2906         } else {
2907             inlineD = 0;
2908         }
2909
2910         /* the directive is implicit */
2911         strncpy (content, "text/plain", sizeof(content));
2912         headers = 0;
2913         strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
2914         for (;;) {
2915             int i;
2916
2917             if (headers >= 0 && uprf (buffer, DESCR_FIELD)
2918                 && buffer[i = strlen (DESCR_FIELD)] == ':') {
2919                 headers = 1;
2920
2921 again_descr:
2922                 ct->c_descr = add (buffer + i + 1, ct->c_descr);
2923                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
2924                     adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
2925                 switch (buffer[0]) {
2926                 case ' ':
2927                 case '\t':
2928                     i = -1;
2929                     goto again_descr;
2930
2931                 case '#':
2932                     adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
2933                     /* NOTREACHED */
2934
2935                 default:
2936                     break;
2937                 }
2938             }
2939
2940             if (headers != 1 || buffer[0] != '\n')
2941                 fputs (buffer, out);
2942
2943 rock_and_roll:
2944             headers = -1;
2945             pos = ftell (in);
2946             if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
2947                 break;
2948             if (buffer[0] == '#') {
2949                 char *bp;
2950
2951                 if (buffer[1] != '#')
2952                     break;
2953                 for (cp = (bp = buffer) + 1; *cp; cp++)
2954                     *bp++ = *cp;
2955                 *bp = '\0';
2956             }
2957         }
2958
2959         if (listsw)
2960             ct->c_end = ftell (out);
2961         fclose (out);
2962
2963         /* parse content type */
2964         if (get_ctinfo (content, ct, inlineD) == NOTOK)
2965             done (1);
2966
2967         for (s2i = str2cts; s2i->si_key; s2i++)
2968             if (!strcasecmp (ci->ci_type, s2i->si_key))
2969                 break;
2970         if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
2971             s2i++;
2972
2973         /*
2974          * check type specified (possibly implicitly)
2975          */
2976         switch (ct->c_type = s2i->si_val) {
2977         case CT_MESSAGE:
2978             if (!strcasecmp (ci->ci_subtype, "rfc822")) {
2979                 ct->c_encoding = CE_7BIT;
2980                 goto call_init;
2981             }
2982             /* else fall... */
2983         case CT_MULTIPART:
2984             adios (NULL, "it doesn't make sense to define an in-line %s content",
2985                    ct->c_type == CT_MESSAGE ? "message" : "multipart");
2986             /* NOTREACHED */
2987
2988         default:
2989 call_init:
2990             if ((ct->c_ctinitfnx = s2i->si_init))
2991                 (*ct->c_ctinitfnx) (ct);
2992             break;
2993         }
2994
2995         if (cp)
2996             fseek (in, pos, SEEK_SET);
2997         return OK;
2998     }
2999
3000     /*
3001      * If we've reached this point, the next line
3002      * must be some type of explicit directive.
3003      */
3004
3005     /* check if directive is external-type */
3006     extrnal = (buf[1] == '@');
3007
3008     /* parse directive */
3009     if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
3010         done (1);
3011
3012     /* check directive against the list of MIME types */
3013     for (s2i = str2cts; s2i->si_key; s2i++)
3014         if (!strcasecmp (ci->ci_type, s2i->si_key))
3015             break;
3016
3017     /*
3018      * Check if the directive specified a valid type.
3019      * This will happen if it was one of the following forms:
3020      *
3021      *    #type/subtype  (or)
3022      *    #@type/subtype
3023      */
3024     if (s2i->si_key) {
3025         if (!ci->ci_subtype)
3026             adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
3027
3028         switch (ct->c_type = s2i->si_val) {
3029         case CT_MULTIPART:
3030             adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
3031                    ci->ci_type, ci->ci_subtype);
3032             /* NOTREACHED */
3033
3034         case CT_MESSAGE:
3035             if (!strcasecmp (ci->ci_subtype, "partial"))
3036                 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
3037                        ci->ci_type, ci->ci_subtype);
3038             if (!strcasecmp (ci->ci_subtype, "external-body"))
3039                 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
3040                        ci->ci_type, ci->ci_subtype);
3041 use_forw:
3042             adios (NULL,
3043                    "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
3044                    ci->ci_type, ci->ci_subtype);
3045             /* NOTREACHED */
3046
3047         default:
3048             if ((ct->c_ctinitfnx = s2i->si_init))
3049                 (*ct->c_ctinitfnx) (ct);
3050             break;
3051         }
3052
3053         /*
3054          * #@type/subtype (external types directive)
3055          */
3056         if (extrnal) {
3057             struct exbody *e;
3058             CT p;
3059
3060             if (!ci->ci_magic)
3061                 adios (NULL, "need external information for \"#@%s/%s\"",
3062                        ci->ci_type, ci->ci_subtype);
3063             p = ct;
3064
3065             snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
3066             free (ci->ci_magic);
3067             ci->ci_magic = NULL;
3068
3069             /*
3070              * Since we are using the current Content structure to
3071              * hold information about the type of the external
3072              * reference, we need to create another Content structure
3073              * for the message/external-body to wrap it in.
3074              */
3075             if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
3076                 adios (NULL, "out of memory");
3077             *ctp = ct;
3078             ci = &ct->c_ctinfo;
3079             if (get_ctinfo (buffer, ct, 0) == NOTOK)
3080                 done (1);
3081             ct->c_type = CT_MESSAGE;
3082             ct->c_subtype = MESSAGE_EXTERNAL;
3083
3084             if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
3085                 adios (NULL, "out of memory");
3086             ct->c_ctparams = (void *) e;
3087
3088             e->eb_parent = ct;
3089             e->eb_content = p;
3090             p->c_ctexbody = e;
3091
3092             if (params_external (ct, 1) == NOTOK)
3093                 done (1);
3094
3095             return OK;
3096         }
3097
3098         /* Handle [file] argument */
3099         if (ci->ci_magic) {
3100             /* check if specifies command to execute */
3101             if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
3102                 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
3103                     continue;
3104                 if (!*cp)
3105                     adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
3106                 cp = add (cp, NULL);
3107                 free (ci->ci_magic);
3108                 ci->ci_magic = cp;
3109             } else {
3110                 /* record filename of decoded contents */
3111                 ce->ce_file = ci->ci_magic;
3112                 if (access (ce->ce_file, R_OK) == NOTOK)
3113                     adios ("reading", "unable to access %s for", ce->ce_file);
3114                 if (listsw && stat (ce->ce_file, &st) != NOTOK)
3115                     ct->c_end = (long) st.st_size;
3116                 ci->ci_magic = NULL;
3117             }
3118             return OK;
3119         }
3120
3121         /*
3122          * No [file] argument, so check profile for
3123          * method to compose content.
3124          */
3125         snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
3126                 invo_name, ci->ci_type, ci->ci_subtype);
3127         if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3128             snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
3129             if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3130                 content_error (NULL, ct, "don't know how to compose content");
3131                 done (1);
3132             }
3133         }
3134         ci->ci_magic = add (cp, NULL);
3135         return OK;
3136     }
3137
3138     if (extrnal)
3139         adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
3140
3141     /*
3142      * Message directive
3143      * #forw [+folder] [msgs]
3144      */
3145     if (!strcasecmp (ci->ci_type, "forw")) {
3146         int msgnum;
3147         char *folder, *arguments[MAXARGS];
3148         struct msgs *mp;
3149
3150         if (ci->ci_magic) {
3151             ap = brkstring (ci->ci_magic, " ", "\n");
3152             copyip (ap, arguments, MAXARGS);
3153         } else {
3154             arguments[0] = "cur";
3155             arguments[1] = NULL;
3156         }
3157         folder = NULL;
3158
3159         /* search the arguments for a folder name */
3160         for (ap = arguments; *ap; ap++) {
3161             cp = *ap;
3162             if (*cp == '+' || *cp == '@')
3163                 if (folder)
3164                     adios (NULL, "only one folder per #forw directive");
3165                 else
3166                     folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
3167         }
3168
3169         /* else, use the current folder */
3170         if (!folder)
3171             folder = add (getfolder (1), NULL);
3172
3173         if (!(mp = folder_read (folder)))
3174             adios (NULL, "unable to read folder %s", folder);
3175         for (ap = arguments; *ap; ap++) {
3176             cp = *ap;
3177             if (*cp != '+' && *cp != '@')
3178                 if (!m_convert (mp, cp))
3179                     done (1);
3180         }
3181         free (folder);
3182         free_ctinfo (ct);
3183
3184         /*
3185          * If there is more than one message to include, make this
3186          * a content of type "multipart/digest" and insert each message
3187          * as a subpart.  If there is only one message, then make this
3188          * a content of type "message/rfc822".
3189          */
3190         if (mp->numsel > 1) {
3191             /* we are forwarding multiple messages */
3192             if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
3193                 done (1);
3194             ct->c_type = CT_MULTIPART;
3195             ct->c_subtype = MULTI_DIGEST;
3196
3197             if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3198                 adios (NULL, "out of memory");
3199             ct->c_ctparams = (void *) m;
3200             pp = &m->mp_parts;
3201
3202             for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
3203                 if (is_selected(mp, msgnum)) {
3204                     struct part *part;
3205                     CT p;
3206                     CE pe;
3207
3208                     if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
3209                         adios (NULL, "out of memory");
3210                     init_decoded_content (p);
3211                     pe = p->c_cefile;
3212                     if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
3213                         done (1);
3214                     p->c_type = CT_MESSAGE;
3215                     p->c_subtype = MESSAGE_RFC822;
3216
3217                     snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3218                     pe->ce_file = add (buffer, NULL);
3219                     if (listsw && stat (pe->ce_file, &st) != NOTOK)
3220                         p->c_end = (long) st.st_size;
3221
3222                     if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3223                         adios (NULL, "out of memory");
3224                     *pp = part;
3225                     pp = &part->mp_next;
3226                     part->mp_part = p;
3227                 }
3228             }
3229         } else {
3230             /* we are forwarding one message */
3231             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
3232                 done (1);
3233             ct->c_type = CT_MESSAGE;
3234             ct->c_subtype = MESSAGE_RFC822;
3235
3236             msgnum = mp->lowsel;
3237             snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3238             ce->ce_file = add (buffer, NULL);
3239             if (listsw && stat (ce->ce_file, &st) != NOTOK)
3240                 ct->c_end = (long) st.st_size;
3241         }
3242
3243         folder_free (mp);       /* free folder/message structure */
3244         return OK;
3245     }
3246
3247     /*
3248      * #end
3249      */
3250     if (!strcasecmp (ci->ci_type, "end")) {
3251         free_content (ct);
3252         *ctp = NULL;
3253         return DONE;
3254     }
3255
3256     /*
3257      * #begin [ alternative | parallel ]
3258      */
3259     if (!strcasecmp (ci->ci_type, "begin")) {
3260         if (!ci->ci_magic) {
3261             vrsn = MULTI_MIXED;
3262             cp = SubMultiPart[vrsn - 1].kv_key;
3263         } else if (!strcasecmp (ci->ci_magic, "alternative")) {
3264             vrsn = MULTI_ALTERNATE;
3265             cp = SubMultiPart[vrsn - 1].kv_key;
3266         } else if (!strcasecmp (ci->ci_magic, "parallel")) {
3267             vrsn = MULTI_PARALLEL;
3268             cp = SubMultiPart[vrsn - 1].kv_key;
3269         } else if (uprf (ci->ci_magic, "digest")) {
3270             goto use_forw;
3271         } else {
3272             vrsn = MULTI_UNKNOWN;
3273             cp = ci->ci_magic;
3274         }
3275
3276         free_ctinfo (ct);
3277         snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
3278         if (get_ctinfo (buffer, ct, 0) == NOTOK)
3279             done (1);
3280         ct->c_type = CT_MULTIPART;
3281         ct->c_subtype = vrsn;
3282
3283         if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3284             adios (NULL, "out of memory");
3285         ct->c_ctparams = (void *) m;
3286
3287         pp = &m->mp_parts;
3288         while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
3289             struct part *part;
3290             CT p;
3291
3292             if (user_content (in, file, buffer, &p) == DONE) {
3293                 if (!m->mp_parts)
3294                     adios (NULL, "empty \"#begin ... #end\" sequence");
3295                 return OK;
3296             }
3297             if (!p)
3298                 continue;
3299
3300             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3301                 adios (NULL, "out of memory");
3302             *pp = part;
3303             pp = &part->mp_next;
3304             part->mp_part = p;
3305         }
3306         admonish (NULL, "premature end-of-file, missing #end");
3307         return OK;
3308     }
3309
3310     /*
3311      * Unknown directive
3312      */
3313     adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
3314     return NOTOK;       /* NOT REACHED */
3315 }
3316
3317
3318 static void
3319 set_id (CT ct, int top)
3320 {
3321     char msgid[BUFSIZ];
3322     static int partno;
3323     static time_t clock = 0;
3324     static char *msgfmt;
3325
3326     if (clock == 0) {
3327         time (&clock);
3328         snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
3329                 (int) getpid(), (long) clock, LocalName());
3330         partno = 0;
3331         msgfmt = getcpy(msgid);
3332     }
3333     snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
3334     ct->c_id = getcpy (msgid);
3335 }
3336
3337
3338 static char ebcdicsafe[0x100] = {
3339     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3340     0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3341     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3342     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3343     0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
3344     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3345     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3346     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3347     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3348     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3349     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3350     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
3351     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3352     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3353     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3354     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3355     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3356     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3357     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3358     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3359     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3360     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3361     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3362     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3363     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3364     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3365     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3366     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3367     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3368     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3369     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3370     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
3371 };
3372
3373
3374 /*
3375  * Fill out, or expand the various contents in the composition
3376  * draft.  Read-in any necessary files.  Parse and execute any
3377  * commands specified by profile composition strings.
3378  */
3379
3380 static int
3381 compose_content (CT ct)
3382 {
3383     CE ce = ct->c_cefile;
3384
3385     switch (ct->c_type) {
3386     case CT_MULTIPART:
3387     {
3388         int partnum;
3389         char *pp;
3390         char partnam[BUFSIZ];
3391         struct multipart *m = (struct multipart *) ct->c_ctparams;
3392         struct part *part;
3393
3394         if (ct->c_partno) {
3395             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
3396             pp = partnam + strlen (partnam);
3397         } else {
3398             pp = partnam;
3399         }
3400
3401         /* first, we call compose_content on all the subparts */
3402         for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
3403             CT p = part->mp_part;
3404
3405             sprintf (pp, "%d", partnum);
3406             p->c_partno = add (partnam, NULL);
3407             if (compose_content (p) == NOTOK)
3408                 return NOTOK;
3409         }
3410
3411         /*
3412          * If the -rfc934mode switch is given, then check all
3413          * the subparts of a multipart/digest.  If they are all
3414          * message/rfc822, then mark this content and all
3415          * subparts with the rfc934 compatibility mode flag.
3416          */
3417         if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
3418             int is934 = 1;
3419
3420             for (part = m->mp_parts; part; part = part->mp_next) {
3421                 CT p = part->mp_part;
3422
3423                 if (p->c_subtype != MESSAGE_RFC822) {
3424                     is934 = 0;
3425                     break;
3426                 }
3427             }
3428             ct->c_rfc934 = is934;
3429             for (part = m->mp_parts; part; part = part->mp_next) {
3430                 CT p = part->mp_part;
3431
3432                 if ((p->c_rfc934 = is934))
3433                     p->c_end++;
3434             }
3435         }
3436
3437         if (listsw) {
3438             ct->c_end = (partnum = strlen (prefix) + 2) + 2;
3439             if (ct->c_rfc934)
3440                 ct->c_end += 1;
3441
3442             for (part = m->mp_parts; part; part = part->mp_next)
3443                 ct->c_end += part->mp_part->c_end + partnum;
3444         }
3445     }
3446     break;
3447
3448     case CT_MESSAGE:
3449         /* Nothing to do for type message */
3450         break;
3451
3452     /*
3453      * Discrete types (text/application/audio/image/video)
3454      */
3455     default:
3456         if (!ce->ce_file) {
3457             pid_t child_id;
3458             int i, xstdout, len, buflen;
3459             char *bp, **ap, *cp;
3460             char *vec[4], buffer[BUFSIZ];
3461             FILE *out;
3462             CI ci = &ct->c_ctinfo;
3463
3464             if (!(cp = ci->ci_magic))
3465                 adios (NULL, "internal error(5)");
3466
3467             ce->ce_file = add (m_tmpfil (invo_name), NULL);
3468             ce->ce_unlink = 1;
3469
3470             xstdout = 0;
3471
3472             /* Get buffer ready to go */
3473             bp = buffer;
3474             bp[0] = '\0';
3475             buflen = sizeof(buffer);
3476
3477             /*
3478              * Parse composition string into buffer
3479              */
3480             for ( ; *cp; cp++) {
3481                 if (*cp == '%') {
3482                     switch (*++cp) {
3483                     case 'a':
3484                     {
3485                         /* insert parameters from directive */
3486                         char **ep;
3487                         char *s = "";
3488
3489                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3490                             snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
3491                             len = strlen (bp);
3492                             bp += len;
3493                             buflen -= len;
3494                             s = " ";
3495                         }
3496                     }
3497                     break;
3498
3499                     case 'F':
3500                         /* %f, and stdout is not-redirected */
3501                         xstdout = 1;
3502                         /* and fall... */
3503
3504                     case 'f':
3505                         /*
3506                          * insert temporary filename where
3507                          * content should be written
3508                          */
3509                         snprintf (bp, buflen, "%s", ce->ce_file);
3510                         break;
3511
3512                     case 's':
3513                         /* insert content subtype */
3514                         strncpy (bp, ci->ci_subtype, buflen);
3515                         break;
3516
3517                     case '%':
3518                         /* insert character % */
3519                         goto raw;
3520
3521                     default:
3522                         *bp++ = *--cp;
3523                         *bp = '\0';
3524                         buflen--;
3525                         continue;
3526                     }
3527                     len = strlen (bp);
3528                     bp += len;
3529                     buflen -= len;
3530                 } else {
3531 raw:
3532                 *bp++ = *cp;
3533                 *bp = '\0';
3534                 buflen--;
3535                 }
3536             }
3537
3538             if (verbosw)
3539                 printf ("composing content %s/%s from command\n\t%s\n",
3540                         ci->ci_type, ci->ci_subtype, buffer);
3541
3542             fflush (stdout);    /* not sure if need for -noverbose */
3543
3544             vec[0] = "/bin/sh";
3545             vec[1] = "-c";
3546             vec[2] = buffer;
3547             vec[3] = NULL;
3548
3549             if ((out = fopen (ce->ce_file, "w")) == NULL)
3550                 adios (ce->ce_file, "unable to open for writing");
3551
3552             for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
3553                 sleep (5);
3554             switch (child_id) {
3555             case NOTOK:
3556                 adios ("fork", "unable to fork");
3557                 /* NOTREACHED */
3558
3559             case OK:
3560                 if (!xstdout)
3561                     dup2 (fileno (out), 1);
3562                 close (fileno (out));
3563                 execvp ("/bin/sh", vec);
3564                 fprintf (stderr, "unable to exec ");
3565                 perror ("/bin/sh");
3566                 _exit (-1);
3567                 /* NOTREACHED */
3568
3569             default:
3570                 fclose (out);
3571                 if (pidXwait(child_id, NULL))
3572                     done (1);
3573                 break;
3574             }
3575         }
3576
3577         /* Check size of file */
3578         if (listsw && ct->c_end == 0L) {
3579             struct stat st;
3580
3581             if (stat (ce->ce_file, &st) != NOTOK)
3582                 ct->c_end = (long) st.st_size;
3583         }
3584         break;
3585     }
3586
3587     return OK;
3588 }
3589
3590
3591 /*
3592  * Scan the content.
3593  *
3594  *    1) choose a transfer encoding.
3595  *    2) check for clashes with multipart boundary string.
3596  *    3) for text content, figure out which character set is being used.
3597  *
3598  * If there is a clash with one of the contents and the multipart boundary,
3599  * this function will exit with NOTOK.  This will cause the scanning process
3600  * to be repeated with a different multipart boundary.  It is possible
3601  * (although highly unlikely) that this scan will be repeated multiple times.
3602  */
3603
3604 static int
3605 scan_content (CT ct)
3606 {
3607     int len;
3608     int check8bit, contains8bit = 0;      /* check if contains 8bit data                */
3609     int checklinelen, linelen = 0;        /* check for long lines                       */
3610     int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary   */
3611     int checklinespace, linespace = 0;    /* check if any line ends with space          */
3612     int checkebcdic, ebcdicunsafe = 0;    /* check if contains ebcdic unsafe characters */
3613     char *cp, buffer[BUFSIZ];
3614     struct text *t;
3615     FILE *in;
3616     CE ce = ct->c_cefile;
3617
3618     /*
3619      * handle multipart by scanning all subparts
3620      * and then checking their encoding.
3621      */
3622     if (ct->c_type == CT_MULTIPART) {
3623         struct multipart *m = (struct multipart *) ct->c_ctparams;
3624         struct part *part;
3625
3626         /* initially mark the domain of enclosing multipart as 7bit */
3627         ct->c_encoding = CE_7BIT;
3628
3629         for (part = m->mp_parts; part; part = part->mp_next) {
3630             CT p = part->mp_part;
3631
3632             if (scan_content (p) == NOTOK)      /* choose encoding for subpart */
3633                 return NOTOK;
3634
3635             /* if necessary, enlarge encoding for enclosing multipart */
3636             if (p->c_encoding == CE_BINARY)
3637                 ct->c_encoding = CE_BINARY;
3638             if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
3639                 ct->c_encoding = CE_8BIT;
3640         }
3641
3642         return OK;
3643     }
3644
3645     /*
3646      * Decide what to check while scanning this content.
3647      */
3648     switch (ct->c_type) {
3649     case CT_TEXT:
3650         check8bit = 1;
3651         checkboundary = 1;
3652         if (ct->c_subtype == TEXT_PLAIN) {
3653             checkebcdic = 0;
3654             checklinelen = 0;
3655             checklinespace = 0;
3656         } else {
3657             checkebcdic = ebcdicsw;
3658             checklinelen = 1;
3659             checklinespace = 1;
3660         }
3661         break;
3662
3663     case CT_APPLICATION:
3664         check8bit = 1;
3665         checkebcdic = ebcdicsw;
3666         checklinelen = 1;
3667         checklinespace = 1;
3668         checkboundary = 1;
3669         break;
3670
3671     case CT_MESSAGE:
3672         check8bit = 0;
3673         checkebcdic = 0;
3674         checklinelen = 0;
3675         checklinespace = 0;
3676
3677         /* don't check anything for message/external */
3678         if (ct->c_subtype == MESSAGE_EXTERNAL)
3679             checkboundary = 0;
3680         else
3681             checkboundary = 1;
3682         break;
3683
3684     case CT_AUDIO:
3685     case CT_IMAGE:
3686     case CT_VIDEO:
3687         /*
3688          * Don't check anything for these types,
3689          * since we are forcing use of base64.
3690          */
3691         check8bit = 0;
3692         checkebcdic = 0;
3693         checklinelen = 0;
3694         checklinespace = 0;
3695         checkboundary = 0;
3696         break;
3697     }
3698
3699     /*
3700      * Scan the unencoded content
3701      */
3702     if (check8bit || checklinelen || checklinespace || checkboundary) {
3703         if ((in = fopen (ce->ce_file, "r")) == NULL)
3704             adios (ce->ce_file, "unable to open for reading");
3705         len = strlen (prefix);
3706
3707         while (fgets (buffer, sizeof(buffer) - 1, in)) {
3708             /*
3709              * Check for 8bit data.
3710              */
3711             if (check8bit) {
3712                 for (cp = buffer; *cp; cp++) {
3713                     if (!isascii (*cp)) {
3714                         contains8bit = 1;
3715                         check8bit = 0;  /* no need to keep checking */
3716                     }
3717                     /*
3718                      * Check if character is ebcdic-safe.  We only check
3719                      * this if also checking for 8bit data.
3720                      */
3721                     if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
3722                         ebcdicunsafe = 1;
3723                         checkebcdic = 0; /* no need to keep checking */
3724                     }
3725                 }
3726             }
3727
3728             /*
3729              * Check line length.
3730              */
3731             if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
3732                 linelen = 1;
3733                 checklinelen = 0;       /* no need to keep checking */
3734             }
3735
3736             /*
3737              * Check if line ends with a space.
3738              */
3739             if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
3740                 linespace = 1;
3741                 checklinespace = 0;     /* no need to keep checking */
3742             }
3743
3744             /*
3745              * Check if content contains a line that clashes
3746              * with our standard boundary for multipart messages.
3747              */
3748             if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
3749                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
3750                     if (!isspace (*cp))
3751                         break;
3752                 *++cp = '\0';
3753                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
3754                     boundaryclash = 1;
3755                     checkboundary = 0;  /* no need to keep checking */
3756                 }
3757             }
3758         }
3759         fclose (in);
3760     }
3761
3762     /*
3763      * Decide which transfer encoding to use.
3764      */
3765     switch (ct->c_type) {
3766     case CT_TEXT:
3767         /*
3768          * If the text content didn't specify a character
3769          * set, we need to figure out which one was used.
3770          */
3771         t = (struct text *) ct->c_ctparams;
3772         if (t->tx_charset == CHARSET_UNSPECIFIED) {
3773             CI ci = &ct->c_ctinfo;
3774             char **ap, **ep;
3775
3776             for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
3777                 continue;
3778
3779             if (contains8bit) {
3780                 t->tx_charset = CHARSET_UNKNOWN;
3781                 *ap = concat ("charset=", write_charset_8bit(), NULL);
3782             } else {
3783                 t->tx_charset = CHARSET_USASCII;
3784                 *ap = add ("charset=us-ascii", NULL);
3785             }
3786
3787             cp = strchr(*ap++, '=');
3788             *ap = NULL;
3789             *cp++ = '\0';
3790             *ep = cp;
3791         }
3792
3793         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3794             ct->c_encoding = CE_QUOTED;
3795         else
3796             ct->c_encoding = CE_7BIT;
3797         break;
3798
3799     case CT_APPLICATION:
3800         /* For application type, use base64, except when postscript */
3801         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3802             ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
3803                 ? CE_QUOTED : CE_BASE64;
3804         else
3805             ct->c_encoding = CE_7BIT;
3806         break;
3807
3808     case CT_MESSAGE:
3809         ct->c_encoding = CE_7BIT;
3810         break;
3811
3812     case CT_AUDIO:
3813     case CT_IMAGE:
3814     case CT_VIDEO:
3815         /* For audio, image, and video contents, just use base64 */
3816         ct->c_encoding = CE_BASE64;
3817         break;
3818     }
3819
3820     return (boundaryclash ? NOTOK : OK);
3821 }
3822
3823
3824 /*
3825  * Scan the content structures, and build header
3826  * fields that will need to be output into the
3827  * message.
3828  */
3829
3830 static int
3831 build_headers (CT ct)
3832 {
3833     int cc, mailbody, len;
3834     char **ap, **ep;
3835     char *np, *vp, buffer[BUFSIZ];
3836     CI ci = &ct->c_ctinfo;
3837
3838     /*
3839      * If message is type multipart, then add the multipart
3840      * boundary to the list of attribute/value pairs.
3841      */
3842     if (ct->c_type == CT_MULTIPART) {
3843         char *cp;
3844         static int level = 0;   /* store nesting level */
3845
3846         ap = ci->ci_attrs;
3847         ep = ci->ci_values;
3848         snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
3849         cp = strchr(*ap++ = add (buffer, NULL), '=');
3850         *ap = NULL;
3851         *cp++ = '\0';
3852         *ep = cp;
3853     }
3854
3855     /*
3856      * Skip the output of Content-Type, parameters, content
3857      * description, and Content-ID if the content is of type
3858      * "message" and the rfc934 compatibility flag is set
3859      * (which means we are inside multipart/digest and the
3860      * switch -rfc934mode was given).
3861      */
3862     if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
3863         goto skip_headers;
3864
3865     /*
3866      * output the content type and subtype
3867      */
3868     np = add (TYPE_FIELD, NULL);
3869     vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
3870
3871     /* keep track of length of line */
3872     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
3873                 + strlen (ci->ci_subtype) + 3;
3874
3875     mailbody = ct->c_type == CT_MESSAGE
3876         && ct->c_subtype == MESSAGE_EXTERNAL
3877         && ((struct exbody *) ct->c_ctparams)->eb_body;
3878
3879     /*
3880      * Append the attribute/value pairs to
3881      * the end of the Content-Type line.
3882      */
3883     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3884         if (mailbody && !strcasecmp (*ap, "body"))
3885             continue;
3886
3887         vp = add (";", vp);
3888         len++;
3889
3890         snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
3891         if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
3892             vp = add ("\n\t", vp);
3893             len = 8;
3894         } else {
3895             vp = add (" ", vp);
3896             len++;
3897         }
3898         vp = add (buffer, vp);
3899         len += cc;
3900     }
3901
3902     /*
3903      * Append any RFC-822 comment to the end of
3904      * the Content-Type line.
3905      */
3906     if (ci->ci_comment) {
3907         snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
3908         if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
3909             vp = add ("\n\t", vp);
3910             len = 8;
3911         } else {
3912             vp = add (" ", vp);
3913             len++;
3914         }
3915         vp = add (buffer, vp);
3916         len += cc;
3917     }
3918     vp = add ("\n", vp);
3919     add_header (ct, np, vp);
3920
3921     /*
3922      * output the Content-ID
3923      */
3924     if (ct->c_id) {
3925         np = add (ID_FIELD, NULL);
3926         vp = concat (" ", ct->c_id, NULL);
3927         add_header (ct, np, vp);
3928     }
3929
3930     /*
3931      * output the Content-Description
3932      */
3933     if (ct->c_descr) {
3934         np = add (DESCR_FIELD, NULL);
3935         vp = concat (" ", ct->c_descr, NULL);
3936         add_header (ct, np, vp);
3937     }
3938
3939 skip_headers:
3940     /*
3941      * If this is the internal content structure for a
3942      * "message/external", then we are done with the
3943      * headers (since it has no body).
3944      */
3945     if (ct->c_ctexbody)
3946         return OK;
3947
3948     /*
3949      * output the Content-MD5
3950      */
3951     if (checksw) {
3952         np = add (MD5_FIELD, NULL);
3953         vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
3954         add_header (ct, np, vp);
3955     }
3956
3957     /*
3958      * output the Content-Transfer-Encoding
3959      */
3960     switch (ct->c_encoding) {
3961     case CE_7BIT:
3962         /* Nothing to output */
3963 #if 0
3964         np = add (ENCODING_FIELD, NULL);
3965         vp = concat (" ", "7bit", "\n", NULL);
3966         add_header (ct, np, vp);
3967 #endif
3968         break;
3969
3970     case CE_8BIT:
3971         if (ct->c_type == CT_MESSAGE)
3972             adios (NULL, "internal error, invalid encoding");
3973
3974         np = add (ENCODING_FIELD, NULL);
3975         vp = concat (" ", "8bit", "\n", NULL);
3976         add_header (ct, np, vp);
3977         break;
3978
3979     case CE_QUOTED:
3980         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
3981             adios (NULL, "internal error, invalid encoding");
3982
3983         np = add (ENCODING_FIELD, NULL);
3984         vp = concat (" ", "quoted-printable", "\n", NULL);
3985         add_header (ct, np, vp);
3986         break;
3987
3988     case CE_BASE64:
3989         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
3990             adios (NULL, "internal error, invalid encoding");
3991
3992         np = add (ENCODING_FIELD, NULL);
3993         vp = concat (" ", "base64", "\n", NULL);
3994         add_header (ct, np, vp);
3995         break;
3996
3997     case CE_BINARY:
3998         if (ct->c_type == CT_MESSAGE)
3999             adios (NULL, "internal error, invalid encoding");
4000
4001         np = add (ENCODING_FIELD, NULL);
4002         vp = concat (" ", "binary", "\n", NULL);
4003         add_header (ct, np, vp);
4004         break;
4005
4006     default:
4007         adios (NULL, "unknown transfer encoding in content");
4008         break;
4009     }
4010
4011     /*
4012      * Additional content specific header processing
4013      */
4014     switch (ct->c_type) {
4015     case CT_MULTIPART:
4016     {
4017         struct multipart *m;
4018         struct part *part;
4019
4020         m = (struct multipart *) ct->c_ctparams;
4021         for (part = m->mp_parts; part; part = part->mp_next) {
4022             CT p;
4023
4024             p = part->mp_part;
4025             build_headers (p);
4026         }
4027     }
4028         break;
4029
4030     case CT_MESSAGE:
4031         if (ct->c_subtype == MESSAGE_EXTERNAL) {
4032             struct exbody *e;
4033
4034             e = (struct exbody *) ct->c_ctparams;
4035             build_headers (e->eb_content);
4036         }
4037         break;
4038
4039     default:
4040         /* Nothing to do */
4041         break;
4042     }
4043
4044     return OK;
4045 }
4046
4047
4048 static char nib2b64[0x40+1] =
4049         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4050
4051 static char *
4052 calculate_digest (CT ct, int asciiP)
4053 {
4054     int cc;
4055     char buffer[BUFSIZ], *vp, *op;
4056     unsigned char *dp;
4057     unsigned char digest[16];
4058     unsigned char outbuf[25];
4059     FILE *in;
4060     MD5_CTX mdContext;
4061     CE ce = ct->c_cefile;
4062
4063     /* open content */
4064     if ((in = fopen (ce->ce_file, "r")) == NULL)
4065         adios (ce->ce_file, "unable to open for reading");
4066
4067     /* Initialize md5 context */
4068     MD5Init (&mdContext);
4069
4070     /* calculate md5 message digest */
4071     if (asciiP) {
4072         while (fgets (buffer, sizeof(buffer) - 1, in)) {
4073             char c, *cp;
4074
4075             cp = buffer + strlen (buffer) - 1;
4076             if ((c = *cp) == '\n')
4077                 *cp = '\0';
4078
4079             MD5Update (&mdContext, (unsigned char *) buffer,
4080                        (unsigned int) strlen (buffer));
4081
4082             if (c == '\n')
4083                 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
4084         }
4085     } else {
4086         while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
4087             MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
4088     }
4089
4090     /* md5 finalization.  Write digest and zero md5 context */
4091     MD5Final (digest, &mdContext);
4092
4093     /* close content */
4094     fclose (in);
4095
4096     /* print debugging info */
4097     if (debugsw) {
4098         unsigned char *ep;
4099
4100         fprintf (stderr, "MD5 digest=");
4101         for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
4102                  dp < ep; dp++)
4103             fprintf (stderr, "%02x", *dp & 0xff);
4104         fprintf (stderr, "\n");
4105     }
4106
4107     /* encode the digest using base64 */
4108     for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
4109                 cc > 0; cc -= 3, op += 4) {
4110         unsigned long bits;
4111         char *bp;
4112
4113         bits = (*dp++ & 0xff) << 16;
4114         if (cc > 1) {
4115             bits |= (*dp++ & 0xff) << 8;
4116             if (cc > 2)
4117                 bits |= *dp++ & 0xff;
4118         }
4119
4120         for (bp = op + 4; bp > op; bits >>= 6)
4121             *--bp = nib2b64[bits & 0x3f];
4122         if (cc < 3) {
4123             *(op + 3) = '=';
4124             if (cc < 2)
4125                 *(op + 2) = '=';
4126         }
4127     }
4128
4129     /* null terminate string */
4130     outbuf[24] = '\0';
4131
4132     /* now make copy and return string */
4133     vp = concat (" ", outbuf, "\n", NULL);
4134     return vp;
4135 }
4136
4137
4138 static int
4139 readDigest (CT ct, char *cp)
4140 {
4141     int bitno, skip;
4142     unsigned long bits;
4143     char *bp = cp;
4144     unsigned char *dp, value, *ep;
4145     unsigned char *b, *b1, *b2, *b3;
4146
4147     b  = (unsigned char *) &bits,
4148     b1 = &b[endian > 0 ? 1 : 2],
4149     b2 = &b[endian > 0 ? 2 : 1],
4150     b3 = &b[endian > 0 ? 3 : 0];
4151     bitno = 18;
4152     bits = 0L;
4153     skip = 0;
4154
4155     for (ep = (dp = ct->c_digest)
4156                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
4157         switch (*cp) {
4158             default:
4159                 if (skip
4160                         || (*cp & 0x80)
4161                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
4162                     if (debugsw)
4163                         fprintf (stderr, "invalid BASE64 encoding\n");
4164                     return NOTOK;
4165                 }
4166
4167                 bits |= value << bitno;
4168 test_end:
4169                 if ((bitno -= 6) < 0) {
4170                     if (dp + (3 - skip) > ep)
4171                         goto invalid_digest;
4172                     *dp++ = *b1;
4173                     if (skip < 2) {
4174                         *dp++ = *b2;
4175                         if (skip < 1)
4176                             *dp++ = *b3;
4177                     }
4178                     bitno = 18;
4179                     bits = 0L;
4180                     skip = 0;
4181                 }
4182                 break;
4183
4184             case '=':
4185                 if (++skip > 3)
4186                     goto self_delimiting;
4187                 goto test_end;
4188         }
4189     if (bitno != 18) {
4190         if (debugsw)
4191             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
4192
4193         return NOTOK;
4194     }
4195 self_delimiting:
4196     if (dp != ep) {
4197 invalid_digest:
4198         if (debugsw) {
4199             while (*cp)
4200                 cp++;
4201             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
4202                      cp - bp);
4203         }
4204
4205         return NOTOK;
4206     }
4207
4208     if (debugsw) {
4209         fprintf (stderr, "MD5 digest=");
4210         for (dp = ct->c_digest; dp < ep; dp++)
4211             fprintf (stderr, "%02x", *dp & 0xff);
4212         fprintf (stderr, "\n");
4213     }
4214
4215     return OK;
4216 }