From: Baptiste Assmann Date: Thu, 24 Apr 2014 20:16:59 +0000 (+0200) Subject: MEDIUM: http: ACL and MAP updates through http-(request|response) rules X-Git-Tag: v1.5-dev24~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fabcbe0de63986c94c3b6c1876eff3f1386497d4;p=thirdparty%2Fhaproxy.git MEDIUM: http: ACL and MAP updates through http-(request|response) rules This patch allows manipulation of ACL and MAP content thanks to any information available in a session: source IP address, HTTP request or response header, etc... It's an update "on the fly" of the content of the map/acls. This means it does not resist to reload or restart of HAProxy. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 6bb5ffdc6e..84c4fb5d78 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2848,7 +2848,12 @@ http-check send-state http-request { allow | deny | tarpit | auth [realm ] | redirect | add-header | set-header | del-header | set-nice | set-log-level | - set-tos | set-mark } + set-tos | set-mark | + add-acl() | + del-acl() | + del-map() | + set-map() + } [ { if | unless } ] Access control for Layer 7 requests @@ -2944,6 +2949,39 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | downloads). This works on Linux kernels 2.6.32 and above and requires admin privileges. + - "add-acl" is used to add a new entry into an ACL. The ACL must be loaded + from a file (even a dummy empty file). The file name of the ACL to be + updated is passed between parentheses. It takes one argument: , + which follows log-format rules, to collect content of the new entry. It + performs a lookup in the ACL before insertion, to avoid duplicated (or + more) values. This lookup is done by a linear search and can be expensive + with large lists! It is the equivalent of the "add acl" command from the + stats socket, but can be triggered by an HTTP request. + + - "del-acl" is used to delete an entry from an ACL. The ACL must be loaded + from a file (even a dummy empty file). The file name of the ACL to be + updated is passed between parentheses. It takes one argument: , + which follows log-format rules, to collect content of the entry to delete. + It is the equivalent of the "del acl" command from the stats socket, but + can be triggered by an HTTP request. + + - "del-map" is used to delete an entry from a MAP. The MAP must be loaded + from a file (even a dummy empty file). The file name of the MAP to be + updated is passed between parentheses. It takes one argument: , + which follows log-format rules, to collect content of the entry to delete. + 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. + + - "set-map" is used to add a new entry into a MAP. The MAP must be loaded + from a file (even a dummy empty file). The file name of the MAP to be + updated is passed between parentheses. It takes 2 arguments: , + which follows log-format rules, used to collect MAP key, and , + which follows log-format rules, used to collect content for the new entry. + It performs a lookup in the MAP before insertion, to avoid duplicated (or + more) values. This lookup is done by a linear search and can be expensive + with large lists! It is the equivalent of the "set map" command from the + stats socket, but can be triggered by an HTTP request. + There is no limit to the number of http-request statements per instance. It is important to know that http-request rules are processed very early in @@ -2976,12 +3014,37 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | http-request set-header X-SSL-Client-NotBefore %{+Q}[ssl_c_notbefore] http-request set-header X-SSL-Client-NotAfter %{+Q}[ssl_c_notafter] + Example: + acl key req.hdr(X-Add-Acl-Key) -m found + acl add path /addacl + acl del path /delacl + + acl myhost hdr(Host) -f myhost.lst + + http-request add-acl(myhost.lst) %[req.hdr(X-Add-Acl-Key)] if key add + http-request del-acl(myhost.lst) %[req.hdr(X-Add-Acl-Key)] if key del + + Example: + acl value req.hdr(X-Value) -m found + acl setmap path /setmap + acl delmap path /delmap + + use_backend bk_appli if { hdr(Host),map_str(map.lst) -m found } + + http-request set-map(map.lst) %[src] %[req.hdr(X-Value)] if setmap value + http-request del-map(map.lst) %[src] if delmap + See also : "stats http-request", section 3.4 about userlists and section 7 about ACL usage. http-response { allow | deny | add-header | set-nice | set-header | del-header | - set-log-level | set-mark | set-tos } + set-log-level | set-mark | set-tos | + add-acl() | + del-acl() | + del-map() | + set-map() + } [ { if | unless } ] Access control for Layer 7 responses @@ -3054,6 +3117,39 @@ http-response { allow | deny | add-header | set-nice | downloads). This works on Linux kernels 2.6.32 and above and requires admin privileges. + - "add-acl" is used to add a new entry into an ACL. The ACL must be loaded + from a file (even a dummy empty file). The file name of the ACL to be + updated is passed between parentheses. It takes one argument: , + which follows log-format rules, to collect content of the new entry. It + performs a lookup in the ACL before insertion, to avoid duplicated (or + more) values. This lookup is done by a linear search and can be expensive + with large lists! It is the equivalent of the "add acl" command from the + stats socket, but can be triggered by an HTTP response. + + - "del-acl" is used to delete an entry from an ACL. The ACL must be loaded + from a file (even a dummy empty file). The file name of the ACL to be + updated is passed between parentheses. It takes one argument: , + which follows log-format rules, to collect content of the entry to delete. + It is the equivalent of the "del acl" command from the stats socket, but + can be triggered by an HTTP response. + + - "del-map" is used to delete an entry from a MAP. The MAP must be loaded + from a file (even a dummy empty file). The file name of the MAP to be + updated is passed between parentheses. It takes one argument: , + which follows log-format rules, to collect content of the entry to delete. + 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. + + - "set-map" is used to add a new entry into a MAP. The MAP must be loaded + from a file (even a dummy empty file). The file name of the MAP to be + updated is passed between parentheses. It takes 2 arguments: , + which follows log-format rules, used to collect MAP key, and , + which follows log-format rules, used to collect content for the new entry. + It performs a lookup in the MAP before insertion, to avoid duplicated (or + more) values. This lookup is done by a linear search and can be expensive + with large lists! It is the equivalent of the "set map" command from the + stats socket, but can be triggered by an HTTP response. + There is no limit to the number of http-response statements per instance. It is important to know that http-response rules are processed very early in @@ -3061,6 +3157,22 @@ http-response { allow | deny | add-header | set-nice | added by "add-header"/"set-header" are visible by almost all further ACL rules. + Example: + acl key_acl res.hdr(X-Acl-Key) -m found + + acl myhost hdr(Host) -f myhost.lst + + http-response add-acl(myhost.lst) %[res.hdr(X-Acl-Key)] if key_acl + http-response del-acl(myhost.lst) %[res.hdr(X-Acl-Key)] if key_acl + + Example: + acl value res.hdr(X-Value) -m found + + use_backend bk_appli if { hdr(Host),map_str(map.lst) -m found } + + http-response set-map(map.lst) %[src] %[res.hdr(X-Value)] if value + http-response del-map(map.lst) %[src] if ! value + See also : "http-request", section 3.4 about userlists and section 7 about ACL usage. diff --git a/include/types/proto_http.h b/include/types/proto_http.h index d937ce1f3c..f084ecd3bd 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -252,6 +252,10 @@ enum { HTTP_REQ_ACT_SET_LOGL, HTTP_REQ_ACT_SET_TOS, HTTP_REQ_ACT_SET_MARK, + HTTP_REQ_ACT_ADD_ACL, + HTTP_REQ_ACT_DEL_ACL, + HTTP_REQ_ACT_DEL_MAP, + HTTP_REQ_ACT_SET_MAP, HTTP_REQ_ACT_MAX /* must always be last */ }; @@ -267,6 +271,10 @@ enum { HTTP_RES_ACT_SET_LOGL, HTTP_RES_ACT_SET_TOS, HTTP_RES_ACT_SET_MARK, + HTTP_RES_ACT_ADD_ACL, + HTTP_RES_ACT_DEL_ACL, + HTTP_RES_ACT_DEL_MAP, + HTTP_RES_ACT_SET_MAP, HTTP_RES_ACT_MAX /* must always be last */ }; @@ -404,6 +412,11 @@ struct http_req_rule { int loglevel; /* log-level value for HTTP_REQ_ACT_SET_LOGL */ int tos; /* tos value for HTTP_REQ_ACT_SET_TOS */ int mark; /* nfmark value for HTTP_REQ_ACT_SET_MARK */ + struct { + char *ref; /* MAP or ACL file name to update */ + struct list key; /* pattern to retrieve MAP or ACL key */ + struct list value; /* pattern to retrieve MAP value */ + } map; } arg; /* arguments used by some actions */ }; @@ -421,6 +434,11 @@ struct http_res_rule { int loglevel; /* log-level value for HTTP_RES_ACT_SET_LOGL */ int tos; /* tos value for HTTP_RES_ACT_SET_TOS */ int mark; /* nfmark value for HTTP_RES_ACT_SET_MARK */ + struct { + char *ref; /* MAP or ACL file name to update */ + struct list key; /* pattern to retrieve MAP or ACL key */ + struct list value; /* pattern to retrieve MAP value */ + } map; } arg; /* arguments used by some actions */ }; diff --git a/src/proto_http.c b/src/proto_http.c index 3675b85aa1..e9004f8422 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -64,6 +64,7 @@ #include #include #include +#include const char HTTP_100[] = "HTTP/1.1 100 Continue\r\n\r\n"; @@ -3204,6 +3205,90 @@ http_req_get_intercept_rule(struct proxy *px, struct list *rules, struct session trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt); http_header_add_tail2(&txn->req, &txn->hdr_idx, trash.str, trash.len); break; + + case HTTP_REQ_ACT_DEL_ACL: + case HTTP_REQ_ACT_DEL_MAP: { + struct pat_ref *ref; + char *key; + int len; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* collect key */ + len = build_logline(s, trash.str, trash.size, &rule->arg.map.key); + key = trash.str; + key[len] = '\0'; + + /* perform update */ + /* returned code: 1=ok, 0=ko */ + pat_ref_delete(ref, key); + + break; + } + + case HTTP_REQ_ACT_ADD_ACL: { + struct pat_ref *ref; + char *key; + struct chunk *trash_key; + int len; + + trash_key = get_trash_chunk(); + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* collect key */ + len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); + key = trash_key->str; + key[len] = '\0'; + + /* perform update */ + /* add entry only if it does not already exist */ + if (pat_ref_find_elt(ref, key) == NULL) + pat_ref_add(ref, key, NULL, NULL); + + break; + } + + case HTTP_REQ_ACT_SET_MAP: { + struct pat_ref *ref; + char *key, *value; + struct chunk *trash_key, *trash_value; + int len; + + trash_key = get_trash_chunk(); + trash_value = get_trash_chunk(); + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* collect key */ + len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); + key = trash_key->str; + key[len] = '\0'; + + /* collect value */ + len = build_logline(s, trash_value->str, trash_value->size, &rule->arg.map.value); + value = trash_value->str; + value[len] = '\0'; + + /* perform update */ + if (pat_ref_find_elt(ref, key) != NULL) + /* update entry if it exists */ + pat_ref_set(ref, key, value, NULL); + else + /* insert a new entry */ + pat_ref_add(ref, key, value, NULL); + + break; + } } } @@ -3293,6 +3378,90 @@ http_res_get_intercept_rule(struct proxy *px, struct list *rules, struct session trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt); http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len); break; + + case HTTP_RES_ACT_DEL_ACL: + case HTTP_RES_ACT_DEL_MAP: { + struct pat_ref *ref; + char *key; + int len; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* collect key */ + len = build_logline(s, trash.str, trash.size, &rule->arg.map.key); + key = trash.str; + key[len] = '\0'; + + /* perform update */ + /* returned code: 1=ok, 0=ko */ + pat_ref_delete(ref, key); + + break; + } + + case HTTP_RES_ACT_ADD_ACL: { + struct pat_ref *ref; + char *key; + struct chunk *trash_key; + int len; + + trash_key = get_trash_chunk(); + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* collect key */ + len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); + key = trash_key->str; + key[len] = '\0'; + + /* perform update */ + /* check if the entry already exists */ + if (pat_ref_find_elt(ref, key) == NULL) + pat_ref_add(ref, key, NULL, NULL); + + break; + } + + case HTTP_RES_ACT_SET_MAP: { + struct pat_ref *ref; + char *key, *value; + struct chunk *trash_key, *trash_value; + int len; + + trash_key = get_trash_chunk(); + trash_value = get_trash_chunk(); + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* collect key */ + len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); + key = trash_key->str; + key[len] = '\0'; + + /* collect value */ + len = build_logline(s, trash_value->str, trash_value->size, &rule->arg.map.value); + value = trash_value->str; + value[len] = '\0'; + + /* perform update */ + if (pat_ref_find_elt(ref, key) != NULL) + /* update entry if it exists */ + pat_ref_set(ref, key, value, NULL); + else + /* insert a new entry */ + pat_ref_add(ref, key, value, NULL); + + break; + } } } @@ -8652,8 +8821,125 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i redir->cond = NULL; cur_arg = 2; return rule; + } else if (strncmp(args[0], "add-acl", 7) == 0) { + /* http-request add-acl() */ + rule->action = HTTP_REQ_ACT_ADD_ACL; + /* + * '+ 8' for 'add-acl(' + * '- 9' for 'add-acl(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || + (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + proxy->conf.args.ctx = ARGC_HRQ; + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + cur_arg += 1; + } else if (strncmp(args[0], "del-acl", 7) == 0) { + /* http-request del-acl() */ + rule->action = HTTP_REQ_ACT_DEL_ACL; + /* + * '+ 8' for 'del-acl(' + * '- 9' for 'del-acl(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || + (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + proxy->conf.args.ctx = ARGC_HRQ; + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + cur_arg += 1; + } else if (strncmp(args[0], "del-map", 7) == 0) { + /* http-request del-map() */ + rule->action = HTTP_REQ_ACT_DEL_MAP; + /* + * '+ 8' for 'del-map(' + * '- 9' for 'del-map(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || + (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + proxy->conf.args.ctx = ARGC_HRQ; + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + cur_arg += 1; + } else if (strncmp(args[0], "set-map", 7) == 0) { + /* http-request set-map() */ + rule->action = HTTP_REQ_ACT_SET_MAP; + /* + * '+ 8' for 'set-map(' + * '- 9' for 'set-map(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || !*args[cur_arg+1] || + (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-request %s' expects exactly 2 arguments.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + LIST_INIT(&rule->arg.map.value); + proxy->conf.args.ctx = ARGC_HRQ; + + /* key pattern */ + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, + file, linenum); + + /* value pattern */ + parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.map.value, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + + cur_arg += 2; } else { - Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', but got '%s'%s.\n", + Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', but got '%s'%s.\n", file, linenum, args[0], *args[0] ? "" : " (missing argument)"); goto out_err; } @@ -8824,8 +9110,128 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i proxy->conf.lfs_file = strdup(proxy->conf.args.file); proxy->conf.lfs_line = proxy->conf.args.line; cur_arg += 1; + } else if (strncmp(args[0], "add-acl", 7) == 0) { + /* http-request add-acl() */ + rule->action = HTTP_RES_ACT_ADD_ACL; + /* + * '+ 8' for 'add-acl(' + * '- 9' for 'add-acl(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || + (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-response %s' expects exactly 1 argument.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + proxy->conf.args.ctx = ARGC_HRS; + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + + cur_arg += 1; + } else if (strncmp(args[0], "del-acl", 7) == 0) { + /* http-response del-acl() */ + rule->action = HTTP_RES_ACT_DEL_ACL; + /* + * '+ 8' for 'del-acl(' + * '- 9' for 'del-acl(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || + (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-response %s' expects exactly 1 argument.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + proxy->conf.args.ctx = ARGC_HRS; + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + cur_arg += 1; + } else if (strncmp(args[0], "del-map", 7) == 0) { + /* http-response del-map() */ + rule->action = HTTP_RES_ACT_DEL_MAP; + /* + * '+ 8' for 'del-map(' + * '- 9' for 'del-map(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || + (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-response %s' expects exactly 1 argument.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + proxy->conf.args.ctx = ARGC_HRS; + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR, + file, linenum); + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + cur_arg += 1; + } else if (strncmp(args[0], "set-map", 7) == 0) { + /* http-response set-map() */ + rule->action = HTTP_RES_ACT_SET_MAP; + /* + * '+ 8' for 'set-map(' + * '- 9' for 'set-map(' + trailing ')' + */ + rule->arg.map.ref = strndup(args[0] + 8, strlen(args[0]) - 9); + + cur_arg = 1; + + if (!*args[cur_arg] || !*args[cur_arg+1] || + (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) { + Alert("parsing [%s:%d]: 'http-response %s' expects exactly 2 arguments.\n", + file, linenum, args[0]); + goto out_err; + } + + LIST_INIT(&rule->arg.map.key); + LIST_INIT(&rule->arg.map.value); + + proxy->conf.args.ctx = ARGC_HRS; + + /* key pattern */ + parse_logformat_string(args[cur_arg], proxy, &rule->arg.map.key, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR, + file, linenum); + + /* value pattern */ + parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.map.value, LOG_OPT_HTTP, + (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR, + file, linenum); + + free(proxy->conf.lfs_file); + proxy->conf.lfs_file = strdup(proxy->conf.args.file); + proxy->conf.lfs_line = proxy->conf.args.line; + + cur_arg += 2; } else { - Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', but got '%s'%s.\n", + Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'del-header', 'set-header', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'del-acl', 'add-acl', 'del-map', 'set-map', but got '%s'%s.\n", file, linenum, args[0], *args[0] ? "" : " (missing argument)"); goto out_err; }