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