NetBSD/usr.bin/mail/mime_attach.c

1057 lines
24 KiB
C
Raw Normal View History

/* $NetBSD: mime_attach.c,v 1.10 2008/04/28 20:24:14 martin Exp $ */
/*-
* Copyright (c) 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Anon Ymous.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef MIME_SUPPORT
#include <sys/cdefs.h>
#ifndef __lint__
__RCSID("$NetBSD: mime_attach.c,v 1.10 2008/04/28 20:24:14 martin Exp $");
#endif /* not __lint__ */
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <libgen.h>
#include <magic.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include "def.h"
#include "extern.h"
#ifdef USE_EDITLINE
#include "complete.h"
#endif
#ifdef MIME_SUPPORT
#include "mime.h"
#include "mime_codecs.h"
#include "mime_child.h"
#endif
#include "glob.h"
#if 0
/*
* XXX - This block is for debugging only and eventually should go away.
*/
# define SHOW_ALIST(a,b) show_alist(a,b)
static void
show_alist(struct attachment *alist, struct attachment *ap)
{
(void)printf("alist=%p ap=%p\n", alist, ap);
for (ap = alist; ap; ap = ap->a_flink) {
(void)printf("ap=%p ap->a_flink=%p ap->a_blink=%p ap->a_name=%s\n",
ap, ap->a_flink, ap->a_blink, ap->a_name ? ap->a_name : "<null>");
}
}
#else
# define SHOW_ALIST(a,b)
#endif
#if 0
#ifndef __lint__ /* Don't lint: the public routines may not be used. */
/*
* XXX - This block for is debugging only and eventually should go away.
*/
static void
show_name(const char *prefix, struct name *np)
{
int i;
i = 0;
for (/*EMPTY*/; np; np = np->n_flink) {
(void)printf("%s[%d]: %s\n", prefix, i, np->n_name);
i++;
}
}
static void fput_mime_content(FILE *fp, struct Content *Cp);
PUBLIC void
show_attach(const char *prefix, struct attachment *ap)
{
int i;
i = 1;
for (/*EMPTY*/; ap; ap = ap->a_flink) {
(void)printf("%s[%d]:\n", prefix, i);
fput_mime_content(stdout, &ap->a_Content);
i++;
}
}
PUBLIC void
show_header(struct header *hp)
{
show_name("TO", hp->h_to);
(void)printf("SUBJECT: %s\n", hp->h_subject);
show_name("CC", hp->h_cc);
show_name("BCC", hp->h_bcc);
show_name("SMOPTS", hp->h_smopts);
show_attach("ATTACH", hp->h_attach);
}
#endif /* __lint__ */
#endif
/***************************
* boundary string routines
*/
static char *
getrandstring(size_t length)
{
void *vbin;
uint32_t *bin;
size_t binlen;
size_t i;
char *b64;
/* XXX - check this stuff again!!! */
binlen = 3 * roundup(length, 4) / 4; /* bytes of binary to encode base64 */
bin = vbin = salloc(roundup(binlen, 4));
for (i = 0; i < roundup(binlen, 4) / 4; i++)
bin[i] = arc4random();
b64 = salloc(roundup(length, 4));
mime_bintob64(b64, vbin, binlen);
b64[length] = '\0';
return b64;
}
/*
* Generate a boundary for MIME multipart messages.
*/
static char *
make_boundary(void)
{
#define BOUND_LEN 70 /* maximum length is 70 characters: RFC2046 sec 5.1.1 */
char *bound;
time_t now;
(void)time(&now);
bound = salloc(BOUND_LEN);
(void)snprintf(bound, BOUND_LEN, "=_%08lx.%s",
(long)now, getrandstring(BOUND_LEN - 12));
return bound;
#undef BOUND_LEN
}
/***************************
* Transfer coding routines
*/
/*
* We determine the recommended transfer encoding type for a file as
* follows:
*
* 1) If there is a NULL byte or a stray CR (not in a CRLF
* combination) in the file, play it safe and use base64.
*
* 2) If any high bit is set, use quoted-printable if the content type
* is "text" and base64 otherwise.
*
* 3) Otherwise:
* a) use quoted-printable if there are any long lines, control
* chars (including CR), end-of-line blank space, or a missing
* terminating NL.
* b) use 7bit in all remaining case, including an empty file.
*
* NOTE: This means that CRLF text (MSDOS) files will be encoded
* quoted-printable.
*/
/*
* RFC 821 imposes the following line length limit:
* The maximum total length of a text line including the
* <CRLF> is 1000 characters (but not counting the leading
* dot duplicated for transparency).
*/
#define MIME_UNENCODED_LINE_MAX (1000 - 2)
static size_t
line_limit(void)
{
int limit;
const char *cp;
limit = -1;
if ((cp = value(ENAME_MIME_UNENC_LINE_MAX)) != NULL)
limit = atoi(cp);
if (limit < 0 || limit > MIME_UNENCODED_LINE_MAX)
limit = MIME_UNENCODED_LINE_MAX;
return (size_t)limit;
}
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
static inline int
is_text(const char *ctype)
{
return ctype &&
strncasecmp(ctype, "text/", sizeof("text/") - 1) == 0;
}
static const char *
content_encoding_core(void *fh, const char *ctype)
{
int c, lastc;
int ctrlchar, endwhite;
size_t curlen, maxlen;
curlen = 0;
maxlen = 0;
ctrlchar = 0;
endwhite = 0;
lastc = EOF;
while ((c = fgetc(fh)) != EOF) {
curlen++;
if (c == '\0' || (lastc == '\r' && c != '\n'))
return MIME_TRANSFER_BASE64;
if (c > 0x7f) {
if (is_text(ctype))
return MIME_TRANSFER_QUOTED;
else
return MIME_TRANSFER_BASE64;
}
if (c == '\n') {
if (is_WSP(lastc))
endwhite = 1;
if (curlen > maxlen)
maxlen = curlen;
curlen = 0;
}
else if ((c < 0x20 && c != '\t') || c == 0x7f)
ctrlchar = 1;
lastc = c;
}
if (lastc == EOF) /* no characters read */
return MIME_TRANSFER_7BIT;
if (lastc != '\n' || ctrlchar || endwhite || maxlen > line_limit())
return MIME_TRANSFER_QUOTED;
return MIME_TRANSFER_7BIT;
}
static const char *
content_encoding_by_name(const char *filename, const char *ctype)
{
FILE *fp;
const char *enc;
fp = Fopen(filename, "r");
if (fp == NULL) {
warn("content_encoding_by_name: %s", filename);
return MIME_TRANSFER_BASE64; /* safe */
}
enc = content_encoding_core(fp, ctype);
(void)Fclose(fp);
return enc;
}
static const char *
content_encoding_by_fileno(int fd, const char *ctype)
{
FILE *fp;
int fd2;
const char *encoding;
off_t cur_pos;
cur_pos = lseek(fd, (off_t)0, SEEK_CUR);
if ((fd2 = dup(fd)) == -1 ||
(fp = Fdopen(fd2, "r")) == NULL) {
warn("content_encoding_by_fileno");
if (fd2 != -1)
(void)close(fd2);
return MIME_TRANSFER_BASE64;
}
encoding = content_encoding_core(fp, ctype);
(void)Fclose(fp);
(void)lseek(fd, cur_pos, SEEK_SET);
return encoding;
}
static const char *
content_encoding(struct attachment *ap, const char *ctype)
{
switch (ap->a_type) {
case ATTACH_FNAME:
return content_encoding_by_name(ap->a_name, ctype);
case ATTACH_MSG:
return "7bit";
case ATTACH_FILENO:
return content_encoding_by_fileno(ap->a_fileno, ctype);
case ATTACH_INVALID:
default:
/* This is a coding error! */
assert(/* CONSTCOND */ 0);
errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
/* NOTREACHED */
}
}
/************************
* Content type routines
*/
/*
* We use libmagic(3) to get the content type, except in the case of a
* 0 or 1 byte file where libmagic gives rather useless results.
*/
static const char *
content_type_by_name(char *filename)
{
const char *cp;
char *cp2;
magic_t magic;
struct stat sb;
/*
* libmagic produces annoying results on very short files.
* The common case is with mime-encode-message defined and an
* empty message body.
*
* Note: a 1-byte message body always consists of a newline,
* so size determines all there. However, 1-byte attachments
* (filename != NULL) could be anything, so check those.
*/
if ((filename != NULL && stat(filename, &sb) == 0) ||
(filename == NULL && fstat(0, &sb) == 0)) {
if (sb.st_size < 2 && S_ISREG(sb.st_mode)) {
FILE *fp;
int ch;
if (sb.st_size == 0 || filename == NULL ||
(fp = Fopen(filename, "r")) == NULL)
return "text/plain";
ch = fgetc(fp);
(void)Fclose(fp);
return isprint(ch) || isspace(ch) ?
"text/plain" : "application/octet-stream";
}
}
magic = magic_open(MAGIC_MIME);
if (magic == NULL) {
warn("magic_open: %s", magic_error(magic));
return NULL;
}
if (magic_load(magic, NULL) != 0) {
warn("magic_load: %s", magic_error(magic));
return NULL;
}
cp = magic_file(magic, filename);
if (cp == NULL) {
warn("magic_load: %s", magic_error(magic));
return NULL;
}
if (filename &&
sasprintf(&cp2, "%s; name=\"%s\"", cp, basename(filename)) != -1)
cp = cp2;
else
cp = savestr(cp);
magic_close(magic);
return cp;
}
static const char *
content_type_by_fileno(int fd)
{
const char *cp;
off_t cur_pos;
int ofd;
cur_pos = lseek(fd, (off_t)0, SEEK_CUR);
ofd = dup(0); /* save stdin */
if (dup2(fd, 0) == -1) /* become stdin */
warn("dup2");
cp = content_type_by_name(NULL);
if (dup2(ofd, 0) == -1) /* restore stdin */
warn("dup2");
(void)close(ofd); /* close the copy */
(void)lseek(fd, cur_pos, SEEK_SET);
return cp;
}
static const char *
content_type(struct attachment *ap)
{
switch (ap->a_type) {
case ATTACH_FNAME:
return content_type_by_name(ap->a_name);
case ATTACH_MSG:
/*
* Note: the encapusulated message header must include
* at least one of the "Date:", "From:", or "Subject:"
* fields. See rfc2046 Sec 5.2.1.
* XXX - Should we really test for this?
*/
return "message/rfc822";
case ATTACH_FILENO:
return content_type_by_fileno(ap->a_fileno);
case ATTACH_INVALID:
default:
/* This is a coding error! */
assert(/* CONSTCOND */ 0);
errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
/* NOTREACHED */
}
}
/*************************
* Other content routines
*/
static const char *
content_disposition(struct attachment *ap)
{
switch (ap->a_type) {
case ATTACH_FNAME: {
char *disp;
(void)sasprintf(&disp, "attachment; filename=\"%s\"",
basename(ap->a_name));
return disp;
}
case ATTACH_MSG:
return NULL;
case ATTACH_FILENO:
return "inline";
case ATTACH_INVALID:
default:
/* This is a coding error! */
assert(/* CONSTCOND */ 0);
errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
/* NOTREACHED */
}
}
/*ARGSUSED*/
static const char *
content_id(struct attachment *ap __unused)
{
/* XXX - to be written. */
return NULL;
}
static const char *
content_description(struct attachment *attach, int attach_num)
{
if (attach_num) {
char *description;
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
(void)sasprintf(&description, "attachment %d", attach_num);
return description;
}
else
return attach->a_Content.C_description;
}
/*******************************************
* Routines to get the MIME content strings.
*/
PUBLIC struct Content
get_mime_content(struct attachment *ap, int i)
{
struct Content Cp;
Cp.C_type = content_type(ap);
Cp.C_encoding = content_encoding(ap, Cp.C_type);
Cp.C_disposition = content_disposition(ap);
Cp.C_id = content_id(ap);
Cp.C_description = content_description(ap, i);
return Cp;
}
/******************
* Output routines
*/
static void
fput_mime_content(FILE *fp, struct Content *Cp)
{
(void)fprintf(fp, MIME_HDR_TYPE ": %s\n", Cp->C_type);
(void)fprintf(fp, MIME_HDR_ENCODING ": %s\n", Cp->C_encoding);
if (Cp->C_disposition)
(void)fprintf(fp, MIME_HDR_DISPOSITION ": %s\n",
Cp->C_disposition);
if (Cp->C_id)
(void)fprintf(fp, MIME_HDR_ID ": %s\n", Cp->C_id);
if (Cp->C_description)
(void)fprintf(fp, MIME_HDR_DESCRIPTION ": %s\n",
Cp->C_description);
}
static void
fput_body(FILE *fi, FILE *fo, struct Content *Cp)
{
mime_codec_t enc;
enc = mime_fio_encoder(Cp->C_encoding);
if (enc == NULL)
warnx("unknown transfer encoding type: %s\n", Cp->C_encoding);
else
enc(fi, fo, 0);
}
static void
fput_attachment(FILE *fo, struct attachment *ap)
{
FILE *fi;
struct Content *Cp = &ap->a_Content;
fput_mime_content(fo, &ap->a_Content);
(void)putc('\n', fo);
switch (ap->a_type) {
case ATTACH_FNAME:
fi = Fopen(ap->a_name, "r");
if (fi == NULL)
err(EXIT_FAILURE, "Fopen: %s", ap->a_name);
break;
case ATTACH_FILENO:
/*
* XXX - we should really dup(2) here, however we are
* finished with the attachment, so the Fclose() below
* is OK for now. This will be changed in the future.
*/
fi = Fdopen(ap->a_fileno, "r");
if (fi == NULL)
err(EXIT_FAILURE, "Fdopen: %d", ap->a_fileno);
break;
case ATTACH_MSG: {
char mailtempname[PATHSIZE];
int fd;
fi = NULL; /* appease gcc */
(void)snprintf(mailtempname, sizeof(mailtempname),
"%s/mail.RsXXXXXXXXXX", tmpdir);
if ((fd = mkstemp(mailtempname)) == -1 ||
(fi = Fdopen(fd, "w+")) == NULL) {
if (fd != -1)
(void)close(fd);
err(EXIT_FAILURE, "%s", mailtempname);
}
(void)rm(mailtempname);
/*
* This is only used for forwarding, so use the forwardtab[].
*
* XXX - sendmessage really needs a 'flags' argument
* so we don't have to play games.
*/
ap->a_msg->m_size--; /* XXX - remove trailing newline */
(void)fputc('>', fi); /* XXX - hide the headerline */
if (sendmessage(ap->a_msg, fi, forwardtab, NULL, NULL))
(void)fprintf(stderr, ". . . forward failed, sorry.\n");
ap->a_msg->m_size++;
rewind(fi);
break;
}
case ATTACH_INVALID:
default:
/* This is a coding error! */
assert(/* CONSTCOND */ 0);
errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type);
}
fput_body(fi, fo, Cp);
(void)Fclose(fi);
}
/***********************************
* Higher level attachment routines.
*/
static int
mktemp_file(FILE **nfo, FILE **nfi, const char *hint)
{
char tempname[PATHSIZE];
int fd, fd2;
(void)snprintf(tempname, sizeof(tempname), "%s/%sXXXXXXXXXX",
tmpdir, hint);
if ((fd = mkstemp(tempname)) == -1 ||
(*nfo = Fdopen(fd, "w")) == NULL) {
if (fd != -1)
(void)close(fd);
warn("%s", tempname);
return -1;
}
(void)rm(tempname);
if ((fd2 = dup(fd)) == -1 ||
(*nfi = Fdopen(fd2, "r")) == NULL) {
warn("%s", tempname);
(void)Fclose(*nfo);
return -1;
}
return 0;
}
/*
* Repackage the mail as a multipart MIME message. This should always
* be called whenever there are attachments, but might be called even
* if there are none if we want to wrap the message in a MIME package.
*/
PUBLIC FILE *
mime_encode(FILE *fi, struct header *header)
{
struct attachment map; /* fake structure for the message body */
struct attachment *attach;
struct attachment *ap;
FILE *nfi, *nfo;
attach = header->h_attach;
/*
* Make new phantom temporary file with read and write file
* handles: nfi and nfo, resp.
*/
if (mktemp_file(&nfo, &nfi, "mail.Rs") != 0)
return fi;
(void)memset(&map, 0, sizeof(map));
map.a_type = ATTACH_FILENO;
map.a_fileno = fileno(fi);
map.a_Content = get_mime_content(&map, 0);
if (attach) {
/* Multi-part message:
* Make an attachment structure for the body message
* and make that the first element in the attach list.
*/
if (fsize(fi)) {
map.a_flink = attach;
attach->a_blink = &map;
attach = &map;
}
/* Construct our MIME boundary string - used by mime_putheader() */
header->h_mime_boundary = make_boundary();
(void)fprintf(nfo, "This is a multi-part message in MIME format.\n");
for (ap = attach; ap; ap = ap->a_flink) {
(void)fprintf(nfo, "\n--%s\n", header->h_mime_boundary);
fput_attachment(nfo, ap);
}
/* the final boundary with two attached dashes */
(void)fprintf(nfo, "\n--%s--\n", header->h_mime_boundary);
}
else {
/* Single-part message (no attachments):
* Update header->h_Content (used by mime_putheader()).
* Output the body contents.
*/
char *encoding;
header->h_Content = map.a_Content;
/* check for an encoding override */
if ((encoding = value(ENAME_MIME_ENCODE_MSG)) && *encoding)
header->h_Content.C_encoding = encoding;
fput_body(fi, nfo, &header->h_Content);
}
(void)Fclose(fi);
(void)Fclose(nfo);
rewind(nfi);
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
return nfi;
}
static char*
check_filename(char *filename, char *canon_name)
{
int fd;
struct stat sb;
char *fname = filename;
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
/* We need to expand '~' if we got here from '~@'. The shell
* does this otherwise.
*/
if (fname[0] == '~' && fname[1] == '/') {
if (homedir && homedir[0] != '~')
(void)easprintf(&fname, "%s/%s",
homedir, fname + 2);
}
if (realpath(fname, canon_name) == NULL) {
warn("realpath: %s", filename);
canon_name = NULL;
goto done;
}
fd = open(canon_name, O_RDONLY, 0);
if (fd == -1) {
warnx("open: cannot read %s", filename);
canon_name = NULL;
goto done;
}
if (fstat(fd, &sb) == -1) {
warn("stat: %s", canon_name);
canon_name = NULL;
goto do_close;
}
if (!S_ISREG(sb.st_mode)) {
warnx("stat: %s is not a file", filename);
canon_name = NULL;
/* goto do_close; */
}
do_close:
(void)close(fd);
done:
if (fname != filename)
free(fname);
return canon_name;
}
static struct attachment *
attach_one_file(struct attachment *ap, char *filename, int attach_num)
{
char canon_name[MAXPATHLEN];
struct attachment *nap;
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
/*
* 1) check that filename is really a readable file; return NULL if not.
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
* 2) allocate an attachment structure.
* 3) save cananonical name for filename, so cd won't screw things later.
* 4) add the structure to the end of the chain.
* 5) return the new attachment structure.
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
*/
if (check_filename(filename, canon_name) == NULL)
return NULL;
nap = csalloc(1, sizeof(*nap));
nap->a_type = ATTACH_FNAME;
nap->a_name = savestr(canon_name);
if (ap) {
for (/*EMPTY*/; ap->a_flink != NULL; ap = ap->a_flink)
continue;
ap->a_flink = nap;
nap->a_blink = ap;
}
if (attach_num)
nap->a_Content = get_mime_content(nap, attach_num);
return nap;
}
static char *
get_line(el_mode_t *em, const char *pr, const char *str, int i)
{
char *prompt;
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
char *line;
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
/*
* Don't use a '\t' in the format string here as completion
* seems to handle it badly.
*/
(void)easprintf(&prompt, "#%-7d %s: ", i, pr);
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
line = my_getline(em, prompt, __UNCONST(str));
/* LINTED */
line = line ? savestr(line) : __UNCONST("");
free(prompt);
From Anon Ymous: 1) Statification of modules. 2) Implement the 'detach' and 'Detach' commands for extracting mime parts from messages. 3) Teach mail to output "In-Reply-To" and "References" header fields when replying so others can thread us. 4) Implement threading, sorting, and tagging, supported by the following commands: 'flatten', 'reverse', 'sort', 'thread', 'unthread', 'down', 'tset', 'up', 'expose', 'hide', 'tag', 'untag', 'invtags', 'tagbelow', 'hidetags', 'showtags'. See the manpage for details (when available - soon). 5) Implement a 'deldups' command to delete duplicate messages based on their "Message-Id" field, e.g., in replies to a mailing list that are also CCed to a subscriber. (This can also be accomplished with the threading and tagging commands.) 6) Implement 'ifdef' and 'ifndef' commands, and make the conditionals nestable (i.e., implement a conditional stack). The if/else/endif commands existed before, but they were primitive and undocumented. The 'if' command currently recognizes the "receiving", "sending", and "headersonly" mode keywords. 7) Teach the message selecting routine to understand regular expressions if "regex-search" is defined. Otherwise only case insensitive substring matches are done (as in the past). 8) Teach the message selection routine to understand boolean expressions. Improved "colon-modifier" support. See the manpage for details (when available - soon). 9) Extend paging to all commands (where relevant). 10) Add shell like piping and redirection of (standard) output (if "enable-piping" is defined). Extend completion to these contexts. 11) The manpage should follow soon!!!!
2006-11-28 21:45:32 +03:00
return line;
}
static void
sget_line(el_mode_t *em, const char *pr, const char **str, int i)
{
char *line;
line = get_line(em, pr, *str, i);
if (strcmp(line, *str) != 0)
*str = savestr(line);
}
static void
sget_encoding(const char **str, const char *filename, const char *ctype, int num)
{
const char *ename;
const char *defename;
defename = NULL;
ename = *str;
for (;;) {
ename = get_line(&elm.mime_enc, "encoding", ename, num);
if (*ename == '\0') {
if (defename == NULL)
defename = content_encoding_by_name(filename, ctype);
ename = defename;
}
else if (mime_fio_encoder(ename) == NULL) {
const void *cookie;
(void)printf("Sorry: valid encoding modes are: ");
cookie = NULL;
ename = mime_next_encoding_name(&cookie);
for (;;) {
(void)printf("%s", ename);
ename = mime_next_encoding_name(&cookie);
if (ename == NULL)
break;
(void)fputc(',', stdout);
}
(void)putchar('\n');
ename = *str;
}
else {
if (strcmp(ename, *str) != 0)
*str = savestr(ename);
break;
}
}
}
/*
* Edit an attachment list.
* Return the new attachment list.
*/
static struct attachment *
edit_attachlist(struct attachment *alist)
{
struct attachment *ap;
char *line;
int attach_num;
(void)printf("Attachments:\n");
attach_num = 1;
ap = alist;
while (ap) {
SHOW_ALIST(alist, ap);
switch(ap->a_type) {
case ATTACH_MSG:
(void)printf("#%-7d message: <not changeable>\n",
attach_num);
break;
case ATTACH_FNAME:
case ATTACH_FILENO:
line = get_line(&elm.filec, "filename", ap->a_name, attach_num);
if (*line == '\0') { /* omit this attachment */
if (ap->a_blink) {
struct attachment *next_ap;
next_ap = ap->a_flink;
ap = ap->a_blink;
ap->a_flink = next_ap;
if (next_ap)
next_ap->a_blink = ap;
else
goto done;
}
else {
alist = ap->a_flink;
if (alist)
alist->a_blink = NULL;
}
}
else {
char canon_name[MAXPATHLEN];
if (strcmp(line, ap->a_name) != 0) { /* new filename */
if (check_filename(line, canon_name) == NULL)
continue;
ap->a_name = savestr(canon_name);
ap->a_Content = get_mime_content(ap, 0);
}
sget_line(&elm.string, "description",
&ap->a_Content.C_description, attach_num);
sget_encoding(&ap->a_Content.C_encoding, ap->a_name,
ap->a_Content.C_type, attach_num);
}
break;
case ATTACH_INVALID:
default:
/* This is a coding error! */
assert(/* CONSTCOND */ 0);
errx(EXIT_FAILURE, "invalid attachment type: %d",
ap->a_type);
}
attach_num++;
if (alist == NULL || ap->a_flink == NULL)
break;
ap = ap->a_flink;
}
ap = alist;
for (;;) {
struct attachment *nap;
SHOW_ALIST(alist, ap);
line = get_line(&elm.filec, "filename", "", attach_num);
if (*line == '\0')
break;
nap = attach_one_file(ap, line, attach_num);
if (nap == NULL)
continue;
if (alist == NULL)
alist = nap;
ap = nap;
sget_line(&elm.string, "description",
&ap->a_Content.C_description, attach_num);
sget_encoding(&ap->a_Content.C_encoding, ap->a_name,
ap->a_Content.C_type, attach_num);
attach_num++;
}
done:
SHOW_ALIST(alist, ap);
return alist;
}
/*
* Hook used by the '~@' escape to attach files.
*/
PUBLIC struct attachment*
mime_attach_files(struct attachment *attach, char *linebuf)
{
struct attachment *ap;
char *argv[MAXARGC];
int argc;
int attach_num;
argc = getrawlist(linebuf, argv, sizeofarray(argv));
attach_num = 1;
for (ap = attach; ap && ap->a_flink; ap = ap->a_flink)
attach_num++;
if (argc) {
int i;
for (i = 0; i < argc; i++) {
struct attachment *ap2;
ap2 = attach_one_file(ap, argv[i], attach_num);
if (ap2 != NULL) {
ap = ap2;
if (attach == NULL)
attach = ap;
attach_num++;
}
}
}
else {
attach = edit_attachlist(attach);
(void)printf("--- end attachments ---\n");
}
return attach;
}
/*
* Hook called in main() to attach files registered by the '-a' flag.
*/
PUBLIC struct attachment *
mime_attach_optargs(struct name *optargs)
{
struct attachment *attach;
struct attachment *ap;
struct name *np;
char *expand_optargs;
int attach_num;
expand_optargs = value(ENAME_MIME_ATTACH_LIST);
attach_num = 1;
ap = NULL;
attach = NULL;
for (np = optargs; np; np = np->n_flink) {
char *argv[MAXARGC];
int argc;
int i;
if (expand_optargs != NULL)
argc = getrawlist(np->n_name, argv, sizeofarray(argv));
else {
if (np->n_name == '\0')
argc = 0;
else {
argc = 1;
argv[0] = np->n_name;
}
argv[argc] = NULL;/* be consistent with getrawlist() */
}
for (i = 0; i < argc; i++) {
struct attachment *ap2;
char *filename;
if (argv[i][0] == '/') /* an absolute path */
(void)easprintf(&filename, "%s", argv[i]);
else
(void)easprintf(&filename, "%s/%s",
origdir, argv[i]);
ap2 = attach_one_file(ap, filename, attach_num);
if (ap2 != NULL) {
ap = ap2;
if (attach == NULL)
attach = ap;
attach_num++;
}
free(filename);
}
}
return attach;
}
/*
* Output MIME header strings as specified in the header structure.
*/
PUBLIC void
mime_putheader(FILE *fp, struct header *header)
{
(void)fprintf(fp, MIME_HDR_VERSION ": " MIME_VERSION "\n");
if (header->h_attach) {
(void)fprintf(fp, MIME_HDR_TYPE ": multipart/mixed;\n");
(void)fprintf(fp, "\tboundary=\"%s\"\n", header->h_mime_boundary);
}
else {
fput_mime_content(fp, &header->h_Content);
}
}
#endif /* MIME_SUPPORT */