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 = ↦
6698207b28aSchristos attach = ↦
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