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