From: heil Date: Thu, 11 Jun 2015 17:03:14 +0000 (+0200) Subject: haproxy: add patches from upstream X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=d5c18252d4c4fe01ff582c17ce62721307c9cd26;p=feed%2Fpackages.git haproxy: add patches from upstream - [PATCH 1/2] BUG/MEDIUM: stats: properly initialize the scope before - [PATCH 2/2] BUG/MEDIUM: http: don't forward client shutdown without - [PATCH 3/8] BUG/MINOR: check: fix tcpcheck error message - [PATCH 4/8] CLEANUP: checks: fix double usage of cur / current_step - [PATCH 5/8] BUG/MEDIUM: checks: do not dereference head of a - [PATCH 6/8] CLEANUP: checks: simplify the loop processing of - [PATCH 7/8] BUG/MAJOR: checks: always check for end of list before - [PATCH 8/8] BUG/MEDIUM: checks: do not dereference a list as a - [PATCH 09/10] BUG/MEDIUM: peers: apply a random reconnection timeout - [PATCH 10/10] DOC: Update doc about weight, act and bck fields in the - [PATCH 11/14] MINOR: ssl: add a destructor to free allocated SSL - [PATCH 12/14] BUG/MEDIUM: ssl: fix tune.ssl.default-dh-param value - [PATCH 13/14] BUG/MINOR: cfgparse: fix typo in 'option httplog' error - [PATCH 14/14] BUG/MEDIUM: cfgparse: segfault when userlist is misused Signed-off-by: heil --- diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile index e4de4d40b3..0e6fbd505d 100644 --- a/net/haproxy/Makefile +++ b/net/haproxy/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=haproxy PKG_VERSION:=1.5.12 -PKG_RELEASE:=01 +PKG_RELEASE:=14 PKG_SOURCE:=haproxy-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=http://haproxy.1wt.eu/download/1.5/src/ PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT)/$(PKG_NAME)-$(PKG_VERSION) diff --git a/net/haproxy/patches/0001-BUG-MEDIUM-stats-properly-initialize-the-scope-befor.patch b/net/haproxy/patches/0001-BUG-MEDIUM-stats-properly-initialize-the-scope-befor.patch new file mode 100644 index 0000000000..4ae6c9a844 --- /dev/null +++ b/net/haproxy/patches/0001-BUG-MEDIUM-stats-properly-initialize-the-scope-befor.patch @@ -0,0 +1,32 @@ +From 0aa5899911bbc765ba16ce52a80fa76230781779 Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Mon, 4 May 2015 18:07:56 +0200 +Subject: [PATCH 1/2] BUG/MEDIUM: stats: properly initialize the scope before + dumping stats + +Issuing a "show sess all" prior to a "show stat" on the CLI results in no +proxy being dumped because the scope_len union member was not properly +reinitialized. + +This fix must be backported into 1.5. +(cherry picked from commit 6bcb95da5b9cb143088102b460c7bcb37c1b3d81) +--- + src/dumpstats.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/dumpstats.c b/src/dumpstats.c +index b616478..ca084ac 100644 +--- a/src/dumpstats.c ++++ b/src/dumpstats.c +@@ -1109,6 +1109,8 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) + arg++; + } + ++ appctx->ctx.stats.scope_str = 0; ++ appctx->ctx.stats.scope_len = 0; + appctx->ctx.stats.flags = 0; + if (strcmp(args[0], "show") == 0) { + if (strcmp(args[1], "stat") == 0) { +-- +2.0.5 + diff --git a/net/haproxy/patches/0002-BUG-MEDIUM-http-don-t-forward-client-shutdown-withou.patch b/net/haproxy/patches/0002-BUG-MEDIUM-http-don-t-forward-client-shutdown-withou.patch new file mode 100644 index 0000000000..3875b88794 --- /dev/null +++ b/net/haproxy/patches/0002-BUG-MEDIUM-http-don-t-forward-client-shutdown-withou.patch @@ -0,0 +1,82 @@ +From 294e4676a3b775a7accb50eb8428f293c218b5e2 Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Mon, 11 May 2015 18:30:33 +0200 +Subject: [PATCH 2/2] BUG/MEDIUM: http: don't forward client shutdown without + NOLINGER except for tunnels + +There's an issue related with shutting down POST transfers or closing the +connection after the end of the upload : the shutdown is forwarded to the +server regardless of the abortonclose option. The problem it causes is that +during a scan, brute force or whatever, it becomes possible that all source +ports are exhausted with all sockets in TIME_WAIT state. + +There are multiple issues at once in fact : + - no action is done for the close, it automatically happens at the lower + layers thanks for channel_auto_close(), so we cannot act on NOLINGER ; + + - we *do* want to continue to send a clean shutdown in tunnel mode because + some protocols transported over HTTP may need this, regardless of option + abortonclose, thus we can't set the option inconditionally + + - for all other modes, we do want to close the dirty way because we're + certain whether we've sent everything or not, and we don't want to eat + all source ports. + +The solution is a bit complex and applies to DONE/TUNNEL states : + + 1) disable automatic close for everything not a tunnel and not just + keep-alive / server-close. Force-close is now covered, as is HTTP/1.0 + which implicitly works in force-close mode ; + + 2) when processing option abortonclose, we know we can disable lingering + if the client has closed and the connection is not in tunnel mode. + +Since the last case above leads to a situation where the client side reports +an error, we know the connection will not be reused, so leaving the flag on +the stream-interface is safe. A client closing in the middle of the data +transmission already aborts the transaction so this case is not a problem. + +This fix must be backported to 1.5 where the problem was detected. +(cherry picked from commit bbfb6c40854925367ae5f9e8b22c5c9a18dc69d5) +--- + src/proto_http.c | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +diff --git a/src/proto_http.c b/src/proto_http.c +index 0ac3a47..5db64b5 100644 +--- a/src/proto_http.c ++++ b/src/proto_http.c +@@ -5452,9 +5452,10 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit + msg->sov -= msg->next; + msg->next = 0; + +- /* for keep-alive we don't want to forward closes on DONE */ +- if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || +- (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) ++ /* we don't want to forward closes on DONE except in ++ * tunnel mode. ++ */ ++ if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) + channel_dont_close(req); + if (http_resync_states(s)) { + /* some state changes occurred, maybe the analyser +@@ -5478,10 +5479,15 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit + * want to monitor the client's connection and forward + * any shutdown notification to the server, which will + * decide whether to close or to go on processing the +- * request. ++ * request. We only do that in tunnel mode, and not in ++ * other modes since it can be abused to exhaust source ++ * ports. + */ + if (s->be->options & PR_O_ABRT_CLOSE) { + channel_auto_read(req); ++ if ((req->flags & (CF_SHUTR|CF_READ_NULL)) && ++ ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN)) ++ s->si[1].flags |= SI_FL_NOLINGER; + channel_auto_close(req); + } + else if (s->txn.meth == HTTP_METH_POST) { +-- +2.0.5 + diff --git a/net/haproxy/patches/0003-BUG-MINOR-check-fix-tcpcheck-error-message.patch b/net/haproxy/patches/0003-BUG-MINOR-check-fix-tcpcheck-error-message.patch new file mode 100644 index 0000000000..9bb33119e6 --- /dev/null +++ b/net/haproxy/patches/0003-BUG-MINOR-check-fix-tcpcheck-error-message.patch @@ -0,0 +1,28 @@ +From 68e4fc2b9910dd090c5e729203b72444f75aaa75 Mon Sep 17 00:00:00 2001 +From: Baptiste Assmann +Date: Fri, 1 May 2015 08:09:29 +0200 +Subject: [PATCH 3/8] BUG/MINOR: check: fix tcpcheck error message + +add the keyword 'string' when required (error in a tcpcheck expect +string) +(cherry picked from commit 96a5c9b57738c05ecce7822093b9c4118123dc1e) +--- + src/checks.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/checks.c b/src/checks.c +index 71debb6..8b53f97 100644 +--- a/src/checks.c ++++ b/src/checks.c +@@ -614,7 +614,7 @@ static void chk_report_conn_err(struct connection *conn, int errno_bck, int expi + } + else if (check->last_started_step && check->last_started_step->action == TCPCHK_ACT_EXPECT) { + if (check->last_started_step->string) +- chunk_appendf(chk, " (string '%s')", check->last_started_step->string); ++ chunk_appendf(chk, " (expect string '%s')", check->last_started_step->string); + else if (check->last_started_step->expect_regex) + chunk_appendf(chk, " (expect regex)"); + } +-- +2.0.5 + diff --git a/net/haproxy/patches/0004-CLEANUP-checks-fix-double-usage-of-cur-current_step-.patch b/net/haproxy/patches/0004-CLEANUP-checks-fix-double-usage-of-cur-current_step-.patch new file mode 100644 index 0000000000..6acba4e87d --- /dev/null +++ b/net/haproxy/patches/0004-CLEANUP-checks-fix-double-usage-of-cur-current_step-.patch @@ -0,0 +1,178 @@ +From 4f889006269e4d3f802de46f280ed198a15e3a69 Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Wed, 13 May 2015 11:23:01 +0200 +Subject: [PATCH 4/8] CLEANUP: checks: fix double usage of cur / current_step + in tcp-checks + +This cleanup is a preliminary requirement to the upcoming fixes for +the bug that affect tcp-check's improper use of lists. It will have +to be backported to 1.5 though it will not easily apply. + +There are two variables pointing to the current rule within the loop, +and either one or the other is used depending on the code blocks, +making it much harder to apply checks to fix the list walking bug. +So first get rid of "cur" and only focus on current_step. +(cherry picked from commit ce8c42a37a44a1e0cb94e81abb7cc2baf3d0ef80) + +[wt: 1.5 doesn't have comments so this patch differs significantly + from 1.6, but it's needed for the next batch of fixes] +--- + src/checks.c | 57 ++++++++++++++++++++++++++++----------------------------- + 1 file changed, 28 insertions(+), 29 deletions(-) + +diff --git a/src/checks.c b/src/checks.c +index 8b53f97..cfdfe8c 100644 +--- a/src/checks.c ++++ b/src/checks.c +@@ -1859,7 +1859,7 @@ static int tcpcheck_get_step_id(struct server *s) + static void tcpcheck_main(struct connection *conn) + { + char *contentptr; +- struct tcpcheck_rule *cur, *next; ++ struct tcpcheck_rule *next; + int done = 0, ret = 0; + struct check *check = conn->owner; + struct server *s = check->server; +@@ -1916,15 +1916,11 @@ static void tcpcheck_main(struct connection *conn) + check->bo->o = 0; + check->bi->p = check->bi->data; + check->bi->i = 0; +- cur = check->current_step = LIST_ELEM(head->n, struct tcpcheck_rule *, list); ++ check->current_step = LIST_ELEM(head->n, struct tcpcheck_rule *, list); + t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter)); + if (s->proxy->timeout.check) + t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check); + } +- /* keep on processing step */ +- else { +- cur = check->current_step; +- } + + /* It's only the rules which will enable send/recv */ + __conn_data_stop_both(conn); +@@ -1934,7 +1930,7 @@ static void tcpcheck_main(struct connection *conn) + * or if we're about to send a string that does not fit in the remaining space. + */ + if (check->bo->o && +- (&cur->list == head || ++ (&check->current_step->list == head || + check->current_step->action != TCPCHK_ACT_SEND || + check->current_step->string_len >= buffer_total_space(check->bo))) { + +@@ -1949,14 +1945,17 @@ static void tcpcheck_main(struct connection *conn) + } + + /* did we reach the end ? If so, let's check that everything was sent */ +- if (&cur->list == head) { ++ if (&check->current_step->list == head) { + if (check->bo->o) + goto out_need_io; + break; + } + +- /* have 'next' point to the next rule or NULL if we're on the last one */ +- next = (struct tcpcheck_rule *)cur->list.n; ++ /* have 'next' point to the next rule or NULL if we're on the ++ * last one, connect() needs this. ++ */ ++ next = (struct tcpcheck_rule *)check->current_step->list.n; ++ + if (&next->list == head) + next = NULL; + +@@ -2058,8 +2057,7 @@ static void tcpcheck_main(struct connection *conn) + } + + /* allow next rule */ +- cur = (struct tcpcheck_rule *)cur->list.n; +- check->current_step = cur; ++ check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; + + /* don't do anything until the connection is established */ + if (!(conn->flags & CO_FL_CONNECTED)) { +@@ -2113,8 +2111,7 @@ static void tcpcheck_main(struct connection *conn) + *check->bo->p = '\0'; /* to make gdb output easier to read */ + + /* go to next rule and try to send */ +- cur = (struct tcpcheck_rule *)cur->list.n; +- check->current_step = cur; ++ check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; + } /* end 'send' */ + else if (check->current_step->action == TCPCHK_ACT_EXPECT) { + if (unlikely(check->result == CHK_RES_FAILED)) +@@ -2167,14 +2164,14 @@ static void tcpcheck_main(struct connection *conn) + goto out_end_tcpcheck; + } + +- if (!done && (cur->string != NULL) && (check->bi->i < cur->string_len) ) ++ if (!done && (check->current_step->string != NULL) && (check->bi->i < check->current_step->string_len) ) + continue; /* try to read more */ + + tcpcheck_expect: +- if (cur->string != NULL) +- ret = my_memmem(contentptr, check->bi->i, cur->string, cur->string_len) != NULL; +- else if (cur->expect_regex != NULL) +- ret = regex_exec(cur->expect_regex, contentptr); ++ if (check->current_step->string != NULL) ++ ret = my_memmem(contentptr, check->bi->i, check->current_step->string, check->current_step->string_len) != NULL; ++ else if (check->current_step->expect_regex != NULL) ++ ret = regex_exec(check->current_step->expect_regex, contentptr); + + if (!ret && !done) + continue; /* try to read more */ +@@ -2182,11 +2179,11 @@ static void tcpcheck_main(struct connection *conn) + /* matched */ + if (ret) { + /* matched but we did not want to => ERROR */ +- if (cur->inverse) { ++ if (check->current_step->inverse) { + /* we were looking for a string */ +- if (cur->string != NULL) { ++ if (check->current_step->string != NULL) { + chunk_printf(&trash, "TCPCHK matched unwanted content '%s' at step %d", +- cur->string, tcpcheck_get_step_id(s)); ++ check->current_step->string, tcpcheck_get_step_id(s)); + } + else { + /* we were looking for a regex */ +@@ -2198,8 +2195,9 @@ static void tcpcheck_main(struct connection *conn) + } + /* matched and was supposed to => OK, next step */ + else { +- cur = (struct tcpcheck_rule*)cur->list.n; +- check->current_step = cur; ++ /* allow next rule */ ++ check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ + if (check->current_step->action == TCPCHK_ACT_EXPECT) + goto tcpcheck_expect; + __conn_data_stop_recv(conn); +@@ -2208,9 +2206,10 @@ static void tcpcheck_main(struct connection *conn) + else { + /* not matched */ + /* not matched and was not supposed to => OK, next step */ +- if (cur->inverse) { +- cur = (struct tcpcheck_rule*)cur->list.n; +- check->current_step = cur; ++ if (check->current_step->inverse) { ++ /* allow next rule */ ++ check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ + if (check->current_step->action == TCPCHK_ACT_EXPECT) + goto tcpcheck_expect; + __conn_data_stop_recv(conn); +@@ -2218,9 +2217,9 @@ static void tcpcheck_main(struct connection *conn) + /* not matched but was supposed to => ERROR */ + else { + /* we were looking for a string */ +- if (cur->string != NULL) { ++ if (check->current_step->string != NULL) { + chunk_printf(&trash, "TCPCHK did not match content '%s' at step %d", +- cur->string, tcpcheck_get_step_id(s)); ++ check->current_step->string, tcpcheck_get_step_id(s)); + } + else { + /* we were looking for a regex */ +-- +2.0.5 + diff --git a/net/haproxy/patches/0005-BUG-MEDIUM-checks-do-not-dereference-head-of-a-tcp-c.patch b/net/haproxy/patches/0005-BUG-MEDIUM-checks-do-not-dereference-head-of-a-tcp-c.patch new file mode 100644 index 0000000000..f4bc683151 --- /dev/null +++ b/net/haproxy/patches/0005-BUG-MEDIUM-checks-do-not-dereference-head-of-a-tcp-c.patch @@ -0,0 +1,53 @@ +From b94a6d5a37499ce6649ad58f4a8c4664779abd8b Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Wed, 13 May 2015 11:38:17 +0200 +Subject: [PATCH 5/8] BUG/MEDIUM: checks: do not dereference head of a + tcp-check at the end + +When the end of the list is reached, the current step's action is checked +to know if we must poll or not. Unfortunately, the main reason for going +there is that we walked past the end of list and current_step points to +the head. We cannot dereference ->action since it does not belong to this +structure and can definitely crash if the address is not mapped. + +This bug is unlikely to cause a crash since the action appears just after +the list, and corresponds to the "char *check_req" pointer in the proxy +struct, and it seems that we can't go there with current_step being null. +At worst it can cause the check to register for recv events. + +This fix needs to be backported to 1.5 since the code is incorrect there +as well. +(cherry picked from commit 53c5a049e1f4dbf67412472e23690dc6b3c8d0f8) +--- + src/checks.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/checks.c b/src/checks.c +index cfdfe8c..a887be1 100644 +--- a/src/checks.c ++++ b/src/checks.c +@@ -2237,10 +2237,12 @@ static void tcpcheck_main(struct connection *conn) + goto out_end_tcpcheck; + + out_need_io: ++ /* warning, current_step may now point to the head */ + if (check->bo->o) + __conn_data_want_send(conn); + +- if (check->current_step->action == TCPCHK_ACT_EXPECT) ++ if (&check->current_step->list != head && ++ check->current_step->action == TCPCHK_ACT_EXPECT) + __conn_data_want_recv(conn); + return; + +@@ -2256,7 +2258,6 @@ static void tcpcheck_main(struct connection *conn) + conn->flags |= CO_FL_ERROR; + + __conn_data_stop_both(conn); +- + return; + } + +-- +2.0.5 + diff --git a/net/haproxy/patches/0006-CLEANUP-checks-simplify-the-loop-processing-of-tcp-c.patch b/net/haproxy/patches/0006-CLEANUP-checks-simplify-the-loop-processing-of-tcp-c.patch new file mode 100644 index 0000000000..45ba960228 --- /dev/null +++ b/net/haproxy/patches/0006-CLEANUP-checks-simplify-the-loop-processing-of-tcp-c.patch @@ -0,0 +1,82 @@ +From ebb2bceb34d7787453548627ed0e99c60354672b Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Wed, 13 May 2015 11:59:14 +0200 +Subject: [PATCH 6/8] CLEANUP: checks: simplify the loop processing of + tcp-checks + +There is some unobvious redundancy between the various ways we can leave +the loop. Some of them can be factored out. So now we leave the loop when +we can't go further, whether it's caused by reaching the end of the rules +or by a blocking I/O. +(cherry picked from commit 263013d031d754c9f96de0d0cb5afcc011af6441) +[wt: this patch is required for the next fix] +--- + src/checks.c | 26 ++++++++++++++------------ + 1 file changed, 14 insertions(+), 12 deletions(-) + +diff --git a/src/checks.c b/src/checks.c +index a887be1..a0c42f2 100644 +--- a/src/checks.c ++++ b/src/checks.c +@@ -1926,8 +1926,10 @@ static void tcpcheck_main(struct connection *conn) + __conn_data_stop_both(conn); + + while (1) { +- /* we have to try to flush the output buffer before reading, at the end, +- * or if we're about to send a string that does not fit in the remaining space. ++ /* We have to try to flush the output buffer before reading, at ++ * the end, or if we're about to send a string that does not fit ++ * in the remaining space. That explains why we break out of the ++ * loop after this control. + */ + if (check->bo->o && + (&check->current_step->list == head || +@@ -1940,16 +1942,12 @@ static void tcpcheck_main(struct connection *conn) + __conn_data_stop_both(conn); + goto out_end_tcpcheck; + } +- goto out_need_io; ++ break; + } + } + +- /* did we reach the end ? If so, let's check that everything was sent */ +- if (&check->current_step->list == head) { +- if (check->bo->o) +- goto out_need_io; ++ if (&check->current_step->list == head) + break; +- } + + /* have 'next' point to the next rule or NULL if we're on the + * last one, connect() needs this. +@@ -2131,7 +2129,7 @@ static void tcpcheck_main(struct connection *conn) + } + } + else +- goto out_need_io; ++ break; + } + + /* mark the step as started */ +@@ -2233,10 +2231,14 @@ static void tcpcheck_main(struct connection *conn) + } /* end expect */ + } /* end loop over double chained step list */ + +- set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)"); +- goto out_end_tcpcheck; ++ /* We're waiting for some I/O to complete, we've reached the end of the ++ * rules, or both. Do what we have to do, otherwise we're done. ++ */ ++ if (&check->current_step->list == head && !check->bo->o) { ++ set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)"); ++ goto out_end_tcpcheck; ++ } + +- out_need_io: + /* warning, current_step may now point to the head */ + if (check->bo->o) + __conn_data_want_send(conn); +-- +2.0.5 + diff --git a/net/haproxy/patches/0007-BUG-MAJOR-checks-always-check-for-end-of-list-before.patch b/net/haproxy/patches/0007-BUG-MAJOR-checks-always-check-for-end-of-list-before.patch new file mode 100644 index 0000000000..7c2d562f39 --- /dev/null +++ b/net/haproxy/patches/0007-BUG-MAJOR-checks-always-check-for-end-of-list-before.patch @@ -0,0 +1,90 @@ +From 97fccc87f1297d189ee80735e5b8746c34956eda Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Wed, 13 May 2015 12:08:21 +0200 +Subject: [PATCH 7/8] BUG/MAJOR: checks: always check for end of list before + proceeding + +This is the most important fix of this series. There's a risk of endless +loop and crashes caused by the fact that we go past the head of the list +when skipping to next rule, without checking if it's still a valid element. +Most of the time, the ->action field is checked, which points to the proxy's +check_req pointer (generally NULL), meaning the element is confused with a +TCPCHK_ACT_SEND action. + +The situation was accidently made worse with the addition of tcp-check +comment since it also skips list elements. However, since the action that +makes it go forward is TCPCHK_ACT_COMMENT (3), there's little chance to +see this as a valid pointer, except on 64-bit machines where it can match +the end of a check_req string pointer. + +This fix heavily depends on previous cleanup and both must be backported +to 1.5 where the bug is present. +(cherry picked from commit f2c87353a7f8160930b5f342bb6d6ad0991ee3d1) +[wt: this patch differs significantly from 1.6 since we don't have comments] +--- + src/cfgparse.c | 4 +++- + src/checks.c | 12 ++++++++++++ + 2 files changed, 15 insertions(+), 1 deletion(-) + +diff --git a/src/cfgparse.c b/src/cfgparse.c +index 746c7eb..dba59d1 100644 +--- a/src/cfgparse.c ++++ b/src/cfgparse.c +@@ -4368,7 +4368,9 @@ stats_error_parsing: + l = (struct list *)&curproxy->tcpcheck_rules; + if (l->p != l->n) { + tcpcheck = (struct tcpcheck_rule *)l->n; +- if (tcpcheck && tcpcheck->action != TCPCHK_ACT_CONNECT) { ++ ++ if (&tcpcheck->list != &curproxy->tcpcheck_rules ++ && tcpcheck->action != TCPCHK_ACT_CONNECT) { + Alert("parsing [%s:%d] : first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset.\n", + file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; +diff --git a/src/checks.c b/src/checks.c +index a0c42f2..e13d561 100644 +--- a/src/checks.c ++++ b/src/checks.c +@@ -2057,6 +2057,9 @@ static void tcpcheck_main(struct connection *conn) + /* allow next rule */ + check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; + ++ if (&check->current_step->list == head) ++ break; ++ + /* don't do anything until the connection is established */ + if (!(conn->flags & CO_FL_CONNECTED)) { + /* update expire time, should be done by process_chk */ +@@ -2110,6 +2113,9 @@ static void tcpcheck_main(struct connection *conn) + + /* go to next rule and try to send */ + check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ ++ if (&check->current_step->list == head) ++ break; + } /* end 'send' */ + else if (check->current_step->action == TCPCHK_ACT_EXPECT) { + if (unlikely(check->result == CHK_RES_FAILED)) +@@ -2196,6 +2202,9 @@ static void tcpcheck_main(struct connection *conn) + /* allow next rule */ + check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; + ++ if (&check->current_step->list == head) ++ break; ++ + if (check->current_step->action == TCPCHK_ACT_EXPECT) + goto tcpcheck_expect; + __conn_data_stop_recv(conn); +@@ -2208,6 +2217,9 @@ static void tcpcheck_main(struct connection *conn) + /* allow next rule */ + check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; + ++ if (&check->current_step->list == head) ++ break; ++ + if (check->current_step->action == TCPCHK_ACT_EXPECT) + goto tcpcheck_expect; + __conn_data_stop_recv(conn); +-- +2.0.5 + diff --git a/net/haproxy/patches/0008-BUG-MEDIUM-checks-do-not-dereference-a-list-as-a-tcp.patch b/net/haproxy/patches/0008-BUG-MEDIUM-checks-do-not-dereference-a-list-as-a-tcp.patch new file mode 100644 index 0000000000..454026097f --- /dev/null +++ b/net/haproxy/patches/0008-BUG-MEDIUM-checks-do-not-dereference-a-list-as-a-tcp.patch @@ -0,0 +1,116 @@ +From 5bff05986c501d9ffb67873b60472f9c2a2e41be Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Wed, 13 May 2015 12:24:53 +0200 +Subject: [PATCH 8/8] BUG/MEDIUM: checks: do not dereference a list as a + tcpcheck struct + +The method used to skip to next rule in the list is wrong, it assumes +that the list element starts at the same offset as the rule. It happens +to be true on most architectures since the list is the first element for +now but it's definitely wrong. Now the code doesn't crash anymore when +the struct list is moved anywhere else in the struct tcpcheck_rule. + +This fix must be backported to 1.5. +(cherry picked from commit 5581c27b579cbfc53afb0ca04cdeebe7e2200131) +[wt: changes from 1.6 : no tcp-check comments, check becomes s->proxy] +--- + src/cfgparse.c | 18 +++++++----------- + src/checks.c | 15 +++++++++------ + 2 files changed, 16 insertions(+), 17 deletions(-) + +diff --git a/src/cfgparse.c b/src/cfgparse.c +index dba59d1..e04eff8 100644 +--- a/src/cfgparse.c ++++ b/src/cfgparse.c +@@ -4362,20 +4362,16 @@ stats_error_parsing: + const char *ptr_arg; + int cur_arg; + struct tcpcheck_rule *tcpcheck; +- struct list *l; + + /* check if first rule is also a 'connect' action */ +- l = (struct list *)&curproxy->tcpcheck_rules; +- if (l->p != l->n) { +- tcpcheck = (struct tcpcheck_rule *)l->n; ++ tcpcheck = LIST_NEXT(&curproxy->tcpcheck_rules, struct tcpcheck_rule *, list); + +- if (&tcpcheck->list != &curproxy->tcpcheck_rules +- && tcpcheck->action != TCPCHK_ACT_CONNECT) { +- Alert("parsing [%s:%d] : first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset.\n", +- file, linenum); +- err_code |= ERR_ALERT | ERR_FATAL; +- goto out; +- } ++ if (&tcpcheck->list != &curproxy->tcpcheck_rules ++ && tcpcheck->action != TCPCHK_ACT_CONNECT) { ++ Alert("parsing [%s:%d] : first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset.\n", ++ file, linenum); ++ err_code |= ERR_ALERT | ERR_FATAL; ++ goto out; + } + + cur_arg = 2; +diff --git a/src/checks.c b/src/checks.c +index e13d561..27a23b2 100644 +--- a/src/checks.c ++++ b/src/checks.c +@@ -1444,7 +1444,10 @@ static int connect_chk(struct task *t) + quickack = check->type == 0 || check->type == PR_O2_TCPCHK_CHK; + + if (check->type == PR_O2_TCPCHK_CHK && !LIST_ISEMPTY(&s->proxy->tcpcheck_rules)) { +- struct tcpcheck_rule *r = (struct tcpcheck_rule *) s->proxy->tcpcheck_rules.n; ++ struct tcpcheck_rule *r; ++ ++ r = LIST_NEXT(&s->proxy->tcpcheck_rules, struct tcpcheck_rule *, list); ++ + /* if first step is a 'connect', then tcpcheck_main must run it */ + if (r->action == TCPCHK_ACT_CONNECT) { + tcpcheck_main(conn); +@@ -1952,7 +1955,7 @@ static void tcpcheck_main(struct connection *conn) + /* have 'next' point to the next rule or NULL if we're on the + * last one, connect() needs this. + */ +- next = (struct tcpcheck_rule *)check->current_step->list.n; ++ next = LIST_NEXT(&check->current_step->list, struct tcpcheck_rule *, list); + + if (&next->list == head) + next = NULL; +@@ -2055,7 +2058,7 @@ static void tcpcheck_main(struct connection *conn) + } + + /* allow next rule */ +- check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ check->current_step = LIST_NEXT(&check->current_step->list, struct tcpcheck_rule *, list); + + if (&check->current_step->list == head) + break; +@@ -2112,7 +2115,7 @@ static void tcpcheck_main(struct connection *conn) + *check->bo->p = '\0'; /* to make gdb output easier to read */ + + /* go to next rule and try to send */ +- check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ check->current_step = LIST_NEXT(&check->current_step->list, struct tcpcheck_rule *, list); + + if (&check->current_step->list == head) + break; +@@ -2200,7 +2203,7 @@ static void tcpcheck_main(struct connection *conn) + /* matched and was supposed to => OK, next step */ + else { + /* allow next rule */ +- check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ check->current_step = LIST_NEXT(&check->current_step->list, struct tcpcheck_rule *, list); + + if (&check->current_step->list == head) + break; +@@ -2215,7 +2218,7 @@ static void tcpcheck_main(struct connection *conn) + /* not matched and was not supposed to => OK, next step */ + if (check->current_step->inverse) { + /* allow next rule */ +- check->current_step = (struct tcpcheck_rule *)check->current_step->list.n; ++ check->current_step = LIST_NEXT(&check->current_step->list, struct tcpcheck_rule *, list); + + if (&check->current_step->list == head) + break; +-- +2.0.5 + diff --git a/net/haproxy/patches/0009-BUG-MEDIUM-peers-apply-a-random-reconnection-timeout.patch b/net/haproxy/patches/0009-BUG-MEDIUM-peers-apply-a-random-reconnection-timeout.patch new file mode 100644 index 0000000000..96aa743aa7 --- /dev/null +++ b/net/haproxy/patches/0009-BUG-MEDIUM-peers-apply-a-random-reconnection-timeout.patch @@ -0,0 +1,77 @@ +From 76a06b2804bcdba0fb2c19f834bdb511ce3cf344 Mon Sep 17 00:00:00 2001 +From: Willy Tarreau +Date: Wed, 20 May 2015 10:39:04 +0200 +Subject: [PATCH 09/10] BUG/MEDIUM: peers: apply a random reconnection timeout + +Commit 9ff95bb ("BUG/MEDIUM: peers: correctly configure the client timeout") +uncovered an old bug in the peers : upon disconnect, we reconnect immediately. +This sometimes results in both ends to do the same thing in parallel causing +a loop of connect/accept/close/close that can last several seconds. The risk +of occurrence of the trouble increases with latency, and is emphasized by the +fact that idle connections are now frequently recycled (after 5s of idle). + +In order to avoid this we must apply a random delay before reconnecting. +Fortunately the mechanism already supports a reconnect delay, so here we +compute the random timeout when killing a session. The delay is 50ms plus +a random between 0 and 2 seconds. Ideally an exponential back-off would +be preferred but it's preferable to keep the fix simple. + +This bug was reported by Marco Corte. + +This fix must be backported to 1.5 since the fix above was backported into +1.5.12. +(cherry picked from commit b4e34da692d8a7f6837ad16b3389f5830dbc11d2) +--- + src/peers.c | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/src/peers.c b/src/peers.c +index b196d88..159f0a4 100644 +--- a/src/peers.c ++++ b/src/peers.c +@@ -1063,6 +1063,7 @@ static void peer_session_forceshutdown(struct session * session) + { + struct stream_interface *oldsi = NULL; + struct appctx *appctx = NULL; ++ struct peer_session *ps; + int i; + + for (i = 0; i <= 1; i++) { +@@ -1079,6 +1080,14 @@ static void peer_session_forceshutdown(struct session * session) + if (!appctx) + return; + ++ ps = (struct peer_session *)appctx->ctx.peers.ptr; ++ /* we're killing a connection, we must apply a random delay before ++ * retrying otherwise the other end will do the same and we can loop ++ * for a while. ++ */ ++ if (ps) ++ ps->reconnect = tick_add(now_ms, MS_TO_TICKS(50 + random() % 2000)); ++ + /* call release to reinit resync states if needed */ + peer_session_release(oldsi); + appctx->st0 = PEER_SESS_ST_END; +@@ -1352,8 +1361,8 @@ static struct task *process_peer_sync(struct task * task) + if (!ps->session) { + /* no active session */ + if (ps->statuscode == 0 || +- ps->statuscode == PEER_SESS_SC_SUCCESSCODE || + ((ps->statuscode == PEER_SESS_SC_CONNECTCODE || ++ ps->statuscode == PEER_SESS_SC_SUCCESSCODE || + ps->statuscode == PEER_SESS_SC_CONNECTEDCODE) && + tick_is_expired(ps->reconnect, now_ms))) { + /* connection never tried +@@ -1364,8 +1373,7 @@ static struct task *process_peer_sync(struct task * task) + /* retry a connect */ + ps->session = peer_session_create(ps->peer, ps); + } +- else if (ps->statuscode == PEER_SESS_SC_CONNECTCODE || +- ps->statuscode == PEER_SESS_SC_CONNECTEDCODE) { ++ else if (!tick_is_expired(ps->reconnect, now_ms)) { + /* If previous session failed during connection + * but reconnection timer is not expired */ + +-- +2.0.5 + diff --git a/net/haproxy/patches/0010-DOC-Update-doc-about-weight-act-and-bck-fields-in-th.patch b/net/haproxy/patches/0010-DOC-Update-doc-about-weight-act-and-bck-fields-in-th.patch new file mode 100644 index 0000000000..563ed64b89 --- /dev/null +++ b/net/haproxy/patches/0010-DOC-Update-doc-about-weight-act-and-bck-fields-in-th.patch @@ -0,0 +1,33 @@ +From ac372e18c422841a9f1197b4238637c470e8edca Mon Sep 17 00:00:00 2001 +From: Pavlos Parissis +Date: Sat, 2 May 2015 20:30:44 +0200 +Subject: [PATCH 10/10] DOC: Update doc about weight, act and bck fields in the + statistics + +Reorder description of the mentioned fields in order to match the +order of types +(cherry picked from commit 1f673c72c11d011bbd24e309d3155384eddf7a46) +--- + doc/configuration.txt | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/doc/configuration.txt b/doc/configuration.txt +index a9d497e..6f5eeb1 100644 +--- a/doc/configuration.txt ++++ b/doc/configuration.txt +@@ -13240,9 +13240,9 @@ S (Servers). + server. The server value counts the number of times that server was + switched away from. + 17. status [LFBS]: status (UP/DOWN/NOLB/MAINT/MAINT(via)...) +- 18. weight [..BS]: server weight (server), total weight (backend) +- 19. act [..BS]: server is active (server), number of active servers (backend) +- 20. bck [..BS]: server is backup (server), number of backup servers (backend) ++ 18. weight [..BS]: total weight (backend), server weight (server) ++ 19. act [..BS]: number of active servers (backend), server is active (server) ++ 20. bck [..BS]: number of backup servers (backend), server is backup (server) + 21. chkfail [...S]: number of failed checks. (Only counts checks failed when + the server is up.) + 22. chkdown [..BS]: number of UP->DOWN transitions. The backend counter counts +-- +2.0.5 + diff --git a/net/haproxy/patches/0011-MINOR-ssl-add-a-destructor-to-free-allocated-SSL-res.patch b/net/haproxy/patches/0011-MINOR-ssl-add-a-destructor-to-free-allocated-SSL-res.patch new file mode 100644 index 0000000000..86be3b9654 --- /dev/null +++ b/net/haproxy/patches/0011-MINOR-ssl-add-a-destructor-to-free-allocated-SSL-res.patch @@ -0,0 +1,64 @@ +From 269a02fbb332da8faf6c2a614d45d5b5018816d1 Mon Sep 17 00:00:00 2001 +From: Remi Gacogne +Date: Thu, 28 May 2015 16:39:47 +0200 +Subject: [PATCH 11/14] MINOR: ssl: add a destructor to free allocated SSL + ressources + +Using valgrind or another memory leak tracking tool is easier +when the memory internally allocated by OpenSSL is cleanly released +at shutdown. +(cherry picked from commit d3a23c3eb8c0950d26204568a133207099923494) +--- + src/ssl_sock.c | 36 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/src/ssl_sock.c b/src/ssl_sock.c +index d0f4d01..a78fc6a 100644 +--- a/src/ssl_sock.c ++++ b/src/ssl_sock.c +@@ -4717,6 +4717,42 @@ static void __ssl_sock_init(void) + cfg_register_keywords(&cfg_kws); + } + ++__attribute__((destructor)) ++static void __ssl_sock_deinit(void) ++{ ++#ifndef OPENSSL_NO_DH ++ if (local_dh_1024) { ++ DH_free(local_dh_1024); ++ local_dh_1024 = NULL; ++ } ++ ++ if (local_dh_2048) { ++ DH_free(local_dh_2048); ++ local_dh_2048 = NULL; ++ } ++ ++ if (local_dh_4096) { ++ DH_free(local_dh_4096); ++ local_dh_4096 = NULL; ++ } ++ ++ if (local_dh_8192) { ++ DH_free(local_dh_8192); ++ local_dh_8192 = NULL; ++ } ++#endif ++ ++ ERR_remove_state(0); ++ ERR_free_strings(); ++ ++ EVP_cleanup(); ++ ++#if OPENSSL_VERSION_NUMBER >= 0x00907000L ++ CRYPTO_cleanup_all_ex_data(); ++#endif ++} ++ ++ + /* + * Local variables: + * c-indent-level: 8 +-- +2.0.5 + diff --git a/net/haproxy/patches/0012-BUG-MEDIUM-ssl-fix-tune.ssl.default-dh-param-value-b.patch b/net/haproxy/patches/0012-BUG-MEDIUM-ssl-fix-tune.ssl.default-dh-param-value-b.patch new file mode 100644 index 0000000000..37600c8a20 --- /dev/null +++ b/net/haproxy/patches/0012-BUG-MEDIUM-ssl-fix-tune.ssl.default-dh-param-value-b.patch @@ -0,0 +1,98 @@ +From 5d769ca828fdb055052b3dbc232864bdf2853c9f Mon Sep 17 00:00:00 2001 +From: Remi Gacogne +Date: Thu, 28 May 2015 16:23:00 +0200 +Subject: [PATCH 12/14] BUG/MEDIUM: ssl: fix tune.ssl.default-dh-param value + being overwritten +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Hervé Commowick reported that the logic used to avoid complaining about +ssl-default-dh-param not being set when static DH params are present +in the certificate file was clearly wrong when more than one sni_ctx +is used. +This patch stores whether static DH params are being used for each +SSL_CTX individually, and does not overwrite the value of +tune.ssl.default-dh-param. +(cherry picked from commit 4f902b88323927c9d25d391a809e3678ac31df41) +--- + src/ssl_sock.c | 28 +++++++++++++++++++++++----- + 1 file changed, 23 insertions(+), 5 deletions(-) + +diff --git a/src/ssl_sock.c b/src/ssl_sock.c +index a78fc6a..0f7819b 100644 +--- a/src/ssl_sock.c ++++ b/src/ssl_sock.c +@@ -47,6 +47,9 @@ + #ifdef SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB + #include + #endif ++#ifndef OPENSSL_NO_DH ++#include ++#endif + + #include + #include +@@ -107,6 +110,7 @@ int sslconns = 0; + int totalsslconns = 0; + + #ifndef OPENSSL_NO_DH ++static int ssl_dh_ptr_index = -1; + static DH *local_dh_1024 = NULL; + static DH *local_dh_2048 = NULL; + static DH *local_dh_4096 = NULL; +@@ -1076,10 +1080,12 @@ int ssl_sock_load_dh_params(SSL_CTX *ctx, const char *file) + if (dh) { + ret = 1; + SSL_CTX_set_tmp_dh(ctx, dh); +- /* Setting ssl default dh param to the size of the static DH params +- found in the file. This way we know that there is no use +- complaining later about ssl-default-dh-param not being set. */ +- global.tune.ssl_default_dh_param = DH_size(dh) * 8; ++ ++ if (ssl_dh_ptr_index >= 0) { ++ /* store a pointer to the DH params to avoid complaining about ++ ssl-default-dh-param not being set for this SSL_CTX */ ++ SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, dh); ++ } + } + else { + /* Clear openssl global errors stack */ +@@ -1274,6 +1280,12 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf + * the tree, so it will be discovered and cleaned in time. + */ + #ifndef OPENSSL_NO_DH ++ /* store a NULL pointer to indicate we have not yet loaded ++ a custom DH param file */ ++ if (ssl_dh_ptr_index >= 0) { ++ SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL); ++ } ++ + ret = ssl_sock_load_dh_params(ctx, path); + if (ret < 0) { + if (err) +@@ -1593,7 +1605,9 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, SSL_CTX *ctx, struct proxy + + /* If tune.ssl.default-dh-param has not been set and + no static DH params were in the certificate file. */ +- if (global.tune.ssl_default_dh_param == 0) { ++ if (global.tune.ssl_default_dh_param == 0 && ++ (ssl_dh_ptr_index == -1 || ++ SSL_CTX_get_ex_data(ctx, ssl_dh_ptr_index) == NULL)) { + ciphers = ctx->cipher_list; + + if (ciphers) { +@@ -4715,6 +4729,10 @@ static void __ssl_sock_init(void) + bind_register_keywords(&bind_kws); + srv_register_keywords(&srv_kws); + cfg_register_keywords(&cfg_kws); ++ ++#ifndef OPENSSL_NO_DH ++ ssl_dh_ptr_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); ++#endif + } + + __attribute__((destructor)) +-- +2.0.5 + diff --git a/net/haproxy/patches/0013-BUG-MINOR-cfgparse-fix-typo-in-option-httplog-error-.patch b/net/haproxy/patches/0013-BUG-MINOR-cfgparse-fix-typo-in-option-httplog-error-.patch new file mode 100644 index 0000000000..d09f18df58 --- /dev/null +++ b/net/haproxy/patches/0013-BUG-MINOR-cfgparse-fix-typo-in-option-httplog-error-.patch @@ -0,0 +1,29 @@ +From 629b1c000b26f0031246b9b529680b275a14118f Mon Sep 17 00:00:00 2001 +From: William Lallemand +Date: Thu, 28 May 2015 18:02:48 +0200 +Subject: [PATCH 13/14] BUG/MINOR: cfgparse: fix typo in 'option httplog' error + message + +The error message was displaying the wrong argument when 'option +httplog' took a wrong argument. +(cherry picked from commit 77063bc0c6ceb4257c4e2c08411811ecc48be1aa) +--- + src/cfgparse.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/cfgparse.c b/src/cfgparse.c +index e04eff8..3c3383d 100644 +--- a/src/cfgparse.c ++++ b/src/cfgparse.c +@@ -3792,7 +3792,7 @@ stats_error_parsing: + curproxy->options2 |= PR_O2_CLFLOG; + logformat = clf_http_log_format; + } else { +- Alert("parsing [%s:%d] : keyword '%s' only supports option 'clf'.\n", file, linenum, args[2]); ++ Alert("parsing [%s:%d] : keyword '%s' only supports option 'clf'.\n", file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } +-- +2.0.5 + diff --git a/net/haproxy/patches/0014-BUG-MEDIUM-cfgparse-segfault-when-userlist-is-misuse.patch b/net/haproxy/patches/0014-BUG-MEDIUM-cfgparse-segfault-when-userlist-is-misuse.patch new file mode 100644 index 0000000000..329505b7bd --- /dev/null +++ b/net/haproxy/patches/0014-BUG-MEDIUM-cfgparse-segfault-when-userlist-is-misuse.patch @@ -0,0 +1,41 @@ +From faf3315f77c527e6e1d027deb7e853cdf6af5858 Mon Sep 17 00:00:00 2001 +From: William Lallemand +Date: Thu, 28 May 2015 18:03:51 +0200 +Subject: [PATCH 14/14] BUG/MEDIUM: cfgparse: segfault when userlist is misused + +If the 'userlist' keyword parsing returns an error and no userlist were +previously created. The parsing of 'user' and 'group' leads to NULL +derefence. + +The userlist pointer is now tested to prevent this issue. +(cherry picked from commit 4ac9f546120d42be8147e3d90588e7b9738af0cc) +--- + src/cfgparse.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/cfgparse.c b/src/cfgparse.c +index 3c3383d..392a78d 100644 +--- a/src/cfgparse.c ++++ b/src/cfgparse.c +@@ -5668,6 +5668,9 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) + goto out; + } + ++ if (!userlist) ++ goto out; ++ + for (ag = userlist->groups; ag; ag = ag->next) + if (!strcmp(ag->name, args[1])) { + Warning("parsing [%s:%d]: ignoring duplicated group '%s' in userlist '%s'.\n", +@@ -5718,6 +5721,8 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } ++ if (!userlist) ++ goto out; + + for (newuser = userlist->users; newuser; newuser = newuser->next) + if (!strcmp(newuser->user, args[1])) { +-- +2.0.5 +