From b46aad6df449445a9fc4aa7b32bd40005438e3f7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:18:05 +0200 Subject: Adding upstream version 2.9.5. Signed-off-by: Daniel Baumann --- src/acl.c | 1377 +++++ src/action.c | 363 ++ src/activity.c | 1248 ++++ src/applet.c | 501 ++ src/arg.c | 479 ++ src/auth.c | 316 + src/backend.c | 3401 +++++++++++ src/base64.c | 303 + src/cache.c | 3014 ++++++++++ src/calltrace.c | 286 + src/cbuf.c | 59 + src/cfgcond.c | 559 ++ src/cfgdiag.c | 97 + src/cfgparse-global.c | 1396 +++++ src/cfgparse-listen.c | 3073 ++++++++++ src/cfgparse-quic.c | 292 + src/cfgparse-ssl.c | 2382 ++++++++ src/cfgparse-tcp.c | 275 + src/cfgparse-unix.c | 135 + src/cfgparse.c | 4798 +++++++++++++++ src/channel.c | 591 ++ src/check.c | 2642 +++++++++ src/chunk.c | 311 + src/cli.c | 3423 +++++++++++ src/clock.c | 460 ++ src/compression.c | 742 +++ src/connection.c | 2748 +++++++++ src/cpuset.c | 296 + src/debug.c | 2301 ++++++++ src/dgram.c | 79 + src/dict.c | 127 + src/dns.c | 1330 +++++ src/dynbuf.c | 129 + src/eb32sctree.c | 472 ++ src/eb32tree.c | 218 + src/eb64tree.c | 218 + src/ebimtree.c | 44 + src/ebistree.c | 42 + src/ebmbtree.c | 77 + src/ebpttree.c | 208 + src/ebsttree.c | 42 + src/ebtree.c | 50 + src/errors.c | 567 ++ src/ev_epoll.c | 413 ++ src/ev_evports.c | 441 ++ src/ev_kqueue.c | 380 ++ src/ev_poll.c | 348 ++ src/ev_select.c | 335 ++ src/event_hdl.c | 999 ++++ src/extcheck.c | 694 +++ src/fcgi-app.c | 1133 ++++ src/fcgi.c | 294 + src/fd.c | 1348 +++++ src/filters.c | 1125 ++++ src/fix.c | 264 + src/flt_bwlim.c | 976 ++++ src/flt_http_comp.c | 1076 ++++ src/flt_spoe.c | 4739 +++++++++++++++ src/flt_trace.c | 675 +++ src/freq_ctr.c | 218 + src/frontend.c | 339 ++ src/h1.c | 1319 +++++ src/h1_htx.c | 1074 ++++ src/h2.c | 814 +++ src/h3.c | 2403 ++++++++ src/h3_stats.c | 276 + src/haproxy.c | 3962 +++++++++++++ src/hash.c | 190 + src/hlua.c | 13961 ++++++++++++++++++++++++++++++++++++++++++++ src/hlua_fcn.c | 2721 +++++++++ src/hpack-dec.c | 475 ++ src/hpack-enc.c | 210 + src/hpack-huff.c | 861 +++ src/hpack-tbl.c | 372 ++ src/hq_interop.c | 174 + src/http.c | 1433 +++++ src/http_acl.c | 185 + src/http_act.c | 2501 ++++++++ src/http_ana.c | 5153 ++++++++++++++++ src/http_client.c | 1598 +++++ src/http_conv.c | 453 ++ src/http_ext.c | 1881 ++++++ src/http_fetch.c | 2368 ++++++++ src/http_htx.c | 3028 ++++++++++ src/http_rules.c | 530 ++ src/htx.c | 1099 ++++ src/init.c | 249 + src/jwt.c | 478 ++ src/lb_chash.c | 517 ++ src/lb_fas.c | 348 ++ src/lb_fwlc.c | 375 ++ src/lb_fwrr.c | 623 ++ src/lb_map.c | 281 + src/linuxcap.c | 191 + src/listener.c | 2487 ++++++++ src/log.c | 4659 +++++++++++++++ src/lru.c | 305 + src/mailers.c | 329 ++ src/map.c | 1232 ++++ src/mjson.c | 1048 ++++ src/mqtt.c | 1281 ++++ src/mux_fcgi.c | 4268 ++++++++++++++ src/mux_h1.c | 5374 +++++++++++++++++ src/mux_h2.c | 7598 ++++++++++++++++++++++++ src/mux_pt.c | 904 +++ src/mux_quic.c | 3067 ++++++++++ src/mworker-prog.c | 359 ++ src/mworker.c | 821 +++ src/namespace.c | 132 + src/ncbuf.c | 986 ++++ src/pattern.c | 2683 +++++++++ src/payload.c | 1448 +++++ src/peers.c | 4231 ++++++++++++++ src/pipe.c | 136 + src/pool.c | 1539 +++++ src/proto_quic.c | 799 +++ src/proto_rhttp.c | 464 ++ src/proto_sockpair.c | 589 ++ src/proto_tcp.c | 834 +++ src/proto_udp.c | 247 + src/proto_uxdg.c | 159 + src/proto_uxst.c | 372 ++ src/protocol.c | 309 + src/proxy.c | 3451 +++++++++++ src/qmux_http.c | 108 + src/qmux_trace.c | 114 + src/qpack-dec.c | 563 ++ src/qpack-enc.c | 185 + src/qpack-tbl.c | 415 ++ src/queue.c | 761 +++ src/quic_ack.c | 258 + src/quic_cc.c | 49 + src/quic_cc_cubic.c | 542 ++ src/quic_cc_newreno.c | 220 + src/quic_cc_nocc.c | 76 + src/quic_cid.c | 286 + src/quic_cli.c | 413 ++ src/quic_conn.c | 1893 ++++++ src/quic_frame.c | 1273 ++++ src/quic_loss.c | 312 + src/quic_openssl_compat.c | 531 ++ src/quic_retransmit.c | 252 + src/quic_retry.c | 320 + src/quic_rx.c | 2290 ++++++++ src/quic_sock.c | 1080 ++++ src/quic_ssl.c | 790 +++ src/quic_stats.c | 215 + src/quic_stream.c | 294 + src/quic_tls.c | 1095 ++++ src/quic_tp.c | 714 +++ src/quic_trace.c | 633 ++ src/quic_tx.c | 2348 ++++++++ src/raw_sock.c | 489 ++ src/regex.c | 459 ++ src/resolvers.c | 3813 ++++++++++++ src/ring.c | 482 ++ src/sample.c | 5173 ++++++++++++++++ src/server.c | 6765 +++++++++++++++++++++ src/server_state.c | 947 +++ src/session.c | 528 ++ src/sha1.c | 308 + src/shctx.c | 320 + src/signal.c | 284 + src/sink.c | 1406 +++++ src/slz.c | 1421 +++++ src/sock.c | 1072 ++++ src/sock_inet.c | 521 ++ src/sock_unix.c | 387 ++ src/ssl_ckch.c | 3968 +++++++++++++ src/ssl_crtlist.c | 1577 +++++ src/ssl_ocsp.c | 1986 +++++++ src/ssl_sample.c | 2389 ++++++++ src/ssl_sock.c | 8100 +++++++++++++++++++++++++ src/ssl_utils.c | 702 +++ src/stats.c | 5521 ++++++++++++++++++ src/stconn.c | 2050 +++++++ src/stick_table.c | 5658 ++++++++++++++++++ src/stream.c | 4045 +++++++++++++ src/task.c | 979 ++++ src/tcp_act.c | 749 +++ src/tcp_rules.c | 1428 +++++ src/tcp_sample.c | 641 ++ src/tcpcheck.c | 5150 ++++++++++++++++ src/thread.c | 1864 ++++++ src/time.c | 147 + src/tools.c | 6348 ++++++++++++++++++++ src/trace.c | 997 ++++ src/uri_auth.c | 318 + src/uri_normalizer.c | 467 ++ src/vars.c | 1454 +++++ src/version.c | 28 + src/wdt.c | 193 + src/xprt_handshake.c | 299 + src/xprt_quic.c | 175 + 194 files changed, 261120 insertions(+) create mode 100644 src/acl.c create mode 100644 src/action.c create mode 100644 src/activity.c create mode 100644 src/applet.c create mode 100644 src/arg.c create mode 100644 src/auth.c create mode 100644 src/backend.c create mode 100644 src/base64.c create mode 100644 src/cache.c create mode 100644 src/calltrace.c create mode 100644 src/cbuf.c create mode 100644 src/cfgcond.c create mode 100644 src/cfgdiag.c create mode 100644 src/cfgparse-global.c create mode 100644 src/cfgparse-listen.c create mode 100644 src/cfgparse-quic.c create mode 100644 src/cfgparse-ssl.c create mode 100644 src/cfgparse-tcp.c create mode 100644 src/cfgparse-unix.c create mode 100644 src/cfgparse.c create mode 100644 src/channel.c create mode 100644 src/check.c create mode 100644 src/chunk.c create mode 100644 src/cli.c create mode 100644 src/clock.c create mode 100644 src/compression.c create mode 100644 src/connection.c create mode 100644 src/cpuset.c create mode 100644 src/debug.c create mode 100644 src/dgram.c create mode 100644 src/dict.c create mode 100644 src/dns.c create mode 100644 src/dynbuf.c create mode 100644 src/eb32sctree.c create mode 100644 src/eb32tree.c create mode 100644 src/eb64tree.c create mode 100644 src/ebimtree.c create mode 100644 src/ebistree.c create mode 100644 src/ebmbtree.c create mode 100644 src/ebpttree.c create mode 100644 src/ebsttree.c create mode 100644 src/ebtree.c create mode 100644 src/errors.c create mode 100644 src/ev_epoll.c create mode 100644 src/ev_evports.c create mode 100644 src/ev_kqueue.c create mode 100644 src/ev_poll.c create mode 100644 src/ev_select.c create mode 100644 src/event_hdl.c create mode 100644 src/extcheck.c create mode 100644 src/fcgi-app.c create mode 100644 src/fcgi.c create mode 100644 src/fd.c create mode 100644 src/filters.c create mode 100644 src/fix.c create mode 100644 src/flt_bwlim.c create mode 100644 src/flt_http_comp.c create mode 100644 src/flt_spoe.c create mode 100644 src/flt_trace.c create mode 100644 src/freq_ctr.c create mode 100644 src/frontend.c create mode 100644 src/h1.c create mode 100644 src/h1_htx.c create mode 100644 src/h2.c create mode 100644 src/h3.c create mode 100644 src/h3_stats.c create mode 100644 src/haproxy.c create mode 100644 src/hash.c create mode 100644 src/hlua.c create mode 100644 src/hlua_fcn.c create mode 100644 src/hpack-dec.c create mode 100644 src/hpack-enc.c create mode 100644 src/hpack-huff.c create mode 100644 src/hpack-tbl.c create mode 100644 src/hq_interop.c create mode 100644 src/http.c create mode 100644 src/http_acl.c create mode 100644 src/http_act.c create mode 100644 src/http_ana.c create mode 100644 src/http_client.c create mode 100644 src/http_conv.c create mode 100644 src/http_ext.c create mode 100644 src/http_fetch.c create mode 100644 src/http_htx.c create mode 100644 src/http_rules.c create mode 100644 src/htx.c create mode 100644 src/init.c create mode 100644 src/jwt.c create mode 100644 src/lb_chash.c create mode 100644 src/lb_fas.c create mode 100644 src/lb_fwlc.c create mode 100644 src/lb_fwrr.c create mode 100644 src/lb_map.c create mode 100644 src/linuxcap.c create mode 100644 src/listener.c create mode 100644 src/log.c create mode 100644 src/lru.c create mode 100644 src/mailers.c create mode 100644 src/map.c create mode 100644 src/mjson.c create mode 100644 src/mqtt.c create mode 100644 src/mux_fcgi.c create mode 100644 src/mux_h1.c create mode 100644 src/mux_h2.c create mode 100644 src/mux_pt.c create mode 100644 src/mux_quic.c create mode 100644 src/mworker-prog.c create mode 100644 src/mworker.c create mode 100644 src/namespace.c create mode 100644 src/ncbuf.c create mode 100644 src/pattern.c create mode 100644 src/payload.c create mode 100644 src/peers.c create mode 100644 src/pipe.c create mode 100644 src/pool.c create mode 100644 src/proto_quic.c create mode 100644 src/proto_rhttp.c create mode 100644 src/proto_sockpair.c create mode 100644 src/proto_tcp.c create mode 100644 src/proto_udp.c create mode 100644 src/proto_uxdg.c create mode 100644 src/proto_uxst.c create mode 100644 src/protocol.c create mode 100644 src/proxy.c create mode 100644 src/qmux_http.c create mode 100644 src/qmux_trace.c create mode 100644 src/qpack-dec.c create mode 100644 src/qpack-enc.c create mode 100644 src/qpack-tbl.c create mode 100644 src/queue.c create mode 100644 src/quic_ack.c create mode 100644 src/quic_cc.c create mode 100644 src/quic_cc_cubic.c create mode 100644 src/quic_cc_newreno.c create mode 100644 src/quic_cc_nocc.c create mode 100644 src/quic_cid.c create mode 100644 src/quic_cli.c create mode 100644 src/quic_conn.c create mode 100644 src/quic_frame.c create mode 100644 src/quic_loss.c create mode 100644 src/quic_openssl_compat.c create mode 100644 src/quic_retransmit.c create mode 100644 src/quic_retry.c create mode 100644 src/quic_rx.c create mode 100644 src/quic_sock.c create mode 100644 src/quic_ssl.c create mode 100644 src/quic_stats.c create mode 100644 src/quic_stream.c create mode 100644 src/quic_tls.c create mode 100644 src/quic_tp.c create mode 100644 src/quic_trace.c create mode 100644 src/quic_tx.c create mode 100644 src/raw_sock.c create mode 100644 src/regex.c create mode 100644 src/resolvers.c create mode 100644 src/ring.c create mode 100644 src/sample.c create mode 100644 src/server.c create mode 100644 src/server_state.c create mode 100644 src/session.c create mode 100644 src/sha1.c create mode 100644 src/shctx.c create mode 100644 src/signal.c create mode 100644 src/sink.c create mode 100644 src/slz.c create mode 100644 src/sock.c create mode 100644 src/sock_inet.c create mode 100644 src/sock_unix.c create mode 100644 src/ssl_ckch.c create mode 100644 src/ssl_crtlist.c create mode 100644 src/ssl_ocsp.c create mode 100644 src/ssl_sample.c create mode 100644 src/ssl_sock.c create mode 100644 src/ssl_utils.c create mode 100644 src/stats.c create mode 100644 src/stconn.c create mode 100644 src/stick_table.c create mode 100644 src/stream.c create mode 100644 src/task.c create mode 100644 src/tcp_act.c create mode 100644 src/tcp_rules.c create mode 100644 src/tcp_sample.c create mode 100644 src/tcpcheck.c create mode 100644 src/thread.c create mode 100644 src/time.c create mode 100644 src/tools.c create mode 100644 src/trace.c create mode 100644 src/uri_auth.c create mode 100644 src/uri_normalizer.c create mode 100644 src/vars.c create mode 100644 src/version.c create mode 100644 src/wdt.c create mode 100644 src/xprt_handshake.c create mode 100644 src/xprt_quic.c (limited to 'src') diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 0000000..8ef2b7d --- /dev/null +++ b/src/acl.c @@ -0,0 +1,1377 @@ +/* + * ACL management functions. + * + * Copyright 2000-2013 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* List head of all known ACL keywords */ +static struct acl_kw_list acl_keywords = { + .list = LIST_HEAD_INIT(acl_keywords.list) +}; + +/* input values are 0 or 3, output is the same */ +static inline enum acl_test_res pat2acl(struct pattern *pat) +{ + if (pat) + return ACL_TEST_PASS; + else + return ACL_TEST_FAIL; +} + +/* + * Registers the ACL keyword list as a list of valid keywords for next + * parsing sessions. + */ +void acl_register_keywords(struct acl_kw_list *kwl) +{ + LIST_APPEND(&acl_keywords.list, &kwl->list); +} + +/* + * Unregisters the ACL keyword list from the list of valid keywords. + */ +void acl_unregister_keywords(struct acl_kw_list *kwl) +{ + LIST_DELETE(&kwl->list); + LIST_INIT(&kwl->list); +} + +/* Return a pointer to the ACL within the list starting at , or + * NULL if not found. + */ +struct acl *find_acl_by_name(const char *name, struct list *head) +{ + struct acl *acl; + list_for_each_entry(acl, head, list) { + if (strcmp(acl->name, name) == 0) + return acl; + } + return NULL; +} + +/* Return a pointer to the ACL keyword , or NULL if not found. Note that if + * contains an opening parenthesis or a comma, only the left part of it is + * checked. + */ +struct acl_keyword *find_acl_kw(const char *kw) +{ + int index; + const char *kwend; + struct acl_kw_list *kwl; + + kwend = kw; + while (is_idchar(*kwend)) + kwend++; + + list_for_each_entry(kwl, &acl_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) && + kwl->kw[index].kw[kwend-kw] == 0) + return &kwl->kw[index]; + } + } + return NULL; +} + +static struct acl_expr *prune_acl_expr(struct acl_expr *expr) +{ + struct arg *arg; + + pattern_prune(&expr->pat); + + for (arg = expr->smp->arg_p; arg; arg++) { + if (arg->type == ARGT_STOP) + break; + if (arg->type == ARGT_STR || arg->unresolved) { + chunk_destroy(&arg->data.str); + arg->unresolved = 0; + } + } + + release_sample_expr(expr->smp); + + return expr; +} + +/* Parse an ACL expression starting at [0], and return it. If is + * not NULL, it will be filled with a pointer to an error message in case of + * error. This pointer must be freeable or NULL. is an arg_list serving + * as a list head to report missing dependencies. It may be NULL if such + * dependencies are not allowed. + * + * Right now, the only accepted syntax is : + * [...] + */ +struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *al, + const char *file, int line) +{ + __label__ out_return, out_free_expr; + struct acl_expr *expr; + struct acl_keyword *aclkw; + int refflags, patflags; + const char *arg; + struct sample_expr *smp = NULL; + int idx = 0; + char *ckw = NULL; + const char *endt; + int cur_type; + int nbargs; + int operator = STD_OP_EQ; + int op; + int contain_colon, have_dot; + const char *dot; + signed long long value, minor; + /* The following buffer contain two numbers, a ':' separator and the final \0. */ + char buffer[NB_LLMAX_STR + 1 + NB_LLMAX_STR + 1]; + int is_loaded; + int unique_id; + char *error; + struct pat_ref *ref; + struct pattern_expr *pattern_expr; + int load_as_map = 0; + int acl_conv_found = 0; + + /* First, we look for an ACL keyword. And if we don't find one, then + * we look for a sample fetch expression starting with a sample fetch + * keyword. + */ + + if (al) { + al->ctx = ARGC_ACL; // to report errors while resolving args late + al->kw = *args; + al->conv = NULL; + } + + aclkw = find_acl_kw(args[0]); + if (aclkw) { + /* OK we have a real ACL keyword */ + + /* build new sample expression for this ACL */ + smp = calloc(1, sizeof(*smp)); + if (!smp) { + memprintf(err, "out of memory when parsing ACL expression"); + goto out_return; + } + LIST_INIT(&(smp->conv_exprs)); + smp->fetch = aclkw->smp; + smp->arg_p = empty_arg_list; + + /* look for the beginning of the subject arguments */ + for (arg = args[0]; is_idchar(*arg); arg++) + ; + + /* At this point, we have : + * - args[0] : beginning of the keyword + * - arg : end of the keyword, first character not part of keyword + */ + nbargs = make_arg_list(arg, -1, smp->fetch->arg_mask, &smp->arg_p, + err, &endt, NULL, al); + if (nbargs < 0) { + /* note that make_arg_list will have set here */ + memprintf(err, "ACL keyword '%s' : %s", aclkw->kw, *err); + goto out_free_smp; + } + + if (!smp->arg_p) { + smp->arg_p = empty_arg_list; + } + else if (smp->fetch->val_args && !smp->fetch->val_args(smp->arg_p, err)) { + /* invalid keyword argument, error must have been + * set by val_args(). + */ + memprintf(err, "in argument to '%s', %s", aclkw->kw, *err); + goto out_free_smp; + } + + /* look for the beginning of the converters list. Those directly attached + * to the ACL keyword are found just after the comma. + * If we find any converter, then we don't use the ACL keyword's match + * anymore but the one related to the converter's output type. + */ + if (!sample_parse_expr_cnv((char **)args, NULL, NULL, err, al, file, line, smp, endt)) { + if (err) + memprintf(err, "ACL keyword '%s' : %s", aclkw->kw, *err); + goto out_free_smp; + } + acl_conv_found = !LIST_ISEMPTY(&smp->conv_exprs); + } + else { + /* This is not an ACL keyword, so we hope this is a sample fetch + * keyword that we're going to transparently use as an ACL. If + * so, we retrieve a completely parsed expression with args and + * convs already done. + */ + smp = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL); + if (!smp) { + memprintf(err, "%s in ACL expression '%s'", *err, *args); + goto out_return; + } + } + + /* get last effective output type for smp */ + cur_type = smp_expr_output_type(smp); + + expr = calloc(1, sizeof(*expr)); + if (!expr) { + memprintf(err, "out of memory when parsing ACL expression"); + goto out_free_smp; + } + + pattern_init_head(&expr->pat); + + expr->pat.expect_type = cur_type; + expr->smp = smp; + expr->kw = smp->fetch->kw; + smp = NULL; /* don't free it anymore */ + + if (aclkw && !acl_conv_found) { + expr->kw = aclkw->kw; + expr->pat.parse = aclkw->parse ? aclkw->parse : pat_parse_fcts[aclkw->match_type]; + expr->pat.index = aclkw->index ? aclkw->index : pat_index_fcts[aclkw->match_type]; + expr->pat.match = aclkw->match ? aclkw->match : pat_match_fcts[aclkw->match_type]; + expr->pat.prune = aclkw->prune ? aclkw->prune : pat_prune_fcts[aclkw->match_type]; + } + + if (!expr->pat.parse) { + /* Parse/index/match functions depend on the expression type, + * so we have to map them now. Some types can be automatically + * converted. + */ + switch (cur_type) { + case SMP_T_BOOL: + expr->pat.parse = pat_parse_fcts[PAT_MATCH_BOOL]; + expr->pat.index = pat_index_fcts[PAT_MATCH_BOOL]; + expr->pat.match = pat_match_fcts[PAT_MATCH_BOOL]; + expr->pat.prune = pat_prune_fcts[PAT_MATCH_BOOL]; + expr->pat.expect_type = pat_match_types[PAT_MATCH_BOOL]; + break; + case SMP_T_SINT: + expr->pat.parse = pat_parse_fcts[PAT_MATCH_INT]; + expr->pat.index = pat_index_fcts[PAT_MATCH_INT]; + expr->pat.match = pat_match_fcts[PAT_MATCH_INT]; + expr->pat.prune = pat_prune_fcts[PAT_MATCH_INT]; + expr->pat.expect_type = pat_match_types[PAT_MATCH_INT]; + break; + case SMP_T_ADDR: + case SMP_T_IPV4: + case SMP_T_IPV6: + expr->pat.parse = pat_parse_fcts[PAT_MATCH_IP]; + expr->pat.index = pat_index_fcts[PAT_MATCH_IP]; + expr->pat.match = pat_match_fcts[PAT_MATCH_IP]; + expr->pat.prune = pat_prune_fcts[PAT_MATCH_IP]; + expr->pat.expect_type = pat_match_types[PAT_MATCH_IP]; + break; + case SMP_T_STR: + expr->pat.parse = pat_parse_fcts[PAT_MATCH_STR]; + expr->pat.index = pat_index_fcts[PAT_MATCH_STR]; + expr->pat.match = pat_match_fcts[PAT_MATCH_STR]; + expr->pat.prune = pat_prune_fcts[PAT_MATCH_STR]; + expr->pat.expect_type = pat_match_types[PAT_MATCH_STR]; + break; + } + } + + /* Additional check to protect against common mistakes */ + if (expr->pat.parse && cur_type != SMP_T_BOOL && !*args[1]) { + ha_warning("parsing acl keyword '%s' :\n" + " no pattern to match against were provided, so this ACL will never match.\n" + " If this is what you intended, please add '--' to get rid of this warning.\n" + " If you intended to match only for existence, please use '-m found'.\n" + " If you wanted to force an int to match as a bool, please use '-m bool'.\n" + "\n", + args[0]); + } + + args++; + + /* check for options before patterns. Supported options are : + * -i : ignore case for all patterns by default + * -f : read patterns from those files + * -m : force matching method (must be used before -f) + * -M : load the file as map file + * -u : force the unique id of the acl + * -- : everything after this is not an option + */ + refflags = PAT_REF_ACL; + patflags = 0; + is_loaded = 0; + unique_id = -1; + while (**args == '-') { + if (strcmp(*args, "-i") == 0) + patflags |= PAT_MF_IGNORE_CASE; + else if (strcmp(*args, "-n") == 0) + patflags |= PAT_MF_NO_DNS; + else if (strcmp(*args, "-u") == 0) { + unique_id = strtol(args[1], &error, 10); + if (*error != '\0') { + memprintf(err, "the argument of -u must be an integer"); + goto out_free_expr; + } + + /* Check if this id is really unique. */ + if (pat_ref_lookupid(unique_id)) { + memprintf(err, "the id is already used"); + goto out_free_expr; + } + + args++; + } + else if (strcmp(*args, "-f") == 0) { + if (!expr->pat.parse) { + memprintf(err, "matching method must be specified first (using '-m') when using a sample fetch of this type ('%s')", expr->kw); + goto out_free_expr; + } + + if (!pattern_read_from_file(&expr->pat, refflags, args[1], patflags, load_as_map, err, file, line)) + goto out_free_expr; + is_loaded = 1; + args++; + } + else if (strcmp(*args, "-m") == 0) { + int idx; + + if (is_loaded) { + memprintf(err, "'-m' must only be specified before patterns and files in parsing ACL expression"); + goto out_free_expr; + } + + idx = pat_find_match_name(args[1]); + if (idx < 0) { + memprintf(err, "unknown matching method '%s' when parsing ACL expression", args[1]); + goto out_free_expr; + } + + /* Note: -m found is always valid, bool/int are compatible, str/bin/reg/len are compatible */ + if (idx != PAT_MATCH_FOUND && !sample_casts[cur_type][pat_match_types[idx]]) { + memprintf(err, "matching method '%s' cannot be used with fetch keyword '%s'", args[1], expr->kw); + goto out_free_expr; + } + expr->pat.parse = pat_parse_fcts[idx]; + expr->pat.index = pat_index_fcts[idx]; + expr->pat.match = pat_match_fcts[idx]; + expr->pat.prune = pat_prune_fcts[idx]; + expr->pat.expect_type = pat_match_types[idx]; + args++; + } + else if (strcmp(*args, "-M") == 0) { + refflags |= PAT_REF_MAP; + load_as_map = 1; + } + else if (strcmp(*args, "--") == 0) { + args++; + break; + } + else { + memprintf(err, "'%s' is not a valid ACL option. Please use '--' before any pattern beginning with a '-'", args[0]); + goto out_free_expr; + break; + } + args++; + } + + if (!expr->pat.parse) { + memprintf(err, "matching method must be specified first (using '-m') when using a sample fetch of this type ('%s')", expr->kw); + goto out_free_expr; + } + + /* Create displayed reference */ + snprintf(trash.area, trash.size, "acl '%s' file '%s' line %d", + expr->kw, file, line); + trash.area[trash.size - 1] = '\0'; + + /* Create new pattern reference. */ + ref = pat_ref_newid(unique_id, trash.area, PAT_REF_ACL); + if (!ref) { + memprintf(err, "memory error"); + goto out_free_expr; + } + + /* Create new pattern expression associated to this reference. */ + pattern_expr = pattern_new_expr(&expr->pat, ref, patflags, err, NULL); + if (!pattern_expr) + goto out_free_expr; + + /* now parse all patterns */ + while (**args) { + arg = *args; + + /* Compatibility layer. Each pattern can parse only one string per pattern, + * but the pat_parser_int() and pat_parse_dotted_ver() parsers were need + * optionally two operators. The first operator is the match method: eq, + * le, lt, ge and gt. pat_parse_int() and pat_parse_dotted_ver() functions + * can have a compatibility syntax based on ranges: + * + * pat_parse_int(): + * + * "eq x" -> "x" or "x:x" + * "le x" -> ":x" + * "lt x" -> ":y" (with y = x - 1) + * "ge x" -> "x:" + * "gt x" -> "y:" (with y = x + 1) + * + * pat_parse_dotted_ver(): + * + * "eq x.y" -> "x.y" or "x.y:x.y" + * "le x.y" -> ":x.y" + * "lt x.y" -> ":w.z" (with w.z = x.y - 1) + * "ge x.y" -> "x.y:" + * "gt x.y" -> "w.z:" (with w.z = x.y + 1) + * + * If y is not present, assume that is "0". + * + * The syntax eq, le, lt, ge and gt are proper to the acl syntax. The + * following block of code detect the operator, and rewrite each value + * in parsable string. + */ + if (expr->pat.parse == pat_parse_int || + expr->pat.parse == pat_parse_dotted_ver) { + /* Check for operator. If the argument is operator, memorise it and + * continue to the next argument. + */ + op = get_std_op(arg); + if (op != -1) { + operator = op; + args++; + continue; + } + + /* Check if the pattern contain ':' or '-' character. */ + contain_colon = (strchr(arg, ':') || strchr(arg, '-')); + + /* If the pattern contain ':' or '-' character, give it to the parser as is. + * If no contain ':' and operator is STD_OP_EQ, give it to the parser as is. + * In other case, try to convert the value according with the operator. + */ + if (!contain_colon && operator != STD_OP_EQ) { + /* Search '.' separator. */ + dot = strchr(arg, '.'); + if (!dot) { + have_dot = 0; + minor = 0; + dot = arg + strlen(arg); + } + else + have_dot = 1; + + /* convert the integer minor part for the pat_parse_dotted_ver() function. */ + if (expr->pat.parse == pat_parse_dotted_ver && have_dot) { + if (strl2llrc(dot+1, strlen(dot+1), &minor) != 0) { + memprintf(err, "'%s' is neither a number nor a supported operator", arg); + goto out_free_expr; + } + if (minor >= 65536) { + memprintf(err, "'%s' contains too large a minor value", arg); + goto out_free_expr; + } + } + + /* convert the integer value for the pat_parse_int() function, and the + * integer major part for the pat_parse_dotted_ver() function. + */ + if (strl2llrc(arg, dot - arg, &value) != 0) { + memprintf(err, "'%s' is neither a number nor a supported operator", arg); + goto out_free_expr; + } + if (expr->pat.parse == pat_parse_dotted_ver) { + if (value >= 65536) { + memprintf(err, "'%s' contains too large a major value", arg); + goto out_free_expr; + } + value = (value << 16) | (minor & 0xffff); + } + + switch (operator) { + + case STD_OP_EQ: /* this case is not possible. */ + memprintf(err, "internal error"); + goto out_free_expr; + + case STD_OP_GT: + value++; /* gt = ge + 1 */ + __fallthrough; + + case STD_OP_GE: + if (expr->pat.parse == pat_parse_int) + snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld:", value); + else + snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld.%lld:", + value >> 16, value & 0xffff); + arg = buffer; + break; + + case STD_OP_LT: + value--; /* lt = le - 1 */ + __fallthrough; + + case STD_OP_LE: + if (expr->pat.parse == pat_parse_int) + snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld", value); + else + snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld.%lld", + value >> 16, value & 0xffff); + arg = buffer; + break; + } + } + } + + /* Add sample to the reference, and try to compile it fior each pattern + * using this value. + */ + if (!pat_ref_add(ref, arg, NULL, err)) + goto out_free_expr; + args++; + } + + return expr; + + out_free_expr: + prune_acl_expr(expr); + free(expr); + out_free_smp: + free(ckw); + free(smp); + out_return: + return NULL; +} + +/* Purge everything in the acl , then return . */ +struct acl *prune_acl(struct acl *acl) { + + struct acl_expr *expr, *exprb; + + free(acl->name); + + list_for_each_entry_safe(expr, exprb, &acl->expr, list) { + LIST_DELETE(&expr->list); + prune_acl_expr(expr); + free(expr); + } + + return acl; +} + +/* Walk the ACL tree, following nested acl() sample fetches, for no more than + * max_recurse evaluations. Returns -1 if a recursive loop is detected, 0 if + * the max_recurse was reached, otherwise the number of max_recurse left. + */ +static int parse_acl_recurse(struct acl *acl, struct acl_expr *expr, int max_recurse) +{ + struct acl_term *term; + struct acl_sample *sample; + + if (strcmp(expr->smp->fetch->kw, "acl") != 0) + return max_recurse; + + if (--max_recurse <= 0) + return 0; + + sample = (struct acl_sample *)expr->smp->arg_p->data.ptr; + list_for_each_entry(term, &sample->suite.terms, list) { + if (term->acl == acl) + return -1; + list_for_each_entry(expr, &term->acl->expr, list) { + max_recurse = parse_acl_recurse(acl, expr, max_recurse); + if (max_recurse <= 0) + return max_recurse; + } + } + + return max_recurse; +} + +/* Parse an ACL with the name starting at [0], and with a list of already + * known ACLs in . If the ACL was not in the list, it will be added. + * A pointer to that ACL is returned. If the ACL has an empty name, then it's + * an anonymous one and it won't be merged with any other one. If is not + * NULL, it will be filled with an appropriate error. This pointer must be + * freeable or NULL. is the arg_list serving as a head for unresolved + * dependencies. It may be NULL if such dependencies are not allowed. + * + * args syntax: + */ +struct acl *parse_acl(const char **args, struct list *known_acl, char **err, struct arg_list *al, + const char *file, int line) +{ + __label__ out_return, out_free_acl_expr, out_free_name; + struct acl *cur_acl; + struct acl_expr *acl_expr; + char *name; + const char *pos; + + if (**args && (pos = invalid_char(*args))) { + memprintf(err, "invalid character in ACL name : '%c'", *pos); + goto out_return; + } + + acl_expr = parse_acl_expr(args + 1, err, al, file, line); + if (!acl_expr) { + /* parse_acl_expr will have filled here */ + goto out_return; + } + + /* Check for args beginning with an opening parenthesis just after the + * subject, as this is almost certainly a typo. Right now we can only + * emit a warning, so let's do so. + */ + if (!strchr(args[1], '(') && *args[2] == '(') + ha_warning("parsing acl '%s' :\n" + " matching '%s' for pattern '%s' is likely a mistake and probably\n" + " not what you want. Maybe you need to remove the extraneous space before '('.\n" + " If you are really sure this is not an error, please insert '--' between the\n" + " match and the pattern to make this warning message disappear.\n", + args[0], args[1], args[2]); + + if (*args[0]) + cur_acl = find_acl_by_name(args[0], known_acl); + else + cur_acl = NULL; + + if (cur_acl) { + int ret = parse_acl_recurse(cur_acl, acl_expr, ACL_MAX_RECURSE); + if (ret <= 0) { + if (ret < 0) + memprintf(err, "have a recursive loop"); + else + memprintf(err, "too deep acl() tree"); + goto out_free_acl_expr; + } + } else { + name = strdup(args[0]); + if (!name) { + memprintf(err, "out of memory when parsing ACL"); + goto out_free_acl_expr; + } + cur_acl = calloc(1, sizeof(*cur_acl)); + if (cur_acl == NULL) { + memprintf(err, "out of memory when parsing ACL"); + goto out_free_name; + } + + LIST_INIT(&cur_acl->expr); + LIST_APPEND(known_acl, &cur_acl->list); + cur_acl->name = name; + } + + /* We want to know what features the ACL needs (typically HTTP parsing), + * and where it may be used. If an ACL relies on multiple matches, it is + * OK if at least one of them may match in the context where it is used. + */ + cur_acl->use |= acl_expr->smp->fetch->use; + cur_acl->val |= acl_expr->smp->fetch->val; + LIST_APPEND(&cur_acl->expr, &acl_expr->list); + return cur_acl; + + out_free_name: + free(name); + out_free_acl_expr: + prune_acl_expr(acl_expr); + free(acl_expr); + out_return: + return NULL; +} + +/* Some useful ACLs provided by default. Only those used are allocated. */ + +const struct { + const char *name; + const char *expr[4]; /* put enough for longest expression */ +} default_acl_list[] = { + { .name = "TRUE", .expr = {"always_true",""}}, + { .name = "FALSE", .expr = {"always_false",""}}, + { .name = "LOCALHOST", .expr = {"src","127.0.0.1/8","::1",""}}, + { .name = "HTTP", .expr = {"req.proto_http",""}}, + { .name = "HTTP_1.0", .expr = {"req.ver","1.0",""}}, + { .name = "HTTP_1.1", .expr = {"req.ver","1.1",""}}, + { .name = "HTTP_2.0", .expr = {"req.ver","2.0",""}}, + { .name = "HTTP_3.0", .expr = {"req.ver","3.0",""}}, + { .name = "METH_CONNECT", .expr = {"method","CONNECT",""}}, + { .name = "METH_DELETE", .expr = {"method","DELETE",""}}, + { .name = "METH_GET", .expr = {"method","GET","HEAD",""}}, + { .name = "METH_HEAD", .expr = {"method","HEAD",""}}, + { .name = "METH_OPTIONS", .expr = {"method","OPTIONS",""}}, + { .name = "METH_POST", .expr = {"method","POST",""}}, + { .name = "METH_PUT", .expr = {"method","PUT",""}}, + { .name = "METH_TRACE", .expr = {"method","TRACE",""}}, + { .name = "HTTP_URL_ABS", .expr = {"url_reg","^[^/:]*://",""}}, + { .name = "HTTP_URL_SLASH", .expr = {"url_beg","/",""}}, + { .name = "HTTP_URL_STAR", .expr = {"url","*",""}}, + { .name = "HTTP_CONTENT", .expr = {"req.hdr_val(content-length)","gt","0",""}}, + { .name = "RDP_COOKIE", .expr = {"req.rdp_cookie_cnt","gt","0",""}}, + { .name = "REQ_CONTENT", .expr = {"req.len","gt","0",""}}, + { .name = "WAIT_END", .expr = {"wait_end",""}}, + { .name = NULL, .expr = {""}} +}; + +/* Find a default ACL from the default_acl list, compile it and return it. + * If the ACL is not found, NULL is returned. In theory, it cannot fail, + * except when default ACLs are broken, in which case it will return NULL. + * If is not NULL, the ACL will be queued at its tail. If is + * not NULL, it will be filled with an error message if an error occurs. This + * pointer must be freeable or NULL. is an arg_list serving as a list head + * to report missing dependencies. It may be NULL if such dependencies are not + * allowed. + */ +static struct acl *find_acl_default(const char *acl_name, struct list *known_acl, + char **err, struct arg_list *al, + const char *file, int line) +{ + __label__ out_return, out_free_acl_expr, out_free_name; + struct acl *cur_acl; + struct acl_expr *acl_expr; + char *name; + int index; + + for (index = 0; default_acl_list[index].name != NULL; index++) { + if (strcmp(acl_name, default_acl_list[index].name) == 0) + break; + } + + if (default_acl_list[index].name == NULL) { + memprintf(err, "no such ACL : '%s'", acl_name); + return NULL; + } + + acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr, err, al, file, line); + if (!acl_expr) { + /* parse_acl_expr must have filled err here */ + goto out_return; + } + + name = strdup(acl_name); + if (!name) { + memprintf(err, "out of memory when building default ACL '%s'", acl_name); + goto out_free_acl_expr; + } + + cur_acl = calloc(1, sizeof(*cur_acl)); + if (cur_acl == NULL) { + memprintf(err, "out of memory when building default ACL '%s'", acl_name); + goto out_free_name; + } + + cur_acl->name = name; + cur_acl->use |= acl_expr->smp->fetch->use; + cur_acl->val |= acl_expr->smp->fetch->val; + LIST_INIT(&cur_acl->expr); + LIST_APPEND(&cur_acl->expr, &acl_expr->list); + if (known_acl) + LIST_APPEND(known_acl, &cur_acl->list); + + return cur_acl; + + out_free_name: + free(name); + out_free_acl_expr: + prune_acl_expr(acl_expr); + free(acl_expr); + out_return: + return NULL; +} + +/* Parse an ACL condition starting at [0], relying on a list of already + * known ACLs passed in . The new condition is returned (or NULL in + * case of low memory). Supports multiple conditions separated by "or". If + * is not NULL, it will be filled with a pointer to an error message in + * case of error, that the caller is responsible for freeing. The initial + * location must either be freeable or NULL. The list serves as a list head + * for unresolved dependencies. It may be NULL if such dependencies are not + * allowed. + */ +struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, + enum acl_cond_pol pol, char **err, struct arg_list *al, + const char *file, int line) +{ + __label__ out_return, out_free_suite, out_free_term; + int arg, neg; + const char *word; + struct acl *cur_acl; + struct acl_term *cur_term; + struct acl_term_suite *cur_suite; + struct acl_cond *cond; + unsigned int suite_val; + + cond = calloc(1, sizeof(*cond)); + if (cond == NULL) { + memprintf(err, "out of memory when parsing condition"); + goto out_return; + } + + LIST_INIT(&cond->list); + LIST_INIT(&cond->suites); + cond->pol = pol; + cond->val = 0; + + cur_suite = NULL; + suite_val = ~0U; + neg = 0; + for (arg = 0; *args[arg]; arg++) { + word = args[arg]; + + /* remove as many exclamation marks as we can */ + while (*word == '!') { + neg = !neg; + word++; + } + + /* an empty word is allowed because we cannot force the user to + * always think about not leaving exclamation marks alone. + */ + if (!*word) + continue; + + if (strcasecmp(word, "or") == 0 || strcmp(word, "||") == 0) { + /* new term suite */ + cond->val |= suite_val; + suite_val = ~0U; + cur_suite = NULL; + neg = 0; + continue; + } + + if (strcmp(word, "{") == 0) { + /* we may have a complete ACL expression between two braces, + * find the last one. + */ + int arg_end = arg + 1; + const char **args_new; + + while (*args[arg_end] && strcmp(args[arg_end], "}") != 0) + arg_end++; + + if (!*args[arg_end]) { + memprintf(err, "missing closing '}' in condition"); + goto out_free_suite; + } + + args_new = calloc(1, (arg_end - arg + 1) * sizeof(*args_new)); + if (!args_new) { + memprintf(err, "out of memory when parsing condition"); + goto out_free_suite; + } + + args_new[0] = ""; + memcpy(args_new + 1, args + arg + 1, (arg_end - arg) * sizeof(*args_new)); + args_new[arg_end - arg] = ""; + cur_acl = parse_acl(args_new, known_acl, err, al, file, line); + free(args_new); + + if (!cur_acl) { + /* note that parse_acl() must have filled here */ + goto out_free_suite; + } + arg = arg_end; + } + else { + /* search for in the known ACL names. If we do not find + * it, let's look for it in the default ACLs, and if found, add + * it to the list of ACLs of this proxy. This makes it possible + * to override them. + */ + cur_acl = find_acl_by_name(word, known_acl); + if (cur_acl == NULL) { + cur_acl = find_acl_default(word, known_acl, err, al, file, line); + if (cur_acl == NULL) { + /* note that find_acl_default() must have filled here */ + goto out_free_suite; + } + } + } + + cur_term = calloc(1, sizeof(*cur_term)); + if (cur_term == NULL) { + memprintf(err, "out of memory when parsing condition"); + goto out_free_suite; + } + + cur_term->acl = cur_acl; + cur_term->neg = neg; + + /* Here it is a bit complex. The acl_term_suite is a conjunction + * of many terms. It may only be used if all of its terms are + * usable at the same time. So the suite's validity domain is an + * AND between all ACL keywords' ones. But, the global condition + * is valid if at least one term suite is OK. So it's an OR between + * all of their validity domains. We could emit a warning as soon + * as suite_val is null because it means that the last ACL is not + * compatible with the previous ones. Let's remain simple for now. + */ + cond->use |= cur_acl->use; + suite_val &= cur_acl->val; + + if (!cur_suite) { + cur_suite = calloc(1, sizeof(*cur_suite)); + if (cur_suite == NULL) { + memprintf(err, "out of memory when parsing condition"); + goto out_free_term; + } + LIST_INIT(&cur_suite->terms); + LIST_APPEND(&cond->suites, &cur_suite->list); + } + LIST_APPEND(&cur_suite->terms, &cur_term->list); + neg = 0; + } + + cond->val |= suite_val; + return cond; + + out_free_term: + free(cur_term); + out_free_suite: + free_acl_cond(cond); + out_return: + return NULL; +} + +/* Builds an ACL condition starting at the if/unless keyword. The complete + * condition is returned. NULL is returned in case of error or if the first + * word is neither "if" nor "unless". It automatically sets the file name and + * the line number in the condition for better error reporting, and sets the + * HTTP initialization requirements in the proxy. If is not NULL, it will + * be filled with a pointer to an error message in case of error, that the + * caller is responsible for freeing. The initial location must either be + * freeable or NULL. + */ +struct acl_cond *build_acl_cond(const char *file, int line, struct list *known_acl, + struct proxy *px, const char **args, char **err) +{ + enum acl_cond_pol pol = ACL_COND_NONE; + struct acl_cond *cond = NULL; + + if (err) + *err = NULL; + + if (strcmp(*args, "if") == 0) { + pol = ACL_COND_IF; + args++; + } + else if (strcmp(*args, "unless") == 0) { + pol = ACL_COND_UNLESS; + args++; + } + else { + memprintf(err, "conditions must start with either 'if' or 'unless'"); + return NULL; + } + + cond = parse_acl_cond(args, known_acl, pol, err, &px->conf.args, file, line); + if (!cond) { + /* note that parse_acl_cond must have filled here */ + return NULL; + } + + cond->file = file; + cond->line = line; + px->http_needed |= !!(cond->use & SMP_USE_HTTP_ANY); + return cond; +} + +/* Execute condition and return either ACL_TEST_FAIL, ACL_TEST_MISS or + * ACL_TEST_PASS depending on the test results. ACL_TEST_MISS may only be + * returned if does not contain SMP_OPT_FINAL, indicating that incomplete + * data is being examined. The function automatically sets SMP_OPT_ITERATE. 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_TEST_MISS) + * return 0; + * if (cond->pol == ACL_COND_UNLESS) + * res = !res; + */ +enum acl_test_res acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *sess, struct stream *strm, unsigned int opt) +{ + __label__ fetch_next; + struct acl_term_suite *suite; + struct acl_term *term; + struct acl_expr *expr; + struct acl *acl; + struct sample smp; + enum acl_test_res acl_res, suite_res, cond_res; + + /* ACLs are iterated over all values, so let's always set the flag to + * indicate this to the fetch functions. + */ + opt |= SMP_OPT_ITERATE; + + /* We're doing a logical OR between conditions so we initialize to FAIL. + * The MISS status is propagated down from the suites. + */ + cond_res = ACL_TEST_FAIL; + list_for_each_entry(suite, &cond->suites, list) { + /* Evaluate condition suite . We stop at the first term + * which returns ACL_TEST_FAIL. The MISS status is still propagated + * in case of uncertainty in the result. + */ + + /* we're doing a logical AND between terms, so we must set the + * initial value to PASS. + */ + suite_res = ACL_TEST_PASS; + list_for_each_entry(term, &suite->terms, list) { + acl = term->acl; + + /* FIXME: use cache ! + * check acl->cache_idx for this. + */ + + /* ACL result not cached. Let's scan all the expressions + * and use the first one to match. + */ + acl_res = ACL_TEST_FAIL; + list_for_each_entry(expr, &acl->expr, list) { + /* we need to reset context and flags */ + memset(&smp, 0, sizeof(smp)); + fetch_next: + if (!sample_process(px, sess, strm, opt, expr->smp, &smp)) { + /* maybe we could not fetch because of missing data */ + if (smp.flags & SMP_F_MAY_CHANGE && !(opt & SMP_OPT_FINAL)) + acl_res |= ACL_TEST_MISS; + continue; + } + + acl_res |= pat2acl(pattern_exec_match(&expr->pat, &smp, 0)); + /* + * OK now acl_res holds the result of this expression + * as one of ACL_TEST_FAIL, ACL_TEST_MISS or ACL_TEST_PASS. + * + * Then if (!MISS) we can cache the result, and put + * (smp.flags & SMP_F_VOLATILE) in the cache flags. + * + * FIXME: implement cache. + * + */ + + /* we're ORing these terms, so a single PASS is enough */ + if (acl_res == ACL_TEST_PASS) + break; + + if (smp.flags & SMP_F_NOT_LAST) + 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 (smp.flags & SMP_F_MAY_CHANGE && !(opt & SMP_OPT_FINAL)) + acl_res |= ACL_TEST_MISS; + } + /* + * Here we have the result of an ACL (cached or not). + * ACLs are combined, negated or not, to form conditions. + */ + + if (term->neg) + acl_res = acl_neg(acl_res); + + suite_res &= acl_res; + + /* we're ANDing these terms, so a single FAIL or MISS is enough */ + if (suite_res != ACL_TEST_PASS) + break; + } + cond_res |= suite_res; + + /* we're ORing these terms, so a single PASS is enough */ + if (cond_res == ACL_TEST_PASS) + break; + } + return cond_res; +} + +/* Returns a pointer to the first ACL conflicting with usage at place + * which is one of the SMP_VAL_* bits indicating a check place, or NULL if + * no conflict is found. Only full conflicts are detected (ACL is not usable). + * Use the next function to check for useless keywords. + */ +const struct acl *acl_cond_conflicts(const struct acl_cond *cond, unsigned int where) +{ + struct acl_term_suite *suite; + struct acl_term *term; + struct acl *acl; + + list_for_each_entry(suite, &cond->suites, list) { + list_for_each_entry(term, &suite->terms, list) { + acl = term->acl; + if (!(acl->val & where)) + return acl; + } + } + return NULL; +} + +/* Returns a pointer to the first ACL and its first keyword to conflict with + * usage at place which is one of the SMP_VAL_* bits indicating a check + * place. Returns true if a conflict is found, with and set (if non + * null), or false if not conflict is found. The first useless keyword is + * returned. + */ +int acl_cond_kw_conflicts(const struct acl_cond *cond, unsigned int where, struct acl const **acl, char const **kw) +{ + struct acl_term_suite *suite; + struct acl_term *term; + struct acl_expr *expr; + + list_for_each_entry(suite, &cond->suites, list) { + list_for_each_entry(term, &suite->terms, list) { + list_for_each_entry(expr, &term->acl->expr, list) { + if (!(expr->smp->fetch->val & where)) { + if (acl) + *acl = term->acl; + if (kw) + *kw = expr->kw; + return 1; + } + } + } + } + return 0; +} + +/* + * Find targets for userlist and groups in acl. Function returns the number + * of errors or OK if everything is fine. It must be called only once sample + * fetch arguments have been resolved (after smp_resolve_args()). + */ +int acl_find_targets(struct proxy *p) +{ + + struct acl *acl; + struct acl_expr *expr; + struct pattern_list *pattern; + int cfgerr = 0; + struct pattern_expr_list *pexp; + + list_for_each_entry(acl, &p->acl, list) { + list_for_each_entry(expr, &acl->expr, list) { + if (strcmp(expr->kw, "http_auth_group") == 0) { + /* Note: the ARGT_USR argument may only have been resolved earlier + * by smp_resolve_args(). + */ + if (expr->smp->arg_p->unresolved) { + ha_alert("Internal bug in proxy %s: %sacl %s %s() makes use of unresolved userlist '%s'. Please report this.\n", + p->id, *acl->name ? "" : "anonymous ", acl->name, expr->kw, + expr->smp->arg_p->data.str.area); + cfgerr++; + continue; + } + + if (LIST_ISEMPTY(&expr->pat.head)) { + ha_alert("proxy %s: acl %s %s(): no groups specified.\n", + p->id, acl->name, expr->kw); + cfgerr++; + continue; + } + + /* For each pattern, check if the group exists. */ + list_for_each_entry(pexp, &expr->pat.head, list) { + if (LIST_ISEMPTY(&pexp->expr->patterns)) { + ha_alert("proxy %s: acl %s %s(): no groups specified.\n", + p->id, acl->name, expr->kw); + cfgerr++; + continue; + } + + list_for_each_entry(pattern, &pexp->expr->patterns, list) { + /* this keyword only has one argument */ + if (!check_group(expr->smp->arg_p->data.usr, pattern->pat.ptr.str)) { + ha_alert("proxy %s: acl %s %s(): invalid group '%s'.\n", + p->id, acl->name, expr->kw, pattern->pat.ptr.str); + cfgerr++; + } + } + } + } + } + } + + return cfgerr; +} + +/* initializes ACLs by resolving the sample fetch names they rely upon. + * Returns 0 on success, otherwise an error. + */ +int init_acl() +{ + int err = 0; + int index; + const char *name; + struct acl_kw_list *kwl; + struct sample_fetch *smp; + + list_for_each_entry(kwl, &acl_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + name = kwl->kw[index].fetch_kw; + if (!name) + name = kwl->kw[index].kw; + + smp = find_sample_fetch(name, strlen(name)); + if (!smp) { + ha_alert("Critical internal error: ACL keyword '%s' relies on sample fetch '%s' which was not registered!\n", + kwl->kw[index].kw, name); + err++; + continue; + } + kwl->kw[index].smp = smp; + } + } + return err; +} + +/* dump known ACL keywords on stdout */ +void acl_dump_kwd(void) +{ + struct acl_kw_list *kwl; + const struct acl_keyword *kwp, *kw; + const char *name; + int index; + + for (kw = kwp = NULL;; kwp = kw) { + list_for_each_entry(kwl, &acl_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if (strordered(kwp ? kwp->kw : NULL, + kwl->kw[index].kw, + kw != kwp ? kw->kw : NULL)) + kw = &kwl->kw[index]; + } + } + + if (kw == kwp) + break; + + name = kw->fetch_kw; + if (!name) + name = kw->kw; + + printf("%s = %s -m %s\n", kw->kw, name, pat_match_names[kw->match_type]); + } +} + +/* Purge everything in the acl_cond , then free */ +void free_acl_cond(struct acl_cond *cond) +{ + struct acl_term_suite *suite, *suiteb; + struct acl_term *term, *termb; + + if (!cond) + return; + + list_for_each_entry_safe(suite, suiteb, &cond->suites, list) { + list_for_each_entry_safe(term, termb, &suite->terms, list) { + LIST_DELETE(&term->list); + free(term); + } + LIST_DELETE(&suite->list); + free(suite); + } + + free(cond); +} + + +static int smp_fetch_acl(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct acl_sample *acl_sample = (struct acl_sample *)args->data.ptr; + enum acl_test_res ret; + + ret = acl_exec_cond(&acl_sample->cond, smp->px, smp->sess, smp->strm, smp->opt); + if (ret == ACL_TEST_MISS) + return 0; + smp->data.u.sint = ret == ACL_TEST_PASS; + smp->data.type = SMP_T_BOOL; + return 1; +} + +int smp_fetch_acl_parse(struct arg *args, char **err_msg) +{ + struct acl_sample *acl_sample; + char *name; + int i; + + for (i = 0; args[i].type != ARGT_STOP; i++) + ; + acl_sample = calloc(1, sizeof(struct acl_sample) + sizeof(struct acl_term) * i); + LIST_INIT(&acl_sample->suite.terms); + LIST_INIT(&acl_sample->cond.suites); + LIST_APPEND(&acl_sample->cond.suites, &acl_sample->suite.list); + acl_sample->cond.val = ~0U; // the keyword is valid everywhere for now. + + args->data.ptr = acl_sample; + + for (i = 0; args[i].type != ARGT_STOP; i++) { + name = args[i].data.str.area; + if (name[0] == '!') { + acl_sample->terms[i].neg = 1; + name++; + } + + if (!(acl_sample->terms[i].acl = find_acl_by_name(name, &curproxy->acl))) { + memprintf(err_msg, "ACL '%s' not found", name); + goto err; + } + + acl_sample->cond.use |= acl_sample->terms[i].acl->use; + acl_sample->cond.val &= acl_sample->terms[i].acl->val; + + LIST_APPEND(&acl_sample->suite.terms, &acl_sample->terms[i].list); + } + + return 1; + +err: + free(acl_sample); + return 0; +} + +/************************************************************************/ +/* All supported sample and ACL keywords must be declared here. */ +/************************************************************************/ + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted. + */ +static struct acl_kw_list acl_kws = {ILH, { + { /* END */ }, +}}; + +INITCALL1(STG_REGISTER, acl_register_keywords, &acl_kws); + +static struct sample_fetch_kw_list smp_kws = {ILH, { + { "acl", smp_fetch_acl, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), smp_fetch_acl_parse, SMP_T_BOOL, SMP_USE_CONST }, + { /* END */ }, +}}; + +INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/action.c b/src/action.c new file mode 100644 index 0000000..47f5f86 --- /dev/null +++ b/src/action.c @@ -0,0 +1,363 @@ +/* + * Action management functions. + * + * Copyright 2017 HAProxy Technologies, Christopher Faulet + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Check an action ruleset validity. It returns the number of error encountered + * and err_code is updated if a warning is emitted. + */ +int check_action_rules(struct list *rules, struct proxy *px, int *err_code) +{ + struct act_rule *rule; + char *errmsg = NULL; + int err = 0; + + list_for_each_entry(rule, rules, list) { + if (rule->check_ptr && !rule->check_ptr(rule, px, &errmsg)) { + ha_alert("Proxy '%s': %s.\n", px->id, errmsg); + err++; + } + *err_code |= warnif_tcp_http_cond(px, rule->cond); + ha_free(&errmsg); + } + + return err; +} + +/* Find and check the target table used by an action track-sc*. This + * function should be called during the configuration validity check. + * + * The function returns 1 in success case, otherwise, it returns 0 and err is + * filled. + */ +int check_trk_action(struct act_rule *rule, struct proxy *px, char **err) +{ + struct stktable *target; + + if (rule->arg.trk_ctr.table.n) + target = stktable_find_by_name(rule->arg.trk_ctr.table.n); + else + target = px->table; + + if (!target) { + memprintf(err, "unable to find table '%s' referenced by track-sc%d", + rule->arg.trk_ctr.table.n ? rule->arg.trk_ctr.table.n : px->id, + rule->action); + return 0; + } + + if (!stktable_compatible_sample(rule->arg.trk_ctr.expr, target->type)) { + memprintf(err, "stick-table '%s' uses a type incompatible with the 'track-sc%d' rule", + rule->arg.trk_ctr.table.n ? rule->arg.trk_ctr.table.n : px->id, + rule->action); + return 0; + } + else { + if (!in_proxies_list(target->proxies_list, px)) { + px->next_stkt_ref = target->proxies_list; + target->proxies_list = px; + } + free(rule->arg.trk_ctr.table.n); + rule->arg.trk_ctr.table.t = target; + /* Note: if we decide to enhance the track-sc syntax, we may be + * able to pass a list of counters to track and allocate them + * right here using stktable_alloc_data_type(). + */ + } + + if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE)) { + if (!px->tcp_req.inspect_delay && !(rule->arg.trk_ctr.expr->fetch->val & SMP_VAL_FE_SES_ACC)) { + ha_warning("%s '%s' : a 'tcp-request content track-sc*' rule explicitly depending on request" + " contents without any 'tcp-request inspect-delay' setting." + " This means that this rule will randomly find its contents. This can be fixed by" + " setting the tcp-request inspect-delay.\n", + proxy_type_str(px), px->id); + } + + /* The following warning is emitted because HTTP multiplexers are able to catch errors + * or timeouts at the session level, before instantiating any stream. + * Thus the tcp-request content ruleset will not be evaluated in such case. It means, + * http_req and http_err counters will not be incremented as expected, even if the tracked + * counter does not use the request content. To track invalid requests it should be + * performed at the session level using a tcp-request session rule. + */ + if (px->mode == PR_MODE_HTTP && + !(rule->arg.trk_ctr.expr->fetch->use & (SMP_USE_L6REQ|SMP_USE_HRQHV|SMP_USE_HRQHP|SMP_USE_HRQBO)) && + (!rule->cond || !(rule->cond->use & (SMP_USE_L6REQ|SMP_USE_HRQHV|SMP_USE_HRQHP|SMP_USE_HRQBO)))) { + ha_warning("%s '%s' : a 'tcp-request content track-sc*' rule not depending on request" + " contents for an HTTP frontend should be executed at the session level, using a" + " 'tcp-request session' rule (mandatory to track invalid HTTP requests).\n", + proxy_type_str(px), px->id); + } + } + + return 1; +} + +/* check a capture rule. This function should be called during the configuration + * validity check. + * + * The function returns 1 in success case, otherwise, it returns 0 and err is + * filled. + */ +int check_capture(struct act_rule *rule, struct proxy *px, char **err) +{ + if (rule->from == ACT_F_TCP_REQ_CNT && (px->cap & PR_CAP_FE) && !px->tcp_req.inspect_delay && + !(rule->arg.cap.expr->fetch->val & SMP_VAL_FE_SES_ACC)) { + ha_warning("%s '%s' : a 'tcp-request capture' rule explicitly depending on request" + " contents without any 'tcp-request inspect-delay' setting." + " This means that this rule will randomly find its contents. This can be fixed by" + " setting the tcp-request inspect-delay.\n", + proxy_type_str(px), px->id); + } + + return 1; +} + +int act_resolution_cb(struct resolv_requester *requester, struct dns_counters *counters) +{ + struct stream *stream; + + if (requester->resolution == NULL) + return 0; + + stream = objt_stream(requester->owner); + if (stream == NULL) + return 0; + + task_wakeup(stream->task, TASK_WOKEN_MSG); + + return 0; +} + +/* + * Do resolve error management callback + * returns: + * 0 if we can trash answser items. + * 1 when safely ignored and we must kept answer items + */ +int act_resolution_error_cb(struct resolv_requester *requester, int error_code) +{ + struct stream *stream; + + if (requester->resolution == NULL) + return 0; + + stream = objt_stream(requester->owner); + if (stream == NULL) + return 0; + + task_wakeup(stream->task, TASK_WOKEN_MSG); + + return 0; +} + +/* Parse a set-timeout rule statement. It first checks if the timeout name is + * valid and proxy is capable of handling it, and returns it in arg.timeout.type>. + * Then the timeout is parsed as a plain value and * returned in arg.timeout.value>. + * If there is a parsing error, the value is reparsed as an expression and + * returned in arg.timeout.expr>. + * + * Returns -1 if the name is invalid or neither a time or an expression can be + * parsed, or if the timeout value is 0. + */ +int cfg_parse_rule_set_timeout(const char **args, int idx, struct act_rule *rule, + struct proxy *px, char **err) +{ + const char *res; + const char *timeout_name = args[idx++]; + + if (strcmp(timeout_name, "server") == 0) { + if (!(px->cap & PR_CAP_BE)) { + memprintf(err, "'%s' has no backend capability", px->id); + return -1; + } + rule->arg.timeout.type = ACT_TIMEOUT_SERVER; + } + else if (strcmp(timeout_name, "tunnel") == 0) { + if (!(px->cap & PR_CAP_BE)) { + memprintf(err, "'%s' has no backend capability", px->id); + return -1; + } + rule->arg.timeout.type = ACT_TIMEOUT_TUNNEL; + } + else if (strcmp(timeout_name, "client") == 0) { + if (!(px->cap & PR_CAP_FE)) { + memprintf(err, "'%s' has no frontend capability", px->id); + return -1; + } + rule->arg.timeout.type = ACT_TIMEOUT_CLIENT; + } + else { + memprintf(err, + "'set-timeout' rule supports 'server'/'tunnel'/'client' (got '%s')", + timeout_name); + return -1; + } + + res = parse_time_err(args[idx], (unsigned int *)&rule->arg.timeout.value, TIME_UNIT_MS); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to rule 'set-timeout %s' (maximum value is 2147483647 ms or ~24.8 days)", + args[idx], timeout_name); + return -1; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to rule 'set-timeout %s' (minimum value is 1 ms)", + args[idx], timeout_name); + return -1; + } + /* res not NULL, parsing error */ + else if (res) { + rule->arg.timeout.expr = sample_parse_expr((char **)args, &idx, px->conf.args.file, + px->conf.args.line, err, &px->conf.args, NULL); + if (!rule->arg.timeout.expr) { + memprintf(err, "unexpected character '%c' in rule 'set-timeout %s'", *res, timeout_name); + return -1; + } + } + /* res NULL, parsing ok but value is 0 */ + else if (!(rule->arg.timeout.value)) { + memprintf(err, "null value is not valid for a 'set-timeout %s' rule", + timeout_name); + return -1; + } + + return 0; +} + +/* tries to find in list a similar looking action as the one in + * , and returns it otherwise NULL. may be NULL or empty. An + * optional array of extra words to compare may be passed in , but it + * must then be terminated by a NULL entry. If unused it may be NULL. + */ +const char *action_suggest(const char *word, const struct list *keywords, const char **extra) +{ + uint8_t word_sig[1024]; + uint8_t list_sig[1024]; + const struct action_kw_list *kwl; + const struct action_kw *best_kw = NULL; + const char *best_ptr = NULL; + int dist, best_dist = INT_MAX; + int index; + + if (!word || !*word) + return NULL; + + make_word_fingerprint(word_sig, word); + list_for_each_entry(kwl, keywords, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + make_word_fingerprint(list_sig, kwl->kw[index].kw); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_kw = &kwl->kw[index]; + best_ptr = best_kw->kw; + } + } + } + + while (extra && *extra) { + make_word_fingerprint(list_sig, *extra); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_kw = NULL; + best_ptr = *extra; + } + extra++; + } + + /* eliminate too different ones, with more tolerance for prefixes + * when they're known to exist (not from extra list). + */ + if (best_ptr && + (best_dist > (2 + (best_kw && (best_kw->flags & KWF_MATCH_PREFIX))) * strlen(word) || + best_dist > (2 + (best_kw && (best_kw->flags & KWF_MATCH_PREFIX))) * strlen(best_ptr))) + best_ptr = NULL; + + return best_ptr; +} + +/* allocates a rule for ruleset (ACT_F_*), from file name and + * line . and may be zero if unknown. Returns the + * rule, otherwise NULL in case of memory allocation error. + */ +struct act_rule *new_act_rule(enum act_from from, const char *file, int linenum) +{ + struct act_rule *rule; + + rule = calloc(1, sizeof(*rule)); + if (!rule) + return NULL; + rule->from = from; + rule->conf.file = file ? strdup(file) : NULL; + rule->conf.line = linenum; + LIST_INIT(&rule->list); + return rule; +} + +/* fees rule and its elements as well as the condition */ +void free_act_rule(struct act_rule *rule) +{ + LIST_DELETE(&rule->list); + free_acl_cond(rule->cond); + if (rule->release_ptr) + rule->release_ptr(rule); + free(rule->conf.file); + free(rule); +} + +void free_act_rules(struct list *rules) +{ + struct act_rule *rule, *ruleb; + + list_for_each_entry_safe(rule, ruleb, rules, list) { + free_act_rule(rule); + } +} + +/* dumps all known actions registered in action rules after prefix + * to stdout. The actions are alphabetically sorted. Those with the + * KWF_MATCH_PREFIX flag have their name suffixed with '*'. + */ +void dump_act_rules(const struct list *rules, const char *pfx) +{ + const struct action_kw *akwp, *akwn; + struct action_kw_list *akwl; + int index; + + for (akwn = akwp = NULL;; akwp = akwn) { + list_for_each_entry(akwl, rules, list) { + for (index = 0; akwl->kw[index].kw != NULL; index++) + if (strordered(akwp ? akwp->kw : NULL, + akwl->kw[index].kw, + akwn != akwp ? akwn->kw : NULL)) + akwn = &akwl->kw[index]; + } + if (akwn == akwp) + break; + printf("%s%s%s\n", pfx ? pfx : "", akwn->kw, + (akwn->flags & KWF_MATCH_PREFIX) ? "*" : ""); + } +} diff --git a/src/activity.c b/src/activity.c new file mode 100644 index 0000000..07a30e6 --- /dev/null +++ b/src/activity.c @@ -0,0 +1,1248 @@ +/* + * activity measurement functions. + * + * Copyright 2000-2018 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* CLI context for the "show profiling" command */ +struct show_prof_ctx { + int dump_step; /* 0,1,2,4,5,6; see cli_iohandler_show_profiling() */ + int linenum; /* next line to be dumped (starts at 0) */ + int maxcnt; /* max line count per step (0=not set) */ + int by_what; /* 0=sort by usage, 1=sort by address, 2=sort by time */ + int aggr; /* 0=dump raw, 1=aggregate on callee */ +}; + +/* CLI context for the "show activity" command */ +struct show_activity_ctx { + int thr; /* thread ID to show or -1 for all */ + int line; /* line number being dumped */ + int col; /* columnline being dumped, 0 to nbt+1 */ +}; + +#if defined(DEBUG_MEM_STATS) +/* these ones are macros in bug.h when DEBUG_MEM_STATS is set, and will + * prevent the new ones from being redefined. + */ +#undef calloc +#undef malloc +#undef realloc +#endif + +/* bit field of profiling options. Beware, may be modified at runtime! */ +unsigned int profiling __read_mostly = HA_PROF_TASKS_AOFF; + +/* start/stop dates of profiling */ +uint64_t prof_task_start_ns = 0; +uint64_t prof_task_stop_ns = 0; +uint64_t prof_mem_start_ns = 0; +uint64_t prof_mem_stop_ns = 0; + +/* One struct per thread containing all collected measurements */ +struct activity activity[MAX_THREADS] __attribute__((aligned(64))) = { }; + +/* One struct per function pointer hash entry (SCHED_ACT_HASH_BUCKETS values, 0=collision) */ +struct sched_activity sched_activity[SCHED_ACT_HASH_BUCKETS] __attribute__((aligned(64))) = { }; + + +#ifdef USE_MEMORY_PROFILING + +static const char *const memprof_methods[MEMPROF_METH_METHODS] = { + "unknown", "malloc", "calloc", "realloc", "free", "p_alloc", "p_free", +}; + +/* last one is for hash collisions ("others") and has no caller address */ +struct memprof_stats memprof_stats[MEMPROF_HASH_BUCKETS + 1] = { }; + +/* used to detect recursive calls */ +static THREAD_LOCAL int in_memprof = 0; + +/* These ones are used by glibc and will be called early. They are in charge of + * initializing the handlers with the original functions. + */ +static void *memprof_malloc_initial_handler(size_t size); +static void *memprof_calloc_initial_handler(size_t nmemb, size_t size); +static void *memprof_realloc_initial_handler(void *ptr, size_t size); +static void memprof_free_initial_handler(void *ptr); + +/* Fallback handlers for the main alloc/free functions. They are preset to + * the initializer in order to save a test in the functions's critical path. + */ +static void *(*memprof_malloc_handler)(size_t size) = memprof_malloc_initial_handler; +static void *(*memprof_calloc_handler)(size_t nmemb, size_t size) = memprof_calloc_initial_handler; +static void *(*memprof_realloc_handler)(void *ptr, size_t size) = memprof_realloc_initial_handler; +static void (*memprof_free_handler)(void *ptr) = memprof_free_initial_handler; + +/* Used to force to die if it's not possible to retrieve the allocation + * functions. We cannot even use stdio in this case. + */ +static __attribute__((noreturn)) void memprof_die(const char *msg) +{ + DISGUISE(write(2, msg, strlen(msg))); + exit(1); +} + +/* Resolve original allocation functions and initialize all handlers. + * This must be called very early at boot, before the very first malloc() + * call, and is not thread-safe! It's not even possible to use stdio there. + * Worse, we have to account for the risk of reentrance from dlsym() when + * it tries to prepare its error messages. Here its ahndled by in_memprof + * that makes allocators return NULL. dlsym() handles it gracefully. An + * alternate approach consists in calling aligned_alloc() from these places + * but that would mean not being able to intercept it later if considered + * useful to do so. + */ +static void memprof_init() +{ + in_memprof++; + memprof_malloc_handler = get_sym_next_addr("malloc"); + if (!memprof_malloc_handler) + memprof_die("FATAL: malloc() function not found.\n"); + + memprof_calloc_handler = get_sym_next_addr("calloc"); + if (!memprof_calloc_handler) + memprof_die("FATAL: calloc() function not found.\n"); + + memprof_realloc_handler = get_sym_next_addr("realloc"); + if (!memprof_realloc_handler) + memprof_die("FATAL: realloc() function not found.\n"); + + memprof_free_handler = get_sym_next_addr("free"); + if (!memprof_free_handler) + memprof_die("FATAL: free() function not found.\n"); + in_memprof--; +} + +/* the initial handlers will initialize all regular handlers and will call the + * one they correspond to. A single one of these functions will typically be + * called, though it's unknown which one (as any might be called before main). + */ +static void *memprof_malloc_initial_handler(size_t size) +{ + if (in_memprof) { + /* it's likely that dlsym() needs malloc(), let's fail */ + return NULL; + } + + memprof_init(); + return memprof_malloc_handler(size); +} + +static void *memprof_calloc_initial_handler(size_t nmemb, size_t size) +{ + if (in_memprof) { + /* it's likely that dlsym() needs calloc(), let's fail */ + return NULL; + } + memprof_init(); + return memprof_calloc_handler(nmemb, size); +} + +static void *memprof_realloc_initial_handler(void *ptr, size_t size) +{ + if (in_memprof) { + /* it's likely that dlsym() needs realloc(), let's fail */ + return NULL; + } + + memprof_init(); + return memprof_realloc_handler(ptr, size); +} + +static void memprof_free_initial_handler(void *ptr) +{ + memprof_init(); + memprof_free_handler(ptr); +} + +/* Assign a bin for the memprof_stats to the return address. May perform a few + * attempts before finding the right one, but always succeeds (in the worst + * case, returns a default bin). The caller address is atomically set except + * for the default one which is never set. + */ +struct memprof_stats *memprof_get_bin(const void *ra, enum memprof_method meth) +{ + int retries = 16; // up to 16 consecutive entries may be tested. + const void *old; + unsigned int bin; + + bin = ptr_hash(ra, MEMPROF_HASH_BITS); + for (; memprof_stats[bin].caller != ra; bin = (bin + 1) & (MEMPROF_HASH_BUCKETS - 1)) { + if (!--retries) { + bin = MEMPROF_HASH_BUCKETS; + break; + } + + old = NULL; + if (!memprof_stats[bin].caller && + HA_ATOMIC_CAS(&memprof_stats[bin].caller, &old, ra)) { + memprof_stats[bin].method = meth; + break; + } + } + return &memprof_stats[bin]; +} + +/* This is the new global malloc() function. It must optimize for the normal + * case (i.e. profiling disabled) hence the first test to permit a direct jump. + * It must remain simple to guarantee the lack of reentrance. stdio is not + * possible there even for debugging. The reported size is the really allocated + * one as returned by malloc_usable_size(), because this will allow it to be + * compared to the one before realloc() or free(). This is a GNU and jemalloc + * extension but other systems may also store this size in ptr[-1]. + */ +void *malloc(size_t size) +{ + struct memprof_stats *bin; + void *ret; + + if (likely(!(profiling & HA_PROF_MEMORY))) + return memprof_malloc_handler(size); + + ret = memprof_malloc_handler(size); + size = malloc_usable_size(ret) + sizeof(void *); + + bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MALLOC); + _HA_ATOMIC_ADD(&bin->alloc_calls, 1); + _HA_ATOMIC_ADD(&bin->alloc_tot, size); + return ret; +} + +/* This is the new global calloc() function. It must optimize for the normal + * case (i.e. profiling disabled) hence the first test to permit a direct jump. + * It must remain simple to guarantee the lack of reentrance. stdio is not + * possible there even for debugging. The reported size is the really allocated + * one as returned by malloc_usable_size(), because this will allow it to be + * compared to the one before realloc() or free(). This is a GNU and jemalloc + * extension but other systems may also store this size in ptr[-1]. + */ +void *calloc(size_t nmemb, size_t size) +{ + struct memprof_stats *bin; + void *ret; + + if (likely(!(profiling & HA_PROF_MEMORY))) + return memprof_calloc_handler(nmemb, size); + + ret = memprof_calloc_handler(nmemb, size); + size = malloc_usable_size(ret) + sizeof(void *); + + bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_CALLOC); + _HA_ATOMIC_ADD(&bin->alloc_calls, 1); + _HA_ATOMIC_ADD(&bin->alloc_tot, size); + return ret; +} + +/* This is the new global realloc() function. It must optimize for the normal + * case (i.e. profiling disabled) hence the first test to permit a direct jump. + * It must remain simple to guarantee the lack of reentrance. stdio is not + * possible there even for debugging. The reported size is the really allocated + * one as returned by malloc_usable_size(), because this will allow it to be + * compared to the one before realloc() or free(). This is a GNU and jemalloc + * extension but other systems may also store this size in ptr[-1]. + * Depending on the old vs new size, it's considered as an allocation or a free + * (or neither if the size remains the same). + */ +void *realloc(void *ptr, size_t size) +{ + struct memprof_stats *bin; + size_t size_before; + void *ret; + + if (likely(!(profiling & HA_PROF_MEMORY))) + return memprof_realloc_handler(ptr, size); + + size_before = malloc_usable_size(ptr); + ret = memprof_realloc_handler(ptr, size); + size = malloc_usable_size(ret); + + /* only count the extra link for new allocations */ + if (!ptr) + size += sizeof(void *); + + bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_REALLOC); + if (size > size_before) { + _HA_ATOMIC_ADD(&bin->alloc_calls, 1); + _HA_ATOMIC_ADD(&bin->alloc_tot, size - size_before); + } else if (size < size_before) { + _HA_ATOMIC_ADD(&bin->free_calls, 1); + _HA_ATOMIC_ADD(&bin->free_tot, size_before - size); + } + return ret; +} + +/* This is the new global free() function. It must optimize for the normal + * case (i.e. profiling disabled) hence the first test to permit a direct jump. + * It must remain simple to guarantee the lack of reentrance. stdio is not + * possible there even for debugging. The reported size is the really allocated + * one as returned by malloc_usable_size(), because this will allow it to be + * compared to the one before realloc() or free(). This is a GNU and jemalloc + * extension but other systems may also store this size in ptr[-1]. Since + * free() is often called on NULL pointers to collect garbage at the end of + * many functions or during config parsing, as a special case free(NULL) + * doesn't update any stats. + */ +void free(void *ptr) +{ + struct memprof_stats *bin; + size_t size_before; + + if (likely(!(profiling & HA_PROF_MEMORY) || !ptr)) { + memprof_free_handler(ptr); + return; + } + + size_before = malloc_usable_size(ptr) + sizeof(void *); + memprof_free_handler(ptr); + + bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_FREE); + _HA_ATOMIC_ADD(&bin->free_calls, 1); + _HA_ATOMIC_ADD(&bin->free_tot, size_before); +} + +#endif // USE_MEMORY_PROFILING + +/* Updates the current thread's statistics about stolen CPU time. The unit for + * is half-milliseconds. + */ +void report_stolen_time(uint64_t stolen) +{ + activity[tid].cpust_total += stolen; + update_freq_ctr(&activity[tid].cpust_1s, stolen); + update_freq_ctr_period(&activity[tid].cpust_15s, 15000, stolen); +} + +/* Update avg_loop value for the current thread and possibly decide to enable + * task-level profiling on the current thread based on its average run time. + * The argument is the number of microseconds elapsed since the + * last time poll() returned. + */ +void activity_count_runtime(uint32_t run_time) +{ + uint32_t up, down; + + /* 1 millisecond per loop on average over last 1024 iterations is + * enough to turn on profiling. + */ + up = 1000; + down = up * 99 / 100; + + run_time = swrate_add(&activity[tid].avg_loop_us, TIME_STATS_SAMPLES, run_time); + + /* In automatic mode, reaching the "up" threshold on average switches + * profiling to "on" when automatic, and going back below the "down" + * threshold switches to off. The forced modes don't check the load. + */ + if (!(_HA_ATOMIC_LOAD(&th_ctx->flags) & TH_FL_TASK_PROFILING)) { + if (unlikely((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_ON || + ((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_AON && + swrate_avg(run_time, TIME_STATS_SAMPLES) >= up))) + _HA_ATOMIC_OR(&th_ctx->flags, TH_FL_TASK_PROFILING); + } else { + if (unlikely((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_OFF || + ((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_AOFF && + swrate_avg(run_time, TIME_STATS_SAMPLES) <= down))) + _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_TASK_PROFILING); + } +} + +#ifdef USE_MEMORY_PROFILING +/* config parser for global "profiling.memory", accepts "on" or "off" */ +static int cfg_parse_prof_memory(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) { + profiling |= HA_PROF_MEMORY; + HA_ATOMIC_STORE(&prof_mem_start_ns, now_ns); + } + else if (strcmp(args[1], "off") == 0) + profiling &= ~HA_PROF_MEMORY; + else { + memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); + return -1; + } + return 0; +} +#endif // USE_MEMORY_PROFILING + +/* config parser for global "profiling.tasks", accepts "on" or "off" */ +static int cfg_parse_prof_tasks(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) { + profiling = (profiling & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_ON; + HA_ATOMIC_STORE(&prof_task_start_ns, now_ns); + } + else if (strcmp(args[1], "auto") == 0) { + profiling = (profiling & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_AOFF; + HA_ATOMIC_STORE(&prof_task_start_ns, now_ns); + } + else if (strcmp(args[1], "off") == 0) + profiling = (profiling & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_OFF; + else { + memprintf(err, "'%s' expects either 'on', 'auto', or 'off' but got '%s'.", args[0], args[1]); + return -1; + } + return 0; +} + +/* parse a "set profiling" command. It always returns 1. */ +static int cli_parse_set_profiling(char **args, char *payload, struct appctx *appctx, void *private) +{ + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + + if (strcmp(args[2], "memory") == 0) { +#ifdef USE_MEMORY_PROFILING + if (strcmp(args[3], "on") == 0) { + unsigned int old = profiling; + int i; + + while (!_HA_ATOMIC_CAS(&profiling, &old, old | HA_PROF_MEMORY)) + ; + + HA_ATOMIC_STORE(&prof_mem_start_ns, now_ns); + HA_ATOMIC_STORE(&prof_mem_stop_ns, 0); + + /* also flush current profiling stats */ + for (i = 0; i < sizeof(memprof_stats) / sizeof(memprof_stats[0]); i++) { + HA_ATOMIC_STORE(&memprof_stats[i].alloc_calls, 0); + HA_ATOMIC_STORE(&memprof_stats[i].free_calls, 0); + HA_ATOMIC_STORE(&memprof_stats[i].alloc_tot, 0); + HA_ATOMIC_STORE(&memprof_stats[i].free_tot, 0); + HA_ATOMIC_STORE(&memprof_stats[i].caller, NULL); + } + } + else if (strcmp(args[3], "off") == 0) { + unsigned int old = profiling; + + while (!_HA_ATOMIC_CAS(&profiling, &old, old & ~HA_PROF_MEMORY)) + ; + + if (HA_ATOMIC_LOAD(&prof_mem_start_ns)) + HA_ATOMIC_STORE(&prof_mem_stop_ns, now_ns); + } + else + return cli_err(appctx, "Expects either 'on' or 'off'.\n"); + return 1; +#else + return cli_err(appctx, "Memory profiling not compiled in.\n"); +#endif + } + + if (strcmp(args[2], "tasks") != 0) + return cli_err(appctx, "Expects either 'tasks' or 'memory'.\n"); + + if (strcmp(args[3], "on") == 0) { + unsigned int old = profiling; + int i; + + while (!_HA_ATOMIC_CAS(&profiling, &old, (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_ON)) + ; + + HA_ATOMIC_STORE(&prof_task_start_ns, now_ns); + HA_ATOMIC_STORE(&prof_task_stop_ns, 0); + + /* also flush current profiling stats */ + for (i = 0; i < SCHED_ACT_HASH_BUCKETS; i++) { + HA_ATOMIC_STORE(&sched_activity[i].calls, 0); + HA_ATOMIC_STORE(&sched_activity[i].cpu_time, 0); + HA_ATOMIC_STORE(&sched_activity[i].lat_time, 0); + HA_ATOMIC_STORE(&sched_activity[i].func, NULL); + HA_ATOMIC_STORE(&sched_activity[i].caller, NULL); + } + } + else if (strcmp(args[3], "auto") == 0) { + unsigned int old = profiling; + unsigned int new; + + do { + if ((old & HA_PROF_TASKS_MASK) >= HA_PROF_TASKS_AON) + new = (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_AON; + else + new = (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_AOFF; + } while (!_HA_ATOMIC_CAS(&profiling, &old, new)); + + HA_ATOMIC_STORE(&prof_task_start_ns, now_ns); + HA_ATOMIC_STORE(&prof_task_stop_ns, 0); + } + else if (strcmp(args[3], "off") == 0) { + unsigned int old = profiling; + while (!_HA_ATOMIC_CAS(&profiling, &old, (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_OFF)) + ; + + if (HA_ATOMIC_LOAD(&prof_task_start_ns)) + HA_ATOMIC_STORE(&prof_task_stop_ns, now_ns); + } + else + return cli_err(appctx, "Expects 'on', 'auto', or 'off'.\n"); + + return 1; +} + +static int cmp_sched_activity_calls(const void *a, const void *b) +{ + const struct sched_activity *l = (const struct sched_activity *)a; + const struct sched_activity *r = (const struct sched_activity *)b; + + if (l->calls > r->calls) + return -1; + else if (l->calls < r->calls) + return 1; + else + return 0; +} + +/* sort by address first, then by call count */ +static int cmp_sched_activity_addr(const void *a, const void *b) +{ + const struct sched_activity *l = (const struct sched_activity *)a; + const struct sched_activity *r = (const struct sched_activity *)b; + + if (l->func > r->func) + return -1; + else if (l->func < r->func) + return 1; + else if (l->calls > r->calls) + return -1; + else if (l->calls < r->calls) + return 1; + else + return 0; +} + +/* sort by cpu time first, then by inverse call count (to spot highest offenders) */ +static int cmp_sched_activity_cpu(const void *a, const void *b) +{ + const struct sched_activity *l = (const struct sched_activity *)a; + const struct sched_activity *r = (const struct sched_activity *)b; + + if (l->cpu_time > r->cpu_time) + return -1; + else if (l->cpu_time < r->cpu_time) + return 1; + else if (l->calls < r->calls) + return -1; + else if (l->calls > r->calls) + return 1; + else + return 0; +} + +#ifdef USE_MEMORY_PROFILING +/* used by qsort below */ +static int cmp_memprof_stats(const void *a, const void *b) +{ + const struct memprof_stats *l = (const struct memprof_stats *)a; + const struct memprof_stats *r = (const struct memprof_stats *)b; + + if (l->alloc_tot + l->free_tot > r->alloc_tot + r->free_tot) + return -1; + else if (l->alloc_tot + l->free_tot < r->alloc_tot + r->free_tot) + return 1; + else + return 0; +} + +static int cmp_memprof_addr(const void *a, const void *b) +{ + const struct memprof_stats *l = (const struct memprof_stats *)a; + const struct memprof_stats *r = (const struct memprof_stats *)b; + + if (l->caller > r->caller) + return -1; + else if (l->caller < r->caller) + return 1; + else + return 0; +} +#endif // USE_MEMORY_PROFILING + +/* Computes the index of function pointer and caller for use + * with sched_activity[] or any other similar array passed in , and + * returns a pointer to the entry after having atomically assigned it to this + * function pointer and caller combination. Note that in case of collision, + * the first entry is returned instead ("other"). + */ +struct sched_activity *sched_activity_entry(struct sched_activity *array, const void *func, const void *caller) +{ + uint32_t hash = ptr2_hash(func, caller, SCHED_ACT_HASH_BITS); + struct sched_activity *ret; + const void *old; + int tries = 16; + + for (tries = 16; tries > 0; tries--, hash++) { + ret = &array[hash]; + + while (1) { + if (likely(ret->func)) { + if (likely(ret->func == func && ret->caller == caller)) + return ret; + break; + } + + /* try to create the new entry. Func is sufficient to + * reserve the node. + */ + old = NULL; + if (HA_ATOMIC_CAS(&ret->func, &old, func)) { + ret->caller = caller; + return ret; + } + /* changed in parallel, check again */ + } + } + + return array; +} + +/* This function dumps all profiling settings. It returns 0 if the output + * buffer is full and it needs to be called again, otherwise non-zero. + * It dumps some parts depending on the following states from show_prof_ctx: + * dump_step: + * 0, 4: dump status, then jump to 1 if 0 + * 1, 5: dump tasks, then jump to 2 if 1 + * 2, 6: dump memory, then stop + * linenum: + * restart line for each step (starts at zero) + * maxcnt: + * may contain a configured max line count for each step (0=not set) + * byaddr: + * 0: sort by usage + * 1: sort by address + */ +static int cli_io_handler_show_profiling(struct appctx *appctx) +{ + struct show_prof_ctx *ctx = appctx->svcctx; + struct sched_activity tmp_activity[SCHED_ACT_HASH_BUCKETS] __attribute__((aligned(64))); +#ifdef USE_MEMORY_PROFILING + struct memprof_stats tmp_memstats[MEMPROF_HASH_BUCKETS + 1]; + unsigned long long tot_alloc_calls, tot_free_calls; + unsigned long long tot_alloc_bytes, tot_free_bytes; +#endif + struct stconn *sc = appctx_sc(appctx); + struct buffer *name_buffer = get_trash_chunk(); + const struct ha_caller *caller; + const char *str; + int max_lines; + int i, j, max; + + /* FIXME: Don't watch the other side ! */ + if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE)) + return 1; + + chunk_reset(&trash); + + switch (profiling & HA_PROF_TASKS_MASK) { + case HA_PROF_TASKS_AOFF: str="auto-off"; break; + case HA_PROF_TASKS_AON: str="auto-on"; break; + case HA_PROF_TASKS_ON: str="on"; break; + default: str="off"; break; + } + + if ((ctx->dump_step & 3) != 0) + goto skip_status; + + chunk_printf(&trash, + "Per-task CPU profiling : %-8s # set profiling tasks {on|auto|off}\n" + "Memory usage profiling : %-8s # set profiling memory {on|off}\n", + str, (profiling & HA_PROF_MEMORY) ? "on" : "off"); + + if (applet_putchk(appctx, &trash) == -1) { + /* failed, try again */ + return 0; + } + + ctx->linenum = 0; // reset first line to dump + if ((ctx->dump_step & 4) == 0) + ctx->dump_step++; // next step + + skip_status: + if ((ctx->dump_step & 3) != 1) + goto skip_tasks; + + memcpy(tmp_activity, sched_activity, sizeof(tmp_activity)); + /* for addr sort and for callee aggregation we have to first sort by address */ + if (ctx->aggr || ctx->by_what == 1) // sort by addr + qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_addr); + + if (ctx->aggr) { + /* merge entries for the same callee and reset their count */ + for (i = j = 0; i < SCHED_ACT_HASH_BUCKETS; i = j) { + for (j = i + 1; j < SCHED_ACT_HASH_BUCKETS && tmp_activity[j].func == tmp_activity[i].func; j++) { + tmp_activity[i].calls += tmp_activity[j].calls; + tmp_activity[i].cpu_time += tmp_activity[j].cpu_time; + tmp_activity[i].lat_time += tmp_activity[j].lat_time; + tmp_activity[j].calls = 0; + } + } + } + + if (!ctx->by_what) // sort by usage + qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_calls); + else if (ctx->by_what == 2) // by cpu_tot + qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_cpu); + + if (!ctx->linenum) + chunk_appendf(&trash, "Tasks activity over %.3f sec till %.3f sec ago:\n" + " function calls cpu_tot cpu_avg lat_tot lat_avg\n", + (prof_task_start_ns ? (prof_task_stop_ns ? prof_task_stop_ns : now_ns) - prof_task_start_ns : 0) / 1000000000.0, + (prof_task_stop_ns ? now_ns - prof_task_stop_ns : 0) / 1000000000.0); + + max_lines = ctx->maxcnt; + if (!max_lines) + max_lines = SCHED_ACT_HASH_BUCKETS; + + for (i = ctx->linenum; i < max_lines; i++) { + if (!tmp_activity[i].calls) + continue; // skip aggregated or empty entries + + ctx->linenum = i; + chunk_reset(name_buffer); + caller = HA_ATOMIC_LOAD(&tmp_activity[i].caller); + + if (!tmp_activity[i].func) + chunk_printf(name_buffer, "other"); + else + resolve_sym_name(name_buffer, "", tmp_activity[i].func); + + /* reserve 35 chars for name+' '+#calls, knowing that longer names + * are often used for less often called functions. + */ + max = 35 - name_buffer->data; + if (max < 1) + max = 1; + chunk_appendf(&trash, " %s%*llu", name_buffer->area, max, (unsigned long long)tmp_activity[i].calls); + + print_time_short(&trash, " ", tmp_activity[i].cpu_time, ""); + print_time_short(&trash, " ", tmp_activity[i].cpu_time / tmp_activity[i].calls, ""); + print_time_short(&trash, " ", tmp_activity[i].lat_time, ""); + print_time_short(&trash, " ", tmp_activity[i].lat_time / tmp_activity[i].calls, ""); + + if (caller && !ctx->aggr && caller->what <= WAKEUP_TYPE_APPCTX_WAKEUP) + chunk_appendf(&trash, " <- %s@%s:%d %s", + caller->func, caller->file, caller->line, + task_wakeup_type_str(caller->what)); + + b_putchr(&trash, '\n'); + + if (applet_putchk(appctx, &trash) == -1) { + /* failed, try again */ + return 0; + } + } + + if (applet_putchk(appctx, &trash) == -1) { + /* failed, try again */ + return 0; + } + + ctx->linenum = 0; // reset first line to dump + if ((ctx->dump_step & 4) == 0) + ctx->dump_step++; // next step + + skip_tasks: + +#ifdef USE_MEMORY_PROFILING + if ((ctx->dump_step & 3) != 2) + goto skip_mem; + + memcpy(tmp_memstats, memprof_stats, sizeof(tmp_memstats)); + if (ctx->by_what) + qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_addr); + else + qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_stats); + + if (!ctx->linenum) + chunk_appendf(&trash, + "Alloc/Free statistics by call place over %.3f sec till %.3f sec ago:\n" + " Calls | Tot Bytes | Caller and method\n" + "<- alloc -> <- free ->|<-- alloc ---> <-- free ---->|\n", + (prof_mem_start_ns ? (prof_mem_stop_ns ? prof_mem_stop_ns : now_ns) - prof_mem_start_ns : 0) / 1000000000.0, + (prof_mem_stop_ns ? now_ns - prof_mem_stop_ns : 0) / 1000000000.0); + + max_lines = ctx->maxcnt; + if (!max_lines) + max_lines = MEMPROF_HASH_BUCKETS + 1; + + for (i = ctx->linenum; i < max_lines; i++) { + struct memprof_stats *entry = &tmp_memstats[i]; + + ctx->linenum = i; + if (!entry->alloc_calls && !entry->free_calls) + continue; + chunk_appendf(&trash, "%11llu %11llu %14llu %14llu| %16p ", + entry->alloc_calls, entry->free_calls, + entry->alloc_tot, entry->free_tot, + entry->caller); + + if (entry->caller) + resolve_sym_name(&trash, NULL, entry->caller); + else + chunk_appendf(&trash, "[other]"); + + chunk_appendf(&trash," %s(%lld)", memprof_methods[entry->method], + (long long)(entry->alloc_tot - entry->free_tot) / (long long)(entry->alloc_calls + entry->free_calls)); + + if (entry->alloc_tot && entry->free_tot) { + /* that's a realloc, show the total diff to help spot leaks */ + chunk_appendf(&trash," [delta=%lld]", (long long)(entry->alloc_tot - entry->free_tot)); + } + + if (entry->info) { + /* that's a pool name */ + const struct pool_head *pool = entry->info; + chunk_appendf(&trash," [pool=%s]", pool->name); + } + + chunk_appendf(&trash, "\n"); + + if (applet_putchk(appctx, &trash) == -1) + return 0; + } + + if (applet_putchk(appctx, &trash) == -1) + return 0; + + tot_alloc_calls = tot_free_calls = tot_alloc_bytes = tot_free_bytes = 0; + for (i = 0; i < max_lines; i++) { + tot_alloc_calls += tmp_memstats[i].alloc_calls; + tot_free_calls += tmp_memstats[i].free_calls; + tot_alloc_bytes += tmp_memstats[i].alloc_tot; + tot_free_bytes += tmp_memstats[i].free_tot; + } + + chunk_appendf(&trash, + "-----------------------|-----------------------------|\n" + "%11llu %11llu %14llu %14llu| <- Total; Delta_calls=%lld; Delta_bytes=%lld\n", + tot_alloc_calls, tot_free_calls, + tot_alloc_bytes, tot_free_bytes, + tot_alloc_calls - tot_free_calls, + tot_alloc_bytes - tot_free_bytes); + + if (applet_putchk(appctx, &trash) == -1) + return 0; + + ctx->linenum = 0; // reset first line to dump + if ((ctx->dump_step & 4) == 0) + ctx->dump_step++; // next step + + skip_mem: +#endif // USE_MEMORY_PROFILING + + return 1; +} + +/* parse a "show profiling" command. It returns 1 on failure, 0 if it starts to dump. + * - cli.i0 is set to the first state (0=all, 4=status, 5=tasks, 6=memory) + * - cli.o1 is set to 1 if the output must be sorted by addr instead of usage + * - cli.o0 is set to the number of lines of output + */ +static int cli_parse_show_profiling(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct show_prof_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + int arg; + + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + + for (arg = 2; *args[arg]; arg++) { + if (strcmp(args[arg], "all") == 0) { + ctx->dump_step = 0; // will cycle through 0,1,2; default + } + else if (strcmp(args[arg], "status") == 0) { + ctx->dump_step = 4; // will visit status only + } + else if (strcmp(args[arg], "tasks") == 0) { + ctx->dump_step = 5; // will visit tasks only + } + else if (strcmp(args[arg], "memory") == 0) { + ctx->dump_step = 6; // will visit memory only + } + else if (strcmp(args[arg], "byaddr") == 0) { + ctx->by_what = 1; // sort output by address instead of usage + } + else if (strcmp(args[arg], "bytime") == 0) { + ctx->by_what = 2; // sort output by total time instead of usage + } + else if (strcmp(args[arg], "aggr") == 0) { + ctx->aggr = 1; // aggregate output by callee + } + else if (isdigit((unsigned char)*args[arg])) { + ctx->maxcnt = atoi(args[arg]); // number of entries to dump + } + else + return cli_err(appctx, "Expects either 'all', 'status', 'tasks', 'memory', 'byaddr', 'bytime', 'aggr' or a max number of output lines.\n"); + } + return 0; +} + +/* This function scans all threads' run queues and collects statistics about + * running tasks. It returns 0 if the output buffer is full and it needs to be + * called again, otherwise non-zero. + */ +static int cli_io_handler_show_tasks(struct appctx *appctx) +{ + struct sched_activity tmp_activity[SCHED_ACT_HASH_BUCKETS] __attribute__((aligned(64))); + struct stconn *sc = appctx_sc(appctx); + struct buffer *name_buffer = get_trash_chunk(); + struct sched_activity *entry; + const struct tasklet *tl; + const struct task *t; + uint64_t now_ns, lat; + struct eb32_node *rqnode; + uint64_t tot_calls; + int thr, queue; + int i, max; + + /* FIXME: Don't watch the other side ! */ + if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE)) + return 1; + + /* It's not possible to scan queues in small chunks and yield in the + * middle of the dump and come back again. So what we're doing instead + * is to freeze all threads and inspect their queues at once as fast as + * possible, using a sched_activity array to collect metrics with + * limited collision, then we'll report statistics only. The tasks' + * #calls will reflect the number of occurrences, and the lat_time will + * reflect the latency when set. We prefer to take the time before + * calling thread_isolate() so that the wait time doesn't impact the + * measurement accuracy. However this requires to take care of negative + * times since tasks might be queued after we retrieve it. + */ + + now_ns = now_mono_time(); + memset(tmp_activity, 0, sizeof(tmp_activity)); + + thread_isolate(); + + /* 1. global run queue */ + +#ifdef USE_THREAD + for (thr = 0; thr < global.nbthread; thr++) { + /* task run queue */ + rqnode = eb32_first(&ha_thread_ctx[thr].rqueue_shared); + while (rqnode) { + t = eb32_entry(rqnode, struct task, rq); + entry = sched_activity_entry(tmp_activity, t->process, NULL); + if (t->wake_date) { + lat = now_ns - t->wake_date; + if ((int64_t)lat > 0) + entry->lat_time += lat; + } + entry->calls++; + rqnode = eb32_next(rqnode); + } + } +#endif + /* 2. all threads's local run queues */ + for (thr = 0; thr < global.nbthread; thr++) { + /* task run queue */ + rqnode = eb32_first(&ha_thread_ctx[thr].rqueue); + while (rqnode) { + t = eb32_entry(rqnode, struct task, rq); + entry = sched_activity_entry(tmp_activity, t->process, NULL); + if (t->wake_date) { + lat = now_ns - t->wake_date; + if ((int64_t)lat > 0) + entry->lat_time += lat; + } + entry->calls++; + rqnode = eb32_next(rqnode); + } + + /* shared tasklet list */ + list_for_each_entry(tl, mt_list_to_list(&ha_thread_ctx[thr].shared_tasklet_list), list) { + t = (const struct task *)tl; + entry = sched_activity_entry(tmp_activity, t->process, NULL); + if (!TASK_IS_TASKLET(t) && t->wake_date) { + lat = now_ns - t->wake_date; + if ((int64_t)lat > 0) + entry->lat_time += lat; + } + entry->calls++; + } + + /* classful tasklets */ + for (queue = 0; queue < TL_CLASSES; queue++) { + list_for_each_entry(tl, &ha_thread_ctx[thr].tasklets[queue], list) { + t = (const struct task *)tl; + entry = sched_activity_entry(tmp_activity, t->process, NULL); + if (!TASK_IS_TASKLET(t) && t->wake_date) { + lat = now_ns - t->wake_date; + if ((int64_t)lat > 0) + entry->lat_time += lat; + } + entry->calls++; + } + } + } + + /* hopefully we're done */ + thread_release(); + + chunk_reset(&trash); + + tot_calls = 0; + for (i = 0; i < SCHED_ACT_HASH_BUCKETS; i++) + tot_calls += tmp_activity[i].calls; + + qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_calls); + + chunk_appendf(&trash, "Running tasks: %d (%d threads)\n" + " function places %% lat_tot lat_avg\n", + (int)tot_calls, global.nbthread); + + for (i = 0; i < SCHED_ACT_HASH_BUCKETS && tmp_activity[i].calls; i++) { + chunk_reset(name_buffer); + + if (!tmp_activity[i].func) + chunk_printf(name_buffer, "other"); + else + resolve_sym_name(name_buffer, "", tmp_activity[i].func); + + /* reserve 35 chars for name+' '+#calls, knowing that longer names + * are often used for less often called functions. + */ + max = 35 - name_buffer->data; + if (max < 1) + max = 1; + chunk_appendf(&trash, " %s%*llu %3d.%1d", + name_buffer->area, max, (unsigned long long)tmp_activity[i].calls, + (int)(100ULL * tmp_activity[i].calls / tot_calls), + (int)((1000ULL * tmp_activity[i].calls / tot_calls)%10)); + print_time_short(&trash, " ", tmp_activity[i].lat_time, ""); + print_time_short(&trash, " ", tmp_activity[i].lat_time / tmp_activity[i].calls, "\n"); + } + + if (applet_putchk(appctx, &trash) == -1) { + /* failed, try again */ + return 0; + } + return 1; +} + +/* This function dumps some activity counters used by developers and support to + * rule out some hypothesis during bug reports. It returns 0 if the output + * buffer is full and it needs to be called again, otherwise non-zero. It dumps + * everything at once in the buffer and is not designed to do it in multiple + * passes. + */ +static int cli_io_handler_show_activity(struct appctx *appctx) +{ + struct stconn *sc = appctx_sc(appctx); + struct show_activity_ctx *actctx = appctx->svcctx; + int tgt = actctx->thr; // target thread, -1 for all, 0 for total only + uint up_sec, up_usec; + int base_line; + ullong up; + + /* FIXME: Don't watch the other side ! */ + if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE)) + return 1; + + /* this macro is used below to dump values. The thread number is "thr", + * and runs from 0 to nbt-1 when values are printed using the formula. + * We normally try to dmup integral lines in order to keep counters + * consistent. If we fail once on a line, we'll detect it next time + * because we'll have committed actctx->col=1 thanks to the header + * always being dumped individually. We'll be called again thanks to + * the header being present, leaving some data in the buffer. In this + * case once we restart we'll proceed one column at a time to make sure + * we don't overflow the buffer again. + */ +#undef SHOW_VAL +#define SHOW_VAL(header, x, formula) \ + do { \ + unsigned int _v[MAX_THREADS]; \ + unsigned int _tot; \ + const int _nbt = global.nbthread; \ + int restarted = actctx->col > 0; \ + int thr; \ + _tot = thr = 0; \ + do { \ + _tot += _v[thr] = (x); \ + } while (++thr < _nbt); \ + for (thr = actctx->col - 2; thr <= _nbt; thr++) { \ + if (thr == -2) { \ + /* line header */ \ + chunk_appendf(&trash, "%s", header); \ + } \ + else if (thr == -1) { \ + /* aggregate value only for multi-thread: all & 0 */ \ + if (_nbt > 1 && tgt <= 0) \ + chunk_appendf(&trash, " %u%s", \ + (formula), \ + (tgt < 0) ? \ + " [" : ""); \ + } \ + else if (thr < _nbt) { \ + /* individual value only for all or exact value */ \ + if (tgt == -1 || tgt == thr+1) \ + chunk_appendf(&trash, " %u", \ + _v[thr]); \ + } \ + else /* thr == _nbt */ { \ + chunk_appendf(&trash, "%s\n", \ + (_nbt > 1 && tgt < 0) ? \ + " ]" : ""); \ + } \ + if (thr == -2 || restarted) { \ + /* failed once, emit one column at a time */\ + if (applet_putchk(appctx, &trash) == -1) \ + break; /* main loop handles it */ \ + chunk_reset(&trash); \ + actctx->col = thr + 3; \ + } \ + } \ + if (applet_putchk(appctx, &trash) == -1) \ + break; /* main loop will handle it */ \ + /* OK dump done for this line */ \ + chunk_reset(&trash); \ + if (thr > _nbt) \ + actctx->col = 0; \ + } while (0) + + /* retrieve uptime */ + up = now_ns - start_time_ns; + up_sec = ns_to_sec(up); + up_usec = (up / 1000U) % 1000000U; + + /* iterate over all dump lines. It happily skips over holes so it's + * not a problem not to have an exact match, we just need to have + * stable and consistent lines during a dump. + */ + base_line = __LINE__; + do { + chunk_reset(&trash); + + switch (actctx->line + base_line) { + case __LINE__: chunk_appendf(&trash, "thread_id: %u (%u..%u)\n", tid + 1, 1, global.nbthread); break; + case __LINE__: chunk_appendf(&trash, "date_now: %lu.%06lu\n", (ulong)date.tv_sec, (ulong)date.tv_usec); break; + case __LINE__: chunk_appendf(&trash, "uptime_now: %u.%06u\n", up_sec, up_usec); break; + case __LINE__: SHOW_VAL("ctxsw:", activity[thr].ctxsw, _tot); break; + case __LINE__: SHOW_VAL("tasksw:", activity[thr].tasksw, _tot); break; + case __LINE__: SHOW_VAL("empty_rq:", activity[thr].empty_rq, _tot); break; + case __LINE__: SHOW_VAL("long_rq:", activity[thr].long_rq, _tot); break; + case __LINE__: SHOW_VAL("curr_rq:", _HA_ATOMIC_LOAD(&ha_thread_ctx[thr].rq_total), _tot); break; + case __LINE__: SHOW_VAL("loops:", activity[thr].loops, _tot); break; + case __LINE__: SHOW_VAL("wake_tasks:", activity[thr].wake_tasks, _tot); break; + case __LINE__: SHOW_VAL("wake_signal:", activity[thr].wake_signal, _tot); break; + case __LINE__: SHOW_VAL("poll_io:", activity[thr].poll_io, _tot); break; + case __LINE__: SHOW_VAL("poll_exp:", activity[thr].poll_exp, _tot); break; + case __LINE__: SHOW_VAL("poll_drop_fd:", activity[thr].poll_drop_fd, _tot); break; + case __LINE__: SHOW_VAL("poll_skip_fd:", activity[thr].poll_skip_fd, _tot); break; + case __LINE__: SHOW_VAL("conn_dead:", activity[thr].conn_dead, _tot); break; + case __LINE__: SHOW_VAL("stream_calls:", activity[thr].stream_calls, _tot); break; + case __LINE__: SHOW_VAL("pool_fail:", activity[thr].pool_fail, _tot); break; + case __LINE__: SHOW_VAL("buf_wait:", activity[thr].buf_wait, _tot); break; + case __LINE__: SHOW_VAL("cpust_ms_tot:", activity[thr].cpust_total / 2, _tot); break; + case __LINE__: SHOW_VAL("cpust_ms_1s:", read_freq_ctr(&activity[thr].cpust_1s) / 2, _tot); break; + case __LINE__: SHOW_VAL("cpust_ms_15s:", read_freq_ctr_period(&activity[thr].cpust_15s, 15000) / 2, _tot); break; + case __LINE__: SHOW_VAL("avg_cpu_pct:", (100 - ha_thread_ctx[thr].idle_pct), (_tot + _nbt/2) / _nbt); break; + case __LINE__: SHOW_VAL("avg_loop_us:", swrate_avg(activity[thr].avg_loop_us, TIME_STATS_SAMPLES), (_tot + _nbt/2) / _nbt); break; + case __LINE__: SHOW_VAL("accepted:", activity[thr].accepted, _tot); break; + case __LINE__: SHOW_VAL("accq_pushed:", activity[thr].accq_pushed, _tot); break; + case __LINE__: SHOW_VAL("accq_full:", activity[thr].accq_full, _tot); break; +#ifdef USE_THREAD + case __LINE__: SHOW_VAL("accq_ring:", accept_queue_ring_len(&accept_queue_rings[thr]), _tot); break; + case __LINE__: SHOW_VAL("fd_takeover:", activity[thr].fd_takeover, _tot); break; + case __LINE__: SHOW_VAL("check_adopted:",activity[thr].check_adopted, _tot); break; +#endif + case __LINE__: SHOW_VAL("check_started:",activity[thr].check_started, _tot); break; + case __LINE__: SHOW_VAL("check_active:", _HA_ATOMIC_LOAD(&ha_thread_ctx[thr].active_checks), _tot); break; + case __LINE__: SHOW_VAL("check_running:",_HA_ATOMIC_LOAD(&ha_thread_ctx[thr].running_checks), _tot); break; + +#if defined(DEBUG_DEV) + /* keep these ones at the end */ + case __LINE__: SHOW_VAL("ctr0:", activity[thr].ctr0, _tot); break; + case __LINE__: SHOW_VAL("ctr1:", activity[thr].ctr1, _tot); break; + case __LINE__: SHOW_VAL("ctr2:", activity[thr].ctr2, _tot); break; +#endif + } +#undef SHOW_VAL + + /* try to dump what was possibly not dumped yet */ + + if (applet_putchk(appctx, &trash) == -1) { + /* buffer full, retry later */ + return 0; + } + /* line was dumped, let's commit it */ + actctx->line++; + } while (actctx->line + base_line < __LINE__); + + /* dump complete */ + return 1; +} + +/* parse a "show activity" CLI request. Returns 0 if it needs to continue, 1 if it + * wants to stop here. It sets a show_activity_ctx context where, if a specific + * thread is requested, it puts the thread number into ->thr otherwise sets it to + * -1. + */ +static int cli_parse_show_activity(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct show_activity_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + ctx->thr = -1; // show all by default + if (*args[2]) + ctx->thr = atoi(args[2]); + + if (ctx->thr < -1 || ctx->thr > global.nbthread) + return cli_err(appctx, "Thread ID number must be between -1 and nbthread\n"); + + return 0; +} + +/* config keyword parsers */ +static struct cfg_kw_list cfg_kws = {ILH, { +#ifdef USE_MEMORY_PROFILING + { CFG_GLOBAL, "profiling.memory", cfg_parse_prof_memory }, +#endif + { CFG_GLOBAL, "profiling.tasks", cfg_parse_prof_tasks }, + { 0, NULL, NULL } +}}; + +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); + +/* register cli keywords */ +static struct cli_kw_list cli_kws = {{ },{ + { { "set", "profiling", NULL }, "set profiling {auto|on|off} : enable/disable resource profiling (tasks,memory)", cli_parse_set_profiling, NULL }, + { { "show", "activity", NULL }, "show activity [-1|0|thread_num] : show per-thread activity stats (for support/developers)", cli_parse_show_activity, cli_io_handler_show_activity, NULL }, + { { "show", "profiling", NULL }, "show profiling [|<#lines>|]*: show profiling state (all,status,tasks,memory)", cli_parse_show_profiling, cli_io_handler_show_profiling, NULL }, + { { "show", "tasks", NULL }, "show tasks : show running tasks", NULL, cli_io_handler_show_tasks, NULL }, + {{},} +}}; + +INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); diff --git a/src/applet.c b/src/applet.c new file mode 100644 index 0000000..a5b0946 --- /dev/null +++ b/src/applet.c @@ -0,0 +1,501 @@ +/* + * Functions managing applets + * + * Copyright 2000-2015 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +unsigned int nb_applets = 0; + +DECLARE_POOL(pool_head_appctx, "appctx", sizeof(struct appctx)); + + +/* trace source and events */ +static void applet_trace(enum trace_level level, uint64_t mask, + const struct trace_source *src, + const struct ist where, const struct ist func, + const void *a1, const void *a2, const void *a3, const void *a4); + +/* The event representation is split like this : + * app - applet + */ +static const struct trace_event applet_trace_events[] = { +#define APPLET_EV_NEW (1ULL << 0) + { .mask = APPLET_EV_NEW, .name = "app_new", .desc = "new appctx" }, +#define APPLET_EV_FREE (1ULL << 1) + { .mask = APPLET_EV_FREE, .name = "app_free", .desc = "free appctx" }, +#define APPLET_EV_RELEASE (1ULL << 2) + { .mask = APPLET_EV_RELEASE, .name = "app_release", .desc = "release appctx" }, +#define APPLET_EV_PROCESS (1ULL << 3) + { .mask = APPLET_EV_PROCESS, .name = "app_proc", .desc = "process appctx" }, +#define APPLET_EV_ERR (1ULL << 4) + { .mask = APPLET_EV_ERR, .name = "app_err", .desc = "error on appctx" }, +#define APPLET_EV_START (1ULL << 5) + { .mask = APPLET_EV_START, .name = "app_start", .desc = "start appctx" }, + {} +}; + +static const struct name_desc applet_trace_lockon_args[4] = { + /* arg1 */ { /* already used by the applet */ }, + /* arg2 */ { }, + /* arg3 */ { }, + /* arg4 */ { } +}; + +static const struct name_desc applet_trace_decoding[] = { +#define STRM_VERB_CLEAN 1 + { .name="clean", .desc="only user-friendly stuff, generally suitable for level \"user\"" }, +#define STRM_VERB_MINIMAL 2 + { .name="minimal", .desc="report info on streams and connectors" }, +#define STRM_VERB_SIMPLE 3 + { .name="simple", .desc="add info on request and response channels" }, +#define STRM_VERB_ADVANCED 4 + { .name="advanced", .desc="add info on channel's buffer for data and developer levels only" }, +#define STRM_VERB_COMPLETE 5 + { .name="complete", .desc="add info on channel's buffer" }, + { /* end */ } +}; + +static struct trace_source trace_applet = { + .name = IST("applet"), + .desc = "Applet endpoint", + .arg_def = TRC_ARG1_APPCTX, // TRACE()'s first argument is always an appctx + .default_cb = applet_trace, + .known_events = applet_trace_events, + .lockon_args = applet_trace_lockon_args, + .decoding = applet_trace_decoding, + .report_events = ~0, // report everything by default +}; + +#define TRACE_SOURCE &trace_applet +INITCALL1(STG_REGISTER, trace_register_source, TRACE_SOURCE); + +/* the applet traces always expect that arg1, if non-null, is of a appctx (from + * which we can derive everything). + */ +static void applet_trace(enum trace_level level, uint64_t mask, const struct trace_source *src, + const struct ist where, const struct ist func, + const void *a1, const void *a2, const void *a3, const void *a4) +{ + const struct appctx *appctx = a1; + const struct stconn *sc = NULL, *sco = NULL; + const struct stream *s = NULL; + const struct channel *ic = NULL, *oc = NULL; + + if (!appctx || src->verbosity < STRM_VERB_CLEAN) + return; + + sc = appctx_sc(appctx); + if (sc) { + s = __sc_strm(sc); + sco = sc_opposite(sc); + ic = sc_ic(sc); + oc = sc_oc(sc); + } + + /* General info about the stream (htx/tcp, id...) */ + if (s) + chunk_appendf(&trace_buf, " : [%s,%s]", + appctx->applet->name, ((s->flags & SF_HTX) ? "HTX" : "TCP")); + else + chunk_appendf(&trace_buf, " : [%s]", appctx->applet->name); + + if (sc) + /* local and opposite stream connector state */ + chunk_appendf(&trace_buf, " SC=(%s,%s)", + sc_state_str(sc->state), sc_state_str(sco->state)); + else + /* local and opposite stream connector state */ + chunk_appendf(&trace_buf, " SC=(none,none)"); + + if (src->verbosity == STRM_VERB_CLEAN) + return; + + chunk_appendf(&trace_buf, " appctx=%p .t=%p .t.exp=%d .state=%d .st0=%d .st1=%d", + appctx, appctx->t, tick_isset(appctx->t->expire) ? TICKS_TO_MS(appctx->t->expire - now_ms) : TICK_ETERNITY, + appctx->state, appctx->st0, appctx->st1); + + if (!sc || src->verbosity == STRM_VERB_MINIMAL) + return; + + chunk_appendf(&trace_buf, " - s=(%p,0x%08x,0x%x)", s, s->flags, s->conn_err_type); + + chunk_appendf(&trace_buf, " sc=(%p,%d,0x%08x,0x%x) sco=(%p,%d,0x%08x,0x%x) sc.exp(r,w)=(%d,%d) sco.exp(r,w)=(%d,%d)", + sc, sc->state, sc->flags, sc->sedesc->flags, + sco, sco->state, sco->flags, sco->sedesc->flags, + tick_isset(sc_ep_rcv_ex(sc)) ? TICKS_TO_MS(sc_ep_rcv_ex(sc) - now_ms) : TICK_ETERNITY, + tick_isset(sc_ep_snd_ex(sc)) ? TICKS_TO_MS(sc_ep_snd_ex(sc) - now_ms) : TICK_ETERNITY, + tick_isset(sc_ep_rcv_ex(sco)) ? TICKS_TO_MS(sc_ep_rcv_ex(sco) - now_ms) : TICK_ETERNITY, + tick_isset(sc_ep_snd_ex(sco)) ? TICKS_TO_MS(sc_ep_snd_ex(sco) - now_ms) : TICK_ETERNITY); + + + /* If txn defined, don't display all channel info */ + if (src->verbosity == STRM_VERB_SIMPLE) { + chunk_appendf(&trace_buf, " ic=(%p .fl=0x%08x .exp=%d)", + ic, ic->flags, tick_isset(ic->analyse_exp) ? TICKS_TO_MS(ic->analyse_exp - now_ms) : TICK_ETERNITY); + chunk_appendf(&trace_buf, " oc=(%p .fl=0x%08x .exp=%d)", + oc, oc->flags, tick_isset(oc->analyse_exp) ? TICKS_TO_MS(oc->analyse_exp - now_ms) : TICK_ETERNITY); + } + else { + chunk_appendf(&trace_buf, " ic=(%p .fl=0x%08x .ana=0x%08x .exp=%u .o=%lu .tot=%llu .to_fwd=%u)", + ic, ic->flags, ic->analysers, ic->analyse_exp, + (long)ic->output, ic->total, ic->to_forward); + chunk_appendf(&trace_buf, " oc=(%p .fl=0x%08x .ana=0x%08x .exp=%u .o=%lu .tot=%llu .to_fwd=%u)", + oc, oc->flags, oc->analysers, oc->analyse_exp, + (long)oc->output, oc->total, oc->to_forward); + } + + if (src->verbosity == STRM_VERB_SIMPLE || + (src->verbosity == STRM_VERB_ADVANCED && src->level < TRACE_LEVEL_DATA)) + return; + + /* channels' buffer info */ + if (s->flags & SF_HTX) { + struct htx *ichtx = htxbuf(&ic->buf); + struct htx *ochtx = htxbuf(&oc->buf); + + chunk_appendf(&trace_buf, " htx=(%u/%u#%u, %u/%u#%u)", + ichtx->data, ichtx->size, htx_nbblks(ichtx), + ochtx->data, ochtx->size, htx_nbblks(ochtx)); + } + else { + chunk_appendf(&trace_buf, " buf=(%u@%p+%u/%u, %u@%p+%u/%u)", + (unsigned int)b_data(&ic->buf), b_orig(&ic->buf), + (unsigned int)b_head_ofs(&ic->buf), (unsigned int)b_size(&ic->buf), + (unsigned int)b_data(&oc->buf), b_orig(&oc->buf), + (unsigned int)b_head_ofs(&oc->buf), (unsigned int)b_size(&oc->buf)); + } +} + +/* Tries to allocate a new appctx and initialize all of its fields. The appctx + * is returned on success, NULL on failure. The appctx must be released using + * appctx_free(). is assigned as the applet, but it can be NULL. + * is the thread ID to start the applet on, and a negative value allows the + * applet to start anywhere. Backend applets may only be created on the current + * thread. + */ +struct appctx *appctx_new_on(struct applet *applet, struct sedesc *sedesc, int thr) +{ + struct appctx *appctx; + + /* Backend appctx cannot be started on another thread than the local one */ + BUG_ON(thr != tid && sedesc); + + TRACE_ENTER(APPLET_EV_NEW); + + appctx = pool_zalloc(pool_head_appctx); + if (unlikely(!appctx)) { + TRACE_ERROR("APPCTX allocation failure", APPLET_EV_NEW|APPLET_EV_ERR); + goto fail_appctx; + } + + LIST_INIT(&appctx->wait_entry); + appctx->obj_type = OBJ_TYPE_APPCTX; + appctx->applet = applet; + appctx->sess = NULL; + + appctx->t = task_new_on(thr); + if (unlikely(!appctx->t)) { + TRACE_ERROR("APPCTX task allocation failure", APPLET_EV_NEW|APPLET_EV_ERR); + goto fail_task; + } + + if (!sedesc) { + sedesc = sedesc_new(); + if (unlikely(!sedesc)) { + TRACE_ERROR("APPCTX sedesc allocation failure", APPLET_EV_NEW|APPLET_EV_ERR); + goto fail_endp; + } + sedesc->se = appctx; + se_fl_set(sedesc, SE_FL_T_APPLET | SE_FL_ORPHAN); + } + + appctx->sedesc = sedesc; + appctx->t->process = task_run_applet; + appctx->t->context = appctx; + + LIST_INIT(&appctx->buffer_wait.list); + appctx->buffer_wait.target = appctx; + appctx->buffer_wait.wakeup_cb = appctx_buf_available; + + _HA_ATOMIC_INC(&nb_applets); + + TRACE_LEAVE(APPLET_EV_NEW, appctx); + return appctx; + + fail_endp: + task_destroy(appctx->t); + fail_task: + pool_free(pool_head_appctx, appctx); + fail_appctx: + return NULL; +} + +/* Finalize the frontend appctx startup. It must not be called for a backend + * appctx. This function is responsible to create the appctx's session and the + * frontend stream connector. By transitivity, the stream is also created. + * + * It returns 0 on success and -1 on error. In this case, it is the caller + * responsibility to release the appctx. However, the session is released if it + * was created. On success, if an error is encountered in the caller function, + * the stream must be released instead of the appctx. To be sure, + * appctx_free_on_early_error() must be called in this case. + */ +int appctx_finalize_startup(struct appctx *appctx, struct proxy *px, struct buffer *input) +{ + struct session *sess; + + /* async startup is only possible for frontend appctx. Thus for orphan + * appctx. Because no backend appctx can be orphan. + */ + BUG_ON(!se_fl_test(appctx->sedesc, SE_FL_ORPHAN)); + + TRACE_ENTER(APPLET_EV_START, appctx); + + sess = session_new(px, NULL, &appctx->obj_type); + if (!sess) { + TRACE_ERROR("APPCTX session allocation failure", APPLET_EV_START|APPLET_EV_ERR, appctx); + return -1; + } + if (!sc_new_from_endp(appctx->sedesc, sess, input)) { + session_free(sess); + TRACE_ERROR("APPCTX sc allocation failure", APPLET_EV_START|APPLET_EV_ERR, appctx); + return -1; + } + + appctx->sess = sess; + TRACE_LEAVE(APPLET_EV_START, appctx); + return 0; +} + +/* Release function to call when an error occurred during init stage of a + * frontend appctx. For a backend appctx, it just calls appctx_free() + */ +void appctx_free_on_early_error(struct appctx *appctx) +{ + /* If a frontend appctx is attached to a stream connector, release the stream + * instead of the appctx. + */ + if (!se_fl_test(appctx->sedesc, SE_FL_ORPHAN) && !(appctx_sc(appctx)->flags & SC_FL_ISBACK)) { + stream_free(appctx_strm(appctx)); + return; + } + appctx_free(appctx); +} + +void appctx_free(struct appctx *appctx) +{ + /* The task is supposed to be run on this thread, so we can just + * check if it's running already (or about to run) or not + */ + if (!(appctx->t->state & (TASK_QUEUED | TASK_RUNNING))) { + TRACE_POINT(APPLET_EV_FREE, appctx); + __appctx_free(appctx); + } + else { + /* if it's running, or about to run, defer the freeing + * until the callback is called. + */ + appctx->state |= APPLET_WANT_DIE; + task_wakeup(appctx->t, TASK_WOKEN_OTHER); + TRACE_DEVEL("Cannot release APPCTX now, wake it up", APPLET_EV_FREE, appctx); + } +} + +/* reserves a command context of at least bytes in the , for + * use by a CLI command or any regular applet. The pointer to this context is + * stored in ctx.svcctx and is returned. The caller doesn't need to release + * it as it's allocated from reserved space. If the size is larger than + * APPLET_MAX_SVCCTX a crash will occur (hence that will never happen outside + * of development). + * + * Note that the command does *not* initialize the area, so that it can easily + * be used upon each entry in a function. It's left to the initialization code + * to do it if needed. The CLI will always zero the whole area before calling + * a keyword's ->parse() function. + */ +void *applet_reserve_svcctx(struct appctx *appctx, size_t size) +{ + BUG_ON(size > APPLET_MAX_SVCCTX); + appctx->svcctx = &appctx->svc.storage; + return appctx->svcctx; +} + +/* This is used to reset an svcctx and the svc.storage without releasing the + * appctx. In fact this is only used by the CLI applet between commands. + */ +void applet_reset_svcctx(struct appctx *appctx) +{ + memset(&appctx->svc.storage, 0, APPLET_MAX_SVCCTX); + appctx->svcctx = NULL; +} + +/* call the applet's release() function if any, and marks the sedesc as shut. + * Needs to be called upon close(). + */ +void appctx_shut(struct appctx *appctx) +{ + if (se_fl_test(appctx->sedesc, SE_FL_SHR | SE_FL_SHW)) + return; + + TRACE_ENTER(APPLET_EV_RELEASE, appctx); + if (appctx->applet->release) + appctx->applet->release(appctx); + + if (LIST_INLIST(&appctx->buffer_wait.list)) + LIST_DEL_INIT(&appctx->buffer_wait.list); + + se_fl_set(appctx->sedesc, SE_FL_SHRR | SE_FL_SHWN); + TRACE_LEAVE(APPLET_EV_RELEASE, appctx); +} + +/* Callback used to wake up an applet when a buffer is available. The applet + * is woken up if an input buffer was requested for the associated + * stream connector. In this case the buffer is immediately allocated and the + * function returns 1. Otherwise it returns 0. Note that this automatically + * covers multiple wake-up attempts by ensuring that the same buffer will not + * be accounted for multiple times. + */ +int appctx_buf_available(void *arg) +{ + struct appctx *appctx = arg; + struct stconn *sc = appctx_sc(appctx); + + /* allocation requested ? */ + if (!(sc->flags & SC_FL_NEED_BUFF)) + return 0; + + sc_have_buff(sc); + + /* was already allocated another way ? if so, don't take this one */ + if (c_size(sc_ic(sc)) || sc_ep_have_ff_data(sc_opposite(sc))) + return 0; + + /* allocation possible now ? */ + if (!b_alloc(&sc_ic(sc)->buf)) { + sc_need_buff(sc); + return 0; + } + + task_wakeup(appctx->t, TASK_WOKEN_RES); + return 1; +} + +/* Default applet handler */ +struct task *task_run_applet(struct task *t, void *context, unsigned int state) +{ + struct appctx *app = context; + struct stconn *sc, *sco; + unsigned int rate; + size_t count; + int did_send = 0; + + TRACE_ENTER(APPLET_EV_PROCESS, app); + + if (app->state & APPLET_WANT_DIE) { + TRACE_DEVEL("APPCTX want die, release it", APPLET_EV_FREE, app); + __appctx_free(app); + return NULL; + } + + if (se_fl_test(app->sedesc, SE_FL_ORPHAN)) { + /* Finalize init of orphan appctx. .init callback function must + * be defined and it must finalize appctx startup. + */ + BUG_ON(!app->applet->init); + + if (appctx_init(app) == -1) { + TRACE_DEVEL("APPCTX init failed", APPLET_EV_FREE|APPLET_EV_ERR, app); + appctx_free_on_early_error(app); + return NULL; + } + BUG_ON(!app->sess || !appctx_sc(app) || !appctx_strm(app)); + TRACE_DEVEL("APPCTX initialized", APPLET_EV_PROCESS, app); + } + + sc = appctx_sc(app); + sco = sc_opposite(sc); + + /* We always pretend the applet can't get and doesn't want to + * put, it's up to it to change this if needed. This ensures + * that one applet which ignores any event will not spin. + */ + applet_need_more_data(app); + applet_have_no_more_data(app); + + /* Now we'll try to allocate the input buffer. We wake up the applet in + * all cases. So this is the applet's responsibility to check if this + * buffer was allocated or not. This leaves a chance for applets to do + * some other processing if needed. The applet doesn't have anything to + * do if it needs the buffer, it will be called again upon readiness. + */ + if (!sc_alloc_ibuf(sc, &app->buffer_wait)) + applet_have_more_data(app); + + count = co_data(sc_oc(sc)); + app->applet->fct(app); + + TRACE_POINT(APPLET_EV_PROCESS, app); + + /* now check if the applet has released some room and forgot to + * notify the other side about it. + */ + if (count != co_data(sc_oc(sc))) { + sc_oc(sc)->flags |= CF_WRITE_EVENT | CF_WROTE_DATA; + if (sco->room_needed < 0 || channel_recv_max(sc_oc(sc)) >= sco->room_needed) + sc_have_room(sco); + did_send = 1; + } + else { + if (!sco->room_needed) + sc_have_room(sco); + } + + if (sc_ic(sc)->flags & CF_READ_EVENT) + sc_ep_report_read_activity(sc); + + if (sc_waiting_room(sc) && (sc->flags & SC_FL_ABRT_DONE)) { + sc_ep_set(sc, SE_FL_EOS|SE_FL_ERROR); + } + + if (!co_data(sc_oc(sc))) { + if (did_send) + sc_ep_report_send_activity(sc); + } + else + sc_ep_report_blocked_send(sc, did_send); + + /* measure the call rate and check for anomalies when too high */ + if (((b_size(sc_ib(sc)) && sc->flags & SC_FL_NEED_BUFF) || // asks for a buffer which is present + (b_size(sc_ib(sc)) && !b_data(sc_ib(sc)) && sc->flags & SC_FL_NEED_ROOM) || // asks for room in an empty buffer + (b_data(sc_ob(sc)) && sc_is_send_allowed(sc)) || // asks for data already present + (!b_data(sc_ib(sc)) && b_data(sc_ob(sc)) && // didn't return anything ... + (!(sc_oc(sc)->flags & CF_WRITE_EVENT) && (sc->flags & SC_FL_SHUT_WANTED))))) { // ... and left data pending after a shut + rate = update_freq_ctr(&app->call_rate, 1); + if (rate >= 100000 && app->call_rate.prev_ctr) // looped like this more than 100k times over last second + stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate)); + } + + sc->app_ops->wake(sc); + channel_release_buffer(sc_ic(sc), &app->buffer_wait); + TRACE_LEAVE(APPLET_EV_PROCESS, app); + return t; +} diff --git a/src/arg.c b/src/arg.c new file mode 100644 index 0000000..2810050 --- /dev/null +++ b/src/arg.c @@ -0,0 +1,479 @@ +/* + * Functions used to parse typed argument lists + * + * Copyright 2012 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +const char *arg_type_names[ARGT_NBTYPES] = { + [ARGT_STOP] = "end of arguments", + [ARGT_SINT] = "integer", + [ARGT_STR] = "string", + [ARGT_IPV4] = "IPv4 address", + [ARGT_MSK4] = "IPv4 mask", + [ARGT_IPV6] = "IPv6 address", + [ARGT_MSK6] = "IPv6 mask", + [ARGT_TIME] = "delay", + [ARGT_SIZE] = "size", + [ARGT_FE] = "frontend", + [ARGT_BE] = "backend", + [ARGT_TAB] = "table", + [ARGT_SRV] = "server", + [ARGT_USR] = "user list", + [ARGT_MAP] = "map", + [ARGT_REG] = "regex", + [ARGT_VAR] = "variable", + [ARGT_PBUF_FNUM] = "Protocol buffers field number", + /* Unassigned types must never happen. Better crash during parsing if they do. */ +}; + +/* This dummy arg list may be used by default when no arg is found, it helps + * parsers by removing pointer checks. + */ +struct arg empty_arg_list[ARGM_NBARGS] = { }; + +/* This function clones a struct arg_list template into a new one which is + * returned. + */ +struct arg_list *arg_list_clone(const struct arg_list *orig) +{ + struct arg_list *new; + + if ((new = calloc(1, sizeof(*new))) != NULL) { + /* ->list will be set by the caller when inserting the element. + * ->arg and ->arg_pos will be set by the caller. + */ + new->ctx = orig->ctx; + new->kw = orig->kw; + new->conv = orig->conv; + new->file = orig->file; + new->line = orig->line; + } + return new; +} + +/* This function clones a struct template into a new one which is + * set to point to arg at pos , and which is returned if the caller + * wants to apply further changes. + */ +struct arg_list *arg_list_add(struct arg_list *orig, struct arg *arg, int pos) +{ + struct arg_list *new; + + new = arg_list_clone(orig); + if (new) { + new->arg = arg; + new->arg_pos = pos; + LIST_APPEND(&orig->list, &new->list); + } + return new; +} + +/* This function builds an argument list from a config line, and stops at the + * first non-matching character, which is pointed to in . A valid arg + * list starts with an opening parenthesis '(', contains a number of comma- + * delimited words, and ends with the closing parenthesis ')'. An empty list + * (with or without the parenthesis) will lead to a valid empty argument if the + * keyword has a mandatory one. The function returns the number of arguments + * emitted, or <0 in case of any error. Everything needed it automatically + * allocated. A pointer to an error message might be returned in err_msg if not + * NULL, in which case it would be allocated and the caller will have to check + * it and free it. The output arg list is returned in argp which must be valid. + * The returned array is always terminated by an arg of type ARGT_STOP (0), + * unless the mask indicates that no argument is supported. Unresolved arguments + * are appended to arg list , which also serves as a template to create new + * entries. may be NULL if unresolved arguments are not allowed. The mask + * is composed of a number of mandatory arguments in its lower ARGM_BITS bits, + * and a concatenation of each argument type in each subsequent ARGT_BITS-bit + * sblock. If is not NULL, it must point to a freeable or NULL + * pointer. The caller is expected to restart the parsing from the new pointer + * set in , which is the first character considered as not being part + * of the arg list. The input string ends on the first between characters + * (when len is positive) or the first NUL character. Placing -1 in will + * make it virtually unbounded (~2GB long strings). + */ +int make_arg_list(const char *in, int len, uint64_t mask, struct arg **argp, + char **err_msg, const char **end_ptr, int *err_arg, + struct arg_list *al) +{ + int nbarg; + int pos; + struct arg *arg; + const char *beg; + const char *ptr_err = NULL; + int min_arg; + int empty; + struct arg_list *new_al = al; + + *argp = NULL; + + empty = 0; + if (!len || *in != '(') { + /* it's already not for us, stop here */ + empty = 1; + len = 0; + } else { + /* skip opening parenthesis */ + len--; + in++; + } + + min_arg = mask & ARGM_MASK; + mask >>= ARGM_BITS; + + pos = 0; + /* find between 0 and NBARGS the max number of args supported by the mask */ + for (nbarg = 0; nbarg < ARGM_NBARGS && ((mask >> (nbarg * ARGT_BITS)) & ARGT_MASK); nbarg++); + + if (!nbarg) + goto end_parse; + + /* Note: an empty input string contains an empty argument if this argument + * is marked mandatory. Otherwise we can ignore it. + */ + if (empty && !min_arg) + goto end_parse; + + arg = *argp = calloc(nbarg + 1, sizeof(**argp)); + + if (!arg) + goto alloc_err; + + /* Note: empty arguments after a comma always exist. */ + while (pos < nbarg) { + unsigned int uint; + int squote = 0, dquote = 0; + char *out; + + chunk_reset(&trash); + out = trash.area; + + while (len && *in && trash.data < trash.size - 1) { + if (*in == '"' && !squote) { /* double quote outside single quotes */ + if (dquote) + dquote = 0; + else + dquote = 1; + in++; len--; + continue; + } + else if (*in == '\'' && !dquote) { /* single quote outside double quotes */ + if (squote) + squote = 0; + else + squote = 1; + in++; len--; + continue; + } + else if (*in == '\\' && !squote && len != 1) { + /* '\', ', ' ', '"' support being escaped by '\' */ + if (in[1] == 0) + goto unquote_err; + + if (in[1] == '\\' || in[1] == ' ' || in[1] == '"' || in[1] == '\'') { + in++; len--; + *out++ = *in; + } + else if (in[1] == 'r') { + in++; len--; + *out++ = '\r'; + } + else if (in[1] == 'n') { + in++; len--; + *out++ = '\n'; + } + else if (in[1] == 't') { + in++; len--; + *out++ = '\t'; + } + else { + /* just a lone '\' */ + *out++ = *in; + } + in++; len--; + } + else { + if (!squote && !dquote && (*in == ',' || *in == ')')) { + /* end of argument */ + break; + } + /* verbatim copy */ + *out++ = *in++; + len--; + } + trash.data = out - trash.area; + } + + if (len && *in && *in != ',' && *in != ')') + goto buffer_err; + + trash.area[trash.data] = 0; + + arg->type = (mask >> (pos * ARGT_BITS)) & ARGT_MASK; + + switch (arg->type) { + case ARGT_SINT: + if (!trash.data) // empty number + goto empty_err; + beg = trash.area; + arg->data.sint = read_int64(&beg, trash.area + trash.data); + if (beg < trash.area + trash.data) + goto parse_err; + arg->type = ARGT_SINT; + break; + + case ARGT_FE: + case ARGT_BE: + case ARGT_TAB: + case ARGT_SRV: + case ARGT_USR: + case ARGT_REG: + /* These argument types need to be stored as strings during + * parsing then resolved later. + */ + if (!al) + goto resolve_err; + arg->unresolved = 1; + new_al = arg_list_add(al, arg, pos); + __fallthrough; + + case ARGT_STR: + /* all types that must be resolved are stored as strings + * during the parsing. The caller must at one point resolve + * them and free the string. + */ + arg->data.str.area = my_strndup(trash.area, trash.data); + arg->data.str.data = trash.data; + arg->data.str.size = trash.data + 1; + break; + + case ARGT_IPV4: + if (!trash.data) // empty address + goto empty_err; + + if (inet_pton(AF_INET, trash.area, &arg->data.ipv4) <= 0) + goto parse_err; + break; + + case ARGT_MSK4: + if (!trash.data) // empty mask + goto empty_err; + + if (!str2mask(trash.area, &arg->data.ipv4)) + goto parse_err; + + arg->type = ARGT_IPV4; + break; + + case ARGT_IPV6: + if (!trash.data) // empty address + goto empty_err; + + if (inet_pton(AF_INET6, trash.area, &arg->data.ipv6) <= 0) + goto parse_err; + break; + + case ARGT_MSK6: + if (!trash.data) // empty mask + goto empty_err; + + if (!str2mask6(trash.area, &arg->data.ipv6)) + goto parse_err; + + arg->type = ARGT_IPV6; + break; + + case ARGT_TIME: + if (!trash.data) // empty time + goto empty_err; + + ptr_err = parse_time_err(trash.area, &uint, TIME_UNIT_MS); + if (ptr_err) { + if (ptr_err == PARSE_TIME_OVER || ptr_err == PARSE_TIME_UNDER) + ptr_err = trash.area; + goto parse_err; + } + arg->data.sint = uint; + arg->type = ARGT_SINT; + break; + + case ARGT_SIZE: + if (!trash.data) // empty size + goto empty_err; + + ptr_err = parse_size_err(trash.area, &uint); + if (ptr_err) + goto parse_err; + + arg->data.sint = uint; + arg->type = ARGT_SINT; + break; + + case ARGT_PBUF_FNUM: + if (!trash.data) + goto empty_err; + + if (!parse_dotted_uints(trash.area, &arg->data.fid.ids, &arg->data.fid.sz)) + goto parse_err; + + break; + + /* FIXME: other types need to be implemented here */ + default: + goto not_impl; + } + + pos++; + arg++; + + /* don't go back to parsing if we reached end */ + if (!len || !*in || *in == ')' || pos >= nbarg) + break; + + /* skip comma */ + in++; len--; + } + + end_parse: + if (pos < min_arg) { + /* not enough arguments */ + memprintf(err_msg, + "missing arguments (got %d/%d), type '%s' expected", + pos, min_arg, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK]); + goto err; + } + + if (empty) { + /* nothing to do */ + } else if (*in == ')') { + /* skip the expected closing parenthesis */ + in++; + } else { + /* the caller is responsible for freeing this message */ + char *word = (len > 0) ? my_strndup(in, len) : (char *)in; + + if (*word) + memprintf(err_msg, "expected ')' before '%s'", word); + else + memprintf(err_msg, "expected ')'"); + + if (len > 0) + free(word); + /* when we're missing a right paren, the empty part preceding + * already created an empty arg, adding one to the position, so + * let's fix the reporting to avoid being confusing. + */ + if (pos > 1) + pos--; + goto err; + } + + /* note that pos might be < nbarg and this is not an error, it's up to the + * caller to decide what to do with optional args. + */ + if (err_arg) + *err_arg = pos; + if (end_ptr) + *end_ptr = in; + return pos; + + err: + if (new_al == al) { + /* only free the arg area if we have not queued unresolved args + * still pointing to it. + */ + free_args(*argp); + free(*argp); + } + *argp = NULL; + if (err_arg) + *err_arg = pos; + if (end_ptr) + *end_ptr = in; + return -1; + + empty_err: + /* If we've only got an empty set of parenthesis with nothing + * in between, there is no arg at all. + */ + if (!pos) { + ha_free(argp); + } + + if (pos >= min_arg) + goto end_parse; + + memprintf(err_msg, "expected type '%s' at position %d, but got nothing", + arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); + goto err; + + parse_err: + /* come here with the word attempted to parse in trash */ + memprintf(err_msg, "failed to parse '%s' as type '%s' at position %d", + trash.area, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); + goto err; + + not_impl: + memprintf(err_msg, "parsing for type '%s' was not implemented, please report this bug", + arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK]); + goto err; + + buffer_err: + memprintf(err_msg, "too small buffer size to store decoded argument %d, increase bufsize ?", + pos + 1); + goto err; + + unquote_err: + /* come here with the parsed part in : and the + * unparsable part in . + */ + trash.area[trash.data] = 0; + memprintf(err_msg, "failed to parse '%s' after '%s' as type '%s' at position %d", + in, trash.area, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); + goto err; + +alloc_err: + memprintf(err_msg, "out of memory"); + goto err; + + resolve_err: + memprintf(err_msg, "unresolved argument of type '%s' at position %d not allowed", + arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); + goto err; +} + +/* Free all args of an args array, taking care of unresolved arguments as well. + * It stops at the ARGT_STOP, which must be present. The array itself is not + * freed, it's up to the caller to do it. However it is returned, allowing to + * call free(free_args(argptr)). It is valid to call it with a NULL args, and + * nothing will be done). + */ +struct arg *free_args(struct arg *args) +{ + struct arg *arg; + + for (arg = args; arg && arg->type != ARGT_STOP; arg++) { + if (arg->type == ARGT_STR || arg->unresolved) + chunk_destroy(&arg->data.str); + else if (arg->type == ARGT_REG) + regex_free(arg->data.reg); + else if (arg->type == ARGT_PBUF_FNUM) + ha_free(&arg->data.fid.ids); + } + return args; +} diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..0031300 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,316 @@ +/* + * User authentication & authorization + * + * Copyright 2010 Krzysztof Piotr Oledzki + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#ifdef USE_LIBCRYPT +/* This is to have crypt() defined on Linux */ +#define _GNU_SOURCE + +#ifdef USE_CRYPT_H +/* some platforms such as Solaris need this */ +#include +#endif +#endif /* USE_LIBCRYPT */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct userlist *userlist = NULL; /* list of all existing userlists */ + +#ifdef USE_LIBCRYPT +#define CRYPT_STATE_MSG "yes" +#ifdef HA_HAVE_CRYPT_R +/* context for crypt_r() */ +static THREAD_LOCAL struct crypt_data crypt_data = { .initialized = 0 }; +#else +/* lock for crypt() */ +__decl_thread(static HA_SPINLOCK_T auth_lock); +#endif +#else /* USE_LIBCRYPT */ +#define CRYPT_STATE_MSG "no" +#endif + +/* find targets for selected groups. The function returns pointer to + * the userlist struct or NULL if name is NULL/empty or unresolvable. + */ + +struct userlist * +auth_find_userlist(char *name) +{ + struct userlist *l; + + if (!name || !*name) + return NULL; + + for (l = userlist; l; l = l->next) + if (strcmp(l->name, name) == 0) + return l; + + return NULL; +} + +int check_group(struct userlist *ul, char *name) +{ + struct auth_groups *ag; + + for (ag = ul->groups; ag; ag = ag->next) + if (strcmp(name, ag->name) == 0) + return 1; + return 0; +} + +void +userlist_free(struct userlist *ul) +{ + struct userlist *tul; + struct auth_users *au, *tau; + struct auth_groups_list *agl, *tagl; + struct auth_groups *ag, *tag; + + while (ul) { + /* Free users. */ + au = ul->users; + while (au) { + /* Free groups that own current user. */ + agl = au->u.groups; + while (agl) { + tagl = agl; + agl = agl->next; + free(tagl); + } + + tau = au; + au = au->next; + free(tau->user); + free(tau->pass); + free(tau); + } + + /* Free grouplist. */ + ag = ul->groups; + while (ag) { + tag = ag; + ag = ag->next; + free(tag->name); + free(tag); + } + + tul = ul; + ul = ul->next; + free(tul->name); + free(tul); + }; +} + +int userlist_postinit() +{ + struct userlist *curuserlist = NULL; + + /* Resolve usernames and groupnames. */ + for (curuserlist = userlist; curuserlist; curuserlist = curuserlist->next) { + struct auth_groups *ag; + struct auth_users *curuser; + struct auth_groups_list *grl; + + for (curuser = curuserlist->users; curuser; curuser = curuser->next) { + char *group = NULL; + struct auth_groups_list *groups = NULL; + + if (!curuser->u.groups_names) + continue; + + while ((group = strtok(group?NULL:curuser->u.groups_names, ","))) { + for (ag = curuserlist->groups; ag; ag = ag->next) { + if (strcmp(ag->name, group) == 0) + break; + } + + if (!ag) { + ha_alert("userlist '%s': no such group '%s' specified in user '%s'\n", + curuserlist->name, group, curuser->user); + free(groups); + return ERR_ALERT | ERR_FATAL; + } + + /* Add this group at the group userlist. */ + grl = calloc(1, sizeof(*grl)); + if (!grl) { + ha_alert("userlist '%s': no more memory when trying to allocate the user groups.\n", + curuserlist->name); + free(groups); + return ERR_ALERT | ERR_FATAL; + } + + grl->group = ag; + grl->next = groups; + groups = grl; + } + + free(curuser->u.groups); + curuser->u.groups = groups; + } + + for (ag = curuserlist->groups; ag; ag = ag->next) { + char *user = NULL; + + if (!ag->groupusers) + continue; + + while ((user = strtok(user?NULL:ag->groupusers, ","))) { + for (curuser = curuserlist->users; curuser; curuser = curuser->next) { + if (strcmp(curuser->user, user) == 0) + break; + } + + if (!curuser) { + ha_alert("userlist '%s': no such user '%s' specified in group '%s'\n", + curuserlist->name, user, ag->name); + return ERR_ALERT | ERR_FATAL; + } + + /* Add this group at the group userlist. */ + grl = calloc(1, sizeof(*grl)); + if (!grl) { + ha_alert("userlist '%s': no more memory when trying to allocate the user groups.\n", + curuserlist->name); + return ERR_ALERT | ERR_FATAL; + } + + grl->group = ag; + grl->next = curuser->u.groups; + curuser->u.groups = grl; + } + + ha_free(&ag->groupusers); + } + +#ifdef DEBUG_AUTH + for (ag = curuserlist->groups; ag; ag = ag->next) { + struct auth_groups_list *agl; + + fprintf(stderr, "group %s, id %p, users:", ag->name, ag); + for (curuser = curuserlist->users; curuser; curuser = curuser->next) { + for (agl = curuser->u.groups; agl; agl = agl->next) { + if (agl->group == ag) + fprintf(stderr, " %s", curuser->user); + } + } + fprintf(stderr, "\n"); + } +#endif + } + + return ERR_NONE; +} + +/* + * Authenticate and authorize user; return 1 if OK, 0 if case of error. + */ +int +check_user(struct userlist *ul, const char *user, const char *pass) +{ + + struct auth_users *u; +#ifdef DEBUG_AUTH + struct auth_groups_list *agl; +#endif + const char *ep; + +#ifdef DEBUG_AUTH + fprintf(stderr, "req: userlist=%s, user=%s, pass=%s\n", + ul->name, user, pass); +#endif + + for (u = ul->users; u; u = u->next) + if (strcmp(user, u->user) == 0) + break; + + if (!u) + return 0; + +#ifdef DEBUG_AUTH + fprintf(stderr, "cfg: user=%s, pass=%s, flags=%X, groups=", + u->user, u->pass, u->flags); + for (agl = u->u.groups; agl; agl = agl->next) + fprintf(stderr, " %s", agl->group->name); +#endif + + if (!(u->flags & AU_O_INSECURE)) { +#ifdef USE_LIBCRYPT +#ifdef HA_HAVE_CRYPT_R + ep = crypt_r(pass, u->pass, &crypt_data); +#else + HA_SPIN_LOCK(AUTH_LOCK, &auth_lock); + ep = crypt(pass, u->pass); + HA_SPIN_UNLOCK(AUTH_LOCK, &auth_lock); +#endif +#else + return 0; +#endif + } else + ep = pass; + +#ifdef DEBUG_AUTH + fprintf(stderr, ", crypt=%s\n", ((ep) ? ep : "")); +#endif + + if (ep && strcmp(ep, u->pass) == 0) + return 1; + else + return 0; +} + +struct pattern * +pat_match_auth(struct sample *smp, struct pattern_expr *expr, int fill) +{ + struct userlist *ul = smp->ctx.a[0]; + struct pattern_list *lst; + struct auth_users *u; + struct auth_groups_list *agl; + struct pattern *pattern; + + /* Check if the userlist is present in the context data. */ + if (!ul) + return NULL; + + /* Browse the userlist for searching user. */ + for (u = ul->users; u; u = u->next) { + if (strcmp(smp->data.u.str.area, u->user) == 0) + break; + } + if (!u) + return NULL; + + /* Browse each pattern. */ + list_for_each_entry(lst, &expr->patterns, list) { + pattern = &lst->pat; + + /* Browse each group for searching group name that match the pattern. */ + for (agl = u->u.groups; agl; agl = agl->next) { + if (strcmp(agl->group->name, pattern->ptr.str) == 0) + return pattern; + } + } + return NULL; +} + +REGISTER_BUILD_OPTS("Encrypted password support via crypt(3): "CRYPT_STATE_MSG); diff --git a/src/backend.c b/src/backend.c new file mode 100644 index 0000000..39d2c75 --- /dev/null +++ b/src/backend.c @@ -0,0 +1,3401 @@ +/* + * Backend variables and functions. + * + * Copyright 2000-2013 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRACE_SOURCE &trace_strm + +int be_lastsession(const struct proxy *be) +{ + if (be->be_counters.last_sess) + return ns_to_sec(now_ns) - be->be_counters.last_sess; + + return -1; +} + +/* helper function to invoke the correct hash method */ +unsigned int gen_hash(const struct proxy* px, const char* key, unsigned long len) +{ + unsigned int hash; + + switch (px->lbprm.algo & BE_LB_HASH_FUNC) { + case BE_LB_HFCN_DJB2: + hash = hash_djb2(key, len); + break; + case BE_LB_HFCN_WT6: + hash = hash_wt6(key, len); + break; + case BE_LB_HFCN_CRC32: + hash = hash_crc32(key, len); + break; + case BE_LB_HFCN_NONE: + /* use key as a hash */ + { + const char *_key = key; + + hash = read_int64(&_key, _key + len); + } + break; + case BE_LB_HFCN_SDBM: + /* this is the default hash function */ + default: + hash = hash_sdbm(key, len); + break; + } + + if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL) + hash = full_hash(hash); + + return hash; +} + +/* + * This function recounts the number of usable active and backup servers for + * proxy

. These numbers are returned into the p->srv_act and p->srv_bck. + * This function also recomputes the total active and backup weights. However, + * it does not update tot_weight nor tot_used. Use update_backend_weight() for + * this. + * This functions is designed to be called before server's weight and state + * commit so it uses 'next' weight and states values. + * + * threads: this is the caller responsibility to lock data. For now, this + * function is called from lb modules, so it should be ok. But if you need to + * call it from another place, be careful (and update this comment). + */ +void recount_servers(struct proxy *px) +{ + struct server *srv; + + px->srv_act = px->srv_bck = 0; + px->lbprm.tot_wact = px->lbprm.tot_wbck = 0; + px->lbprm.fbck = NULL; + for (srv = px->srv; srv != NULL; srv = srv->next) { + if (!srv_willbe_usable(srv)) + continue; + + if (srv->flags & SRV_F_BACKUP) { + if (!px->srv_bck && + !(px->options & PR_O_USE_ALL_BK)) + px->lbprm.fbck = srv; + px->srv_bck++; + srv->cumulative_weight = px->lbprm.tot_wbck; + px->lbprm.tot_wbck += srv->next_eweight; + } else { + px->srv_act++; + srv->cumulative_weight = px->lbprm.tot_wact; + px->lbprm.tot_wact += srv->next_eweight; + } + } +} + +/* This function simply updates the backend's tot_weight and tot_used values + * after servers weights have been updated. It is designed to be used after + * recount_servers() or equivalent. + * + * threads: this is the caller responsibility to lock data. For now, this + * function is called from lb modules, so it should be ok. But if you need to + * call it from another place, be careful (and update this comment). + */ +void update_backend_weight(struct proxy *px) +{ + if (px->srv_act) { + px->lbprm.tot_weight = px->lbprm.tot_wact; + px->lbprm.tot_used = px->srv_act; + } + else if (px->lbprm.fbck) { + /* use only the first backup server */ + px->lbprm.tot_weight = px->lbprm.fbck->next_eweight; + px->lbprm.tot_used = 1; + } + else { + px->lbprm.tot_weight = px->lbprm.tot_wbck; + px->lbprm.tot_used = px->srv_bck; + } +} + +/* + * This function tries to find a running server for the proxy following + * the source hash method. Depending on the number of active/backup servers, + * it will either look for active servers, or for backup servers. + * If any server is found, it will be returned. If no valid server is found, + * NULL is returned. + */ +static struct server *get_server_sh(struct proxy *px, const char *addr, int len, const struct server *avoid) +{ + unsigned int h, l; + + if (px->lbprm.tot_weight == 0) + return NULL; + + l = h = 0; + + /* note: we won't hash if there's only one server left */ + if (px->lbprm.tot_used == 1) + goto hash_done; + + while ((l + sizeof (int)) <= len) { + h ^= ntohl(*(unsigned int *)(&addr[l])); + l += sizeof (int); + } + /* FIXME: why don't we use gen_hash() here as well? + * -> we don't take into account hash function from "hash_type" + * options here.. + */ + if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL) + h = full_hash(h); + hash_done: + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, h, avoid); + else + return map_get_server_hash(px, h); +} + +/* + * This function tries to find a running server for the proxy following + * the URI hash method. In order to optimize cache hits, the hash computation + * ends at the question mark. Depending on the number of active/backup servers, + * it will either look for active servers, or for backup servers. + * If any server is found, it will be returned. If no valid server is found, + * NULL is returned. The lbprm.arg_opt{1,2,3} values correspond respectively to + * the "whole" optional argument (boolean, bit0), the "len" argument (numeric) + * and the "depth" argument (numeric). + * + * This code was contributed by Guillaume Dallaire, who also selected this hash + * algorithm out of a tens because it gave him the best results. + * + */ +static struct server *get_server_uh(struct proxy *px, char *uri, int uri_len, const struct server *avoid) +{ + unsigned int hash = 0; + int c; + int slashes = 0; + const char *start, *end; + + if (px->lbprm.tot_weight == 0) + return NULL; + + /* note: we won't hash if there's only one server left */ + if (px->lbprm.tot_used == 1) + goto hash_done; + + if (px->lbprm.arg_opt2) // "len" + uri_len = MIN(uri_len, px->lbprm.arg_opt2); + + start = end = uri; + while (uri_len--) { + c = *end; + if (c == '/') { + slashes++; + if (slashes == px->lbprm.arg_opt3) /* depth+1 */ + break; + } + else if (c == '?' && !(px->lbprm.arg_opt1 & 1)) // "whole" + break; + end++; + } + + hash = gen_hash(px, start, (end - start)); + + hash_done: + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, hash, avoid); + else + return map_get_server_hash(px, hash); +} + +/* + * This function tries to find a running server for the proxy following + * the URL parameter hash method. It looks for a specific parameter in the + * URL and hashes it to compute the server ID. This is useful to optimize + * performance by avoiding bounces between servers in contexts where sessions + * are shared but cookies are not usable. If the parameter is not found, NULL + * is returned. If any server is found, it will be returned. If no valid server + * is found, NULL is returned. + */ +static struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len, const struct server *avoid) +{ + unsigned int hash = 0; + const char *start, *end; + const char *p; + const char *params; + int plen; + + /* when tot_weight is 0 then so is srv_count */ + if (px->lbprm.tot_weight == 0) + return NULL; + + if ((p = memchr(uri, '?', uri_len)) == NULL) + return NULL; + + p++; + + uri_len -= (p - uri); + plen = px->lbprm.arg_len; + params = p; + + while (uri_len > plen) { + /* Look for the parameter name followed by an equal symbol */ + if (params[plen] == '=') { + if (memcmp(params, px->lbprm.arg_str, plen) == 0) { + /* OK, we have the parameter here at , and + * the value after the equal sign, at

+ * skip the equal symbol + */ + p += plen + 1; + start = end = p; + uri_len -= plen + 1; + + while (uri_len && *end != '&') { + uri_len--; + end++; + } + hash = gen_hash(px, start, (end - start)); + + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, hash, avoid); + else + return map_get_server_hash(px, hash); + } + } + /* skip to next parameter */ + p = memchr(params, '&', uri_len); + if (!p) + return NULL; + p++; + uri_len -= (p - params); + params = p; + } + return NULL; +} + +/* + * this does the same as the previous server_ph, but check the body contents + */ +static struct server *get_server_ph_post(struct stream *s, const struct server *avoid) +{ + unsigned int hash = 0; + struct channel *req = &s->req; + struct proxy *px = s->be; + struct htx *htx = htxbuf(&req->buf); + struct htx_blk *blk; + unsigned int plen = px->lbprm.arg_len; + unsigned long len; + const char *params, *p, *start, *end; + + if (px->lbprm.tot_weight == 0) + return NULL; + + p = params = NULL; + len = 0; + for (blk = htx_get_first_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) { + enum htx_blk_type type = htx_get_blk_type(blk); + struct ist v; + + if (type != HTX_BLK_DATA) + continue; + v = htx_get_blk_value(htx, blk); + p = params = v.ptr; + len = v.len; + break; + } + + while (len > plen) { + /* Look for the parameter name followed by an equal symbol */ + if (params[plen] == '=') { + if (memcmp(params, px->lbprm.arg_str, plen) == 0) { + /* OK, we have the parameter here at , and + * the value after the equal sign, at

+ * skip the equal symbol + */ + p += plen + 1; + start = end = p; + len -= plen + 1; + + while (len && *end != '&') { + if (unlikely(!HTTP_IS_TOKEN(*p))) { + /* if in a POST, body must be URI encoded or it's not a URI. + * Do not interpret any possible binary data as a parameter. + */ + if (likely(HTTP_IS_LWS(*p))) /* eol, uncertain uri len */ + break; + return NULL; /* oh, no; this is not uri-encoded. + * This body does not contain parameters. + */ + } + len--; + end++; + /* should we break if vlen exceeds limit? */ + } + hash = gen_hash(px, start, (end - start)); + + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, hash, avoid); + else + return map_get_server_hash(px, hash); + } + } + /* skip to next parameter */ + p = memchr(params, '&', len); + if (!p) + return NULL; + p++; + len -= (p - params); + params = p; + } + return NULL; +} + + +/* + * This function tries to find a running server for the proxy following + * the Header parameter hash method. It looks for a specific parameter in the + * URL and hashes it to compute the server ID. This is useful to optimize + * performance by avoiding bounces between servers in contexts where sessions + * are shared but cookies are not usable. If the parameter is not found, NULL + * is returned. If any server is found, it will be returned. If no valid server + * is found, NULL is returned. When lbprm.arg_opt1 is set, the hash will only + * apply to the middle part of a domain name ("use_domain_only" option). + */ +static struct server *get_server_hh(struct stream *s, const struct server *avoid) +{ + unsigned int hash = 0; + struct proxy *px = s->be; + unsigned int plen = px->lbprm.arg_len; + unsigned long len; + const char *p; + const char *start, *end; + struct htx *htx = htxbuf(&s->req.buf); + struct http_hdr_ctx ctx = { .blk = NULL }; + + /* tot_weight appears to mean srv_count */ + if (px->lbprm.tot_weight == 0) + return NULL; + + /* note: we won't hash if there's only one server left */ + if (px->lbprm.tot_used == 1) + goto hash_done; + + http_find_header(htx, ist2(px->lbprm.arg_str, plen), &ctx, 0); + + /* if the header is not found or empty, let's fallback to round robin */ + if (!ctx.blk || !ctx.value.len) + return NULL; + + /* Found a the param_name in the headers. + * we will compute the hash based on this value ctx.val. + */ + len = ctx.value.len; + p = ctx.value.ptr; + + if (!px->lbprm.arg_opt1) { + hash = gen_hash(px, p, len); + } else { + int dohash = 0; + p += len; + /* special computation, use only main domain name, not tld/host + * going back from the end of string, start hashing at first + * dot stop at next. + * This is designed to work with the 'Host' header, and requires + * a special option to activate this. + */ + end = p; + while (len) { + if (dohash) { + /* Rewind the pointer until the previous char + * is a dot, this will allow to set the start + * position of the domain. */ + if (*(p - 1) == '.') + break; + } + else if (*p == '.') { + /* The pointer is rewinded to the dot before the + * tld, we memorize the end of the domain and + * can enter the domain processing. */ + end = p; + dohash = 1; + } + p--; + len--; + } + start = p; + hash = gen_hash(px, start, (end - start)); + } + + hash_done: + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, hash, avoid); + else + return map_get_server_hash(px, hash); +} + +/* RDP Cookie HASH. */ +static struct server *get_server_rch(struct stream *s, const struct server *avoid) +{ + unsigned int hash = 0; + struct proxy *px = s->be; + unsigned long len; + int ret; + struct sample smp; + int rewind; + + /* tot_weight appears to mean srv_count */ + if (px->lbprm.tot_weight == 0) + return NULL; + + memset(&smp, 0, sizeof(smp)); + + rewind = co_data(&s->req); + c_rew(&s->req, rewind); + + ret = fetch_rdp_cookie_name(s, &smp, px->lbprm.arg_str, px->lbprm.arg_len); + len = smp.data.u.str.data; + + c_adv(&s->req, rewind); + + if (ret == 0 || (smp.flags & SMP_F_MAY_CHANGE) || len == 0) + return NULL; + + /* note: we won't hash if there's only one server left */ + if (px->lbprm.tot_used == 1) + goto hash_done; + + /* Found the param_name in the headers. + * we will compute the hash based on this value ctx.val. + */ + hash = gen_hash(px, smp.data.u.str.area, len); + + hash_done: + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, hash, avoid); + else + return map_get_server_hash(px, hash); +} + +/* sample expression HASH. Returns NULL if the sample is not found or if there + * are no server, relying on the caller to fall back to round robin instead. + */ +static struct server *get_server_expr(struct stream *s, const struct server *avoid) +{ + struct proxy *px = s->be; + struct sample *smp; + unsigned int hash = 0; + + if (px->lbprm.tot_weight == 0) + return NULL; + + /* note: no need to hash if there's only one server left */ + if (px->lbprm.tot_used == 1) + goto hash_done; + + smp = sample_fetch_as_type(px, s->sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, px->lbprm.expr, SMP_T_BIN); + if (!smp) + return NULL; + + /* We have the desired data. Let's hash it according to the configured + * options and algorithm. + */ + hash = gen_hash(px, smp->data.u.str.area, smp->data.u.str.data); + + hash_done: + if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + return chash_get_server_hash(px, hash, avoid); + else + return map_get_server_hash(px, hash); +} + +/* random value */ +static struct server *get_server_rnd(struct stream *s, const struct server *avoid) +{ + unsigned int hash = 0; + struct proxy *px = s->be; + struct server *prev, *curr; + int draws = px->lbprm.arg_opt1; // number of draws + + /* tot_weight appears to mean srv_count */ + if (px->lbprm.tot_weight == 0) + return NULL; + + curr = NULL; + do { + prev = curr; + hash = statistical_prng(); + curr = chash_get_server_hash(px, hash, avoid); + if (!curr) + break; + + /* compare the new server to the previous best choice and pick + * the one with the least currently served requests. + */ + if (prev && prev != curr && + curr->served * prev->cur_eweight > prev->served * curr->cur_eweight) + curr = prev; + } while (--draws > 0); + + /* if the selected server is full, pretend we have none so that we reach + * the backend's queue instead. + */ + if (curr && + (curr->queue.length || (curr->maxconn && curr->served >= srv_dynamic_maxconn(curr)))) + curr = NULL; + + return curr; +} + +/* + * This function applies the load-balancing algorithm to the stream, as + * defined by the backend it is assigned to. The stream is then marked as + * 'assigned'. + * + * This function MAY NOT be called with SF_ASSIGNED already set. If the stream + * had a server previously assigned, it is rebalanced, trying to avoid the same + * server, which should still be present in target_srv(&s->target) before the call. + * The function tries to keep the original connection slot if it reconnects to + * the same server, otherwise it releases it and tries to offer it. + * + * It is illegal to call this function with a stream in a queue. + * + * It may return : + * SRV_STATUS_OK if everything is OK. ->srv and ->target are assigned. + * SRV_STATUS_NOSRV if no server is available. Stream is not ASSIGNED + * SRV_STATUS_FULL if all servers are saturated. Stream is not ASSIGNED + * SRV_STATUS_INTERNAL for other unrecoverable errors. + * + * Upon successful return, the stream flag SF_ASSIGNED is set to indicate that + * it does not need to be called anymore. This means that target_srv(&s->target) + * can be trusted in balance and direct modes. + * + */ + +int assign_server(struct stream *s) +{ + struct connection *conn = NULL; + struct server *conn_slot; + struct server *srv = NULL, *prev_srv; + int err; + + err = SRV_STATUS_INTERNAL; + if (unlikely(s->pend_pos || s->flags & SF_ASSIGNED)) + goto out_err; + + prev_srv = objt_server(s->target); + conn_slot = s->srv_conn; + + /* We have to release any connection slot before applying any LB algo, + * otherwise we may erroneously end up with no available slot. + */ + if (conn_slot) + sess_change_server(s, NULL); + + /* We will now try to find the good server and store it into target)>. + * Note that target)> may be NULL in case of dispatch or proxy mode, + * as well as if no server is available (check error code). + */ + + srv = NULL; + s->target = NULL; + + if ((s->be->lbprm.algo & BE_LB_KIND) != BE_LB_KIND_HI && + ((s->sess->flags & SESS_FL_PREFER_LAST) || + (s->be->options & PR_O_PREF_LAST))) { + struct sess_srv_list *srv_list; + list_for_each_entry(srv_list, &s->sess->srv_list, srv_list) { + struct server *tmpsrv = objt_server(srv_list->target); + + if (tmpsrv && tmpsrv->proxy == s->be && + ((s->sess->flags & SESS_FL_PREFER_LAST) || + (!s->be->max_ka_queue || + server_has_room(tmpsrv) || ( + tmpsrv->queue.length + 1 < s->be->max_ka_queue))) && + srv_currently_usable(tmpsrv)) { + list_for_each_entry(conn, &srv_list->conn_list, session_list) { + if (!(conn->flags & CO_FL_WAIT_XPRT)) { + srv = tmpsrv; + s->target = &srv->obj_type; + if (conn->flags & CO_FL_SESS_IDLE) { + conn->flags &= ~CO_FL_SESS_IDLE; + s->sess->idle_conns--; + } + goto out_ok; + } + } + } + } + } + + if (s->be->lbprm.algo & BE_LB_KIND) { + /* we must check if we have at least one server available */ + if (!s->be->lbprm.tot_weight) { + err = SRV_STATUS_NOSRV; + goto out; + } + + /* if there's some queue on the backend, with certain algos we + * know it's because all servers are full. + */ + if (s->be->queue.length && s->be->queue.length != s->be->beconn && + (((s->be->lbprm.algo & (BE_LB_KIND|BE_LB_NEED|BE_LB_PARM)) == BE_LB_ALGO_FAS)|| // first + ((s->be->lbprm.algo & (BE_LB_KIND|BE_LB_NEED|BE_LB_PARM)) == BE_LB_ALGO_RR) || // roundrobin + ((s->be->lbprm.algo & (BE_LB_KIND|BE_LB_NEED|BE_LB_PARM)) == BE_LB_ALGO_SRR))) { // static-rr + err = SRV_STATUS_FULL; + goto out; + } + + /* First check whether we need to fetch some data or simply call + * the LB lookup function. Only the hashing functions will need + * some input data in fact, and will support multiple algorithms. + */ + switch (s->be->lbprm.algo & BE_LB_LKUP) { + case BE_LB_LKUP_RRTREE: + srv = fwrr_get_next_server(s->be, prev_srv); + break; + + case BE_LB_LKUP_FSTREE: + srv = fas_get_next_server(s->be, prev_srv); + break; + + case BE_LB_LKUP_LCTREE: + srv = fwlc_get_next_server(s->be, prev_srv); + break; + + case BE_LB_LKUP_CHTREE: + case BE_LB_LKUP_MAP: + if ((s->be->lbprm.algo & BE_LB_KIND) == BE_LB_KIND_RR) { + /* static-rr (map) or random (chash) */ + if ((s->be->lbprm.algo & BE_LB_PARM) == BE_LB_RR_RANDOM) + srv = get_server_rnd(s, prev_srv); + else + srv = map_get_server_rr(s->be, prev_srv); + break; + } + else if ((s->be->lbprm.algo & BE_LB_KIND) != BE_LB_KIND_HI) { + /* unknown balancing algorithm */ + err = SRV_STATUS_INTERNAL; + goto out; + } + + switch (s->be->lbprm.algo & BE_LB_PARM) { + const struct sockaddr_storage *src; + + case BE_LB_HASH_SRC: + src = sc_src(s->scf); + if (src && src->ss_family == AF_INET) { + srv = get_server_sh(s->be, + (void *)&((struct sockaddr_in *)src)->sin_addr, + 4, prev_srv); + } + else if (src && src->ss_family == AF_INET6) { + srv = get_server_sh(s->be, + (void *)&((struct sockaddr_in6 *)src)->sin6_addr, + 16, prev_srv); + } + break; + + case BE_LB_HASH_URI: + /* URI hashing */ + if (IS_HTX_STRM(s) && s->txn->req.msg_state >= HTTP_MSG_BODY) { + struct ist uri; + + uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf))); + if (s->be->lbprm.arg_opt1 & 2) { + struct http_uri_parser parser = + http_uri_parser_init(uri); + + uri = http_parse_path(&parser); + if (!isttest(uri)) + uri = ist(""); + } + srv = get_server_uh(s->be, uri.ptr, uri.len, prev_srv); + } + break; + + case BE_LB_HASH_PRM: + /* URL Parameter hashing */ + if (IS_HTX_STRM(s) && s->txn->req.msg_state >= HTTP_MSG_BODY) { + struct ist uri; + + uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf))); + srv = get_server_ph(s->be, uri.ptr, uri.len, prev_srv); + + if (!srv && s->txn->meth == HTTP_METH_POST) + srv = get_server_ph_post(s, prev_srv); + } + break; + + case BE_LB_HASH_HDR: + /* Header Parameter hashing */ + if (IS_HTX_STRM(s) && s->txn->req.msg_state >= HTTP_MSG_BODY) + srv = get_server_hh(s, prev_srv); + break; + + case BE_LB_HASH_RDP: + /* RDP Cookie hashing */ + srv = get_server_rch(s, prev_srv); + break; + + case BE_LB_HASH_SMP: + /* sample expression hashing */ + srv = get_server_expr(s, prev_srv); + break; + + default: + /* unknown balancing algorithm */ + err = SRV_STATUS_INTERNAL; + goto out; + } + + /* If the hashing parameter was not found, let's fall + * back to round robin on the map. + */ + if (!srv) { + if ((s->be->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE) + srv = chash_get_next_server(s->be, prev_srv); + else + srv = map_get_server_rr(s->be, prev_srv); + } + + /* end of map-based LB */ + break; + + default: + /* unknown balancing algorithm */ + err = SRV_STATUS_INTERNAL; + goto out; + } + + if (!srv) { + err = SRV_STATUS_FULL; + goto out; + } + else if (srv != prev_srv) { + _HA_ATOMIC_INC(&s->be->be_counters.cum_lbconn); + _HA_ATOMIC_INC(&srv->counters.cum_lbconn); + } + s->target = &srv->obj_type; + } + else if (s->be->options & (PR_O_DISPATCH | PR_O_TRANSP)) { + s->target = &s->be->obj_type; + } + else { + err = SRV_STATUS_NOSRV; + goto out; + } + +out_ok: + s->flags |= SF_ASSIGNED; + err = SRV_STATUS_OK; + out: + + /* Either we take back our connection slot, or we offer it to someone + * else if we don't need it anymore. + */ + if (conn_slot) { + if (conn_slot == srv) { + sess_change_server(s, srv); + } else { + if (may_dequeue_tasks(conn_slot, s->be)) + process_srv_queue(conn_slot); + } + } + + out_err: + return err; +} + +/* Allocate an address for the destination endpoint + * The address is taken from the currently assigned server, or from the + * dispatch or transparent address. + * + * Returns SRV_STATUS_OK on success. Does nothing if the address was + * already set. + * On error, no address is allocated and SRV_STATUS_INTERNAL is returned. + */ +static int alloc_dst_address(struct sockaddr_storage **ss, + struct server *srv, struct stream *s) +{ + const struct sockaddr_storage *dst; + + if (*ss) + return SRV_STATUS_OK; + + if ((s->flags & SF_DIRECT) || (s->be->lbprm.algo & BE_LB_KIND)) { + /* A server is necessarily known for this stream */ + if (!(s->flags & SF_ASSIGNED)) + return SRV_STATUS_INTERNAL; + + if (!sockaddr_alloc(ss, NULL, 0)) + return SRV_STATUS_INTERNAL; + + **ss = srv->addr; + set_host_port(*ss, srv->svc_port); + if (!is_addr(*ss)) { + /* if the server has no address, we use the same address + * the client asked, which is handy for remapping ports + * locally on multiple addresses at once. Nothing is done + * for AF_UNIX addresses. + */ + dst = sc_dst(s->scf); + if (dst && dst->ss_family == AF_INET) { + ((struct sockaddr_in *)*ss)->sin_family = AF_INET; + ((struct sockaddr_in *)*ss)->sin_addr = + ((struct sockaddr_in *)dst)->sin_addr; + } else if (dst && dst->ss_family == AF_INET6) { + ((struct sockaddr_in6 *)*ss)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)*ss)->sin6_addr = + ((struct sockaddr_in6 *)dst)->sin6_addr; + } + } + + /* if this server remaps proxied ports, we'll use + * the port the client connected to with an offset. */ + if ((srv->flags & SRV_F_MAPPORTS)) { + int base_port; + + dst = sc_dst(s->scf); + if (dst) { + /* First, retrieve the port from the incoming connection */ + base_port = get_host_port(dst); + + /* Second, assign the outgoing connection's port */ + base_port += get_host_port(*ss); + set_host_port(*ss, base_port); + } + } + } + else if (s->be->options & PR_O_DISPATCH) { + if (!sockaddr_alloc(ss, NULL, 0)) + return SRV_STATUS_INTERNAL; + + /* connect to the defined dispatch addr */ + **ss = s->be->dispatch_addr; + } + else if ((s->be->options & PR_O_TRANSP)) { + if (!sockaddr_alloc(ss, NULL, 0)) + return SRV_STATUS_INTERNAL; + + /* in transparent mode, use the original dest addr if no dispatch specified */ + dst = sc_dst(s->scf); + if (dst && (dst->ss_family == AF_INET || dst->ss_family == AF_INET6)) + **ss = *dst; + } + else { + /* no server and no LB algorithm ! */ + return SRV_STATUS_INTERNAL; + } + + return SRV_STATUS_OK; +} + +/* This function assigns a server to stream if required, and can add the + * connection to either the assigned server's queue or to the proxy's queue. + * If ->srv_conn is set, the stream is first released from the server. + * It may also be called with SF_DIRECT and/or SF_ASSIGNED though. It will + * be called before any connection and after any retry or redispatch occurs. + * + * It is not allowed to call this function with a stream in a queue. + * + * Returns : + * + * SRV_STATUS_OK if everything is OK. + * SRV_STATUS_NOSRV if no server is available. objt_server(s->target) = NULL. + * SRV_STATUS_QUEUED if the connection has been queued. + * SRV_STATUS_FULL if the server(s) is/are saturated and the + * connection could not be queued at the server's, + * which may be NULL if we queue on the backend. + * SRV_STATUS_INTERNAL for other unrecoverable errors. + * + */ +int assign_server_and_queue(struct stream *s) +{ + struct pendconn *p; + struct server *srv; + int err; + + if (s->pend_pos) + return SRV_STATUS_INTERNAL; + + err = SRV_STATUS_OK; + if (!(s->flags & SF_ASSIGNED)) { + struct server *prev_srv = objt_server(s->target); + + err = assign_server(s); + if (prev_srv) { + /* This stream was previously assigned to a server. We have to + * update the stream's and the server's stats : + * - if the server changed : + * - set TX_CK_DOWN if txn.flags was TX_CK_VALID + * - set SF_REDISP if it was successfully redispatched + * - increment srv->redispatches and be->redispatches + * - if the server remained the same : update retries. + */ + + if (prev_srv != objt_server(s->target)) { + if (s->txn && (s->txn->flags & TX_CK_MASK) == TX_CK_VALID) { + s->txn->flags &= ~TX_CK_MASK; + s->txn->flags |= TX_CK_DOWN; + } + s->flags |= SF_REDISP; + _HA_ATOMIC_INC(&prev_srv->counters.redispatches); + _HA_ATOMIC_INC(&s->be->be_counters.redispatches); + } else { + _HA_ATOMIC_INC(&prev_srv->counters.retries); + _HA_ATOMIC_INC(&s->be->be_counters.retries); + } + } + } + + switch (err) { + case SRV_STATUS_OK: + /* we have SF_ASSIGNED set */ + srv = objt_server(s->target); + if (!srv) + return SRV_STATUS_OK; /* dispatch or proxy mode */ + + /* If we already have a connection slot, no need to check any queue */ + if (s->srv_conn == srv) + return SRV_STATUS_OK; + + /* OK, this stream already has an assigned server, but no + * connection slot yet. Either it is a redispatch, or it was + * assigned from persistence information (direct mode). + */ + if ((s->flags & SF_REDIRECTABLE) && srv->rdr_len) { + /* server scheduled for redirection, and already assigned. We + * don't want to go further nor check the queue. + */ + sess_change_server(s, srv); /* not really needed in fact */ + return SRV_STATUS_OK; + } + + /* We might have to queue this stream if the assigned server is full. + * We know we have to queue it into the server's queue, so if a maxqueue + * is set on the server, we must also check that the server's queue is + * not full, in which case we have to return FULL. + */ + if (srv->maxconn && + (srv->queue.length || srv->served >= srv_dynamic_maxconn(srv))) { + + if (srv->maxqueue > 0 && srv->queue.length >= srv->maxqueue) + return SRV_STATUS_FULL; + + p = pendconn_add(s); + if (p) + return SRV_STATUS_QUEUED; + else + return SRV_STATUS_INTERNAL; + } + + /* OK, we can use this server. Let's reserve our place */ + sess_change_server(s, srv); + return SRV_STATUS_OK; + + case SRV_STATUS_FULL: + /* queue this stream into the proxy's queue */ + p = pendconn_add(s); + if (p) + return SRV_STATUS_QUEUED; + else + return SRV_STATUS_INTERNAL; + + case SRV_STATUS_NOSRV: + return err; + + case SRV_STATUS_INTERNAL: + return err; + + default: + return SRV_STATUS_INTERNAL; + } +} + +/* Allocate an address if an explicit source address must be used for a backend + * connection. + * + * Two parameters are taken into account to check if specific source address is + * configured. The first one is which is the server instance to connect + * to. It may be NULL when dispatching is used. The second one is the + * backend instance which contains the target server or dispatch. + * + * A stream instance can be used to set the stream owner of the backend + * connection. It is a required parameter if the source address is a dynamic + * parameter. + * + * Returns SRV_STATUS_OK if either no specific source address specified or its + * allocation is done correctly. On error returns SRV_STATUS_INTERNAL. + */ +int alloc_bind_address(struct sockaddr_storage **ss, + struct server *srv, struct proxy *be, + struct stream *s) +{ +#if defined(CONFIG_HAP_TRANSPARENT) + const struct sockaddr_storage *addr; + struct conn_src *src = NULL; + struct sockaddr_in *sin; + char *vptr; + size_t vlen; +#endif + + /* Ensure the function will not overwrite an allocated address. */ + BUG_ON(*ss); + +#if defined(CONFIG_HAP_TRANSPARENT) + if (srv && srv->conn_src.opts & CO_SRC_BIND) + src = &srv->conn_src; + else if (be->conn_src.opts & CO_SRC_BIND) + src = &be->conn_src; + + /* no transparent mode, no need to allocate an address, returns OK */ + if (!src) + return SRV_STATUS_OK; + + switch (src->opts & CO_SRC_TPROXY_MASK) { + case CO_SRC_TPROXY_ADDR: + if (!sockaddr_alloc(ss, NULL, 0)) + return SRV_STATUS_INTERNAL; + + **ss = src->tproxy_addr; + break; + + case CO_SRC_TPROXY_CLI: + case CO_SRC_TPROXY_CIP: + BUG_ON(!s); /* Dynamic source setting requires a stream instance. */ + + /* FIXME: what can we do if the client connects in IPv6 or unix socket ? */ + addr = sc_src(s->scf); + if (!addr) + return SRV_STATUS_INTERNAL; + + if (!sockaddr_alloc(ss, NULL, 0)) + return SRV_STATUS_INTERNAL; + + **ss = *addr; + break; + + case CO_SRC_TPROXY_DYN: + BUG_ON(!s); /* Dynamic source setting requires a stream instance. */ + + if (!src->bind_hdr_occ || !IS_HTX_STRM(s)) + return SRV_STATUS_INTERNAL; + + if (!sockaddr_alloc(ss, NULL, 0)) + return SRV_STATUS_INTERNAL; + + /* bind to the IP in a header */ + sin = (struct sockaddr_in *)*ss; + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = 0; + if (!http_get_htx_hdr(htxbuf(&s->req.buf), + ist2(src->bind_hdr_name, src->bind_hdr_len), + src->bind_hdr_occ, NULL, &vptr, &vlen)) { + sockaddr_free(ss); + return SRV_STATUS_INTERNAL; + } + + sin->sin_addr.s_addr = htonl(inetaddr_host_lim(vptr, vptr + vlen)); + break; + + default: + ; + } +#endif + + return SRV_STATUS_OK; +} + +/* Attempt to get a backend connection from the specified mt_list array + * (safe or idle connections). The argument means what type of + * connection the caller wants. + */ +struct connection *conn_backend_get(struct stream *s, struct server *srv, int is_safe, int64_t hash) +{ + struct connection *conn = NULL; + int i; // thread number + int found = 0; + int stop; + + /* We need to lock even if this is our own list, because another + * thread may be trying to migrate that connection, and we don't want + * to end up with two threads using the same connection. + */ + i = tid; + HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + conn = srv_lookup_conn(is_safe ? &srv->per_thr[tid].safe_conns : &srv->per_thr[tid].idle_conns, hash); + if (conn) + conn_delete_from_tree(conn); + + /* If we failed to pick a connection from the idle list, let's try again with + * the safe list. + */ + if (!conn && !is_safe && srv->curr_safe_nb > 0) { + conn = srv_lookup_conn(&srv->per_thr[tid].safe_conns, hash); + if (conn) { + conn_delete_from_tree(conn); + is_safe = 1; + } + } + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + + /* If we found a connection in our own list, and we don't have to + * steal one from another thread, then we're done. + */ + if (conn) + goto done; + + /* pool sharing globally disabled ? */ + if (!(global.tune.options & GTUNE_IDLE_POOL_SHARED)) + goto done; + + /* Are we allowed to pick from another thread ? We'll still try + * it if we're running low on FDs as we don't want to create + * extra conns in this case, otherwise we can give up if we have + * too few idle conns and the server protocol supports establishing + * connections (i.e. not a reverse-http server for example). + */ + if (srv->curr_idle_conns < srv->low_idle_conns && + ha_used_fds < global.tune.pool_low_count) { + const struct protocol *srv_proto = protocol_lookup(srv->addr.ss_family, PROTO_TYPE_STREAM, 0); + + if (srv_proto && srv_proto->connect) + goto done; + } + + /* Lookup all other threads for an idle connection, starting from last + * unvisited thread, but always staying in the same group. + */ + stop = srv->per_tgrp[tgid - 1].next_takeover; + if (stop >= tg->count) + stop %= tg->count; + + stop += tg->base; + i = stop; + do { + if (!srv->curr_idle_thr[i] || i == tid) + continue; + + if (HA_SPIN_TRYLOCK(IDLE_CONNS_LOCK, &idle_conns[i].idle_conns_lock) != 0) + continue; + conn = srv_lookup_conn(is_safe ? &srv->per_thr[i].safe_conns : &srv->per_thr[i].idle_conns, hash); + while (conn) { + if (conn->mux->takeover && conn->mux->takeover(conn, i) == 0) { + conn_delete_from_tree(conn); + _HA_ATOMIC_INC(&activity[tid].fd_takeover); + found = 1; + break; + } + + conn = srv_lookup_conn_next(conn); + } + + if (!found && !is_safe && srv->curr_safe_nb > 0) { + conn = srv_lookup_conn(&srv->per_thr[i].safe_conns, hash); + while (conn) { + if (conn->mux->takeover && conn->mux->takeover(conn, i) == 0) { + conn_delete_from_tree(conn); + _HA_ATOMIC_INC(&activity[tid].fd_takeover); + found = 1; + is_safe = 1; + break; + } + + conn = srv_lookup_conn_next(conn); + } + } + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[i].idle_conns_lock); + } while (!found && (i = (i + 1 == tg->base + tg->count) ? tg->base : i + 1) != stop); + + if (!found) + conn = NULL; + done: + if (conn) { + _HA_ATOMIC_STORE(&srv->per_tgrp[tgid - 1].next_takeover, (i + 1 == tg->base + tg->count) ? tg->base : i + 1); + + srv_use_conn(srv, conn); + + _HA_ATOMIC_DEC(&srv->curr_idle_conns); + _HA_ATOMIC_DEC(conn->flags & CO_FL_SAFE_LIST ? &srv->curr_safe_nb : &srv->curr_idle_nb); + _HA_ATOMIC_DEC(&srv->curr_idle_thr[i]); + conn->flags &= ~CO_FL_LIST_MASK; + __ha_barrier_atomic_store(); + + if ((s->be->options & PR_O_REUSE_MASK) == PR_O_REUSE_SAFE && + conn->mux->flags & MX_FL_HOL_RISK) { + /* attach the connection to the session private list + */ + conn->owner = s->sess; + session_add_conn(s->sess, conn, conn->target); + } + else { + srv_add_to_avail_list(srv, conn); + } + } + return conn; +} + +static int do_connect_server(struct stream *s, struct connection *conn) +{ + int ret = SF_ERR_NONE; + int conn_flags = 0; + + if (unlikely(!conn || !conn->ctrl || !conn->ctrl->connect)) + return SF_ERR_INTERNAL; + + if (co_data(&s->res)) + conn_flags |= CONNECT_HAS_DATA; + if (s->conn_retries == s->be->conn_retries) + conn_flags |= CONNECT_CAN_USE_TFO; + if (!conn_ctrl_ready(conn) || !conn_xprt_ready(conn)) { + ret = conn->ctrl->connect(conn, conn_flags); + if (ret != SF_ERR_NONE) + return ret; + + /* we're in the process of establishing a connection */ + s->scb->state = SC_ST_CON; + } + else { + /* try to reuse the existing connection, it will be + * confirmed once we can send on it. + */ + /* Is the connection really ready ? */ + if (conn->mux->ctl(conn, MUX_CTL_STATUS, NULL) & MUX_STATUS_READY) + s->scb->state = SC_ST_RDY; + else + s->scb->state = SC_ST_CON; + } + + /* needs src ip/port for logging */ + if (s->flags & SF_SRC_ADDR) + conn_get_src(conn); + + return ret; +} + +/* + * This function initiates a connection to the server assigned to this stream + * (s->target, (s->scb)->addr.to). It will assign a server if none + * is assigned yet. + * It can return one of : + * - SF_ERR_NONE if everything's OK + * - SF_ERR_SRVTO if there are no more servers + * - SF_ERR_SRVCL if the connection was refused by the server + * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn) + * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...) + * - SF_ERR_INTERNAL for any other purely internal errors + * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted. + * The server-facing stream connector is expected to hold a pre-allocated connection. + */ +int connect_server(struct stream *s) +{ + struct connection *cli_conn = objt_conn(strm_orig(s)); + struct connection *srv_conn = NULL; + struct server *srv; + int reuse_mode = s->be->options & PR_O_REUSE_MASK; + int reuse = 0; + int init_mux = 0; + int err; +#ifdef USE_OPENSSL + struct sample *sni_smp = NULL; +#endif + struct sockaddr_storage *bind_addr = NULL; + int proxy_line_ret; + int64_t hash = 0; + struct conn_hash_params hash_params; + + /* in standard configuration, srv will be valid + * it can be NULL for dispatch mode or transparent backend */ + srv = objt_server(s->target); + + /* Override reuse-mode if reverse-connect is used. */ + if (srv && srv->flags & SRV_F_RHTTP) + reuse_mode = PR_O_REUSE_ALWS; + + err = alloc_dst_address(&s->scb->dst, srv, s); + if (err != SRV_STATUS_OK) + return SF_ERR_INTERNAL; + + err = alloc_bind_address(&bind_addr, srv, s->be, s); + if (err != SRV_STATUS_OK) + return SF_ERR_INTERNAL; + +#ifdef USE_OPENSSL + if (srv && srv->ssl_ctx.sni) { + sni_smp = sample_fetch_as_type(s->be, s->sess, s, + SMP_OPT_DIR_REQ | SMP_OPT_FINAL, + srv->ssl_ctx.sni, SMP_T_STR); + } +#endif + + /* do not reuse if mode is not http */ + if (!IS_HTX_STRM(s)) { + DBG_TRACE_STATE("skip idle connections reuse: no htx", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto skip_reuse; + } + + /* disable reuse if websocket stream and the protocol to use is not the + * same as the main protocol of the server. + */ + if (unlikely(s->flags & SF_WEBSOCKET) && srv) { + if (!srv_check_reuse_ws(srv)) { + DBG_TRACE_STATE("skip idle connections reuse: websocket stream", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto skip_reuse; + } + } + + /* first, set unique connection parameters and then calculate hash */ + memset(&hash_params, 0, sizeof(hash_params)); + + /* 1. target */ + hash_params.target = s->target; + +#ifdef USE_OPENSSL + /* 2. sni + * only test if the sample is not null as smp_make_safe (called before + * ssl_sock_set_servername) can only fails if this is not the case + */ + if (sni_smp) { + hash_params.sni_prehash = + conn_hash_prehash(sni_smp->data.u.str.area, + sni_smp->data.u.str.data); + } +#endif /* USE_OPENSSL */ + + /* 3. destination address */ + if (srv && srv_is_transparent(srv)) + hash_params.dst_addr = s->scb->dst; + + /* 4. source address */ + hash_params.src_addr = bind_addr; + + /* 5. proxy protocol */ + if (srv && srv->pp_opts) { + proxy_line_ret = make_proxy_line(trash.area, trash.size, srv, cli_conn, s); + if (proxy_line_ret) { + hash_params.proxy_prehash = + conn_hash_prehash(trash.area, proxy_line_ret); + } + } + + hash = conn_calculate_hash(&hash_params); + + /* first, search for a matching connection in the session's idle conns */ + srv_conn = session_get_conn(s->sess, s->target, hash); + if (srv_conn) { + DBG_TRACE_STATE("reuse connection from session", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + reuse = 1; + } + + if (srv && !reuse && reuse_mode != PR_O_REUSE_NEVR) { + /* Below we pick connections from the safe, idle or + * available (which are safe too) lists based + * on the strategy, the fact that this is a first or second + * (retryable) request, with the indicated priority (1 or 2) : + * + * SAFE AGGR ALWS + * + * +-----+-----+ +-----+-----+ +-----+-----+ + * req| 1st | 2nd | req| 1st | 2nd | req| 1st | 2nd | + * ----+-----+-----+ ----+-----+-----+ ----+-----+-----+ + * safe| - | 2 | safe| 1 | 2 | safe| 1 | 2 | + * ----+-----+-----+ ----+-----+-----+ ----+-----+-----+ + * idle| - | 1 | idle| - | 1 | idle| 2 | 1 | + * ----+-----+-----+ ----+-----+-----+ ----+-----+-----+ + * + * Idle conns are necessarily looked up on the same thread so + * that there is no concurrency issues. + */ + if (!eb_is_empty(&srv->per_thr[tid].avail_conns)) { + srv_conn = srv_lookup_conn(&srv->per_thr[tid].avail_conns, hash); + if (srv_conn) { + /* connection cannot be in idle list if used as an avail idle conn. */ + BUG_ON(LIST_INLIST(&srv_conn->idle_list)); + + DBG_TRACE_STATE("reuse connection from avail", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + reuse = 1; + } + } + + /* if no available connections found, search for an idle/safe */ + if (!srv_conn && srv->max_idle_conns && srv->curr_idle_conns > 0) { + const int not_first_req = s->txn && s->txn->flags & TX_NOT_FIRST; + const int idle = srv->curr_idle_nb > 0; + const int safe = srv->curr_safe_nb > 0; + const int retry_safe = (s->be->retry_type & (PR_RE_CONN_FAILED | PR_RE_DISCONNECTED | PR_RE_TIMEOUT)) == + (PR_RE_CONN_FAILED | PR_RE_DISCONNECTED | PR_RE_TIMEOUT); + + /* second column of the tables above, + * search for an idle then safe conn */ + if (not_first_req || retry_safe) { + if (idle || safe) + srv_conn = conn_backend_get(s, srv, 0, hash); + } + /* first column of the tables above */ + else if (reuse_mode >= PR_O_REUSE_AGGR) { + /* search for a safe conn */ + if (safe) + srv_conn = conn_backend_get(s, srv, 1, hash); + + /* search for an idle conn if no safe conn found + * on always reuse mode */ + if (!srv_conn && + reuse_mode == PR_O_REUSE_ALWS && idle) { + /* TODO conn_backend_get should not check the + * safe list is this case */ + srv_conn = conn_backend_get(s, srv, 0, hash); + } + } + + if (srv_conn) { + DBG_TRACE_STATE("reuse connection from idle/safe", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + reuse = 1; + } + } + } + + + /* here reuse might have been set above, indicating srv_conn finally + * is OK. + */ + + if (ha_used_fds > global.tune.pool_high_count && srv) { + struct connection *tokill_conn = NULL; + /* We can't reuse a connection, and e have more FDs than deemd + * acceptable, attempt to kill an idling connection + */ + /* First, try from our own idle list */ + HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + if (!LIST_ISEMPTY(&srv->per_thr[tid].idle_conn_list)) { + tokill_conn = LIST_ELEM(srv->per_thr[tid].idle_conn_list.n, struct connection *, idle_list); + conn_delete_from_tree(tokill_conn); + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + + /* Release the idle lock before calling mux->destroy. + * It will in turn call srv_release_conn through + * conn_free which also uses it. + */ + tokill_conn->mux->destroy(tokill_conn->ctx); + } + else { + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + } + + /* If not, iterate over other thread's idling pool, and try to grab one */ + if (!tokill_conn) { + int i; + + for (i = tid; (i = ((i + 1 == global.nbthread) ? 0 : i + 1)) != tid;) { + // just silence stupid gcc which reports an absurd + // out-of-bounds warning for which is always + // exactly zero without threads, but it seems to + // see it possibly larger. + ALREADY_CHECKED(i); + + if (HA_SPIN_TRYLOCK(IDLE_CONNS_LOCK, &idle_conns[i].idle_conns_lock) != 0) + continue; + + if (!LIST_ISEMPTY(&srv->per_thr[i].idle_conn_list)) { + tokill_conn = LIST_ELEM(srv->per_thr[i].idle_conn_list.n, struct connection *, idle_list); + conn_delete_from_tree(tokill_conn); + } + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[i].idle_conns_lock); + + if (tokill_conn) { + /* We got one, put it into the concerned thread's to kill list, and wake it's kill task */ + + MT_LIST_APPEND(&idle_conns[i].toremove_conns, + &tokill_conn->toremove_list); + task_wakeup(idle_conns[i].cleanup_task, TASK_WOKEN_OTHER); + break; + } + } + } + + } + + if (reuse) { + if (srv_conn->mux) { + int avail = srv_conn->mux->avail_streams(srv_conn); + + if (avail <= 1) { + /* No more streams available, remove it from the list */ + HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + conn_delete_from_tree(srv_conn); + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + } + + if (avail >= 1) { + if (srv_conn->mux->attach(srv_conn, s->scb->sedesc, s->sess) == -1) { + srv_conn = NULL; + if (sc_reset_endp(s->scb) < 0) + return SF_ERR_INTERNAL; + sc_ep_clr(s->scb, ~SE_FL_DETACHED); + } + } + else + srv_conn = NULL; + } + /* otherwise srv_conn is left intact */ + } + else + srv_conn = NULL; + +skip_reuse: + /* no reuse or failed to reuse the connection above, pick a new one */ + if (!srv_conn) { + if (srv && (srv->flags & SRV_F_RHTTP)) { + DBG_TRACE_USER("cannot open a new connection for reverse server", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + s->conn_err_type = STRM_ET_CONN_ERR; + return SF_ERR_INTERNAL; + } + + srv_conn = conn_new(s->target); + if (srv_conn) { + DBG_TRACE_STATE("alloc new be connection", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + srv_conn->owner = s->sess; + + /* connection will be attached to the session if + * http-reuse mode is never or it is not targeted to a + * server */ + if (reuse_mode == PR_O_REUSE_NEVR || !srv) + conn_set_private(srv_conn); + + /* assign bind_addr to srv_conn */ + srv_conn->src = bind_addr; + bind_addr = NULL; + + srv_conn->hash_node->node.key = hash; + } + } + + /* if bind_addr is non NULL free it */ + sockaddr_free(&bind_addr); + + /* srv_conn is still NULL only on allocation failure */ + if (!srv_conn) + return SF_ERR_RESOURCE; + + /* copy the target address into the connection */ + *srv_conn->dst = *s->scb->dst; + + /* Copy network namespace from client connection */ + srv_conn->proxy_netns = cli_conn ? cli_conn->proxy_netns : NULL; + + if (!srv_conn->xprt) { + /* set the correct protocol on the output stream connector */ + if (srv) { + if (conn_prepare(srv_conn, protocol_lookup(srv_conn->dst->ss_family, PROTO_TYPE_STREAM, 0), srv->xprt)) { + conn_free(srv_conn); + return SF_ERR_INTERNAL; + } + } else if (obj_type(s->target) == OBJ_TYPE_PROXY) { + int ret; + + /* proxies exclusively run on raw_sock right now */ + ret = conn_prepare(srv_conn, protocol_lookup(srv_conn->dst->ss_family, PROTO_TYPE_STREAM, 0), xprt_get(XPRT_RAW)); + if (ret < 0 || !(srv_conn->ctrl)) { + conn_free(srv_conn); + return SF_ERR_INTERNAL; + } + } + else { + conn_free(srv_conn); + return SF_ERR_INTERNAL; /* how did we get there ? */ + } + + if (sc_attach_mux(s->scb, NULL, srv_conn) < 0) { + conn_free(srv_conn); + return SF_ERR_INTERNAL; /* how did we get there ? */ + } + srv_conn->ctx = s->scb; + +#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + if (!srv || + (srv->use_ssl != 1 || (!(srv->ssl_ctx.alpn_str) && !(srv->ssl_ctx.npn_str)) || + srv->mux_proto || !IS_HTX_STRM(s))) +#endif + init_mux = 1; + + /* process the case where the server requires the PROXY protocol to be sent */ + srv_conn->send_proxy_ofs = 0; + + if (srv && srv->pp_opts) { + srv_conn->flags |= CO_FL_SEND_PROXY; + srv_conn->send_proxy_ofs = 1; /* must compute size */ + } + + if (srv && (srv->flags & SRV_F_SOCKS4_PROXY)) { + srv_conn->send_proxy_ofs = 1; + srv_conn->flags |= CO_FL_SOCKS4; + } + +#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + /* if websocket stream, try to update connection ALPN. */ + if (unlikely(s->flags & SF_WEBSOCKET) && + srv && srv->use_ssl && srv->ssl_ctx.alpn_str) { + char *alpn = ""; + int force = 0; + + switch (srv->ws) { + case SRV_WS_AUTO: + alpn = "\x08http/1.1"; + force = 0; + break; + case SRV_WS_H1: + alpn = "\x08http/1.1"; + force = 1; + break; + case SRV_WS_H2: + alpn = "\x02h2"; + force = 1; + break; + } + + if (!conn_update_alpn(srv_conn, ist(alpn), force)) + DBG_TRACE_STATE("update alpn for websocket", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + } +#endif + } + else { + s->flags |= SF_SRV_REUSED; + + /* Currently there seems to be no known cases of xprt ready + * without the mux installed here. + */ + BUG_ON(!srv_conn->mux); + + if (!(srv_conn->mux->ctl(srv_conn, MUX_CTL_STATUS, NULL) & MUX_STATUS_READY)) + s->flags |= SF_SRV_REUSED_ANTICIPATED; + } + + /* flag for logging source ip/port */ + if (strm_fe(s)->options2 & PR_O2_SRC_ADDR) + s->flags |= SF_SRC_ADDR; + + /* disable lingering */ + if (s->be->options & PR_O_TCP_NOLING) + s->scb->flags |= SC_FL_NOLINGER; + + if (s->flags & SF_SRV_REUSED) { + _HA_ATOMIC_INC(&s->be->be_counters.reuse); + if (srv) + _HA_ATOMIC_INC(&srv->counters.reuse); + } else { + _HA_ATOMIC_INC(&s->be->be_counters.connect); + if (srv) + _HA_ATOMIC_INC(&srv->counters.connect); + } + + err = do_connect_server(s, srv_conn); + if (err != SF_ERR_NONE) + return err; + +#ifdef USE_OPENSSL + if (!(s->flags & SF_SRV_REUSED)) { + if (smp_make_safe(sni_smp)) + ssl_sock_set_servername(srv_conn, sni_smp->data.u.str.area); + } +#endif /* USE_OPENSSL */ + + /* The CO_FL_SEND_PROXY flag may have been set by the connect method, + * if so, add our handshake pseudo-XPRT now. + */ + if ((srv_conn->flags & CO_FL_HANDSHAKE)) { + if (xprt_add_hs(srv_conn) < 0) { + conn_full_close(srv_conn); + return SF_ERR_INTERNAL; + } + } + conn_xprt_start(srv_conn); + + /* We have to defer the mux initialization until after si_connect() + * has been called, as we need the xprt to have been properly + * initialized, or any attempt to recv during the mux init may + * fail, and flag the connection as CO_FL_ERROR. + */ + if (init_mux) { + const struct mux_ops *alt_mux = + likely(!(s->flags & SF_WEBSOCKET)) ? NULL : srv_get_ws_proto(srv); + if (conn_install_mux_be(srv_conn, s->scb, s->sess, alt_mux) < 0) { + conn_full_close(srv_conn); + return SF_ERR_INTERNAL; + } + if (IS_HTX_STRM(s)) { + /* If we're doing http-reuse always, and the connection + * is not private with available streams (an http2 + * connection), add it to the available list, so that + * others can use it right away. If the connection is + * private or we're doing http-reuse safe and the mux + * protocol supports multiplexing, add it in the + * session server list. + */ + if (srv && reuse_mode == PR_O_REUSE_ALWS && + !(srv_conn->flags & CO_FL_PRIVATE) && + srv_conn->mux->avail_streams(srv_conn) > 0) { + srv_add_to_avail_list(srv, srv_conn); + } + else if (srv_conn->flags & CO_FL_PRIVATE || + (reuse_mode == PR_O_REUSE_SAFE && + srv_conn->mux->flags & MX_FL_HOL_RISK)) { + /* If it fail now, the same will be done in mux->detach() callback */ + session_add_conn(s->sess, srv_conn, srv_conn->target); + } + } + } + +#if defined(USE_OPENSSL) && (defined(OPENSSL_IS_BORINGSSL) || (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L)) + + if (!reuse && cli_conn && srv && srv_conn->mux && + (srv->ssl_ctx.options & SRV_SSL_O_EARLY_DATA) && + /* Only attempt to use early data if either the client sent + * early data, so that we know it can handle a 425, or if + * we are allowed to retry requests on early data failure, and + * it's our first try + */ + ((cli_conn->flags & CO_FL_EARLY_DATA) || + ((s->be->retry_type & PR_RE_EARLY_ERROR) && !s->conn_retries)) && + co_data(sc_oc(s->scb)) && + srv_conn->flags & CO_FL_SSL_WAIT_HS) + srv_conn->flags &= ~(CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN); +#endif + + /* set connect timeout */ + s->conn_exp = tick_add_ifset(now_ms, s->be->timeout.connect); + + if (srv) { + int count; + + s->flags |= SF_CURR_SESS; + count = _HA_ATOMIC_ADD_FETCH(&srv->cur_sess, 1); + HA_ATOMIC_UPDATE_MAX(&srv->counters.cur_sess_max, count); + if (s->be->lbprm.server_take_conn) + s->be->lbprm.server_take_conn(srv); + } + + /* Now handle synchronously connected sockets. We know the stream connector + * is at least in state SC_ST_CON. These ones typically are UNIX + * sockets, socket pairs, andoccasionally TCP connections on the + * loopback on a heavily loaded system. + */ + if (srv_conn->flags & CO_FL_ERROR) + s->scb->flags |= SC_FL_ERROR; + + /* If we had early data, and the handshake ended, then + * we can remove the flag, and attempt to wake the task up, + * in the event there's an analyser waiting for the end of + * the handshake. + */ + if (!(srv_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS))) + sc_ep_clr(s->scb, SE_FL_WAIT_FOR_HS); + + if (!sc_state_in(s->scb->state, SC_SB_EST|SC_SB_DIS|SC_SB_CLO) && + (srv_conn->flags & CO_FL_WAIT_XPRT) == 0) { + s->conn_exp = TICK_ETERNITY; + sc_oc(s->scb)->flags |= CF_WRITE_EVENT; + if (s->scb->state == SC_ST_CON) + s->scb->state = SC_ST_RDY; + } + + /* Report EOI on the channel if it was reached from the mux point of + * view. + * + * Note: This test is only required because si_cs_process is also the SI + * wake callback. Otherwise si_cs_recv()/si_cs_send() already take + * care of it. + */ + if (sc_ep_test(s->scb, SE_FL_EOI) && !(s->scb->flags & SC_FL_EOI)) { + s->scb->flags |= SC_FL_EOI; + sc_ic(s->scb)->flags |= CF_READ_EVENT; + } + + /* catch all sync connect while the mux is not already installed */ + if (!srv_conn->mux && !(srv_conn->flags & CO_FL_WAIT_XPRT)) { + if (conn_create_mux(srv_conn) < 0) { + conn_full_close(srv_conn); + return SF_ERR_INTERNAL; + } + } + + return SF_ERR_NONE; /* connection is OK */ +} + + +/* This function performs the "redispatch" part of a connection attempt. It + * will assign a server if required, queue the connection if required, and + * handle errors that might arise at this level. It can change the server + * state. It will return 1 if it encounters an error, switches the server + * state, or has to queue a connection. Otherwise, it will return 0 indicating + * that the connection is ready to use. + */ + +int srv_redispatch_connect(struct stream *s) +{ + struct server *srv; + int conn_err; + + /* We know that we don't have any connection pending, so we will + * try to get a new one, and wait in this state if it's queued + */ + redispatch: + conn_err = assign_server_and_queue(s); + srv = objt_server(s->target); + + switch (conn_err) { + case SRV_STATUS_OK: + break; + + case SRV_STATUS_FULL: + /* The server has reached its maxqueue limit. Either PR_O_REDISP is set + * and we can redispatch to another server, or it is not and we return + * 503. This only makes sense in DIRECT mode however, because normal LB + * algorithms would never select such a server, and hash algorithms + * would bring us on the same server again. Note that s->target is set + * in this case. + */ + if (((s->flags & (SF_DIRECT|SF_FORCE_PRST)) == SF_DIRECT) && + (s->be->options & PR_O_REDISP)) { + s->flags &= ~(SF_DIRECT | SF_ASSIGNED); + sockaddr_free(&s->scb->dst); + goto redispatch; + } + + if (!s->conn_err_type) { + s->conn_err_type = STRM_ET_QUEUE_ERR; + } + + _HA_ATOMIC_INC(&srv->counters.failed_conns); + _HA_ATOMIC_INC(&s->be->be_counters.failed_conns); + return 1; + + case SRV_STATUS_NOSRV: + /* note: it is guaranteed that srv == NULL here */ + if (!s->conn_err_type) { + s->conn_err_type = STRM_ET_CONN_ERR; + } + + _HA_ATOMIC_INC(&s->be->be_counters.failed_conns); + return 1; + + case SRV_STATUS_QUEUED: + s->conn_exp = tick_add_ifset(now_ms, s->be->timeout.queue); + s->scb->state = SC_ST_QUE; + /* do nothing else and do not wake any other stream up */ + return 1; + + case SRV_STATUS_INTERNAL: + default: + if (!s->conn_err_type) { + s->conn_err_type = STRM_ET_CONN_OTHER; + } + + if (srv) + srv_inc_sess_ctr(srv); + if (srv) + srv_set_sess_last(srv); + if (srv) + _HA_ATOMIC_INC(&srv->counters.failed_conns); + _HA_ATOMIC_INC(&s->be->be_counters.failed_conns); + + /* release other streams waiting for this server */ + if (may_dequeue_tasks(srv, s->be)) + process_srv_queue(srv); + return 1; + } + /* if we get here, it's because we got SRV_STATUS_OK, which also + * means that the connection has not been queued. + */ + return 0; +} + +/* Check if the connection request is in such a state that it can be aborted. */ +static int back_may_abort_req(struct channel *req, struct stream *s) +{ + return ((s->scf->flags & SC_FL_ERROR) || + ((s->scb->flags & (SC_FL_SHUT_WANTED|SC_FL_SHUT_DONE)) && /* empty and client aborted */ + (!co_data(req) || (s->be->options & PR_O_ABRT_CLOSE)))); +} + +/* Update back stream connector status for input states SC_ST_ASS, SC_ST_QUE, + * SC_ST_TAR. Other input states are simply ignored. + * Possible output states are SC_ST_CLO, SC_ST_TAR, SC_ST_ASS, SC_ST_REQ, SC_ST_CON + * and SC_ST_EST. Flags must have previously been updated for timeouts and other + * conditions. + */ +void back_try_conn_req(struct stream *s) +{ + struct server *srv = objt_server(s->target); + struct stconn *sc = s->scb; + struct channel *req = &s->req; + + DBG_TRACE_ENTER(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + + if (sc->state == SC_ST_ASS) { + /* Server assigned to connection request, we have to try to connect now */ + int conn_err; + + /* Before we try to initiate the connection, see if the + * request may be aborted instead. + */ + if (back_may_abort_req(req, s)) { + s->conn_err_type |= STRM_ET_CONN_ABRT; + DBG_TRACE_STATE("connection aborted", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto abort_connection; + } + + conn_err = connect_server(s); + srv = objt_server(s->target); + + if (conn_err == SF_ERR_NONE) { + /* state = SC_ST_CON or SC_ST_EST now */ + if (srv) + srv_inc_sess_ctr(srv); + if (srv) + srv_set_sess_last(srv); + DBG_TRACE_STATE("connection attempt", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto end; + } + + /* We have received a synchronous error. We might have to + * abort, retry immediately or redispatch. + */ + if (conn_err == SF_ERR_INTERNAL) { + if (!s->conn_err_type) { + s->conn_err_type = STRM_ET_CONN_OTHER; + } + + if (srv) + srv_inc_sess_ctr(srv); + if (srv) + srv_set_sess_last(srv); + if (srv) + _HA_ATOMIC_INC(&srv->counters.failed_conns); + _HA_ATOMIC_INC(&s->be->be_counters.failed_conns); + + /* release other streams waiting for this server */ + sess_change_server(s, NULL); + if (may_dequeue_tasks(srv, s->be)) + process_srv_queue(srv); + + /* Failed and not retryable. */ + sc_abort(sc); + sc_shutdown(sc); + sc->flags |= SC_FL_ERROR; + + s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts); + + /* we may need to know the position in the queue for logging */ + pendconn_cond_unlink(s->pend_pos); + + /* no stream was ever accounted for this server */ + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + DBG_TRACE_STATE("internal error during connection", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + /* We are facing a retryable error, but we don't want to run a + * turn-around now, as the problem is likely a source port + * allocation problem, so we want to retry now. + */ + sc->state = SC_ST_CER; + sc->flags &= ~SC_FL_ERROR; + back_handle_st_cer(s); + + DBG_TRACE_STATE("connection error, retry", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + /* now sc->state is one of SC_ST_CLO, SC_ST_TAR, SC_ST_ASS, SC_ST_REQ */ + } + else if (sc->state == SC_ST_QUE) { + /* connection request was queued, check for any update */ + if (!pendconn_dequeue(s)) { + /* The connection is not in the queue anymore. Either + * we have a server connection slot available and we + * go directly to the assigned state, or we need to + * load-balance first and go to the INI state. + */ + s->conn_exp = TICK_ETERNITY; + if (unlikely(!(s->flags & SF_ASSIGNED))) + sc->state = SC_ST_REQ; + else { + s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts); + sc->state = SC_ST_ASS; + } + DBG_TRACE_STATE("dequeue connection request", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto end; + } + + /* Connection request still in queue... */ + if (s->flags & SF_CONN_EXP) { + /* ... and timeout expired */ + s->conn_exp = TICK_ETERNITY; + s->flags &= ~SF_CONN_EXP; + s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts); + + /* we may need to know the position in the queue for logging */ + pendconn_cond_unlink(s->pend_pos); + + if (srv) + _HA_ATOMIC_INC(&srv->counters.failed_conns); + _HA_ATOMIC_INC(&s->be->be_counters.failed_conns); + sc_abort(sc); + sc_shutdown(sc); + req->flags |= CF_WRITE_TIMEOUT; + if (!s->conn_err_type) + s->conn_err_type = STRM_ET_QUEUE_TO; + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + DBG_TRACE_STATE("connection request still queued", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto end; + } + + /* Connection remains in queue, check if we have to abort it */ + if (back_may_abort_req(req, s)) { + s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts); + + /* we may need to know the position in the queue for logging */ + pendconn_cond_unlink(s->pend_pos); + + s->conn_err_type |= STRM_ET_QUEUE_ABRT; + DBG_TRACE_STATE("abort queued connection request", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto abort_connection; + } + + /* Nothing changed */ + } + else if (sc->state == SC_ST_TAR) { + /* Connection request might be aborted */ + if (back_may_abort_req(req, s)) { + s->conn_err_type |= STRM_ET_CONN_ABRT; + DBG_TRACE_STATE("connection aborted", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto abort_connection; + } + + if (!(s->flags & SF_CONN_EXP)) + return; /* still in turn-around */ + + s->flags &= ~SF_CONN_EXP; + s->conn_exp = TICK_ETERNITY; + + /* we keep trying on the same server as long as the stream is + * marked "assigned". + * FIXME: Should we force a redispatch attempt when the server is down ? + */ + if (s->flags & SF_ASSIGNED) + sc->state = SC_ST_ASS; + else + sc->state = SC_ST_REQ; + + DBG_TRACE_STATE("retry connection now", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + } + + end: + DBG_TRACE_LEAVE(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + return; + +abort_connection: + /* give up */ + s->conn_exp = TICK_ETERNITY; + s->flags &= ~SF_CONN_EXP; + sc_abort(sc); + sc_shutdown(sc); + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + DBG_TRACE_DEVEL("leaving on error", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + return; +} + +/* This function initiates a server connection request on a stream connector + * already in SC_ST_REQ state. Upon success, the state goes to SC_ST_ASS for + * a real connection to a server, indicating that a server has been assigned, + * or SC_ST_RDY for a successful connection to an applet. It may also return + * SC_ST_QUE, or SC_ST_CLO upon error. + */ +void back_handle_st_req(struct stream *s) +{ + struct stconn *sc = s->scb; + + if (sc->state != SC_ST_REQ) + return; + + DBG_TRACE_ENTER(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + + if (unlikely(obj_type(s->target) == OBJ_TYPE_APPLET)) { + struct appctx *appctx; + + /* The target is an applet but the SC is in SC_ST_REQ. Thus it + * means no appctx are attached to the SC. Otherwise, it will be + * in SC_ST_RDY state. So, try to create the appctx now. + */ + BUG_ON(sc_appctx(sc)); + appctx = sc_applet_create(sc, objt_applet(s->target)); + if (!appctx) { + /* No more memory, let's immediately abort. Force the + * error code to ignore the ERR_LOCAL which is not a + * real error. + */ + s->flags &= ~(SF_ERR_MASK | SF_FINST_MASK); + + sc_abort(sc); + sc_shutdown(sc); + sc->flags |= SC_FL_ERROR; + s->conn_err_type = STRM_ET_CONN_RES; + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + DBG_TRACE_STATE("failed to register applet", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + DBG_TRACE_STATE("applet registered", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto end; + } + + /* Try to assign a server */ + if (srv_redispatch_connect(s) != 0) { + /* We did not get a server. Either we queued the + * connection request, or we encountered an error. + */ + if (sc->state == SC_ST_QUE) { + DBG_TRACE_STATE("connection request queued", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + goto end; + } + + /* we did not get any server, let's check the cause */ + sc_abort(sc); + sc_shutdown(sc); + sc->flags |= SC_FL_ERROR; + if (!s->conn_err_type) + s->conn_err_type = STRM_ET_CONN_OTHER; + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + DBG_TRACE_STATE("connection request failed", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + /* The server is assigned */ + s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts); + sc->state = SC_ST_ASS; + be_set_sess_last(s->be); + DBG_TRACE_STATE("connection request assigned to a server", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + + end: + DBG_TRACE_LEAVE(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); +} + +/* This function is called with (sc->state == SC_ST_CON) meaning that a + * connection was attempted and that the file descriptor is already allocated. + * We must check for timeout, error and abort. Possible output states are + * SC_ST_CER (error), SC_ST_DIS (abort), and SC_ST_CON (no change). This only + * works with connection-based streams. We know that there were no I/O event + * when reaching this function. Timeouts and errors are *not* cleared. + */ +void back_handle_st_con(struct stream *s) +{ + struct stconn *sc = s->scb; + struct channel *req = &s->req; + + DBG_TRACE_ENTER(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + + /* the client might want to abort */ + if ((s->scf->flags & SC_FL_SHUT_DONE) || + ((s->scb->flags & SC_FL_SHUT_WANTED) && + (!co_data(req) || (s->be->options & PR_O_ABRT_CLOSE)))) { + sc->flags |= SC_FL_NOLINGER; + sc_shutdown(sc); + s->conn_err_type |= STRM_ET_CONN_ABRT; + if (s->srv_error) + s->srv_error(s, sc); + /* Note: state = SC_ST_DIS now */ + DBG_TRACE_STATE("client abort during connection attempt", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + done: + /* retryable error ? */ + if ((s->flags & SF_CONN_EXP) || (sc->flags & SC_FL_ERROR)) { + if (!s->conn_err_type) { + if ((sc->flags & SC_FL_ERROR)) + s->conn_err_type = STRM_ET_CONN_ERR; + else + s->conn_err_type = STRM_ET_CONN_TO; + } + + sc->state = SC_ST_CER; + DBG_TRACE_STATE("connection failed, retry", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + } + + end: + DBG_TRACE_LEAVE(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); +} + +/* This function is called with (sc->state == SC_ST_CER) meaning that a + * previous connection attempt has failed and that the file descriptor + * has already been released. Possible causes include asynchronous error + * notification and time out. Possible output states are SC_ST_CLO when + * retries are exhausted, SC_ST_TAR when a delay is wanted before a new + * connection attempt, SC_ST_ASS when it's wise to retry on the same server, + * and SC_ST_REQ when an immediate redispatch is wanted. The buffers are + * marked as in error state. Timeouts and errors are cleared before retrying. + */ +void back_handle_st_cer(struct stream *s) +{ + struct stconn *sc = s->scb; + int must_tar = !!(sc->flags & SC_FL_ERROR); + + DBG_TRACE_ENTER(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + + s->conn_exp = TICK_ETERNITY; + s->flags &= ~SF_CONN_EXP; + + /* we probably have to release last stream from the server */ + if (objt_server(s->target)) { + struct connection *conn = sc_conn(sc); + + health_adjust(__objt_server(s->target), HANA_STATUS_L4_ERR); + + if (s->flags & SF_CURR_SESS) { + s->flags &= ~SF_CURR_SESS; + _HA_ATOMIC_DEC(&__objt_server(s->target)->cur_sess); + } + + if ((sc->flags & SC_FL_ERROR) && + conn && conn->err_code == CO_ER_SSL_MISMATCH_SNI) { + /* We tried to connect to a server which is configured + * with "verify required" and which doesn't have the + * "verifyhost" directive. The server presented a wrong + * certificate (a certificate for an unexpected name), + * which implies that we have used SNI in the handshake, + * and that the server doesn't have the associated cert + * and presented a default one. + * + * This is a serious enough issue not to retry. It's + * especially important because this wrong name might + * either be the result of a configuration error, and + * retrying will only hammer the server, or is caused + * by the use of a wrong SNI value, most likely + * provided by the client and we don't want to let the + * client provoke retries. + */ + s->conn_retries = s->be->conn_retries; + DBG_TRACE_DEVEL("Bad SSL cert, disable connection retries", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + } + } + + /* ensure that we have enough retries left */ + if (s->conn_retries >= s->be->conn_retries || !(s->be->retry_type & PR_RE_CONN_FAILED)) { + if (!s->conn_err_type) { + s->conn_err_type = STRM_ET_CONN_ERR; + } + + if (objt_server(s->target)) + _HA_ATOMIC_INC(&objt_server(s->target)->counters.failed_conns); + _HA_ATOMIC_INC(&s->be->be_counters.failed_conns); + sess_change_server(s, NULL); + if (may_dequeue_tasks(objt_server(s->target), s->be)) + process_srv_queue(objt_server(s->target)); + + /* shutw is enough to stop a connecting socket */ + sc_shutdown(sc); + sc->flags |= SC_FL_ERROR; + + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + + DBG_TRACE_STATE("connection failed", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + /* At this stage, we will trigger a connection retry (with or without + * redispatch). Thus we must reset the SI endpoint on the server side + * an close the attached connection. It is especially important to do it + * now if the retry is not immediately performed, to be sure to release + * resources as soon as possible and to not catch errors from the lower + * layers in an unexpected state (i.e < ST_CONN). + * + * Note: the stream connector will be switched to ST_REQ, ST_ASS or + * ST_TAR and SC_FL_ERROR and SF_CONN_EXP flags will be unset. + */ + if (sc_reset_endp(sc) < 0) { + if (!s->conn_err_type) + s->conn_err_type = STRM_ET_CONN_OTHER; + + if (objt_server(s->target)) + _HA_ATOMIC_INC(&objt_server(s->target)->counters.internal_errors); + _HA_ATOMIC_INC(&s->be->be_counters.internal_errors); + sess_change_server(s, NULL); + if (may_dequeue_tasks(objt_server(s->target), s->be)) + process_srv_queue(objt_server(s->target)); + + /* shutw is enough to stop a connecting socket */ + sc_shutdown(sc); + sc->flags |= SC_FL_ERROR; + + sc->state = SC_ST_CLO; + if (s->srv_error) + s->srv_error(s, sc); + + DBG_TRACE_STATE("error resetting endpoint", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + s->conn_retries++; + stream_choose_redispatch(s); + + if (must_tar) { + /* The error was an asynchronous connection error, and we will + * likely have to retry connecting to the same server, most + * likely leading to the same result. To avoid this, we wait + * MIN(one second, connect timeout) before retrying. We don't + * do it when the failure happened on a reused connection + * though. + */ + + int delay = 1000; + const int reused = (s->flags & SF_SRV_REUSED) && + !(s->flags & SF_SRV_REUSED_ANTICIPATED); + + if (s->be->timeout.connect && s->be->timeout.connect < delay) + delay = s->be->timeout.connect; + + if (!s->conn_err_type) + s->conn_err_type = STRM_ET_CONN_ERR; + + /* only wait when we're retrying on the same server */ + if ((sc->state == SC_ST_ASS || + (s->be->srv_act <= 1)) && !reused) { + sc->state = SC_ST_TAR; + s->conn_exp = tick_add(now_ms, MS_TO_TICKS(delay)); + } + DBG_TRACE_STATE("retry a new connection", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + } + + end: + DBG_TRACE_LEAVE(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); +} + +/* This function is called with (sc->state == SC_ST_RDY) meaning that a + * connection was attempted, that the file descriptor is already allocated, + * and that it has succeeded. We must still check for errors and aborts. + * Possible output states are SC_ST_EST (established), SC_ST_CER (error), + * and SC_ST_DIS (abort). This only works with connection-based streams. + * Timeouts and errors are *not* cleared. + */ +void back_handle_st_rdy(struct stream *s) +{ + struct stconn *sc = s->scb; + struct channel *req = &s->req; + + DBG_TRACE_ENTER(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + + if (unlikely(obj_type(s->target) == OBJ_TYPE_APPLET)) { + /* Here the appctx must exists because the SC was set to + * SC_ST_RDY state when the appctx was created. + */ + BUG_ON(!sc_appctx(s->scb)); + + if (!s->logs.request_ts) + s->logs.request_ts = now_ns; + s->logs.t_queue = ns_to_ms(now_ns - s->logs.accept_ts); + be_set_sess_last(s->be); + } + + /* We know the connection at least succeeded, though it could have + * since met an error for any other reason. At least it didn't time out + * even though the timeout might have been reported right after success. + * We need to take care of various situations here : + * - everything might be OK. We have to switch to established. + * - an I/O error might have been reported after a successful transfer, + * which is not retryable and needs to be logged correctly, and needs + * established as well + * - SC_ST_CON implies !CF_WROTE_DATA but not conversely as we could + * have validated a connection with incoming data (e.g. TCP with a + * banner protocol), or just a successful connect() probe. + * - the client might have requested a connection abort, this needs to + * be checked before we decide to retry anything. + */ + + /* it's still possible to handle client aborts or connection retries + * before any data were sent. + */ + if (!(req->flags & CF_WROTE_DATA)) { + /* client abort ? */ + if ((s->scf->flags & SC_FL_SHUT_DONE) || + ((s->scb->flags & SC_FL_SHUT_WANTED) && + (!co_data(req) || (s->be->options & PR_O_ABRT_CLOSE)))) { + /* give up */ + sc->flags |= SC_FL_NOLINGER; + sc_shutdown(sc); + s->conn_err_type |= STRM_ET_CONN_ABRT; + if (s->srv_error) + s->srv_error(s, sc); + DBG_TRACE_STATE("client abort during connection attempt", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + + /* retryable error ? */ + if (sc->flags & SC_FL_ERROR) { + if (!s->conn_err_type) + s->conn_err_type = STRM_ET_CONN_ERR; + sc->state = SC_ST_CER; + DBG_TRACE_STATE("connection failed, retry", STRM_EV_STRM_PROC|STRM_EV_CS_ST|STRM_EV_STRM_ERR, s); + goto end; + } + } + + /* data were sent and/or we had no error, back_establish() will + * now take over. + */ + DBG_TRACE_STATE("connection established", STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); + s->conn_err_type = STRM_ET_NONE; + sc->state = SC_ST_EST; + + end: + DBG_TRACE_LEAVE(STRM_EV_STRM_PROC|STRM_EV_CS_ST, s); +} + +/* sends a log message when a backend goes down, and also sets last + * change date. + */ +void set_backend_down(struct proxy *be) +{ + be->last_change = ns_to_sec(now_ns); + _HA_ATOMIC_INC(&be->down_trans); + + if (!(global.mode & MODE_STARTING)) { + ha_alert("%s '%s' has no server available!\n", proxy_type_str(be), be->id); + send_log(be, LOG_EMERG, "%s %s has no server available!\n", proxy_type_str(be), be->id); + } +} + +/* Apply RDP cookie persistence to the current stream. For this, the function + * tries to extract an RDP cookie from the request buffer, and look for the + * matching server in the list. If the server is found, it is assigned to the + * stream. This always returns 1, and the analyser removes itself from the + * list. Nothing is performed if a server was already assigned. + */ +int tcp_persist_rdp_cookie(struct stream *s, struct channel *req, int an_bit) +{ + struct proxy *px = s->be; + int ret; + struct sample smp; + struct server *srv = px->srv; + uint16_t port; + uint32_t addr; + char *p; + + DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s); + + if (s->flags & SF_ASSIGNED) + goto no_cookie; + + memset(&smp, 0, sizeof(smp)); + + ret = fetch_rdp_cookie_name(s, &smp, s->be->rdp_cookie_name, s->be->rdp_cookie_len); + if (ret == 0 || (smp.flags & SMP_F_MAY_CHANGE) || smp.data.u.str.data == 0) + goto no_cookie; + + /* Considering an rdp cookie detected using acl, str ended with and should return. + * The cookie format is "." where "ip" is the integer corresponding to the + * server's IP address in network order, and "port" is the integer corresponding to the + * server's port in network order. Comments please Emeric. + */ + addr = strtoul(smp.data.u.str.area, &p, 10); + if (*p != '.') + goto no_cookie; + p++; + + port = ntohs(strtoul(p, &p, 10)); + if (*p != '.') + goto no_cookie; + + s->target = NULL; + while (srv) { + if (srv->addr.ss_family == AF_INET && + port == srv->svc_port && + addr == ((struct sockaddr_in *)&srv->addr)->sin_addr.s_addr) { + if ((srv->cur_state != SRV_ST_STOPPED) || (px->options & PR_O_PERSIST)) { + /* we found the server and it is usable */ + s->flags |= SF_DIRECT | SF_ASSIGNED; + s->target = &srv->obj_type; + break; + } + } + srv = srv->next; + } + +no_cookie: + req->analysers &= ~an_bit; + req->analyse_exp = TICK_ETERNITY; + DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s); + return 1; +} + +int be_downtime(struct proxy *px) { + if (px->lbprm.tot_weight && px->last_change < ns_to_sec(now_ns)) // ignore negative time + return px->down_time; + + return ns_to_sec(now_ns) - px->last_change + px->down_time; +} + +/* + * This function returns a string containing the balancing + * mode of the proxy in a format suitable for stats. + */ + +const char *backend_lb_algo_str(int algo) { + + if (algo == BE_LB_ALGO_RR) + return "roundrobin"; + else if (algo == BE_LB_ALGO_SRR) + return "static-rr"; + else if (algo == BE_LB_ALGO_FAS) + return "first"; + else if (algo == BE_LB_ALGO_LC) + return "leastconn"; + else if (algo == BE_LB_ALGO_SH) + return "source"; + else if (algo == BE_LB_ALGO_UH) + return "uri"; + else if (algo == BE_LB_ALGO_PH) + return "url_param"; + else if (algo == BE_LB_ALGO_HH) + return "hdr"; + else if (algo == BE_LB_ALGO_RCH) + return "rdp-cookie"; + else if (algo == BE_LB_ALGO_SMP) + return "hash"; + else if (algo == BE_LB_ALGO_NONE) + return "none"; + else + return "unknown"; +} + +/* This function parses a "balance" statement in a backend section describing + * . It returns -1 if there is any error, otherwise zero. If it + * returns -1, it will write an error message into the buffer which will + * automatically be allocated and must be passed as NULL. The trailing '\n' + * will not be written. The function must be called with pointing to the + * first word after "balance". + */ +int backend_parse_balance(const char **args, char **err, struct proxy *curproxy) +{ + if (!*(args[0])) { + /* if no option is set, use round-robin by default */ + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_RR; + return 0; + } + + if (strcmp(args[0], "roundrobin") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_RR; + } + else if (strcmp(args[0], "static-rr") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_SRR; + } + else if (strcmp(args[0], "first") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_FAS; + } + else if (strcmp(args[0], "leastconn") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_LC; + } + else if (!strncmp(args[0], "random", 6)) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_RND; + curproxy->lbprm.arg_opt1 = 2; + + if (*(args[0] + 6) == '(' && *(args[0] + 7) != ')') { /* number of draws */ + const char *beg; + char *end; + + beg = args[0] + 7; + curproxy->lbprm.arg_opt1 = strtol(beg, &end, 0); + + if (*end != ')') { + if (!*end) + memprintf(err, "random : missing closing parenthesis."); + else + memprintf(err, "random : unexpected character '%c' after argument.", *end); + return -1; + } + + if (curproxy->lbprm.arg_opt1 < 1) { + memprintf(err, "random : number of draws must be at least 1."); + return -1; + } + } + } + else if (strcmp(args[0], "source") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_SH; + } + else if (strcmp(args[0], "uri") == 0) { + int arg = 1; + + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_UH; + curproxy->lbprm.arg_opt1 = 0; // "whole", "path-only" + curproxy->lbprm.arg_opt2 = 0; // "len" + curproxy->lbprm.arg_opt3 = 0; // "depth" + + while (*args[arg]) { + if (strcmp(args[arg], "len") == 0) { + if (!*args[arg+1] || (atoi(args[arg+1]) <= 0)) { + memprintf(err, "%s : '%s' expects a positive integer (got '%s').", args[0], args[arg], args[arg+1]); + return -1; + } + curproxy->lbprm.arg_opt2 = atoi(args[arg+1]); + arg += 2; + } + else if (strcmp(args[arg], "depth") == 0) { + if (!*args[arg+1] || (atoi(args[arg+1]) <= 0)) { + memprintf(err, "%s : '%s' expects a positive integer (got '%s').", args[0], args[arg], args[arg+1]); + return -1; + } + /* hint: we store the position of the ending '/' (depth+1) so + * that we avoid a comparison while computing the hash. + */ + curproxy->lbprm.arg_opt3 = atoi(args[arg+1]) + 1; + arg += 2; + } + else if (strcmp(args[arg], "whole") == 0) { + curproxy->lbprm.arg_opt1 |= 1; + arg += 1; + } + else if (strcmp(args[arg], "path-only") == 0) { + curproxy->lbprm.arg_opt1 |= 2; + arg += 1; + } + else { + memprintf(err, "%s only accepts parameters 'len', 'depth', 'path-only', and 'whole' (got '%s').", args[0], args[arg]); + return -1; + } + } + } + else if (strcmp(args[0], "url_param") == 0) { + if (!*args[1]) { + memprintf(err, "%s requires an URL parameter name.", args[0]); + return -1; + } + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_PH; + + free(curproxy->lbprm.arg_str); + curproxy->lbprm.arg_str = strdup(args[1]); + curproxy->lbprm.arg_len = strlen(args[1]); + if (*args[2]) { + if (strcmp(args[2], "check_post") != 0) { + memprintf(err, "%s only accepts 'check_post' modifier (got '%s').", args[0], args[2]); + return -1; + } + } + } + else if (strcmp(args[0], "hash") == 0) { + if (!*args[1]) { + memprintf(err, "%s requires a sample expression.", args[0]); + return -1; + } + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_SMP; + + ha_free(&curproxy->lbprm.arg_str); + curproxy->lbprm.arg_str = strdup(args[1]); + curproxy->lbprm.arg_len = strlen(args[1]); + + if (*args[2]) { + memprintf(err, "%s takes no other argument (got '%s').", args[0], args[2]); + return -1; + } + } + else if (!strncmp(args[0], "hdr(", 4)) { + const char *beg, *end; + + beg = args[0] + 4; + end = strchr(beg, ')'); + + if (!end || end == beg) { + memprintf(err, "hdr requires an http header field name."); + return -1; + } + + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_HH; + + free(curproxy->lbprm.arg_str); + curproxy->lbprm.arg_len = end - beg; + curproxy->lbprm.arg_str = my_strndup(beg, end - beg); + curproxy->lbprm.arg_opt1 = 0; + + if (*args[1]) { + if (strcmp(args[1], "use_domain_only") != 0) { + memprintf(err, "%s only accepts 'use_domain_only' modifier (got '%s').", args[0], args[1]); + return -1; + } + curproxy->lbprm.arg_opt1 = 1; + } + } + else if (!strncmp(args[0], "rdp-cookie", 10)) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_RCH; + + if ( *(args[0] + 10 ) == '(' ) { /* cookie name */ + const char *beg, *end; + + beg = args[0] + 11; + end = strchr(beg, ')'); + + if (!end || end == beg) { + memprintf(err, "rdp-cookie : missing cookie name."); + return -1; + } + + free(curproxy->lbprm.arg_str); + curproxy->lbprm.arg_str = my_strndup(beg, end - beg); + curproxy->lbprm.arg_len = end - beg; + } + else if ( *(args[0] + 10 ) == '\0' ) { /* default cookie name 'mstshash' */ + free(curproxy->lbprm.arg_str); + curproxy->lbprm.arg_str = strdup("mstshash"); + curproxy->lbprm.arg_len = strlen(curproxy->lbprm.arg_str); + } + else { /* syntax */ + memprintf(err, "rdp-cookie : missing cookie name."); + return -1; + } + } + else if (strcmp(args[0], "log-hash") == 0) { + if (!*args[1]) { + memprintf(err, "%s requires a converter list.", args[0]); + return -1; + } + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_LH; + + ha_free(&curproxy->lbprm.arg_str); + curproxy->lbprm.arg_str = strdup(args[1]); + } + else if (strcmp(args[0], "sticky") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_LS; + } + else { + memprintf(err, "only supports 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri', 'url_param', 'hash', 'hdr(name)', 'rdp-cookie(name)', 'log-hash' and 'sticky' options."); + return -1; + } + return 0; +} + + +/************************************************************************/ +/* All supported sample and ACL keywords must be declared here. */ +/************************************************************************/ + +/* set temp integer to the number of enabled servers on the proxy. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_nbsrv(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = args->data.prx; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + + smp->data.u.sint = be_usable_srv(px); + + return 1; +} + +/* report in smp->flags a success or failure depending on the designated + * server's state. There is no match function involved since there's no pattern. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_is_up(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct server *srv = args->data.srv; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_BOOL; + if (!(srv->cur_admin & SRV_ADMF_MAINT) && + (!(srv->check.state & CHK_ST_CONFIGURED) || (srv->cur_state != SRV_ST_STOPPED))) + smp->data.u.sint = 1; + else + smp->data.u.sint = 0; + return 1; +} + +/* set temp integer to the number of enabled servers on the proxy. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_connslots(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct server *iterator; + struct proxy *px = args->data.prx; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = 0; + + for (iterator = px->srv; iterator; iterator = iterator->next) { + if (iterator->cur_state == SRV_ST_STOPPED) + continue; + + if (iterator->maxconn == 0 || iterator->maxqueue == 0) { + /* configuration is stupid */ + smp->data.u.sint = -1; /* FIXME: stupid value! */ + return 1; + } + + smp->data.u.sint += (iterator->maxconn - iterator->cur_sess) + + (iterator->maxqueue - iterator->queue.length); + } + + return 1; +} + +/* set temp integer to the id of the backend */ +static int +smp_fetch_be_id(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = NULL; + + if (smp->strm) + px = smp->strm->be; + else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK) + px = __objt_check(smp->sess->origin)->proxy; + if (!px) + return 0; + + smp->flags = SMP_F_VOL_TXN; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = px->uuid; + return 1; +} + +/* set string to the name of the backend */ +static int +smp_fetch_be_name(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = NULL; + + if (smp->strm) + px = smp->strm->be; + else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK) + px = __objt_check(smp->sess->origin)->proxy; + if (!px) + return 0; + + smp->data.u.str.area = (char *)px->id; + if (!smp->data.u.str.area) + return 0; + + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + smp->data.u.str.data = strlen(smp->data.u.str.area); + + return 1; +} + +/* set temp integer to the id of the server */ +static int +smp_fetch_srv_id(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct server *srv = NULL; + + if (smp->strm) + srv = objt_server(smp->strm->target); + else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK) + srv = __objt_check(smp->sess->origin)->server; + if (!srv) + return 0; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = srv->puid; + + return 1; +} + +/* set string to the name of the server */ +static int +smp_fetch_srv_name(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct server *srv = NULL; + + if (smp->strm) + srv = objt_server(smp->strm->target); + else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK) + srv = __objt_check(smp->sess->origin)->server; + if (!srv) + return 0; + + smp->data.u.str.area = srv->id; + if (!smp->data.u.str.area) + return 0; + + smp->data.type = SMP_T_STR; + smp->data.u.str.data = strlen(smp->data.u.str.area); + + return 1; +} + +/* set temp integer to the number of connections per second reaching the backend. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_be_sess_rate(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = args->data.prx; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = read_freq_ctr(&px->be_sess_per_sec); + return 1; +} + +/* set temp integer to the number of concurrent connections on the backend. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_be_conn(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = args->data.prx; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = px->beconn; + return 1; +} + +/* set temp integer to the number of available connections across available + * servers on the backend. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_be_conn_free(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct server *iterator; + struct proxy *px = args->data.prx; + unsigned int maxconn; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = 0; + + for (iterator = px->srv; iterator; iterator = iterator->next) { + if (iterator->cur_state == SRV_ST_STOPPED) + continue; + + px = iterator->proxy; + if (!srv_currently_usable(iterator) || + ((iterator->flags & SRV_F_BACKUP) && + (px->srv_act || (iterator != px->lbprm.fbck && !(px->options & PR_O_USE_ALL_BK))))) + continue; + + if (iterator->maxconn == 0) { + /* one active server is unlimited, return -1 */ + smp->data.u.sint = -1; + return 1; + } + + maxconn = srv_dynamic_maxconn(iterator); + if (maxconn > iterator->cur_sess) + smp->data.u.sint += maxconn - iterator->cur_sess; + } + + return 1; +} + +/* set temp integer to the total number of queued connections on the backend. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_queue_size(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = args->data.prx; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = px->totpend; + return 1; +} + +/* set temp integer to the total number of queued connections on the backend divided + * by the number of running servers and rounded up. If there is no running + * server, we return twice the total, just as if we had half a running server. + * This is more or less correct anyway, since we expect the last server to come + * back soon. + * Accepts exactly 1 argument. Argument is a backend, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_avg_queue_size(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct proxy *px = args->data.prx; + int nbsrv; + + if (px == NULL) + return 0; + if (px->cap & PR_CAP_DEF) + px = smp->px; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + + nbsrv = be_usable_srv(px); + + if (nbsrv > 0) + smp->data.u.sint = (px->totpend + nbsrv - 1) / nbsrv; + else + smp->data.u.sint = px->totpend * 2; + + return 1; +} + +/* set temp integer to the number of concurrent connections on the server in the backend. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_conn(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = args->data.srv->cur_sess; + return 1; +} + +/* set temp integer to the number of available connections on the server in the backend. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_conn_free(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + unsigned int maxconn; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + + if (args->data.srv->maxconn == 0) { + /* one active server is unlimited, return -1 */ + smp->data.u.sint = -1; + return 1; + } + + maxconn = srv_dynamic_maxconn(args->data.srv); + if (maxconn > args->data.srv->cur_sess) + smp->data.u.sint = maxconn - args->data.srv->cur_sess; + else + smp->data.u.sint = 0; + + return 1; +} + +/* set temp integer to the number of connections pending in the server's queue. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_queue(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = args->data.srv->queue.length; + return 1; +} + +/* set temp integer to the number of enabled servers on the proxy. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_sess_rate(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = read_freq_ctr(&args->data.srv->sess_per_sec); + return 1; +} + +/* set temp integer to the server weight. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_weight(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct server *srv = args->data.srv; + struct proxy *px = srv->proxy; + + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = (srv->cur_eweight * px->lbprm.wmult + px->lbprm.wdiv - 1) / px->lbprm.wdiv; + return 1; +} + +/* set temp integer to the server initial weight. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_iweight(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = args->data.srv->iweight; + return 1; +} + +/* set temp integer to the server user-specified weight. + * Accepts exactly 1 argument. Argument is a server, other types will lead to + * undefined behaviour. + */ +static int +smp_fetch_srv_uweight(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + smp->flags = SMP_F_VOL_TEST; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = args->data.srv->uweight; + return 1; +} + +static int +smp_fetch_be_server_timeout(const struct arg *args, struct sample *smp, const char *km, void *private) +{ + struct proxy *px = NULL; + + if (smp->strm) + px = smp->strm->be; + else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK) + px = __objt_check(smp->sess->origin)->proxy; + if (!px) + return 0; + + smp->flags = SMP_F_VOL_TXN; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = TICKS_TO_MS(px->timeout.server); + return 1; +} + +static int +smp_fetch_be_tunnel_timeout(const struct arg *args, struct sample *smp, const char *km, void *private) +{ + struct proxy *px = NULL; + + if (smp->strm) + px = smp->strm->be; + else if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK) + px = __objt_check(smp->sess->origin)->proxy; + if (!px) + return 0; + + smp->flags = SMP_F_VOL_TXN; + smp->data.type = SMP_T_SINT; + smp->data.u.sint = TICKS_TO_MS(px->timeout.tunnel); + return 1; +} + +static int sample_conv_nbsrv(const struct arg *args, struct sample *smp, void *private) +{ + + struct proxy *px; + + if (!smp_make_safe(smp)) + return 0; + + px = proxy_find_by_name(smp->data.u.str.area, PR_CAP_BE, 0); + if (!px) + return 0; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = be_usable_srv(px); + + return 1; +} + +static int +sample_conv_srv_queue(const struct arg *args, struct sample *smp, void *private) +{ + struct proxy *px; + struct server *srv; + char *bksep; + + if (!smp_make_safe(smp)) + return 0; + + bksep = strchr(smp->data.u.str.area, '/'); + + if (bksep) { + *bksep = '\0'; + px = proxy_find_by_name(smp->data.u.str.area, PR_CAP_BE, 0); + if (!px) + return 0; + smp->data.u.str.area = bksep + 1; + } else { + if (!(smp->px->cap & PR_CAP_BE)) + return 0; + px = smp->px; + } + + srv = server_find_by_name(px, smp->data.u.str.area); + if (!srv) + return 0; + + smp->data.type = SMP_T_SINT; + smp->data.u.sint = srv->queue.length; + return 1; +} + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted. + */ +static struct sample_fetch_kw_list smp_kws = {ILH, { + { "avg_queue", smp_fetch_avg_queue_size, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "be_conn", smp_fetch_be_conn, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "be_conn_free", smp_fetch_be_conn_free, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "be_id", smp_fetch_be_id, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, }, + { "be_name", smp_fetch_be_name, 0, NULL, SMP_T_STR, SMP_USE_BKEND, }, + { "be_server_timeout", smp_fetch_be_server_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, }, + { "be_sess_rate", smp_fetch_be_sess_rate, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "be_tunnel_timeout", smp_fetch_be_tunnel_timeout, 0, NULL, SMP_T_SINT, SMP_USE_BKEND, }, + { "connslots", smp_fetch_connslots, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "nbsrv", smp_fetch_nbsrv, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "queue", smp_fetch_queue_size, ARG1(1,BE), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_conn", smp_fetch_srv_conn, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_conn_free", smp_fetch_srv_conn_free, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_id", smp_fetch_srv_id, 0, NULL, SMP_T_SINT, SMP_USE_SERVR, }, + { "srv_is_up", smp_fetch_srv_is_up, ARG1(1,SRV), NULL, SMP_T_BOOL, SMP_USE_INTRN, }, + { "srv_name", smp_fetch_srv_name, 0, NULL, SMP_T_STR, SMP_USE_SERVR, }, + { "srv_queue", smp_fetch_srv_queue, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_sess_rate", smp_fetch_srv_sess_rate, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_weight", smp_fetch_srv_weight, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_iweight", smp_fetch_srv_iweight, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { "srv_uweight", smp_fetch_srv_uweight, ARG1(1,SRV), NULL, SMP_T_SINT, SMP_USE_INTRN, }, + { /* END */ }, +}}; + +INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws); + +/* Note: must not be declared as its list will be overwritten */ +static struct sample_conv_kw_list sample_conv_kws = {ILH, { + { "nbsrv", sample_conv_nbsrv, 0, NULL, SMP_T_STR, SMP_T_SINT }, + { "srv_queue", sample_conv_srv_queue, 0, NULL, SMP_T_STR, SMP_T_SINT }, + { /* END */ }, +}}; + +INITCALL1(STG_REGISTER, sample_register_convs, &sample_conv_kws); + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted. + */ +static struct acl_kw_list acl_kws = {ILH, { + { /* END */ }, +}}; + +INITCALL1(STG_REGISTER, acl_register_keywords, &acl_kws); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..0601bf6 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,303 @@ +/* + * ASCII <-> Base64 conversion as described in RFC1421. + * + * Copyright 2006-2010 Willy Tarreau + * Copyright 2009-2010 Krzysztof Piotr Oledzki + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include + +#include +#include + +#define B64BASE '#' /* arbitrary chosen base value */ +#define B64CMIN '+' +#define UB64CMIN '-' +#define B64CMAX 'z' +#define B64PADV 64 /* Base64 chosen special pad value */ + +const char base64tab[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +const char base64rev[]="b###cXYZ[\\]^_`a###d###$%&'()*+,-./0123456789:;<=######>?@ABCDEFGHIJKLMNOPQRSTUVW"; +const char ubase64tab[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +const char ubase64rev[]="b##XYZ[\\]^_`a###c###$%&'()*+,-./0123456789:;<=####c#>?@ABCDEFGHIJKLMNOPQRSTUVW"; + +/* Encodes bytes from to for at most chars (including + * the trailing zero). Returns the number of bytes written. No check is made + * for or to be NULL. Returns negative value if is too short + * to accept . 4 output bytes are produced for 1 to 3 input bytes. + */ +int a2base64(char *in, int ilen, char *out, int olen) +{ + int convlen; + + convlen = ((ilen + 2) / 3) * 4; + + if (convlen >= olen) + return -1; + + /* we don't need to check olen anymore */ + while (ilen >= 3) { + out[0] = base64tab[(((unsigned char)in[0]) >> 2)]; + out[1] = base64tab[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)]; + out[2] = base64tab[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)]; + out[3] = base64tab[(((unsigned char)in[2] & 0x3F))]; + out += 4; + in += 3; ilen -= 3; + } + + if (!ilen) { + out[0] = '\0'; + } else { + out[0] = base64tab[((unsigned char)in[0]) >> 2]; + if (ilen == 1) { + out[1] = base64tab[((unsigned char)in[0] & 0x03) << 4]; + out[2] = '='; + } else { + out[1] = base64tab[(((unsigned char)in[0] & 0x03) << 4) | + (((unsigned char)in[1]) >> 4)]; + out[2] = base64tab[((unsigned char)in[1] & 0x0F) << 2]; + } + out[3] = '='; + out[4] = '\0'; + } + + return convlen; +} + +/* url variant of a2base64 */ +int a2base64url(const char *in, size_t ilen, char *out, size_t olen) +{ + int convlen; + + convlen = ((ilen + 2) / 3) * 4; + + if (convlen >= olen) + return -1; + + /* we don't need to check olen anymore */ + while (ilen >= 3) { + out[0] = ubase64tab[(((unsigned char)in[0]) >> 2)]; + out[1] = ubase64tab[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)]; + out[2] = ubase64tab[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)]; + out[3] = ubase64tab[(((unsigned char)in[2] & 0x3F))]; + out += 4; + in += 3; + ilen -= 3; + } + + if (!ilen) { + out[0] = '\0'; + return convlen; + } + + out[0] = ubase64tab[((unsigned char)in[0]) >> 2]; + if (ilen == 1) { + out[1] = ubase64tab[((unsigned char)in[0] & 0x03) << 4]; + out[2] = '\0'; + convlen -= 2; + } else { + out[1] = ubase64tab[(((unsigned char)in[0] & 0x03) << 4) | + (((unsigned char)in[1]) >> 4)]; + out[2] = ubase64tab[((unsigned char)in[1] & 0x0F) << 2]; + out[3] = '\0'; + convlen -= 1; + } + + return convlen; +} + +/* Decodes bytes from to for at most chars. + * Returns the number of bytes converted. No check is made for + * or to be NULL. Returns -1 if is invalid or ilen + * has wrong size, -2 if is too short. + * 1 to 3 output bytes are produced for 4 input bytes. + */ +int base64dec(const char *in, size_t ilen, char *out, size_t olen) { + + unsigned char t[4]; + signed char b; + int convlen = 0, i = 0, pad = 0; + + if (ilen % 4) + return -1; + + if (olen < ((ilen / 4 * 3) + - (in[ilen-1] == '=' ? 1 : 0) + - (in[ilen-2] == '=' ? 1 : 0))) + return -2; + + while (ilen) { + + /* if (*p < B64CMIN || *p > B64CMAX) */ + b = (signed char)*in - B64CMIN; + if ((unsigned char)b > (B64CMAX-B64CMIN)) + return -1; + + b = base64rev[b] - B64BASE - 1; + + /* b == -1: invalid character */ + if (b < 0) + return -1; + + /* padding has to be continuous */ + if (pad && b != B64PADV) + return -1; + + /* valid padding: "XX==" or "XXX=", but never "X===" or "====" */ + if (pad && i < 2) + return -1; + + if (b == B64PADV) + pad++; + + t[i++] = b; + + if (i == 4) { + /* + * WARNING: we allow to write little more data than we + * should, but the checks from the beginning of the + * functions guarantee that we can safely do that. + */ + + /* xx000000 xx001111 xx111122 xx222222 */ + if (convlen < olen) + out[convlen] = ((t[0] << 2) + (t[1] >> 4)); + if (convlen+1 < olen) + out[convlen+1] = ((t[1] << 4) + (t[2] >> 2)); + if (convlen+2 < olen) + out[convlen+2] = ((t[2] << 6) + (t[3] >> 0)); + + convlen += 3-pad; + + pad = i = 0; + } + + in++; + ilen--; + } + + return convlen; +} + +/* url variant of base64dec */ +/* The reverse tab used to decode base64 is generated via /dev/base64/base64rev-gen.c */ +int base64urldec(const char *in, size_t ilen, char *out, size_t olen) +{ + unsigned char t[4]; + signed char b; + int convlen = 0, i = 0, pad = 0, padlen = 0; + + switch (ilen % 4) { + case 0: + break; + case 2: + padlen = pad = 2; + break; + case 3: + padlen = pad = 1; + break; + default: + return -1; + } + + if (olen < (((ilen + pad) / 4 * 3) - pad)) + return -2; + + while (ilen + pad) { + if (ilen) { + /* if (*p < UB64CMIN || *p > B64CMAX) */ + b = (signed char) * in - UB64CMIN; + if ((unsigned char)b > (B64CMAX - UB64CMIN)) + return -1; + + b = ubase64rev[b] - B64BASE - 1; + /* b == -1: invalid character */ + if (b < 0) + return -1; + + in++; + ilen--; + + } else { + b = B64PADV; + pad--; + } + + t[i++] = b; + + if (i == 4) { + /* + * WARNING: we allow to write little more data than we + * should, but the checks from the beginning of the + * functions guarantee that we can safely do that. + */ + + /* xx000000 xx001111 xx111122 xx222222 */ + if (convlen < olen) + out[convlen] = ((t[0] << 2) + (t[1] >> 4)); + if (convlen+1 < olen) + out[convlen+1] = ((t[1] << 4) + (t[2] >> 2)); + if (convlen+2 < olen) + out[convlen+2] = ((t[2] << 6) + (t[3] >> 0)); + + convlen += 3; + i = 0; + } + } + convlen -= padlen; + + return convlen; +} + +/* Converts the lower 30 bits of an integer to a 5-char base64 string. The + * caller is responsible for ensuring that the output buffer can accept 6 bytes + * (5 + the trailing zero). The pointer to the string is returned. The + * conversion is performed with MSB first and in a format that can be + * decoded with b64tos30(). This format is not padded and thus is not + * compatible with usual base64 routines. + */ +const char *s30tob64(int in, char *out) +{ + int i; + for (i = 0; i < 5; i++) { + out[i] = base64tab[(in >> 24) & 0x3F]; + in <<= 6; + } + out[5] = '\0'; + return out; +} + +/* Converts a 5-char base64 string encoded by s30tob64() into a 30-bit integer. + * The caller is responsible for ensuring that the input contains at least 5 + * chars. If any unexpected character is encountered, a negative value is + * returned. Otherwise the decoded value is returned. + */ +int b64tos30(const char *in) +{ + int i, out; + signed char b; + + out = 0; + for (i = 0; i < 5; i++) { + b = (signed char)in[i] - B64CMIN; + if ((unsigned char)b > (B64CMAX - B64CMIN)) + return -1; /* input character out of range */ + + b = base64rev[b] - B64BASE - 1; + if (b < 0) /* invalid character */ + return -1; + + if (b == B64PADV) /* padding not allowed */ + return -1; + + out = (out << 6) + b; + } + return out; +} diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..9f12f10 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,3014 @@ +/* + * Cache management + * + * Copyright 2017 HAProxy Technologies + * William Lallemand + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CACHE_FLT_F_IMPLICIT_DECL 0x00000001 /* The cache filtre was implicitly declared (ie without + * the filter keyword) */ +#define CACHE_FLT_INIT 0x00000002 /* Whether the cache name was freed. */ + +static uint64_t cache_hash_seed = 0; + +const char *cache_store_flt_id = "cache store filter"; + +extern struct applet http_cache_applet; + +struct flt_ops cache_ops; + +struct cache_tree { + struct eb_root entries; /* head of cache entries based on keys */ + __decl_thread(HA_RWLOCK_T lock); + + struct list cleanup_list; + __decl_thread(HA_SPINLOCK_T cleanup_lock); +} ALIGNED(64); + +struct cache { + struct cache_tree trees[CACHE_TREE_NUM]; + struct list list; /* cache linked list */ + unsigned int maxage; /* max-age */ + unsigned int maxblocks; + unsigned int maxobjsz; /* max-object-size (in bytes) */ + unsigned int max_secondary_entries; /* maximum number of secondary entries with the same primary hash */ + uint8_t vary_processing_enabled; /* boolean : manage Vary header (disabled by default) */ + char id[33]; /* cache name */ +}; + +/* the appctx context of a cache applet, stored in appctx->svcctx */ +struct cache_appctx { + struct cache_tree *cache_tree; + struct cache_entry *entry; /* Entry to be sent from cache. */ + unsigned int sent; /* The number of bytes already sent for this cache entry. */ + unsigned int offset; /* start offset of remaining data relative to beginning of the next block */ + unsigned int rem_data; /* Remaining bytes for the last data block (HTX only, 0 means process next block) */ + unsigned int send_notmodified:1; /* In case of conditional request, we might want to send a "304 Not Modified" response instead of the stored data. */ + unsigned int unused:31; + struct shared_block *next; /* The next block of data to be sent for this cache entry. */ +}; + +/* cache config for filters */ +struct cache_flt_conf { + union { + struct cache *cache; /* cache used by the filter */ + char *name; /* cache name used during conf parsing */ + } c; + unsigned int flags; /* CACHE_FLT_F_* */ +}; + +/* CLI context used during "show cache" */ +struct show_cache_ctx { + struct cache *cache; + struct cache_tree *cache_tree; + uint next_key; +}; + + +/* + * Vary-related structures and functions + */ +enum vary_header_bit { + VARY_ACCEPT_ENCODING = (1 << 0), + VARY_REFERER = (1 << 1), + VARY_ORIGIN = (1 << 2), + VARY_LAST /* should always be last */ +}; + +/* + * Encoding list extracted from + * https://www.iana.org/assignments/http-parameters/http-parameters.xhtml + * and RFC7231#5.3.4. + */ +enum vary_encoding { + VARY_ENCODING_GZIP = (1 << 0), + VARY_ENCODING_DEFLATE = (1 << 1), + VARY_ENCODING_BR = (1 << 2), + VARY_ENCODING_COMPRESS = (1 << 3), + VARY_ENCODING_AES128GCM = (1 << 4), + VARY_ENCODING_EXI = (1 << 5), + VARY_ENCODING_PACK200_GZIP = (1 << 6), + VARY_ENCODING_ZSTD = (1 << 7), + VARY_ENCODING_IDENTITY = (1 << 8), + VARY_ENCODING_STAR = (1 << 9), + VARY_ENCODING_OTHER = (1 << 10) +}; + +struct vary_hashing_information { + struct ist hdr_name; /* Header name */ + enum vary_header_bit value; /* Bit representing the header in a vary signature */ + unsigned int hash_length; /* Size of the sub hash for this header's value */ + int(*norm_fn)(struct htx*,struct ist hdr_name,char* buf,unsigned int* buf_len); /* Normalization function */ + int(*cmp_fn)(const void *ref, const void *new, unsigned int len); /* Comparison function, should return 0 if the hashes are alike */ +}; + +static int http_request_prebuild_full_secondary_key(struct stream *s); +static int http_request_build_secondary_key(struct stream *s, int vary_signature); +static int http_request_reduce_secondary_key(unsigned int vary_signature, + char prebuilt_key[HTTP_CACHE_SEC_KEY_LEN]); + +static int parse_encoding_value(struct ist value, unsigned int *encoding_value, + unsigned int *has_null_weight); + +static int accept_encoding_normalizer(struct htx *htx, struct ist hdr_name, + char *buf, unsigned int *buf_len); +static int default_normalizer(struct htx *htx, struct ist hdr_name, + char *buf, unsigned int *buf_len); + +static int accept_encoding_bitmap_cmp(const void *ref, const void *new, unsigned int len); + +/* Warning : do not forget to update HTTP_CACHE_SEC_KEY_LEN when new items are + * added to this array. */ +const struct vary_hashing_information vary_information[] = { + { IST("accept-encoding"), VARY_ACCEPT_ENCODING, sizeof(uint32_t), &accept_encoding_normalizer, &accept_encoding_bitmap_cmp }, + { IST("referer"), VARY_REFERER, sizeof(uint64_t), &default_normalizer, NULL }, + { IST("origin"), VARY_ORIGIN, sizeof(uint64_t), &default_normalizer, NULL }, +}; + + +static inline void cache_rdlock(struct cache_tree *cache) +{ + HA_RWLOCK_RDLOCK(CACHE_LOCK, &cache->lock); +} + +static inline void cache_rdunlock(struct cache_tree *cache) +{ + HA_RWLOCK_RDUNLOCK(CACHE_LOCK, &cache->lock); +} + +static inline void cache_wrlock(struct cache_tree *cache) +{ + HA_RWLOCK_WRLOCK(CACHE_LOCK, &cache->lock); +} + +static inline void cache_wrunlock(struct cache_tree *cache) +{ + HA_RWLOCK_WRUNLOCK(CACHE_LOCK, &cache->lock); +} + +/* + * cache ctx for filters + */ +struct cache_st { + struct shared_block *first_block; + struct list detached_head; +}; + +#define DEFAULT_MAX_SECONDARY_ENTRY 10 + +struct cache_entry { + unsigned int complete; /* An entry won't be valid until complete is not null. */ + unsigned int latest_validation; /* latest validation date */ + unsigned int expire; /* expiration date (wall clock time) */ + unsigned int age; /* Origin server "Age" header value */ + + int refcount; + + struct eb32_node eb; /* ebtree node used to hold the cache object */ + char hash[20]; + + struct list cleanup_list;/* List used between the cache_free_blocks and cache_reserve_finish calls */ + + char secondary_key[HTTP_CACHE_SEC_KEY_LEN]; /* Optional secondary key. */ + unsigned int secondary_key_signature; /* Bitfield of the HTTP headers that should be used + * to build secondary keys for this cache entry. */ + unsigned int secondary_entries_count; /* Should only be filled in the last entry of a list of dup entries */ + unsigned int last_clear_ts; /* Timestamp of the last call to clear_expired_duplicates. */ + + unsigned int etag_length; /* Length of the ETag value (if one was found in the response). */ + unsigned int etag_offset; /* Offset of the ETag value in the data buffer. */ + + time_t last_modified; /* Origin server "Last-Modified" header value converted in + * seconds since epoch. If no "Last-Modified" + * header is found, use "Date" header value, + * otherwise use reception time. This field will + * be used in case of an "If-Modified-Since"-based + * conditional request. */ + + unsigned char data[0]; +}; + +#define CACHE_BLOCKSIZE 1024 +#define CACHE_ENTRY_MAX_AGE 2147483648U + +static struct list caches = LIST_HEAD_INIT(caches); +static struct list caches_config = LIST_HEAD_INIT(caches_config); /* cache config to init */ +static struct cache *tmp_cache_config = NULL; + +DECLARE_STATIC_POOL(pool_head_cache_st, "cache_st", sizeof(struct cache_st)); + +static struct eb32_node *insert_entry(struct cache *cache, struct cache_tree *tree, struct cache_entry *new_entry); +static void delete_entry(struct cache_entry *del_entry); +static void release_entry_locked(struct cache_tree *cache, struct cache_entry *entry); +static void release_entry_unlocked(struct cache_tree *cache, struct cache_entry *entry); + +/* + * Find a cache_entry in the 's tree that has the hash . + * If is 0 then the entry is left untouched if it is found but + * is already expired, and NULL is returned. Otherwise, the expired entry is + * removed from the tree and NULL is returned. + * Returns a valid (not expired) cache_tree pointer. + * The returned entry is not retained, it should be explicitly retained only + * when necessary. + * + * This function must be called under a cache lock, either read if + * delete_expired==0, write otherwise. + */ +struct cache_entry *get_entry(struct cache_tree *cache_tree, char *hash, int delete_expired) +{ + struct eb32_node *node; + struct cache_entry *entry; + + node = eb32_lookup(&cache_tree->entries, read_u32(hash)); + if (!node) + return NULL; + + entry = eb32_entry(node, struct cache_entry, eb); + + /* if that's not the right node */ + if (memcmp(entry->hash, hash, sizeof(entry->hash))) + return NULL; + + if (entry->expire > date.tv_sec) { + return entry; + } else if (delete_expired) { + release_entry_locked(cache_tree, entry); + } + return NULL; +} + +/* + * Increment a cache_entry's reference counter. + */ +static void retain_entry(struct cache_entry *entry) +{ + if (entry) + HA_ATOMIC_INC(&entry->refcount); +} + +/* + * Decrement a cache_entry's reference counter and remove it from the 's + * tree if the reference counter becomes 0. + * If is 0 then the cache lock was already taken by the caller, + * otherwise it must be taken in write mode before actually deleting the entry. + */ +static void release_entry(struct cache_tree *cache, struct cache_entry *entry, int needs_locking) +{ + if (!entry) + return; + + if (HA_ATOMIC_SUB_FETCH(&entry->refcount, 1) <= 0) { + if (needs_locking) { + cache_wrlock(cache); + /* The value might have changed between the last time we + * checked it and now, we need to recheck it just in + * case. + */ + if (HA_ATOMIC_LOAD(&entry->refcount) > 0) { + cache_wrunlock(cache); + return; + } + } + delete_entry(entry); + if (needs_locking) { + cache_wrunlock(cache); + } + } +} + +/* + * Decrement a cache_entry's reference counter and remove it from the 's + * tree if the reference counter becomes 0. + * This function must be called under the cache lock in write mode. + */ +static inline void release_entry_locked(struct cache_tree *cache, struct cache_entry *entry) +{ + release_entry(cache, entry, 0); +} + +/* + * Decrement a cache_entry's reference counter and remove it from the 's + * tree if the reference counter becomes 0. + * This function must not be called under the cache lock or the shctx lock. The + * cache lock might be taken in write mode (if the entry gets deleted). + */ +static inline void release_entry_unlocked(struct cache_tree *cache, struct cache_entry *entry) +{ + release_entry(cache, entry, 1); +} + + +/* + * Compare a newly built secondary key to the one found in a cache_entry. + * Every sub-part of the key is compared to the reference through the dedicated + * comparison function of the sub-part (that might do more than a simple + * memcmp). + * Returns 0 if the keys are alike. + */ +static int secondary_key_cmp(const char *ref_key, const char *new_key) +{ + int retval = 0; + size_t idx = 0; + unsigned int offset = 0; + const struct vary_hashing_information *info; + + for (idx = 0; idx < sizeof(vary_information)/sizeof(*vary_information) && !retval; ++idx) { + info = &vary_information[idx]; + + if (info->cmp_fn) + retval = info->cmp_fn(&ref_key[offset], &new_key[offset], info->hash_length); + else + retval = memcmp(&ref_key[offset], &new_key[offset], info->hash_length); + + offset += info->hash_length; + } + + return retval; +} + +/* + * There can be multiple entries with the same primary key in the ebtree so in + * order to get the proper one out of the list, we use a secondary_key. + * This function simply iterates over all the entries with the same primary_key + * until it finds the right one. + * If is 0 then the entry is left untouched if it is found but + * is already expired, and NULL is returned. Otherwise, the expired entry is + * removed from the tree and NULL is returned. + * Returns the cache_entry in case of success, NULL otherwise. + * + * This function must be called under a cache lock, either read if + * delete_expired==0, write otherwise. + */ +struct cache_entry *get_secondary_entry(struct cache_tree *cache, struct cache_entry *entry, + const char *secondary_key, int delete_expired) +{ + struct eb32_node *node = &entry->eb; + + if (!entry->secondary_key_signature) + return NULL; + + while (entry && secondary_key_cmp(entry->secondary_key, secondary_key) != 0) { + node = eb32_next_dup(node); + + /* Make the best use of this iteration and clear expired entries + * when we find them. Calling delete_entry would be too costly + * so we simply call eb32_delete. The secondary_entry count will + * be updated when we try to insert a new entry to this list. */ + if (entry->expire <= date.tv_sec && delete_expired) { + release_entry_locked(cache, entry); + } + + entry = node ? eb32_entry(node, struct cache_entry, eb) : NULL; + } + + /* Expired entry */ + if (entry && entry->expire <= date.tv_sec) { + if (delete_expired) { + release_entry_locked(cache, entry); + } + entry = NULL; + } + + return entry; +} + +static inline struct cache_tree *get_cache_tree_from_hash(struct cache *cache, unsigned int hash) +{ + if (!cache) + return NULL; + + return &cache->trees[hash % CACHE_TREE_NUM]; +} + + +/* + * Remove all expired entries from a list of duplicates. + * Return the number of alive entries in the list and sets dup_tail to the + * current last item of the list. + * + * This function must be called under a cache write lock. + */ +static unsigned int clear_expired_duplicates(struct cache_tree *cache, struct eb32_node **dup_tail) +{ + unsigned int entry_count = 0; + struct cache_entry *entry = NULL; + struct eb32_node *prev = *dup_tail; + struct eb32_node *tail = NULL; + + while (prev) { + entry = container_of(prev, struct cache_entry, eb); + prev = eb32_prev_dup(prev); + if (entry->expire <= date.tv_sec) { + release_entry_locked(cache, entry); + } + else { + if (!tail) + tail = &entry->eb; + ++entry_count; + } + } + + *dup_tail = tail; + + return entry_count; +} + + +/* + * This function inserts a cache_entry in the cache's ebtree. In case of + * duplicate entries (vary), it then checks that the number of entries did not + * reach the max number of secondary entries. If this entry should not have been + * created, remove it. + * In the regular case (unique entries), this function does not do more than a + * simple insert. In case of secondary entries, it will at most cost an + * insertion+max_sec_entries time checks and entry deletion. + * Returns the newly inserted node in case of success, NULL otherwise. + * + * This function must be called under a cache write lock. + */ +static struct eb32_node *insert_entry(struct cache *cache, struct cache_tree *tree, struct cache_entry *new_entry) +{ + struct eb32_node *prev = NULL; + struct cache_entry *entry = NULL; + unsigned int entry_count = 0; + unsigned int last_clear_ts = date.tv_sec; + + struct eb32_node *node = eb32_insert(&tree->entries, &new_entry->eb); + + new_entry->refcount = 1; + + /* We should not have multiple entries with the same primary key unless + * the entry has a non null vary signature. */ + if (!new_entry->secondary_key_signature) + return node; + + prev = eb32_prev_dup(node); + if (prev != NULL) { + /* The last entry of a duplicate list should contain the current + * number of entries in the list. */ + entry = container_of(prev, struct cache_entry, eb); + entry_count = entry->secondary_entries_count; + last_clear_ts = entry->last_clear_ts; + + if (entry_count >= cache->max_secondary_entries) { + /* Some entries of the duplicate list might be expired so + * we will iterate over all the items in order to free some + * space. In order to avoid going over the same list too + * often, we first check the timestamp of the last check + * performed. */ + if (last_clear_ts == date.tv_sec) { + /* Too many entries for this primary key, clear the + * one that was inserted. */ + release_entry_locked(tree, entry); + return NULL; + } + + entry_count = clear_expired_duplicates(tree, &prev); + if (entry_count >= cache->max_secondary_entries) { + /* Still too many entries for this primary key, delete + * the newly inserted one. */ + entry = container_of(prev, struct cache_entry, eb); + entry->last_clear_ts = date.tv_sec; + release_entry_locked(tree, entry); + return NULL; + } + } + } + + new_entry->secondary_entries_count = entry_count + 1; + new_entry->last_clear_ts = last_clear_ts; + + return node; +} + + +/* + * This function removes an entry from the ebtree. If the entry was a duplicate + * (in case of Vary), it updates the secondary entry counter in another + * duplicate entry (the last entry of the dup list). + * + * This function must be called under a cache write lock. + */ +static void delete_entry(struct cache_entry *del_entry) +{ + struct eb32_node *prev = NULL, *next = NULL; + struct cache_entry *entry = NULL; + struct eb32_node *last = NULL; + + /* The entry might have been removed from the cache before. In such a + * case calling eb32_next_dup would crash. */ + if (del_entry->secondary_key_signature && del_entry->eb.key != 0) { + next = &del_entry->eb; + + /* Look for last entry of the duplicates list. */ + while ((next = eb32_next_dup(next))) { + last = next; + } + + if (last) { + entry = container_of(last, struct cache_entry, eb); + --entry->secondary_entries_count; + } + else { + /* The current entry is the last one, look for the + * previous one to update its counter. */ + prev = eb32_prev_dup(&del_entry->eb); + if (prev) { + entry = container_of(prev, struct cache_entry, eb); + entry->secondary_entries_count = del_entry->secondary_entries_count - 1; + } + } + } + eb32_delete(&del_entry->eb); + del_entry->eb.key = 0; +} + + +static inline struct shared_context *shctx_ptr(struct cache *cache) +{ + return (struct shared_context *)((unsigned char *)cache - offsetof(struct shared_context, data)); +} + +static inline struct shared_block *block_ptr(struct cache_entry *entry) +{ + return (struct shared_block *)((unsigned char *)entry - offsetof(struct shared_block, data)); +} + + + +static int +cache_store_init(struct proxy *px, struct flt_conf *fconf) +{ + fconf->flags |= FLT_CFG_FL_HTX; + return 0; +} + +static void +cache_store_deinit(struct proxy *px, struct flt_conf *fconf) +{ + struct cache_flt_conf *cconf = fconf->conf; + + if (!(cconf->flags & CACHE_FLT_INIT)) + free(cconf->c.name); + free(cconf); +} + +static int +cache_store_check(struct proxy *px, struct flt_conf *fconf) +{ + struct cache_flt_conf *cconf = fconf->conf; + struct flt_conf *f; + struct cache *cache; + int comp = 0; + + /* Find the cache corresponding to the name in the filter config. The + * cache will not be referenced now in the filter config because it is + * not fully allocated. This step will be performed during the cache + * post_check. + */ + list_for_each_entry(cache, &caches_config, list) { + if (strcmp(cache->id, cconf->c.name) == 0) + goto found; + } + + ha_alert("config: %s '%s': unable to find the cache '%s' referenced by the filter 'cache'.\n", + proxy_type_str(px), px->id, (char *)cconf->c.name); + return 1; + + found: + /* Here points on the cache the filter must use and + * points on the cache filter configuration. */ + + /* Check all filters for proxy to know if the compression is + * enabled and if it is after the cache. When the compression is before + * the cache, an error is returned. Also check if the cache filter must + * be explicitly declaired or not. */ + list_for_each_entry(f, &px->filter_configs, list) { + if (f == fconf) { + /* The compression filter must be evaluated after the cache. */ + if (comp) { + ha_alert("config: %s '%s': unable to enable the compression filter before " + "the cache '%s'.\n", proxy_type_str(px), px->id, cache->id); + return 1; + } + } + else if (f->id == http_comp_flt_id) + comp = 1; + else if (f->id == fcgi_flt_id) + continue; + else if ((f->id != fconf->id) && (cconf->flags & CACHE_FLT_F_IMPLICIT_DECL)) { + /* Implicit declaration is only allowed with the + * compression and fcgi. For other filters, an implicit + * declaration is required. */ + ha_alert("config: %s '%s': require an explicit filter declaration " + "to use the cache '%s'.\n", proxy_type_str(px), px->id, cache->id); + return 1; + } + + } + return 0; +} + +static int +cache_store_strm_init(struct stream *s, struct filter *filter) +{ + struct cache_st *st; + + st = pool_alloc(pool_head_cache_st); + if (st == NULL) + return -1; + + st->first_block = NULL; + filter->ctx = st; + + /* Register post-analyzer on AN_RES_WAIT_HTTP */ + filter->post_analyzers |= AN_RES_WAIT_HTTP; + return 1; +} + +static void +cache_store_strm_deinit(struct stream *s, struct filter *filter) +{ + struct cache_st *st = filter->ctx; + struct cache_flt_conf *cconf = FLT_CONF(filter); + struct cache *cache = cconf->c.cache; + struct shared_context *shctx = shctx_ptr(cache); + + /* Everything should be released in the http_end filter, but we need to do it + * there too, in case of errors */ + if (st && st->first_block) { + struct cache_entry *object = (struct cache_entry *)st->first_block->data; + if (!object->complete) { + /* The stream was closed but the 'complete' flag was not + * set which means that cache_store_http_end was not + * called. The stream must have been closed before we + * could store the full answer in the cache. + */ + release_entry_unlocked(&cache->trees[object->eb.key % CACHE_TREE_NUM], object); + } + shctx_wrlock(shctx); + shctx_row_reattach(shctx, st->first_block); + shctx_wrunlock(shctx); + } + if (st) { + pool_free(pool_head_cache_st, st); + filter->ctx = NULL; + } +} + +static int +cache_store_post_analyze(struct stream *s, struct filter *filter, struct channel *chn, + unsigned an_bit) +{ + struct http_txn *txn = s->txn; + struct http_msg *msg = &txn->rsp; + struct cache_st *st = filter->ctx; + + if (an_bit != AN_RES_WAIT_HTTP) + goto end; + + /* Here we need to check if any compression filter precedes the cache + * filter. This is only possible when the compression is configured in + * the frontend while the cache filter is configured on the + * backend. This case cannot be detected during HAProxy startup. So in + * such cases, the cache is disabled. + */ + if (st && (msg->flags & HTTP_MSGF_COMPRESSING)) { + pool_free(pool_head_cache_st, st); + filter->ctx = NULL; + } + + end: + return 1; +} + +static int +cache_store_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg) +{ + struct cache_st *st = filter->ctx; + + if (!(msg->chn->flags & CF_ISRESP) || !st) + return 1; + + if (st->first_block) + register_data_filter(s, msg->chn, filter); + return 1; +} + +static inline void disable_cache_entry(struct cache_st *st, + struct filter *filter, struct shared_context *shctx) +{ + struct cache_entry *object; + struct cache *cache = (struct cache*)shctx->data; + + object = (struct cache_entry *)st->first_block->data; + filter->ctx = NULL; /* disable cache */ + release_entry_unlocked(&cache->trees[object->eb.key % CACHE_TREE_NUM], object); + shctx_wrlock(shctx); + shctx_row_reattach(shctx, st->first_block); + shctx_wrunlock(shctx); + pool_free(pool_head_cache_st, st); +} + +static int +cache_store_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg, + unsigned int offset, unsigned int len) +{ + struct cache_flt_conf *cconf = FLT_CONF(filter); + struct shared_context *shctx = shctx_ptr(cconf->c.cache); + struct cache_st *st = filter->ctx; + struct htx *htx = htxbuf(&msg->chn->buf); + struct htx_blk *blk; + struct shared_block *fb; + struct htx_ret htxret; + unsigned int orig_len, to_forward; + int ret; + + if (!len) + return len; + + if (!st->first_block) { + unregister_data_filter(s, msg->chn, filter); + return len; + } + + chunk_reset(&trash); + orig_len = len; + to_forward = 0; + + htxret = htx_find_offset(htx, offset); + blk = htxret.blk; + offset = htxret.ret; + for (; blk && len; blk = htx_get_next_blk(htx, blk)) { + enum htx_blk_type type = htx_get_blk_type(blk); + uint32_t info, sz = htx_get_blksz(blk); + struct ist v; + + switch (type) { + case HTX_BLK_UNUSED: + break; + + case HTX_BLK_DATA: + v = htx_get_blk_value(htx, blk); + v = istadv(v, offset); + v = isttrim(v, len); + + info = (type << 28) + v.len; + chunk_memcat(&trash, (char *)&info, sizeof(info)); + chunk_istcat(&trash, v); + to_forward += v.len; + len -= v.len; + break; + + default: + /* Here offset must always be 0 because only + * DATA blocks can be partially transferred. */ + if (offset) + goto no_cache; + if (sz > len) + goto end; + + chunk_memcat(&trash, (char *)&blk->info, sizeof(blk->info)); + chunk_memcat(&trash, htx_get_blk_ptr(htx, blk), sz); + to_forward += sz; + len -= sz; + break; + } + + offset = 0; + } + + end: + + fb = shctx_row_reserve_hot(shctx, st->first_block, trash.data); + if (!fb) { + goto no_cache; + } + + ret = shctx_row_data_append(shctx, st->first_block, + (unsigned char *)b_head(&trash), b_data(&trash)); + if (ret < 0) + goto no_cache; + + return to_forward; + + no_cache: + disable_cache_entry(st, filter, shctx); + unregister_data_filter(s, msg->chn, filter); + return orig_len; +} + +static int +cache_store_http_end(struct stream *s, struct filter *filter, + struct http_msg *msg) +{ + struct cache_st *st = filter->ctx; + struct cache_flt_conf *cconf = FLT_CONF(filter); + struct cache *cache = cconf->c.cache; + struct shared_context *shctx = shctx_ptr(cache); + struct cache_entry *object; + + if (!(msg->chn->flags & CF_ISRESP)) + return 1; + + if (st && st->first_block) { + + object = (struct cache_entry *)st->first_block->data; + + shctx_wrlock(shctx); + /* The whole payload was cached, the entry can now be used. */ + object->complete = 1; + /* remove from the hotlist */ + shctx_row_reattach(shctx, st->first_block); + shctx_wrunlock(shctx); + + } + if (st) { + pool_free(pool_head_cache_st, st); + filter->ctx = NULL; + } + + return 1; +} + + /* + * This intends to be used when checking HTTP headers for some + * word=value directive. Return a pointer to the first character of value, if + * the word was not found or if there wasn't any value assigned to it return NULL + */ +char *directive_value(const char *sample, int slen, const char *word, int wlen) +{ + int st = 0; + + if (slen < wlen) + return 0; + + while (wlen) { + char c = *sample ^ *word; + if (c && c != ('A' ^ 'a')) + return NULL; + sample++; + word++; + slen--; + wlen--; + } + + while (slen) { + if (st == 0) { + if (*sample != '=') + return NULL; + sample++; + slen--; + st = 1; + continue; + } else { + return (char *)sample; + } + } + + return NULL; +} + +/* + * Return the maxage in seconds of an HTTP response. + * The returned value will always take the cache's configuration into account + * (cache->maxage) but the actual max age of the response will be set in the + * true_maxage parameter. It will be used to determine if a response is already + * stale or not. + * Compute the maxage using either: + * - the assigned max-age of the cache + * - the s-maxage directive + * - the max-age directive + * - (Expires - Data) headers + * - the default-max-age of the cache + * + */ +int http_calc_maxage(struct stream *s, struct cache *cache, int *true_maxage) +{ + struct htx *htx = htxbuf(&s->res.buf); + struct http_hdr_ctx ctx = { .blk = NULL }; + long smaxage = -1; + long maxage = -1; + int expires = -1; + struct tm tm = {}; + time_t expires_val = 0; + char *endptr = NULL; + int offset = 0; + + /* The Cache-Control max-age and s-maxage directives should be followed by + * a positive numerical value (see RFC 7234#5.2.1.1). According to the + * specs, a sender "should not" generate a quoted-string value but we will + * still accept this format since it isn't strictly forbidden. */ + while (http_find_header(htx, ist("cache-control"), &ctx, 0)) { + char *value; + + value = directive_value(ctx.value.ptr, ctx.value.len, "s-maxage", 8); + if (value) { + struct buffer *chk = get_trash_chunk(); + + chunk_memcat(chk, value, ctx.value.len - 8 + 1); + chunk_memcat(chk, "", 1); + offset = (*chk->area == '"') ? 1 : 0; + smaxage = strtol(chk->area + offset, &endptr, 10); + if (unlikely(smaxage < 0 || endptr == chk->area + offset)) + return -1; + } + + value = directive_value(ctx.value.ptr, ctx.value.len, "max-age", 7); + if (value) { + struct buffer *chk = get_trash_chunk(); + + chunk_memcat(chk, value, ctx.value.len - 7 + 1); + chunk_memcat(chk, "", 1); + offset = (*chk->area == '"') ? 1 : 0; + maxage = strtol(chk->area + offset, &endptr, 10); + if (unlikely(maxage < 0 || endptr == chk->area + offset)) + return -1; + } + } + + /* Look for Expires header if no s-maxage or max-age Cache-Control data + * was found. */ + if (maxage == -1 && smaxage == -1) { + ctx.blk = NULL; + if (http_find_header(htx, ist("expires"), &ctx, 1)) { + if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) { + expires_val = my_timegm(&tm); + /* A request having an expiring date earlier + * than the current date should be considered as + * stale. */ + expires = (expires_val >= date.tv_sec) ? + (expires_val - date.tv_sec) : 0; + } + else { + /* Following RFC 7234#5.3, an invalid date + * format must be treated as a date in the past + * so the cache entry must be seen as already + * expired. */ + expires = 0; + } + } + } + + + if (smaxage > 0) { + if (true_maxage) + *true_maxage = smaxage; + return MIN(smaxage, cache->maxage); + } + + if (maxage > 0) { + if (true_maxage) + *true_maxage = maxage; + return MIN(maxage, cache->maxage); + } + + if (expires >= 0) { + if (true_maxage) + *true_maxage = expires; + return MIN(expires, cache->maxage); + } + + return cache->maxage; + +} + + +static void cache_free_blocks(struct shared_block *first, void *data) +{ + struct cache_entry *object = (struct cache_entry *)first->data; + struct cache *cache = (struct cache *)data; + struct cache_tree *cache_tree; + + if (object->eb.key) { + object->complete = 0; + cache_tree = &cache->trees[object->eb.key % CACHE_TREE_NUM]; + retain_entry(object); + HA_SPIN_LOCK(CACHE_LOCK, &cache_tree->cleanup_lock); + LIST_INSERT(&cache_tree->cleanup_list, &object->cleanup_list); + HA_SPIN_UNLOCK(CACHE_LOCK, &cache_tree->cleanup_lock); + } +} + +static void cache_reserve_finish(struct shared_context *shctx) +{ + struct cache_entry *object, *back; + struct cache *cache = (struct cache *)shctx->data; + struct cache_tree *cache_tree; + int cache_tree_idx = 0; + + for (; cache_tree_idx < CACHE_TREE_NUM; ++cache_tree_idx) { + cache_tree = &cache->trees[cache_tree_idx]; + + cache_wrlock(cache_tree); + HA_SPIN_LOCK(CACHE_LOCK, &cache_tree->cleanup_lock); + + list_for_each_entry_safe(object, back, &cache_tree->cleanup_list, cleanup_list) { + LIST_DELETE(&object->cleanup_list); + /* + * At this point we locked the cache tree in write mode + * so no new thread could retain the current entry + * because the only two places where it can happen is in + * the cache_use case which is under cache_rdlock and + * the reserve_hot case which would require the + * corresponding block to still be in the avail list, + * which is impossible (we reserved it for a thread and + * took it out of the avail list already). The only two + * references are then the default one (upon cache_entry + * creation) and the one in this cleanup list. + */ + BUG_ON(object->refcount > 2); + delete_entry(object); + } + + HA_SPIN_UNLOCK(CACHE_LOCK, &cache_tree->cleanup_lock); + cache_wrunlock(cache_tree); + } +} + + +/* As per RFC 7234#4.3.2, in case of "If-Modified-Since" conditional request, the + * date value should be compared to a date determined by in a previous response (for + * the same entity). This date could either be the "Last-Modified" value, or the "Date" + * value of the response's reception time (by decreasing order of priority). */ +static time_t get_last_modified_time(struct htx *htx) +{ + time_t last_modified = 0; + struct http_hdr_ctx ctx = { .blk = NULL }; + struct tm tm = {}; + + if (http_find_header(htx, ist("last-modified"), &ctx, 1)) { + if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) { + last_modified = my_timegm(&tm); + } + } + + if (!last_modified) { + ctx.blk = NULL; + if (http_find_header(htx, ist("date"), &ctx, 1)) { + if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) { + last_modified = my_timegm(&tm); + } + } + } + + /* Fallback on the current time if no "Last-Modified" or "Date" header + * was found. */ + if (!last_modified) + last_modified = date.tv_sec; + + return last_modified; +} + +/* + * Checks the vary header's value. The headers on which vary should be applied + * must be explicitly supported in the vary_information array (see cache.c). If + * any other header is mentioned, we won't store the response. + * Returns 1 if Vary-based storage can work, 0 otherwise. + */ +static int http_check_vary_header(struct htx *htx, unsigned int *vary_signature) +{ + unsigned int vary_idx; + unsigned int vary_info_count; + const struct vary_hashing_information *vary_info; + struct http_hdr_ctx ctx = { .blk = NULL }; + + int retval = 1; + + *vary_signature = 0; + + vary_info_count = sizeof(vary_information)/sizeof(*vary_information); + while (retval && http_find_header(htx, ist("Vary"), &ctx, 0)) { + for (vary_idx = 0; vary_idx < vary_info_count; ++vary_idx) { + vary_info = &vary_information[vary_idx]; + if (isteqi(ctx.value, vary_info->hdr_name)) { + *vary_signature |= vary_info->value; + break; + } + } + retval = (vary_idx < vary_info_count); + } + + return retval; +} + + +/* + * Look for the accept-encoding part of the secondary_key and replace the + * encoding bitmap part of the hash with the actual encoding of the response, + * extracted from the content-encoding header value. + * Responses that have an unknown encoding will not be cached if they also + * "vary" on the accept-encoding value. + * Returns 0 if we found a known encoding in the response, -1 otherwise. + */ +static int set_secondary_key_encoding(struct htx *htx, char *secondary_key) +{ + unsigned int resp_encoding_bitmap = 0; + const struct vary_hashing_information *info = vary_information; + unsigned int offset = 0; + unsigned int count = 0; + unsigned int hash_info_count = sizeof(vary_information)/sizeof(*vary_information); + unsigned int encoding_value; + struct http_hdr_ctx ctx = { .blk = NULL }; + + /* Look for the accept-encoding part of the secondary_key. */ + while (count < hash_info_count && info->value != VARY_ACCEPT_ENCODING) { + offset += info->hash_length; + ++info; + ++count; + } + + if (count == hash_info_count) + return -1; + + while (http_find_header(htx, ist("content-encoding"), &ctx, 0)) { + if (parse_encoding_value(ctx.value, &encoding_value, NULL)) + return -1; /* Do not store responses with an unknown encoding */ + resp_encoding_bitmap |= encoding_value; + } + + if (!resp_encoding_bitmap) + resp_encoding_bitmap |= VARY_ENCODING_IDENTITY; + + /* Rewrite the bitmap part of the hash with the new bitmap that only + * corresponds the the response's encoding. */ + write_u32(secondary_key + offset, resp_encoding_bitmap); + + return 0; +} + + +/* + * This function will store the headers of the response in a buffer and then + * register a filter to store the data + */ +enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + int effective_maxage = 0; + int true_maxage = 0; + struct http_txn *txn = s->txn; + struct http_msg *msg = &txn->rsp; + struct filter *filter; + struct shared_block *first = NULL; + struct cache_flt_conf *cconf = rule->arg.act.p[0]; + struct cache *cache = cconf->c.cache; + struct shared_context *shctx = shctx_ptr(cache); + struct cache_st *cache_ctx = NULL; + struct cache_entry *object, *old; + unsigned int key = read_u32(txn->cache_hash); + struct htx *htx; + struct http_hdr_ctx ctx; + size_t hdrs_len = 0; + int32_t pos; + unsigned int vary_signature = 0; + struct cache_tree *cache_tree = NULL; + + /* Don't cache if the response came from a cache */ + if ((obj_type(s->target) == OBJ_TYPE_APPLET) && + s->target == &http_cache_applet.obj_type) { + goto out; + } + + /* cache only HTTP/1.1 */ + if (!(txn->req.flags & HTTP_MSGF_VER_11)) + goto out; + + cache_tree = get_cache_tree_from_hash(cache, read_u32(txn->cache_hash)); + + /* cache only GET method */ + if (txn->meth != HTTP_METH_GET) { + /* In case of successful unsafe method on a stored resource, the + * cached entry must be invalidated (see RFC7234#4.4). + * A "non-error response" is one with a 2xx (Successful) or 3xx + * (Redirection) status code. */ + if (txn->status >= 200 && txn->status < 400) { + switch (txn->meth) { + case HTTP_METH_OPTIONS: + case HTTP_METH_GET: + case HTTP_METH_HEAD: + case HTTP_METH_TRACE: + break; + + default: /* Any unsafe method */ + /* Discard any corresponding entry in case of successful + * unsafe request (such as PUT, POST or DELETE). */ + cache_wrlock(cache_tree); + + old = get_entry(cache_tree, txn->cache_hash, 1); + if (old) + release_entry_locked(cache_tree, old); + cache_wrunlock(cache_tree); + } + } + goto out; + } + + /* cache key was not computed */ + if (!key) + goto out; + + /* cache only 200 status code */ + if (txn->status != 200) + goto out; + + /* Find the corresponding filter instance for the current stream */ + list_for_each_entry(filter, &s->strm_flt.filters, list) { + if (FLT_ID(filter) == cache_store_flt_id && FLT_CONF(filter) == cconf) { + /* No filter ctx, don't cache anything */ + if (!filter->ctx) + goto out; + cache_ctx = filter->ctx; + break; + } + } + + /* from there, cache_ctx is always defined */ + htx = htxbuf(&s->res.buf); + + /* Do not cache too big objects. */ + if ((msg->flags & HTTP_MSGF_CNT_LEN) && shctx->max_obj_size > 0 && + htx->data + htx->extra > shctx->max_obj_size) + goto out; + + /* Only a subset of headers are supported in our Vary implementation. If + * any other header is present in the Vary header value, we won't be + * able to use the cache. Likewise, if Vary header support is disabled, + * avoid caching responses that contain such a header. */ + ctx.blk = NULL; + if (cache->vary_processing_enabled) { + if (!http_check_vary_header(htx, &vary_signature)) + goto out; + if (vary_signature) { + /* If something went wrong during the secondary key + * building, do not store the response. */ + if (!(txn->flags & TX_CACHE_HAS_SEC_KEY)) + goto out; + http_request_reduce_secondary_key(vary_signature, txn->cache_secondary_hash); + } + } + else if (http_find_header(htx, ist("Vary"), &ctx, 0)) { + goto out; + } + + http_check_response_for_cacheability(s, &s->res); + + if (!(txn->flags & TX_CACHEABLE) || !(txn->flags & TX_CACHE_COOK)) + goto out; + + cache_wrlock(cache_tree); + old = get_entry(cache_tree, txn->cache_hash, 1); + if (old) { + if (vary_signature) + old = get_secondary_entry(cache_tree, old, + txn->cache_secondary_hash, 1); + if (old) { + if (!old->complete) { + /* An entry with the same primary key is already being + * created, we should not try to store the current + * response because it will waste space in the cache. */ + cache_wrunlock(cache_tree); + goto out; + } + release_entry_locked(cache_tree, old); + } + } + cache_wrunlock(cache_tree); + + first = shctx_row_reserve_hot(shctx, NULL, sizeof(struct cache_entry)); + if (!first) { + goto out; + } + + /* the received memory is not initialized, we need at least to mark + * the object as not indexed yet. + */ + object = (struct cache_entry *)first->data; + memset(object, 0, sizeof(*object)); + object->eb.key = key; + object->secondary_key_signature = vary_signature; + /* We need to temporarily set a valid expiring time until the actual one + * is set by the end of this function (in case of concurrent accesses to + * the same resource). This way the second access will find an existing + * but not yet usable entry in the tree and will avoid storing its data. */ + object->expire = date.tv_sec + 2; + + memcpy(object->hash, txn->cache_hash, sizeof(object->hash)); + if (vary_signature) + memcpy(object->secondary_key, txn->cache_secondary_hash, HTTP_CACHE_SEC_KEY_LEN); + + cache_wrlock(cache_tree); + /* Insert the entry in the tree even if the payload is not cached yet. */ + if (insert_entry(cache, cache_tree, object) != &object->eb) { + object->eb.key = 0; + cache_wrunlock(cache_tree); + goto out; + } + cache_wrunlock(cache_tree); + + /* reserve space for the cache_entry structure */ + first->len = sizeof(struct cache_entry); + first->last_append = NULL; + + /* Determine the entry's maximum age (taking into account the cache's + * configuration) as well as the response's explicit max age (extracted + * from cache-control directives or the expires header). */ + effective_maxage = http_calc_maxage(s, cache, &true_maxage); + + ctx.blk = NULL; + if (http_find_header(htx, ist("Age"), &ctx, 0)) { + long long hdr_age; + if (!strl2llrc(ctx.value.ptr, ctx.value.len, &hdr_age) && hdr_age > 0) { + if (unlikely(hdr_age > CACHE_ENTRY_MAX_AGE)) + hdr_age = CACHE_ENTRY_MAX_AGE; + /* A response with an Age value greater than its + * announced max age is stale and should not be stored. */ + object->age = hdr_age; + if (unlikely(object->age > true_maxage)) + goto out; + } + else + goto out; + http_remove_header(htx, &ctx); + } + + /* Build a last-modified time that will be stored in the cache_entry and + * compared to a future If-Modified-Since client header. */ + object->last_modified = get_last_modified_time(htx); + + chunk_reset(&trash); + for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) { + struct htx_blk *blk = htx_get_blk(htx, pos); + enum htx_blk_type type = htx_get_blk_type(blk); + uint32_t sz = htx_get_blksz(blk); + + hdrs_len += sizeof(*blk) + sz; + chunk_memcat(&trash, (char *)&blk->info, sizeof(blk->info)); + chunk_memcat(&trash, htx_get_blk_ptr(htx, blk), sz); + + /* Look for optional ETag header. + * We need to store the offset of the ETag value in order for + * future conditional requests to be able to perform ETag + * comparisons. */ + if (type == HTX_BLK_HDR) { + struct ist header_name = htx_get_blk_name(htx, blk); + if (isteq(header_name, ist("etag"))) { + object->etag_length = sz - istlen(header_name); + object->etag_offset = sizeof(struct cache_entry) + b_data(&trash) - sz + istlen(header_name); + } + } + if (type == HTX_BLK_EOH) + break; + } + + /* Do not cache objects if the headers are too big. */ + if (hdrs_len > htx->size - global.tune.maxrewrite) + goto out; + + /* If the response has a secondary_key, fill its key part related to + * encodings with the actual encoding of the response. This way any + * subsequent request having the same primary key will have its accepted + * encodings tested upon the cached response's one. + * We will not cache a response that has an unknown encoding (not + * explicitly supported in parse_encoding_value function). */ + if (cache->vary_processing_enabled && vary_signature) + if (set_secondary_key_encoding(htx, object->secondary_key)) + goto out; + + if (!shctx_row_reserve_hot(shctx, first, trash.data)) { + goto out; + } + + /* cache the headers in a http action because it allows to chose what + * to cache, for example you might want to cache a response before + * modifying some HTTP headers, or on the contrary after modifying + * those headers. + */ + /* does not need to be locked because it's in the "hot" list, + * copy the headers */ + if (shctx_row_data_append(shctx, first, (unsigned char *)trash.area, trash.data) < 0) + goto out; + + /* register the buffer in the filter ctx for filling it with data*/ + if (cache_ctx) { + cache_ctx->first_block = first; + LIST_INIT(&cache_ctx->detached_head); + /* store latest value and expiration time */ + object->latest_validation = date.tv_sec; + object->expire = date.tv_sec + effective_maxage; + return ACT_RET_CONT; + } + +out: + /* if does not cache */ + if (first) { + first->len = 0; + if (object->eb.key) { + release_entry_unlocked(cache_tree, object); + } + shctx_wrlock(shctx); + shctx_row_reattach(shctx, first); + shctx_wrunlock(shctx); + } + + return ACT_RET_CONT; +} + +#define HTX_CACHE_INIT 0 /* Initial state. */ +#define HTX_CACHE_HEADER 1 /* Cache entry headers forwarding */ +#define HTX_CACHE_DATA 2 /* Cache entry data forwarding */ +#define HTX_CACHE_EOM 3 /* Cache entry completely forwarded. Finish the HTX message */ +#define HTX_CACHE_END 4 /* Cache entry treatment terminated */ + +static void http_cache_applet_release(struct appctx *appctx) +{ + struct cache_appctx *ctx = appctx->svcctx; + struct cache_flt_conf *cconf = appctx->rule->arg.act.p[0]; + struct cache_entry *cache_ptr = ctx->entry; + struct cache *cache = cconf->c.cache; + struct shared_context *shctx = shctx_ptr(cache); + struct shared_block *first = block_ptr(cache_ptr); + + release_entry(ctx->cache_tree, cache_ptr, 1); + + shctx_wrlock(shctx); + shctx_row_reattach(shctx, first); + shctx_wrunlock(shctx); +} + + +static unsigned int htx_cache_dump_blk(struct appctx *appctx, struct htx *htx, enum htx_blk_type type, + uint32_t info, struct shared_block *shblk, unsigned int offset) +{ + struct cache_appctx *ctx = appctx->svcctx; + struct cache_flt_conf *cconf = appctx->rule->arg.act.p[0]; + struct shared_context *shctx = shctx_ptr(cconf->c.cache); + struct htx_blk *blk; + char *ptr; + unsigned int max, total; + uint32_t blksz; + + max = htx_get_max_blksz(htx, + channel_htx_recv_max(sc_ic(appctx_sc(appctx)), htx)); + if (!max) + return 0; + blksz = ((type == HTX_BLK_HDR || type == HTX_BLK_TLR) + ? (info & 0xff) + ((info >> 8) & 0xfffff) + : info & 0xfffffff); + if (blksz > max) + return 0; + + blk = htx_add_blk(htx, type, blksz); + if (!blk) + return 0; + + blk->info = info; + total = 4; + ptr = htx_get_blk_ptr(htx, blk); + while (blksz) { + max = MIN(blksz, shctx->block_size - offset); + memcpy(ptr, (const char *)shblk->data + offset, max); + offset += max; + blksz -= max; + total += max; + ptr += max; + if (blksz || offset == shctx->block_size) { + shblk = LIST_NEXT(&shblk->list, typeof(shblk), list); + offset = 0; + } + } + ctx->offset = offset; + ctx->next = shblk; + ctx->sent += total; + return total; +} + +static unsigned int htx_cache_dump_data_blk(struct appctx *appctx, struct htx *htx, + uint32_t info, struct shared_block *shblk, unsigned int offset) +{ + struct cache_appctx *ctx = appctx->svcctx; + struct cache_flt_conf *cconf = appctx->rule->arg.act.p[0]; + struct shared_context *shctx = shctx_ptr(cconf->c.cache); + unsigned int max, total, rem_data; + uint32_t blksz; + + max = htx_get_max_blksz(htx, + channel_htx_recv_max(sc_ic(appctx_sc(appctx)), htx)); + if (!max) + return 0; + + rem_data = 0; + if (ctx->rem_data) { + blksz = ctx->rem_data; + total = 0; + } + else { + blksz = (info & 0xfffffff); + total = 4; + } + if (blksz > max) { + rem_data = blksz - max; + blksz = max; + } + + while (blksz) { + size_t sz; + + max = MIN(blksz, shctx->block_size - offset); + sz = htx_add_data(htx, ist2(shblk->data + offset, max)); + offset += sz; + blksz -= sz; + total += sz; + if (sz < max) + break; + if (blksz || offset == shctx->block_size) { + shblk = LIST_NEXT(&shblk->list, typeof(shblk), list); + offset = 0; + } + } + + ctx->offset = offset; + ctx->next = shblk; + ctx->sent += total; + ctx->rem_data = rem_data + blksz; + return total; +} + +static size_t htx_cache_dump_msg(struct appctx *appctx, struct htx *htx, unsigned int len, + enum htx_blk_type mark) +{ + struct cache_appctx *ctx = appctx->svcctx; + struct cache_flt_conf *cconf = appctx->rule->arg.act.p[0]; + struct shared_context *shctx = shctx_ptr(cconf->c.cache); + struct shared_block *shblk; + unsigned int offset, sz; + unsigned int ret, total = 0; + + while (len) { + enum htx_blk_type type; + uint32_t info; + + shblk = ctx->next; + offset = ctx->offset; + if (ctx->rem_data) { + type = HTX_BLK_DATA; + info = 0; + goto add_data_blk; + } + + /* Get info of the next HTX block. May be split on 2 shblk */ + sz = MIN(4, shctx->block_size - offset); + memcpy((char *)&info, (const char *)shblk->data + offset, sz); + offset += sz; + if (sz < 4) { + shblk = LIST_NEXT(&shblk->list, typeof(shblk), list); + memcpy(((char *)&info)+sz, (const char *)shblk->data, 4 - sz); + offset = (4 - sz); + } + + /* Get payload of the next HTX block and insert it. */ + type = (info >> 28); + if (type != HTX_BLK_DATA) + ret = htx_cache_dump_blk(appctx, htx, type, info, shblk, offset); + else { + add_data_blk: + ret = htx_cache_dump_data_blk(appctx, htx, info, shblk, offset); + } + + if (!ret) + break; + total += ret; + len -= ret; + + if (ctx->rem_data || type == mark) + break; + } + + return total; +} + +static int htx_cache_add_age_hdr(struct appctx *appctx, struct htx *htx) +{ + struct cache_appctx *ctx = appctx->svcctx; + struct cache_entry *cache_ptr = ctx->entry; + unsigned int age; + char *end; + + chunk_reset(&trash); + age = MAX(0, (int)(date.tv_sec - cache_ptr->latest_validation)) + cache_ptr->age; + if (unlikely(age > CACHE_ENTRY_MAX_AGE)) + age = CACHE_ENTRY_MAX_AGE; + end = ultoa_o(age, b_head(&trash), b_size(&trash)); + b_set_data(&trash, end - b_head(&trash)); + if (!http_add_header(htx, ist("Age"), ist2(b_head(&trash), b_data(&trash)))) + return 0; + return 1; +} + +static void http_cache_io_handler(struct appctx *appctx) +{ + struct cache_appctx *ctx = appctx->svcctx; + struct cache_entry *cache_ptr = ctx->entry; + struct shared_block *first = block_ptr(cache_ptr); + struct stconn *sc = appctx_sc(appctx); + struct channel *req = sc_oc(sc); + struct channel *res = sc_ic(sc); + struct htx *req_htx, *res_htx; + struct buffer *errmsg; + unsigned int len; + size_t ret, total = 0; + + res_htx = htx_from_buf(&res->buf); + total = res_htx->data; + + if (unlikely(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) + goto out; + + /* Check if the input buffer is available. */ + if (!b_size(&res->buf)) { + sc_need_room(sc, 0); + goto out; + } + + if (appctx->st0 == HTX_CACHE_INIT) { + ctx->next = block_ptr(cache_ptr); + ctx->offset = sizeof(*cache_ptr); + ctx->sent = 0; + ctx->rem_data = 0; + appctx->st0 = HTX_CACHE_HEADER; + } + + if (appctx->st0 == HTX_CACHE_HEADER) { + /* Headers must be dump at once. Otherwise it is an error */ + len = first->len - sizeof(*cache_ptr) - ctx->sent; + ret = htx_cache_dump_msg(appctx, res_htx, len, HTX_BLK_EOH); + if (!ret || (htx_get_tail_type(res_htx) != HTX_BLK_EOH) || + !htx_cache_add_age_hdr(appctx, res_htx)) + goto error; + + /* In case of a conditional request, we might want to send a + * "304 Not Modified" response instead of the stored data. */ + if (ctx->send_notmodified) { + if (!http_replace_res_status(res_htx, ist("304"), ist("Not Modified"))) { + /* If replacing the status code fails we need to send the full response. */ + ctx->send_notmodified = 0; + } + } + + /* Skip response body for HEAD requests or in case of "304 Not + * Modified" response. */ + if (__sc_strm(sc)->txn->meth == HTTP_METH_HEAD || ctx->send_notmodified) + appctx->st0 = HTX_CACHE_EOM; + else + appctx->st0 = HTX_CACHE_DATA; + } + + if (appctx->st0 == HTX_CACHE_DATA) { + len = first->len - sizeof(*cache_ptr) - ctx->sent; + if (len) { + ret = htx_cache_dump_msg(appctx, res_htx, len, HTX_BLK_UNUSED); + if (ret < len) { + sc_need_room(sc, channel_htx_recv_max(res, res_htx) + 1); + goto out; + } + } + appctx->st0 = HTX_CACHE_EOM; + } + + if (appctx->st0 == HTX_CACHE_EOM) { + /* no more data are expected. */ + res_htx->flags |= HTX_FL_EOM; + se_fl_set(appctx->sedesc, SE_FL_EOI); + + appctx->st0 = HTX_CACHE_END; + } + + end: + if (appctx->st0 == HTX_CACHE_END) + se_fl_set(appctx->sedesc, SE_FL_EOS); + + out: + total = res_htx->data - total; + if (total) + channel_add_input(res, total); + htx_to_buf(res_htx, &res->buf); + + /* eat the whole request */ + if (co_data(req)) { + req_htx = htx_from_buf(&req->buf); + co_htx_skip(req, req_htx, co_data(req)); + htx_to_buf(req_htx, &req->buf); + } + return; + + error: + /* Sent and HTTP error 500 */ + b_reset(&res->buf); + errmsg = &http_err_chunks[HTTP_ERR_500]; + res->buf.data = b_data(errmsg); + memcpy(res->buf.area, b_head(errmsg), b_data(errmsg)); + res_htx = htx_from_buf(&res->buf); + + total = 0; + se_fl_set(appctx->sedesc, SE_FL_ERROR); + appctx->st0 = HTX_CACHE_END; + goto end; +} + + +static int parse_cache_rule(struct proxy *proxy, const char *name, struct act_rule *rule, char **err) +{ + struct flt_conf *fconf; + struct cache_flt_conf *cconf = NULL; + + if (!*name || strcmp(name, "if") == 0 || strcmp(name, "unless") == 0) { + memprintf(err, "expects a cache name"); + goto err; + } + + /* check if a cache filter was already registered with this cache + * name, if that's the case, must use it. */ + list_for_each_entry(fconf, &proxy->filter_configs, list) { + if (fconf->id == cache_store_flt_id) { + cconf = fconf->conf; + if (cconf && strcmp((char *)cconf->c.name, name) == 0) { + rule->arg.act.p[0] = cconf; + return 1; + } + } + } + + /* Create the filter cache config */ + cconf = calloc(1, sizeof(*cconf)); + if (!cconf) { + memprintf(err, "out of memory\n"); + goto err; + } + cconf->flags = CACHE_FLT_F_IMPLICIT_DECL; + cconf->c.name = strdup(name); + if (!cconf->c.name) { + memprintf(err, "out of memory\n"); + goto err; + } + + /* register a filter to fill the cache buffer */ + fconf = calloc(1, sizeof(*fconf)); + if (!fconf) { + memprintf(err, "out of memory\n"); + goto err; + } + fconf->id = cache_store_flt_id; + fconf->conf = cconf; + fconf->ops = &cache_ops; + LIST_APPEND(&proxy->filter_configs, &fconf->list); + + rule->arg.act.p[0] = cconf; + return 1; + + err: + free(cconf); + return 0; +} + +enum act_parse_ret parse_cache_store(const char **args, int *orig_arg, struct proxy *proxy, + struct act_rule *rule, char **err) +{ + rule->action = ACT_CUSTOM; + rule->action_ptr = http_action_store_cache; + + if (!parse_cache_rule(proxy, args[*orig_arg], rule, err)) + return ACT_RET_PRS_ERR; + + (*orig_arg)++; + return ACT_RET_PRS_OK; +} + +/* This produces a sha1 hash of the concatenation of the HTTP method, + * the first occurrence of the Host header followed by the path component + * if it begins with a slash ('/'). */ +int sha1_hosturi(struct stream *s) +{ + struct http_txn *txn = s->txn; + struct htx *htx = htxbuf(&s->req.buf); + struct htx_sl *sl; + struct http_hdr_ctx ctx; + struct ist uri; + blk_SHA_CTX sha1_ctx; + struct buffer *trash; + + trash = get_trash_chunk(); + ctx.blk = NULL; + + sl = http_get_stline(htx); + uri = htx_sl_req_uri(sl); // whole uri + if (!uri.len) + return 0; + + /* In HTTP/1, most URIs are seen in origin form ('/path/to/resource'), + * unless haproxy is deployed in front of an outbound cache. In HTTP/2, + * URIs are almost always sent in absolute form with their scheme. In + * this case, the scheme is almost always "https". In order to support + * sharing of cache objects between H1 and H2, we'll hash the absolute + * URI whenever known, or prepend "https://" + the Host header for + * relative URIs. The difference will only appear on absolute HTTP/1 + * requests sent to an origin server, which practically is never met in + * the real world so we don't care about the ability to share the same + * key here.URIs are normalized from the absolute URI to an origin form as + * well. + */ + if (!(sl->flags & HTX_SL_F_HAS_AUTHORITY)) { + chunk_istcat(trash, ist("https://")); + if (!http_find_header(htx, ist("Host"), &ctx, 0)) + return 0; + chunk_istcat(trash, ctx.value); + } + + chunk_istcat(trash, uri); + + /* hash everything */ + blk_SHA1_Init(&sha1_ctx); + blk_SHA1_Update(&sha1_ctx, trash->area, trash->data); + blk_SHA1_Final((unsigned char *)txn->cache_hash, &sha1_ctx); + + return 1; +} + +/* Looks for "If-None-Match" headers in the request and compares their value + * with the one that might have been stored in the cache_entry. If any of them + * matches, a "304 Not Modified" response should be sent instead of the cached + * data. + * Although unlikely in a GET/HEAD request, the "If-None-Match: *" syntax is + * valid and should receive a "304 Not Modified" response (RFC 7234#4.3.2). + * + * If no "If-None-Match" header was found, look for an "If-Modified-Since" + * header and compare its value (date) to the one stored in the cache_entry. + * If the request's date is later than the cached one, we also send a + * "304 Not Modified" response (see RFCs 7232#3.3 and 7234#4.3.2). + * + * Returns 1 if "304 Not Modified" should be sent, 0 otherwise. + */ +static int should_send_notmodified_response(struct cache *cache, struct htx *htx, + struct cache_entry *entry) +{ + int retval = 0; + + struct http_hdr_ctx ctx = { .blk = NULL }; + struct ist cache_entry_etag = IST_NULL; + struct buffer *etag_buffer = NULL; + int if_none_match_found = 0; + + struct tm tm = {}; + time_t if_modified_since = 0; + + /* If we find a "If-None-Match" header in the request, rebuild the + * cache_entry's ETag in order to perform comparisons. + * There could be multiple "if-none-match" header lines. */ + while (http_find_header(htx, ist("if-none-match"), &ctx, 0)) { + if_none_match_found = 1; + + /* A '*' matches everything. */ + if (isteq(ctx.value, ist("*")) != 0) { + retval = 1; + break; + } + + /* No need to rebuild an etag if none was stored in the cache. */ + if (entry->etag_length == 0) + break; + + /* Rebuild the stored ETag. */ + if (etag_buffer == NULL) { + etag_buffer = get_trash_chunk(); + + if (shctx_row_data_get(shctx_ptr(cache), block_ptr(entry), + (unsigned char*)b_orig(etag_buffer), + entry->etag_offset, entry->etag_length) == 0) { + cache_entry_etag = ist2(b_orig(etag_buffer), entry->etag_length); + } else { + /* We could not rebuild the ETag in one go, we + * won't send a "304 Not Modified" response. */ + break; + } + } + + if (http_compare_etags(cache_entry_etag, ctx.value) == 1) { + retval = 1; + break; + } + } + + /* If the request did not contain an "If-None-Match" header, we look for + * an "If-Modified-Since" header (see RFC 7232#3.3). */ + if (retval == 0 && if_none_match_found == 0) { + ctx.blk = NULL; + if (http_find_header(htx, ist("if-modified-since"), &ctx, 1)) { + if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) { + if_modified_since = my_timegm(&tm); + + /* We send a "304 Not Modified" response if the + * entry's last modified date is earlier than + * the one found in the "If-Modified-Since" + * header. */ + retval = (entry->last_modified <= if_modified_since); + } + } + } + + return retval; +} + +enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + + struct http_txn *txn = s->txn; + struct cache_entry *res, *sec_entry = NULL; + struct cache_flt_conf *cconf = rule->arg.act.p[0]; + struct cache *cache = cconf->c.cache; + struct shared_context *shctx = shctx_ptr(cache); + struct shared_block *entry_block; + + struct cache_tree *cache_tree = NULL; + + /* Ignore cache for HTTP/1.0 requests and for requests other than GET + * and HEAD */ + if (!(txn->req.flags & HTTP_MSGF_VER_11) || + (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD)) + txn->flags |= TX_CACHE_IGNORE; + + http_check_request_for_cacheability(s, &s->req); + + /* The request's hash has to be calculated for all requests, even POSTs + * or PUTs for instance because RFC7234 specifies that a successful + * "unsafe" method on a stored resource must invalidate it + * (see RFC7234#4.4). */ + if (!sha1_hosturi(s)) + return ACT_RET_CONT; + + if (s->txn->flags & TX_CACHE_IGNORE) + return ACT_RET_CONT; + + if (px == strm_fe(s)) + _HA_ATOMIC_INC(&px->fe_counters.p.http.cache_lookups); + else + _HA_ATOMIC_INC(&px->be_counters.p.http.cache_lookups); + + cache_tree = get_cache_tree_from_hash(cache, read_u32(s->txn->cache_hash)); + + if (!cache_tree) + return ACT_RET_CONT; + + cache_rdlock(cache_tree); + res = get_entry(cache_tree, s->txn->cache_hash, 0); + /* We must not use an entry that is not complete but the check will be + * performed after we look for a potential secondary entry (in case of + * Vary). */ + if (res) { + struct appctx *appctx; + int detached = 0; + + retain_entry(res); + + entry_block = block_ptr(res); + shctx_wrlock(shctx); + if (res->complete) { + shctx_row_detach(shctx, entry_block); + detached = 1; + } else { + release_entry(cache_tree, res, 0); + res = NULL; + } + shctx_wrunlock(shctx); + cache_rdunlock(cache_tree); + + /* In case of Vary, we could have multiple entries with the same + * primary hash. We need to calculate the secondary hash in order + * to find the actual entry we want (if it exists). */ + if (res && res->secondary_key_signature) { + if (!http_request_build_secondary_key(s, res->secondary_key_signature)) { + cache_rdlock(cache_tree); + sec_entry = get_secondary_entry(cache_tree, res, + s->txn->cache_secondary_hash, 0); + if (sec_entry && sec_entry != res) { + /* The wrong row was added to the hot list. */ + release_entry(cache_tree, res, 0); + retain_entry(sec_entry); + shctx_wrlock(shctx); + if (detached) + shctx_row_reattach(shctx, entry_block); + entry_block = block_ptr(sec_entry); + shctx_row_detach(shctx, entry_block); + shctx_wrunlock(shctx); + } + res = sec_entry; + cache_rdunlock(cache_tree); + } + else { + release_entry(cache_tree, res, 1); + + res = NULL; + shctx_wrlock(shctx); + shctx_row_reattach(shctx, entry_block); + shctx_wrunlock(shctx); + } + } + + /* We either looked for a valid secondary entry and could not + * find one, or the entry we want to use is not complete. We + * can't use the cache's entry and must forward the request to + * the server. */ + if (!res) { + return ACT_RET_CONT; + } else if (!res->complete) { + release_entry(cache_tree, res, 1); + return ACT_RET_CONT; + } + + s->target = &http_cache_applet.obj_type; + if ((appctx = sc_applet_create(s->scb, objt_applet(s->target)))) { + struct cache_appctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + + appctx->st0 = HTX_CACHE_INIT; + appctx->rule = rule; + ctx->cache_tree = cache_tree; + ctx->entry = res; + ctx->next = NULL; + ctx->sent = 0; + ctx->send_notmodified = + should_send_notmodified_response(cache, htxbuf(&s->req.buf), res); + + if (px == strm_fe(s)) + _HA_ATOMIC_INC(&px->fe_counters.p.http.cache_hits); + else + _HA_ATOMIC_INC(&px->be_counters.p.http.cache_hits); + return ACT_RET_CONT; + } else { + s->target = NULL; + release_entry(cache_tree, res, 1); + shctx_wrlock(shctx); + shctx_row_reattach(shctx, entry_block); + shctx_wrunlock(shctx); + return ACT_RET_CONT; + } + } + cache_rdunlock(cache_tree); + + /* Shared context does not need to be locked while we calculate the + * secondary hash. */ + if (!res && cache->vary_processing_enabled) { + /* Build a complete secondary hash until the server response + * tells us which fields should be kept (if any). */ + http_request_prebuild_full_secondary_key(s); + } + return ACT_RET_CONT; +} + + +enum act_parse_ret parse_cache_use(const char **args, int *orig_arg, struct proxy *proxy, + struct act_rule *rule, char **err) +{ + rule->action = ACT_CUSTOM; + rule->action_ptr = http_action_req_cache_use; + + if (!parse_cache_rule(proxy, args[*orig_arg], rule, err)) + return ACT_RET_PRS_ERR; + + (*orig_arg)++; + return ACT_RET_PRS_OK; +} + +int cfg_parse_cache(const char *file, int linenum, char **args, int kwm) +{ + int err_code = 0; + + if (strcmp(args[0], "cache") == 0) { /* new cache section */ + + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' expects a argument\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) { + err_code |= ERR_ABORT; + goto out; + } + + if (tmp_cache_config == NULL) { + struct cache *cache_config; + + tmp_cache_config = calloc(1, sizeof(*tmp_cache_config)); + if (!tmp_cache_config) { + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + strlcpy2(tmp_cache_config->id, args[1], 33); + if (strlen(args[1]) > 32) { + ha_warning("parsing [%s:%d]: cache name is limited to 32 characters, truncate to '%s'.\n", + file, linenum, tmp_cache_config->id); + err_code |= ERR_WARN; + } + + list_for_each_entry(cache_config, &caches_config, list) { + if (strcmp(tmp_cache_config->id, cache_config->id) == 0) { + ha_alert("parsing [%s:%d]: Duplicate cache name '%s'.\n", + file, linenum, tmp_cache_config->id); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + } + + tmp_cache_config->maxage = 60; + tmp_cache_config->maxblocks = 0; + tmp_cache_config->maxobjsz = 0; + tmp_cache_config->max_secondary_entries = DEFAULT_MAX_SECONDARY_ENTRY; + } + } else if (strcmp(args[0], "total-max-size") == 0) { + unsigned long int maxsize; + char *err; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) { + err_code |= ERR_ABORT; + goto out; + } + + maxsize = strtoul(args[1], &err, 10); + if (err == args[1] || *err != '\0') { + ha_warning("parsing [%s:%d]: total-max-size wrong value '%s'\n", + file, linenum, args[1]); + err_code |= ERR_ABORT; + goto out; + } + + if (maxsize > (UINT_MAX >> 20)) { + ha_warning("parsing [%s:%d]: \"total-max-size\" (%s) must not be greater than %u\n", + file, linenum, args[1], UINT_MAX >> 20); + err_code |= ERR_ABORT; + goto out; + } + + /* size in megabytes */ + maxsize *= 1024 * 1024 / CACHE_BLOCKSIZE; + tmp_cache_config->maxblocks = maxsize; + } else if (strcmp(args[0], "max-age") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) { + err_code |= ERR_ABORT; + goto out; + } + + if (!*args[1]) { + ha_warning("parsing [%s:%d]: '%s' expects an age parameter in seconds.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + tmp_cache_config->maxage = atoi(args[1]); + } else if (strcmp(args[0], "max-object-size") == 0) { + unsigned int maxobjsz; + char *err; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) { + err_code |= ERR_ABORT; + goto out; + } + + if (!*args[1]) { + ha_warning("parsing [%s:%d]: '%s' expects a maximum file size parameter in bytes.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + maxobjsz = strtoul(args[1], &err, 10); + if (err == args[1] || *err != '\0') { + ha_warning("parsing [%s:%d]: max-object-size wrong value '%s'\n", + file, linenum, args[1]); + err_code |= ERR_ABORT; + goto out; + } + tmp_cache_config->maxobjsz = maxobjsz; + } else if (strcmp(args[0], "process-vary") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) { + err_code |= ERR_ABORT; + goto out; + } + + if (!*args[1]) { + ha_warning("parsing [%s:%d]: '%s' expects \"on\" or \"off\" (enable or disable vary processing).\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + if (strcmp(args[1], "on") == 0) + tmp_cache_config->vary_processing_enabled = 1; + else if (strcmp(args[1], "off") == 0) + tmp_cache_config->vary_processing_enabled = 0; + else { + ha_warning("parsing [%s:%d]: '%s' expects \"on\" or \"off\" (enable or disable vary processing).\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + } else if (strcmp(args[0], "max-secondary-entries") == 0) { + unsigned int max_sec_entries; + char *err; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) { + err_code |= ERR_ABORT; + goto out; + } + + if (!*args[1]) { + ha_warning("parsing [%s:%d]: '%s' expects a strictly positive number.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + max_sec_entries = strtoul(args[1], &err, 10); + if (err == args[1] || *err != '\0' || max_sec_entries == 0) { + ha_warning("parsing [%s:%d]: max-secondary-entries wrong value '%s'\n", + file, linenum, args[1]); + err_code |= ERR_ABORT; + goto out; + } + tmp_cache_config->max_secondary_entries = max_sec_entries; + } + else if (*args[0] != 0) { + ha_alert("parsing [%s:%d] : unknown keyword '%s' in 'cache' section\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } +out: + return err_code; +} + +/* once the cache section is parsed */ + +int cfg_post_parse_section_cache() +{ + int err_code = 0; + + if (tmp_cache_config) { + + if (tmp_cache_config->maxblocks <= 0) { + ha_alert("Size not specified for cache '%s'\n", tmp_cache_config->id); + err_code |= ERR_FATAL | ERR_ALERT; + goto out; + } + + if (!tmp_cache_config->maxobjsz) { + /* Default max. file size is a 256th of the cache size. */ + tmp_cache_config->maxobjsz = + (tmp_cache_config->maxblocks * CACHE_BLOCKSIZE) >> 8; + } + else if (tmp_cache_config->maxobjsz > tmp_cache_config->maxblocks * CACHE_BLOCKSIZE / 2) { + ha_alert("\"max-object-size\" is limited to an half of \"total-max-size\" => %u\n", tmp_cache_config->maxblocks * CACHE_BLOCKSIZE / 2); + err_code |= ERR_FATAL | ERR_ALERT; + goto out; + } + + /* add to the list of cache to init and reinit tmp_cache_config + * for next cache section, if any. + */ + LIST_APPEND(&caches_config, &tmp_cache_config->list); + tmp_cache_config = NULL; + return err_code; + } +out: + ha_free(&tmp_cache_config); + return err_code; + +} + +int post_check_cache() +{ + struct proxy *px; + struct cache *back, *cache_config, *cache; + struct shared_context *shctx; + int ret_shctx; + int err_code = ERR_NONE; + int i; + + list_for_each_entry_safe(cache_config, back, &caches_config, list) { + + ret_shctx = shctx_init(&shctx, cache_config->maxblocks, CACHE_BLOCKSIZE, + cache_config->maxobjsz, sizeof(struct cache)); + + if (ret_shctx <= 0) { + if (ret_shctx == SHCTX_E_INIT_LOCK) + ha_alert("Unable to initialize the lock for the cache.\n"); + else + ha_alert("Unable to allocate cache.\n"); + + err_code |= ERR_FATAL | ERR_ALERT; + goto out; + } + shctx->free_block = cache_free_blocks; + shctx->reserve_finish = cache_reserve_finish; + shctx->cb_data = (void*)shctx->data; + /* the cache structure is stored in the shctx and added to the + * caches list, we can remove the entry from the caches_config + * list */ + memcpy(shctx->data, cache_config, sizeof(struct cache)); + cache = (struct cache *)shctx->data; + LIST_APPEND(&caches, &cache->list); + LIST_DELETE(&cache_config->list); + free(cache_config); + for (i = 0; i < CACHE_TREE_NUM; ++i) { + cache->trees[i].entries = EB_ROOT; + HA_RWLOCK_INIT(&cache->trees[i].lock); + + LIST_INIT(&cache->trees[i].cleanup_list); + HA_SPIN_INIT(&cache->trees[i].cleanup_lock); + } + + /* Find all references for this cache in the existing filters + * (over all proxies) and reference it in matching filters. + */ + for (px = proxies_list; px; px = px->next) { + struct flt_conf *fconf; + struct cache_flt_conf *cconf; + + list_for_each_entry(fconf, &px->filter_configs, list) { + if (fconf->id != cache_store_flt_id) + continue; + + cconf = fconf->conf; + if (strcmp(cache->id, cconf->c.name) == 0) { + free(cconf->c.name); + cconf->flags |= CACHE_FLT_INIT; + cconf->c.cache = cache; + break; + } + } + } + } + +out: + return err_code; + +} + +struct flt_ops cache_ops = { + .init = cache_store_init, + .check = cache_store_check, + .deinit = cache_store_deinit, + + /* Handle stream init/deinit */ + .attach = cache_store_strm_init, + .detach = cache_store_strm_deinit, + + /* Handle channels activity */ + .channel_post_analyze = cache_store_post_analyze, + + /* Filter HTTP requests and responses */ + .http_headers = cache_store_http_headers, + .http_payload = cache_store_http_payload, + .http_end = cache_store_http_end, +}; + + +#define CHECK_ENCODING(str, encoding_name, encoding_value) \ + ({ \ + int retval = 0; \ + if (istmatch(str, (struct ist){ .ptr = encoding_name+1, .len = sizeof(encoding_name) - 2 })) { \ + retval = encoding_value; \ + encoding = istadv(encoding, sizeof(encoding_name) - 2); \ + } \ + (retval); \ + }) + +/* + * Parse the encoding and try to match the encoding part upon an + * encoding list of explicitly supported encodings (which all have a specific + * bit in an encoding bitmap). If a weight is included in the value, find out if + * it is null or not. The bit value will be set in the + * parameter and the will be set to 1 if the weight is strictly + * 0, 1 otherwise. + * The encodings list is extracted from + * https://www.iana.org/assignments/http-parameters/http-parameters.xhtml. + * Returns 0 in case of success and -1 in case of error. + */ +static int parse_encoding_value(struct ist encoding, unsigned int *encoding_value, + unsigned int *has_null_weight) +{ + int retval = 0; + + if (!encoding_value) + return -1; + + if (!istlen(encoding)) + return -1; /* Invalid encoding */ + + *encoding_value = 0; + if (has_null_weight) + *has_null_weight = 0; + + switch (*encoding.ptr) { + case 'a': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "aes128gcm", VARY_ENCODING_AES128GCM); + break; + case 'b': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "br", VARY_ENCODING_BR); + break; + case 'c': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "compress", VARY_ENCODING_COMPRESS); + break; + case 'd': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "deflate", VARY_ENCODING_DEFLATE); + break; + case 'e': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "exi", VARY_ENCODING_EXI); + break; + case 'g': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "gzip", VARY_ENCODING_GZIP); + break; + case 'i': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "identity", VARY_ENCODING_IDENTITY); + break; + case 'p': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "pack200-gzip", VARY_ENCODING_PACK200_GZIP); + break; + case 'x': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "x-gzip", VARY_ENCODING_GZIP); + if (!*encoding_value) + *encoding_value = CHECK_ENCODING(encoding, "x-compress", VARY_ENCODING_COMPRESS); + break; + case 'z': + encoding = istnext(encoding); + *encoding_value = CHECK_ENCODING(encoding, "zstd", VARY_ENCODING_ZSTD); + break; + case '*': + encoding = istnext(encoding); + *encoding_value = VARY_ENCODING_STAR; + break; + default: + retval = -1; /* Unmanaged encoding */ + break; + } + + /* Process the optional weight part of the encoding. */ + if (*encoding_value) { + encoding = http_trim_leading_spht(encoding); + if (istlen(encoding)) { + if (*encoding.ptr != ';') + return -1; + + if (has_null_weight) { + encoding = istnext(encoding); + + encoding = http_trim_leading_spht(encoding); + + *has_null_weight = isteq(encoding, ist("q=0")); + } + } + } + + return retval; +} + +#define ACCEPT_ENCODING_MAX_ENTRIES 16 +/* + * Build a bitmap of the accept-encoding header. + * + * The bitmap is built by matching every sub-part of the accept-encoding value + * with a subset of explicitly supported encodings, which all have their own bit + * in the bitmap. This bitmap will be used to determine if a response can be + * served to a client (that is if it has an encoding that is accepted by the + * client). Any unknown encodings will be indicated by the VARY_ENCODING_OTHER + * bit. + * + * Returns 0 in case of success and -1 in case of error. + */ +static int accept_encoding_normalizer(struct htx *htx, struct ist hdr_name, + char *buf, unsigned int *buf_len) +{ + size_t count = 0; + uint32_t encoding_bitmap = 0; + unsigned int encoding_bmp_bl = -1; + struct http_hdr_ctx ctx = { .blk = NULL }; + unsigned int encoding_value; + unsigned int rejected_encoding; + + /* A user agent always accepts an unencoded value unless it explicitly + * refuses it through an "identity;q=0" accept-encoding value. */ + encoding_bitmap |= VARY_ENCODING_IDENTITY; + + /* Iterate over all the ACCEPT_ENCODING_MAX_ENTRIES first accept-encoding + * values that might span acrosse multiple accept-encoding headers. */ + while (http_find_header(htx, hdr_name, &ctx, 0) && count < ACCEPT_ENCODING_MAX_ENTRIES) { + count++; + + /* As per RFC7231#5.3.4, "An Accept-Encoding header field with a + * combined field-value that is empty implies that the user agent + * does not want any content-coding in response." + * + * We must (and did) count the existence of this empty header to not + * hit the `count == 0` case below, but must ignore the value to not + * include VARY_ENCODING_OTHER into the final bitmap. + */ + if (istlen(ctx.value) == 0) + continue; + + /* Turn accept-encoding value to lower case */ + ist2bin_lc(istptr(ctx.value), ctx.value); + + /* Try to identify a known encoding and to manage null weights. */ + if (!parse_encoding_value(ctx.value, &encoding_value, &rejected_encoding)) { + if (rejected_encoding) + encoding_bmp_bl &= ~encoding_value; + else + encoding_bitmap |= encoding_value; + } + else { + /* Unknown encoding */ + encoding_bitmap |= VARY_ENCODING_OTHER; + } + } + + /* If a "*" was found in the accepted encodings (without a null weight), + * all the encoding are accepted except the ones explicitly rejected. */ + if (encoding_bitmap & VARY_ENCODING_STAR) { + encoding_bitmap = ~0; + } + + /* Clear explicitly rejected encodings from the bitmap */ + encoding_bitmap &= encoding_bmp_bl; + + /* As per RFC7231#5.3.4, "If no Accept-Encoding field is in the request, + * any content-coding is considered acceptable by the user agent". */ + if (count == 0) + encoding_bitmap = ~0; + + /* A request with more than ACCEPT_ENCODING_MAX_ENTRIES accepted + * encodings might be illegitimate so we will not use it. */ + if (count == ACCEPT_ENCODING_MAX_ENTRIES) + return -1; + + write_u32(buf, encoding_bitmap); + *buf_len = sizeof(encoding_bitmap); + + /* This function fills the hash buffer correctly even if no header was + * found, hence the 0 return value (success). */ + return 0; +} +#undef ACCEPT_ENCODING_MAX_ENTRIES + +/* + * Normalizer used by default for the Referer and Origin header. It only + * calculates a hash of the whole value using xxhash algorithm. + * Only the first occurrence of the header will be taken into account in the + * hash. + * Returns 0 in case of success, 1 if the hash buffer should be filled with 0s + * and -1 in case of error. + */ +static int default_normalizer(struct htx *htx, struct ist hdr_name, + char *buf, unsigned int *buf_len) +{ + int retval = 1; + struct http_hdr_ctx ctx = { .blk = NULL }; + + if (http_find_header(htx, hdr_name, &ctx, 1)) { + retval = 0; + write_u64(buf, XXH3(istptr(ctx.value), istlen(ctx.value), cache_hash_seed)); + *buf_len = sizeof(uint64_t); + } + + return retval; +} + +/* + * Accept-Encoding bitmap comparison function. + * Returns 0 if the bitmaps are compatible. + */ +static int accept_encoding_bitmap_cmp(const void *ref, const void *new, unsigned int len) +{ + uint32_t ref_bitmap = read_u32(ref); + uint32_t new_bitmap = read_u32(new); + + if (!(ref_bitmap & VARY_ENCODING_OTHER)) { + /* All the bits set in the reference bitmap correspond to the + * stored response' encoding and should all be set in the new + * encoding bitmap in order for the client to be able to manage + * the response. + * + * If this is the case the cached response has encodings that + * are accepted by the client. It can be served directly by + * the cache (as far as the accept-encoding part is concerned). + */ + + return (ref_bitmap & new_bitmap) != ref_bitmap; + } + else { + return 1; + } +} + + +/* + * Pre-calculate the hashes of all the supported headers (in our Vary + * implementation) of a given request. We have to calculate all the hashes + * in advance because the actual Vary signature won't be known until the first + * response. + * Only the first occurrence of every header will be taken into account in the + * hash. + * If the header is not present, the hash portion of the given header will be + * filled with zeros. + * Returns 0 in case of success. + */ +static int http_request_prebuild_full_secondary_key(struct stream *s) +{ + /* The fake signature (second parameter) will ensure that every part of the + * secondary key is calculated. */ + return http_request_build_secondary_key(s, ~0); +} + + +/* + * Calculate the secondary key for a request for which we already have a known + * vary signature. The key is made by aggregating hashes calculated for every + * header mentioned in the vary signature. + * Only the first occurrence of every header will be taken into account in the + * hash. + * If the header is not present, the hash portion of the given header will be + * filled with zeros. + * Returns 0 in case of success. + */ +static int http_request_build_secondary_key(struct stream *s, int vary_signature) +{ + struct http_txn *txn = s->txn; + struct htx *htx = htxbuf(&s->req.buf); + + unsigned int idx; + const struct vary_hashing_information *info = NULL; + unsigned int hash_length = 0; + int retval = 0; + int offset = 0; + + for (idx = 0; idx < sizeof(vary_information)/sizeof(*vary_information) && retval >= 0; ++idx) { + info = &vary_information[idx]; + + /* The normalizing functions will be in charge of getting the + * header values from the htx. This way they can manage multiple + * occurrences of their processed header. */ + if ((vary_signature & info->value) && info->norm_fn != NULL && + !(retval = info->norm_fn(htx, info->hdr_name, &txn->cache_secondary_hash[offset], &hash_length))) { + offset += hash_length; + } + else { + /* Fill hash with 0s. */ + hash_length = info->hash_length; + memset(&txn->cache_secondary_hash[offset], 0, hash_length); + offset += hash_length; + } + } + + if (retval >= 0) + txn->flags |= TX_CACHE_HAS_SEC_KEY; + + return (retval < 0); +} + +/* + * Build the actual secondary key of a given request out of the prebuilt key and + * the actual vary signature (extracted from the response). + * Returns 0 in case of success. + */ +static int http_request_reduce_secondary_key(unsigned int vary_signature, + char prebuilt_key[HTTP_CACHE_SEC_KEY_LEN]) +{ + int offset = 0; + int global_offset = 0; + int vary_info_count = 0; + int keep = 0; + unsigned int vary_idx; + const struct vary_hashing_information *vary_info; + + vary_info_count = sizeof(vary_information)/sizeof(*vary_information); + for (vary_idx = 0; vary_idx < vary_info_count; ++vary_idx) { + vary_info = &vary_information[vary_idx]; + keep = (vary_signature & vary_info->value) ? 0xff : 0; + + for (offset = 0; offset < vary_info->hash_length; ++offset,++global_offset) { + prebuilt_key[global_offset] &= keep; + } + } + + return 0; +} + + + +static int +parse_cache_flt(char **args, int *cur_arg, struct proxy *px, + struct flt_conf *fconf, char **err, void *private) +{ + struct flt_conf *f, *back; + struct cache_flt_conf *cconf = NULL; + char *name = NULL; + int pos = *cur_arg; + + /* Get the cache filter name. point on "cache" keyword */ + if (!*args[pos + 1]) { + memprintf(err, "%s : expects a argument", args[pos]); + goto error; + } + name = strdup(args[pos + 1]); + if (!name) { + memprintf(err, "%s '%s' : out of memory", args[pos], args[pos + 1]); + goto error; + } + pos += 2; + + /* Check if an implicit filter with the same name already exists. If so, + * we remove the implicit filter to use the explicit one. */ + list_for_each_entry_safe(f, back, &px->filter_configs, list) { + if (f->id != cache_store_flt_id) + continue; + + cconf = f->conf; + if (strcmp(name, cconf->c.name) != 0) { + cconf = NULL; + continue; + } + + if (!(cconf->flags & CACHE_FLT_F_IMPLICIT_DECL)) { + cconf = NULL; + memprintf(err, "%s: multiple explicit declarations of the cache filter '%s'", + px->id, name); + goto error; + } + + /* Remove the implicit filter. is kept for the explicit one */ + LIST_DELETE(&f->list); + free(f); + free(name); + break; + } + + /* No implicit cache filter found, create configuration for the explicit one */ + if (!cconf) { + cconf = calloc(1, sizeof(*cconf)); + if (!cconf) { + memprintf(err, "%s: out of memory", args[*cur_arg]); + goto error; + } + cconf->c.name = name; + } + + cconf->flags = 0; + fconf->id = cache_store_flt_id; + fconf->conf = cconf; + fconf->ops = &cache_ops; + + *cur_arg = pos; + return 0; + + error: + free(name); + free(cconf); + return -1; +} + +/* It reserves a struct show_cache_ctx for the local variables */ +static int cli_parse_show_cache(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct show_cache_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + + ctx->cache = LIST_ELEM((caches).n, typeof(struct cache *), list); + return 0; +} + +/* It uses a struct show_cache_ctx for the local variables */ +static int cli_io_handler_show_cache(struct appctx *appctx) +{ + struct show_cache_ctx *ctx = appctx->svcctx; + struct cache* cache = ctx->cache; + struct buffer *buf = alloc_trash_chunk(); + + if (buf == NULL) + return 1; + + list_for_each_entry_from(cache, &caches, list) { + struct eb32_node *node = NULL; + unsigned int next_key; + struct cache_entry *entry; + unsigned int i; + struct shared_context *shctx = shctx_ptr(cache); + int cache_tree_index = 0; + struct cache_tree *cache_tree = NULL; + + next_key = ctx->next_key; + if (!next_key) { + shctx_rdlock(shctx); + chunk_printf(buf, "%p: %s (shctx:%p, available blocks:%d)\n", cache, cache->id, shctx_ptr(cache), shctx_ptr(cache)->nbav); + shctx_rdunlock(shctx); + if (applet_putchk(appctx, buf) == -1) { + goto yield; + } + } + + ctx->cache = cache; + + if (ctx->cache_tree) + cache_tree_index = (ctx->cache_tree - ctx->cache->trees); + + for (;cache_tree_index < CACHE_TREE_NUM; ++cache_tree_index) { + + ctx->cache_tree = cache_tree = &ctx->cache->trees[cache_tree_index]; + + cache_rdlock(cache_tree); + + while (1) { + node = eb32_lookup_ge(&cache_tree->entries, next_key); + if (!node) { + ctx->next_key = 0; + break; + } + + entry = container_of(node, struct cache_entry, eb); + next_key = node->key + 1; + + if (entry->expire > date.tv_sec) { + chunk_printf(buf, "%p hash:%u vary:0x", entry, read_u32(entry->hash)); + for (i = 0; i < HTTP_CACHE_SEC_KEY_LEN; ++i) + chunk_appendf(buf, "%02x", (unsigned char)entry->secondary_key[i]); + chunk_appendf(buf, " size:%u (%u blocks), refcount:%u, expire:%d\n", + block_ptr(entry)->len, block_ptr(entry)->block_count, + block_ptr(entry)->refcount, entry->expire - (int)date.tv_sec); + } + + ctx->next_key = next_key; + + if (applet_putchk(appctx, buf) == -1) { + cache_rdunlock(cache_tree); + goto yield; + } + } + cache_rdunlock(cache_tree); + } + } + + free_trash_chunk(buf); + return 1; + +yield: + free_trash_chunk(buf); + return 0; +} + + +/* + * boolean, returns true if response was built out of a cache entry. + */ +static int +smp_fetch_res_cache_hit(const struct arg *args, struct sample *smp, + const char *kw, void *private) +{ + smp->data.type = SMP_T_BOOL; + smp->data.u.sint = (smp->strm ? (smp->strm->target == &http_cache_applet.obj_type) : 0); + + return 1; +} + +/* + * string, returns cache name (if response came from a cache). + */ +static int +smp_fetch_res_cache_name(const struct arg *args, struct sample *smp, + const char *kw, void *private) +{ + struct appctx *appctx = NULL; + + struct cache_flt_conf *cconf = NULL; + struct cache *cache = NULL; + + if (!smp->strm || smp->strm->target != &http_cache_applet.obj_type) + return 0; + + /* Get appctx from the stream connector. */ + appctx = sc_appctx(smp->strm->scb); + if (appctx && appctx->rule) { + cconf = appctx->rule->arg.act.p[0]; + if (cconf) { + cache = cconf->c.cache; + + smp->data.type = SMP_T_STR; + smp->flags = SMP_F_CONST; + smp->data.u.str.area = cache->id; + smp->data.u.str.data = strlen(cache->id); + return 1; + } + } + + return 0; +} + + +/* early boot initialization */ +static void cache_init() +{ + cache_hash_seed = ha_random64(); +} + +INITCALL0(STG_PREPARE, cache_init); + +/* Declare the filter parser for "cache" keyword */ +static struct flt_kw_list filter_kws = { "CACHE", { }, { + { "cache", parse_cache_flt, NULL }, + { NULL, NULL, NULL }, + } +}; + +INITCALL1(STG_REGISTER, flt_register_keywords, &filter_kws); + +static struct cli_kw_list cli_kws = {{},{ + { { "show", "cache", NULL }, "show cache : show cache status", cli_parse_show_cache, cli_io_handler_show_cache, NULL, NULL }, + {{},} +}}; + +INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); + +static struct action_kw_list http_res_actions = { + .kw = { + { "cache-store", parse_cache_store }, + { NULL, NULL } + } +}; + +INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions); + +static struct action_kw_list http_req_actions = { + .kw = { + { "cache-use", parse_cache_use }, + { NULL, NULL } + } +}; + +INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions); + +struct applet http_cache_applet = { + .obj_type = OBJ_TYPE_APPLET, + .name = "", /* used for logging */ + .fct = http_cache_io_handler, + .release = http_cache_applet_release, +}; + +/* config parsers for this section */ +REGISTER_CONFIG_SECTION("cache", cfg_parse_cache, cfg_post_parse_section_cache); +REGISTER_POST_CHECK(post_check_cache); + + +/* Note: must not be declared as its list will be overwritten */ +static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { + { "res.cache_hit", smp_fetch_res_cache_hit, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP, SMP_VAL_RESPONSE }, + { "res.cache_name", smp_fetch_res_cache_name, 0, NULL, SMP_T_STR, SMP_USE_HRSHP, SMP_VAL_RESPONSE }, + { /* END */ }, + } +}; + +INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords); diff --git a/src/calltrace.c b/src/calltrace.c new file mode 100644 index 0000000..3946b28 --- /dev/null +++ b/src/calltrace.c @@ -0,0 +1,286 @@ +/* + * Function call tracing for gcc >= 2.95 + * WARNING! THIS CODE IS NOT THREAD-SAFE! + * + * Copyright 2012 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * gcc is able to call a specific function when entering and leaving any + * function when compiled with -finstrument-functions. This code must not + * be built with this argument. The performance impact is huge, so this + * feature should only be used when debugging. + * + * The entry and exits of all functions will be dumped into a file designated + * by the HAPROXY_TRACE environment variable, or by default "trace.out". If the + * trace file name is empty or "/dev/null", then traces are disabled. If + * opening the trace file fails, then stderr is used. If HAPROXY_TRACE_FAST is + * used, then the time is taken from the global variable. Last, if + * HAPROXY_TRACE_TSC is used, then the machine's TSC is used instead of the + * real time (almost twice as fast). + * + * The output format is : + * + *

+ * or : + * + * + * where is '>' when entering a function and '<' when leaving. + * + * It is also possible to emit comments using the calltrace() function which uses + * the printf() format. Such comments are then inserted by replacing the caller + * pointer with a sharp ('#') like this : + * + * # + * or : + * # + * + * The article below is a nice explanation of how this works : + * http://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static FILE *log; +static int level; +static int disabled; +static int fast_time; +static int use_tsc; +static struct timeval trace_now; +static struct timeval *now_ptr; +static char line[128]; /* more than enough for a message (9+1+6+1+3+1+18+1+1+18+1+1) */ + +static int open_trace() +{ + const char *output = getenv("HAPROXY_TRACE"); + + if (!output) + output = "trace.out"; + + if (!*output || strcmp(output, "/dev/null") == 0) { + disabled = 1; + return 0; + } + + log = fopen(output, "w"); + if (!log) + log = stderr; + + now_ptr = &date; + if (getenv("HAPROXY_TRACE_FAST") != NULL) { + fast_time = 1; + now_ptr = &trace_now; + } + if (getenv("HAPROXY_TRACE_TSC") != NULL) { + fast_time = 1; + use_tsc = 1; + } + return 1; +} + +/* This function first divides the number by 100M then iteratively multiplies it + * by 100 (using adds and shifts). The trick is that dividing by 100M is equivalent + * to multiplying by 1/100M, which approximates to 1441151881/2^57. All local + * variables fit in registers on x86. This version outputs two digits per round. + * indicates the minimum number of pairs of digits that have to be + * emitted, which might be left-padded with zeroes. + * It returns the pointer to the ending '\0'. + */ +static char *ultoad2(unsigned int x, char *out, int min_pairs) +{ + unsigned int q; + char *p = out; + int pos = 4; + unsigned long long y; + + static const unsigned short bcd[100] = { + 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, + 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, + 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, + 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, + 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, + 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, + 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, + 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, + 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, + 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 }; + + y = x * 1441151881ULL; /* y>>57 will be the integer part of x/100M */ + while (1) { + q = y >> 57; + /* Q is composed of the first digit in the lower byte and the second + * digit in the higher byte. + */ + if (p != out || q > 9 || pos < min_pairs) { +#if defined(__i386__) || defined(__x86_64__) + /* unaligned accesses are fast on x86 */ + *(unsigned short *)p = bcd[q]; + p += 2; +#else + *(p++) = bcd[q]; + *(p++) = bcd[q] >> 8; +#endif + } + else if (q || !pos) { + /* only at most one digit */ + *(p++) = bcd[q] >> 8; + } + if (--pos < 0) + break; + + y &= 0x1FFFFFFFFFFFFFFULL; // remainder + + if (sizeof(long) >= sizeof(long long)) { + /* shifting is preferred on 64-bit archs, while mult is faster on 32-bit. + * We multiply by 100 by doing *5, *5 and *4, all of which are trivial. + */ + y += (y << 2); + y += (y << 2); + y <<= 2; + } + else + y *= 100; + } + + *p = '\0'; + return p; +} + +/* Send as hex into . Returns the pointer to the ending '\0'. */ +static char *emit_hex(unsigned long h, char *out) +{ + static unsigned char hextab[16] = "0123456789abcdef"; + int shift = sizeof(h) * 8 - 4; + unsigned int idx; + + do { + idx = (h >> shift); + if (idx || !shift) + *out++ = hextab[idx & 15]; + shift -= 4; + } while (shift >= 0); + *out = '\0'; + return out; +} + +static void make_line(void *from, void *to, int level, char dir, long ret) +{ + char *p = line; + + if (unlikely(!log) && !open_trace()) + return; + + if (unlikely(!fast_time)) + gettimeofday(now_ptr, NULL); + +#ifdef USE_SLOW_FPRINTF + if (!use_tsc) + fprintf(log, "%u.%06u %d %p %c %p\n", + (unsigned int)now_ptr->tv_sec, + (unsigned int)now_ptr->tv_usec, + level, from, dir, to); + else + fprintf(log, "%llx %d %p %c %p\n", + rdtsc(), level, from, dir, to); + return; +#endif + + if (unlikely(!use_tsc)) { + /* "%u.06u", tv_sec, tv_usec */ + p = ultoad2(now_ptr->tv_sec, p, 0); + *p++ = '.'; + p = ultoad2(now_ptr->tv_usec, p, 3); + } else { + /* "%08x%08x", high, low */ + unsigned long long t = rdtsc(); + if (sizeof(long) < sizeof(long long)) + p = emit_hex((unsigned long)(t >> 32U), p); + p = emit_hex((unsigned long)(t), p); + } + + /* " %u", level */ + *p++ = ' '; + p = ultoad2(level, p, 0); + + /* " %p", from */ + *p++ = ' '; *p++ = '0'; *p++ = 'x'; + p = emit_hex((unsigned long)from, p); + + /* " %c", dir */ + *p++ = ' '; *p++ = dir; + + /* " %p", to */ + *p++ = ' '; *p++ = '0'; *p++ = 'x'; + p = emit_hex((unsigned long)to, p); + + if (dir == '<') { + /* " %x", ret */ + *p++ = ' '; *p++ = '0'; *p++ = 'x'; + p = emit_hex(ret, p); + } + + *p++ = '\n'; + + fwrite(line, p - line, 1, log); +} + +/* These are the functions GCC calls */ +void __cyg_profile_func_enter(void *to, void *from) +{ + if (!disabled) + return make_line(from, to, ++level, '>', 0); +} + +void __cyg_profile_func_exit(void *to, void *from) +{ + long ret = 0; + +#if defined(__x86_64__) + /* on x86_64, the return value (eax) is temporarily stored in ebx + * during the call to __cyg_profile_func_exit() so we can snoop it. + */ + asm volatile("mov %%rbx, %0" : "=r"(ret)); +#endif + if (!disabled) + return make_line(from, to, level--, '<', ret); +} + +/* the one adds comments in the trace above. The output format is : + * # + */ +__attribute__((format(printf, 1, 2))) +void calltrace(char *fmt, ...) +{ + va_list ap; + + if (unlikely(!log) && !open_trace()) + return; + + if (unlikely(!fast_time)) + gettimeofday(now_ptr, NULL); + + if (!use_tsc) + fprintf(log, "%u.%06u %d # ", + (unsigned int)now_ptr->tv_sec, + (unsigned int)now_ptr->tv_usec, + level + 1); + else + fprintf(log, "%llx %d # ", + rdtsc(), level + 1); + + va_start(ap, fmt); + vfprintf(log, fmt, ap); + va_end(ap); + fputc('\n', log); + fflush(log); +} diff --git a/src/cbuf.c b/src/cbuf.c new file mode 100644 index 0000000..b36bbeb --- /dev/null +++ b/src/cbuf.c @@ -0,0 +1,59 @@ +/* + * Circular buffer management + * + * Copyright 2021 HAProxy Technologies, Frederic Lecaille + * + * 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 + */ + +#include +#include +#include + +DECLARE_POOL(pool_head_cbuf, "cbuf", sizeof(struct cbuf)); + +/* Allocate and return a new circular buffer with as byte internal buffer + * if succeeded, NULL if not. + */ +struct cbuf *cbuf_new(unsigned char *buf, size_t sz) +{ + struct cbuf *cbuf; + + cbuf = pool_alloc(pool_head_cbuf); + if (cbuf) { + cbuf->sz = sz; + cbuf->buf = buf; + cbuf->wr = 0; + cbuf->rd = 0; + } + + return cbuf; +} + +/* Free QUIC ring */ +void cbuf_free(struct cbuf *cbuf) +{ + if (!cbuf) + return; + + pool_free(pool_head_cbuf, cbuf); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/cfgcond.c b/src/cfgcond.c new file mode 100644 index 0000000..117cf6c --- /dev/null +++ b/src/cfgcond.c @@ -0,0 +1,559 @@ +/* + * Configuration condition preprocessor + * + * Copyright 2000-2021 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +/* supported condition predicates */ +const struct cond_pred_kw cond_predicates[] = { + { "defined", CFG_PRED_DEFINED, ARG1(1, STR) }, + { "feature", CFG_PRED_FEATURE, ARG1(1, STR) }, + { "streq", CFG_PRED_STREQ, ARG2(2, STR, STR) }, + { "strneq", CFG_PRED_STRNEQ, ARG2(2, STR, STR) }, + { "strstr", CFG_PRED_STRSTR, ARG2(2, STR, STR) }, + { "version_atleast", CFG_PRED_VERSION_ATLEAST, ARG1(1, STR) }, + { "version_before", CFG_PRED_VERSION_BEFORE, ARG1(1, STR) }, + { "openssl_version_atleast", CFG_PRED_OSSL_VERSION_ATLEAST, ARG1(1, STR) }, + { "openssl_version_before", CFG_PRED_OSSL_VERSION_BEFORE, ARG1(1, STR) }, + { "ssllib_name_startswith", CFG_PRED_SSLLIB_NAME_STARTSWITH, ARG1(1, STR) }, + { "enabled", CFG_PRED_ENABLED, ARG1(1, STR) }, + { NULL, CFG_PRED_NONE, 0 } +}; + +/* looks up a cond predicate matching the keyword in , possibly followed + * by a parenthesis. Returns a pointer to it or NULL if not found. + */ +const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str) +{ + const struct cond_pred_kw *ret; + int len = strcspn(str, " ("); + + for (ret = &cond_predicates[0]; ret->word; ret++) { + if (len != strlen(ret->word)) + continue; + if (strncmp(str, ret->word, len) != 0) + continue; + return ret; + } + return NULL; +} + +/* Frees and its args. NULL is supported and does nothing. */ +void cfg_free_cond_term(struct cfg_cond_term *term) +{ + if (!term) + return; + + if (term->type == CCTT_PAREN) { + cfg_free_cond_expr(term->expr); + term->expr = NULL; + } + + free_args(term->args); + free(term->args); + free(term); +} + +/* Parse an indirect input text as a possible config condition term. + * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on + * success. is allocated and filled with the parsed info, and + * is updated on success to point to the first unparsed character, or is left + * untouched on failure. On success, the caller must free using + * cfg_free_cond_term(). An error will be set in on error, and only + * in this case. In this case the first bad character will be reported in + * . corresponds to the maximum recursion depth permitted, + * it is decremented on each recursive call and the parsing will fail one + * reaching <= 0. + */ +int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr, int maxdepth) +{ + struct cfg_cond_term *t; + const char *in = *text; + const char *end_ptr; + int err_arg; + int nbargs; + char *end; + long val; + + while (*in == ' ' || *in == '\t') + in++; + + if (!*in) /* empty term does not parse */ + return 0; + + *term = NULL; + if (maxdepth <= 0) + goto fail0; + + t = *term = calloc(1, sizeof(**term)); + if (!t) { + memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text); + goto fail1; + } + + t->type = CCTT_NONE; + t->args = NULL; + t->neg = 0; + + /* ! negates the term. White spaces permitted */ + while (*in == '!') { + t->neg = !t->neg; + do { in++; } while (*in == ' ' || *in == '\t'); + } + + val = strtol(in, &end, 0); + if (end != in) { + t->type = val ? CCTT_TRUE : CCTT_FALSE; + *text = end; + return 1; + } + + /* Try to parse '(' EXPR ')' */ + if (*in == '(') { + int ret; + + t->type = CCTT_PAREN; + t->args = NULL; + + do { in++; } while (*in == ' ' || *in == '\t'); + ret = cfg_parse_cond_expr(&in, &t->expr, err, errptr, maxdepth - 1); + if (ret == -1) + goto fail2; + if (ret == 0) + goto fail0; + + /* find the closing ')' */ + while (*in == ' ' || *in == '\t') + in++; + if (*in != ')') { + memprintf(err, "expected ')' after conditional expression '%s'", *text); + goto fail1; + } + do { in++; } while (*in == ' ' || *in == '\t'); + *text = in; + return 1; + } + + /* below we'll likely all make_arg_list() so we must return only via + * the label which frees the arg list. + */ + t->pred = cfg_lookup_cond_pred(in); + if (t->pred) { + t->type = CCTT_PRED; + nbargs = make_arg_list(in + strlen(t->pred->word), -1, + t->pred->arg_mask, &t->args, err, + &end_ptr, &err_arg, NULL); + if (nbargs < 0) { + memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, t->pred->word); + if (errptr) + *errptr = end_ptr; + goto fail2; + } + *text = end_ptr; + return 1; + } + + fail0: + memprintf(err, "unparsable conditional expression '%s'", *text); + fail1: + if (errptr) + *errptr = *text; + fail2: + cfg_free_cond_term(*term); + *term = NULL; + return -1; +} + +/* evaluate a "enabled" expression. Only a subset of options are matched. It + * returns 1 if the option is enabled. 0 is returned is the option is not + * enabled or if it is not recognized. + */ +static int cfg_eval_cond_enabled(const char *str) +{ + if (strcmp(str, "POLL") == 0) + return !!(global.tune.options & GTUNE_USE_POLL); + else if (strcmp(str, "EPOLL") == 0) + return !!(global.tune.options & GTUNE_USE_EPOLL); + else if (strcmp(str, "KQUEUE") == 0) + return !!(global.tune.options & GTUNE_USE_EPOLL); + else if (strcmp(str, "EVPORTS") == 0) + return !!(global.tune.options & GTUNE_USE_EVPORTS); + else if (strcmp(str, "SPLICE") == 0) + return !!(global.tune.options & GTUNE_USE_SPLICE); + else if (strcmp(str, "GETADDRINFO") == 0) + return !!(global.tune.options & GTUNE_USE_GAI); + else if (strcmp(str, "REUSEPORT") == 0) + return !!(proto_tcpv4.flags & PROTO_F_REUSEPORT_SUPPORTED); + else if (strcmp(str, "FAST-FORWARD") == 0) + return !!(global.tune.options & GTUNE_USE_FAST_FWD); + else if (strcmp(str, "SERVER-SSL-VERIFY-NONE") == 0) + return !!(global.ssl_server_verify == SSL_SERVER_VERIFY_NONE); + return 0; +} + +/* evaluate a condition term on a .if/.elif line. The condition was already + * parsed in . Returns -1 on error (in which case err is filled with a + * message, and only in this case), 0 if the condition is false, 1 if it's + * true. + */ +int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err) +{ + int ret = -1; + + if (term->type == CCTT_FALSE) + ret = 0; + else if (term->type == CCTT_TRUE) + ret = 1; + else if (term->type == CCTT_PRED) { + /* here we know we have a valid predicate with valid arguments + * placed in term->args (which the caller will free). + */ + switch (term->pred->prd) { + case CFG_PRED_DEFINED: // checks if arg exists as an environment variable + ret = getenv(term->args[0].data.str.area) != NULL; + break; + + case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature + const char *p; + + ret = 0; // assume feature not found + for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) { + if (p > build_features && + (p[term->args[0].data.str.data] == ' ' || + p[term->args[0].data.str.data] == 0)) { + if (*(p-1) == '+') { // e.g. "+OPENSSL" + ret = 1; + break; + } + else if (*(p-1) == '-') { // e.g. "-OPENSSL" + ret = 0; + break; + } + /* it was a sub-word, let's restart from next place */ + } + } + break; + } + case CFG_PRED_STREQ: // checks if the two arg are equal + ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0; + break; + + case CFG_PRED_STRNEQ: // checks if the two arg are different + ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0; + break; + + case CFG_PRED_STRSTR: // checks if the 2nd arg is found in the first one + ret = strstr(term->args[0].data.str.area, term->args[1].data.str.area) != NULL; + break; + + case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one + ret = compare_current_version(term->args[0].data.str.area) <= 0; + break; + + case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one + ret = compare_current_version(term->args[0].data.str.area) > 0; + break; + + case CFG_PRED_OSSL_VERSION_ATLEAST: { // checks if the current openssl version is at least this one + int opensslret = openssl_compare_current_version(term->args[0].data.str.area); + + if (opensslret < -1) /* can't parse the string or no openssl available */ + ret = -1; + else + ret = opensslret <= 0; + break; + } + case CFG_PRED_OSSL_VERSION_BEFORE: { // checks if the current openssl version is older than this one + int opensslret = openssl_compare_current_version(term->args[0].data.str.area); + + if (opensslret < -1) /* can't parse the string or no openssl available */ + ret = -1; + else + ret = opensslret > 0; + break; + } + case CFG_PRED_SSLLIB_NAME_STARTSWITH: { // checks if the current SSL library's name starts with a specified string (can be used to distinguish OpenSSL from LibreSSL or BoringSSL) + ret = openssl_compare_current_name(term->args[0].data.str.area) == 0; + break; + } + case CFG_PRED_ENABLED: { // checks if the arg matches on a subset of enabled options + ret = cfg_eval_cond_enabled(term->args[0].data.str.area) != 0; + break; + } + default: + memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word); + break; + } + } + else if (term->type == CCTT_PAREN) { + ret = cfg_eval_cond_expr(term->expr, err); + } + else { + memprintf(err, "internal error: unhandled condition term type %d", (int)term->type); + } + + if (ret >= 0 && term->neg) + ret = !ret; + return ret; +} + + +/* Frees and its terms and args. NULL is supported and does nothing. */ +void cfg_free_cond_and(struct cfg_cond_and *expr) +{ + struct cfg_cond_and *prev; + + while (expr) { + cfg_free_cond_term(expr->left); + prev = expr; + expr = expr->right; + free(prev); + } +} + +/* Frees and its terms and args. NULL is supported and does nothing. */ +void cfg_free_cond_expr(struct cfg_cond_expr *expr) +{ + struct cfg_cond_expr *prev; + + while (expr) { + cfg_free_cond_and(expr->left); + prev = expr; + expr = expr->right; + free(prev); + } +} + +/* Parse an indirect input text as a possible config condition sub-expr. + * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on + * success. is filled with the parsed info, and is updated on + * success to point to the first unparsed character, or is left untouched + * on failure. On success, the caller will have to free all lower-level + * allocated structs using cfg_free_cond_expr(). An error will be set in + * on error, and only in this case. In this case the first bad + * character will be reported in . corresponds to the + * maximum recursion depth permitted, it is decremented on each recursive + * call and the parsing will fail one reaching <= 0. + */ +int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr, int maxdepth) +{ + struct cfg_cond_and *e; + const char *in = *text; + int ret = -1; + + if (!*in) /* empty expr does not parse */ + return 0; + + *expr = NULL; + if (maxdepth <= 0) { + memprintf(err, "unparsable conditional sub-expression '%s'", in); + if (errptr) + *errptr = in; + goto done; + } + + e = *expr = calloc(1, sizeof(**expr)); + if (!e) { + memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text); + goto done; + } + + ret = cfg_parse_cond_term(&in, &e->left, err, errptr, maxdepth - 1); + if (ret == -1) // parse error, error already reported + goto done; + + if (ret == 0) { + /* ret == 0, no other way to parse this */ + memprintf(err, "unparsable conditional sub-expression '%s'", in); + if (errptr) + *errptr = in; + ret = -1; + goto done; + } + + /* ret=1, we have a term in the left hand set */ + + /* find an optional '&&' */ + while (*in == ' ' || *in == '\t') + in++; + + *text = in; + if (in[0] != '&' || in[1] != '&') + goto done; + + /* we have a '&&', let's parse the right handset's subexp */ + in += 2; + while (*in == ' ' || *in == '\t') + in++; + + ret = cfg_parse_cond_and(&in, &e->right, err, errptr, maxdepth - 1); + if (ret > 0) + *text = in; + done: + if (ret < 0) { + cfg_free_cond_and(*expr); + *expr = NULL; + } + return ret; +} + +/* Parse an indirect input text as a possible config condition term. + * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on + * success. is filled with the parsed info, and is updated on + * success to point to the first unparsed character, or is left untouched + * on failure. On success, the caller will have to free all lower-level + * allocated structs using cfg_free_cond_expr(). An error will be set in + * on error, and only in this case. In this case the first bad + * character will be reported in . corresponds to the + * maximum recursion depth permitted, it is decremented on each recursive call + * and the parsing will fail one reaching <= 0. + */ +int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr, int maxdepth) +{ + struct cfg_cond_expr *e; + const char *in = *text; + int ret = -1; + + if (!*in) /* empty expr does not parse */ + return 0; + + *expr = NULL; + if (maxdepth <= 0) { + memprintf(err, "unparsable conditional expression '%s'", in); + if (errptr) + *errptr = in; + goto done; + } + + e = *expr = calloc(1, sizeof(**expr)); + if (!e) { + memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text); + goto done; + } + + ret = cfg_parse_cond_and(&in, &e->left, err, errptr, maxdepth - 1); + if (ret == -1) // parse error, error already reported + goto done; + + if (ret == 0) { + /* ret == 0, no other way to parse this */ + memprintf(err, "unparsable conditional expression '%s'", in); + if (errptr) + *errptr = in; + ret = -1; + goto done; + } + + /* ret=1, we have a sub-expr in the left hand set */ + + /* find an optional '||' */ + while (*in == ' ' || *in == '\t') + in++; + + *text = in; + if (in[0] != '|' || in[1] != '|') + goto done; + + /* we have a '||', let's parse the right handset's subexp */ + in += 2; + while (*in == ' ' || *in == '\t') + in++; + + ret = cfg_parse_cond_expr(&in, &e->right, err, errptr, maxdepth - 1); + if (ret > 0) + *text = in; + done: + if (ret < 0) { + cfg_free_cond_expr(*expr); + *expr = NULL; + } + return ret; +} + +/* evaluate an sub-expression on a .if/.elif line. The expression is valid and + * was already parsed in . Returns -1 on error (in which case err is + * filled with a message, and only in this case), 0 if the condition is false, + * 1 if it's true. + */ +int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err) +{ + int ret; + + /* AND: loop on terms and sub-exp's terms as long as they're TRUE + * (stop on FALSE and ERROR). + */ + while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right) + expr = expr->right; + return ret; +} + +/* evaluate an expression on a .if/.elif line. The expression is valid and was + * already parsed in . Returns -1 on error (in which case err is filled + * with a message, and only in this case), 0 if the condition is false, 1 if + * it's true. + */ +int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err) +{ + int ret; + + /* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */ + while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right) + expr = expr->right; + return ret; +} + +/* evaluate a condition on a .if/.elif line. The condition is already tokenized + * in . Returns -1 on error (in which case err is filled with a message, + * and only in this case), 0 if the condition is false, 1 if it's true. If + * is not NULL, it's set to the first invalid character on error. + */ +int cfg_eval_condition(char **args, char **err, const char **errptr) +{ + struct cfg_cond_expr *expr = NULL; + const char *text = args[0]; + int ret = -1; + + if (!*text) /* note: empty = false */ + return 0; + + ret = cfg_parse_cond_expr(&text, &expr, err, errptr, MAX_CFG_RECURSION); + if (ret != 0) { + if (ret == -1) // parse error, error already reported + goto done; + while (*text == ' ' || *text == '\t') + text++; + + if (*text) { + ret = -1; + memprintf(err, "unexpected character '%c' at the end of conditional expression '%s'", + *text, args[0]); + goto fail; + } + + ret = cfg_eval_cond_expr(expr, err); + goto done; + } + + /* ret == 0, no other way to parse this */ + ret = -1; + memprintf(err, "unparsable conditional expression '%s'", args[0]); + fail: + if (errptr) + *errptr = text; + done: + cfg_free_cond_expr(expr); + return ret; +} diff --git a/src/cfgdiag.c b/src/cfgdiag.c new file mode 100644 index 0000000..f8e4a9e --- /dev/null +++ b/src/cfgdiag.c @@ -0,0 +1,97 @@ +#include +#include + +#include + +#include +#include +#include +#include + +/* Use this function to emit diagnostic. + * This can be used as a shortcut to set value pointed by to 1 at the + * same time. + */ +static inline void diag_warning(int *ret, char *fmt, ...) +{ + va_list argp; + + va_start(argp, fmt); + *ret = 1; + _ha_vdiag_warning(fmt, argp); + va_end(argp); +} + +/* Use this for dynamic allocation in diagnostics. + * In case of allocation failure, this will immediately terminates haproxy. + */ +static inline void *diag_alloc(size_t size) +{ + void *out = NULL; + + if (!(out = malloc(size))) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + + return out; +} + +/* Checks that two servers from the same backend does not share the same cookie + * value. Backup servers are not taken into account as it can be quite common to + * share cookie values in this case. + */ +static void check_server_cookies(int *ret) +{ + struct cookie_entry { + struct ebpt_node node; + }; + + struct proxy *px; + struct server *srv; + + struct eb_root cookies_tree = EB_ROOT_UNIQUE; + struct ebpt_node *cookie_node; + struct cookie_entry *cookie_entry; + struct ebpt_node *node; + + for (px = proxies_list; px; px = px->next) { + for (srv = px->srv; srv; srv = srv->next) { + /* do not take into account backup servers */ + if (!srv->cookie || (srv->flags & SRV_F_BACKUP)) + continue; + + cookie_node = ebis_lookup(&cookies_tree, srv->cookie); + if (cookie_node) { + diag_warning(ret, "parsing [%s:%d] : 'server %s' : same cookie value is set for a previous non-backup server in the same backend, it may break connection persistence\n", + srv->conf.file, srv->conf.line, srv->id); + continue; + } + + cookie_entry = diag_alloc(sizeof(*cookie_entry)); + cookie_entry->node.key = srv->cookie; + ebis_insert(&cookies_tree, &cookie_entry->node); + } + + /* clear the tree and free its entries */ + while ((node = ebpt_first(&cookies_tree))) { + cookie_entry = ebpt_entry(node, struct cookie_entry, node); + eb_delete(&node->node); + free(cookie_entry); + } + } +} + +/* Placeholder to execute various diagnostic checks after the configuration file + * has been fully parsed. It will output a warning for each diagnostic found. + * + * Returns 0 if no diagnostic message has been found else 1. + */ +int cfg_run_diagnostics() +{ + int ret = 0; + + check_server_cookies(&ret); + + return ret; +} diff --git a/src/cfgparse-global.c b/src/cfgparse-global.c new file mode 100644 index 0000000..f31e7a0 --- /dev/null +++ b/src/cfgparse-global.c @@ -0,0 +1,1396 @@ +#define _GNU_SOURCE /* for cpu_set_t from haproxy/cpuset.h */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#ifdef USE_CPU_AFFINITY +#include +#endif +#include +#include +#include +#include +#include +#include + +int cluster_secret_isset; + +/* some keywords that are still being parsed using strcmp() and are not + * registered anywhere. They are used as suggestions for mistyped words. + */ +static const char *common_kw_list[] = { + "global", "daemon", "master-worker", "noepoll", "nokqueue", + "noevports", "nopoll", "busy-polling", "set-dumpable", + "insecure-fork-wanted", "insecure-setuid-wanted", "nosplice", + "nogetaddrinfo", "noreuseport", "quiet", "zero-warning", + "tune.runqueue-depth", "tune.maxpollevents", "tune.maxaccept", + "tune.recv_enough", "tune.buffers.limit", + "tune.buffers.reserve", "tune.bufsize", "tune.maxrewrite", + "tune.idletimer", "tune.rcvbuf.client", "tune.rcvbuf.server", + "tune.sndbuf.client", "tune.sndbuf.server", "tune.pipesize", + "tune.http.cookielen", "tune.http.logurilen", "tune.http.maxhdr", + "tune.comp.maxlevel", "tune.pattern.cache-size", + "tune.fast-forward", "uid", "gid", + "external-check", "user", "group", "nbproc", "maxconn", + "ssl-server-verify", "maxconnrate", "maxsessrate", "maxsslrate", + "maxcomprate", "maxpipes", "maxzlibmem", "maxcompcpuusage", "ulimit-n", + "chroot", "description", "node", "pidfile", "unix-bind", "log", + "log-send-hostname", "server-state-base", "server-state-file", + "log-tag", "spread-checks", "max-spread-checks", "cpu-map", "setenv", + "presetenv", "unsetenv", "resetenv", "strict-limits", "localpeer", + "numa-cpu-mapping", "defaults", "listen", "frontend", "backend", + "peers", "resolvers", "cluster-secret", "no-quic", "limited-quic", + NULL /* must be last */ +}; + +/* + * parse a line in a section. Returns the error code, 0 if OK, or + * any combination of : + * - ERR_ABORT: must abort ASAP + * - ERR_FATAL: we can continue parsing but not start the service + * - ERR_WARN: a warning has been emitted + * - ERR_ALERT: an alert has been emitted + * Only the two first ones can stop processing, the two others are just + * indicators. + */ +int cfg_parse_global(const char *file, int linenum, char **args, int kwm) +{ + int err_code = 0; + char *errmsg = NULL; + + if (strcmp(args[0], "global") == 0) { /* new section */ + /* no option, nothing special to do */ + alertif_too_many_args(0, file, linenum, args, &err_code); + goto out; + } + else if (strcmp(args[0], "expose-experimental-directives") == 0) { + experimental_directives_allowed = 1; + } + else if (strcmp(args[0], "daemon") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.mode |= MODE_DAEMON; + } + else if (strcmp(args[0], "master-worker") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*args[1]) { + if (strcmp(args[1], "no-exit-on-failure") == 0) { + global.tune.options |= GTUNE_NOEXIT_ONFAILURE; + } else { + ha_alert("parsing [%s:%d] : '%s' only supports 'no-exit-on-failure' option.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + global.mode |= MODE_MWORKER; + } + else if (strcmp(args[0], "noepoll") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_EPOLL; + } + else if (strcmp(args[0], "nokqueue") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_KQUEUE; + } + else if (strcmp(args[0], "noevports") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_EVPORTS; + } + else if (strcmp(args[0], "nopoll") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_POLL; + } + else if (strcmp(args[0], "limited-quic") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + + global.tune.options |= GTUNE_LIMITED_QUIC; + } + else if (strcmp(args[0], "no-quic") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + + global.tune.options |= GTUNE_NO_QUIC; + } + else if (strcmp(args[0], "busy-polling") == 0) { /* "no busy-polling" or "busy-polling" */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_NO) + global.tune.options &= ~GTUNE_BUSY_POLLING; + else + global.tune.options |= GTUNE_BUSY_POLLING; + } + else if (strcmp(args[0], "set-dumpable") == 0) { /* "no set-dumpable" or "set-dumpable" */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_NO) + global.tune.options &= ~GTUNE_SET_DUMPABLE; + else + global.tune.options |= GTUNE_SET_DUMPABLE; + } + else if (strcmp(args[0], "h2-workaround-bogus-websocket-clients") == 0) { /* "no h2-workaround-bogus-websocket-clients" or "h2-workaround-bogus-websocket-clients" */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_NO) + global.tune.options &= ~GTUNE_DISABLE_H2_WEBSOCKET; + else + global.tune.options |= GTUNE_DISABLE_H2_WEBSOCKET; + } + else if (strcmp(args[0], "insecure-fork-wanted") == 0) { /* "no insecure-fork-wanted" or "insecure-fork-wanted" */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_NO) + global.tune.options &= ~GTUNE_INSECURE_FORK; + else + global.tune.options |= GTUNE_INSECURE_FORK; + } + else if (strcmp(args[0], "insecure-setuid-wanted") == 0) { /* "no insecure-setuid-wanted" or "insecure-setuid-wanted" */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_NO) + global.tune.options &= ~GTUNE_INSECURE_SETUID; + else + global.tune.options |= GTUNE_INSECURE_SETUID; + } + else if (strcmp(args[0], "nosplice") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_SPLICE; + } + else if (strcmp(args[0], "nogetaddrinfo") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_GAI; + } + else if (strcmp(args[0], "noreuseport") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + protocol_clrf_all(PROTO_F_REUSEPORT_SUPPORTED); + } + else if (strcmp(args[0], "quiet") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.mode |= MODE_QUIET; + } + else if (strcmp(args[0], "zero-warning") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.mode |= MODE_ZERO_WARNING; + } + else if (strcmp(args[0], "tune.runqueue-depth") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.runqueue_depth != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.runqueue_depth = atol(args[1]); + + } + else if (strcmp(args[0], "tune.maxpollevents") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.maxpollevents != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.maxpollevents = atol(args[1]); + } + else if (strcmp(args[0], "tune.maxaccept") == 0) { + long max; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.maxaccept != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + max = atol(args[1]); + if (/*max < -1 || */max > INT_MAX) { + ha_alert("parsing [%s:%d] : '%s' expects -1 or an integer from 0 to INT_MAX.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.maxaccept = max; + } + else if (strcmp(args[0], "tune.chksize") == 0) { + ha_alert("parsing [%s:%d]: option '%s' is not supported any more (tune.bufsize is used instead).\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "tune.recv_enough") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.recv_enough = atol(args[1]); + } + else if (strcmp(args[0], "tune.buffers.limit") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.buf_limit = atol(args[1]); + if (global.tune.buf_limit) { + if (global.tune.buf_limit < 3) + global.tune.buf_limit = 3; + if (global.tune.buf_limit <= global.tune.reserved_bufs) + global.tune.buf_limit = global.tune.reserved_bufs + 1; + } + } + else if (strcmp(args[0], "tune.buffers.reserve") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.reserved_bufs = atol(args[1]); + if (global.tune.reserved_bufs < 2) + global.tune.reserved_bufs = 2; + if (global.tune.buf_limit && global.tune.buf_limit <= global.tune.reserved_bufs) + global.tune.buf_limit = global.tune.reserved_bufs + 1; + } + else if (strcmp(args[0], "tune.bufsize") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.bufsize = atol(args[1]); + /* round it up to support a two-pointer alignment at the end */ + global.tune.bufsize = (global.tune.bufsize + 2 * sizeof(void *) - 1) & -(2 * sizeof(void *)); + if (global.tune.bufsize <= 0) { + ha_alert("parsing [%s:%d] : '%s' expects a positive integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "tune.maxrewrite") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.maxrewrite = atol(args[1]); + if (global.tune.maxrewrite < 0) { + ha_alert("parsing [%s:%d] : '%s' expects a positive integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "tune.idletimer") == 0) { + unsigned int idle; + const char *res; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a timer value between 0 and 65535 ms.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + res = parse_time_err(args[1], &idle, TIME_UNIT_MS); + if (res == PARSE_TIME_OVER) { + ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to <%s>, maximum value is 65535 ms.\n", + file, linenum, args[1], args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (res == PARSE_TIME_UNDER) { + ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to <%s>, minimum non-null value is 1 ms.\n", + file, linenum, args[1], args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (res) { + ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to <%s>.\n", + file, linenum, *res, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (idle > 65535) { + ha_alert("parsing [%s:%d] : '%s' expects a timer value between 0 and 65535 ms.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.idle_timer = idle; + } + else if (strcmp(args[0], "tune.rcvbuf.client") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.client_rcvbuf != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.client_rcvbuf = atol(args[1]); + } + else if (strcmp(args[0], "tune.rcvbuf.server") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.server_rcvbuf != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.server_rcvbuf = atol(args[1]); + } + else if (strcmp(args[0], "tune.sndbuf.client") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.client_sndbuf != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.client_sndbuf = atol(args[1]); + } + else if (strcmp(args[0], "tune.sndbuf.server") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.tune.server_sndbuf != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.server_sndbuf = atol(args[1]); + } + else if (strcmp(args[0], "tune.pipesize") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.pipesize = atol(args[1]); + } + else if (strcmp(args[0], "tune.http.cookielen") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.cookie_len = atol(args[1]) + 1; + } + else if (strcmp(args[0], "tune.http.logurilen") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.requri_len = atol(args[1]) + 1; + } + else if (strcmp(args[0], "tune.http.maxhdr") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.tune.max_http_hdr = atoi(args[1]); + if (global.tune.max_http_hdr < 1 || global.tune.max_http_hdr > 32767) { + ha_alert("parsing [%s:%d] : '%s' expects a numeric value between 1 and 32767\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "tune.comp.maxlevel") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*args[1]) { + global.tune.comp_maxlevel = atoi(args[1]); + if (global.tune.comp_maxlevel < 1 || global.tune.comp_maxlevel > 9) { + ha_alert("parsing [%s:%d] : '%s' expects a numeric value between 1 and 9\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } else { + ha_alert("parsing [%s:%d] : '%s' expects a numeric value between 1 and 9\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "tune.pattern.cache-size") == 0) { + if (*args[1]) { + global.tune.pattern_cache = atoi(args[1]); + if (global.tune.pattern_cache < 0) { + ha_alert("parsing [%s:%d] : '%s' expects a positive numeric value\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } else { + ha_alert("parsing [%s:%d] : '%s' expects a positive numeric value\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "tune.disable-fast-forward") == 0) { + if (!experimental_directives_allowed) { + ha_alert("parsing [%s:%d] : '%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED); + + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_USE_FAST_FWD; + } + else if (strcmp(args[0], "tune.disable-zero-copy-forwarding") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.no_zero_copy_fwd |= NO_ZERO_COPY_FWD; + } + else if (strcmp(args[0], "cluster-secret") == 0) { + blk_SHA_CTX sha1_ctx; + unsigned char sha1_out[20]; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*args[1] == 0) { + ha_alert("parsing [%s:%d] : expects an ASCII string argument.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (cluster_secret_isset) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + + blk_SHA1_Init(&sha1_ctx); + blk_SHA1_Update(&sha1_ctx, args[1], strlen(args[1])); + blk_SHA1_Final(sha1_out, &sha1_ctx); + BUG_ON(sizeof sha1_out < sizeof global.cluster_secret); + memcpy(global.cluster_secret, sha1_out, sizeof global.cluster_secret); + cluster_secret_isset = 1; + } + else if (strcmp(args[0], "uid") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.uid != 0) { + ha_alert("parsing [%s:%d] : user/uid already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (strl2irc(args[1], strlen(args[1]), &global.uid) != 0) { + ha_warning("parsing [%s:%d] : uid: string '%s' is not a number.\n | You might want to use the 'user' parameter to use a system user name.\n", file, linenum, args[1]); + err_code |= ERR_WARN; + goto out; + } + + } + else if (strcmp(args[0], "gid") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.gid != 0) { + ha_alert("parsing [%s:%d] : group/gid already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (strl2irc(args[1], strlen(args[1]), &global.gid) != 0) { + ha_warning("parsing [%s:%d] : gid: string '%s' is not a number.\n | You might want to use the 'group' parameter to use a system group name.\n", file, linenum, args[1]); + err_code |= ERR_WARN; + goto out; + } + } + else if (strcmp(args[0], "external-check") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + global.external_check = 1; + if (strcmp(args[1], "preserve-env") == 0) { + global.external_check = 2; + } else if (*args[1]) { + ha_alert("parsing [%s:%d] : '%s' only supports 'preserve-env' as an argument, found '%s'.\n", file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + /* user/group name handling */ + else if (strcmp(args[0], "user") == 0) { + struct passwd *ha_user; + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.uid != 0) { + ha_alert("parsing [%s:%d] : user/uid already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + errno = 0; + ha_user = getpwnam(args[1]); + if (ha_user != NULL) { + global.uid = (int)ha_user->pw_uid; + } + else { + ha_alert("parsing [%s:%d] : cannot find user id for '%s' (%d:%s)\n", file, linenum, args[1], errno, strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + } + } + else if (strcmp(args[0], "group") == 0) { + struct group *ha_group; + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.gid != 0) { + ha_alert("parsing [%s:%d] : gid/group was already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + errno = 0; + ha_group = getgrnam(args[1]); + if (ha_group != NULL) { + global.gid = (int)ha_group->gr_gid; + } + else { + ha_alert("parsing [%s:%d] : cannot find group id for '%s' (%d:%s)\n", file, linenum, args[1], errno, strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + } + } + /* end of user/group name handling*/ + else if (strcmp(args[0], "nbproc") == 0) { + ha_alert("parsing [%s:%d] : nbproc is not supported any more since HAProxy 2.5. Threads will automatically be used on multi-processor machines if available.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "maxconn") == 0) { + char *stop; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.maxconn != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.maxconn = strtol(args[1], &stop, 10); + if (*stop != '\0') { + ha_alert("parsing [%s:%d] : cannot parse '%s' value '%s', an integer is expected.\n", file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } +#ifdef SYSTEM_MAXCONN + if (global.maxconn > SYSTEM_MAXCONN && cfg_maxconn <= SYSTEM_MAXCONN) { + ha_alert("parsing [%s:%d] : maxconn value %d too high for this system.\nLimiting to %d. Please use '-n' to force the value.\n", file, linenum, global.maxconn, SYSTEM_MAXCONN); + global.maxconn = SYSTEM_MAXCONN; + err_code |= ERR_ALERT; + } +#endif /* SYSTEM_MAXCONN */ + } + else if (strcmp(args[0], "ssl-server-verify") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (strcmp(args[1],"none") == 0) + global.ssl_server_verify = SSL_SERVER_VERIFY_NONE; + else if (strcmp(args[1],"required") == 0) + global.ssl_server_verify = SSL_SERVER_VERIFY_REQUIRED; + else { + ha_alert("parsing [%s:%d] : '%s' expects 'none' or 'required' as argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "maxconnrate") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.cps_lim != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.cps_lim = atol(args[1]); + } + else if (strcmp(args[0], "maxsessrate") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.sps_lim != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.sps_lim = atol(args[1]); + } + else if (strcmp(args[0], "maxsslrate") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.ssl_lim != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.ssl_lim = atol(args[1]); + } + else if (strcmp(args[0], "maxcomprate") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument in kb/s.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.comp_rate_lim = atoi(args[1]) * 1024; + } + else if (strcmp(args[0], "maxpipes") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.maxpipes != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.maxpipes = atol(args[1]); + } + else if (strcmp(args[0], "maxzlibmem") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.maxzlibmem = atol(args[1]) * 1024L * 1024L; + } + else if (strcmp(args[0], "maxcompcpuusage") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument between 0 and 100.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + compress_min_idle = 100 - atoi(args[1]); + if (compress_min_idle > 100) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument between 0 and 100.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "fd-hard-limit") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.fd_hard_limit != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.fd_hard_limit = atol(args[1]); + } + else if (strcmp(args[0], "ulimit-n") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.rlimit_nofile != 0) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.rlimit_nofile = atol(args[1]); + } + else if (strcmp(args[0], "chroot") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.chroot != NULL) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a directory as an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.chroot = strdup(args[1]); + } + else if (strcmp(args[0], "description") == 0) { + int i, len=0; + char *d; + + if (!*args[1]) { + ha_alert("parsing [%s:%d]: '%s' expects a string argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + for (i = 1; *args[i]; i++) + len += strlen(args[i]) + 1; + + if (global.desc) + free(global.desc); + + global.desc = d = calloc(1, len); + + d += snprintf(d, global.desc + len - d, "%s", args[1]); + for (i = 2; *args[i]; i++) + d += snprintf(d, global.desc + len - d, " %s", args[i]); + } + else if (strcmp(args[0], "node") == 0) { + int i; + char c; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + for (i=0; args[1][i]; i++) { + c = args[1][i]; + if (!isupper((unsigned char)c) && !islower((unsigned char)c) && + !isdigit((unsigned char)c) && c != '_' && c != '-' && c != '.') + break; + } + + if (!i || args[1][i]) { + ha_alert("parsing [%s:%d]: '%s' requires valid node name - non-empty string" + " with digits(0-9), letters(A-Z, a-z), dot(.), hyphen(-) or underscode(_).\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (global.node) + free(global.node); + + global.node = strdup(args[1]); + } + else if (strcmp(args[0], "pidfile") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.pidfile != NULL) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a file name as an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.pidfile = strdup(args[1]); + } + else if (strcmp(args[0], "unix-bind") == 0) { + int cur_arg = 1; + while (*(args[cur_arg])) { + if (strcmp(args[cur_arg], "prefix") == 0) { + if (global.unix_bind.prefix != NULL) { + ha_alert("parsing [%s:%d] : unix-bind '%s' already specified. Continuing.\n", file, linenum, args[cur_arg]); + err_code |= ERR_ALERT; + cur_arg += 2; + continue; + } + + if (*(args[cur_arg+1]) == 0) { + ha_alert("parsing [%s:%d] : unix_bind '%s' expects a path as an argument.\n", file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.unix_bind.prefix = strdup(args[cur_arg+1]); + cur_arg += 2; + continue; + } + + if (strcmp(args[cur_arg], "mode") == 0) { + + global.unix_bind.ux.mode = strtol(args[cur_arg + 1], NULL, 8); + cur_arg += 2; + continue; + } + + if (strcmp(args[cur_arg], "uid") == 0) { + + global.unix_bind.ux.uid = atol(args[cur_arg + 1 ]); + cur_arg += 2; + continue; + } + + if (strcmp(args[cur_arg], "gid") == 0) { + + global.unix_bind.ux.gid = atol(args[cur_arg + 1 ]); + cur_arg += 2; + continue; + } + + if (strcmp(args[cur_arg], "user") == 0) { + struct passwd *user; + + user = getpwnam(args[cur_arg + 1]); + if (!user) { + ha_alert("parsing [%s:%d] : '%s' : '%s' unknown user.\n", + file, linenum, args[0], args[cur_arg + 1 ]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + global.unix_bind.ux.uid = user->pw_uid; + cur_arg += 2; + continue; + } + + if (strcmp(args[cur_arg], "group") == 0) { + struct group *group; + + group = getgrnam(args[cur_arg + 1]); + if (!group) { + ha_alert("parsing [%s:%d] : '%s' : '%s' unknown group.\n", + file, linenum, args[0], args[cur_arg + 1 ]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + global.unix_bind.ux.gid = group->gr_gid; + cur_arg += 2; + continue; + } + + ha_alert("parsing [%s:%d] : '%s' only supports the 'prefix', 'mode', 'uid', 'gid', 'user' and 'group' options.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "log") == 0) { /* "no log" or "log ..." */ + if (!parse_logger(args, &global.loggers, (kwm == KWM_NO), file, linenum, &errmsg)) { + ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "log-send-hostname") == 0) { /* set the hostname in syslog header */ + char *name; + + if (global.log_send_hostname != NULL) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + + if (*(args[1])) + name = args[1]; + else + name = hostname; + + free(global.log_send_hostname); + global.log_send_hostname = strdup(name); + } + else if (strcmp(args[0], "server-state-base") == 0) { /* path base where HAProxy can find server state files */ + if (global.server_state_base != NULL) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : '%s' expects one argument: a directory path.\n", file, linenum, args[0]); + err_code |= ERR_FATAL; + goto out; + } + + global.server_state_base = strdup(args[1]); + } + else if (strcmp(args[0], "server-state-file") == 0) { /* path to the file where HAProxy can load the server states */ + if (global.server_state_file != NULL) { + ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : '%s' expect one argument: a file path.\n", file, linenum, args[0]); + err_code |= ERR_FATAL; + goto out; + } + + global.server_state_file = strdup(args[1]); + } + else if (strcmp(args[0], "log-tag") == 0) { /* tag to report to syslog */ + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a tag for use in syslog.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + chunk_destroy(&global.log_tag); + chunk_initlen(&global.log_tag, strdup(args[1]), strlen(args[1]), strlen(args[1])); + if (b_orig(&global.log_tag) == NULL) { + chunk_destroy(&global.log_tag); + ha_alert("parsing [%s:%d]: cannot allocate memory for '%s'.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "spread-checks") == 0) { /* random time between checks (0-50) */ + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (global.spread_checks != 0) { + ha_alert("parsing [%s:%d]: spread-checks already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d]: '%s' expects an integer argument (0..50).\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.spread_checks = atol(args[1]); + if (global.spread_checks < 0 || global.spread_checks > 50) { + ha_alert("parsing [%s:%d]: 'spread-checks' needs a positive value in range 0..50.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + } + } + else if (strcmp(args[0], "max-spread-checks") == 0) { /* maximum time between first and last check */ + const char *err; + unsigned int val; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d]: '%s' expects an integer argument (0..50).\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err = parse_time_err(args[1], &val, TIME_UNIT_MS); + if (err == PARSE_TIME_OVER) { + ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to <%s>, maximum value is 2147483647 ms (~24.8 days).\n", + file, linenum, args[1], args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + } + else if (err == PARSE_TIME_UNDER) { + ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to <%s>, minimum non-null value is 1 ms.\n", + file, linenum, args[1], args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + } + else if (err) { + ha_alert("parsing [%s:%d]: unsupported character '%c' in '%s' (wants an integer delay).\n", file, linenum, *err, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + } + global.max_spread_checks = val; + } + else if (strcmp(args[0], "cpu-map") == 0) { + /* map a process list to a CPU set */ +#ifdef USE_CPU_AFFINITY + char *slash; + unsigned long tgroup = 0, thread = 0; + int g, j, n, autoinc; + struct hap_cpuset cpus, cpus_copy; + + if (!*args[1] || !*args[2]) { + ha_alert("parsing [%s:%d] : %s expects a thread group number " + " ('all', 'odd', 'even', a number from 1 to %d or a range), " + " followed by a list of CPU ranges with numbers from 0 to %d.\n", + file, linenum, args[0], LONGBITS, LONGBITS - 1); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if ((slash = strchr(args[1], '/')) != NULL) + *slash = 0; + + /* note: we silently ignore thread group numbers over MAX_TGROUPS + * and threads over MAX_THREADS so as not to make configurations a + * pain to maintain. + */ + if (parse_process_number(args[1], &tgroup, LONGBITS, &autoinc, &errmsg)) { + ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (slash) { + if (parse_process_number(slash+1, &thread, LONGBITS, NULL, &errmsg)) { + ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + *slash = '/'; + } else + thread = ~0UL; /* missing '/' = 'all' */ + + /* from now on, thread cannot be NULL anymore */ + + if (parse_cpu_set((const char **)args+2, &cpus, &errmsg)) { + ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (autoinc && + my_popcountl(tgroup) != ha_cpuset_count(&cpus) && + my_popcountl(thread) != ha_cpuset_count(&cpus)) { + ha_alert("parsing [%s:%d] : %s : TGROUP/THREAD range and CPU sets " + "must have the same size to be automatically bound\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* we now have to deal with 3 real cases : + * cpu-map P-Q => mapping for whole tgroups, numbers P to Q + * cpu-map P-Q/1 => mapping of first thread of groups P to Q + * cpu-map P/T-U => mapping of threads T to U of tgroup P + */ + /* first tgroup, iterate on threads. E.g. cpu-map 1/1-4 0-3 */ + for (g = 0; g < MAX_TGROUPS; g++) { + /* No mapping for this tgroup */ + if (!(tgroup & (1UL << g))) + continue; + + ha_cpuset_assign(&cpus_copy, &cpus); + + /* a thread set is specified, apply the + * CPU set to these threads. + */ + for (j = n = 0; j < MAX_THREADS_PER_GROUP; j++) { + /* No mapping for this thread */ + if (!(thread & (1UL << j))) + continue; + + if (!autoinc) + ha_cpuset_assign(&cpu_map[g].thread[j], &cpus); + else { + ha_cpuset_zero(&cpu_map[g].thread[j]); + n = ha_cpuset_ffs(&cpus_copy) - 1; + ha_cpuset_clr(&cpus_copy, n); + ha_cpuset_set(&cpu_map[g].thread[j], n); + } + } + } +#else + ha_alert("parsing [%s:%d] : '%s' is not enabled, please check build options for USE_CPU_AFFINITY.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; +#endif /* ! USE_CPU_AFFINITY */ + } + else if (strcmp(args[0], "setenv") == 0 || strcmp(args[0], "presetenv") == 0) { + if (alertif_too_many_args(3, file, linenum, args, &err_code)) + goto out; + + if (*(args[2]) == 0) { + ha_alert("parsing [%s:%d]: '%s' expects a name and a value.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* "setenv" overwrites, "presetenv" only sets if not yet set */ + if (setenv(args[1], args[2], (args[0][0] == 's')) != 0) { + ha_alert("parsing [%s:%d]: '%s' failed on variable '%s' : %s.\n", file, linenum, args[0], args[1], strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "unsetenv") == 0) { + int arg; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d]: '%s' expects at least one variable name.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + for (arg = 1; *args[arg]; arg++) { + if (unsetenv(args[arg]) != 0) { + ha_alert("parsing [%s:%d]: '%s' failed on variable '%s' : %s.\n", file, linenum, args[0], args[arg], strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + } + else if (strcmp(args[0], "resetenv") == 0) { + extern char **environ; + char **env = environ; + + /* args contain variable names to keep, one per argument */ + while (*env) { + int arg; + + /* look for current variable in among all those we want to keep */ + for (arg = 1; *args[arg]; arg++) { + if (strncmp(*env, args[arg], strlen(args[arg])) == 0 && + (*env)[strlen(args[arg])] == '=') + break; + } + + /* delete this variable */ + if (!*args[arg]) { + char *delim = strchr(*env, '='); + + if (!delim || delim - *env >= trash.size) { + ha_alert("parsing [%s:%d]: '%s' failed to unset invalid variable '%s'.\n", file, linenum, args[0], *env); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + memcpy(trash.area, *env, delim - *env); + trash.area[delim - *env] = 0; + + if (unsetenv(trash.area) != 0) { + ha_alert("parsing [%s:%d]: '%s' failed to unset variable '%s' : %s.\n", file, linenum, args[0], *env, strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else + env++; + } + } + else if (strcmp(args[0], "quick-exit") == 0) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options |= GTUNE_QUICK_EXIT; + } + else if (strcmp(args[0], "strict-limits") == 0) { /* "no strict-limits" or "strict-limits" */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_NO) + global.tune.options &= ~GTUNE_STRICT_LIMITS; + } + else if (strcmp(args[0], "localpeer") == 0) { + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a name as an argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (global.localpeer_cmdline != 0) { + ha_warning("parsing [%s:%d] : '%s' ignored since it is already set by using the '-L' " + "command line argument.\n", file, linenum, args[0]); + err_code |= ERR_WARN; + goto out; + } + + if (cfg_peers) { + ha_warning("parsing [%s:%d] : '%s' ignored since it is used after 'peers' section.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + goto out; + } + + free(localpeer); + if ((localpeer = strdup(args[1])) == NULL) { + ha_alert("parsing [%s:%d]: cannot allocate memory for '%s'.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + setenv("HAPROXY_LOCALPEER", localpeer, 1); + } + else if (strcmp(args[0], "numa-cpu-mapping") == 0) { + global.numa_cpu_mapping = (kwm == KWM_NO) ? 0 : 1; + } + else if (strcmp(args[0], "anonkey") == 0) { + long long tmp = 0; + + if (*args[1] == 0) { + ha_alert("parsing [%s:%d]: a key is expected after '%s'.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (HA_ATOMIC_LOAD(&global.anon_key) == 0) { + tmp = atoll(args[1]); + if (tmp < 0 || tmp > UINT_MAX) { + ha_alert("parsing [%s:%d]: '%s' value must be within range %u-%u (was '%s').\n", + file, linenum, args[0], 0, UINT_MAX, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + HA_ATOMIC_STORE(&global.anon_key, tmp); + } + } + else { + struct cfg_kw_list *kwl; + const char *best; + int index; + int rc; + + list_for_each_entry(kwl, &cfg_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if (kwl->kw[index].section != CFG_GLOBAL) + continue; + if (strcmp(kwl->kw[index].kw, args[0]) == 0) { + if (check_kw_experimental(&kwl->kw[index], file, linenum, &errmsg)) { + ha_alert("%s\n", errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + rc = kwl->kw[index].parse(args, CFG_GLOBAL, NULL, NULL, file, linenum, &errmsg); + if (rc < 0) { + ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + } + else if (rc > 0) { + ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_WARN; + goto out; + } + goto out; + } + } + } + + best = cfg_find_best_match(args[0], &cfg_keywords.list, CFG_GLOBAL, common_kw_list); + if (best) + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section; did you mean '%s' maybe ?\n", file, linenum, args[0], cursection, best); + else + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "global"); + err_code |= ERR_ALERT | ERR_FATAL; + } + + out: + free(errmsg); + return err_code; +} + +static int cfg_parse_prealloc_fd(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(0, args, err, NULL)) + return -1; + + global.prealloc_fd = 1; + + return 0; +} + +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_GLOBAL, "prealloc-fd", cfg_parse_prealloc_fd }, + { 0, NULL, NULL }, +}}; + +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c new file mode 100644 index 0000000..4f88b77 --- /dev/null +++ b/src/cfgparse-listen.c @@ -0,0 +1,3073 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* some keywords that are still being parsed using strcmp() and are not + * registered anywhere. They are used as suggestions for mistyped words. + */ +static const char *common_kw_list[] = { + "listen", "frontend", "backend", "defaults", "server", + "default-server", "server-template", "bind", "monitor-net", + "monitor-uri", "mode", "id", "description", "disabled", "enabled", + "acl", "dynamic-cookie-key", "cookie", "email-alert", + "persist", "appsession", "load-server-state-from-file", + "server-state-file-name", "max-session-srv-conns", "capture", + "retries", "http-request", "http-response", "http-after-response", + "http-send-name-header", "block", "redirect", "use_backend", + "use-server", "force-persist", "ignore-persist", "force-persist", + "stick-table", "stick", "stats", "option", "default_backend", + "http-reuse", "monitor", "transparent", "maxconn", "backlog", + "fullconn", "dispatch", "balance", "log-balance", "hash-type", + "hash-balance-factor", "unique-id-format", "unique-id-header", + "log-format", "log-format-sd", "log-tag", "log", "source", "usesrc", + "error-log-format", + NULL /* must be last */ +}; + +static const char *common_options[] = { + "httpclose", "http-server-close", "http-keep-alive", + "redispatch", "httplog", "tcplog", "tcpka", "httpchk", + "ssl-hello-chk", "smtpchk", "pgsql-check", "redis-check", + "mysql-check", "ldap-check", "spop-check", "tcp-check", + "external-check", "forwardfor", "original-to", "forwarded", + NULL /* must be last */ +}; + +/* Report a warning if a rule is placed after a 'tcp-request session' rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_tcp_sess(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->tcp_req.l5_rules)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after a 'tcp-request session' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* Report a warning if a rule is placed after a 'tcp-request content' rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_tcp_cont(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->tcp_req.inspect_rules)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after a 'tcp-request content' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* Report a warning if a rule is placed after a 'monitor fail' rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_monitor(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->mon_fail_cond)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after a 'monitor fail' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* Report a warning if a rule is placed after an 'http_request' rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_http_req(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->http_req_rules)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after an 'http-request' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* Report a warning if a rule is placed after a redirect rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_redirect(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->redirect_rules)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after a 'redirect' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* Report a warning if a rule is placed after a 'use_backend' rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_use_backend(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->switching_rules)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after a 'use_backend' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* Report a warning if a rule is placed after a 'use-server' rule. + * Return 1 if the warning has been emitted, otherwise 0. + */ +int warnif_rule_after_use_server(struct proxy *proxy, const char *file, int line, const char *arg) +{ + if (!LIST_ISEMPTY(&proxy->server_rules)) { + ha_warning("parsing [%s:%d] : a '%s' rule placed after a 'use-server' rule will still be processed before.\n", + file, line, arg); + return 1; + } + return 0; +} + +/* report a warning if a redirect rule is dangerously placed */ +int warnif_misplaced_redirect(struct proxy *proxy, const char *file, int line, const char *arg) +{ + return warnif_rule_after_use_backend(proxy, file, line, arg) || + warnif_rule_after_use_server(proxy, file, line, arg); +} + +/* report a warning if an http-request rule is dangerously placed */ +int warnif_misplaced_http_req(struct proxy *proxy, const char *file, int line, const char *arg) +{ + return warnif_rule_after_redirect(proxy, file, line, arg) || + warnif_misplaced_redirect(proxy, file, line, arg); +} + +/* report a warning if a block rule is dangerously placed */ +int warnif_misplaced_monitor(struct proxy *proxy, const char *file, int line, const char *arg) +{ + return warnif_rule_after_http_req(proxy, file, line, arg) || + warnif_misplaced_http_req(proxy, file, line, arg); +} + +/* report a warning if a "tcp request content" rule is dangerously placed */ +int warnif_misplaced_tcp_cont(struct proxy *proxy, const char *file, int line, const char *arg) +{ + return warnif_rule_after_monitor(proxy, file, line, arg) || + warnif_misplaced_monitor(proxy, file, line, arg); +} + +/* report a warning if a "tcp request session" rule is dangerously placed */ +int warnif_misplaced_tcp_sess(struct proxy *proxy, const char *file, int line, const char *arg) +{ + return warnif_rule_after_tcp_cont(proxy, file, line, arg) || + warnif_misplaced_tcp_cont(proxy, file, line, arg); +} + +/* report a warning if a "tcp request connection" rule is dangerously placed */ +int warnif_misplaced_tcp_conn(struct proxy *proxy, const char *file, int line, const char *arg) +{ + return warnif_rule_after_tcp_sess(proxy, file, line, arg) || + warnif_misplaced_tcp_sess(proxy, file, line, arg); +} + +int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) +{ + static struct proxy *curr_defproxy = NULL; + static struct proxy *last_defproxy = NULL; + const char *err; + int rc; + int err_code = 0; + struct acl_cond *cond = NULL; + char *errmsg = NULL; + struct bind_conf *bind_conf; + + if (!last_defproxy) { + /* we need a default proxy and none was created yet */ + last_defproxy = alloc_new_proxy("", PR_CAP_DEF|PR_CAP_LISTEN, &errmsg); + + curr_defproxy = last_defproxy; + if (!last_defproxy) { + ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + } + + if (strcmp(args[0], "listen") == 0) + rc = PR_CAP_LISTEN | PR_CAP_LB; + else if (strcmp(args[0], "frontend") == 0) + rc = PR_CAP_FE | PR_CAP_LB; + else if (strcmp(args[0], "backend") == 0) + rc = PR_CAP_BE | PR_CAP_LB; + else if (strcmp(args[0], "defaults") == 0) { + /* "defaults" must first delete the last no-name defaults if any */ + curr_defproxy = NULL; + rc = PR_CAP_DEF | PR_CAP_LISTEN; + } + else + rc = PR_CAP_NONE; + + if ((rc & PR_CAP_LISTEN) && !(rc & PR_CAP_DEF)) { /* new proxy */ + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' expects an argument\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + err = invalid_char(args[1]); + if (err) { + ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n", + file, linenum, *err, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + } + + curproxy = (rc & PR_CAP_FE) ? proxy_fe_by_name(args[1]) : proxy_be_by_name(args[1]); + if (curproxy) { + ha_alert("Parsing [%s:%d]: %s '%s' has the same name as %s '%s' declared at %s:%d.\n", + file, linenum, proxy_cap_str(rc), args[1], proxy_type_str(curproxy), + curproxy->id, curproxy->conf.file, curproxy->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + } + + curproxy = log_forward_by_name(args[1]); + if (curproxy) { + ha_alert("Parsing [%s:%d]: %s '%s' has the same name as log forward section '%s' declared at %s:%d.\n", + file, linenum, proxy_cap_str(rc), args[1], + curproxy->id, curproxy->conf.file, curproxy->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + } + + if ((*args[2] && (!*args[3] || strcmp(args[2], "from") != 0)) || + alertif_too_many_args(3, file, linenum, args, &err_code)) { + if (rc & PR_CAP_FE) + ha_alert("parsing [%s:%d] : please use the 'bind' keyword for listening addresses.\n", file, linenum); + goto out; + } + } + + if (rc & PR_CAP_LISTEN) { /* new proxy or defaults section */ + const char *name = args[1]; + int arg = 2; + + if (rc & PR_CAP_DEF && strcmp(args[1], "from") == 0 && *args[2] && !*args[3]) { + // also support "defaults from blah" (no name then) + arg = 1; + name = ""; + } + + /* only regular proxies inherit from the previous defaults section */ + if (!(rc & PR_CAP_DEF)) + curr_defproxy = last_defproxy; + + if (strcmp(args[arg], "from") == 0) { + struct ebpt_node *next_by_name; + + curr_defproxy = proxy_find_by_name(args[arg+1], PR_CAP_DEF, 0); + + if (!curr_defproxy) { + ha_alert("parsing [%s:%d] : defaults section '%s' not found for %s '%s'.\n", file, linenum, args[arg+1], proxy_cap_str(rc), name); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if ((next_by_name = ebpt_next_dup(&curr_defproxy->conf.by_name))) { + struct proxy *px2 = container_of(next_by_name, struct proxy, conf.by_name); + + ha_alert("parsing [%s:%d] : ambiguous defaults section name '%s' referenced by %s '%s' exists at least at %s:%d and %s:%d.\n", + file, linenum, args[arg+1], proxy_cap_str(rc), name, + curr_defproxy->conf.file, curr_defproxy->conf.line, px2->conf.file, px2->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + } + + err = invalid_char(args[arg+1]); + if (err) { + ha_alert("parsing [%s:%d] : character '%c' is not permitted in defaults section name '%s' when designated by its name (section found at %s:%d).\n", + file, linenum, *err, args[arg+1], curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + } + curr_defproxy->flags |= PR_FL_EXPLICIT_REF; + } + else if (curr_defproxy) + curr_defproxy->flags |= PR_FL_IMPLICIT_REF; + + if (curr_defproxy && (curr_defproxy->flags & (PR_FL_EXPLICIT_REF|PR_FL_IMPLICIT_REF)) == (PR_FL_EXPLICIT_REF|PR_FL_IMPLICIT_REF)) { + ha_warning("parsing [%s:%d] : defaults section '%s' (declared at %s:%d) is explicitly referenced by another proxy and implicitly used here." + " To avoid any ambiguity don't mix both usage. Add a last defaults section not explicitly used or always use explicit references.\n", + file, linenum, curr_defproxy->id, curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_WARN; + } + + curproxy = parse_new_proxy(name, rc, file, linenum, curr_defproxy); + if (!curproxy) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if (curr_defproxy && (!LIST_ISEMPTY(&curr_defproxy->http_req_rules) || + !LIST_ISEMPTY(&curr_defproxy->http_res_rules) || + !LIST_ISEMPTY(&curr_defproxy->http_after_res_rules) || + !LIST_ISEMPTY(&curr_defproxy->tcp_req.l4_rules) || + !LIST_ISEMPTY(&curr_defproxy->tcp_req.l5_rules) || + !LIST_ISEMPTY(&curr_defproxy->tcp_req.inspect_rules) || + !LIST_ISEMPTY(&curr_defproxy->tcp_rep.inspect_rules))) { + /* If the current default proxy defines TCP/HTTP rules, the + * current proxy will keep a reference on it. But some sanity + * checks are performed first: + * + * - It cannot be used to init a defaults section + * - It cannot be used to init a listen section + * - It cannot be used to init backend and frontend sections at + * same time. It can be used to init several sections of the + * same type only. + * - It cannot define L4/L5 TCP rules if it is used to init + * backend sections. + * - It cannot define 'tcp-response content' rules if it + * is used to init frontend sections. + * + * If no error is found, refcount of the default proxy is incremented. + */ + + /* Note: Add tcpcheck_rules too if unresolve args become allowed in defaults section */ + if (rc & PR_CAP_DEF) { + ha_alert("parsing [%s:%d]: a defaults section cannot inherit from a defaults section defining TCP/HTTP rules (defaults section at %s:%d).\n", + file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + } + else if ((rc & PR_CAP_LISTEN) == PR_CAP_LISTEN) { + ha_alert("parsing [%s:%d]: a listen section cannot inherit from a defaults section defining TCP/HTTP rules.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + } + else { + char defcap = (curr_defproxy->cap & PR_CAP_LISTEN); + + if ((defcap == PR_CAP_BE || defcap == PR_CAP_FE) && (rc & PR_CAP_LISTEN) != defcap) { + ha_alert("parsing [%s:%d]: frontends and backends cannot inherit from the same defaults section" + " if it defines TCP/HTTP rules (defaults section at %s:%d).\n", + file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + } + else if (!(rc & PR_CAP_FE) && (!LIST_ISEMPTY(&curr_defproxy->tcp_req.l4_rules) || + !LIST_ISEMPTY(&curr_defproxy->tcp_req.l5_rules))) { + ha_alert("parsing [%s:%d]: a backend section cannot inherit from a defaults section defining" + " 'tcp-request connection' or 'tcp-request session' rules (defaults section at %s:%d).\n", + file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + } + else if (!(rc & PR_CAP_BE) && !LIST_ISEMPTY(&curr_defproxy->tcp_rep.inspect_rules)) { + ha_alert("parsing [%s:%d]: a frontend section cannot inherit from a defaults section defining" + " 'tcp-response content' rules (defaults section at %s:%d).\n", + file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + } + else { + curr_defproxy->cap = (curr_defproxy->cap & ~PR_CAP_LISTEN) | (rc & PR_CAP_LISTEN); + proxy_ref_defaults(curproxy, curr_defproxy); + } + } + } + + if (curr_defproxy && (curr_defproxy->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) && + (curproxy->cap & PR_CAP_LISTEN) == PR_CAP_BE) { + /* If the current default proxy defines tcpcheck rules, the + * current proxy will keep a reference on it. but only if the + * current proxy has the backend capability. + */ + proxy_ref_defaults(curproxy, curr_defproxy); + } + + if ((rc & PR_CAP_BE) && curr_defproxy && (curr_defproxy->nb_req_cap || curr_defproxy->nb_rsp_cap)) { + ha_alert("parsing [%s:%d]: backend or defaults sections cannot inherit from a defaults section defining" + " capptures (defaults section at %s:%d).\n", + file, linenum, curr_defproxy->conf.file, curr_defproxy->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + } + + if (rc & PR_CAP_DEF) { + /* last and current proxies must be updated to this one */ + curr_defproxy = last_defproxy = curproxy; + } else { + /* regular proxies are in a list */ + curproxy->next = proxies_list; + proxies_list = curproxy; + } + goto out; + } + else if (curproxy == NULL) { + ha_alert("parsing [%s:%d] : 'listen' or 'defaults' expected.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* update the current file and line being parsed */ + curproxy->conf.args.file = curproxy->conf.file; + curproxy->conf.args.line = linenum; + + /* Now let's parse the proxy-specific keywords */ + if ((strcmp(args[0], "server") == 0)) { + err_code |= parse_server(file, linenum, args, + curproxy, curr_defproxy, + SRV_PARSE_PARSE_ADDR); + + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[0], "default-server") == 0) { + err_code |= parse_server(file, linenum, args, + curproxy, curr_defproxy, + SRV_PARSE_DEFAULT_SERVER); + + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[0], "server-template") == 0) { + err_code |= parse_server(file, linenum, args, + curproxy, curr_defproxy, + SRV_PARSE_TEMPLATE|SRV_PARSE_PARSE_ADDR); + + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[0], "bind") == 0) { /* new listen addresses */ + struct listener *l; + int cur_arg; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : '%s' expects {|[addr1]:port1[-end1]}{,[addr]:port[-end]}... as arguments.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + bind_conf = bind_conf_alloc(curproxy, file, linenum, args[1], xprt_get(XPRT_RAW)); + if (!bind_conf) + goto alloc_error; + + /* use default settings for unix sockets */ + bind_conf->settings.ux.uid = global.unix_bind.ux.uid; + bind_conf->settings.ux.gid = global.unix_bind.ux.gid; + bind_conf->settings.ux.mode = global.unix_bind.ux.mode; + + /* NOTE: the following line might create several listeners if there + * are comma-separated IPs or port ranges. So all further processing + * will have to be applied to all listeners created after last_listen. + */ + if (!str2listener(args[1], curproxy, bind_conf, file, linenum, &errmsg)) { + if (errmsg && *errmsg) { + indent_msg(&errmsg, 2); + ha_alert("parsing [%s:%d] : '%s' : %s\n", file, linenum, args[0], errmsg); + } + else + ha_alert("parsing [%s:%d] : '%s' : error encountered while parsing listening address '%s'.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + list_for_each_entry(l, &bind_conf->listeners, by_bind) { + /* Set default global rights and owner for unix bind */ + global.maxsock++; + } + + cur_arg = 2; + err_code |= bind_parse_args_list(bind_conf, args, cur_arg, cursection, file, linenum); + goto out; + } + else if (strcmp(args[0], "monitor-net") == 0) { /* set the range of IPs to ignore */ + ha_alert("parsing [%s:%d] : 'monitor-net' doesn't exist anymore. Please use 'http-request return status 200 if { src %s }' instead.\n", file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "monitor-uri") == 0) { /* set the URI to intercept */ + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' expects an URI.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + istfree(&curproxy->monitor_uri); + curproxy->monitor_uri = istdup(ist(args[1])); + if (!isttest(curproxy->monitor_uri)) + goto alloc_error; + + goto out; + } + else if (strcmp(args[0], "mode") == 0) { /* sets the proxy mode */ + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + if (strcmp(args[1], "http") == 0) curproxy->mode = PR_MODE_HTTP; + else if (strcmp(args[1], "tcp") == 0) curproxy->mode = PR_MODE_TCP; + else if (strcmp(args[1], "log") == 0 && (curproxy->cap & PR_CAP_BE)) curproxy->mode = PR_MODE_SYSLOG; + else if (strcmp(args[1], "health") == 0) { + ha_alert("parsing [%s:%d] : 'mode health' doesn't exist anymore. Please use 'http-request return status 200' instead.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else { + ha_alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "id") == 0) { + struct eb32_node *node; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + if (!*args[1]) { + ha_alert("parsing [%s:%d]: '%s' expects an integer argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->uuid = atol(args[1]); + curproxy->conf.id.key = curproxy->uuid; + curproxy->options |= PR_O_FORCED_ID; + + if (curproxy->uuid <= 0) { + ha_alert("parsing [%s:%d]: custom id has to be > 0.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + node = eb32_lookup(&used_proxy_id, curproxy->uuid); + if (node) { + struct proxy *target = container_of(node, struct proxy, conf.id); + ha_alert("parsing [%s:%d]: %s %s reuses same custom id as %s %s (declared at %s:%d).\n", + file, linenum, proxy_type_str(curproxy), curproxy->id, + proxy_type_str(target), target->id, target->conf.file, target->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + eb32_insert(&used_proxy_id, &curproxy->conf.id); + } + else if (strcmp(args[0], "description") == 0) { + int i, len=0; + char *d; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!*args[1]) { + ha_alert("parsing [%s:%d]: '%s' expects a string argument.\n", + file, linenum, args[0]); + return -1; + } + + for (i = 1; *args[i]; i++) + len += strlen(args[i]) + 1; + + d = calloc(1, len); + if (!d) + goto alloc_error; + curproxy->desc = d; + + d += snprintf(d, curproxy->desc + len - d, "%s", args[1]); + for (i = 2; *args[i]; i++) + d += snprintf(d, curproxy->desc + len - d, " %s", args[i]); + + } + else if (strcmp(args[0], "disabled") == 0) { /* disables this proxy */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + curproxy->flags |= PR_FL_DISABLED; + } + else if (strcmp(args[0], "enabled") == 0) { /* enables this proxy (used to revert a disabled default) */ + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + curproxy->flags &= ~PR_FL_DISABLED; + } + else if (strcmp(args[0], "bind-process") == 0) { /* enable this proxy only on some processes */ + ha_alert("parsing [%s:%d]: '%s' is not supported anymore.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + } + else if (strcmp(args[0], "acl") == 0) { /* add an ACL */ + if ((curproxy->cap & PR_CAP_DEF) && strlen(curproxy->id) == 0) { + ha_alert("parsing [%s:%d] : '%s' not allowed in anonymous 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err = invalid_char(args[1]); + if (err) { + ha_alert("parsing [%s:%d] : character '%c' is not permitted in acl name '%s'.\n", + file, linenum, *err, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (strcasecmp(args[1], "or") == 0) { + ha_alert("parsing [%s:%d] : acl name '%s' will never match. 'or' is used to express a " + "logical disjunction within a condition.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (parse_acl((const char **)args + 1, &curproxy->acl, &errmsg, &curproxy->conf.args, file, linenum) == NULL) { + ha_alert("parsing [%s:%d] : error detected while parsing ACL '%s' : %s.\n", + file, linenum, args[1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "dynamic-cookie-key") == 0) { /* Dynamic cookies secret key */ + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects as argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->dyncookie_key); + curproxy->dyncookie_key = strdup(args[1]); + } + else if (strcmp(args[0], "cookie") == 0) { /* cookie name */ + int cur_arg; + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects as argument.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->ck_opts = 0; + curproxy->cookie_maxidle = curproxy->cookie_maxlife = 0; + ha_free(&curproxy->cookie_domain); + free(curproxy->cookie_name); + curproxy->cookie_name = strdup(args[1]); + if (!curproxy->cookie_name) + goto alloc_error; + curproxy->cookie_len = strlen(curproxy->cookie_name); + + cur_arg = 2; + while (*(args[cur_arg])) { + if (strcmp(args[cur_arg], "rewrite") == 0) { + curproxy->ck_opts |= PR_CK_RW; + } + else if (strcmp(args[cur_arg], "indirect") == 0) { + curproxy->ck_opts |= PR_CK_IND; + } + else if (strcmp(args[cur_arg], "insert") == 0) { + curproxy->ck_opts |= PR_CK_INS; + } + else if (strcmp(args[cur_arg], "nocache") == 0) { + curproxy->ck_opts |= PR_CK_NOC; + } + else if (strcmp(args[cur_arg], "postonly") == 0) { + curproxy->ck_opts |= PR_CK_POST; + } + else if (strcmp(args[cur_arg], "preserve") == 0) { + curproxy->ck_opts |= PR_CK_PSV; + } + else if (strcmp(args[cur_arg], "prefix") == 0) { + curproxy->ck_opts |= PR_CK_PFX; + } + else if (strcmp(args[cur_arg], "httponly") == 0) { + curproxy->ck_opts |= PR_CK_HTTPONLY; + } + else if (strcmp(args[cur_arg], "secure") == 0) { + curproxy->ck_opts |= PR_CK_SECURE; + } + else if (strcmp(args[cur_arg], "domain") == 0) { + if (!*args[cur_arg + 1]) { + ha_alert("parsing [%s:%d]: '%s' expects as argument.\n", + file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!strchr(args[cur_arg + 1], '.')) { + /* rfc6265, 5.2.3 The Domain Attribute */ + ha_warning("parsing [%s:%d]: domain '%s' contains no embedded dot," + " this configuration may not work properly (see RFC6265#5.2.3).\n", + file, linenum, args[cur_arg + 1]); + err_code |= ERR_WARN; + } + + err = invalid_domainchar(args[cur_arg + 1]); + if (err) { + ha_alert("parsing [%s:%d]: character '%c' is not permitted in domain name '%s'.\n", + file, linenum, *err, args[cur_arg + 1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!curproxy->cookie_domain) { + curproxy->cookie_domain = strdup(args[cur_arg + 1]); + } else { + /* one domain was already specified, add another one by + * building the string which will be returned along with + * the cookie. + */ + memprintf(&curproxy->cookie_domain, "%s; domain=%s", curproxy->cookie_domain, args[cur_arg+1]); + } + + if (!curproxy->cookie_domain) + goto alloc_error; + cur_arg++; + } + else if (strcmp(args[cur_arg], "maxidle") == 0) { + unsigned int maxidle; + const char *res; + + if (!*args[cur_arg + 1]) { + ha_alert("parsing [%s:%d]: '%s' expects in seconds as argument.\n", + file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + res = parse_time_err(args[cur_arg + 1], &maxidle, TIME_UNIT_S); + if (res == PARSE_TIME_OVER) { + ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to <%s>, maximum value is 2147483647 s (~68 years).\n", + file, linenum, args[cur_arg+1], args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (res == PARSE_TIME_UNDER) { + ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to <%s>, minimum non-null value is 1 s.\n", + file, linenum, args[cur_arg+1], args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (res) { + ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to <%s>.\n", + file, linenum, *res, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->cookie_maxidle = maxidle; + cur_arg++; + } + else if (strcmp(args[cur_arg], "maxlife") == 0) { + unsigned int maxlife; + const char *res; + + if (!*args[cur_arg + 1]) { + ha_alert("parsing [%s:%d]: '%s' expects in seconds as argument.\n", + file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + + res = parse_time_err(args[cur_arg + 1], &maxlife, TIME_UNIT_S); + if (res == PARSE_TIME_OVER) { + ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to <%s>, maximum value is 2147483647 s (~68 years).\n", + file, linenum, args[cur_arg+1], args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (res == PARSE_TIME_UNDER) { + ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to <%s>, minimum non-null value is 1 s.\n", + file, linenum, args[cur_arg+1], args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (res) { + ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to <%s>.\n", + file, linenum, *res, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->cookie_maxlife = maxlife; + cur_arg++; + } + else if (strcmp(args[cur_arg], "dynamic") == 0) { /* Dynamic persistent cookies secret key */ + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[cur_arg], NULL)) + err_code |= ERR_WARN; + curproxy->ck_opts |= PR_CK_DYNAMIC; + } + else if (strcmp(args[cur_arg], "attr") == 0) { + char *val; + if (!*args[cur_arg + 1]) { + ha_alert("parsing [%s:%d]: '%s' expects as argument.\n", + file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + val = args[cur_arg + 1]; + while (*val) { + if (iscntrl((unsigned char)*val) || *val == ';') { + ha_alert("parsing [%s:%d]: character '%%x%02X' is not permitted in attribute value.\n", + file, linenum, *val); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + val++; + } + /* don't add ';' for the first attribute */ + if (!curproxy->cookie_attrs) + curproxy->cookie_attrs = strdup(args[cur_arg + 1]); + else + memprintf(&curproxy->cookie_attrs, "%s; %s", curproxy->cookie_attrs, args[cur_arg + 1]); + + if (!curproxy->cookie_attrs) + goto alloc_error; + cur_arg++; + } + + else { + ha_alert("parsing [%s:%d] : '%s' supports 'rewrite', 'insert', 'prefix', 'indirect', 'nocache', 'postonly', 'domain', 'maxidle', 'dynamic', 'maxlife' and 'attr' options.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + cur_arg++; + } + if (!POWEROF2(curproxy->ck_opts & (PR_CK_RW|PR_CK_IND))) { + ha_alert("parsing [%s:%d] : cookie 'rewrite' and 'indirect' modes are incompatible.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + } + + if (!POWEROF2(curproxy->ck_opts & (PR_CK_RW|PR_CK_INS|PR_CK_PFX))) { + ha_alert("parsing [%s:%d] : cookie 'rewrite', 'insert' and 'prefix' modes are incompatible.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + } + + if ((curproxy->ck_opts & (PR_CK_PSV | PR_CK_INS | PR_CK_IND)) == PR_CK_PSV) { + ha_alert("parsing [%s:%d] : cookie 'preserve' requires at least 'insert' or 'indirect'.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + } + }/* end else if (!strcmp(args[0], "cookie")) */ + else if (strcmp(args[0], "email-alert") == 0) { + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : missing argument after '%s'.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (strcmp(args[1], "from") == 0) { + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : missing argument after '%s'.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->email_alert.from); + curproxy->email_alert.from = strdup(args[2]); + if (!curproxy->email_alert.from) + goto alloc_error; + } + else if (strcmp(args[1], "mailers") == 0) { + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : missing argument after '%s'.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->email_alert.mailers.name); + curproxy->email_alert.mailers.name = strdup(args[2]); + if (!curproxy->email_alert.mailers.name) + goto alloc_error; + } + else if (strcmp(args[1], "myhostname") == 0) { + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : missing argument after '%s'.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->email_alert.myhostname); + curproxy->email_alert.myhostname = strdup(args[2]); + if (!curproxy->email_alert.myhostname) + goto alloc_error; + } + else if (strcmp(args[1], "level") == 0) { + curproxy->email_alert.level = get_log_level(args[2]); + if (curproxy->email_alert.level < 0) { + ha_alert("parsing [%s:%d] : unknown log level '%s' after '%s'\n", + file, linenum, args[1], args[2]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[1], "to") == 0) { + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : missing argument after '%s'.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->email_alert.to); + curproxy->email_alert.to = strdup(args[2]); + if (!curproxy->email_alert.to) + goto alloc_error; + } + else { + ha_alert("parsing [%s:%d] : email-alert: unknown argument '%s'.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + /* Indicate that the email_alert is at least partially configured */ + curproxy->email_alert.set = 1; + }/* end else if (!strcmp(args[0], "email-alert")) */ + else if (strcmp(args[0], "persist") == 0) { /* persist */ + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : missing persist method.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!strncmp(args[1], "rdp-cookie", 10)) { + curproxy->options2 |= PR_O2_RDPC_PRST; + + if (*(args[1] + 10) == '(') { /* cookie name */ + const char *beg, *end; + + beg = args[1] + 11; + end = strchr(beg, ')'); + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + if (!end || end == beg) { + ha_alert("parsing [%s:%d] : persist rdp-cookie(name)' requires an rdp cookie name.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + free(curproxy->rdp_cookie_name); + curproxy->rdp_cookie_name = my_strndup(beg, end - beg); + if (!curproxy->rdp_cookie_name) + goto alloc_error; + curproxy->rdp_cookie_len = end-beg; + } + else if (*(args[1] + 10) == '\0') { /* default cookie name 'msts' */ + free(curproxy->rdp_cookie_name); + curproxy->rdp_cookie_name = strdup("msts"); + if (!curproxy->rdp_cookie_name) + goto alloc_error; + curproxy->rdp_cookie_len = strlen(curproxy->rdp_cookie_name); + } + else { /* syntax */ + ha_alert("parsing [%s:%d] : persist rdp-cookie(name)' requires an rdp cookie name.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else { + ha_alert("parsing [%s:%d] : unknown persist method.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "appsession") == 0) { /* cookie name */ + ha_alert("parsing [%s:%d] : '%s' is not supported anymore since HAProxy 1.6.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "load-server-state-from-file") == 0) { + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + if (strcmp(args[1], "global") == 0) { /* use the file pointed to by global server-state-file directive */ + curproxy->load_server_state_from_file = PR_SRV_STATE_FILE_GLOBAL; + } + else if (strcmp(args[1], "local") == 0) { /* use the server-state-file-name variable to locate the server-state file */ + curproxy->load_server_state_from_file = PR_SRV_STATE_FILE_LOCAL; + } + else if (strcmp(args[1], "none") == 0) { /* don't use server-state-file directive for this backend */ + curproxy->load_server_state_from_file = PR_SRV_STATE_FILE_NONE; + } + else { + ha_alert("parsing [%s:%d] : '%s' expects 'global', 'local' or 'none'. Got '%s'\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "server-state-file-name") == 0) { + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + ha_free(&curproxy->server_state_file_name); + + if (*(args[1]) == 0 || strcmp(args[1], "use-backend-name") == 0) + curproxy->server_state_file_name = strdup(curproxy->id); + else + curproxy->server_state_file_name = strdup(args[1]); + + if (!curproxy->server_state_file_name) + goto alloc_error; + } + else if (strcmp(args[0], "max-session-srv-conns") == 0) { + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + if (*(args[1]) == 0) { + ha_alert("parsine [%s:%d] : '%s' expects a number. Got no argument\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->max_out_conns = atoi(args[1]); + } + else if (strcmp(args[0], "capture") == 0) { + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (strcmp(args[1], "cookie") == 0) { /* name of a cookie to capture */ + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (alertif_too_many_args_idx(4, 1, file, linenum, args, &err_code)) + goto out; + + if (*(args[4]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects 'cookie' 'len' .\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->capture_name); + curproxy->capture_name = strdup(args[2]); + if (!curproxy->capture_name) + goto alloc_error; + curproxy->capture_namelen = strlen(curproxy->capture_name); + curproxy->capture_len = atol(args[4]); + curproxy->to_log |= LW_COOKIE; + } + else if (strcmp(args[1], "request") == 0 && strcmp(args[2], "header") == 0) { + struct cap_hdr *hdr; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (alertif_too_many_args_idx(4, 1, file, linenum, args, &err_code)) + goto out; + + if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) { + ha_alert("parsing [%s:%d] : '%s %s' expects 'header' 'len' .\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + hdr = calloc(1, sizeof(*hdr)); + if (!hdr) + goto req_caphdr_alloc_error; + hdr->next = curproxy->req_cap; + hdr->name = strdup(args[3]); + if (!hdr->name) + goto req_caphdr_alloc_error; + hdr->namelen = strlen(args[3]); + hdr->len = atol(args[5]); + hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED); + if (!hdr->pool) { + req_caphdr_alloc_error: + if (hdr) + ha_free(&hdr->name); + ha_free(&hdr); + goto alloc_error; + } + hdr->index = curproxy->nb_req_cap++; + curproxy->req_cap = hdr; + curproxy->to_log |= LW_REQHDR; + } + else if (strcmp(args[1], "response") == 0 && strcmp(args[2], "header") == 0) { + struct cap_hdr *hdr; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (alertif_too_many_args_idx(4, 1, file, linenum, args, &err_code)) + goto out; + + if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) { + ha_alert("parsing [%s:%d] : '%s %s' expects 'header' 'len' .\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + hdr = calloc(1, sizeof(*hdr)); + if (!hdr) + goto res_caphdr_alloc_error; + hdr->next = curproxy->rsp_cap; + hdr->name = strdup(args[3]); + if (!hdr->name) + goto res_caphdr_alloc_error; + hdr->namelen = strlen(args[3]); + hdr->len = atol(args[5]); + hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED); + if (!hdr->pool) { + res_caphdr_alloc_error: + if (hdr) + ha_free(&hdr->name); + ha_free(&hdr); + goto alloc_error; + } + hdr->index = curproxy->nb_rsp_cap++; + curproxy->rsp_cap = hdr; + curproxy->to_log |= LW_RSPHDR; + } + else { + ha_alert("parsing [%s:%d] : '%s' expects 'cookie' or 'request header' or 'response header'.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "retries") == 0) { /* connection retries */ + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument (dispatch counts for one).\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->conn_retries = atol(args[1]); + } + else if (strcmp(args[0], "http-request") == 0) { /* request access control: allow/deny/auth */ + struct act_rule *rule; + int where = 0; + + if ((curproxy->cap & PR_CAP_DEF) && strlen(curproxy->id) == 0) { + ha_alert("parsing [%s:%d] : '%s' not allowed in anonymous 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!LIST_ISEMPTY(&curproxy->http_req_rules) && + !LIST_PREV(&curproxy->http_req_rules, struct act_rule *, list)->cond && + (LIST_PREV(&curproxy->http_req_rules, struct act_rule *, list)->flags & ACT_FLAG_FINAL)) { + ha_warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + rule = parse_http_req_cond((const char **)args + 1, file, linenum, curproxy); + + if (!rule) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + err_code |= warnif_misplaced_http_req(curproxy, file, linenum, args[0]); + + if (curproxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRQ_HDR; + if (curproxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + err_code |= warnif_cond_conflicts(rule->cond, where, file, linenum); + + LIST_APPEND(&curproxy->http_req_rules, &rule->list); + } + else if (strcmp(args[0], "http-response") == 0) { /* response access control */ + struct act_rule *rule; + int where = 0; + + if ((curproxy->cap & PR_CAP_DEF) && strlen(curproxy->id) == 0) { + ha_alert("parsing [%s:%d] : '%s' not allowed in anonymous 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!LIST_ISEMPTY(&curproxy->http_res_rules) && + !LIST_PREV(&curproxy->http_res_rules, struct act_rule *, list)->cond && + (LIST_PREV(&curproxy->http_res_rules, struct act_rule *, list)->flags & ACT_FLAG_FINAL)) { + ha_warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + rule = parse_http_res_cond((const char **)args + 1, file, linenum, curproxy); + + if (!rule) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if (curproxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRS_HDR; + if (curproxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRS_HDR; + err_code |= warnif_cond_conflicts(rule->cond, where, file, linenum); + + LIST_APPEND(&curproxy->http_res_rules, &rule->list); + } + else if (strcmp(args[0], "http-after-response") == 0) { + struct act_rule *rule; + int where = 0; + if ((curproxy->cap & PR_CAP_DEF) && strlen(curproxy->id) == 0) { + ha_alert("parsing [%s:%d] : '%s' not allowed in anonymous 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!LIST_ISEMPTY(&curproxy->http_after_res_rules) && + !LIST_PREV(&curproxy->http_after_res_rules, struct act_rule *, list)->cond && + (LIST_PREV(&curproxy->http_after_res_rules, struct act_rule *, list)->flags & ACT_FLAG_FINAL)) { + ha_warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + rule = parse_http_after_res_cond((const char **)args + 1, file, linenum, curproxy); + + if (!rule) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if (curproxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRS_HDR; + if (curproxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRS_HDR; + err_code |= warnif_cond_conflicts(rule->cond, where, file, linenum); + + LIST_APPEND(&curproxy->http_after_res_rules, &rule->list); + } + else if (strcmp(args[0], "http-send-name-header") == 0) { /* send server name in request header */ + /* set the header name and length into the proxy structure */ + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' requires a header string.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* set the desired header name, in lower case */ + istfree(&curproxy->server_id_hdr_name); + curproxy->server_id_hdr_name = istdup(ist(args[1])); + if (!isttest(curproxy->server_id_hdr_name)) + goto alloc_error; + ist2bin_lc(istptr(curproxy->server_id_hdr_name), curproxy->server_id_hdr_name); + } + else if (strcmp(args[0], "block") == 0) { + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. Use 'http-request deny' which uses the exact same syntax.\n", file, linenum, args[0]); + + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "redirect") == 0) { + struct redirect_rule *rule; + int where = 0; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if ((rule = http_parse_redirect_rule(file, linenum, curproxy, (const char **)args + 1, &errmsg, 0, 0)) == NULL) { + ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing redirect rule : %s.\n", + file, linenum, proxy_type_str(curproxy), curproxy->id, errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + LIST_APPEND(&curproxy->redirect_rules, &rule->list); + err_code |= warnif_misplaced_redirect(curproxy, file, linenum, args[0]); + + if (curproxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRQ_HDR; + if (curproxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + err_code |= warnif_cond_conflicts(rule->cond, where, file, linenum); + } + else if (strcmp(args[0], "use_backend") == 0) { + struct switching_rule *rule; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a backend name.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (strcmp(args[2], "if") == 0 || strcmp(args[2], "unless") == 0) { + if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 2, &errmsg)) == NULL) { + ha_alert("parsing [%s:%d] : error detected while parsing switching rule : %s.\n", + file, linenum, errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err_code |= warnif_cond_conflicts(cond, SMP_VAL_FE_SET_BCK, file, linenum); + } + else if (*args[2]) { + ha_alert("parsing [%s:%d] : unexpected keyword '%s' after switching rule, only 'if' and 'unless' are allowed.\n", + file, linenum, args[2]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + rule = calloc(1, sizeof(*rule)); + if (!rule) + goto use_backend_alloc_error; + rule->cond = cond; + rule->be.name = strdup(args[1]); + if (!rule->be.name) + goto use_backend_alloc_error; + rule->line = linenum; + rule->file = strdup(file); + if (!rule->file) { + use_backend_alloc_error: + free_acl_cond(cond); + if (rule) + ha_free(&(rule->be.name)); + ha_free(&rule); + goto alloc_error; + } + LIST_INIT(&rule->list); + LIST_APPEND(&curproxy->switching_rules, &rule->list); + } + else if (strcmp(args[0], "use-server") == 0) { + struct server_rule *rule; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a server name.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (strcmp(args[2], "if") != 0 && strcmp(args[2], "unless") != 0) { + ha_alert("parsing [%s:%d] : '%s' requires either 'if' or 'unless' followed by a condition.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 2, &errmsg)) == NULL) { + ha_alert("parsing [%s:%d] : error detected while parsing switching rule : %s.\n", + file, linenum, errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_SET_SRV, file, linenum); + + rule = calloc(1, sizeof(*rule)); + if (!rule) + goto use_server_alloc_error; + rule->cond = cond; + rule->srv.name = strdup(args[1]); + if (!rule->srv.name) + goto use_server_alloc_error; + rule->line = linenum; + rule->file = strdup(file); + if (!rule->file) { + use_server_alloc_error: + free_acl_cond(cond); + if (rule) + ha_free(&(rule->srv.name)); + ha_free(&rule); + goto alloc_error; + } + LIST_INIT(&rule->list); + LIST_APPEND(&curproxy->server_rules, &rule->list); + curproxy->be_req_ana |= AN_REQ_SRV_RULES; + } + else if ((strcmp(args[0], "force-persist") == 0) || + (strcmp(args[0], "ignore-persist") == 0)) { + struct persist_rule *rule; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (strcmp(args[1], "if") != 0 && strcmp(args[1], "unless") != 0) { + ha_alert("parsing [%s:%d] : '%s' requires either 'if' or 'unless' followed by a condition.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 1, &errmsg)) == NULL) { + ha_alert("parsing [%s:%d] : error detected while parsing a '%s' rule : %s.\n", + file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* note: BE_REQ_CNT is the first one after FE_SET_BCK, which is + * where force-persist is applied. + */ + err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_REQ_CNT, file, linenum); + + rule = calloc(1, sizeof(*rule)); + if (!rule) { + free_acl_cond(cond); + goto alloc_error; + } + rule->cond = cond; + if (strcmp(args[0], "force-persist") == 0) { + rule->type = PERSIST_TYPE_FORCE; + } else { + rule->type = PERSIST_TYPE_IGNORE; + } + LIST_INIT(&rule->list); + LIST_APPEND(&curproxy->persist_rules, &rule->list); + } + else if (strcmp(args[0], "stick-table") == 0) { + struct stktable *other; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : 'stick-table' is not supported in 'defaults' section.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + other = stktable_find_by_name(curproxy->id); + if (other) { + ha_alert("parsing [%s:%d] : stick-table name '%s' conflicts with table declared in %s '%s' at %s:%d.\n", + file, linenum, curproxy->id, + other->proxy ? proxy_cap_str(other->proxy->cap) : "peers", + other->proxy ? other->id : other->peers.p->id, + other->conf.file, other->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->table = calloc(1, sizeof *curproxy->table); + if (!curproxy->table) { + ha_alert("parsing [%s:%d]: '%s %s' : memory allocation failed\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err_code |= parse_stick_table(file, linenum, args, curproxy->table, + curproxy->id, curproxy->id, NULL); + if (err_code & ERR_FATAL) { + ha_free(&curproxy->table); + goto out; + } + + /* Store the proxy in the stick-table. */ + curproxy->table->proxy = curproxy; + + stktable_store_name(curproxy->table); + curproxy->table->next = stktables_list; + stktables_list = curproxy->table; + + /* Add this proxy to the list of proxies which refer to its stick-table. */ + if (curproxy->table->proxies_list != curproxy) { + curproxy->next_stkt_ref = curproxy->table->proxies_list; + curproxy->table->proxies_list = curproxy; + } + } + else if (strcmp(args[0], "stick") == 0) { + struct sticking_rule *rule; + struct sample_expr *expr; + int myidx = 0; + const char *name = NULL; + int flags; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) { + err_code |= ERR_WARN; + goto out; + } + + myidx++; + if ((strcmp(args[myidx], "store") == 0) || + (strcmp(args[myidx], "store-request") == 0)) { + myidx++; + flags = STK_IS_STORE; + } + else if (strcmp(args[myidx], "store-response") == 0) { + myidx++; + flags = STK_IS_STORE | STK_ON_RSP; + } + else if (strcmp(args[myidx], "match") == 0) { + myidx++; + flags = STK_IS_MATCH; + } + else if (strcmp(args[myidx], "on") == 0) { + myidx++; + flags = STK_IS_MATCH | STK_IS_STORE; + } + else { + ha_alert("parsing [%s:%d] : '%s' expects 'on', 'match', or 'store'.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (*(args[myidx]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a fetch method.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->conf.args.ctx = ARGC_STK; + expr = sample_parse_expr(args, &myidx, file, linenum, &errmsg, &curproxy->conf.args, NULL); + if (!expr) { + ha_alert("parsing [%s:%d] : '%s': %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (flags & STK_ON_RSP) { + if (!(expr->fetch->val & SMP_VAL_BE_STO_RUL)) { + ha_alert("parsing [%s:%d] : '%s': fetch method '%s' extracts information from '%s', none of which is available for 'store-response'.\n", + file, linenum, args[0], expr->fetch->kw, sample_src_names(expr->fetch->use)); + err_code |= ERR_ALERT | ERR_FATAL; + free(expr); + goto out; + } + } else { + if (!(expr->fetch->val & SMP_VAL_BE_SET_SRV)) { + ha_alert("parsing [%s:%d] : '%s': fetch method '%s' extracts information from '%s', none of which is available during request.\n", + file, linenum, args[0], expr->fetch->kw, sample_src_names(expr->fetch->use)); + err_code |= ERR_ALERT | ERR_FATAL; + free(expr); + goto out; + } + } + + /* check if we need to allocate an http_txn struct for HTTP parsing */ + curproxy->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY); + + if (strcmp(args[myidx], "table") == 0) { + myidx++; + name = args[myidx++]; + } + + if (strcmp(args[myidx], "if") == 0 || strcmp(args[myidx], "unless") == 0) { + if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + myidx, &errmsg)) == NULL) { + ha_alert("parsing [%s:%d] : '%s': error detected while parsing sticking condition : %s.\n", + file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + free(expr); + goto out; + } + } + else if (*(args[myidx])) { + ha_alert("parsing [%s:%d] : '%s': unknown keyword '%s'.\n", + file, linenum, args[0], args[myidx]); + err_code |= ERR_ALERT | ERR_FATAL; + free(expr); + goto out; + } + if (flags & STK_ON_RSP) + err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_STO_RUL, file, linenum); + else + err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_SET_SRV, file, linenum); + + rule = calloc(1, sizeof(*rule)); + if (!rule) { + free_acl_cond(cond); + goto alloc_error; + } + rule->cond = cond; + rule->expr = expr; + rule->flags = flags; + rule->table.name = name ? strdup(name) : NULL; + LIST_INIT(&rule->list); + if (flags & STK_ON_RSP) + LIST_APPEND(&curproxy->storersp_rules, &rule->list); + else + LIST_APPEND(&curproxy->sticking_rules, &rule->list); + } + else if (strcmp(args[0], "stats") == 0) { + if (!(curproxy->cap & PR_CAP_DEF) && curproxy->uri_auth == curr_defproxy->uri_auth) + curproxy->uri_auth = NULL; /* we must detach from the default config */ + + if (!*args[1]) { + goto stats_error_parsing; + } else if (strcmp(args[1], "admin") == 0) { + struct stats_admin_rule *rule; + int where = 0; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d]: '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!stats_check_init_uri_auth(&curproxy->uri_auth)) + goto alloc_error; + + if (strcmp(args[2], "if") != 0 && strcmp(args[2], "unless") != 0) { + ha_alert("parsing [%s:%d] : '%s %s' requires either 'if' or 'unless' followed by a condition.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 2, &errmsg)) == NULL) { + ha_alert("parsing [%s:%d] : error detected while parsing a '%s %s' rule : %s.\n", + file, linenum, args[0], args[1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (curproxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRQ_HDR; + if (curproxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + err_code |= warnif_cond_conflicts(cond, where, file, linenum); + + rule = calloc(1, sizeof(*rule)); + if (!rule) { + free_acl_cond(cond); + goto alloc_error; + } + rule->cond = cond; + LIST_INIT(&rule->list); + LIST_APPEND(&curproxy->uri_auth->admin_rules, &rule->list); + } else if (strcmp(args[1], "uri") == 0) { + if (*(args[2]) == 0) { + ha_alert("parsing [%s:%d] : 'uri' needs an URI prefix.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } else if (!stats_set_uri(&curproxy->uri_auth, args[2])) + goto alloc_error; + } else if (strcmp(args[1], "realm") == 0) { + if (*(args[2]) == 0) { + ha_alert("parsing [%s:%d] : 'realm' needs an realm name.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } else if (!stats_set_realm(&curproxy->uri_auth, args[2])) + goto alloc_error; + } else if (strcmp(args[1], "refresh") == 0) { + unsigned interval; + + err = parse_time_err(args[2], &interval, TIME_UNIT_S); + if (err == PARSE_TIME_OVER) { + ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to stats refresh interval, maximum value is 2147483647 s (~68 years).\n", + file, linenum, args[2]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (err == PARSE_TIME_UNDER) { + ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to stats refresh interval, minimum non-null value is 1 s.\n", + file, linenum, args[2]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (err) { + ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to stats refresh interval.\n", + file, linenum, *err); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } else if (!stats_set_refresh(&curproxy->uri_auth, interval)) + goto alloc_error; + } else if (strcmp(args[1], "http-request") == 0) { /* request access control: allow/deny/auth */ + struct act_rule *rule; + int where = 0; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!stats_check_init_uri_auth(&curproxy->uri_auth)) + goto alloc_error; + + if (!LIST_ISEMPTY(&curproxy->uri_auth->http_req_rules) && + !LIST_PREV(&curproxy->uri_auth->http_req_rules, struct act_rule *, list)->cond) { + ha_warning("parsing [%s:%d]: previous '%s' action has no condition attached, further entries are NOOP.\n", + file, linenum, args[0]); + err_code |= ERR_WARN; + } + + rule = parse_http_req_cond((const char **)args + 2, file, linenum, curproxy); + + if (!rule) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if (curproxy->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRQ_HDR; + if (curproxy->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + err_code |= warnif_cond_conflicts(rule->cond, where, file, linenum); + LIST_APPEND(&curproxy->uri_auth->http_req_rules, &rule->list); + + } else if (strcmp(args[1], "auth") == 0) { + if (*(args[2]) == 0) { + ha_alert("parsing [%s:%d] : 'auth' needs a user:password account.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } else if (!stats_add_auth(&curproxy->uri_auth, args[2])) + goto alloc_error; + } else if (strcmp(args[1], "scope") == 0) { + if (*(args[2]) == 0) { + ha_alert("parsing [%s:%d] : 'scope' needs a proxy name.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } else if (!stats_add_scope(&curproxy->uri_auth, args[2])) + goto alloc_error; + } else if (strcmp(args[1], "enable") == 0) { + if (!stats_check_init_uri_auth(&curproxy->uri_auth)) + goto alloc_error; + } else if (strcmp(args[1], "hide-version") == 0) { + if (!stats_set_flag(&curproxy->uri_auth, STAT_HIDEVER)) + goto alloc_error; + } else if (strcmp(args[1], "show-legends") == 0) { + if (!stats_set_flag(&curproxy->uri_auth, STAT_SHLGNDS)) + goto alloc_error; + } else if (strcmp(args[1], "show-modules") == 0) { + if (!stats_set_flag(&curproxy->uri_auth, STAT_SHMODULES)) + goto alloc_error; + } else if (strcmp(args[1], "show-node") == 0) { + + if (*args[2]) { + int i; + char c; + + for (i=0; args[2][i]; i++) { + c = args[2][i]; + if (!isupper((unsigned char)c) && !islower((unsigned char)c) && + !isdigit((unsigned char)c) && c != '_' && c != '-' && c != '.') + break; + } + + if (!i || args[2][i]) { + ha_alert("parsing [%s:%d]: '%s %s' invalid node name - should be a string" + "with digits(0-9), letters(A-Z, a-z), hyphen(-) or underscode(_).\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + + if (!stats_set_node(&curproxy->uri_auth, args[2])) + goto alloc_error; + } else if (strcmp(args[1], "show-desc") == 0) { + char *desc = NULL; + + if (*args[2]) { + int i, len=0; + char *d; + + for (i = 2; *args[i]; i++) + len += strlen(args[i]) + 1; + + desc = d = calloc(1, len); + + d += snprintf(d, desc + len - d, "%s", args[2]); + for (i = 3; *args[i]; i++) + d += snprintf(d, desc + len - d, " %s", args[i]); + } + + if (!*args[2] && !global.desc) + ha_warning("parsing [%s:%d]: '%s' requires a parameter or 'desc' to be set in the global section.\n", + file, linenum, args[1]); + else { + if (!stats_set_desc(&curproxy->uri_auth, desc)) { + free(desc); + goto alloc_error; + } + free(desc); + } + } else { +stats_error_parsing: + ha_alert("parsing [%s:%d]: %s '%s', expects 'admin', 'uri', 'realm', 'auth', 'scope', 'enable', 'hide-version', 'show-node', 'show-desc' or 'show-legends'.\n", + file, linenum, *args[1]?"unknown stats parameter":"missing keyword in", args[*args[1]?1:0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "option") == 0) { + int optnum; + + if (*(args[1]) == '\0') { + ha_alert("parsing [%s:%d]: '%s' expects an option name.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + for (optnum = 0; cfg_opts[optnum].name; optnum++) { + if (strcmp(args[1], cfg_opts[optnum].name) == 0) { + if (cfg_opts[optnum].cap == PR_CAP_NONE) { + ha_alert("parsing [%s:%d]: option '%s' is not supported due to build options.\n", + file, linenum, cfg_opts[optnum].name); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + + if (warnifnotcap(curproxy, cfg_opts[optnum].cap, file, linenum, args[1], NULL)) { + err_code |= ERR_WARN; + goto out; + } + + curproxy->no_options &= ~cfg_opts[optnum].val; + curproxy->options &= ~cfg_opts[optnum].val; + + switch (kwm) { + case KWM_STD: + curproxy->options |= cfg_opts[optnum].val; + break; + case KWM_NO: + curproxy->no_options |= cfg_opts[optnum].val; + break; + case KWM_DEF: /* already cleared */ + break; + } + + goto out; + } + } + + for (optnum = 0; cfg_opts2[optnum].name; optnum++) { + if (strcmp(args[1], cfg_opts2[optnum].name) == 0) { + if (cfg_opts2[optnum].cap == PR_CAP_NONE) { + ha_alert("parsing [%s:%d]: option '%s' is not supported due to build options.\n", + file, linenum, cfg_opts2[optnum].name); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + if (warnifnotcap(curproxy, cfg_opts2[optnum].cap, file, linenum, args[1], NULL)) { + err_code |= ERR_WARN; + goto out; + } + + curproxy->no_options2 &= ~cfg_opts2[optnum].val; + curproxy->options2 &= ~cfg_opts2[optnum].val; + + switch (kwm) { + case KWM_STD: + curproxy->options2 |= cfg_opts2[optnum].val; + break; + case KWM_NO: + curproxy->no_options2 |= cfg_opts2[optnum].val; + break; + case KWM_DEF: /* already cleared */ + break; + } + goto out; + } + } + + /* HTTP options override each other. They can be cancelled using + * "no option xxx" which only switches to default mode if the mode + * was this one (useful for cancelling options set in defaults + * sections). + */ + if (strcmp(args[1], "forceclose") == 0) { + ha_alert("parsing [%s:%d]: option '%s' is not supported any more since HAProxy 2.0, please just remove it, or use 'option httpclose' if absolutely needed.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[1], "httpclose") == 0) { + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_STD) { + curproxy->options &= ~PR_O_HTTP_MODE; + curproxy->options |= PR_O_HTTP_CLO; + goto out; + } + else if (kwm == KWM_NO) { + if ((curproxy->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO) + curproxy->options &= ~PR_O_HTTP_MODE; + goto out; + } + } + else if (strcmp(args[1], "http-server-close") == 0) { + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_STD) { + curproxy->options &= ~PR_O_HTTP_MODE; + curproxy->options |= PR_O_HTTP_SCL; + goto out; + } + else if (kwm == KWM_NO) { + if ((curproxy->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL) + curproxy->options &= ~PR_O_HTTP_MODE; + goto out; + } + } + else if (strcmp(args[1], "http-keep-alive") == 0) { + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + if (kwm == KWM_STD) { + curproxy->options &= ~PR_O_HTTP_MODE; + curproxy->options |= PR_O_HTTP_KAL; + goto out; + } + else if (kwm == KWM_NO) { + if ((curproxy->options & PR_O_HTTP_MODE) == PR_O_HTTP_KAL) + curproxy->options &= ~PR_O_HTTP_MODE; + goto out; + } + } + else if (strcmp(args[1], "http-tunnel") == 0) { + ha_alert("parsing [%s:%d]: option '%s' is not supported any more since HAProxy 2.1, please just remove it, it shouldn't be needed.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[1], "forwarded") == 0) { + if (kwm == KWM_STD) { + err_code |= proxy_http_parse_7239(args, 0, curproxy, curr_defproxy, file, linenum); + goto out; + } + else if (kwm == KWM_NO) { + if (curproxy->http_ext) + http_ext_7239_clean(curproxy); + goto out; + } + } + + /* Redispatch can take an integer argument that control when the + * resispatch occurs. All values are relative to the retries option. + * This can be cancelled using "no option xxx". + */ + if (strcmp(args[1], "redispatch") == 0) { + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL)) { + err_code |= ERR_WARN; + goto out; + } + + curproxy->no_options &= ~PR_O_REDISP; + curproxy->options &= ~PR_O_REDISP; + + switch (kwm) { + case KWM_STD: + curproxy->options |= PR_O_REDISP; + curproxy->redispatch_after = -1; + if(*args[2]) { + curproxy->redispatch_after = atol(args[2]); + } + break; + case KWM_NO: + curproxy->no_options |= PR_O_REDISP; + curproxy->redispatch_after = 0; + break; + case KWM_DEF: /* already cleared */ + break; + } + goto out; + } + + if (strcmp(args[1], "http_proxy") == 0) { + ha_alert("parsing [%s:%d]: option '%s' is not supported any more since HAProxy 2.5. This option stopped working in HAProxy 1.9 and usually had nasty side effects. It can be more reliably implemented with combinations of 'http-request set-dst' and 'http-request set-uri', and even 'http-request do-resolve' if DNS resolution is desired.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (kwm != KWM_STD) { + ha_alert("parsing [%s:%d]: negation/default is not supported for option '%s'.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (strcmp(args[1], "httplog") == 0) { + char *logformat; + /* generate a complete HTTP log */ + logformat = default_http_log_format; + if (*(args[2]) != '\0') { + if (strcmp(args[2], "clf") == 0) { + curproxy->options2 |= PR_O2_CLFLOG; + logformat = clf_http_log_format; + } else { + ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'clf'.\n", file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (alertif_too_many_args_idx(1, 1, file, linenum, args, &err_code)) + goto out; + } + if (curproxy->conf.logformat_string && curproxy->cap & PR_CAP_DEF) { + char *oldlogformat = "log-format"; + char *clflogformat = ""; + + if (curproxy->conf.logformat_string == default_http_log_format) + oldlogformat = "option httplog"; + else if (curproxy->conf.logformat_string == default_tcp_log_format) + oldlogformat = "option tcplog"; + else if (curproxy->conf.logformat_string == clf_http_log_format) + oldlogformat = "option httplog clf"; + else if (curproxy->conf.logformat_string == default_https_log_format) + oldlogformat = "option httpslog"; + if (logformat == clf_http_log_format) + clflogformat = " clf"; + ha_warning("parsing [%s:%d]: 'option httplog%s' overrides previous '%s' in 'defaults' section.\n", + file, linenum, clflogformat, oldlogformat); + } + if (curproxy->conf.logformat_string != default_http_log_format && + curproxy->conf.logformat_string != default_tcp_log_format && + curproxy->conf.logformat_string != clf_http_log_format && + curproxy->conf.logformat_string != default_https_log_format) + free(curproxy->conf.logformat_string); + curproxy->conf.logformat_string = logformat; + + free(curproxy->conf.lfs_file); + curproxy->conf.lfs_file = strdup(curproxy->conf.args.file); + curproxy->conf.lfs_line = curproxy->conf.args.line; + + if (!(curproxy->cap & PR_CAP_DEF) && !(curproxy->cap & PR_CAP_FE)) { + ha_warning("parsing [%s:%d] : backend '%s' : 'option httplog' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + } + else if (strcmp(args[1], "tcplog") == 0) { + if (curproxy->conf.logformat_string && curproxy->cap & PR_CAP_DEF) { + char *oldlogformat = "log-format"; + + if (curproxy->conf.logformat_string == default_http_log_format) + oldlogformat = "option httplog"; + else if (curproxy->conf.logformat_string == default_tcp_log_format) + oldlogformat = "option tcplog"; + else if (curproxy->conf.logformat_string == clf_http_log_format) + oldlogformat = "option httplog clf"; + else if (curproxy->conf.logformat_string == default_https_log_format) + oldlogformat = "option httpslog"; + ha_warning("parsing [%s:%d]: 'option tcplog' overrides previous '%s' in 'defaults' section.\n", + file, linenum, oldlogformat); + } + /* generate a detailed TCP log */ + if (curproxy->conf.logformat_string != default_http_log_format && + curproxy->conf.logformat_string != default_tcp_log_format && + curproxy->conf.logformat_string != clf_http_log_format && + curproxy->conf.logformat_string != default_https_log_format) + free(curproxy->conf.logformat_string); + curproxy->conf.logformat_string = default_tcp_log_format; + + free(curproxy->conf.lfs_file); + curproxy->conf.lfs_file = strdup(curproxy->conf.args.file); + curproxy->conf.lfs_line = curproxy->conf.args.line; + + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + + if (!(curproxy->cap & PR_CAP_DEF) && !(curproxy->cap & PR_CAP_FE)) { + ha_warning("parsing [%s:%d] : backend '%s' : 'option tcplog' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + } + else if (strcmp(args[1], "httpslog") == 0) { + char *logformat; + /* generate a complete HTTP log */ + logformat = default_https_log_format; + if (curproxy->conf.logformat_string && curproxy->cap & PR_CAP_DEF) { + char *oldlogformat = "log-format"; + + if (curproxy->conf.logformat_string == default_http_log_format) + oldlogformat = "option httplog"; + else if (curproxy->conf.logformat_string == default_tcp_log_format) + oldlogformat = "option tcplog"; + else if (curproxy->conf.logformat_string == clf_http_log_format) + oldlogformat = "option httplog clf"; + else if (curproxy->conf.logformat_string == default_https_log_format) + oldlogformat = "option httpslog"; + ha_warning("parsing [%s:%d]: 'option httplog' overrides previous '%s' in 'defaults' section.\n", + file, linenum, oldlogformat); + } + if (curproxy->conf.logformat_string != default_http_log_format && + curproxy->conf.logformat_string != default_tcp_log_format && + curproxy->conf.logformat_string != clf_http_log_format && + curproxy->conf.logformat_string != default_https_log_format) + free(curproxy->conf.logformat_string); + curproxy->conf.logformat_string = logformat; + + free(curproxy->conf.lfs_file); + curproxy->conf.lfs_file = strdup(curproxy->conf.args.file); + curproxy->conf.lfs_line = curproxy->conf.args.line; + + if (!(curproxy->cap & PR_CAP_DEF) && !(curproxy->cap & PR_CAP_FE)) { + ha_warning("parsing [%s:%d] : backend '%s' : 'option httpslog' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + } + else if (strcmp(args[1], "tcpka") == 0) { + /* enable TCP keep-alives on client and server streams */ + if (warnifnotcap(curproxy, PR_CAP_BE | PR_CAP_FE, file, linenum, args[1], NULL)) + err_code |= ERR_WARN; + + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + + if (curproxy->cap & PR_CAP_FE) + curproxy->options |= PR_O_TCP_CLI_KA; + if (curproxy->cap & PR_CAP_BE) + curproxy->options |= PR_O_TCP_SRV_KA; + } + else if (strcmp(args[1], "httpchk") == 0) { + err_code |= proxy_parse_httpchk_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "ssl-hello-chk") == 0) { + err_code |= proxy_parse_ssl_hello_chk_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "smtpchk") == 0) { + err_code |= proxy_parse_smtpchk_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "pgsql-check") == 0) { + err_code |= proxy_parse_pgsql_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "redis-check") == 0) { + err_code |= proxy_parse_redis_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "mysql-check") == 0) { + err_code |= proxy_parse_mysql_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "ldap-check") == 0) { + err_code |= proxy_parse_ldap_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "spop-check") == 0) { + err_code |= proxy_parse_spop_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "tcp-check") == 0) { + err_code |= proxy_parse_tcp_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "external-check") == 0) { + err_code |= proxy_parse_external_check_opt(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "forwardfor") == 0) { + err_code |= proxy_http_parse_xff(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "originalto") == 0) { + err_code |= proxy_http_parse_xot(args, 0, curproxy, curr_defproxy, file, linenum); + if (err_code & ERR_FATAL) + goto out; + } + else if (strcmp(args[1], "http-restrict-req-hdr-names") == 0) { + if (alertif_too_many_args(2, file, linenum, args, &err_code)) + goto out; + + if (*(args[2]) == 0) { + ha_alert("parsing [%s:%d] : missing parameter. option '%s' expects 'preserve', 'reject' or 'delete' option.\n", + file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->options2 &= ~PR_O2_RSTRICT_REQ_HDR_NAMES_MASK; + if (strcmp(args[2], "preserve") == 0) + curproxy->options2 |= PR_O2_RSTRICT_REQ_HDR_NAMES_NOOP; + else if (strcmp(args[2], "reject") == 0) + curproxy->options2 |= PR_O2_RSTRICT_REQ_HDR_NAMES_BLK; + else if (strcmp(args[2], "delete") == 0) + curproxy->options2 |= PR_O2_RSTRICT_REQ_HDR_NAMES_DEL; + else { + ha_alert("parsing [%s:%d] : invalid parameter '%s'. option '%s' expects 'preserve', 'reject' or 'delete' option.\n", + file, linenum, args[2], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else { + const char *best = proxy_find_best_option(args[1], common_options); + + if (best) + ha_alert("parsing [%s:%d] : unknown option '%s'; did you mean '%s' maybe ?\n", file, linenum, args[1], best); + else + ha_alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]); + + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + goto out; + } + else if (strcmp(args[0], "default_backend") == 0) { + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a backend name.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->defbe.name); + curproxy->defbe.name = strdup(args[1]); + if (!curproxy->defbe.name) + goto alloc_error; + + if (alertif_too_many_args_idx(1, 0, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[0], "redispatch") == 0 || strcmp(args[0], "redisp") == 0) { + ha_alert("parsing [%s:%d] : keyword '%s' directive is not supported anymore since HAProxy 2.1. Use 'option redispatch'.\n", file, linenum, args[0]); + + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "http-reuse") == 0) { + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (strcmp(args[1], "never") == 0) { + /* enable a graceful server shutdown on an HTTP 404 response */ + curproxy->options &= ~PR_O_REUSE_MASK; + curproxy->options |= PR_O_REUSE_NEVR; + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[1], "safe") == 0) { + /* enable a graceful server shutdown on an HTTP 404 response */ + curproxy->options &= ~PR_O_REUSE_MASK; + curproxy->options |= PR_O_REUSE_SAFE; + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[1], "aggressive") == 0) { + curproxy->options &= ~PR_O_REUSE_MASK; + curproxy->options |= PR_O_REUSE_AGGR; + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[1], "always") == 0) { + /* enable a graceful server shutdown on an HTTP 404 response */ + curproxy->options &= ~PR_O_REUSE_MASK; + curproxy->options |= PR_O_REUSE_ALWS; + if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code)) + goto out; + } + else { + ha_alert("parsing [%s:%d] : '%s' only supports 'never', 'safe', 'aggressive', 'always'.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "monitor") == 0) { + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (strcmp(args[1], "fail") == 0) { + /* add a condition to fail monitor requests */ + if (strcmp(args[2], "if") != 0 && strcmp(args[2], "unless") != 0) { + ha_alert("parsing [%s:%d] : '%s %s' requires either 'if' or 'unless' followed by a condition.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err_code |= warnif_misplaced_monitor(curproxy, file, linenum, "monitor fail"); + if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 2, &errmsg)) == NULL) { + ha_alert("parsing [%s:%d] : error detected while parsing a '%s %s' condition : %s.\n", + file, linenum, args[0], args[1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + LIST_APPEND(&curproxy->mon_fail_cond, &cond->list); + } + else { + ha_alert("parsing [%s:%d] : '%s' only supports 'fail'.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } +#ifdef USE_TPROXY + else if (strcmp(args[0], "transparent") == 0) { + /* enable transparent proxy connections */ + curproxy->options |= PR_O_TRANSP; + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + } +#endif + else if (strcmp(args[0], "maxconn") == 0) { /* maxconn */ + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], " Maybe you want 'fullconn' instead ?")) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->maxconn = atol(args[1]); + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[0], "backlog") == 0) { /* backlog */ + if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->backlog = atol(args[1]); + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[0], "fullconn") == 0) { /* fullconn */ + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], " Maybe you want 'maxconn' instead ?")) + err_code |= ERR_WARN; + + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->fullconn = atol(args[1]); + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + } + else if (strcmp(args[0], "grace") == 0) { /* grace time (ms) */ + ha_alert("parsing [%s:%d]: the '%s' keyword is not supported any more since HAProxy version 2.5.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "dispatch") == 0) { /* dispatch address */ + struct sockaddr_storage *sk; + int port1, port2; + + if (curproxy->cap & PR_CAP_DEF) { + ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + sk = str2sa_range(args[1], NULL, &port1, &port2, NULL, NULL, NULL, + &errmsg, NULL, NULL, + PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_STREAM | PA_O_XPRT | PA_O_CONNECT); + if (!sk) { + ha_alert("parsing [%s:%d] : '%s' : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + curproxy->dispatch_addr = *sk; + curproxy->options |= PR_O_DISPATCH; + } + else if (strcmp(args[0], "balance") == 0) { /* set balancing with optional algorithm */ + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (backend_parse_balance((const char **)args + 1, &errmsg, curproxy) < 0) { + ha_alert("parsing [%s:%d] : %s %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "hash-type") == 0) { /* set hashing method */ + /** + * The syntax for hash-type config element is + * hash-type {map-based|consistent} [[] avalanche] + * + * The default hash function is sdbm for map-based and sdbm+avalanche for consistent. + */ + curproxy->lbprm.algo &= ~(BE_LB_HASH_TYPE | BE_LB_HASH_FUNC | BE_LB_HASH_MOD); + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (strcmp(args[1], "consistent") == 0) { /* use consistent hashing */ + curproxy->lbprm.algo |= BE_LB_HASH_CONS; + } + else if (strcmp(args[1], "map-based") == 0) { /* use map-based hashing */ + curproxy->lbprm.algo |= BE_LB_HASH_MAP; + } + else if (strcmp(args[1], "avalanche") == 0) { + ha_alert("parsing [%s:%d] : experimental feature '%s %s' is not supported anymore, please use '%s map-based sdbm avalanche' instead.\n", file, linenum, args[0], args[1], args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else { + ha_alert("parsing [%s:%d] : '%s' only supports 'consistent' and 'map-based'.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* set the hash function to use */ + if (!*args[2]) { + /* the default algo is sdbm */ + curproxy->lbprm.algo |= BE_LB_HFCN_SDBM; + + /* if consistent with no argument, then avalanche modifier is also applied */ + if ((curproxy->lbprm.algo & BE_LB_HASH_TYPE) == BE_LB_HASH_CONS) + curproxy->lbprm.algo |= BE_LB_HMOD_AVAL; + } else { + /* set the hash function */ + if (strcmp(args[2], "sdbm") == 0) { + curproxy->lbprm.algo |= BE_LB_HFCN_SDBM; + } + else if (strcmp(args[2], "djb2") == 0) { + curproxy->lbprm.algo |= BE_LB_HFCN_DJB2; + } + else if (strcmp(args[2], "wt6") == 0) { + curproxy->lbprm.algo |= BE_LB_HFCN_WT6; + } + else if (strcmp(args[2], "crc32") == 0) { + curproxy->lbprm.algo |= BE_LB_HFCN_CRC32; + } + else if (strcmp(args[2], "none") == 0) { + curproxy->lbprm.algo |= BE_LB_HFCN_NONE; + } + else { + ha_alert("parsing [%s:%d] : '%s' only supports 'sdbm', 'djb2', 'crc32', or 'wt6' hash functions.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* set the hash modifier */ + if (strcmp(args[3], "avalanche") == 0) { + curproxy->lbprm.algo |= BE_LB_HMOD_AVAL; + } + else if (*args[3]) { + ha_alert("parsing [%s:%d] : '%s' only supports 'avalanche' as a modifier for hash functions.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + } + else if (strcmp(args[0], "hash-balance-factor") == 0) { + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + curproxy->lbprm.hash_balance_factor = atol(args[1]); + if (curproxy->lbprm.hash_balance_factor != 0 && curproxy->lbprm.hash_balance_factor <= 100) { + ha_alert("parsing [%s:%d] : '%s' must be 0 or greater than 100.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "unique-id-format") == 0) { + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (*(args[2])) { + ha_alert("parsing [%s:%d] : %s expects only one argument, don't forget to escape spaces!\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->conf.uniqueid_format_string); + curproxy->conf.uniqueid_format_string = strdup(args[1]); + if (!curproxy->conf.uniqueid_format_string) + goto alloc_error; + + free(curproxy->conf.uif_file); + curproxy->conf.uif_file = strdup(curproxy->conf.args.file); + curproxy->conf.uif_line = curproxy->conf.args.line; + } + + else if (strcmp(args[0], "unique-id-header") == 0) { + char *copy; + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + copy = strdup(args[1]); + if (copy == NULL) { + ha_alert("parsing [%s:%d] : failed to allocate memory for unique-id-header\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + istfree(&curproxy->header_unique_id); + curproxy->header_unique_id = ist(copy); + } + + else if (strcmp(args[0], "log-format") == 0) { + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (*(args[2])) { + ha_alert("parsing [%s:%d] : %s expects only one argument, don't forget to escape spaces!\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (curproxy->conf.logformat_string && curproxy->cap & PR_CAP_DEF) { + char *oldlogformat = "log-format"; + + if (curproxy->conf.logformat_string == default_http_log_format) + oldlogformat = "option httplog"; + else if (curproxy->conf.logformat_string == default_tcp_log_format) + oldlogformat = "option tcplog"; + else if (curproxy->conf.logformat_string == clf_http_log_format) + oldlogformat = "option httplog clf"; + else if (curproxy->conf.logformat_string == default_https_log_format) + oldlogformat = "option httpslog"; + ha_warning("parsing [%s:%d]: 'log-format' overrides previous '%s' in 'defaults' section.\n", + file, linenum, oldlogformat); + } + if (curproxy->conf.logformat_string != default_http_log_format && + curproxy->conf.logformat_string != default_tcp_log_format && + curproxy->conf.logformat_string != clf_http_log_format && + curproxy->conf.logformat_string != default_https_log_format) + free(curproxy->conf.logformat_string); + curproxy->conf.logformat_string = strdup(args[1]); + if (!curproxy->conf.logformat_string) + goto alloc_error; + + free(curproxy->conf.lfs_file); + curproxy->conf.lfs_file = strdup(curproxy->conf.args.file); + curproxy->conf.lfs_line = curproxy->conf.args.line; + + /* get a chance to improve log-format error reporting by + * reporting the correct line-number when possible. + */ + if (!(curproxy->cap & PR_CAP_DEF) && !(curproxy->cap & PR_CAP_FE)) { + ha_warning("parsing [%s:%d] : backend '%s' : 'log-format' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + } + else if (strcmp(args[0], "log-format-sd") == 0) { + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (*(args[2])) { + ha_alert("parsing [%s:%d] : %s expects only one argument, don't forget to escape spaces!\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (curproxy->conf.logformat_sd_string != default_rfc5424_sd_log_format) + free(curproxy->conf.logformat_sd_string); + curproxy->conf.logformat_sd_string = strdup(args[1]); + if (!curproxy->conf.logformat_sd_string) + goto alloc_error; + + free(curproxy->conf.lfsd_file); + curproxy->conf.lfsd_file = strdup(curproxy->conf.args.file); + curproxy->conf.lfsd_line = curproxy->conf.args.line; + + /* get a chance to improve log-format-sd error reporting by + * reporting the correct line-number when possible. + */ + if (!(curproxy->cap & PR_CAP_DEF) && !(curproxy->cap & PR_CAP_FE)) { + ha_warning("parsing [%s:%d] : backend '%s' : 'log-format-sd' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + } + else if (strcmp(args[0], "error-log-format") == 0) { + if (!*(args[1])) { + ha_alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (*(args[2])) { + ha_alert("parsing [%s:%d] : %s expects only one argument, don't forget to escape spaces!\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + if (curproxy->conf.error_logformat_string && curproxy->cap & PR_CAP_DEF) { + ha_warning("parsing [%s:%d]: 'error-log-format' overrides previous 'error-log-format' in 'defaults' section.\n", + file, linenum); + } + free(curproxy->conf.error_logformat_string); + curproxy->conf.error_logformat_string = strdup(args[1]); + if (!curproxy->conf.error_logformat_string) + goto alloc_error; + + free(curproxy->conf.elfs_file); + curproxy->conf.elfs_file = strdup(curproxy->conf.args.file); + curproxy->conf.elfs_line = curproxy->conf.args.line; + + /* get a chance to improve log-format error reporting by + * reporting the correct line-number when possible. + */ + if (!(curproxy->cap & PR_CAP_DEF) && !(curproxy->cap & PR_CAP_FE)) { + ha_warning("parsing [%s:%d] : backend '%s' : 'error-log-format' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + } + else if (strcmp(args[0], "log-tag") == 0) { /* tag to report to syslog */ + if (*(args[1]) == 0) { + ha_alert("parsing [%s:%d] : '%s' expects a tag for use in syslog.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + chunk_destroy(&curproxy->log_tag); + chunk_initlen(&curproxy->log_tag, strdup(args[1]), strlen(args[1]), strlen(args[1])); + if (b_orig(&curproxy->log_tag) == NULL) { + chunk_destroy(&curproxy->log_tag); + ha_alert("parsing [%s:%d]: cannot allocate memory for '%s'.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "log") == 0) { /* "no log" or "log ..." */ + if (!parse_logger(args, &curproxy->loggers, (kwm == KWM_NO), file, linenum, &errmsg)) { + ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "source") == 0) { /* address to which we bind when connecting */ + int cur_arg; + int port1, port2; + struct sockaddr_storage *sk; + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' expects [:], and optionally '%s' , and '%s' .\n", + file, linenum, "source", "usesrc", "interface"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* we must first clear any optional default setting */ + curproxy->conn_src.opts &= ~CO_SRC_TPROXY_MASK; + ha_free(&curproxy->conn_src.iface_name); + curproxy->conn_src.iface_len = 0; + + sk = str2sa_range(args[1], NULL, &port1, &port2, NULL, NULL, NULL, + &errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT); + if (!sk) { + ha_alert("parsing [%s:%d] : '%s %s' : %s\n", + file, linenum, args[0], args[1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->conn_src.source_addr = *sk; + curproxy->conn_src.opts |= CO_SRC_BIND; + + cur_arg = 2; + while (*(args[cur_arg])) { + if (strcmp(args[cur_arg], "usesrc") == 0) { /* address to use outside */ +#if defined(CONFIG_HAP_TRANSPARENT) + if (!*args[cur_arg + 1]) { + ha_alert("parsing [%s:%d] : '%s' expects [:], 'client', or 'clientip' as argument.\n", + file, linenum, "usesrc"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (strcmp(args[cur_arg + 1], "client") == 0) { + curproxy->conn_src.opts &= ~CO_SRC_TPROXY_MASK; + curproxy->conn_src.opts |= CO_SRC_TPROXY_CLI; + } else if (strcmp(args[cur_arg + 1], "clientip") == 0) { + curproxy->conn_src.opts &= ~CO_SRC_TPROXY_MASK; + curproxy->conn_src.opts |= CO_SRC_TPROXY_CIP; + } else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) { + char *name, *end; + + name = args[cur_arg+1] + 7; + while (isspace((unsigned char)*name)) + name++; + + end = name; + while (*end && !isspace((unsigned char)*end) && *end != ',' && *end != ')') + end++; + + curproxy->conn_src.opts &= ~CO_SRC_TPROXY_MASK; + curproxy->conn_src.opts |= CO_SRC_TPROXY_DYN; + free(curproxy->conn_src.bind_hdr_name); + curproxy->conn_src.bind_hdr_name = calloc(1, end - name + 1); + if (!curproxy->conn_src.bind_hdr_name) + goto alloc_error; + curproxy->conn_src.bind_hdr_len = end - name; + memcpy(curproxy->conn_src.bind_hdr_name, name, end - name); + curproxy->conn_src.bind_hdr_name[end-name] = '\0'; + curproxy->conn_src.bind_hdr_occ = -1; + + /* now look for an occurrence number */ + while (isspace((unsigned char)*end)) + end++; + if (*end == ',') { + end++; + name = end; + if (*end == '-') + end++; + while (isdigit((unsigned char)*end)) + end++; + curproxy->conn_src.bind_hdr_occ = strl2ic(name, end-name); + } + + if (curproxy->conn_src.bind_hdr_occ < -MAX_HDR_HISTORY) { + ha_alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative" + " occurrences values smaller than %d.\n", + file, linenum, MAX_HDR_HISTORY); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } else { + struct sockaddr_storage *sk; + + sk = str2sa_range(args[cur_arg + 1], NULL, &port1, &port2, NULL, NULL, NULL, + &errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT); + if (!sk) { + ha_alert("parsing [%s:%d] : '%s %s' : %s\n", + file, linenum, args[cur_arg], args[cur_arg+1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + curproxy->conn_src.tproxy_addr = *sk; + curproxy->conn_src.opts |= CO_SRC_TPROXY_ADDR; + } + global.last_checks |= LSTCHK_NETADM; +#else /* no TPROXY support */ + ha_alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n", + file, linenum, "usesrc"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; +#endif + cur_arg += 2; + continue; + } + + if (strcmp(args[cur_arg], "interface") == 0) { /* specifically bind to this interface */ +#ifdef SO_BINDTODEVICE + if (!*args[cur_arg + 1]) { + ha_alert("parsing [%s:%d] : '%s' : missing interface name.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + free(curproxy->conn_src.iface_name); + curproxy->conn_src.iface_name = strdup(args[cur_arg + 1]); + if (!curproxy->conn_src.iface_name) + goto alloc_error; + curproxy->conn_src.iface_len = strlen(curproxy->conn_src.iface_name); + global.last_checks |= LSTCHK_NETADM; +#else + ha_alert("parsing [%s:%d] : '%s' : '%s' option not implemented.\n", + file, linenum, args[0], args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; +#endif + cur_arg += 2; + continue; + } + ha_alert("parsing [%s:%d] : '%s' only supports optional keywords '%s' and '%s'.\n", + file, linenum, args[0], "interface", "usesrc"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "usesrc") == 0) { /* address to use outside: needs "source" first */ + ha_alert("parsing [%s:%d] : '%s' only allowed after a '%s' statement.\n", + file, linenum, "usesrc", "source"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "cliexp") == 0 || strcmp(args[0], "reqrep") == 0) { /* replace request header from a regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request replace-path', 'http-request replace-uri' or 'http-request replace-header' instead.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqdel") == 0) { /* delete request header from a regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request del-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqdeny") == 0) { /* deny a request if a header matches this regex */ + ha_alert("parsing [%s:%d] : The '%s' not supported anymore since HAProxy 2.1. " + "Use 'http-request deny' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqpass") == 0) { /* pass this header without allowing or denying the request */ + ha_alert("parsing [%s:%d] : The '%s' not supported anymore since HAProxy 2.1.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqallow") == 0) { /* allow a request if a header matches this regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request allow' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqtarpit") == 0) { /* tarpit a request if a header matches this regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request tarpit' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqirep") == 0) { /* replace request header from a regex, ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request replace-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqidel") == 0) { /* delete request header from a regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request del-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqideny") == 0) { /* deny a request if a header matches this regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request deny' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqipass") == 0) { /* pass this header without allowing or denying the request */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqiallow") == 0) { /* allow a request if a header matches this regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request allow' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqitarpit") == 0) { /* tarpit a request if a header matches this regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request tarpit' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "reqadd") == 0) { /* add request header */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-request add-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "srvexp") == 0 || strcmp(args[0], "rsprep") == 0) { /* replace response header from a regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response replace-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "rspdel") == 0) { /* delete response header from a regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response del-header' .\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "rspdeny") == 0) { /* block response header from a regex */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response deny' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "rspirep") == 0) { /* replace response header from a regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response replace-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "rspidel") == 0) { /* delete response header from a regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response del-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "rspideny") == 0) { /* block response header from a regex ignoring case */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response deny' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (strcmp(args[0], "rspadd") == 0) { /* add response header */ + ha_alert("parsing [%s:%d] : The '%s' directive is not supported anymore since HAProxy 2.1. " + "Use 'http-response add-header' instead.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else { + struct cfg_kw_list *kwl; + const char *best; + int index; + + list_for_each_entry(kwl, &cfg_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if (kwl->kw[index].section != CFG_LISTEN) + continue; + if (strcmp(kwl->kw[index].kw, args[0]) == 0) { + if (check_kw_experimental(&kwl->kw[index], file, linenum, &errmsg)) { + ha_alert("%s\n", errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + /* prepare error message just in case */ + rc = kwl->kw[index].parse(args, CFG_LISTEN, curproxy, curr_defproxy, file, linenum, &errmsg); + if (rc < 0) { + ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (rc > 0) { + ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_WARN; + goto out; + } + goto out; + } + } + } + + best = cfg_find_best_match(args[0], &cfg_keywords.list, CFG_LISTEN, common_kw_list); + if (best) + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section; did you mean '%s' maybe ?\n", file, linenum, args[0], cursection, best); + else + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + out: + free(errmsg); + return err_code; + + alloc_error: + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; +} diff --git a/src/cfgparse-quic.c b/src/cfgparse-quic.c new file mode 100644 index 0000000..3b38efa --- /dev/null +++ b/src/cfgparse-quic.c @@ -0,0 +1,292 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define QUIC_CC_NEWRENO_STR "newreno" +#define QUIC_CC_CUBIC_STR "cubic" +#define QUIC_CC_NO_CC_STR "nocc" + +static int bind_parse_quic_force_retry(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->options |= BC_O_QUIC_FORCE_RETRY; + return 0; +} + +/* parse "quic-cc-algo" bind keyword */ +static int bind_parse_quic_cc_algo(char **args, int cur_arg, struct proxy *px, + struct bind_conf *conf, char **err) +{ + struct quic_cc_algo *cc_algo; + const char *algo = NULL; + char *arg; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing control congestion algorithm", args[cur_arg]); + goto fail; + } + + arg = args[cur_arg + 1]; + if (strncmp(arg, QUIC_CC_NEWRENO_STR, strlen(QUIC_CC_NEWRENO_STR)) == 0) { + /* newreno */ + algo = QUIC_CC_NEWRENO_STR; + cc_algo = &quic_cc_algo_nr; + arg += strlen(QUIC_CC_NEWRENO_STR); + } + else if (strncmp(arg, QUIC_CC_CUBIC_STR, strlen(QUIC_CC_CUBIC_STR)) == 0) { + /* cubic */ + algo = QUIC_CC_CUBIC_STR; + cc_algo = &quic_cc_algo_cubic; + arg += strlen(QUIC_CC_CUBIC_STR); + } + else if (strncmp(arg, QUIC_CC_NO_CC_STR, strlen(QUIC_CC_NO_CC_STR)) == 0) { + /* nocc */ + if (!experimental_directives_allowed) { + ha_alert("'%s' algo is experimental, must be allowed via a global " + "'expose-experimental-directives'\n", arg); + goto fail; + } + + algo = QUIC_CC_NO_CC_STR; + cc_algo = &quic_cc_algo_nocc; + arg += strlen(QUIC_CC_NO_CC_STR); + } + else { + memprintf(err, "'%s' : unknown control congestion algorithm", args[cur_arg + 1]); + goto fail; + } + + if (*arg++ == '(') { + unsigned long cwnd; + char *end_opt; + + errno = 0; + cwnd = strtoul(arg, &end_opt, 0); + if (end_opt == arg || errno != 0) { + memprintf(err, "'%s' : could not parse congestion window value", args[cur_arg + 1]); + goto fail; + } + + if (*end_opt == 'k') { + cwnd <<= 10; + end_opt++; + } + else if (*end_opt == 'm') { + cwnd <<= 20; + end_opt++; + } + else if (*end_opt == 'g') { + cwnd <<= 30; + end_opt++; + } + + if (*end_opt != ')') { + memprintf(err, "'%s' : expects %s()", args[cur_arg + 1], algo); + goto fail; + } + + if (cwnd < 10240 || cwnd > (4UL << 30)) { + memprintf(err, "'%s' : should be greater than 10k and smaller than 4g", args[cur_arg + 1]); + goto fail; + } + + conf->max_cwnd = cwnd; + } + + conf->quic_cc_algo = cc_algo; + return 0; + + fail: + return ERR_ALERT | ERR_FATAL; +} + +static int bind_parse_quic_socket(char **args, int cur_arg, struct proxy *px, + struct bind_conf *conf, char **err) +{ + char *arg; + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing argument, use either connection or listener.", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + arg = args[cur_arg + 1]; + if (strcmp(arg, "connection") == 0) { + conf->quic_mode = QUIC_SOCK_MODE_CONN; + } + else if (strcmp(arg, "listener") == 0) { + conf->quic_mode = QUIC_SOCK_MODE_LSTNR; + } + else { + memprintf(err, "'%s' : unknown argument, use either connection or listener.", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +static struct bind_kw_list bind_kws = { "QUIC", { }, { + { "quic-force-retry", bind_parse_quic_force_retry, 0 }, + { "quic-cc-algo", bind_parse_quic_cc_algo, 1 }, + { "quic-socket", bind_parse_quic_socket, 1 }, + { NULL, NULL, 0 }, +}}; + +INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws); + +/* parse "tune.quic.socket-owner", accepts "listener" or "connection" */ +static int cfg_parse_quic_tune_socket_owner(char **args, int section_type, + struct proxy *curpx, + const struct proxy *defpx, + const char *file, int line, char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "connection") == 0) { + global.tune.options |= GTUNE_QUIC_SOCK_PER_CONN; + } + else if (strcmp(args[1], "listener") == 0) { + global.tune.options &= ~GTUNE_QUIC_SOCK_PER_CONN; + } + else { + memprintf(err, "'%s' expects either 'listener' or 'connection' but got '%s'.", args[0], args[1]); + return -1; + } + + return 0; +} + +/* Must be used to parse tune.quic.* setting which requires a time + * as value. + * Return -1 on alert, or 0 if succeeded. + */ +static int cfg_parse_quic_time(char **args, int section_type, + struct proxy *curpx, + const struct proxy *defpx, + const char *file, int line, char **err) +{ + unsigned int time; + const char *res, *name, *value; + int prefix_len = strlen("tune.quic."); + + if (too_many_args(1, args, err, NULL)) + return -1; + + name = args[0]; + value = args[1]; + res = parse_time_err(value, &time, TIME_UNIT_MS); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to '%s' " + "(maximum value is 2147483647 ms or ~24.8 days)", value, name); + return -1; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to '%s' " + "(minimum non-null value is 1 ms)", value, name); + return -1; + } + else if (res) { + memprintf(err, "unexpected character '%c' in '%s'", *res, name); + return -1; + } + + if (strcmp(name + prefix_len, "frontend.max-idle-timeout") == 0) + global.tune.quic_frontend_max_idle_timeout = time; + else if (strcmp(name + prefix_len, "backend.max-idle-timeout") == 0) + global.tune.quic_backend_max_idle_timeout = time; + else { + memprintf(err, "'%s' keyword not unhandled (please report this bug).", args[0]); + return -1; + } + + return 0; +} + +/* Parse any tune.quic.* setting with strictly positive integer values. + * Return -1 on alert, or 0 if succeeded. + */ +static int cfg_parse_quic_tune_setting(char **args, int section_type, + struct proxy *curpx, + const struct proxy *defpx, + const char *file, int line, char **err) +{ + unsigned int arg = 0; + int prefix_len = strlen("tune.quic."); + const char *suffix; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) != 0) + arg = atoi(args[1]); + + if (arg < 1) { + memprintf(err, "'%s' expects a positive integer.", args[0]); + return -1; + } + + suffix = args[0] + prefix_len; + if (strcmp(suffix, "frontend.conn-tx-buffers.limit") == 0) + global.tune.quic_streams_buf = arg; + else if (strcmp(suffix, "frontend.max-streams-bidi") == 0) + global.tune.quic_frontend_max_streams_bidi = arg; + else if (strcmp(suffix, "max-frame-loss") == 0) + global.tune.quic_max_frame_loss = arg; + else if (strcmp(suffix, "reorder-ratio") == 0) { + if (arg > 100) { + memprintf(err, "'%s' expects an integer argument between 0 and 100.", args[0]); + return -1; + } + + global.tune.quic_reorder_ratio = arg; + } + else if (strcmp(suffix, "retry-threshold") == 0) + global.tune.quic_retry_threshold = arg; + else { + memprintf(err, "'%s' keyword not unhandled (please report this bug).", args[0]); + return -1; + } + + return 0; +} + +/* config parser for global "tune.quic.zero-copy-fwd-send" */ +static int cfg_parse_quic_zero_copy_fwd_snd(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) + global.tune.no_zero_copy_fwd &= ~NO_ZERO_COPY_FWD_QUIC_SND; + else if (strcmp(args[1], "off") == 0) + global.tune.no_zero_copy_fwd |= NO_ZERO_COPY_FWD_QUIC_SND; + else { + memprintf(err, "'%s' expects 'on' or 'off'.", args[0]); + return -1; + } + return 0; +} + +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_GLOBAL, "tune.quic.socket-owner", cfg_parse_quic_tune_socket_owner }, + { CFG_GLOBAL, "tune.quic.backend.max-idle-timeou", cfg_parse_quic_time }, + { CFG_GLOBAL, "tune.quic.frontend.conn-tx-buffers.limit", cfg_parse_quic_tune_setting }, + { CFG_GLOBAL, "tune.quic.frontend.max-streams-bidi", cfg_parse_quic_tune_setting }, + { CFG_GLOBAL, "tune.quic.frontend.max-idle-timeout", cfg_parse_quic_time }, + { CFG_GLOBAL, "tune.quic.max-frame-loss", cfg_parse_quic_tune_setting }, + { CFG_GLOBAL, "tune.quic.reorder-ratio", cfg_parse_quic_tune_setting }, + { CFG_GLOBAL, "tune.quic.retry-threshold", cfg_parse_quic_tune_setting }, + { CFG_GLOBAL, "tune.quic.zero-copy-fwd-send", cfg_parse_quic_zero_copy_fwd_snd }, + { 0, NULL, NULL } +}}; + +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); diff --git a/src/cfgparse-ssl.c b/src/cfgparse-ssl.c new file mode 100644 index 0000000..5666336 --- /dev/null +++ b/src/cfgparse-ssl.c @@ -0,0 +1,2382 @@ +/* + * + * Copyright (C) 2012 EXCELIANCE, Emeric Brun + * Copyright (C) 2020 HAProxy Technologies, William Lallemand + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * + * Configuration parsing for SSL. + * This file is split in 3 parts: + * - global section parsing + * - bind keyword parsing + * - server keyword parsing + * + * Please insert the new keywords at the right place + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/****************** Global Section Parsing ********************************************/ + +static int ssl_load_global_issuers_from_path(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char *path; + struct dirent **de_list; + int i, n; + struct stat buf; + char *end; + char fp[MAXPATHLEN+1]; + + if (too_many_args(1, args, err, NULL)) + return -1; + + path = args[1]; + if (*path == 0 || stat(path, &buf)) { + memprintf(err, "%sglobal statement '%s' expects a directory path as an argument.\n", + err && *err ? *err : "", args[0]); + return -1; + } + if (S_ISDIR(buf.st_mode) == 0) { + memprintf(err, "%sglobal statement '%s': %s is not a directory.\n", + err && *err ? *err : "", args[0], path); + return -1; + } + + /* strip trailing slashes, including first one */ + for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--) + *end = 0; + /* path already parsed? */ + if (global_ssl.issuers_chain_path && strcmp(global_ssl.issuers_chain_path, path) == 0) + return 0; + /* overwrite old issuers_chain_path */ + free(global_ssl.issuers_chain_path); + global_ssl.issuers_chain_path = strdup(path); + ssl_free_global_issuers(); + + n = scandir(path, &de_list, 0, alphasort); + if (n < 0) { + memprintf(err, "%sglobal statement '%s': unable to scan directory '%s' : %s.\n", + err && *err ? *err : "", args[0], path, strerror(errno)); + return -1; + } + for (i = 0; i < n; i++) { + struct dirent *de = de_list[i]; + BIO *in = NULL; + char *warn = NULL; + + snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name); + free(de); + if (stat(fp, &buf) != 0) { + ha_warning("unable to stat certificate from file '%s' : %s.\n", fp, strerror(errno)); + goto next; + } + if (!S_ISREG(buf.st_mode)) + goto next; + + in = BIO_new(BIO_s_file()); + if (in == NULL) + goto next; + if (BIO_read_filename(in, fp) <= 0) + goto next; + ssl_load_global_issuer_from_BIO(in, fp, &warn); + if (warn) { + ha_warning("%s", warn); + ha_free(&warn); + } + next: + if (in) + BIO_free(in); + } + free(de_list); + + return 0; +} + +/* parse the "ssl-mode-async" keyword in global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ssl_async(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ +#ifdef SSL_MODE_ASYNC + global_ssl.async = 1; + global.ssl_used_async_engines = nb_engines; + return 0; +#else + memprintf(err, "'%s': openssl library does not support async mode", args[0]); + return -1; +#endif +} + +#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE) +/* parse the "ssl-engine" keyword in global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ssl_engine(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char *algo; + int ret = -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a valid engine name as an argument.", args[0]); + return ret; + } + + if (*(args[2]) == 0) { + /* if no list of algorithms is given, it defaults to ALL */ + algo = strdup("ALL"); + goto add_engine; + } + + /* otherwise the expected format is ssl-engine algo */ + if (strcmp(args[2], "algo") != 0) { + memprintf(err, "global statement '%s' expects to have algo keyword.", args[0]); + return ret; + } + + if (*(args[3]) == 0) { + memprintf(err, "global statement '%s' expects algorithm names as an argument.", args[0]); + return ret; + } + algo = strdup(args[3]); + +add_engine: + if (ssl_init_single_engine(args[1], algo)==0) { + openssl_engines_initialized++; + ret = 0; + } + free(algo); + return ret; +} +#endif + +#ifdef HAVE_SSL_PROVIDERS +/* parse the "ssl-propquery" keyword in global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ssl_propquery(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int ret = -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a property string as an argument.", args[0]); + return ret; + } + + if (EVP_set_default_properties(NULL, args[1])) + ret = 0; + + return ret; +} + +/* parse the "ssl-provider" keyword in global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ssl_provider(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int ret = -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a valid engine provider name as an argument.", args[0]); + return ret; + } + + if (ssl_init_provider(args[1]) == 0) + ret = 0; + + return ret; +} + +/* parse the "ssl-provider-path" keyword in global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ssl_provider_path(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a directory path as an argument.", args[0]); + return -1; + } + + OSSL_PROVIDER_set_default_search_path(NULL, args[1]); + + return 0; +} +#endif + +/* parse the "ssl-default-bind-ciphers" / "ssl-default-server-ciphers" keywords + * in global section. Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ciphers(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char **target; + + target = (args[0][12] == 'b') ? &global_ssl.listen_default_ciphers : &global_ssl.connect_default_ciphers; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a cipher suite as an argument.", args[0]); + return -1; + } + + free(*target); + *target = strdup(args[1]); + return 0; +} + +/* parse the "ssl-default-bind-ciphersuites" / "ssl-default-server-ciphersuites" keywords + * in global section. Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ciphersuites(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + char **target; + + target = (args[0][12] == 'b') ? &global_ssl.listen_default_ciphersuites : &global_ssl.connect_default_ciphersuites; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a cipher suite as an argument.", args[0]); + return -1; + } + + free(*target); + *target = strdup(args[1]); + return 0; +#else /* ! HAVE_SSL_CTX_SET_CIPHERSUITES */ + memprintf(err, "'%s' not supported for your SSL library (%s).", args[0], OPENSSL_VERSION_TEXT); + return -1; + +#endif +} + +#if defined(SSL_CTX_set1_curves_list) +/* + * parse the "ssl-default-bind-curves" keyword in a global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_curves(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char **target; + target = (args[0][12] == 'b') ? &global_ssl.listen_default_curves : &global_ssl.connect_default_curves; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a curves suite as an arguments.", args[0]); + return -1; + } + + free(*target); + *target = strdup(args[1]); + return 0; +} +#endif + +#if defined(SSL_CTX_set1_sigalgs_list) +/* + * parse the "ssl-default-bind-sigalgs" and "ssl-default-server-sigalgs" keyword in a global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_sigalgs(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char **target; + + target = (args[0][12] == 'b') ? &global_ssl.listen_default_sigalgs : &global_ssl.connect_default_sigalgs; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a curves suite as an arguments.", args[0]); + return -1; + } + + free(*target); + *target = strdup(args[1]); + return 0; +} +#endif + +#if defined(SSL_CTX_set1_client_sigalgs_list) +/* + * parse the "ssl-default-bind-client-sigalgs" keyword in a global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_client_sigalgs(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char **target; + + target = (args[0][12] == 'b') ? &global_ssl.listen_default_client_sigalgs : &global_ssl.connect_default_client_sigalgs; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects signature algorithms as an arguments.", args[0]); + return -1; + } + + free(*target); + *target = strdup(args[1]); + return 0; +} +#endif + +/* parse various global tune.ssl settings consisting in positive integers. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_int(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int *target; + + if (strcmp(args[0], "tune.ssl.cachesize") == 0) + target = &global.tune.sslcachesize; + else if (strcmp(args[0], "tune.ssl.maxrecord") == 0) + target = (int *)&global_ssl.max_record; + else if (strcmp(args[0], "tune.ssl.hard-maxrecord") == 0) + target = (int *)&global_ssl.hard_max_record; + else if (strcmp(args[0], "tune.ssl.ssl-ctx-cache-size") == 0) + target = &global_ssl.ctx_cache; + else if (strcmp(args[0], "maxsslconn") == 0) + target = &global.maxsslconn; + else if (strcmp(args[0], "tune.ssl.capture-buffer-size") == 0) + target = &global_ssl.capture_buffer_size; + else if (strcmp(args[0], "tune.ssl.capture-cipherlist-size") == 0) { + target = &global_ssl.capture_buffer_size; + ha_warning("parsing [%s:%d]: '%s' is deprecated and will be removed in version 2.7. Please use 'tune.ssl.capture-buffer-size' instead.\n", + file, line, args[0]); + } + else { + memprintf(err, "'%s' keyword not unhandled (please report this bug).", args[0]); + return -1; + } + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects an integer argument.", args[0]); + return -1; + } + + *target = atoi(args[1]); + if (*target < 0) { + memprintf(err, "'%s' expects a positive numeric value.", args[0]); + return -1; + } + return 0; +} + +static int ssl_parse_global_capture_buffer(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int ret; + + ret = ssl_parse_global_int(args, section_type, curpx, defpx, file, line, err); + if (ret != 0) + return ret; + + if (pool_head_ssl_capture) { + memprintf(err, "'%s' is already configured.", args[0]); + return -1; + } + + pool_head_ssl_capture = create_pool("ssl-capture", sizeof(struct ssl_capture) + global_ssl.capture_buffer_size, MEM_F_SHARED); + if (!pool_head_ssl_capture) { + memprintf(err, "Out of memory error."); + return -1; + } + return 0; +} + +/* init the SSLKEYLOGFILE pool */ +#ifdef HAVE_SSL_KEYLOG +static int ssl_parse_global_keylog(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) + global_ssl.keylog = 1; + else if (strcmp(args[1], "off") == 0) + global_ssl.keylog = 0; + else { + memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); + return -1; + } + + if (pool_head_ssl_keylog) /* already configured */ + return 0; + + pool_head_ssl_keylog = create_pool("ssl-keylogfile", sizeof(struct ssl_keylog), MEM_F_SHARED); + if (!pool_head_ssl_keylog) { + memprintf(err, "Out of memory error."); + return -1; + } + + pool_head_ssl_keylog_str = create_pool("ssl-keylogfile-str", sizeof(char) * SSL_KEYLOG_MAX_SECRET_SIZE, MEM_F_SHARED); + if (!pool_head_ssl_keylog_str) { + memprintf(err, "Out of memory error."); + return -1; + } + + return 0; +} +#else +static int ssl_parse_global_keylog(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + memprintf(err, "'%s' requires at least OpenSSL 1.1.1.", args[0]); + return -1; +} +#endif + +/* parse "ssl.force-private-cache". + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_private_cache(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(0, args, err, NULL)) + return -1; + + global_ssl.private_cache = 1; + return 0; +} + +/* parse "ssl.lifetime". + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_lifetime(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + const char *res; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects ssl sessions in seconds as argument.", args[0]); + return -1; + } + + res = parse_time_err(args[1], &global_ssl.life_time, TIME_UNIT_S); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to <%s> (maximum value is 2147483647 s or ~68 years).", + args[1], args[0]); + return -1; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to <%s> (minimum non-null value is 1 s).", + args[1], args[0]); + return -1; + } + else if (res) { + memprintf(err, "unexpected character '%c' in argument to <%s>.", *res, args[0]); + return -1; + } + return 0; +} + +#ifndef OPENSSL_NO_DH +/* parse "ssl-dh-param-file". + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_dh_param_file(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects a file path as an argument.", args[0]); + return -1; + } + + if (ssl_sock_load_global_dh_param_from_file(args[1])) { + memprintf(err, "'%s': unable to load DH parameters from file <%s>.", args[0], args[1]); + return -1; + } + return 0; +} + +/* parse "ssl.default-dh-param". + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_default_dh(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects an integer argument.", args[0]); + return -1; + } + + global_ssl.default_dh_param = atoi(args[1]); + if (global_ssl.default_dh_param < 1024) { + memprintf(err, "'%s' expects a value >= 1024.", args[0]); + return -1; + } + return 0; +} +#endif + + +/* + * parse "ssl-load-extra-files". + * multiple arguments are allowed: "bundle", "sctl", "ocsp", "issuer", "all", "none" + */ +static int ssl_parse_global_extra_files(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int i; + int gf = SSL_GF_NONE; + + if (*(args[1]) == 0) + goto err_arg; + + for (i = 1; *args[i]; i++) { + + if (strcmp("bundle", args[i]) == 0) { + gf |= SSL_GF_BUNDLE; + + } else if (strcmp("sctl", args[i]) == 0) { + gf |= SSL_GF_SCTL; + + } else if (strcmp("ocsp", args[i]) == 0){ + gf |= SSL_GF_OCSP; + + } else if (strcmp("issuer", args[i]) == 0){ + gf |= SSL_GF_OCSP_ISSUER; + + } else if (strcmp("key", args[i]) == 0) { + gf |= SSL_GF_KEY; + + } else if (strcmp("none", args[i]) == 0) { + if (gf != SSL_GF_NONE) + goto err_alone; + gf = SSL_GF_NONE; + i++; + break; + + } else if (strcmp("all", args[i]) == 0) { + if (gf != SSL_GF_NONE) + goto err_alone; + gf = SSL_GF_ALL; + i++; + break; + } else { + goto err_arg; + } + } + /* break from loop but there are still arguments */ + if (*args[i]) + goto err_alone; + + global_ssl.extra_files = gf; + + return 0; + +err_alone: + memprintf(err, "'%s' 'none' and 'all' can be only used alone", args[0]); + return -1; + +err_arg: + memprintf(err, "'%s' expects one or multiple arguments (none, all, bundle, sctl, ocsp, issuer).", args[0]); + return -1; +} + + +/* parse 'ssl-load-extra-del-ext */ +static int ssl_parse_global_extra_noext(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + global_ssl.extra_files_noext = 1; + return 0; +} + + +/***************************** Bind keyword Parsing ********************************************/ + +/* for ca-file and ca-verify-file */ +static int ssl_bind_parse_ca_file_common(char **args, int cur_arg, char **ca_file_p, int from_cli, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing CAfile path", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[cur_arg + 1] != '/') && (*args[cur_arg + 1] != '@') && global_ssl.ca_base) + memprintf(ca_file_p, "%s/%s", global_ssl.ca_base, args[cur_arg + 1]); + else + memprintf(ca_file_p, "%s", args[cur_arg + 1]); + + if (!ssl_store_load_locations_file(*ca_file_p, !from_cli, CAFILE_CERT)) { + memprintf(err, "'%s' : unable to load %s", args[cur_arg], *ca_file_p); + return ERR_ALERT | ERR_FATAL; + } + return 0; +} + +/* parse the "ca-file" bind keyword */ +static int ssl_bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + return ssl_bind_parse_ca_file_common(args, cur_arg, &conf->ca_file, from_cli, err); +} +static int bind_parse_ca_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_ca_file(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "ca-verify-file" bind keyword */ +static int ssl_bind_parse_ca_verify_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + return ssl_bind_parse_ca_file_common(args, cur_arg, &conf->ca_verify_file, from_cli, err); +} +static int bind_parse_ca_verify_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_ca_verify_file(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "ca-sign-file" bind keyword */ +static int bind_parse_ca_sign_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing CAfile path", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[cur_arg + 1] != '/') && (*args[cur_arg + 1] != '@') && global_ssl.ca_base) + memprintf(&conf->ca_sign_file, "%s/%s", global_ssl.ca_base, args[cur_arg + 1]); + else + memprintf(&conf->ca_sign_file, "%s", args[cur_arg + 1]); + + return 0; +} + +/* parse the "ca-sign-pass" bind keyword */ +static int bind_parse_ca_sign_pass(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing CAkey password", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + memprintf(&conf->ca_sign_pass, "%s", args[cur_arg + 1]); + return 0; +} + +/* parse the "ciphers" bind keyword */ +static int ssl_bind_parse_ciphers(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing cipher suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(conf->ciphers); + conf->ciphers = strdup(args[cur_arg + 1]); + return 0; +} +static int bind_parse_ciphers(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_ciphers(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "ciphersuites" bind keyword */ +static int ssl_bind_parse_ciphersuites(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing cipher suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(conf->ciphersuites); + conf->ciphersuites = strdup(args[cur_arg + 1]); + return 0; +#else + memprintf(err, "'%s' keyword not supported for this SSL library version (%s).", args[cur_arg], OPENSSL_VERSION_TEXT); + return ERR_ALERT | ERR_FATAL; +#endif +} + +static int bind_parse_ciphersuites(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_ciphersuites(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "crt" bind keyword. Returns a set of ERR_* flags possibly with an error in . */ +static int bind_parse_crt(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + char path[MAXPATHLEN]; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing certificate location", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[cur_arg + 1] != '/' ) && global_ssl.crt_base) { + if ((strlen(global_ssl.crt_base) + 1 + strlen(args[cur_arg + 1]) + 1) > sizeof(path) || + snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, args[cur_arg + 1]) > sizeof(path)) { + memprintf(err, "'%s' : path too long", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + return ssl_sock_load_cert(path, conf, err); + } + + return ssl_sock_load_cert(args[cur_arg + 1], conf, err); +} + +/* parse the "crt-list" bind keyword. Returns a set of ERR_* flags possibly with an error in . */ +static int bind_parse_crt_list(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + int err_code; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing certificate location", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + err_code = ssl_sock_load_cert_list_file(args[cur_arg + 1], 0, conf, px, err); + if (err_code) + memprintf(err, "'%s' : %s", args[cur_arg], *err); + + return err_code; +} + +/* parse the "crl-file" bind keyword */ +static int ssl_bind_parse_crl_file(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#ifndef X509_V_FLAG_CRL_CHECK + memprintf(err, "'%s' : library does not support CRL verify", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing CRLfile path", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[cur_arg + 1] != '/') && (*args[cur_arg + 1] != '@') && global_ssl.ca_base) + memprintf(&conf->crl_file, "%s/%s", global_ssl.ca_base, args[cur_arg + 1]); + else + memprintf(&conf->crl_file, "%s", args[cur_arg + 1]); + + if (!ssl_store_load_locations_file(conf->crl_file, !from_cli, CAFILE_CRL)) { + memprintf(err, "'%s' : unable to load %s", args[cur_arg], conf->crl_file); + return ERR_ALERT | ERR_FATAL; + } + return 0; +#endif +} +static int bind_parse_crl_file(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_crl_file(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "curves" bind keyword keyword */ +static int ssl_bind_parse_curves(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#if defined(SSL_CTX_set1_curves_list) + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing curve suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + conf->curves = strdup(args[cur_arg + 1]); + return 0; +#else + memprintf(err, "'%s' : library does not support curve suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} +static int bind_parse_curves(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_curves(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "sigalgs" bind keyword */ +static int ssl_bind_parse_sigalgs(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#if defined(SSL_CTX_set1_sigalgs_list) + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing signature algorithm list", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + conf->sigalgs = strdup(args[cur_arg + 1]); + return 0; +#else + memprintf(err, "'%s' : library does not support setting signature algorithms", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} +static int bind_parse_sigalgs(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_sigalgs(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "client-sigalgs" bind keyword */ +static int ssl_bind_parse_client_sigalgs(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#if defined(SSL_CTX_set1_client_sigalgs_list) + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing signature algorithm list", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + conf->client_sigalgs = strdup(args[cur_arg + 1]); + return 0; +#else + memprintf(err, "'%s' : library does not support setting signature algorithms", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} +static int bind_parse_client_sigalgs(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_client_sigalgs(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + + +/* parse the "ecdhe" bind keyword keyword */ +static int ssl_bind_parse_ecdhe(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#if !defined(SSL_CTX_set_tmp_ecdh) + memprintf(err, "'%s' : library does not support elliptic curve Diffie-Hellman (too old)", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#elif defined(OPENSSL_NO_ECDH) + memprintf(err, "'%s' : library does not support elliptic curve Diffie-Hellman (disabled via OPENSSL_NO_ECDH)", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing named curve", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + conf->ecdhe = strdup(args[cur_arg + 1]); + + return 0; +#endif +} +static int bind_parse_ecdhe(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_ecdhe(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "crt-ignore-err" and "ca-ignore-err" bind keywords */ +static int bind_parse_ignore_err(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + int code; + char *s1 = NULL, *s2 = NULL; + char *token = NULL; + char *p = args[cur_arg + 1]; + char *str; + unsigned long long *ignerr = conf->crt_ignerr_bitfield; + + if (!*p) { + memprintf(err, "'%s' : missing error IDs list", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (strcmp(args[cur_arg], "ca-ignore-err") == 0) + ignerr = conf->ca_ignerr_bitfield; + + if (strcmp(p, "all") == 0) { + cert_ignerr_bitfield_set_all(ignerr); + return 0; + } + + /* copy the string to be able to dump the complete one in case of + * error, because strtok_r is writing \0 inside. */ + str = strdup(p); + if (!str) { + memprintf(err, "'%s' : Could not allocate memory", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + s1 = str; + while ((token = strtok_r(s1, ",", &s2))) { + s1 = NULL; + if (isdigit((int)*token)) { + code = atoi(token); + if ((code <= 0) || (code > SSL_MAX_VFY_ERROR_CODE)) { + memprintf(err, "'%s' : ID '%d' out of range (1..%d) in error IDs list '%s'", + args[cur_arg], code, SSL_MAX_VFY_ERROR_CODE, args[cur_arg + 1]); + free(str); + return ERR_ALERT | ERR_FATAL; + } + } else { + code = x509_v_err_str_to_int(token); + if (code < 0) { + memprintf(err, "'%s' : error constant '%s' unknown in error IDs list '%s'", + args[cur_arg], token, args[cur_arg + 1]); + free(str); + return ERR_ALERT | ERR_FATAL; + } + } + cert_ignerr_bitfield_set(ignerr, code); + } + + free(str); + return 0; +} + +/* parse tls_method_options "no-xxx" and "force-xxx" */ +static int parse_tls_method_options(char *arg, struct tls_version_filter *methods, char **err) +{ + uint16_t v; + char *p; + p = strchr(arg, '-'); + if (!p) + goto fail; + p++; + if (strcmp(p, "sslv3") == 0) + v = CONF_SSLV3; + else if (strcmp(p, "tlsv10") == 0) + v = CONF_TLSV10; + else if (strcmp(p, "tlsv11") == 0) + v = CONF_TLSV11; + else if (strcmp(p, "tlsv12") == 0) + v = CONF_TLSV12; + else if (strcmp(p, "tlsv13") == 0) + v = CONF_TLSV13; + else + goto fail; + if (!strncmp(arg, "no-", 3)) + methods->flags |= methodVersions[v].flag; + else if (!strncmp(arg, "force-", 6)) + methods->min = methods->max = v; + else + goto fail; + return 0; + fail: + memprintf(err, "'%s' : option not implemented", arg); + return ERR_ALERT | ERR_FATAL; +} + +static int bind_parse_tls_method_options(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return parse_tls_method_options(args[cur_arg], &conf->ssl_conf.ssl_methods, err); +} + +static int srv_parse_tls_method_options(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + return parse_tls_method_options(args[*cur_arg], &newsrv->ssl_ctx.methods, err); +} + +/* parse tls_method min/max: "ssl-min-ver" and "ssl-max-ver" */ +static int parse_tls_method_minmax(char **args, int cur_arg, struct tls_version_filter *methods, char **err) +{ + uint16_t i, v = 0; + char *argv = args[cur_arg + 1]; + if (!*argv) { + memprintf(err, "'%s' : missing the ssl/tls version", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + for (i = CONF_TLSV_MIN; i <= CONF_TLSV_MAX; i++) + if (strcmp(argv, methodVersions[i].name) == 0) + v = i; + if (!v) { + memprintf(err, "'%s' : unknown ssl/tls version", args[cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + if (strcmp("ssl-min-ver", args[cur_arg]) == 0) + methods->min = v; + else if (strcmp("ssl-max-ver", args[cur_arg]) == 0) + methods->max = v; + else { + memprintf(err, "'%s' : option not implemented", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + return 0; +} + +static int ssl_bind_parse_tls_method_minmax(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + int ret; + +#if (HA_OPENSSL_VERSION_NUMBER < 0x10101000L) && !defined(OPENSSL_IS_BORINGSSL) + ha_warning("crt-list: ssl-min-ver and ssl-max-ver are not supported with this Openssl version (skipped).\n"); +#endif + ret = parse_tls_method_minmax(args, cur_arg, &conf->ssl_methods_cfg, err); + if (ret != ERR_NONE) + return ret; + + conf->ssl_methods.min = conf->ssl_methods_cfg.min; + conf->ssl_methods.max = conf->ssl_methods_cfg.max; + + return ret; +} +static int bind_parse_tls_method_minmax(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return parse_tls_method_minmax(args, cur_arg, &conf->ssl_conf.ssl_methods, err); +} + +static int srv_parse_tls_method_minmax(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + return parse_tls_method_minmax(args, *cur_arg, &newsrv->ssl_ctx.methods, err); +} + +/* parse the "no-tls-tickets" bind keyword */ +static int bind_parse_no_tls_tickets(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->ssl_options |= BC_SSL_O_NO_TLS_TICKETS; + return 0; +} + +/* parse the "allow-0rtt" bind keyword */ +static int ssl_bind_parse_allow_0rtt(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + conf->early_data = 1; + return 0; +} + +static int bind_parse_allow_0rtt(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->ssl_conf.early_data = 1; + return 0; +} + +/* parse the "npn" bind keyword */ +static int ssl_bind_parse_npn(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) + char *p1, *p2; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing the comma-delimited NPN protocol suite", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(conf->npn_str); + + /* the NPN string is built as a suite of ( )*, + * so we reuse each comma to store the next and need + * one more for the end of the string. + */ + conf->npn_len = strlen(args[cur_arg + 1]) + 1; + conf->npn_str = calloc(1, conf->npn_len + 1); + if (!conf->npn_str) { + memprintf(err, "out of memory"); + return ERR_ALERT | ERR_FATAL; + } + + memcpy(conf->npn_str + 1, args[cur_arg + 1], conf->npn_len); + + /* replace commas with the name length */ + p1 = conf->npn_str; + p2 = p1 + 1; + while (1) { + p2 = memchr(p1 + 1, ',', conf->npn_str + conf->npn_len - (p1 + 1)); + if (!p2) + p2 = p1 + 1 + strlen(p1 + 1); + + if (p2 - (p1 + 1) > 255) { + *p2 = '\0'; + memprintf(err, "'%s' : NPN protocol name too long : '%s'", args[cur_arg], p1 + 1); + return ERR_ALERT | ERR_FATAL; + } + + *p1 = p2 - (p1 + 1); + p1 = p2; + + if (!*p2) + break; + + *(p2++) = '\0'; + } + return 0; +#else + memprintf(err, "'%s' : library does not support TLS NPN extension", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} + +static int bind_parse_npn(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_npn(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + + +/* Parses a alpn string and converts it to the right format for the SSL api */ +int ssl_sock_parse_alpn(char *arg, char **alpn_str, int *alpn_len, char **err) +{ + char *p1, *p2, *alpn = NULL; + int len, ret = 0; + + *alpn_str = NULL; + *alpn_len = 0; + + if (!*arg) { + memprintf(err, "missing the comma-delimited ALPN protocol suite"); + goto error; + } + + /* the ALPN string is built as a suite of ( )*, + * so we reuse each comma to store the next and need + * one more for the end of the string. + */ + len = strlen(arg) + 1; + alpn = calloc(1, len+1); + if (!alpn) { + memprintf(err, "'%s' : out of memory", arg); + goto error; + } + memcpy(alpn+1, arg, len); + + /* replace commas with the name length */ + p1 = alpn; + p2 = p1 + 1; + while (1) { + p2 = memchr(p1 + 1, ',', alpn + len - (p1 + 1)); + if (!p2) + p2 = p1 + 1 + strlen(p1 + 1); + + if (p2 - (p1 + 1) > 255) { + *p2 = '\0'; + memprintf(err, "ALPN protocol name too long : '%s'", p1 + 1); + goto error; + } + + *p1 = p2 - (p1 + 1); + p1 = p2; + + if (!*p2) + break; + + *(p2++) = '\0'; + } + + *alpn_str = alpn; + *alpn_len = len; + + out: + return ret; + + error: + free(alpn); + ret = ERR_ALERT | ERR_FATAL; + goto out; +} + +/* parse the "alpn" bind keyword */ +static int ssl_bind_parse_alpn(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + int ret; + + free(conf->alpn_str); + + ret = ssl_sock_parse_alpn(args[cur_arg + 1], &conf->alpn_str, &conf->alpn_len, err); + if (ret) + memprintf(err, "'%s' : %s", args[cur_arg], *err); + return ret; +#else + memprintf(err, "'%s' : library does not support TLS ALPN extension", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} + +static int bind_parse_alpn(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_alpn(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "ssl" bind keyword */ +static int bind_parse_ssl(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->options |= BC_O_USE_SSL; + + if (global_ssl.listen_default_ciphers && !conf->ssl_conf.ciphers) + conf->ssl_conf.ciphers = strdup(global_ssl.listen_default_ciphers); +#if defined(SSL_CTX_set1_curves_list) + if (global_ssl.listen_default_curves && !conf->ssl_conf.curves) + conf->ssl_conf.curves = strdup(global_ssl.listen_default_curves); +#endif +#if defined(SSL_CTX_set1_sigalgs_list) + if (global_ssl.listen_default_sigalgs && !conf->ssl_conf.sigalgs) + conf->ssl_conf.sigalgs = strdup(global_ssl.listen_default_sigalgs); +#endif +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + if (global_ssl.listen_default_ciphersuites && !conf->ssl_conf.ciphersuites) + conf->ssl_conf.ciphersuites = strdup(global_ssl.listen_default_ciphersuites); +#endif + conf->ssl_options |= global_ssl.listen_default_ssloptions; + conf->ssl_conf.ssl_methods.flags |= global_ssl.listen_default_sslmethods.flags; + if (!conf->ssl_conf.ssl_methods.min) + conf->ssl_conf.ssl_methods.min = global_ssl.listen_default_sslmethods.min; + if (!conf->ssl_conf.ssl_methods.max) + conf->ssl_conf.ssl_methods.max = global_ssl.listen_default_sslmethods.max; + + return 0; +} + +/* parse the "prefer-client-ciphers" bind keyword */ +static int bind_parse_pcc(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->ssl_options |= BC_SSL_O_PREF_CLIE_CIPH; + return 0; +} + +/* parse the "generate-certificates" bind keyword */ +static int bind_parse_generate_certs(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ +#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) + conf->options |= BC_O_GENERATE_CERTS; +#else + memprintf(err, "%sthis version of openssl cannot generate SSL certificates.\n", + err && *err ? *err : ""); +#endif + return 0; +} + +/* parse the "strict-sni" bind keyword */ +static int bind_parse_strict_sni(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->strict_sni = 1; + return 0; +} + +/* parse the "tls-ticket-keys" bind keyword */ +static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ +#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0) + FILE *f = NULL; + int i = 0; + char thisline[LINESIZE]; + struct tls_keys_ref *keys_ref = NULL; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing TLS ticket keys file path", args[cur_arg]); + goto fail; + } + + keys_ref = tlskeys_ref_lookup(args[cur_arg + 1]); + if (keys_ref) { + keys_ref->refcount++; + conf->keys_ref = keys_ref; + return 0; + } + + keys_ref = calloc(1, sizeof(*keys_ref)); + if (!keys_ref) { + memprintf(err, "'%s' : allocation error", args[cur_arg+1]); + goto fail; + } + + keys_ref->tlskeys = malloc(TLS_TICKETS_NO * sizeof(union tls_sess_key)); + if (!keys_ref->tlskeys) { + memprintf(err, "'%s' : allocation error", args[cur_arg+1]); + goto fail; + } + + if ((f = fopen(args[cur_arg + 1], "r")) == NULL) { + memprintf(err, "'%s' : unable to load ssl tickets keys file", args[cur_arg+1]); + goto fail; + } + + keys_ref->filename = strdup(args[cur_arg + 1]); + if (!keys_ref->filename) { + memprintf(err, "'%s' : allocation error", args[cur_arg+1]); + goto fail; + } + + keys_ref->key_size_bits = 0; + while (fgets(thisline, sizeof(thisline), f) != NULL) { + int len = strlen(thisline); + int dec_size; + + /* Strip newline characters from the end */ + if(thisline[len - 1] == '\n') + thisline[--len] = 0; + + if(thisline[len - 1] == '\r') + thisline[--len] = 0; + + dec_size = base64dec(thisline, len, (char *) (keys_ref->tlskeys + i % TLS_TICKETS_NO), sizeof(union tls_sess_key)); + if (dec_size < 0) { + memprintf(err, "'%s' : unable to decode base64 key on line %d", args[cur_arg+1], i + 1); + goto fail; + } + else if (!keys_ref->key_size_bits && (dec_size == sizeof(struct tls_sess_key_128))) { + keys_ref->key_size_bits = 128; + } + else if (!keys_ref->key_size_bits && (dec_size == sizeof(struct tls_sess_key_256))) { + keys_ref->key_size_bits = 256; + } + else if (((dec_size != sizeof(struct tls_sess_key_128)) && (dec_size != sizeof(struct tls_sess_key_256))) + || ((dec_size == sizeof(struct tls_sess_key_128) && (keys_ref->key_size_bits != 128))) + || ((dec_size == sizeof(struct tls_sess_key_256) && (keys_ref->key_size_bits != 256)))) { + memprintf(err, "'%s' : wrong sized key on line %d", args[cur_arg+1], i + 1); + goto fail; + } + i++; + } + + if (i < TLS_TICKETS_NO) { + memprintf(err, "'%s' : please supply at least %d keys in the tls-tickets-file", args[cur_arg+1], TLS_TICKETS_NO); + goto fail; + } + + fclose(f); + + /* Use penultimate key for encryption, handle when TLS_TICKETS_NO = 1 */ + i -= 2; + keys_ref->tls_ticket_enc_index = i < 0 ? 0 : i % TLS_TICKETS_NO; + keys_ref->unique_id = -1; + keys_ref->refcount = 1; + HA_RWLOCK_INIT(&keys_ref->lock); + conf->keys_ref = keys_ref; + + LIST_INSERT(&tlskeys_reference, &keys_ref->list); + + return 0; + + fail: + if (f) + fclose(f); + if (keys_ref) { + free(keys_ref->filename); + free(keys_ref->tlskeys); + free(keys_ref); + } + return ERR_ALERT | ERR_FATAL; + +#else + memprintf(err, "'%s' : TLS ticket callback extension not supported", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */ +} + +/* parse the "verify" bind keyword */ +static int ssl_bind_parse_verify(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing verify method", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (strcmp(args[cur_arg + 1], "none") == 0) + conf->verify = SSL_SOCK_VERIFY_NONE; + else if (strcmp(args[cur_arg + 1], "optional") == 0) + conf->verify = SSL_SOCK_VERIFY_OPTIONAL; + else if (strcmp(args[cur_arg + 1], "required") == 0) + conf->verify = SSL_SOCK_VERIFY_REQUIRED; + else { + memprintf(err, "'%s' : unknown verify method '%s', only 'none', 'optional', and 'required' are supported\n", + args[cur_arg], args[cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} +static int bind_parse_verify(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_verify(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + +/* parse the "no-alpn" ssl-bind keyword, storing an empty ALPN string */ +static int ssl_bind_parse_no_alpn(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + free(conf->alpn_str); + conf->alpn_len = 0; + conf->alpn_str = strdup(""); + + if (!conf->alpn_str) { + memprintf(err, "'%s' : out of memory", *args); + return ERR_ALERT | ERR_FATAL; + } + return 0; +} + +/* parse the "no-alpn" bind keyword, storing an empty ALPN string */ +static int bind_parse_no_alpn(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_no_alpn(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + + +/* parse the "no-ca-names" bind keyword */ +static int ssl_bind_parse_no_ca_names(char **args, int cur_arg, struct proxy *px, struct ssl_bind_conf *conf, int from_cli, char **err) +{ + conf->no_ca_names = 1; + return 0; +} + +static int bind_parse_no_ca_names(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + return ssl_bind_parse_no_ca_names(args, cur_arg, px, &conf->ssl_conf, 0, err); +} + + +static int ssl_bind_parse_ocsp_update(char **args, int cur_arg, struct proxy *px, + struct ssl_bind_conf *ssl_conf, int from_cli, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : expecting ", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (strcmp(args[cur_arg + 1], "on") == 0) + ssl_conf->ocsp_update = SSL_SOCK_OCSP_UPDATE_ON; + else if (strcmp(args[cur_arg + 1], "off") == 0) + ssl_conf->ocsp_update = SSL_SOCK_OCSP_UPDATE_OFF; + else { + memprintf(err, "'%s' : expecting ", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (ssl_conf->ocsp_update == SSL_SOCK_OCSP_UPDATE_ON) { + /* We might need to create the main ocsp update task */ + int ret = ssl_create_ocsp_update_task(err); + if (ret) + return ret; + } + + return 0; +} + + +/***************************** "server" keywords Parsing ********************************************/ + +/* parse the "npn" bind keyword */ +static int srv_parse_npn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG) + char *p1, *p2; + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing the comma-delimited NPN protocol suite", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(newsrv->ssl_ctx.npn_str); + + /* the NPN string is built as a suite of ( )*, + * so we reuse each comma to store the next and need + * one more for the end of the string. + */ + newsrv->ssl_ctx.npn_len = strlen(args[*cur_arg + 1]) + 1; + newsrv->ssl_ctx.npn_str = calloc(1, newsrv->ssl_ctx.npn_len + 1); + if (!newsrv->ssl_ctx.npn_str) { + memprintf(err, "out of memory"); + return ERR_ALERT | ERR_FATAL; + } + + memcpy(newsrv->ssl_ctx.npn_str + 1, args[*cur_arg + 1], + newsrv->ssl_ctx.npn_len); + + /* replace commas with the name length */ + p1 = newsrv->ssl_ctx.npn_str; + p2 = p1 + 1; + while (1) { + p2 = memchr(p1 + 1, ',', newsrv->ssl_ctx.npn_str + + newsrv->ssl_ctx.npn_len - (p1 + 1)); + if (!p2) + p2 = p1 + 1 + strlen(p1 + 1); + + if (p2 - (p1 + 1) > 255) { + *p2 = '\0'; + memprintf(err, "'%s' : NPN protocol name too long : '%s'", args[*cur_arg], p1 + 1); + return ERR_ALERT | ERR_FATAL; + } + + *p1 = p2 - (p1 + 1); + p1 = p2; + + if (!*p2) + break; + + *(p2++) = '\0'; + } + return 0; +#else + memprintf(err, "'%s' : library does not support TLS NPN extension", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +static int parse_alpn(char *alpn, char **out_alpn_str, int *out_alpn_len, char **err) +{ + free(*out_alpn_str); + return ssl_sock_parse_alpn(alpn, out_alpn_str, out_alpn_len, err); +} +#endif + +/* parse the "alpn" server keyword */ +static int srv_parse_alpn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + int ret = parse_alpn(args[*cur_arg + 1], + &newsrv->ssl_ctx.alpn_str, + &newsrv->ssl_ctx.alpn_len, err); + if (ret) + memprintf(err, "'%s' : %s", args[*cur_arg], *err); + return ret; +#else + memprintf(err, "'%s' : library does not support TLS ALPN extension", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} + +/* parse the "check-alpn" server keyword */ +static int srv_parse_check_alpn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + int ret = parse_alpn(args[*cur_arg + 1], + &newsrv->check.alpn_str, + &newsrv->check.alpn_len, err); + if (ret) + memprintf(err, "'%s' : %s", args[*cur_arg], *err); + return ret; +#else + memprintf(err, "'%s' : library does not support TLS ALPN extension", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#endif +} + +/* parse the "ca-file" server keyword */ +static int srv_parse_ca_file(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + const int create_if_none = newsrv->flags & SRV_F_DYNAMIC ? 0 : 1; + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing CAfile path", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[*cur_arg + 1] != '/') && (*args[*cur_arg + 1] != '@') && global_ssl.ca_base) + memprintf(&newsrv->ssl_ctx.ca_file, "%s/%s", global_ssl.ca_base, args[*cur_arg + 1]); + else + memprintf(&newsrv->ssl_ctx.ca_file, "%s", args[*cur_arg + 1]); + + if (!ssl_store_load_locations_file(newsrv->ssl_ctx.ca_file, create_if_none, CAFILE_CERT)) { + memprintf(err, "'%s' : unable to load %s", args[*cur_arg], newsrv->ssl_ctx.ca_file); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "check-sni" server keyword */ +static int srv_parse_check_sni(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing SNI", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + newsrv->check.sni = strdup(args[*cur_arg + 1]); + if (!newsrv->check.sni) { + memprintf(err, "'%s' : failed to allocate memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + return 0; + +} + +/* common function to init ssl_ctx */ +static int ssl_sock_init_srv(struct server *s) +{ + if (global_ssl.connect_default_ciphers && !s->ssl_ctx.ciphers) + s->ssl_ctx.ciphers = strdup(global_ssl.connect_default_ciphers); +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + if (global_ssl.connect_default_ciphersuites && !s->ssl_ctx.ciphersuites) { + s->ssl_ctx.ciphersuites = strdup(global_ssl.connect_default_ciphersuites); + if (!s->ssl_ctx.ciphersuites) + return 1; + } +#endif + s->ssl_ctx.options |= global_ssl.connect_default_ssloptions; + s->ssl_ctx.methods.flags |= global_ssl.connect_default_sslmethods.flags; + + if (!s->ssl_ctx.methods.min) + s->ssl_ctx.methods.min = global_ssl.connect_default_sslmethods.min; + + if (!s->ssl_ctx.methods.max) + s->ssl_ctx.methods.max = global_ssl.connect_default_sslmethods.max; + +#if defined(SSL_CTX_set1_sigalgs_list) + if (global_ssl.connect_default_sigalgs && !s->ssl_ctx.sigalgs) { + s->ssl_ctx.sigalgs = strdup(global_ssl.connect_default_sigalgs); + if (!s->ssl_ctx.sigalgs) + return 1; + } +#endif + +#if defined(SSL_CTX_set1_client_sigalgs_list) + if (global_ssl.connect_default_client_sigalgs && !s->ssl_ctx.client_sigalgs) { + s->ssl_ctx.client_sigalgs = strdup(global_ssl.connect_default_client_sigalgs); + if (!s->ssl_ctx.client_sigalgs) + return 1; + } +#endif + +#if defined(SSL_CTX_set1_curves_list) + if (global_ssl.connect_default_curves && !s->ssl_ctx.curves) { + s->ssl_ctx.curves = strdup(global_ssl.connect_default_curves); + if (!s->ssl_ctx.curves) + return 1; + } +#endif + + return 0; +} + +/* parse the "check-ssl" server keyword */ +static int srv_parse_check_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->check.use_ssl = 1; + if (ssl_sock_init_srv(newsrv)) { + memprintf(err, "'%s' : not enough memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "ciphers" server keyword */ +static int srv_parse_ciphers(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing cipher suite", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(newsrv->ssl_ctx.ciphers); + newsrv->ssl_ctx.ciphers = strdup(args[*cur_arg + 1]); + + if (!newsrv->ssl_ctx.ciphers) { + memprintf(err, "'%s' : not enough memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "ciphersuites" server keyword */ +static int srv_parse_ciphersuites(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing cipher suite", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(newsrv->ssl_ctx.ciphersuites); + newsrv->ssl_ctx.ciphersuites = strdup(args[*cur_arg + 1]); + + if (!newsrv->ssl_ctx.ciphersuites) { + memprintf(err, "'%s' : not enough memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +#else /* ! HAVE_SSL_CTX_SET_CIPHERSUITES */ + memprintf(err, "'%s' not supported for your SSL library (%s).", args[*cur_arg], OPENSSL_VERSION_TEXT); + return ERR_ALERT | ERR_FATAL; + +#endif +} + +/* parse the "client-sigalgs" server keyword */ +static int srv_parse_client_sigalgs(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifndef SSL_CTX_set1_client_sigalgs_list + memprintf(err, "'%s' : library does not support setting signature algorithms", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + char *arg; + + arg = args[*cur_arg + 1]; + if (!*arg) { + memprintf(err, "'%s' : missing signature algorithm list", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + newsrv->ssl_ctx.client_sigalgs = strdup(arg); + if (!newsrv->ssl_ctx.client_sigalgs) { + memprintf(err, "out of memory"); + return ERR_ALERT | ERR_FATAL; + } + return 0; +#endif +} + + +/* parse the "crl-file" server keyword */ +static int srv_parse_crl_file(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifndef X509_V_FLAG_CRL_CHECK + memprintf(err, "'%s' : library does not support CRL verify", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + const int create_if_none = newsrv->flags & SRV_F_DYNAMIC ? 0 : 1; + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing CRLfile path", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[*cur_arg + 1] != '/') && (*args[*cur_arg + 1] != '@') && global_ssl.ca_base) + memprintf(&newsrv->ssl_ctx.crl_file, "%s/%s", global_ssl.ca_base, args[*cur_arg + 1]); + else + memprintf(&newsrv->ssl_ctx.crl_file, "%s", args[*cur_arg + 1]); + + if (!ssl_store_load_locations_file(newsrv->ssl_ctx.crl_file, create_if_none, CAFILE_CRL)) { + memprintf(err, "'%s' : unable to load %s", args[*cur_arg], newsrv->ssl_ctx.crl_file); + return ERR_ALERT | ERR_FATAL; + } + return 0; +#endif +} + +/* parse the "curves" server keyword */ +static int srv_parse_curves(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifndef SSL_CTX_set1_curves_list + memprintf(err, "'%s' : library does not support setting curves list", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + char *arg; + + arg = args[*cur_arg + 1]; + if (!*arg) { + memprintf(err, "'%s' : missing curves list", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + newsrv->ssl_ctx.curves = strdup(arg); + if (!newsrv->ssl_ctx.curves) { + memprintf(err, "out of memory"); + return ERR_ALERT | ERR_FATAL; + } + return 0; +#endif +} + +/* parse the "crt" server keyword */ +static int srv_parse_crt(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing certificate file path", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if ((*args[*cur_arg + 1] != '/') && global_ssl.crt_base) + memprintf(&newsrv->ssl_ctx.client_crt, "%s/%s", global_ssl.crt_base, args[*cur_arg + 1]); + else + memprintf(&newsrv->ssl_ctx.client_crt, "%s", args[*cur_arg + 1]); + + return 0; +} + +/* parse the "no-check-ssl" server keyword */ +static int srv_parse_no_check_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->check.use_ssl = -1; + ha_free(&newsrv->ssl_ctx.ciphers); + newsrv->ssl_ctx.options &= ~global_ssl.connect_default_ssloptions; + return 0; +} + +/* parse the "no-send-proxy-v2-ssl" server keyword */ +static int srv_parse_no_send_proxy_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->pp_opts &= ~SRV_PP_V2; + newsrv->pp_opts &= ~SRV_PP_V2_SSL; + return 0; +} + +/* parse the "no-send-proxy-v2-ssl-cn" server keyword */ +static int srv_parse_no_send_proxy_cn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->pp_opts &= ~SRV_PP_V2; + newsrv->pp_opts &= ~SRV_PP_V2_SSL; + newsrv->pp_opts &= ~SRV_PP_V2_SSL_CN; + return 0; +} + +/* parse the "no-ssl" server keyword */ +static int srv_parse_no_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + /* if default-server have use_ssl, prepare ssl settings */ + if (newsrv->use_ssl == 1) { + if (ssl_sock_init_srv(newsrv)) { + memprintf(err, "'%s' : not enough memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + } + else { + ha_free(&newsrv->ssl_ctx.ciphers); + } + newsrv->use_ssl = -1; + return 0; +} + +/* parse the "allow-0rtt" server keyword */ +static int srv_parse_allow_0rtt(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->ssl_ctx.options |= SRV_SSL_O_EARLY_DATA; + return 0; +} + +/* parse the "no-ssl-reuse" server keyword */ +static int srv_parse_no_ssl_reuse(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->ssl_ctx.options |= SRV_SSL_O_NO_REUSE; + return 0; +} + +/* parse the "no-tls-tickets" server keyword */ +static int srv_parse_no_tls_tickets(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->ssl_ctx.options |= SRV_SSL_O_NO_TLS_TICKETS; + return 0; +} +/* parse the "send-proxy-v2-ssl" server keyword */ +static int srv_parse_send_proxy_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->pp_opts |= SRV_PP_V2; + newsrv->pp_opts |= SRV_PP_V2_SSL; + return 0; +} + +/* parse the "send-proxy-v2-ssl-cn" server keyword */ +static int srv_parse_send_proxy_cn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->pp_opts |= SRV_PP_V2; + newsrv->pp_opts |= SRV_PP_V2_SSL; + newsrv->pp_opts |= SRV_PP_V2_SSL_CN; + return 0; +} + +/* parse the "sigalgs" server keyword */ +static int srv_parse_sigalgs(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifndef SSL_CTX_set1_sigalgs_list + memprintf(err, "'%s' : library does not support setting signature algorithms", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + char *arg; + + arg = args[*cur_arg + 1]; + if (!*arg) { + memprintf(err, "'%s' : missing signature algorithm list", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + newsrv->ssl_ctx.sigalgs = strdup(arg); + if (!newsrv->ssl_ctx.sigalgs) { + memprintf(err, "out of memory"); + return ERR_ALERT | ERR_FATAL; + } + return 0; +#endif +} + +/* parse the "sni" server keyword */ +static int srv_parse_sni(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME + memprintf(err, "'%s' : the current SSL library doesn't support the SNI TLS extension", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + char *arg; + + arg = args[*cur_arg + 1]; + if (!*arg) { + memprintf(err, "'%s' : missing sni expression", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(newsrv->sni_expr); + newsrv->sni_expr = strdup(arg); + if (!newsrv->sni_expr) { + memprintf(err, "out of memory"); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +#endif +} + +/* parse the "ssl" server keyword */ +static int srv_parse_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->use_ssl = 1; + if (ssl_sock_init_srv(newsrv)) { + memprintf(err, "'%s' : not enough memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "ssl-reuse" server keyword */ +static int srv_parse_ssl_reuse(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->ssl_ctx.options &= ~SRV_SSL_O_NO_REUSE; + return 0; +} + +/* parse the "tls-tickets" server keyword */ +static int srv_parse_tls_tickets(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + newsrv->ssl_ctx.options &= ~SRV_SSL_O_NO_TLS_TICKETS; + return 0; +} + +/* parse the "verify" server keyword */ +static int srv_parse_verify(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing verify method", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (strcmp(args[*cur_arg + 1], "none") == 0) + newsrv->ssl_ctx.verify = SSL_SOCK_VERIFY_NONE; + else if (strcmp(args[*cur_arg + 1], "required") == 0) + newsrv->ssl_ctx.verify = SSL_SOCK_VERIFY_REQUIRED; + else { + memprintf(err, "'%s' : unknown verify method '%s', only 'none' and 'required' are supported\n", + args[*cur_arg], args[*cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "verifyhost" server keyword */ +static int srv_parse_verifyhost(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing hostname to verify against", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + free(newsrv->ssl_ctx.verify_host); + newsrv->ssl_ctx.verify_host = strdup(args[*cur_arg + 1]); + + if (!newsrv->ssl_ctx.verify_host) { + memprintf(err, "'%s' : not enough memory", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "ssl-default-bind-options" keyword in global section */ +static int ssl_parse_default_bind_options(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) { + int i = 1; + + if (*(args[i]) == 0) { + memprintf(err, "global statement '%s' expects an option as an argument.", args[0]); + return -1; + } + while (*(args[i])) { + if (strcmp(args[i], "no-tls-tickets") == 0) + global_ssl.listen_default_ssloptions |= BC_SSL_O_NO_TLS_TICKETS; + else if (strcmp(args[i], "prefer-client-ciphers") == 0) + global_ssl.listen_default_ssloptions |= BC_SSL_O_PREF_CLIE_CIPH; + else if (strcmp(args[i], "ssl-min-ver") == 0 || strcmp(args[i], "ssl-max-ver") == 0) { + if (!parse_tls_method_minmax(args, i, &global_ssl.listen_default_sslmethods, err)) + i++; + else { + memprintf(err, "%s on global statement '%s'.", *err, args[0]); + return -1; + } + } + else if (parse_tls_method_options(args[i], &global_ssl.listen_default_sslmethods, err)) { + memprintf(err, "unknown option '%s' on global statement '%s'.", args[i], args[0]); + return -1; + } + i++; + } + return 0; +} + +/* parse the "ssl-default-server-options" keyword in global section */ +static int ssl_parse_default_server_options(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) { + int i = 1; + + if (*(args[i]) == 0) { + memprintf(err, "global statement '%s' expects an option as an argument.", args[0]); + return -1; + } + while (*(args[i])) { + if (strcmp(args[i], "no-tls-tickets") == 0) + global_ssl.connect_default_ssloptions |= SRV_SSL_O_NO_TLS_TICKETS; + else if (strcmp(args[i], "ssl-min-ver") == 0 || strcmp(args[i], "ssl-max-ver") == 0) { + if (!parse_tls_method_minmax(args, i, &global_ssl.connect_default_sslmethods, err)) + i++; + else { + memprintf(err, "%s on global statement '%s'.", *err, args[0]); + return -1; + } + } + else if (parse_tls_method_options(args[i], &global_ssl.connect_default_sslmethods, err)) { + memprintf(err, "unknown option '%s' on global statement '%s'.", args[i], args[0]); + return -1; + } + i++; + } + return 0; +} + +/* parse the "ca-base" / "crt-base" keywords in global section. + * Returns <0 on alert, >0 on warning, 0 on success. + */ +static int ssl_parse_global_ca_crt_base(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char **target; + + target = (args[0][1] == 'a') ? &global_ssl.ca_base : &global_ssl.crt_base; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (*target) { + memprintf(err, "'%s' already specified.", args[0]); + return -1; + } + + if (*(args[1]) == 0) { + memprintf(err, "global statement '%s' expects a directory path as an argument.", args[0]); + return -1; + } + *target = strdup(args[1]); + return 0; +} + +/* parse the "ssl-skip-self-issued-ca" keyword in global section. */ +static int ssl_parse_skip_self_issued_ca(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ +#ifdef SSL_CTX_build_cert_chain + global_ssl.skip_self_issued_ca = 1; + return 0; +#else + memprintf(err, "global statement '%s' requires at least OpenSSL 1.0.2.", args[0]); + return -1; +#endif +} + + +static int ssl_parse_global_ocsp_maxdelay(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int value = 0; + + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects an integer argument.", args[0]); + return -1; + } + + value = atoi(args[1]); + if (value < 0) { + memprintf(err, "'%s' expects a positive numeric value.", args[0]); + return -1; + } + + if (global_ssl.ocsp_update.delay_min > value) { + memprintf(err, "'%s' can not be lower than tune.ssl.ocsp-update.mindelay.", args[0]); + return -1; + } + + global_ssl.ocsp_update.delay_max = value; + + return 0; +} + +static int ssl_parse_global_ocsp_mindelay(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int value = 0; + + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects an integer argument.", args[0]); + return -1; + } + + value = atoi(args[1]); + if (value < 0) { + memprintf(err, "'%s' expects a positive numeric value.", args[0]); + return -1; + } + + if (value > global_ssl.ocsp_update.delay_max) { + memprintf(err, "'%s' can not be higher than tune.ssl.ocsp-update.maxdelay.", args[0]); + return -1; + } + + global_ssl.ocsp_update.delay_min = value; + + return 0; +} + + + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted, doing so helps + * all code contributors. + * Optional keywords are also declared with a NULL ->parse() function so that + * the config parser can report an appropriate error when a known keyword was + * not enabled. + */ + +/* the keywords are used for crt-list parsing, they *MUST* be safe + * with their proxy argument NULL and must only fill the ssl_bind_conf */ +struct ssl_crtlist_kw ssl_crtlist_kws[] = { + { "allow-0rtt", ssl_bind_parse_allow_0rtt, 0 }, /* allow 0-RTT */ + { "alpn", ssl_bind_parse_alpn, 1 }, /* set ALPN supported protocols */ + { "ca-file", ssl_bind_parse_ca_file, 1 }, /* set CAfile to process ca-names and verify on client cert */ + { "ca-verify-file", ssl_bind_parse_ca_verify_file, 1 }, /* set CAverify file to process verify on client cert */ + { "ciphers", ssl_bind_parse_ciphers, 1 }, /* set SSL cipher suite */ + { "ciphersuites", ssl_bind_parse_ciphersuites, 1 }, /* set TLS 1.3 cipher suite */ + { "client-sigalgs", ssl_bind_parse_client_sigalgs, 1 }, /* set SSL client signature algorithms */ + { "crl-file", ssl_bind_parse_crl_file, 1 }, /* set certificate revocation list file use on client cert verify */ + { "curves", ssl_bind_parse_curves, 1 }, /* set SSL curve suite */ + { "ecdhe", ssl_bind_parse_ecdhe, 1 }, /* defines named curve for elliptic curve Diffie-Hellman */ + { "no-alpn", ssl_bind_parse_no_alpn, 0 }, /* disable sending ALPN */ + { "no-ca-names", ssl_bind_parse_no_ca_names, 0 }, /* do not send ca names to clients (ca_file related) */ + { "npn", ssl_bind_parse_npn, 1 }, /* set NPN supported protocols */ + { "sigalgs", ssl_bind_parse_sigalgs, 1 }, /* set SSL signature algorithms */ + { "ssl-min-ver", ssl_bind_parse_tls_method_minmax,1 }, /* minimum version */ + { "ssl-max-ver", ssl_bind_parse_tls_method_minmax,1 }, /* maximum version */ + { "verify", ssl_bind_parse_verify, 1 }, /* set SSL verify method */ + { "ocsp-update", ssl_bind_parse_ocsp_update, 1 }, /* ocsp update mode (on or off) */ + { NULL, NULL, 0 }, +}; + +/* no initcall for ssl_bind_kws, these ones are parsed in the parser loop */ + +static struct bind_kw_list bind_kws = { "SSL", { }, { + { "allow-0rtt", bind_parse_allow_0rtt, 0 }, /* Allow 0RTT */ + { "alpn", bind_parse_alpn, 1 }, /* set ALPN supported protocols */ + { "ca-file", bind_parse_ca_file, 1 }, /* set CAfile to process ca-names and verify on client cert */ + { "ca-verify-file", bind_parse_ca_verify_file, 1 }, /* set CAverify file to process verify on client cert */ + { "ca-ignore-err", bind_parse_ignore_err, 1 }, /* set error IDs to ignore on verify depth > 0 */ + { "ca-sign-file", bind_parse_ca_sign_file, 1 }, /* set CAFile used to generate and sign server certs */ + { "ca-sign-pass", bind_parse_ca_sign_pass, 1 }, /* set CAKey passphrase */ + { "ciphers", bind_parse_ciphers, 1 }, /* set SSL cipher suite */ + { "ciphersuites", bind_parse_ciphersuites, 1 }, /* set TLS 1.3 cipher suite */ + { "client-sigalgs", bind_parse_client_sigalgs, 1 }, /* set SSL client signature algorithms */ + { "crl-file", bind_parse_crl_file, 1 }, /* set certificate revocation list file use on client cert verify */ + { "crt", bind_parse_crt, 1 }, /* load SSL certificates from this location */ + { "crt-ignore-err", bind_parse_ignore_err, 1 }, /* set error IDs to ignore on verify depth == 0 */ + { "crt-list", bind_parse_crt_list, 1 }, /* load a list of crt from this location */ + { "curves", bind_parse_curves, 1 }, /* set SSL curve suite */ + { "ecdhe", bind_parse_ecdhe, 1 }, /* defines named curve for elliptic curve Diffie-Hellman */ + { "force-sslv3", bind_parse_tls_method_options, 0 }, /* force SSLv3 */ + { "force-tlsv10", bind_parse_tls_method_options, 0 }, /* force TLSv10 */ + { "force-tlsv11", bind_parse_tls_method_options, 0 }, /* force TLSv11 */ + { "force-tlsv12", bind_parse_tls_method_options, 0 }, /* force TLSv12 */ + { "force-tlsv13", bind_parse_tls_method_options, 0 }, /* force TLSv13 */ + { "generate-certificates", bind_parse_generate_certs, 0 }, /* enable the server certificates generation */ + { "no-alpn", bind_parse_no_alpn, 0 }, /* disable sending ALPN */ + { "no-ca-names", bind_parse_no_ca_names, 0 }, /* do not send ca names to clients (ca_file related) */ + { "no-sslv3", bind_parse_tls_method_options, 0 }, /* disable SSLv3 */ + { "no-tlsv10", bind_parse_tls_method_options, 0 }, /* disable TLSv10 */ + { "no-tlsv11", bind_parse_tls_method_options, 0 }, /* disable TLSv11 */ + { "no-tlsv12", bind_parse_tls_method_options, 0 }, /* disable TLSv12 */ + { "no-tlsv13", bind_parse_tls_method_options, 0 }, /* disable TLSv13 */ + { "no-tls-tickets", bind_parse_no_tls_tickets, 0 }, /* disable session resumption tickets */ + { "sigalgs", bind_parse_sigalgs, 1 }, /* set SSL signature algorithms */ + { "ssl", bind_parse_ssl, 0 }, /* enable SSL processing */ + { "ssl-min-ver", bind_parse_tls_method_minmax, 1 }, /* minimum version */ + { "ssl-max-ver", bind_parse_tls_method_minmax, 1 }, /* maximum version */ + { "strict-sni", bind_parse_strict_sni, 0 }, /* refuse negotiation if sni doesn't match a certificate */ + { "tls-ticket-keys", bind_parse_tls_ticket_keys, 1 }, /* set file to load TLS ticket keys from */ + { "verify", bind_parse_verify, 1 }, /* set SSL verify method */ + { "npn", bind_parse_npn, 1 }, /* set NPN supported protocols */ + { "prefer-client-ciphers", bind_parse_pcc, 0 }, /* prefer client ciphers */ + { NULL, NULL, 0 }, +}}; + +INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws); + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted, doing so helps + * all code contributors. + * Optional keywords are also declared with a NULL ->parse() function so that + * the config parser can report an appropriate error when a known keyword was + * not enabled. + */ +static struct srv_kw_list srv_kws = { "SSL", { }, { + { "allow-0rtt", srv_parse_allow_0rtt, 0, 1, 1 }, /* Allow using early data on this server */ + { "alpn", srv_parse_alpn, 1, 1, 1 }, /* Set ALPN supported protocols */ + { "ca-file", srv_parse_ca_file, 1, 1, 1 }, /* set CAfile to process verify server cert */ + { "check-alpn", srv_parse_check_alpn, 1, 1, 1 }, /* Set ALPN used for checks */ + { "check-sni", srv_parse_check_sni, 1, 1, 1 }, /* set SNI */ + { "check-ssl", srv_parse_check_ssl, 0, 1, 1 }, /* enable SSL for health checks */ + { "ciphers", srv_parse_ciphers, 1, 1, 1 }, /* select the cipher suite */ + { "ciphersuites", srv_parse_ciphersuites, 1, 1, 1 }, /* select the cipher suite */ + { "client-sigalgs", srv_parse_client_sigalgs, 1, 1, 1 }, /* signature algorithms */ + { "crl-file", srv_parse_crl_file, 1, 1, 1 }, /* set certificate revocation list file use on server cert verify */ + { "curves", srv_parse_curves, 1, 1, 1 }, /* set TLS curves list */ + { "crt", srv_parse_crt, 1, 1, 1 }, /* set client certificate */ + { "force-sslv3", srv_parse_tls_method_options, 0, 1, 1 }, /* force SSLv3 */ + { "force-tlsv10", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv10 */ + { "force-tlsv11", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv11 */ + { "force-tlsv12", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv12 */ + { "force-tlsv13", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv13 */ + { "no-check-ssl", srv_parse_no_check_ssl, 0, 1, 0 }, /* disable SSL for health checks */ + { "no-send-proxy-v2-ssl", srv_parse_no_send_proxy_ssl, 0, 1, 0 }, /* do not send PROXY protocol header v2 with SSL info */ + { "no-send-proxy-v2-ssl-cn", srv_parse_no_send_proxy_cn, 0, 1, 0 }, /* do not send PROXY protocol header v2 with CN */ + { "no-ssl", srv_parse_no_ssl, 0, 1, 0 }, /* disable SSL processing */ + { "no-ssl-reuse", srv_parse_no_ssl_reuse, 0, 1, 1 }, /* disable session reuse */ + { "no-sslv3", srv_parse_tls_method_options, 0, 0, 1 }, /* disable SSLv3 */ + { "no-tlsv10", srv_parse_tls_method_options, 0, 0, 1 }, /* disable TLSv10 */ + { "no-tlsv11", srv_parse_tls_method_options, 0, 0, 1 }, /* disable TLSv11 */ + { "no-tlsv12", srv_parse_tls_method_options, 0, 0, 1 }, /* disable TLSv12 */ + { "no-tlsv13", srv_parse_tls_method_options, 0, 0, 1 }, /* disable TLSv13 */ + { "no-tls-tickets", srv_parse_no_tls_tickets, 0, 1, 1 }, /* disable session resumption tickets */ + { "npn", srv_parse_npn, 1, 1, 1 }, /* Set NPN supported protocols */ + { "send-proxy-v2-ssl", srv_parse_send_proxy_ssl, 0, 1, 1 }, /* send PROXY protocol header v2 with SSL info */ + { "send-proxy-v2-ssl-cn", srv_parse_send_proxy_cn, 0, 1, 1 }, /* send PROXY protocol header v2 with CN */ + { "sigalgs", srv_parse_sigalgs, 1, 1, 1 }, /* signature algorithms */ + { "sni", srv_parse_sni, 1, 1, 1 }, /* send SNI extension */ + { "ssl", srv_parse_ssl, 0, 1, 1 }, /* enable SSL processing */ + { "ssl-min-ver", srv_parse_tls_method_minmax, 1, 1, 1 }, /* minimum version */ + { "ssl-max-ver", srv_parse_tls_method_minmax, 1, 1, 1 }, /* maximum version */ + { "ssl-reuse", srv_parse_ssl_reuse, 0, 1, 0 }, /* enable session reuse */ + { "tls-tickets", srv_parse_tls_tickets, 0, 1, 1 }, /* enable session resumption tickets */ + { "verify", srv_parse_verify, 1, 1, 1 }, /* set SSL verify method */ + { "verifyhost", srv_parse_verifyhost, 1, 1, 1 }, /* require that SSL cert verifies for hostname */ + { NULL, NULL, 0, 0 }, +}}; + +INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws); + +static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_GLOBAL, "ca-base", ssl_parse_global_ca_crt_base }, + { CFG_GLOBAL, "crt-base", ssl_parse_global_ca_crt_base }, + { CFG_GLOBAL, "issuers-chain-path", ssl_load_global_issuers_from_path }, + { CFG_GLOBAL, "maxsslconn", ssl_parse_global_int }, + { CFG_GLOBAL, "ssl-default-bind-options", ssl_parse_default_bind_options }, + { CFG_GLOBAL, "ssl-default-server-options", ssl_parse_default_server_options }, +#ifndef OPENSSL_NO_DH + { CFG_GLOBAL, "ssl-dh-param-file", ssl_parse_global_dh_param_file }, +#endif + { CFG_GLOBAL, "ssl-mode-async", ssl_parse_global_ssl_async }, +#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE) + { CFG_GLOBAL, "ssl-engine", ssl_parse_global_ssl_engine }, +#endif +#ifdef HAVE_SSL_PROVIDERS + { CFG_GLOBAL, "ssl-propquery", ssl_parse_global_ssl_propquery }, + { CFG_GLOBAL, "ssl-provider", ssl_parse_global_ssl_provider }, + { CFG_GLOBAL, "ssl-provider-path", ssl_parse_global_ssl_provider_path }, +#endif + { CFG_GLOBAL, "ssl-skip-self-issued-ca", ssl_parse_skip_self_issued_ca }, + { CFG_GLOBAL, "tune.ssl.cachesize", ssl_parse_global_int }, +#ifndef OPENSSL_NO_DH + { CFG_GLOBAL, "tune.ssl.default-dh-param", ssl_parse_global_default_dh }, +#endif + { CFG_GLOBAL, "tune.ssl.force-private-cache", ssl_parse_global_private_cache }, + { CFG_GLOBAL, "tune.ssl.lifetime", ssl_parse_global_lifetime }, + { CFG_GLOBAL, "tune.ssl.maxrecord", ssl_parse_global_int }, + { CFG_GLOBAL, "tune.ssl.hard-maxrecord", ssl_parse_global_int }, + { CFG_GLOBAL, "tune.ssl.ssl-ctx-cache-size", ssl_parse_global_int }, + { CFG_GLOBAL, "tune.ssl.capture-cipherlist-size", ssl_parse_global_capture_buffer }, + { CFG_GLOBAL, "tune.ssl.capture-buffer-size", ssl_parse_global_capture_buffer }, + { CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog }, + { CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers }, + { CFG_GLOBAL, "ssl-default-server-ciphers", ssl_parse_global_ciphers }, +#if defined(SSL_CTX_set1_curves_list) + { CFG_GLOBAL, "ssl-default-bind-curves", ssl_parse_global_curves }, + { CFG_GLOBAL, "ssl-default-server-curves", ssl_parse_global_curves }, +#endif +#if defined(SSL_CTX_set1_sigalgs_list) + { CFG_GLOBAL, "ssl-default-bind-sigalgs", ssl_parse_global_sigalgs }, + { CFG_GLOBAL, "ssl-default-server-sigalgs", ssl_parse_global_sigalgs }, +#endif +#if defined(SSL_CTX_set1_client_sigalgs_list) + { CFG_GLOBAL, "ssl-default-bind-client-sigalgs", ssl_parse_global_client_sigalgs }, + { CFG_GLOBAL, "ssl-default-server-client-sigalgs", ssl_parse_global_client_sigalgs }, +#endif + { CFG_GLOBAL, "ssl-default-bind-ciphersuites", ssl_parse_global_ciphersuites }, + { CFG_GLOBAL, "ssl-default-server-ciphersuites", ssl_parse_global_ciphersuites }, + { CFG_GLOBAL, "ssl-load-extra-files", ssl_parse_global_extra_files }, + { CFG_GLOBAL, "ssl-load-extra-del-ext", ssl_parse_global_extra_noext }, +#ifndef OPENSSL_NO_OCSP + { CFG_GLOBAL, "tune.ssl.ocsp-update.maxdelay", ssl_parse_global_ocsp_maxdelay }, + { CFG_GLOBAL, "tune.ssl.ocsp-update.mindelay", ssl_parse_global_ocsp_mindelay }, +#endif + { 0, NULL, NULL }, +}}; + +INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); diff --git a/src/cfgparse-tcp.c b/src/cfgparse-tcp.c new file mode 100644 index 0000000..a4f6f29 --- /dev/null +++ b/src/cfgparse-tcp.c @@ -0,0 +1,275 @@ +/* + * Configuration parsing for TCP (bind and server keywords) + * + * Copyright 2000-2020 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef IPV6_V6ONLY +/* parse the "v4v6" bind keyword */ +static int bind_parse_v4v6(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->settings.options |= RX_O_V4V6; + return 0; +} + +/* parse the "v6only" bind keyword */ +static int bind_parse_v6only(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->settings.options |= RX_O_V6ONLY; + return 0; +} +#endif + +#ifdef CONFIG_HAP_TRANSPARENT +/* parse the "transparent" bind keyword */ +static int bind_parse_transparent(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->settings.options |= RX_O_FOREIGN; + return 0; +} +#endif + +#if defined(TCP_DEFER_ACCEPT) || defined(SO_ACCEPTFILTER) +/* parse the "defer-accept" bind keyword */ +static int bind_parse_defer_accept(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->options |= BC_O_DEF_ACCEPT; + return 0; +} +#endif + +#ifdef TCP_FASTOPEN +/* parse the "tfo" bind keyword */ +static int bind_parse_tfo(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + conf->options |= BC_O_TCP_FO; + return 0; +} +#endif + +#ifdef TCP_MAXSEG +/* parse the "mss" bind keyword */ +static int bind_parse_mss(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + int mss; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing MSS value", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + mss = atoi(args[cur_arg + 1]); + if (!mss || abs(mss) > 65535) { + memprintf(err, "'%s' : expects an MSS with and absolute value between 1 and 65535", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + conf->maxseg = mss; + return 0; +} +#endif + +#ifdef TCP_USER_TIMEOUT +/* parse the "tcp-ut" bind keyword */ +static int bind_parse_tcp_ut(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + const char *ptr = NULL; + unsigned int timeout; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing TCP User Timeout value", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + ptr = parse_time_err(args[cur_arg + 1], &timeout, TIME_UNIT_MS); + if (ptr == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)", + args[cur_arg+1], args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + else if (ptr == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)", + args[cur_arg+1], args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + else if (ptr) { + memprintf(err, "'%s' : expects a positive delay in milliseconds", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + conf->tcp_ut = timeout; + return 0; +} +#endif + +#ifdef SO_BINDTODEVICE +/* parse the "interface" bind keyword */ +static int bind_parse_interface(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing interface name", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + ha_free(&conf->settings.interface); + conf->settings.interface = strdup(args[cur_arg + 1]); + return 0; +} +#endif + +#ifdef USE_NS +/* parse the "namespace" bind keyword */ +static int bind_parse_namespace(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + char *namespace = NULL; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing namespace id", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + namespace = args[cur_arg + 1]; + + conf->settings.netns = netns_store_lookup(namespace, strlen(namespace)); + + if (conf->settings.netns == NULL) + conf->settings.netns = netns_store_insert(namespace); + + if (conf->settings.netns == NULL) { + ha_alert("Cannot open namespace '%s'.\n", args[cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + return 0; +} +#endif + +#ifdef TCP_USER_TIMEOUT +/* parse the "tcp-ut" server keyword */ +static int srv_parse_tcp_ut(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ + const char *ptr = NULL; + unsigned int timeout; + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing TCP User Timeout value", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + ptr = parse_time_err(args[*cur_arg + 1], &timeout, TIME_UNIT_MS); + if (ptr == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)", + args[*cur_arg+1], args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + else if (ptr == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)", + args[*cur_arg+1], args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + else if (ptr) { + memprintf(err, "'%s' : expects a positive delay in milliseconds", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + if (newsrv->addr.ss_family == AF_INET || newsrv->addr.ss_family == AF_INET6) + newsrv->tcp_ut = timeout; + + return 0; +} +#endif + + +/************************************************************************/ +/* All supported bind keywords must be declared here. */ +/************************************************************************/ + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted, doing so helps + * all code contributors. + * Optional keywords are also declared with a NULL ->parse() function so that + * the config parser can report an appropriate error when a known keyword was + * not enabled. + */ +static struct bind_kw_list bind_kws = { "TCP", { }, { +#if defined(TCP_DEFER_ACCEPT) || defined(SO_ACCEPTFILTER) + { "defer-accept", bind_parse_defer_accept, 0 }, /* wait for some data for 1 second max before doing accept */ +#endif +#ifdef SO_BINDTODEVICE + { "interface", bind_parse_interface, 1 }, /* specifically bind to this interface */ +#endif +#ifdef TCP_MAXSEG + { "mss", bind_parse_mss, 1 }, /* set MSS of listening socket */ +#endif +#ifdef TCP_USER_TIMEOUT + { "tcp-ut", bind_parse_tcp_ut, 1 }, /* set User Timeout on listening socket */ +#endif +#ifdef TCP_FASTOPEN + { "tfo", bind_parse_tfo, 0 }, /* enable TCP_FASTOPEN of listening socket */ +#endif +#ifdef CONFIG_HAP_TRANSPARENT + { "transparent", bind_parse_transparent, 0 }, /* transparently bind to the specified addresses */ +#endif +#ifdef IPV6_V6ONLY + { "v4v6", bind_parse_v4v6, 0 }, /* force socket to bind to IPv4+IPv6 */ + { "v6only", bind_parse_v6only, 0 }, /* force socket to bind to IPv6 only */ +#endif +#ifdef USE_NS + { "namespace", bind_parse_namespace, 1 }, +#endif + /* the versions with the NULL parse function*/ + { "defer-accept", NULL, 0 }, + { "interface", NULL, 1 }, + { "mss", NULL, 1 }, + { "transparent", NULL, 0 }, + { "v4v6", NULL, 0 }, + { "v6only", NULL, 0 }, + { NULL, NULL, 0 }, +}}; + +INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws); + +static struct srv_kw_list srv_kws = { "TCP", { }, { +#ifdef TCP_USER_TIMEOUT + { "tcp-ut", srv_parse_tcp_ut, 1, 1, 0 }, /* set TCP user timeout on server */ +#endif + { NULL, NULL, 0 }, +}}; + +INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/cfgparse-unix.c b/src/cfgparse-unix.c new file mode 100644 index 0000000..b1fb1e2 --- /dev/null +++ b/src/cfgparse-unix.c @@ -0,0 +1,135 @@ +/* + * Configuration parsing for UNIX sockets (bind and server keywords) + * + * Copyright 2000-2020 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* parse the "mode" bind keyword */ +static int bind_parse_mode(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + char *endptr; + + conf->settings.ux.mode = strtol(args[cur_arg + 1], &endptr, 8); + + if (!*args[cur_arg + 1] || *endptr) { + memprintf(err, "'%s' : missing or invalid mode '%s' (octal integer expected)", args[cur_arg], args[cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + + return 0; +} + +/* parse the "gid" bind keyword */ +static int bind_parse_gid(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing value", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + conf->settings.ux.gid = atol(args[cur_arg + 1]); + return 0; +} + +/* parse the "group" bind keyword */ +static int bind_parse_group(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + struct group *group; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing group name", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + group = getgrnam(args[cur_arg + 1]); + if (!group) { + memprintf(err, "'%s' : unknown group name '%s'", args[cur_arg], args[cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + + conf->settings.ux.gid = group->gr_gid; + return 0; +} + +/* parse the "uid" bind keyword */ +static int bind_parse_uid(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing value", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + conf->settings.ux.uid = atol(args[cur_arg + 1]); + return 0; +} + +/* parse the "user" bind keyword */ +static int bind_parse_user(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) +{ + struct passwd *user; + + if (!*args[cur_arg + 1]) { + memprintf(err, "'%s' : missing user name", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + user = getpwnam(args[cur_arg + 1]); + if (!user) { + memprintf(err, "'%s' : unknown user name '%s'", args[cur_arg], args[cur_arg + 1]); + return ERR_ALERT | ERR_FATAL; + } + + conf->settings.ux.uid = user->pw_uid; + return 0; +} + +/* Note: must not be declared as its list will be overwritten. + * Please take care of keeping this list alphabetically sorted, doing so helps + * all code contributors. + * Optional keywords are also declared with a NULL ->parse() function so that + * the config parser can report an appropriate error when a known keyword was + * not enabled. + */ +static struct bind_kw_list bind_kws = { "UNIX", { }, { + { "gid", bind_parse_gid, 1 }, /* set the socket's gid */ + { "group", bind_parse_group, 1 }, /* set the socket's gid from the group name */ + { "mode", bind_parse_mode, 1 }, /* set the socket's mode (eg: 0644)*/ + { "uid", bind_parse_uid, 1 }, /* set the socket's uid */ + { "user", bind_parse_user, 1 }, /* set the socket's uid from the user name */ + { NULL, NULL, 0 }, +}}; + +INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws); diff --git a/src/cfgparse.c b/src/cfgparse.c new file mode 100644 index 0000000..2744f97 --- /dev/null +++ b/src/cfgparse.c @@ -0,0 +1,4798 @@ +/* + * Configuration parser + * + * Copyright 2000-2011 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +/* This is to have crypt() and sched_setaffinity() defined on Linux */ +#define _GNU_SOURCE + +#ifdef USE_LIBCRYPT +#ifdef USE_CRYPT_H +/* some platforms such as Solaris need this */ +#include +#endif +#endif /* USE_LIBCRYPT */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_CPU_AFFINITY +#include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_CPU_AFFINITY +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Used to chain configuration sections definitions. This list + * stores struct cfg_section + */ +struct list sections = LIST_HEAD_INIT(sections); + +struct list postparsers = LIST_HEAD_INIT(postparsers); + +extern struct proxy *mworker_proxy; + +/* curproxy is only valid during parsing and will be NULL afterwards. */ +struct proxy *curproxy = NULL; + +char *cursection = NULL; +int cfg_maxpconn = 0; /* # of simultaneous connections per proxy (-N) */ +int cfg_maxconn = 0; /* # of simultaneous connections, (-n) */ +char *cfg_scope = NULL; /* the current scope during the configuration parsing */ +int non_global_section_parsed = 0; + +/* how to handle default paths */ +static enum default_path_mode { + DEFAULT_PATH_CURRENT = 0, /* "current": paths are relative to CWD (this is the default) */ + DEFAULT_PATH_CONFIG, /* "config": paths are relative to config file */ + DEFAULT_PATH_PARENT, /* "parent": paths are relative to config file's ".." */ + DEFAULT_PATH_ORIGIN, /* "origin": paths are relative to default_path_origin */ +} default_path_mode; + +static char initial_cwd[PATH_MAX]; +static char current_cwd[PATH_MAX]; + +/* List head of all known configuration keywords */ +struct cfg_kw_list cfg_keywords = { + .list = LIST_HEAD_INIT(cfg_keywords.list) +}; + +/* + * converts to a list of listeners which are dynamically allocated. + * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where : + * - can be empty or "*" to indicate INADDR_ANY ; + * - is a numerical port from 1 to 65535 ; + * - indicates to use the range from to instead (inclusive). + * This can be repeated as many times as necessary, separated by a coma. + * Function returns 1 for success or 0 if error. In case of errors, if is + * not NULL, it must be a valid pointer to either NULL or a freeable area that + * will be replaced with an error message. + */ +int str2listener(char *str, struct proxy *curproxy, struct bind_conf *bind_conf, const char *file, int line, char **err) +{ + struct protocol *proto; + char *next, *dupstr; + int port, end; + + next = dupstr = strdup(str); + + while (next && *next) { + struct sockaddr_storage *ss2; + int fd = -1; + + str = next; + /* 1) look for the end of the first address */ + if ((next = strchr(str, ',')) != NULL) { + *next++ = 0; + } + + ss2 = str2sa_range(str, NULL, &port, &end, &fd, &proto, NULL, err, + (curproxy == global.cli_fe || curproxy == mworker_proxy) ? NULL : global.unix_bind.prefix, + NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_PORT_RANGE | + PA_O_SOCKET_FD | PA_O_STREAM | PA_O_XPRT); + if (!ss2) + goto fail; + + if (ss2->ss_family == AF_CUST_RHTTP_SRV) { + /* Check if a previous non reverse HTTP present is + * already defined. If DGRAM or STREAM is set, this + * indicates that we are currently parsing the second + * or more address. + */ + if (bind_conf->options & (BC_O_USE_SOCK_DGRAM|BC_O_USE_SOCK_STREAM) && + !(bind_conf->options & BC_O_REVERSE_HTTP)) { + memprintf(err, "Cannot mix reverse HTTP bind with others.\n"); + goto fail; + } + + bind_conf->rhttp_srvname = strdup(str + strlen("rhttp@")); + if (!bind_conf->rhttp_srvname) { + memprintf(err, "Cannot allocate reverse HTTP bind.\n"); + goto fail; + } + + bind_conf->options |= BC_O_REVERSE_HTTP; + } + else if (bind_conf->options & BC_O_REVERSE_HTTP) { + /* Standard address mixed with a previous reverse HTTP one. */ + memprintf(err, "Cannot mix reverse HTTP bind with others.\n"); + goto fail; + } + + /* OK the address looks correct */ + if (proto->proto_type == PROTO_TYPE_DGRAM) + bind_conf->options |= BC_O_USE_SOCK_DGRAM; + else + bind_conf->options |= BC_O_USE_SOCK_STREAM; + + if (proto->xprt_type == PROTO_TYPE_DGRAM) + bind_conf->options |= BC_O_USE_XPRT_DGRAM; + else + bind_conf->options |= BC_O_USE_XPRT_STREAM; + + if (!create_listeners(bind_conf, ss2, port, end, fd, proto, err)) { + memprintf(err, "%s for address '%s'.\n", *err, str); + goto fail; + } + } /* end while(next) */ + free(dupstr); + return 1; + fail: + free(dupstr); + return 0; +} + +/* + * converts to a list of datagram-oriented listeners which are dynamically + * allocated. + * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where : + * - can be empty or "*" to indicate INADDR_ANY ; + * - is a numerical port from 1 to 65535 ; + * - indicates to use the range from to instead (inclusive). + * This can be repeated as many times as necessary, separated by a coma. + * Function returns 1 for success or 0 if error. In case of errors, if is + * not NULL, it must be a valid pointer to either NULL or a freeable area that + * will be replaced with an error message. + */ +int str2receiver(char *str, struct proxy *curproxy, struct bind_conf *bind_conf, const char *file, int line, char **err) +{ + struct protocol *proto; + char *next, *dupstr; + int port, end; + + next = dupstr = strdup(str); + + while (next && *next) { + struct sockaddr_storage *ss2; + int fd = -1; + + str = next; + /* 1) look for the end of the first address */ + if ((next = strchr(str, ',')) != NULL) { + *next++ = 0; + } + + ss2 = str2sa_range(str, NULL, &port, &end, &fd, &proto, NULL, err, + curproxy == global.cli_fe ? NULL : global.unix_bind.prefix, + NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_PORT_RANGE | + PA_O_SOCKET_FD | PA_O_DGRAM | PA_O_XPRT); + if (!ss2) + goto fail; + + /* OK the address looks correct */ + if (!create_listeners(bind_conf, ss2, port, end, fd, proto, err)) { + memprintf(err, "%s for address '%s'.\n", *err, str); + goto fail; + } + } /* end while(next) */ + free(dupstr); + return 1; + fail: + free(dupstr); + return 0; +} + +/* + * Sends a warning if proxy does not have at least one of the + * capabilities in . An optional may be added at the end + * of the warning to help the user. Returns 1 if a warning was emitted + * or 0 if the condition is valid. + */ +int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint) +{ + char *msg; + + switch (cap) { + case PR_CAP_BE: msg = "no backend"; break; + case PR_CAP_FE: msg = "no frontend"; break; + case PR_CAP_BE|PR_CAP_FE: msg = "neither frontend nor backend"; break; + default: msg = "not enough"; break; + } + + if (!(proxy->cap & cap)) { + ha_warning("parsing [%s:%d] : '%s' ignored because %s '%s' has %s capability.%s\n", + file, line, arg, proxy_type_str(proxy), proxy->id, msg, hint ? hint : ""); + return 1; + } + return 0; +} + +/* + * Sends an alert if proxy does not have at least one of the + * capabilities in . An optional may be added at the end + * of the alert to help the user. Returns 1 if an alert was emitted + * or 0 if the condition is valid. + */ +int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint) +{ + char *msg; + + switch (cap) { + case PR_CAP_BE: msg = "no backend"; break; + case PR_CAP_FE: msg = "no frontend"; break; + case PR_CAP_BE|PR_CAP_FE: msg = "neither frontend nor backend"; break; + default: msg = "not enough"; break; + } + + if (!(proxy->cap & cap)) { + ha_alert("parsing [%s:%d] : '%s' not allowed because %s '%s' has %s capability.%s\n", + file, line, arg, proxy_type_str(proxy), proxy->id, msg, hint ? hint : ""); + return 1; + } + return 0; +} + +/* + * Report an error in when there are too many arguments. This version is + * intended to be used by keyword parsers so that the message will be included + * into the general error message. The index is the current keyword in args. + * Return 0 if the number of argument is correct, otherwise build a message and + * return 1. Fill err_code with an ERR_ALERT and an ERR_FATAL if not null. The + * message may also be null, it will simply not be produced (useful to check only). + * and are only affected on error. + */ +int too_many_args_idx(int maxarg, int index, char **args, char **msg, int *err_code) +{ + int i; + + if (!*args[index + maxarg + 1]) + return 0; + + if (msg) { + *msg = NULL; + memprintf(msg, "%s", args[0]); + for (i = 1; i <= index; i++) + memprintf(msg, "%s %s", *msg, args[i]); + + memprintf(msg, "'%s' cannot handle unexpected argument '%s'.", *msg, args[index + maxarg + 1]); + } + if (err_code) + *err_code |= ERR_ALERT | ERR_FATAL; + + return 1; +} + +/* + * same as too_many_args_idx with a 0 index + */ +int too_many_args(int maxarg, char **args, char **msg, int *err_code) +{ + return too_many_args_idx(maxarg, 0, args, msg, err_code); +} + +/* + * Report a fatal Alert when there is too much arguments + * The index is the current keyword in args + * Return 0 if the number of argument is correct, otherwise emit an alert and return 1 + * Fill err_code with an ERR_ALERT and an ERR_FATAL + */ +int alertif_too_many_args_idx(int maxarg, int index, const char *file, int linenum, char **args, int *err_code) +{ + char *kw = NULL; + int i; + + if (!*args[index + maxarg + 1]) + return 0; + + memprintf(&kw, "%s", args[0]); + for (i = 1; i <= index; i++) { + memprintf(&kw, "%s %s", kw, args[i]); + } + + ha_alert("parsing [%s:%d] : '%s' cannot handle unexpected argument '%s'.\n", file, linenum, kw, args[index + maxarg + 1]); + free(kw); + *err_code |= ERR_ALERT | ERR_FATAL; + return 1; +} + +/* + * same as alertif_too_many_args_idx with a 0 index + */ +int alertif_too_many_args(int maxarg, const char *file, int linenum, char **args, int *err_code) +{ + return alertif_too_many_args_idx(maxarg, 0, file, linenum, args, err_code); +} + + +/* Report it if a request ACL condition uses some keywords that are incompatible + * with the place where the ACL is used. It returns either 0 or ERR_WARN so that + * its result can be or'ed with err_code. Note that may be NULL and then + * will be ignored. + */ +int warnif_cond_conflicts(const struct acl_cond *cond, unsigned int where, const char *file, int line) +{ + const struct acl *acl; + const char *kw; + + if (!cond) + return 0; + + acl = acl_cond_conflicts(cond, where); + if (acl) { + if (acl->name && *acl->name) + ha_warning("parsing [%s:%d] : acl '%s' will never match because it only involves keywords that are incompatible with '%s'\n", + file, line, acl->name, sample_ckp_names(where)); + else + ha_warning("parsing [%s:%d] : anonymous acl will never match because it uses keyword '%s' which is incompatible with '%s'\n", + file, line, LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw, sample_ckp_names(where)); + return ERR_WARN; + } + if (!acl_cond_kw_conflicts(cond, where, &acl, &kw)) + return 0; + + if (acl->name && *acl->name) + ha_warning("parsing [%s:%d] : acl '%s' involves keywords '%s' which is incompatible with '%s'\n", + file, line, acl->name, kw, sample_ckp_names(where)); + else + ha_warning("parsing [%s:%d] : anonymous acl involves keyword '%s' which is incompatible with '%s'\n", + file, line, kw, sample_ckp_names(where)); + return ERR_WARN; +} + +/* Report it if an ACL uses a L6 sample fetch from an HTTP proxy. It returns + * either 0 or ERR_WARN so that its result can be or'ed with err_code. Note that + * may be NULL and then will be ignored. +*/ +int warnif_tcp_http_cond(const struct proxy *px, const struct acl_cond *cond) +{ + if (!cond || px->mode != PR_MODE_HTTP) + return 0; + + if (cond->use & (SMP_USE_L6REQ|SMP_USE_L6RES)) { + ha_warning("Proxy '%s': L6 sample fetches ignored on HTTP proxies (declared at %s:%d).\n", + px->id, cond->file, cond->line); + return ERR_WARN; + } + return 0; +} + +/* try to find in the word that looks closest to by counting + * transitions between letters, digits and other characters. Will return the + * best matching word if found, otherwise NULL. An optional array of extra + * words to compare may be passed in , but it must then be terminated + * by a NULL entry. If unused it may be NULL. + */ +const char *cfg_find_best_match(const char *word, const struct list *list, int section, const char **extra) +{ + uint8_t word_sig[1024]; // 0..25=letter, 26=digit, 27=other, 28=begin, 29=end + uint8_t list_sig[1024]; + const struct cfg_kw_list *kwl; + int index; + const char *best_ptr = NULL; + int dist, best_dist = INT_MAX; + + make_word_fingerprint(word_sig, word); + list_for_each_entry(kwl, list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if (kwl->kw[index].section != section) + continue; + + make_word_fingerprint(list_sig, kwl->kw[index].kw); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_ptr = kwl->kw[index].kw; + } + } + } + + while (extra && *extra) { + make_word_fingerprint(list_sig, *extra); + dist = word_fingerprint_distance(word_sig, list_sig); + if (dist < best_dist) { + best_dist = dist; + best_ptr = *extra; + } + extra++; + } + + if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr))) + best_ptr = NULL; + return best_ptr; +} + +/* Parse a string representing a process number or a set of processes. It must + * be "all", "odd", "even", a number between 1 and or a range with + * two such numbers delimited by a dash ('-'). On success, it returns + * 0. otherwise it returns 1 with an error message in . + * + * Note: this function can also be used to parse a thread number or a set of + * threads. + */ +int parse_process_number(const char *arg, unsigned long *proc, int max, int *autoinc, char **err) +{ + if (autoinc) { + *autoinc = 0; + if (strncmp(arg, "auto:", 5) == 0) { + arg += 5; + *autoinc = 1; + } + } + + if (strcmp(arg, "all") == 0) + *proc |= ~0UL; + else if (strcmp(arg, "odd") == 0) + *proc |= ~0UL/3UL; /* 0x555....555 */ + else if (strcmp(arg, "even") == 0) + *proc |= (~0UL/3UL) << 1; /* 0xAAA...AAA */ + else { + const char *p, *dash = NULL; + unsigned int low, high; + + for (p = arg; *p; p++) { + if (*p == '-' && !dash) + dash = p; + else if (!isdigit((unsigned char)*p)) { + memprintf(err, "'%s' is not a valid number/range.", arg); + return -1; + } + } + + low = high = str2uic(arg); + if (dash) + high = ((!*(dash+1)) ? max : str2uic(dash + 1)); + + if (high < low) { + unsigned int swap = low; + low = high; + high = swap; + } + + if (low < 1 || low > max || high > max) { + memprintf(err, "'%s' is not a valid number/range." + " It supports numbers from 1 to %d.\n", + arg, max); + return 1; + } + + for (;low <= high; low++) + *proc |= 1UL << (low-1); + } + *proc &= ~0UL >> (LONGBITS - max); + + return 0; +} + +/* Allocate and initialize the frontend of a "peers" section found in + * file at line with as ID. + * Return 0 if succeeded, -1 if not. + * Note that this function may be called from "default-server" + * or "peer" lines. + */ +static int init_peers_frontend(const char *file, int linenum, + const char *id, struct peers *peers) +{ + struct proxy *p; + + if (peers->peers_fe) { + p = peers->peers_fe; + goto out; + } + + p = calloc(1, sizeof *p); + if (!p) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + + init_new_proxy(p); + peers_setup_frontend(p); + p->parent = peers; + /* Finally store this frontend. */ + peers->peers_fe = p; + + out: + if (id && !p->id) + p->id = strdup(id); + free(p->conf.file); + p->conf.args.file = p->conf.file = strdup(file); + if (linenum != -1) + p->conf.args.line = p->conf.line = linenum; + + return 0; +} + +/* Only change ->file, ->line and ->arg struct bind_conf member values + * if already present. + */ +static struct bind_conf *bind_conf_uniq_alloc(struct proxy *p, + const char *file, int line, + const char *arg, struct xprt_ops *xprt) +{ + struct bind_conf *bind_conf; + + if (!LIST_ISEMPTY(&p->conf.bind)) { + bind_conf = LIST_ELEM((&p->conf.bind)->n, typeof(bind_conf), by_fe); + /* + * We keep bind_conf->file and bind_conf->line unchanged + * to make them available for error messages + */ + if (arg) { + free(bind_conf->arg); + bind_conf->arg = strdup(arg); + } + } + else { + bind_conf = bind_conf_alloc(p, file, line, arg, xprt); + } + + return bind_conf; +} + +/* + * Allocate a new struct peer parsed at line in file + * to be added to . + * Returns the new allocated structure if succeeded, NULL if not. + */ +static struct peer *cfg_peers_add_peer(struct peers *peers, + const char *file, int linenum, + const char *id, int local) +{ + struct peer *p; + + p = calloc(1, sizeof *p); + if (!p) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return NULL; + } + + /* the peers are linked backwards first */ + peers->count++; + p->peers = peers; + p->next = peers->remote; + peers->remote = p; + p->conf.file = strdup(file); + p->conf.line = linenum; + p->last_change = ns_to_sec(now_ns); + p->xprt = xprt_get(XPRT_RAW); + p->sock_init_arg = NULL; + HA_SPIN_INIT(&p->lock); + if (id) + p->id = strdup(id); + if (local) { + p->local = 1; + peers->local = p; + } + + return p; +} + +/* + * Parse a line in a , or section. + * Returns the error code, 0 if OK, or any combination of : + * - ERR_ABORT: must abort ASAP + * - ERR_FATAL: we can continue parsing but not start the service + * - ERR_WARN: a warning has been emitted + * - ERR_ALERT: an alert has been emitted + * Only the two first ones can stop processing, the two others are just + * indicators. + */ +int cfg_parse_peers(const char *file, int linenum, char **args, int kwm) +{ + static struct peers *curpeers = NULL; + static int nb_shards = 0; + struct peer *newpeer = NULL; + const char *err; + struct bind_conf *bind_conf; + int err_code = 0; + char *errmsg = NULL; + static int bind_line, peer_line; + + if (strcmp(args[0], "bind") == 0 || strcmp(args[0], "default-bind") == 0) { + int cur_arg; + struct bind_conf *bind_conf; + int ret; + + cur_arg = 1; + + if (init_peers_frontend(file, linenum, NULL, curpeers) != 0) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + bind_conf = bind_conf_uniq_alloc(curpeers->peers_fe, file, linenum, + args[1], xprt_get(XPRT_RAW)); + if (!bind_conf) { + ha_alert("parsing [%s:%d] : '%s %s' : cannot allocate memory.\n", file, linenum, args[0], args[1]); + err_code |= ERR_FATAL; + goto out; + } + + bind_conf->maxaccept = 1; + bind_conf->accept = session_accept_fd; + bind_conf->options |= BC_O_UNLIMITED; /* don't make the peers subject to global limits */ + + if (*args[0] == 'b') { + struct listener *l; + + if (peer_line) { + ha_alert("parsing [%s:%d] : mixing \"peer\" and \"bind\" line is forbidden\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (!LIST_ISEMPTY(&bind_conf->listeners)) { + ha_alert("parsing [%s:%d] : One listener per \"peers\" section is authorized but another is already configured at [%s:%d].\n", file, linenum, bind_conf->file, bind_conf->line); + err_code |= ERR_FATAL; + } + + if (!str2listener(args[1], curpeers->peers_fe, bind_conf, file, linenum, &errmsg)) { + if (errmsg && *errmsg) { + indent_msg(&errmsg, 2); + ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); + } + else + ha_alert("parsing [%s:%d] : '%s %s' : error encountered while parsing listening address %s.\n", + file, linenum, args[0], args[1], args[1]); + err_code |= ERR_FATAL; + goto out; + } + + /* Only one listener supported. Compare first listener + * against the last one. It must be the same one. + */ + if (bind_conf->listeners.n != bind_conf->listeners.p) { + ha_alert("parsing [%s:%d] : Only one listener per \"peers\" section is authorized. Multiple listening addresses or port range are not supported.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + /* + * Newly allocated listener is at the end of the list + */ + l = LIST_ELEM(bind_conf->listeners.p, typeof(l), by_bind); + + global.maxsock++; /* for the listening socket */ + + bind_line = 1; + if (cfg_peers->local) { + newpeer = cfg_peers->local; + } + else { + /* This peer is local. + * Note that we do not set the peer ID. This latter is initialized + * when parsing "peer" or "server" line. + */ + newpeer = cfg_peers_add_peer(curpeers, file, linenum, NULL, 1); + if (!newpeer) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + } + newpeer->addr = l->rx.addr; + newpeer->proto = l->rx.proto; + cur_arg++; + } + + ret = bind_parse_args_list(bind_conf, args, cur_arg, cursection, file, linenum); + err_code |= ret; + if (ret != 0) + goto out; + } + else if (strcmp(args[0], "default-server") == 0) { + if (init_peers_frontend(file, -1, NULL, curpeers) != 0) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + err_code |= parse_server(file, linenum, args, curpeers->peers_fe, NULL, + SRV_PARSE_DEFAULT_SERVER|SRV_PARSE_IN_PEER_SECTION|SRV_PARSE_INITIAL_RESOLVE); + } + else if (strcmp(args[0], "log") == 0) { + if (init_peers_frontend(file, linenum, NULL, curpeers) != 0) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + if (!parse_logger(args, &curpeers->peers_fe->loggers, (kwm == KWM_NO), file, linenum, &errmsg)) { + ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } + else if (strcmp(args[0], "peers") == 0) { /* new peers section */ + /* Initialize these static variables when entering a new "peers" section*/ + bind_line = peer_line = 0; + if (!*args[1]) { + ha_alert("parsing [%s:%d] : missing name for peers section.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + if (alertif_too_many_args(1, file, linenum, args, &err_code)) + goto out; + + err = invalid_char(args[1]); + if (err) { + ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n", + file, linenum, *err, args[0], args[1]); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + for (curpeers = cfg_peers; curpeers != NULL; curpeers = curpeers->next) { + /* + * If there are two proxies with the same name only following + * combinations are allowed: + */ + if (strcmp(curpeers->id, args[1]) == 0) { + ha_alert("Parsing [%s:%d]: peers section '%s' has the same name as another peers section declared at %s:%d.\n", + file, linenum, args[1], curpeers->conf.file, curpeers->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + } + } + + if ((curpeers = calloc(1, sizeof(*curpeers))) == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + curpeers->next = cfg_peers; + cfg_peers = curpeers; + curpeers->conf.file = strdup(file); + curpeers->conf.line = linenum; + curpeers->last_change = ns_to_sec(now_ns); + curpeers->id = strdup(args[1]); + curpeers->disabled = 0; + } + else if (strcmp(args[0], "peer") == 0 || + strcmp(args[0], "server") == 0) { /* peer or server definition */ + int local_peer, peer; + int parse_addr = 0; + + peer = *args[0] == 'p'; + local_peer = strcmp(args[1], localpeer) == 0; + /* The local peer may have already partially been parsed on a "bind" line. */ + if (*args[0] == 'p') { + if (bind_line) { + ha_alert("parsing [%s:%d] : mixing \"peer\" and \"bind\" line is forbidden\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + peer_line = 1; + } + if (cfg_peers->local && !cfg_peers->local->id && local_peer) { + /* The local peer has already been initialized on a "bind" line. + * Let's use it and store its ID. + */ + newpeer = cfg_peers->local; + newpeer->id = strdup(localpeer); + } + else { + if (local_peer && cfg_peers->local) { + ha_alert("parsing [%s:%d] : '%s %s' : local peer name already referenced at %s:%d. %s\n", + file, linenum, args[0], args[1], + curpeers->peers_fe->conf.file, curpeers->peers_fe->conf.line, cfg_peers->local->id); + err_code |= ERR_FATAL; + goto out; + } + newpeer = cfg_peers_add_peer(curpeers, file, linenum, args[1], local_peer); + if (!newpeer) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + } + + /* Line number and peer ID are updated only if this peer is the local one. */ + if (init_peers_frontend(file, + newpeer->local ? linenum: -1, + newpeer->local ? newpeer->id : NULL, + curpeers) != 0) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + /* This initializes curpeer->peers->peers_fe->srv. + * The server address is parsed only if we are parsing a "peer" line, + * or if we are parsing a "server" line and the current peer is not the local one. + */ + parse_addr = (peer || !local_peer) ? SRV_PARSE_PARSE_ADDR : 0; + err_code |= parse_server(file, linenum, args, curpeers->peers_fe, NULL, + SRV_PARSE_IN_PEER_SECTION|parse_addr|SRV_PARSE_INITIAL_RESOLVE); + if (!curpeers->peers_fe->srv) { + /* Remove the newly allocated peer. */ + if (newpeer != curpeers->local) { + struct peer *p; + + p = curpeers->remote; + curpeers->remote = curpeers->remote->next; + free(p->id); + free(p); + } + goto out; + } + + if (nb_shards && curpeers->peers_fe->srv->shard > nb_shards) { + ha_warning("parsing [%s:%d] : '%s %s' : %d peer shard greater value than %d shards value is ignored.\n", + file, linenum, args[0], args[1], curpeers->peers_fe->srv->shard, nb_shards); + curpeers->peers_fe->srv->shard = 0; + err_code |= ERR_WARN; + } + + if (curpeers->peers_fe->srv->init_addr_methods || curpeers->peers_fe->srv->resolvers_id || + curpeers->peers_fe->srv->do_check || curpeers->peers_fe->srv->do_agent) { + ha_warning("parsing [%s:%d] : '%s %s' : init_addr, resolvers, check and agent are ignored for peers.\n", file, linenum, args[0], args[1]); + err_code |= ERR_WARN; + } + + /* If the peer address has just been parsed, let's copy it to + * and initializes ->proto. + */ + if (peer || !local_peer) { + newpeer->addr = curpeers->peers_fe->srv->addr; + newpeer->proto = protocol_lookup(newpeer->addr.ss_family, PROTO_TYPE_STREAM, 0); + } + + newpeer->xprt = xprt_get(XPRT_RAW); + newpeer->sock_init_arg = NULL; + HA_SPIN_INIT(&newpeer->lock); + + newpeer->srv = curpeers->peers_fe->srv; + if (!newpeer->local) + goto out; + + /* The lines above are reserved to "peer" lines. */ + if (*args[0] == 's') + goto out; + + bind_conf = bind_conf_uniq_alloc(curpeers->peers_fe, file, linenum, args[2], xprt_get(XPRT_RAW)); + if (!bind_conf) { + ha_alert("parsing [%s:%d] : '%s %s' : Cannot allocate memory.\n", file, linenum, args[0], args[1]); + err_code |= ERR_FATAL; + goto out; + } + + bind_conf->maxaccept = 1; + bind_conf->accept = session_accept_fd; + bind_conf->options |= BC_O_UNLIMITED; /* don't make the peers subject to global limits */ + + if (!LIST_ISEMPTY(&bind_conf->listeners)) { + ha_alert("parsing [%s:%d] : One listener per \"peers\" section is authorized but another is already configured at [%s:%d].\n", file, linenum, bind_conf->file, bind_conf->line); + err_code |= ERR_FATAL; + } + + if (!str2listener(args[2], curpeers->peers_fe, bind_conf, file, linenum, &errmsg)) { + if (errmsg && *errmsg) { + indent_msg(&errmsg, 2); + ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); + } + else + ha_alert("parsing [%s:%d] : '%s %s' : error encountered while parsing listening address %s.\n", + file, linenum, args[0], args[1], args[2]); + err_code |= ERR_FATAL; + goto out; + } + + global.maxsock++; /* for the listening socket */ + } + else if (strcmp(args[0], "shards") == 0) { + char *endptr; + + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' : missing value\n", file, linenum, args[0]); + err_code |= ERR_FATAL; + goto out; + } + + curpeers->nb_shards = strtol(args[1], &endptr, 10); + if (*endptr != '\0') { + ha_alert("parsing [%s:%d] : '%s' : expects an integer argument, found '%s'\n", + file, linenum, args[0], args[1]); + err_code |= ERR_FATAL; + goto out; + } + + if (!curpeers->nb_shards) { + ha_alert("parsing [%s:%d] : '%s' : expects a strictly positive integer argument\n", + file, linenum, args[0]); + err_code |= ERR_FATAL; + goto out; + } + + nb_shards = curpeers->nb_shards; + } + else if (strcmp(args[0], "table") == 0) { + struct stktable *t, *other; + char *id; + size_t prefix_len; + + /* Line number and peer ID are updated only if this peer is the local one. */ + if (init_peers_frontend(file, -1, NULL, curpeers) != 0) { + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + /* Build the stick-table name, concatenating the "peers" section name + * followed by a '/' character and the table name argument. + */ + chunk_reset(&trash); + if (!chunk_strcpy(&trash, curpeers->id)) { + ha_alert("parsing [%s:%d]: '%s %s' : stick-table name too long.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + prefix_len = trash.data; + if (!chunk_memcat(&trash, "/", 1) || !chunk_strcat(&trash, args[1])) { + ha_alert("parsing [%s:%d]: '%s %s' : stick-table name too long.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + t = calloc(1, sizeof *t); + id = strdup(trash.area); + if (!t || !id) { + ha_alert("parsing [%s:%d]: '%s %s' : memory allocation failed\n", + file, linenum, args[0], args[1]); + free(t); + free(id); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + other = stktable_find_by_name(trash.area); + if (other) { + ha_alert("parsing [%s:%d] : stick-table name '%s' conflicts with table declared in %s '%s' at %s:%d.\n", + file, linenum, args[1], + other->proxy ? proxy_cap_str(other->proxy->cap) : "peers", + other->proxy ? other->id : other->peers.p->id, + other->conf.file, other->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + + err_code |= parse_stick_table(file, linenum, args, t, id, id + prefix_len, curpeers); + if (err_code & ERR_FATAL) { + free(t); + free(id); + goto out; + } + + stktable_store_name(t); + t->next = stktables_list; + stktables_list = t; + } + else if (strcmp(args[0], "disabled") == 0) { /* disables this peers section */ + curpeers->disabled |= PR_FL_DISABLED; + } + else if (strcmp(args[0], "enabled") == 0) { /* enables this peers section (used to revert a disabled default) */ + curpeers->disabled = 0; + } + else if (*args[0] != 0) { + struct peers_kw_list *pkwl; + int index; + int rc = -1; + + list_for_each_entry(pkwl, &peers_keywords.list, list) { + for (index = 0; pkwl->kw[index].kw != NULL; index++) { + if (strcmp(pkwl->kw[index].kw, args[0]) == 0) { + rc = pkwl->kw[index].parse(args, curpeers, file, linenum, &errmsg); + if (rc < 0) { + ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + else if (rc > 0) { + ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg); + err_code |= ERR_WARN; + goto out; + } + goto out; + } + } + } + + ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + +out: + free(errmsg); + return err_code; +} + +/* + * Parse a line in a , or section. + * Returns the error code, 0 if OK, or any combination of : + * - ERR_ABORT: must abort ASAP + * - ERR_FATAL: we can continue parsing but not start the service + * - ERR_WARN: a warning has been emitted + * - ERR_ALERT: an alert has been emitted + * Only the two first ones can stop processing, the two others are just + * indicators. + */ +int cfg_parse_mailers(const char *file, int linenum, char **args, int kwm) +{ + static struct mailers *curmailers = NULL; + struct mailer *newmailer = NULL; + const char *err; + int err_code = 0; + char *errmsg = NULL; + + if (strcmp(args[0], "mailers") == 0) { /* new mailers section */ + if (!*args[1]) { + ha_alert("parsing [%s:%d] : missing name for mailers section.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + err = invalid_char(args[1]); + if (err) { + ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n", + file, linenum, *err, args[0], args[1]); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + for (curmailers = mailers; curmailers != NULL; curmailers = curmailers->next) { + /* + * If there are two proxies with the same name only following + * combinations are allowed: + */ + if (strcmp(curmailers->id, args[1]) == 0) { + ha_alert("Parsing [%s:%d]: mailers section '%s' has the same name as another mailers section declared at %s:%d.\n", + file, linenum, args[1], curmailers->conf.file, curmailers->conf.line); + err_code |= ERR_ALERT | ERR_FATAL; + } + } + + if ((curmailers = calloc(1, sizeof(*curmailers))) == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + curmailers->next = mailers; + mailers = curmailers; + curmailers->conf.file = strdup(file); + curmailers->conf.line = linenum; + curmailers->id = strdup(args[1]); + curmailers->timeout.mail = DEF_MAILALERTTIME;/* XXX: Would like to Skip to the next alert, if any, ASAP. + * But need enough time so that timeouts don't occur + * during tcp procssing. For now just us an arbitrary default. */ + } + else if (strcmp(args[0], "mailer") == 0) { /* mailer definition */ + struct sockaddr_storage *sk; + int port1, port2; + struct protocol *proto; + + if (!*args[2]) { + ha_alert("parsing [%s:%d] : '%s' expects and [:] as arguments.\n", + file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + err = invalid_char(args[1]); + if (err) { + ha_alert("parsing [%s:%d] : character '%c' is not permitted in server name '%s'.\n", + file, linenum, *err, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if ((newmailer = calloc(1, sizeof(*newmailer))) == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + /* the mailers are linked backwards first */ + curmailers->count++; + newmailer->next = curmailers->mailer_list; + curmailers->mailer_list = newmailer; + newmailer->mailers = curmailers; + newmailer->conf.file = strdup(file); + newmailer->conf.line = linenum; + + newmailer->id = strdup(args[1]); + + sk = str2sa_range(args[2], NULL, &port1, &port2, NULL, &proto, NULL, + &errmsg, NULL, NULL, + PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_STREAM | PA_O_XPRT | PA_O_CONNECT); + if (!sk) { + ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (proto->sock_prot != IPPROTO_TCP) { + ha_alert("parsing [%s:%d] : '%s %s' : TCP not supported for this address family.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + newmailer->addr = *sk; + newmailer->proto = proto; + newmailer->xprt = xprt_get(XPRT_RAW); + newmailer->sock_init_arg = NULL; + } + else if (strcmp(args[0], "timeout") == 0) { + if (!*args[1]) { + ha_alert("parsing [%s:%d] : '%s' expects 'mail' and