]> diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
53c06e956accef467846d3e308508e8769aff77b
[nmh] / uip / mhbuildsbr.c
1
2 /*
3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 /*
11 * This code was originally part of mhn.c. I split it into
12 * a separate program (mhbuild.c) and then later split it
13 * again (mhbuildsbr.c). But the code still has some of
14 * the mhn.c code in it. This program needs additional
15 * streamlining and removal of unneeded code.
16 */
17
18 #include <h/mh.h>
19 #include <fcntl.h>
20 #include <h/signals.h>
21 #include <h/md5.h>
22 #include <h/mts.h>
23 #include <h/tws.h>
24 #include <h/mime.h>
25 #include <h/mhparse.h>
26 #include <h/utils.h>
27
28 #ifdef HAVE_SYS_TIME_H
29 # include <sys/time.h>
30 #endif
31 #include <time.h>
32
33
34 extern int debugsw;
35 extern int verbosw;
36
37 extern int listsw;
38 extern int rfc934sw;
39 extern int contentidsw;
40
41 /* cache policies */
42 extern int rcachesw; /* mhcachesbr.c */
43 extern int wcachesw; /* mhcachesbr.c */
44
45 /*
46 * Directory to place tmp files. This must
47 * be set before these routines are called.
48 */
49 char *tmp;
50
51 pid_t xpid = 0;
52
53 static char prefix[] = "----- =_aaaaaaaaaa";
54
55 struct attach_list {
56 char *filename;
57 struct attach_list *next;
58 };
59
60 /*
61 * Maximum size of URL token in message/external-body
62 */
63
64 #define MAXURLTOKEN 40
65
66
67 /* mhmisc.c */
68 void content_error (char *, CT, char *, ...);
69
70 /* mhcachesbr.c */
71 int find_cache (CT, int, int *, char *, char *, int);
72
73 /* mhfree.c */
74 void free_ctinfo (CT);
75 void free_encoding (CT, int);
76
77 /*
78 * static prototypes
79 */
80 static int init_decoded_content (CT);
81 static void setup_attach_content(CT, char *);
82 static char *fgetstr (char *, int, FILE *);
83 static int user_content (FILE *, char *, char *, CT *);
84 static void set_id (CT, int);
85 static int compose_content (CT);
86 static int scan_content (CT);
87 static int build_headers (CT);
88 static char *calculate_digest (CT, int);
89
90
91 static unsigned char directives_stack[32];
92 static unsigned int directives_index;
93
94 static int do_direct(void)
95 {
96 return directives_stack[directives_index];
97 }
98
99 static void directive_onoff(int onoff)
100 {
101 if (directives_index >= sizeof(directives_stack) - 1) {
102 fprintf(stderr, "mhbuild: #on/off overflow, continuing\n");
103 return;
104 }
105 directives_stack[++directives_index] = onoff;
106 }
107
108 static void directive_init(int onoff)
109 {
110 directives_index = 0;
111 directives_stack[0] = onoff;
112 }
113
114 static void directive_pop(void)
115 {
116 if (directives_index > 0)
117 directives_index--;
118 else
119 fprintf(stderr, "mhbuild: #pop underflow, continuing\n");
120 }
121
122 /*
123 * Main routine for translating composition file
124 * into valid MIME message. It translates the draft
125 * into a content structure (actually a tree of content
126 * structures). This message then can be manipulated
127 * in various ways, including being output via
128 * output_message().
129 */
130
131 CT
132 build_mime (char *infile, int directives, int header_encoding)
133 {
134 int compnum, state;
135 char buf[BUFSIZ], name[NAMESZ];
136 char *cp, *np, *vp;
137 struct multipart *m;
138 struct part **pp;
139 CT ct;
140 FILE *in;
141 HF hp;
142 m_getfld_state_t gstate = 0;
143 struct attach_list *attach_head = NULL, *attach_tail = NULL, *at_entry;
144
145 directive_init(directives);
146
147 umask (~m_gmprot ());
148
149 /* open the composition draft */
150 if ((in = fopen (infile, "r")) == NULL)
151 adios (infile, "unable to open for reading");
152
153 /*
154 * Allocate space for primary (outside) content
155 */
156 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
157 adios (NULL, "out of memory");
158
159 /*
160 * Allocate structure for handling decoded content
161 * for this part. We don't really need this, but
162 * allocate it to remain consistent.
163 */
164 init_decoded_content (ct);
165
166 /*
167 * Parse some of the header fields in the composition
168 * draft into the linked list of header fields for
169 * the new MIME message.
170 */
171 m_getfld_track_filepos (&gstate, in);
172 for (compnum = 1;;) {
173 int bufsz = sizeof buf;
174 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
175 case FLD:
176 case FLDPLUS:
177 compnum++;
178
179 /* abort if draft has Mime-Version header field */
180 if (!strcasecmp (name, VRSN_FIELD))
181 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
182
183 /* abort if draft has Content-Transfer-Encoding header field */
184 if (!strcasecmp (name, ENCODING_FIELD))
185 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
186
187 /* ignore any Content-Type fields in the header */
188 if (!strcasecmp (name, TYPE_FIELD)) {
189 while (state == FLDPLUS) {
190 bufsz = sizeof buf;
191 state = m_getfld (&gstate, name, buf, &bufsz, in);
192 }
193 goto finish_field;
194 }
195
196 /* get copies of the buffers */
197 np = add (name, NULL);
198 vp = add (buf, NULL);
199
200 /* if necessary, get rest of field */
201 while (state == FLDPLUS) {
202 bufsz = sizeof buf;
203 state = m_getfld (&gstate, name, buf, &bufsz, in);
204 vp = add (buf, vp); /* add to previous value */
205 }
206
207 /*
208 * Now add the header data to the list, unless it's an attach
209 * header; in that case, add it to our attach list
210 */
211
212 if (strcasecmp(ATTACH_FIELD, np) == 0) {
213 struct attach_list *entry;
214 char *s = vp, *e = vp + strlen(vp) - 1;
215 free(np);
216
217 /*
218 * Make sure we can find the start of this filename.
219 * If it's blank, we skip completely. Otherwise, strip
220 * off any leading spaces and trailing newlines.
221 */
222
223 while (isspace((unsigned char) *s))
224 s++;
225
226 while (e > s && *e == '\n')
227 *e-- = '\0';
228
229 if (*s == '\0') {
230 free(vp);
231 goto finish_field;
232 }
233
234 entry = mh_xmalloc(sizeof(*entry));
235 entry->filename = getcpy(s);
236 free(vp);
237 if (attach_tail) {
238 attach_tail->next = entry;
239 attach_tail = entry;
240 } else {
241 attach_head = attach_tail = entry;
242 }
243 } else {
244 add_header (ct, np, vp);
245 }
246
247 finish_field:
248 /* if this wasn't the last header field, then continue */
249 continue;
250
251 case BODY:
252 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
253 /* fall through */
254 case FILEEOF:
255 break;
256
257 case LENERR:
258 case FMTERR:
259 adios (NULL, "message format error in component #%d", compnum);
260
261 default:
262 adios (NULL, "getfld() returned %d", state);
263 }
264 break;
265 }
266 m_getfld_state_destroy (&gstate);
267
268 /*
269 * Iterate through the list of headers and call the function to MIME-ify
270 * them if required.
271 */
272
273 for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
274 if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
275 adios(NULL, "Unable to encode header \"%s\"", hp->name);
276 }
277 }
278
279 /*
280 * Now add the MIME-Version header field
281 * to the list of header fields.
282 */
283 np = add (VRSN_FIELD, NULL);
284 vp = concat (" ", VRSN_VALUE, "\n", NULL);
285 add_header (ct, np, vp);
286
287 /*
288 * We initally assume we will find multiple contents in the
289 * draft. So create a multipart/mixed content to hold everything.
290 * We can remove this later, if it is not needed.
291 */
292 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
293 done (1);
294 ct->c_type = CT_MULTIPART;
295 ct->c_subtype = MULTI_MIXED;
296 ct->c_file = add (infile, NULL);
297
298 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
299 adios (NULL, "out of memory");
300 ct->c_ctparams = (void *) m;
301 pp = &m->mp_parts;
302
303 /*
304 * read and parse the composition file
305 * and the directives it contains.
306 */
307 while (fgetstr (buf, sizeof(buf) - 1, in)) {
308 struct part *part;
309 CT p;
310
311 if (user_content (in, infile, buf, &p) == DONE) {
312 admonish (NULL, "ignoring spurious #end");
313 continue;
314 }
315 if (!p)
316 continue;
317
318 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
319 adios (NULL, "out of memory");
320 *pp = part;
321 pp = &part->mp_next;
322 part->mp_part = p;
323 }
324
325 /*
326 * close the composition draft since
327 * it's not needed any longer.
328 */
329 fclose (in);
330
331 /*
332 * Add any Attach headers to the list of MIME parts at the end of the
333 * message.
334 */
335
336 for (at_entry = attach_head; at_entry; ) {
337 struct attach_list *at_prev = at_entry;
338 struct part *part;
339 CT p;
340
341 if (access(at_entry->filename, R_OK) != 0) {
342 adios("reading", "Unable to open %s for", at_entry->filename);
343 }
344
345 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
346 adios(NULL, "out of memory");
347
348 init_decoded_content(p);
349
350 /*
351 * Initialize our content structure based on the filename,
352 * and fill in all of the relevant fields. Also place MIME
353 * parameters in the attributes array.
354 */
355
356 setup_attach_content(p, at_entry->filename);
357
358 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
359 adios (NULL, "out of memory");
360 *pp = part;
361 pp = &part->mp_next;
362 part->mp_part = p;
363
364 at_entry = at_entry->next;
365 free(at_prev->filename);
366 free(at_prev);
367 }
368
369 /* check if any contents were found */
370 if (!m->mp_parts)
371 adios (NULL, "no content directives found");
372
373 /*
374 * If only one content was found, then remove and
375 * free the outer multipart content.
376 */
377 if (!m->mp_parts->mp_next) {
378 CT p;
379
380 p = m->mp_parts->mp_part;
381 m->mp_parts->mp_part = NULL;
382
383 /* move header fields */
384 p->c_first_hf = ct->c_first_hf;
385 p->c_last_hf = ct->c_last_hf;
386 ct->c_first_hf = NULL;
387 ct->c_last_hf = NULL;
388
389 free_content (ct);
390 ct = p;
391 } else {
392 set_id (ct, 1);
393 }
394
395 /*
396 * Fill out, or expand directives. Parse and execute
397 * commands specified by profile composition strings.
398 */
399 compose_content (ct);
400
401 if ((cp = strchr(prefix, 'a')) == NULL)
402 adios (NULL, "internal error(4)");
403
404 /*
405 * Scan the contents. Choose a transfer encoding, and
406 * check if prefix for multipart boundary clashes with
407 * any of the contents.
408 */
409 while (scan_content (ct) == NOTOK) {
410 if (*cp < 'z') {
411 (*cp)++;
412 } else {
413 if (*++cp == 0)
414 adios (NULL, "giving up trying to find a unique delimiter string");
415 else
416 (*cp)++;
417 }
418 }
419
420 /* Build the rest of the header field structures */
421 build_headers (ct);
422
423 return ct;
424 }
425
426
427 /*
428 * Set up structures for placing unencoded
429 * content when building parts.
430 */
431
432 static int
433 init_decoded_content (CT ct)
434 {
435 ct->c_ceopenfnx = open7Bit; /* since unencoded */
436 ct->c_ceclosefnx = close_encoding;
437 ct->c_cesizefnx = NULL; /* since unencoded */
438
439 return OK;
440 }
441
442
443 static char *
444 fgetstr (char *s, int n, FILE *stream)
445 {
446 char *cp, *ep;
447 int o_n = n;
448
449 while(1) {
450 for (ep = (cp = s) + o_n; cp < ep; ) {
451 int i;
452
453 if (!fgets (cp, n, stream))
454 return (cp != s ? s : NULL);
455
456 if (cp == s && *cp != '#')
457 return s;
458
459 cp += (i = strlen (cp)) - 1;
460 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
461 break;
462 *cp = '\0';
463 n -= (i - 2);
464 }
465
466 if (strcmp(s, "#on\n") == 0) {
467 directive_onoff(1);
468 } else if (strcmp(s, "#off\n") == 0) {
469 directive_onoff(0);
470 } else if (strcmp(s, "#pop\n") == 0) {
471 directive_pop();
472 } else {
473 break;
474 }
475 }
476
477 return s;
478 }
479
480
481 /*
482 * Parse the composition draft for text and directives.
483 * Do initial setup of Content structure.
484 */
485
486 static int
487 user_content (FILE *in, char *file, char *buf, CT *ctp)
488 {
489 int extrnal, vrsn;
490 char *cp, **ap;
491 char buffer[BUFSIZ];
492 struct multipart *m;
493 struct part **pp;
494 struct stat st;
495 struct str2init *s2i;
496 CI ci;
497 CT ct;
498 CE ce;
499
500 if (buf[0] == '\n' || (do_direct() && strcmp (buf, "#\n") == 0)) {
501 *ctp = NULL;
502 return OK;
503 }
504
505 /* allocate basic Content structure */
506 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
507 adios (NULL, "out of memory");
508 *ctp = ct;
509
510 /* allocate basic structure for handling decoded content */
511 init_decoded_content (ct);
512 ce = &ct->c_cefile;
513
514 ci = &ct->c_ctinfo;
515 set_id (ct, 0);
516
517 /*
518 * Handle inline text. Check if line
519 * is one of the following forms:
520 *
521 * 1) doesn't begin with '#' (implicit directive)
522 * 2) begins with "##" (implicit directive)
523 * 3) begins with "#<"
524 */
525 if (!do_direct() || buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
526 int headers;
527 int inlineD;
528 long pos;
529 char content[BUFSIZ];
530 FILE *out;
531 char *cp;
532
533 cp = m_mktemp2(NULL, invo_name, NULL, &out);
534 if (cp == NULL) adios("mhbuildsbr", "unable to create temporary file");
535
536 /* use a temp file to collect the plain text lines */
537 ce->ce_file = add (cp, NULL);
538 ce->ce_unlink = 1;
539
540 if (do_direct() && (buf[0] == '#' && buf[1] == '<')) {
541 strncpy (content, buf + 2, sizeof(content));
542 inlineD = 1;
543 goto rock_and_roll;
544 } else {
545 inlineD = 0;
546 }
547
548 /* the directive is implicit */
549 strncpy (content, "text/plain", sizeof(content));
550 headers = 0;
551 strncpy (buffer, (!do_direct() || buf[0] != '#') ? buf : buf + 1, sizeof(buffer));
552 for (;;) {
553 int i;
554
555 if (headers >= 0 && do_direct() && uprf (buffer, DESCR_FIELD)
556 && buffer[i = strlen (DESCR_FIELD)] == ':') {
557 headers = 1;
558
559 again_descr:
560 ct->c_descr = add (buffer + i + 1, ct->c_descr);
561 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
562 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
563 switch (buffer[0]) {
564 case ' ':
565 case '\t':
566 i = -1;
567 goto again_descr;
568
569 case '#':
570 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
571 /* NOTREACHED */
572
573 default:
574 break;
575 }
576 }
577
578 if (headers >= 0 && do_direct() && uprf (buffer, DISPO_FIELD)
579 && buffer[i = strlen (DISPO_FIELD)] == ':') {
580 headers = 1;
581
582 again_dispo:
583 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
584 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
585 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
586 switch (buffer[0]) {
587 case ' ':
588 case '\t':
589 i = -1;
590 goto again_dispo;
591
592 case '#':
593 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
594 /* NOTREACHED */
595
596 default:
597 break;
598 }
599 }
600
601 if (headers != 1 || buffer[0] != '\n')
602 fputs (buffer, out);
603
604 rock_and_roll:
605 headers = -1;
606 pos = ftell (in);
607 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
608 break;
609 if (do_direct() && buffer[0] == '#') {
610 char *bp;
611
612 if (buffer[1] != '#')
613 break;
614 for (cp = (bp = buffer) + 1; *cp; cp++)
615 *bp++ = *cp;
616 *bp = '\0';
617 }
618 }
619
620 if (listsw)
621 ct->c_end = ftell (out);
622 fclose (out);
623
624 /* parse content type */
625 if (get_ctinfo (content, ct, inlineD) == NOTOK)
626 done (1);
627
628 for (s2i = str2cts; s2i->si_key; s2i++)
629 if (!strcasecmp (ci->ci_type, s2i->si_key))
630 break;
631 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
632 s2i++;
633
634 /*
635 * check type specified (possibly implicitly)
636 */
637 switch (ct->c_type = s2i->si_val) {
638 case CT_MESSAGE:
639 if (!strcasecmp (ci->ci_subtype, "rfc822")) {
640 ct->c_encoding = CE_7BIT;
641 goto call_init;
642 }
643 /* else fall... */
644 case CT_MULTIPART:
645 adios (NULL, "it doesn't make sense to define an in-line %s content",
646 ct->c_type == CT_MESSAGE ? "message" : "multipart");
647 /* NOTREACHED */
648
649 default:
650 call_init:
651 if ((ct->c_ctinitfnx = s2i->si_init))
652 (*ct->c_ctinitfnx) (ct);
653 break;
654 }
655
656 if (cp)
657 fseek (in, pos, SEEK_SET);
658 return OK;
659 }
660
661 /*
662 * If we've reached this point, the next line
663 * must be some type of explicit directive.
664 */
665
666 /* check if directive is external-type */
667 extrnal = (buf[1] == '@');
668
669 /* parse directive */
670 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
671 done (1);
672
673 /* check directive against the list of MIME types */
674 for (s2i = str2cts; s2i->si_key; s2i++)
675 if (!strcasecmp (ci->ci_type, s2i->si_key))
676 break;
677
678 /*
679 * Check if the directive specified a valid type.
680 * This will happen if it was one of the following forms:
681 *
682 * #type/subtype (or)
683 * #@type/subtype
684 */
685 if (s2i->si_key) {
686 if (!ci->ci_subtype)
687 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
688
689 switch (ct->c_type = s2i->si_val) {
690 case CT_MULTIPART:
691 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
692 ci->ci_type, ci->ci_subtype);
693 /* NOTREACHED */
694
695 case CT_MESSAGE:
696 if (!strcasecmp (ci->ci_subtype, "partial"))
697 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
698 ci->ci_type, ci->ci_subtype);
699 if (!strcasecmp (ci->ci_subtype, "external-body"))
700 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
701 ci->ci_type, ci->ci_subtype);
702 use_forw:
703 adios (NULL,
704 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
705 ci->ci_type, ci->ci_subtype);
706 /* NOTREACHED */
707
708 default:
709 if ((ct->c_ctinitfnx = s2i->si_init))
710 (*ct->c_ctinitfnx) (ct);
711 break;
712 }
713
714 /*
715 * #@type/subtype (external types directive)
716 */
717 if (extrnal) {
718 struct exbody *e;
719 CT p;
720
721 if (!ci->ci_magic)
722 adios (NULL, "need external information for \"#@%s/%s\"",
723 ci->ci_type, ci->ci_subtype);
724 p = ct;
725
726 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
727 free (ci->ci_magic);
728 ci->ci_magic = NULL;
729
730 /*
731 * Since we are using the current Content structure to
732 * hold information about the type of the external
733 * reference, we need to create another Content structure
734 * for the message/external-body to wrap it in.
735 */
736 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
737 adios (NULL, "out of memory");
738 *ctp = ct;
739 ci = &ct->c_ctinfo;
740 if (get_ctinfo (buffer, ct, 0) == NOTOK)
741 done (1);
742 ct->c_type = CT_MESSAGE;
743 ct->c_subtype = MESSAGE_EXTERNAL;
744
745 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
746 adios (NULL, "out of memory");
747 ct->c_ctparams = (void *) e;
748
749 e->eb_parent = ct;
750 e->eb_content = p;
751 p->c_ctexbody = e;
752
753 if (params_external (ct, 1) == NOTOK)
754 done (1);
755
756 return OK;
757 }
758
759 /* Handle [file] argument */
760 if (ci->ci_magic) {
761 /* check if specifies command to execute */
762 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
763 for (cp = ci->ci_magic + 1; isspace ((unsigned char) *cp); cp++)
764 continue;
765 if (!*cp)
766 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
767 cp = add (cp, NULL);
768 free (ci->ci_magic);
769 ci->ci_magic = cp;
770 } else {
771 /* record filename of decoded contents */
772 ce->ce_file = ci->ci_magic;
773 if (access (ce->ce_file, R_OK) == NOTOK)
774 adios ("reading", "unable to access %s for", ce->ce_file);
775 if (listsw && stat (ce->ce_file, &st) != NOTOK)
776 ct->c_end = (long) st.st_size;
777 ci->ci_magic = NULL;
778 }
779 return OK;
780 }
781
782 /*
783 * No [file] argument, so check profile for
784 * method to compose content.
785 */
786 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
787 invo_name, ci->ci_type, ci->ci_subtype);
788 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
789 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
790 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
791 content_error (NULL, ct, "don't know how to compose content");
792 done (1);
793 }
794 }
795 ci->ci_magic = add (cp, NULL);
796 return OK;
797 }
798
799 if (extrnal)
800 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
801
802 /*
803 * Message directive
804 * #forw [+folder] [msgs]
805 */
806 if (!strcasecmp (ci->ci_type, "forw")) {
807 int msgnum;
808 char *folder, *arguments[MAXARGS];
809 struct msgs *mp;
810
811 if (ci->ci_magic) {
812 ap = brkstring (ci->ci_magic, " ", "\n");
813 copyip (ap, arguments, MAXARGS);
814 } else {
815 arguments[0] = "cur";
816 arguments[1] = NULL;
817 }
818 folder = NULL;
819
820 /* search the arguments for a folder name */
821 for (ap = arguments; *ap; ap++) {
822 cp = *ap;
823 if (*cp == '+' || *cp == '@') {
824 if (folder)
825 adios (NULL, "only one folder per #forw directive");
826 else
827 folder = pluspath (cp);
828 }
829 }
830
831 /* else, use the current folder */
832 if (!folder)
833 folder = add (getfolder (1), NULL);
834
835 if (!(mp = folder_read (folder, 0)))
836 adios (NULL, "unable to read folder %s", folder);
837 for (ap = arguments; *ap; ap++) {
838 cp = *ap;
839 if (*cp != '+' && *cp != '@')
840 if (!m_convert (mp, cp))
841 done (1);
842 }
843 free (folder);
844 free_ctinfo (ct);
845
846 /*
847 * If there is more than one message to include, make this
848 * a content of type "multipart/digest" and insert each message
849 * as a subpart. If there is only one message, then make this
850 * a content of type "message/rfc822".
851 */
852 if (mp->numsel > 1) {
853 /* we are forwarding multiple messages */
854 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
855 done (1);
856 ct->c_type = CT_MULTIPART;
857 ct->c_subtype = MULTI_DIGEST;
858
859 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
860 adios (NULL, "out of memory");
861 ct->c_ctparams = (void *) m;
862 pp = &m->mp_parts;
863
864 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
865 if (is_selected(mp, msgnum)) {
866 struct part *part;
867 CT p;
868 CE pe;
869
870 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
871 adios (NULL, "out of memory");
872 init_decoded_content (p);
873 pe = &p->c_cefile;
874 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
875 done (1);
876 p->c_type = CT_MESSAGE;
877 p->c_subtype = MESSAGE_RFC822;
878
879 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
880 pe->ce_file = add (buffer, NULL);
881 if (listsw && stat (pe->ce_file, &st) != NOTOK)
882 p->c_end = (long) st.st_size;
883
884 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
885 adios (NULL, "out of memory");
886 *pp = part;
887 pp = &part->mp_next;
888 part->mp_part = p;
889 }
890 }
891 } else {
892 /* we are forwarding one message */
893 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
894 done (1);
895 ct->c_type = CT_MESSAGE;
896 ct->c_subtype = MESSAGE_RFC822;
897
898 msgnum = mp->lowsel;
899 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
900 ce->ce_file = add (buffer, NULL);
901 if (listsw && stat (ce->ce_file, &st) != NOTOK)
902 ct->c_end = (long) st.st_size;
903 }
904
905 folder_free (mp); /* free folder/message structure */
906 return OK;
907 }
908
909 /*
910 * #end
911 */
912 if (!strcasecmp (ci->ci_type, "end")) {
913 free_content (ct);
914 *ctp = NULL;
915 return DONE;
916 }
917
918 /*
919 * #begin [ alternative | parallel ]
920 */
921 if (!strcasecmp (ci->ci_type, "begin")) {
922 if (!ci->ci_magic) {
923 vrsn = MULTI_MIXED;
924 cp = SubMultiPart[vrsn - 1].kv_key;
925 } else if (!strcasecmp (ci->ci_magic, "alternative")) {
926 vrsn = MULTI_ALTERNATE;
927 cp = SubMultiPart[vrsn - 1].kv_key;
928 } else if (!strcasecmp (ci->ci_magic, "parallel")) {
929 vrsn = MULTI_PARALLEL;
930 cp = SubMultiPart[vrsn - 1].kv_key;
931 } else if (uprf (ci->ci_magic, "digest")) {
932 goto use_forw;
933 } else {
934 vrsn = MULTI_UNKNOWN;
935 cp = ci->ci_magic;
936 }
937
938 free_ctinfo (ct);
939 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
940 if (get_ctinfo (buffer, ct, 0) == NOTOK)
941 done (1);
942 ct->c_type = CT_MULTIPART;
943 ct->c_subtype = vrsn;
944
945 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
946 adios (NULL, "out of memory");
947 ct->c_ctparams = (void *) m;
948
949 pp = &m->mp_parts;
950 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
951 struct part *part;
952 CT p;
953
954 if (user_content (in, file, buffer, &p) == DONE) {
955 if (!m->mp_parts)
956 adios (NULL, "empty \"#begin ... #end\" sequence");
957 return OK;
958 }
959 if (!p)
960 continue;
961
962 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
963 adios (NULL, "out of memory");
964 *pp = part;
965 pp = &part->mp_next;
966 part->mp_part = p;
967 }
968 admonish (NULL, "premature end-of-file, missing #end");
969 return OK;
970 }
971
972 /*
973 * Unknown directive
974 */
975 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
976 return NOTOK; /* NOT REACHED */
977 }
978
979
980 static void
981 set_id (CT ct, int top)
982 {
983 char contentid[BUFSIZ];
984 static int partno;
985 static time_t clock = 0;
986 static char *msgfmt;
987
988 if (clock == 0) {
989 time (&clock);
990 snprintf (contentid, sizeof(contentid), "%s\n", message_id (clock, 1));
991 partno = 0;
992 msgfmt = getcpy(contentid);
993 }
994 snprintf (contentid, sizeof(contentid), msgfmt, top ? 0 : ++partno);
995 ct->c_id = getcpy (contentid);
996 }
997
998
999 /*
1000 * Fill out, or expand the various contents in the composition
1001 * draft. Read-in any necessary files. Parse and execute any
1002 * commands specified by profile composition strings.
1003 */
1004
1005 static int
1006 compose_content (CT ct)
1007 {
1008 CE ce = &ct->c_cefile;
1009
1010 switch (ct->c_type) {
1011 case CT_MULTIPART:
1012 {
1013 int partnum;
1014 char *pp;
1015 char partnam[BUFSIZ];
1016 struct multipart *m = (struct multipart *) ct->c_ctparams;
1017 struct part *part;
1018
1019 if (ct->c_partno) {
1020 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1021 pp = partnam + strlen (partnam);
1022 } else {
1023 pp = partnam;
1024 }
1025
1026 /* first, we call compose_content on all the subparts */
1027 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
1028 CT p = part->mp_part;
1029
1030 sprintf (pp, "%d", partnum);
1031 p->c_partno = add (partnam, NULL);
1032 if (compose_content (p) == NOTOK)
1033 return NOTOK;
1034 }
1035
1036 /*
1037 * If the -rfc934mode switch is given, then check all
1038 * the subparts of a multipart/digest. If they are all
1039 * message/rfc822, then mark this content and all
1040 * subparts with the rfc934 compatibility mode flag.
1041 */
1042 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1043 int is934 = 1;
1044
1045 for (part = m->mp_parts; part; part = part->mp_next) {
1046 CT p = part->mp_part;
1047
1048 if (p->c_subtype != MESSAGE_RFC822) {
1049 is934 = 0;
1050 break;
1051 }
1052 }
1053 ct->c_rfc934 = is934;
1054 for (part = m->mp_parts; part; part = part->mp_next) {
1055 CT p = part->mp_part;
1056
1057 if ((p->c_rfc934 = is934))
1058 p->c_end++;
1059 }
1060 }
1061
1062 if (listsw) {
1063 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
1064 if (ct->c_rfc934)
1065 ct->c_end += 1;
1066
1067 for (part = m->mp_parts; part; part = part->mp_next)
1068 ct->c_end += part->mp_part->c_end + partnum;
1069 }
1070 }
1071 break;
1072
1073 case CT_MESSAGE:
1074 /* Nothing to do for type message */
1075 break;
1076
1077 /*
1078 * Discrete types (text/application/audio/image/video)
1079 */
1080 default:
1081 if (!ce->ce_file) {
1082 pid_t child_id;
1083 int i, xstdout, len, buflen;
1084 char *bp, **ap, *cp;
1085 char *vec[4], buffer[BUFSIZ];
1086 FILE *out;
1087 CI ci = &ct->c_ctinfo;
1088 char *tfile = NULL;
1089
1090 if (!(cp = ci->ci_magic))
1091 adios (NULL, "internal error(5)");
1092
1093 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1094 if (tfile == NULL) {
1095 adios("mhbuildsbr", "unable to create temporary file");
1096 }
1097 ce->ce_file = add (tfile, NULL);
1098 ce->ce_unlink = 1;
1099
1100 xstdout = 0;
1101
1102 /* Get buffer ready to go */
1103 bp = buffer;
1104 bp[0] = '\0';
1105 buflen = sizeof(buffer);
1106
1107 /*
1108 * Parse composition string into buffer
1109 */
1110 for ( ; *cp; cp++) {
1111 if (*cp == '%') {
1112 switch (*++cp) {
1113 case 'a':
1114 {
1115 /* insert parameters from directive */
1116 char **ep;
1117 char *s = "";
1118
1119 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1120 snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1121 len = strlen (bp);
1122 bp += len;
1123 buflen -= len;
1124 s = " ";
1125 }
1126 }
1127 break;
1128
1129 case 'F':
1130 /* %f, and stdout is not-redirected */
1131 xstdout = 1;
1132 /* and fall... */
1133
1134 case 'f':
1135 /*
1136 * insert temporary filename where
1137 * content should be written
1138 */
1139 snprintf (bp, buflen, "%s", ce->ce_file);
1140 break;
1141
1142 case 's':
1143 /* insert content subtype */
1144 strncpy (bp, ci->ci_subtype, buflen);
1145 break;
1146
1147 case '%':
1148 /* insert character % */
1149 goto raw;
1150
1151 default:
1152 *bp++ = *--cp;
1153 *bp = '\0';
1154 buflen--;
1155 continue;
1156 }
1157 len = strlen (bp);
1158 bp += len;
1159 buflen -= len;
1160 } else {
1161 raw:
1162 *bp++ = *cp;
1163 *bp = '\0';
1164 buflen--;
1165 }
1166 }
1167
1168 if (verbosw)
1169 printf ("composing content %s/%s from command\n\t%s\n",
1170 ci->ci_type, ci->ci_subtype, buffer);
1171
1172 fflush (stdout); /* not sure if need for -noverbose */
1173
1174 vec[0] = "/bin/sh";
1175 vec[1] = "-c";
1176 vec[2] = buffer;
1177 vec[3] = NULL;
1178
1179 if ((out = fopen (ce->ce_file, "w")) == NULL)
1180 adios (ce->ce_file, "unable to open for writing");
1181
1182 for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1183 sleep (5);
1184 switch (child_id) {
1185 case NOTOK:
1186 adios ("fork", "unable to fork");
1187 /* NOTREACHED */
1188
1189 case OK:
1190 if (!xstdout)
1191 dup2 (fileno (out), 1);
1192 close (fileno (out));
1193 execvp ("/bin/sh", vec);
1194 fprintf (stderr, "unable to exec ");
1195 perror ("/bin/sh");
1196 _exit (-1);
1197 /* NOTREACHED */
1198
1199 default:
1200 fclose (out);
1201 if (pidXwait(child_id, NULL))
1202 done (1);
1203 break;
1204 }
1205 }
1206
1207 /* Check size of file */
1208 if (listsw && ct->c_end == 0L) {
1209 struct stat st;
1210
1211 if (stat (ce->ce_file, &st) != NOTOK)
1212 ct->c_end = (long) st.st_size;
1213 }
1214 break;
1215 }
1216
1217 return OK;
1218 }
1219
1220
1221 /*
1222 * Scan the content.
1223 *
1224 * 1) choose a transfer encoding.
1225 * 2) check for clashes with multipart boundary string.
1226 * 3) for text content, figure out which character set is being used.
1227 *
1228 * If there is a clash with one of the contents and the multipart boundary,
1229 * this function will exit with NOTOK. This will cause the scanning process
1230 * to be repeated with a different multipart boundary. It is possible
1231 * (although highly unlikely) that this scan will be repeated multiple times.
1232 */
1233
1234 static int
1235 scan_content (CT ct)
1236 {
1237 int len;
1238 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1239 int checklinelen = 0, linelen = 0; /* check for long lines */
1240 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1241 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1242 char *cp = NULL, buffer[BUFSIZ];
1243 struct text *t = NULL;
1244 FILE *in = NULL;
1245 CE ce = &ct->c_cefile;
1246
1247 /*
1248 * handle multipart by scanning all subparts
1249 * and then checking their encoding.
1250 */
1251 if (ct->c_type == CT_MULTIPART) {
1252 struct multipart *m = (struct multipart *) ct->c_ctparams;
1253 struct part *part;
1254
1255 /* initially mark the domain of enclosing multipart as 7bit */
1256 ct->c_encoding = CE_7BIT;
1257
1258 for (part = m->mp_parts; part; part = part->mp_next) {
1259 CT p = part->mp_part;
1260
1261 if (scan_content (p) == NOTOK) /* choose encoding for subpart */
1262 return NOTOK;
1263
1264 /* if necessary, enlarge encoding for enclosing multipart */
1265 if (p->c_encoding == CE_BINARY)
1266 ct->c_encoding = CE_BINARY;
1267 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1268 ct->c_encoding = CE_8BIT;
1269 }
1270
1271 return OK;
1272 }
1273
1274 /*
1275 * Decide what to check while scanning this content.
1276 */
1277 switch (ct->c_type) {
1278 case CT_TEXT:
1279 check8bit = 1;
1280 checkboundary = 1;
1281 if (ct->c_subtype == TEXT_PLAIN) {
1282 checklinelen = 0;
1283 checklinespace = 0;
1284 } else {
1285 checklinelen = 1;
1286 checklinespace = 1;
1287 }
1288 break;
1289
1290 case CT_APPLICATION:
1291 check8bit = 1;
1292 checklinelen = 1;
1293 checklinespace = 1;
1294 checkboundary = 1;
1295 break;
1296
1297 case CT_MESSAGE:
1298 check8bit = 0;
1299 checklinelen = 0;
1300 checklinespace = 0;
1301
1302 /* don't check anything for message/external */
1303 if (ct->c_subtype == MESSAGE_EXTERNAL)
1304 checkboundary = 0;
1305 else
1306 checkboundary = 1;
1307 break;
1308
1309 case CT_AUDIO:
1310 case CT_IMAGE:
1311 case CT_VIDEO:
1312 /*
1313 * Don't check anything for these types,
1314 * since we are forcing use of base64.
1315 */
1316 check8bit = 0;
1317 checklinelen = 0;
1318 checklinespace = 0;
1319 checkboundary = 0;
1320 break;
1321 }
1322
1323 /*
1324 * Scan the unencoded content
1325 */
1326 if (check8bit || checklinelen || checklinespace || checkboundary) {
1327 if ((in = fopen (ce->ce_file, "r")) == NULL)
1328 adios (ce->ce_file, "unable to open for reading");
1329 len = strlen (prefix);
1330
1331 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1332 /*
1333 * Check for 8bit data.
1334 */
1335 if (check8bit) {
1336 for (cp = buffer; *cp; cp++) {
1337 if (!isascii ((unsigned char) *cp)) {
1338 contains8bit = 1;
1339 check8bit = 0; /* no need to keep checking */
1340 }
1341 }
1342 }
1343
1344 /*
1345 * Check line length.
1346 */
1347 if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1348 linelen = 1;
1349 checklinelen = 0; /* no need to keep checking */
1350 }
1351
1352 /*
1353 * Check if line ends with a space.
1354 */
1355 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace ((unsigned char) *cp)) {
1356 linespace = 1;
1357 checklinespace = 0; /* no need to keep checking */
1358 }
1359
1360 /*
1361 * Check if content contains a line that clashes
1362 * with our standard boundary for multipart messages.
1363 */
1364 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1365 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1366 if (!isspace ((unsigned char) *cp))
1367 break;
1368 *++cp = '\0';
1369 if (!strncmp(buffer + 2, prefix, len) && isdigit((unsigned char) buffer[2 + len])) {
1370 boundaryclash = 1;
1371 checkboundary = 0; /* no need to keep checking */
1372 }
1373 }
1374 }
1375 fclose (in);
1376 }
1377
1378 /*
1379 * Decide which transfer encoding to use.
1380 */
1381 switch (ct->c_type) {
1382 case CT_TEXT:
1383 /*
1384 * If the text content didn't specify a character
1385 * set, we need to figure out which one was used.
1386 */
1387 t = (struct text *) ct->c_ctparams;
1388 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1389 CI ci = &ct->c_ctinfo;
1390 char **ap, **ep;
1391
1392 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1393 continue;
1394
1395 if (contains8bit) {
1396 *ap = concat ("charset=", write_charset_8bit(), NULL);
1397 } else {
1398 *ap = add ("charset=us-ascii", NULL);
1399 }
1400 t->tx_charset = CHARSET_SPECIFIED;
1401
1402 cp = strchr(*ap++, '=');
1403 *ap = NULL;
1404 *cp++ = '\0';
1405 *ep = cp;
1406 }
1407
1408 if (contains8bit || linelen || linespace || checksw)
1409 ct->c_encoding = CE_QUOTED;
1410 else
1411 ct->c_encoding = CE_7BIT;
1412 break;
1413
1414 case CT_APPLICATION:
1415 /* For application type, use base64, except when postscript */
1416 if (contains8bit || linelen || linespace || checksw)
1417 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1418 ? CE_QUOTED : CE_BASE64;
1419 else
1420 ct->c_encoding = CE_7BIT;
1421 break;
1422
1423 case CT_MESSAGE:
1424 ct->c_encoding = CE_7BIT;
1425 break;
1426
1427 case CT_AUDIO:
1428 case CT_IMAGE:
1429 case CT_VIDEO:
1430 /* For audio, image, and video contents, just use base64 */
1431 ct->c_encoding = CE_BASE64;
1432 break;
1433 }
1434
1435 return (boundaryclash ? NOTOK : OK);
1436 }
1437
1438
1439 /*
1440 * Scan the content structures, and build header
1441 * fields that will need to be output into the
1442 * message.
1443 */
1444
1445 static int
1446 build_headers (CT ct)
1447 {
1448 int cc, mailbody, extbody, len;
1449 char **ap, **ep;
1450 char *np, *vp, buffer[BUFSIZ];
1451 CI ci = &ct->c_ctinfo;
1452
1453 /*
1454 * If message is type multipart, then add the multipart
1455 * boundary to the list of attribute/value pairs.
1456 */
1457 if (ct->c_type == CT_MULTIPART) {
1458 char *cp;
1459 static int level = 0; /* store nesting level */
1460
1461 ap = ci->ci_attrs;
1462 ep = ci->ci_values;
1463 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1464 cp = strchr(*ap++ = add (buffer, NULL), '=');
1465 *ap = NULL;
1466 *cp++ = '\0';
1467 *ep = cp;
1468 }
1469
1470 /*
1471 * Skip the output of Content-Type, parameters, content
1472 * description and disposition, and Content-ID if the
1473 * content is of type "message" and the rfc934 compatibility
1474 * flag is set (which means we are inside multipart/digest
1475 * and the switch -rfc934mode was given).
1476 */
1477 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1478 goto skip_headers;
1479
1480 /*
1481 * output the content type and subtype
1482 */
1483 np = add (TYPE_FIELD, NULL);
1484 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1485
1486 /* keep track of length of line */
1487 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1488 + strlen (ci->ci_subtype) + 3;
1489
1490 extbody = ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL;
1491 mailbody = extbody && ((struct exbody *) ct->c_ctparams)->eb_body;
1492
1493 /*
1494 * Append the attribute/value pairs to
1495 * the end of the Content-Type line.
1496 */
1497 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1498 if (mailbody && !strcasecmp (*ap, "body"))
1499 continue;
1500
1501 vp = add (";", vp);
1502 len++;
1503
1504 /*
1505 * According to RFC 2017, if we have a URL longer than 40 characters
1506 * we have to break it across multiple lines
1507 */
1508
1509 if (extbody && strcasecmp (*ap, "url") == 0) {
1510 char *value = *ep;
1511
1512 /* 7 here refers to " url=\"\"" */
1513 if (len + 1 + (cc = (min(MAXURLTOKEN, strlen(value)) + 7)) >=
1514 CPERLIN) {
1515 vp = add ("\n\t", vp);
1516 len = 8;
1517 } else {
1518 vp = add (" ", vp);
1519 len++;
1520 }
1521
1522 vp = add ("url=\"", vp);
1523 len += 5;
1524
1525 while (strlen(value) > MAXURLTOKEN) {
1526 strncpy(buffer, value, MAXURLTOKEN);
1527 buffer[MAXURLTOKEN] = '\0';
1528 vp = add (buffer, vp);
1529 vp = add ("\n\t", vp);
1530 value += MAXURLTOKEN;
1531 len = 8;
1532 }
1533
1534 vp = add (value, vp);
1535 vp = add ("\"", vp);
1536 len += strlen(value) + 1;
1537 continue;
1538 }
1539
1540 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1541 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1542 vp = add ("\n\t", vp);
1543 len = 8;
1544 } else {
1545 vp = add (" ", vp);
1546 len++;
1547 }
1548 vp = add (buffer, vp);
1549 len += cc;
1550 }
1551
1552 /*
1553 * Append any RFC-822 comment to the end of
1554 * the Content-Type line.
1555 */
1556 if (ci->ci_comment) {
1557 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1558 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1559 vp = add ("\n\t", vp);
1560 len = 8;
1561 } else {
1562 vp = add (" ", vp);
1563 len++;
1564 }
1565 vp = add (buffer, vp);
1566 len += cc;
1567 }
1568 vp = add ("\n", vp);
1569 add_header (ct, np, vp);
1570
1571 /*
1572 * output the Content-ID, unless disabled by -nocontentid
1573 */
1574 if (contentidsw && ct->c_id) {
1575 np = add (ID_FIELD, NULL);
1576 vp = concat (" ", ct->c_id, NULL);
1577 add_header (ct, np, vp);
1578 }
1579
1580 /*
1581 * output the Content-Description
1582 */
1583 if (ct->c_descr) {
1584 np = add (DESCR_FIELD, NULL);
1585 vp = concat (" ", ct->c_descr, NULL);
1586 add_header (ct, np, vp);
1587 }
1588
1589 /*
1590 * output the Content-Disposition
1591 */
1592 if (ct->c_dispo) {
1593 np = add (DISPO_FIELD, NULL);
1594 vp = concat (" ", ct->c_dispo, NULL);
1595 add_header (ct, np, vp);
1596 }
1597
1598 skip_headers:
1599 /*
1600 * If this is the internal content structure for a
1601 * "message/external", then we are done with the
1602 * headers (since it has no body).
1603 */
1604 if (ct->c_ctexbody)
1605 return OK;
1606
1607 /*
1608 * output the Content-MD5
1609 */
1610 if (checksw) {
1611 np = add (MD5_FIELD, NULL);
1612 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1613 add_header (ct, np, vp);
1614 }
1615
1616 /*
1617 * output the Content-Transfer-Encoding
1618 */
1619 switch (ct->c_encoding) {
1620 case CE_7BIT:
1621 /* Nothing to output */
1622 break;
1623
1624 case CE_8BIT:
1625 if (ct->c_type == CT_MESSAGE)
1626 adios (NULL, "internal error, invalid encoding");
1627
1628 np = add (ENCODING_FIELD, NULL);
1629 vp = concat (" ", "8bit", "\n", NULL);
1630 add_header (ct, np, vp);
1631 break;
1632
1633 case CE_QUOTED:
1634 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1635 adios (NULL, "internal error, invalid encoding");
1636
1637 np = add (ENCODING_FIELD, NULL);
1638 vp = concat (" ", "quoted-printable", "\n", NULL);
1639 add_header (ct, np, vp);
1640 break;
1641
1642 case CE_BASE64:
1643 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1644 adios (NULL, "internal error, invalid encoding");
1645
1646 np = add (ENCODING_FIELD, NULL);
1647 vp = concat (" ", "base64", "\n", NULL);
1648 add_header (ct, np, vp);
1649 break;
1650
1651 case CE_BINARY:
1652 if (ct->c_type == CT_MESSAGE)
1653 adios (NULL, "internal error, invalid encoding");
1654
1655 np = add (ENCODING_FIELD, NULL);
1656 vp = concat (" ", "binary", "\n", NULL);
1657 add_header (ct, np, vp);
1658 break;
1659
1660 default:
1661 adios (NULL, "unknown transfer encoding in content");
1662 break;
1663 }
1664
1665 /*
1666 * Additional content specific header processing
1667 */
1668 switch (ct->c_type) {
1669 case CT_MULTIPART:
1670 {
1671 struct multipart *m;
1672 struct part *part;
1673
1674 m = (struct multipart *) ct->c_ctparams;
1675 for (part = m->mp_parts; part; part = part->mp_next) {
1676 CT p;
1677
1678 p = part->mp_part;
1679 build_headers (p);
1680 }
1681 }
1682 break;
1683
1684 case CT_MESSAGE:
1685 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1686 struct exbody *e;
1687
1688 e = (struct exbody *) ct->c_ctparams;
1689 build_headers (e->eb_content);
1690 }
1691 break;
1692
1693 default:
1694 /* Nothing to do */
1695 break;
1696 }
1697
1698 return OK;
1699 }
1700
1701
1702 static char nib2b64[0x40+1] =
1703 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1704
1705 static char *
1706 calculate_digest (CT ct, int asciiP)
1707 {
1708 int cc;
1709 char buffer[BUFSIZ], *vp, *op;
1710 unsigned char *dp;
1711 unsigned char digest[16];
1712 unsigned char outbuf[25];
1713 MD5_CTX mdContext;
1714 CE ce = &ct->c_cefile;
1715 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1716 FILE *in;
1717
1718 /* open content */
1719 if ((in = fopen (infilename, "r")) == NULL)
1720 adios (infilename, "unable to open for reading");
1721
1722 /* Initialize md5 context */
1723 MD5Init (&mdContext);
1724
1725 /* calculate md5 message digest */
1726 if (asciiP) {
1727 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1728 char c, *cp;
1729
1730 cp = buffer + strlen (buffer) - 1;
1731 if ((c = *cp) == '\n')
1732 *cp = '\0';
1733
1734 MD5Update (&mdContext, (unsigned char *) buffer,
1735 (unsigned int) strlen (buffer));
1736
1737 if (c == '\n')
1738 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1739 }
1740 } else {
1741 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1742 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1743 }
1744
1745 /* md5 finalization. Write digest and zero md5 context */
1746 MD5Final (digest, &mdContext);
1747
1748 /* close content */
1749 fclose (in);
1750
1751 /* print debugging info */
1752 if (debugsw) {
1753 unsigned char *ep;
1754
1755 fprintf (stderr, "MD5 digest=");
1756 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1757 dp < ep; dp++)
1758 fprintf (stderr, "%02x", *dp & 0xff);
1759 fprintf (stderr, "\n");
1760 }
1761
1762 /* encode the digest using base64 */
1763 for (dp = digest, op = (char *) outbuf,
1764 cc = sizeof(digest) / sizeof(digest[0]);
1765 cc > 0; cc -= 3, op += 4) {
1766 unsigned long bits;
1767 char *bp;
1768
1769 bits = (*dp++ & 0xff) << 16;
1770 if (cc > 1) {
1771 bits |= (*dp++ & 0xff) << 8;
1772 if (cc > 2)
1773 bits |= *dp++ & 0xff;
1774 }
1775
1776 for (bp = op + 4; bp > op; bits >>= 6)
1777 *--bp = nib2b64[bits & 0x3f];
1778 if (cc < 3) {
1779 *(op + 3) = '=';
1780 if (cc < 2)
1781 *(op + 2) = '=';
1782 }
1783 }
1784
1785 /* null terminate string */
1786 outbuf[24] = '\0';
1787
1788 /* now make copy and return string */
1789 vp = concat (" ", outbuf, "\n", NULL);
1790 return vp;
1791 }
1792
1793 /*
1794 * Set things up for the content structure for file "filename" that
1795 * we want to attach
1796 */
1797
1798 static void
1799 setup_attach_content(CT ct, char *filename)
1800 {
1801 char *type, **ap, **ep, *simplename = r1bindex(filename, '/');
1802 struct str2init *s2i;
1803
1804 if (! (type = mime_type(filename))) {
1805 adios(NULL, "Unable to determine MIME type of \"%s\"", filename);
1806 }
1807
1808 /*
1809 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1810 * since we're just feeding it a MIME type we have to add those ourself.
1811 * Map that to a valid content-type label and call any initialization
1812 * function.
1813 */
1814
1815 if (get_ctinfo(type, ct, 0) == NOTOK)
1816 done(1);
1817
1818 free(type);
1819
1820 for (s2i = str2cts; s2i->si_key; s2i++)
1821 if (strcasecmp(ct->c_ctinfo.ci_type, s2i->si_key) == 0)
1822 break;
1823 if (!s2i->si_key && !uprf(ct->c_ctinfo.ci_type, "X-"))
1824 s2i++;
1825
1826 /*
1827 * Make sure the type isn't incompatible with what we can handle
1828 */
1829
1830 switch (ct->c_type = s2i->si_val) {
1831 case CT_MULTIPART:
1832 adios (NULL, "multipart types must be specified by mhbuild directives");
1833 /* NOTREACHED */
1834
1835 case CT_MESSAGE:
1836 if (strcasecmp(ct->c_ctinfo.ci_subtype, "partial") == 0)
1837 adios(NULL, "Sorry, %s/%s isn't supported", ct->c_ctinfo.ci_type,
1838 ct->c_ctinfo.ci_subtype);
1839 if (strcasecmp(ct->c_ctinfo.ci_subtype, "external-body") == 0)
1840 adios(NULL, "external-body messages must be specified "
1841 "by mhbuild directives");
1842 /* Fall through */
1843
1844 default:
1845 /*
1846 * This sets the subtype, if it's significant
1847 */
1848 if ((ct->c_ctinitfnx = s2i->si_init))
1849 (*ct->c_ctinitfnx)(ct);
1850 break;
1851 }
1852
1853 /*
1854 * Feed in a few attributes; specifically, the name attribute, the
1855 * content-description, and the content-disposition.
1856 */
1857
1858 for (ap = ct->c_ctinfo.ci_attrs, ep = ct->c_ctinfo.ci_values; *ap;
1859 ap++, ep++) {
1860 if (strcasecmp(*ap, "name") == 0) {
1861 if (*ep)
1862 free(*ep);
1863 *ep = getcpy(simplename);
1864 break;
1865 }
1866 }
1867
1868 if (*ap == NULL) {
1869 *ap = getcpy("name");
1870 *ep = getcpy(simplename);
1871 }
1872
1873 ct->c_descr = getcpy(simplename);
1874 ct->c_descr = add("\n", ct->c_descr);
1875 ct->c_cefile.ce_file = getcpy(filename);
1876
1877 /*
1878 * If it's a text/calendar, we need to make sure it's an inline,
1879 * otherwise it won't work with some calendar programs. Otherwise
1880 * assume attachment
1881 */
1882
1883 if (strcasecmp(ct->c_ctinfo.ci_type, "text") == 0 &&
1884 strcasecmp(ct->c_ctinfo.ci_subtype, "calendar") == 0) {
1885 ct->c_dispo = getcpy("inline; filename=\"");
1886 } else {
1887 ct->c_dispo = getcpy("attachment; filename=\"");
1888 }
1889
1890 ct->c_dispo = add(simplename, ct->c_dispo);
1891 ct->c_dispo = add("\"\n", ct->c_dispo);
1892 }