/* * Copyright (c) 2005, 2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: t-msgmod-0.c,v 1.24 2006/04/02 06:34:21 ca Exp $") #include "sm/assert.h" #include "sm/error.h" #include "sm/string.h" #include "sm/str.h" #include "sm/cstr.h" #include "sm/io.h" #include "sm/ctype.h" #include "sm/mta.h" #include "sm/da.h" #include "smtpc.h" #define SMTPC_LOG_DEFINES 1 #include "log.h" #include "sm/test.h" #include "sm/sysexits.h" #define SC_PHASE_NOREPLY 0 /* ** SC_COMMAND -- send one SMTP command (dummy) ** ** 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; sc_sess_P sc_sess; SM_REQUIRE(sc_t_ctx != NULL); sc_sess = sc_t_ctx->sct_sess; l = sm_str_getlen(sc_sess->scse_wr); ret = sm_io_write(smioout, sm_str_data(sc_sess->scse_wr), l, &b); 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_data, 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; sc_sess = sc_t_ctx->sct_sess; SM_REQUIRE(sc_sess != NULL); 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; sc_sess = sc_t_ctx->sct_sess; SM_REQUIRE(sc_sess != NULL); 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; sc_sess = sc_t_ctx->sct_sess; SM_REQUIRE(sc_sess != NULL); 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; sc_sess = sc_t_ctx->sct_sess; SM_REQUIRE(sc_sess != NULL); 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 ----- */ static void usage(const char *prg) { sm_io_fprintf(smioerr, "usage: %s [options]\n" "Test message modifications for delivery\n" "options:\n" "-b n set I/O buffer size to n\n" "-d n delete header n\n" "-f n set flags\n" "-i pos=header insert header at pos\n" "-M type=header prepend or append header\n" " type=a or p\n" "-r pos=header replace header at pos\n" , prg); } int main(int argc, char **argv) { int c; unsigned long l; sm_ret_T ret; off_t total_size; size_t size; sc_ta_P sc_ta; sc_sess_P sc_sess; sc_t_ctx_P sc_t_ctx; sc_ctx_P sc_ctx; sm_hdrmod_P sm_hdrmod; char hdr[256], *str; sc_ta = NULL; sc_ta = sm_zalloc(sizeof(*sc_ta)); if (NULL == sc_ta) return(EX_OSERR); sc_sess = sm_zalloc(sizeof(*sc_sess)); if (NULL == sc_sess) return(EX_OSERR); sc_t_ctx = sm_zalloc(sizeof(*sc_t_ctx)); if (NULL == sc_t_ctx) return(EX_OSERR); sc_ctx = sm_zalloc(sizeof(*sc_ctx)); if (NULL == sc_ctx) return(EX_OSERR); sc_t_ctx->sm_magic = SM_SC_T_CTX_MAGIC; sc_sess->sm_magic = SM_SC_SESS_MAGIC; sc_ta->sm_magic = SM_SC_TA_MAGIC; sc_t_ctx->sct_sc_ctx = sc_ctx; sc_sess->scse_sct_ctx = sc_t_ctx; sc_t_ctx->sct_sess = sc_sess; sc_sess->scse_fp = smioout; sc_sess->scse_ta = sc_ta; while ((c = getopt(argc, argv, "b:d:f:i:M:r:")) != -1) { switch (c) { case 'b': size = strtoul(optarg, NULL, 0); ret = sm_io_setvbuf(smioin, NULL, SM_IO_FBF, size); break; case 'd': sm_hdrmod = NULL; ret = sm_hdrmod_new(&sc_ta->scta_hdrmodhd, false, &sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); SM_TEST(sm_hdrmod != NULL); if (NULL == sm_hdrmod) return(EX_OSERR); sm_hdrmod->sm_hm_type = SM_HM_TYPE_REMOVE; sm_hdrmod->sm_hm_pos = strtoul(optarg, NULL, 0); ret = sm_hdrmod_insert(sc_ta->scta_hdrmodhd, sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); SCTA_SET_FLAG(sc_ta, SCTA_FL_HDR_SCAN); break; case 'f': sc_ta->scta_flags = strtol(optarg, NULL, 0); break; case 'i': l = strtoul(optarg, &str, 0); if (ULONG_MAX == l || l > UINT_MAX) usage(argv[0]); if (NULL == str || *str != '=') usage(argv[0]); sm_hdrmod = NULL; strlcpy(hdr, str + 1, sizeof(hdr)); strlcat(hdr, "\r\n", sizeof(hdr)); ret = sm_hdrmod_new(&sc_ta->scta_hdrmodhd, false, &sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); SM_TEST(sm_hdrmod != NULL); if (NULL == sm_hdrmod) return(EX_OSERR); sm_hdrmod->sm_hm_hdr = sm_cstr_scpyn0((const uchar *)hdr , strlen(hdr)); sm_hdrmod->sm_hm_pos = l; sm_hdrmod->sm_hm_type = SM_HM_TYPE_INSERT; SCTA_SET_FLAG(sc_ta, SCTA_FL_HDR_SCAN); ret = sm_hdrmod_insert(sc_ta->scta_hdrmodhd, sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); break; case 'M': if (optarg == NULL || (optarg[0] != 'p' && optarg[0] != 'a') || optarg[1] != '=') { usage(argv[0]); break; } sm_hdrmod = NULL; strlcpy(hdr, optarg + 2, sizeof(hdr)); strlcat(hdr, "\r\n", sizeof(hdr)); ret = sm_hdrmod_new(&sc_ta->scta_hdrmodhd, false, &sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); SM_TEST(sm_hdrmod != NULL); if (NULL == sm_hdrmod) return(EX_OSERR); sm_hdrmod->sm_hm_hdr = sm_cstr_scpyn0((const uchar *)hdr , strlen(hdr)); if (optarg[0] == 'p') sm_hdrmod->sm_hm_type = SM_HM_TYPE_PREPEND; else { sm_hdrmod->sm_hm_type = SM_HM_TYPE_APPEND; SCTA_SET_FLAG(sc_ta, SCTA_FL_HDR_SCAN); } ret = sm_hdrmod_insert(sc_ta->scta_hdrmodhd, sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); break; case 'r': l = strtoul(optarg, &str, 0); if (ULONG_MAX == l || l > UINT_MAX) usage(argv[0]); if (NULL == str || *str != '=') usage(argv[0]); sm_hdrmod = NULL; strlcpy(hdr, str + 1, sizeof(hdr)); strlcat(hdr, "\r\n", sizeof(hdr)); ret = sm_hdrmod_new(&sc_ta->scta_hdrmodhd, false, &sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); SM_TEST(sm_hdrmod != NULL); if (NULL == sm_hdrmod) return(EX_OSERR); sm_hdrmod->sm_hm_hdr = sm_cstr_scpyn0((const uchar *)hdr , strlen(hdr)); sm_hdrmod->sm_hm_pos = l; sm_hdrmod->sm_hm_type = SM_HM_TYPE_REPLACE; SCTA_SET_FLAG(sc_ta, SCTA_FL_HDR_SCAN); ret = sm_hdrmod_insert(sc_ta->scta_hdrmodhd, sm_hdrmod); SM_TEST(SM_SUCCESS == ret); if (SM_SUCCESS != ret) return(EX_OSERR); break; default: usage(argv[0]); return(EX_USAGE); } } sm_test_begin(argc, argv, "message modification test 0"); argc -= optind; argv += optind; sc_ta->scta_cdb_fp = smioin; sc_sess->scse_wr = sm_str_new(NULL, 256, 1024); SM_TEST(sc_sess->scse_wr != NULL); sc_sess->scse_str = sm_str_new(NULL, 256, 1024); SM_TEST(sc_sess->scse_str != NULL); total_size = 0; if (!HDRMODL_EMPTY(sc_ta->scta_hdrmodhd)) ret = sc_msgmod(sc_t_ctx, &total_size); else ret = sc_msgdsn(sc_t_ctx, &total_size); SM_TEST(SM_SUCCESS == ret); sm_io_flush(smioout); SM_STR_FREE(sc_sess->scse_wr); SM_STR_FREE(sc_sess->scse_str); if (sc_ta != NULL) sm_free(sc_ta); return sm_test_end(); }