MEDIUM: http-rules: Rely on http reply for http deny/tarpit rules

"http-request deny", "http-request tarpit" and "http-response deny" rules now
use the same syntax than http return rules and internally rely on the http
replies. The behaviour is not the same when no argument is specified (or only
the status code). For http replies, a dummy response is produced, with no
payload. For old deny/tarpit rules, the proxy's error messages are used. Thus,
to be compatible with existing configuration, the "default-errorfiles" parameter
is implied. For instance :

  http-request deny deny_status 404

is now an alias of

  http-request deny status 404 default-errorfiles
This commit is contained in:
Christopher Faulet 2020-05-13 17:56:56 +02:00
parent 0e2ad61315
commit 5cb513abeb
7 changed files with 176 additions and 165 deletions

View File

@ -4991,18 +4991,22 @@ http-request del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
It takes one argument: "file name" It is the equivalent of the "del map" It takes one argument: "file name" It is the equivalent of the "del map"
command from the stats socket, but can be triggered by an HTTP request. command from the stats socket, but can be triggered by an HTTP request.
http-request deny [deny_status <status>] [ { errorfile | errorfiles } <err> ] http-request deny [deny_status <status>] [ { if | unless } <condition> ]
[ { if | unless } <condition> ] http-request deny [ { status | deny_status } <code>] [content-type <type>]
[ { default-errorfiles | errorfile <file> | errorfiles <name> |
file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
[ hdr <name> <fmt> ]*
[ { if | unless } <condition> ]
This stops the evaluation of the rules and immediately rejects the request This stops the evaluation of the rules and immediately rejects the request.
and emits an HTTP 403 error, or optionally the status code specified as an By default an HTTP 403 error is returned. But the response may be customized
argument to "deny_status". The list of permitted status codes is limited to using same syntax than "http-request return" rules. Thus, see "http-request
those that can be overridden by the "errorfile" directive. A specific error return" for details. For compatiblity purpose, when no argument is defined,
message may be specified. It may be an error file, using the "errorfile" or only "deny_status", the argument "default-errorfiles" is implied. It means
keyword followed by the file containing the full HTTP response. It may also "http-request deny [deny_status <status>]" is an alias of
be an error from an http-errors section, using the "errorfiles" keyword "http-request deny [status <status>] default-errorfiles".
followed by the section name.
No further "http-request" rules are evaluated. No further "http-request" rules are evaluated.
See also "http-request return".
http-request disable-l7-retry [ { if | unless } <condition> ] http-request disable-l7-retry [ { if | unless } <condition> ]
This disables any attempt to retry the request if it fails for any other This disables any attempt to retry the request if it fails for any other
@ -5576,26 +5580,32 @@ http-request strict-mode { on | off }
the frontend, the default mode is restored when HAProxy starts the backend the frontend, the default mode is restored when HAProxy starts the backend
rules evaluation. rules evaluation.
http-request tarpit [deny_status <status>] [ { errorfile | errorfiles } <err> ] http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
[ { if | unless } <condition> ] http-request tarpit [ { status | deny_status } <code>] [content-type <type>]
[ { default-errorfiles | errorfile <file> | errorfiles <name> |
file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
[ hdr <name> <fmt> ]*
[ { if | unless } <condition> ]
This stops the evaluation of the rules and immediately blocks the request This stops the evaluation of the rules and immediately blocks the request
without responding for a delay specified by "timeout tarpit" or without responding for a delay specified by "timeout tarpit" or
"timeout connect" if the former is not set. After that delay, if the client "timeout connect" if the former is not set. After that delay, if the client
is still connected, an HTTP error 500 (or optionally the status code is still connected, a response is returned so that the client does not
specified as an argument to "deny_status") is returned so that the client suspect it has been tarpitted. Logs will report the flags "PT". The goal of
does not suspect it has been tarpitted. Logs will report the flags "PT". the tarpit rule is to slow down robots during an attack when they're limited
The goal of the tarpit rule is to slow down robots during an attack when on the number of concurrent requests. It can be very efficient against very
they're limited on the number of concurrent requests. It can be very dumb robots, and will significantly reduce the load on firewalls compared to
efficient against very dumb robots, and will significantly reduce the load a "deny" rule. But when facing "correctly" developed robots, it can make
on firewalls compared to a "deny" rule. But when facing "correctly" things worse by forcing haproxy and the front firewall to support insane
developed robots, it can make things worse by forcing haproxy and the front number of concurrent connections. By default an HTTP error 500 is returned.
firewall to support insane number of concurrent connections. A specific error But the response may be customized using same syntax than
message may be specified. It may be an error file, using the "errorfile" "http-request return" rules. Thus, see "http-request return" for details.
keyword followed by the file containing the full HTTP response. It may also For compatiblity purpose, when no argument is defined, or only "deny_status",
be an error from an http-errors section, using the "errorfiles" keyword the argument "default-errorfiles" is implied. It means
followed by the section name. "http-request tarpit [deny_status <status>]" is an alias of
See also the "silent-drop" action. "http-request tarpit [status <status>] default-errorfiles".
No further "http-request" rules are evaluated.
See also "http-request return" and "http-request silent-drop".
http-request track-sc0 <key> [table <table>] [ { if | unless } <condition> ] http-request track-sc0 <key> [table <table>] [ { if | unless } <condition> ]
http-request track-sc1 <key> [table <table>] [ { if | unless } <condition> ] http-request track-sc1 <key> [table <table>] [ { if | unless } <condition> ]
@ -5779,18 +5789,22 @@ http-response del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
It takes one argument: "file name" It is the equivalent of the "del map" It takes one argument: "file name" It is the equivalent of the "del map"
command from the stats socket, but can be triggered by an HTTP response. command from the stats socket, but can be triggered by an HTTP response.
http-response deny [deny_status <status>] [ { errorfile | errorfiles } <err> ] http-response deny [deny_status <status>] [ { if | unless } <condition> ]
[ { if | unless } <condition> ] http-response deny [ { status | deny_status } <code>] [content-type <type>]
[ { default-errorfiles | errorfile <file> | errorfiles <name> |
file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
[ hdr <name> <fmt> ]*
[ { if | unless } <condition> ]
This stops the evaluation of the rules and immediately rejects the response This stops the evaluation of the rules and immediately rejects the response.
and emits an HTTP 502 error, or optionally the status code specified as an By default an HTTP 502 error is returned. But the response may be customized
argument to "deny_status". The list of permitted status codes is limited to using same syntax than "http-response return" rules. Thus, see
those that can be overridden by the "errorfile" directive. A specific error "http-response return" for details. For compatiblity purpose, when no
message may be specified. It may be an error file, using the "errorfile" argument is defined, or only "deny_status", the argument "default-errorfiles"
keyword followed by the file containing the full HTTP response. It may also is implied. It means "http-response deny [deny_status <status>]" is an alias
be an error from an http-errors section, using the "errorfiles" keyword of "http-response deny [status <status>] default-errorfiles".
followed by the section name.
No further "http-response" rules are evaluated. No further "http-response" rules are evaluated.
See also "http-response return".
http-response redirect <rule> [ { if | unless } <condition> ] http-response redirect <rule> [ { if | unless } <condition> ]

View File

@ -125,11 +125,7 @@ struct act_rule {
struct list fmt; /* log-format compatible expression */ struct list fmt; /* log-format compatible expression */
struct my_regex *re; /* used by replace-header/value/uri/path */ struct my_regex *re; /* used by replace-header/value/uri/path */
} http; /* args used by some HTTP rules */ } http; /* args used by some HTTP rules */
struct { struct http_reply *http_reply; /* HTTP response to be used by return/deny/tarpit rules */
int status; /* status code */
struct buffer *errmsg; /* HTTP error message, may be NULL */
} http_deny; /* args used by HTTP deny rules */
struct http_reply *http_reply; /* HTTP response to be used by return rules */
struct redirect_rule *redir; /* redirect rule or "http-request redirect" */ struct redirect_rule *redir; /* redirect rule or "http-request redirect" */
struct { struct {
char *ref; /* MAP or ACL file name to update */ char *ref; /* MAP or ACL file name to update */

View File

@ -26,6 +26,7 @@
#include <common/http.h> #include <common/http.h>
#include <types/channel.h> #include <types/channel.h>
#include <types/http_htx.h>
/* These are the flags that are found in txn->flags */ /* These are the flags that are found in txn->flags */
@ -174,6 +175,7 @@ struct http_txn {
/* 1 unused byte here */ /* 1 unused byte here */
short status; /* HTTP status from the server, negative if from proxy */ short status; /* HTTP status from the server, negative if from proxy */
struct buffer *errmsg; /* custom HTTP error message to use as reply */ struct buffer *errmsg; /* custom HTTP error message to use as reply */
struct http_reply *http_reply; /* The HTTP reply to use as reply */
char cache_hash[20]; /* Store the cache hash */ char cache_hash[20]; /* Store the cache hash */
char *uri; /* first line if log needed, NULL otherwise */ char *uri; /* first line if log needed, NULL otherwise */

View File

@ -0,0 +1 @@
The path "%[path]" is forbidden

View File

@ -27,6 +27,9 @@ haproxy h1 -conf {
http-request deny deny_status 500 errorfile /dev/null if { path /500-1 } http-request deny deny_status 500 errorfile /dev/null if { path /500-1 }
http-request deny deny_status 500 errorfiles errors-1 if { path /500-2 } http-request deny deny_status 500 errorfiles errors-1 if { path /500-2 }
http-request deny status 500 hdr x-err-info "path=%[path]" content-type "text/plain" string "Internal Error" if { path /int-err }
http-request deny status 403 hdr x-err-info "path=%[path]" content-type "text/plain" lf-file ${testdir}/errors/lf-403.txt if { path /forbidden }
} -start } -start
client c1r1 -connect ${h1_fe1_sock} { client c1r1 -connect ${h1_fe1_sock} {
@ -55,3 +58,20 @@ client c1r5 -connect ${h1_fe1_sock} {
txreq -req GET -url /500-2 txreq -req GET -url /500-2
expect_close expect_close
} -run } -run
client c1r6 -connect ${h1_fe1_sock} {
txreq -req GET -url /int-err
rxresp
expect resp.status == 500
expect resp.http.x-err-info == "path=/int-err"
expect resp.http.content-type == "text/plain"
expect resp.http.content-length == 14
expect resp.body == "Internal Error"
} -run
client c1r7 -connect ${h1_fe1_sock} {
txreq -req GET -url /forbidden
rxresp
expect resp.status == 403
expect resp.http.x-err-info == "path=/forbidden"
expect resp.http.content-type == "text/plain"
expect resp.body == "The path \"/forbidden\" is forbidden\n"
} -run

View File

@ -58,6 +58,30 @@ static void release_http_action(struct act_rule *rule)
} }
} }
/* Release memory allocated by HTTP actions relying on an http reply. Concretly,
* it releases <.arg.http_reply>
*/
static void release_act_http_reply(struct act_rule *rule)
{
release_http_reply(rule->arg.http_reply);
rule->arg.http_reply = NULL;
}
/* Check function for HTTP actions relying on an http reply. The function
* returns 1 in success case, otherwise, it returns 0 and err is filled.
*/
static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
{
struct http_reply *reply = rule->arg.http_reply;
if (!http_check_http_reply(reply, px, err)) {
release_act_http_reply(rule);
return 0;
}
return 1;
}
/* This function executes one of the set-{method,path,query,uri} actions. It /* This function executes one of the set-{method,path,query,uri} actions. It
* builds a string in the trash from the specified format string. It finds * builds a string in the trash from the specified format string. It finds
@ -797,119 +821,66 @@ static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, str
return ACT_RET_PRS_OK; return ACT_RET_PRS_OK;
} }
/* Check an "http-request deny" action when an http-errors section is referenced.
*
* The function returns 1 in success case, otherwise, it returns 0 and err is
* filled.
*/
static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
{
struct http_errors *http_errs;
int status = (intptr_t)(rule->arg.act.p[0]);
int ret = 1;
list_for_each_entry(http_errs, &http_errors_list, list) {
if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
free(rule->arg.act.p[1]);
rule->arg.http_deny.status = status;
rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
if (!rule->arg.http_deny.errmsg)
ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
"not declared in http-errors section '%s'.\n",
px->id, status, http_errs->id);
break;
}
}
if (&http_errs->list == &http_errors_list) {
memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
(char *)rule->arg.act.p[1]);
free(rule->arg.act.p[1]);
ret = 0;
}
return ret;
}
/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a /* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
* response rule. It may take optional arguments to define the status code, the * response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
* error file or the http-errors section to use. It returns ACT_RET_PRS_OK on * error. It relies on http_parse_http_reply() to set
* success, ACT_RET_PRS_ERR on error. * <.arg.http_reply>.
*/ */
static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px, static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
struct act_rule *rule, char **err) struct act_rule *rule, char **err)
{ {
int default_status, status, hc, cur_arg; int default_status;
int cur_arg, arg = 0;
cur_arg = *orig_arg; cur_arg = *orig_arg;
if (rule->from == ACT_F_HTTP_REQ) { if (rule->from == ACT_F_HTTP_REQ) {
if (!strcmp(args[cur_arg-1], "tarpit")) { if (!strcmp(args[cur_arg-1], "tarpit")) {
rule->action = ACT_HTTP_REQ_TARPIT; rule->action = ACT_HTTP_REQ_TARPIT;
default_status = status = 500; default_status = 500;
} }
else { else {
rule->action = ACT_ACTION_DENY; rule->action = ACT_ACTION_DENY;
default_status = status = 403; default_status = 403;
} }
} }
else { else {
rule->action = ACT_ACTION_DENY; rule->action = ACT_ACTION_DENY;
default_status = status = 502; default_status = 502;
}
/* If no args or only a deny_status specified, fallback on the legacy
* mode and use default error files despite the fact that
* default-errorfiles is not used. Otherwise, parse an http reply.
*/
/* Prepare parsing of log-format strings */
px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
if (!*(args[cur_arg])) {
rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
goto end;
} }
rule->flags |= ACT_FLAG_FINAL;
if (strcmp(args[cur_arg], "deny_status") == 0) { if (strcmp(args[cur_arg], "deny_status") == 0) {
cur_arg++; if (!*(args[cur_arg+2]) ||
if (!*args[cur_arg]) { (strcmp(args[cur_arg+2], "errorfile") != 0 && strcmp(args[cur_arg+2], "errorfiles") != 0)) {
memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]); rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
return ACT_RET_PRS_ERR; &arg, px, default_status, err);
} *orig_arg += 2;
goto end;
status = atol(args[cur_arg]);
cur_arg++;
for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
if (http_err_codes[hc] == status)
break;
}
if (hc >= HTTP_ERR_SIZE) {
memprintf(err, "status code '%d' not handled, using default code '%d'",
status, default_status);
status = default_status;
hc = http_get_status_idx(status);
} }
args[cur_arg] += 5; /* skip "deny_" for the parsing */
} }
if (strcmp(args[cur_arg], "errorfile") == 0) { rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
cur_arg++;
if (!*args[cur_arg]) {
memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
return ACT_RET_PRS_ERR;
}
rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err); end:
if (!rule->arg.http_deny.errmsg) if (!rule->arg.http_reply)
return ACT_RET_PRS_ERR; return ACT_RET_PRS_ERR;
cur_arg++;
}
else if (strcmp(args[cur_arg], "errorfiles") == 0) {
cur_arg++;
if (!*args[cur_arg]) {
memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
return ACT_RET_PRS_ERR;
}
/* Must be resolved during the config validity check */
rule->arg.act.p[0] = (void *)((intptr_t)status);
rule->arg.act.p[1] = strdup(args[cur_arg]);
rule->check_ptr = check_http_deny_action;
cur_arg++;
goto out;
}
rule->arg.http_deny.status = status; rule->flags |= ACT_FLAG_FINAL;
rule->check_ptr = check_act_http_reply;
out: rule->release_ptr = release_act_http_reply;
*orig_arg = cur_arg;
return ACT_RET_PRS_OK; return ACT_RET_PRS_OK;
} }
@ -1802,13 +1773,6 @@ static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_ar
return ACT_RET_PRS_OK; return ACT_RET_PRS_OK;
} }
/* Release <.arg.http_reply> */
static void release_http_return(struct act_rule *rule)
{
release_http_reply(rule->arg.http_reply);
rule->arg.http_reply = NULL;
}
/* This function executes a return action. It builds an HTX message from an /* This function executes a return action. It builds an HTX message from an
* errorfile, an raw file or a log-format string, depending on <.action> * errorfile, an raw file or a log-format string, depending on <.action>
* value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is * value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
@ -1839,20 +1803,6 @@ static enum act_return http_action_return(struct act_rule *rule, struct proxy *p
return ACT_RET_ABRT; return ACT_RET_ABRT;
} }
/* Check an "http-request return" action. The function returns 1 in success
* case, otherwise, it returns 0 and err is filled.
*/
static int check_http_return_action(struct act_rule *rule, struct proxy *px, char **err)
{
struct http_reply *reply = rule->arg.http_reply;
if (!http_check_http_reply(reply, px, err)) {
release_http_return(rule);
return 0;
}
return 1;
}
/* Parse a "return" action. It returns ACT_RET_PRS_OK on success, /* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
* ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set * ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
* <.arg.http_reply>. * <.arg.http_reply>.
@ -1868,9 +1818,9 @@ static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, st
rule->flags |= ACT_FLAG_FINAL; rule->flags |= ACT_FLAG_FINAL;
rule->action = ACT_CUSTOM; rule->action = ACT_CUSTOM;
rule->check_ptr = check_http_return_action; rule->check_ptr = check_act_http_reply;
rule->action_ptr = http_action_return; rule->action_ptr = http_action_return;
rule->release_ptr = release_http_return; rule->release_ptr = release_act_http_reply;
return ACT_RET_PRS_OK; return ACT_RET_PRS_OK;
} }

View File

@ -672,6 +672,12 @@ int http_process_req_common(struct stream *s, struct channel *req, int an_bit, s
_HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1); _HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
if (sess->listener->counters) if (sess->listener->counters)
_HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1); _HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
if (txn->http_reply) {
if (http_reply_message(s, txn->http_reply) == -1)
goto return_int_err;
goto return_prx_cond;
}
goto return_prx_err; goto return_prx_err;
return_int_err: return_int_err:
@ -1002,8 +1008,28 @@ int http_process_tarpit(struct stream *s, struct channel *req, int an_bit)
*/ */
s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now);
http_reply_and_close(s, txn->status, (!(req->flags & CF_READ_ERROR) ? http_error_message(s) : NULL)); if (req->flags & CF_READ_ERROR) {
http_reply_and_close(s, txn->status, NULL);
goto end;
}
if (txn->http_reply) {
if (!http_reply_message(s, txn->http_reply))
goto end;
txn->status = 500;
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_INTERNAL;
_HA_ATOMIC_ADD(&s->sess->fe->fe_counters.internal_errors, 1);
if (s->flags & SF_BE_ASSIGNED)
_HA_ATOMIC_ADD(&s->be->be_counters.internal_errors, 1);
if (s->sess->listener->counters)
_HA_ATOMIC_ADD(&s->sess->listener->counters->internal_errors, 1);
}
http_reply_and_close(s, txn->status, http_error_message(s));
end:
req->analysers &= AN_REQ_FLT_END; req->analysers &= AN_REQ_FLT_END;
req->analyse_exp = TICK_ETERNITY; req->analyse_exp = TICK_ETERNITY;
@ -2159,6 +2185,12 @@ int http_process_res_common(struct stream *s, struct channel *rep, int an_bit, s
_HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1); _HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1);
if (objt_server(s->target)) if (objt_server(s->target))
_HA_ATOMIC_ADD(&__objt_server(s->target)->counters.denied_resp, 1); _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.denied_resp, 1);
if (txn->http_reply) {
if (http_reply_message(s, txn->http_reply) == -1)
goto return_int_err;
goto return_prx_cond;
}
goto return_prx_err; goto return_prx_err;
return_int_err: return_int_err:
@ -2907,17 +2939,15 @@ static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct lis
goto end; goto end;
case ACT_ACTION_DENY: case ACT_ACTION_DENY:
txn->status = rule->arg.http_deny.status; txn->status = rule->arg.http_reply->status;
if (rule->arg.http_deny.errmsg) txn->http_reply = rule->arg.http_reply;
txn->errmsg = rule->arg.http_deny.errmsg;
rule_ret = HTTP_RULE_RES_DENY; rule_ret = HTTP_RULE_RES_DENY;
goto end; goto end;
case ACT_HTTP_REQ_TARPIT: case ACT_HTTP_REQ_TARPIT:
txn->flags |= TX_CLTARPIT; txn->flags |= TX_CLTARPIT;
txn->status = rule->arg.http_deny.status; txn->status = rule->arg.http_reply->status;
if (rule->arg.http_deny.errmsg) txn->http_reply = rule->arg.http_reply;
txn->errmsg = rule->arg.http_deny.errmsg;
rule_ret = HTTP_RULE_RES_DENY; rule_ret = HTTP_RULE_RES_DENY;
goto end; goto end;
@ -3085,9 +3115,8 @@ static enum rule_result http_res_get_intercept_rule(struct proxy *px, struct lis
goto end; goto end;
case ACT_ACTION_DENY: case ACT_ACTION_DENY:
txn->status = rule->arg.http_deny.status; txn->status = rule->arg.http_reply->status;
if (rule->arg.http_deny.errmsg) txn->http_reply = rule->arg.http_reply;
txn->errmsg = rule->arg.http_deny.errmsg;
rule_ret = HTTP_RULE_RES_DENY; rule_ret = HTTP_RULE_RES_DENY;
goto end; goto end;
@ -4688,9 +4717,7 @@ int http_reply_message(struct stream *s, struct http_reply *reply)
/* get default error message */ /* get default error message */
errmsg = http_error_message(s); errmsg = http_error_message(s);
} }
if (b_is_null(errmsg)) if (!b_is_null(errmsg) && !channel_htx_copy_msg(res, htx, errmsg))
goto leave;
if (!channel_htx_copy_msg(res, htx, errmsg))
goto fail; goto fail;
} }
else { else {
@ -5178,6 +5205,7 @@ void http_init_txn(struct stream *s)
: 0); : 0);
txn->status = -1; txn->status = -1;
txn->errmsg = NULL; txn->errmsg = NULL;
txn->http_reply = NULL;
write_u32(txn->cache_hash, 0); write_u32(txn->cache_hash, 0);
txn->cookie_first_date = 0; txn->cookie_first_date = 0;