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