xref: /netbsd-src/usr.bin/mail/mime_attach.c (revision 684b182f813418fbf004008a32d738baa45c875a)
1*684b182fSmrg /*	$NetBSD: mime_attach.c,v 1.20 2019/02/01 08:29:04 mrg Exp $	*/
28207b28aSchristos 
38207b28aSchristos /*-
48207b28aSchristos  * Copyright (c) 2006 The NetBSD Foundation, Inc.
58207b28aSchristos  * All rights reserved.
68207b28aSchristos  *
78207b28aSchristos  * This code is derived from software contributed to The NetBSD Foundation
88207b28aSchristos  * by Anon Ymous.
98207b28aSchristos  *
108207b28aSchristos  * Redistribution and use in source and binary forms, with or without
118207b28aSchristos  * modification, are permitted provided that the following conditions
128207b28aSchristos  * are met:
138207b28aSchristos  * 1. Redistributions of source code must retain the above copyright
148207b28aSchristos  *    notice, this list of conditions and the following disclaimer.
158207b28aSchristos  * 2. Redistributions in binary form must reproduce the above copyright
168207b28aSchristos  *    notice, this list of conditions and the following disclaimer in the
178207b28aSchristos  *    documentation and/or other materials provided with the distribution.
188207b28aSchristos  *
198207b28aSchristos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
208207b28aSchristos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
218207b28aSchristos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
228207b28aSchristos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
238207b28aSchristos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
248207b28aSchristos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
258207b28aSchristos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
268207b28aSchristos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
278207b28aSchristos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
288207b28aSchristos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
298207b28aSchristos  * POSSIBILITY OF SUCH DAMAGE.
308207b28aSchristos  */
318207b28aSchristos 
328207b28aSchristos #ifdef MIME_SUPPORT
338207b28aSchristos 
348207b28aSchristos #include <sys/cdefs.h>
358207b28aSchristos #ifndef __lint__
36*684b182fSmrg __RCSID("$NetBSD: mime_attach.c,v 1.20 2019/02/01 08:29:04 mrg Exp $");
378207b28aSchristos #endif /* not __lint__ */
388207b28aSchristos 
398207b28aSchristos #include <assert.h>
408207b28aSchristos #include <err.h>
418207b28aSchristos #include <fcntl.h>
428207b28aSchristos #include <libgen.h>
438207b28aSchristos #include <magic.h>
448207b28aSchristos #include <signal.h>
458207b28aSchristos #include <stdio.h>
468207b28aSchristos #include <stdlib.h>
478207b28aSchristos #include <string.h>
488207b28aSchristos #include <unistd.h>
498207b28aSchristos #include <util.h>
508207b28aSchristos 
518207b28aSchristos #include "def.h"
528207b28aSchristos #include "extern.h"
538207b28aSchristos #ifdef USE_EDITLINE
548207b28aSchristos #include "complete.h"
558207b28aSchristos #endif
568207b28aSchristos #ifdef MIME_SUPPORT
578207b28aSchristos #include "mime.h"
588207b28aSchristos #include "mime_codecs.h"
598207b28aSchristos #include "mime_child.h"
608207b28aSchristos #endif
618207b28aSchristos #include "glob.h"
62ca13337dSchristos #include "sig.h"
638207b28aSchristos 
648207b28aSchristos #if 0
658207b28aSchristos /*
66349d9781Schristos  * XXX - This block is for debugging only and eventually should go away.
67349d9781Schristos  */
68349d9781Schristos # define SHOW_ALIST(a,b) show_alist(a,b)
69349d9781Schristos static void
70349d9781Schristos show_alist(struct attachment *alist, struct attachment *ap)
71349d9781Schristos {
72349d9781Schristos 	(void)printf("alist=%p ap=%p\n", alist, ap);
73349d9781Schristos 	for (ap = alist; ap; ap = ap->a_flink) {
74349d9781Schristos 		(void)printf("ap=%p ap->a_flink=%p ap->a_blink=%p ap->a_name=%s\n",
75349d9781Schristos 		    ap, ap->a_flink, ap->a_blink, ap->a_name ? ap->a_name : "<null>");
76349d9781Schristos 	}
77349d9781Schristos }
78349d9781Schristos #else
79349d9781Schristos # define SHOW_ALIST(a,b)
80349d9781Schristos #endif
81349d9781Schristos 
82349d9781Schristos #if 0
83349d9781Schristos #ifndef __lint__ /* Don't lint: the public routines may not be used. */
84349d9781Schristos /*
85349d9781Schristos  * XXX - This block for is debugging only and eventually should go away.
868207b28aSchristos  */
878207b28aSchristos static void
888207b28aSchristos show_name(const char *prefix, struct name *np)
898207b28aSchristos {
908207b28aSchristos 	int i;
918207b28aSchristos 
928207b28aSchristos 	i = 0;
938207b28aSchristos 	for (/*EMPTY*/; np; np = np->n_flink) {
948207b28aSchristos 		(void)printf("%s[%d]: %s\n", prefix, i, np->n_name);
958207b28aSchristos 		i++;
968207b28aSchristos 	}
978207b28aSchristos }
988207b28aSchristos 
99798fbc60Schristos static void fput_mime_content(FILE *fp, struct Content *Cp);
100798fbc60Schristos 
1018207b28aSchristos PUBLIC void
1028207b28aSchristos show_attach(const char *prefix, struct attachment *ap)
1038207b28aSchristos {
1048207b28aSchristos 	int i;
1058207b28aSchristos 	i = 1;
1068207b28aSchristos 	for (/*EMPTY*/; ap; ap = ap->a_flink) {
1078207b28aSchristos 		(void)printf("%s[%d]:\n", prefix, i);
1088207b28aSchristos 		fput_mime_content(stdout, &ap->a_Content);
1098207b28aSchristos 		i++;
1108207b28aSchristos 	}
1118207b28aSchristos }
1128207b28aSchristos 
1138207b28aSchristos PUBLIC void
1148207b28aSchristos show_header(struct header *hp)
1158207b28aSchristos {
1168207b28aSchristos 	show_name("TO", hp->h_to);
1178207b28aSchristos 	(void)printf("SUBJECT: %s\n", hp->h_subject);
1188207b28aSchristos 	show_name("CC", hp->h_cc);
1198207b28aSchristos 	show_name("BCC", hp->h_bcc);
1208207b28aSchristos 	show_name("SMOPTS", hp->h_smopts);
1218207b28aSchristos 	show_attach("ATTACH", hp->h_attach);
1228207b28aSchristos }
1238207b28aSchristos #endif	/* __lint__ */
1248207b28aSchristos #endif
1258207b28aSchristos 
1268207b28aSchristos /***************************
1278207b28aSchristos  * boundary string routines
1288207b28aSchristos  */
1298207b28aSchristos static char *
getrandstring(size_t length)1308207b28aSchristos getrandstring(size_t length)
1318207b28aSchristos {
1328207b28aSchristos 	void *vbin;
1338207b28aSchristos 	uint32_t *bin;
1348207b28aSchristos 	size_t binlen;
1358207b28aSchristos 	size_t i;
1368207b28aSchristos 	char *b64;
1378207b28aSchristos 
1388207b28aSchristos 	/* XXX - check this stuff again!!! */
1398207b28aSchristos 
1408207b28aSchristos 	binlen = 3 * roundup(length, 4) / 4;	/* bytes of binary to encode base64 */
1418207b28aSchristos 	bin = vbin = salloc(roundup(binlen, 4));
1428207b28aSchristos 	for (i = 0; i < roundup(binlen, 4) / 4; i++)
1438207b28aSchristos 		bin[i] = arc4random();
1448207b28aSchristos 
1458207b28aSchristos 	b64 = salloc(roundup(length, 4));
1468207b28aSchristos 	mime_bintob64(b64, vbin, binlen);
1478207b28aSchristos 	b64[length] = '\0';
1488207b28aSchristos 
1498207b28aSchristos 	return b64;
1508207b28aSchristos }
1518207b28aSchristos 
1528207b28aSchristos /*
1538207b28aSchristos  * Generate a boundary for MIME multipart messages.
1548207b28aSchristos  */
1558207b28aSchristos static char *
make_boundary(void)1568207b28aSchristos make_boundary(void)
1578207b28aSchristos {
1588207b28aSchristos #define BOUND_LEN 70	/* maximum length is 70 characters: RFC2046 sec 5.1.1 */
1598207b28aSchristos 
1608207b28aSchristos 	char *bound;
1618207b28aSchristos 	time_t	now;
1628207b28aSchristos 
1638207b28aSchristos 	(void)time(&now);
1648207b28aSchristos 	bound = salloc(BOUND_LEN);
1658207b28aSchristos 	(void)snprintf(bound, BOUND_LEN, "=_%08lx.%s",
1668207b28aSchristos 	    (long)now, getrandstring(BOUND_LEN - 12));
1678207b28aSchristos 	return bound;
1688207b28aSchristos 
1698207b28aSchristos #undef BOUND_LEN
1708207b28aSchristos }
1718207b28aSchristos 
1728207b28aSchristos /***************************
1738207b28aSchristos  * Transfer coding routines
1748207b28aSchristos  */
1758207b28aSchristos /*
1768207b28aSchristos  * We determine the recommended transfer encoding type for a file as
1778207b28aSchristos  * follows:
1788207b28aSchristos  *
1798207b28aSchristos  * 1) If there is a NULL byte or a stray CR (not in a CRLF
1808207b28aSchristos  *    combination) in the file, play it safe and use base64.
1818207b28aSchristos  *
1828207b28aSchristos  * 2) If any high bit is set, use quoted-printable if the content type
1838207b28aSchristos  *    is "text" and base64 otherwise.
1848207b28aSchristos  *
1858207b28aSchristos  * 3) Otherwise:
1868207b28aSchristos  *    a) use quoted-printable if there are any long lines, control
1878207b28aSchristos  *       chars (including CR), end-of-line blank space, or a missing
1888207b28aSchristos  *       terminating NL.
1898207b28aSchristos  *    b) use 7bit in all remaining case, including an empty file.
1908207b28aSchristos  *
1918207b28aSchristos  * NOTE: This means that CRLF text (MSDOS) files will be encoded
1928207b28aSchristos  * quoted-printable.
1938207b28aSchristos  */
1948207b28aSchristos /*
1958207b28aSchristos  * RFC 821 imposes the following line length limit:
1968207b28aSchristos  *  The maximum total length of a text line including the
1978207b28aSchristos  *  <CRLF> is 1000 characters (but not counting the leading
1988207b28aSchristos  *  dot duplicated for transparency).
1998207b28aSchristos  */
2008207b28aSchristos #define MIME_UNENCODED_LINE_MAX	(1000 - 2)
2018207b28aSchristos static size_t
line_limit(void)2028207b28aSchristos line_limit(void)
2038207b28aSchristos {
2048207b28aSchristos 	int limit;
2058207b28aSchristos 	const char *cp;
2068207b28aSchristos 	limit = -1;
2078207b28aSchristos 
2088207b28aSchristos 	if ((cp = value(ENAME_MIME_UNENC_LINE_MAX)) != NULL)
2098207b28aSchristos 		limit = atoi(cp);
2108207b28aSchristos 
2118207b28aSchristos 	if (limit < 0 || limit > MIME_UNENCODED_LINE_MAX)
2128207b28aSchristos 		limit = MIME_UNENCODED_LINE_MAX;
2138207b28aSchristos 
2148207b28aSchristos 	return (size_t)limit;
2158207b28aSchristos }
2168207b28aSchristos 
217f3098750Schristos static inline int
is_text(const char * ctype)218f3098750Schristos is_text(const char *ctype)
2198207b28aSchristos {
2208207b28aSchristos 	return ctype &&
2218207b28aSchristos 	    strncasecmp(ctype, "text/", sizeof("text/") - 1) == 0;
2228207b28aSchristos }
2238207b28aSchristos 
2248207b28aSchristos static const char *
content_encoding_core(void * fh,const char * ctype)2258207b28aSchristos content_encoding_core(void *fh, const char *ctype)
2268207b28aSchristos {
227fed14775Schristos #define MAILMSG_CLEAN	0x0
228fed14775Schristos #define MAILMSG_ENDWS	0x1
229fed14775Schristos #define MAILMSG_CTRLC	0x2
230fed14775Schristos #define MAILMSG_8BIT	0x4
231fed14775Schristos #define MAILMSG_LONGL	0x8
232fed14775Schristos 	int c, lastc, state;
2338207b28aSchristos 	size_t curlen, maxlen;
2348207b28aSchristos 
235fed14775Schristos 	state = MAILMSG_CLEAN;
2368207b28aSchristos 	curlen = 0;
237fed14775Schristos 	maxlen = line_limit();
2388207b28aSchristos 	lastc = EOF;
2398207b28aSchristos 	while ((c = fgetc(fh)) != EOF) {
2408207b28aSchristos 		curlen++;
2418207b28aSchristos 
242ec0bd159Schristos 		if (c == '\0')
2438207b28aSchristos 			return MIME_TRANSFER_BASE64;
2448207b28aSchristos 
2458207b28aSchristos 		if (c > 0x7f) {
246fed14775Schristos 			if (!is_text(ctype))
2478207b28aSchristos 				return MIME_TRANSFER_BASE64;
248fed14775Schristos 			state |= MAILMSG_8BIT;
249fed14775Schristos 			continue;
2508207b28aSchristos 		}
2518207b28aSchristos 		if (c == '\n') {
252d727506fSchristos 			if (is_WSP(lastc))
253fed14775Schristos 				state |= MAILMSG_ENDWS;
2548207b28aSchristos 			if (curlen > maxlen)
255fed14775Schristos 				state |= MAILMSG_LONGL;
2568207b28aSchristos 			curlen = 0;
2578207b28aSchristos 		}
258ec0bd159Schristos 		else if ((c < 0x20 && c != '\t') || c == 0x7f || lastc == '\r')
259fed14775Schristos 			state |= MAILMSG_CTRLC;
2608207b28aSchristos 		lastc = c;
2618207b28aSchristos 	}
2628207b28aSchristos 	if (lastc == EOF) /* no characters read */
2638207b28aSchristos 		return MIME_TRANSFER_7BIT;
2648207b28aSchristos 
265fed14775Schristos 	if (lastc != '\n' || state != MAILMSG_CLEAN)
2668207b28aSchristos 		return MIME_TRANSFER_QUOTED;
2678207b28aSchristos 
2688207b28aSchristos 	return MIME_TRANSFER_7BIT;
2698207b28aSchristos }
2708207b28aSchristos 
2718207b28aSchristos static const char *
content_encoding_by_name(const char * filename,const char * ctype)2728207b28aSchristos content_encoding_by_name(const char *filename, const char *ctype)
2738207b28aSchristos {
2748207b28aSchristos 	FILE *fp;
2758207b28aSchristos 	const char *enc;
2764fe1ef32Schristos 	fp = Fopen(filename, "ref");
2778207b28aSchristos 	if (fp == NULL) {
2788207b28aSchristos 		warn("content_encoding_by_name: %s", filename);
2798207b28aSchristos 		return MIME_TRANSFER_BASE64;	/* safe */
2808207b28aSchristos 	}
2818207b28aSchristos 	enc = content_encoding_core(fp, ctype);
282428c7a1fSchristos 	(void)Fclose(fp);
2838207b28aSchristos 	return enc;
2848207b28aSchristos }
2858207b28aSchristos 
2868207b28aSchristos static const char *
content_encoding_by_fileno(int fd,const char * ctype)2878207b28aSchristos content_encoding_by_fileno(int fd, const char *ctype)
2888207b28aSchristos {
2898207b28aSchristos 	FILE *fp;
290428c7a1fSchristos 	int fd2;
2918207b28aSchristos 	const char *encoding;
2928207b28aSchristos 	off_t cur_pos;
2938207b28aSchristos 
2948207b28aSchristos 	cur_pos = lseek(fd, (off_t)0, SEEK_CUR);
295428c7a1fSchristos 	if ((fd2 = dup(fd)) == -1 ||
2964fe1ef32Schristos 	    (fp = Fdopen(fd2, "ref")) == NULL) {
2978207b28aSchristos 		warn("content_encoding_by_fileno");
298428c7a1fSchristos 		if (fd2 != -1)
299428c7a1fSchristos 			(void)close(fd2);
3008207b28aSchristos 		return MIME_TRANSFER_BASE64;
3018207b28aSchristos 	}
3028207b28aSchristos 	encoding = content_encoding_core(fp, ctype);
303a2fe0ba0Schristos 	(void)Fclose(fp);
3048207b28aSchristos 	(void)lseek(fd, cur_pos, SEEK_SET);
3058207b28aSchristos 	return encoding;
3068207b28aSchristos }
3078207b28aSchristos 
3088207b28aSchristos static const char *
content_encoding(struct attachment * ap,const char * ctype)309349d9781Schristos content_encoding(struct attachment *ap, const char *ctype)
3108207b28aSchristos {
311349d9781Schristos 	switch (ap->a_type) {
3128207b28aSchristos 	case ATTACH_FNAME:
313349d9781Schristos 		return content_encoding_by_name(ap->a_name, ctype);
3148207b28aSchristos 	case ATTACH_MSG:
315349d9781Schristos 		return "7bit";
3168207b28aSchristos 	case ATTACH_FILENO:
317349d9781Schristos 		return content_encoding_by_fileno(ap->a_fileno, ctype);
318349d9781Schristos 	case ATTACH_INVALID:
3198207b28aSchristos 	default:
320349d9781Schristos 		/* This is a coding error! */
321349d9781Schristos 		assert(/* CONSTCOND */ 0);
322349d9781Schristos 		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
3238207b28aSchristos 		/* NOTREACHED */
3248207b28aSchristos 	}
325349d9781Schristos }
3268207b28aSchristos 
3278207b28aSchristos /************************
3288207b28aSchristos  * Content type routines
3298207b28aSchristos  */
3308207b28aSchristos /*
3318207b28aSchristos  * We use libmagic(3) to get the content type, except in the case of a
33207a95a1dSchristos  * 0 or 1 byte file where libmagic gives rather useless results.
3338207b28aSchristos  */
3348207b28aSchristos static const char *
content_type_by_name(char * filename)33507a95a1dSchristos content_type_by_name(char *filename)
3368207b28aSchristos {
3378207b28aSchristos 	const char *cp;
33807a95a1dSchristos 	char *cp2;
3398207b28aSchristos 	magic_t magic;
3408207b28aSchristos 	struct stat sb;
3418207b28aSchristos 
34227b54bd9Schristos #ifdef BROKEN_MAGIC
3438207b28aSchristos 	/*
34427b54bd9Schristos 	 * libmagic(3) produces annoying results on very short files.
34527b54bd9Schristos 	 * The common case is MIME encoding an empty message body.
34627b54bd9Schristos 	 * XXX - it would be better to fix libmagic(3)!
34707a95a1dSchristos 	 *
34807a95a1dSchristos 	 * Note: a 1-byte message body always consists of a newline,
34907a95a1dSchristos 	 * so size determines all there.  However, 1-byte attachments
35007a95a1dSchristos 	 * (filename != NULL) could be anything, so check those.
3518207b28aSchristos 	 */
3528207b28aSchristos 	if ((filename != NULL && stat(filename, &sb) == 0) ||
35307a95a1dSchristos 	    (filename == NULL && fstat(0, &sb) == 0)) {
35407a95a1dSchristos 		if (sb.st_size < 2 && S_ISREG(sb.st_mode)) {
35507a95a1dSchristos 			FILE *fp;
35607a95a1dSchristos 			int ch;
35727b54bd9Schristos 
35807a95a1dSchristos 			if (sb.st_size == 0 || filename == NULL ||
3594fe1ef32Schristos 			    (fp = Fopen(filename, "ref")) == NULL)
3608207b28aSchristos 				return "text/plain";
3618207b28aSchristos 
36207a95a1dSchristos 			ch = fgetc(fp);
363428c7a1fSchristos 			(void)Fclose(fp);
36407a95a1dSchristos 
36507a95a1dSchristos 			return isprint(ch) || isspace(ch) ?
36607a95a1dSchristos 			    "text/plain" : "application/octet-stream";
36707a95a1dSchristos 		}
36807a95a1dSchristos 	}
36927b54bd9Schristos #endif
3708207b28aSchristos 	magic = magic_open(MAGIC_MIME);
3718207b28aSchristos 	if (magic == NULL) {
372df684630Schristos 		warnx("magic_open: %s", magic_error(magic));
3738207b28aSchristos 		return NULL;
3748207b28aSchristos 	}
3758207b28aSchristos 	if (magic_load(magic, NULL) != 0) {
376df684630Schristos 		warnx("magic_load: %s", magic_error(magic));
3778207b28aSchristos 		return NULL;
3788207b28aSchristos 	}
3798207b28aSchristos 	cp = magic_file(magic, filename);
3808207b28aSchristos 	if (cp == NULL) {
381ca13337dSchristos 		warnx("magic_load: %s", magic_error(magic));
3828207b28aSchristos 		return NULL;
3838207b28aSchristos 	}
38407a95a1dSchristos 	if (filename &&
38507a95a1dSchristos 	    sasprintf(&cp2, "%s; name=\"%s\"", cp, basename(filename)) != -1)
38607a95a1dSchristos 		cp = cp2;
38707a95a1dSchristos 	else
3888207b28aSchristos 		cp = savestr(cp);
3898207b28aSchristos 	magic_close(magic);
3908207b28aSchristos 	return cp;
3918207b28aSchristos }
3928207b28aSchristos 
3938207b28aSchristos static const char *
content_type_by_fileno(int fd)3948207b28aSchristos content_type_by_fileno(int fd)
3958207b28aSchristos {
3968207b28aSchristos 	const char *cp;
3978207b28aSchristos 	off_t cur_pos;
3988207b28aSchristos 	int ofd;
3998207b28aSchristos 
4008207b28aSchristos 	cur_pos = lseek(fd, (off_t)0, SEEK_CUR);
4018207b28aSchristos 
4028207b28aSchristos 	ofd = dup(0);		/* save stdin */
4038207b28aSchristos 	if (dup2(fd, 0) == -1)	/* become stdin */
4048207b28aSchristos 		warn("dup2");
4058207b28aSchristos 
4068207b28aSchristos 	cp = content_type_by_name(NULL);
4078207b28aSchristos 
4088207b28aSchristos 	if (dup2(ofd, 0) == -1)	/* restore stdin */
4098207b28aSchristos 		warn("dup2");
4108207b28aSchristos 	(void)close(ofd);	/* close the copy */
4118207b28aSchristos 
4128207b28aSchristos 	(void)lseek(fd, cur_pos, SEEK_SET);
4138207b28aSchristos 	return cp;
4148207b28aSchristos }
4158207b28aSchristos 
4168207b28aSchristos static const char *
content_type(struct attachment * ap)417349d9781Schristos content_type(struct attachment *ap)
4188207b28aSchristos {
419349d9781Schristos 	switch (ap->a_type) {
4208207b28aSchristos 	case ATTACH_FNAME:
421349d9781Schristos 		return content_type_by_name(ap->a_name);
4228207b28aSchristos 	case ATTACH_MSG:
423349d9781Schristos 		/*
424349d9781Schristos 		 * Note: the encapusulated message header must include
425349d9781Schristos 		 * at least one of the "Date:", "From:", or "Subject:"
426349d9781Schristos 		 * fields.  See rfc2046 Sec 5.2.1.
427349d9781Schristos 		 * XXX - Should we really test for this?
428349d9781Schristos 		 */
4298207b28aSchristos 		return "message/rfc822";
4308207b28aSchristos 	case ATTACH_FILENO:
431349d9781Schristos 		return content_type_by_fileno(ap->a_fileno);
432349d9781Schristos 	case ATTACH_INVALID:
4338207b28aSchristos 	default:
4348207b28aSchristos 		/* This is a coding error! */
4358207b28aSchristos 		assert(/* CONSTCOND */ 0);
436349d9781Schristos 		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
437349d9781Schristos 		/* NOTREACHED */
4388207b28aSchristos 	}
4398207b28aSchristos }
4408207b28aSchristos 
4418207b28aSchristos /*************************
4428207b28aSchristos  * Other content routines
4438207b28aSchristos  */
4448207b28aSchristos 
4458207b28aSchristos static const char *
content_disposition(struct attachment * ap)4468207b28aSchristos content_disposition(struct attachment *ap)
4478207b28aSchristos {
4488207b28aSchristos 	switch (ap->a_type) {
4498207b28aSchristos 	case ATTACH_FNAME: {
4508207b28aSchristos 		char *disp;
451349d9781Schristos 		(void)sasprintf(&disp, "attachment; filename=\"%s\"",
452349d9781Schristos 		    basename(ap->a_name));
4538207b28aSchristos 		return disp;
4548207b28aSchristos 	}
4558207b28aSchristos 	case ATTACH_MSG:
456349d9781Schristos 		return NULL;
4578207b28aSchristos 	case ATTACH_FILENO:
4588207b28aSchristos 		return "inline";
4598207b28aSchristos 
460349d9781Schristos 	case ATTACH_INVALID:
4618207b28aSchristos 	default:
462349d9781Schristos 		/* This is a coding error! */
463349d9781Schristos 		assert(/* CONSTCOND */ 0);
464349d9781Schristos 		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
465349d9781Schristos 		/* NOTREACHED */
4668207b28aSchristos 	}
4678207b28aSchristos }
4688207b28aSchristos 
4698207b28aSchristos /*ARGSUSED*/
4708207b28aSchristos static const char *
content_id(struct attachment * ap __unused)471349d9781Schristos content_id(struct attachment *ap __unused)
4728207b28aSchristos {
4738207b28aSchristos 	/* XXX - to be written. */
4748207b28aSchristos 
4758207b28aSchristos 	return NULL;
4768207b28aSchristos }
4778207b28aSchristos 
4788207b28aSchristos static const char *
content_description(struct attachment * attach,int attach_num)4798207b28aSchristos content_description(struct attachment *attach, int attach_num)
4808207b28aSchristos {
4818207b28aSchristos 	if (attach_num) {
4828207b28aSchristos 		char *description;
483f3098750Schristos 		(void)sasprintf(&description, "attachment %d", attach_num);
4848207b28aSchristos 		return description;
4858207b28aSchristos 	}
4868207b28aSchristos 	else
4878207b28aSchristos 		return attach->a_Content.C_description;
4888207b28aSchristos }
4898207b28aSchristos 
4908207b28aSchristos /*******************************************
4918207b28aSchristos  * Routines to get the MIME content strings.
4928207b28aSchristos  */
493349d9781Schristos PUBLIC struct Content
get_mime_content(struct attachment * ap,int i)4948207b28aSchristos get_mime_content(struct attachment *ap, int i)
4958207b28aSchristos {
4968207b28aSchristos 	struct Content Cp;
4978207b28aSchristos 
4988207b28aSchristos 	Cp.C_type	 = content_type(ap);
4998207b28aSchristos 	Cp.C_encoding	 = content_encoding(ap, Cp.C_type);
5008207b28aSchristos 	Cp.C_disposition = content_disposition(ap);
5018207b28aSchristos 	Cp.C_id		 = content_id(ap);
5028207b28aSchristos 	Cp.C_description = content_description(ap, i);
5038207b28aSchristos 
5048207b28aSchristos 	return Cp;
5058207b28aSchristos }
5068207b28aSchristos 
5078207b28aSchristos /******************
5088207b28aSchristos  * Output routines
5098207b28aSchristos  */
5108207b28aSchristos static void
fput_mime_content(FILE * fp,struct Content * Cp)5118207b28aSchristos fput_mime_content(FILE *fp, struct Content *Cp)
5128207b28aSchristos {
5138207b28aSchristos 	(void)fprintf(fp, MIME_HDR_TYPE ": %s\n", Cp->C_type);
5148207b28aSchristos 	(void)fprintf(fp, MIME_HDR_ENCODING ": %s\n", Cp->C_encoding);
5158207b28aSchristos 	if (Cp->C_disposition)
5168207b28aSchristos 		(void)fprintf(fp, MIME_HDR_DISPOSITION ": %s\n",
5178207b28aSchristos 		    Cp->C_disposition);
5188207b28aSchristos 	if (Cp->C_id)
5198207b28aSchristos 		(void)fprintf(fp, MIME_HDR_ID ": %s\n", Cp->C_id);
5208207b28aSchristos 	if (Cp->C_description)
5218207b28aSchristos 		(void)fprintf(fp, MIME_HDR_DESCRIPTION ": %s\n",
5228207b28aSchristos 		    Cp->C_description);
5238207b28aSchristos }
5248207b28aSchristos 
5258207b28aSchristos static void
fput_body(FILE * fi,FILE * fo,struct Content * Cp)5268207b28aSchristos fput_body(FILE *fi, FILE *fo, struct Content *Cp)
5278207b28aSchristos {
5288207b28aSchristos 	mime_codec_t enc;
5298207b28aSchristos 
5308207b28aSchristos 	enc = mime_fio_encoder(Cp->C_encoding);
5318207b28aSchristos 	if (enc == NULL)
5327d718edeSchristos 		warnx("unknown transfer encoding type: %s", Cp->C_encoding);
5338207b28aSchristos 	else
5348207b28aSchristos 		enc(fi, fo, 0);
5358207b28aSchristos }
5368207b28aSchristos 
5378207b28aSchristos static void
fput_attachment(FILE * fo,struct attachment * ap)5388207b28aSchristos fput_attachment(FILE *fo, struct attachment *ap)
5398207b28aSchristos {
5408207b28aSchristos 	FILE *fi;
5418207b28aSchristos 	struct Content *Cp = &ap->a_Content;
5428207b28aSchristos 
5438207b28aSchristos 	fput_mime_content(fo, &ap->a_Content);
5448207b28aSchristos 	(void)putc('\n', fo);
5458207b28aSchristos 
5468207b28aSchristos 	switch (ap->a_type) {
5478207b28aSchristos 	case ATTACH_FNAME:
5484fe1ef32Schristos 		fi = Fopen(ap->a_name, "ref");
5498207b28aSchristos 		if (fi == NULL)
550428c7a1fSchristos 			err(EXIT_FAILURE, "Fopen: %s", ap->a_name);
5518207b28aSchristos 		break;
5528207b28aSchristos 
5538207b28aSchristos 	case ATTACH_FILENO:
554428c7a1fSchristos 		/*
555428c7a1fSchristos 		 * XXX - we should really dup(2) here, however we are
556428c7a1fSchristos 		 * finished with the attachment, so the Fclose() below
557428c7a1fSchristos 		 * is OK for now.  This will be changed in the future.
558428c7a1fSchristos 		 */
5594fe1ef32Schristos 		fi = Fdopen(ap->a_fileno, "ref");
5608207b28aSchristos 		if (fi == NULL)
561428c7a1fSchristos 			err(EXIT_FAILURE, "Fdopen: %d", ap->a_fileno);
5628207b28aSchristos 		break;
5638207b28aSchristos 
564349d9781Schristos 	case ATTACH_MSG: {
565349d9781Schristos 		char mailtempname[PATHSIZE];
566349d9781Schristos 		int fd;
567349d9781Schristos 
568349d9781Schristos 		fi = NULL;	/* appease gcc */
569349d9781Schristos 		(void)snprintf(mailtempname, sizeof(mailtempname),
570349d9781Schristos 		    "%s/mail.RsXXXXXXXXXX", tmpdir);
571349d9781Schristos 		if ((fd = mkstemp(mailtempname)) == -1 ||
5724fe1ef32Schristos 		    (fi = Fdopen(fd, "wef+")) == NULL) {
573349d9781Schristos 			if (fd != -1)
574349d9781Schristos 				(void)close(fd);
575349d9781Schristos 			err(EXIT_FAILURE, "%s", mailtempname);
576349d9781Schristos 		}
577349d9781Schristos 		(void)rm(mailtempname);
578349d9781Schristos 
579349d9781Schristos 		/*
580349d9781Schristos 		 * This is only used for forwarding, so use the forwardtab[].
581349d9781Schristos 		 *
582349d9781Schristos 		 * XXX - sendmessage really needs a 'flags' argument
583349d9781Schristos 		 * so we don't have to play games.
584349d9781Schristos 		 */
585349d9781Schristos 		ap->a_msg->m_size--;	/* XXX - remove trailing newline */
586349d9781Schristos 		(void)fputc('>', fi);	/* XXX - hide the headerline */
587349d9781Schristos 		if (sendmessage(ap->a_msg, fi, forwardtab, NULL, NULL))
588349d9781Schristos 			(void)fprintf(stderr, ". . . forward failed, sorry.\n");
589349d9781Schristos 		ap->a_msg->m_size++;
590349d9781Schristos 
591349d9781Schristos 		rewind(fi);
592349d9781Schristos 		break;
593349d9781Schristos 	}
594349d9781Schristos 	case ATTACH_INVALID:
5958207b28aSchristos 	default:
596349d9781Schristos 		/* This is a coding error! */
597349d9781Schristos 		assert(/* CONSTCOND */ 0);
598349d9781Schristos 		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
5998207b28aSchristos 	}
6008207b28aSchristos 
6018207b28aSchristos 	fput_body(fi, fo, Cp);
602428c7a1fSchristos 	(void)Fclose(fi);
6038207b28aSchristos }
6048207b28aSchristos 
6058207b28aSchristos /***********************************
6068207b28aSchristos  * Higher level attachment routines.
6078207b28aSchristos  */
6088207b28aSchristos 
6098207b28aSchristos static int
mktemp_file(FILE ** nfo,FILE ** nfi,const char * hint)6108207b28aSchristos mktemp_file(FILE **nfo, FILE **nfi, const char *hint)
6118207b28aSchristos {
6128207b28aSchristos 	char tempname[PATHSIZE];
6138207b28aSchristos 	int fd, fd2;
6148207b28aSchristos 	(void)snprintf(tempname, sizeof(tempname), "%s/%sXXXXXXXXXX",
6158207b28aSchristos 	    tmpdir, hint);
6168207b28aSchristos 	if ((fd = mkstemp(tempname)) == -1 ||
6174fe1ef32Schristos 	    (*nfo = Fdopen(fd, "wef")) == NULL) {
6188207b28aSchristos 		if (fd != -1)
6198207b28aSchristos 			(void)close(fd);
6208207b28aSchristos 		warn("%s", tempname);
6218207b28aSchristos 		return -1;
6228207b28aSchristos 	}
6238207b28aSchristos 	(void)rm(tempname);
6248207b28aSchristos 	if ((fd2 = dup(fd)) == -1 ||
6254fe1ef32Schristos 	    (*nfi = Fdopen(fd2, "ref")) == NULL) {
6268207b28aSchristos 		warn("%s", tempname);
6278207b28aSchristos 		(void)Fclose(*nfo);
6288207b28aSchristos 		return -1;
6298207b28aSchristos 	}
6308207b28aSchristos 	return 0;
6318207b28aSchristos }
6328207b28aSchristos 
6338207b28aSchristos /*
6348207b28aSchristos  * Repackage the mail as a multipart MIME message.  This should always
6358207b28aSchristos  * be called whenever there are attachments, but might be called even
6368207b28aSchristos  * if there are none if we want to wrap the message in a MIME package.
6378207b28aSchristos  */
6388207b28aSchristos PUBLIC FILE *
mime_encode(FILE * fi,struct header * header)6398207b28aSchristos mime_encode(FILE *fi, struct header *header)
6408207b28aSchristos {
6418207b28aSchristos 	struct attachment map;	/* fake structure for the message body */
6428207b28aSchristos 	struct attachment *attach;
6438207b28aSchristos 	struct attachment *ap;
6448207b28aSchristos 	FILE *nfi, *nfo;
6458207b28aSchristos 
6468207b28aSchristos 	attach = header->h_attach;
6478207b28aSchristos 
6488207b28aSchristos 	/*
6498207b28aSchristos 	 * Make new phantom temporary file with read and write file
6508207b28aSchristos 	 * handles: nfi and nfo, resp.
6518207b28aSchristos 	 */
6528207b28aSchristos 	if (mktemp_file(&nfo, &nfi, "mail.Rs") != 0)
6538207b28aSchristos 		return fi;
6548207b28aSchristos 
6558207b28aSchristos 	(void)memset(&map, 0, sizeof(map));
6568207b28aSchristos 	map.a_type = ATTACH_FILENO;
6578207b28aSchristos 	map.a_fileno = fileno(fi);
6588207b28aSchristos 
6598207b28aSchristos  	map.a_Content = get_mime_content(&map, 0);
6608207b28aSchristos 
6618207b28aSchristos 	if (attach) {
6628207b28aSchristos 		/* Multi-part message:
6638207b28aSchristos 		 * Make an attachment structure for the body message
6648207b28aSchristos 		 * and make that the first element in the attach list.
6658207b28aSchristos 		 */
6668207b28aSchristos 		if (fsize(fi)) {
6678207b28aSchristos 			map.a_flink = attach;
6688207b28aSchristos 			attach->a_blink = &map;
6698207b28aSchristos 			attach = &map;
6708207b28aSchristos 		}
6718207b28aSchristos 
6728207b28aSchristos 		/* Construct our MIME boundary string - used by mime_putheader() */
6738207b28aSchristos 		header->h_mime_boundary = make_boundary();
6748207b28aSchristos 
6758207b28aSchristos 		(void)fprintf(nfo, "This is a multi-part message in MIME format.\n");
6768207b28aSchristos 
6778207b28aSchristos 		for (ap = attach; ap; ap = ap->a_flink) {
6788207b28aSchristos 			(void)fprintf(nfo, "\n--%s\n", header->h_mime_boundary);
6798207b28aSchristos 			fput_attachment(nfo, ap);
6808207b28aSchristos 		}
6818207b28aSchristos 
6828207b28aSchristos 		/* the final boundary with two attached dashes */
6838207b28aSchristos 		(void)fprintf(nfo, "\n--%s--\n", header->h_mime_boundary);
6848207b28aSchristos 	}
6858207b28aSchristos 	else {
6868207b28aSchristos 		/* Single-part message (no attachments):
6878207b28aSchristos 		 * Update header->h_Content (used by mime_putheader()).
6888207b28aSchristos 		 * Output the body contents.
6898207b28aSchristos 		 */
6908207b28aSchristos 		char *encoding;
6918207b28aSchristos 
6928207b28aSchristos 		header->h_Content = map.a_Content;
6938207b28aSchristos 
6948207b28aSchristos 		/* check for an encoding override */
6958207b28aSchristos 		if ((encoding = value(ENAME_MIME_ENCODE_MSG)) && *encoding)
6968207b28aSchristos 			header->h_Content.C_encoding = encoding;
6978207b28aSchristos 
6988207b28aSchristos 		fput_body(fi, nfo, &header->h_Content);
6998207b28aSchristos 	}
7008207b28aSchristos 	(void)Fclose(fi);
7018207b28aSchristos 	(void)Fclose(nfo);
7028207b28aSchristos 	rewind(nfi);
703f3098750Schristos 	return nfi;
7048207b28aSchristos }
7058207b28aSchristos 
7068207b28aSchristos static char*
check_filename(char * filename,char * canon_name)7078207b28aSchristos check_filename(char *filename, char *canon_name)
7088207b28aSchristos {
7098207b28aSchristos 	int fd;
7108207b28aSchristos 	struct stat sb;
7118207b28aSchristos 	char *fname = filename;
712f3098750Schristos 
713798fbc60Schristos 	/* We need to expand '~' if we got here from '~@'.  The shell
714798fbc60Schristos 	 * does this otherwise.
7158207b28aSchristos 	 */
716798fbc60Schristos 	if (fname[0] == '~' && fname[1] == '/') {
717798fbc60Schristos 		if (homedir && homedir[0] != '~')
718798fbc60Schristos 			(void)easprintf(&fname, "%s/%s",
719798fbc60Schristos 			    homedir, fname + 2);
7208207b28aSchristos 	}
7218207b28aSchristos 	if (realpath(fname, canon_name) == NULL) {
7228207b28aSchristos 		warn("realpath: %s", filename);
7238207b28aSchristos 		canon_name = NULL;
7248207b28aSchristos 		goto done;
7258207b28aSchristos 	}
7268207b28aSchristos 	fd = open(canon_name, O_RDONLY, 0);
7278207b28aSchristos 	if (fd == -1) {
7288207b28aSchristos 		warnx("open: cannot read %s", filename);
7298207b28aSchristos 		canon_name = NULL;
7308207b28aSchristos 		goto done;
7318207b28aSchristos 	}
7328207b28aSchristos 	if (fstat(fd, &sb) == -1) {
7338207b28aSchristos 		warn("stat: %s", canon_name);
7348207b28aSchristos 		canon_name = NULL;
7358207b28aSchristos 		goto do_close;
7368207b28aSchristos 	}
7378207b28aSchristos 	if (!S_ISREG(sb.st_mode)) {
7388207b28aSchristos 		warnx("stat: %s is not a file", filename);
7398207b28aSchristos 		canon_name = NULL;
7408207b28aSchristos 	     /*	goto do_close; */
7418207b28aSchristos 	}
7428207b28aSchristos  do_close:
7438207b28aSchristos 	(void)close(fd);
7448207b28aSchristos  done:
7458207b28aSchristos 	if (fname != filename)
7468207b28aSchristos 		free(fname);
7478207b28aSchristos 
7488207b28aSchristos 	return canon_name;
7498207b28aSchristos }
7508207b28aSchristos 
7518207b28aSchristos static struct attachment *
attach_one_file(struct attachment * ap,char * filename,int attach_num)752349d9781Schristos attach_one_file(struct attachment *ap, char *filename, int attach_num)
7538207b28aSchristos {
7548207b28aSchristos 	char canon_name[MAXPATHLEN];
755349d9781Schristos 	struct attachment *nap;
7568207b28aSchristos 
757f3098750Schristos 	/*
758349d9781Schristos 	 * 1) check that filename is really a readable file; return NULL if not.
759f3098750Schristos 	 * 2) allocate an attachment structure.
760f3098750Schristos 	 * 3) save cananonical name for filename, so cd won't screw things later.
761f3098750Schristos 	 * 4) add the structure to the end of the chain.
762349d9781Schristos 	 * 5) return the new attachment structure.
763f3098750Schristos 	 */
7648207b28aSchristos 	if (check_filename(filename, canon_name) == NULL)
7658207b28aSchristos 		return NULL;
7668207b28aSchristos 
7678207b28aSchristos 	nap = csalloc(1, sizeof(*nap));
7688207b28aSchristos 	nap->a_type = ATTACH_FNAME;
7698207b28aSchristos 	nap->a_name = savestr(canon_name);
7708207b28aSchristos 
771349d9781Schristos 	if (ap) {
772349d9781Schristos 		for (/*EMPTY*/; ap->a_flink != NULL; ap = ap->a_flink)
7738207b28aSchristos 			continue;
7748207b28aSchristos 		ap->a_flink = nap;
7758207b28aSchristos 		nap->a_blink = ap;
7768207b28aSchristos 	}
7778207b28aSchristos 
7788207b28aSchristos 	if (attach_num)
7798207b28aSchristos 		nap->a_Content = get_mime_content(nap, attach_num);
7808207b28aSchristos 
781349d9781Schristos 	return nap;
7828207b28aSchristos }
7838207b28aSchristos 
7848207b28aSchristos static char *
get_line(el_mode_t * em,const char * pr,const char * str,int i)7858207b28aSchristos get_line(el_mode_t *em, const char *pr, const char *str, int i)
7868207b28aSchristos {
7878207b28aSchristos 	char *prompt;
788f3098750Schristos 	char *line;
7898207b28aSchristos 
790f3098750Schristos 	/*
791f3098750Schristos 	 * Don't use a '\t' in the format string here as completion
792f3098750Schristos 	 * seems to handle it badly.
793f3098750Schristos 	 */
794349d9781Schristos 	(void)easprintf(&prompt, "#%-7d %s: ", i, pr);
795ca13337dSchristos 	line = my_gets(em, prompt, __UNCONST(str));
796ca13337dSchristos 	if (line != NULL) {
797ca13337dSchristos 		(void)strip_WSP(line);	/* strip trailing whitespace */
798ca13337dSchristos 		line = skip_WSP(line);	/* skip leading white space */
799ca13337dSchristos 		line = savestr(line);	/* XXX - do we need this? */
800ca13337dSchristos 	}
801ca13337dSchristos 	else {
802ca13337dSchristos 		line = __UNCONST("");
803ca13337dSchristos 	}
8048207b28aSchristos 	free(prompt);
8058207b28aSchristos 
806f3098750Schristos 	return line;
8078207b28aSchristos }
8088207b28aSchristos 
8098207b28aSchristos static void
sget_line(el_mode_t * em,const char * pr,const char ** str,int i)8108207b28aSchristos sget_line(el_mode_t *em, const char *pr, const char **str, int i)
8118207b28aSchristos {
8128207b28aSchristos 	char *line;
8138207b28aSchristos 	line = get_line(em, pr, *str, i);
814ca13337dSchristos 	if (line != NULL && strcmp(line, *str) != 0)
815ca13337dSchristos 		*str = line;
8168207b28aSchristos }
8178207b28aSchristos 
8188207b28aSchristos static void
sget_encoding(const char ** str,const char * filename,const char * ctype,int num)8198207b28aSchristos sget_encoding(const char **str, const char *filename, const char *ctype, int num)
8208207b28aSchristos {
8218207b28aSchristos 	const char *ename;
8228207b28aSchristos 	const char *defename;
8238207b28aSchristos 
8248207b28aSchristos 	defename = NULL;
8258207b28aSchristos 	ename = *str;
8268207b28aSchristos 	for (;;) {
8278207b28aSchristos 		ename = get_line(&elm.mime_enc, "encoding", ename, num);
8288207b28aSchristos 
8298207b28aSchristos 		if (*ename == '\0') {
8308207b28aSchristos 			if (defename == NULL)
8318207b28aSchristos 				defename = content_encoding_by_name(filename, ctype);
8328207b28aSchristos 			ename = defename;
8338207b28aSchristos 		}
8348207b28aSchristos 		else if (mime_fio_encoder(ename) == NULL) {
8358207b28aSchristos 			const void *cookie;
8368207b28aSchristos 			(void)printf("Sorry: valid encoding modes are: ");
8378207b28aSchristos 			cookie = NULL;
8388207b28aSchristos 			ename = mime_next_encoding_name(&cookie);
8398207b28aSchristos 			for (;;) {
8408207b28aSchristos 				(void)printf("%s", ename);
8418207b28aSchristos 				ename = mime_next_encoding_name(&cookie);
8428207b28aSchristos 				if (ename == NULL)
8438207b28aSchristos 					break;
8448207b28aSchristos 				(void)fputc(',', stdout);
8458207b28aSchristos 			}
846349d9781Schristos 			(void)putchar('\n');
8478207b28aSchristos 			ename = *str;
8488207b28aSchristos 		}
8498207b28aSchristos 		else {
8508207b28aSchristos 			if (strcmp(ename, *str) != 0)
8518207b28aSchristos 				*str = savestr(ename);
8528207b28aSchristos 			break;
8538207b28aSchristos 		}
8548207b28aSchristos 	}
8558207b28aSchristos }
8568207b28aSchristos 
857349d9781Schristos /*
858349d9781Schristos  * Edit an attachment list.
859349d9781Schristos  * Return the new attachment list.
860349d9781Schristos  */
8618207b28aSchristos static struct attachment *
edit_attachlist(struct attachment * alist)862349d9781Schristos edit_attachlist(struct attachment *alist)
8638207b28aSchristos {
8648207b28aSchristos 	struct attachment *ap;
8658207b28aSchristos 	char *line;
866798fbc60Schristos 	int attach_num;
8678207b28aSchristos 
8688207b28aSchristos 	(void)printf("Attachments:\n");
8698207b28aSchristos 
870798fbc60Schristos 	attach_num = 1;
871349d9781Schristos 	ap = alist;
8728207b28aSchristos 	while (ap) {
873349d9781Schristos 		SHOW_ALIST(alist, ap);
874349d9781Schristos 
875349d9781Schristos 		switch(ap->a_type) {
876349d9781Schristos 		case ATTACH_MSG:
877349d9781Schristos 			(void)printf("#%-7d message:  <not changeable>\n",
878349d9781Schristos 			    attach_num);
879349d9781Schristos 			break;
880349d9781Schristos 		case ATTACH_FNAME:
881349d9781Schristos 		case ATTACH_FILENO:
882798fbc60Schristos 			line = get_line(&elm.filec, "filename", ap->a_name, attach_num);
8838207b28aSchristos 			if (*line == '\0') {	/* omit this attachment */
884349d9781Schristos 				if (ap->a_blink) {
885349d9781Schristos 					struct attachment *next_ap;
886349d9781Schristos 					next_ap = ap->a_flink;
887349d9781Schristos 					ap = ap->a_blink;
888349d9781Schristos 					ap->a_flink = next_ap;
889349d9781Schristos 					if (next_ap)
890349d9781Schristos 						next_ap->a_blink = ap;
8918207b28aSchristos 					else
892349d9781Schristos 						goto done;
8938207b28aSchristos 				}
8948207b28aSchristos 				else {
895349d9781Schristos 					alist = ap->a_flink;
896349d9781Schristos 					if (alist)
897349d9781Schristos 						alist->a_blink = NULL;
898349d9781Schristos 				}
899349d9781Schristos 			}
900349d9781Schristos 			else {
901349d9781Schristos 				char canon_name[MAXPATHLEN];
9028207b28aSchristos 				if (strcmp(line, ap->a_name) != 0) { /* new filename */
9038207b28aSchristos 					if (check_filename(line, canon_name) == NULL)
9048207b28aSchristos 						continue;
9058207b28aSchristos 					ap->a_name = savestr(canon_name);
9068207b28aSchristos 					ap->a_Content = get_mime_content(ap, 0);
9078207b28aSchristos 				}
908798fbc60Schristos 				sget_line(&elm.string, "description",
909798fbc60Schristos 				    &ap->a_Content.C_description, attach_num);
910798fbc60Schristos 				sget_encoding(&ap->a_Content.C_encoding, ap->a_name,
911798fbc60Schristos 				    ap->a_Content.C_type, attach_num);
9128207b28aSchristos 			}
913349d9781Schristos 			break;
914349d9781Schristos 		case ATTACH_INVALID:
915349d9781Schristos 		default:
916349d9781Schristos 			/* This is a coding error! */
917349d9781Schristos 			assert(/* CONSTCOND */ 0);
918349d9781Schristos 			errx(EXIT_FAILURE, "invalid attachment type: %d",
919349d9781Schristos 			    ap->a_type);
920349d9781Schristos 		}
921349d9781Schristos 
922798fbc60Schristos 		attach_num++;
923349d9781Schristos 		if (alist == NULL || ap->a_flink == NULL)
9248207b28aSchristos 			break;
9258207b28aSchristos 
9268207b28aSchristos 		ap = ap->a_flink;
9278207b28aSchristos 	}
9288207b28aSchristos 
929349d9781Schristos 	ap = alist;
930349d9781Schristos 	for (;;) {
9318207b28aSchristos 		struct attachment *nap;
9328207b28aSchristos 
933349d9781Schristos 		SHOW_ALIST(alist, ap);
934349d9781Schristos 
935798fbc60Schristos 		line = get_line(&elm.filec, "filename", "", attach_num);
9368207b28aSchristos 		if (*line == '\0')
9378207b28aSchristos 			break;
9388207b28aSchristos 
939798fbc60Schristos 		nap = attach_one_file(ap, line, attach_num);
9408207b28aSchristos 		if (nap == NULL)
9418207b28aSchristos 			continue;
9428207b28aSchristos 
943349d9781Schristos 		if (alist == NULL)
944349d9781Schristos 			alist = nap;
945349d9781Schristos 		ap = nap;
9468207b28aSchristos 
947798fbc60Schristos 		sget_line(&elm.string, "description",
948798fbc60Schristos 		    &ap->a_Content.C_description, attach_num);
949798fbc60Schristos 		sget_encoding(&ap->a_Content.C_encoding, ap->a_name,
950798fbc60Schristos 		    ap->a_Content.C_type, attach_num);
951798fbc60Schristos 		attach_num++;
952349d9781Schristos 	}
953349d9781Schristos  done:
954349d9781Schristos 	SHOW_ALIST(alist, ap);
9558207b28aSchristos 
956349d9781Schristos 	return alist;
9578207b28aSchristos }
9588207b28aSchristos 
9598207b28aSchristos /*
960798fbc60Schristos  * Hook used by the '~@' escape to attach files.
9618207b28aSchristos  */
9628207b28aSchristos PUBLIC struct attachment*
mime_attach_files(struct attachment * volatile attach,char * linebuf)963ca13337dSchristos mime_attach_files(struct attachment * volatile attach, char *linebuf)
9648207b28aSchristos {
9658207b28aSchristos 	struct attachment *ap;
9668207b28aSchristos 	char *argv[MAXARGC];
9678207b28aSchristos 	int argc;
9688207b28aSchristos 	int attach_num;
9698207b28aSchristos 
970ca13337dSchristos 	argc = getrawlist(linebuf, argv, (int)__arraycount(argv));
971798fbc60Schristos 	attach_num = 1;
9728207b28aSchristos 	for (ap = attach; ap && ap->a_flink; ap = ap->a_flink)
9738207b28aSchristos 			attach_num++;
9748207b28aSchristos 
9758207b28aSchristos 	if (argc) {
9768207b28aSchristos 		int i;
9778207b28aSchristos 		for (i = 0; i < argc; i++) {
978798fbc60Schristos 			struct attachment *ap2;
979798fbc60Schristos 			ap2 = attach_one_file(ap, argv[i], attach_num);
980798fbc60Schristos 			if (ap2 != NULL) {
981798fbc60Schristos 				ap = ap2;
9828207b28aSchristos 				if (attach == NULL)
9838207b28aSchristos 					attach = ap;
984798fbc60Schristos 				attach_num++;
985798fbc60Schristos 			}
9868207b28aSchristos 		}
9878207b28aSchristos 	}
9888207b28aSchristos 	else {
989349d9781Schristos 		attach = edit_attachlist(attach);
9908207b28aSchristos 		(void)printf("--- end attachments ---\n");
9918207b28aSchristos 	}
9928207b28aSchristos 
9938207b28aSchristos 	return attach;
9948207b28aSchristos }
9958207b28aSchristos 
9968207b28aSchristos /*
997798fbc60Schristos  * Hook called in main() to attach files registered by the '-a' flag.
998798fbc60Schristos  */
999798fbc60Schristos PUBLIC struct attachment *
mime_attach_optargs(struct name * optargs)1000798fbc60Schristos mime_attach_optargs(struct name *optargs)
1001798fbc60Schristos {
1002798fbc60Schristos 	struct attachment *attach;
1003798fbc60Schristos 	struct attachment *ap;
1004798fbc60Schristos 	struct name *np;
1005798fbc60Schristos 	char *expand_optargs;
1006798fbc60Schristos 	int attach_num;
1007798fbc60Schristos 
1008798fbc60Schristos 	expand_optargs = value(ENAME_MIME_ATTACH_LIST);
1009798fbc60Schristos 	attach_num = 1;
1010798fbc60Schristos 	ap = NULL;
1011798fbc60Schristos 	attach = NULL;
1012798fbc60Schristos 	for (np = optargs; np; np = np->n_flink) {
1013798fbc60Schristos 		char *argv[MAXARGC];
1014798fbc60Schristos 		int argc;
1015798fbc60Schristos 		int i;
1016798fbc60Schristos 
1017798fbc60Schristos 		if (expand_optargs != NULL)
1018ca13337dSchristos 			argc = getrawlist(np->n_name,
1019ca13337dSchristos 			    argv, (int)__arraycount(argv));
1020798fbc60Schristos 		else {
1021*684b182fSmrg 			if (np->n_name == NULL)
1022798fbc60Schristos 				argc = 0;
1023798fbc60Schristos 			else {
1024798fbc60Schristos 				argc = 1;
1025798fbc60Schristos 				argv[0] = np->n_name;
1026798fbc60Schristos 			}
1027798fbc60Schristos 			argv[argc] = NULL;/* be consistent with getrawlist() */
1028798fbc60Schristos 		}
1029798fbc60Schristos 		for (i = 0; i < argc; i++) {
1030798fbc60Schristos 			struct attachment *ap2;
1031798fbc60Schristos 			char *filename;
1032798fbc60Schristos 
1033798fbc60Schristos 			if (argv[i][0] == '/')	/* an absolute path */
1034798fbc60Schristos 				(void)easprintf(&filename, "%s", argv[i]);
1035798fbc60Schristos 			else
1036798fbc60Schristos 				(void)easprintf(&filename, "%s/%s",
1037798fbc60Schristos 				    origdir, argv[i]);
1038798fbc60Schristos 
1039798fbc60Schristos 			ap2 = attach_one_file(ap, filename, attach_num);
1040798fbc60Schristos 			if (ap2 != NULL) {
1041798fbc60Schristos 				ap = ap2;
1042798fbc60Schristos 				if (attach == NULL)
1043798fbc60Schristos 					attach = ap;
1044798fbc60Schristos 				attach_num++;
1045798fbc60Schristos 			}
1046349d9781Schristos 			free(filename);
1047798fbc60Schristos 		}
1048798fbc60Schristos 	}
1049798fbc60Schristos 	return attach;
1050798fbc60Schristos }
1051798fbc60Schristos 
1052798fbc60Schristos /*
10538207b28aSchristos  * Output MIME header strings as specified in the header structure.
10548207b28aSchristos  */
10558207b28aSchristos PUBLIC void
mime_putheader(FILE * fp,struct header * header)10568207b28aSchristos mime_putheader(FILE *fp, struct header *header)
10578207b28aSchristos {
10588207b28aSchristos 	(void)fprintf(fp, MIME_HDR_VERSION ": " MIME_VERSION "\n");
10598207b28aSchristos 	if (header->h_attach) {
10608207b28aSchristos 		(void)fprintf(fp, MIME_HDR_TYPE ": multipart/mixed;\n");
10618207b28aSchristos 		(void)fprintf(fp, "\tboundary=\"%s\"\n", header->h_mime_boundary);
10628207b28aSchristos 	}
10638207b28aSchristos 	else {
10648207b28aSchristos 		fput_mime_content(fp, &header->h_Content);
10658207b28aSchristos 	}
10668207b28aSchristos }
10678207b28aSchristos 
10688207b28aSchristos #endif /* MIME_SUPPORT */
1069