Files
bongo/src/agents/smtp/smtpd.c
T
2026-07-16 18:52:00 +02:00

4990 lines
187 KiB
C

/***************************************************************************
* <Novell-copyright>
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail, you
* may find current contact information at www.novell.com.
* </Novell-copyright>
* (C) 2007 Patrick Felt
****************************************************************************/
#include <config.h>
/* Compile-time options */
#define NETDB_USE_INTERNET 1
#undef USE_HOPCOUNT_DETECTION
#include <xpl.h>
#include <xpldns.h>
#include <connio.h>
#include <nmlib.h>
#include <msgapi.h>
#define LOGGERNAME "smtpd"
#define PRODUCT_NAME "Bongo SMTP Agent"
#define PRODUCT_VERSION "$Revision: 1.7 $"
#include <logger.h>
#include <nmap.h>
#include <bongoagent.h>
#include <bongoutil.h>
#include "mailauth.h"
#include "smtpd.h"
#include "external_accounts.h"
struct {
int port;
int port_ssl;
BOOL submission_enabled;
int submission_port;
int submission_port_ssl;
BOOL submission_require_auth;
BOOL submission_allow_sender_override;
BOOL internal_relay_enabled;
int internal_relay_port;
char *internal_relay_bind_address;
GArray *internal_relay_networks;
BOOL internal_relay_allow_legacy_tls;
int internal_relay_messages_per_minute;
int internal_relay_recipients_per_minute;
int internal_relay_bytes_per_minute;
int internal_relay_connections_per_ip;
char *internal_relay_domain;
GArray *internal_relay_device_mappings;
GArray *internal_relay_dns_suffixes;
BOOL allow_client_ssl;
BOOL allow_legacy_tls;
BOOL smtp_utf8_enabled;
BOOL allow_expn;
BOOL allow_auth;
BOOL require_auth;
BOOL allow_vrfy;
BOOL verify_address;
BOOL accept_etrn;
BOOL send_etrn;
int max_recips;
int message_limit;
BOOL use_relay;
BOOL relay_local;
char *relay_host;
BOOL block_rts;
int max_thread_load;
int max_flood_count;
int max_null_sender;
int max_mx_servers;
int socket_timeout;
BOOL spf_verify;
BOOL dkim_verify;
BOOL dmarc_verify;
BOOL dmarc_enforce;
BOOL srs_reverse;
char *srs_domain;
char *srs_secret_file;
GArray *lmtp_transports;
} SMTP;
static BongoConfigItem LMTPTransportList = {
BONGO_JSON_STRING, NULL, &SMTP.lmtp_transports
};
static BongoConfigItem InternalRelayNetworkList = {
BONGO_JSON_STRING, NULL, &SMTP.internal_relay_networks
};
static BongoConfigItem InternalRelayDeviceMappingList = {
BONGO_JSON_STRING, NULL, &SMTP.internal_relay_device_mappings
};
static BongoConfigItem InternalRelayDNSSuffixList = {
BONGO_JSON_STRING, NULL, &SMTP.internal_relay_dns_suffixes
};
static BongoConfigItem SMTPConfig[] = {
{ BONGO_JSON_INT, "o:port/i", &SMTP.port },
{ BONGO_JSON_INT, "o:port_ssl/i", &SMTP.port_ssl },
{ BONGO_JSON_BOOL, "o:submission_enabled/b", &SMTP.submission_enabled },
{ BONGO_JSON_INT, "o:submission_port/i", &SMTP.submission_port },
{ BONGO_JSON_INT, "o:submission_port_ssl/i", &SMTP.submission_port_ssl },
{ BONGO_JSON_BOOL, "o:submission_require_auth/b", &SMTP.submission_require_auth },
{ BONGO_JSON_BOOL, "o:submission_allow_sender_override/b", &SMTP.submission_allow_sender_override },
{ BONGO_JSON_BOOL, "o:internal_relay_enabled/b", &SMTP.internal_relay_enabled },
{ BONGO_JSON_INT, "o:internal_relay_port/i", &SMTP.internal_relay_port },
{ BONGO_JSON_STRING, "o:internal_relay_bind_address/s", &SMTP.internal_relay_bind_address },
{ BONGO_JSON_ARRAY, "o:internal_relay_networks/a", &InternalRelayNetworkList },
{ BONGO_JSON_BOOL, "o:internal_relay_allow_legacy_tls/b", &SMTP.internal_relay_allow_legacy_tls },
{ BONGO_JSON_INT, "o:internal_relay_messages_per_minute/i", &SMTP.internal_relay_messages_per_minute },
{ BONGO_JSON_INT, "o:internal_relay_recipients_per_minute/i", &SMTP.internal_relay_recipients_per_minute },
{ BONGO_JSON_INT, "o:internal_relay_bytes_per_minute/i", &SMTP.internal_relay_bytes_per_minute },
{ BONGO_JSON_INT, "o:internal_relay_connections_per_ip/i", &SMTP.internal_relay_connections_per_ip },
{ BONGO_JSON_STRING, "o:internal_relay_domain/s", &SMTP.internal_relay_domain },
{ BONGO_JSON_ARRAY, "o:internal_relay_device_mappings/a", &InternalRelayDeviceMappingList },
{ BONGO_JSON_ARRAY, "o:internal_relay_dns_suffixes/a", &InternalRelayDNSSuffixList },
{ BONGO_JSON_BOOL, "o:allow_client_ssl/b", &SMTP.allow_client_ssl },
{ BONGO_JSON_BOOL, "o:allow_legacy_tls/b", &SMTP.allow_legacy_tls },
{ BONGO_JSON_BOOL, "o:smtp_utf8_enabled/b", &SMTP.smtp_utf8_enabled },
{ BONGO_JSON_BOOL, "o:allow_expn/b", &SMTP.allow_expn },
{ BONGO_JSON_BOOL, "o:allow_vrfy/b", &SMTP.allow_vrfy },
{ BONGO_JSON_BOOL, "o:allow_auth/b", &SMTP.allow_auth },
{ BONGO_JSON_BOOL, "o:verify_address/b", &SMTP.verify_address },
{ BONGO_JSON_BOOL, "o:accept_etrn/b", &SMTP.accept_etrn },
{ BONGO_JSON_BOOL, "o:send_etrn/b", &SMTP.send_etrn },
{ BONGO_JSON_INT, "o:maximum_recipients/i", &SMTP.max_recips },
{ BONGO_JSON_INT, "o:message_size_limit/i", &SMTP.message_limit },
{ BONGO_JSON_BOOL, "o:use_relay_host/b", &SMTP.use_relay },
{ BONGO_JSON_STRING, "o:relay_host/s", &SMTP.relay_host },
{ BONGO_JSON_BOOL, "o:block_rts_spam/b", &SMTP.block_rts },
{ BONGO_JSON_INT, "o:max_thread_load/i", &SMTP.max_thread_load },
{ BONGO_JSON_INT, "o:max_flood_count/i", &SMTP.max_flood_count }, // UNUSED ?
{ BONGO_JSON_INT, "o:max_null_sender/i", &SMTP.max_null_sender },
{ BONGO_JSON_INT, "o:socket_timeout/i", &SMTP.socket_timeout },
{ BONGO_JSON_INT, "o:max_mx_servers/i", &SMTP.max_mx_servers },
{ BONGO_JSON_BOOL, "o:relay_local_mail/b", &SMTP.relay_local },
{ BONGO_JSON_BOOL, "o:require_auth/b", &SMTP.require_auth },
{ BONGO_JSON_BOOL, "o:spf_verify/b", &SMTP.spf_verify },
{ BONGO_JSON_BOOL, "o:dkim_verify/b", &SMTP.dkim_verify },
{ BONGO_JSON_BOOL, "o:dmarc_verify/b", &SMTP.dmarc_verify },
{ BONGO_JSON_BOOL, "o:dmarc_enforce/b", &SMTP.dmarc_enforce },
{ BONGO_JSON_BOOL, "o:srs_reverse/b", &SMTP.srs_reverse },
{ BONGO_JSON_STRING, "o:srs_domain/s", &SMTP.srs_domain },
{ BONGO_JSON_STRING, "o:srs_secret_file/s", &SMTP.srs_secret_file },
{ BONGO_JSON_ARRAY, "o:lmtp_transports/a", &LMTPTransportList },
{ BONGO_JSON_NULL, NULL, NULL }
};
/* Globals */
BOOL Exiting = FALSE;
XplSemaphore SMTPShutdownSemaphore;
XplSemaphore SMTPServerSemaphore;
XplAtomic SMTPServerThreads;
XplAtomic SMTPConnThreads;
XplAtomic SMTPIdleConnThreads;
XplAtomic SMTPQueueThreads;
Connection *SMTPServerConnection;
Connection *SMTPServerConnectionSSL;
Connection *SMTPInternalServerConnection;
Connection *SMTPSubmissionServerConnection;
Connection *SMTPQServerConnection;
Connection *SMTPQServerConnectionSSL;
int TGid;
/* UBE measures */
XplRWLock ConfigLock;
BOOL SMTPReceiverStopped = FALSE;
BOOL Notify = FALSE;
BOOL AllowClientSSL = FALSE;
BOOL DomainUsernames = TRUE;
time_t LastBounce;
time_t BounceInterval;
unsigned long MaxBounceCount;
unsigned long BounceCount;
char NMAPServer[20] = "127.0.0.1";
unsigned long LocalAddress = 0;
bongo_ssl_context *SSLContext = NULL;
static bongo_ssl_context *InternalSSLContext = NULL;
static GHashTable *InternalRelayRates;
static XplMutex InternalRelayRateLock;
typedef struct {
time_t window;
unsigned int messages;
unsigned int recipients;
unsigned long bytes;
unsigned int connections;
} InternalRelayRate;
static char NMAPHash[NMAP_HASH_SIZE];
#ifdef USE_HOPCOUNT_DETECTION
int MAX_HOPS = 16;
#endif
#define BUFSIZE 1023
#define LINESIZE BUFSIZE-10
#define MTU 1536*3
#define MAX_USERPART_LEN 127
#define STACKSIZE_Q 128000 /* 32767 */
#define STACKSIZE_S 128000 /* 32767 */
/* Values other than these are no longer supported */
#define UBE_REMOTE_AUTH_ONLY (1<<3)
#define UBE_SMTP_AFTER_POP (1<<5)
#define UBE_DISABLE_AUTH (1<<8)
#define UBE_DEFAULT_NOT_TRUSTED (UBE_REMOTE_AUTH_ONLY | UBE_SMTP_AFTER_POP)
/* Minutes * 60 (1 second granularity) */
/*#define CONNECTION_TIMEOUT (10*60)*/
/* Seconds */
#define MAILBOX_TIMEOUT 15
#define ChopNL(String) { char *pTr; pTr=strchr((String), 0x0a); if (pTr) *pTr='\0'; pTr=strrchr((String), 0x0d); if (pTr) *pTr='\0'; }
struct __InternalConnectionStruct {
Connection *conn;
char *buffer;
unsigned long buflen;
BOOL ssl;
};
typedef struct
{
struct __InternalConnectionStruct client;
struct __InternalConnectionStruct nmap;
struct __InternalConnectionStruct remotesmtp;
unsigned long State; /* Connection state */
unsigned long Flags; /* Status flags */
unsigned long RecipCount; /* Number of recipients */
NMAPMessageFlags MsgFlags; /* current msg flags */
char RemoteHost[MAXEMAILNAMESIZE + 1]; /* Name of remote host */
char *Command; /* Current command */
char *From; /* For Routing Disabled */
char *AuthFrom; /* Sender Authenticated As */
char User[64]; /* Fixme - Make this bigger */
BOOL IsEHLO; /* used for RFC 3848 */
BOOL HasSPFResult;
BongoSPFResponse SPFResult;
BOOL InternalRelay;
BOOL Submission;
InternalRelayRate *InternalRate;
char InternalDevice[64];
} ConnectionStruct;
static BOOL
InternalRelayConsume(ConnectionStruct *client, unsigned int messages,
unsigned int recipients, unsigned long bytes)
{
InternalRelayRate *rate = client->InternalRate;
time_t now = time(NULL);
BOOL allowed = TRUE;
if (!client->InternalRelay || rate == NULL) return TRUE;
XplMutexLock(InternalRelayRateLock);
if (now < rate->window || now - rate->window >= 60) {
rate->window = now;
rate->messages = rate->recipients = 0;
rate->bytes = 0;
}
if ((messages && rate->messages + messages > (unsigned int) SMTP.internal_relay_messages_per_minute) ||
(recipients && rate->recipients + recipients > (unsigned int) SMTP.internal_relay_recipients_per_minute) ||
(bytes && rate->bytes + bytes > (unsigned long) SMTP.internal_relay_bytes_per_minute)) {
allowed = FALSE;
} else {
rate->messages += messages;
rate->recipients += recipients;
rate->bytes += bytes;
}
XplMutexUnlock(InternalRelayRateLock);
return allowed;
}
static BOOL
NormalizeInternalSender(const char *sender, const char *device, char *normalized,
size_t normalizedSize)
{
const char *at;
const char *domain;
size_t localLength;
size_t domainLength;
if (sender == NULL || normalized == NULL || normalizedSize == 0 ||
SMTP.internal_relay_domain == NULL || *SMTP.internal_relay_domain == '\0') return FALSE;
at = strrchr(sender, '@');
if (at == NULL || at == sender || at[1] == '\0') return FALSE;
domain = at + 1;
localLength = (size_t) (at - sender);
domainLength = strlen(domain);
if (strcasecmp(domain, "localhost") == 0 ||
strcasecmp(domain, "localdomain") == 0 ||
strcasecmp(domain, "localhost.localdomain") == 0) {
if (device != NULL && *device != '\0' &&
(localLength == 4 && strncasecmp(sender, "root", 4) == 0)) {
return snprintf(normalized, normalizedSize, "%.*s+%s@%s", (int) localLength,
sender, device, SMTP.internal_relay_domain) < (int) normalizedSize;
}
return snprintf(normalized, normalizedSize, "%.*s@%s", (int) localLength,
sender, SMTP.internal_relay_domain) < (int) normalizedSize;
}
if (domainLength > strlen(SMTP.internal_relay_domain) + 1 &&
domain[domainLength - strlen(SMTP.internal_relay_domain) - 1] == '.' &&
strcasecmp(domain + domainLength - strlen(SMTP.internal_relay_domain),
SMTP.internal_relay_domain) == 0) {
size_t subdomainLength = domainLength - strlen(SMTP.internal_relay_domain) - 1;
return snprintf(normalized, normalizedSize, "%.*s+%.*s@%s", (int) localLength,
sender, (int) subdomainLength, domain,
SMTP.internal_relay_domain) < (int) normalizedSize;
}
return FALSE;
}
static BOOL
AddressInNetwork(struct in_addr address, const char *network)
{
char text[INET_ADDRSTRLEN + 4];
char *slash;
char *end;
struct in_addr base;
unsigned long prefix = 32;
uint32_t mask;
if (network == NULL || strlen(network) >= sizeof(text)) {
return FALSE;
}
strcpy(text, network);
slash = strchr(text, '/');
if (slash != NULL) {
*slash++ = '\0';
errno = 0;
prefix = strtoul(slash, &end, 10);
if (errno != 0 || *slash == '\0' || *end != '\0' || prefix > 32) {
return FALSE;
}
}
if (inet_pton(AF_INET, text, &base) != 1) {
return FALSE;
}
mask = prefix == 0 ? 0 : UINT32_MAX << (32 - prefix);
return (ntohl(address.s_addr) & mask) == (ntohl(base.s_addr) & mask);
}
static BOOL
InternalRelayAddressAllowed(struct in_addr address)
{
unsigned int index;
for (index = 0; SMTP.internal_relay_networks != NULL &&
index < SMTP.internal_relay_networks->len; index++) {
const char *network = g_array_index(SMTP.internal_relay_networks, char *, index);
if (AddressInNetwork(address, network)) {
return TRUE;
}
}
return FALSE;
}
static BOOL
ValidDeviceName(const char *name)
{
const unsigned char *p = (const unsigned char *) name;
if (p == NULL || *p == '\0') return FALSE;
while (*p != '\0') {
if (!isalnum(*p) && *p != '-') return FALSE;
p++;
}
return TRUE;
}
static BOOL
HostHasAllowedSuffix(const char *host)
{
unsigned int index;
size_t hostLength = strlen(host);
for (index = 0; SMTP.internal_relay_dns_suffixes != NULL &&
index < SMTP.internal_relay_dns_suffixes->len; index++) {
const char *suffix = g_array_index(SMTP.internal_relay_dns_suffixes, char *, index);
size_t suffixLength = suffix != NULL ? strlen(suffix) : 0;
if (suffixLength && hostLength > suffixLength &&
host[hostLength - suffixLength - 1] == '.' &&
strcasecmp(host + hostLength - suffixLength, suffix) == 0) return TRUE;
}
return FALSE;
}
static BOOL
IdentifyInternalDevice(struct in_addr address, char *device, size_t deviceSize)
{
unsigned int index;
char ip[INET_ADDRSTRLEN];
char host[NI_MAXHOST];
struct sockaddr_in peer;
struct addrinfo hints;
struct addrinfo *answers = NULL;
struct addrinfo *answer;
BOOL forwardMatch = FALSE;
if (inet_ntop(AF_INET, &address, ip, sizeof(ip)) == NULL) return FALSE;
for (index = 0; SMTP.internal_relay_device_mappings != NULL &&
index < SMTP.internal_relay_device_mappings->len; index++) {
const char *mapping = g_array_index(SMTP.internal_relay_device_mappings, char *, index);
const char *equals = mapping != NULL ? strchr(mapping, '=') : NULL;
if (equals != NULL && (size_t) (equals - mapping) == strlen(ip) &&
strncmp(mapping, ip, strlen(ip)) == 0 && ValidDeviceName(equals + 1)) {
snprintf(device, deviceSize, "%s", equals + 1);
return TRUE;
}
}
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr = address;
if (getnameinfo((struct sockaddr *) &peer, sizeof(peer), host, sizeof(host),
NULL, 0, NI_NAMEREQD) != 0 || !HostHasAllowedSuffix(host)) return FALSE;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(host, NULL, &hints, &answers) != 0) return FALSE;
for (answer = answers; answer != NULL; answer = answer->ai_next) {
struct sockaddr_in *resolved = (struct sockaddr_in *) answer->ai_addr;
if (resolved->sin_addr.s_addr == address.s_addr) {
forwardMatch = TRUE;
break;
}
}
freeaddrinfo(answers);
if (forwardMatch) {
char *dot = strchr(host, '.');
if (dot != NULL) *dot = '\0';
if (ValidDeviceName(host)) {
snprintf(device, deviceSize, "%s", host);
return TRUE;
}
}
return FALSE;
}
static const char *
SPFResultName(BongoSPFResult result)
{
static const char *const names[] = {
"none", "neutral", "pass", "fail", "softfail", "none",
"temperror", "permerror"
};
return result >= BONGO_SPF_INVALID && result <= BONGO_SPF_PERMERROR
? names[result] : "none";
}
static BOOL
IsTransportRecipient(const char *address)
{
const char *at;
unsigned int index;
if (address == NULL || SMTP.lmtp_transports == NULL ||
(at = strrchr(address, '@')) == NULL || at[1] == '\0') {
return FALSE;
}
for (index = 0U; index < SMTP.lmtp_transports->len; index++) {
const char *entry = g_array_index(SMTP.lmtp_transports, char *, index);
const char *equals = entry != NULL ? strchr(entry, '=') : NULL;
size_t domainLength;
if (equals == NULL || equals == entry) {
continue;
}
domainLength = (size_t) (equals - entry);
if (strlen(at + 1) == domainLength &&
strncasecmp(at + 1, entry, domainLength) == 0) {
return TRUE;
}
}
return FALSE;
}
static BOOL
AuthenticateUser(ConnectionStruct *client, const char *user, const char *password)
{
if (user == NULL || password == NULL || *user == '\0' ||
MsgAuthFindUser(user) != 0 || MsgAuthVerifyPassword(user, password) != 0) {
Log(LOG_NOTICE, "Authentication failed for user %s at host %s",
user != NULL ? user : "", LOGIP(client->client.conn->socketAddress));
return FALSE;
}
Log(LOG_NOTICE, "Successful login for user %s at host %s", user,
LOGIP(client->client.conn->socketAddress));
MemFree(client->AuthFrom);
client->AuthFrom = MemStrdup(user);
return client->AuthFrom != NULL;
}
static BOOL
SubmissionSenderAllowed(const char *user, const char *sender)
{
const char *at;
size_t localLength;
sqlite3 *database;
CollectorOutboundAccount account;
int found;
if (SMTP.submission_allow_sender_override || user == NULL || sender == NULL ||
*sender == '\0') return TRUE;
if (strcasecmp(user, sender) == 0) return TRUE;
if (strchr(user, '@') == NULL) {
at = strrchr(sender, '@');
localLength = at != NULL ? (size_t) (at - sender) : strlen(sender);
if (strlen(user) == localLength &&
strncasecmp(user, sender, localLength) == 0) return TRUE;
}
database = CollectorAccountsOpen();
if (database == NULL) return FALSE;
found = CollectorAccountsOutboundForSender(database, user, sender, &account);
CollectorAccountsClose(database);
return found == 1;
}
static BOOL
ParsePlainCredentials(char *encoded, const char **user, const char **password)
{
size_t encodedLength;
size_t decodedLimit;
char *decoded;
char *authcid;
char *separator;
if (encoded == NULL || user == NULL || password == NULL) {
return FALSE;
}
encodedLength = strlen(encoded);
if (encodedLength == 0 || encodedLength > BUFSIZE) {
return FALSE;
}
decodedLimit = ((encodedLength + 3U) / 4U) * 3U;
decoded = DecodeBase64(encoded);
if (decoded == NULL) {
return FALSE;
}
separator = memchr(decoded, '\0', decodedLimit);
if (separator == NULL) {
return FALSE;
}
authcid = separator + 1;
separator = memchr(authcid, '\0', decodedLimit - (size_t)(authcid - decoded));
if (separator == NULL || authcid == separator) {
return FALSE;
}
*user = authcid;
*password = separator + 1;
return (size_t)(*password - decoded) < decodedLimit;
}
typedef struct
{
char To[MAXEMAILNAMESIZE+1];
char ORecip[MAXEMAILNAMESIZE+1];
unsigned long Flags;
int Result;
} RecipStruct;
/* Encoding constants: none (unknown), 7bit, 8bitmime, binarymime, binary */
#define CODE_NONE 0
#define CODE_7BIT 1
#define CODE_8BITM 2
#define CODE_BINM 3
#define CODE_BIN 4
#define SENDER_LOCAL (1<<10)
#define GetSMTPConnection() MemPrivatePoolGetEntryDirect(SMTPConnectionPool, __FILE__, __LINE__)
void *SMTPConnectionPool = NULL;
/* Prototypes */
long ReadConnection(Connection *conn, char **buffer, unsigned long *buflen);
void ProcessRemoteEntry (ConnectionStruct * Client, unsigned long Size, int Lines);
int RewriteAddress (Connection * conn, const char *Source, char *Target, unsigned int TargetSize);
BOOL FlushClient (ConnectionStruct * Client);
BOOL EndClientConnection (ConnectionStruct * Client);
static int PullLine (char *Line, unsigned long LineSize, char **NextLine);
/* this PullLine will strip off the crlf where the other one only seems to strip off the lf
* eventually i'd like to remove the first and convert everything to using the straight connio
* stuff for speed and security */
static int PullLine2 (char *Line, unsigned long LineSize, char **NextLine);
static BOOL
SMTPConnectionAllocCB (void *Buffer, void *ClientData)
{
UNUSED_PARAMETER(ClientData);
ConnectionStruct *c = (ConnectionStruct *) Buffer;
memset (c, 0, sizeof (ConnectionStruct));
c->State = STATE_FRESH;
c->MsgFlags = MSG_FLAG_ENCODING_NONE;
return (TRUE);
}
static void
ReturnSMTPConnection (ConnectionStruct * Client)
{
ConnectionStruct *c = Client;
memset (c, 0, sizeof (ConnectionStruct));
c->State = STATE_FRESH;
c->MsgFlags = MSG_FLAG_ENCODING_NONE;
MemPrivatePoolReturnEntry (SMTPConnectionPool, c);
return;
}
__inline static char *
strchrRN (char *Buffer, char SrchChar,
char *EndPtr)
{
char *ptr = Buffer;
char srchChar = SrchChar;
do {
while (*ptr != '\0') {
if (*ptr != srchChar) {
ptr++;
continue;
}
else {
return (ptr);
}
}
if (ptr == EndPtr) {
return (NULL);
}
*ptr = ' ';
ptr++;
} while (ptr < EndPtr);
return (NULL);
}
#define FreeInternalConnection(c) \
if (c.conn) { \
ConnClose(c.conn); \
ConnFree(c.conn); \
c.conn = NULL; \
\
if (c.buffer) { \
MemFree(c.buffer); \
c.buffer = NULL; \
c.buflen = 0; \
} \
} \
BOOL
EndClientConnection (ConnectionStruct * Client)
{
char Reply[BUFSIZE + 1];
if (Client) {
if (Client->State == STATE_ENDING) {
return (TRUE);
}
Client->Flags = Client->State; /* abusing the field - saves stack */
Client->State = STATE_ENDING;
if (Client->From) {
MemFree (Client->From);
Client->From = NULL;
}
if (Client->nmap.conn) {
if (Client->Flags != STATE_HELO) {
/* if smtp is in the middle of receiving a message, */
/* we need to send a dot on a line by itself */
/* to get NMAP out of the receiving state and into */
/* the command state so we can issue the QABRT and */
/* QUIT commands. If NMAP is already in the command */
/* state, the dot on a line by itself will not hurt */
/* anything. NMAP will just send 'unknown command' */
NMAPSendCommand (Client->nmap.conn, "\r\n.\r\n", 5);
NMAPReadResponse(Client->nmap.conn, Reply, sizeof(Reply), TRUE); //empty line
NMAPReadResponse(Client->nmap.conn, Reply, sizeof(Reply), TRUE); //.
NMAPSendCommand (Client->nmap.conn, "QABRT\r\nQUIT\r\n", 13);
NMAPReadResponse(Client->nmap.conn, Reply, sizeof(Reply), TRUE); //qabrt which we've already done once....
NMAPReadResponse(Client->nmap.conn, Reply, sizeof(Reply), TRUE); //quit
} else {
NMAPSendCommand (Client->nmap.conn, "QUIT\r\n", 6);
NMAPReadResponse(Client->nmap.conn, Reply, sizeof(Reply), TRUE); //quit
}
FreeInternalConnection(Client->nmap);
}
if (Client->client.conn) {
FreeInternalConnection(Client->client);
}
if (Client->remotesmtp.conn) {
ConnWrite(Client->remotesmtp.conn, "RSET\r\nQUIT\r\n", 12);
FreeInternalConnection(Client->remotesmtp);
}
if (Client->AuthFrom != NULL) {
MemFree (Client->AuthFrom);
Client->AuthFrom = NULL;
}
if (Client->InternalRate != NULL) {
XplMutexLock(InternalRelayRateLock);
if (Client->InternalRate->connections > 0) Client->InternalRate->connections--;
XplMutexUnlock(InternalRelayRateLock);
Client->InternalRate = NULL;
}
/* Bump our thread count */
if (Client->Flags < STATE_WAITING) {
XplSafeDecrement (SMTPConnThreads);
} else {
XplSafeDecrement (SMTPQueueThreads);
}
ReturnSMTPConnection (Client);
}
XplExitThread (TSR_THREAD, 0);
return (FALSE);
}
static BOOL
HandleConnection (void *param)
{
ConnectionStruct *Client = (ConnectionStruct *) param;
int count;
int ReplyInt = 0;
BOOL Ready;
BOOL Working = TRUE;
BOOL IsTrusted = TRUE;
BOOL IsAuthed = FALSE;
BOOL AllowAuth = TRUE;
BOOL NullSender = FALSE;
BOOL TooManyNullSenderRecips = FALSE;
char *ptr, *ptr2;
char Answer[BUFSIZE + 1];
char Reply[BUFSIZE + 1];
struct sockaddr_in soc_address;
struct sockaddr_in *sin = &soc_address;
time_t connectionTime;
time (&connectionTime);
if (Client->InternalRelay &&
IdentifyInternalDevice(Client->client.conn->socketAddress.sin_addr,
Client->InternalDevice,
sizeof(Client->InternalDevice))) {
Log(LOG_INFO, "Identified internal SMTP device %s at %s",
Client->InternalDevice, LOGIP(Client->client.conn->socketAddress));
}
if (Client->client.conn->ssl.enable) {
Log(LOG_INFO, "SSL connection from %s", LOGIP(Client->client.conn->socketAddress));
if (!ConnNegotiate(Client->client.conn,
Client->InternalRelay ? InternalSSLContext : SSLContext)) {
return (EndClientConnection (Client));
}
} else {
Log(LOG_INFO, "Connection from %s", LOGIP(Client->client.conn->socketAddress));
}
XplRWReadLockAcquire (&ConfigLock);
if (!SMTP.allow_auth || Client->InternalRelay) {
AllowAuth = FALSE;
}
XplRWReadLockRelease (&ConfigLock);
/* Connect to Queue agent */
sin->sin_addr.s_addr=inet_addr(NMAPServer);
sin->sin_family=AF_INET;
sin->sin_port=htons(BONGO_QUEUE_PORT);
if ((Client->nmap.conn = NMAPConnectEx(NULL, sin, Client->client.conn->trace.destination)) == NULL ||
!NMAPAuthenticateToStore(Client->nmap.conn, Answer, sizeof(Answer))) {
if (Client->nmap.conn) {
NMAPQuit(Client->nmap.conn);
Client->nmap.conn = NULL;
Log(LOG_ERROR, "Unable to authenticate to Queue agent at %s",
LOGIP(Client->client.conn->socketAddress));
} else {
Log(LOG_ERROR, "Unable to connect to Queue agent at %s",
LOGIP(Client->client.conn->socketAddress));
}
count = snprintf(Reply, sizeof(Reply), "421 %s %s\r\n", BongoGlobals.hostname, MSG421SHUTDOWN);
ConnWrite (Client->client.conn, Reply, count);
ConnFlush (Client->client.conn);
return (EndClientConnection (Client));
}
snprintf (Answer, sizeof (Answer), "220 %s %s %s\r\n", BongoGlobals.hostname, MSG220READY, PRODUCT_VERSION);
ConnWrite (Client->client.conn, Answer, strlen (Answer));
ConnFlush (Client->client.conn);
while (Working) {
Ready = FALSE;
while (!Ready) {
if (Exiting == FALSE) {
count = ConnReadToAllocatedBuffer(Client->client.conn, &(Client->client.buffer), &(Client->client.buflen));
} else {
snprintf (Answer, sizeof (Answer), "421 %s %s\r\n", BongoGlobals.hostname, MSG421SHUTDOWN);
ConnWrite (Client->client.conn, Answer, strlen (Answer));
ConnFlush(Client->client.conn);
return (EndClientConnection (Client));
}
if (count > 0) {
/* this simplifies the rest of the code */
Client->Command = Client->client.buffer;
Ready = TRUE;
} else if (Exiting == FALSE) {
Log(LOG_ERROR, "Connection to %s timed out (time: %d)",
LOGIP(soc_address), time(NULL) - connectionTime);
return (EndClientConnection (Client));
} else if (count < 1) {
snprintf (Answer, sizeof (Answer), "421 %s %s\r\n", BongoGlobals.hostname, MSG421SHUTDOWN);
ConnWrite (Client->client.conn, Answer, strlen (Answer));
ConnFlush(Client->client.conn);
return (EndClientConnection (Client));
}
}
switch (toupper (Client->Command[0])) {
case 'H':
switch (toupper (Client->Command[3])) {
case 'O': /* HELO */
if (Client->State == STATE_FRESH) {
snprintf (Answer, sizeof (Answer), "250 %s %s\r\n", BongoGlobals.hostname, MSG250HELLO);
ConnWrite (Client->client.conn, Answer, strlen (Answer));
snprintf(Client->RemoteHost, sizeof(Client->RemoteHost),
"%s", Client->Command + 5);
Client->State = STATE_HELO;
NullSender = FALSE;
TooManyNullSenderRecips = FALSE;
}
else {
ConnWrite (Client->client.conn, MSG503BADORDER, MSG503BADORDER_LEN);
}
break;
case 'P': /* HELP */
ConnWrite (Client->client.conn, MSG211HELP, MSG211HELP_LEN);
break;
default:
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
break;
case 'S':{ /* SAML, SOML, SEND */
switch (toupper (Client->Command[1])) {
case 'T':{ /* STARTTLS */
if ((!Client->InternalRelay && !Client->Submission && !SMTP.allow_client_ssl) ||
(Client->InternalRelay && InternalSSLContext == NULL)) {
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
ConnWrite (Client->client.conn, MSG220TLSREADY, MSG220TLSREADY_LEN);
ConnFlush (Client->client.conn);
/* try to negotiate the connection */
Client->client.conn->ssl.enable = TRUE;
if (ConnNegotiate(Client->client.conn,
Client->InternalRelay ? InternalSSLContext : SSLContext)) {
/* negotiation worked */
Client->State = STATE_FRESH;
}
break;
}
case 'A': /* SAML */
case 'O': /* SOML */
case 'E':{ /* SEND */
ConnWrite (Client->client.conn, MSG502NOTIMPLEMENTED,
MSG502NOTIMPLEMENTED_LEN);
break;
}
default:{
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
}
break;
}
case 'A':{ /* AUTH */
const char *user = NULL;
const char *password = NULL;
BOOL authenticated = FALSE;
if (AllowAuth == FALSE || !Client->client.conn->ssl.enable) {
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
/* RFC 4954 */
if (IsAuthed) {
ConnWrite (Client->client.conn, MSG503BADORDER, MSG503BADORDER_LEN);
break;
}
if (XplStrNCaseCmp(Client->Command + 5, "PLAIN", 5) == 0) {
char *encoded = Client->Command + 10;
while (isspace((unsigned char)*encoded)) {
encoded++;
}
if (*encoded == '\0') {
ConnWrite(Client->client.conn, "334 \r\n", 6);
ConnFlush(Client->client.conn);
if (ConnReadToAllocatedBuffer(Client->client.conn,
&Client->client.buffer, &Client->client.buflen) <= 0) {
return EndClientConnection(Client);
}
encoded = Client->client.buffer;
}
if (strcmp(encoded, "*") == 0) {
ConnWrite(Client->client.conn,
"501 5.7.0 Authentication canceled\r\n", 35);
break;
}
if (ParsePlainCredentials(encoded, &user, &password)) {
authenticated = AuthenticateUser(Client, user, password);
}
} else if (XplStrNCaseCmp(Client->Command + 5, "LOGIN", 5) == 0) {
if (!isspace((unsigned char)Client->Command[10])) {
ConnWrite (Client->client.conn, "334 VXNlcm5hbWU6\r\n", 18);
ConnFlush (Client->client.conn);
if (ConnReadToAllocatedBuffer(Client->client.conn,
&Client->client.buffer, &Client->client.buflen) <= 0) {
return EndClientConnection(Client);
}
snprintf(Reply, sizeof(Reply), "%s", Client->client.buffer);
} else {
snprintf(Reply, sizeof(Reply), "%s", Client->Command + 11);
}
if (strcmp(Reply, "*") != 0) {
user = DecodeBase64(Reply);
ConnWrite(Client->client.conn, "334 UGFzc3dvcmQ6\r\n", 18);
ConnFlush(Client->client.conn);
if (ConnReadToAllocatedBuffer(Client->client.conn,
&Client->client.buffer, &Client->client.buflen) <= 0) {
return EndClientConnection(Client);
}
if (strcmp(Client->client.buffer, "*") != 0) {
password = DecodeBase64(Client->client.buffer);
authenticated = AuthenticateUser(Client, user, password);
}
}
} else {
ConnWrite(Client->client.conn, MSG504BADAUTH, MSG504BADAUTH_LEN);
break;
}
if (authenticated) {
Client->State = STATE_AUTH;
IsAuthed = TRUE;
IsTrusted = TRUE;
ConnWrite(Client->client.conn,
"235 2.7.0 Authentication successful\r\n", 37);
} else {
ConnWrite(Client->client.conn,
"535 5.7.8 Authentication credentials invalid\r\n", 46);
}
break;
}
case 'R':
switch (toupper (Client->Command[1])) {
case 'S': /* RSET */
if (Client->State != STATE_FRESH) {
NMAPSendCommand (Client->nmap.conn, "QABRT\r\n", 7);
NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE);
}
Client->State = (Client->State) ? STATE_HELO : STATE_FRESH;
Client->Flags = 0;
Client->RecipCount = 0;
Client->MsgFlags = MSG_FLAG_ENCODING_NONE;
if (Client->From) {
MemFree (Client->From);
Client->From = NULL;
}
ConnWrite (Client->client.conn, MSG250OK, MSG250OK_LEN);
break;
case 'C':{ /* RCPT TO */
char temp, *name;
BOOL GotFlags = FALSE;
char To[MAXEMAILNAMESIZE + 1] = "";
char SRSAddress[MAXEMAILNAMESIZE + 1] = "";
char *Orcpt = NULL;
const char *RoutingName;
if (Client->State < STATE_FROM) {
ConnWrite (Client->client.conn, MSG501NOSENDER,
MSG501NOSENDER_LEN);
break;
}
if ((ptr = strchr (Client->Command, ':')) == NULL) {
ConnWrite (Client->client.conn, MSG501SYNTAX, MSG501SYNTAX_LEN);
break;
}
if (TooManyNullSenderRecips == TRUE) {
XplDelay (250);
Client->RecipCount++;
ConnWrite (Client->client.conn, MSG550TOOMANY, MSG550TOOMANY_LEN);
break;
}
if ((ptr2 = strchr (ptr + 1, '<')) != NULL) {
ptr = ptr2 + 1;
if (*ptr == '\0') {
ConnWrite (Client->client.conn, MSG501SYNTAX,
MSG501SYNTAX_LEN);
break;
}
ptr2 = strchr (ptr, '>');
if (ptr2) {
temp = '>';
*ptr2 = '\0';
}
else
temp = '\0';
}
else {
ptr++;
while (isspace (*ptr))
ptr++;
if (*ptr == '\0') {
ConnWrite (Client->client.conn, MSG501SYNTAX,
MSG501SYNTAX_LEN);
break;
}
ptr2 = ptr;
while (*ptr2 && *ptr2 != ' ')
ptr2++;
temp = *ptr2;
*ptr2 = '\0';
}
name = ptr;
if (temp != '\0') {
do {
ptr2++;
} while (isspace (*ptr2));
}
while (ptr2 && *ptr2 != '\0') {
switch (toupper (*ptr2)) {
case 'N': /* NOTIFY */
GotFlags = TRUE;
ptr2 += 6;
while (*ptr2 == '=' || *ptr2 == ',') {
ptr2++;
switch (toupper (*ptr2)) {
case 'N':
ptr2 += 5;
Client->Flags &=
~(DSN_SUCCESS | DSN_TIMEOUT |
DSN_FAILURE);
break;
case 'S':
ptr2 += 7;
Client->Flags |= DSN_SUCCESS;
break;
case 'F':
ptr2 += 7;
Client->Flags |= DSN_FAILURE;
break;
case 'D':
ptr2 += 5;
Client->Flags |= DSN_TIMEOUT;
break;
default:
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitRcpt;
break;
}
}
break;
case 'O': /* ORCPT */
ptr = ptr2 + 6;
ptr2 = ptr;
while (!isspace (*ptr2) && *ptr2 != '\0')
ptr2++;
temp = *ptr2;
*ptr2 = '\0';
if (*ptr) {
Orcpt = MemStrdup (ptr);
}
else {
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitRcpt;
}
*ptr2 = temp;
if (!Orcpt) {
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
goto QuitRcpt;
}
break;
default:
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitRcpt;
break;
}
ptr2 = strchr (ptr2, ' ');
if (ptr2)
while (isspace (*ptr2))
ptr2++;
}
if (!GotFlags)
Client->Flags |= DSN_HEADER | DSN_FAILURE;
ReplyInt = strlen (name);
if (ReplyInt > MAXEMAILNAMESIZE) {
ConnWrite (Client->client.conn, MSG501RECIPNO, MSG501RECIPNO_LEN);
break;
}
ptr = strchr (name, '@');
if (!ptr && ReplyInt > MAX_USERPART_LEN) {
ConnWrite (Client->client.conn, MSG501RECIPNO, MSG501RECIPNO_LEN);
break;
}
else if (ptr && ((ptr - name) > MAX_USERPART_LEN)) {
ConnWrite (Client->client.conn, MSG501RECIPNO, MSG501RECIPNO_LEN);
break;
}
RoutingName = name;
if (ptr != NULL && strncasecmp(name, "SRS", 3) == 0) {
BOOL reverseSRS;
char srsDomain[MAXEMAILNAMESIZE + 1];
char secretFile[XPL_MAX_PATH];
XplRWReadLockAcquire(&ConfigLock);
reverseSRS = SMTP.srs_reverse;
snprintf(srsDomain, sizeof(srsDomain), "%s",
SMTP.srs_domain != NULL ? SMTP.srs_domain : "");
snprintf(secretFile, sizeof(secretFile), "%s",
SMTP.srs_secret_file != NULL
? SMTP.srs_secret_file : "");
XplRWReadLockRelease(&ConfigLock);
if (reverseSRS && srsDomain[0] != '\0' &&
strcasecmp(ptr + 1, srsDomain) == 0) {
BongoMailAuthStatus srsStatus =
BongoMailAuthSRSReverse(secretFile, name,
SRSAddress,
sizeof(SRSAddress));
if (srsStatus == BONGO_MAILAUTH_PROTOCOL_ERROR) {
Log(LOG_NOTICE,
"Rejected invalid SRS recipient %s from host %s",
name,
LOGIP(Client->client.conn->socketAddress));
ConnWrite(Client->client.conn, MSG550SRSINVALID,
MSG550SRSINVALID_LEN);
goto QuitRcpt;
}
if (srsStatus != BONGO_MAILAUTH_OK) {
Log(LOG_ERROR,
"Unable to validate SRS recipient %s (status %d)",
name, (int) srsStatus);
ConnWrite(Client->client.conn,
MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
goto QuitRcpt;
}
RoutingName = SRSAddress;
}
}
if ((ReplyInt = RewriteAddress (Client->nmap.conn,
RoutingName, To,
sizeof (To))) == MAIL_BOGUS) {
ConnWrite (Client->client.conn, MSG501RECIPNO, MSG501RECIPNO_LEN);
}
else {
if (ReplyInt == MAIL_REMOTE &&
IsTransportRecipient(RoutingName)) {
ReplyInt = MAIL_RELAY;
snprintf(To, sizeof(To), "%s", RoutingName);
}
switch (ReplyInt) {
case MAIL_REMOTE:{
if (!IsAuthed) {
Log(LOG_INFO, "Recipient %s by host %s blocked",
name, LOGIP(Client->client.conn->socketAddress));
ConnWrite (Client->client.conn,
MSG571SPAMBLOCK,
MSG571SPAMBLOCK_LEN);
goto QuitRcpt;
}
XplRWReadLockAcquire (&ConfigLock);
if (Client->RecipCount >= (unsigned int)SMTP.max_recips) {
XplRWReadLockRelease (&ConfigLock);
Log(LOG_INFO, "Recipient limit from %s at host %s to user %s",
Client->AuthFrom?
(char *) Client->AuthFrom : "",
LOGIP(Client->client.conn->socketAddress),
To);
ConnWrite (Client->client.conn, MSG550TOOMANY,
MSG550TOOMANY_LEN);
goto QuitRcpt;
}
XplRWReadLockRelease (&ConfigLock);
snprintf (Answer, sizeof (Answer),
"QSTOR TO %s %s %lu\r\n", To,
Orcpt ? Orcpt : To,
(unsigned long) (Client->
Flags &
DSN_FLAGS));
Log(LOG_INFO, "Message from %s at host %s relayed to %s",
Client->AuthFrom?
(char *)Client->AuthFrom : "",
LOGIP(Client->client.conn->socketAddress),
To);
break;
}
case MAIL_RELAY:{
Log(LOG_INFO, "Message from %s at host %s relayed to %s",
Client->AuthFrom?
(char *)Client->AuthFrom : "",
LOGIP(Client->client.conn->socketAddress),
To);
snprintf (Answer, sizeof (Answer),
"QSTOR TO %s %s %lu\r\n", To,
Orcpt ? Orcpt : To,
(unsigned long) (Client->
Flags &
DSN_FLAGS));
break;
}
case MAIL_LOCAL:{
XplRWReadLockAcquire (&ConfigLock);
if ((NullSender == FALSE) ||
(Client->RecipCount < (unsigned int)SMTP.max_null_sender)) {
if (!SMTP.verify_address) {
XplRWReadLockRelease (&ConfigLock);
snprintf (Answer, sizeof (Answer),
"QSTOR LOCAL %s %s %lu\r\n",
To, Orcpt ? Orcpt : To,
(unsigned long) (Client->Flags & DSN_FLAGS));
} else {
XplRWReadLockRelease (&ConfigLock);
if (MsgAuthFindUser(To) == 0) {
snprintf (Answer, sizeof (Answer),
"QSTOR LOCAL %s %s %lu\r\n",
To, Orcpt ? Orcpt : To,
(unsigned long) (Client->Flags & DSN_FLAGS));
} else {
ConnWrite (Client->client.conn, MSG550NOTFOUND, MSG550NOTFOUND_LEN);
goto QuitRcpt;
}
}
}
else {
/* TODO - Inform the connection manager that this address should be blocked */
XplRWReadLockRelease (&ConfigLock);
TooManyNullSenderRecips = TRUE;
XplDelay (250);
ConnWrite (Client->client.conn, MSG550TOOMANY,
MSG550TOOMANY_LEN);
Client->RecipCount++;
goto QuitRcpt;
}
break;
}
}
if (!InternalRelayConsume(Client, 0, 1, 0)) {
ConnWrite(Client->client.conn,
"451 4.7.1 Internal relay recipient rate exceeded\r\n",
strlen("451 4.7.1 Internal relay recipient rate exceeded\r\n"));
goto QuitRcpt;
}
NMAPSendCommand (Client->nmap.conn, Answer, strlen (Answer));
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
if (Orcpt)
MemFree (Orcpt);
return (EndClientConnection (Client));
}
Client->State = STATE_TO;
Client->RecipCount++;
ConnWrite (Client->client.conn, MSG250RECIPOK, MSG250RECIPOK_LEN);
}
QuitRcpt:
if (Orcpt)
MemFree (Orcpt);
}
break;
default:
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
break;
case 'M':{ /* MAIL FROM */
unsigned long size = 0;
char temp, *name;
char *Envid = NULL;
char *more;
char normalizedSender[MAXEMAILNAMESIZE + 1];
if (SMTP.require_auth && !IsTrusted) {
ConnWrite (Client->client.conn, MSG553SPAMBLOCK, MSG553SPAMBLOCK_LEN);
break;
}
if (Client->Submission && SMTP.submission_require_auth && !IsAuthed) {
ConnWrite(Client->client.conn,
"530 5.7.0 Authentication required\r\n",
strlen("530 5.7.0 Authentication required\r\n"));
break;
}
/* rfc 2821 */
if (!Client->State || Client->State >= STATE_FROM) {
ConnWrite (Client->client.conn, MSG503BADORDER, MSG503BADORDER_LEN);
break;
}
if ((ptr = strchr (Client->Command, ':')) == NULL) {
ConnWrite (Client->client.conn, MSG501SYNTAX, MSG501SYNTAX_LEN);
break;
}
ptr++;
while (isspace (*ptr)) {
ptr++;
}
if (*ptr == '<') {
ptr++;
name = ptr;
do {
if ((*ptr != '\0') && !isspace (*ptr)) {
if (*ptr != '>') {
ptr++;
continue;
}
/* we found the end */
*ptr = '\0';
more = ptr + 1;
if (ptr > name) {
break;
}
else {
NullSender = TRUE;
break;
}
}
/* illegal address */
name = NULL;
break;
} while (TRUE);
}
else {
name = ptr;
do {
if ((*ptr != '\0') && !isspace (*ptr)) {
ptr++;
continue;
}
/* we found the end */
if (*ptr) {
*ptr = '\0';
more = ptr + 1;
}
else {
more = NULL;
}
if (ptr > name) {
break;
}
else {
NullSender = TRUE;
break;
}
} while (TRUE);
}
if (name) {
;
}
else {
ConnWrite (Client->client.conn, MSG501SYNTAX, MSG501SYNTAX_LEN);
break;
}
if (more) {
do {
if (isspace (*more)) {
more++;
continue;
}
break;
} while (TRUE);
}
#if 0
/* We're abusing size, so we don't need another stack variable */
/* Spam block */
if (SMTP.block_rts && (name[0] == '\0')) {
XplRWReadLockAcquire (&ConfigLock);
time (&size);
if (LastBounce >= size - BounceInterval) {
/* Should make a decision */
LastBounce = size;
BounceCount++;
if (BounceCount > MaxBounceCount) {
/* We don't want the bounce, probably spam */
XplRWReadLockRelease (&ConfigLock);
SendClient (Client, MSG572SPAMBOUNCEBLOCK,
MSG572SPAMBOUNCEBLOCK_LEN);
break;
}
}
else {
LastBounce = size;
BounceCount = 0;
}
size = 0;
XplRWReadLockRelease (&ConfigLock);
}
#endif
while (more && *more != '\0') {
switch (toupper (*more)) {
case 'A':{ /* AUTH */
while ((*more) && !isspace (*more)) {
more++;
}
break;
}
case 'B': /* BODY */
Client->MsgFlags &= ~MSG_FLAG_ENCODING_NONE;
switch (more[5]) {
case '7': /* 7BIT */
Client->MsgFlags |= MSG_FLAG_ENCODING_7BIT;
break;
case '8': /* 8BITMIME */
Client->MsgFlags |= MSG_FLAG_ENCODING_8BITM;
break;
case 'B':
case 'b': /* BINARYMIME */
Client->MsgFlags |= MSG_FLAG_ENCODING_BINM;
break;
default:
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitMail;
break;
}
break;
case 'R': /* RET */
switch (toupper (more[4])) {
case 'F': /* FULL */
Client->Flags |= DSN_HEADER | DSN_BODY;
break;
case 'H': /* HDRS */
Client->Flags |= DSN_HEADER;
break;
default:
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitMail;
break;
}
break;
case 'E': /* ENVID */
ptr = strchr (more, '=');
if (!ptr) {
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitMail;
break;
}
ptr++;
more = ptr;
while (!isspace (*more) && *more != '\0')
more++;
temp = *more;
*more = '\0';
Envid = MemStrdup (ptr);
*more = temp;
if (!Envid) {
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
goto QuitMail;
}
break;
case 'S': /* SIZE or SMTPUTF8 */
if (strncasecmp(more, "SMTPUTF8", 8) == 0 &&
(more[8] == '\0' || isspace((unsigned char) more[8]))) {
if (!SMTP.smtp_utf8_enabled) {
ConnWrite(Client->client.conn, MSG502NOTIMPLEMENTED,
MSG502NOTIMPLEMENTED_LEN);
goto QuitMail;
}
break;
}
ptr = strchr (more, '=');
if (!ptr) {
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitMail;
break;
}
ptr++;
size = atol (ptr);
XplRWReadLockAcquire (&ConfigLock);
if (SMTP.message_limit > 0 &&
size > (unsigned long)SMTP.message_limit) {
XplRWReadLockRelease (&ConfigLock);
ConnWrite (Client->client.conn, MSG552MSGTOOBIG,
MSG552MSGTOOBIG_LEN);
goto QuitMail;
}
XplRWReadLockRelease (&ConfigLock);
break;
default:
ConnWrite (Client->client.conn, MSG501PARAMERROR,
MSG501PARAMERROR_LEN);
goto QuitMail;
break;
}
more = strchr (more, ' ');
if (more)
while (isspace (*more))
more++;
}
if (Client->InternalRelay &&
NormalizeInternalSender(name, Client->InternalDevice, normalizedSender,
sizeof(normalizedSender))) {
Log(LOG_INFO, "Normalized internal sender %s to %s for %s",
name, normalizedSender, LOGIP(Client->client.conn->socketAddress));
name = normalizedSender;
}
if (Client->Submission &&
!SubmissionSenderAllowed(Client->AuthFrom, name)) {
Log(LOG_WARN, "Submission user %s attempted sender %s from %s",
Client->AuthFrom != NULL ? Client->AuthFrom : "", name,
LOGIP(Client->client.conn->socketAddress));
ConnWrite(Client->client.conn,
"553 5.7.1 Sender address not permitted\r\n",
strlen("553 5.7.1 Sender address not permitted\r\n"));
goto QuitMail;
}
ReplyInt = strlen (name);
if (ReplyInt > MAXEMAILNAMESIZE) {
ConnWrite (Client->client.conn, MSG501GOAWAY, MSG501GOAWAY_LEN);
if (Envid)
MemFree (Envid);
break;
}
ptr = strchr (name, '@');
if (!ptr && ReplyInt > MAX_USERPART_LEN) {
if (Envid)
MemFree (Envid);
ConnWrite (Client->client.conn, MSG501GOAWAY, MSG501GOAWAY_LEN);
break;
}
else if (ptr && ((ptr - name) > MAX_USERPART_LEN)) {
if (Envid)
MemFree (Envid);
ConnWrite (Client->client.conn, MSG501GOAWAY, MSG501GOAWAY_LEN);
break;
}
Client->HasSPFResult = FALSE;
XplRWReadLockAcquire(&ConfigLock);
{
BOOL verifySPF = SMTP.spf_verify;
XplRWReadLockRelease(&ConfigLock);
if (verifySPF) {
char clientAddress[INET_ADDRSTRLEN];
BongoMailAuthStatus spfStatus;
if (inet_ntop(AF_INET,
&Client->client.conn->socketAddress.sin_addr,
clientAddress, sizeof(clientAddress)) != NULL) {
spfStatus = BongoMailAuthCheckSPF(
BongoGlobals.hostname, clientAddress,
Client->RemoteHost,
name[0] != '\0' ? name : "",
&Client->SPFResult);
Client->HasSPFResult =
spfStatus == BONGO_MAILAUTH_OK ||
spfStatus == BONGO_MAILAUTH_PROTOCOL_ERROR;
}
}
}
if (size > 0) {
NMAPSendCommand (Client->nmap.conn, "QDSPC\r\n", 7);
if ((ReplyInt = NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE)) != 1000) {
ConnWrite (Client->client.conn, MSG451INTERNALERR, MSG451INTERNALERR_LEN);
if (Envid) {
MemFree (Envid);
}
return (EndClientConnection (Client));
}
if ((unsigned long) atol (Reply) < size) {
ConnWrite (Client->client.conn, MSG452NOSPACE, MSG452NOSPACE_LEN);
goto QuitMail;
}
}
NMAPSendCommand (Client->nmap.conn, "QCREA\r\n", 7);
if ((ReplyInt = NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE)) != 1000) {
if (ReplyInt == 5221) {
ConnWrite (Client->client.conn, MSG452NOSPACE, MSG452NOSPACE_LEN);
}
else {
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
}
if (Envid)
MemFree (Envid);
return (EndClientConnection (Client));
}
if (Client->From != NULL) {
MemFree (Client->From);
}
Client->From = MemStrdup (name);
snprintf (Answer, sizeof (Answer), "QSTOR FROM %s %s %s\r\n",
name[0] != '\0' ? (char *) name : "-",
Client->AuthFrom ? (char *) Client->
AuthFrom : (char *) "-",
Envid ? (char *) Envid : (char *) "");
NMAPSendCommand (Client->nmap.conn, Answer, strlen (Answer));
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
if (Envid) {
MemFree (Envid);
}
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
snprintf (Answer, sizeof (Answer), "QSTOR ADDRESS %u\r\n",
XplHostToLittle (Client->client.conn->socketAddress.sin_addr.s_addr));
NMAPSendCommand (Client->nmap.conn, Answer, strlen (Answer));
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
if (Envid) {
MemFree (Envid);
}
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
if (!(Client->Flags & DSN_HEADER)
&& !(Client->Flags & DSN_BODY))
Client->Flags |= DSN_HEADER;
Client->State = STATE_FROM;
ConnWrite (Client->client.conn, MSG250SENDEROK, MSG250SENDEROK_LEN);
QuitMail:
if (Envid) {
MemFree (Envid);
}
}
break;
case 'D':{ /* DATA */
char TimeBuf[80];
unsigned long BReceived = 0;
char WithProtocol[8]; /* ESMTPSA */
#ifdef USE_HOPCOUNT_DETECTION
long HopCount = 0;
#endif
if (Client->State < STATE_TO && Client->State != STATE_BDAT) {
ConnWrite (Client->client.conn, MSG503BADORDER, MSG503BADORDER_LEN);
break;
}
if (Client->MsgFlags & MSG_FLAG_ENCODING_NONE) {
Client->MsgFlags &= ~MSG_FLAG_ENCODING_NONE;
Client->MsgFlags |= MSG_FLAG_ENCODING_7BIT;
}
Client->MsgFlags |= MSG_FLAG_SOURCE_EXTERNAL;
snprintf (Answer, sizeof (Answer), "QSTOR FLAGS %d\r\n",
Client->MsgFlags);
NMAPSendCommand (Client->nmap.conn, Answer, strlen (Answer));
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
ConnWrite (Client->client.conn, MSG451INTERNALERR, MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
NMAPSendCommand (Client->nmap.conn, "QSTOR MESSAGE\r\n", 15);
ConnWrite (Client->client.conn, MSG354DATA, MSG354DATA_LEN);
ConnFlush (Client->client.conn);
MsgGetRFC822Date(-1, 0, TimeBuf);
/* rfc 3848 */
if (Client->IsEHLO) {
snprintf(WithProtocol, 8, "ESMTP%s%s",
Client->client.conn->ssl.enable ? "S" : "",
Client->AuthFrom ? "A" : "");
} else {
snprintf(WithProtocol, 5, "SMTP");
}
snprintf(Answer, sizeof(Answer), "Received: from %s (%d.%d.%d.%d) by %s\r\n\twith %sSMTP%s%s (bongosmtp Agent); %s\r\n",
Client->RemoteHost,
Client->client.conn->socketAddress.sin_addr.s_net,
Client->client.conn->socketAddress.sin_addr.s_host,
Client->client.conn->socketAddress.sin_addr.s_lh,
Client->client.conn->socketAddress.sin_addr.s_impno,
BongoGlobals.hostname,
(Client->IsEHLO) ? "E" : "",
(Client->client.conn->ssl.enable) ? "S" : "",
(Client->AuthFrom) ? "A" : "",
TimeBuf);
NMAPSendCommand(Client->nmap.conn, Answer, strlen(Answer));
if (Client->HasSPFResult) {
snprintf(Answer, sizeof(Answer),
"Authentication-Results: %s; spf=%s smtp.mailfrom=%s\r\n",
BongoGlobals.hostname,
SPFResultName(Client->SPFResult.result),
(Client->From != NULL && Client->From[0] != '\0')
? Client->From : "<>");
NMAPSendCommand(Client->nmap.conn, Answer, strlen(Answer));
}
/* read all the data to the nmap connection. this function
* takes into account the single . terminating the connection */
count = ConnReadToConnUntilEOS(Client->client.conn, Client->nmap.conn);
if (count < 0) {
ConnWrite(Client->client.conn, MSG451INTERNALERR, MSG451INTERNALERR_LEN);
return(EndClientConnection(Client));
}
BReceived = (unsigned long) count;
if (!InternalRelayConsume(Client, 1, 0, BReceived)) {
NMAPSendCommand(Client->nmap.conn, "QABRT\r\n", 7);
NMAPReadResponse(Client->nmap.conn, Reply, sizeof(Reply), TRUE);
ConnWrite(Client->client.conn,
"451 4.7.1 Internal relay message rate exceeded\r\n",
strlen("451 4.7.1 Internal relay message rate exceeded\r\n"));
Client->State = STATE_HELO;
Client->RecipCount = 0;
break;
}
XplRWReadLockAcquire (&ConfigLock);
if (SMTP.message_limit > 0 &&
BReceived > (unsigned long)SMTP.message_limit) { /* Message over limit */
XplRWReadLockRelease (&ConfigLock);
if ((NMAPSendCommand (Client->nmap.conn, "QABRT\r\n", 7) < 0) ||
NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
ConnWrite (Client->client.conn, MSG552MSGTOOBIG, MSG552MSGTOOBIG_LEN);
}
else {
XplRWReadLockRelease (&ConfigLock);
if ((NullSender == FALSE)
|| (Client->RecipCount < (unsigned int)SMTP.max_null_sender)) {
if (NMAPSendCommand (Client->nmap.conn, "QRUN\r\n", 6)<0) {
ConnWrite (Client->client.conn, MSG451INTERNALERR,
MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
Log(LOG_INFO,
"Message received from %s at host %s (%d)",
Client->From? (char *)Client->From : "",
inet_ntoa(Client->client.conn->socketAddress.sin_addr),
BReceived);
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
ConnWrite (Client->client.conn, MSG451INTERNALERR, MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
ConnWrite (Client->client.conn, MSG250OK, MSG250OK_LEN);
NullSender = FALSE;
TooManyNullSenderRecips = FALSE;
Client->State = STATE_HELO;
Client->RecipCount = 0;
break;
}
Log(LOG_NOTICE, "Added %s at host %s to block list (count: %d)",
Client->From? (char *) Client->From : "",
LOGIP(Client->client.conn->socketAddress),
Client->RecipCount);
/* We are going to send him away! */
ConnWrite (Client->client.conn, MSG550TOOMANY, MSG550TOOMANY_LEN);
/* EndClientConnection will send QABRT and QUIT to the NMAP server. */
return (EndClientConnection (Client));
}
break;
}
case 'Q': /* QUIT */
if (Client->State != STATE_DATA) {
if (Client->State != STATE_FRESH) {
NMAPSendCommand (Client->nmap.conn, "QABRT\r\n", 7);
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
ConnWrite (Client->client.conn, MSG451INTERNALERR, MSG451INTERNALERR_LEN);
return (EndClientConnection (Client));
}
}
}
snprintf (Answer, sizeof (Answer), "221 %s %s\r\n", BongoGlobals.hostname, MSG221QUIT);
ConnWrite (Client->client.conn, Answer, strlen (Answer));
return (EndClientConnection (Client));
break;
case 'N': /* NOOP */
ConnWrite (Client->client.conn, MSG250OK, MSG250OK_LEN);
break;
case 'E':{
switch (toupper (Client->Command[1])) {
case 'H':{ /* EHLO */
if (Client->State == STATE_FRESH) {
XplRWReadLockAcquire (&ConfigLock);
if (SMTP.message_limit > 0) {
snprintf (Answer, sizeof (Answer),
"250-%s %s\r\n%s%s%s%s%s %d\r\n",
BongoGlobals.hostname, MSG250HELLO,
SMTP.accept_etrn ? MSG250ETRN : "",
((SMTP.allow_client_ssl || Client->InternalRelay || Client->Submission)
&& (Client->InternalRelay ? InternalSSLContext != NULL : TRUE)
&& !Client->client.conn->ssl.enable) ? MSG250TLS : "",
(AllowAuth == TRUE &&
Client->client.conn->ssl.enable) ? MSG250AUTH : "",
SMTP.smtp_utf8_enabled ? MSG250SMTPUTF8 : "",
MSG250EHLO, SMTP.message_limit);
}
else {
snprintf (Answer, sizeof (Answer),
"250-%s %s\r\n%s%s%s%s%s\r\n",
BongoGlobals.hostname, MSG250HELLO,
SMTP.accept_etrn ? MSG250ETRN : "",
((SMTP.allow_client_ssl || Client->InternalRelay || Client->Submission)
&& (Client->InternalRelay ? InternalSSLContext != NULL : TRUE)
&& !Client->client.conn->ssl.enable) ? MSG250TLS : "",
(AllowAuth == TRUE &&
Client->client.conn->ssl.enable) ? MSG250AUTH : "",
SMTP.smtp_utf8_enabled ? MSG250SMTPUTF8 : "",
MSG250EHLO);
}
XplRWReadLockRelease (&ConfigLock);
ConnWrite (Client->client.conn, Answer, strlen (Answer));
snprintf(Client->RemoteHost, sizeof(Client->RemoteHost),
"%s", Client->Command + 5);
Client->State = STATE_HELO;
NullSender = FALSE;
TooManyNullSenderRecips = FALSE;
Client->IsEHLO = TRUE;
}
else {
ConnWrite (Client->client.conn, MSG503BADORDER,
MSG503BADORDER_LEN);
}
}
break;
case 'T':{ /* ETRN */
count = 0;
XplRWReadLockAcquire (&ConfigLock);
if (SMTP.accept_etrn) {
XplRWReadLockRelease (&ConfigLock);
ReplyInt =
snprintf (Answer, sizeof (Answer),
"QSRCH DOMAIN %s\r\n",
Client->Command + 5);
NMAPSendCommand (Client->nmap.conn, Answer, ReplyInt);
while (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) == 2001) {
ReplyInt = snprintf (Answer, sizeof (Answer), "QRUN %s\r\n", Reply);
NMAPSendCommand (Client->nmap.conn, Answer, ReplyInt);
count++;
}
for (ReplyInt = 0; ReplyInt < count; ReplyInt++) {
NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE);
}
if (count > 0) {
ConnWrite (Client->client.conn, MSG252QUEUESTARTED, MSG252QUEUESTARTED_LEN);
}
else {
ConnWrite (Client->client.conn, MSG251NOMESSAGES, MSG251NOMESSAGES_LEN);
}
}
else {
XplRWReadLockRelease (&ConfigLock);
ConnWrite (Client->client.conn, MSG502DISABLED, MSG502DISABLED_LEN);
}
break;
}
case 'X':{ /* EXPN */
XplRWReadLockAcquire (&ConfigLock);
if (SMTP.allow_expn) {
BOOL Found = FALSE;
XplRWReadLockRelease (&ConfigLock);
snprintf (Answer, sizeof (Answer), "VRFY %s\r\n",
Client->Command + 5);
NMAPSendCommand (Client->nmap.conn, Answer, strlen (Answer));
while (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
Found = TRUE;
ConnWrite (Client->client.conn, "250-", 4);
ConnWrite (Client->client.conn, Reply, strlen (Reply));
ConnWrite (Client->client.conn, "\r\n", 2);
}
if (Found) {
ConnWrite (Client->client.conn, MSG250OK, MSG250OK_LEN);
}
else {
ConnWrite (Client->client.conn, MSG550NOTFOUND,
MSG550NOTFOUND_LEN);
}
}
else {
XplRWReadLockRelease (&ConfigLock);
ConnWrite (Client->client.conn, MSG502DISABLED,
MSG502DISABLED_LEN);
}
break;
}
default:
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
break;
}
case 'V':{ /* VRFY */
XplRWReadLockAcquire (&ConfigLock);
if (SMTP.allow_vrfy) {
BOOL Found = FALSE;
XplRWReadLockRelease (&ConfigLock);
snprintf (Answer, sizeof (Answer), "VRFY %s\r\n",
Client->Command + 5);
NMAPSendCommand (Client->nmap.conn, Answer, strlen (Answer));
while (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
Found = TRUE;
ConnWrite (Client->client.conn, "250-", 4);
ConnWrite (Client->client.conn, Reply, strlen (Reply));
ConnWrite (Client->client.conn, "\r\n", 2);
}
if (Found) {
ConnWrite (Client->client.conn, MSG250OK, MSG250OK_LEN);
}
else {
ConnWrite (Client->client.conn, MSG550NOTFOUND,
MSG550NOTFOUND_LEN);
}
}
else {
XplRWReadLockRelease (&ConfigLock);
ConnWrite (Client->client.conn, MSG502DISABLED, MSG502DISABLED_LEN);
}
break;
}
default:
ConnWrite (Client->client.conn, MSG500UNKNOWN, MSG500UNKNOWN_LEN);
break;
}
ConnFlush (Client->client.conn);
}
return (TRUE);
}
#define DELIVER_ERROR(_e) { \
unsigned int i; \
for(i = 0; i < RecipCount; i++) { \
Recips[i].Result = _e; \
} \
return(_e); \
}
#define DELIVER_ERROR_NO_RETURN(_e) { \
unsigned int i; \
for(i = 0; i < RecipCount; i++) { \
Recips[i].Result = _e; \
} \
}
#define HandleFailure(CheckFor) if (status!=CheckFor) { \
if ((int)strlen(Reply) > ResultLen) { \
strncpy(Result, Reply, ResultLen-1); \
Result[ResultLen-1]='\0'; \
} else { \
strcpy(Result, Reply); \
} \
if (Exiting || (status/100==4)) { \
DELIVER_ERROR(DELIVER_TRY_LATER); \
} else { \
DELIVER_ERROR(DELIVER_FAILURE); \
} \
}
static int
GetEHLO (ConnectionStruct * Client, unsigned long *Extensions, long *Size)
{
BOOL MultiLine;
int Result;
char *ptr;
*Size = 0;
do {
ConnReadToAllocatedBuffer(Client->remotesmtp.conn, &(Client->remotesmtp.buffer), &(Client->remotesmtp.buflen));
if (Client->remotesmtp.buffer[3] == '-') {
MultiLine = TRUE;
Client->remotesmtp.buffer[3] = ' ';
} else {
MultiLine = FALSE;
}
if (QuickCmp (Client->remotesmtp.buffer, "250 DSN"))
*Extensions |= EXT_DSN;
else if (QuickCmp (Client->remotesmtp.buffer, "250 PIPELINING"))
*Extensions |= EXT_PIPELINING;
else if (QuickCmp (Client->remotesmtp.buffer, "250 8BITMIME"))
*Extensions |= EXT_8BITMIME;
else if (QuickCmp (Client->remotesmtp.buffer, "250 AUTH=LOGIN"))
*Extensions |= EXT_AUTH_LOGIN;
else if (QuickCmp (Client->remotesmtp.buffer, "250 CHUNKING"))
*Extensions |= EXT_CHUNKING;
else if (QuickCmp (Client->remotesmtp.buffer, "250 BINARYMIME"))
*Extensions |= EXT_BINARYMIME;
else if (QuickCmp (Client->remotesmtp.buffer, "250 ETRN"))
*Extensions |= EXT_ETRN;
else if (QuickCmp (Client->remotesmtp.buffer, "250 STARTTLS"))
*Extensions |= EXT_TLS;
else if (QuickNCmp (Client->remotesmtp.buffer, "250 SIZE", 8)) {
*Extensions |= EXT_SIZE;
if (Client->remotesmtp.buffer[8] == ' ') {
*Size = atol (Client->remotesmtp.buffer + 9);
}
}
} while (MultiLine);
ptr = strchr (Client->remotesmtp.buffer, ' ');
if (!ptr) {
return (-1);
}
*ptr = '\0';
Result = atoi (Client->remotesmtp.buffer);
return (Result);
}
static int
GetAnswer (ConnectionStruct * Client, char *Reply, int ReplyLen)
{
BOOL MultiLine;
int Result;
char *ptr;
do {
ConnReadToAllocatedBuffer(Client->remotesmtp.conn, &(Client->remotesmtp.buffer), &(Client->remotesmtp.buflen));
if (Client->remotesmtp.buffer[3] == '-') {
MultiLine = TRUE;
}
else {
MultiLine = FALSE;
}
} while (MultiLine);
ptr = strchr (Client->remotesmtp.buffer, ' ');
if (!ptr) {
return (-1);
}
*ptr = '\0';
Result = atoi (Client->remotesmtp.buffer);
memmove (Reply, ptr + 1, min(strlen(ptr + 1) + 1, (unsigned int)ReplyLen-1));
return (Result);
}
static BOOL
SendServerEscaped (ConnectionStruct * Client, char *Data,
unsigned long Len, BOOL * EscapedState)
{
char *ptr = Data;
unsigned long Pos, EndPos;
EndPos = Len - 1;
if (*EscapedState) {
if (Data[0] == '.') {
if (ConnWrite(Client->remotesmtp.conn, ".", 1) < 1) {
return (FALSE);
}
}
}
*EscapedState = FALSE;
Pos = 0;
while (Pos < EndPos) {
if (Data[Pos] != '\n') {
Pos++;
}
else {
if (Data[Pos + 1] == '.') {
if (ConnWrite(Client->remotesmtp.conn, ptr, Pos - (ptr - Data) + 1) < 1) {
return (FALSE);
}
if (ConnWrite(Client->remotesmtp.conn, ".", 1) < 1) {
return (FALSE);
}
ptr = Data + Pos + 1;
}
Pos++;
}
}
if (ConnWrite(Client->remotesmtp.conn, ptr, Pos - (ptr - Data) + 1) < 1) {
return (FALSE);
}
if (Data[EndPos] == '\n') {
*EscapedState = TRUE;
}
return (TRUE);
}
static int
DeliverSMTPMessage (ConnectionStruct * Client, char *Sender,
RecipStruct * Recips, unsigned int RecipCount,
unsigned int MsgFlags, char *Result,
int ResultLen)
{
unsigned long Extensions = 0;
char Answer[1026];
char Reply[1024];
char *ptr, *EnvID = NULL;
int status;
long Size, len;
unsigned int i;
BOOL EscapedState = FALSE;
unsigned long LastNMAPContact;
char TimeBuf[80];
status = GetAnswer (Client, Reply, sizeof (Reply));
HandleFailure (220);
snprintf (Answer, sizeof (Answer), "EHLO %s\r\n", BongoGlobals.hostname);
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
ConnFlush(Client->remotesmtp.conn);
status = GetEHLO (Client, &Extensions, &Size);
if (status != 250) {
snprintf (Answer, sizeof (Answer), "HELO %s\r\n", BongoGlobals.hostname);
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
ConnFlush(Client->remotesmtp.conn);
status = GetAnswer (Client, Reply, sizeof (Reply));
HandleFailure (250);
}
else {
/* The other server supports ESMTP */
// SSL delivery disabled per bug #9536
if (0 && SMTP.allow_client_ssl && (Extensions & EXT_TLS)) {
snprintf (Answer, sizeof (Answer), "STARTTLS\r\n");
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
ConnFlush(Client->remotesmtp.conn);
ConnReadAnswer(Client->remotesmtp.conn, Reply, sizeof(Reply));
if (Reply[0] == '2') {
if(ConnEncrypt(Client->remotesmtp.conn, SSLContext) < 0) {
DELIVER_ERROR (DELIVER_FAILURE);
}
status = snprintf (Answer, sizeof (Answer), "EHLO %s\r\n", BongoGlobals.hostname);
if (ConnWrite(Client->remotesmtp.conn, Answer, status) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
ConnFlush(Client->remotesmtp.conn);
status = GetAnswer (Client, Reply, sizeof (Reply));
}
}
/* Do a size check */
if ((Size > 0) && ((unsigned long) Size < Client->Flags)) { /* Client->Flags holds size of data file */
sprintf (Result,
"The recipient's server does not accept messages larger than %ld bytes. Your message was %ld bytes.",
Size, Client->Flags);
sprintf (Answer, "QUIT\r\n");
ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer));
ConnFlush(Client->remotesmtp.conn);
status = GetAnswer (Client, Reply, sizeof (Reply));
DELIVER_ERROR (DELIVER_FAILURE);
}
}
/* We abuse reply for the sender string; must be non-destructive; will get reused if multiple recipients */
if (Extensions & EXT_DSN) {
strcpy (Reply, Sender);
ptr = strchr (Reply, ' ');
if (ptr) {
*ptr = '\0';
ptr = strchr (ptr + 1, ' ');
if (ptr) {
EnvID = ptr + 1;
if (!*EnvID)
EnvID = NULL;
}
}
}
else {
strcpy (Reply, Sender);
ptr = strchr (Reply, ' ');
if (ptr) {
*ptr = '\0';
}
}
if (Reply[0] == '-' && Reply[1] == '\0') {
Reply[0] = '\0';
}
/* Keep this close to the above code; we rely on Reply not being overwritten */
if (ConnWriteF(Client->remotesmtp.conn, "MAIL FROM:<%s>", Reply) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (Extensions & EXT_SIZE) {
sprintf (Answer, " SIZE=%ld", Client->Flags);
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
if (Extensions & EXT_DSN) {
if (Recips[0].Flags & DSN_BODY) {
if (ConnWrite(Client->remotesmtp.conn, " RET=FULL", 9) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
else if (Recips[0].Flags & DSN_HEADER) {
if (ConnWrite(Client->remotesmtp.conn, " RET=HDRS", 9) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
if (EnvID) {
snprintf (Answer, sizeof (Answer), " ENVID=%s", EnvID);
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
}
/* End of Reply variable protection area */
if ((Extensions & EXT_8BITMIME) && (MsgFlags & MSG_FLAG_ENCODING_8BITM)) {
if (ConnWrite(Client->remotesmtp.conn, " BODY=8BITMIME", 14) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
if (ConnWrite(Client->remotesmtp.conn, "\r\n", 2) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (!(Extensions & EXT_PIPELINING)) {
if (!ConnFlush(Client->remotesmtp.conn)) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
status = GetAnswer (Client, Reply, sizeof (Reply));
HandleFailure (250);
}
/* We abuse Size to determine if any recipients are left */
Size = 0;
LastNMAPContact = time (NULL);
/* RCPT TO */
for (i = 0; i < RecipCount; i++) {
if (Recips[i].Result != 0) {
continue;
}
/* Some servers slow down responses to RCPT TO to discourage spamming */
/* Bad things happen if NMAP times out. Ping NMAP if 10 minutes goes by */
if (((i & 0xf) == 0) && ((time (NULL) - LastNMAPContact) > 300)) {
NMAPSendCommand (Client->client.conn, "NOOP\r\n", 6);
if (NMAPReadResponse (Client->nmap.conn, Reply, sizeof (Reply), TRUE) != 1000) {
DELIVER_ERROR (DELIVER_TRY_LATER);
} else {
LastNMAPContact = time (NULL);
}
}
snprintf (Answer, sizeof (Answer), "RCPT TO:<%s>", Recips[i].To);
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (Extensions & EXT_DSN) {
if (Recips[i].ORecip[0] != '\0') {
if (XplStrNCaseCmp (Recips[i].ORecip, "rfc822;", 7) == 0) { /* Add "rfc822;" if not already present */
snprintf (Answer, sizeof (Answer), " ORCPT=%s",
Recips[i].ORecip);
}
else {
/* Encode according to RFC 1891 */
char HexTable[] = "0123456789ABCDEF";
char *src;
char *dst;
strcpy (Answer, " ORCPT=rfc822;");
dst = Answer + 14;
src = Recips[i].ORecip;
while (*src != '\0') {
switch (*src) {
case '+':
case '=':{
*(dst++) = '+';
*(dst++) = HexTable[((0xF0 & *src) >> 4)];
*(dst++) = HexTable[(0x0F & *src)];
break;
}
default:{
*dst = *src;
dst++;
break;
}
}
src++;
}
*dst = '\0';
}
if (ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer)) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
if (Recips[i].Flags & (DSN_SUCCESS | DSN_FAILURE | DSN_TIMEOUT)) {
BOOL Comma = FALSE;
if (ConnWrite(Client->remotesmtp.conn, " NOTIFY=", 8) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (Recips[i].Flags & DSN_SUCCESS) {
if (ConnWrite(Client->remotesmtp.conn, "SUCCESS", 7) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
Comma = TRUE;
}
if (Recips[i].Flags & DSN_TIMEOUT) {
if (Comma) {
if (ConnWrite(Client->remotesmtp.conn, ",DELAY", 6) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
else {
if (ConnWrite(Client->remotesmtp.conn, "DELAY", 5) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
Comma = TRUE;
}
if (Recips[i].Flags & DSN_FAILURE) {
if (Comma) {
if (ConnWrite(Client->remotesmtp.conn, ",FAILURE", 8) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
else {
if (ConnWrite(Client->remotesmtp.conn, "FAILURE", 7) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
}
}
else {
if (ConnWrite(Client->remotesmtp.conn, " NOTIFY=NEVER", 13) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
}
if (ConnWrite(Client->remotesmtp.conn, "\r\n", 2) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (!(Extensions & EXT_PIPELINING)) {
if (!ConnFlush(Client->remotesmtp.conn)) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
status = GetAnswer (Client, Reply, sizeof (Reply));
if (Exiting) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (status == 251 || status == 250) {
Log(LOG_INFO, "Message sent from %s to %s (flags: %d, status: %d)",
Sender, Recips[i].To, Client->Flags, status);
Size++;
continue;
}
else if (status / 100 == 4) {
Log(LOG_INFO, "Message try later from %s to %s Flags %d Status %d",
Sender, Recips[i].To, Client->Flags, status);
Recips[i].Result = DELIVER_TRY_LATER;
continue;
}
else {
Log(LOG_INFO, "Message failed delivery from %s to %s Flags %d Status %d: %s",
Sender, Recips[i].To, Client->Flags, status, Reply);
if (status == 550) {
Recips[i].Result = DELIVER_USER_UNKNOWN;
if ((unsigned long) ResultLen > strlen (Reply)) {
snprintf (Result, ResultLen,
">>> RCPT TO: <%s>\\r\\n<<< %d %s",
Recips[i].To, status, Reply);
}
else {
strncpy (Result, Reply, ResultLen);
}
continue;
}
else if ((status == 501) && Recips[i].ORecip[0] != '\0'
&& (Extensions & EXT_DSN)) {
/* workaround for smtp implimentations that do not do ORCPT: properly */
Extensions ^= EXT_DSN;
i--;
continue;
}
else {
Recips[i].Result = DELIVER_FAILURE;
}
}
}
}
/* If Pipelining is turned on, we didn't read the responses (yet) */
if (Extensions & EXT_PIPELINING) {
if (!ConnFlush(Client->remotesmtp.conn)) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
status = GetAnswer (Client, Reply, sizeof (Reply)); /* Pipelined answer from MAIL FROM */
HandleFailure (250);
for (i = 0; i < RecipCount; i++) {
if (Recips[i].Result == 0) {
status = GetAnswer (Client, Reply, sizeof (Reply)); /* Pipelined answer from RCPT TO */
if (Exiting) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
if (status == 251 || status == 250) {
Log(LOG_INFO, "Message sent from %s to %s Flags %d Status %d",
Sender, Recips[i].To, Client->Flags, status);
Size++;
continue;
}
else if (status / 100 == 4) {
Log(LOG_INFO, "Message try later from %s to %s Flags %d Status %d",
Sender, Recips[i].To, Client->Flags, status);
Recips[i].Result = DELIVER_TRY_LATER;
continue;
}
else {
Log(LOG_INFO, "Message failed delivery from %s to %s Flags %d Status %d",
Sender, Recips[i].To, Client->Flags, status);
if (status == 550) {
Recips[i].Result = DELIVER_USER_UNKNOWN;
if ((unsigned long) ResultLen > strlen (Reply)) { /* FIXME? This is not enough to prevent overwrites, but the buffer is really big... */
snprintf (Result, ResultLen,
">>> RCPT TO: <%s>\\r\\n<<< %d %s",
Recips[i].To, status, Reply);
}
else {
strncpy (Result, Reply, ResultLen);
}
continue;
}
else {
Recips[i].Result = DELIVER_FAILURE;
}
}
}
}
}
if (Size == 0) {
FreeInternalConnection(Client->remotesmtp);
return (DELIVER_FAILURE);
}
if (ConnWrite(Client->remotesmtp.conn, "DATA\r\n", 6) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
ConnFlush(Client->remotesmtp.conn);
status = GetAnswer (Client, Reply, sizeof (Reply));
HandleFailure (354);
MsgGetRFC822Date(-1, 0, TimeBuf);
snprintf(Answer, sizeof(Answer), "Received: from %s (%d.%d.%d.%d) by %s\r\n\twith NMAP (bongosmtp Agent); %s\r\n",
BongoGlobals.hostname, /* FIXME: this should eventually refelct the queue agent's ip hostname */
Client->client.conn->socketAddress.sin_addr.s_net,
Client->client.conn->socketAddress.sin_addr.s_host,
Client->client.conn->socketAddress.sin_addr.s_lh,
Client->client.conn->socketAddress.sin_addr.s_impno,
BongoGlobals.hostname,
TimeBuf);
ConnWrite(Client->remotesmtp.conn, Answer, strlen(Answer));
snprintf(Answer, sizeof(Answer), "QRETR %s MESSAGE\r\n", Client->RemoteHost);
NMAPSendCommand(Client->client.conn, Answer, strlen(Answer));
status = NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE);
if (status != 2023) {
DELIVER_ERROR (DELIVER_FAILURE);
}
Size = atol (Reply);
if (!Size) {
DELIVER_ERROR (DELIVER_FAILURE);
}
/* Sending message to remote system; must escape any dots on a line */
while (Size > 0) {
if ((unsigned int)Size < sizeof (Answer)) {
len = ConnRead (Client->client.conn, Answer, Size);
}
else {
len = ConnRead (Client->client.conn, Answer, sizeof(Answer));
}
if (len > 0) {
if (!SendServerEscaped (Client, Answer, len, &EscapedState)) {
DELIVER_ERROR_NO_RETURN (DELIVER_TRY_LATER);
}
Size -= len;
}
else {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
}
/* FIX ME, check for 1000 */
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* Get the 1000 OK message for QRETR MESG */
if (ConnWrite(Client->remotesmtp.conn, "\r\n.\r\n", 5) < 1) {
DELIVER_ERROR (DELIVER_TRY_LATER);
}
ConnFlush(Client->remotesmtp.conn);
status = GetAnswer (Client, Reply, sizeof (Reply));
HandleFailure (250);
/* Right here we know we've delivered the messages successfully */
for (i = 0; i < RecipCount; i++) {
if (Recips[i].Result == 0) {
Recips[i].Result = DELIVER_SUCCESS;
if (Extensions & EXT_DSN) {
Recips[i].Result++;
}
}
}
if (ConnWrite(Client->remotesmtp.conn, "QUIT\r\n", 6) < 1) {
status = GetAnswer (Client, Reply, sizeof (Reply));
}
ConnFlush(Client->remotesmtp.conn);
return (DELIVER_SUCCESS);
}
__inline static BOOL
IPAddressIsLocal (struct in_addr IPAddress)
{
return (((IPAddress.s_addr) == LocalAddress) || ((IPAddress.s_net) == 127)
|| ((IPAddress.s_addr) == 0));
}
static int
DeliverRemoteMessage (ConnectionStruct * Client, char *Sender,
RecipStruct * Recips, unsigned int RecipCount,
unsigned long MsgFlags, char *Result,
int ResultLen)
{
char Host[MAXEMAILNAMESIZE + 1];
char *ptr;
int status;
int RetVal;
XplDns_MxLookup *mx = NULL;
XplDns_IpList *list = NULL;
// If we have a relay configured, use that.
// Any type of failure is treated as temporary.
if (SMTP.use_relay) {
Connection *conn = ConnAlloc(TRUE);
conn->socketAddress.sin_addr.s_addr = inet_addr(SMTP.relay_host);
conn->socketAddress.sin_family = AF_INET;
conn->socketAddress.sin_port = htons (25); // TODO: configurable?
if (conn->socketAddress.sin_addr.s_addr == INADDR_NONE) {
// TODO: Resolve host? Implies a resolve loop for A recs. tho.
DELIVER_ERROR_NO_RETURN(DELIVER_TRY_LATER);
} else {
Client->remotesmtp.conn = conn;
RetVal = DeliverSMTPMessage(Client, Sender, Recips, RecipCount,
MsgFlags, Result, ResultLen);
}
ConnClose(conn);
ConnFree(conn);
if (RetVal != DELIVER_SUCCESS) {
DELIVER_ERROR(DELIVER_TRY_LATER);
}
return DELIVER_SUCCESS;
}
// no relay, therefore we want to look for mail exchangers
ptr = strchr (Recips[0].To, '@');
if (!ptr) DELIVER_ERROR (DELIVER_BOGUS_NAME);
ptr++;
if (ptr[0] == '[') {
// potential ip address in the host name (e.g. jdoe@[1.1.1.1])
char *end;
end = strchr(ptr, ']');
if (end) {
*end = '\0';
} else {
DELIVER_ERROR (DELIVER_BOGUS_NAME);
}
// FIXME: assumes our DNS routines will accept IP address.
}
strncpy(Host, ptr, MAXEMAILNAMESIZE - 1);
// Resolve Host using MX routines.
mx = XplDnsNewMxLookup(Host);
if (mx->status != XPLDNS_SUCCESS) {
switch(mx->status) {
case XPLDNS_SUCCESS:
break;
case XPLDNS_TRY_AGAIN:
RetVal = DELIVER_TIMEOUT;
break;
case XPLDNS_NOT_FOUND:
case XPLDNS_NO_DATA:
RetVal = DELIVER_HOST_UNKNOWN;
break;
default:
case XPLDNS_DNS_ERROR:
RetVal = DELIVER_TRY_LATER;
break;
}
DELIVER_ERROR(RetVal);
goto finish;
}
while ((list = XplDnsNextMxLookupIpList(mx)) != NULL) {
int x;
for (x = 0; x < list->number; x++) {
// clear return status; not an error
DELIVER_ERROR_NO_RETURN(0);
if (Exiting) {
DELIVER_ERROR(DELIVER_TRY_LATER);
goto finish;
}
// FIXME: check for ETRN, or mail being relayed.
Client->remotesmtp.conn = ConnAlloc(TRUE);
Client->remotesmtp.conn->socketAddress.sin_addr.s_addr = list->ip_list[x];
Client->remotesmtp.conn->socketAddress.sin_family = AF_INET;
Client->remotesmtp.conn->socketAddress.sin_port = htons (25);
status = ConnConnect(Client->remotesmtp.conn, NULL, 0, NULL);
if (status < 0) {
switch (errno) {
case ETIMEDOUT:
RetVal = DELIVER_TIMEOUT;
break;
case ECONNREFUSED:
RetVal = DELIVER_REFUSED;
break;
case ENETUNREACH:
RetVal = DELIVER_UNREACHABLE;
break;
default:
RetVal = DELIVER_TRY_LATER;
break;
}
DELIVER_ERROR_NO_RETURN(RetVal);
} else {
Result[0] = '\0';
RetVal = DeliverSMTPMessage(Client, Sender, Recips, RecipCount,
MsgFlags, Result, ResultLen);
}
FreeInternalConnection(Client->remotesmtp);
if (RetVal == DELIVER_SUCCESS) {
goto finish;
}
// TODO: Should take into account max MX setting here
if (RetVal == DELIVER_FAILURE) {
goto finish;
}
if (Exiting) goto finish;
// ping the Queue agent to ensure we don't time out
NMAPSendCommand (Client->client.conn, "NOOP\r\n", 6);
NMAPReadResponse (Client->client.conn, Result, ResultLen, TRUE);
Result[0] = '\0';
}
}
finish:
XplDnsFreeMxLookup(mx);
return (RetVal);
}
int
RewriteAddress(Connection * conn, const char *Source, char *Target, unsigned int TargetSize)
{
char WorkSpace[1024];
char *Src, *Dst;
const char *Input;
unsigned int i;
int RetVal = MAIL_BOGUS;
/***
This routine is supposed to rewrite any address into proper format
several rules exist:
Bang: host1!host2!host3!user -> should become user%host3%host2@host1
Percent: Check percent address and strip of local hostname if existent,
replace next % with @
IP: check for IP-Addresses peter@[151.155.10.117]
Address: <dns-addr> (username)
Clean out spaces etc...
***/
i = strlen (Source);
if ((TargetSize < i) || (i >= MAXEMAILNAMESIZE)) {
return (RetVal);
}
/** remove illegal chars **/
Input = Source;
Dst = WorkSpace;
while (*Input) {
if (!isspace ((unsigned char) *Input) && *Input != '"') {
*(Dst++) = *(Input++);
}
else {
Input++;
}
}
*Dst = '\0';
/* Remove a stray @ at the beginning or end of an address */
if (WorkSpace[0] == '@') {
memcpy (WorkSpace, WorkSpace + 1, strlen (WorkSpace));
}
else if (WorkSpace[strlen (WorkSpace) - 1] == '@') {
WorkSpace[strlen (WorkSpace) - 1] = '\0';
}
/* Clean address now in Workspace */
/** Address grab **/
/* Find the address */
if ((Src = strchr (WorkSpace, '<')) != NULL) { /* Address hidden inside <> */
Dst = strchr (Src + 1, '>');
if (!Dst) {
return (RetVal);
}
else {
*Dst = '\0';
if (strlen (Src) < TargetSize) {
strcpy (Target, Src);
}
else {
strncpy (Target, Src, TargetSize - 1);
Target[TargetSize - 1] = '\0';
}
}
}
else {
if (strlen (WorkSpace) < TargetSize) {
strcpy (Target, WorkSpace);
}
else {
strncpy (Target, WorkSpace, TargetSize - 1);
Target[TargetSize - 1] = '\0';
}
if ((Src = strchr (Target, '(')) != NULL) { /* Remove name and () */
Dst = strchr (Src + 1, ')');
if (Dst) {
strcpy (Src, Dst + 1);
}
}
}
/* Clean address now in Target */
/** Convert Bang - addresses into Domain format **/
WorkSpace[0] = '\0';
if ((Src = strrchr (Target, '!')) != NULL) {
/* Might have % and @ -> make them all @ */
while ((Dst = strchr (Target, '@')) != NULL) {
*Dst = '%';
}
/* Cut off all @ addresses and remember them (via Dst) */
if ((Dst = strchr (Target, '%')) != NULL) {
*Dst = '\0';
}
while ((Src = strrchr (Target, '!')) != NULL) {
strncat (WorkSpace, Src + 1, strlen (Src) + 1);
strcat (WorkSpace, "%");
*Src = '\0';
}
/* Copy the remembered addresses */
if (Dst) {
strcat (WorkSpace, Dst + 1);
strcat (WorkSpace, "%");
}
}
if ((strlen (WorkSpace) + strlen (Target)) < sizeof (WorkSpace)) {
strcat (WorkSpace, Target);
}
/* Clean address now in WorkSpace */
/* Do we have a @ in the name? *//* I'll check reverse so I can reuse Src for the next check */
if ((Src = strrchr (WorkSpace, '@')) == NULL) {
/* No @, replace the last % with an @ */
Dst = strrchr (WorkSpace, '%');
if (!Dst) { /* No @ or %, simple address, just return */
if (strlen (WorkSpace) < TargetSize) {
strcpy (Target, WorkSpace);
}
else {
strncpy (Target, WorkSpace, TargetSize - 1);
Target[TargetSize - 1] = '\0';
}
return (MAIL_LOCAL);
}
*Dst = '@';
}
else {
/* Do we have multiple @ in the name, if yes, replace them with % */
Src--;
while (Src > WorkSpace) {
if (*Src == '@')
*Src = '%';
Src--;
}
}
/* Clean address now in WorkSpace */
if (WorkSpace[0] == '\0') {
return (MAIL_BOGUS);
}
/* TODO: i'm not sure this code is functionally equivalient with its replacement due to the address checking */
{
char addr[1024];
char *ptr;
NMAPSendCommandF(conn, "ADDRESS RESOLVE %s\r\n", WorkSpace);
RetVal = NMAPReadResponse(conn, addr, 1023, FALSE);
/* find the second space */
ptr = strchr(addr, ' ');
if (ptr) {
ptr++;
ptr = strchr(ptr, ' ');
if (ptr) {
ptr++;
}
}
if (!ptr) {
/* there was an error getting back the address */
ptr = WorkSpace;
}
if (strlen (ptr) < TargetSize) {
strcpy (Target, ptr);
} else {
strncpy (Target, ptr, TargetSize - 1);
Target[TargetSize - 1] = '\0';
}
switch (RetVal) {
case 1000:
RetVal = MAIL_LOCAL;
break;
case 1001:
RetVal = MAIL_RELAY;
break;
case 1002:
RetVal = MAIL_REMOTE;
break;
default:
RetVal = MAIL_BOGUS;
break;
}
}
return (RetVal);
}
#define QuickCmpToUpper(check,result) result=check & 0xdf;
static int
RecipCompare (const RecipStruct * Recip1, const RecipStruct * Recip2)
{
const char *ptr1, *ptr2;
char char1, char2;
if ((!(Recip1->To[0])) || (!(Recip2->To[0]))) {
return (-1);
}
ptr1 = strchr ((const char *) Recip1->To, '@') + 1; /* The @s were validated earlier. */
ptr2 = strchr ((const char *) Recip2->To, '@') + 1;
while (*ptr1 && *ptr2 && *ptr1 != ' ' && *ptr2 != ' ') {
char1 = *(ptr1++) & 0xdf;
char2 = *(ptr2++) & 0xdf;
if (char1 < char2) {
return (-1);
}
else if (char1 > char2) {
return (1);
}
}
return (0);
}
void
ProcessRemoteEntry (ConnectionStruct * Client, unsigned long Size, int Lines)
{
char *ptr, *ptr2;
char From[BUFSIZE + 1];
char Reply[BUFSIZE + 1];
char Result[BUFSIZE + 1];
BOOL Local = FALSE;
int rc, i, j = 0, len;
unsigned long MsgFlags = MSG_FLAG_ENCODING_7BIT;
RecipStruct *Recips;
int NumRecips = 0;
char *Envelope;
char *EnvelopePtr;
if (!Client)
return;
Envelope = MemMalloc(Size+1);
Recips = MemMalloc(Lines * sizeof(RecipStruct));
if (!Recips) {
Log(LOG_ERROR, "Out of memory File %s %d Line %d",
__FILE__, (Lines * sizeof (RecipStruct)), __LINE__);
return;
}
if (!Envelope) {
MemFree(Recips);
Log(LOG_ERROR, "Out of memory File %s %d Line %d",
__FILE__, Size, __LINE__);
return;
}
/* read in the entire stream. this uses more memory but protects a little better from overflows */
rc = ConnReadCount(Client->client.conn, (char *)Envelope, Size);
if ((rc < 0) || ((unsigned long)rc != Size) || (NMAPReadResponse(Client->client.conn, Reply, BUFSIZE, FALSE)) != 6021) {
MemFree(Envelope);
MemFree(Recips);
return;
}
EnvelopePtr = Envelope;
while ((rc = PullLine2(Reply, sizeof(Reply), &EnvelopePtr)) != 6021) {
switch (Reply[0]) {
case QUEUE_FROM:{
rc = snprintf (Result, sizeof (Result), "QMOD RAW %s\r\n", Reply);
NMAPSendCommand (Client->client.conn, Result, rc);
strcpy (From, Reply + 1);
}
break;
case QUEUE_FLAGS:{
MsgFlags = atoi (Reply + 1);
}
break;
case QUEUE_RECIP_REMOTE:{
/* R<recipient> <original address> <flags> */
memset(Recips[NumRecips].ORecip, '\0', MAXEMAILNAMESIZE+1);
Recips[NumRecips].Flags = DSN_FAILURE;
Recips[NumRecips].Result = 0;
ptr = strchr (Reply + 1, ' ');
if (ptr) {
*ptr = '\0';
}
rc = RewriteAddress (Client->client.conn, Reply + 1, Recips[NumRecips].To, MAXEMAILNAMESIZE);
if (rc == MAIL_BOGUS) {
rc = DELIVER_BOGUS_NAME;
Recips[NumRecips].Result = rc;
}
/* now go after the original address */
ptr++;
ptr2 = strchr(ptr, ' ');
if (ptr2) {
*ptr2 = '\0';
}
strncpy(Recips[NumRecips].ORecip, ptr, MAXEMAILNAMESIZE);
/* lastly go after the flags */
Recips[NumRecips].Flags = atoi(ptr2+1);
if (Recips[NumRecips].Result == 0) {
switch (rc) {
case MAIL_RELAY:
case MAIL_REMOTE:{
NumRecips++;
break;
}
default:{
/* The responses from these commands will be in NMAPs send buffer after our ctrl file */
/* we read them after the loop */
if (!Local) {
rc = snprintf (Reply, sizeof (Reply), "QCOPY %s\r\n", Client->RemoteHost); /* RemoteHost abused for queue id */
NMAPSendCommand (Client->client.conn, Reply, rc);
NMAPSendCommand (Client->client.conn, Reply,
snprintf (Reply,
sizeof (Reply),
"QSTOR FLAGS %ld\r\n",
MsgFlags));
rc = snprintf (Reply, sizeof (Reply),
"QSTOR FROM %s\r\n", From);
NMAPSendCommand (Client->client.conn, Reply, rc);
Local = TRUE;
j = 0;
}
if (Recips[NumRecips].ORecip[0] != '\0') {
rc = snprintf (Reply, sizeof (Reply),
"QSTOR LOCAL %s %s %lu\r\n",
Recips[NumRecips].To,
Recips[NumRecips].ORecip,
Recips[NumRecips].Flags);
}
else {
rc = snprintf (Reply, sizeof (Reply),
"QSTOR LOCAL %s %s %lu\r\n",
Recips[NumRecips].To,
Recips[NumRecips].To,
Recips[NumRecips].Flags);
}
NMAPSendCommand (Client->client.conn, Reply, rc);
j++;
break;
}
}
}
else {
NumRecips++;
}
}
break;
default:{
rc = snprintf (Result, sizeof (Result), "QMOD RAW %s\r\n", Reply);
NMAPSendCommand (Client->client.conn, Result, rc);
}
break;
}
}
if (Local) {
NMAPSendCommand (Client->client.conn, "QRUN\r\n", 6);
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QCOPY */
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QSTOR FLAGS */
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QSTOR FROM */
for (i = 0; i < j; i++) {
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QSTOR LOCALS */
}
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QRUN */
}
qsort (Recips, NumRecips, sizeof (RecipStruct), (void *) RecipCompare);
i = 0;
while (i < NumRecips) {
rc = 1;
while (i + rc < NumRecips && RecipCompare (&Recips[i], &Recips[i + rc]) == 0) {
rc++;
}
Result[0] = '\0';
DeliverRemoteMessage (Client, From, &Recips[i], rc, MsgFlags, Result, BUFSIZE);
for (j = i; j < (i + rc); j++) {
switch (Recips[j].Result) {
case DELIVER_SUCCESS:{
if (Recips[j].Flags & DSN_SUCCESS) {
len = snprintf (Reply, sizeof (Reply),
"QRTS %s %s %lu %d\r\n", Recips[j].To,
Recips[j].ORecip, Recips[j].Flags,
DELIVER_SUCCESS);
NMAPSendCommand (Client->client.conn, Reply, len);
}
}
break;
case DELIVER_TIMEOUT:
case DELIVER_REFUSED:
case DELIVER_UNREACHABLE:
case DELIVER_TRY_LATER:{
len = snprintf (Reply, sizeof (Reply),
"QMOD RAW R%s %s %lu\r\n", Recips[j].To,
Recips[j].ORecip[0] != '\0' ? Recips[j].
ORecip : Recips[j].To, Recips[j].Flags);
NMAPSendCommand (Client->client.conn, Reply, len);
}
break;
case DELIVER_HOST_UNKNOWN:
case DELIVER_USER_UNKNOWN:
case DELIVER_BOGUS_NAME:
case DELIVER_INTERNAL_ERROR:
case DELIVER_FAILURE:{
if (Recips[j].Flags & DSN_FAILURE) {
if (Result[0] != '\0') {
len = snprintf (Reply, sizeof (Reply),
"QRTS %s %s %lu %d %s\r\n",
Recips[j].To,
Recips[j].ORecip[0] != '\0' ? Recips[j].
ORecip : Recips[j].To,
Recips[j].Flags, Recips[j].Result,
Result);
} else {
len = snprintf (Reply, sizeof (Reply),
"QRTS %s %s %lu %d\r\n",
Recips[j].To,
Recips[j].ORecip[0] != '\0' ? Recips[j].
ORecip : Recips[j].To,
Recips[j].Flags, Recips[j].Result);
}
NMAPSendCommand (Client->client.conn, Reply, len);
}
}
break;
}
}
i += rc;
}
MemFree(Envelope);
MemFree(Recips);
}
static void
RelayRemoteEntry (ConnectionStruct * client, unsigned long size, int lines)
{
int i;
int rc;
int j = 0;
int recipCount = 0;
unsigned long msgFlags = MSG_FLAG_ENCODING_7BIT;
char *ptr;
char *ptr2;
char *buffer;
char *bufferPtr;
char from[BUFSIZE + 1];
char reply[BUFSIZE + 1];
char result[BUFSIZE + 1];
BOOL local = FALSE;
RecipStruct *recips;
if (!client) {
return;
}
lines += 3;
recips = MemMalloc (size + (lines * sizeof (RecipStruct)));
if (!recips) {
Log(LOG_ERROR, "Out of memory File %s %d Line %d",
__FILE__, size + (lines * sizeof (RecipStruct)), __LINE__);
return;
}
if (client->From != NULL) {
MemFree (client->From);
}
/* This makes sure the recip structure gets freed if we */
/* go into EndClientConnection for an error or shutdown */
client->From = (char *) recips;
/* This adds lines * sizeof(RecipStruct) */
buffer = (char *) (recips + lines);
bufferPtr = buffer;
while ((rc = NMAPReadResponse (client->client.conn, reply, sizeof (reply), FALSE)) != 6021) {
switch (reply[0]) {
case QUEUE_FROM:{
strcpy (from, reply + 1);
NMAPSendCommand (client->client.conn, result, snprintf (result, sizeof (result), "QMOD RAW %s\r\n", reply));
continue;
}
case QUEUE_FLAGS:{
msgFlags = atoi (reply + 1);
NMAPSendCommand (client->client.conn, result, snprintf (result, sizeof (result), "QMOD RAW %s\r\n", reply));
continue;
}
case QUEUE_CALENDAR_LOCAL:{
/* All calendar messages should be delivered locally. */
NMAPSendCommand (client->client.conn, result,
snprintf (result, sizeof(result),
"QMOD RAW %s\r\n", reply));
continue;
}
case QUEUE_RECIP_LOCAL:
case QUEUE_RECIP_MBOX_LOCAL:{
if (msgFlags & (MSG_FLAG_SOURCE_EXTERNAL | MSG_FLAG_CALENDAR_OBJECT)) {
/* Calendar messages should be delivered locally.
Externally received messages (relayed here) should be delivered locally. */
NMAPSendCommand (client->client.conn, result,
snprintf (result, sizeof(result),
"QMOD RAW %s\r\n", reply));
continue;
}
if (msgFlags & MSG_FLAG_SOURCE_EXTERNAL) {
/* Reuse space */
bufferPtr = recips[recipCount].To;
continue;
}
__attribute__((fallthrough));
/* All internally generated messages should be relayed. */
}
case QUEUE_RECIP_REMOTE:{
memset(recips[recipCount].ORecip, '\0', MAXEMAILNAMESIZE+1);
recips[recipCount].Flags = DSN_FAILURE;
recips[recipCount].Result = 0;
/* R<recipient>[ <original recipient>[ flags]] */
/* Look for the first optional ' ' delim between <recipient> and <original recipient> */
ptr = strchr (reply + 1, ' ');
if (ptr) {
*ptr = '\0';
}
*bufferPtr = '\0';
rc = RewriteAddress (client->client.conn, reply + 1, bufferPtr, size - (bufferPtr - buffer));
if (rc == MAIL_BOGUS) {
rc = DELIVER_BOGUS_NAME;
recips[recipCount].Result = rc;
}
strncpy(recips[recipCount].To, bufferPtr, MAXEMAILNAMESIZE);
/* Leave NULL terminator */
bufferPtr += strlen (bufferPtr) + 1;
if (ptr) {
ptr2 = strchr (ptr + 1, ' ');
if (ptr2) {
*ptr2 = '\0';
recips[recipCount].Flags = atoi (ptr2 + 1);
}
strncpy(recips[recipCount].ORecip, ptr+1, MAXEMAILNAMESIZE);
bufferPtr += strlen (bufferPtr) + 1;
}
if (recips[recipCount].Result == 0) {
switch (rc) {
case MAIL_REMOTE:
case MAIL_RELAY:{
/* All internally generated messages should be relayed */
if (!(msgFlags & MSG_FLAG_SOURCE_EXTERNAL)) {
recipCount++;
}
continue;
}
default:{
if (msgFlags &
(MSG_FLAG_SOURCE_EXTERNAL |
MSG_FLAG_CALENDAR_OBJECT)) {
/* Calendar messages should be delivered locally.
Externally received messages (relayed here) should be delivered locally. */
/* The responses from these commands will be in NMAPs send buffer after our ctrl file */
/* we read them after the loop */
if (!local) {
/* RemoteHost abused for queue id */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply,
sizeof (reply),
"QCOPY %s\r\n",
client->
RemoteHost));
NMAPSendCommand (client->client.conn, reply,
snprintf (reply,
sizeof (reply),
"QSTOR FLAGS %ld\r\n",
msgFlags));
NMAPSendCommand (client->client.conn, reply,
snprintf (reply,
sizeof (reply),
"QSTOR FROM %s\r\n",
from));
local = TRUE;
j = 0;
}
if (recips[recipCount].ORecip[0] != '\0') {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply,
sizeof (reply),
"QSTOR LOCAL %s %s %lu\r\n",
recips
[recipCount].To,
recips
[recipCount].
ORecip,
recips
[recipCount].
Flags));
}
else {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply,
sizeof (reply),
"QSTOR LOCAL %s %s %lu\r\n",
recips
[recipCount].To,
recips
[recipCount].To,
recips
[recipCount].
Flags));
}
/* Reuse space */
bufferPtr = recips[recipCount].To;
j++;
}
else if (!(msgFlags & MSG_FLAG_SOURCE_EXTERNAL)) {
/* All internally generated messages should be relayed. */
strncpy(recips[recipCount].To, recips[recipCount].ORecip, MAXEMAILNAMESIZE);
recipCount++;
}
continue;
}
}
}
else {
recipCount++;
}
break;
}
default:{
NMAPSendCommand (client->client.conn, result,
snprintf (result, sizeof (result),
"QMOD RAW %s\r\n", reply));
break;
}
}
}
if (local) {
NMAPSendCommand (client->client.conn, "QRUN\r\n", 6);
/* QCOPY */
NMAPReadResponse (client->client.conn, reply, sizeof (reply), TRUE);
/* QSTOR FLAGS */
NMAPReadResponse (client->client.conn, reply, sizeof (reply), TRUE);
/* QSTOR FROM */
NMAPReadResponse (client->client.conn, reply, sizeof (reply), TRUE);
/* QSTOR LOCALS */
for (i = 0; i < j; i++) {
NMAPReadResponse (client->client.conn, reply, sizeof (reply), TRUE);
}
/* QRUN */
NMAPReadResponse (client->client.conn, reply, sizeof (reply), TRUE);
}
qsort (recips, recipCount, sizeof (RecipStruct), (void *) RecipCompare);
i = 0;
while (i < recipCount) {
rc = 1;
while (i + rc < recipCount
&& RecipCompare (&recips[i], &recips[i + rc]) == 0) {
rc++;
}
result[0] = '\0';
DeliverRemoteMessage (client, from, &recips[i], rc, msgFlags, result,
BUFSIZE);
for (j = i; j < (i + rc); j++) {
switch (recips[j].Result) {
case DELIVER_SUCCESS:{
if (recips[j].Flags & DSN_SUCCESS) {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, sizeof (reply),
"QRTS %s %s %lu %d\r\n",
recips[j].To,
recips[j].ORecip,
recips[j].Flags,
DELIVER_SUCCESS));
}
break;
}
case DELIVER_TIMEOUT:
case DELIVER_REFUSED:
case DELIVER_UNREACHABLE:
case DELIVER_TRY_LATER:{
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, sizeof (reply),
"QMOD RAW R%s %s %lu\r\n",
recips[j].To,
recips[j].ORecip[0] != '\0' ? recips[j].
ORecip : recips[j].To,
recips[j].Flags));
break;
}
case DELIVER_HOST_UNKNOWN:
case DELIVER_USER_UNKNOWN:
case DELIVER_BOGUS_NAME:
case DELIVER_INTERNAL_ERROR:
case DELIVER_FAILURE:{
if (recips[j].Flags & DSN_FAILURE) {
if (result[0] != '\0') {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, sizeof (reply),
"QRTS %s %s %lu %d %s\r\n",
recips[j].To,
recips[j].
ORecip[0] != '\0' ? recips[j].
ORecip : recips[j].To,
recips[j].Flags,
recips[j].Result,
result));
}
else {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, sizeof (reply),
"QRTS %s %s %lu %d\r\n",
recips[j].To,
recips[j].
ORecip[0] != '\0' ? recips[j].
ORecip : recips[j].To,
recips[j].Flags,
recips[j].Result));
}
}
break;
}
}
}
i += rc;
}
/* This includes buffer */
MemFree (recips);
client->From = NULL;
return;
}
static int PullLine2 (char *Line, unsigned long LineSize, char **NextLine) {
char *ptr;
ptr = *NextLine;
*NextLine = strchr (ptr, '\r');
if (*NextLine) {
**NextLine = '\0';
(*NextLine) += 2;
}
if (strlen (ptr) < LineSize) {
strcpy (Line, ptr);
} else {
strncpy (Line, ptr, LineSize - 1);
Line[LineSize - 1] = '\0';
}
if (*NextLine) {
return (0);
} else {
return (6021);
}
}
static int PullLine (char *Line, unsigned long LineSize, char **NextLine) {
char *ptr;
ptr = *NextLine;
*NextLine = strchr (ptr, '\n');
if (*NextLine) {
**NextLine = '\0';
*NextLine = (*NextLine) + 1;
}
if (strlen (ptr) < LineSize) {
strcpy (Line, ptr);
} else {
strncpy (Line, ptr, LineSize - 1);
Line[LineSize - 1] = '\0';
}
if (*NextLine) {
return (0);
} else {
return (6021);
}
}
static void
RelayLocalEntry (ConnectionStruct * client, unsigned long size,
unsigned long lines)
{
int rc;
int flags;
unsigned long msgFlags = MSG_FLAG_ENCODING_7BIT;
char *ptr;
char *ptr2;
char *next;
char *envelope;
char buffer[1024];
char to[MAXEMAILNAMESIZE + 1];
char from[BUFSIZE + 1];
char reply[BUFSIZE + 1];
BOOL Local = FALSE;
UNUSED_PARAMETER(lines);
envelope = MemMalloc (size + 1);
next = envelope;
while ((rc = NMAPReadResponse (client->client.conn, buffer, sizeof (buffer) - 1, FALSE)) != 6021) {
rc = strlen (buffer);
memcpy (next, buffer, rc);
next[rc++] = '\n';
next += rc;
}
*next = '\0';
next = envelope;
while ((rc = PullLine (buffer, sizeof (buffer) - 1, &next)) != 6021) {
flags = DSN_FAILURE;
switch (buffer[0]) {
case QUEUE_FROM:{
strcpy (from, buffer + 1);
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE, "QMOD RAW %s\r\n",
buffer));
continue;
}
case QUEUE_FLAGS:{
msgFlags = atol (buffer + 1);
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE, "QMOD RAW %s\r\n",
buffer));
continue;
}
case QUEUE_RECIP_REMOTE:{
ptr = strchr (buffer + 1, ' ');
if (ptr) {
*ptr = '\0';
ptr2 = strchr (ptr + 1, ' ');
if (ptr2) {
flags = atol (ptr2 + 1);
}
}
rc = RewriteAddress (client->client.conn, buffer + 1, to, sizeof (to));
switch (rc) {
case MAIL_BOGUS:{
if (flags & DSN_FAILURE) {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QRTS %s %s %lu %d Addressformat not valid\r\n",
to, ptr + 1,
(long unsigned int)
flags,
DELIVER_BOGUS_NAME));
}
continue;
}
case MAIL_LOCAL:{
if (msgFlags &
(MSG_FLAG_SOURCE_EXTERNAL |
MSG_FLAG_CALENDAR_OBJECT)) {
/* Calendar messages should be delivered locally.
Externally received messages (relayed here) should be delivered locally. */
if (!Local) {
/* RemoteHost abused for queue id */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QCOPY %s\r\n",
client->
RemoteHost));
NMAPReadResponse (client->client.conn, reply, BUFSIZE, TRUE); /* QCOPY */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QSTOR FLAGS %lu\r\n",
msgFlags));
NMAPReadResponse (client->client.conn, reply, BUFSIZE, TRUE); /* QSTOR FLAGS */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QSTOR FROM %s\r\n",
from));
NMAPReadResponse (client->client.conn, reply, BUFSIZE, TRUE); /* QSTOR FROM */
Local = TRUE;
}
if (ptr) {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QSTOR LOCAL %s %s\r\n",
to, ptr + 1));
}
else {
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QSTOR LOCAL %s\r\n",
to));
}
NMAPReadResponse (client->client.conn, reply, BUFSIZE, TRUE); /* QSTOR LOCALS */
}
else if (!(msgFlags & MSG_FLAG_SOURCE_EXTERNAL)) {
if (ptr) {
*ptr = ' ';
}
/* All internally generated messages should be relayed. */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QMOD RAW "
QUEUES_RECIP_REMOTE
"%s\r\n", buffer + 1));
}
continue;
}
case MAIL_REMOTE:
case MAIL_RELAY:{
/* All internally generated messages should be relayed. */
if (!(msgFlags & MSG_FLAG_SOURCE_EXTERNAL)) {
if (ptr) {
*ptr = ' ';
}
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QMOD RAW "
QUEUES_RECIP_REMOTE
"%s\r\n", buffer + 1));
}
continue;
}
}
continue;
}
case QUEUE_CALENDAR_LOCAL:{
msgFlags |= MSG_FLAG_CALENDAR_OBJECT;
__attribute__((fallthrough));
}
case QUEUE_RECIP_LOCAL:
case QUEUE_RECIP_MBOX_LOCAL:{
if (msgFlags &
(MSG_FLAG_SOURCE_EXTERNAL | MSG_FLAG_CALENDAR_OBJECT)) {
/* Calendar messages should be delivered locally.
Externally received messages (relayed here) should be delivered locally. */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QMOD RAW %s\r\n", buffer));
}
else if (!(msgFlags & MSG_FLAG_SOURCE_EXTERNAL)) {
/* All internally generated messages should be relayed. */
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE,
"QMOD RAW " QUEUES_RECIP_REMOTE
"%s\r\n", buffer + 1));
}
continue;
}
default:{
NMAPSendCommand (client->client.conn, reply,
snprintf (reply, BUFSIZE, "QMOD RAW %s\r\n",
buffer));
continue;
}
}
}
MemFree (envelope);
if (Local) {
NMAPSendCommand (client->client.conn, "QRUN\r\n", 6);
NMAPReadResponse (client->client.conn, reply, BUFSIZE, TRUE); /* QRUN */
}
}
static void
ProcessLocalEntry (ConnectionStruct * Client, unsigned long Size,
unsigned long Lines)
{
unsigned long msgFlags = 0;
char Line[1024];
char Reply[BUFSIZE + 1];
char From[BUFSIZE + 1];
char To[MAXEMAILNAMESIZE + 1];
char *ptr, *ptr2;
int len;
int rc, j;
int Flags;
BOOL Local = FALSE;
char *Envelope = MemMalloc (Size + 1);
char *NextLine = Envelope;
UNUSED_PARAMETER(Lines);
while ((rc = NMAPReadResponse (Client->client.conn, Line, sizeof (Line), FALSE)) != 6021) {
rc = strlen (Line);
memcpy (NextLine, Line, rc);
NextLine[rc++] = '\n';
NextLine += rc;
}
NextLine[0] = '\0';
NextLine = Envelope;
while ((rc = PullLine (Line, sizeof (Line), &NextLine)) != 6021) {
Flags = DSN_FAILURE;
switch (Line[0]) {
case QUEUE_FROM:{
len =
snprintf (Reply, sizeof (Reply), "QMOD RAW %s\r\n", Line);
NMAPSendCommand (Client->client.conn, Reply, len);
strcpy (From, Line + 1);
}
break;
case QUEUE_FLAGS:{
msgFlags = atol (Line + 1);
NMAPSendCommand (Client->client.conn, Reply,
snprintf (Reply, sizeof (Reply),
"QMOD RAW %s\r\n", Line));
break;
}
case QUEUE_RECIP_REMOTE:{
ptr = strchr (Line + 1, ' ');
if (ptr) {
*ptr = '\0';
ptr2 = strchr (ptr + 1, ' ');
if (ptr2) {
Flags = atol (ptr2 + 1);
}
}
rc = RewriteAddress (Client->client.conn, Line + 1, To, sizeof (To));
switch (rc) {
case MAIL_BOGUS:{
if (Flags & DSN_FAILURE) {
len =
snprintf (Reply, sizeof (Reply),
"QRTS %s %s %lu %d Addressformat not valid\r\n",
To, ptr + 1,
(long unsigned int) Flags,
DELIVER_BOGUS_NAME);
NMAPSendCommand (Client->client.conn, Reply, len);
}
continue;
break;
}
case MAIL_RELAY:
case MAIL_REMOTE:{
if (ptr) {
len =
snprintf (Reply, sizeof (Reply),
"QMOD RAW " QUEUES_RECIP_REMOTE
"%s %s\r\n", To, ptr + 1);
}
else {
len =
snprintf (Reply, sizeof (Reply),
"QMOD RAW " QUEUES_RECIP_REMOTE
"%s\r\n", To);
}
NMAPSendCommand (Client->client.conn, Reply, len);
break;
}
default:{
if (!Local) {
len = snprintf (Reply, sizeof (Reply), "QCOPY %s\r\n", Client->RemoteHost); /* RemoteHost abused for queue id */
NMAPSendCommand (Client->client.conn, Reply, len);
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QCOPY */
NMAPSendCommand (Client->client.conn, Reply,
snprintf (Reply, sizeof (Reply),
"QSTOR FLAGS %ld\r\n",
msgFlags));
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QSTOR FLAGS */
len =
snprintf (Reply, sizeof (Reply),
"QSTOR FROM %s\r\n", From);
NMAPSendCommand (Client->client.conn, Reply, len);
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QSTOR FROM */
Local = TRUE;
j = 0;
}
if (ptr) {
len =
snprintf (Reply, sizeof (Reply),
"QSTOR LOCAL %s %s\r\n", To,
ptr + 1);
}
else {
len =
snprintf (Reply, sizeof (Reply),
"QSTOR LOCAL %s\r\n", To);
}
NMAPSendCommand (Client->client.conn, Reply, len);
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QSTOR LOCALS */
j++;
break;
}
}
break;
}
default:{
len =
snprintf (Reply, sizeof (Reply), "QMOD RAW %s\r\n", Line);
NMAPSendCommand (Client->client.conn, Reply, len);
break;
}
}
}
MemFree (Envelope);
if (Local) {
NMAPSendCommand (Client->client.conn, "QRUN\r\n", 6);
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE); /* QRUN */
}
}
static void
HandleQueueConnection (void *ClientIn)
{
int ReplyInt, Queue = Q_INCOMING;
char Reply[1024];
ConnectionStruct *Client = (ConnectionStruct *) ClientIn;
char *ptr;
unsigned long Lines;
unsigned long Size;
Client->State = STATE_WAITING;
ReplyInt = NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE);
if (ReplyInt != 6020) {
NMAPSendCommand (Client->client.conn, "QDONE\r\n", 7);
EndClientConnection (Client);
return;
}
if (Reply[3] == '-') {
Reply[3] = '\0';
Queue = atoi (Reply);
Reply[3] = '-';
}
ptr = strchr (Reply, ' ');
if (!ptr) {
EndClientConnection (Client);
return;
}
*ptr = '\0';
if (strlen (Reply) < sizeof (Client->RemoteHost)) {
strcpy (Client->RemoteHost, Reply); /* Abuse RemoteHost for the queue ID */
}
else {
EndClientConnection (Client);
return;
}
Size = atol (ptr + 1);
ptr = strchr (ptr + 1, ' ');
if (!ptr) {
EndClientConnection (Client);
return;
}
Client->Flags = atol (ptr + 1);
ptr = strchr (ptr + 1, ' ');
if (!ptr) {
EndClientConnection (Client);
return;
}
Lines = atol (ptr + 1);
if (!SMTP.relay_local) {
if (Queue == Q_OUTGOING) {
ProcessRemoteEntry (Client, Size, Lines);
}
else {
ProcessLocalEntry (Client, Size, Lines);
}
}
else {
if (Queue == Q_OUTGOING) {
RelayRemoteEntry (Client, Size, Lines);
}
else {
RelayLocalEntry (Client, Size, Lines);
}
}
NMAPSendCommand (Client->client.conn, "QDONE\r\n", 7);
NMAPReadResponse (Client->client.conn, Reply, sizeof (Reply), TRUE);
EndClientConnection (Client);
return;
}
static void __attribute__((unused))
QueueServerStartup (void *ignored)
{
Connection *conn;
ConnectionStruct *client;
int ccode;
XplThreadID id = 0;
BOOL qd = FALSE, qo = FALSE;
UNUSED_PARAMETER(ignored);
XplRenameThread (XplGetThreadID (), "SMTP NMAP Q Monitor");
SMTPQServerConnection = ConnAlloc(FALSE);
if (!SMTPQServerConnection) {
raise(SIGTERM);
return;
}
memset(&(SMTPQServerConnection->socketAddress), 0, sizeof(SMTPQServerConnection->socketAddress));
SMTPQServerConnection->socketAddress.sin_family = AF_INET;
SMTPQServerConnection->socketAddress.sin_addr.s_addr = MsgGetAgentBindIPAddress();
SMTPQServerConnection->socket = ConnServerSocket(SMTPQServerConnection, 2048);
if (SMTPQServerConnection->socket == -1) {
ConnFree(SMTPQServerConnection);
raise(SIGTERM);
return;
}
/* register on the two queues we need to be on */
while (!qd && !qo) {
if (!qd) {
qd = (QueueRegister(MSGSRV_AGENT_SMTP, Q_DELIVER, SMTPQServerConnection->socketAddress.sin_port) == REGISTRATION_COMPLETED);
}
if (!qo) {
qo = (QueueRegister(MSGSRV_AGENT_SMTP, Q_OUTGOING, SMTPQServerConnection->socketAddress.sin_port) == REGISTRATION_COMPLETED);
}
if (Exiting) {
ConnFree(SMTPQServerConnection);
raise(SIGTERM);
return;
}
if (!qd || !qo) {
Log(LOG_ERROR, "Could not register with bongoqueue, sleeping for 3 seconds");
XplDelay(3000);
}
};
while (!Exiting) {
if (ConnAccept(SMTPQServerConnection, &conn) != -1) {
if (!Exiting) {
client = GetSMTPConnection();
if (client) {
/* this may seem wrong here, but really the nmap agent is the
* client here as it connected to us */
client->client.conn = conn;
XplBeginCountedThread (&id, HandleQueueConnection, STACKSIZE_Q, client, ccode, SMTPQueueThreads);
if (ccode == 0) {
continue;
}
ReturnSMTPConnection(client);
}
/* GetSMTPConnection failed */
ConnClose(conn);
ConnFree(conn);
continue;
}
ConnClose(conn);
ConnFree(conn);
}
}
XplSafeDecrement (SMTPServerThreads);
Log(LOG_DEBUG, "Queue monitor thread done.");
return;
}
static void
SMTPShutdownSigHandler (int sigtype)
{
static BOOL signaled = FALSE;
if (!signaled && (sigtype == SIGTERM || sigtype == SIGINT)) {
signaled = Exiting = TRUE;
if (SMTPServerConnection) {
ConnClose(SMTPServerConnection);
ConnFree(SMTPServerConnection);
SMTPServerConnection = NULL;
}
if (SMTPInternalServerConnection) {
ConnClose(SMTPInternalServerConnection);
ConnFree(SMTPInternalServerConnection);
SMTPInternalServerConnection = NULL;
}
if (SMTPSubmissionServerConnection) {
ConnClose(SMTPSubmissionServerConnection);
ConnFree(SMTPSubmissionServerConnection);
SMTPSubmissionServerConnection = NULL;
}
XplSignalLocalSemaphore (SMTPShutdownSemaphore);
} else if (signaled && (sigtype == SIGTERM || sigtype == SIGINT)) {
XplExit(0);
}
return;
}
static int
ServerSocketInit (void)
{
int ccode;
SMTPServerConnection = ConnAlloc(FALSE);
if (!SMTPServerConnection) {
Log(LOG_ERROR, "Could not allocate the connection.");
return -1;
}
SMTPServerConnection->socketAddress.sin_family = AF_INET;
SMTPServerConnection->socketAddress.sin_port = htons(SMTP.port);
SMTPServerConnection->socketAddress.sin_addr.s_addr = MsgGetAgentBindIPAddress();
/* Get root privs back for the bind. It's ok if this fails -
* the user might not need to be root to bind to the port */
XplSetEffectiveUserId (0);
SMTPServerConnection->socket = ConnServerSocket(SMTPServerConnection, 2048);
/* drop the privs back */
if (XplSetEffectiveUser (MsgGetUnprivilegedUser ()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user '%s'", MsgGetUnprivilegedUser ());
return -1;
}
if (SMTPServerConnection->socket < 0) {
ccode = SMTPServerConnection->socket;
Log(LOG_ERROR, "Could not allocate SMTP socket");
ConnFree(SMTPServerConnection);
return ccode;
}
return 0;
}
static int
ServerSocketSSLInit (void)
{
int ccode;
SMTPServerConnectionSSL = ConnAlloc(FALSE);
if (SMTPServerConnectionSSL == NULL) {
Log(LOG_ERROR, "Could not allocate the SSL connection");
ConnFree(SMTPServerConnection);
return -1;
}
SMTPServerConnectionSSL->socketAddress.sin_family = AF_INET;
SMTPServerConnectionSSL->socketAddress.sin_port = htons(
SMTP.submission_enabled ? SMTP.submission_port_ssl : SMTP.port_ssl);
SMTPServerConnectionSSL->socketAddress.sin_addr.s_addr = MsgGetAgentBindIPAddress ();
/* Get root privs back for the bind. It's ok if this fails -
* the user might not need to be root to bind to the port */
XplSetEffectiveUserId (0);
SMTPServerConnectionSSL->socket = ConnServerSocket(SMTPServerConnectionSSL, 2048);
/* drop the privs back */
if (XplSetEffectiveUser (MsgGetUnprivilegedUser ()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user '%s' for SSL", MsgGetUnprivilegedUser ());
return -1;
}
if (SMTPServerConnectionSSL->socket < 0) {
ccode = SMTPServerConnectionSSL->socket;
Log(LOG_ERROR, "Could not allocate SSL socket");
ConnFree(SMTPServerConnection);
return ccode;
}
return 0;
}
static int
SubmissionServerSocketInit(void)
{
int ccode;
SMTPSubmissionServerConnection = ConnAlloc(FALSE);
if (SMTPSubmissionServerConnection == NULL) return -1;
SMTPSubmissionServerConnection->socketAddress.sin_family = AF_INET;
SMTPSubmissionServerConnection->socketAddress.sin_port = htons(SMTP.submission_port);
SMTPSubmissionServerConnection->socketAddress.sin_addr.s_addr = MsgGetAgentBindIPAddress();
XplSetEffectiveUserId(0);
SMTPSubmissionServerConnection->socket = ConnServerSocket(SMTPSubmissionServerConnection, 256);
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) return -1;
if (SMTPSubmissionServerConnection->socket < 0) {
ccode = SMTPSubmissionServerConnection->socket;
ConnFree(SMTPSubmissionServerConnection);
SMTPSubmissionServerConnection = NULL;
return ccode;
}
return 0;
}
static void
SMTPSubmissionServer(void *ignored)
{
Connection *conn;
ConnectionStruct *client;
XplThreadID id;
int ccode;
(void) ignored;
XplRenameThread(XplGetThreadID(), "SMTP Submission Server");
while (!Exiting) {
if (ConnAccept(SMTPSubmissionServerConnection, &conn) == -1) {
if (!Exiting && errno == EINTR) continue;
break;
}
if (SMTPReceiverStopped || XplSafeRead(SMTPConnThreads) >= SMTP.max_thread_load) {
ConnSend(conn, MSG451NOCONNECTIONS, MSG451NOCONNECTIONS_LEN);
ConnClose(conn);
ConnFree(conn);
continue;
}
client = GetSMTPConnection();
if (client == NULL) {
ConnSend(conn, MSG500NOMEMORY, MSG500NOMEMORY_LEN);
ConnClose(conn);
ConnFree(conn);
continue;
}
client->client.conn = conn;
client->Submission = TRUE;
XplBeginCountedThread(&id, HandleConnection, STACKSIZE_S, client, ccode, SMTPConnThreads);
if (ccode == 0) continue;
ReturnSMTPConnection(client);
ConnClose(conn);
ConnFree(conn);
}
XplSafeDecrement(SMTPServerThreads);
Log(LOG_DEBUG, "SMTP submission listening thread terminated");
}
static int
InternalServerSocketInit(void)
{
int ccode;
SMTPInternalServerConnection = ConnAlloc(FALSE);
if (SMTPInternalServerConnection == NULL) {
return -1;
}
SMTPInternalServerConnection->socketAddress.sin_family = AF_INET;
SMTPInternalServerConnection->socketAddress.sin_port = htons(SMTP.internal_relay_port);
if (inet_pton(AF_INET, SMTP.internal_relay_bind_address,
&SMTPInternalServerConnection->socketAddress.sin_addr) != 1) {
Log(LOG_ERROR, "Invalid internal SMTP bind address: %s",
SMTP.internal_relay_bind_address);
ConnFree(SMTPInternalServerConnection);
SMTPInternalServerConnection = NULL;
return -1;
}
XplSetEffectiveUserId(0);
SMTPInternalServerConnection->socket = ConnServerSocket(SMTPInternalServerConnection, 128);
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) {
return -1;
}
if (SMTPInternalServerConnection->socket < 0) {
ccode = SMTPInternalServerConnection->socket;
Log(LOG_ERROR, "Could not bind internal SMTP to %s:%d",
SMTP.internal_relay_bind_address, SMTP.internal_relay_port);
ConnFree(SMTPInternalServerConnection);
SMTPInternalServerConnection = NULL;
return ccode;
}
return 0;
}
static void
SMTPInternalServer(void *ignored)
{
Connection *conn;
ConnectionStruct *client;
XplThreadID id;
int ccode;
(void) ignored;
XplRenameThread(XplGetThreadID(), "SMTP Internal Server");
while (!Exiting) {
if (ConnAccept(SMTPInternalServerConnection, &conn) == -1) {
if (!Exiting && errno == EINTR) continue;
break;
}
if (!InternalRelayAddressAllowed(conn->socketAddress.sin_addr)) {
Log(LOG_WARN, "Rejected internal SMTP connection from %s", LOGIP(conn->socketAddress));
ConnSend(conn, "554 5.7.1 Source address not permitted\r\n", 41);
ConnClose(conn);
ConnFree(conn);
continue;
}
if (SMTPReceiverStopped || XplSafeRead(SMTPConnThreads) >= SMTP.max_thread_load) {
ConnSend(conn, MSG451NOCONNECTIONS, MSG451NOCONNECTIONS_LEN);
ConnClose(conn);
ConnFree(conn);
continue;
}
client = GetSMTPConnection();
if (client == NULL) {
ConnSend(conn, MSG500NOMEMORY, MSG500NOMEMORY_LEN);
ConnClose(conn);
ConnFree(conn);
continue;
}
client->client.conn = conn;
client->InternalRelay = TRUE;
XplMutexLock(InternalRelayRateLock);
client->InternalRate = g_hash_table_lookup(InternalRelayRates,
&conn->socketAddress.sin_addr.s_addr);
if (client->InternalRate == NULL) {
uint32_t *key = g_new(uint32_t, 1);
client->InternalRate = g_new0(InternalRelayRate, 1);
if (key != NULL && client->InternalRate != NULL) {
*key = conn->socketAddress.sin_addr.s_addr;
client->InternalRate->window = time(NULL);
g_hash_table_insert(InternalRelayRates, key, client->InternalRate);
} else {
g_free(key);
g_free(client->InternalRate);
client->InternalRate = NULL;
}
}
if (client->InternalRate == NULL ||
client->InternalRate->connections >=
(unsigned int) SMTP.internal_relay_connections_per_ip) {
XplMutexUnlock(InternalRelayRateLock);
ConnSend(conn, "451 4.7.1 Too many internal relay connections\r\n",
strlen("451 4.7.1 Too many internal relay connections\r\n"));
ReturnSMTPConnection(client);
ConnClose(conn);
ConnFree(conn);
continue;
}
client->InternalRate->connections++;
XplMutexUnlock(InternalRelayRateLock);
XplBeginCountedThread(&id, HandleConnection, STACKSIZE_S, client, ccode, SMTPConnThreads);
if (ccode == 0) continue;
XplMutexLock(InternalRelayRateLock);
if (client->InternalRate != NULL && client->InternalRate->connections > 0) {
client->InternalRate->connections--;
}
XplMutexUnlock(InternalRelayRateLock);
ReturnSMTPConnection(client);
ConnClose(conn);
ConnFree(conn);
}
XplSafeDecrement(SMTPServerThreads);
Log(LOG_DEBUG, "Internal SMTP listening thread terminated");
}
static void
SMTPServer (void *ignored)
{
Connection *conn;
int ccode;
int arg;
int oldTGID;
ConnectionStruct *client;
XplThreadID id;
UNUSED_PARAMETER(ignored);
XplSafeIncrement (SMTPServerThreads);
XplRenameThread (XplGetThreadID(), "SMTP Server");
while (!Exiting) {
if (ConnAccept(SMTPServerConnection, &conn) != -1) {
if (!Exiting) {
if (!SMTPReceiverStopped) {
if (XplSafeRead(SMTPConnThreads) < SMTP.max_thread_load) {
client = GetSMTPConnection();
if (client) {
client->client.conn = conn;
XplBeginCountedThread(&id, HandleConnection, STACKSIZE_S, client, ccode, SMTPConnThreads);
if (ccode == 0) {
continue;
}
ReturnSMTPConnection(client);
}
/* GetSMTPConnection failed */
ConnSend(conn, MSG500NOMEMORY, MSG500NOMEMORY_LEN);
ConnClose(conn);
ConnFree(conn);
continue;
} else {
/* No more threads available */
ConnSend(conn, MSG451NOCONNECTIONS, MSG451NOCONNECTIONS_LEN);
ConnClose(conn);
ConnFree(conn);
}
} else {
/* RecieverStopped */
ConnSend(conn, MSG451RECEIVERDOWN, MSG451RECEIVERDOWN_LEN);
ConnClose(conn);
ConnFree(conn);
}
}
ConnClose(conn);
ConnFree(conn);
continue;
}
if (!Exiting) {
switch (errno) {
case ECONNABORTED:
#ifdef EPROTO
case EPROTO:
#endif
case EINTR:{
Log(LOG_ERROR, "Accept failure %s Errno %d", "Server", errno);
continue;
}
case EIO:{
Log(LOG_ERROR, "Accept failure Errno %d %d", errno, 2);
Exiting = TRUE;
break;
}
default:{
Log(LOG_ALERT, "Accept failure: Errno %d", errno);
break;
}
}
break;
}
/* Shutdown signaled! */
break;
}
/* Shutting down! */
Exiting = TRUE;
oldTGID = XplSetThreadGroupID (TGid);
UNUSED_PARAMETER(oldTGID);
Log(LOG_DEBUG, "Closing server socket");
if (SMTPServerConnection) {
ConnClose(SMTPServerConnection);
ConnFree(SMTPServerConnection);
SMTPServerConnection = NULL;
}
if (SMTPServerConnectionSSL) {
ConnClose(SMTPServerConnectionSSL);
ConnFree(SMTPServerConnectionSSL);
SMTPServerConnectionSSL = NULL;
}
if (SMTPInternalServerConnection) {
ConnClose(SMTPInternalServerConnection);
ConnFree(SMTPInternalServerConnection);
SMTPInternalServerConnection = NULL;
}
if (SMTPSubmissionServerConnection) {
ConnClose(SMTPSubmissionServerConnection);
ConnFree(SMTPSubmissionServerConnection);
SMTPSubmissionServerConnection = NULL;
}
if (SMTPQServerConnection) {
ConnClose(SMTPQServerConnection);
ConnFree(SMTPQServerConnection);
SMTPQServerConnection = NULL;
}
if (SMTPQServerConnectionSSL) {
ConnClose(SMTPQServerConnectionSSL);
ConnFree(SMTPQServerConnectionSSL);
SMTPQServerConnectionSSL = NULL;
}
/* Wake up the children and set them free! */
/* fixme - SocketShutdown; */
/* Wait for the our siblings to leave quietly. */
for (arg = 0; (XplSafeRead (SMTPServerThreads) > 1) && (arg < 60); arg++) {
XplDelay (1000);
}
if (XplSafeRead (SMTPServerThreads) > 1) {
Log(LOG_ERROR, "%d server threads outstanding; attempting forceful unload.", XplSafeRead(SMTPServerThreads)-1);
}
Log(LOG_DEBUG, "Shutting down %d conn client threads and %d queue client threads.", XplSafeRead(SMTPConnThreads), XplSafeRead(SMTPQueueThreads));
/* Make sure the kids have flown the coop. */
for (arg = 0; (XplSafeRead (SMTPConnThreads) + XplSafeRead (SMTPQueueThreads)) && (arg < 3 * 60); arg++) {
XplDelay (1000);
}
if (XplSafeRead (SMTPConnThreads) + XplSafeRead (SMTPQueueThreads)) {
Log(LOG_ERROR, "%d threads outstanding; attempting forceful unload.", XplSafeRead(SMTPConnThreads)+XplSafeRead(SMTPQueueThreads));
}
Log(LOG_DEBUG, "Removing SSL data");
/* Cleanup SSL */
if (SSLContext) {
ConnSSLContextFree(SSLContext);
SSLContext = NULL;
}
if (InternalSSLContext && InternalSSLContext != SSLContext) {
ConnSSLContextFree(InternalSSLContext);
InternalSSLContext = NULL;
}
g_hash_table_destroy(InternalRelayRates);
InternalRelayRates = NULL;
XplMutexDestroy(InternalRelayRateLock);
LogShutdown ();
XplRWLockDestroy (&ConfigLock);
MsgShutdown ();
ConnShutdown ();
MemPrivatePoolFree (SMTPConnectionPool);
Log(LOG_DEBUG, "Shutdown complete.");
XplSignalLocalSemaphore (SMTPServerSemaphore);
XplWaitOnLocalSemaphore (SMTPShutdownSemaphore);
XplCloseLocalSemaphore (SMTPShutdownSemaphore);
XplCloseLocalSemaphore (SMTPServerSemaphore);
XplSetThreadGroupID (oldTGID);
return;
}
static void
SMTPSSLServer (void *ignored)
{
Connection *conn;
int ccode;
int arg;
char *message;
ConnectionStruct *client;
XplThreadID id = 0;
UNUSED_PARAMETER(ignored);
XplRenameThread (XplGetThreadID (), "SMTP SSL Server");
while(!Exiting) {
if (ConnAccept(SMTPServerConnectionSSL, &conn) != -1) {
conn->ssl.enable = TRUE;
if (!Exiting) {
if (!SMTPReceiverStopped) {
if (XplSafeRead(SMTPConnThreads) < SMTP.max_thread_load) {
client = GetSMTPConnection();
if (client) {
client->client.conn = conn;
client->Submission = SMTP.submission_enabled;
XplBeginCountedThread (&id, HandleConnection, STACKSIZE_S, client, ccode, SMTPConnThreads);
if (ccode == 0) {
continue;
}
ReturnSMTPConnection(client);
ConnClose(conn);
ConnFree(conn);
continue;
} else {
/* GetSMTPConnection() failed */
message = MSG500NOMEMORY;
arg = MSG500NOMEMORY_LEN;
}
} else {
/* no more threads */
message = MSG451NOCONNECTIONS;
arg = MSG451NOCONNECTIONS_LEN;
}
} else {
/* receiver stopped */
message = MSG451RECEIVERDOWN;
arg = MSG451RECEIVERDOWN_LEN;
}
if (ConnNegotiate(conn, SSLContext)) {
if (ConnWrite(conn, message, arg) != -1) {
ConnFlush(conn);
}
ConnClose(conn);
ConnFree(conn);
continue;
}
ConnSend(conn, message, arg);
ConnClose(conn);
ConnFree(conn);
continue;
}
break;
}
if (!Exiting) {
switch (errno) {
case ECONNABORTED:
#ifdef EPROTO
case EPROTO:
#endif
case EINTR:{
Log(LOG_ERROR, "Accept failure %s Errno %d", "SSL Server", errno);
continue;
}
case EIO:{
Log(LOG_ERROR, "Accept failure Errno %d %d", errno, 2);
Exiting = TRUE;
break;
}
default:{
Log(LOG_ALERT, "Accept failure Errno %d", errno);
break;
}
}
break;
}
}
XplSafeDecrement (SMTPServerThreads);
Log(LOG_DEBUG, "SSL Listening thread terminated.");
if (SMTPServerConnectionSSL) {
ConnFree(SMTPServerConnectionSSL);
SMTPServerConnectionSSL = NULL;
}
if (!Exiting) {
raise (SIGTERM);
}
return;
}
#define SetPtrToValue(Ptr,String) Ptr=String;while(isspace(*Ptr)) Ptr++;if ((*Ptr=='=') || (*Ptr==':')) Ptr++; while(isspace(*Ptr)) Ptr++;
static BOOL
ReadConfiguration (void)
{
/*
struct sockaddr_in soc_address;
struct sockaddr_in *sin = &soc_address;
soc_address.sin_addr.s_addr = XplGetHostIPAddress ();
sprintf (SMTP.hostaddr, "[%d.%d.%d.%d]", sin->sin_addr.s_net,
sin->sin_addr.s_host, sin->sin_addr.s_lh, sin->sin_addr.s_impno);
*/
// FIXME: Does SMTP need to know about the various domains? Or will queue fix that?
/* FIXME. This should be replaced by pulling config from the store. */
#if 0
if (MsgReadIP (MSGSRV_AGENT_SMTP, MSGSRV_A_QUEUE_SERVER, Config)) {
strcpy (NMAPServer, Config->Value[0]);
Log(LOG_INFO, "Configuration string %s Value %s",
"MSGSRV_A_QUEUE_SERVER", Config->Value[0]);
}
MDBFreeValues (Config);
#endif
LocalAddress = MsgGetHostIPAddress ();
MsgGetServerCredential(NMAPHash);
return (TRUE);
}
static BOOL
ValidateListenerConfiguration(void)
{
unsigned int index;
if (SMTP.submission_enabled &&
(SMTP.submission_port < 1 || SMTP.submission_port > 65535 ||
SMTP.submission_port_ssl < 1 || SMTP.submission_port_ssl > 65535)) {
Log(LOG_ERROR, "SMTP submission ports must be between 1 and 65535");
return FALSE;
}
if (SMTP.submission_enabled && (!SMTP.allow_auth || !SMTP.submission_require_auth)) {
Log(LOG_ERROR, "SMTP submission must have authentication enabled and required");
return FALSE;
}
if (SMTP.submission_enabled &&
(SMTP.submission_port == SMTP.port ||
SMTP.submission_port_ssl == SMTP.port ||
SMTP.submission_port == SMTP.submission_port_ssl)) {
Log(LOG_ERROR, "SMTP relay and submission ports must be distinct");
return FALSE;
}
if (SMTP.internal_relay_enabled) {
if (SMTP.internal_relay_port < 1 || SMTP.internal_relay_port > 65535) {
Log(LOG_ERROR, "Internal SMTP relay port must be between 1 and 65535");
return FALSE;
}
if (SMTP.internal_relay_port == SMTP.port ||
(SMTP.submission_enabled &&
(SMTP.internal_relay_port == SMTP.submission_port ||
SMTP.internal_relay_port == SMTP.submission_port_ssl))) {
Log(LOG_ERROR, "Internal SMTP relay port conflicts with another SMTP listener");
return FALSE;
}
if (SMTP.internal_relay_bind_address == NULL ||
*SMTP.internal_relay_bind_address == '\0' ||
SMTP.internal_relay_networks == NULL ||
SMTP.internal_relay_networks->len == 0) {
Log(LOG_ERROR, "Internal SMTP relay requires a bind address and at least one allowed source network");
return FALSE;
}
if (SMTP.internal_relay_messages_per_minute < 1 ||
SMTP.internal_relay_recipients_per_minute < 1 ||
SMTP.internal_relay_bytes_per_minute < 1 ||
SMTP.internal_relay_connections_per_ip < 1) {
Log(LOG_ERROR, "Internal SMTP relay limits must be greater than zero");
return FALSE;
}
if (SMTP.internal_relay_domain == NULL ||
*SMTP.internal_relay_domain == '\0') {
Log(LOG_ERROR, "Internal SMTP relay requires a normalization domain");
return FALSE;
}
for (index = 0; SMTP.internal_relay_device_mappings != NULL &&
index < SMTP.internal_relay_device_mappings->len; index++) {
const char *mapping = g_array_index(SMTP.internal_relay_device_mappings,
char *, index);
const char *equals = mapping != NULL ? strchr(mapping, '=') : NULL;
char ip[INET_ADDRSTRLEN];
struct in_addr parsed;
size_t length = equals != NULL ? (size_t) (equals - mapping) : 0;
if (length == 0 || length >= sizeof(ip) || !ValidDeviceName(equals + 1)) {
Log(LOG_ERROR, "Invalid internal SMTP device mapping: %s",
mapping != NULL ? mapping : "(null)");
return FALSE;
}
memcpy(ip, mapping, length);
ip[length] = '\0';
if (inet_pton(AF_INET, ip, &parsed) != 1) {
Log(LOG_ERROR, "Invalid IP address in internal SMTP device mapping: %s", mapping);
return FALSE;
}
}
}
return TRUE;
}
XplServiceCode (SMTPShutdownSigHandler)
int XplServiceMain (int argc, char *argv[])
{
int ccode;
XplThreadID ID;
LogStart();
/* Done binding to ports, drop privs permanently */
if (XplSetEffectiveUser (MsgGetUnprivilegedUser ()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user %s", MsgGetUnprivilegedUser());
return 1;
}
XplInit();
XplMutexInit(InternalRelayRateLock);
InternalRelayRates = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free);
if (InternalRelayRates == NULL) {
Log(LOG_ERROR, "Could not allocate internal SMTP rate-limit table");
return 1;
}
XplSignalHandler (SMTPShutdownSigHandler);
XplSafeWrite (SMTPServerThreads, 0);
XplSafeWrite (SMTPQueueThreads, 0);
XplSafeWrite (SMTPConnThreads, 0);
XplSafeWrite (SMTPIdleConnThreads, 0);
TGid = XplGetThreadGroupID ();
SMTPConnectionPool =
MemPrivatePoolAlloc ("SMTP Connections",
sizeof (ConnectionStruct), 0, 3072, TRUE,
FALSE, SMTPConnectionAllocCB, NULL, NULL);
if (SMTPConnectionPool != NULL) {
XplOpenLocalSemaphore (SMTPServerSemaphore, 0);
XplOpenLocalSemaphore (SMTPShutdownSemaphore, 1);
}
else {
Log(LOG_ERROR, "Unable to create connection pool, shutting down");
return (-1);
}
MsgInit();
MsgAuthInit();
// FIXME: Connio socket timeout needs to be a run-time tunable. bug #9924
// ConnStartup (SMTP.socket_timeout, TRUE);
ConnStartup(600);
NMAPInitialize();
XplRWLockInit (&ConfigLock);
for (ccode = 1; argc && (ccode < argc); ccode++) {
if (XplStrNCaseCmp (argv[ccode], "--forwarder=", 12) == 0) {
SMTP.use_relay = TRUE;
strcpy (SMTP.relay_host, argv[ccode] + 12);
}
}
if (! ReadBongoConfiguration(SMTPConfig, "smtp")) {
Log(LOG_ERROR, "Unable to read SMTP configuration from the store.");
exit(-1);
}
if (! ReadBongoConfiguration(GlobalConfig, "global")) {
Log(LOG_ERROR, "Unable to read Global configration from the store.");
exit(-1);
}
if (!ValidateListenerConfiguration()) {
return 1;
}
ReadConfiguration ();
if (ServerSocketInit () < 0) {
Log(LOG_WARN, "Can't open ports, exiting");
return 1;
}
// XplBeginCountedThread (&ID, QueueServerStartup, STACKSIZE_Q, NULL, ccode, SMTPServerThreads);
if (SMTP.allow_client_ssl || SMTP.submission_enabled) {
if (!ServerSocketSSLInit()) {
ConnSSLConfiguration sslconfig;
memset(&sslconfig, 0, sizeof(sslconfig));
if (SMTP.allow_legacy_tls) {
sslconfig.options |= SSL_ALLOW_LEGACY_PROTOCOLS;
}
sslconfig.key.file = MsgGetFile(MSGAPI_FILE_PRIVKEY, NULL, 0);
sslconfig.certificate.file = MsgGetFile(MSGAPI_FILE_PUBKEY, NULL, 0);
sslconfig.key.type = GNUTLS_X509_FMT_PEM;
SSLContext = ConnSSLContextAlloc(&sslconfig);
if (SSLContext) {
XplBeginCountedThread (&ID, SMTPSSLServer, STACKSIZE_S, NULL, ccode, SMTPServerThreads);
} else {
if (!SMTP.submission_enabled) SMTP.allow_client_ssl = FALSE;
}
} else {
if (!SMTP.submission_enabled) SMTP.allow_client_ssl = FALSE;
}
}
if (SMTP.submission_enabled) {
if (SSLContext == NULL) {
Log(LOG_ERROR, "SMTP submission requires a usable TLS certificate and key");
return 1;
}
if (SubmissionServerSocketInit() < 0) {
Log(LOG_ERROR, "Can't open SMTP submission port %d", SMTP.submission_port);
return 1;
}
XplBeginCountedThread(&ID, SMTPSubmissionServer, STACKSIZE_S, NULL, ccode,
SMTPServerThreads);
if (ccode != 0) return 1;
}
if (SMTP.internal_relay_enabled) {
ConnSSLConfiguration sslconfig;
if (InternalServerSocketInit() < 0) {
Log(LOG_ERROR, "Can't open internal SMTP relay port");
return 1;
}
memset(&sslconfig, 0, sizeof(sslconfig));
if (SMTP.internal_relay_allow_legacy_tls) {
sslconfig.options |= SSL_ALLOW_LEGACY_PROTOCOLS;
}
sslconfig.key.file = MsgGetFile(MSGAPI_FILE_PRIVKEY, NULL, 0);
sslconfig.certificate.file = MsgGetFile(MSGAPI_FILE_PUBKEY, NULL, 0);
sslconfig.key.type = GNUTLS_X509_FMT_PEM;
InternalSSLContext = ConnSSLContextAlloc(&sslconfig);
if (InternalSSLContext == NULL) {
Log(LOG_WARN, "Internal SMTP STARTTLS disabled: TLS context could not be loaded");
}
XplBeginCountedThread(&ID, SMTPInternalServer, STACKSIZE_S, NULL, ccode,
SMTPServerThreads);
if (ccode != 0) {
Log(LOG_ERROR, "Could not start internal SMTP listener thread");
return 1;
}
}
/* Done binding to ports, drop privs permanently */
if (XplSetRealUser (MsgGetUnprivilegedUser ()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user %s", MsgGetUnprivilegedUser());
return 1;
}
XplStartMainThread (PRODUCT_SHORT_NAME, &ID, SMTPServer, 8192, NULL, ccode);
XplUnloadApp (XplGetThreadID ());
return (0);
}