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