diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h index 2c432cb94..ee7586c61 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -23,6 +23,7 @@ #define _PROTO_PROTO_TCP_H #include +#include #include #include diff --git a/include/types/acl.h b/include/types/acl.h index 0f1cd0701..51acfe743 100644 --- a/include/types/acl.h +++ b/include/types/acl.h @@ -78,13 +78,16 @@ enum { ACL_TEST_F_VOL_TXN = 1 << 5, /* result sensitive to new transaction (eg: persist) */ ACL_TEST_F_VOL_SESS = 1 << 6, /* result sensitive to new session (eg: IP) */ ACL_TEST_F_VOLATILE = (1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6), - ACL_TEST_F_FETCH_MORE = 1 << 7, /* if test does not match, retry with next entry */ + ACL_TEST_F_FETCH_MORE = 1 << 7, /* if test does not match, retry with next entry (for multi-match) */ + ACL_TEST_F_MAY_CHANGE = 1 << 8, /* if test does not match, retry later (eg: request size) */ }; -/* ACLs can be evaluated on requests and on responses. */ +/* ACLs can be evaluated on requests and on responses, and on partial or complete data */ enum { ACL_DIR_REQ = 0, /* ACL evaluated on request */ - ACL_DIR_RTR, /* ACL evaluated on response */ + ACL_DIR_RTR = (1 << 0), /* ACL evaluated on response */ + ACL_DIR_MASK = (ACL_DIR_REQ | ACL_DIR_RTR), + ACL_PARTIAL = (1 << 1), /* partial data, return MISS if data are missing */ }; /* possible flags for expressions or patterns */ diff --git a/include/types/client.h b/include/types/client.h index 24f405f20..8ce88c215 100644 --- a/include/types/client.h +++ b/include/types/client.h @@ -24,19 +24,6 @@ #include -/* - * FIXME: break this into HTTP state and TCP socket state. - * See server.h for the other end. - */ - -/* different possible states for the client side */ -#define CL_STHEADERS 0 -#define CL_STDATA 1 -#define CL_STSHUTR 2 -#define CL_STSHUTW 3 -#define CL_STCLOSE 4 - - #endif /* _TYPES_CLIENT_H */ /* diff --git a/include/types/proto_http.h b/include/types/proto_http.h index cc647a67f..713da49c1 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -29,9 +29,16 @@ /* * FIXME: break this into HTTP state and TCP socket state. - * See client.h for the other end. */ +/* different possible states for the client side */ +#define CL_STINSPECT 0 +#define CL_STHEADERS 1 +#define CL_STDATA 2 +#define CL_STSHUTR 3 +#define CL_STSHUTW 4 +#define CL_STCLOSE 5 + /* different possible states for the server side */ #define SV_STIDLE 0 #define SV_STANALYZE 1 /* this server state is set by the client to study the body for server assignment */ diff --git a/include/types/proto_tcp.h b/include/types/proto_tcp.h new file mode 100644 index 000000000..54d12a7d5 --- /dev/null +++ b/include/types/proto_tcp.h @@ -0,0 +1,49 @@ +/* + include/types/proto_tcp.h + This file contains TCP protocol definitions. + + Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_PROTO_TCP_H +#define _TYPES_PROTO_TCP_H + +#include +#include + +#include + +/* Layer4 accept/reject rules */ +enum { + TCP_ACT_ACCEPT = 1, + TCP_ACT_REJECT = 2, +}; + +struct tcp_rule { + struct list list; + struct acl_cond *cond; + int action; +}; + +#endif /* _TYPES_PROTO_TCP_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/proxy.h b/include/types/proxy.h index 2a84aaf7f..415b1a1af 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -131,6 +131,10 @@ struct proxy { struct list block_cond; /* early blocking conditions (chained) */ struct list redirect_rules; /* content redirecting rules (chained) */ struct list switching_rules; /* content switching rules (chained) */ + struct { /* TCP request processing */ + int inspect_delay; /* inspection delay */ + struct list inspect_rules; /* inspection rules */ + } tcp_req; struct server *srv; /* known servers */ int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */ diff --git a/include/types/session.h b/include/types/session.h index b97a54ad4..5bcd0d795 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -115,6 +115,7 @@ struct session { struct server *prev_srv; /* the server the was running on, after a redispatch, otherwise NULL */ struct pendconn *pend_pos; /* if not NULL, points to the position in the pending queue */ struct http_txn txn; /* current HTTP transaction being processed. Should become a list. */ + int inspect_exp; /* expiration date for data to be inspected, in ticks */ struct { int logwait; /* log fields waiting to be collected : LW_* */ struct timeval accept_date; /* date of the accept() in user date */ diff --git a/src/acl.c b/src/acl.c index ddbd4bc2d..036424eae 100644 --- a/src/acl.c +++ b/src/acl.c @@ -818,11 +818,16 @@ struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int p } /* Execute condition and return either ACL_PAT_FAIL, ACL_PAT_MISS or - * ACL_PAT_PASS depending on the test results. This function only computes the - * condition, it does not apply the polarity required by IF/UNLESS, it's up to - * the caller to do this using something like this : + * ACL_PAT_PASS depending on the test results. ACL_PAT_MISS may only be + * returned if contains ACL_PARTIAL, indicating that incomplete data + * is being examined. + * This function only computes the condition, it does not apply the polarity + * required by IF/UNLESS, it's up to the caller to do this using something like + * this : * * res = acl_pass(res); + * if (res == ACL_PAT_MISS) + * return 0; * if (cond->pol == ACL_COND_UNLESS) * res = !res; */ @@ -866,8 +871,12 @@ int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, v /* we need to reset context and flags */ memset(&test, 0, sizeof(test)); fetch_next: - if (!expr->kw->fetch(px, l4, l7, dir, expr, &test)) + if (!expr->kw->fetch(px, l4, l7, dir, expr, &test)) { + /* maybe we could not fetch because of missing data */ + if (test.flags & ACL_TEST_F_MAY_CHANGE && dir & ACL_PARTIAL) + acl_res |= ACL_PAT_MISS; continue; + } /* apply all tests to this value */ list_for_each_entry(pattern, &expr->patterns, list) { @@ -898,6 +907,13 @@ int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, v if (test.flags & ACL_TEST_F_FETCH_MORE) goto fetch_next; + + /* sometimes we know the fetched data is subject to change + * later and give another chance for a new match (eg: request + * size, time, ...) + */ + if (test.flags & ACL_TEST_F_MAY_CHANGE && dir & ACL_PARTIAL) + acl_res |= ACL_PAT_MISS; } /* * Here we have the result of an ACL (cached or not). diff --git a/src/cfgparse.c b/src/cfgparse.c index b2af64814..a492a932e 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -614,6 +614,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv) LIST_INIT(&curproxy->redirect_rules); LIST_INIT(&curproxy->mon_fail_cond); LIST_INIT(&curproxy->switching_rules); + LIST_INIT(&curproxy->tcp_req.inspect_rules); /* Timeouts are defined as -1, so we cannot use the zeroed area * as a default value. diff --git a/src/client.c b/src/client.c index a40c2a408..e76a74109 100644 --- a/src/client.c +++ b/src/client.c @@ -168,7 +168,10 @@ int event_accept(int fd) { * backend must be assigned if set. */ if (p->mode == PR_MODE_HTTP) { - s->cli_state = CL_STHEADERS; + if (s->fe->tcp_req.inspect_delay) + s->cli_state = CL_STINSPECT; + else + s->cli_state = CL_STHEADERS; } else { /* We must assign any default backend now since * there will be no header processing. @@ -178,7 +181,10 @@ int event_accept(int fd) { s->be = p->defbe.be; s->flags |= SN_BE_ASSIGNED; } - s->cli_state = CL_STDATA; /* no HTTP headers for non-HTTP proxies */ + if (s->fe->tcp_req.inspect_delay) + s->cli_state = CL_STINSPECT; + else + s->cli_state = CL_STDATA; /* no HTTP headers for non-HTTP proxies */ } s->srv_state = SV_STIDLE; @@ -340,7 +346,7 @@ int event_accept(int fd) { buffer_init(s->req); s->req->rlim += BUFSIZE; - if (s->cli_state == CL_STHEADERS) /* reserve some space for header rewriting */ + if (p->mode == PR_MODE_HTTP) /* reserve some space for header rewriting */ s->req->rlim -= MAXREWRITE; s->req->rto = s->fe->timeout.client; @@ -390,6 +396,7 @@ int event_accept(int fd) { s->rep->rex = TICK_ETERNITY; s->rep->wex = TICK_ETERNITY; s->txn.exp = TICK_ETERNITY; + s->inspect_exp = TICK_ETERNITY; t->expire = TICK_ETERNITY; if (s->fe->timeout.client) { @@ -407,6 +414,10 @@ int event_accept(int fd) { s->txn.exp = tick_add(now_ms, s->fe->timeout.httpreq); t->expire = tick_first(t->expire, s->txn.exp); } + else if (s->cli_state == CL_STINSPECT && s->fe->tcp_req.inspect_delay) { + s->inspect_exp = tick_add(now_ms, s->fe->tcp_req.inspect_delay); + t->expire = tick_first(t->expire, s->inspect_exp); + } if (p->mode != PR_MODE_HEALTH) task_wakeup(t); diff --git a/src/proto_http.c b/src/proto_http.c index 3a8ab9aa7..ac29c64a4 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -680,6 +681,8 @@ void process_session(struct task *t, int *next) t->expire = tick_first(t->expire, s->req->cex); if (s->cli_state == CL_STHEADERS) t->expire = tick_first(t->expire, s->txn.exp); + else if (s->cli_state == CL_STINSPECT) + t->expire = tick_first(t->expire, s->inspect_exp); /* restore t to its place in the task list */ task_queue(t); @@ -1550,7 +1553,104 @@ int process_cli(struct session *t) req->rex.tv_sec, req->rex.tv_usec, rep->wex.tv_sec, rep->wex.tv_usec); - if (c == CL_STHEADERS) { + if (c == CL_STINSPECT) { + struct tcp_rule *rule; + int partial; + + /* We will abort if we encounter a read error. In theory, + * we should not abort if we get a close, it might be + * valid, also very unlikely. FIXME: we'll abort for now, + * this will be easier to change later. + */ + if (unlikely(req->flags & (BF_READ_ERROR | BF_READ_NULL))) { + t->inspect_exp = TICK_ETERNITY; + buffer_shutr(req); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + t->fe->failed_req++; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLICL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + /* Abort if client read timeout has expired */ + else if (unlikely(tick_is_expired(req->rex, now_ms))) { + t->inspect_exp = TICK_ETERNITY; + buffer_shutr(req); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + t->fe->failed_req++; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + /* We don't know whether we have enough data, so must proceed + * this way : + * - iterate through all rules in their declaration order + * - if one rule returns MISS, it means the inspect delay is + * not over yet, then return immediately, otherwise consider + * it as a non-match. + * - if one rule returns OK, then return OK + * - if one rule returns KO, then return KO + */ + + if (tick_is_expired(t->inspect_exp, now_ms)) + partial = 0; + else + partial = ACL_PARTIAL; + + list_for_each_entry(rule, &t->fe->tcp_req.inspect_rules, list) { + int ret = ACL_PAT_PASS; + + if (rule->cond) { + ret = acl_exec_cond(rule->cond, t->fe, t, NULL, ACL_DIR_REQ | partial); + if (ret == ACL_PAT_MISS) { + req->rex = tick_add_ifset(now_ms, t->fe->timeout.client); + return 0; + } + ret = acl_pass(ret); + if (rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + } + + if (ret) { + /* we have a matching rule. */ + if (rule->action == TCP_ACT_REJECT) { + buffer_shutr(req); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + t->fe->failed_req++; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + t->inspect_exp = TICK_ETERNITY; + return 1; + } + /* otherwise accept */ + break; + } + } + + /* if we get there, it means we have no rule which matches, so + * we apply the default accept. + */ + req->rex = tick_add_ifset(now_ms, t->fe->timeout.client); + if (t->fe->mode == PR_MODE_HTTP) { + t->cli_state = CL_STHEADERS; + t->txn.exp = tick_add_ifset(now_ms, t->fe->timeout.httpreq); + } else { + t->cli_state = CL_STDATA; + } + t->inspect_exp = TICK_ETERNITY; + return 1; + } + else if (c == CL_STHEADERS) { /* * Now parse the partial (or complete) lines. * We will check the request syntax, and also join multi-line @@ -2606,7 +2706,7 @@ int process_srv(struct session *t) * we need to defer server selection until more data arrives, if possible. * This is rare, and only if balancing on parameter hash with values in the entity of a POST */ - if (c == CL_STHEADERS ) + if (c == CL_STHEADERS || c == CL_STINSPECT) return 0; /* stay in idle, waiting for data to reach the client side */ else if (c == CL_STCLOSE || c == CL_STSHUTW || (c == CL_STSHUTR && @@ -5202,6 +5302,9 @@ acl_fetch_meth(struct proxy *px, struct session *l4, void *l7, int dir, int meth; struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; @@ -5259,6 +5362,9 @@ acl_fetch_rqver(struct proxy *px, struct session *l4, void *l7, int dir, char *ptr; int len; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; @@ -5284,6 +5390,9 @@ acl_fetch_stver(struct proxy *px, struct session *l4, void *l7, int dir, char *ptr; int len; + if (!txn) + return 0; + if (txn->rsp.msg_state != HTTP_MSG_BODY) return 0; @@ -5310,6 +5419,9 @@ acl_fetch_stcode(struct proxy *px, struct session *l4, void *l7, int dir, char *ptr; int len; + if (!txn) + return 0; + if (txn->rsp.msg_state != HTTP_MSG_BODY) return 0; @@ -5328,8 +5440,12 @@ acl_fetch_url(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; @@ -5348,8 +5464,12 @@ acl_fetch_url_ip(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; @@ -5376,8 +5496,12 @@ acl_fetch_url_port(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; @@ -5404,6 +5528,9 @@ acl_fetch_hdr(struct proxy *px, struct session *l4, void *l7, char *sol, struct hdr_idx *idx = &txn->hdr_idx; struct hdr_ctx *ctx = (struct hdr_ctx *)test->ctx.a; + if (!txn) + return 0; + if (!(test->flags & ACL_TEST_F_FETCH_MORE)) /* search for header from the beginning */ ctx->idx = 0; @@ -5427,8 +5554,12 @@ acl_fetch_chdr(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; @@ -5442,6 +5573,9 @@ acl_fetch_shdr(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->rsp.msg_state != HTTP_MSG_BODY) return 0; @@ -5460,6 +5594,9 @@ acl_fetch_hdr_cnt(struct proxy *px, struct session *l4, void *l7, char *sol, struct hdr_ctx ctx; int cnt; + if (!txn) + return 0; + ctx.idx = 0; cnt = 0; while (http_find_header2(expr->arg.str, expr->arg_len, sol, idx, &ctx)) @@ -5476,8 +5613,12 @@ acl_fetch_chdr_cnt(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; @@ -5491,6 +5632,9 @@ acl_fetch_shdr_cnt(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->rsp.msg_state != HTTP_MSG_BODY) return 0; @@ -5509,6 +5653,9 @@ acl_fetch_hdr_val(struct proxy *px, struct session *l4, void *l7, char *sol, struct hdr_idx *idx = &txn->hdr_idx; struct hdr_ctx *ctx = (struct hdr_ctx *)test->ctx.a; + if (!txn) + return 0; + if (!(test->flags & ACL_TEST_F_FETCH_MORE)) /* search for header from the beginning */ ctx->idx = 0; @@ -5531,8 +5678,12 @@ acl_fetch_chdr_val(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; @@ -5546,6 +5697,9 @@ acl_fetch_shdr_val(struct proxy *px, struct session *l4, void *l7, int dir, { struct http_txn *txn = l7; + if (!txn) + return 0; + if (txn->rsp.msg_state != HTTP_MSG_BODY) return 0; @@ -5562,8 +5716,12 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir, struct http_txn *txn = l7; char *ptr, *end; + if (!txn) + return 0; + if (txn->req.msg_state != HTTP_MSG_BODY) return 0; + if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE) /* ensure the indexes are not affected */ return 0; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index d122a03a1..e2afb9467 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -329,11 +331,139 @@ void tcpv6_add_listener(struct listener *listener) proto_tcpv6.nb_listeners++; } +/* This function should be called to parse a line starting with the "tcp-request" + * keyword. + */ +static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, char *err, int errlen) +{ + const char *ptr = NULL; + int val; + int retlen; + + if (!*args[1]) { + snprintf(err, errlen, "missing argument for '%s' in %s '%s'", + args[0], proxy_type_str(proxy), curpx->id); + return -1; + } + + if (!strcmp(args[1], "inspect-delay")) { + if (curpx == defpx) { + snprintf(err, errlen, "%s %s is not allowed in 'defaults' sections", + args[0], args[1]); + return -1; + } + + if (!(curpx->cap & PR_CAP_FE)) { + snprintf(err, errlen, "%s %s will be ignored because %s '%s' has no %s capability", + args[0], args[1], proxy_type_str(proxy), curpx->id, + "frontend"); + return 1; + } + + if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) { + retlen = snprintf(err, errlen, + "'%s %s' expects a positive delay in milliseconds, in %s '%s'", + args[0], args[1], proxy_type_str(proxy), curpx->id); + if (ptr && retlen < errlen) + retlen += snprintf(err+retlen, errlen - retlen, + " (unexpected character '%c')", *ptr); + return -1; + } + + if (curpx->tcp_req.inspect_delay) { + snprintf(err, errlen, "ignoring %s %s (was already defined) in %s '%s'", + args[0], args[1], proxy_type_str(proxy), curpx->id); + return 1; + } + curpx->tcp_req.inspect_delay = val; + return 0; + } + + if (!strcmp(args[1], "content")) { + int action; + int pol = ACL_COND_NONE; + struct acl_cond *cond; + struct tcp_rule *rule; + + if (curpx == defpx) { + snprintf(err, errlen, "%s %s is not allowed in 'defaults' sections", + args[0], args[1]); + return -1; + } + + if (!strcmp(args[2], "accept")) + action = TCP_ACT_ACCEPT; + else if (!strcmp(args[2], "reject")) + action = TCP_ACT_REJECT; + else { + retlen = snprintf(err, errlen, + "'%s %s' expects 'accept' or 'reject', in %s '%s' (was '%s')", + args[0], args[1], proxy_type_str(curpx), curpx->id, args[2]); + return -1; + } + + pol = ACL_COND_NONE; + cond = NULL; + + if (!strcmp(args[3], "if")) + pol = ACL_COND_IF; + else if (!strcmp(args[3], "unless")) + pol = ACL_COND_UNLESS; + + /* Note: we consider "if TRUE" when there is no condition */ + if (pol != ACL_COND_NONE && + (cond = parse_acl_cond((const char **)args+4, &curpx->acl, pol)) == NULL) { + retlen = snprintf(err, errlen, + "Error detected in %s '%s' while parsing '%s' condition", + proxy_type_str(curpx), curpx->id, args[3]); + return -1; + } + + rule = (struct tcp_rule *)calloc(1, sizeof(*rule)); + rule->cond = cond; + rule->action = action; + LIST_INIT(&rule->list); + LIST_ADDQ(&curpx->tcp_req.inspect_rules, &rule->list); + return 0; + } + + snprintf(err, errlen, "unknown argument '%s' after '%s' in %s '%s'", + args[1], args[0], proxy_type_str(proxy), curpx->id); + return -1; +} + +/* return the number of bytes in the request buffer */ +static int +acl_fetch_req_len(struct proxy *px, struct session *l4, void *l7, int dir, + struct acl_expr *expr, struct acl_test *test) +{ + if (!l4 || !l4->req) + return 0; + + test->i = l4->req->l; + test->flags = ACL_TEST_F_VOLATILE | ACL_TEST_F_MAY_CHANGE; + return 1; +} + + +static struct cfg_kw_list cfg_kws = {{ },{ + { CFG_LISTEN, "tcp-request", tcp_parse_tcp_req }, + { 0, NULL, NULL }, +}}; + +static struct acl_kw_list acl_kws = {{ },{ + { "req_len", acl_parse_int, acl_fetch_req_len, acl_match_int }, + { NULL, NULL, NULL, NULL }, +}}; + __attribute__((constructor)) static void __tcp_protocol_init(void) { protocol_register(&proto_tcpv4); protocol_register(&proto_tcpv6); + cfg_register_keywords(&cfg_kws); + acl_register_keywords(&acl_kws); } diff --git a/tests/test-inspect-smtp.cfg b/tests/test-inspect-smtp.cfg new file mode 100644 index 000000000..1c0d46ddd --- /dev/null +++ b/tests/test-inspect-smtp.cfg @@ -0,0 +1,35 @@ +# This is a test configuration. It listens on port 8025, waits for an incoming +# connection, and applies the following rules : +# - if the address is in the white list, then accept it and forward the +# connection to the server (local port 25) +# - if the address is in the black list, then immediately drop it +# - otherwise, wait up to 3 seconds. If the client talks during this time, +# drop the connection. +# - then accept the connection if it passes all the tests. +# +# Note that the rules are evaluated at every new chunk of data read, and at +# delay expiration. Rules which apply to incomplete data don't match as long +# as the timer has not expired. + +listen block-fake-mailers + log 127.0.0.1:514 local0 + option tcplog + + mode tcp + bind :8025 + timeout client 6s + timeout server 6s + timeout connect 6s + + tcp-request inspect-delay 4s + + acl white_list src 127.0.0.2 + acl black_list src 127.0.0.3 + acl talkative req_len gt 0 + + tcp-request content accept if white_list + tcp-request content reject if black_list + tcp-request content reject if talkative + + balance roundrobin + server mail 127.0.0.1:25