diff options
Diffstat (limited to 'sendmail/libmilter/engine.c')
-rw-r--r-- | sendmail/libmilter/engine.c | 1777 |
1 files changed, 1777 insertions, 0 deletions
diff --git a/sendmail/libmilter/engine.c b/sendmail/libmilter/engine.c new file mode 100644 index 0000000..a024312 --- /dev/null +++ b/sendmail/libmilter/engine.c @@ -0,0 +1,1777 @@ +/* + * Copyright (c) 1999-2004, 2006, 2007 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/gen.h> +SM_RCSID("@(#)$Id: engine.c,v 8.159 2007/04/23 22:22:50 ca Exp $") + +#include "libmilter.h" + +#if NETINET || NETINET6 +# include <arpa/inet.h> +#endif /* NETINET || NETINET6 */ + +/* generic argument for functions in the command table */ +struct arg_struct +{ + size_t a_len; /* length of buffer */ + char *a_buf; /* argument string */ + int a_idx; /* index for macro array */ + SMFICTX_PTR a_ctx; /* context */ +}; + +typedef struct arg_struct genarg; + +/* structure for commands received from MTA */ +struct cmdfct_t +{ + char cm_cmd; /* command */ + int cm_argt; /* type of arguments expected */ + int cm_next; /* next state */ + int cm_todo; /* what to do next */ + int cm_macros; /* index for macros */ + int (*cm_fct) __P((genarg *)); /* function to execute */ +}; + +typedef struct cmdfct_t cmdfct; + +/* possible values for cm_argt */ +#define CM_ARG0 0 /* no args */ +#define CM_ARG1 1 /* one arg (string) */ +#define CM_ARG2 2 /* two args (strings) */ +#define CM_ARGA 4 /* one string and _SOCK_ADDR */ +#define CM_ARGO 5 /* two integers */ +#define CM_ARGV 8 /* \0 separated list of args, NULL-terminated */ +#define CM_ARGN 9 /* \0 separated list of args (strings) */ + +/* possible values for cm_todo */ +#define CT_CONT 0x0000 /* continue reading commands */ +#define CT_IGNO 0x0001 /* continue even when error */ + +/* not needed right now, done via return code instead */ +#define CT_KEEP 0x0004 /* keep buffer (contains symbols) */ +#define CT_END 0x0008 /* last command of session, stop replying */ + +/* index in macro array: macros only for these commands */ +#define CI_NONE (-1) +#define CI_CONN 0 +#define CI_HELO 1 +#define CI_MAIL 2 +#define CI_RCPT 3 +#define CI_DATA 4 +#define CI_EOM 5 +#define CI_EOH 6 +#define CI_LAST CI_EOH +#if CI_LAST < CI_DATA +ERROR: do not compile with CI_LAST < CI_DATA +#endif +#if CI_LAST < CI_EOM +ERROR: do not compile with CI_LAST < CI_EOM +#endif +#if CI_LAST < CI_EOH +ERROR: do not compile with CI_LAST < CI_EOH +#endif +#if CI_LAST < CI_ENVRCPT +ERROR: do not compile with CI_LAST < CI_ENVRCPT +#endif +#if CI_LAST < CI_ENVFROM +ERROR: do not compile with CI_LAST < CI_ENVFROM +#endif +#if CI_LAST < CI_HELO +ERROR: do not compile with CI_LAST < CI_HELO +#endif +#if CI_LAST < CI_CONNECT +ERROR: do not compile with CI_LAST < CI_CONNECT +#endif +#if CI_LAST >= MAX_MACROS_ENTRIES +ERROR: do not compile with CI_LAST >= MAX_MACROS_ENTRIES +#endif + +/* function prototypes */ +static int st_abortfct __P((genarg *)); +static int st_macros __P((genarg *)); +static int st_optionneg __P((genarg *)); +static int st_bodychunk __P((genarg *)); +static int st_connectinfo __P((genarg *)); +static int st_bodyend __P((genarg *)); +static int st_helo __P((genarg *)); +static int st_header __P((genarg *)); +static int st_sender __P((genarg *)); +static int st_rcpt __P((genarg *)); +static int st_unknown __P((genarg *)); +static int st_data __P((genarg *)); +static int st_eoh __P((genarg *)); +static int st_quit __P((genarg *)); +static int sendreply __P((sfsistat, socket_t, struct timeval *, SMFICTX_PTR)); +static void fix_stm __P((SMFICTX_PTR)); +static bool trans_ok __P((int, int)); +static char **dec_argv __P((char *, size_t)); +static int dec_arg2 __P((char *, size_t, char **, char **)); + +#if _FFR_WORKERS_POOL +static bool mi_rd_socket_ready __P((int)); +#endif /* _FFR_WORKERS_POOL */ + +/* states */ +#define ST_NONE (-1) +#define ST_INIT 0 /* initial state */ +#define ST_OPTS 1 /* option negotiation */ +#define ST_CONN 2 /* connection info */ +#define ST_HELO 3 /* helo */ +#define ST_MAIL 4 /* mail from */ +#define ST_RCPT 5 /* rcpt to */ +#define ST_DATA 6 /* data */ +#define ST_HDRS 7 /* headers */ +#define ST_EOHS 8 /* end of headers */ +#define ST_BODY 9 /* body */ +#define ST_ENDM 10 /* end of message */ +#define ST_QUIT 11 /* quit */ +#define ST_ABRT 12 /* abort */ +#define ST_UNKN 13 /* unknown SMTP command */ +#define ST_Q_NC 14 /* quit, new connection follows */ +#define ST_LAST ST_Q_NC /* last valid state */ +#define ST_SKIP 16 /* not a state but required for the state table */ + +/* in a mail transaction? must be before eom according to spec. */ +#define ST_IN_MAIL(st) ((st) >= ST_MAIL && (st) < ST_ENDM) + +/* +** set of next states +** each state (ST_*) corresponds to bit in an int value (1 << state) +** each state has a set of allowed transitions ('or' of bits of states) +** so a state transition is valid if the mask of the next state +** is set in the NX_* value +** this function is coded in trans_ok(), see below. +*/ + +#define MI_MASK(x) (0x0001 << (x)) /* generate a bit "mask" for a state */ +#define NX_INIT (MI_MASK(ST_OPTS)) +#define NX_OPTS (MI_MASK(ST_CONN) | MI_MASK(ST_UNKN)) +#define NX_CONN (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN)) +#define NX_HELO (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN)) +#define NX_MAIL (MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | MI_MASK(ST_UNKN)) +#define NX_RCPT (MI_MASK(ST_HDRS) | MI_MASK(ST_EOHS) | MI_MASK(ST_DATA) | \ + MI_MASK(ST_BODY) | MI_MASK(ST_ENDM) | \ + MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | MI_MASK(ST_UNKN)) +#define NX_DATA (MI_MASK(ST_EOHS) | MI_MASK(ST_HDRS) | MI_MASK(ST_ABRT)) +#define NX_HDRS (MI_MASK(ST_EOHS) | MI_MASK(ST_HDRS) | MI_MASK(ST_ABRT)) +#define NX_EOHS (MI_MASK(ST_BODY) | MI_MASK(ST_ENDM) | MI_MASK(ST_ABRT)) +#define NX_BODY (MI_MASK(ST_ENDM) | MI_MASK(ST_BODY) | MI_MASK(ST_ABRT)) +#define NX_ENDM (MI_MASK(ST_QUIT) | MI_MASK(ST_MAIL) | MI_MASK(ST_UNKN) | \ + MI_MASK(ST_Q_NC)) +#define NX_QUIT 0 +#define NX_ABRT 0 +#define NX_UNKN (MI_MASK(ST_HELO) | MI_MASK(ST_MAIL) | \ + MI_MASK(ST_RCPT) | MI_MASK(ST_ABRT) | \ + MI_MASK(ST_DATA) | \ + MI_MASK(ST_BODY) | MI_MASK(ST_UNKN) | \ + MI_MASK(ST_ABRT) | MI_MASK(ST_QUIT) | MI_MASK(ST_Q_NC)) +#define NX_Q_NC (MI_MASK(ST_CONN) | MI_MASK(ST_UNKN)) +#define NX_SKIP MI_MASK(ST_SKIP) + +static int next_states[] = +{ + NX_INIT + , NX_OPTS + , NX_CONN + , NX_HELO + , NX_MAIL + , NX_RCPT + , NX_DATA + , NX_HDRS + , NX_EOHS + , NX_BODY + , NX_ENDM + , NX_QUIT + , NX_ABRT + , NX_UNKN + , NX_Q_NC +}; + +#define SIZE_NEXT_STATES (sizeof(next_states) / sizeof(next_states[0])) + +/* commands received by milter */ +static cmdfct cmds[] = +{ + {SMFIC_ABORT, CM_ARG0, ST_ABRT, CT_CONT, CI_NONE, st_abortfct } +, {SMFIC_MACRO, CM_ARGV, ST_NONE, CT_KEEP, CI_NONE, st_macros } +, {SMFIC_BODY, CM_ARG1, ST_BODY, CT_CONT, CI_NONE, st_bodychunk } +, {SMFIC_CONNECT, CM_ARG2, ST_CONN, CT_CONT, CI_CONN, st_connectinfo } +, {SMFIC_BODYEOB, CM_ARG1, ST_ENDM, CT_CONT, CI_EOM, st_bodyend } +, {SMFIC_HELO, CM_ARG1, ST_HELO, CT_CONT, CI_HELO, st_helo } +, {SMFIC_HEADER, CM_ARG2, ST_HDRS, CT_CONT, CI_NONE, st_header } +, {SMFIC_MAIL, CM_ARGV, ST_MAIL, CT_CONT, CI_MAIL, st_sender } +, {SMFIC_OPTNEG, CM_ARGO, ST_OPTS, CT_CONT, CI_NONE, st_optionneg } +, {SMFIC_EOH, CM_ARG0, ST_EOHS, CT_CONT, CI_EOH, st_eoh } +, {SMFIC_QUIT, CM_ARG0, ST_QUIT, CT_END, CI_NONE, st_quit } +, {SMFIC_DATA, CM_ARG0, ST_DATA, CT_CONT, CI_DATA, st_data } +, {SMFIC_RCPT, CM_ARGV, ST_RCPT, CT_IGNO, CI_RCPT, st_rcpt } +, {SMFIC_UNKNOWN, CM_ARG1, ST_UNKN, CT_IGNO, CI_NONE, st_unknown } +, {SMFIC_QUIT_NC, CM_ARG0, ST_Q_NC, CT_CONT, CI_NONE, st_quit } +}; + +/* +** Additional (internal) reply codes; +** must be coordinated wit libmilter/mfapi.h +*/ + +#define _SMFIS_KEEP 20 +#define _SMFIS_ABORT 21 +#define _SMFIS_OPTIONS 22 +#define _SMFIS_NOREPLY SMFIS_NOREPLY +#define _SMFIS_FAIL (-1) +#define _SMFIS_NONE (-2) + +/* +** MI_ENGINE -- receive commands and process them +** +** Parameters: +** ctx -- context structure +** +** Returns: +** MI_FAILURE/MI_SUCCESS +*/ + +int +mi_engine(ctx) + SMFICTX_PTR ctx; +{ + size_t len; + int i; + socket_t sd; + int ret = MI_SUCCESS; + int ncmds = sizeof(cmds) / sizeof(cmdfct); + int curstate = ST_INIT; + int newstate; + bool call_abort; + sfsistat r; + char cmd; + char *buf = NULL; + genarg arg; + struct timeval timeout; + int (*f) __P((genarg *)); + sfsistat (*fi_abort) __P((SMFICTX *)); + sfsistat (*fi_close) __P((SMFICTX *)); + + arg.a_ctx = ctx; + sd = ctx->ctx_sd; + fi_abort = ctx->ctx_smfi->xxfi_abort; +#if _FFR_WORKERS_POOL + curstate = ctx->ctx_state; + if (curstate == ST_INIT) + { + mi_clr_macros(ctx, 0); + fix_stm(ctx); + } +#else /* _FFR_WORKERS_POOL */ + mi_clr_macros(ctx, 0); + fix_stm(ctx); +#endif /* _FFR_WORKERS_POOL */ + r = _SMFIS_NONE; + do + { + /* call abort only if in a mail transaction */ + call_abort = ST_IN_MAIL(curstate); + timeout.tv_sec = ctx->ctx_timeout; + timeout.tv_usec = 0; + if (mi_stop() == MILTER_ABRT) + { + if (ctx->ctx_dbg > 3) + sm_dprintf("[%ld] milter_abort\n", + (long) ctx->ctx_id); + ret = MI_FAILURE; + break; + } + + /* + ** Notice: buf is allocated by mi_rd_cmd() and it will + ** usually be free()d after it has been used in f(). + ** However, if the function returns _SMFIS_KEEP then buf + ** contains macros and will not be free()d. + ** Hence r must be set to _SMFIS_NONE if a new buf is + ** allocated to avoid problem with housekeeping, esp. + ** if the code "break"s out of the loop. + */ + +#if _FFR_WORKERS_POOL + /* Is the socket ready to be read ??? */ + if (!mi_rd_socket_ready(sd)) + { + ret = MI_CONTINUE; + break; + } +#endif /* _FFR_WORKERS_POOL */ + + r = _SMFIS_NONE; + if ((buf = mi_rd_cmd(sd, &timeout, &cmd, &len, + ctx->ctx_smfi->xxfi_name)) == NULL && + cmd < SMFIC_VALIDCMD) + { + if (ctx->ctx_dbg > 5) + sm_dprintf("[%ld] mi_engine: mi_rd_cmd error (%x)\n", + (long) ctx->ctx_id, (int) cmd); + + /* + ** eof is currently treated as failure -> + ** abort() instead of close(), otherwise use: + ** if (cmd != SMFIC_EOF) + */ + + ret = MI_FAILURE; + break; + } + if (ctx->ctx_dbg > 4) + sm_dprintf("[%ld] got cmd '%c' len %d\n", + (long) ctx->ctx_id, cmd, (int) len); + for (i = 0; i < ncmds; i++) + { + if (cmd == cmds[i].cm_cmd) + break; + } + if (i >= ncmds) + { + /* unknown command */ + if (ctx->ctx_dbg > 1) + sm_dprintf("[%ld] cmd '%c' unknown\n", + (long) ctx->ctx_id, cmd); + ret = MI_FAILURE; + break; + } + if ((f = cmds[i].cm_fct) == NULL) + { + /* stop for now */ + if (ctx->ctx_dbg > 1) + sm_dprintf("[%ld] cmd '%c' not impl\n", + (long) ctx->ctx_id, cmd); + ret = MI_FAILURE; + break; + } + + /* is new state ok? */ + newstate = cmds[i].cm_next; + if (ctx->ctx_dbg > 5) + sm_dprintf("[%ld] cur %x new %x nextmask %x\n", + (long) ctx->ctx_id, + curstate, newstate, next_states[curstate]); + + if (newstate != ST_NONE && !trans_ok(curstate, newstate)) + { + if (ctx->ctx_dbg > 1) + sm_dprintf("[%ld] abort: cur %d (%x) new %d (%x) next %x\n", + (long) ctx->ctx_id, + curstate, MI_MASK(curstate), + newstate, MI_MASK(newstate), + next_states[curstate]); + + /* call abort only if in a mail transaction */ + if (fi_abort != NULL && call_abort) + (void) (*fi_abort)(ctx); + + /* + ** try to reach the new state from HELO + ** if it can't be reached, ignore the command. + */ + + curstate = ST_HELO; + if (!trans_ok(curstate, newstate)) + { + if (buf != NULL) + { + free(buf); + buf = NULL; + } + continue; + } + } + arg.a_len = len; + arg.a_buf = buf; + if (newstate != ST_NONE) + { + curstate = newstate; + ctx->ctx_state = curstate; + } + arg.a_idx = cmds[i].cm_macros; + call_abort = ST_IN_MAIL(curstate); + + /* call function to deal with command */ + MI_MONITOR_BEGIN(ctx, cmd); + r = (*f)(&arg); + MI_MONITOR_END(ctx, cmd); + if (r != _SMFIS_KEEP && buf != NULL) + { + free(buf); + buf = NULL; + } + if (sendreply(r, sd, &timeout, ctx) != MI_SUCCESS) + { + ret = MI_FAILURE; + break; + } + + if (r == SMFIS_ACCEPT) + { + /* accept mail, no further actions taken */ + curstate = ST_HELO; + } + else if (r == SMFIS_REJECT || r == SMFIS_DISCARD || + r == SMFIS_TEMPFAIL) + { + /* + ** further actions depend on current state + ** if the IGNO bit is set: "ignore" the error, + ** i.e., stay in the current state + */ + if (!bitset(CT_IGNO, cmds[i].cm_todo)) + curstate = ST_HELO; + } + else if (r == _SMFIS_ABORT) + { + if (ctx->ctx_dbg > 5) + sm_dprintf("[%ld] function returned abort\n", + (long) ctx->ctx_id); + ret = MI_FAILURE; + break; + } + } while (!bitset(CT_END, cmds[i].cm_todo)); + + ctx->ctx_state = curstate; + + if (ret == MI_FAILURE) + { + /* call abort only if in a mail transaction */ + if (fi_abort != NULL && call_abort) + (void) (*fi_abort)(ctx); + } + + /* has close been called? */ + if (ctx->ctx_state != ST_QUIT +#if _FFR_WORKERS_POOL + && ret != MI_CONTINUE +#endif /* _FFR_WORKERS_POOL */ + ) + { + if ((fi_close = ctx->ctx_smfi->xxfi_close) != NULL) + (void) (*fi_close)(ctx); + } + if (r != _SMFIS_KEEP && buf != NULL) + free(buf); +#if !_FFR_WORKERS_POOL + mi_clr_macros(ctx, 0); +#endif /* _FFR_WORKERS_POOL */ + return ret; +} + +static size_t milter_addsymlist __P((SMFICTX_PTR, char *, char **)); + +static size_t +milter_addsymlist(ctx, buf, newbuf) + SMFICTX_PTR ctx; + char *buf; + char **newbuf; +{ + size_t len; + int i; + mi_int32 v; + char *buffer; + + SM_ASSERT(ctx != NULL); + SM_ASSERT(buf != NULL); + SM_ASSERT(newbuf != NULL); + len = 0; + for (i = 0; i < MAX_MACROS_ENTRIES; i++) + { + if (ctx->ctx_mac_list[i] != NULL) + { + len += strlen(ctx->ctx_mac_list[i]) + 1 + + MILTER_LEN_BYTES; + } + } + if (len > 0) + { + size_t offset; + + SM_ASSERT(len + MILTER_OPTLEN > len); + len += MILTER_OPTLEN; + buffer = malloc(len); + if (buffer != NULL) + { + (void) memcpy(buffer, buf, MILTER_OPTLEN); + offset = MILTER_OPTLEN; + for (i = 0; i < MAX_MACROS_ENTRIES; i++) + { + size_t l; + + if (ctx->ctx_mac_list[i] == NULL) + continue; + + SM_ASSERT(offset + MILTER_LEN_BYTES < len); + v = htonl(i); + (void) memcpy(buffer + offset, (void *) &v, + MILTER_LEN_BYTES); + offset += MILTER_LEN_BYTES; + l = strlen(ctx->ctx_mac_list[i]) + 1; + SM_ASSERT(offset + l <= len); + (void) memcpy(buffer + offset, + ctx->ctx_mac_list[i], l); + offset += l; + } + } + else + { + /* oops ... */ + } + } + else + { + len = MILTER_OPTLEN; + buffer = buf; + } + *newbuf = buffer; + return len; +} + +/* +** GET_NR_BIT -- get "no reply" bit matching state +** +** Parameters: +** state -- current protocol stage +** +** Returns: +** 0: no matching bit +** >0: the matching "no reply" bit +*/ + +static unsigned long get_nr_bit __P((int)); + +static unsigned long +get_nr_bit(state) + int state; +{ + unsigned long bit; + + switch (state) + { + case ST_CONN: + bit = SMFIP_NR_CONN; + break; + case ST_HELO: + bit = SMFIP_NR_HELO; + break; + case ST_MAIL: + bit = SMFIP_NR_MAIL; + break; + case ST_RCPT: + bit = SMFIP_NR_RCPT; + break; + case ST_DATA: + bit = SMFIP_NR_DATA; + break; + case ST_UNKN: + bit = SMFIP_NR_UNKN; + break; + case ST_HDRS: + bit = SMFIP_NR_HDR; + break; + case ST_EOHS: + bit = SMFIP_NR_EOH; + break; + case ST_BODY: + bit = SMFIP_NR_BODY; + break; + default: + bit = 0; + break; + } + return bit; +} + +/* +** SENDREPLY -- send a reply to the MTA +** +** Parameters: +** r -- reply code +** sd -- socket descriptor +** timeout_ptr -- (ptr to) timeout to use for sending +** ctx -- context structure +** +** Returns: +** MI_SUCCESS/MI_FAILURE +*/ + +static int +sendreply(r, sd, timeout_ptr, ctx) + sfsistat r; + socket_t sd; + struct timeval *timeout_ptr; + SMFICTX_PTR ctx; +{ + int ret; + unsigned long bit; + + ret = MI_SUCCESS; + + bit = get_nr_bit(ctx->ctx_state); + if (bit != 0 && (ctx->ctx_pflags & bit) != 0 && r != SMFIS_NOREPLY) + { + if (r >= SMFIS_CONTINUE && r < _SMFIS_KEEP) + { + /* milter said it wouldn't reply, but it lied... */ + smi_log(SMI_LOG_ERR, + "%s: milter claimed not to reply in state %d but did anyway %d\n", + ctx->ctx_smfi->xxfi_name, + ctx->ctx_state, r); + + } + + /* + ** Force specified behavior, otherwise libmilter + ** and MTA will fail to communicate properly. + */ + + switch (r) + { + case SMFIS_CONTINUE: + case SMFIS_TEMPFAIL: + case SMFIS_REJECT: + case SMFIS_DISCARD: + case SMFIS_ACCEPT: + case SMFIS_SKIP: + case _SMFIS_OPTIONS: + r = SMFIS_NOREPLY; + break; + } + } + + switch (r) + { + case SMFIS_CONTINUE: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_CONTINUE, NULL, 0); + break; + case SMFIS_TEMPFAIL: + case SMFIS_REJECT: + if (ctx->ctx_reply != NULL && + ((r == SMFIS_TEMPFAIL && *ctx->ctx_reply == '4') || + (r == SMFIS_REJECT && *ctx->ctx_reply == '5'))) + { + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_REPLYCODE, + ctx->ctx_reply, + strlen(ctx->ctx_reply) + 1); + free(ctx->ctx_reply); + ctx->ctx_reply = NULL; + } + else + { + ret = mi_wr_cmd(sd, timeout_ptr, r == SMFIS_REJECT ? + SMFIR_REJECT : SMFIR_TEMPFAIL, NULL, 0); + } + break; + case SMFIS_DISCARD: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_DISCARD, NULL, 0); + break; + case SMFIS_ACCEPT: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_ACCEPT, NULL, 0); + break; + case SMFIS_SKIP: + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_SKIP, NULL, 0); + break; + case _SMFIS_OPTIONS: + { + mi_int32 v; + size_t len; + char *buffer; + char buf[MILTER_OPTLEN]; + + v = htonl(ctx->ctx_prot_vers2mta); + (void) memcpy(&(buf[0]), (void *) &v, + MILTER_LEN_BYTES); + v = htonl(ctx->ctx_aflags); + (void) memcpy(&(buf[MILTER_LEN_BYTES]), (void *) &v, + MILTER_LEN_BYTES); + v = htonl(ctx->ctx_pflags2mta); + (void) memcpy(&(buf[MILTER_LEN_BYTES * 2]), + (void *) &v, MILTER_LEN_BYTES); + len = milter_addsymlist(ctx, buf, &buffer); + if (buffer != NULL) + ret = mi_wr_cmd(sd, timeout_ptr, SMFIC_OPTNEG, + buffer, len); + else + ret = MI_FAILURE; + } + break; + case SMFIS_NOREPLY: + if (bit != 0 && + (ctx->ctx_pflags & bit) != 0 && + (ctx->ctx_mta_pflags & bit) == 0) + { + /* + ** milter doesn't want to send a reply, + ** but the MTA doesn't have that feature: fake it. + */ + + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_CONTINUE, NULL, + 0); + } + break; + default: /* don't send a reply */ + break; + } + return ret; +} + +/* +** CLR_MACROS -- clear set of macros starting from a given index +** +** Parameters: +** ctx -- context structure +** m -- index from which to clear all macros +** +** Returns: +** None. +*/ + +void +mi_clr_macros(ctx, m) + SMFICTX_PTR ctx; + int m; +{ + int i; + + for (i = m; i < MAX_MACROS_ENTRIES; i++) + { + if (ctx->ctx_mac_ptr[i] != NULL) + { + free(ctx->ctx_mac_ptr[i]); + ctx->ctx_mac_ptr[i] = NULL; + } + if (ctx->ctx_mac_buf[i] != NULL) + { + free(ctx->ctx_mac_buf[i]); + ctx->ctx_mac_buf[i] = NULL; + } + } +} + +/* +** ST_OPTIONNEG -- negotiate options +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** abort/send options/continue +*/ + +static int +st_optionneg(g) + genarg *g; +{ + mi_int32 i, v, fake_pflags; + SMFICTX_PTR ctx; + int (*fi_negotiate) __P((SMFICTX *, + unsigned long, unsigned long, + unsigned long, unsigned long, + unsigned long *, unsigned long *, + unsigned long *, unsigned long *)); + + if (g == NULL || g->a_ctx->ctx_smfi == NULL) + return SMFIS_CONTINUE; + ctx = g->a_ctx; + mi_clr_macros(ctx, g->a_idx + 1); + ctx->ctx_prot_vers = SMFI_PROT_VERSION; + + /* check for minimum length */ + if (g->a_len < MILTER_OPTLEN) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%ld]: len too short %d < %d", + ctx->ctx_smfi->xxfi_name, + (long) ctx->ctx_id, (int) g->a_len, + MILTER_OPTLEN); + return _SMFIS_ABORT; + } + + /* protocol version */ + (void) memcpy((void *) &i, (void *) &(g->a_buf[0]), MILTER_LEN_BYTES); + v = ntohl(i); + +#define SMFI_PROT_VERSION_MIN 2 + + /* check for minimum version */ + if (v < SMFI_PROT_VERSION_MIN) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%ld]: protocol version too old %d < %d", + ctx->ctx_smfi->xxfi_name, + (long) ctx->ctx_id, v, SMFI_PROT_VERSION_MIN); + return _SMFIS_ABORT; + } + ctx->ctx_mta_prot_vers = v; + if (ctx->ctx_prot_vers < ctx->ctx_mta_prot_vers) + ctx->ctx_prot_vers2mta = ctx->ctx_prot_vers; + else + ctx->ctx_prot_vers2mta = ctx->ctx_mta_prot_vers; + + (void) memcpy((void *) &i, (void *) &(g->a_buf[MILTER_LEN_BYTES]), + MILTER_LEN_BYTES); + v = ntohl(i); + + /* no flags? set to default value for V1 actions */ + if (v == 0) + v = SMFI_V1_ACTS; + ctx->ctx_mta_aflags = v; /* MTA action flags */ + + (void) memcpy((void *) &i, (void *) &(g->a_buf[MILTER_LEN_BYTES * 2]), + MILTER_LEN_BYTES); + v = ntohl(i); + + /* no flags? set to default value for V1 protocol */ + if (v == 0) + v = SMFI_V1_PROT; + ctx->ctx_mta_pflags = v; /* MTA protocol flags */ + + /* + ** Copy flags from milter struct into libmilter context; + ** this variable will be used later on to check whether + ** the MTA "actions" can fulfill the milter requirements, + ** but it may be overwritten by the negotiate callback. + */ + + ctx->ctx_aflags = ctx->ctx_smfi->xxfi_flags; + fake_pflags = SMFIP_NR_CONN + |SMFIP_NR_HELO + |SMFIP_NR_MAIL + |SMFIP_NR_RCPT + |SMFIP_NR_DATA + |SMFIP_NR_UNKN + |SMFIP_NR_HDR + |SMFIP_NR_EOH + |SMFIP_NR_BODY + ; + + if (g->a_ctx->ctx_smfi != NULL && + g->a_ctx->ctx_smfi->xxfi_version > 4 && + (fi_negotiate = g->a_ctx->ctx_smfi->xxfi_negotiate) != NULL) + { + int r; + unsigned long m_aflags, m_pflags, m_f2, m_f3; + + /* + ** let milter decide whether the features offered by the + ** MTA are "good enough". + ** Notes: + ** - libmilter can "fake" some features (e.g., SMFIP_NR_HDR) + ** - m_f2, m_f3 are for future extensions + */ + + m_f2 = m_f3 = 0; + m_aflags = ctx->ctx_mta_aflags; + m_pflags = ctx->ctx_pflags; + if ((SMFIP_SKIP & ctx->ctx_mta_pflags) != 0) + m_pflags |= SMFIP_SKIP; + r = fi_negotiate(g->a_ctx, + ctx->ctx_mta_aflags, + ctx->ctx_mta_pflags|fake_pflags, + 0, 0, + &m_aflags, &m_pflags, &m_f2, &m_f3); + + /* + ** Types of protocol flags (pflags): + ** 1. do NOT send protocol step X + ** 2. MTA can do/understand something extra (SKIP, + ** send unknown RCPTs) + ** 3. MTA can deal with "no reply" for various protocol steps + ** Note: this mean that it isn't possible to simply set all + ** flags to get "everything": + ** setting a flag of type 1 turns off a step + ** (it should be the other way around: + ** a flag means a protocol step can be sent) + ** setting a flag of type 3 requires that milter + ** never sends a reply for the corresponding step. + ** Summary: the "negation" of protocol flags is causing + ** problems, but at least for type 3 there is no simple + ** solution. + ** + ** What should "all options" mean? + ** send all protocol steps _except_ those for which there is + ** no callback (currently registered in ctx_pflags) + ** expect SKIP as return code? Yes + ** send unknown RCPTs? No, + ** must be explicitly requested? + ** "no reply" for some protocol steps? No, + ** must be explicitly requested. + */ + + if (SMFIS_ALL_OPTS == r) + { + ctx->ctx_aflags = ctx->ctx_mta_aflags; + ctx->ctx_pflags2mta = ctx->ctx_pflags; + if ((SMFIP_SKIP & ctx->ctx_mta_pflags) != 0) + ctx->ctx_pflags2mta |= SMFIP_SKIP; + } + else if (r != SMFIS_CONTINUE) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%ld]: xxfi_negotiate returned %d (protocol options=0x%lx, actions=0x%lx)", + ctx->ctx_smfi->xxfi_name, + (long) ctx->ctx_id, r, ctx->ctx_mta_pflags, + ctx->ctx_mta_aflags); + return _SMFIS_ABORT; + } + else + { + ctx->ctx_aflags = m_aflags; + ctx->ctx_pflags = m_pflags; + ctx->ctx_pflags2mta = m_pflags; + } + + /* check whether some flags need to be "faked" */ + i = ctx->ctx_pflags2mta; + if ((ctx->ctx_mta_pflags & i) != i) + { + unsigned int idx; + unsigned long b; + + /* + ** If some behavior can be faked (set in fake_pflags), + ** but the MTA doesn't support it, then unset + ** that flag in the value that is sent to the MTA. + */ + + for (idx = 0; idx < 32; idx++) + { + b = 1 << idx; + if ((ctx->ctx_mta_pflags & b) != b && + (fake_pflags & b) == b) + ctx->ctx_pflags2mta &= ~b; + } + } + } + else + { + /* + ** Set the protocol flags based on the values determined + ** in mi_listener() which checked the defined callbacks. + */ + + ctx->ctx_pflags2mta = ctx->ctx_pflags; + } + + /* check whether actions and protocol requirements can be satisfied */ + i = ctx->ctx_aflags; + if ((i & ctx->ctx_mta_aflags) != i) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%ld]: 0x%lx does not fulfill action requirements 0x%x", + ctx->ctx_smfi->xxfi_name, + (long) ctx->ctx_id, ctx->ctx_mta_aflags, i); + return _SMFIS_ABORT; + } + + i = ctx->ctx_pflags2mta; + if ((ctx->ctx_mta_pflags & i) != i) + { + /* + ** Older MTAs do not support some protocol steps. + ** As this protocol is a bit "wierd" (it asks for steps + ** NOT to be taken/sent) we have to check whether we + ** should turn off those "negative" requests. + ** Currently these are only SMFIP_NODATA and SMFIP_NOUNKNOWN. + */ + + if (bitset(SMFIP_NODATA, ctx->ctx_pflags2mta) && + !bitset(SMFIP_NODATA, ctx->ctx_mta_pflags)) + ctx->ctx_pflags2mta &= ~SMFIP_NODATA; + if (bitset(SMFIP_NOUNKNOWN, ctx->ctx_pflags2mta) && + !bitset(SMFIP_NOUNKNOWN, ctx->ctx_mta_pflags)) + ctx->ctx_pflags2mta &= ~SMFIP_NOUNKNOWN; + i = ctx->ctx_pflags2mta; + } + + if ((ctx->ctx_mta_pflags & i) != i) + { + smi_log(SMI_LOG_ERR, + "%s: st_optionneg[%ld]: 0x%lx does not fulfill protocol requirements 0x%x", + ctx->ctx_smfi->xxfi_name, + (long) ctx->ctx_id, ctx->ctx_mta_pflags, i); + return _SMFIS_ABORT; + } + + if (ctx->ctx_dbg > 3) + sm_dprintf("[%ld] milter_negotiate:" + " mta_actions=0x%lx, mta_flags=0x%lx" + " actions=0x%lx, flags=0x%lx\n" + , (long) ctx->ctx_id + , ctx->ctx_mta_aflags, ctx->ctx_mta_pflags + , ctx->ctx_aflags, ctx->ctx_pflags); + + return _SMFIS_OPTIONS; +} + +/* +** ST_CONNECTINFO -- receive connection information +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_connectinfo(g) + genarg *g; +{ + size_t l; + size_t i; + char *s, family; + unsigned short port = 0; + _SOCK_ADDR sockaddr; + sfsistat (*fi_connect) __P((SMFICTX *, char *, _SOCK_ADDR *)); + + if (g == NULL) + return _SMFIS_ABORT; + mi_clr_macros(g->a_ctx, g->a_idx + 1); + if (g->a_ctx->ctx_smfi == NULL || + (fi_connect = g->a_ctx->ctx_smfi->xxfi_connect) == NULL) + return SMFIS_CONTINUE; + + s = g->a_buf; + i = 0; + l = g->a_len; + while (s[i] != '\0' && i <= l) + ++i; + if (i + 1 >= l) + return _SMFIS_ABORT; + + /* Move past trailing \0 in host string */ + i++; + family = s[i++]; + (void) memset(&sockaddr, '\0', sizeof sockaddr); + if (family != SMFIA_UNKNOWN) + { + if (i + sizeof port >= l) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%ld]: wrong len %d >= %d", + g->a_ctx->ctx_smfi->xxfi_name, + (long) g->a_ctx->ctx_id, (int) i, (int) l); + return _SMFIS_ABORT; + } + (void) memcpy((void *) &port, (void *) (s + i), + sizeof port); + i += sizeof port; + + /* make sure string is terminated */ + if (s[l - 1] != '\0') + return _SMFIS_ABORT; +# if NETINET + if (family == SMFIA_INET) + { + if (inet_aton(s + i, (struct in_addr *) &sockaddr.sin.sin_addr) + != 1) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%ld]: inet_aton failed", + g->a_ctx->ctx_smfi->xxfi_name, + (long) g->a_ctx->ctx_id); + return _SMFIS_ABORT; + } + sockaddr.sa.sa_family = AF_INET; + if (port > 0) + sockaddr.sin.sin_port = port; + } + else +# endif /* NETINET */ +# if NETINET6 + if (family == SMFIA_INET6) + { + if (mi_inet_pton(AF_INET6, s + i, + &sockaddr.sin6.sin6_addr) != 1) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%ld]: mi_inet_pton failed", + g->a_ctx->ctx_smfi->xxfi_name, + (long) g->a_ctx->ctx_id); + return _SMFIS_ABORT; + } + sockaddr.sa.sa_family = AF_INET6; + if (port > 0) + sockaddr.sin6.sin6_port = port; + } + else +# endif /* NETINET6 */ +# if NETUNIX + if (family == SMFIA_UNIX) + { + if (sm_strlcpy(sockaddr.sunix.sun_path, s + i, + sizeof sockaddr.sunix.sun_path) >= + sizeof sockaddr.sunix.sun_path) + { + smi_log(SMI_LOG_ERR, + "%s: connect[%ld]: path too long", + g->a_ctx->ctx_smfi->xxfi_name, + (long) g->a_ctx->ctx_id); + return _SMFIS_ABORT; + } + sockaddr.sunix.sun_family = AF_UNIX; + } + else +# endif /* NETUNIX */ + { + smi_log(SMI_LOG_ERR, + "%s: connect[%ld]: unknown family %d", + g->a_ctx->ctx_smfi->xxfi_name, + (long) g->a_ctx->ctx_id, family); + return _SMFIS_ABORT; + } + } + return (*fi_connect)(g->a_ctx, g->a_buf, + family != SMFIA_UNKNOWN ? &sockaddr : NULL); +} + +/* +** ST_EOH -- end of headers +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_eoh(g) + genarg *g; +{ + sfsistat (*fi_eoh) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + (fi_eoh = g->a_ctx->ctx_smfi->xxfi_eoh) != NULL) + return (*fi_eoh)(g->a_ctx); + return SMFIS_CONTINUE; +} + +/* +** ST_DATA -- DATA command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_data(g) + genarg *g; +{ + sfsistat (*fi_data) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + g->a_ctx->ctx_smfi->xxfi_version > 3 && + (fi_data = g->a_ctx->ctx_smfi->xxfi_data) != NULL) + return (*fi_data)(g->a_ctx); + return SMFIS_CONTINUE; +} + +/* +** ST_HELO -- helo/ehlo command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_helo(g) + genarg *g; +{ + sfsistat (*fi_helo) __P((SMFICTX *, char *)); + + if (g == NULL) + return _SMFIS_ABORT; + mi_clr_macros(g->a_ctx, g->a_idx + 1); + if (g->a_ctx->ctx_smfi != NULL && + (fi_helo = g->a_ctx->ctx_smfi->xxfi_helo) != NULL) + { + /* paranoia: check for terminating '\0' */ + if (g->a_len == 0 || g->a_buf[g->a_len - 1] != '\0') + return MI_FAILURE; + return (*fi_helo)(g->a_ctx, g->a_buf); + } + return SMFIS_CONTINUE; +} + +/* +** ST_HEADER -- header line +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_header(g) + genarg *g; +{ + char *hf, *hv; + sfsistat (*fi_header) __P((SMFICTX *, char *, char *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi == NULL || + (fi_header = g->a_ctx->ctx_smfi->xxfi_header) == NULL) + return SMFIS_CONTINUE; + if (dec_arg2(g->a_buf, g->a_len, &hf, &hv) == MI_SUCCESS) + return (*fi_header)(g->a_ctx, hf, hv); + else + return _SMFIS_ABORT; +} + +#define ARGV_FCT(lf, rf, idx) \ + char **argv; \ + sfsistat (*lf) __P((SMFICTX *, char **)); \ + int r; \ + \ + if (g == NULL) \ + return _SMFIS_ABORT; \ + mi_clr_macros(g->a_ctx, g->a_idx + 1); \ + if (g->a_ctx->ctx_smfi == NULL || \ + (lf = g->a_ctx->ctx_smfi->rf) == NULL) \ + return SMFIS_CONTINUE; \ + if ((argv = dec_argv(g->a_buf, g->a_len)) == NULL) \ + return _SMFIS_ABORT; \ + r = (*lf)(g->a_ctx, argv); \ + free(argv); \ + return r; + +/* +** ST_SENDER -- MAIL FROM command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_sender(g) + genarg *g; +{ + ARGV_FCT(fi_envfrom, xxfi_envfrom, CI_MAIL) +} + +/* +** ST_RCPT -- RCPT TO command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_rcpt(g) + genarg *g; +{ + ARGV_FCT(fi_envrcpt, xxfi_envrcpt, CI_RCPT) +} + +/* +** ST_UNKNOWN -- unrecognized or unimplemented command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_unknown(g) + genarg *g; +{ + sfsistat (*fi_unknown) __P((SMFICTX *, const char *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + g->a_ctx->ctx_smfi->xxfi_version > 2 && + (fi_unknown = g->a_ctx->ctx_smfi->xxfi_unknown) != NULL) + return (*fi_unknown)(g->a_ctx, (const char *) g->a_buf); + return SMFIS_CONTINUE; +} + +/* +** ST_MACROS -- deal with macros received from the MTA +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue/keep +** +** Side effects: +** set pointer in macro array to current values. +*/ + +static int +st_macros(g) + genarg *g; +{ + int i; + char **argv; + + if (g == NULL || g->a_len < 1) + return _SMFIS_FAIL; + if ((argv = dec_argv(g->a_buf + 1, g->a_len - 1)) == NULL) + return _SMFIS_FAIL; + switch (g->a_buf[0]) + { + case SMFIC_CONNECT: + i = CI_CONN; + break; + case SMFIC_HELO: + i = CI_HELO; + break; + case SMFIC_MAIL: + i = CI_MAIL; + break; + case SMFIC_RCPT: + i = CI_RCPT; + break; + case SMFIC_DATA: + i = CI_DATA; + break; + case SMFIC_BODYEOB: + i = CI_EOM; + break; + case SMFIC_EOH: + i = CI_EOH; + break; + default: + free(argv); + return _SMFIS_FAIL; + } + if (g->a_ctx->ctx_mac_ptr[i] != NULL) + free(g->a_ctx->ctx_mac_ptr[i]); + if (g->a_ctx->ctx_mac_buf[i] != NULL) + free(g->a_ctx->ctx_mac_buf[i]); + g->a_ctx->ctx_mac_ptr[i] = argv; + g->a_ctx->ctx_mac_buf[i] = g->a_buf; + return _SMFIS_KEEP; +} + +/* +** ST_QUIT -- quit command +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** noreply +*/ + +/* ARGSUSED */ +static int +st_quit(g) + genarg *g; +{ + sfsistat (*fi_close) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + (fi_close = g->a_ctx->ctx_smfi->xxfi_close) != NULL) + (void) (*fi_close)(g->a_ctx); + mi_clr_macros(g->a_ctx, 0); + return _SMFIS_NOREPLY; +} + +/* +** ST_BODYCHUNK -- deal with a piece of the mail body +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +*/ + +static int +st_bodychunk(g) + genarg *g; +{ + sfsistat (*fi_body) __P((SMFICTX *, unsigned char *, size_t)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g->a_ctx->ctx_smfi != NULL && + (fi_body = g->a_ctx->ctx_smfi->xxfi_body) != NULL) + return (*fi_body)(g->a_ctx, (unsigned char *)g->a_buf, + g->a_len); + return SMFIS_CONTINUE; +} + +/* +** ST_BODYEND -- deal with the last piece of the mail body +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** continue or filter-specified value +** +** Side effects: +** sends a reply for the body part (if non-empty). +*/ + +static int +st_bodyend(g) + genarg *g; +{ + sfsistat r; + sfsistat (*fi_body) __P((SMFICTX *, unsigned char *, size_t)); + sfsistat (*fi_eom) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + r = SMFIS_CONTINUE; + if (g->a_ctx->ctx_smfi != NULL) + { + if ((fi_body = g->a_ctx->ctx_smfi->xxfi_body) != NULL && + g->a_len > 0) + { + socket_t sd; + struct timeval timeout; + + timeout.tv_sec = g->a_ctx->ctx_timeout; + timeout.tv_usec = 0; + sd = g->a_ctx->ctx_sd; + r = (*fi_body)(g->a_ctx, (unsigned char *)g->a_buf, + g->a_len); + if (r != SMFIS_CONTINUE && + sendreply(r, sd, &timeout, g->a_ctx) != MI_SUCCESS) + return _SMFIS_ABORT; + } + } + if (r == SMFIS_CONTINUE && + (fi_eom = g->a_ctx->ctx_smfi->xxfi_eom) != NULL) + return (*fi_eom)(g->a_ctx); + return r; +} + +/* +** ST_ABORTFCT -- deal with aborts +** +** Parameters: +** g -- generic argument structure +** +** Returns: +** abort or filter-specified value +*/ + +static int +st_abortfct(g) + genarg *g; +{ + sfsistat (*fi_abort) __P((SMFICTX *)); + + if (g == NULL) + return _SMFIS_ABORT; + if (g != NULL && g->a_ctx->ctx_smfi != NULL && + (fi_abort = g->a_ctx->ctx_smfi->xxfi_abort) != NULL) + (void) (*fi_abort)(g->a_ctx); + return _SMFIS_NOREPLY; +} + +/* +** TRANS_OK -- is the state transition ok? +** +** Parameters: +** old -- old state +** new -- new state +** +** Returns: +** state transition ok +*/ + +static bool +trans_ok(old, new) + int old, new; +{ + int s, n; + + s = old; + if (s >= SIZE_NEXT_STATES) + return false; + do + { + /* is this state transition allowed? */ + if ((MI_MASK(new) & next_states[s]) != 0) + return true; + + /* + ** no: try next state; + ** this works since the relevant states are ordered + ** strict sequentially + */ + + n = s + 1; + if (n >= SIZE_NEXT_STATES) + return false; + + /* + ** can we actually "skip" this state? + ** see fix_stm() which sets this bit for those + ** states which the filter program is not interested in + */ + + if (bitset(NX_SKIP, next_states[n])) + s = n; + else + return false; + } while (s < SIZE_NEXT_STATES); + return false; +} + +/* +** FIX_STM -- add "skip" bits to the state transition table +** +** Parameters: +** ctx -- context structure +** +** Returns: +** None. +** +** Side effects: +** may change state transition table. +*/ + +static void +fix_stm(ctx) + SMFICTX_PTR ctx; +{ + unsigned long fl; + + if (ctx == NULL || ctx->ctx_smfi == NULL) + return; + fl = ctx->ctx_pflags; + if (bitset(SMFIP_NOCONNECT, fl)) + next_states[ST_CONN] |= NX_SKIP; + if (bitset(SMFIP_NOHELO, fl)) + next_states[ST_HELO] |= NX_SKIP; + if (bitset(SMFIP_NOMAIL, fl)) + next_states[ST_MAIL] |= NX_SKIP; + if (bitset(SMFIP_NORCPT, fl)) + next_states[ST_RCPT] |= NX_SKIP; + if (bitset(SMFIP_NOHDRS, fl)) + next_states[ST_HDRS] |= NX_SKIP; + if (bitset(SMFIP_NOEOH, fl)) + next_states[ST_EOHS] |= NX_SKIP; + if (bitset(SMFIP_NOBODY, fl)) + next_states[ST_BODY] |= NX_SKIP; + if (bitset(SMFIP_NODATA, fl)) + next_states[ST_DATA] |= NX_SKIP; + if (bitset(SMFIP_NOUNKNOWN, fl)) + next_states[ST_UNKN] |= NX_SKIP; +} + +/* +** DEC_ARGV -- split a buffer into a list of strings, NULL terminated +** +** Parameters: +** buf -- buffer with several strings +** len -- length of buffer +** +** Returns: +** array of pointers to the individual strings +*/ + +static char ** +dec_argv(buf, len) + char *buf; + size_t len; +{ + char **s; + size_t i; + int elem, nelem; + + nelem = 0; + for (i = 0; i < len; i++) + { + if (buf[i] == '\0') + ++nelem; + } + if (nelem == 0) + return NULL; + + /* last entry is only for the name */ + s = (char **)malloc((nelem + 1) * (sizeof *s)); + if (s == NULL) + return NULL; + s[0] = buf; + for (i = 0, elem = 0; i < len && elem < nelem; i++) + { + if (buf[i] == '\0') + { + ++elem; + if (i + 1 >= len) + s[elem] = NULL; + else + s[elem] = &(buf[i + 1]); + } + } + + /* overwrite last entry (already done above, just paranoia) */ + s[elem] = NULL; + return s; +} + +/* +** DEC_ARG2 -- split a buffer into two strings +** +** Parameters: +** buf -- buffer with two strings +** len -- length of buffer +** s1,s2 -- pointer to result strings +** +** Returns: +** MI_FAILURE/MI_SUCCESS +*/ + +static int +dec_arg2(buf, len, s1, s2) + char *buf; + size_t len; + char **s1; + char **s2; +{ + size_t i; + + /* paranoia: check for terminating '\0' */ + if (len == 0 || buf[len - 1] != '\0') + return MI_FAILURE; + *s1 = buf; + for (i = 1; i < len && buf[i] != '\0'; i++) + continue; + if (i >= len - 1) + return MI_FAILURE; + *s2 = buf + i + 1; + return MI_SUCCESS; +} + +/* +** SENDOK -- is it ok for the filter to send stuff to the MTA? +** +** Parameters: +** ctx -- context structure +** flag -- flag to check +** +** Returns: +** sending allowed (in current state) +*/ + +bool +mi_sendok(ctx, flag) + SMFICTX_PTR ctx; + int flag; +{ + if (ctx == NULL || ctx->ctx_smfi == NULL) + return false; + + /* did the milter request this operation? */ + if (flag != 0 && !bitset(flag, ctx->ctx_aflags)) + return false; + + /* are we in the correct state? It must be "End of Message". */ + return ctx->ctx_state == ST_ENDM; +} + +#if _FFR_WORKERS_POOL +/* +** MI_RD_SOCKET_READY - checks if the socket is ready for read(2) +** +** Parameters: +** sd -- socket_t +** +** Returns: +** true iff socket is ready for read(2) +*/ + +#define MI_RD_CMD_TO 1 +#define MI_RD_MAX_ERR 16 + +static bool +mi_rd_socket_ready (sd) + socket_t sd; +{ + int n; + int nerr = 0; +#if SM_CONF_POLL + struct pollfd pfd; +#else /* SM_CONF_POLL */ + fd_set rd_set, exc_set; +#endif /* SM_CONF_POLL */ + + do + { +#if SM_CONF_POLL + pfd.fd = sd; + pfd.events = POLLIN; + pfd.revents = 0; + + n = poll(&pfd, 1, MI_RD_CMD_TO); +#else /* SM_CONF_POLL */ + struct timeval timeout; + + FD_ZERO(&rd_set); + FD_ZERO(&exc_set); + FD_SET(sd, &rd_set); + FD_SET(sd, &exc_set); + + timeout.tv_sec = MI_RD_CMD_TO / 1000; + timeout.tv_usec = 0; + n = select(sd + 1, &rd_set, NULL, &exc_set, &timeout); +#endif /* SM_CONF_POLL */ + + if (n < 0) + { + if (errno == EINTR) + { + nerr++; + continue; + } + return true; + } + + if (n == 0) + return false; + break; + } while (nerr < MI_RD_MAX_ERR); + if (nerr >= MI_RD_MAX_ERR) + return false; + +#if SM_CONF_POLL + return (pfd.revents != 0); +#else /* SM_CONF_POLL */ + return FD_ISSET(sd, &rd_set) || FD_ISSET(sd, &exc_set); +#endif /* SM_CONF_POLL */ +} +#endif /* _FFR_WORKERS_POOL */ |