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