/*
* Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 2006 Claus Assmann
*
* 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: smclt.c,v 1.243 2007/09/01 15:44:26 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/string.h"
#include "sm/str.h"
#include "sm/io.h"
#include "sm/ctype.h"
#include "sm/net.h"
#include "sm/cdb.h"
#include "sm/reccom.h"
#include "sm/da.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#include "smtpc.h"
#include "log.h"
#include "statethreads/st.h"
#if SM_STATETHREADS_DEBUG
/* HACK for debugging, otherwise structs are hidden */
# include "statethreads/common.h"
#endif
#if SM_SMTPC_HDRMOD_TEST
extern sm_cstr_P Hdr_pre, Hdr_app;
#endif
static sm_ret_T sc_command(sc_t_ctx_P _sc_t_ctx, int _phase);
/*
** macros for sc_rd_reply() phase
** This is bit of a hack: it combines a state with a flag
*/
#define SC_PIPELINE_OK 0x0100 /* don't need to read reply now */
#define SC_PHASE_OTHER (0|SC_PIPELINE_OK) /* no special treatment */
#define SC_PHASE_INIT 1 /* initial 220 greeting */
#define SC_PHASE_EHLO 2 /* EHLO response */
#define SC_PHASE_TLS 3 /* STARTTLS response */
#define SC_PHASE_MAIL (4|SC_PIPELINE_OK) /* MAIL response */
#define SC_PHASE_RCPT (5|SC_PIPELINE_OK) /* RCPT response */
#define SC_PHASE_DATA 6 /* DATA response */
#define SC_PHASE_LDAT 7 /* LMTP DATA response */
#define SC_PHASE_DOT 8 /* response to final dot */
#define SC_PHASE_RSAD 9 /* RCPT status after final dot */
#define SC_PHASE_QUIT 10 /* QUIT */
#define SC_PHASE_NOREPLY 11 /* no reply requested */
#define sc_pipeline_ok(phase) (((phase) & SC_PIPELINE_OK) != 0)
/* 3 digits reply code plus ' ' or '-' */
#define SMTP_REPLY_OFFSET 4
/*
** SC_EHLO_OPTIONS -- read server capabilities
**
** Parameters:
** sc_sess -- SMTPC session context
**
** Returns:
** usual sm_error code
** (currently only SM_SUCCESS)
*/
static sm_ret_T
sc_ehlo_options(sc_sess_P sc_sess)
{
char *opt;
SM_IS_SC_SE(sc_sess);
/* "250 " + something useful: at least 2 characters??? */
if (sm_str_getlen(sc_sess->scse_rd) < 6)
return SM_SUCCESS;
if (sm_str_rd_elem(sc_sess->scse_rd, 0) != '2' ||
sm_str_rd_elem(sc_sess->scse_rd, 1) != '5' ||
sm_str_rd_elem(sc_sess->scse_rd, 2) != '0' ||
(sm_str_rd_elem(sc_sess->scse_rd, 3) != ' ' &&
sm_str_rd_elem(sc_sess->scse_rd, 3) != '-'))
return SM_SUCCESS; /* error?? */
#define SMTPC_IS_CAPN(str) (sm_strcaseeqn(opt, (str), strlen(str)))
#define SMTPC_IS_CAP(str) (sm_strcaseeq(opt, (str)))
opt = (char *) sm_str_getdata(sc_sess->scse_rd);
SM_REQUIRE(opt != NULL);
opt += SMTP_REPLY_OFFSET;
if (SMTPC_IS_CAP("pipelining"))
SCSE_SET_CAP(sc_sess, SCSE_CAP_PIPELINING);
else if (SMTPC_IS_CAP("8bitmime"))
SCSE_SET_CAP(sc_sess, SCSE_CAP_8BITMIME);
else if (SMTPC_IS_CAP("size"))
SCSE_SET_CAP(sc_sess, SCSE_CAP_SIZE);
else if (SMTPC_IS_CAPN("size ")) {
ulonglong_T max_sz_b; /* off_t? */
char *endptr;
errno = 0;
/* 5 == strlen("size ") */
max_sz_b = sm_strtoull(opt + 5, &endptr, 10);
if (!((max_sz_b == ULLONG_MAX && errno == ERANGE) || endptr == opt + 5))
{
SCSE_SET_CAP(sc_sess, SCSE_CAP_SIZE);
sc_sess->scse_max_sz_b = max_sz_b;
}
/* else: silently ignore ... */
}
else if (SMTPC_IS_CAP("enhancedstatuscodes"))
SCSE_SET_CAP(sc_sess, SCSE_CAP_ENHSTAT);
else if (SMTPC_IS_CAPN("auth "))
SCSE_SET_CAP(sc_sess, SCSE_CAP_AUTH);
else if (SMTPC_IS_CAP("starttls"))
SCSE_SET_CAP(sc_sess, SCSE_CAP_STARTTLS);
#if MTA_USE_RSAD
else if (SMTPC_IS_CAP("prdr"))
SCSE_SET_CAP(sc_sess, SCSE_CAP_RSAD);
#endif
return SM_SUCCESS;
}
/*
** SC_TALKINGTOMYSELF -- does the server greet us with my own name?
**
** Parameters:
** sc_sess -- SMTPC session context
**
** Returns:
** usual sm_error code
*/
static sm_ret_T
sc_talkingtomyself(sc_sess_P sc_sess)
{
sm_str_P hn;
size_t l;
SM_IS_SC_SE(sc_sess);
if (SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP|SCSE_FL_NOTTM))
return SM_SUCCESS;
/* "250 " + something useful: at least 1 characters */
if (sm_str_getlen(sc_sess->scse_rd) < 5)
return SM_SUCCESS;
if (sm_str_rd_elem(sc_sess->scse_rd, 0) != '2' ||
sm_str_rd_elem(sc_sess->scse_rd, 1) != '5' ||
sm_str_rd_elem(sc_sess->scse_rd, 2) != '0' ||
(sm_str_rd_elem(sc_sess->scse_rd, 3) != ' ' &&
sm_str_rd_elem(sc_sess->scse_rd, 3) != '-'))
return SM_SUCCESS; /* error?? */
hn = sc_sess->scse_sct_ctx->sct_sc_ctx->scc_hostname;
l = sm_str_getlen(hn) + SMTP_REPLY_OFFSET;
if (sm_str_getlen(sc_sess->scse_rd) >= l
&& (strncasecmp((char *) (sm_str_getdata(sc_sess->scse_rd) +
SMTP_REPLY_OFFSET),
(char *) sm_str_getdata(hn), l - SMTP_REPLY_OFFSET) == 0)
&& (sm_str_getlen(sc_sess->scse_rd) == l ||
(sm_str_getlen(sc_sess->scse_rd) > l &&
sm_str_rd_elem(sc_sess->scse_rd, l) == ' '))
)
{
return sm_error_perm(SM_EM_SMTPC, SM_E_TTMYSELF);
}
return SM_SUCCESS;
}
/*
** SC_RD_REPLY -- read reply/replies from SMTP server
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** phase -- phase of SMTP dialogue
**
** Returns:
** SMTP reply code (2xy -> SMTP_OK) or error code
**
** Side Effects:
** If the server responded:
** sc_sess->scse_str contains entire response (provided it is
** big enough to hold all data);
** sc_sess->scse_rd contains last line of response
** (identical for single line responses).
*/
static sm_ret_T
sc_rd_reply(sc_t_ctx_P sc_t_ctx, int phase)
{
sm_ret_T ret, ttmyself;
uint thr_id;
int rcode;
bool firstline;
bool used; /* reply has been used for MAIL/RCPT? */
sc_sess_P sc_sess;
#if SC_PIPELINING
sc_ta_P sc_ta;
#endif
sm_ret_T dot_ret;
SM_IS_SC_T_CTX(sc_t_ctx);
/* no need to wait for reply to QUIT */
if (SC_PHASE_QUIT == phase &&
!SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_confflags,
SCC_CFL_READ_QUIT))
return SM_SUCCESS;
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
thr_id = sc_t_ctx->sct_thr_id;
ttmyself = SM_SUCCESS;
#if SC_PIPELINING
sc_ta = sc_sess->scse_ta;
again:
#endif
dot_ret = SMTP_NO_REPLY;
firstline = true;
used = false;
rcode = -1;
sm_str_clr(sc_sess->scse_str);
do {
#if SC_PIPELINING
/* also check how much data has been sent? */
if (sc_pipeline_ok(phase)
&& SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING)
&& !sm_io_getinfo(sc_sess->scse_fp, SM_IO_IS_READABLE, NULL))
return SMTP_NO_REPLY;
#endif /* SC_PIPELINING */
sm_str_clr(sc_sess->scse_rd);
if (sc_sess->scse_fp == NULL || SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR))
ret = EOF;
else
ret = sm_fgetline0(sc_sess->scse_fp, sc_sess->scse_rd);
/* timeout error: EOF */
#if SC_DEBUG
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3) {
ssize_t b;
/*
** multiple statements to print one entry...
** non-trivial to convert to logging.
*/
sm_io_fprintf(smioerr,
"thread=%u, da_sess=%s, read [len=%d, res=%r]: ",
thr_id, sc_sess->scse_id, sm_str_getlen(sc_sess->scse_rd), ret);
#if SC_PIPELINING
if (sc_ta != NULL)
sm_io_fprintf(smioerr,
"[flags=%x, rcpts_rcvd=%d, snt=%d]: ",
sc_ta->scta_state,
sc_ta->scta_rcpts_rcvd,
sc_ta->scta_rcpts_snt);
#endif /* SC_PIPELINING */
/*
** WARNING: data directly from server!
** May contain "bogus" characters.
*/
sm_io_write(smioerr, sm_str_data(sc_sess->scse_rd),
sm_str_getlen(sc_sess->scse_rd), &b);
sm_io_flush(smioerr);
}
#endif /* SC_DEBUG */
if (sm_is_err(ret)) {
if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED) &&
!SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR))
{
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO,
SCSE_IS_FLAG(sc_sess, SCSE_FL_SSD)
? 14 : 9,
"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, phase=%#x, ret=%m",
thr_id, sc_sess->scse_id, phase, ret);
}
/* I/O error (always??) */
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR|SCSE_FL_LOGGED);
goto fail;
}
if (SC_PHASE_INIT == phase) {
/* question: perform a "loops back to me" check?? */
}
else if (SC_PHASE_EHLO == phase) {
if (firstline) {
ttmyself = sc_talkingtomyself(sc_sess);
}
else {
/* get server capabilities */
ret = sc_ehlo_options(sc_sess);
}
}
else if (sm_is_success(ret) && IS_SMTP_REPLY_STR(sc_sess->scse_rd, 0)) {
int r;
r = SMTP_REPLY_STR2VAL(sc_sess->scse_rd, 0);
if (!firstline && rcode != r) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_WARN, 9,
"sev=WARN, func=sc_rd_reply, thread=%u, da_sess=%s, phase=%#x, status=inconsistent_multiline_reply_codes, previous=%d, current=%d"
, thr_id, sc_sess->scse_id, phase
, rcode, r);
}
rcode = r;
}
/* other cases? */
/* bogus response? check every line, not just last */
if (sm_is_success(ret) && !IS_SMTP_REPLY_STR(sc_sess->scse_rd, 0))
ret = SMTPC_PROT;
if (!firstline)
(void) sm_str_put(sc_sess->scse_str, ' ');
(void) sm_str_cat(sc_sess->scse_str, sc_sess->scse_rd);
firstline = false;
} while (sm_is_success(ret) && sm_str_getlen(sc_sess->scse_rd) > 3 &&
sm_str_rd_elem(sc_sess->scse_rd, 3) == '-');
if (sm_is_err(ttmyself))
ret = ttmyself;
if (sm_is_err(ret) || sm_str_getlen(sc_sess->scse_rd) < 3)
goto fail;
/*
** Note: in case of a multi-line reply sc_sess->scse_rd contains
** now the last line... we may have to store all of them for
** better error handling, i.e., to return the complete reply
** to a user in case of a DSN or for logging.
*/
#if SC_DEBUG
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3
&& phase == SC_PHASE_EHLO)
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 17,
"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, capabilities=%x",
thr_id, sc_sess->scse_id, sc_sess->scse_cap);
#endif /* SC_DEBUG */
if (sm_str_rd_elem(sc_sess->scse_rd, 0) == '2')
ret = SMTP_OK;
else if (!IS_SMTP_REPLY_STR(sc_sess->scse_rd, 0))
ret = SMTPC_PROT;
else {
ret = SMTP_REPLY_STR2VAL(sc_sess->scse_rd, 0);
if (SMTP_R_SSD == ret)
SCSE_SET_FLAG(sc_sess, SCSE_FL_SSD);
}
#if SC_PIPELINING
/*
* do we need to collect more responses (SC_PHASE_LDAT)
* or find the right command for a reponse?
*/
if (SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING) &&
(phase == SC_PHASE_RCPT
|| phase == SC_PHASE_DATA
|| phase == SC_PHASE_LDAT
#if MTA_USE_RSAD
|| phase == SC_PHASE_RSAD
#endif
))
{
SM_IS_SC_TA(sc_ta);
if (!SCTA_IS_STATE(sc_ta, SCTA_MAIL_R)) {
/* reply for MAIL */
SCTA_SET_STATE(sc_ta, SCTA_MAIL_R);
sc_ta->scta_mail->scm_st = ret;
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, mail=%@S, stat=%m, reply=%@S",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_ta->scta_mail->scm_pa, ret,
sc_sess->scse_rd);
if (ret != SMTP_OK) {
sc_ta->scta_mail->scm_reply = sm_str_dup(sc_ta->scta_rpool,
sc_sess->scse_rd);
sc_ta->scta_err_state = DA_TA_ERR_MAIL_S;
sc_ta->scta_status = ret;
SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
? SCTA_FAIL_PERM : SCTA_FAIL_TEMP);
}
ret = SMTP_NO_REPLY;
used = true;
}
else if (sc_ta->scta_rcpts_rcvd < sc_ta->scta_rcpts_snt) {
sc_rcpt_P sc_rcpt;
/* only check rcpts that have been sent to the server */
do {
sc_rcpt = sc_ta->scta_rcpt_p;
if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT))
sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
} while (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT) &&
sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
SM_ASSERT(sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
if (SMTP_OK == ret) {
sc_rcpt->scr_st = ret;
#if SC_STATS
++SC_RCPT_CNT_OK(sc_t_ctx->sct_sc_ctx);
#endif
}
/*
** Set error state only if there wasn't a transaction
** error yet, i.e., if MAIL was successful, otherwise
** the RCPT commands are irrelevant anyway.
*/
else if (!SMTPC_R_IS_OK(ret) &&
!SCTA_IS_STATE(sc_ta, SCTA_FAIL_PERM|SCTA_FAIL_TEMP))
{
sc_rcpt->scr_st = ret;
sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool,
sc_sess->scse_rd);
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
? SCTA_R_PERM : SCTA_R_TEMP);
if (smtp_is_reply_temp(ret))
{
/*
** Save temporary failure state for
** later perusal if necessary
*/
sc_ta->scta_status = ret;
}
}
if (ret != SMTP_NO_REPLY &&
!SCTA_IS_STATE(sc_ta, SCTA_FAIL_PERM|SCTA_FAIL_TEMP) &&
!SCR_IS_FLAG(sc_rcpt, SCR_FL_LOGGED))
{
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, rcpt=%@S, stat=%m, reply=%@S",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_rcpt->scr_pa, ret,
sc_sess->scse_rd);
SCR_SET_FLAG(sc_rcpt, SCR_FL_LOGGED);
}
#if SC_DEBUG
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 15,
"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, rcpt=%@S, idx=%d, stat=%m, scta_rcpts_rcvd=%d, scta_rcpts_snt=%d, reply=%p, err_st=%x, state=%x",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_rcpt->scr_pa,
sc_rcpt->scr_idx, ret,
sc_ta->scta_rcpts_rcvd,
sc_ta->scta_rcpts_snt,
sc_rcpt->scr_reply,
sc_ta->scta_err_state,
sc_ta->scta_state);
#endif /* SC_DEBUG */
if (SMTP_OK == ret)
++sc_ta->scta_rcpts_ok;
++sc_ta->scta_rcpts_rcvd;
sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
ret = SMTP_NO_REPLY;
if (sc_ta->scta_rcpts_rcvd == sc_ta->scta_rcpts_snt)
SCTA_SET_STATE(sc_ta, SCTA_RCPT_R);
used = true;
}
/* need to collect all replies? */
if (SC_PHASE_DATA == phase) {
if (used)
goto again;
SCTA_SET_STATE(sc_ta, SCTA_DATA_R);
}
}
#endif /* SC_PIPELINING */
if (SC_PHASE_LDAT == phase) {
/*
* LMTP RCPT status after final dot:
* there is no reply for data, all replies are for
* RCPTs that have been "OK"ed before (250).
*/
sc_rcpt_P sc_rcpt;
/* only check rcpts that have been sent to the server */
do {
sc_rcpt = sc_ta->scta_rcpt_p;
if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT))
sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
} while (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT) &&
sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
SM_ASSERT(sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
/* only collect replies for "OK" recipients */
while (sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts)
&& sc_rcpt->scr_st != SMTP_OK)
sc_rcpt = SC_RCPTS_NEXT(sc_rcpt);
if (sc_rcpt == SC_RCPTS_END(&sc_ta->scta_rcpts)) {
/* went beyond the list... */
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 1,
"sev=ERROR, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=LMTP, status=end_of_list",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id);
return ret;
}
#if SC_DEBUG
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_DEBUG, 15,
"sev=DBG, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, LMTP_rcpt=%S, idx=%d, stat=%m, rcpts_lmtp=%d",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_rcpt->scr_pa,
sc_rcpt->scr_idx, ret,
sc_ta->scta_rcpts_dot);
#endif /* SC_DEBUG */
sc_rcpt->scr_st = ret;
if (ret != SMTP_OK) {
sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool,
sc_sess->scse_rd);
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
--sc_ta->scta_rcpts_ok;
}
/* Hack: use the "lowest" value for dot_ret */
if (dot_ret > ret)
dot_ret = ret;
--sc_ta->scta_rcpts_dot;
sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
/* all responses collected? */
if (0 == sc_ta->scta_rcpts_dot) {
/*
** RFC2033 4.2: there is no separate response
** for DATA, but only responses for previously
** accepted recipients, hence we don't set used,
** but use dot_ret as return value.
*/
SCTA_SET_STATE(sc_ta, SCTA_L_RCPT_R);
ret = dot_ret;
}
else {
used = true;
ret = SMTP_NO_REPLY;
}
if (used)
goto again;
SCTA_SET_STATE(sc_ta, SCTA_DOT_R);
}
#if MTA_USE_RSAD
/* RCPT status after final dot */
if (SC_PHASE_RSAD == phase) {
sc_rcpt_P sc_rcpt;
/*
* Maybe restructure this?
* if (SC_PHASE_RSAD == phase && !SCTA_IS_STATE(sc_ta, SCTA_DOT_R))
* ...
* if (SC_PHASE_RSAD == phase && !SCTA_IS_STATE(sc_ta, SCTA_D_RCPT_R))
* ...
*/
if (!SCTA_IS_STATE(sc_ta, SCTA_DOT_R)) {
/*
* first reply: for end of mail data
* 353: expect individual RCPT replies
* other values: overall status (and no RCPT replies!)
*/
SCTA_SET_STATE(sc_ta, SCTA_DOT_R);
/* 3yz: individual RCPT replies follow */
if (SMTP_RTYPE_CONT == smtp_reply_type(ret))
goto again;
/* else: do not try to get individual RCPT replies */
SCTA_SET_STATE(sc_ta, SCTA_D_RCPT_R);
/* this is also the final reply */
SCTA_SET_STATE(sc_ta, SCTA_DOT_F_R);
}
/* get individual RCPT replies? */
if (!SCTA_IS_STATE(sc_ta, SCTA_D_RCPT_R)) {
/* only check rcpts that have been sent to the server */
do {
sc_rcpt = sc_ta->scta_rcpt_p;
if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT))
sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
} while (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT) &&
sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
SM_ASSERT(sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
#define SMTPC_RCTP_IS_OK(ret) ((ret) == SMTP_OK || smtp_reply_type(ret) == SMTP_RTYPE_OK)
/* only collect replies for "OK" recipients */
while (sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts)
&& !SMTPC_RCTP_IS_OK(sc_rcpt->scr_st))
sc_rcpt = SC_RCPTS_NEXT(sc_rcpt);
if (sc_rcpt == SC_RCPTS_END(&sc_ta->scta_rcpts)) {
/* went beyond the list... */
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 1,
"sev=ERROR, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=RSAD, status=end_of_list",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id);
return ret;
}
#if SC_DEBUG || 1
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_DEBUG, 15,
"sev=DBG, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, RSAD=%S, idx=%d, stat=%m, rcpts_dot=%d",
thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_rcpt->scr_pa,
sc_rcpt->scr_idx, ret,
sc_ta->scta_rcpts_dot);
#endif /* SC_DEBUG */
/* set reply for this RCPT */
sc_rcpt->scr_st = ret;
if (ret != SMTP_OK) {
sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool,
sc_sess->scse_rd);
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
if (SMTP_IS_REPLY_ERROR(ret))
SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
? SCTA_R_PERM : SCTA_R_TEMP);
--sc_ta->scta_rcpts_ok;
}
--sc_ta->scta_rcpts_dot;
sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
if (0 == sc_ta->scta_rcpts_dot)
SCTA_SET_STATE(sc_ta, SCTA_D_RCPT_R);
/* get another response */
ret = SMTP_NO_REPLY;
goto again;
}
/* last reply: overall status, ret will be returned to caller */
SCTA_SET_STATE(sc_ta, SCTA_DOT_F_R);
}
#endif /* MTA_USE_RSAD */
#if SC_DEBUG
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 15,
"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, phase=%d, stat=%m",
thr_id, sc_sess->scse_id, phase, ret);
#endif /* SC_DEBUG */
return ret;
fail:
return ret;
}
/*
** SC_GEN_DSN_HDR -- generate bounce header
** on successful return scse_str contains MIME delimiter for this TA.
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
**
** Returns:
** usual sm_error code
**
** Side Effects:
** on success scse_wr contains a header for a bounce message
** uses scse_str as temporary string;
** on successful return scse_str contains the MIME delimiter!
*/
static sm_ret_T
sc_gen_dsn_hdr(sc_t_ctx_P sc_t_ctx)
{
sm_ret_T ret;
time_T now;
sc_sess_P sc_sess;
sc_ta_P sc_ta;
sc_rcpt_P sc_rcpt;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_DBOUNCE) &&
!SCTA_IS_FLAG(sc_ta, SCTA_FL_BOUNCE) &&
!SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY))
return SM_SUCCESS;
now = st_time();
sm_str_clr(sc_sess->scse_str);
ret = arpadate(&now, sc_sess->scse_str);
sc_rcpt = SC_RCPTS_FIRST(&sc_ta->scta_rcpts);
/* Send header for bounce */
/* fixme: Put this in a library function */
/* requires hostname (Hack!) */
if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
"From: Mailer-Daemon@")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_t_ctx->sct_sc_ctx->scc_hostname)) ||
sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
"\r\n"
"Date: ")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_sess->scse_str)) ||
sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
"\r\nSubject: ")))
goto error;
if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY)
? "Delayed mail\r\n"
: SCTA_IS_FLAG(sc_ta, SCTA_FL_BOUNCE)
? "Undeliverable mail\r\n"
: "Double Bounce\r\n")))
goto error;
if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "To: ")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_rcpt->scr_pa)) ||
sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\r\n")))
goto error;
/* create MIME delimiter in scse_str */
sm_str_clr(sc_sess->scse_str);
if (sm_is_err(ret = sm_str_cat(sc_sess->scse_str,
sc_t_ctx->sct_sc_ctx->scc_hostname)) ||
sm_is_err(ret = sm_str_put(sc_sess->scse_str, '/')) ||
sm_is_err(ret = sm_str_scat(sc_sess->scse_str, sc_ta->scta_id)))
goto error;
/* Add MIME header here... */
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
(SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY) ||
!SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)))
{
if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/mixed; boundary=\""))
|| sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_sess->scse_str))
|| sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\"\r\n\r\n"
"This is a MIME-encapsulated message\r\n\r\n--"))
|| sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_sess->scse_str))
|| sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\r\n")))
goto error;
}
if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY)
? "\r\n"
"A mail from you has not yet been successfully delivered.\r\n"
"It will be tried again, so you do not need to resend it!\r\n"
"See below for details.\r\n\r\n"
: SCTA_IS_FLAG(sc_ta, SCTA_FL_BOUNCE)
? "\r\n"
"A mail from you could not be delivered. "
"See below for details.\r\n\r\n"
: "\r\nA bounce message could not be delivered.\r\n"
"See below for details.\r\n\r\n")))
{
goto error;
}
return SM_SUCCESS;
error:
sm_str_clr(sc_sess->scse_str);
return ret;
}
/* ----- BEGIN COPY FROM smtpc/smclt.c ----- */
/*
** SC_WRTBUF -- write buffer
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** buf -- buffer to write
** wrt -- number of bytes to write
** cfp -- output file
** ptotal_size -- (pointer to) total size (in/out)
**
** Returns:
** usual error code
*/
static sm_ret_T
sc_wrtbuf(sc_t_ctx_P sc_t_ctx, uchar *buf, size_t wrt, sm_file_T *cfp, off_t *ptotal_size)
{
size_t offset;
ssize_t byteswritten;
sm_ret_T ret;
off_t total_size;
ret = SM_SUCCESS;
if (wrt == 0)
return ret;
offset = 0;
total_size = *ptotal_size;
do {
ret = sm_io_write(cfp, buf + offset, wrt, &byteswritten);
if (sm_is_err(ret))
return ret;
if (byteswritten > 0) {
total_size += byteswritten;
offset += byteswritten;
}
/* paranoia... should this be just an if ()? */
SM_ASSERT(wrt >= byteswritten);
if (wrt > byteswritten)
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_wrtbuf, wrt=%d, written=%d, ret=%m",
wrt, byteswritten, ret);
wrt -= byteswritten;
} while (wrt > 0);
*ptotal_size = total_size;
return ret;
}
/*
** SC_MSGDSN -- send msg (with modifications for DSN)
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** ptotal_size -- (pointer to) total size (in/out)
**
** Returns:
** usual error code
*/
static sm_ret_T
sc_msgdsn(sc_t_ctx_P sc_t_ctx, off_t *ptotal_size)
{
int c;
size_t wrt, idx;
sm_ret_T ret;
uint eoh_state;
off_t total_size, cdb_rd;
sc_ta_P sc_ta;
sc_sess_P sc_sess;
sm_file_T *cfp;
static SM_DECL_EOH;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
cfp = sc_sess->scse_fp;
eoh_state = 0;
cdb_rd = 0;
total_size = *ptotal_size;
ret = SM_SUCCESS;
do {
/* get new buffer */
c = sm_rget(sc_ta->scta_cdb_fp);
if (SM_IO_EOF == c)
break;
/* +1 because we got the first char already */
wrt = f_r(*(sc_ta->scta_cdb_fp)) + 1;
cdb_rd += wrt;
/* check whether eoh is in this block */
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY) && eoh_state < SM_EOH_LEN) {
uchar *p;
p = f_bfbase(*sc_ta->scta_cdb_fp);
idx = 0;
do {
if (c == eoh[eoh_state])
++eoh_state;
else {
eoh_state = 0;
if (c == eoh[eoh_state])
++eoh_state;
}
++idx;
if (idx < wrt)
c = p[idx];
} while (eoh_state < SM_EOH_LEN && idx < wrt);
/*
** Found end of header? If yes: set the
** number of bytes to write; the buffer
** MUST end with \r\n such that the final
** dot is properly recognized (see below).
*/
if (eoh_state >= SM_EOH_LEN) {
SCTA_SET_FLAG(sc_ta, SCTA_FL_CUT_HDR);
wrt = idx;
}
}
/*
** For a MIME DSN the last 3 bytes (.\r\n)
** of cdb must not be sent.
*/
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
!SCTA_IS_FLAG(sc_ta, SCTA_FL_CDB_CUT) &&
cdb_rd + 3 >= sc_ta->scta_msg_sz_b
)
{
size_t cutoff;
SM_ASSERT(sc_ta->scta_msg_sz_b >= SM_EOT_LEN);
SM_ASSERT(cdb_rd <= sc_ta->scta_msg_sz_b);
cutoff = sc_ta->scta_msg_sz_b - cdb_rd + 3;
if (wrt > cutoff)
wrt -= cutoff;
else
wrt = 0;
SCTA_SET_FLAG(sc_ta, SCTA_FL_CUT_DOT);
}
ret = sc_wrtbuf(sc_t_ctx,
f_bfbase(*sc_ta->scta_cdb_fp), wrt, cfp, &total_size);
if (sm_is_err(ret))
goto error;
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_CUT_HDR) ||
SCTA_IS_FLAG(sc_ta, SCTA_FL_CUT_DOT))
{
/* the data above ended with \r\n (eoh) */
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME)) {
if (sm_is_err(ret =
sm_str_scopy(sc_sess->scse_wr, "\r\n--"))
|| sm_is_err(ret =
sm_str_cat(sc_sess->scse_wr, sc_sess->scse_str))
|| sm_is_err(ret =
sm_str_scat(sc_sess->scse_wr, "--\r\n.\r\n")))
goto error;
}
else
ret = sm_str_scopy(sc_sess->scse_wr, ".\r\n");
if (sm_is_err(ret))
goto error;
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
if (sm_is_err(ret))
goto error;
c = SM_IO_EOF; /* force end of loop */
}
} while (c != SM_IO_EOF);
*ptotal_size = total_size;
return ret;
error:
*ptotal_size = total_size;
return ret;
}
/*
** SC_HDRS_APP -- append headers
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** sm_hdrmod -- current header to append
** ptotal_size -- (pointer to) total size (in/out)
**
** Returns:
** usual error code
*/
static sm_ret_T
sc_hdrs_app(sc_t_ctx_P sc_t_ctx, sm_hdrmod_P sm_hdrmod, off_t *ptotal_size)
{
sm_ret_T ret;
sc_ta_P sc_ta;
sc_sess_P sc_sess;
sm_file_T *cfp;
sm_cstr_P hdr;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
cfp = sc_sess->scse_fp;
ret = SM_SUCCESS;
while (sm_hdrmod != NULL &&
SM_HM_TYPE_APPEND == sm_hdrmod->sm_hm_type
&& (hdr = sm_hdrmod->sm_hm_hdr) != NULL)
{
ret = sc_wrtbuf(sc_t_ctx, sm_cstr_data(hdr),
sm_cstr_getlen(hdr),
cfp, ptotal_size);
sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
}
return ret;
}
/*
** SC_HDRS_INS -- perform header insertions at a given position
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** hdr_count -- current header number
** writeit -- are we writing or skipping headers?
** idx -- current (read) index in file buffer
** poffset -- (ptr) current (write) offset in file buffer (in/out)
** ptotal_size -- (pointer to) total size (in/out)
**
** Returns:
** >0: number of header insertions
** <=0: usual error code
*/
static sm_ret_T
sc_hdrs_ins(sc_t_ctx_P sc_t_ctx, uint hdr_count, bool writeit, size_t idx, size_t *poffset, off_t *ptotal_size)
{
sm_ret_T ret;
uint mods;
sm_hdrmod_P sm_hdrmod;
sm_cstr_P hdr;
sc_ta_P sc_ta;
sc_sess_P sc_sess;
sm_file_T *cfp;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
mods = 0;
cfp = sc_sess->scse_fp;
if (writeit &&
(sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd)) != NULL &&
(SM_HM_TYPE_INSERT == sm_hdrmod->sm_hm_type ||
SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type) &&
hdr_count == sm_hdrmod->sm_hm_pos &&
(hdr = sm_hdrmod->sm_hm_hdr) != NULL)
{
size_t offset;
SM_ASSERT(poffset != NULL);
offset = *poffset;
SM_ASSERT(idx >= offset);
ret = sc_wrtbuf(sc_t_ctx,
f_bfbase(*sc_ta->scta_cdb_fp) + offset,
idx - offset, cfp, ptotal_size);
if (sm_is_err(ret))
return ret;
*poffset = idx;
}
while ((sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd)) != NULL &&
(SM_HM_TYPE_INSERT == sm_hdrmod->sm_hm_type ||
SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type) &&
hdr_count == sm_hdrmod->sm_hm_pos &&
(hdr = sm_hdrmod->sm_hm_hdr) != NULL)
{
++mods;
ret = sc_wrtbuf(sc_t_ctx, sm_cstr_data(hdr),
sm_cstr_getlen(hdr), cfp, ptotal_size);
if (sm_is_err(ret))
return ret;
sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
}
return (mods > 0) ? mods : SM_SUCCESS;
}
/*
** SC_MSGMOD -- send msg (possibly with modifications)
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** ptotal_size -- (pointer to) total size (in/out)
**
** Returns:
** usual error code
*/
static sm_ret_T
sc_msgmod(sc_t_ctx_P sc_t_ctx, off_t *ptotal_size)
{
int c;
size_t wrt, idx;
sm_ret_T ret;
uint eoh_state;
uint hdr_count, eol_state;
bool writeit;
off_t total_size, cdb_rd;
sc_ta_P sc_ta;
sc_sess_P sc_sess;
sm_file_T *cfp;
sm_hdrmod_P sm_hdrmod;
sm_cstr_P hdr;
static SM_DECL_EOH;
static SM_DECL_EOL;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
cfp = sc_sess->scse_fp;
eoh_state = 0;
cdb_rd = 0;
total_size = *ptotal_size;
ret = SM_SUCCESS;
hdr_count = 0;
eol_state = 0;
writeit = true;
sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
while (sm_hdrmod != NULL &&
SM_HM_TYPE_PREPEND == sm_hdrmod->sm_hm_type &&
(hdr = sm_hdrmod->sm_hm_hdr) != NULL)
{
ret = sc_wrtbuf(sc_t_ctx, sm_cstr_data(hdr),
sm_cstr_getlen(hdr), cfp, &total_size);
if (sm_is_err(ret))
goto error;
sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
}
if (sm_hdrmod != NULL &&
(SM_HM_TYPE_REMOVE == sm_hdrmod->sm_hm_type ||
SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type) &&
hdr_count == sm_hdrmod->sm_hm_pos)
writeit = false;
ret = sc_hdrs_ins(sc_t_ctx, hdr_count, false, 0, NULL, &total_size);
if (sm_is_err(ret))
goto error;
do {
size_t offset;
offset = 0;
/* get new buffer */
c = sm_rget(sc_ta->scta_cdb_fp);
if (SM_IO_EOF == c)
break;
/* +1 because we got the first char already */
wrt = f_r(*(sc_ta->scta_cdb_fp)) + 1;
cdb_rd += wrt;
/* analyse current buffer (if it contains headers) */
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY|SCTA_FL_HDR_SCAN)
&& eoh_state < SM_EOH_LEN)
{
uchar *p;
p = f_bfbase(*sc_ta->scta_cdb_fp);
idx = 0;
do {
if (c == eoh[eoh_state])
++eoh_state;
else {
eoh_state = 0;
if (c == eoh[eoh_state])
++eoh_state;
}
if (eol_state >= SM_EOL_LEN && c != ' ' && c != '\t') {
bool hdr_rm;
++hdr_count;
hdr_rm = (sm_hdrmod != NULL &&
(SM_HM_TYPE_REMOVE == sm_hdrmod->sm_hm_type ||
SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type)
&& hdr_count == sm_hdrmod->sm_hm_pos);
if (hdr_rm &&
SM_HM_TYPE_REMOVE == sm_hdrmod->sm_hm_type)
{
sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
}
if (writeit && hdr_rm) {
SM_ASSERT(idx >= offset);
ret = sc_wrtbuf(sc_t_ctx,
f_bfbase(*sc_ta->scta_cdb_fp) + offset,
idx - offset,
cfp,
&total_size);
if (sm_is_err(ret))
goto error;
offset = idx;
if (c != '\r')
writeit = false;
}
else if (!writeit && !hdr_rm) {
writeit = true;
offset = idx;
}
ret = sc_hdrs_ins(sc_t_ctx, hdr_count,
writeit, idx, &offset, &total_size);
if (ret > 0)
sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
}
/*
** End of headers reached? This is not
** entirely correct as it just checks for CR
** not CR LF, but we have to "insert" these
** headers before the second CRLF
*/
if (eol_state >= SM_EOL_LEN && c == '\r' &&
sm_hdrmod != NULL &&
SM_HM_TYPE_APPEND == sm_hdrmod->sm_hm_type
&& sm_hdrmod->sm_hm_hdr != NULL)
{
ret = sc_wrtbuf(sc_t_ctx,
f_bfbase(*sc_ta->scta_cdb_fp) + offset,
idx - offset, cfp,
&total_size);
if (sm_is_err(ret))
goto error;
ret = sc_hdrs_app(sc_t_ctx, sm_hdrmod, &total_size);
offset = idx;
}
if (c == eol[eol_state])
++eol_state;
else {
eol_state = 0;
if (c == eol[eol_state])
++eol_state;
}
++idx;
if (idx < wrt)
c = p[idx];
} while (eoh_state < SM_EOH_LEN && idx < wrt);
}
if (writeit) {
ret = sc_wrtbuf(sc_t_ctx,
f_bfbase(*sc_ta->scta_cdb_fp) + offset,
wrt - offset, cfp, &total_size);
if (sm_is_err(ret))
goto error;
}
} while (c != SM_IO_EOF);
*ptotal_size = total_size;
return ret;
error:
*ptotal_size = total_size;
return ret;
}
/* ----- END COPY FROM smtpc/smclt.c ----- */
/*
** SC_MSG -- send msg (without modifications)
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** ptotal_size -- (pointer to) total size (in/out)
**
** Returns:
** usual error code
*/
static sm_ret_T
sc_msg(sc_t_ctx_P sc_t_ctx, off_t *ptotal_size)
{
int c;
size_t wrt;
sm_ret_T ret;
off_t total_size, cdb_rd;
sc_ta_P sc_ta;
sc_sess_P sc_sess;
sm_file_T *cfp;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
cfp = sc_sess->scse_fp;
/* read data and send it out... */
cdb_rd = 0;
total_size = *ptotal_size;
ret = SM_SUCCESS;
#if SC_TEST
if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
SCC_CNF_TEST_BODY_TMO))
{
st_sleep(SEC2USEC(10));
}
#endif
do {
/* get new buffer */
c = sm_rget(sc_ta->scta_cdb_fp);
if (SM_IO_EOF == c)
break;
/* +1 because we got the first char already */
wrt = f_r(*(sc_ta->scta_cdb_fp)) + 1;
cdb_rd += wrt;
ret = sc_wrtbuf(sc_t_ctx, f_bfbase(*sc_ta->scta_cdb_fp), wrt,
cfp, &total_size);
if (sm_is_err(ret))
goto error;
} while (c != SM_IO_EOF);
*ptotal_size = total_size;
return ret;
error:
*ptotal_size = total_size;
return ret;
}
/*
** SC_DATA -- send data
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
**
** Returns:
** >=0: SMTP reply code
** <0: error code
*/
static sm_ret_T
sc_data(sc_t_ctx_P sc_t_ctx)
{
ssize_t byteswritten;
sm_ret_T ret;
off_t total_size;
sc_sess_P sc_sess;
sc_ta_P sc_ta;
sm_file_T *cfp;
cdb_ctx_P cdb_ctx;
#if SM_SMTPC_HDRMOD_TEST
sm_hdrmod_P sm_hdrmod;
#endif
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
cfp = sc_sess->scse_fp;
total_size = 0;
cdb_ctx = sc_t_ctx->sct_sc_ctx->scc_cdb_ctx;
#if SC_PIPELINING
if (NO_RCPTS(sc_ta)) {
const uchar eod[] = ".\r\n";
/* just send a single dot, see RFC 2920, 3.1 */
ret = sm_io_write(cfp, eod, 3, &byteswritten);
if (sm_is_err(ret))
goto error;
if (byteswritten > 0)
total_size += byteswritten;
}
else {
#endif /* SC_PIPELINING */
/* Compose bounce message (header, text from QMGR) */
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY|SCTA_FL_BOUNCE|SCTA_FL_DBOUNCE)) {
sm_str_P str_wr;
sm_str_clr(sc_sess->scse_wr);
if (SCSE_IS_FLAG(sc_sess, SCSE_FL_RETPATH)) {
if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
"Return-Path: ")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_ta->scta_mail->scm_pa)) ||
sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
goto error;
}
/* Create header for bounce */
ret = sc_gen_dsn_hdr(sc_t_ctx);
if (sm_is_err(ret))
goto error;
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
if (sm_is_err(ret))
goto error;
/* Hack: save wr string and replace it temporarily */
str_wr = sc_sess->scse_wr;
sc_sess->scse_wr = sc_ta->scta_b_msg;
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
sc_sess->scse_wr = str_wr;
if (sm_is_err(ret))
goto error;
total_size += sm_str_getlen(sc_ta->scta_b_msg);
sm_str_clr(sc_sess->scse_wr);
if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY))
{
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
"\r\nThe headers of your original mail follow:\r\n")))
goto error;
}
else if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
!SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY))
{
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
"\r\nYour original mail follows:\r\n")))
goto error;
}
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
(SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY) ||
!SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)))
{
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
"\r\n--"))
|| sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_sess->scse_str))
|| sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
"\r\n"
"Content-Type: message/rfc822"
"\r\n\r\n")))
goto error;
}
if (sm_str_getlen(sc_sess->scse_wr)) {
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
if (sm_is_err(ret))
goto error;
sm_str_clr(sc_sess->scse_wr);
}
}
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)) {
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "\r\n.\r\n")))
goto error;
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
if (sm_is_err(ret))
goto error;
}
else {
SM_ASSERT(sc_ta->scta_cdb_fp != NULL);
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME)) {
off_t cdb_sz_b;
ret = sm_io_getinfo(sc_ta->scta_cdb_fp, SM_IO_WHAT_SIZE, &cdb_sz_b);
if (sc_ta->scta_msg_sz_b == 0)
sc_ta->scta_msg_sz_b = cdb_sz_b;
else if (cdb_sz_b != sc_ta->scta_msg_sz_b) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERROR, 8,
"sev=ERROR, func=sc_data, status=size_mismatch, cdb_sz_b=%lu, msg_sz_b=%lu, ret=%d",
(ulong) cdb_sz_b, (ulong) sc_ta->scta_msg_sz_b,
ret);
}
}
if (SCSE_IS_FLAG(sc_sess, SCSE_FL_RETPATH) &&
!SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY|SCTA_FL_BOUNCE|SCTA_FL_DBOUNCE))
{
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
"Return-Path: ")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_ta->scta_mail->scm_pa)) ||
sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
goto error;
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
if (sm_is_err(ret))
goto error;
}
#if SM_SMTPC_HDRMOD_TEST
sm_hdrmod = NULL;
if (Hdr_pre != NULL || Hdr_app != NULL)
ret = sc_hdrmod_new(sc_ta, &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 = sc_hdrmod_new(sc_ta, &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;
}
#endif /* SM_SMTPC_HDRMOD_TEST */
if (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY|SCTA_FL_DSN_MIME))
ret = sc_msgdsn(sc_t_ctx, &total_size);
else if (!HDRMODL_EMPTY(sc_ta->scta_hdrmodhd))
ret = sc_msgmod(sc_t_ctx, &total_size);
else
ret = sc_msg(sc_t_ctx, &total_size);
if (sm_is_err(ret))
goto error;
ret = cdb_close(cdb_ctx, sc_ta->scta_cdb_fp, SM_IO_CF_NONE);
if (sm_is_err(ret))
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_data, cdb_close=%m"
, ret);
sc_ta->scta_cdb_fp = NULL;
}
#if SC_PIPELINING
}
/*
** Send QUIT here if this is only one TA (and PIPELINING is active)
** to avoid one round trip.
** However, doing this makes error handling more complicated:
** why should we abort the transaction if there is an error on QUIT?
** The server could have replied properly to the final dot.
*/
if (SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING) &&
!SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_confflags,
SCC_CFL_SEP_DOT_QUIT) &&
SCSE_IS_FLAG(sc_sess, SCSE_FL_LAST_TA|SCSE_FL_CLOSE))
{
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "QUIT\r\n")))
goto error;
ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
if (sm_is_err(ret))
goto error;
SCSE_SET_FLAG(sc_sess, SCSE_FL_QUIT_S);
}
#endif
SCTA_SET_STATE(sc_ta, SCTA_DOT_S);
ret = sc_rd_reply(sc_t_ctx,
SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
? SC_PHASE_LDAT
: SCSE_IS_FLAG(sc_sess, SCSE_FL_RSAD)
? SC_PHASE_RSAD : SC_PHASE_DOT);
if (ret != SMTP_OK && sm_str_getlen(sc_sess->scse_str) > 0) {
sc_ta->scta_reply = sm_str_dup(sc_ta->scta_rpool, sc_sess->scse_str);
}
#if SC_STATS
else
++SC_TA_CNT_OK(sc_t_ctx->sct_sc_ctx);
#endif
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_data, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=final_dot, size=%lu, stat=%m, reply=%@S",
sc_t_ctx->sct_thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
(ulong) total_size, ret, sc_sess->scse_str);
return ret;
error:
/* we have to abort the connection */
if (sc_ta->scta_cdb_fp != NULL) {
(void) cdb_close(cdb_ctx, sc_ta->scta_cdb_fp, SM_IO_CF_NONE);
sc_ta->scta_cdb_fp = NULL;
}
if (cfp != NULL) {
(void) sm_io_close(cfp, SM_IO_CF_NONE);
sc_sess->scse_fp = NULL;
sc_sess->scse_state = SCSE_ST_CLOSED; /* error state? */
}
if (sc_ta->scta_err_state == DA_ERR_NONE ||
(da_ta_err_cmd(sc_ta->scta_err_state) == DA_TA_ERR_RCPT &&
sc_ta->scta_rcpts_ok > 0))
sc_ta->scta_err_state = DA_TA_ERR_BODY_I;
return ret;
}
/*
** SC_COMMAND -- send one SMTP command, read reply (unless turned off)
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** phase -- phase of SMTP dialogue
**
** Returns:
** SMTP reply code (2xy -> SMTP_OK) or error code
*/
static sm_ret_T
sc_command(sc_t_ctx_P sc_t_ctx, int phase)
{
sm_ret_T ret;
ssize_t b;
size_t l;
uint thr_id;
sc_sess_P sc_sess;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
thr_id = sc_t_ctx->sct_thr_id;
l = sm_str_getlen(sc_sess->scse_wr);
#if SC_DEBUG
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3) {
/* multiple print statements... log? */
sm_io_fprintf(smioerr, "thread=%u, da_sess=%s, send[%d]: ", thr_id, sc_sess->scse_id, l);
sm_io_write(smioerr, sm_str_data(sc_sess->scse_wr), l, &b);
sm_io_fprintf(smioerr, "\n");
sm_io_flush(smioerr);
}
#endif /* SC_DEBUG */
do {
ret = sm_io_write(sc_sess->scse_fp,
sm_str_data(sc_sess->scse_wr), l, &b);
if (sm_is_err(ret))
{
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_WARN, 11,
"sev=WARN, func=sc_command, thread=%u, da_sess=%s, where=write, n=%d, r=%d, ret=%m",
thr_id, sc_sess->scse_id, l, (int) b, ret);
/* I/O error (always??) */
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR|SCSE_FL_LOGGED);
return ret;
}
/* paranoia... should this be just an if (..)? */
SM_ASSERT(l >= b);
l -= b;
} while (l > 0);
#if SC_PIPELINING
if (!SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING) || !sc_pipeline_ok(phase)) {
#endif
ret = sm_io_flush(sc_sess->scse_fp);
if (sm_is_err(ret)) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_WARN, 11,
"sev=WARN, func=sc_command, thread=%u, da_sess=%s, where=flush, n=%d, ret=%m",
thr_id, sc_sess->scse_id, (int) b, ret);
return ret;
}
#if SC_PIPELINING
}
#endif
if (SC_PHASE_NOREPLY == phase)
return SM_SUCCESS;
return sc_rd_reply(sc_t_ctx, phase);
}
#if MTA_USE_TLS
/*
** SC_TLSREQ -- Check STARTTLS requirements
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
** rcode -- reply code to use in case of an error
**
** Returns:
** usual return code
*/
static sm_ret_T
sc_tlsreq(sc_t_ctx_P sc_t_ctx, tlsreq_cnf_P tlsreq_cnf, sm_ret_T rcode)
{
sm_ret_T ret;
uint u;
const char *cstr;
sm_str_P str;
sc_sess_P sc_sess;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
ret = SM_SUCCESS;
if (SM_IS_FLAG(tlsreq_cnf->tlsreqcnf_flags, TLSREQ_FL_VRFD) &&
TLS_VRFY_OK != sc_sess->scse_tlsi->tlsi_vrfy)
return rcode;
u = tlsreq_cnf->tlsreqcnf_min_cipher_bits;
if (SM_IS_FLAG(tlsreq_cnf->tlsreqcnf_flags, TLSREQ_FL_ENCR) &&
0 == u)
return rcode;
if (u > 0 && u > sc_sess->scse_tlsi->tlsi_cipher_bits)
return rcode;
if ((cstr = tlsreq_cnf->tlsreqcnf_cert_subject) != NULL &&
((str = sc_sess->scse_tlsi->tlsi_cert_subject) == NULL ||
strcasecmp(cstr, sm_str_getdata(str)) != 0))
{
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT, SM_LOG_INFO, 9,
"sev=INFO, func=sc_tlsreq, cert_subject=%#S, required=%s"
, str, cstr);
return rcode;
}
if ((cstr = tlsreq_cnf->tlsreqcnf_cert_issuer) != NULL &&
((str = sc_sess->scse_tlsi->tlsi_cert_issuer) == NULL ||
strcasecmp(cstr, sm_str_getdata(str)) != 0))
{
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT, SM_LOG_INFO, 9,
"sev=INFO, func=sc_tlsreq, cert_issuer=%#S, required=%s"
, str, cstr);
return rcode;
}
if ((cstr = tlsreq_cnf->tlsreqcnf_common_name) != NULL &&
((str = sc_sess->scse_tlsi->tlsi_cn_subject) == NULL ||
strcasecmp(cstr, sm_str_getdata(str)) != 0))
{
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT, SM_LOG_INFO, 9,
"sev=INFO, func=sc_tlsreq, common_name=%#S, required=%s"
, str, cstr);
return rcode;
}
return ret;
}
#endif /* MTA_USE_TLS */
/*
** SC_ONE_TA -- perform one SMTPC transaction
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
**
** Returns:
** SMTP reply code or error code
*/
sm_ret_T
sc_one_ta(sc_t_ctx_P sc_t_ctx)
{
uint thr_id;
sm_ret_T ret;
sc_sess_P sc_sess;
sc_ta_P sc_ta;
sc_rcpt_P sc_rcpt, sc_rcpt_nxt;
sc_rcpts_P sc_rcpts;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
sc_ta = sc_sess->scse_ta;
SM_IS_SC_TA(sc_ta);
ret = SM_SUCCESS;
thr_id = sc_t_ctx->sct_thr_id;
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 14,
"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=start_transaction, ta_state=%x",
thr_id, sc_sess->scse_id, sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_ta->scta_state);
#if SC_TEST
if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
SCC_CNF_TEST_DUMMY))
{
SCTA_SET_STATE(sc_ta, SCTA_COMPLETE);
#if SC_STATS
++SC_TOTAL(sc_t_ctx);
#endif
return SM_SUCCESS;
}
#endif
/* already closed? check also state? */
if (NULL == sc_sess->scse_fp)
goto done;
#if MTA_USE_TLS
ret = sc_tlsreq(sc_t_ctx, &sc_sess->scse_tlsreq_cnf, SMTP_R_SSD);
if (sm_is_err(ret)) {
ret = SMTP_R_SSD;
goto done;
}
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
/* better/selectable error code? */
if (SM_IS_FLAG(sc_sess->scse_tlsreq_cnf.tlsreqcnf_viol,
TLSREQ_VIOL_PERM))
sm_str_scopy(sc_sess->scse_wr,
"503 5.7.0 security requirements not met.\r\n");
else if (SM_IS_FLAG(sc_sess->scse_tlsreq_cnf.tlsreqcnf_viol,
TLSREQ_VIOL_TEMP))
sm_str_scopy(sc_sess->scse_wr,
"403 4.7.0 security requirements not met.\r\n");
else { /* default behavior */
sm_str_scopy(sc_sess->scse_wr,
"421 4.7.0 security requirements not met.\r\n");
ret = SMTP_R_SSD;
}
goto done;
}
#endif /* MTA_USE_TLS */
if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)) {
/* open cdb entry */
ret = cdb_open_read(sc_t_ctx->sct_sc_ctx->scc_cdb_ctx,
sm_str_getdata(sc_ta->scta_cdb_id),
sc_ta->scta_cdb_fp);
if (sm_is_err(ret)) {
/*
** todo: send error code back to QMGR?? Don't retry
** if file does not exist? (some bozo may have
** removed it or there was a communication problem
** between qmgr and smtps)
*/
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 4,
"sev=ERROR, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, cdb=%N, cdb_open=%m",
sc_t_ctx->sct_thr_id, sc_sess->scse_id,
sc_ta->scta_id, sc_ta->scta_ssta_id,
sc_ta->scta_cdb_id, ret);
sc_ta->scta_err_state = DA_TA_ERR_CDB_I;
if (sm_error_value(ret) == ENOENT) {
ret = SMTP_CDB_PERM;
goto fail;
}
else {
/*
** Treat every other error as temp??
** Note: this returns the cdb_open() error
** code directly to QMGR which might not
** understand it... fixme?
*/
sc_ta->scta_status = SMTP_CDB_TEMP;
SCTA_SET_STATE(sc_ta, SCTA_FAIL_TEMP);
goto done;
}
}
}
/*
** How can we simplify this? Create some constant str and just
** use sm_str_catv()?
**
** What about PIPELINING?
** Decouple sending of commands and reading of replies.
**
** How to treat errors in this code? That is, if some operation
** in smtpc fails, what should be returned to QMGR?
** Answer: an internal error, see sm/da.h
*/
#if MTA_USE_RSAD
if (SCSE_IS_CAP(sc_sess, SCSE_CAP_RSAD) && sc_ta->scta_rcpts_tot > 1)
SCSE_SET_FLAG(sc_sess, SCSE_FL_RSAD);
else
SCSE_CLR_FLAG(sc_sess, SCSE_FL_RSAD);
#endif
#define MAIL_EXT1 (SCSE_IS_FLAG(sc_sess, SCSE_FL_RSAD) ? " PRDR" : "")
if (SCSE_IS_CAP(sc_sess, SCSE_CAP_SIZE) && sc_ta->scta_msg_sz_b > 0) {
sm_str_clr(sc_sess->scse_wr);
if (sm_strprintf(sc_sess->scse_wr,
"MAIL From:%S SIZE=%lu%s\r\n"
, sc_ta->scta_mail->scm_pa, (unsigned long) sc_ta->scta_msg_sz_b
, MAIL_EXT1) <= 18)
{
sc_ta->scta_err_state = DA_TA_ERR_MAIL_I;
goto fail;
}
}
else if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "MAIL From:"))
|| sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_ta->scta_mail->scm_pa))
#if MTA_USE_RSAD
|| (SCSE_IS_FLAG(sc_sess, SCSE_FL_RSAD) &&
sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, " PRDR")))
#endif
|| sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
{
sc_ta->scta_err_state = DA_TA_ERR_MAIL_I;
goto fail;
}
ret = sc_command(sc_t_ctx, SC_PHASE_MAIL);
#if SC_STATS
++SC_MAIL_COUNT(sc_t_ctx->sct_sc_ctx);
#endif
sc_ta->scta_mail->scm_st = ret;
if (ret != SMTP_NO_REPLY) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, mail=%@S, stat=%m, reply=%@S",
thr_id, sc_sess->scse_id, sc_ta->scta_id,
sc_ta->scta_ssta_id, sc_ta->scta_mail->scm_pa, ret,
sc_sess->scse_str);
}
if (!SMTPC_OK_REPLY(ret)) {
sc_ta->scta_mail->scm_reply = sm_str_dup(NULL, sc_sess->scse_rd);
sc_ta->scta_err_state = DA_TA_ERR_MAIL_S;
if (SMTP_R_SSD == ret)
goto done;
goto fail;
}
#if SC_PIPELINING
if (SMTP_NO_REPLY == ret)
SCTA_SET_STATE(sc_ta, SCTA_MAIL_S);
else
#endif
SCTA_SET_STATE(sc_ta, SCTA_MAIL_S|SCTA_MAIL_R);
sc_rcpts = &sc_ta->scta_rcpts;
#if SC_PIPELINING
sc_ta->scta_rcpt_p = SC_RCPTS_FIRST(sc_rcpts);
#endif
for (sc_rcpt = SC_RCPTS_FIRST(sc_rcpts);
sc_rcpt != SC_RCPTS_END(sc_rcpts);
sc_rcpt = sc_rcpt_nxt)
{
sc_rcpt_nxt = SC_RCPTS_NEXT(sc_rcpt);
#if MTA_USE_TLS
/* default error code? */
ret = sc_tlsreq(sc_t_ctx, &sc_rcpt->scr_tlsreq_cnf, 403);
if (sm_is_err(ret) ||
(IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)))
{
/* better/selectable error code? */
if (!sm_is_err(ret) &&
SM_IS_FLAG(sc_rcpt->scr_tlsreq_cnf.tlsreqcnf_viol,
TLSREQ_VIOL_PERM))
sm_str_scopy(sc_sess->scse_rd,
"503 5.7.0 smtpc security requirements not met.\r\n");
else if (!sm_is_err(ret) &&
SM_IS_FLAG(sc_rcpt->scr_tlsreq_cnf.tlsreqcnf_viol,
TLSREQ_VIOL_TEMP))
sm_str_scopy(sc_sess->scse_rd,
"403 4.7.0 smtpc security requirements not met.\r\n");
else { /* default behavior */
sm_str_scopy(sc_sess->scse_rd,
"421 4.7.0 smtpc security requirements not met.\r\n");
ret = SMTP_R_SSD;
}
sm_str_cpy(sc_sess->scse_str, sc_sess->scse_rd);
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_RCPT_I;
/* fake reply */
++sc_ta->scta_rcpts_rcvd;
}
else {
#endif /* MTA_USE_TLS */
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "RCPT To:")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_rcpt->scr_pa)) ||
sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
{
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_RCPT_I;
goto fail;
}
/*
* note: this is a bit early but sc_command() doesn't have
* a simple way to set this flag (a callback seems like overkill).
*/
SCR_SET_FLAG(sc_rcpt, SCR_FL_SENT);
ret = sc_command(sc_t_ctx, SC_PHASE_RCPT);
#if SC_STATS
++SC_RCPT_COUNT(sc_t_ctx->sct_sc_ctx);
#endif
#if MTA_USE_TLS
}
#endif /* MTA_USE_TLS */
/* this might not be valid (PIPELINING) */
sc_rcpt->scr_st = ret;
if (ret != SMTP_NO_REPLY) {
SCR_SET_FLAG(sc_rcpt, SCR_FL_STAT);
if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_LOGGED)) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, rcpt=%@S, stat=%m, reply=%@S",
thr_id, sc_sess->scse_id, sc_ta->scta_id,
sc_ta->scta_ssta_id, sc_rcpt->scr_pa, ret,
sc_sess->scse_str);
SCR_SET_FLAG(sc_rcpt, SCR_FL_LOGGED);
}
}
if (!SMTPC_R_IS_OK(ret)) {
sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool, sc_sess->scse_rd);
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
? SCTA_R_PERM : SCTA_R_TEMP);
if (SMTP_R_SSD == ret)
goto done;
}
#if SC_STATS
else if (SMTP_OK == ret)
++SC_RCPT_CNT_OK(sc_t_ctx->sct_sc_ctx);
#endif
if (sm_is_err(ret))
goto fail;
sc_ta->scta_rcpts_snt++;
if (SMTP_OK == ret) {
sc_ta->scta_rcpts_ok++;
#if SC_PIPELINING
if (sc_ta->scta_rcpts_rcvd == sc_ta->scta_rcpts_snt)
SCTA_SET_STATE(sc_ta, SCTA_RCPT_S|SCTA_RCPT_R);
else
SCTA_SET_STATE(sc_ta, SCTA_RCPT_S);
#else
SCTA_SET_STATE(sc_ta, SCTA_RCPT_S|SCTA_RCPT_R);
#endif
}
#if SC_PIPELINING
else
SCTA_SET_STATE(sc_ta, SCTA_RCPT_S);
#endif
}
/* no valid recipients? */
if (NO_RCPTS(sc_ta)
#if SC_PIPELINING
&& !SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING)
#endif
)
goto fail;
ret = sm_str_scopy(sc_sess->scse_wr, "DATA\r\n");
if (sm_is_err(ret)) {
if (DA_ERR_NONE == sc_ta->scta_err_state)
sc_ta->scta_err_state = DA_TA_ERR_DATA_I;
goto fail;
}
/* this will collect all outstanding results if PIPELINING is active */
ret = sc_command(sc_t_ctx, SC_PHASE_DATA);
if (smtp_reply_type(ret) != SMTP_RTYPE_CONT) {
/*
** where=data is confusing for PIPELINING...
** state gives an indication what's going, needs to be
** translated to text.
*/
#if SC_PIPELINING
if (NO_RCPTS(sc_ta))
goto norcpts;
#endif
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=data, ret=%m, rcpts_ok=%d, state=%r, reply=%@S"
, thr_id, sc_sess->scse_id, sc_ta->scta_id
, sc_ta->scta_ssta_id, ret
, sc_ta->scta_rcpts_ok
, sc_ta->scta_state
, sc_sess->scse_str);
if (sc_ta->scta_err_state == DA_ERR_NONE ||
(da_ta_err_cmd(sc_ta->scta_err_state) == DA_TA_ERR_RCPT &&
sc_ta->scta_rcpts_ok > 0))
sc_ta->scta_err_state = DA_TA_ERR_DATA_S;
if (SMTP_R_SSD == ret)
goto done;
goto fail;
}
SCTA_SET_STATE(sc_ta, SCTA_DATA_S|SCTA_DATA_R);
if (SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP|SCSE_FL_RSAD)) {
#if SC_PIPELINING
/* reset recipient list to collect results */
sc_ta->scta_rcpt_p = SC_RCPTS_FIRST(sc_rcpts);
#endif
sc_ta->scta_rcpts_dot = sc_ta->scta_rcpts_ok;
}
/* Send data */
ret = sc_data(sc_t_ctx);
if (ret != SMTP_OK) {
#if SC_PIPELINING
if (0 == sc_ta->scta_rcpts_ok)
goto norcpts;
#endif
if (sc_ta->scta_err_state == DA_ERR_NONE ||
(da_ta_err_cmd(sc_ta->scta_err_state) == DA_TA_ERR_RCPT &&
sc_ta->scta_rcpts_ok > 0))
sc_ta->scta_err_state = DA_TA_ERR_DOT_S; /* really?? */
if (SMTP_R_SSD == ret)
goto done;
goto fail;
}
SCTA_SET_STATE(sc_ta, SCTA_DOT_R);
SCTA_SET_STATE(sc_ta, SCTA_COMPLETE);
#if SC_STATS
++SC_TOTAL(sc_t_ctx);
#endif
goto done;
#if SC_PIPELINING
norcpts:
if (SCTA_IS_STATE(sc_ta, SCTA_FAIL_TEMP|SCTA_R_TEMP)) {
ret = sc_ta->scta_status;
goto done;
}
#endif /* SC_PIPELINING */
fail:
/* clean up? */
if (sm_is_err(ret)) {
SCTA_SET_STATE(sc_ta, sm_is_temp_err(ret)
? SCTA_FAIL_TEMP : SCTA_FAIL_PERM);
}
else if (IS_SMTP_REPLY(ret)) {
SCTA_SET_STATE(sc_ta, smtp_is_reply_temp(ret)
? SCTA_FAIL_TEMP : SCTA_FAIL_PERM);
}
else {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ret=%d, status=unexpected_error_code"
, thr_id, sc_sess->scse_id, sc_ta->scta_id
, ret);
}
done:
if (sc_ta->scta_cdb_fp != NULL) {
(void) cdb_close(sc_t_ctx->sct_sc_ctx->scc_cdb_ctx,
sc_ta->scta_cdb_fp, SM_IO_CF_NONE);
sc_ta->scta_cdb_fp = NULL;
}
return ret;
}
/*
** SC_SESS_OPEN -- open SMTPC session
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
**
** Returns:
** SMTP reply code or error code
**
** Side Effects:
** on error sc_sess->scse_str should contain an error string
*/
sm_ret_T
sc_sess_open(sc_t_ctx_P sc_t_ctx)
{
st_netfd_t rmt_nfd;
int sock, r, addrlen;
uint thr_id;
sm_ret_T ret;
sc_sess_P sc_sess;
#if MTA_USE_TLS
sc_ctx_P sc_ctx;
#endif
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
thr_id = sc_t_ctx->sct_thr_id;
ret = SM_SUCCESS;
sc_sess->scse_fp = NULL;
rmt_nfd = INVALID_NETFD;
sm_str_clr(sc_sess->scse_str);
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 14,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=open",
thr_id, sc_sess->scse_id);
#if SC_STATS
++SC_BUSY(sc_t_ctx);
#endif
#if SC_TEST
if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
SCC_CNF_TEST_DUMMY))
{
sc_sess->scse_state = SCSE_ST_OPEN;
return SM_SUCCESS;
}
#endif
/* Connect to remote host */
sock = socket(sc_sess->scse_rmt_addr.sa.sa_family, SOCK_STREAM, 0);
if (sock < 0) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, socket()=failed, error=%s"
, thr_id, sc_sess->scse_id, strerror(errno));
sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
goto done;
}
ret = sm_io_open(&SmStThrIO, (void *) &sock, SM_IO_RDWR,
&sc_sess->scse_fp, NULL);
if (ret != SM_SUCCESS) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, sm_io_open()=%m, error=%s"
, thr_id, sc_sess->scse_id, ret, strerror(errno));
close(sock);
sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
goto done;
}
sm_io_clrblocking(sc_sess->scse_fp);
rmt_nfd = (st_netfd_t) f_cookie(*(sc_sess->scse_fp));
r = sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_timeout;
ret = sm_io_setinfo(sc_sess->scse_fp, SM_IO_WHAT_TIMEOUT, &r);
if (ret != SM_SUCCESS) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, set_timeout()=%m, error=%s"
, thr_id, sc_sess->scse_id, ret, strerror(errno));
sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
goto fail;
}
ret = sm_io_setinfo(sc_sess->scse_fp, SM_IO_DOUBLE, NULL);
if (ret != SM_SUCCESS) {
r = errno;
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, set_double()=%m, error=%s"
, thr_id, sc_sess->scse_id, ret, strerror(errno));
sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
goto fail;
}
/* fixme: put this into a library? */
switch (sc_sess->scse_rmt_addr.sa.sa_family) {
case AF_INET:
addrlen = sizeof(sc_sess->scse_rmt_addr.sin);
break;
case AF_UNIX:
addrlen = sizeof(sc_sess->scse_rmt_addr.sunix);
break;
default:
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_ERR, 8,
"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, where=connect, unsupported_family=%d"
, thr_id, sc_sess->scse_id
, sc_sess->scse_rmt_addr.sa.sa_family);
sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
goto fail;
}
if (st_connect(rmt_nfd, (sockaddr_P) &sc_sess->scse_rmt_addr, addrlen,
SEC2USEC(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_timeout)) < 0)
{
r = errno;
/* fixme: always temp?? */
ret = sm_error_temp(SM_EM_SMTPC, r);
#if 0
sc_sess->scse_ret = ret;
#endif
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connect, port=%d, addr=%s, error=%s",
thr_id, sc_sess->scse_id,
(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
ntohs(sc_sess->scse_rmt_addr.sin.sin_port): -1,
(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
inet_ntoa(sc_sess->scse_rmt_addr.sin.sin_addr) :
sc_sess->scse_rmt_addr.sunix.sun_path,
strerror(r));
sm_str_scat(sc_sess->scse_str, strerror(r));
sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR|SCSE_FL_LOGGED);
goto fail;
}
sc_sess->scse_state = SCSE_ST_CONNECTED;
sc_sess->scse_rmt_fd = rmt_nfd;
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 10,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, status=connected, port=%d, addr=%s",
thr_id, sc_sess->scse_id,
(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
ntohs(sc_sess->scse_rmt_addr.sin.sin_port): -1,
(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
inet_ntoa(sc_sess->scse_rmt_addr.sin.sin_addr) :
sc_sess->scse_rmt_addr.sunix.sun_path);
ret = sc_rd_reply(sc_t_ctx, SC_PHASE_INIT);
if (ret != SMTP_OK) {
if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED)) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 9,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection_rd_reply, ret=%m"
, thr_id, sc_sess->scse_id, ret);
SCSE_SET_FLAG(sc_sess, SCSE_FL_LOGGED);
}
sc_sess->scse_err_st = DA_SE_ERR_GRET_R;
if (sc_sess->scse_reply != NULL) {
sm_str_clr(sc_sess->scse_reply);
sm_str_cat(sc_sess->scse_reply, sc_sess->scse_str);
}
else
sc_sess->scse_reply = sm_str_dup(NULL, sc_sess->scse_str);
goto fail;
}
#if 0
/* HACK turn on LMTP */
if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug == 9)
SCSE_SET_FLAG(sc_sess, SCSE_FL_LMTP);
#endif
ehlo:
sc_sess->scse_cap = SCSE_CAP_NONE;
if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
? "LHLO "
: SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
? "HELO " : "EHLO ")) ||
sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
sc_t_ctx->sct_sc_ctx->scc_hostname)) ||
sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\r\n")))
{
sc_sess->scse_err_st = SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
? DA_SE_ERR_LHLO_I
: SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
? DA_SE_ERR_HELO_I : DA_SE_ERR_EHLO_I;
goto fail;
}
ret = sc_command(sc_t_ctx, SC_PHASE_EHLO);
if (ret == sm_error_perm(SM_EM_SMTPC, SM_E_TTMYSELF)) {
sc_sess->scse_err_st = DA_SE_ERR_TTMYSLEF_S;
goto fail;
}
else if (ret != SMTP_OK) {
if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
&& !SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
&& !SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR))
{
SCSE_SET_FLAG(sc_sess, SCSE_FL_EHLO);
goto ehlo;
}
/* distinguish between SMTP reply code and sm/error code! */
sc_sess->scse_err_st = DA_SET_ERR_RS(
SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
? DA_SE_ERR_LHLO
: SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
? DA_SE_ERR_HELO : DA_SE_ERR_EHLO, ret);
goto fail;
}
#if MTA_USE_TLS
sc_ctx = sc_t_ctx->sct_sc_ctx;
if (SC_IS_FLAG(sc_ctx, SCC_FL_TLS_OK) &&
SCSE_IS_CAP(sc_sess, SCSE_CAP_STARTTLS) &&
!SCSE_IS_FLAG(sc_sess, SCSE_FL_STARTTLS))
{
ssize_t written;
sc_sess->scse_con = SSL_new(sc_ctx->scc_ssl_ctx);
if (NULL == sc_sess->scse_con)
goto notls;
ret = sm_str_scopy(sc_sess->scse_wr, "STARTTLS\r\n");
if (sm_is_err(ret)) {
SSL_free(sc_sess->scse_con);
sc_sess->scse_con = NULL;
goto notls;
}
ret = sc_command(sc_t_ctx, SC_PHASE_TLS);
if (ret != SMTP_OK) {
SSL_free(sc_sess->scse_con);
sc_sess->scse_con = NULL;
goto notls;
}
SSL_set_connect_state(sc_sess->scse_con);
ret = tls_open(sc_sess->scse_fp, sc_sess->scse_con,
sc_ctx->scc_tlsl_ctx, &sc_sess->scse_fptls);
if (sm_is_success(ret)) {
/* HACK */
sc_sess->scse_fp = sc_sess->scse_fptls;
ret = do_tls_operation(sc_sess->scse_fp,
SSL_connect, NULL, NULL, NULL, 0,
&written);
if (sm_is_err(ret)) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection, starttls=%m",
thr_id, sc_sess->scse_id, ret);
sc_sess->scse_err_st = DA_SE_ERR_STLS_S;
/* get error message from OpenSSL? */
sm_str_scat(sc_sess->scse_str,
"TLS Handshake failed");
/* avoid further I/O, TLS handshake failed */
SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
goto fail;
}
r = 1;
ret = sm_io_setinfo(sc_sess->scse_fp, SM_IO_DOUBLE, &r);
if (sm_is_err(ret))
goto fail;
(void) tls_get_info(sc_ctx->scc_tlsl_ctx,
sc_sess->scse_con,
TLS_F_SRV|TLS_F_CERT_REQ,
(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET)
? inet_ntoa(sc_sess->scse_rmt_addr.sin.sin_addr)
: sc_sess->scse_rmt_addr.sunix.sun_path,
sc_sess->scse_tlsi);
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection, starttls=successful, cipher=%S, bits=%d/%d, verify=%s"
, thr_id, sc_sess->scse_id
, sc_sess->scse_tlsi->tlsi_cipher
, sc_sess->scse_tlsi->tlsi_cipher_bits
, sc_sess->scse_tlsi->tlsi_algs_bits
, tls_vrfy2txt(sc_sess->scse_tlsi->tlsi_vrfy));
SCSE_SET_FLAG(sc_sess, SCSE_FL_STARTTLS);
goto ehlo;
}
else {
/* more cleanup?? */
SSL_free(sc_sess->scse_con);
sc_sess->scse_con = NULL;
}
}
notls:
#endif /* MTA_USE_TLS */
sc_sess->scse_state = SCSE_ST_OPEN;
return SM_SUCCESS;
fail:
sc_sess_close(sc_t_ctx);
if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED)) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 8,
"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection, status=failed, state=0x%x, ret=%m",
thr_id, sc_sess->scse_id, sc_sess->scse_err_st, ret);
SCSE_SET_FLAG(sc_sess, SCSE_FL_LOGGED);
}
/* error state? */
done:
return ret;
}
/*
** SC_SESS_CLOSE -- close SMTPC session
**
** Parameters:
** sc_t_ctx -- SMTPC thread context
**
** Returns:
** SMTP reply code or error code
*/
sm_ret_T
sc_sess_close(sc_t_ctx_P sc_t_ctx)
{
uint thr_id;
sm_ret_T ret;
sc_sess_P sc_sess;
sc_ctx_P sc_ctx;
SM_IS_SC_T_CTX(sc_t_ctx);
sc_sess = sc_t_ctx->sct_sess;
SM_IS_SC_SE(sc_sess);
thr_id = sc_t_ctx->sct_thr_id;
sc_ctx = sc_t_ctx->sct_sc_ctx;
SM_IS_SC_CTX(sc_ctx);
sm_log_write(sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 14,
"sev=INFO, func=sc_sess_close, thread=%u, da_sess=%s, where=close, fp=%p",
thr_id, sc_sess->scse_id, sc_sess->scse_fp);
SC_RQST_COUNT(sc_ctx)++;
#if SC_STATS
if (SC_OPEN_SE(sc_ctx) > 0)
--SC_OPEN_SE(sc_ctx);
#endif
#if SC_TEST
if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
SCC_CNF_TEST_DUMMY))
goto done;
#endif
/*
** Already closed? Check also status?
** Don't send QUIT if we had an I/O error in the session...
** What about 421?
*/
if (sc_sess->scse_fp != NULL &&
!SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR) &&
sc_sess->scse_state >= SCSE_ST_CONNECTED &&
sc_sess->scse_state < SCSE_ST_CLOSED &&
sm_is_success(ret = sm_str_scopy(sc_sess->scse_wr, "QUIT\r\n")))
{
/* is it really necessary to wait for a reply? */
if (SCSE_IS_FLAG(sc_sess, SCSE_FL_QUIT_S))
ret = sc_rd_reply(sc_t_ctx, SC_PHASE_QUIT);
else
ret = sc_command(sc_t_ctx, SC_PHASE_QUIT);
SCSE_SET_FLAG(sc_sess, SCSE_FL_QUIT_R);
if (ret != SMTP_OK) {
sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
SC_LCAT_CLIENT, SC_LMOD_CLIENT,
SM_LOG_INFO, 14,
"sev=INFO, func=sc_sess_close, thread=%u, da_sess=%s, quit=%m",
thr_id, sc_sess->scse_id, ret);
}
}
if (sc_sess->scse_fp != NULL) {
sm_io_close(sc_sess->scse_fp, SM_IO_CF_NONE);
sc_sess->scse_fp = NULL;
}
#if 0
// if (SCSE_IS_FLAG(sc_sess, SCSE_FL_TELL_QMGR)) {
// sc_sess->scse_err_st = 0;
// ret = sc_c2q(sc_t_ctx, RT_C2Q_SECLSD, 0 /* SESS_TIMEOUT? */, &c2q_ctx);
// SCSE_CLR_FLAG(sc_sess, SCSE_FL_TELL_QMGR);
// }
#endif /* 0 */
#if SC_TEST
done:
#endif
sc_sess->scse_state = SCSE_ST_CLOSED;
#if SC_STATS
--SC_BUSY(sc_t_ctx);
#endif
return SM_SUCCESS;
}
syntax highlighted by Code2HTML, v. 0.9.1