/* * 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); }