/*++
/* NAME
/* postscreen_smtpd 3
/* SUMMARY
/* postscreen built-in SMTP server engine
/* SYNOPSIS
/* #include <postscreen.h>
/*
/* void psc_smtpd_pre_jail_init(void)
/*
/* void psc_smtpd_init(void)
/*
/* void psc_smtpd_tests(state)
/* PSC_STATE *state;
/*
/* void PSC_SMTPD_X21(state, final_reply)
/* PSC_STATE *state;
/* const char *final_reply;
/* DESCRIPTION
/* psc_smtpd_pre_jail_init() performs one-time per-process
/* initialization during the "before chroot" execution phase.
/*
/* psc_smtpd_init() performs one-time per-process initialization.
/*
/* psc_smtpd_tests() starts up an SMTP server engine for deep
/* protocol tests and for collecting helo/sender/recipient
/* information.
/*
/* PSC_SMTPD_X21() redirects the SMTP client to an SMTP server
/* engine, which sends the specified final reply at the first
/* legitimate opportunity without doing any protocol tests.
/*
/* Unlike the Postfix SMTP server, this engine does not announce
/* PIPELINING support. This exposes spambots that pipeline
/* their commands anyway. Like the Postfix SMTP server, this
/* engine will accept input with bare newline characters. To
/* pass the "pipelining" and "bare newline" test, the client
/* has to properly speak SMTP all the way to the RCPT TO
/* command. These tests fail if the client violates the protocol
/* at any stage.
/*
/* No support is announced for AUTH, XCLIENT or XFORWARD.
/* Clients that need this should be allowlisted or should talk
/* directly to the submission service.
/*
/* The engine rejects RCPT TO and VRFY commands with the
/* state->rcpt_reply response which depends on program history,
/* rejects ETRN with a generic response, and closes the
/* connection after QUIT.
/*
/* Since this engine defers or rejects all non-junk commands,
/* there is no point maintaining separate counters for "error"
/* commands and "junk" commands. Instead, the engine maintains
/* a per-session command counter, and terminates the session
/* with a 421 reply when the command count exceeds the limit.
/*
/* We limit the command count, as well as the total time to
/* receive a command. This limits the time per client more
/* effectively than would be possible with read() timeouts.
/*
/* There is no concern about getting blocked on output. The
/* psc_send() routine uses non-blocking output, and discards
/* output that the client is not willing to receive.
/* PROTOCOL INSPECTION VERSUS CONTENT INSPECTION
/* The goal of postscreen is to keep spambots away from Postfix.
/* To recognize spambots, postscreen measures properties of
/* the client IP address and of the client SMTP protocol
/* implementation. These client properties don't change with
/* each delivery attempt. Therefore it is possible to make a
/* long-term decision after a single measurement. For example,
/* allow a good client to skip the DNSBL test for 24 hours,
/* or to skip the pipelining test for one week.
/*
/* If postscreen were to measure properties of message content
/* (MIME compliance, etc.) then it would measure properties
/* that may change with each delivery attempt. Here, it would
/* be wrong to make a long-term decision after a single
/* measurement. Instead, postscreen would need to develop a
/* ranking based on the content of multiple messages from the
/* same client.
/*
/* Many spambots avoid spamming the same site repeatedly.
/* Thus, postscreen must make decisions after a single
/* measurement. Message content is not a good indicator for
/* making long-term decisions after single measurements, and
/* that is why postscreen does not inspect message content.
/* REJECTING RCPT TO VERSUS SENDING LIVE SOCKETS TO SMTPD(8)
/* When post-handshake protocol tests are enabled, postscreen
/* rejects the RCPT TO command from a good client, and forces
/* it to deliver mail in a later session. This is why
/* post-handshake protocol tests have a longer expiration time
/* than pre-handshake tests.
/*
/* Instead, postscreen could send the network socket to smtpd(8)
/* and ship the session history (including TLS and other SMTP
/* or non-SMTP attributes) as auxiliary data. The Postfix SMTP
/* server would then use new code to replay the session history,
/* and would use existing code to validate the client, helo,
/* sender and recipient address.
/*
/* Such an approach would increase the implementation and
/* maintenance effort, because:
/*
/* 1) New replay code would be needed in smtpd(8), such that
/* the HELO, EHLO, and MAIL command handlers can delay their
/* error responses until the RCPT TO reply.
/*
/* 2) postscreen(8) would have to implement more of smtpd(8)'s
/* syntax checks, to avoid confusing delayed "syntax error"
/* and other error responses syntax error responses while
/* replaying history.
/*
/* 3) New code would be needed in postscreen(8) and smtpd(8)
/* to send and receive the session history (including TLS and
/* other SMTP or non-SMTP attributes) as auxiliary data while
/* sending the network socket from postscreen(8) to smtpd(8).
/* REJECTING RCPT TO VERSUS PROXYING LIVE SESSIONS TO SMTPD(8)
/* An alternative would be to proxy the session history to a
/* real Postfix SMTP process, presumably passing TLS and other
/* attributes via an extended XCLIENT implementation. That
/* would require all the work described in 2) above, plus
/* duplication of all the features of the smtpd(8) TLS engine,
/* plus additional XCLIENT support for a lot more attributes.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <string.h>
#include <ctype.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <stringops.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <vstring.h>
/* Global library. */
#include <mail_params.h>
#include <mail_proto.h>
#include <is_header.h>
#include <string_list.h>
#include <maps.h>
#include <ehlo_mask.h>
#include <lex_822.h>
#include <info_log_addr_form.h>
/* TLS library. */
#include <tls.h>
/* Application-specific. */
#include <postscreen.h>
/*
* Plan for future body processing . See smtp - sink . c . For now , we have no
* per - session push - back except for the single - character push - back that
* VSTREAM guarantees after we read one character .
*/
#define PSC_SMTPD_HAVE_PUSH_BACK(state) (0 )
#define PSC_SMTPD_PUSH_BACK_CHAR(state, ch) \
vstream_ungetc((state)->smtp_client_stream, (ch))
#define PSC_SMTPD_NEXT_CHAR(state) \
VSTREAM_GETC((state)->smtp_client_stream)
#define PSC_SMTPD_BUFFER_EMPTY(state) \
(!PSC_SMTPD_HAVE_PUSH_BACK(state) \
&& vstream_peek((state)->smtp_client_stream) <= 0 )
#define PSC_SMTPD_PEEK_DATA(state) \
vstream_peek_data((state)->smtp_client_stream)
#define PSC_SMTPD_PEEK_LEN(state) \
vstream_peek((state)->smtp_client_stream)
/*
* Dynamic reply strings . To minimize overhead we format these once .
*/
static char *psc_smtpd_greeting; /* smtp banner */
static char *psc_smtpd_helo_reply; /* helo reply */
static char *psc_smtpd_ehlo_reply_plain;/* multi-line ehlo reply, non-TLS */
static char *psc_smtpd_ehlo_reply_tls; /* multi-line ehlo reply, with TLS */
static char *psc_smtpd_timeout_reply; /* timeout reply */
static char *psc_smtpd_421_reply; /* generic final_reply value */
/*
* Forward declaration , needed by PSC_CLEAR_EVENT_REQUEST .
*/
static void psc_smtpd_time_event(int , void *);
static void psc_smtpd_read_event(int , void *);
/*
* Encapsulation . The STARTTLS , EHLO and AUTH command handlers temporarily
* suspend SMTP command events , send an asynchronous proxy request , and
* resume SMTP command events after receiving the asynchronous proxy
* response ( the EHLO handler must asynchronously talk to the auth server
* before it can announce the SASL mechanism list ; the list can depend on
* the client IP address and on the presence on TLS encryption ) .
*/
#define PSC_RESUME_SMTP_CMD_EVENTS(state) do { \
PSC_READ_EVENT_REQUEST2(vstream_fileno((state)->smtp_client_stream), \
psc_smtpd_read_event, psc_smtpd_time_event, \
(void *) (state), PSC_EFF_CMD_TIME_LIMIT); \
if (!PSC_SMTPD_BUFFER_EMPTY(state)) \
psc_smtpd_read_event(EVENT_READ, (void *) state); \
} while (0 )
#define PSC_SUSPEND_SMTP_CMD_EVENTS(state) \
PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
psc_smtpd_time_event, (void *) (state));
/*
* Make control characters and other non - text visible .
*/
#define PSC_SMTPD_ESCAPE_TEXT(dest, src, src_len, max_len) do { \
ssize_t _s_len = (src_len); \
ssize_t _m_len = (max_len); \
(void ) escape((dest), (src), _s_len < _m_len ? _s_len : _m_len); \
} while (0 )
/*
* Command parser support .
*/
#define PSC_SMTPD_NEXT_TOKEN(ptr) mystrtok(&(ptr), " " )
/*
* EHLO keyword filter
*/
static MAPS *psc_ehlo_discard_maps;
static int psc_ehlo_discard_mask;
/*
* Command editing filter .
*/
static DICT *psc_cmd_filter;
/*
* Encapsulation . We must not forget turn off input / timer events when we
* terminate the SMTP protocol engine .
*
* It would be safer to turn off input / timer events after each event , and to
* turn on input / timer events again when we want more input . But experience
* with the Postfix smtp - source and smtp - sink tools shows that this would
* noticeably increase the run - time cost .
*/
#define PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, event, reply) do { \
PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
(event), (void *) (state)); \
PSC_DROP_SESSION_STATE((state), (reply)); \
} while (0 )
#define PSC_CLEAR_EVENT_HANGUP(state, event) do { \
PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
(event), (void *) (state)); \
psc_hangup_event(state); \
} while (0 )
/* psc_helo_cmd - record HELO and respond */
static int psc_helo_cmd(PSC_STATE *state, char *args)
{
char *helo_name = PSC_SMTPD_NEXT_TOKEN(args);
/*
* smtpd ( 8 ) incompatibility : we ignore extra words ; smtpd ( 8 ) saves them .
*/
if (helo_name == 0 )
return (PSC_SEND_REPLY(state, "501 Syntax: HELO hostname\r\n" ));
PSC_STRING_UPDATE(state->helo_name, helo_name);
PSC_STRING_RESET(state->sender);
/* Don't downgrade state->protocol, in case some test depends on this. */
return (PSC_SEND_REPLY(state, psc_smtpd_helo_reply));
}
/* psc_smtpd_format_ehlo_reply - format EHLO response */
static void psc_smtpd_format_ehlo_reply(VSTRING *buf, int discard_mask
/* , const char *sasl_mechanism_list */ )
{
const char *myname = "psc_smtpd_format_ehlo_reply" ;
int saved_len = 0 ;
if (msg_verbose)
msg_info("%s: discard_mask %s" , myname, str_ehlo_mask(discard_mask));
#define PSC_EHLO_APPEND(save, buf, fmt) do { \
(save) = LEN(buf); \
vstring_sprintf_append((buf), (fmt)); \
} while (0 )
#define PSC_EHLO_APPEND1(save, buf, fmt, arg1) do { \
(save) = LEN(buf); \
vstring_sprintf_append((buf), (fmt), (arg1)); \
} while (0 )
vstring_sprintf(psc_temp, "250-%s\r\n" , var_myhostname);
if ((discard_mask & EHLO_MASK_SIZE) == 0 ) {
if (ENFORCING_SIZE_LIMIT(var_message_limit))
PSC_EHLO_APPEND1(saved_len, psc_temp, "250-SIZE %lu\r\n" ,
(unsigned long ) var_message_limit);
else
PSC_EHLO_APPEND(saved_len, psc_temp, "250-SIZE\r\n" );
}
if ((discard_mask & EHLO_MASK_VRFY) == 0 && var_disable_vrfy_cmd == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-VRFY\r\n" );
if ((discard_mask & EHLO_MASK_ETRN) == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-ETRN\r\n" );
if ((discard_mask & EHLO_MASK_STARTTLS) == 0 && var_psc_use_tls)
PSC_EHLO_APPEND(saved_len, psc_temp, "250-STARTTLS\r\n" );
#ifdef TODO_SASL_AUTH
if ((discard_mask & EHLO_MASK_AUTH) == 0 && sasl_mechanism_list
&& (!var_psc_tls_auth_only || (discard_mask & EHLO_MASK_STARTTLS))) {
PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH %s" , sasl_mechanism_list);
if (var_broken_auth_clients)
PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH=%s" , sasl_mechanism_list);
}
#endif
if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-ENHANCEDSTATUSCODES\r\n" );
if ((discard_mask & EHLO_MASK_8BITMIME) == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-8BITMIME\r\n" );
if ((discard_mask & EHLO_MASK_DSN) == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-DSN\r\n" );
/* Fix 20140708: announce SMTPUTF8. */
if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-SMTPUTF8\r\n" );
if ((discard_mask & EHLO_MASK_CHUNKING) == 0 )
PSC_EHLO_APPEND(saved_len, psc_temp, "250-CHUNKING\r\n" );
STR(psc_temp)[saved_len + 3 ] = ' ' ;
}
/* psc_ehlo_cmd - record EHLO and respond */
static int psc_ehlo_cmd(PSC_STATE *state, char *args)
{
char *helo_name = PSC_SMTPD_NEXT_TOKEN(args);
const char *ehlo_words;
int discard_mask;
char *reply;
/*
* smtpd ( 8 ) incompatibility : we ignore extra words ; smtpd ( 8 ) saves them .
*/
if (helo_name == 0 )
return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n" ));
PSC_STRING_UPDATE(state->helo_name, helo_name);
PSC_STRING_RESET(state->sender);
state->protocol = MAIL_PROTO_ESMTP;
/*
* smtpd ( 8 ) compatibility : dynamic reply filtering .
*/
if (psc_ehlo_discard_maps != 0
&& (ehlo_words = psc_maps_find(psc_ehlo_discard_maps,
state->smtp_client_addr, 0 )) != 0
&& (discard_mask = ehlo_mask(ehlo_words)) != psc_ehlo_discard_mask) {
if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
msg_info("[%s]%s: discarding EHLO keywords: %s" ,
PSC_CLIENT_ADDR_PORT(state), str_ehlo_mask(discard_mask));
if (state->flags & PSC_STATE_FLAG_USING_TLS)
discard_mask |= EHLO_MASK_STARTTLS;
psc_smtpd_format_ehlo_reply(psc_temp, discard_mask);
reply = STR(psc_temp);
state->ehlo_discard_mask = discard_mask;
} else if (psc_ehlo_discard_maps && psc_ehlo_discard_maps->error) {
msg_fatal("%s lookup error for %s" ,
psc_ehlo_discard_maps->title, state->smtp_client_addr);
} else if (state->flags & PSC_STATE_FLAG_USING_TLS) {
reply = psc_smtpd_ehlo_reply_tls;
state->ehlo_discard_mask = psc_ehlo_discard_mask | EHLO_MASK_STARTTLS;
} else {
reply = psc_smtpd_ehlo_reply_plain;
state->ehlo_discard_mask = psc_ehlo_discard_mask;
}
return (PSC_SEND_REPLY(state, reply));
}
/* psc_starttls_resume - resume the SMTP protocol after tlsproxy activation */
static void psc_starttls_resume(int unused_event, void *context)
{
const char *myname = "psc_starttls_resume" ;
PSC_STATE *state = (PSC_STATE *) context;
/*
* Reset SMTP server state if STARTTLS was successful .
*/
if (state->flags & PSC_STATE_FLAG_USING_TLS) {
/* Purge the push-back buffer, when implemented. */
PSC_STRING_RESET(state->helo_name);
PSC_STRING_RESET(state->sender);
#ifdef TODO_SASL_AUTH
/* Reset SASL AUTH state. Dovecot responses may change. */
#endif
}
/*
* Resume read / timeout events . If we still have unread input , resume the
* command processor immediately .
*/
PSC_RESUME_SMTP_CMD_EVENTS(state);
}
/* psc_starttls_cmd - activate the tlsproxy server */
static int psc_starttls_cmd(PSC_STATE *state, char *args)
{
const char *myname = "psc_starttls_cmd" ;
/*
* smtpd ( 8 ) incompatibility : we can ' t send a 4 XX reply that TLS is
* unavailable when tlsproxy ( 8 ) detects the problem too late .
*/
if (PSC_SMTPD_NEXT_TOKEN(args) != 0 )
return (PSC_SEND_REPLY(state, "501 5.5.4 Syntax: STARTTLS\r\n" ));
if (state->flags & PSC_STATE_FLAG_USING_TLS)
return (PSC_SEND_REPLY(state,
"554 5.5.1 Error: TLS already active\r\n" ));
if (var_psc_use_tls == 0 || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS))
return (PSC_SEND_REPLY(state,
"502 5.5.1 Error: command not implemented\r\n" ));
/*
* Suspend the SMTP protocol until psc_starttls_resume ( ) is called .
*/
PSC_SUSPEND_SMTP_CMD_EVENTS(state);
psc_starttls_open(state, psc_starttls_resume);
return (0 );
}
/* psc_extract_addr - extract MAIL/RCPT address, unquoted form */
static char *psc_extract_addr(VSTRING *result, const char *string)
{
const unsigned char *cp = (const unsigned char *) string;
char *addr;
char *colon;
int stop_at;
int inquote = 0 ;
/*
* smtpd ( 8 ) incompatibility : we allow more invalid address forms , and we
* don ' t validate recipients . We are not going to deliver them so we
* won ' t have to worry about deliverability . This may have to change when
* we pass the socket to a real SMTP server and replay message envelope
* commands .
*/
/* Skip SP characters. */
while (*cp && *cp == ' ' )
cp++;
/* Choose the terminator for <addr> or bare addr. */
if (*cp == '<' ) {
cp++;
stop_at = '>' ;
} else {
stop_at = ' ' ;
}
/* Skip to terminator or end. */
VSTRING_RESET(result);
for ( /* void */ ; *cp; cp++) {
if (!inquote && *cp == stop_at)
break ;
if (*cp == '"' ) {
inquote = !inquote;
} else {
if (*cp == '\\' && *++cp == 0 )
break ;
VSTRING_ADDCH(result, *cp);
}
}
VSTRING_TERMINATE(result);
/*
* smtpd ( 8 ) compatibility : truncate deprecated route address form . This
* is primarily to simplify logfile analysis .
*/
addr = STR(result);
if (*addr == '@' && (colon = strchr(addr, ':' )) != 0 )
addr = colon + 1 ;
return (addr);
}
/* psc_mail_cmd - record MAIL and respond */
static int psc_mail_cmd(PSC_STATE *state, char *args)
{
char *colon;
char *addr;
/*
* smtpd ( 8 ) incompatibility : we never reject the sender , and we ignore
* additional arguments .
*/
if (var_psc_helo_required && state->helo_name == 0 )
return (PSC_SEND_REPLY(state,
"503 5.5.1 Error: send HELO/EHLO first\r\n" ));
if (state->sender != 0 )
return (PSC_SEND_REPLY(state,
"503 5.5.1 Error: nested MAIL command\r\n" ));
if (args == 0 || (colon = strchr(args, ':' )) == 0 )
return (PSC_SEND_REPLY(state,
"501 5.5.4 Syntax: MAIL FROM:<address>\r\n" ));
if ((addr = psc_extract_addr(psc_temp, colon + 1 )) == 0 )
return (PSC_SEND_REPLY(state,
"501 5.1.7 Bad sender address syntax\r\n" ));
PSC_STRING_UPDATE(state->sender, addr);
return (PSC_SEND_REPLY(state, "250 2.1.0 Ok\r\n" ));
}
/* psc_soften_reply - copy and soft-bounce a reply */
static char *psc_soften_reply(const char *reply)
{
static VSTRING *buf = 0 ;
if (buf == 0 )
buf = vstring_alloc(100 );
vstring_strcpy(buf, reply);
if (reply[0 ] == '5' )
STR(buf)[0 ] = '4' ;
if (reply[4 ] == '5' )
STR(buf)[4 ] = '4' ;
return (STR(buf));
}
/* psc_rcpt_cmd record RCPT and respond */
static int psc_rcpt_cmd(PSC_STATE *state, char *args)
{
char *colon;
char *addr;
/*
* smtpd ( 8 ) incompatibility : we reject all recipients , and ignore
* additional arguments .
*/
if (state->sender == 0 )
return (PSC_SEND_REPLY(state,
"503 5.5.1 Error: need MAIL command\r\n" ));
if (args == 0 || (colon = strchr(args, ':' )) == 0 )
return (PSC_SEND_REPLY(state,
"501 5.5.4 Syntax: RCPT TO:<address>\r\n" ));
if ((addr = psc_extract_addr(psc_temp, colon + 1 )) == 0 )
return (PSC_SEND_REPLY(state,
"501 5.1.3 Bad recipient address syntax\r\n" ));
msg_info("NOQUEUE: reject: RCPT from [%s]:%s: %.*s; "
"from=<%s>, to=<%s>, proto=%s, helo=<%s>" ,
PSC_CLIENT_ADDR_PORT(state),
(int ) strlen(state->rcpt_reply) - 2 ,
var_soft_bounce == 0 ? state->rcpt_reply :
psc_soften_reply(state->rcpt_reply),
info_log_addr_form_sender(state->sender),
info_log_addr_form_recipient(addr), state->protocol,
state->helo_name ? state->helo_name : "" );
return (PSC_SEND_REPLY(state, state->rcpt_reply));
}
/* psc_data_cmd - respond to DATA and disconnect */
static int psc_data_cmd(PSC_STATE *state, char *args)
{
const char myname[] = "psc_data_cmd" ;
/*
* smtpd ( 8 ) incompatibility : postscreen ( 8 ) drops the connection , instead
* of waiting for the next command . Justification : postscreen ( 8 ) should
* never see DATA from a legitimate client , because 1 ) the server rejects
* every recipient , and 2 ) the server does not announce PIPELINING .
*/
msg_info("DATA without valid RCPT from [%s]:%s" ,
PSC_CLIENT_ADDR_PORT(state));
if (PSC_SMTPD_NEXT_TOKEN(args) != 0 )
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"501 5.5.4 Syntax: DATA\r\n" );
else if (state->sender == 0 )
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"503 5.5.1 Error: need RCPT command\r\n" );
else
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"554 5.5.1 Error: no valid recipients\r\n" );
/* Caution: state is now a dangling pointer. */
return (0 );
}
/* psc_bdat_cmd - respond to BDAT and disconnect */
static int psc_bdat_cmd(PSC_STATE *state, char *args)
{
const char *myname = "psc_bdat_cmd" ;
/*
* smtpd ( 8 ) incompatibility : postscreen ( 8 ) drops the connection , instead
* of reading the entire BDAT chunk and staying in sync with the client .
* Justification : postscreen ( 8 ) should never see BDAT from a legitimate
* client , because 1 ) the server rejects every recipient , and 2 ) the
* server does not announce PIPELINING .
*/
msg_info("BDAT without valid RCPT from [%s]:%s" ,
PSC_CLIENT_ADDR_PORT(state));
if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING)
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"502 5.5.1 Error: command not implemented\r\n" );
else if (PSC_SMTPD_NEXT_TOKEN(args) == 0 )
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"501 5.5.4 Syntax: BDAT count [LAST]\r\n" );
else if (state->sender == 0 )
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"554 5.5.1 Error: need MAIL command\r\n" );
else
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"554 5.5.1 Error: no valid recipients\r\n" );
/* Caution: state is now a dangling pointer. */
return (0 );
}
/* psc_rset_cmd - reset, send 250 OK */
static int psc_rset_cmd(PSC_STATE *state, char *unused_args)
{
PSC_STRING_RESET(state->sender);
return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n" ));
}
/* psc_noop_cmd - respond to something */
static int psc_noop_cmd(PSC_STATE *state, char *unused_args)
{
return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n" ));
}
/* psc_vrfy_cmd - respond to VRFY */
static int psc_vrfy_cmd(PSC_STATE *state, char *args)
{
/*
* smtpd ( 8 ) incompatibility : we reject all requests , and ignore
* additional arguments .
*/
if (PSC_SMTPD_NEXT_TOKEN(args) == 0 )
return (PSC_SEND_REPLY(state,
"501 5.5.4 Syntax: VRFY address\r\n" ));
if (var_psc_disable_vrfy)
return (PSC_SEND_REPLY(state,
"502 5.5.1 VRFY command is disabled\r\n" ));
return (PSC_SEND_REPLY(state, state->rcpt_reply));
}
/* psc_etrn_cmd - reset, send 250 OK */
static int psc_etrn_cmd(PSC_STATE *state, char *args)
{
/*
* smtpd ( 8 ) incompatibility : we reject all requests , and ignore
* additional arguments .
*/
if (var_psc_helo_required && state->helo_name == 0 )
return (PSC_SEND_REPLY(state,
"503 5.5.1 Error: send HELO/EHLO first\r\n" ));
if (PSC_SMTPD_NEXT_TOKEN(args) == 0 )
return (PSC_SEND_REPLY(state,
"500 Syntax: ETRN domain\r\n" ));
return (PSC_SEND_REPLY(state, "458 Unable to queue messages\r\n" ));
}
/* psc_quit_cmd - respond to QUIT and disconnect */
static int psc_quit_cmd(PSC_STATE *state, char *unused_args)
{
const char *myname = "psc_quit_cmd" ;
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
"221 2.0.0 Bye\r\n" );
/* Caution: state is now a dangling pointer. */
return (0 );
}
/* psc_smtpd_time_event - handle per-session time limit */
static void psc_smtpd_time_event(int event, void *context)
{
const char *myname = "psc_smtpd_time_event" ;
PSC_STATE *state = (PSC_STATE *) context;
if (msg_verbose > 1 )
msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s" ,
myname, psc_post_queue_length, psc_check_queue_length,
event, vstream_fileno(state->smtp_client_stream),
state->smtp_client_addr, state->smtp_client_port,
psc_print_state_flags(state->flags, myname));
msg_info("COMMAND TIME LIMIT from [%s]:%s after %s" ,
PSC_CLIENT_ADDR_PORT(state), state->where);
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
psc_smtpd_timeout_reply);
}
/*
* The table of all SMTP commands that we know .
*/
typedef struct {
const char *name;
int (*action) (PSC_STATE *, char *);
int flags; /* see below */
} PSC_SMTPD_COMMAND;
#define PSC_SMTPD_CMD_FLAG_NONE (0 ) /* no flags (i.e. disabled) */
#define PSC_SMTPD_CMD_FLAG_ENABLE (1 <<0 ) /* command is enabled */
#define PSC_SMTPD_CMD_FLAG_DESTROY (1 <<1 ) /* dangling pointer alert */
#define PSC_SMTPD_CMD_FLAG_PRE_TLS (1 <<2 ) /* allowed with mandatory TLS */
#define PSC_SMTPD_CMD_FLAG_SUSPEND (1 <<3 ) /* suspend command engine */
#define PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD (1 <<4 ) /* command has payload */
static const PSC_SMTPD_COMMAND command_table[] = {
"HELO" , psc_helo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
"EHLO" , psc_ehlo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
"STARTTLS" , psc_starttls_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS | PSC_SMTPD_CMD_FLAG_SUSPEND,
"XCLIENT" , psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
"XFORWARD" , psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
"AUTH" , psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
"MAIL" , psc_mail_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"RCPT" , psc_rcpt_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"DATA" , psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY,
/* ".", psc_dot_cmd, PSC_SMTPD_CMD_FLAG_NONE, */
"BDAT" , psc_bdat_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD,
"RSET" , psc_rset_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"NOOP" , psc_noop_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
"VRFY" , psc_vrfy_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"ETRN" , psc_etrn_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"QUIT" , psc_quit_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_PRE_TLS,
0 ,
};
/* psc_smtpd_read_event - pseudo responder */
static void psc_smtpd_read_event(int event, void *context)
{
const char *myname = "psc_smtpd_read_event" ;
PSC_STATE *state = (PSC_STATE *) context;
time_t *expire_time = state->client_info->expire_time;
int ch;
struct cmd_trans {
int state;
int want;
int next_state;
};
const char *saved_where;
#define PSC_SMTPD_CMD_ST_ANY 0
#define PSC_SMTPD_CMD_ST_CR 1
#define PSC_SMTPD_CMD_ST_CR_LF 2
static const struct cmd_trans cmd_trans[] = {
PSC_SMTPD_CMD_ST_ANY, '\r' , PSC_SMTPD_CMD_ST_CR,
PSC_SMTPD_CMD_ST_CR, '\n' , PSC_SMTPD_CMD_ST_CR_LF,
0 , 0 , 0 ,
};
const struct cmd_trans *transp;
char *cmd_buffer_ptr;
char *command;
const PSC_SMTPD_COMMAND *cmdp;
int write_stat;
if (msg_verbose > 1 )
msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s" ,
myname, psc_post_queue_length, psc_check_queue_length,
event, vstream_fileno(state->smtp_client_stream),
state->smtp_client_addr, state->smtp_client_port,
psc_print_state_flags(state->flags, myname));
/*
* Basic liveness requirements .
*
* Drain all input in the VSTREAM buffer , otherwise this socket will not
* receive further read event notification until the client disconnects !
*
* To suspend this loop temporarily before the buffer is drained , use the
* PSC_SUSPEND_SMTP_CMD_EVENTS ( ) and PSC_RESUME_SMTP_CMD_EVENTS ( ) macros ,
* and set the PSC_SMTPD_CMD_FLAG_SUSPEND flag in the command table .
*
* Don ' t try to read input before it has arrived , otherwise we would starve
* the pseudo threads of other sessions . Get out of here as soon as the
* VSTREAM read buffer dries up . Do not look for more input in kernel
* buffers . That input wasn ' t likely there when psc_smtpd_read_event ( )
* was called . Also , yielding the pseudo thread will improve fairness for
* other pseudo threads .
*/
/*
* Note : on entry into this function the VSTREAM buffer may or may not be
* empty , so we test the " no more input " condition at the bottom of the
* loops .
*/
for (;;) {
/*
* Read one command line , possibly one fragment at a time .
*/
for (;;) {
if ((ch = PSC_SMTPD_NEXT_CHAR(state)) == VSTREAM_EOF) {
PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event);
return ;
}
/*
* Sanity check . We don ' t want to store infinitely long commands .
*/
if (state->read_state == PSC_SMTPD_CMD_ST_ANY
&& VSTRING_LEN(state->cmd_buffer) >= var_line_limit) {
msg_info("COMMAND LENGTH LIMIT from [%s]:%s after %s" ,
PSC_CLIENT_ADDR_PORT(state), state->where);
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
psc_smtpd_421_reply);
return ;
}
VSTRING_ADDCH(state->cmd_buffer, ch);
/*
* Try to match the current character desired by the state
* machine . If that fails , try to restart the machine with a
* match for its first state . Like smtpd ( 8 ) , we understand lines
* ending in < CR > < LF > and bare < LF > . Unlike smtpd ( 8 ) , we may
* treat lines ending in bare < LF > as an offense .
*/
for (transp = cmd_trans; transp->state != state->read_state; transp++)
if (transp->want == 0 )
msg_panic("%s: command_read: unknown state: %d" ,
myname, state->read_state);
if (ch == transp->want)
state->read_state = transp->next_state;
else if (ch == cmd_trans[0 ].want)
state->read_state = cmd_trans[0 ].next_state;
else
state->read_state = PSC_SMTPD_CMD_ST_ANY;
if (state->read_state == PSC_SMTPD_CMD_ST_CR_LF) {
vstring_truncate(state->cmd_buffer,
VSTRING_LEN(state->cmd_buffer) - 2 );
break ;
}
/*
* Bare newline test . Note : at this point , state - > cmd_buffer is
* not null - terminated and may contain embedded null bytes .
*/
if (ch == '\n' ) {
if ((state->flags & PSC_STATE_MASK_BARLF_TODO_SKIP)
== PSC_STATE_FLAG_BARLF_TODO) {
PSC_SMTPD_ESCAPE_TEXT(psc_temp, STR(state->cmd_buffer),
VSTRING_LEN(state->cmd_buffer) - 1 , 100 );
msg_info("BARE NEWLINE from [%s]:%s after %s" ,
PSC_CLIENT_ADDR_PORT(state), STR(psc_temp));
PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_FAIL);
PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_PASS);
expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED; /* XXX */
/* Skip this test for the remainder of this session. */
PSC_SKIP_SESSION_STATE(state, "bare newline test" ,
PSC_STATE_FLAG_BARLF_SKIP);
switch (psc_barlf_action) {
case PSC_ACT_DROP:
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"521 5.5.1 Protocol error\r\n" );
return ;
case PSC_ACT_ENFORCE:
PSC_ENFORCE_SESSION_STATE(state,
"550 5.5.1 Protocol error\r\n" );
break ;
case PSC_ACT_IGNORE:
PSC_UNFAIL_SESSION_STATE(state,
PSC_STATE_FLAG_BARLF_FAIL);
/* Temporarily allowlist until something expires. */
PSC_PASS_SESSION_STATE(state, "bare newline test" ,
PSC_STATE_FLAG_BARLF_PASS);
expire_time[PSC_TINDX_BARLF] = event_time() + psc_min_ttl;
break ;
default :
msg_panic("%s: unknown bare_newline action value %d" ,
myname, psc_barlf_action);
}
}
vstring_truncate(state->cmd_buffer,
VSTRING_LEN(state->cmd_buffer) - 1 );
break ;
}
/*
* Yield this pseudo thread when the VSTREAM buffer is empty in
* the middle of a command .
*
* XXX Do not reset the read timeout . The entire command must be
* received within the time limit .
*/
if (PSC_SMTPD_BUFFER_EMPTY(state))
return ;
}
/*
* Avoid complaints from Postfix maps about malformed content . Note :
* this will stop at the first null byte , just like the code that
* parses the command name or command arguments .
*/
#define PSC_BAD_UTF8(str) \
(var_smtputf8_enable && !valid_utf8_stringz(str))
/*
* Terminate the command buffer , and apply the last - resort command
* editing workaround .
*/
VSTRING_TERMINATE(state->cmd_buffer);
if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer))) {
const char *cp;
for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++)
/* void */ ;
if ((cp = psc_dict_get(psc_cmd_filter, cp)) != 0 ) {
msg_info("[%s]:%s: replacing command \" %.100 s\" with \" %.100 s\"" ,
state->smtp_client_addr, state->smtp_client_port,
STR(state->cmd_buffer), cp);
vstring_strcpy(state->cmd_buffer, cp);
} else if (psc_cmd_filter->error != 0 ) {
msg_fatal("%s:%s lookup error for \" %.100 s\"" ,
psc_cmd_filter->type, psc_cmd_filter->name,
STR(state->cmd_buffer));
}
}
/*
* Reset the command buffer write pointer and state machine in
* preparation for the next command . For this to work as expected ,
* VSTRING_RESET ( ) must be non - destructive . We just can ' t ask for the
* VSTRING_LEN ( ) and vstring_end ( ) results .
*/
state->read_state = PSC_SMTPD_CMD_ST_ANY;
VSTRING_RESET(state->cmd_buffer);
/*
* Process the command line .
*
* Caution : some command handlers terminate the session and destroy the
* session state structure . When this happens we must leave the SMTP
* engine to avoid a dangling pointer problem .
*/
cmd_buffer_ptr = STR(state->cmd_buffer);
if (msg_verbose)
msg_info("< [%s]:%s: %s" , state->smtp_client_addr,
state->smtp_client_port, cmd_buffer_ptr);
/* Parse the command name. */
if ((command = PSC_SMTPD_NEXT_TOKEN(cmd_buffer_ptr)) == 0 )
command = "" ;
/*
* The non - SMTP , PIPELINING and command COUNT tests depend on the
* client command handler .
*
* Caution : cmdp - > name and cmdp - > action may be null on loop exit .
*/
saved_where = state->where;
state->where = PSC_SMTPD_CMD_UNIMPL;
for (cmdp = command_table; cmdp->name != 0 ; cmdp++) {
if (strcasecmp(command, cmdp->name) == 0 ) {
state->where = cmdp->name;
break ;
}
}
if ((state->flags & PSC_STATE_FLAG_SMTPD_X21)
&& cmdp->action != psc_quit_cmd) {
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
state->final_reply);
return ;
}
/* Non-SMTP command test. */
if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP)
== PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0
&& (is_header(command)
|| PSC_BAD_UTF8(command)
/* Ignore forbid_cmds lookup errors. Non-critical feature. */
|| (*var_psc_forbid_cmds
&& string_list_match(psc_forbid_cmds, command)))) {
printable(command, '?' );
PSC_SMTPD_ESCAPE_TEXT(psc_temp, cmd_buffer_ptr,
strlen(cmd_buffer_ptr), 100 );
msg_info("NON-SMTP COMMAND from [%s]:%s after %s: %.100s %s" ,
PSC_CLIENT_ADDR_PORT(state), saved_where,
command, STR(psc_temp));
PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_FAIL);
PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_PASS);
expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; /* XXX */
/* Skip this test for the remainder of this SMTP session. */
PSC_SKIP_SESSION_STATE(state, "non-smtp test" ,
PSC_STATE_FLAG_NSMTP_SKIP);
switch (psc_nsmtp_action) {
case PSC_ACT_DROP:
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"521 5.7.0 Error: I can break rules, too. Goodbye.\r\n" );
return ;
case PSC_ACT_ENFORCE:
PSC_ENFORCE_SESSION_STATE(state,
"550 5.5.1 Protocol error\r\n" );
break ;
case PSC_ACT_IGNORE:
PSC_UNFAIL_SESSION_STATE(state,
PSC_STATE_FLAG_NSMTP_FAIL);
/* Temporarily allowlist until something else expires. */
PSC_PASS_SESSION_STATE(state, "non-smtp test" ,
PSC_STATE_FLAG_NSMTP_PASS);
expire_time[PSC_TINDX_NSMTP] = event_time() + psc_min_ttl;
break ;
default :
msg_panic("%s: unknown non_smtp_command action value %d" ,
myname, psc_nsmtp_action);
}
}
/* Command PIPELINING test. */
if ((cmdp->flags & PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD) == 0
&& (state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP)
== PSC_STATE_FLAG_PIPEL_TODO && !PSC_SMTPD_BUFFER_EMPTY(state)) {
printable(command, '?' );
PSC_SMTPD_ESCAPE_TEXT(psc_temp, PSC_SMTPD_PEEK_DATA(state),
PSC_SMTPD_PEEK_LEN(state), 100 );
msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s" ,
PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp));
PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL);
PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS);
expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; /* XXX */
/* Skip this test for the remainder of this SMTP session. */
PSC_SKIP_SESSION_STATE(state, "pipelining test" ,
PSC_STATE_FLAG_PIPEL_SKIP);
switch (psc_pipel_action) {
case PSC_ACT_DROP:
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
psc_smtpd_time_event,
"521 5.5.1 Protocol error\r\n" );
return ;
case PSC_ACT_ENFORCE:
PSC_ENFORCE_SESSION_STATE(state,
"550 5.5.1 Protocol error\r\n" );
break ;
case PSC_ACT_IGNORE:
PSC_UNFAIL_SESSION_STATE(state,
PSC_STATE_FLAG_PIPEL_FAIL);
/* Temporarily allowlist until something else expires. */
PSC_PASS_SESSION_STATE(state, "pipelining test" ,
PSC_STATE_FLAG_PIPEL_PASS);
expire_time[PSC_TINDX_PIPEL] = event_time() + psc_min_ttl;
break ;
default :
msg_panic("%s: unknown pipelining action value %d" ,
myname, psc_pipel_action);
}
}
/*
* The following tests don ' t pass until the client gets all the way
* to the RCPT TO command . However , the client can still fail these
* tests with some later command .
*/
if (cmdp->action == psc_rcpt_cmd) {
if ((state->flags & PSC_STATE_MASK_BARLF_TODO_PASS_FAIL)
== PSC_STATE_FLAG_BARLF_TODO) {
PSC_PASS_SESSION_STATE(state, "bare newline test" ,
PSC_STATE_FLAG_BARLF_PASS);
/* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
expire_time[PSC_TINDX_BARLF] = event_time() + var_psc_barlf_ttl;
}
if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL)
== PSC_STATE_FLAG_NSMTP_TODO) {
PSC_PASS_SESSION_STATE(state, "non-smtp test" ,
PSC_STATE_FLAG_NSMTP_PASS);
/* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
expire_time[PSC_TINDX_NSMTP] = event_time() + var_psc_nsmtp_ttl;
}
if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL)
== PSC_STATE_FLAG_PIPEL_TODO) {
PSC_PASS_SESSION_STATE(state, "pipelining test" ,
PSC_STATE_FLAG_PIPEL_PASS);
/* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */
expire_time[PSC_TINDX_PIPEL] = event_time() + var_psc_pipel_ttl;
}
}
/* Command COUNT limit test. */
if (++state->command_count > var_psc_cmd_count
&& cmdp->action != psc_quit_cmd) {
msg_info("COMMAND COUNT LIMIT from [%s]:%s after %s" ,
PSC_CLIENT_ADDR_PORT(state), saved_where);
PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event,
psc_smtpd_421_reply);
return ;
}
/* Finally, execute the command. */
if (cmdp->name == 0 || (cmdp->flags & PSC_SMTPD_CMD_FLAG_ENABLE) == 0 ) {
write_stat = PSC_SEND_REPLY(state,
"502 5.5.2 Error: command not recognized\r\n" );
} else if (var_psc_enforce_tls
&& (state->flags & PSC_STATE_FLAG_USING_TLS) == 0
&& (cmdp->flags & PSC_SMTPD_CMD_FLAG_PRE_TLS) == 0 ) {
write_stat = PSC_SEND_REPLY(state,
"530 5.7.0 Must issue a STARTTLS command first\r\n" );
} else {
write_stat = cmdp->action(state, cmd_buffer_ptr);
if (cmdp->flags & PSC_SMTPD_CMD_FLAG_DESTROY)
return ;
}
/*
* Terminate the session after a write error .
*/
if (write_stat < 0 ) {
PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event);
return ;
}
/*
* We ' re suspended , waiting for some external event to happen .
* Hopefully , someone will call us back to process the remainder of
* the pending input , otherwise we could hang .
*/
if (cmdp->flags & PSC_SMTPD_CMD_FLAG_SUSPEND)
return ;
/*
* Reset the command read timeout before reading the next command .
*/
event_request_timer(psc_smtpd_time_event, (void *) state,
PSC_EFF_CMD_TIME_LIMIT);
/*
* Yield this pseudo thread when the VSTREAM buffer is empty .
*/
if (PSC_SMTPD_BUFFER_EMPTY(state))
return ;
}
}
/* psc_smtpd_tests - per-session deep protocol test initialization */
void psc_smtpd_tests(PSC_STATE *state)
{
static char *myname = "psc_smtpd_tests" ;
/*
* Report errors and progress in the context of this test .
*/
PSC_BEGIN_TESTS(state, "tests after SMTP handshake" );
/*
* Initialize per - session state that is used only by the dummy engine :
* the command read buffer and the command read state machine .
*/
state->cmd_buffer = vstring_alloc(100 );
state->read_state = PSC_SMTPD_CMD_ST_ANY;
/*
* Disable all after - 220 tests when we need to reply with 421 and hang up
* after reading the next SMTP client command .
*
* Opportunistically make postscreen more useful , by turning on all
* after - 220 tests when a bad client failed a before - 220 test .
*
* Otherwise , only apply the explicitly - configured after - 220 tests .
*/
if (state->flags & PSC_STATE_FLAG_SMTPD_X21) {
state->flags &= ~PSC_STATE_MASK_SMTPD_TODO;
} else if (state->flags & PSC_STATE_MASK_ANY_FAIL) {
state->flags |= PSC_STATE_MASK_SMTPD_TODO;
}
/*
* Send no SMTP banner to pregreeting clients . This eliminates a lot of
* " NON - SMTP COMMAND " events , and improves sender / recipient logging .
*/
if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL) == 0
&& PSC_SEND_REPLY(state, psc_smtpd_greeting) != 0 ) {
psc_hangup_event(state);
return ;
}
/*
* Wait for the client to respond .
*/
PSC_READ_EVENT_REQUEST2(vstream_fileno(state->smtp_client_stream),
psc_smtpd_read_event, psc_smtpd_time_event,
(void *) state, PSC_EFF_CMD_TIME_LIMIT);
}
/* psc_smtpd_init - per-process deep protocol test initialization */
void psc_smtpd_init(void )
{
/*
* Initialize the server banner .
*/
vstring_sprintf(psc_temp, "220 %s\r\n" , var_smtpd_banner);
psc_smtpd_greeting = mystrdup(STR(psc_temp));
/*
* Initialize the HELO reply .
*/
vstring_sprintf(psc_temp, "250 %s\r\n" , var_myhostname);
psc_smtpd_helo_reply = mystrdup(STR(psc_temp));
/*
* STARTTLS support . Note the complete absence of # ifdef USE_TLS
* throughout the postscreen ( 8 ) source code . If Postfix is built without
* TLS support , then the TLS proxy will simply report that TLS is not
* available , and conventional error handling will take care of the
* issue .
*
* Legacy code copied from smtpd ( 8 ) . The pre - fabricated EHLO reply depends
* on this .
*/
if (*var_psc_tls_level) {
switch (tls_level_lookup(var_psc_tls_level)) {
default :
msg_fatal("Invalid TLS level \" %s\"" , var_psc_tls_level);
/* NOTREACHED */
break ;
case TLS_LEV_SECURE:
case TLS_LEV_VERIFY:
case TLS_LEV_FPRINT:
msg_warn("%s: unsupported TLS level \" %s\", using \" encrypt\"" ,
VAR_PSC_TLS_LEVEL, var_psc_tls_level);
/* FALLTHROUGH */
case TLS_LEV_ENCRYPT:
var_psc_enforce_tls = var_psc_use_tls = 1 ;
break ;
case TLS_LEV_MAY:
var_psc_enforce_tls = 0 ;
var_psc_use_tls = 1 ;
break ;
case TLS_LEV_NONE:
var_psc_enforce_tls = var_psc_use_tls = 0 ;
break ;
}
}
var_psc_use_tls = var_psc_use_tls || var_psc_enforce_tls;
#ifdef TODO_SASL_AUTH
var_psc_tls_auth_only = var_psc_tls_auth_only || var_psc_enforce_tls;
#endif
/*
* Initialize the EHLO reply . Once for plaintext sessions , and once for
* TLS sessions .
*/
psc_smtpd_format_ehlo_reply(psc_temp, psc_ehlo_discard_mask);
psc_smtpd_ehlo_reply_plain = mystrdup(STR(psc_temp));
psc_smtpd_format_ehlo_reply(psc_temp,
psc_ehlo_discard_mask | EHLO_MASK_STARTTLS);
psc_smtpd_ehlo_reply_tls = mystrdup(STR(psc_temp));
/*
* Initialize the 421 timeout reply .
*/
vstring_sprintf(psc_temp, "421 4.4.2 %s Error: timeout exceeded\r\n" ,
var_myhostname);
psc_smtpd_timeout_reply = mystrdup(STR(psc_temp));
/*
* Initialize the generic 421 reply .
*/
vstring_sprintf(psc_temp, "421 %s Service unavailable - try again later\r\n" ,
var_myhostname);
psc_smtpd_421_reply = mystrdup(STR(psc_temp));
/*
* Initialize the reply footer .
*/
if (*var_psc_rej_footer || *var_psc_rej_ftr_maps)
psc_expand_init();
}
/* psc_smtpd_pre_jail_init - per-process deep protocol test initialization */
void psc_smtpd_pre_jail_init(void )
{
/*
* Determine what server ESMTP features to suppress , typically to avoid
* inter - operability problems . We do the default filter here , and
* determine client - dependent filtering on the fly .
*
* XXX Bugger . This means we have to restart when the table changes !
*/
if (*var_psc_ehlo_dis_maps)
psc_ehlo_discard_maps = maps_create(VAR_PSC_EHLO_DIS_MAPS,
var_psc_ehlo_dis_maps,
DICT_FLAG_LOCK);
psc_ehlo_discard_mask = ehlo_mask(var_psc_ehlo_dis_words);
/*
* Last - resort command editing support .
*/
if (*var_psc_cmd_filter)
psc_cmd_filter = dict_open(var_psc_cmd_filter, O_RDONLY,
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
/*
* SMTP server reply footer .
*/
if (*var_psc_rej_ftr_maps)
pcs_send_pre_jail_init();
}
Messung V0.5 in Prozent C=87 H=98 G=92
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland