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