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