/*
* Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: smtpsrv.c,v 1.201 2007/11/02 04:54:59 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/ctype.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/ioctl.h"
#include "sm/filio.h"
#include "sm/str.h"
#include "sm/string.h"
#include "sm/time.h"
#include "sm/limits.h"
#include "sm/types.h"
#include "sm/net.h"
#include "sm/cdb.h"
#include "statethreads/st.h"
#define MTA_USE_STATETHREADS 1
#include "sm/stsock.h"
#include "sm/util.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#include "sm/sasl.h"
#include "sm/rfc2821.h"
#include "sm/misc.h"
#include "s2q.h"
#include "smtps.h"
#include "s2m.h"
#include "smtpsrv.h"
#include "smtpsh.h"
#include "sm/smar.h"
#include "log.h"
#include "sm/resource.h"
#include "sm/version.h"
#if MTA_USE_PMILTER
# include "pmilter.h"
#endif
#define SMTPS_R_IS_OK(ret) ((ret) == SM_SUCCESS)
#define NO_RCPTS(ss_ta) (0 == (ss_ta)->ssta_rcpts_ok)
#define SS_DATA_GOT_IT(ss_sess, ss_ta) do { \
sm_str_scopy(ss_sess->ssse_wr, "250 2.0.0 got it id="); \
sm_str_scat(ss_sess->ssse_wr, ss_ta->ssta_id); \
sm_str_scat(ss_sess->ssse_wr, "\r\n"); \
} while (0)
#ifndef MTA_GREETING
# define MTA_GREETING MTA_VERSION_STR
#endif
#define SM_DONT_LOG 99 /* value beyond "reasonable" loglevel */
/*
** SS_CONN_LOG -- log connection information
**
** Parameters:
** ss_sess -- SMTP server session context
** level -- log level to use
**
** Returns:
** SM_SUCCESS
*/
#define SS_CONN_LOG(ss_sess, level, where) \
do { \
if (!SSSE_IS_FLAG(ss_sess, SSSE_FL_CONN_LOGGED)) \
ss_conn_log(ss_sess, level, where); \
} while (0)
static sm_ret_T
ss_conn_log(ss_sess_P ss_sess, uint level, const char *where)
{
ss_ctx_P ss_ctx;
SM_IS_SS_SESS(ss_sess);
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_CONN_LOGGED))
return SM_SUCCESS;
ss_ctx = ss_sess->ssse_sctx;
SM_IS_SS_CTX(ss_ctx);
#if SS_STATS
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, level,
"sev=INFO, func=%s, ss_sess=%s, client_ipv4=%A, client_name=%C, connect=%ld"
, where, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
, ss_sess->ssse_cltname
, (long) ss_sess->ssse_connect);
#else /* SS_STATS */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, level,
"sev=INFO, func=%s, ss_sess=%s, client_ipv4=%A, client_name=%C"
, where, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
, ss_sess->ssse_cltname);
#endif /* SS_STATS */
if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER,
level))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
return SM_SUCCESS;
}
/*
** macros for ss_crt_reply() phase
*/
#define SS_PHASE_OTHER 0 /* no special treatment */
#define SS_PHASE_INIT 1 /* initial 220 greeting */
#if SS_EHLO_ACCESS_CHK
#define SS_PHASE_EHLO 2 /* EHLO response */
#endif
#define SS_PHASE_TLS 3 /* STARTTLS response */
#define SS_PHASE_AUTH 4 /* AUTH response */
#define SS_PHASE_MAIL 5 /* MAIL response */
#define SS_PHASE_RCPT 6 /* RCPT response */
#define SS_PHASE_DATA 7 /* DATA response */
#define SS_PHASE_DOT 8 /* response to final dot */
#define SS_PHASE_QUIT 9 /* QUIT */
#define SS_PHASE_NOREPLY 10 /* no reply requested */
/*
** SS_CRT_REPLY -- create SMTP reply text based on error code.
** ToDo: add a "phase" parameter, e.g., RCPT, so REJECT can be changed to
** 550 5.1.1 User unknown
**
** Parameters:
** reply -- str for SMTP reply text (output)
** ret -- error code to transform
** phase -- SMTP phase
** new -- clear reply before writing something into it?
**
** Returns:
** usual return code
**
** Note:
** If there's no matching error text for an SMTP reply code,
** check the reply type and return a generic error text.
** This overrides the reply code... hence every function that
** generates an error should either use a defined value or its own
** error text.
*/
sm_ret_T
ss_crt_reply(sm_str_P reply, sm_ret_T ret, int phase, bool new)
{
SM_REQUIRE(reply != NULL);
/* use existing string if it has an SMTP reply code */
if (!new && sm_str_getlen(reply) > 3 && IS_SMTP_REPLY_STR(reply, 0))
return SM_SUCCESS;
sm_str_clr(reply);
switch (ret) {
case SM_SUCCESS:
switch (phase) {
case SS_PHASE_MAIL:
return sm_str_scopy(reply, "250 2.1.0 Sender ok\r\n");
case SS_PHASE_RCPT:
return sm_str_scopy(reply, "250 2.1.5 Recipient ok\r\n");
default:
return sm_str_scopy(reply, SS_R_OK);
}
#if 0
case SMTP_R_NULL_SERVER:
if (ss_sess->ssse_acc.ssa_reply_text == NULL)
return sm_str_scopy(ss_sess->ssse_wr, "550 5.5.0 No\r\n");
else
return sm_str_cpy(ss_sess->ssse_wr, ss_sess->ssse_acc.ssa_reply_text);
#endif /* 0 */
case SMTP_R_REJECT:
switch (phase) {
#if SS_EHLO_ACCESS_CHK
case SS_PHASE_EHLO:
return sm_str_scopy(reply, "550 5.7.1 Not now.\r\n");
#endif
case SS_PHASE_MAIL:
return sm_str_scopy(reply, "550 5.1.8 Sender rejected.\r\n");
case SS_PHASE_RCPT:
return sm_str_scopy(reply, "550 5.1.1 Recipient rejected.\r\n");
default:
return sm_str_scopy(reply, SS_R_REJ);
}
case SMTP_R_SYNTAX:
switch (phase) {
case SS_PHASE_MAIL:
return sm_str_scopy(reply, "550 5.1.7 Syntax error.\r\n");
case SS_PHASE_RCPT:
return sm_str_scopy(reply, "550 5.1.3 Syntax error.\r\n");
default:
return sm_str_scopy(reply, SS_R_SYN_PAR);
}
case SMTP_R_TEMP:
return sm_str_scopy(reply, SS_R_TMP);
case SMTP_R_SSD:
return sm_str_scopy(reply, SS_R_SSD);
}
switch (smtp_reply_type(ret)) {
case SMTP_RTYPE_OK:
return sm_str_scopy(reply, SS_R_OK);
case SMTP_RTYPE_CONT:
return sm_str_scopy(reply, SS_R_CONT);
case SMTP_RTYPE_TEMP:
return sm_str_scopy(reply, SS_R_TMP);
case SMTP_RTYPE_PERM:
return sm_str_scopy(reply, SS_R_REJ);
}
return sm_str_scopy(reply, SS_R_OK);
}
/*
** SS_REPLY -- send an SMTP reply
**
** Parameters:
** ss_sess -- SMTPS session
** str -- text to send
** fp -- file to use
** loglevel -- use for logging
** flushit -- invoke flush()?
**
** Returns:
** usual return code
*/
/* read/write error */
#define SMTP_RD_ERR sm_error_temp(SM_EM_SMTPS, SM_E_RD)
#define SMTP_WR_ERR sm_error_temp(SM_EM_SMTPS, SM_E_WR)
static sm_ret_T
ss_reply(ss_sess_P ss_sess, sm_str_P str, sm_file_T *fp, int loglevel, bool flushit)
{
ssize_t r;
sm_ret_T ret;
if (ss_sess->ssse_sctx->ssc_cnf.ss_cnf_debug > 3) {
sm_io_fprintf(smioerr, "send: ");
sm_io_write(smioerr, sm_str_getdata(str), sm_str_getlen(str), &r);
sm_io_flush(smioerr);
}
/* softbounce? replace "5xy 5.a.b" -> "4xy 4.a.b" */
if (SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_SOFTBOUNCE) &&
sm_str_getlen(str) > 3 &&
sm_str_rd_elem(str, 0) == '5')
{
sm_str_wr_elem(str, 0, (uchar) '4');
if (sm_str_getlen(str) > 10 && sm_str_rd_elem(str, 4) == '5')
sm_str_wr_elem(str, 4, (uchar) '4');
}
ret = sm_io_write(fp, sm_str_getdata(str), sm_str_getlen(str), &r);
if (r != sm_str_getlen(str)) {
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, (loglevel >= 0) ? loglevel : 5,
"sev=WARN, func=ss_reply, ss_sess=%s, write=error, n=%d, r=%d, ret=%m"
, ss_sess->ssse_id, sm_str_getlen(str), (int) r, ret);
if (sm_is_err(ret))
return ret;
return SMTP_WR_ERR;
}
if (flushit) {
ret = sm_io_flush(fp);
if (sm_is_err(ret)) {
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, (loglevel >= 0) ? loglevel : 5,
"sev=WARN, func=ss_reply, ss_sess=%s, flush=%m"
, ss_sess->ssse_id, ret);
return ret;
}
}
return SMTP_OK;
}
/*
** SS_READ_CMD -- read an SMTP command (into ssse_rd, \r\n not in ssse_rd)
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_read_cmd(ss_sess_P ss_sess)
{
ssize_t r;
sm_ret_T ret;
sm_str_clr(ss_sess->ssse_rd);
#if 0
/* Switch to read mode? Not really necessary */
sm_iotord(ss_sess->ssse_fp);
#endif
errno = 0;
ret = sm_fgetline0(ss_sess->ssse_fp, ss_sess->ssse_rd);
if (ss_sess->ssse_sctx->ssc_cnf.ss_cnf_debug > 3) {
sm_io_fprintf(smioerr, "rcvd [len=%d, ret=%r]: "
, sm_str_getlen(ss_sess->ssse_rd), ret);
sm_io_write(smioerr, sm_str_getdata(ss_sess->ssse_rd),
sm_str_getlen(ss_sess->ssse_rd), &r);
sm_io_flush(smioerr);
}
if (SM_IO_EOF == ret)
return ret;
if (sm_is_err(ret)) {
if (ss_sess->ssse_sctx->ssc_cnf.ss_cnf_debug > 1)
sm_io_fprintf(smioerr,
"sev=DEBUG, func=ss_read_cmd, read=error, ret=%r\n", ret);
return ret;
}
return SMTP_OK;
}
/*
** SS_NOPCMD -- Some "nop" cmd was entered: check whether counters are exceeded
**
** Parameters:
** ss_sess -- SMTPS session
** checkonly -- do not increase counters, only check them
** reply -- default reply
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_nopcmd(ss_sess_P ss_sess, bool checkonly, const char *reply)
{
ss_ta_P ss_ta;
ss_ctx_P ss_ctx;
bool toomany;
SM_IS_SS_SESS(ss_sess);
ss_ctx = ss_sess->ssse_sctx;
SM_IS_SS_CTX(ss_ctx);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
if (!checkonly)
++ss_ta->ssta_nopcmds;
toomany = ss_ta->ssta_nopcmds > ss_ctx->ssc_cnf.ss_cnf_ta_max_nopcmds;
if (!toomany && !SSTA_IS_ACTIVE(ss_ta)) {
if (!checkonly)
++ss_sess->ssse_nopcmds;
toomany = ss_sess->ssse_nopcmds > ss_ctx->ssc_cnf.ss_cnf_sess_max_nopcmds;
}
if (toomany) {
sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 Too many useless commands.\r\n");
ss_sess->ssse_state = SSSE_ST_QUIT;
return sm_error_temp(SM_EM_SMTPS, EPERM);
}
sm_str_scopy(ss_sess->ssse_wr, reply);
return SM_SUCCESS;
}
/*
** SS_BADCMD -- Some "bad" cmd was entered: check whether counters are exceeded
**
** Parameters:
** ss_sess -- SMTPS session
** checkonly -- do not increase counters, only check them
** reply -- default reply
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_badcmd(ss_sess_P ss_sess, bool checkonly, const char *reply)
{
ss_ta_P ss_ta;
ss_ctx_P ss_ctx;
bool toomany;
SM_IS_SS_SESS(ss_sess);
ss_ctx = ss_sess->ssse_sctx;
SM_IS_SS_CTX(ss_ctx);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
if (SSTA_IS_ACTIVE(ss_ta)) {
if (!checkonly)
++ss_ta->ssta_badcmds;
toomany = ss_ta->ssta_badcmds > ss_ctx->ssc_cnf.ss_cnf_ta_max_badcmds;
}
else {
if (!checkonly)
++ss_sess->ssse_badcmds;
toomany = ss_sess->ssse_badcmds > ss_ctx->ssc_cnf.ss_cnf_sess_max_badcmds;
}
if (toomany) {
sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 Too many bad commands.\r\n");
ss_sess->ssse_state = SSSE_ST_QUIT;
return sm_error_temp(SM_EM_SMTPS, EPERM);
}
if (reply != NULL)
sm_str_scopy(ss_sess->ssse_wr, reply);
return SM_SUCCESS;
}
/*
** SS_INVALIDADDR -- invalid address: check whether counters are exceeded
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** SUCCESS or SSD
*/
static sm_ret_T
ss_invalidaddr(ss_sess_P ss_sess)
{
ss_ta_P ss_ta;
ss_ctx_P ss_ctx;
bool toomany;
SM_IS_SS_SESS(ss_sess);
ss_ctx = ss_sess->ssse_sctx;
SM_IS_SS_CTX(ss_ctx);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
toomany = false;
if (SSTA_IS_ACTIVE(ss_ta)) {
++ss_ta->ssta_invldaddr;
toomany = ss_ta->ssta_invldaddr > ss_ctx->ssc_cnf.ss_cnf_ta_max_invldaddr;
}
++ss_sess->ssse_invldaddr;
if (!toomany)
toomany = ss_sess->ssse_invldaddr > ss_ctx->ssc_cnf.ss_cnf_sess_max_invldaddr;
if (!toomany)
return SM_SUCCESS;
sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 Too many invalid addresses.\r\n");
ss_sess->ssse_state = SSSE_ST_QUIT;
return SMTP_R_SSD;
}
/*
** SS_RSET -- RSET command
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_rset(ss_sess_P ss_sess)
{
sm_ret_T ret;
ss_ta_P ss_ta;
SM_IS_SS_SESS(ss_sess);
ss_ta = ss_sess->ssse_ta;
if (!SSTA_IS_ACTIVE(ss_ta)) {
ret = ss_nopcmd(ss_sess, false, SS_R_OK);
if (sm_is_err(ret))
return ret;
}
ret = ss_ta_abort(ss_sess, ss_ta);
ss_ta->ssta_state = SSTA_ST_NONE;
if (sm_is_err(ret))
return ret; /* error message alread in ssse_wr */
return sm_str_scopy(ss_sess->ssse_wr, SS_R_OK);
}
/*
** SS_EHLO -- EHLO/HELO command
**
** Parameters:
** ss_sess -- SMTPS session
** ehlo -- client sent ehlo?
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_ehlo(ss_sess_P ss_sess, bool ehlo)
{
size_t l, i;
ss_ta_P ss_ta;
sm_ret_T ret;
ulong max_sz_b;
#if SS_EHLO_ACCESS_CHK
ss_ctx_P ss_ctx;
#endif
SM_IS_SS_SESS(ss_sess);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
ret = ss_ta_abort(ss_sess, ss_ta);
if (sm_is_err(ret))
return ret; /* error message already in ssse_wr */
#if SS_EHLO_ACCESS_CHK
ss_ctx = ss_sess->ssse_sctx;
#endif
if (!ehlo)
SSSE_SET_FLAG(ss_sess, SSSE_FL_HELO);
else
SSSE_CLR_FLAG(ss_sess, SSSE_FL_HELO);
/* need to copy HELO parameter here */
sm_str_clr(ss_sess->ssse_helo);
l = sm_str_getlen(ss_sess->ssse_rd);
if (l < 5 || !ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, 4)))
goto syntaxerror;
i = 5;
/* RFC 2821: only one space... */
while (i < l && ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, i)))
++i;
if (i >= l)
goto syntaxerror;
/* RFC 2821: parameter must be Domain, see syntax in sm/rfc2821.h */
while (i < l && sm_str_rd_elem(ss_sess->ssse_rd, i) != '\0' &&
!ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, i)) &&
ISPRINT(sm_str_rd_elem(ss_sess->ssse_rd, i)))
{
/* further syntax check? valid char? */
if (sm_is_err(sm_str_put(ss_sess->ssse_helo,
sm_str_rd_elem(ss_sess->ssse_rd, i))))
goto error;
++i;
}
if (i > l || (i < l && sm_str_rd_elem(ss_sess->ssse_rd, i) != '\0'))
goto syntaxerror;
if (sm_is_err(sm_str_term(ss_sess->ssse_helo)))
goto error;
if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_EHLO_SYNTAX)) {
if (!validdomain(ss_sess->ssse_helo, R2821_NO_TRLDOT)) {
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_NOTICE, 10,
"sev=NOTICE, func=ss_ehlo, ss_sess=%s, %s=%@S, stat=%d, status=Invalid_domain"
, ss_sess->ssse_id, ehlo ? "ehlo" : "helo", ss_sess->ssse_helo, 501);
goto syntaxerror;
}
}
if (sm_io_getinfo(ss_sess->ssse_fp, SM_IO_IS_READABLE, NULL)) {
sm_str_scopy(ss_sess->ssse_wr, "421 Illegal Pipelining detected\r\n");
return sm_error_temp(SM_EM_SMTPS, SM_E_ILL_PIPE);
}
#if SS_EHLO_ACCESS_CHK
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) &&
SSSE_IS_CFLAG(ss_sess, SSSE_CFL_CHK_EHLO) &&
!SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY) &&
!SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK)
)
{
ret = sm_s2a_str(ss_sess, ss_ctx->ssc_s2a_ctx,
ss_sess->ssse_id,
ss_sess->ssse_helo, RT_S2A_EHLO, SMARA_LT_EHLO_ACC,
SMARA_LFL_STR);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 7,
"sev=ERROR, func=ss_ehlo, ss_sess=%s, sm_s2a_str=%r"
, ss_sess->ssse_id, ret);
goto error;
}
else /* not really required because of goto above */
ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
ss_ctx->ssc_s2a_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_ehlo, ss_sess=%s, func=ss_ehlo, where=smar_check, sm_w4q2s_reply=%r"
, ss_sess->ssse_id, ret);
goto error;
}
else if (SMAR_RISQUICK(ret)) {
/* SSTA_SET_FLAG(ss_ta, SSTA_FL_EHLO_QCK); */
SMAR_RCLRQUICK(ret);
}
else if (SSTA_IS_FLAG(ss_ta, SSTA_FL_DELAY_CHKS)
&& IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
{
sm_str_P str;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DEBUG, ss_sess=%s, func=ss_ehlo, where=smar_check, sm_w4q2s_reply=%r, reply=%r, text=%@T"
, ss_sess->ssse_id, ret
, ss_ta->ssta_ehlo_acc.ssa_reply_code
, ss_sess->ssse_wr);
/* delay rejection */
str = sm_str_dup(NULL, ss_sess->ssse_wr);
if (str != NULL) {
ss_ta->ssta_ehlo_acc.ssa_reply_text = str;
sm_str_clr(ss_sess->ssse_wr);
ret = SMTP_R_OK;
}
else {
/* can't accept transaction: ENOMEM */
ret = SMTP_R_SSD;
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_EHLO, true);
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 4,
"sev=ERROR, ss_sess=%s, ss_ta=%s, func=ss_ehlo, sm_str_dup=ENOMEM"
, ss_sess->ssse_id, ss_ta->ssta_id);
}
}
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
int rc;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 10,
"sev=INFO, ss_sess=%s, ehlo=%@N, stat=%r, text=%@T"
, ss_sess->ssse_id, ss_sess->ssse_helo, ret, ss_sess->ssse_wr);
if (!SMTP_REPLY_MATCHES_RCODE(ss_sess->ssse_wr, ret, 0, rc))
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, true);
return SM_SUCCESS; /* to avoid abort in session loop */
}
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"sev=INFO, ss_sess=%s, ss_ta=%s, func=ss_ehlo, where=smar_check, sm_w4q2s_reply=%r"
, ss_sess->ssse_id
, ss_ta->ssta_id, ret);
if (SMTP_R_DISCARD == ret) {
SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
ret = SM_SUCCESS; /* "normalize" */
}
}
}
#endif /* SS_EHLO_ACCESS_CHK */
/* refuse hostname as EHLO parameter unless localhost */
if (ss_sess->ssse_client.s_addr != ntohl(INADDR_LOOPBACK) &&
!SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY) &&
!SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK) &&
sm_str_getlen(ss_sess->ssse_helo) ==
sm_str_getlen(ss_sess->ssse_sctx->ssc_hostname) &&
strncasecmp((char *) sm_str_data(ss_sess->ssse_helo),
(char *) sm_str_data(ss_sess->ssse_sctx->ssc_hostname),
sm_str_getlen(ss_sess->ssse_helo)) == 0)
{
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_NOTICE, 9,
"sev=NOTICE, func=ss_ehlo, ss_sess=%s, %s=%@S, stat=%d, status=Identity_Theft"
, ss_sess->ssse_id, ehlo ? "ehlo" : "helo", ss_sess->ssse_helo, 550);
sm_str_scopy(ss_sess->ssse_wr, "550 Identity Theft!\r\n");
return SM_SUCCESS; /* to avoid abort in session loop */
}
#if MTA_USE_PMILTER
ret = sspm_helo(ss_sess, ehlo);
if (sm_is_err(ret))
goto error;
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
return ret; /* goto some-error?? */
/* currently no handling of delay_checks here! */
#endif
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_NULL) || !ehlo) {
if (sm_is_err(sm_str_scopy(ss_sess->ssse_wr, "250 "))
|| sm_is_err(sm_str_cat(ss_sess->ssse_wr, ss_sess->ssse_sctx->ssc_hostname))
|| sm_is_err(sm_str_scat(ss_sess->ssse_wr, " Hi there\r\n")))
goto error;
}
else {
if (sm_is_err(sm_str_scopy(ss_sess->ssse_wr, "250-"))
|| sm_is_err(sm_str_cat(ss_sess->ssse_wr, ss_sess->ssse_sctx->ssc_hostname))
|| sm_is_err(sm_str_scat(ss_sess->ssse_wr, " ESMTP Hi there\r\n250-PIPELINING\r\n")))
goto error;
if (SSC_IS_FLAG(ss_sess->ssse_sctx, SSC_FL_TLS_OK) &&
SSSE_IS_CFLAG(ss_sess, SSSE_CFL_STARTTLS) &&
!SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS) &&
sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-STARTTLS\r\n")))
goto error;
#if MTA_USE_SASL
if (SSC_IS_FLAG(ss_sess->ssse_sctx, SSC_FL_SASL_OK) &&
SSSE_IS_CFLAG(ss_sess, SSSE_CFL_AUTH) &&
!SSSE_IS_FLAG(ss_sess, SSSE_FL_AUTH) &&
ss_sess->ssse_sasl_mech_list != NULL
&& sm_is_err(sm_strprintf(ss_sess->ssse_wr, "250-AUTH %s\r\n", ss_sess->ssse_sasl_mech_list)))
goto error;
#endif /* MTA_USE_SASL */
max_sz_b = ss_sess->ssse_sctx->ssc_cnf.ss_cnf_max_msg_sz_kb * ONEKB;
if (max_sz_b > 0 &&
sm_strprintf(ss_sess->ssse_wr, "250-SIZE %lu\r\n", max_sz_b) <= 8)
goto error;
if (SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_8BITMIME) &&
sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-8BITMIME\r\n")))
goto error;
if (sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-ENHANCEDSTATUSCODES\r\n")))
goto error;
if (SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_XVERP) &&
sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-XVERP\r\n")))
goto error;
#if MTA_USE_PMILTER && MTA_USE_RSAD
if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_RSAD) &&
sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-PRDR\r\n")))
goto error;
#endif
if (sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250 HELP\r\n")))
goto error;
}
ss_sess->ssse_state = ehlo ? SSSE_ST_EHLO : SSSE_ST_HELO;
return SM_SUCCESS;
error:
sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
return sm_error_temp(SM_EM_SMTPS, ENOMEM);
syntaxerror:
ret = ss_badcmd(ss_sess, false, "501 Syntax error.\r\n");
return ret; /* to avoid abort in session loop */
}
/* check for null server and return proper error code */
/* ss_sess->ssse_acc.ssa_reply_text shouldn't be NULL */
#define SMTPS_NULL_SERVER \
do { \
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_NULL)) \
return (ss_sess->ssse_acc.ssa_reply_text == NULL || \
sm_str_getlen(ss_sess->ssse_acc.ssa_reply_text)\
<= 4) \
? sm_str_scopy(ss_sess->ssse_wr, \
"550 5.7.1 Access denied\r\n") \
: sm_str_cpy(ss_sess->ssse_wr, \
ss_sess->ssse_acc.ssa_reply_text); \
} while (0)
/* hack: caller should send no reply; FIX THIS: return value semantics! */
#define SS_NO_REPLY 1
#if MTA_USE_TLS
/*
** SS_TLSREQ -- Check STARTTLS requirements
**
** Parameters:
** ss_sess -- SMTPS session
** rcode -- reply code to use in case of an error
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_tlsreq(ss_sess_P ss_sess, sm_ret_T rcode)
{
sm_ret_T ret;
uint u;
const char *cstr;
sm_str_P str;
SM_IS_SS_SESS(ss_sess);
ret = SM_SUCCESS;
if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_flags, TLSREQ_FL_VRFD) &&
TLS_VRFY_OK != ss_sess->ssse_tlsi->tlsi_vrfy)
return rcode;
u = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_min_cipher_bits;
if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_flags, TLSREQ_FL_ENCR) &&
0 == u)
return rcode;
if (u > 0 && u > ss_sess->ssse_tlsi->tlsi_cipher_bits)
return rcode;
if ((cstr = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_cert_subject) != NULL &&
((str = ss_sess->ssse_tlsi->tlsi_cert_subject) == NULL ||
strcasecmp(cstr, sm_str_getdata(str)) != 0))
{
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
"sev=INFO, func=ss_tlsreq, cert_subject=%#S, required=%s"
, str, cstr);
return rcode;
}
if ((cstr = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_cert_issuer) != NULL &&
((str = ss_sess->ssse_tlsi->tlsi_cert_issuer) == NULL ||
strcasecmp(cstr, sm_str_getdata(str)) != 0))
{
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
"sev=INFO, func=ss_tlsreq, cert_issuer=%#S, required=%s"
, str, cstr);
return rcode;
}
if ((cstr = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_common_name) != NULL &&
((str = ss_sess->ssse_tlsi->tlsi_cn_subject) == NULL ||
strcasecmp(cstr, sm_str_getdata(str)) != 0))
{
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
"sev=INFO, func=ss_tlsreq, common_name=%#S, required=%s"
, str, cstr);
return rcode;
}
return ret;
}
/*
** SS_TLS -- STARTTLS command
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_tls(ss_sess_P ss_sess)
{
sm_ret_T ret;
ssize_t written;
int r;
ss_ta_P ss_ta;
ss_ctx_P ss_ctx;
/* check handling of error cases; see comments below */
SM_IS_SS_SESS(ss_sess);
SMTPS_NULL_SERVER;
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
/* is this check ok? should we just reset the transaction? */
if (ss_ta->ssta_state != SSTA_ST_NONE)
return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);
ss_ctx = ss_sess->ssse_sctx;
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_TLS_OK) ||
SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS) ||
!SSSE_IS_CFLAG(ss_sess, SSSE_CFL_STARTTLS))
return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);
/*
** Set verification globally (per SSL_CTX)
** Last parameter: request a client cert -- should be an option
** (globally, per client).
*/
SSSE_SET_FLAG(ss_sess, SSSE_FL_STARTTLS);
ss_sess->ssse_con = SSL_new(ss_ctx->ssc_ssl_ctx);
if (NULL == ss_sess->ssse_con)
goto notls;
tls_set_verify(ss_ctx->ssc_ssl_ctx, ss_sess->ssse_con, true);
ret = sm_str_scopy(ss_sess->ssse_wr, "220 2.0.0 try it\r\n");
if (sm_is_err(ret))
goto fail;
ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
sm_str_clr(ss_sess->ssse_wr);
if (ret != SMTP_OK) {
SS_CONN_LOG(ss_sess, 2, "ss_tls");
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
"sev=WARN, func=ss_tls, ss_sess=%s, where=try_starttls, send=%m"
, ss_sess->ssse_id, ret);
goto fail;
}
/* maybe do this before replying in case this fails?? */
ret = tls_open(ss_sess->ssse_fp, ss_sess->ssse_con,
ss_sess->ssse_sctx->ssc_tlsl_ctx, &ss_sess->ssse_fptls);
if (sm_is_err(ret)) {
SS_CONN_LOG(ss_sess, 8, "ss_tls");
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_tls, ss_sess=%s, where=connection, tls_open=%m"
, ss_sess->ssse_id, ret);
goto fail;
}
SSL_set_accept_state(ss_sess->ssse_con);
ret = do_tls_operation(ss_sess->ssse_fptls, SSL_accept, NULL, NULL,
NULL, 0, &written);
if (sm_is_err(ret)) {
SS_CONN_LOG(ss_sess, 8, "ss_tls");
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_tls, ss_sess=%s, where=connection, starttls=%m"
, ss_sess->ssse_id, ret);
goto closetls;
}
r = 1;
ret = sm_io_setinfo(ss_sess->ssse_fptls, SM_IO_DOUBLE, &r);
if (sm_is_err(ret))
goto closetls;
(void) tls_get_info(ss_ctx->ssc_tlsl_ctx, ss_sess->ssse_con,
TLS_F_SRV|TLS_F_CERT_REQ, inet_ntoa(ss_sess->ssse_client),
ss_sess->ssse_tlsi);
SS_CONN_LOG(ss_sess, 9, "ss_tls");
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_tls, ss_sess=%s, where=connection, starttls=successful, cipher=%S, bits=%d/%d, verify=%s"
, ss_sess->ssse_id
, ss_sess->ssse_tlsi->tlsi_cipher
, ss_sess->ssse_tlsi->tlsi_cipher_bits
, ss_sess->ssse_tlsi->tlsi_algs_bits
, tls_vrfy2txt(ss_sess->ssse_tlsi->tlsi_vrfy)
);
if (TLS_VRFY_OK == ss_sess->ssse_tlsi->tlsi_vrfy &&
SSC_IS_CFLAG(ss_ctx, SSC_CFL_TLS_REL_VRFY))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
else if (TLS_VRFY_OK == ss_sess->ssse_tlsi->tlsi_vrfy &&
SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) &&
SSC_IS_CFLAG(ss_ctx, SSC_CFL_TLS_REL_ACC))
{
/* check access map... */
ret = sm_s2a_tls(ss_sess, ss_ctx->ssc_s2a_ctx);
if (sm_is_err(ret)) {
SS_CONN_LOG(ss_sess, 7, "ss_tls");
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 7,
"sev=ERROR, func=ss_tls, ss_sess=%s, sm_s2a_tls=%m"
, ss_sess->ssse_id, ret);
goto closetls;
}
else { /* not really required because of goto above */
ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
ss_ctx->ssc_s2a_ctx);
}
if (sm_is_err(ret)) {
SS_CONN_LOG(ss_sess, 6, "ss_tls");
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_tls, ss_sess=%s, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ret);
goto closetls;
}
/* common code: see smtps.c */
else if (SMAR_RISQUICK(ret)) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_QUICK);
SMAR_RCLRQUICK(ret);
}
if (SMTP_R_RELAY == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
ret = SM_SUCCESS; /* "normalize" */
}
else if (SMTP_R_DISCARD == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_DISCARD);
ret = SM_SUCCESS; /* "normalize" */
}
else if (SMTP_R_OK == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_OK);
ret = SM_SUCCESS; /* "normalize" */
}
else if (SMTP_R_CONT == ret) {
ret = SM_SUCCESS; /* "normalize" */
}
/* end common code */
/* reject/accept TLS??? there is no further reply after the handshake */
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
SS_CONN_LOG(ss_sess, 12, "ss_tls");
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_tls, ss_sess=%s, sm_w4q2s_reply=%m, reply=%r, text=%@T"
, ss_sess->ssse_id, ret
, ss_sess->ssse_acc.ssa_reply_code
, ss_sess->ssse_wr);
#if 0
/* copy error text (if valid) */
if (sm_str_getlen(ss_sess->ssse_wr) > 4 &&
sm_str_cpy(ss_sess->ssse_acc.ssa_reply_text,
ss_sess->ssse_wr) == SM_SUCCESS)
{
/* delay rejection? */
if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_DELAY_CHKS)) {
sm_str_clr(ss_sess->ssse_wr);
ret = SMTP_R_OK;
}
}
else {
/*
** Can't accept session; complain??
** Decrease concurrency?
*/
ret = SMTP_R_SSD;
sm_str_clr(ss_sess->ssse_wr);
sm_str_clr(ss_sess->ssse_acc.ssa_reply_text);
}
#endif /* 0 */
}
}
/* HACK */
ss_sess->ssse_fp = ss_sess->ssse_fptls;
SSSE_SET_FLAG(ss_sess, SSSE_FL_STARTTLS);
#if MTA_USE_SASL
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_SASL_OK)) {
/* r = cipher_bits; see above! */
ss_sess->ssse_sasl_ext_ssf = ss_sess->ssse_tlsi->tlsi_cipher_bits;
ret = sasl_setprop(ss_sess->ssse_sasl_conn, SASL_SSF_EXTERNAL, &r);
SS_CONN_LOG(ss_sess, 9, "ss_tls");
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_tls, ss_sess=%s, set_ssf=%d"
, ss_sess->ssse_id, ret);
ss_ctx->ssc_sasl_ctx->sm_sasl_n_mechs = sm_saslmechs(
ss_ctx->ssc_sasl_ctx, ss_sess->ssse_sasl_conn,
&ss_sess->ssse_sasl_mech_list);
if (ss_ctx->ssc_sasl_ctx->sm_sasl_n_mechs <= 0)
SSSE_CLR_FLAG(ss_sess, SSSE_FL_SASL_OK);
}
#endif /* MTA_USE_SASL */
return SS_NO_REPLY;
notls:
ret = sm_str_scopy(ss_sess->ssse_wr, "454 4.3.3 Nope\r\n");
return SM_SUCCESS;
closetls:
/* shut down */
if (ss_sess->ssse_fptls != NULL) {
sm_io_close(ss_sess->ssse_fptls, SM_IO_CF_NONE);
ss_sess->ssse_fptls = NULL;
ss_sess->ssse_fp = NULL;
}
fail:
sm_str_clr(ss_sess->ssse_wr);
return ret;
}
#endif /* MTA_USE_TLS */
#if MTA_USE_SASL
/*
** RESET_SASLCONN -- reset SASL connection data
**
** Parameters:
** sasl_conn -- SASL connection context
** hostname -- host name
** various connection data
**
** Returns:
** SASL result
*/
static int
reset_saslconn(sasl_conn_t **sasl_conn, char *hostname, char *remoteip, char *localip, char *auth_id, sasl_ssf_t *ext_ssf)
{
int ret;
sasl_dispose(sasl_conn);
ret = sasl_server_new("smtp", hostname, NULL, NULL, NULL, NULL, 0, sasl_conn);
if (ret != SASL_OK)
return ret;
# if NETINET || NETINET6
if (remoteip != NULL) {
ret = sasl_setprop(*sasl_conn, SASL_IPREMOTEPORT, remoteip);
if (ret != SASL_OK)
return ret;
}
if (localip != NULL) {
ret = sasl_setprop(*sasl_conn, SASL_IPLOCALPORT, localip);
if (ret != SASL_OK)
return ret;
}
# endif /* NETINET || NETINET6 */
ret = sasl_setprop(*sasl_conn, SASL_SSF_EXTERNAL, ext_ssf);
if (ret != SASL_OK)
return ret;
ret = sasl_setprop(*sasl_conn, SASL_AUTH_EXTERNAL, auth_id);
if (ret != SASL_OK)
return ret;
return SASL_OK;
}
/*
** SS_AUTH -- AUTH command
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
*/
#define RESET_SASLCONN do { \
int r; \
ss_sess->ssse_sasl_state = SASL_NOT_AUTH; \
if ((r = reset_saslconn(&ss_sess->ssse_sasl_conn, \
(char *)sm_str_getdata(ss_ctx->ssc_hostname), NULL, NULL, NULL, \
(sasl_ssf_t *)&ss_sess->ssse_sasl_ext_ssf)) != SASL_OK) \
{ \
SSSE_CLR_FLAG(ss_sess, SSSE_FL_SASL_OK); \
sm_log_write(ss_ctx->ssc_lctx, \
SS_LCAT_SERVER, SS_LMOD_SERVER, \
SM_LOG_WARN, 9, \
"sev=WARN, func=ss_auth, reset_saslconn=%d", \
r); \
} \
} while (0)
static sm_ret_T
ss_auth(ss_sess_P ss_sess)
{
sm_ret_T ret, args;
uint i, cltinlen, more;
uint argv[SS_MAX_ARGS];
uint challengelen, encodelen;
ss_ta_P ss_ta;
ss_ctx_P ss_ctx;
char *cltin, *str;
const char *challenge, *mech;
SM_IS_SS_SESS(ss_sess);
SMTPS_NULL_SERVER;
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
if (ss_ta->ssta_state != SSTA_ST_NONE)
return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);
ss_ctx = ss_sess->ssse_sctx;
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SASL_OK) ||
!SSSE_IS_CFLAG(ss_sess, SSSE_CFL_AUTH) ||
SSSE_IS_FLAG(ss_sess, SSSE_FL_AUTH))
return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);
/* debugging .... remove/change later on */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"auth=%S, tries=%u",
ss_sess->ssse_rd, ss_sess->ssse_sasl_tries);
/* hardcoded limit (CONF) */
if (++ss_sess->ssse_sasl_tries > 3) {
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_SSD, SS_PHASE_AUTH, true);
return sm_error_temp(SM_EM_SMTPS, EPERM); /* error code?? */
}
/* 5: "auth " */
args = sm_str2argv(ss_sess->ssse_rd, 5, sizeof(argv)/sizeof(argv[0]), argv);
/* debugging .... remove/change later on */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"auth=%S, ret=%#x",ss_sess->ssse_rd, args);
if (sm_is_err(args) || args < 1)
return sm_str_scopy(ss_sess->ssse_wr,
"501 5.5.2 AUTH mechanism must be specified\r\n");
more = 0;
cltinlen = 0;
cltin = NULL;
ret = sm_str_cpy(ss_sess->ssse_str, ss_sess->ssse_rd);
if (sm_is_err(ret))
return sm_str_scopy(ss_sess->ssse_wr, SS_R_TMP);
/*
** Note: mech only points to storage provided by ssse_str!
** That is, ssse_str must NOT be changed!
*/
mech = (const char *)sm_str_getdata(ss_sess->ssse_str) + argv[0];
/*
** check whether mechanism is available;
** would sasl_server_start do that itself?
*/
#if 0
if (iteminlist(mech, ss_ctx->ssse_sasl_mech_list, " ") == NULL) {
return sm_strprintf(ss_sess->ssse_wr,
"501 5.5.2 AUTH mechanism %.32s not available\r\n",
mech);
}
#endif /* 0 */
/*
** check whether argument is available
** start server (with/without argument)
** while (CONTINUE) {
** send response
** get more data
** feed it into sasl_server_step()
** }
** if (OK) done
** else fail
*/
if (args >= 2) {
cltin = (char *)sm_str_getdata(ss_sess->ssse_str) + argv[1];
i = strlen(cltin);
/*
** RFC 2554
** 4. The AUTH command [[...]]
** Unlike a zero-length client answer to a 334 reply, a zero-
** length initial response is sent as a single equals sign ("=").
*/
if ('=' == cltin[0] && '\0' == cltin[1]) {
cltin = "";
cltinlen = 0;
}
else {
str = (char *)sm_str_data(ss_sess->ssse_wr);
ret = sasl_decode64(cltin, i, str,
sm_str_getsize(ss_sess->ssse_wr), &cltinlen);
if (ret != SASL_OK) {
return sm_strprintf(ss_sess->ssse_wr,
"501 5.5.4 cannot decode '%s' %d\r\n",
cltin, ret);
}
SM_STR_SETLEN(ss_sess->ssse_wr, cltinlen);
cltin = (char *)sm_str_getdata(ss_sess->ssse_wr);
}
}
else {
cltin = NULL;
cltinlen = 0;
}
/* see if that auth type exists; challenge is allocated by sasl */
ret = sasl_server_start(ss_sess->ssse_sasl_conn, mech,
cltin, cltinlen, &challenge, &challengelen);
ss_sess->ssse_sasl_state = SASL_PROC_AUTH;
while (SASL_CONTINUE == ret) {
if (NULL == challenge) {
/* ??? something seems to be wrong... */
ret = SASL_FAIL;
break;
}
/* encode data from challenge/challengelen into ssse_rd */
sm_str_clr(ss_sess->ssse_rd);
str = (char *)sm_str_data(ss_sess->ssse_rd);
ret = sasl_encode64(challenge, challengelen,
str, sm_str_getsize(ss_sess->ssse_rd),
&encodelen);
/*
** according to Cyrus SASL doc challenge is free()d by the
** library: it's stored in the context
*/
if (ret != SASL_OK) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 5,
"sev=WARN, func=ss_auth, ss_sess=%s, sasl_encode=%d"
, ss_sess->ssse_id, ret);
/* start over? */
RESET_SASLCONN;
return sm_str_scopy(ss_sess->ssse_wr,
"454 4.5.4 Temporary authentication failure\r\n");
}
SM_STR_SETLEN(ss_sess->ssse_rd, encodelen);
/* create a reply in ssse_wr */
sm_str_clr(ss_sess->ssse_wr);
sm_str_scat(ss_sess->ssse_wr, "334 ");
sm_str_cat(ss_sess->ssse_wr, ss_sess->ssse_rd);
sm_str_scat(ss_sess->ssse_wr, "\r\n");
ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
sm_str_clr(ss_sess->ssse_wr);
if (ret != SMTP_OK) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
"sev=WARN, func=ss_auth, ss_sess=%s, client_ipv4=%A, where=auth_continue, send=%m"
, ss_sess->ssse_id
, ss_sess->ssse_client.s_addr, ret);
goto fail;
}
ret = ss_read_cmd(ss_sess);
if (sm_is_err(ret)) {
RESET_SASLCONN;
return ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_AUTH, false);
break;
}
/* remove "\r\n" */
sm_str_rm_trail(ss_sess->ssse_rd, "\r\n");
if (sm_str_getlen(ss_sess->ssse_rd) == 1 &&
sm_str_rd_elem(ss_sess->ssse_rd, 0) == '*')
{
/* rfc 2254 4. */
RESET_SASLCONN;
return sm_str_scopy(ss_sess->ssse_wr, "501 5.7.0 AUTH aborted\r\n");
break;
}
/* decode data from _rd into _wr */
str = (char *)sm_str_data(ss_sess->ssse_wr);
ret = sasl_decode64((const char *)sm_str_getdata(ss_sess->ssse_rd),
sm_str_getlen(ss_sess->ssse_rd), str,
sm_str_getsize(ss_sess->ssse_wr), &cltinlen);
if (ret != SASL_OK) {
/* rfc 2254 4. */
RESET_SASLCONN;
return sm_str_scopy(ss_sess->ssse_wr,
"501 5.5.4 cannot decode AUTH parameter\r\n");
break;
}
SM_STR_SETLEN(ss_sess->ssse_wr, cltinlen);
/*
** use decoded data in _wr for step,
** new data in challenge/challengelen
*/
str = (char *)sm_str_getdata(ss_sess->ssse_wr);
ret = sasl_server_step(ss_sess->ssse_sasl_conn,
str, sm_str_getlen(ss_sess->ssse_wr),
&challenge, &challengelen);
}
if (SASL_OK == ret) {
bool istrustedmech;
SSSE_SET_FLAG(ss_sess, SSSE_FL_AUTH);
istrustedmech = iteminlist(mech, ss_ctx->ssc_cnf.ss_cnf_trusted_mechs,
" ") != NULL;
if (istrustedmech)
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
ss_sess->ssse_sasl_state = SASL_IS_AUTH;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_auth, ss_sess=%s, auth=%s, trusted_mech=%s"
, ss_sess->ssse_id, mech
, istrustedmech ? "yes" : "no");
# if MTA_USE_PMILTER
cltinlen = strlen(mech);
ss_sess->ssse_sasl_mech = sm_str_scpy0(NULL, mech, cltinlen + 2);
/* need to set:
ss_sess->ssse_sasl_authen
ss_sess->ssse_sasl_author
*/
# endif /* MTA_USE_PMILTER */
return sm_str_scopy(ss_sess->ssse_wr, "235 2.0.0 OK Authenticated\r\n");
}
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
"sev=WARN, func=ss_auth, ss_sess=%s, where=auth, ret=%d, errstring=%s, errdetail=%s"
, ss_sess->ssse_id, ret
, sasl_errstring(ret, NULL, NULL)
, sasl_errdetail(ss_sess->ssse_sasl_conn));
RESET_SASLCONN;
return sm_str_scopy(ss_sess->ssse_wr,
"535 5.7.0 authentication failed\r\n");
}
return ret;
fail:
sm_str_clr(ss_sess->ssse_wr);
return ret;
}
#endif /* MTA_USE_SASL */
/*
** SS_MAIL_ARGS -- Check arguments for MAIL command
**
** Parameters:
** ss_sess -- SMTPS session
** offset -- offset in ssse_rd where arguments begin
**
** Returns:
** usual return code
**
** Side Effects:
** ss_sess->ssse_wr contains error text in case of an error
*/
static sm_ret_T
ss_mail_args(ss_sess_P ss_sess, size_t offset)
{
int i, args;
sm_ret_T ret;
char *argname, *argvalue;
uint argnames[SS_MAX_ARGS], argvalues[SS_MAX_ARGS];
/* ssse_rd is '\0' terminated; make a copy as sm_str2args() "damages" it. */
ret = sm_str_cpy(ss_sess->ssse_str, ss_sess->ssse_rd);
if (sm_is_err(ret)) {
sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
return ret;
}
args = sm_str2args(ss_sess->ssse_str, offset,
sizeof(argnames)/sizeof(argnames[0]), argnames, argvalues);
if (sm_is_err(args)) {
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_ARG);
return sm_error_perm(SM_EM_SMTPS, EINVAL);
}
for (i = 0; i < args; i++) {
argname = (char *) sm_str_getdata(ss_sess->ssse_str) + argnames[i];
if (argvalues[i] > 0)
argvalue = (char *)sm_str_getdata(ss_sess->ssse_str) + argvalues[i];
else
argvalue = NULL;
if (sm_strcaseeq(argname, "body") &&
SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_8BITMIME))
{
if (!sm_strcaseeq(argvalue, "8bitmime")
&& !sm_strcaseeq(argvalue, "7bit"))
{
sm_str_scopy(ss_sess->ssse_wr,
"501 5.5.4 unknown body= value\r\n");
return sm_error_perm(SM_EM_SMTPS, EINVAL);
}
}
else if (sm_strcaseeq(argname, "size")) {
ulong msg_size, max_msg_sz_kb;
char *endptr;
errno = 0;
msg_size = strtoul(argvalue, &endptr, 10);
if ((ULONG_MAX == msg_size && errno == ERANGE) ||
!ISDIGIT(argvalue[0]))
{
sm_str_scopy(ss_sess->ssse_wr,
"501 5.5.4 invalid size= value\r\n");
return sm_error_perm(SM_EM_SMTPS, EINVAL);
}
max_msg_sz_kb = ss_sess->ssse_sctx->ssc_cnf.ss_cnf_max_msg_sz_kb
* ONEKB;
if (max_msg_sz_kb > 0 && msg_size > max_msg_sz_kb) {
sm_strprintf(ss_sess->ssse_wr,
"552 5.3.4 Size %lu exceeds maximum value %lu\r\n"
, msg_size, max_msg_sz_kb);
return sm_error_perm(SM_EM_SMTPS, EINVAL);
}
}
else if (sm_strcaseeq(argname, "xverp") &&
SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_XVERP))
{
SSTA_SET_FLAG(ss_sess->ssse_ta, SSTA_FL_XVERP);
/* ignore argvalue for now */
}
#if MTA_USE_PMILTER && MTA_USE_RSAD
else if (sm_strcaseeq(argname, "prdr") &&
SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_RSAD) &&
NULL == argvalue)
{
SSTA_SET_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD);
}
#endif
#if MTA_USE_SASL
else if (sm_strcaseeq(argname, "auth")) {
/* just accept it... log? */
/* needed for pmilter macro? */
}
#endif /* MTA_USE_SASL */
else {
sm_str_scopy(ss_sess->ssse_wr, "501 5.5.0 Unknown argument\r\n");
return sm_error_perm(SM_EM_SMTPS, EINVAL);
}
}
return SM_SUCCESS;
}
/*
** SS_MAIL -- MAIL command
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_mail(ss_sess_P ss_sess)
{
sm_ret_T ret;
uint flags, ltype, argoffset;
int log;
ss_ta_P ss_ta;
ss_mail_P mail;
ss_ctx_P ss_ctx;
#define MAILLEN 10 /* length of "MAIL FROM:" */
SM_IS_SS_SESS(ss_sess);
SMTPS_NULL_SERVER;
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
ss_ctx = ss_sess->ssse_sctx;
mail = NULL;
log = SM_DONT_LOG;
argoffset = 0;
sm_str_clr(ss_sess->ssse_wr);
if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_EHLO_REQ) &&
!(SSSE_ST_EHLO == ss_sess->ssse_state ||
SSSE_ST_HELO == ss_sess->ssse_state))
{
log = 14;
sm_str_scopy(ss_sess->ssse_wr, SS_R_EHLO_REQ);
goto cleanup;
}
if (SSTA_ST_NONE == ss_ta->ssta_state) {
ret = ss_ta_init(ss_sess, ss_ta);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, ss_ta_init=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
if (sm_is_err(ret))
goto ssd;
}
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 15,
"sev=DBG, func=ss_mail, ss_sess=%s, tss_a=%s, status=ta_reuse"
, ss_sess->ssse_id, ss_ta->ssta_id);
}
if (ss_ta->ssta_state != SSTA_ST_INIT) {
sm_str_scopy(ss_sess->ssse_wr, SS_R_SEQ);
goto cleanup;
}
if (strncasecmp((char *) sm_str_getdata(ss_sess->ssse_rd), "mail from:",
MAILLEN) != 0)
{
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
goto cleanup;
}
if (ss_sess->ssse_ta_tot >= ss_ctx->ssc_cnf.ss_cnf_t_tot_lim) {
log = 10;
sm_str_scopy(ss_sess->ssse_wr, "452 4.3.0 Too many transactions.\r\n");
goto cleanup;
}
++ss_sess->ssse_ta_tot;
#if MTA_USE_TLS
if (ss_sess->ssse_cnf != NULL) {
ret = ss_tlsreq(ss_sess, SMTP_R_SSD);
if (sm_is_err(ret))
goto ssd;
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
log = 8;
/* better/selectable error code? */
if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_viol,
TLSREQ_VIOL_PERM))
sm_str_scopy(ss_sess->ssse_wr,
"503 5.7.0 security requirements not met.\r\n");
else if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_viol,
TLSREQ_VIOL_TEMP))
sm_str_scopy(ss_sess->ssse_wr,
"403 4.7.0 security requirements not met.\r\n");
else { /* default behavior */
sm_str_scopy(ss_sess->ssse_wr,
"421 4.7.0 security requirements not met.\r\n");
goto ssd;
}
goto cleanup;
}
}
#endif
ret = ssm_mail_new(ss_ta, true, &mail);
if (sm_is_err(ret))
goto ssd;
ret = t2821_scan((sm_rdstr_P) ss_sess->ssse_rd, &mail->ssm_a2821, MAILLEN);
if (sm_is_err(ret)) {
log = 14;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_S_A);
goto cleanup;
}
if ((uint)ret < sm_str_getlen(ss_sess->ssse_rd) &&
!ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, ret)))
{
log = 18;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_PAR);
goto cleanup;
}
if (ret > MAILLEN && (uint)ret < sm_str_getlen(ss_sess->ssse_rd)) {
argoffset = ret;
ret = ss_mail_args(ss_sess, ret);
if (sm_is_err(ret)) {
log = 12;
goto cleanup;
}
}
flags = R2821_CORRECT|R2821_EMPTY;
ret = t2821_parse(&mail->ssm_a2821, flags);
if (sm_is_err(ret)) {
log = 13;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_S_A);
goto cleanup;
}
sm_str_clr(ss_sess->ssse_str);
ret = t2821_str(&mail->ssm_a2821, ss_sess->ssse_str, 0);
if (sm_is_err(ret))
goto ssd;
sm_str_clr(ss_sess->ssse_wr); /* clear reply string */
if (sm_str_getlen(ss_sess->ssse_str) == 2
&& sm_memeq(sm_str_data(ss_sess->ssse_str), "<>", 2))
SSTA_SET_FLAG(ss_ta, SSTA_FL_DSN);
sm_str_clr(mail->ssm_pa);
ret = sm_str_cpy(mail->ssm_pa, ss_sess->ssse_str);
if (sm_is_err(ret))
goto ssd;
/*
** Need to perform checks if
** sender is not "<>"
** and neither QUICK:RELAY nor QUICK:OK for session are set
*/
if (!SSTA_IS_FLAG(ss_ta, SSTA_FL_DSN) &&
!SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY) &&
!SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK)
)
{
ltype = SMARA_LT_MAIL_ROUTE|SMARA_LT_MAIL_LOCAL;
flags = SMARA_LFL_2821;
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB)) {
ltype |= SMARA_LT_MAIL_ACC;
flags |= SMARA_LFL_MXACC;
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCIMPLDET))
flags |= SMARA_LFL_ACCIMPLDET;
}
ret = sm_s2a_addr(ss_sess, ss_ctx->ssc_s2a_ctx,
ss_ta->ssta_id, ss_sess->ssse_str, RT_S2A_MAIL, ltype, flags);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 7,
"sev=ERROR, func=ss_mail, ss_sess=%s, ss_ta=%s, sm_s2a_addr=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
goto ssd;
}
else /* not really required because of goto above */
ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
ss_ctx->ssc_s2a_ctx);
if (SMAR_R_VAL(ret) == SMTP_R_CONT)
ret = SMAR_R_FLAGS(ret)|SM_SUCCESS; /* "normalize" */
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_mail, ss_sess=%s, ss_ta=%s, where=smar_check, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
goto ssd;
}
else if (SMAR_RISQUICK(ret)) {
/* SSTA_SET_FLAG(ss_ta, SSTA_FL_MAIL_QCK); */
SMAR_RCLRQUICK(ret);
}
else if (SSTA_IS_FLAG(ss_ta, SSTA_FL_DELAY_CHKS)
&& IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
{
sm_str_P str;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_mail, ss_sess=%s, ss_ta=%s, where=smar_check, sm_w4q2s_reply=%m, reply=%r, text=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id, ret
, ss_ta->ssta_mail_acc.ssa_reply_code
, ss_sess->ssse_wr);
/* delay rejection */
str = sm_str_dup(NULL, ss_sess->ssse_wr);
if (str != NULL) {
ss_ta->ssta_mail_acc.ssa_reply_text = str;
sm_str_clr(ss_sess->ssse_wr);
ret = SMTP_R_OK;
}
else {
/* can't accept transaction: ENOMEM */
ret = SMTP_R_SSD;
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, true);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 4,
"sev=ERROR, func=ss_mail, ss_sess=%s, ss_ta=%s, sm_str_dup=ENOMEM"
, ss_sess->ssse_id, ss_ta->ssta_id);
}
}
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
int rc;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 10,
"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, mail=%@N, stat=%d, text=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id
, ss_sess->ssse_str
, ret, ss_sess->ssse_wr);
if (!SMTP_REPLY_MATCHES_RCODE(ss_sess->ssse_wr, ret, 0, rc))
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, true);
goto error;
}
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, where=smar_check, sm_w4q2s_reply=%m"
, ss_sess->ssse_id
, ss_ta->ssta_id, ret);
if (SMTP_R_DISCARD == ret) {
SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
ret = SM_SUCCESS; /* "normalize" */
}
}
}
#if MTA_USE_PMILTER
ret = sspm_mail(ss_sess, ret, argoffset);
if (sm_is_err(ret))
goto error;
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
goto error;
#endif
ss_ta->ssta_state = SSTA_ST_MAIL;
ss_ta->ssta_mail = mail;
SS_CONN_LOG(ss_sess, 2, "ss_mail");
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
"sev=INFO, ss_sess=%s, ss_ta=%s, mail=%@N, stat=%d"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
, ret | (SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD) ? SMTP_R_DISCARD : 0));
return ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, false);
cleanup:
ret = SM_SUCCESS;
ssd: /* only called for sm_is_err(); hence fallthrough from cleanup is ok */
if (sm_is_err(ret)) {
sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
/* fail on which errors? */
if (SM_E_FULL == sm_error_value(ret)
|| SM_E_CONN_CLSD == sm_error_value(ret)
|| S2s_IS_IOERR(ss_ctx))
{
Max_cur_threads = 0; /* force shutdown */
SSC_SET_FLAG(ss_ctx, SSC_FL_TERMINATING|SSC_FL_SHUTDOWN);
if (0 == ss_ctx->ssc_shutdown)
ss_ctx->ssc_shutdown = st_time();
#if 0
/*
* ask for restart of server process?
* should not be really necessary as mcp should do that anyway
*/
if (SM_E_CONN_CLSD == sm_error_value(ret))
SSC_SET_FLAG(ss_ctx, SSC_FL_RESTARTDEP);
#endif
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
"sev=ERROR, func=ss_mail, pid=%d, status=forcing_shutdown"
, (int) My_pid);
}
}
error:
if (log < SM_DONT_LOG && sm_str_getlen(ss_sess->ssse_wr) > 0) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, log,
"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, ret=%m, status=%@T, input=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id, ret
, ss_sess->ssse_wr, ss_sess->ssse_rd);
}
ssm_mail_free(ss_ta, mail);
return ret;
}
/* macro for ss_rcpt */
#define SS_RELAYING_DENIED(rcpt_str) do { \
sm_log_write(ss_ctx->ssc_lctx, \
SS_LCAT_SERVER, SS_LMOD_SERVER, \
SM_LOG_NOTICE, 3, \
"sev=NOTICE, ss_sess=%s, ss_ta=%s, rcpt=%@N, stat=%d, status=%s Relaying denied" \
, ss_sess->ssse_id, ss_ta->ssta_id, rcpt_str \
, SSSE_IS_FLAG(ss_sess, SSSE_FL_CLT_RLY_TMP) \
? 450 : 550 \
, SSSE_IS_FLAG(ss_sess, SSSE_FL_CLT_RLY_TMP) \
? "450 4.4.0" : "550 5.7.1"); \
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_CLT_RLY_TMP)) \
sm_str_scopy(ss_sess->ssse_wr, \
"450 4.4.0 Relaying temporarily denied.\r\n"); \
else \
sm_str_scopy(ss_sess->ssse_wr, \
"550 5.7.1 Relaying denied.\r\n"); \
} while (0)
/*
** SS_RCPT -- RCPT command
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_rcpt(ss_sess_P ss_sess)
{
uint flags, ltype;
uint fct_flags;
int log;
sm_ret_T ret, res;
ss_ta_P ss_ta;
ss_rcpt_P ss_rcpt;
ss_ctx_P ss_ctx;
#define RCPTLEN 8 /* length of "RCPT TO:" */
#define SSRF_FL_RELAY 0x0001 /* relaying allowed */
#define SSRF_FL_OK2CPM 0x0002 /* ok to call pmilter */
#define SSRF_FL_PMC 0x0004 /* pmilter has been called */
#define SSRF_FL_BAD 0x0008 /* treat as "bad command" */
SM_IS_SS_SESS(ss_sess);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
ss_rcpt = NULL;
fct_flags = SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY) ?
SSRF_FL_RELAY : 0;
ss_ctx = ss_sess->ssse_sctx;
ret = SM_SUCCESS;
log = SM_DONT_LOG;
sm_str_clr(ss_sess->ssse_wr);
sm_str_clr(ss_sess->ssse_str);
if (ss_ta->ssta_state != SSTA_ST_MAIL && ss_ta->ssta_state != SSTA_ST_RCPT)
{
log = 20;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SEQ);
goto cleanup;
}
if (strncasecmp((char *) sm_str_getdata(ss_sess->ssse_rd), "rcpt to:",
RCPTLEN) != 0)
{
log = 14;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
if (ss_sess->ssse_rcpts_tot >= ss_ctx->ssc_cnf.ss_cnf_r_tot_lim
|| ss_ta->ssta_rcpts_tot >= ss_ctx->ssc_cnf.ss_cnf_r_ta_lim
|| ss_ta->ssta_rcpts_tot >= ss_ctx->ssc_cnf.ss_cnf_r_tot_lim
)
{
log = 14;
sm_str_scopy(ss_sess->ssse_wr, "452 4.5.3 Too many recipients.\r\n");
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
if (ss_ta->ssta_rcpts_len >= QSS_RC_MAXSZ / 2) {
log = 14;
sm_str_scopy(ss_sess->ssse_wr, "452 4.5.3 Too many recipients.\r\n");
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
ret = ssr_rcpts_new(ss_ta, &ss_ta->ssta_rcpts, &ss_rcpt);
if (sm_is_err(ret)) {
log = 12;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
goto error;
}
ss_sess->ssse_rcpts_tot++;
/*
** HACK: convert "<postmaster>" to "<postmaster@HOST.NAME>"
** Notes:
** 1. This checks the ENTIRE string! It will NOT work if arguments
** exist! (doesn't matter right now as there are none allowed).
** 2. it unconditionally use ss_sess->ssse_sctx->ssc_hostname;
** maybe this should be configurable.
** 3. Many other functions in smX "expect" an address of the
** form "<localpart@domain.part>", so this is the easiest way
** to satisfy that expectation.
*/
(void) sm_postmaster_add_domain(ss_sess->ssse_rd,
ss_sess->ssse_sctx->ssc_hostname, RCPTLEN);
ret = t2821_scan((sm_rdstr_P) ss_sess->ssse_rd, &ss_rcpt->ssr_a2821,
RCPTLEN);
if (sm_is_err(ret)) {
log = 12;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_R_A);
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
if ((uint)ret < sm_str_getlen(ss_sess->ssse_rd) &&
!ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, ret)))
{
log = 12;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
/* check args: none for now, fixme: misleading error if no address */
if ((uint)ret < sm_str_getlen(ss_sess->ssse_rd)) {
log = 14;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
/* "555 5.5.5 Parameter unsupported.\r\n") */
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
/*
* keep track of total length of all rcpts to avoid overflowing RCB
* (entire string: it's too much but that doesn't matter much as we just
* try to establish some upper bound).
*/
ss_ta->ssta_rcpts_len += sm_str_getlen(ss_sess->ssse_rd);
/* further checks should be somewhere else, configurable */
ret = t2821_parse(&ss_rcpt->ssr_a2821, R2821_CORRECT);
if (sm_is_err(ret)) {
log = 12;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_R_A);
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
SM_SET_FLAG(fct_flags, SSRF_FL_OK2CPM);
/* Simple anti-relay test... */
if (!SM_IS_FLAG(fct_flags, SSRF_FL_RELAY)) {
int r;
ret = t2821_str(&ss_rcpt->ssr_a2821, ss_sess->ssse_wr, 0);
if (sm_is_err(ret)) {
log = 14;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_R_A);
goto cleanup;
}
r = regexec(&ss_ctx->ssc_relayto,
(const char *) sm_str_getdata(ss_sess->ssse_wr), 0, NULL, 0);
if (0 == r)
SM_SET_FLAG(fct_flags, SSRF_FL_RELAY);
if (r != 0 && !SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB)
&& SSC_IS_CFLAG(ss_ctx, SSC_CFL_LMTPNOTRELAY))
{
SS_RELAYING_DENIED(ss_sess->ssse_wr);
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
sm_str_clr(ss_sess->ssse_wr);
}
/*
** Check whether RCPT is acceptable ... Where/Who should do it?
** For example: local domain: check user list.
** Should SMTPS do this or SMAR?
** There is a comment in qmgr (sm_q_rcptid()) to do it there;
** however, it might be simpler to call an "anti-spam" SMAR
** routine from here than going via QMGR.
*/
/* send the recipient information to SMAR then QMGR */
sm_str_clr(ss_sess->ssse_str);
ret = t2821_str(&ss_rcpt->ssr_a2821, ss_sess->ssse_str, 0);
if (sm_is_err(ret)) {
log = 12;
sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
goto error;
}
ltype = SMARA_LT_RCPT_LOCAL;
flags = SMARA_LFL_2821;
if (!SSC_IS_CFLAG(ss_ctx, SSC_CFL_LMTPNOTRELAY))
ltype |= SMARA_LT_RCPT_LCL_R;
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_PROTBYMAIL|SSC_CFL_PROTBYCLTADDR))
ltype |= SMARA_LT_RCPT_PROT;
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_PROTMAP))
flags |= SMARA_LFL_PROTMAP;
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_PROTIMPLDET))
flags |= SMARA_LFL_PROTIMPLDET;
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCIMPLDET))
flags |= SMARA_LFL_ACCIMPLDET;
/*
** Need to check access map if
** access feature is set
** but not QUICK:RELAY for session
** and not QUICK:OK for session unless !relay (i.e., it might be
** necessary to allow relaying)
*/
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) &&
!(SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY)) &&
!(SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK)
&& SM_IS_FLAG(fct_flags, SSRF_FL_RELAY))
)
{
ltype |= SMARA_LT_RCPT_ACC;
}
/*
** greylisting: if requested
** and not done before in this session
** and not client:ok
** and not client relaying allowed
*/
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_GREY) &&
!SSSE_IS_FLAG(ss_sess, SSSE_FL_GREYCHKD) &&
!SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_OK) &&
!SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY))
{
ltype |= SMARA_LT_GREY;
}
ret = sm_s2a_rcptid(ss_sess, ss_ctx->ssc_s2a_ctx,
ss_ta->ssta_id, ss_rcpt->ssr_idx, ltype, flags, ss_sess->ssse_str);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_rcpt, ss_sess=%s, ss_ta=%s, sm_s2a_rcptid=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
goto error;
}
ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
ss_ctx->ssc_s2a_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_rcpt, ss_sess=%s, ss_ta=%s, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
goto error;
}
else if (SMAR_RISQUICK(ret)) {
/* SSTA_SET_FLAG(ss_ta, SSTA_FL_RCPT_QCK); */
SMAR_RCLRQUICK(ret);
/* quick:discard -> discard entire transaction */
if (SMTP_R_DISCARD == ret)
SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
}
if (SMAR_RISGREYCHKD(ret)) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_GREYCHKD);
SMAR_RCLRGREYCHKD(ret);
}
if (SMAR_RISGREY(ret)) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_GREYLSTD);
SMAR_RCLRGREY(ret);
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_GREY_DATA))
{
ret = SMAR_R_FLAGS(ret)|SM_SUCCESS;
sm_str_clr(ss_sess->ssse_wr);
}
}
if (!SM_IS_FLAG(fct_flags, SSRF_FL_RELAY)
&& SMAR_R_IS(ret, SMAR_R_LOC_RCPT)
&& !SSC_IS_CFLAG(ss_ctx, SSC_CFL_LMTPNOTRELAY))
SM_SET_FLAG(fct_flags, SSRF_FL_RELAY);
SMAR_CLRFLAGS(ret);
if (!SM_IS_FLAG(fct_flags, SSRF_FL_RELAY) && ret != SMTP_R_RELAY
&& !SMTP_IS_REPLY_ERROR(ret))
{
SS_RELAYING_DENIED(ss_sess->ssse_str);
SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
goto cleanup;
}
if (SMTP_R_RELAY == ret)
ret = SM_SUCCESS; /* "normalize" */
else if (SMTP_R_DISCARD == ret) /* check before relaying denied?? */
goto discard;
else if (SMTP_R_CONT == ret)
ret = SM_SUCCESS; /* "normalize" */
/*
** Note: it might be useful to process several RCPT commands
** concurrently (if PIPELINING is active). This can "hide"
** latencies in the address processing, esp. DNS lookups.
** Question: how much would this complicate the code?
** Currently probably quite a lot, because the code has
** to check whether there is data from the client
** and whether there is a response from the QMGR.
**
** Notes:
** If a pmilter should allow relaying, then this code needs
** to be moved up (before SS_RELAYING_DENIED()) or that
** information must be maintained somehow (e.g., check "relay"
** after pmilter call).
**
** This is "flow through" code: ret may contain an error
** which must not be overridden (unless "increased").
*/
#if MTA_USE_PMILTER
res = sspm_rcpt(ss_sess, ss_rcpt, ret);
# if MTA_USE_RSAD
ss_rcpt->ssr_rcode = res;
# endif
SM_SET_FLAG(fct_flags, SSRF_FL_PMC);
if (sm_is_err(res)) {
/* check whether res is more "severe" than ret? */
ret = res;
goto error;
}
if (SMTP_R_DISCARD == res) {
ret = res;
goto discard;
}
/* HACK: relies on order of SMTP reply code: 5 more "severe" than 4 */
if (smtp_reply_type(res) > smtp_reply_type(ret))
ret = res;
#endif
/* XXX how does this interact with RSAD??? */
/* everything is fine so far? check previous rejections */
if (SMTPS_R_IS_OK(ret) && SSTA_IS_FLAG(ss_ta, SSTA_FL_DELAY_CHKS)) {
ss_acc_P ss_acc;
ss_acc = NULL;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_rcpt, ss_sess=%s, ss_ta=%s, ta_result=%d, se_result=%d, map2_res=%d, reply2=%d"
, ss_sess->ssse_id, ss_ta->ssta_id
, ss_ta->ssta_mail_acc.ssa_map_result
, ss_sess->ssse_acc.ssa_map_result
, ss_ta->ssta_rcpt_acc2.ssa_map_result
, ss_ta->ssta_rcpt_acc2.ssa_reply_code
);
#if 0
/* todo: should check for SpamFriend... */
if (ss_ta->ssta_rcpt_acc2.ssa_map_result == SM_ACC_FOUND
&& ss_ta->ssta_rcpt_acc2.ssa_reply_code == SMTP_R_OK)
{
ret = SM_SUCCESS;
}
#else /* 0 */
if (ss_ta->ssta_rcpt_acc.ssa_map_result == SM_ACC_FOUND
&& SMAR_RISQUICK(ss_ta->ssta_rcpt_acc.ssa_reply_code)
&& (SMAR_RWOFLAGS(ss_ta->ssta_rcpt_acc.ssa_reply_code)
== SMTP_R_OK ||
SMAR_RWOFLAGS(ss_ta->ssta_rcpt_acc.ssa_reply_code)
== SMTP_R_RELAY)
)
{
ret = SM_SUCCESS;
}
#endif /* 0 */
else if (ss_ta->ssta_mail_acc.ssa_map_result == SM_ACC_FOUND
&& IS_SMTP_REPLY(ss_ta->ssta_mail_acc.ssa_reply_code))
ss_acc = &ss_ta->ssta_mail_acc;
else if (ss_sess->ssse_acc.ssa_map_result == SM_ACC_FOUND
&& IS_SMTP_REPLY(ss_sess->ssse_acc.ssa_reply_code))
ss_acc = &ss_sess->ssse_acc;
if (ss_acc != NULL) {
ret = ss_acc->ssa_reply_code;
if (ss_acc->ssa_reply_text != NULL)
sm_str_cpy(ss_sess->ssse_wr, ss_acc->ssa_reply_text);
else
sm_str_clr(ss_sess->ssse_wr);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_rcpt, ss_sess=%s, ss_ta=%s, reply=%m, text=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id
, ret, ss_sess->ssse_wr);
}
}
res = ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_RCPT, false);
if (SMTPS_R_IS_OK(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, idx=%u, stat=%d"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
, ss_rcpt->ssr_idx, ret);
}
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, idx=%u, stat=%d, status=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
, ss_rcpt->ssr_idx, ret, ss_sess->ssse_wr);
}
/* everything is fine? */
if (SM_SUCCESS == ret) {
ss_ta->ssta_state = SSTA_ST_RCPT;
ss_ta->ssta_rcpts_ok++;
}
else {
SS_RCPTS_REMOVE_FREE(ss_ta, &ss_ta->ssta_rcpts, ss_rcpt);
if (IS_SMTP_REPLY(ret) && smtp_is_reply_fail(ret) &&
ss_invalidaddr(ss_sess) == SMTP_R_SSD)
ret = SMTP_R_SSD;
}
return (SMTP_R_SSD == ret) ? ret : res;
discard:
ss_ta->ssta_state = SSTA_ST_RCPT;
SSTA_SET_FLAG(ss_ta, SSTA_FL_DSCRD_RCPT);
if (sm_str_getlen(ss_sess->ssse_wr) == 0)
sm_str_scopy(ss_sess->ssse_wr, SS_R_OK);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, stat=%d"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
, SMTP_R_DISCARD);
log = SM_DONT_LOG;
cleanup:
/*
** Note: ssse_str might be empty if an error occurred before rcpt was
** parse properly. Should we log the input instead?
*/
if (SM_SUCCESS == ret && IS_SMTP_REPLY_STR(ss_sess->ssse_wr, 0))
ret = SMTP_REPLY_STR2VAL(ss_sess->ssse_wr, 0);
if (log < SM_DONT_LOG && sm_str_getlen(ss_sess->ssse_wr) > 0) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, log,
"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, error=%m, status=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
, ret, ss_sess->ssse_wr);
}
#if MTA_USE_PMILTER
if (!SM_IS_FLAG(fct_flags, SSRF_FL_PMC) &&
SM_IS_FLAG(fct_flags, SSRF_FL_OK2CPM))
{
res = sspm_rcpt(ss_sess, ss_rcpt, ret);
SM_SET_FLAG(fct_flags, SSRF_FL_PMC);
}
#endif
ret = SM_SUCCESS;
error:
if (sm_str_getlen(ss_sess->ssse_wr) == 0)
sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
if (ret != SM_SUCCESS && log < SM_DONT_LOG &&
sm_str_getlen(ss_sess->ssse_wr) > 0)
{
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, log,
"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, error=%m, status=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
, ret, ss_sess->ssse_wr);
}
/* remove rcpt from list again and free it */
if (ss_rcpt != NULL)
SS_RCPTS_REMOVE_FREE(ss_ta, &ss_ta->ssta_rcpts, ss_rcpt);
if (SM_IS_FLAG(fct_flags, SSRF_FL_BAD) &&
IS_SMTP_REPLY(ret) && smtp_is_reply_fail(ret) &&
ss_invalidaddr(ss_sess) == SMTP_R_SSD)
ret = SMTP_R_SSD;
return ret;
}
/*
** SS_DATA_EOB -- found EOB in data stream: write block to CDB etc
**
** Parameters:
** ss_sess -- SMTPS session
** bufp -- pointer to (begin of) file buffer
** pskip -- (pointer to) skip rest of input? (output)
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_data_eob(ss_sess_P ss_sess, uchar *bufp, bool *pskip)
{
sm_ret_T ret;
size_t bytes2write, max_sz_kb;
ssize_t byteswritten;
ss_ta_P ss_ta;
SM_IS_SS_SESS(ss_sess);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
SM_REQUIRE(pskip != NULL);
/* write the current buffer */
bytes2write = f_p(*ss_sess->ssse_fp) - bufp;
if (0 == bytes2write)
return SM_SUCCESS;
ret = cdb_write(cdb_ctx, ss_ta->ssta_dfp, bufp, bytes2write, &byteswritten);
if (sm_is_err(ret)) {
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data_eob, ss_sess=%s, ss_ta=%s, write_cdb=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP, SS_PHASE_DOT, true);
*pskip = true;
return ret;
}
ss_ta->ssta_msg_sz_b += byteswritten;
max_sz_kb = ss_sess->ssse_sctx->ssc_cnf.ss_cnf_max_msg_sz_kb;
if (max_sz_kb > 0 && max_sz_kb < ss_ta->ssta_msg_sz_b / 1024) {
sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_NOTICE, 8,
"sev=NOTICE, func=ss_data_eob, ss_sess=%s, ss_ta=%s, max_message_size=%lu, status=exceeded"
, ss_sess->ssse_id, ss_ta->ssta_id
, (ulong) max_sz_kb);
sm_str_scopy(ss_sess->ssse_wr,
"552 5.3.4 Maximum message size exceeded.\r\n");
*pskip = true;
}
#if MTA_USE_PMILTER
ret = sspm_msg(ss_sess, bufp, bytes2write, false, pskip);
#endif
return ret;
}
#if MTA_USE_PMILTER && MTA_USE_RSAD
/*
** SS_RSAD_SET_REPLIES -- Set RCPT replies after end of mail data
**
** Parameters:
** ss_ctx -- SMTPS context
** ss_sess -- SMTPS session
** ss_ta -- SMTP server transaction context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_rsad_set_replies(ss_ctx_P ss_ctx, ss_sess_P ss_sess, ss_ta_P ss_ta)
{
sm_ret_T ret, rcode;
uint u;
ss_rcpt_P ss_rcpt;
SM_IS_SS_SESS(ss_sess);
SM_IS_SS_TA(ss_ta);
if (ss_ta->ssta_nreplies != ss_ta->ssta_rcpts_ok_orig) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_NOTICE, 2,
"sev=NOTICE, func=ss_rsad_set_replies, ss_sess=%s, ss_ta=%s, replies=%u, ok=%u, status=inconsistent"
, ss_sess->ssse_id, ss_ta->ssta_id
, ss_ta->ssta_nreplies, ss_ta->ssta_rcpts_ok_orig);
return SMTP_R_SSD;
}
#define SMTP_R_IS_OK(rcode) (SMTP_OK == rcode || smtp_reply_type(rcode) == SMTP_RTYPE_OK)
/* set replies */
if (SS_RCPTS_EMPTY(&ss_ta->ssta_rcpts)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_rsad_set_replies, ss_sess=%s, ss_ta=%s, replies=%u, ok=%u, status=no_rcpts"
, ss_sess->ssse_id, ss_ta->ssta_id
, ss_ta->ssta_nreplies, ss_ta->ssta_rcpts_ok);
return SMTP_R_SSD;
}
ss_rcpt = SS_RCPTS_FIRST(&ss_ta->ssta_rcpts);
rcode = ss_ta->ssta_rcodes[0];
for (u = 0; u < ss_ta->ssta_nreplies; u++) {
while (!SMTP_R_IS_OK(ss_rcpt->ssr_rcode)
&& ss_rcpt->ssr_type != PM_RCPT_DEL)
{
ss_rcpt = SS_RCPTS_NEXT(ss_rcpt);
if (ss_rcpt == SS_RCPTS_END(&ss_ta->ssta_rcpts)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 2,
"sev=WARN, func=ss_rsad_set_replies, ss_sess=%s, ss_ta=%s, status=no_valid_rcpt_found"
, ss_sess->ssse_id, ss_ta->ssta_id);
return SMTP_R_SSD;
}
}
ret = ss_ta->ssta_rcodes[u];
if (ret < rcode)
rcode = ret;
if (!SMTP_R_IS_OK(ret)) {
ss_rcpt->ssr_type = PM_RCPT_RSAD;
ss_rcpt->ssr_rcode = ret;
--ss_ta->ssta_rcpts_ok; /* ??? */
}
ss_rcpt = SS_RCPTS_NEXT(ss_rcpt);
}
/* "normalize" rcode */
if (rcode != SMTP_OK && smtp_reply_type(rcode) == SMTP_RTYPE_OK)
rcode = SMTP_OK;
return rcode;
}
/*
** SS_RSAD_SEND_REPLIES -- Send deferred RCPT replies after end of mail data
**
** Parameters:
** ss_ctx -- SMTPS context
** ss_sess -- SMTPS session
** ss_ta -- SMTP server transaction context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_rsad_send_replies(ss_ctx_P ss_ctx, ss_sess_P ss_sess, ss_ta_P ss_ta)
{
sm_ret_T ret, rcode;
uint ui;
ss_rcpt_P ss_rcpt;
SM_IS_SS_SESS(ss_sess);
SM_IS_SS_TA(ss_ta);
rcode = 599;
#if 0
/* optimization: check for common RCPT status */
for (ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
ss_rcpt != SS_RCPTS_END(&ss_rcpts_hd);
ss_rcpt = SS_RCPTS_NEXT(ss_rcpt))
{
n = atoi(ss_rcpt->ssr_reply);
if (CRS_INIT == common_rcpt_status)
common_rcpt_status = n;
else if (n != common_rcpt_status) {
common_rcpt_status = CRS_NONE;
break;
}
}
if (IS_SMTP_REPLY(common_rcpt_status))
sm_snprintf(resp, sizeof(resp), "%d first\r\n", common_rcpt_status);
else
strlcpy(resp, "353 RCPT status follows\r\n", sizeof resp);
#endif
# if MTA_USE_RSAD == 2
/* send "first reply" */
sm_str_scopy(ss_sess->ssse_wr, "353 RCPT replies follow.\r\n");
ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
sm_str_clr(ss_sess->ssse_wr);
if (ret != SMTP_OK) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 2,
"sev=WARN, func=ss_rsad_send_replies, ss_sess=%s, ss_ta=%s, status=write_error_first_reply, ret=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
goto error;
}
# endif /* MTA_USE_RSAD == 2 */
/* send all replies back to client */
for (ss_rcpt = SS_RCPTS_FIRST(&ss_ta->ssta_rcpts), ui = 0;
ss_rcpt != SS_RCPTS_END(&ss_ta->ssta_rcpts)
&& ui < ss_ta->ssta_rcpts_ok_orig;
ss_rcpt = SS_RCPTS_NEXT(ss_rcpt), ui++)
{
if (PM_RCPT_ADD == ss_rcpt->ssr_type) {
/* shouldn't happen, ssta_rcpts_ok_orig should have stopped loop! */
break;
}
ret = ss_rcpt->ssr_rcode;
if (ret < rcode)
rcode = ret;
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_RCPT, true);
ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
sm_str_clr(ss_sess->ssse_wr);
if (ret != SMTP_OK) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 2,
"sev=WARN, func=ss_rsad_send_replies, ss_sess=%s, ss_ta=%s, idx=%u, status=write_error_rsad, ret=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_rcpt->ssr_idx, ret);
goto error;
}
else
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 10,
"sev=INFO, func=ss_rsad_send_replies, ss_sess=%s, ss_ta=%s, idx=%u, ret=%d"
, ss_sess->ssse_id, ss_ta->ssta_id, ss_rcpt->ssr_idx, ss_rcpt->ssr_rcode);
#if 0
if we do this, then the loop must be a bit more clever,
i.e.,
ss_rcpt_nxt = SS_RCPTS_NEXT(ss_rcpt);
if (ss_rcpt->ssr_rcode != SMTP_OK)
SS_RCPTS_REMOVE_FREE(ss_ta, &ss_ta->ssta_rcpts, ss_rcpt);
and in the loop as "next" part:
ss_rcpt = ss_rcpt_nxt;
#endif
}
/* "normalize" rcode */
if (rcode != SMTP_OK && smtp_reply_type(rcode) == SMTP_RTYPE_OK)
rcode = SMTP_OK;
return rcode;
error:
return ret;
}
#endif /* MTA_USE_PMILTER && MTA_USE_RSAD */
/*
** SS_DATA -- DATA command
**
** Parameters:
** ss_sess -- SMTPS session
**
** Returns:
** usual return code
**
** Side Effects: writes cdb (unless DISCARD is set or errors occur)
**
** Note: SMTP requires that the input is read completely before
** returning a reply, hence error handling is done by
** remembering the error and setting "skip" instead of
** just jumping to the error handling label. The only
** exception is a read error in which case the connection
** is dropped.
*/
#ifndef SS_DATA_DEBUG
# define SS_DATA_DEBUG 0
#endif
/*
RFC 2822:
message-id = "Message-ID:" msg-id CRLF
in-reply-to = "In-Reply-To:" 1*msg-id CRLF
references = "References:" 1*msg-id CRLF
msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS]
id-left = dot-atom-text / no-fold-quote / obs-id-left
id-right = dot-atom-text / no-fold-literal / obs-id-right
no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE
no-fold-literal = "[" *(dtext / quoted-pair) "]"
The state machine below does not implement this, it just skips leading
whitespace. This should be "good enough" because currently the message-id
is only used for logging.
*/
#define MSGID_ST_NONE 0
#define MSGID_ST_NEW (-1) /* found message-id: header */
#define MSGID_ST_FIRST (-2) /* found message-id: header, str allocated */
#define MSGID_ST_SKIP (-3) /* skipping over initial white space */
#define MSGID_ST_RDING (-4) /* reading message id */
#define MSGID_ST_GOT (-5) /* got message id */
#define MSGID_ST_NOMEM (-9) /* can't allocate memory for message id */
static sm_ret_T
ss_data(ss_sess_P ss_sess)
{
int c;
uint eot_state, eoh_state, rcvd_state, hops;
#if SS_CHECK_LINE_LEN
uint eol_state, line_len;
#endif
int msgid_state, res_msgid_state;
size_t bufs, bytes2write, max_sz_kb;
ssize_t byteswritten;
bool skip;
#if SS_DATA_DEBUG
char sbuf[512];
ssize_t b;
#endif
sm_ret_T ret, res;
ss_ta_P ss_ta;
uchar *bufp;
time_t currt;
cdb_ctx_P cdb_ctx;
ss_ctx_P ss_ctx;
static SM_DECL_EOT;
static SM_DECL_EOH;
#if SS_CHECK_LINE_LEN
static const char eol[] = "\r\n";
#define SM_EOL_LEN (sizeof(eol) - 1)
#endif
static const char rcvd[] = "\r\nreceived:";
static const char msgid[] = "\r\nmessage-id:";
static const char res_msgid[] = "\r\nresent-message-id:";
#if SS_TEST
extern int Unsafe;
#endif
SM_IS_SS_SESS(ss_sess);
ss_ta = ss_sess->ssse_ta;
SM_IS_SS_TA(ss_ta);
ss_ctx = ss_sess->ssse_sctx;
/* beware of pipelining and discarded recipients */
if (ss_ta->ssta_state != SSTA_ST_RCPT ||
(NO_RCPTS(ss_ta) && !SSTA_IS_FLAG(ss_ta, SSTA_FL_DSCRD_RCPT)))
return sm_str_scopy(ss_sess->ssse_wr, SS_R_SEQ);
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_GREYLSTD)) {
(void) sm_str_scopy(ss_sess->ssse_wr, SS_R_GREY);
ret = SMTP_R_SSD;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
"sev=INFO, func=ss_data, ss_sess=%s, ss_ta=%s, stat=%d, status=%@T"
, ss_sess->ssse_id, ss_ta->ssta_id
, ret, ss_sess->ssse_wr);
return ret;
}
/* discard entire transaction if all recipients have been discarded */
if (ss_ta->ssta_rcpts_ok == 0 &&
SSTA_IS_FLAG(ss_ta, SSTA_FL_DSCRD_RCPT))
SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
skip = SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD);
ss_ta->ssta_msg_sz_b = 0;
byteswritten = 0;
#if SS_CHECK_LINE_LEN
eol_state = 0;
line_len = 0;
SSTA_CLR_FLAG(ss_ta, SSTA_FL_LL_EXC);
#endif
max_sz_kb = ss_ctx->ssc_cnf.ss_cnf_max_msg_sz_kb;
ss_ta->ssta_dfp = NULL;
currt = st_time();
cdb_ctx = ss_ctx->ssc_cdb_ctx;
/* Check for "illegal" pipelining: DATA is synchronization point */
if (sm_io_getinfo(ss_sess->ssse_fp, SM_IO_IS_READABLE, NULL)) {
sm_str_scopy(ss_sess->ssse_wr,
"421 4.5.0 Illegal Pipelining detected (DATA)\r\n");
ret = sm_error_temp(SM_EM_SMTPS, SM_E_ILL_PIPE);
goto error;
}
#if MTA_USE_PMILTER
ret = sspm_data(ss_sess, &skip);
if (sm_is_err(ret))
goto error;
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
goto error;
#endif
if (!skip) {
/* open cdb entry */
ret = cdb_open_write(cdb_ctx, ss_ta->ssta_id, ss_ta->ssta_dfp,
SM_IO_WREXCL, 0, NULL);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, cdb_open=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
SS_PHASE_DATA, true);
goto error;
}
SSTA_SET_FLAG(ss_ta, SSTA_FL_CDB_EXISTS);
/* set group id? It might be inherited from directory */
if (ss_ctx->ssc_cnf.ss_cnf_cdb_gid > 0 &&
(c = fchown(sm_io_getinfo(ss_ta->ssta_dfp, SM_IO_WHAT_FD, NULL),
(uid_t) -1, ss_ctx->ssc_cnf.ss_cnf_cdb_gid)) < 0)
{
#if SS_DEBUG_CHOWN_CDB
struct stat sb;
#endif
ret = sm_error_temp(SM_EM_SMTPS, errno);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, chgrp_cdb=%d"
, ss_sess->ssse_id, ss_ta->ssta_id, errno);
#if SS_DEBUG_CHOWN_CDB
c = fstat(f_fd(*ss_ta->ssta_dfp), &sb);
if (0 == c)
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, uid=%d, gid=%d, cdb_gid=%d"
, ss_sess->ssse_id, ss_ta->ssta_id
, (int) sb.st_uid, (int) sb.st_gid
, (int) ss_ctx->ssc_cnf.ss_cnf_cdb_gid);
else
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, fstat=%d"
, ss_sess->ssse_id, ss_ta->ssta_id, c);
#endif /* SS_DEBUG_CHOWN_CDB */
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
SS_PHASE_DATA, true);
goto error;
}
}
ss_ta->ssta_state = SSTA_ST_DATA;
/* initial state: begin of line, hence \r\n are "skipped" */
eot_state = rcvd_state = SM_GOT_CRLF;
msgid_state = res_msgid_state = SM_GOT_CRLF;
eoh_state = hops = 0;
sm_str_scopy(ss_sess->ssse_wr, "354 Go\r\n");
if ((ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1,
true)) != SMTP_OK)
{
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, write_354_response=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
if (!sm_is_err(ret))
ret = SMTP_WR_ERR;
goto error;
}
/*
** Write Received: header; do NOT use %S for helo: it ends in '\0'.
** More data??
*/
sm_str_clr(ss_sess->ssse_str);
#if MTA_USE_TLS
if (!skip && SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS)) {
sm_str_scat(ss_sess->ssse_str, "(TLS");
if (ss_sess->ssse_tlsi->tlsi_version != NULL) {
sm_str_scat(ss_sess->ssse_str, "=");
sm_str_cat(ss_sess->ssse_str, ss_sess->ssse_tlsi->tlsi_version);
}
if (ss_sess->ssse_tlsi->tlsi_cipher != NULL) {
sm_str_scat(ss_sess->ssse_str, ", cipher=");
sm_str_cat(ss_sess->ssse_str, ss_sess->ssse_tlsi->tlsi_cipher);
}
if (ss_sess->ssse_tlsi->tlsi_cipher_bits != 0) {
sm_str_scat(ss_sess->ssse_str, ", bits=");
sm_strprintf(ss_sess->ssse_str, "%d",
ss_sess->ssse_tlsi->tlsi_cipher_bits);
}
sm_str_scat(ss_sess->ssse_str, ", verify=");
sm_str_scat(ss_sess->ssse_str,
tls_vrfy2txt(ss_sess->ssse_tlsi->tlsi_vrfy));
if (sm_str_getlen(ss_sess->ssse_str) > 40)
sm_str_scat(ss_sess->ssse_str, ")\r\n\t");
else
sm_str_scat(ss_sess->ssse_str, ") ");
}
#endif
sm_str_clr(ss_sess->ssse_wr);
if (!skip) {
sm_strprintf(ss_sess->ssse_wr,
"Received: from %.128N (%.128C [%A])\r\n\tby %S (%s) with %sSMTP%s%s\r\n\t%Sid %s; "
, ss_sess->ssse_helo
, ss_sess->ssse_cltname
, ss_sess->ssse_client.s_addr
, ss_ctx->ssc_hostname
, MTA_VERSION_STR
, SSSE_IS_FLAG(ss_sess, SSSE_FL_HELO) ? "" : "E"
, SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS) ? "S" : ""
, SSSE_IS_FLAG(ss_sess, SSSE_FL_AUTH) ? "A" : ""
, ss_sess->ssse_str
, ss_ta->ssta_id
);
ret = arpadate(&currt, ss_sess->ssse_wr);
ret = sm_str_scat(ss_sess->ssse_wr, "\r\n");
ret = cdb_write(cdb_ctx, ss_ta->ssta_dfp,
sm_str_data(ss_sess->ssse_wr),
sm_str_getlen(ss_sess->ssse_wr), &byteswritten);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, write_cdb=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
SS_PHASE_DOT, true);
skip = true;
}
else {
#if MTA_USE_PMILTER
ret = sspm_msg(ss_sess, sm_str_data(ss_sess->ssse_wr),
sm_str_getlen(ss_sess->ssse_wr), true, &skip);
#endif
sm_str_clr(ss_sess->ssse_wr);
}
ss_ta->ssta_msg_sz_b += byteswritten;
}
/*
** Notes: need to check whether the first line contains
** a header: if it doesn't: print a blank line.
** Also need to check the number of Received: headers,
** this means we need to recognize header/body boundary
** and Received: headers. This is done below in a simple way.
*/
/*
** See the smX docs: libraries.func.tex Required I/O Functionality
*/
bufs = 0;
/* Switch to read mode before accessing the buffer */
sm_iotord(ss_sess->ssse_fp);
bufp = f_p(*ss_sess->ssse_fp);
for (;;) {
c = sm_getb(ss_sess->ssse_fp);
do {
if (SM_IO_EOF == c) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, read_data=EOF, bufs=%lu"
, ss_sess->ssse_id, ss_ta->ssta_id
, (ulong) bufs);
ret = sm_error_perm(SM_EM_SMTPS, SM_E_EOF);
goto error;
}
if (c != SM_IO_EOB) {
SM_ASSERT(c >= 0);
break;
}
if (!skip) {
(void) ss_data_eob(ss_sess, bufp, &skip);
/* ignore return code, skip is important */
}
/* get new buffer */
c = sm_rget(ss_sess->ssse_fp);
if (sm_is_err(c)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, read_error=%m, bufs=%lu"
, ss_sess->ssse_id, ss_ta->ssta_id, c
, (ulong) bufs);
ret = c; /* SMTP_RD_ERR; */
goto error;
}
++bufs;
bufp = f_p(*ss_sess->ssse_fp) - 1;
if (bufp < f_bfbase(*ss_sess->ssse_fp))
bufp = f_bfbase(*ss_sess->ssse_fp);
#if SS_DATA_DEBUG
if (ss_ctx->ssc_cnf.ss_cnf_debug > 1) {
int e = errno;
sm_snprintf(sbuf, sizeof(sbuf),
"data: read buf=%d, c=%3d, f_r=%x, f_p=%lx, bufp=%lx, *bufp=%d, f_bfbase=%lx, f_bfsize=%x, e=%d\r\n"
, bufs, c, f_r(*ss_sess->ssse_fp), (long) f_p(*ss_sess->ssse_fp), (long) bufp, (int) *bufp, (long) f_bfbase(*ss_sess->ssse_fp), f_bfsize(*ss_sess->ssse_fp), e);
sm_io_write(smioerr, (uchar *)sbuf, strlen(sbuf), &b);
sm_io_flush(smioerr);
errno = e;
}
#endif /* SS_DATA_DEBUG */
} while (c < 0); /* HACK!! */
#if SS_CHECK_LINE_LEN
if (ss_sess->ssse_max_line_len > 0 && !skip) {
if (++line_len > ss_sess->ssse_max_line_len) {
skip = true;
SSTA_SET_FLAG(ss_ta, SSTA_FL_LL_EXC);
}
if (c == eol[eol_state]) {
if (++eol_state >= SM_EOL_LEN) {
line_len = 0;
eol_state = 0;
}
}
else {
eol_state = 0;
if (c == eol[eol_state])
++eol_state;
}
}
#endif /* SS_CHECK_LINE_LEN */
if (eoh_state < SM_EOH_LEN) {
if (c == eoh[eoh_state])
++eoh_state;
else {
eoh_state = 0;
if (c == eoh[eoh_state])
++eoh_state;
}
if (TOLOWER(c) == rcvd[rcvd_state]) {
if (++rcvd_state >= strlen(rcvd)) {
if (++hops > ss_ctx->ssc_cnf.ss_cnf_maxhops)
skip = true;
rcvd_state = 0;
#if SS_DATA_DEBUG
if (ss_ctx->ssc_cnf.ss_cnf_debug > 2) {
sm_snprintf(sbuf, sizeof(sbuf), "data: hops=%d\r\n"
, hops);
sm_io_write(smioerr, (uchar *)sbuf, strlen(sbuf), &b);
sm_io_flush(smioerr);
}
#endif /* SS_DATA_DEBUG */
}
}
else {
rcvd_state = 0;
if (TOLOWER(c) == rcvd[rcvd_state])
++rcvd_state;
}
/* put this into some function so it can be reused?? */
if (NULL == ss_ta->ssta_msgid && msgid_state >= 0) {
if (TOLOWER(c) == msgid[msgid_state]) {
if (++msgid_state >= (int)strlen(msgid))
msgid_state = MSGID_ST_NEW;
}
else {
msgid_state = 0;
if (TOLOWER(c) == msgid[msgid_state])
++msgid_state;
}
}
if (res_msgid_state >= 0) {
if (TOLOWER(c) == res_msgid[res_msgid_state]) {
if (++res_msgid_state >=
(int)strlen(res_msgid))
msgid_state = MSGID_ST_NEW;
}
else {
res_msgid_state = 0;
if (TOLOWER(c) ==
res_msgid[res_msgid_state])
++res_msgid_state;
}
}
if (MSGID_ST_NEW == msgid_state) {
if (NULL == ss_ta->ssta_msgid)
ss_ta->ssta_msgid = sm_str_new(NULL, SMTPMSGIDSIZE,
SMTPMAXSIZE);
else
sm_str_clr(ss_ta->ssta_msgid);
if (ss_ta->ssta_msgid != NULL)
msgid_state = MSGID_ST_FIRST;
else
msgid_state = MSGID_ST_NOMEM;
}
if (MSGID_ST_FIRST == msgid_state)
msgid_state = MSGID_ST_SKIP;
else if (MSGID_ST_SKIP == msgid_state && ISSPACE(c))
;
else if (MSGID_ST_SKIP == msgid_state && ISPRINT(c))
msgid_state = MSGID_ST_RDING;
if (MSGID_ST_RDING == msgid_state) {
if (ISSPACE(c)) {
msgid_state = MSGID_ST_GOT;
sm_str_term(ss_ta->ssta_msgid);
}
else if (ISPRINT(c))
SM_STR_PUT(ss_ta->ssta_msgid, c);
}
}
if (c == eot[eot_state]) {
if (++eot_state >= SM_EOT_LEN)
break;
}
else {
eot_state = 0;
if (c == eot[eot_state])
++eot_state;
}
#if 0
if (ss_ctx->ssc_cnf.ss_cnf_debug > 1 && r > 0) {
sm_snprintf(sbuf, sizeof(sbuf),
"eot_state: %d, c=%c\n"
, eot_state, isprint(c) ? c : '_');
sm_io_write(smioerr, (uchar *)sbuf, strlen(sbuf), &b);
}
#endif /* 0 */
}
SM_ASSERT(eot_state >= SM_EOT_LEN); /* really?? */
/* should we complain if skip is set?? */
if (hops > ss_ctx->ssc_cnf.ss_cnf_maxhops) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, too_many_hops=%u, max=%d"
, ss_sess->ssse_id, ss_ta->ssta_id
, hops
, ss_ctx->ssc_cnf.ss_cnf_maxhops);
sm_str_scopy(ss_sess->ssse_wr, "554 5.4.6 Too many hops\r\n");
ret = 554;
goto error;
}
#if SS_CHECK_LINE_LEN
/* should we complain if skip is set?? */
if (SSTA_IS_FLAG(ss_ta, SSTA_FL_LL_EXC)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, line_length_exceeded=%u, max=%u"
, ss_sess->ssse_id, ss_ta->ssta_id
, line_len, ss_sess->ssse_max_line_len);
sm_str_scopy(ss_sess->ssse_wr,
"554 5.5.0 Some line in mail too long\r\n");
ret = 554;
goto error;
}
#endif /* SS_CHECK_LINE_LEN */
if (skip) {
/* pmilter will not be contacted! */
if (!SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD))
ret = 552;
goto error;
}
/* write the current buffer */
bytes2write = f_p(*ss_sess->ssse_fp) - bufp;
ret = cdb_write(cdb_ctx, ss_ta->ssta_dfp, bufp, bytes2write, &byteswritten);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, write_cdb=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
SS_PHASE_DOT, true);
goto error;
}
/* "else" not needed because of "goto error" above */
ss_ta->ssta_msg_sz_b += byteswritten;
if (max_sz_kb > 0 && max_sz_kb < ss_ta->ssta_msg_sz_b / 1024) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 8,
"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, max_message_size=%lu, status=exceeded"
, ss_sess->ssse_id, ss_ta->ssta_id
, (ulong) max_sz_kb);
sm_str_scopy(ss_sess->ssse_wr,
"552 5.3.4 Maximum message size exceeded.\r\n");
ret = 552;
skip = true;
goto error;
}
#if MTA_USE_PMILTER
ss_ta->ssta_rcpts_ok_orig = ss_ta->ssta_rcpts_ok;
ss_ta->ssta_rcpts_tot_orig = ss_ta->ssta_rcpts_tot;
ss_ta->ssta_hops = hops;
ret = sspm_eob(ss_sess, bufp, bytes2write, &skip);
if (sm_is_err(ret))
goto error;
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
goto error;
# if MTA_USE_RSAD
if (ret != SMTP_R_D_EOM)
SSTA_CLR_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD);
# endif
if (SSC_IS_PMCAP(ss_ctx, SM_SCAP_PM_MSG_RC) &&
ss_ta->ssta_msg_acc.ssa_map_result == SM_ACC_FOUND &&
IS_SMTP_REPLY(ss_ta->ssta_msg_acc.ssa_reply_code) &&
SMTP_IS_REPLY_ERROR(ss_ta->ssta_msg_acc.ssa_reply_code))
{
if (ss_ta->ssta_msg_acc.ssa_reply_text != NULL) {
sm_str_clr(ss_sess->ssse_wr);
sm_str_cat(ss_sess->ssse_wr, ss_ta->ssta_msg_acc.ssa_reply_text);
}
ret = ss_ta->ssta_msg_acc.ssa_reply_code;
goto error;
}
if (skip) {
if (!SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD))
ret = 550;
goto error;
}
# if SM_TEST_HDRMOD
{
sm_hdrmod_P sm_hdrmod;
extern sm_cstr_P Hdr_pre, Hdr_app;
/* HACK FOR TESTING */
sm_hdrmod = NULL;
if (Hdr_pre != NULL || Hdr_app != NULL)
ret = sm_hdrmod_new(&ss_ta->ssta_hdrmodhd, true, &sm_hdrmod);
if (Hdr_pre != NULL && sm_hdrmod != NULL) {
sm_hdrmod->sm_hm_hdr = Hdr_pre;
sm_hdrmod->sm_hm_type = SM_HM_TYPE_PREPEND;
Hdr_pre = NULL;
if (Hdr_app != NULL)
ret = sm_hdrmod_new(&ss_ta->ssta_hdrmodhd, true, &sm_hdrmod);
}
if (Hdr_app != NULL && sm_hdrmod != NULL) {
sm_hdrmod->sm_hm_hdr = Hdr_app;
sm_hdrmod->sm_hm_type = SM_HM_TYPE_APPEND;
Hdr_app = NULL;
}
/* END HACK FOR TESTING */
}
# endif /* SM_TEST_HDRMOD */
#endif /* MTA_USE_PMILTER */
/* data MUST now be safely stored.... */
if (ss_ta->ssta_dfp != NULL) {
int flags;
flags = SM_IO_CF_SYNC;
#if SS_TEST
if (Unsafe > 0)
flags = SM_IO_CF_NONE;
#endif
#if SS_TEST
if (Unsafe <= 1) {
#endif
ret = cdb_close(cdb_ctx, ss_ta->ssta_dfp, flags);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, close_cdb=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
SS_PHASE_DOT, true);
goto error;
}
ss_ta->ssta_dfp = NULL;
#if SS_TEST
}
#endif
}
#if MTA_USE_PMILTER && MTA_USE_RSAD
if (SSTA_IS_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD)) {
ret = ss_rsad_set_replies(ss_ctx, ss_sess, ss_ta);
if (SMTP_R_SSD == ret)
goto errorreply;
/* overall status: error -> don't accept */
if (ret != SMTP_OK) {
(void) ss_rsad_send_replies(ss_ctx, ss_sess, ss_ta);
goto errorreply;
}
}
#endif /* MTA_USE_PMILTER && MTA_USE_RSAD */
ret = sm_s2q_1taid(ss_sess, ss_ctx->ssc_s2q_ctx);
if (sm_is_err(ret)) {
/* change error */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, sm_s2q_1taid=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
ret = SMTP_R_SSD;
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, true);
goto error;
}
ret = sm_w4q2s_reply(ss_sess, TMO_W4Q2S, ss_ctx->ssc_s2q_ctx);
if (sm_is_err(ret)) {
/* change error */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, where=ss_data, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ss_ta->ssta_id, ret);
ret = SMTP_R_SSD;
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, true);
goto error;
}
#if MTA_USE_PMILTER && MTA_USE_RSAD
if (SSTA_IS_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD))
ret = ss_rsad_send_replies(ss_ctx, ss_sess, ss_ta);
#endif
if (SM_SUCCESS == ret) {
SS_DATA_GOT_IT(ss_sess, ss_ta);
}
else {
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, false);
goto error;
}
ss_ta->ssta_state = SSTA_ST_DOT; /* NONE ? */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 3,
"sev=INFO, ss_sess=%s, ss_ta=%s, msgid=%#N, size=%lu, stat=0"
, ss_sess->ssse_id, ss_ta->ssta_id
, ss_ta->ssta_msgid, (ulong) ss_ta->ssta_msg_sz_b);
ret = SMTP_OK;
/* clear transaction data */
ss_ta_clr(ss_ta);
ss_sess->ssse_nopcmds = 0;
return ret;
errorreply:
(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, true);
error:
/* Remove message */
if (ss_ta->ssta_dfp != NULL) {
res = cdb_abort(cdb_ctx, ss_ta->ssta_dfp);
ss_ta->ssta_dfp = NULL;
SSTA_CLR_FLAG(ss_ta, SSTA_FL_CDB_EXISTS);
}
else if (SSTA_IS_FLAG(ss_ta, SSTA_FL_CDB_EXISTS))
res = cdb_unlink(cdb_ctx, ss_ta->ssta_id);
else
res = SM_SUCCESS;
if (sm_is_err(res))
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_ERR, 4,
"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, cdb_%s=%m"
, ss_sess->ssse_id, ss_ta->ssta_id
, SSTA_IS_FLAG(ss_ta, SSTA_FL_CDB_EXISTS)
? "unlink" : "abort"
, res);
if (SM_SUCCESS == ret) {
/* pretend we got it: this works only for DISCARD !*/
SM_ASSERT(SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD));
SS_DATA_GOT_IT(ss_sess, ss_ta);
}
(void) ss_ta_abort(ss_sess, ss_ta);
/* returning an error will cause a session abort! */
return ret;
}
/*
** SS_TA_ZERO -- initialize transaction context (first time)
** ss_ta is NOT allocated, it is a local variable in ss_hdl_session()
**
** Parameters:
** ss_ta -- SMTP server transaction context
**
** Returns:
** SM_SUCCESS
*/
static sm_ret_T
ss_ta_zero(ss_ta_P ss_ta)
{
sm_memzero(ss_ta, sizeof(*ss_ta));
return SM_SUCCESS;
}
/*
** SS_HDL_SESSION -- Session handling function stub.
**
** Parameters:
** ss_sess -- SMTP server session context
** must be freed after use.
** sessok -- is it ok to accept the session?
**
** Returns:
** usual return code
*/
sm_ret_T
ss_hdl_session(ss_sess_P ss_sess, sm_ret_T sessok)
{
int r;
sm_ret_T ret;
ssize_t b;
bool dontlog;
char *buf;
ss_ta_T ss_ta; /* should be malloc()ated? */
ss_ctx_P ss_ctx;
SM_IS_SS_SESS(ss_sess);
dontlog = false;
ss_ctx = ss_sess->ssse_sctx;
SM_IS_SS_CTX(ss_ctx);
ss_ta_zero(&ss_ta);
/* don't accept at all? */
if (SMTP_R_SSD == sessok) {
(void) ss_crt_reply(ss_sess->ssse_wr, sessok, SS_PHASE_INIT, false);
(void) ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, true);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 11,
"sev=INFO, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, client_name=%C, stat=%d, text=%@T"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
, ss_sess->ssse_cltname
, sessok, ss_sess->ssse_wr);
if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER, 11))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
goto err_cseid;
}
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 13,
"sev=DBG, func=ss_hdl_session, pid=%d, idx=%d, ss_sess=%s, hdl_session=%d"
, (int) My_pid, ss_ctx->ssc_id, ss_sess->ssse_id, sessok);
ret = ss_ta_clr(&ss_ta);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_hdl_session, ss_sess=%s, ss_ta_clr=%m"
, ss_sess->ssse_id, ret);
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_SSD, SS_PHASE_INIT, false);
(void) ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, true);
goto err_no_ta;
}
ss_sess->ssse_state = SSSE_ST_CONNECTED;
ss_sess->ssse_ta = &ss_ta;
/* Check for "illegal" pipelining. NOT for smtp over SSL! */
if (sm_io_getinfo(ss_sess->ssse_fp, SM_IO_IS_READABLE, NULL)) {
bool iseof;
/*
** RFC 2821 says only "SHOULD", not must wait for greeting.
** Hence this must be optional... (override per IP address?)
*/
iseof = false;
#ifdef FIONREAD
/* alternative: try to read one byte, session terminates */
r = 1;
(void) ioctl(f_fd(*(ss_sess->ssse_fp)), FIONREAD, &r);
iseof = (0 == r);
#endif
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 3,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=%s_before_greeting"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
, iseof ? "EOF" : "data");
if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER, 3))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
ret = sm_error_temp(SM_EM_SMTPS, SM_E_ILL_PIPE);
dontlog = true;
sm_str_clr(ss_sess->ssse_wr);
if (!iseof && !SSSE_IS_CFLAG(ss_sess, SSSE_CFL_DATA_BEFORE_GREET))
{
sm_str_scopy(ss_sess->ssse_wr,
"421 client SHOULD wait for greeting\r\n");
goto errreply;
}
goto error;
}
/* create greeting */
sm_str_scopy(ss_sess->ssse_wr, "220 ");
sm_str_cat(ss_sess->ssse_wr, ss_ctx->ssc_hostname);
sm_str_scat(ss_sess->ssse_wr, " ESMTP " MTA_GREETING "\r\n");
ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, true);
if (ret != SMTP_OK) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 9,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, write_greeting=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER,
SS_LMOD_SERVER, 9))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
goto error;
}
ss_sess->ssse_state = SSSE_ST_GREETED;
if (!SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY)) {
r = regexec(&ss_ctx->ssc_relayfrom, inet_ntoa(ss_sess->ssse_client),
(size_t) 0, (regmatch_t *) NULL, 0);
if (0 == r)
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
}
(void) ss_conn_log(ss_sess, 9, "ss_hdl_session");
/*
** Note: the ss_*() commands must return SM_SUCCESS almost always;
** a (fatal) error must only be returned if the session should
** be terminated. Maybe the functions can return a warning.
*/
while ((ret = ss_read_cmd(ss_sess)) == SMTP_OK) {
/*
** RFC 2821: 4.1.1 Command Semantics and Syntax
** SMTP commands are character strings terminated by
** <CRLF>. The commands themselves are alphabetic characters
** terminated by <SP> if parameters follow and <CRLF>
** otherwise. (In the interest of improved interoperability,
** SMTP receivers are encouraged to tolerate trailing white
** space before the terminating <CRLF>.)
*/
r = sm_str_getlen(ss_sess->ssse_rd);
if (0 == r) {
if (sm_eof(ss_sess->ssse_fp)) {
/* can't happen?? see ss_read_cmd() */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 6,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=EOF_without_QUIT"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr);
goto error;
}
switch (errno) {
case 0:
break;
case EINTR:
continue;
case ETIMEDOUT:
case EAGAIN:
default:
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 2,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=read_error, error=%m"
, ss_sess->ssse_id
, ss_sess->ssse_client.s_addr
, sm_err_temp(errno));
if (sm_log_wouldlog(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, 3))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
goto error;
}
}
(void) sm_str_rm_trail_sp(ss_sess->ssse_rd);
buf = (char *)sm_str_getdata(ss_sess->ssse_rd);
if (NULL == buf) {
(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_SSD,
SS_PHASE_OTHER, false);
(void) ss_reply(ss_sess, ss_sess->ssse_wr,
ss_sess->ssse_fp, -1, true);
goto errreply;
}
if (ss_ctx->ssc_cnf.ss_cnf_debug > 5)
sm_io_write(smioerr, (uchar *)buf, r, &b);
/*
** Try to find command and invoke corresponding function.
** Expected return codes:
** sm_is_err(): return an error to client and abort!
** SS_NO_REPLY: don't send a reply (only some commands)
** default: send reply stored in ss_sess->ssse_wr
*/
if (strcasecmp(buf, "quit") == 0) {
sm_str_scopy(ss_sess->ssse_wr, "221 2.0.0 Bye\r\n");
ss_sess->ssse_state = SSSE_ST_QUIT;
(void) ss_ta_abort(ss_sess, &ss_ta);
}
else if (strncasecmp(buf, "helo", 4) == 0) {
ret = ss_ehlo(ss_sess, false);
if (sm_is_err(ret))
goto errreply;
}
else if (strncasecmp(buf, "ehlo", 4) == 0) {
ret = ss_ehlo(ss_sess, true);
if (sm_is_err(ret))
goto errreply;
}
#if MTA_USE_TLS
else if (strncasecmp(buf, "starttls", 8) == 0) {
ret = ss_tls(ss_sess);
if (sm_is_err(ret))
goto errreply;
if (SS_NO_REPLY == ret)
continue;
}
#endif /* MTA_USE_TLS */
#if MTA_USE_SASL
else if (strncasecmp(buf, "auth ", 5) == 0) {
ret = ss_auth(ss_sess);
SS_CONN_LOG(ss_sess, 15, "ss_hdl_session");
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 15,
"sev=INFO, func=ss_hdl_session, ss_sess=%s, auth=%#x"
, ss_sess->ssse_id, ret);
if (sm_is_err(ret))
goto errreply;
if (SS_NO_REPLY == ret)
continue;
}
#endif /* MTA_USE_SASL */
else if (strncasecmp(buf, "mail", 4) == 0) {
ret = ss_mail(ss_sess);
if (sm_is_err(ret))
goto errreply;
}
else if (strncasecmp(buf, "rcpt", 4) == 0) {
ret = ss_rcpt(ss_sess);
if (sm_is_err(ret))
goto errreply;
}
else if (strcasecmp(buf, "data") == 0) {
ret = ss_data(ss_sess);
if (sm_is_err(ret))
goto errreply;
TA_COUNT(ss_sess->ssse_idx)++;
}
else if (strcasecmp(buf, "rset") == 0) {
ret = ss_rset(ss_sess);
if (sm_is_err(ret))
goto errreply;
}
else if (strncasecmp(buf, "expn", 4) == 0 ||
strncasecmp(buf, "vrfy", 4) == 0)
{
ret = ss_nopcmd(ss_sess, false, "502 5.7.0 Nope\r\n");
if (sm_is_err(ret))
goto errreply;
}
else if (strncasecmp(buf, "noop", 4) == 0) {
ret = ss_nopcmd(ss_sess, false, SS_R_OK);
if (sm_is_err(ret))
goto errreply;
}
else if (strcasecmp(buf, "help") == 0) {
sm_str_scopy(ss_sess->ssse_wr,
"250 2.0.0 http://www.ietf.org/rfc/rfc2821.txt\r\n");
}
else if (strncasecmp(buf, "connect", 7) == 0 ||
strncasecmp(buf, "post", 4) == 0 ||
strncasecmp(buf, "user", 4) == 0 ||
strncasecmp(buf, "get", 3) == 0)
{
/* todo: more proxy commands? */
sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 No HTTP\r\n");
ss_sess->ssse_state = SSSE_ST_QUIT;
}
else {
/* increment error counter */
ret = ss_badcmd(ss_sess, false, "500 5.5.1 What?\r\n");
if (sm_is_err(ret))
goto errreply;
/* maybe sleep... */
st_sleep(1);
}
#if 0
sm_log_write(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
"sev=INFO, func=ss_hdl_session, ss_sess=%s, reply=%S", ss_sess->ssse_id, ss_sess->ssse_wr);
#endif
if (SMTP_R_SSD == ret)
ss_sess->ssse_state = SSSE_ST_QUIT;
if (ret != SMTP_NO_REPLY)
ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
else
ret = SMTP_OK;
sm_str_clr(ss_sess->ssse_wr);
if (ret != SMTP_OK) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 2,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=write_error, ret=%m"
, ss_sess->ssse_id
, ss_sess->ssse_client.s_addr, ret);
if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER,
SS_LMOD_SERVER, 2))
SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
goto error;
}
if (SSSE_ST_QUIT == ss_sess->ssse_state) {
/* close transaction and session */
RQST_COUNT(ss_sess->ssse_idx)++;
goto done;
}
}
if (SM_IO_EOF == ret) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 11,
"sev=INFO, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=EOF_without_QUIT, se_state=0x%x, ta_state=0x%x"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
, ss_sess->ssse_state, ss_ta.ssta_state);
sm_str_clr(ss_sess->ssse_wr);
goto error;
}
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=read_error, ret=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
sm_str_clr(ss_sess->ssse_wr);
goto error;
}
done: /* same cleanup for now as in error */
errreply:
if (sm_str_getlen(ss_sess->ssse_wr) > 0)
(void) ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp,
dontlog ? 14 : -1, true);
error:
if (sm_is_err(ret) && (r = sm_str_getlen(ss_sess->ssse_wr)) > 0
&& !dontlog)
{
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 8,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=%@T, ret=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
, ss_sess->ssse_wr, ret);
}
(void) ss_ta_abort(ss_sess, &ss_ta);
err_no_ta:
(void) sm_rcb_close_decn(ss_sess->ssse_rcb);
err_cseid:
ret = sm_s2q_cseid(ss_sess, ss_ctx->ssc_s2q_ctx, ss_sess->ssse_id);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, sm_s2q_cseid=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
}
#if MTA_USE_PMILTER
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_PM_CALLED)) {
ret = sm_s2m_cseid(ss_sess, ss_ctx->ssc_s2m_ctx, ss_sess->ssse_id);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 5,
"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, sm_s2m_cseid=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
}
}
#endif /* MTA_USE_PMILTER */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 13,
"sev=DBG, func=ss_hdl_session, ss_sess=%s, status=closing"
, ss_sess->ssse_id);
ss_sess_free(ss_sess);
/* always success... */
return SM_SUCCESS;
}
syntax highlighted by Code2HTML, v. 0.9.1