1/*	$NetBSD: mime_attach.c,v 1.20 2019/02/01 08:29:04 mrg Exp $	*/
2
3/*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Anon Ymous.
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 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#ifdef MIME_SUPPORT
33
34#include <sys/cdefs.h>
35#ifndef __lint__
36__RCSID("$NetBSD: mime_attach.c,v 1.20 2019/02/01 08:29:04 mrg Exp $");
37#endif /* not __lint__ */
38
39#include <assert.h>
40#include <err.h>
41#include <fcntl.h>
42#include <libgen.h>
43#include <magic.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49#include <util.h>
50
51#include "def.h"
52#include "extern.h"
53#ifdef USE_EDITLINE
54#include "complete.h"
55#endif
56#ifdef MIME_SUPPORT
57#include "mime.h"
58#include "mime_codecs.h"
59#include "mime_child.h"
60#endif
61#include "glob.h"
62#include "sig.h"
63
64#if 0
65/*
66 * XXX - This block is for debugging only and eventually should go away.
67 */
68# define SHOW_ALIST(a,b) show_alist(a,b)
69static void
70show_alist(struct attachment *alist, struct attachment *ap)
71{
72	(void)printf("alist=%p ap=%p\n", alist, ap);
73	for (ap = alist; ap; ap = ap->a_flink) {
74		(void)printf("ap=%p ap->a_flink=%p ap->a_blink=%p ap->a_name=%s\n",
75		    ap, ap->a_flink, ap->a_blink, ap->a_name ? ap->a_name : "<null>");
76	}
77}
78#else
79# define SHOW_ALIST(a,b)
80#endif
81
82#if 0
83#ifndef __lint__ /* Don't lint: the public routines may not be used. */
84/*
85 * XXX - This block for is debugging only and eventually should go away.
86 */
87static void
88show_name(const char *prefix, struct name *np)
89{
90	int i;
91
92	i = 0;
93	for (/*EMPTY*/; np; np = np->n_flink) {
94		(void)printf("%s[%d]: %s\n", prefix, i, np->n_name);
95		i++;
96	}
97}
98
99static void fput_mime_content(FILE *fp, struct Content *Cp);
100
101PUBLIC void
102show_attach(const char *prefix, struct attachment *ap)
103{
104	int i;
105	i = 1;
106	for (/*EMPTY*/; ap; ap = ap->a_flink) {
107		(void)printf("%s[%d]:\n", prefix, i);
108		fput_mime_content(stdout, &ap->a_Content);
109		i++;
110	}
111}
112
113PUBLIC void
114show_header(struct header *hp)
115{
116	show_name("TO", hp->h_to);
117	(void)printf("SUBJECT: %s\n", hp->h_subject);
118	show_name("CC", hp->h_cc);
119	show_name("BCC", hp->h_bcc);
120	show_name("SMOPTS", hp->h_smopts);
121	show_attach("ATTACH", hp->h_attach);
122}
123#endif	/* __lint__ */
124#endif
125
126/***************************
127 * boundary string routines
128 */
129static char *
130getrandstring(size_t length)
131{
132	void *vbin;
133	uint32_t *bin;
134	size_t binlen;
135	size_t i;
136	char *b64;
137
138	/* XXX - check this stuff again!!! */
139
140	binlen = 3 * roundup(length, 4) / 4;	/* bytes of binary to encode base64 */
141	bin = vbin = salloc(roundup(binlen, 4));
142	for (i = 0; i < roundup(binlen, 4) / 4; i++)
143		bin[i] = arc4random();
144
145	b64 = salloc(roundup(length, 4));
146	mime_bintob64(b64, vbin, binlen);
147	b64[length] = '\0';
148
149	return b64;
150}
151
152/*
153 * Generate a boundary for MIME multipart messages.
154 */
155static char *
156make_boundary(void)
157{
158#define BOUND_LEN 70	/* maximum length is 70 characters: RFC2046 sec 5.1.1 */
159
160	char *bound;
161	time_t	now;
162
163	(void)time(&now);
164	bound = salloc(BOUND_LEN);
165	(void)snprintf(bound, BOUND_LEN, "=_%08lx.%s",
166	    (long)now, getrandstring(BOUND_LEN - 12));
167	return bound;
168
169#undef BOUND_LEN
170}
171
172/***************************
173 * Transfer coding routines
174 */
175/*
176 * We determine the recommended transfer encoding type for a file as
177 * follows:
178 *
179 * 1) If there is a NULL byte or a stray CR (not in a CRLF
180 *    combination) in the file, play it safe and use base64.
181 *
182 * 2) If any high bit is set, use quoted-printable if the content type
183 *    is "text" and base64 otherwise.
184 *
185 * 3) Otherwise:
186 *    a) use quoted-printable if there are any long lines, control
187 *       chars (including CR), end-of-line blank space, or a missing
188 *       terminating NL.
189 *    b) use 7bit in all remaining case, including an empty file.
190 *
191 * NOTE: This means that CRLF text (MSDOS) files will be encoded
192 * quoted-printable.
193 */
194/*
195 * RFC 821 imposes the following line length limit:
196 *  The maximum total length of a text line including the
197 *  <CRLF> is 1000 characters (but not counting the leading
198 *  dot duplicated for transparency).
199 */
200#define MIME_UNENCODED_LINE_MAX	(1000 - 2)
201static size_t
202line_limit(void)
203{
204	int limit;
205	const char *cp;
206	limit = -1;
207
208	if ((cp = value(ENAME_MIME_UNENC_LINE_MAX)) != NULL)
209		limit = atoi(cp);
210
211	if (limit < 0 || limit > MIME_UNENCODED_LINE_MAX)
212		limit = MIME_UNENCODED_LINE_MAX;
213
214	return (size_t)limit;
215}
216
217static inline int
218is_text(const char *ctype)
219{
220	return ctype &&
221	    strncasecmp(ctype, "text/", sizeof("text/") - 1) == 0;
222}
223
224static const char *
225content_encoding_core(void *fh, const char *ctype)
226{
227#define MAILMSG_CLEAN	0x0
228#define MAILMSG_ENDWS	0x1
229#define MAILMSG_CTRLC	0x2
230#define MAILMSG_8BIT	0x4
231#define MAILMSG_LONGL	0x8
232	int c, lastc, state;
233	size_t curlen, maxlen;
234
235	state = MAILMSG_CLEAN;
236	curlen = 0;
237	maxlen = line_limit();
238	lastc = EOF;
239	while ((c = fgetc(fh)) != EOF) {
240		curlen++;
241
242		if (c == '\0')
243			return MIME_TRANSFER_BASE64;
244
245		if (c > 0x7f) {
246			if (!is_text(ctype))
247				return MIME_TRANSFER_BASE64;
248			state |= MAILMSG_8BIT;
249			continue;
250		}
251		if (c == '\n') {
252			if (is_WSP(lastc))
253				state |= MAILMSG_ENDWS;
254			if (curlen > maxlen)
255				state |= MAILMSG_LONGL;
256			curlen = 0;
257		}
258		else if ((c < 0x20 && c != '\t') || c == 0x7f || lastc == '\r')
259			state |= MAILMSG_CTRLC;
260		lastc = c;
261	}
262	if (lastc == EOF) /* no characters read */
263		return MIME_TRANSFER_7BIT;
264
265	if (lastc != '\n' || state != MAILMSG_CLEAN)
266		return MIME_TRANSFER_QUOTED;
267
268	return MIME_TRANSFER_7BIT;
269}
270
271static const char *
272content_encoding_by_name(const char *filename, const char *ctype)
273{
274	FILE *fp;
275	const char *enc;
276	fp = Fopen(filename, "ref");
277	if (fp == NULL) {
278		warn("content_encoding_by_name: %s", filename);
279		return MIME_TRANSFER_BASE64;	/* safe */
280	}
281	enc = content_encoding_core(fp, ctype);
282	(void)Fclose(fp);
283	return enc;
284}
285
286static const char *
287content_encoding_by_fileno(int fd, const char *ctype)
288{
289	FILE *fp;
290	int fd2;
291	const char *encoding;
292	off_t cur_pos;
293
294	cur_pos = lseek(fd, (off_t)0, SEEK_CUR);
295	if ((fd2 = dup(fd)) == -1 ||
296	    (fp = Fdopen(fd2, "ref")) == NULL) {
297		warn("content_encoding_by_fileno");
298		if (fd2 != -1)
299			(void)close(fd2);
300		return MIME_TRANSFER_BASE64;
301	}
302	encoding = content_encoding_core(fp, ctype);
303	(void)Fclose(fp);
304	(void)lseek(fd, cur_pos, SEEK_SET);
305	return encoding;
306}
307
308static const char *
309content_encoding(struct attachment *ap, const char *ctype)
310{
311	switch (ap->a_type) {
312	case ATTACH_FNAME:
313		return content_encoding_by_name(ap->a_name, ctype);
314	case ATTACH_MSG:
315		return "7bit";
316	case ATTACH_FILENO:
317		return content_encoding_by_fileno(ap->a_fileno, ctype);
318	case ATTACH_INVALID:
319	default:
320		/* This is a coding error! */
321		assert(/* CONSTCOND */ 0);
322		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
323		/* NOTREACHED */
324	}
325}
326
327/************************
328 * Content type routines
329 */
330/*
331 * We use libmagic(3) to get the content type, except in the case of a
332 * 0 or 1 byte file where libmagic gives rather useless results.
333 */
334static const char *
335content_type_by_name(char *filename)
336{
337	const char *cp;
338	char *cp2;
339	magic_t magic;
340	struct stat sb;
341
342#ifdef BROKEN_MAGIC
343	/*
344	 * libmagic(3) produces annoying results on very short files.
345	 * The common case is MIME encoding an empty message body.
346	 * XXX - it would be better to fix libmagic(3)!
347	 *
348	 * Note: a 1-byte message body always consists of a newline,
349	 * so size determines all there.  However, 1-byte attachments
350	 * (filename != NULL) could be anything, so check those.
351	 */
352	if ((filename != NULL && stat(filename, &sb) == 0) ||
353	    (filename == NULL && fstat(0, &sb) == 0)) {
354		if (sb.st_size < 2 && S_ISREG(sb.st_mode)) {
355			FILE *fp;
356			int ch;
357
358			if (sb.st_size == 0 || filename == NULL ||
359			    (fp = Fopen(filename, "ref")) == NULL)
360				return "text/plain";
361
362			ch = fgetc(fp);
363			(void)Fclose(fp);
364
365			return isprint(ch) || isspace(ch) ?
366			    "text/plain" : "application/octet-stream";
367		}
368	}
369#endif
370	magic = magic_open(MAGIC_MIME);
371	if (magic == NULL) {
372		warnx("magic_open: %s", magic_error(magic));
373		return NULL;
374	}
375	if (magic_load(magic, NULL) != 0) {
376		warnx("magic_load: %s", magic_error(magic));
377		return NULL;
378	}
379	cp = magic_file(magic, filename);
380	if (cp == NULL) {
381		warnx("magic_load: %s", magic_error(magic));
382		return NULL;
383	}
384	if (filename &&
385	    sasprintf(&cp2, "%s; name=\"%s\"", cp, basename(filename)) != -1)
386		cp = cp2;
387	else
388		cp = savestr(cp);
389	magic_close(magic);
390	return cp;
391}
392
393static const char *
394content_type_by_fileno(int fd)
395{
396	const char *cp;
397	off_t cur_pos;
398	int ofd;
399
400	cur_pos = lseek(fd, (off_t)0, SEEK_CUR);
401
402	ofd = dup(0);		/* save stdin */
403	if (dup2(fd, 0) == -1)	/* become stdin */
404		warn("dup2");
405
406	cp = content_type_by_name(NULL);
407
408	if (dup2(ofd, 0) == -1)	/* restore stdin */
409		warn("dup2");
410	(void)close(ofd);	/* close the copy */
411
412	(void)lseek(fd, cur_pos, SEEK_SET);
413	return cp;
414}
415
416static const char *
417content_type(struct attachment *ap)
418{
419	switch (ap->a_type) {
420	case ATTACH_FNAME:
421		return content_type_by_name(ap->a_name);
422	case ATTACH_MSG:
423		/*
424		 * Note: the encapusulated message header must include
425		 * at least one of the "Date:", "From:", or "Subject:"
426		 * fields.  See rfc2046 Sec 5.2.1.
427		 * XXX - Should we really test for this?
428		 */
429		return "message/rfc822";
430	case ATTACH_FILENO:
431		return content_type_by_fileno(ap->a_fileno);
432	case ATTACH_INVALID:
433	default:
434		/* This is a coding error! */
435		assert(/* CONSTCOND */ 0);
436		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
437		/* NOTREACHED */
438	}
439}
440
441/*************************
442 * Other content routines
443 */
444
445static const char *
446content_disposition(struct attachment *ap)
447{
448	switch (ap->a_type) {
449	case ATTACH_FNAME: {
450		char *disp;
451		(void)sasprintf(&disp, "attachment; filename=\"%s\"",
452		    basename(ap->a_name));
453		return disp;
454	}
455	case ATTACH_MSG:
456		return NULL;
457	case ATTACH_FILENO:
458		return "inline";
459
460	case ATTACH_INVALID:
461	default:
462		/* This is a coding error! */
463		assert(/* CONSTCOND */ 0);
464		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
465		/* NOTREACHED */
466	}
467}
468
469/*ARGSUSED*/
470static const char *
471content_id(struct attachment *ap __unused)
472{
473	/* XXX - to be written. */
474
475	return NULL;
476}
477
478static const char *
479content_description(struct attachment *attach, int attach_num)
480{
481	if (attach_num) {
482		char *description;
483		(void)sasprintf(&description, "attachment %d", attach_num);
484		return description;
485	}
486	else
487		return attach->a_Content.C_description;
488}
489
490/*******************************************
491 * Routines to get the MIME content strings.
492 */
493PUBLIC struct Content
494get_mime_content(struct attachment *ap, int i)
495{
496	struct Content Cp;
497
498	Cp.C_type	 = content_type(ap);
499	Cp.C_encoding	 = content_encoding(ap, Cp.C_type);
500	Cp.C_disposition = content_disposition(ap);
501	Cp.C_id		 = content_id(ap);
502	Cp.C_description = content_description(ap, i);
503
504	return Cp;
505}
506
507/******************
508 * Output routines
509 */
510static void
511fput_mime_content(FILE *fp, struct Content *Cp)
512{
513	(void)fprintf(fp, MIME_HDR_TYPE ": %s\n", Cp->C_type);
514	(void)fprintf(fp, MIME_HDR_ENCODING ": %s\n", Cp->C_encoding);
515	if (Cp->C_disposition)
516		(void)fprintf(fp, MIME_HDR_DISPOSITION ": %s\n",
517		    Cp->C_disposition);
518	if (Cp->C_id)
519		(void)fprintf(fp, MIME_HDR_ID ": %s\n", Cp->C_id);
520	if (Cp->C_description)
521		(void)fprintf(fp, MIME_HDR_DESCRIPTION ": %s\n",
522		    Cp->C_description);
523}
524
525static void
526fput_body(FILE *fi, FILE *fo, struct Content *Cp)
527{
528	mime_codec_t enc;
529
530	enc = mime_fio_encoder(Cp->C_encoding);
531	if (enc == NULL)
532		warnx("unknown transfer encoding type: %s", Cp->C_encoding);
533	else
534		enc(fi, fo, 0);
535}
536
537static void
538fput_attachment(FILE *fo, struct attachment *ap)
539{
540	FILE *fi;
541	struct Content *Cp = &ap->a_Content;
542
543	fput_mime_content(fo, &ap->a_Content);
544	(void)putc('\n', fo);
545
546	switch (ap->a_type) {
547	case ATTACH_FNAME:
548		fi = Fopen(ap->a_name, "ref");
549		if (fi == NULL)
550			err(EXIT_FAILURE, "Fopen: %s", ap->a_name);
551		break;
552
553	case ATTACH_FILENO:
554		/*
555		 * XXX - we should really dup(2) here, however we are
556		 * finished with the attachment, so the Fclose() below
557		 * is OK for now.  This will be changed in the future.
558		 */
559		fi = Fdopen(ap->a_fileno, "ref");
560		if (fi == NULL)
561			err(EXIT_FAILURE, "Fdopen: %d", ap->a_fileno);
562		break;
563
564	case ATTACH_MSG: {
565		char mailtempname[PATHSIZE];
566		int fd;
567
568		fi = NULL;	/* appease gcc */
569		(void)snprintf(mailtempname, sizeof(mailtempname),
570		    "%s/mail.RsXXXXXXXXXX", tmpdir);
571		if ((fd = mkstemp(mailtempname)) == -1 ||
572		    (fi = Fdopen(fd, "wef+")) == NULL) {
573			if (fd != -1)
574				(void)close(fd);
575			err(EXIT_FAILURE, "%s", mailtempname);
576		}
577		(void)rm(mailtempname);
578
579		/*
580		 * This is only used for forwarding, so use the forwardtab[].
581		 *
582		 * XXX - sendmessage really needs a 'flags' argument
583		 * so we don't have to play games.
584		 */
585		ap->a_msg->m_size--;	/* XXX - remove trailing newline */
586		(void)fputc('>', fi);	/* XXX - hide the headerline */
587		if (sendmessage(ap->a_msg, fi, forwardtab, NULL, NULL))
588			(void)fprintf(stderr, ". . . forward failed, sorry.\n");
589		ap->a_msg->m_size++;
590
591		rewind(fi);
592		break;
593	}
594	case ATTACH_INVALID:
595	default:
596		/* This is a coding error! */
597		assert(/* CONSTCOND */ 0);
598		errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
599	}
600
601	fput_body(fi, fo, Cp);
602	(void)Fclose(fi);
603}
604
605/***********************************
606 * Higher level attachment routines.
607 */
608
609static int
610mktemp_file(FILE **nfo, FILE **nfi, const char *hint)
611{
612	char tempname[PATHSIZE];
613	int fd, fd2;
614	(void)snprintf(tempname, sizeof(tempname), "%s/%sXXXXXXXXXX",
615	    tmpdir, hint);
616	if ((fd = mkstemp(tempname)) == -1 ||
617	    (*nfo = Fdopen(fd, "wef")) == NULL) {
618		if (fd != -1)
619			(void)close(fd);
620		warn("%s", tempname);
621		return -1;
622	}
623	(void)rm(tempname);
624	if ((fd2 = dup(fd)) == -1 ||
625	    (*nfi = Fdopen(fd2, "ref")) == NULL) {
626		warn("%s", tempname);
627		(void)Fclose(*nfo);
628		return -1;
629	}
630	return 0;
631}
632
633/*
634 * Repackage the mail as a multipart MIME message.  This should always
635 * be called whenever there are attachments, but might be called even
636 * if there are none if we want to wrap the message in a MIME package.
637 */
638PUBLIC FILE *
639mime_encode(FILE *fi, struct header *header)
640{
641	struct attachment map;	/* fake structure for the message body */
642	struct attachment *attach;
643	struct attachment *ap;
644	FILE *nfi, *nfo;
645
646	attach = header->h_attach;
647
648	/*
649	 * Make new phantom temporary file with read and write file
650	 * handles: nfi and nfo, resp.
651	 */
652	if (mktemp_file(&nfo, &nfi, "mail.Rs") != 0)
653		return fi;
654
655	(void)memset(&map, 0, sizeof(map));
656	map.a_type = ATTACH_FILENO;
657	map.a_fileno = fileno(fi);
658
659 	map.a_Content = get_mime_content(&map, 0);
660
661	if (attach) {
662		/* Multi-part message:
663		 * Make an attachment structure for the body message
664		 * and make that the first element in the attach list.
665		 */
666		if (fsize(fi)) {
667			map.a_flink = attach;
668			attach->a_blink = &map;
669			attach = &map;
670		}
671
672		/* Construct our MIME boundary string - used by mime_putheader() */
673		header->h_mime_boundary = make_boundary();
674
675		(void)fprintf(nfo, "This is a multi-part message in MIME format.\n");
676
677		for (ap = attach; ap; ap = ap->a_flink) {
678			(void)fprintf(nfo, "\n--%s\n", header->h_mime_boundary);
679			fput_attachment(nfo, ap);
680		}
681
682		/* the final boundary with two attached dashes */
683		(void)fprintf(nfo, "\n--%s--\n", header->h_mime_boundary);
684	}
685	else {
686		/* Single-part message (no attachments):
687		 * Update header->h_Content (used by mime_putheader()).
688		 * Output the body contents.
689		 */
690		char *encoding;
691
692		header->h_Content = map.a_Content;
693
694		/* check for an encoding override */
695		if ((encoding = value(ENAME_MIME_ENCODE_MSG)) && *encoding)
696			header->h_Content.C_encoding = encoding;
697
698		fput_body(fi, nfo, &header->h_Content);
699	}
700	(void)Fclose(fi);
701	(void)Fclose(nfo);
702	rewind(nfi);
703	return nfi;
704}
705
706static char*
707check_filename(char *filename, char *canon_name)
708{
709	int fd;
710	struct stat sb;
711	char *fname = filename;
712
713	/* We need to expand '~' if we got here from '~@'.  The shell
714	 * does this otherwise.
715	 */
716	if (fname[0] == '~' && fname[1] == '/') {
717		if (homedir && homedir[0] != '~')
718			(void)easprintf(&fname, "%s/%s",
719			    homedir, fname + 2);
720	}
721	if (realpath(fname, canon_name) == NULL) {
722		warn("realpath: %s", filename);
723		canon_name = NULL;
724		goto done;
725	}
726	fd = open(canon_name, O_RDONLY, 0);
727	if (fd == -1) {
728		warnx("open: cannot read %s", filename);
729		canon_name = NULL;
730		goto done;
731	}
732	if (fstat(fd, &sb) == -1) {
733		warn("stat: %s", canon_name);
734		canon_name = NULL;
735		goto do_close;
736	}
737	if (!S_ISREG(sb.st_mode)) {
738		warnx("stat: %s is not a file", filename);
739		canon_name = NULL;
740	     /*	goto do_close; */
741	}
742 do_close:
743	(void)close(fd);
744 done:
745	if (fname != filename)
746		free(fname);
747
748	return canon_name;
749}
750
751static struct attachment *
752attach_one_file(struct attachment *ap, char *filename, int attach_num)
753{
754	char canon_name[MAXPATHLEN];
755	struct attachment *nap;
756
757	/*
758	 * 1) check that filename is really a readable file; return NULL if not.
759	 * 2) allocate an attachment structure.
760	 * 3) save cananonical name for filename, so cd won't screw things later.
761	 * 4) add the structure to the end of the chain.
762	 * 5) return the new attachment structure.
763	 */
764	if (check_filename(filename, canon_name) == NULL)
765		return NULL;
766
767	nap = csalloc(1, sizeof(*nap));
768	nap->a_type = ATTACH_FNAME;
769	nap->a_name = savestr(canon_name);
770
771	if (ap) {
772		for (/*EMPTY*/; ap->a_flink != NULL; ap = ap->a_flink)
773			continue;
774		ap->a_flink = nap;
775		nap->a_blink = ap;
776	}
777
778	if (attach_num)
779		nap->a_Content = get_mime_content(nap, attach_num);
780
781	return nap;
782}
783
784static char *
785get_line(el_mode_t *em, const char *pr, const char *str, int i)
786{
787	char *prompt;
788	char *line;
789
790	/*
791	 * Don't use a '\t' in the format string here as completion
792	 * seems to handle it badly.
793	 */
794	(void)easprintf(&prompt, "#%-7d %s: ", i, pr);
795	line = my_gets(em, prompt, __UNCONST(str));
796	if (line != NULL) {
797		(void)strip_WSP(line);	/* strip trailing whitespace */
798		line = skip_WSP(line);	/* skip leading white space */
799		line = savestr(line);	/* XXX - do we need this? */
800	}
801	else {
802		line = __UNCONST("");
803	}
804	free(prompt);
805
806	return line;
807}
808
809static void
810sget_line(el_mode_t *em, const char *pr, const char **str, int i)
811{
812	char *line;
813	line = get_line(em, pr, *str, i);
814	if (line != NULL && strcmp(line, *str) != 0)
815		*str = line;
816}
817
818static void
819sget_encoding(const char **str, const char *filename, const char *ctype, int num)
820{
821	const char *ename;
822	const char *defename;
823
824	defename = NULL;
825	ename = *str;
826	for (;;) {
827		ename = get_line(&elm.mime_enc, "encoding", ename, num);
828
829		if (*ename == '\0') {
830			if (defename == NULL)
831				defename = content_encoding_by_name(filename, ctype);
832			ename = defename;
833		}
834		else if (mime_fio_encoder(ename) == NULL) {
835			const void *cookie;
836			(void)printf("Sorry: valid encoding modes are: ");
837			cookie = NULL;
838			ename = mime_next_encoding_name(&cookie);
839			for (;;) {
840				(void)printf("%s", ename);
841				ename = mime_next_encoding_name(&cookie);
842				if (ename == NULL)
843					break;
844				(void)fputc(',', stdout);
845			}
846			(void)putchar('\n');
847			ename = *str;
848		}
849		else {
850			if (strcmp(ename, *str) != 0)
851				*str = savestr(ename);
852			break;
853		}
854	}
855}
856
857/*
858 * Edit an attachment list.
859 * Return the new attachment list.
860 */
861static struct attachment *
862edit_attachlist(struct attachment *alist)
863{
864	struct attachment *ap;
865	char *line;
866	int attach_num;
867
868	(void)printf("Attachments:\n");
869
870	attach_num = 1;
871	ap = alist;
872	while (ap) {
873		SHOW_ALIST(alist, ap);
874
875		switch(ap->a_type) {
876		case ATTACH_MSG:
877			(void)printf("#%-7d message:  <not changeable>\n",
878			    attach_num);
879			break;
880		case ATTACH_FNAME:
881		case ATTACH_FILENO:
882			line = get_line(&elm.filec, "filename", ap->a_name, attach_num);
883			if (*line == '\0') {	/* omit this attachment */
884				if (ap->a_blink) {
885					struct attachment *next_ap;
886					next_ap = ap->a_flink;
887					ap = ap->a_blink;
888					ap->a_flink = next_ap;
889					if (next_ap)
890						next_ap->a_blink = ap;
891					else
892						goto done;
893				}
894				else {
895					alist = ap->a_flink;
896					if (alist)
897						alist->a_blink = NULL;
898				}
899			}
900			else {
901				char canon_name[MAXPATHLEN];
902				if (strcmp(line, ap->a_name) != 0) { /* new filename */
903					if (check_filename(line, canon_name) == NULL)
904						continue;
905					ap->a_name = savestr(canon_name);
906					ap->a_Content = get_mime_content(ap, 0);
907				}
908				sget_line(&elm.string, "description",
909				    &ap->a_Content.C_description, attach_num);
910				sget_encoding(&ap->a_Content.C_encoding, ap->a_name,
911				    ap->a_Content.C_type, attach_num);
912			}
913			break;
914		case ATTACH_INVALID:
915		default:
916			/* This is a coding error! */
917			assert(/* CONSTCOND */ 0);
918			errx(EXIT_FAILURE, "invalid attachment type: %d",
919			    ap->a_type);
920		}
921
922		attach_num++;
923		if (alist == NULL || ap->a_flink == NULL)
924			break;
925
926		ap = ap->a_flink;
927	}
928
929	ap = alist;
930	for (;;) {
931		struct attachment *nap;
932
933		SHOW_ALIST(alist, ap);
934
935		line = get_line(&elm.filec, "filename", "", attach_num);
936		if (*line == '\0')
937			break;
938
939		nap = attach_one_file(ap, line, attach_num);
940		if (nap == NULL)
941			continue;
942
943		if (alist == NULL)
944			alist = nap;
945		ap = nap;
946
947		sget_line(&elm.string, "description",
948		    &ap->a_Content.C_description, attach_num);
949		sget_encoding(&ap->a_Content.C_encoding, ap->a_name,
950		    ap->a_Content.C_type, attach_num);
951		attach_num++;
952	}
953 done:
954	SHOW_ALIST(alist, ap);
955
956	return alist;
957}
958
959/*
960 * Hook used by the '~@' escape to attach files.
961 */
962PUBLIC struct attachment*
963mime_attach_files(struct attachment * volatile attach, char *linebuf)
964{
965	struct attachment *ap;
966	char *argv[MAXARGC];
967	int argc;
968	int attach_num;
969
970	argc = getrawlist(linebuf, argv, (int)__arraycount(argv));
971	attach_num = 1;
972	for (ap = attach; ap && ap->a_flink; ap = ap->a_flink)
973			attach_num++;
974
975	if (argc) {
976		int i;
977		for (i = 0; i < argc; i++) {
978			struct attachment *ap2;
979			ap2 = attach_one_file(ap, argv[i], attach_num);
980			if (ap2 != NULL) {
981				ap = ap2;
982				if (attach == NULL)
983					attach = ap;
984				attach_num++;
985			}
986		}
987	}
988	else {
989		attach = edit_attachlist(attach);
990		(void)printf("--- end attachments ---\n");
991	}
992
993	return attach;
994}
995
996/*
997 * Hook called in main() to attach files registered by the '-a' flag.
998 */
999PUBLIC struct attachment *
1000mime_attach_optargs(struct name *optargs)
1001{
1002	struct attachment *attach;
1003	struct attachment *ap;
1004	struct name *np;
1005	char *expand_optargs;
1006	int attach_num;
1007
1008	expand_optargs = value(ENAME_MIME_ATTACH_LIST);
1009	attach_num = 1;
1010	ap = NULL;
1011	attach = NULL;
1012	for (np = optargs; np; np = np->n_flink) {
1013		char *argv[MAXARGC];
1014		int argc;
1015		int i;
1016
1017		if (expand_optargs != NULL)
1018			argc = getrawlist(np->n_name,
1019			    argv, (int)__arraycount(argv));
1020		else {
1021			if (np->n_name == NULL)
1022				argc = 0;
1023			else {
1024				argc = 1;
1025				argv[0] = np->n_name;
1026			}
1027			argv[argc] = NULL;/* be consistent with getrawlist() */
1028		}
1029		for (i = 0; i < argc; i++) {
1030			struct attachment *ap2;
1031			char *filename;
1032
1033			if (argv[i][0] == '/')	/* an absolute path */
1034				(void)easprintf(&filename, "%s", argv[i]);
1035			else
1036				(void)easprintf(&filename, "%s/%s",
1037				    origdir, argv[i]);
1038
1039			ap2 = attach_one_file(ap, filename, attach_num);
1040			if (ap2 != NULL) {
1041				ap = ap2;
1042				if (attach == NULL)
1043					attach = ap;
1044				attach_num++;
1045			}
1046			free(filename);
1047		}
1048	}
1049	return attach;
1050}
1051
1052/*
1053 * Output MIME header strings as specified in the header structure.
1054 */
1055PUBLIC void
1056mime_putheader(FILE *fp, struct header *header)
1057{
1058	(void)fprintf(fp, MIME_HDR_VERSION ": " MIME_VERSION "\n");
1059	if (header->h_attach) {
1060		(void)fprintf(fp, MIME_HDR_TYPE ": multipart/mixed;\n");
1061		(void)fprintf(fp, "\tboundary=\"%s\"\n", header->h_mime_boundary);
1062	}
1063	else {
1064		fput_mime_content(fp, &header->h_Content);
1065	}
1066}
1067
1068#endif /* MIME_SUPPORT */
1069