fixhead(): use ndup()
[s-mailx.git] / send.c
blob53a3860078575f4967e50f0dee2a50176874b762
1 /*
2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef lint
41 #ifdef DOSCCS
42 static char sccsid[] = "@(#)send.c 2.86 (gritter) 2/4/08";
43 #endif
44 #endif /* not lint */
46 #include "rcv.h"
47 #include "extern.h"
48 #include <time.h>
49 #include <unistd.h>
50 #include <sys/stat.h>
53 * Mail -- a mail program
55 * Mail to mail folders and displays.
58 /* TODO longjmp() globbering as in cmd1.c and cmd3.c (see there)
59 * TODO Problem: Popen doesn't encapsulate all cases of open failures,
60 * TODO may leave child running if fdopen() fails! */
62 enum parseflags {
63 PARSE_DEFAULT = 0,
64 PARSE_DECRYPT = 01,
65 PARSE_PARTS = 02
68 static void onpipe(int signo);
69 extern void brokpipe(int signo);
70 static int sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
71 struct ignoretab *doign, char *prefix, size_t prefixlen,
72 enum sendaction action, off_t *stats, int level);
73 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
74 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
75 enum parseflags pf, int level);
76 static void parsemultipart(struct message *zmp, struct mimepart *ip,
77 enum parseflags pf, int level);
78 static void newpart(struct mimepart *ip, struct mimepart **np, off_t offs,
79 int *part);
80 static void endpart(struct mimepart **np, off_t xoffs, long lines);
81 static void parse822(struct message *zmp, struct mimepart *ip,
82 enum parseflags pf, int level);
83 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
84 enum parseflags pf, int level);
85 static size_t out(char *buf, size_t len, FILE *fp,
86 enum conversion convert, enum sendaction action,
87 char *prefix, size_t prefixlen, off_t *stats,
88 char **restp, size_t *restsizep);
89 static void addstats(off_t *stats, off_t lines, off_t bytes);
90 static FILE *newfile(struct mimepart *ip, int *ispipe,
91 sighandler_type *oldpipe);
92 static char *getpipecmd(char *content);
93 static FILE *getpipefile(char *cmd, FILE **qbuf, int quote);
94 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
95 char *prefix, size_t prefixlen, off_t *stats);
96 static void statusput(const struct message *mp, FILE *obuf,
97 char *prefix, off_t *stats);
98 static void xstatusput(const struct message *mp, FILE *obuf,
99 char *prefix, off_t *stats);
100 static void put_from_(FILE *fp, struct mimepart *ip);
102 static sigjmp_buf pipejmp;
104 /*ARGSUSED*/
105 static void
106 onpipe(int signo)
108 (void)signo;
109 siglongjmp(pipejmp, 1);
113 * Send message described by the passed pointer to the
114 * passed output buffer. Return -1 on error.
115 * Adjust the status: field if need be.
116 * If doign is given, suppress ignored header fields.
117 * prefix is a string to prepend to each output line.
118 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
119 * stats[0] is line count, stats[1] is character count. stats may be NULL.
120 * Note that stats[0] is valid for SEND_MBOX only.
123 send(struct message *mp, FILE *obuf, struct ignoretab *doign,
124 char *prefix, enum sendaction action, off_t *stats)
126 size_t count;
127 FILE *ibuf;
128 size_t prefixlen, sz;
129 int c;
130 enum parseflags pf;
131 struct mimepart *ip;
132 char *cp, *cp2;
134 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
135 did_print_dot = 1;
136 if (stats)
137 stats[0] = stats[1] = 0;
139 * Compute the prefix string, without trailing whitespace
141 if (prefix != NULL) {
142 cp2 = 0;
143 for (cp = prefix; *cp; cp++)
144 if (!blankchar(*cp & 0377))
145 cp2 = cp;
146 prefixlen = cp2 == 0 ? 0 : cp2 - prefix + 1;
147 } else
148 prefixlen = 0;
150 * First line is the From_ line, so no headers there to worry about.
152 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
153 return -1;
154 count = mp->m_size;
155 sz = 0;
156 if (mp->m_flag & MNOFROM) {
157 if (doign != allignore && doign != fwdignore &&
158 action != SEND_RFC822)
159 sz = fprintf(obuf, "%sFrom %s %s\n",
160 prefix ? prefix : "",
161 fakefrom(mp), fakedate(mp->m_time));
162 } else {
163 if (prefix && doign != allignore && doign != fwdignore &&
164 action != SEND_RFC822) {
165 fputs(prefix, obuf);
166 sz += strlen(prefix);
168 while (count && (c = getc(ibuf)) != EOF) {
169 if (doign != allignore && doign != fwdignore &&
170 action != SEND_RFC822) {
171 putc(c, obuf);
172 sz++;
174 count--;
175 if (c == '\n')
176 break;
179 if (sz)
180 addstats(stats, 1, sz);
181 pf = 0;
182 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
183 pf |= PARSE_DECRYPT|PARSE_PARTS;
184 if ((ip = parsemsg(mp, pf)) == NULL)
185 return -1;
186 return sendpart(mp, ip, obuf, doign, prefix, prefixlen, action, stats,
190 static int
191 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
192 struct ignoretab *doign, char *prefix, size_t prefixlen,
193 enum sendaction action, off_t *stats, int level)
195 char *line = NULL;
196 size_t linesize = 0, linelen, count, len;
197 int dostat, infld = 0, ignoring = 1, isenc;
198 char *cp, *cp2, *start;
199 int c;
200 struct mimepart *np;
201 FILE *ibuf = NULL, *pbuf = obuf, *qbuf = obuf, *origobuf = obuf;
202 char *tcs, *pipecmd = NULL;
203 enum conversion convert;
204 sighandler_type oldpipe = SIG_DFL;
205 int rt = 0;
206 long lineno = 0;
207 int ispipe = 0;
208 char *rest;
209 size_t restsize;
210 int eof;
212 (void)&ibuf;
213 (void)&pbuf;
214 (void)&convert;
215 (void)&oldpipe;
216 (void)&rt;
217 (void)&obuf;
218 (void)&stats;
219 (void)&action;
220 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
221 action != SEND_MBOX && action != SEND_RFC822 &&
222 action != SEND_SHOW)
223 goto skip;
224 dostat = 0;
225 if (level == 0) {
226 if (doign != NULL) {
227 if (!is_ign("status", 6, doign))
228 dostat |= 1;
229 if (!is_ign("x-status", 8, doign))
230 dostat |= 2;
231 } else
232 dostat = 3;
234 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
235 return -1;
236 count = ip->m_size;
237 if (ip->m_mimecontent == MIME_DISCARD)
238 goto skip;
239 if ((ip->m_flag&MNOFROM) == 0)
240 while (count && (c = getc(ibuf)) != EOF) {
241 count--;
242 if (c == '\n')
243 break;
245 isenc = 0;
246 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
247 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
248 action == SEND_TOSRCH || action == SEND_TOFLTR ?
249 CONV_FROMHDR : CONV_NONE;
250 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
251 lineno++;
252 if (line[0] == '\n') {
254 * If line is blank, we've reached end of
255 * headers, so force out status: field
256 * and note that we are no longer in header
257 * fields
259 if (dostat & 1)
260 statusput(zmp, obuf, prefix, stats);
261 if (dostat & 2)
262 xstatusput(zmp, obuf, prefix, stats);
263 if (doign != allignore)
264 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
265 prefix, prefixlen, stats,
266 NULL, NULL);
267 break;
269 isenc &= ~1;
270 if (infld && blankchar(line[0]&0377)) {
272 * If this line is a continuation (via space or tab)
273 * of a previous header field, determine if the start
274 * of the line is a MIME encoded word.
276 if (isenc & 2) {
277 for (cp = line; blankchar(*cp&0377); cp++);
278 if (cp > line && linelen - (cp - line) > 8 &&
279 cp[0] == '=' && cp[1] == '?')
280 isenc |= 1;
282 } else {
284 * Pick up the header field if we have one.
286 for (cp = line; (c = *cp&0377) && c != ':' &&
287 !spacechar(c); cp++);
288 cp2 = cp;
289 while (spacechar(*cp&0377))
290 cp++;
291 if (cp[0] != ':' && level == 0 && lineno == 1) {
293 * Not a header line, force out status:
294 * This happens in uucp style mail where
295 * there are no headers at all.
297 if (dostat & 1)
298 statusput(zmp, obuf, prefix, stats);
299 if (dostat & 2)
300 xstatusput(zmp, obuf, prefix, stats);
301 if (doign != allignore)
302 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
303 prefix, prefixlen, stats,
304 NULL, NULL);
305 break;
308 * If it is an ignored field and
309 * we care about such things, skip it.
311 c = *cp2;
312 *cp2 = 0; /* temporarily null terminate */
313 if (doign && is_ign(line, cp2 - line, doign))
314 ignoring = 1;
315 else if (asccasecmp(line, "status") == 0) {
317 * If the field is "status," go compute
318 * and print the real Status: field
320 if (dostat & 1) {
321 statusput(zmp, obuf, prefix, stats);
322 dostat &= ~1;
323 ignoring = 1;
325 } else if (asccasecmp(line, "x-status") == 0) {
327 * If the field is "status," go compute
328 * and print the real Status: field
330 if (dostat & 2) {
331 xstatusput(zmp, obuf, prefix, stats);
332 dostat &= ~2;
333 ignoring = 1;
335 } else
336 ignoring = 0;
337 *cp2 = c;
338 infld = 1;
341 * Determine if the end of the line is a MIME encoded word.
343 isenc &= ~2;
344 if (count && (c = getc(ibuf)) != EOF) {
345 if (blankchar(c)) {
346 if (linelen > 0 && line[linelen-1] == '\n')
347 cp = &line[linelen-2];
348 else
349 cp = &line[linelen-1];
350 while (cp >= line && whitechar(*cp&0377))
351 cp++;
352 if (cp - line > 8 && cp[0] == '=' &&
353 cp[-1] == '?')
354 isenc |= 2;
356 ungetc(c, ibuf);
358 if (!ignoring) {
359 start = line;
360 len = linelen;
361 if (action == SEND_TODISP ||
362 action == SEND_TODISP_ALL ||
363 action == SEND_QUOTE ||
364 action == SEND_QUOTE_ALL ||
365 action == SEND_TOSRCH ||
366 action == SEND_TOFLTR) {
368 * Strip blank characters if two MIME-encoded
369 * words follow on continuing lines.
371 if (isenc & 1)
372 while (len>0&&blankchar(*start&0377)) {
373 start++;
374 len--;
376 if (isenc & 2)
377 if (len > 0 && start[len-1] == '\n')
378 len--;
379 while (len > 0 && blankchar(start[len-1]&0377))
380 len--;
382 out(start, len, obuf, convert,
383 action, prefix, prefixlen, stats,
384 NULL, NULL);
385 if (ferror(obuf)) {
386 free(line);
387 return -1;
391 free(line);
392 line = NULL;
393 skip: switch (ip->m_mimecontent) {
394 case MIME_822:
395 switch (action) {
396 case SEND_TOFLTR:
397 putc('\0', obuf);
398 /*FALLTHRU*/
399 case SEND_TODISP:
400 case SEND_TODISP_ALL:
401 case SEND_QUOTE:
402 case SEND_QUOTE_ALL:
403 put_from_(obuf, ip->m_multipart);
404 /*FALLTHRU*/
405 case SEND_TOSRCH:
406 case SEND_DECRYPT:
407 goto multi;
408 case SEND_TOFILE:
409 case SEND_TOPIPE:
410 put_from_(obuf, ip->m_multipart);
411 /*FALLTHRU*/
412 case SEND_MBOX:
413 case SEND_RFC822:
414 case SEND_SHOW:
415 break;
417 break;
418 case MIME_TEXT_HTML:
419 if (action == SEND_TOFLTR)
420 putc('\b', obuf);
421 /*FALLTHRU*/
422 case MIME_TEXT:
423 case MIME_TEXT_PLAIN:
424 switch (action) {
425 case SEND_TODISP:
426 case SEND_TODISP_ALL:
427 case SEND_QUOTE:
428 case SEND_QUOTE_ALL:
429 pipecmd = getpipecmd(ip->m_ct_type_plain);
430 /*FALLTHRU*/
431 default:
432 break;
434 break;
435 case MIME_DISCARD:
436 if (action != SEND_DECRYPT)
437 return rt;
438 break;
439 case MIME_PKCS7:
440 if (action != SEND_MBOX && action != SEND_RFC822 &&
441 action != SEND_SHOW && ip->m_multipart)
442 goto multi;
443 /*FALLTHRU*/
444 default:
445 switch (action) {
446 case SEND_TODISP:
447 case SEND_TODISP_ALL:
448 case SEND_QUOTE:
449 case SEND_QUOTE_ALL:
450 if ((pipecmd = getpipecmd(ip->m_ct_type_plain)) != NULL)
451 break;
452 if (level == 0 && count) {
453 cp = "[Binary content]\n\n";
454 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
455 prefix, prefixlen, stats,
456 NULL, NULL);
458 /*FALLTHRU*/
459 case SEND_TOFLTR:
460 return rt;
461 case SEND_TOFILE:
462 case SEND_TOPIPE:
463 case SEND_TOSRCH:
464 case SEND_DECRYPT:
465 case SEND_MBOX:
466 case SEND_RFC822:
467 case SEND_SHOW:
468 break;
470 break;
471 case MIME_ALTERNATIVE:
472 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
473 value("print-alternatives") == NULL)
474 for (np = ip->m_multipart; np; np = np->m_nextpart)
475 if (np->m_mimecontent == MIME_TEXT_PLAIN) {
476 if (sendpart(zmp, np, obuf,
477 doign, prefix,
478 prefixlen,
479 action, stats,
480 level+1) < 0)
481 return -1;
482 return rt;
484 /*FALLTHRU*/
485 case MIME_MULTI:
486 case MIME_DIGEST:
487 switch (action) {
488 case SEND_TODISP:
489 case SEND_TODISP_ALL:
490 case SEND_QUOTE:
491 case SEND_QUOTE_ALL:
492 case SEND_TOFILE:
493 case SEND_TOPIPE:
494 case SEND_TOSRCH:
495 case SEND_TOFLTR:
496 case SEND_DECRYPT:
497 multi:
498 if ((action == SEND_TODISP ||
499 action == SEND_TODISP_ALL) &&
500 ip->m_multipart != NULL &&
501 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
502 ip->m_multipart->m_nextpart == NULL) {
503 cp = "[Missing multipart boundary - "
504 "use \"show\" to display "
505 "the raw message]\n\n";
506 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
507 prefix, prefixlen, stats,
508 NULL, NULL);
510 for (np = ip->m_multipart; np; np = np->m_nextpart) {
511 if (np->m_mimecontent == MIME_DISCARD &&
512 action != SEND_DECRYPT)
513 continue;
514 switch (action) {
515 case SEND_TOFILE:
516 if (np->m_partstring &&
517 strcmp(np->m_partstring,
518 "1") == 0)
519 break;
520 stats = NULL;
521 if ((obuf = newfile(np, &ispipe,
522 &oldpipe)) == NULL)
523 continue;
524 break;
525 case SEND_TODISP:
526 case SEND_TODISP_ALL:
527 case SEND_QUOTE_ALL:
528 if ((ip->m_mimecontent == MIME_MULTI ||
529 ip->m_mimecontent ==
530 MIME_ALTERNATIVE ||
531 ip->m_mimecontent ==
532 MIME_DIGEST) &&
533 np->m_partstring) {
534 len = strlen(np->m_partstring) +
536 cp = ac_alloc(len);
537 snprintf(cp, len,
538 "%sPart %s:\n", level ||
539 strcmp(np->m_partstring,
540 "1") ?
541 "\n" : "",
542 np->m_partstring);
543 out(cp, strlen(cp), obuf,
544 CONV_NONE, SEND_MBOX,
545 prefix, prefixlen,
546 stats,
547 NULL, NULL);
548 ac_free(cp);
550 break;
551 case SEND_TOFLTR:
552 putc('\0', obuf);
553 /*FALLTHRU*/
554 case SEND_MBOX:
555 case SEND_RFC822:
556 case SEND_SHOW:
557 case SEND_TOSRCH:
558 case SEND_QUOTE:
559 case SEND_DECRYPT:
560 case SEND_TOPIPE:
561 break;
563 if (sendpart(zmp, np, obuf,
564 doign, prefix, prefixlen,
565 action, stats, level+1) < 0)
566 rt = -1;
567 else if (action == SEND_QUOTE)
568 break;
569 if (action == SEND_TOFILE && obuf != origobuf) {
570 if (ispipe == 0)
571 Fclose(obuf);
572 else {
573 safe_signal(SIGPIPE, SIG_IGN);
574 Pclose(obuf);
575 safe_signal(SIGPIPE, oldpipe);
579 return rt;
580 case SEND_MBOX:
581 case SEND_RFC822:
582 case SEND_SHOW:
583 break;
587 * Copy out message body
589 if (doign == allignore && level == 0) /* skip final blank line */
590 count--;
591 switch (ip->m_mimeenc) {
592 case MIME_BIN:
593 if (stats)
594 stats[0] = -1;
595 /*FALLTHRU*/
596 case MIME_7B:
597 case MIME_8B:
598 convert = CONV_NONE;
599 break;
600 case MIME_QP:
601 convert = CONV_FROMQP;
602 break;
603 case MIME_B64:
604 switch (ip->m_mimecontent) {
605 case MIME_TEXT:
606 case MIME_TEXT_PLAIN:
607 case MIME_TEXT_HTML:
608 convert = CONV_FROMB64_T;
609 break;
610 default:
611 convert = CONV_FROMB64;
613 break;
614 default:
615 convert = CONV_NONE;
617 if (action == SEND_DECRYPT || action == SEND_MBOX ||
618 action == SEND_RFC822 || action == SEND_SHOW)
619 convert = CONV_NONE;
620 tcs = gettcharset();
621 #ifdef HAVE_ICONV
622 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
623 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
624 action == SEND_TOSRCH) &&
625 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
626 ip->m_mimecontent == MIME_TEXT_HTML ||
627 ip->m_mimecontent == MIME_TEXT)) {
628 if (iconvd != (iconv_t)-1)
629 iconv_close(iconvd);
630 if (asccasecmp(tcs, ip->m_charset) &&
631 asccasecmp(us_ascii, ip->m_charset))
632 iconvd = iconv_open_ft(tcs, ip->m_charset);
633 else
634 iconvd = (iconv_t)-1;
636 #endif /* HAVE_ICONV */
637 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
638 action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
639 pipecmd != NULL) {
640 qbuf = obuf;
641 pbuf = getpipefile(pipecmd, &qbuf,
642 action == SEND_QUOTE || action == SEND_QUOTE_ALL);
643 action = SEND_TOPIPE;
644 if (pbuf != qbuf) {
645 oldpipe = safe_signal(SIGPIPE, onpipe);
646 if (sigsetjmp(pipejmp, 1))
647 goto end;
649 } else
650 pbuf = qbuf = obuf;
651 eof = 0;
652 while (!eof && foldergets(&line, &linesize, &count, &linelen, ibuf)) {
653 lineno++;
654 while (convert == CONV_FROMQP && linelen >= 2 &&
655 line[linelen-2] == '=') {
656 char *line2;
657 size_t linesize2, linelen2;
658 nextl:
659 line2 = NULL;
660 linesize2 = 0;
661 if (foldergets(&line2, &linesize2, &count, &linelen2,
662 ibuf) == NULL) {
663 eof = 1;
664 break;
666 if (linelen + linelen2 + 1 > linesize)
667 line = srealloc(line, linesize = linelen +
668 linelen2 + 1);
669 memcpy(&line[linelen], line2, linelen2+1);
670 linelen += linelen2;
671 free(line2);
673 rest = NULL;
674 restsize = 0;
675 out(line, linelen, pbuf, convert, action,
676 pbuf == origobuf ? prefix : NULL,
677 pbuf == origobuf ? prefixlen : 0,
678 pbuf == origobuf ? stats : NULL,
679 eof ? NULL : &rest, eof ? NULL : &restsize);
680 if (ferror(pbuf)) {
681 rt = -1;
682 break;
684 if (restsize) {
685 if (line != rest)
686 memmove(line, rest, restsize);
687 linelen = restsize;
688 goto nextl;
691 end: free(line);
692 if (pbuf != qbuf) {
693 safe_signal(SIGPIPE, SIG_IGN);
694 Pclose(pbuf);
695 safe_signal(SIGPIPE, oldpipe);
696 if (qbuf != obuf)
697 pipecpy(qbuf, obuf, origobuf, prefix, prefixlen, stats);
699 #ifdef HAVE_ICONV
700 if (iconvd != (iconv_t)-1) {
701 iconv_close(iconvd);
702 iconvd = (iconv_t)-1;
704 #endif
705 return rt;
708 static struct mimepart *
709 parsemsg(struct message *mp, enum parseflags pf)
711 struct mimepart *ip;
713 ip = csalloc(1, sizeof *ip);
714 ip->m_flag = mp->m_flag;
715 ip->m_have = mp->m_have;
716 ip->m_block = mp->m_block;
717 ip->m_offset = mp->m_offset;
718 ip->m_size = mp->m_size;
719 ip->m_xsize = mp->m_xsize;
720 ip->m_lines = mp->m_lines;
721 ip->m_xlines = mp->m_lines;
722 if (parsepart(mp, ip, pf, 0) != OKAY)
723 return NULL;
724 return ip;
727 static enum okay
728 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
729 int level)
731 char *cp;
733 ip->m_ct_type = hfield("content-type", (struct message *)ip);
734 if (ip->m_ct_type != NULL) {
735 ip->m_ct_type_plain = savestr(ip->m_ct_type);
736 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
737 *cp = '\0';
738 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
739 ip->m_ct_type_plain = "message/rfc822";
740 else
741 ip->m_ct_type_plain = "text/plain";
742 ip->m_mimecontent = mime_getcontent(ip->m_ct_type_plain);
743 if (ip->m_ct_type)
744 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
745 if (ip->m_charset == NULL)
746 ip->m_charset = us_ascii;
747 ip->m_ct_transfer_enc = hfield("content-transfer-encoding",
748 (struct message *)ip);
749 ip->m_mimeenc = ip->m_ct_transfer_enc ?
750 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
751 if ((cp = hfield("content-disposition", (struct message *)ip)) == 0 ||
752 (ip->m_filename = mime_getparam("filename", cp)) == 0)
753 if (ip->m_ct_type != NULL)
754 ip->m_filename = mime_getparam("name", ip->m_ct_type);
755 if (pf & PARSE_PARTS) {
756 if (level > 9999) {
757 fprintf(stderr, "MIME content too deeply nested.\n");
758 return STOP;
760 switch (ip->m_mimecontent) {
761 case MIME_PKCS7:
762 if (pf & PARSE_DECRYPT) {
763 parsepkcs7(zmp, ip, pf, level);
764 break;
766 /*FALLTHRU*/
767 default:
768 break;
769 case MIME_MULTI:
770 case MIME_ALTERNATIVE:
771 case MIME_DIGEST:
772 parsemultipart(zmp, ip, pf, level);
773 break;
774 case MIME_822:
775 parse822(zmp, ip, pf, level);
776 break;
779 return OKAY;
782 static void
783 parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
784 int level)
786 char *boundary;
787 char *line = NULL;
788 size_t linesize = 0, linelen, count, boundlen;
789 FILE *ibuf;
790 struct mimepart *np = NULL;
791 off_t offs;
792 int part = 0;
793 long lines = 0;
795 if ((boundary = mime_getboundary(ip->m_ct_type)) == NULL)
796 return;
797 boundlen = strlen(boundary);
798 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
799 return;
800 count = ip->m_size;
801 while (foldergets(&line, &linesize, &count, &linelen, ibuf))
802 if (line[0] == '\n')
803 break;
804 offs = ftell(ibuf);
805 newpart(ip, &np, offs, NULL);
806 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
807 if ((lines > 0 || part == 0) && linelen >= boundlen + 1 &&
808 strncmp(line, boundary, boundlen) == 0) {
809 if (line[boundlen] == '\n') {
810 offs = ftell(ibuf);
811 if (part != 0) {
812 endpart(&np, offs-boundlen-2, lines);
813 newpart(ip, &np, offs-boundlen-2, NULL);
815 endpart(&np, offs, 2);
816 newpart(ip, &np, offs, &part);
817 lines = 0;
818 } else if (line[boundlen] == '-' &&
819 line[boundlen+1] == '-' &&
820 line[boundlen+2] == '\n') {
821 offs = ftell(ibuf);
822 if (part != 0) {
823 endpart(&np, offs-boundlen-4, lines);
824 newpart(ip, &np, offs-boundlen-4, NULL);
826 endpart(&np, offs+count, 2);
827 break;
828 } else
829 lines++;
830 } else
831 lines++;
833 if (np) {
834 offs = ftell(ibuf);
835 endpart(&np, offs, lines);
837 for (np = ip->m_multipart; np; np = np->m_nextpart)
838 if (np->m_mimecontent != MIME_DISCARD)
839 parsepart(zmp, np, pf, level+1);
840 free(line);
843 static void
844 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
846 struct mimepart *pp;
847 size_t sz;
849 *np = csalloc(1, sizeof **np);
850 (*np)->m_flag = MNOFROM;
851 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
852 (*np)->m_block = mailx_blockof(offs);
853 (*np)->m_offset = mailx_offsetof(offs);
854 if (part) {
855 (*part)++;
856 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
857 sz += 20;
858 (*np)->m_partstring = salloc(sz);
859 if (ip->m_partstring)
860 snprintf((*np)->m_partstring, sz, "%s.%u",
861 ip->m_partstring, *part);
862 else
863 snprintf((*np)->m_partstring, sz, "%u", *part);
864 } else
865 (*np)->m_mimecontent = MIME_DISCARD;
866 (*np)->m_parent = ip;
867 if (ip->m_multipart) {
868 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
869 pp->m_nextpart = *np;
870 } else
871 ip->m_multipart = *np;
874 static void
875 endpart(struct mimepart **np, off_t xoffs, long lines)
877 off_t offs;
879 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
880 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
881 (*np)->m_lines = (*np)->m_xlines = lines;
882 *np = NULL;
885 static void
886 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
887 int level)
889 int c, lastc = '\n';
890 size_t count;
891 FILE *ibuf;
892 off_t offs;
893 struct mimepart *np;
894 long lines;
896 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
897 return;
898 count = ip->m_size;
899 lines = ip->m_lines;
900 while (count && ((c = getc(ibuf)) != EOF)) {
901 count--;
902 if (c == '\n') {
903 lines--;
904 if (lastc == '\n')
905 break;
907 lastc = c;
909 offs = ftell(ibuf);
910 np = csalloc(1, sizeof *np);
911 np->m_flag = MNOFROM;
912 np->m_have = HAVE_HEADER|HAVE_BODY;
913 np->m_block = mailx_blockof(offs);
914 np->m_offset = mailx_offsetof(offs);
915 np->m_size = np->m_xsize = count;
916 np->m_lines = np->m_xlines = lines;
917 np->m_partstring = ip->m_partstring;
918 np->m_parent = ip;
919 ip->m_multipart = np;
920 substdate((struct message *)np);
921 np->m_from = fakefrom((struct message *)np);
922 parsepart(zmp, np, pf, level+1);
925 static void
926 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
927 int level)
929 struct message m, *xmp;
930 struct mimepart *np;
931 char *to, *cc;
933 memcpy(&m, ip, sizeof m);
934 to = hfield("to", zmp);
935 cc = hfield("cc", zmp);
936 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
937 np = csalloc(1, sizeof *np);
938 np->m_flag = xmp->m_flag;
939 np->m_have = xmp->m_have;
940 np->m_block = xmp->m_block;
941 np->m_offset = xmp->m_offset;
942 np->m_size = xmp->m_size;
943 np->m_xsize = xmp->m_xsize;
944 np->m_lines = xmp->m_lines;
945 np->m_xlines = xmp->m_xlines;
946 np->m_partstring = ip->m_partstring;
947 if (parsepart(zmp, np, pf, level+1) == OKAY) {
948 np->m_parent = ip;
949 ip->m_multipart = np;
954 static size_t
955 out(char *buf, size_t len, FILE *fp,
956 enum conversion convert, enum sendaction action,
957 char *prefix, size_t prefixlen, off_t *stats,
958 char **restp, size_t *restsizep)
960 size_t sz, n;
961 char *cp;
962 long lines;
964 sz = 0;
965 if (action == SEND_MBOX || action == SEND_DECRYPT) {
966 cp = buf;
967 n = len;
968 while (n && cp[0] == '>')
969 cp++, n--;
970 if (n >= 5 && cp[0] == 'F' && cp[1] == 'r' && cp[2] == 'o' &&
971 cp[3] == 'm' && cp[4] == ' ') {
972 putc('>', fp);
973 sz++;
976 sz += mime_write(buf, len, fp,
977 action == SEND_MBOX ? CONV_NONE : convert,
978 action == SEND_TODISP || action == SEND_TODISP_ALL ||
979 action == SEND_QUOTE ||
980 action == SEND_QUOTE_ALL ?
981 TD_ISPR|TD_ICONV :
982 action == SEND_TOSRCH || action == SEND_TOPIPE ?
983 TD_ICONV :
984 action == SEND_TOFLTR ?
985 TD_DELCTRL :
986 action == SEND_SHOW ?
987 TD_ISPR : TD_NONE,
988 prefix, prefixlen,
989 restp, restsizep);
990 lines = 0;
991 if (stats && stats[0] != -1) {
992 for (cp = buf; cp < &buf[sz]; cp++)
993 if (*cp == '\n')
994 lines++;
996 addstats(stats, lines, sz);
997 return sz;
1000 static void
1001 addstats(off_t *stats, off_t lines, off_t bytes)
1003 if (stats) {
1004 if (stats[0] >= 0)
1005 stats[0] += lines;
1006 stats[1] += bytes;
1011 * Get a file for an attachment.
1013 static FILE *
1014 newfile(struct mimepart *ip, int *ispipe, sighandler_type *oldpipe)
1016 char *f = ip->m_filename;
1017 struct str in, out;
1018 FILE *fp;
1020 *ispipe = 0;
1021 if (f != NULL && f != (char *)-1) {
1022 in.s = f;
1023 in.l = strlen(f);
1024 mime_fromhdr(&in, &out, TD_ISPR);
1025 memcpy(f, out.s, out.l);
1026 *(f + out.l) = '\0';
1027 free(out.s);
1029 if (value("interactive") != NULL) {
1030 printf("Enter filename for part %s (%s)",
1031 ip->m_partstring ? ip->m_partstring : "?",
1032 ip->m_ct_type_plain);
1033 f = readtty(catgets(catd, CATSET, 173, ": "),
1034 f != (char *)-1 ? f : NULL);
1036 if (f == NULL || f == (char *)-1)
1037 return NULL;
1039 if (*f == '|') {
1040 char *cp;
1041 cp = value("SHELL");
1042 if (cp == NULL)
1043 cp = SHELL;
1044 fp = Popen(f+1, "w", cp, 1);
1045 if (fp == NULL) {
1046 perror(f);
1047 fp = stdout;
1048 } else {
1049 *oldpipe = safe_signal(SIGPIPE, brokpipe);
1050 *ispipe = 1;
1052 } else {
1053 if ((fp = Fopen(f, "w")) == NULL)
1054 fprintf(stderr, "Cannot open %s\n", f);
1056 return fp;
1059 static char *
1060 getpipecmd(char *content)
1062 char *penv, *cp, *cq, *pipecmd;
1064 if (content == NULL)
1065 return NULL;
1066 penv = ac_alloc(strlen(content) + 6);
1067 strcpy(penv, "pipe-");
1068 cp = &penv[5];
1069 cq = content;
1071 *cp++ = lowerconv(*cq & 0377);
1072 while (*cq++);
1073 pipecmd = value(penv);
1074 ac_free(penv);
1075 return pipecmd;
1078 static FILE *
1079 getpipefile(char *pipecmd, FILE **qbuf, int quote)
1081 char *shell;
1082 FILE *rbuf = *qbuf;
1084 if (pipecmd != NULL) {
1085 if (quote) {
1086 char *tempPipe;
1088 if ((*qbuf = Ftemp(&tempPipe, "Rp", "w+", 0600, 1))
1089 == NULL) {
1090 perror(catgets(catd, CATSET, 173, "tmpfile"));
1091 *qbuf = rbuf;
1093 unlink(tempPipe);
1094 Ftfree(&tempPipe);
1096 if ((shell = value("SHELL")) == NULL)
1097 shell = SHELL;
1098 if ((rbuf = Popen(pipecmd, "W", shell, fileno(*qbuf)))
1099 == NULL) {
1100 perror(pipecmd);
1101 } else {
1102 fflush(*qbuf);
1103 if (*qbuf != stdout)
1104 fflush(stdout);
1107 return rbuf;
1110 static void
1111 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
1112 char *prefix, size_t prefixlen, off_t *stats)
1114 char *line = NULL;
1115 size_t linesize = 0, linelen, sz, count;
1117 fflush(pipebuf);
1118 rewind(pipebuf);
1119 count = fsize(pipebuf);
1120 while (fgetline(&line, &linesize, &count, &linelen, pipebuf, 0)
1121 != NULL) {
1122 sz = prefixwrite(line, sizeof *line, linelen, outbuf,
1123 prefix, prefixlen);
1124 if (outbuf == origobuf)
1125 addstats(stats, 1, sz);
1127 if (line)
1128 free(line);
1129 fclose(pipebuf);
1133 * Output a reasonable looking status field.
1135 static void
1136 statusput(const struct message *mp, FILE *obuf, char *prefix, off_t *stats)
1138 char statout[3];
1139 char *cp = statout;
1141 if (mp->m_flag & MREAD)
1142 *cp++ = 'R';
1143 if ((mp->m_flag & MNEW) == 0)
1144 *cp++ = 'O';
1145 *cp = 0;
1146 if (statout[0])
1147 fprintf(obuf, "%sStatus: %s\n",
1148 prefix == NULL ? "" : prefix, statout);
1149 addstats(stats, 1, (prefix ? strlen(prefix) : 0) + 9 + cp - statout);
1152 static void
1153 xstatusput(const struct message *mp, FILE *obuf, char *prefix, off_t *stats)
1155 char xstatout[4];
1156 char *xp = xstatout;
1158 if (mp->m_flag & MFLAGGED)
1159 *xp++ = 'F';
1160 if (mp->m_flag & MANSWERED)
1161 *xp++ = 'A';
1162 if (mp->m_flag & MDRAFTED)
1163 *xp++ = 'T';
1164 *xp = 0;
1165 if (xstatout[0])
1166 fprintf(obuf, "%sX-Status: %s\n",
1167 prefix == NULL ? "" : prefix, xstatout);
1168 addstats(stats, 1, (prefix ? strlen(prefix) : 0) + 11 + xp - xstatout);
1171 static void
1172 put_from_(FILE *fp, struct mimepart *ip)
1174 time_t now;
1176 if (ip && ip->m_from)
1177 fprintf(fp, "From %s %s\n", ip->m_from, fakedate(ip->m_time));
1178 else {
1179 time(&now);
1180 fprintf(fp, "From %s %s", myname, ctime(&now));
1185 * This is fgetline for mbox lines.
1187 char *
1188 foldergets(char **s, size_t *size, size_t *count, size_t *llen, FILE *stream)
1190 char *p, *top;
1192 if ((p = fgetline(s, size, count, llen, stream, 0)) == NULL)
1193 return NULL;
1194 if (*p == '>') {
1195 p++;
1196 while (*p == '>') p++;
1197 if (strncmp(p, "From ", 5) == 0) {
1198 /* we got a masked From line */
1199 top = &(*s)[*llen];
1200 p = *s;
1202 p[0] = p[1];
1203 while (++p < top);
1204 (*llen)--;
1207 return *s;