1694872231
If input mail does not have a newline on the last line dma must add
one. This was broken by the addition of long-line splitting, with the
switch from strlen(line) to linelen returned by getline().
PR: 266629
Reviewed by: bapt, Mikko Lehto
Tested by: Mikko Lehto
MFC after: 1 week
Fixes: b0b2d05fd0
("Split body of mails not respecting...")
Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D36763
524 lines
11 KiB
C
524 lines
11 KiB
C
/*
|
|
* Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
|
|
* Copyright (c) 2008 The DragonFly Project. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The DragonFly Project
|
|
* by Simon Schubert <2@0x2c.org>.
|
|
*
|
|
* 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.
|
|
* 3. Neither the name of The DragonFly Project nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific, prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
|
|
* COPYRIGHT HOLDERS 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.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <malloc_np.h>
|
|
#include <signal.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#include "dma.h"
|
|
|
|
#define MAX_LINE_RFC822 999 /* 998 characters plus \n */
|
|
|
|
void
|
|
bounce(struct qitem *it, const char *reason)
|
|
{
|
|
struct queue bounceq;
|
|
char line[1000];
|
|
size_t pos;
|
|
int error;
|
|
|
|
/* Don't bounce bounced mails */
|
|
if (it->sender[0] == 0) {
|
|
syslog(LOG_INFO, "can not bounce a bounce message, discarding");
|
|
exit(EX_SOFTWARE);
|
|
}
|
|
|
|
bzero(&bounceq, sizeof(bounceq));
|
|
LIST_INIT(&bounceq.queue);
|
|
bounceq.sender = "";
|
|
if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0)
|
|
goto fail;
|
|
|
|
if (newspoolf(&bounceq) != 0)
|
|
goto fail;
|
|
|
|
syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id);
|
|
setlogident("%s", bounceq.id);
|
|
|
|
error = fprintf(bounceq.mailf,
|
|
"Received: from MAILER-DAEMON\n"
|
|
"\tid %s\n"
|
|
"\tby %s (%s on %s);\n"
|
|
"\t%s\n"
|
|
"X-Original-To: <%s>\n"
|
|
"From: MAILER-DAEMON <>\n"
|
|
"To: %s\n"
|
|
"Subject: Mail delivery failed\n"
|
|
"Message-Id: <%s@%s>\n"
|
|
"Date: %s\n"
|
|
"\n"
|
|
"This is the %s at %s.\n"
|
|
"\n"
|
|
"There was an error delivering your mail to <%s>.\n"
|
|
"\n"
|
|
"%s\n"
|
|
"\n"
|
|
"%s\n"
|
|
"\n",
|
|
bounceq.id,
|
|
hostname(), VERSION, systemhostname(),
|
|
rfc822date(),
|
|
it->addr,
|
|
it->sender,
|
|
bounceq.id, hostname(),
|
|
rfc822date(),
|
|
VERSION, hostname(),
|
|
it->addr,
|
|
reason,
|
|
config.features & FULLBOUNCE ?
|
|
"Original message follows." :
|
|
"Message headers follow.");
|
|
if (error < 0)
|
|
goto fail;
|
|
|
|
if (fseek(it->mailf, 0, SEEK_SET) != 0)
|
|
goto fail;
|
|
if (config.features & FULLBOUNCE) {
|
|
while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
|
|
if (fwrite(line, 1, pos, bounceq.mailf) != pos)
|
|
goto fail;
|
|
}
|
|
} else {
|
|
while (!feof(it->mailf)) {
|
|
if (fgets(line, sizeof(line), it->mailf) == NULL)
|
|
break;
|
|
if (line[0] == '\n')
|
|
break;
|
|
if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1)
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (linkspool(&bounceq) != 0)
|
|
goto fail;
|
|
/* bounce is safe */
|
|
|
|
delqueue(it);
|
|
|
|
run_queue(&bounceq);
|
|
/* NOTREACHED */
|
|
|
|
fail:
|
|
syslog(LOG_CRIT, "error creating bounce: %m");
|
|
delqueue(it);
|
|
exit(EX_IOERR);
|
|
}
|
|
|
|
struct parse_state {
|
|
char addr[1000];
|
|
int pos;
|
|
|
|
enum {
|
|
NONE = 0,
|
|
START,
|
|
MAIN,
|
|
EOL,
|
|
QUIT
|
|
} state;
|
|
int comment;
|
|
int quote;
|
|
int brackets;
|
|
int esc;
|
|
};
|
|
|
|
/*
|
|
* Simplified RFC2822 header/address parsing.
|
|
* We copy escapes and quoted strings directly, since
|
|
* we have to pass them like this to the mail server anyways.
|
|
* XXX local addresses will need treatment
|
|
*/
|
|
static int
|
|
parse_addrs(struct parse_state *ps, char *s, struct queue *queue)
|
|
{
|
|
char *addr;
|
|
|
|
again:
|
|
switch (ps->state) {
|
|
case NONE:
|
|
return (-1);
|
|
|
|
case START:
|
|
/* init our data */
|
|
bzero(ps, sizeof(*ps));
|
|
|
|
/* skip over header name */
|
|
while (*s != ':')
|
|
s++;
|
|
s++;
|
|
ps->state = MAIN;
|
|
break;
|
|
|
|
case MAIN:
|
|
/* all fine */
|
|
break;
|
|
|
|
case EOL:
|
|
switch (*s) {
|
|
case ' ':
|
|
case '\t':
|
|
ps->state = MAIN;
|
|
break;
|
|
|
|
default:
|
|
ps->state = QUIT;
|
|
if (ps->pos != 0)
|
|
goto newaddr;
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
case QUIT:
|
|
return (0);
|
|
}
|
|
|
|
for (; *s != 0; s++) {
|
|
if (ps->esc) {
|
|
ps->esc = 0;
|
|
|
|
switch (*s) {
|
|
case '\r':
|
|
case '\n':
|
|
goto err;
|
|
|
|
default:
|
|
goto copy;
|
|
}
|
|
}
|
|
|
|
if (ps->quote) {
|
|
switch (*s) {
|
|
case '"':
|
|
ps->quote = 0;
|
|
goto copy;
|
|
|
|
case '\\':
|
|
ps->esc = 1;
|
|
goto copy;
|
|
|
|
case '\r':
|
|
case '\n':
|
|
goto eol;
|
|
|
|
default:
|
|
goto copy;
|
|
}
|
|
}
|
|
|
|
switch (*s) {
|
|
case '(':
|
|
ps->comment++;
|
|
break;
|
|
|
|
case ')':
|
|
if (ps->comment)
|
|
ps->comment--;
|
|
else
|
|
goto err;
|
|
goto skip;
|
|
|
|
case '"':
|
|
ps->quote = 1;
|
|
goto copy;
|
|
|
|
case '\\':
|
|
ps->esc = 1;
|
|
goto copy;
|
|
|
|
case '\r':
|
|
case '\n':
|
|
goto eol;
|
|
}
|
|
|
|
if (ps->comment)
|
|
goto skip;
|
|
|
|
switch (*s) {
|
|
case ' ':
|
|
case '\t':
|
|
/* ignore whitespace */
|
|
goto skip;
|
|
|
|
case '<':
|
|
/* this is the real address now */
|
|
ps->brackets = 1;
|
|
ps->pos = 0;
|
|
goto skip;
|
|
|
|
case '>':
|
|
if (!ps->brackets)
|
|
goto err;
|
|
ps->brackets = 0;
|
|
|
|
s++;
|
|
goto newaddr;
|
|
|
|
case ':':
|
|
/* group - ignore */
|
|
ps->pos = 0;
|
|
goto skip;
|
|
|
|
case ',':
|
|
case ';':
|
|
/*
|
|
* Next address, copy previous one.
|
|
* However, we might be directly after
|
|
* a <address>, or have two consecutive
|
|
* commas.
|
|
* Skip the comma unless there is
|
|
* really something to copy.
|
|
*/
|
|
if (ps->pos == 0)
|
|
goto skip;
|
|
s++;
|
|
goto newaddr;
|
|
|
|
default:
|
|
goto copy;
|
|
}
|
|
|
|
copy:
|
|
if (ps->comment)
|
|
goto skip;
|
|
|
|
if (ps->pos + 1 == sizeof(ps->addr))
|
|
goto err;
|
|
ps->addr[ps->pos++] = *s;
|
|
|
|
skip:
|
|
;
|
|
}
|
|
|
|
eol:
|
|
ps->state = EOL;
|
|
return (0);
|
|
|
|
err:
|
|
ps->state = QUIT;
|
|
return (-1);
|
|
|
|
newaddr:
|
|
ps->addr[ps->pos] = 0;
|
|
ps->pos = 0;
|
|
addr = strdup(ps->addr);
|
|
if (addr == NULL)
|
|
errlog(EX_SOFTWARE, "strdup");
|
|
|
|
if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
|
|
errlogx(EX_DATAERR, "invalid recipient `%s'", addr);
|
|
|
|
goto again;
|
|
}
|
|
|
|
static int
|
|
writeline(struct queue *queue, const char *line, ssize_t linelen)
|
|
{
|
|
ssize_t len;
|
|
|
|
while (linelen > 0) {
|
|
len = linelen;
|
|
if (linelen > MAX_LINE_RFC822) {
|
|
len = MAX_LINE_RFC822 - 10;
|
|
}
|
|
|
|
if (fwrite(line, len, 1, queue->mailf) != 1)
|
|
return (-1);
|
|
|
|
if (linelen <= MAX_LINE_RFC822)
|
|
break;
|
|
|
|
if (fwrite("\n", 1, 1, queue->mailf) != 1)
|
|
return (-1);
|
|
|
|
line += MAX_LINE_RFC822 - 10;
|
|
linelen = strlen(line);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
readmail(struct queue *queue, int nodot, int recp_from_header)
|
|
{
|
|
struct parse_state parse_state;
|
|
char *line = NULL;
|
|
ssize_t linelen;
|
|
size_t linecap = 0;
|
|
char newline[MAX_LINE_RFC822 + 1];
|
|
size_t error;
|
|
int had_headers = 0;
|
|
int had_from = 0;
|
|
int had_messagid = 0;
|
|
int had_date = 0;
|
|
int had_first_line = 0;
|
|
int had_last_line = 0;
|
|
int nocopy = 0;
|
|
int ret = -1;
|
|
|
|
parse_state.state = NONE;
|
|
|
|
error = fprintf(queue->mailf,
|
|
"Received: from %s (uid %d)\n"
|
|
"\t(envelope-from %s)\n"
|
|
"\tid %s\n"
|
|
"\tby %s (%s on %s);\n"
|
|
"\t%s\n",
|
|
username, useruid,
|
|
queue->sender,
|
|
queue->id,
|
|
hostname(), VERSION, systemhostname(),
|
|
rfc822date());
|
|
if ((ssize_t)error < 0)
|
|
return (-1);
|
|
|
|
while ((linelen = getline(&line, &linecap, stdin)) > 0) {
|
|
newline[0] = '\0';
|
|
if (had_last_line)
|
|
errlogx(EX_DATAERR, "bad mail input format:"
|
|
" from %s (uid %d) (envelope-from %s)",
|
|
username, useruid, queue->sender);
|
|
linelen = strlen(line);
|
|
if (linelen == 0 || line[linelen - 1] != '\n') {
|
|
/*
|
|
* This line did not end with a newline character.
|
|
* If we fix it, it better be the last line of
|
|
* the file.
|
|
*/
|
|
if ((size_t)linelen + 1 > linecap) {
|
|
line = realloc(line, linelen + 2);
|
|
if (line == NULL)
|
|
errlogx(EX_SOFTWARE, "realloc");
|
|
linecap = malloc_usable_size(line);
|
|
}
|
|
line[linelen++] = '\n';
|
|
line[linelen] = 0;
|
|
had_last_line = 1;
|
|
}
|
|
if (!had_first_line) {
|
|
/*
|
|
* Ignore a leading RFC-976 From_ or >From_ line mistakenly
|
|
* inserted by some programs.
|
|
*/
|
|
if (strprefixcmp(line, "From ") == 0 || strprefixcmp(line, ">From ") == 0)
|
|
continue;
|
|
had_first_line = 1;
|
|
}
|
|
if (!had_headers) {
|
|
if (linelen > MAX_LINE_RFC822) {
|
|
/* XXX also split headers */
|
|
errlogx(EX_DATAERR, "bad mail input format:"
|
|
" from %s (uid %d) (envelope-from %s)",
|
|
username, useruid, queue->sender);
|
|
}
|
|
/*
|
|
* Unless this is a continuation, switch of
|
|
* the Bcc: nocopy flag.
|
|
*/
|
|
if (!(line[0] == ' ' || line[0] == '\t'))
|
|
nocopy = 0;
|
|
|
|
if (strprefixcmp(line, "Date:") == 0)
|
|
had_date = 1;
|
|
else if (strprefixcmp(line, "Message-Id:") == 0)
|
|
had_messagid = 1;
|
|
else if (strprefixcmp(line, "From:") == 0)
|
|
had_from = 1;
|
|
else if (strprefixcmp(line, "Bcc:") == 0)
|
|
nocopy = 1;
|
|
|
|
if (parse_state.state != NONE) {
|
|
if (parse_addrs(&parse_state, line, queue) < 0) {
|
|
errlogx(EX_DATAERR, "invalid address in header\n");
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
|
|
if (recp_from_header && (
|
|
strprefixcmp(line, "To:") == 0 ||
|
|
strprefixcmp(line, "Cc:") == 0 ||
|
|
strprefixcmp(line, "Bcc:") == 0)) {
|
|
parse_state.state = START;
|
|
if (parse_addrs(&parse_state, line, queue) < 0) {
|
|
errlogx(EX_DATAERR, "invalid address in header\n");
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strcmp(line, "\n") == 0 && !had_headers) {
|
|
had_headers = 1;
|
|
while (!had_date || !had_messagid || !had_from) {
|
|
if (!had_date) {
|
|
had_date = 1;
|
|
snprintf(newline, sizeof(newline), "Date: %s\n", rfc822date());
|
|
} else if (!had_messagid) {
|
|
/* XXX msgid, assign earlier and log? */
|
|
had_messagid = 1;
|
|
snprintf(newline, sizeof(newline), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
|
|
(uintmax_t)time(NULL),
|
|
queue->id,
|
|
(uintmax_t)random(),
|
|
hostname());
|
|
} else if (!had_from) {
|
|
had_from = 1;
|
|
snprintf(newline, sizeof(newline), "From: <%s>\n", queue->sender);
|
|
}
|
|
if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
|
|
goto fail;
|
|
}
|
|
strlcpy(newline, "\n", sizeof(newline));
|
|
}
|
|
if (!nodot && linelen == 2 && line[0] == '.')
|
|
break;
|
|
if (!nocopy) {
|
|
if (newline[0]) {
|
|
if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1)
|
|
goto fail;
|
|
} else {
|
|
if (writeline(queue, line, linelen) != 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
if (ferror(stdin) == 0)
|
|
ret = 0;
|
|
fail:
|
|
free(line);
|
|
return (ret);
|
|
}
|