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