/*
* Copyright (c) 2006-2017 Hypertriton, Inc.
* All rights reserved.
*
* 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 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 AUTHOR 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mailprocd.h"
int smtpMasterPipe = -1; /* Pipe to master process */
int smtpMsgsTot; /* Total messages processed */
static int looping = 0; /* Loop detected during DATA */
static int outofmem = 0; /* Out of memory during DATA */
static int inHeader = 1; /* Currently reading header */
static int inData = 0; /* DATA phase */
static MPD_Message *msg = NULL;
static enum {
ST_HELO,
ST_MAIL_FROM,
ST_RCPT_TO,
ST_DATA,
ST_DATA_IN,
ST_QUIT
} state = ST_HELO;
static __inline__ void
WriteClient(const char *code, const char *ext, const char *s)
{
fputs(code, stdout);
fputc(' ', stdout);
if (ext != NULL) {
fputs(ext, stdout);
fputc(' ', stdout);
}
fputs(s, stdout);
fputc('\n', stdout);
fflush(stdout);
}
/*
* Remove whitespace/brackets from an address and convert to lowercase.
* Destination must be able to hold ADDRESS_MAX characters.
*/
static int
GetAddress(char *dest, char *buf)
{
char *c, *cStart;
for (c = &buf[0]; *c == ' ' || *c == '<'; c++)
;;
cStart = c;
for (; *c != '>' && *c != '\0'; c++)
;;
if (*c == '>')
*c = '\0';
if (Strlcpy(dest, cStart, ADDRESS_MAX) >= ADDRESS_MAX) {
return (-1);
}
for (c = &dest[0]; *c != '\0'; c++) {
*c = tolower((int)*c);
}
return (0);
}
static void
GoodBye(void)
{
WriteClient("221", "2.0.0", "Bye");
shutdown(fileno(stdin), SHUT_RDWR);
shutdown(fileno(stdout), SHUT_RDWR);
}
/* Respond to a bad command or one issued out of sequence. */
static void
InvalidCmd(const char *expected, const char *cmd)
{
if (expected != NULL && cmd != NULL) {
syslog(LOG_ERR, "SMTP Error: Expected %s, got `%s'",
expected, cmd);
}
WriteClient("503", "5.5.1", "Invalid command");
fflush(stdout);
}
static __inline__ void
ResetState(void)
{
if (msg != NULL) {
MPD_MessageFree(msg);
msg = NULL;
}
looping = 0;
outofmem = 0;
}
static void
ProcessMailLoop(MPD_Message *msg)
{
MPD_Recipient *rcpt;
ResetState();
WriteClient("550", "5.4.6", "User mail-to-command loop detected!");
/* XXX */
TAILQ_FOREACH(rcpt, &msg->rcpts, rcpts) {
syslog(LOG_ERR, "Mail loop with <%s>?", rcpt->addr);
Debug("Mail loop detected under <%s>?", rcpt->addr);
}
}
/* Process an incoming SMTP/LMTP session initiated by the MTA. */
int
SMTP_Main(enum smtp_prot prot)
{
char *buf, *lbuf = NULL, *pbuf, *p;
size_t len;
setvbuf(stdout, NULL, _IOFBF, 0);
WriteClient("220", NULL, (prot == ESMTP_PROTOCOL) ?
"localhost ESMTP" :
"localhost LMTP");
while ((buf = Fgetln(stdin, &len)) != NULL) {
if (buf[len-1] == '\n') {
if (len > 1 && buf[len-2] == '\r') {
len--;
}
buf[len-1] = '\0';
} else {
if ((lbuf = malloc(len+1)) == NULL) {
MPD_SetErrorS("malloc line");
goto fail;
}
memcpy(lbuf, buf, len);
lbuf[len] = '\0';
buf = lbuf;
}
if (!inData) {
/*
* Commands allowed in any context other than DATA.
*/
if (strcasecmp(buf, "quit") == 0) {
GoodBye();
goto out;
}
if (strncasecmp(buf, "rset", 4) == 0) {
ResetState();
WriteClient("250", "2.0.0", "Ok");
state = ST_MAIL_FROM;
goto next_line;
}
if (strncasecmp(buf, "noop", 4) == 0) {
WriteClient("250", "2.0.0", "Ok");
goto next_line;
}
if (state != ST_HELO &&
(strncasecmp(buf, "helo", 4) == 0 ||
strncasecmp(buf, "ehlo", 4) == 0 ||
strncasecmp(buf, "lhlo", 4) == 0)) {
/*
* Per RFC2821, we reset the state as
* if RSET had been issued.
*/
state = ST_HELO;
}
}
reprocess:
switch (state) {
case ST_HELO:
if (strncasecmp(buf, "helo", 4) == 0) {
WriteClient("221", NULL, "localhost");
state = ST_MAIL_FROM;
} else if (strncasecmp(buf, "ehlo", 4) == 0 ||
strncasecmp(buf, "lhlo", 4) == 0) {
fputs("250-localhost\n"
"250-PIPELINING\n"
"250-8BITMIME\n"
"250-SIZE 104857600\n"
"250 ENHANCEDSTATUSCODES\n", stdout);
fflush(stdout);
state = ST_MAIL_FROM;
} else if (strncasecmp(buf, "mail from", 9) == 0) {
state = ST_MAIL_FROM;
goto reprocess;
} else {
InvalidCmd("HELO/EHLO", buf);
}
break;
case ST_MAIL_FROM:
if (strncasecmp(buf, "mail from:", 10) == 0) {
ResetState();
if ((msg = MPD_MessageNew()) == NULL) {
WriteClient("452", "4.3.2", "Out of memory");
goto fail;
}
if ((msg->text = malloc(1)) == NULL) {
WriteClient("452", "4.3.2", "Out of memory");
MPD_SetErrorS("Out of memory");
goto fail;
}
msg->text[0] = '\0';
msg->text_len = 0;
if (GetAddress(msg->mail_from, &buf[10]) == -1){
WriteClient("503", "5.1.7", "Bad sender address");
ResetState();
state = ST_HELO;
break;
}
WriteClient("250", "2.1.0", "Ok");
state = ST_RCPT_TO;
} else {
InvalidCmd("MAIL FROM", buf);
}
break;
case ST_RCPT_TO:
if (strncasecmp(buf, "rcpt to:", 8) == 0) {
MPD_Recipient *rcpt;
if (msg == NULL) {
InvalidCmd("FROM", buf);
break;
}
if ((rcpt = MPD_MessageAddRecipient(msg, NULL))
== NULL) {
WriteClient("452", "4.3.2", "Out of memory");
MPD_SetErrorS("Out of memory");
goto fail;
}
if (GetAddress(rcpt->addr, &buf[8]) == -1 ||
MPD_ParseRecipientParts(rcpt) == -1) {
WriteClient("503", "5.1.3",
"Bad recipient address");
ResetState();
state = ST_HELO;
break;
}
WriteClient("250", "2.1.5", "Ok");
state = ST_DATA;
} else {
InvalidCmd("RCPT TO", buf);
}
break;
case ST_DATA:
if (msg == NULL) {
WriteClient("503", "5.5.1",
"DATA without RCPT/TO");
break;
}
if (strncasecmp(buf, "data", 4) == 0) {
WriteClient("354", NULL, "End data with "
".");
state = ST_DATA_IN;
inData = 1;
inHeader = 1;
} else if (strncasecmp(buf, "mail from:", 10) == 0) {
state = ST_MAIL_FROM;
goto reprocess;
} else if (strncasecmp(buf, "rcpt to:", 8) == 0) {
state = ST_RCPT_TO;
goto reprocess;
} else {
InvalidCmd("DATA/RCPT TO", buf);
}
break;
case ST_DATA_IN:
if (msg == NULL) {
WriteClient("503", "5.5.1",
"DATA without RCPT/TO");
break;
}
if (buf[0] == '.' && buf[1] == '\0') {
MPD_Recipient *rcpt;
int nsuccess = 0;
inHeader = 0;
inData = 0;
if (outofmem) {
WriteClient("452", "4.3.2", "Out of memory");
MPD_SetErrorS("Out of memory");
goto fail;
}
if (looping) {
ProcessMailLoop(msg);
state = ST_QUIT;
break;
}
msg->text[msg->text_len] = '\0';
TAILQ_FOREACH(rcpt, &msg->rcpts, rcpts) {
if (QMGR_Queue(msg, rcpt,
smtpMasterPipe) == 0) {
nsuccess++;
} else {
syslog(LOG_ERR,
"QMGR Error: %s",
MPD_GetError());
}
}
if (nsuccess > 0) {
WriteClient("250", "2.0.0", "Ok");
} else {
WriteClient("452", "4.3.1",
"Mail system full");
}
ResetState();
state = ST_QUIT;
break;
}
if (outofmem || looping) {
break;
}
if (buf[0] == '\0') {
inHeader = 0;
} else if (inHeader && buf[0] == 'X' &&
strncmp(buf, "X-Csoft-Pipe: ", 14) == 0) {
MPD_Recipient *xRcpt;
TAILQ_FOREACH(xRcpt, &msg->rcpts, rcpts) {
if (strcasecmp(&buf[14], xRcpt->addr)
== 0) {
looping = 1;
break;
}
}
} else if (inHeader && msg->ip[0] == '\0' &&
buf[0] == 'R' && !strncmp(buf, "Received:",9)) {
if ((p = strchr(buf, '[')) != NULL &&
p[1] != '\0') {
int nIP = 0;
for (p++;
*p != ']' && *p != '\0' &&
nIP < IP_ADDRESS_MAX;
p++) {
msg->ip[nIP++] = *p;
}
msg->ip[nIP] = '\0';
}
}
pbuf = realloc(msg->text, msg->text_len+len+1);
if (pbuf == NULL) {
outofmem = 1;
break;
}
msg->text = pbuf;
buf[len-1] = '\n';
memcpy(&msg->text[msg->text_len], buf, len);
msg->text_len += len;
break;
case ST_QUIT:
if (strncasecmp(buf, "quit", 4) == 0) {
GoodBye();
goto out;
} else if (strncasecmp(buf, "mail from:", 10) == 0) {
state = ST_MAIL_FROM;
goto reprocess;
} else if (strncasecmp(buf, "rcpt to:", 8) == 0) {
state = ST_RCPT_TO;
goto reprocess;
} else {
InvalidCmd(NULL, NULL);
break;
}
break;
default:
Debug("SMTP invalid command: %s\n", buf);
InvalidCmd(NULL, NULL);
break;
}
next_line:
Free(lbuf);
lbuf = NULL;
}
if (ferror(stdin)) {
MPD_SetErrorS("Read error");
goto fail;
}
out:
Free(lbuf);
if (msg != NULL) { MPD_MessageFree(msg); }
return (0);
fail:
Free(lbuf);
if (msg != NULL) { MPD_MessageFree(msg); }
return (-1);
}