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