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