1 /*
2  * "tcp" rules processing
3  *
4  * Copyright 2000-2016 Willy Tarreau <w@1wt.eu>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version
9  * 2 of the License, or (at your option) any later version.
10  *
11  */
12 #include <common/cfgparse.h>
13 #include <common/compat.h>
14 #include <common/config.h>
15 #include <common/debug.h>
16 #include <common/mini-clist.h>
17 #include <common/standard.h>
18 #include <common/ticks.h>
19 #include <common/time.h>
20 
21 #include <types/arg.h>
22 #include <types/capture.h>
23 #include <types/connection.h>
24 #include <types/global.h>
25 
26 #include <proto/acl.h>
27 #include <proto/action.h>
28 #include <proto/channel.h>
29 #include <proto/connection.h>
30 #include <proto/log.h>
31 #include <proto/proxy.h>
32 #include <proto/sample.h>
33 #include <proto/stick_table.h>
34 #include <proto/stream.h>
35 #include <proto/stream_interface.h>
36 #include <proto/tcp_rules.h>
37 
38 /* List head of all known action keywords for "tcp-request connection" */
39 struct list tcp_req_conn_keywords = LIST_HEAD_INIT(tcp_req_conn_keywords);
40 struct list tcp_req_sess_keywords = LIST_HEAD_INIT(tcp_req_sess_keywords);
41 struct list tcp_req_cont_keywords = LIST_HEAD_INIT(tcp_req_cont_keywords);
42 struct list tcp_res_cont_keywords = LIST_HEAD_INIT(tcp_res_cont_keywords);
43 
44 /*
45  * Register keywords.
46  */
tcp_req_conn_keywords_register(struct action_kw_list * kw_list)47 void tcp_req_conn_keywords_register(struct action_kw_list *kw_list)
48 {
49 	LIST_ADDQ(&tcp_req_conn_keywords, &kw_list->list);
50 }
51 
tcp_req_sess_keywords_register(struct action_kw_list * kw_list)52 void tcp_req_sess_keywords_register(struct action_kw_list *kw_list)
53 {
54 	LIST_ADDQ(&tcp_req_sess_keywords, &kw_list->list);
55 }
56 
tcp_req_cont_keywords_register(struct action_kw_list * kw_list)57 void tcp_req_cont_keywords_register(struct action_kw_list *kw_list)
58 {
59 	LIST_ADDQ(&tcp_req_cont_keywords, &kw_list->list);
60 }
61 
tcp_res_cont_keywords_register(struct action_kw_list * kw_list)62 void tcp_res_cont_keywords_register(struct action_kw_list *kw_list)
63 {
64 	LIST_ADDQ(&tcp_res_cont_keywords, &kw_list->list);
65 }
66 
67 /*
68  * Return the struct tcp_req_action_kw associated to a keyword.
69  */
tcp_req_conn_action(const char * kw)70 struct action_kw *tcp_req_conn_action(const char *kw)
71 {
72 	return action_lookup(&tcp_req_conn_keywords, kw);
73 }
74 
tcp_req_sess_action(const char * kw)75 struct action_kw *tcp_req_sess_action(const char *kw)
76 {
77 	return action_lookup(&tcp_req_sess_keywords, kw);
78 }
79 
tcp_req_cont_action(const char * kw)80 struct action_kw *tcp_req_cont_action(const char *kw)
81 {
82 	return action_lookup(&tcp_req_cont_keywords, kw);
83 }
84 
tcp_res_cont_action(const char * kw)85 struct action_kw *tcp_res_cont_action(const char *kw)
86 {
87 	return action_lookup(&tcp_res_cont_keywords, kw);
88 }
89 
90 /* This function performs the TCP request analysis on the current request. It
91  * returns 1 if the processing can continue on next analysers, or zero if it
92  * needs more data, encounters an error, or wants to immediately abort the
93  * request. It relies on buffers flags, and updates s->req->analysers. The
94  * function may be called for frontend rules and backend rules. It only relies
95  * on the backend pointer so this works for both cases.
96  */
tcp_inspect_request(struct stream * s,struct channel * req,int an_bit)97 int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit)
98 {
99 	struct session *sess = s->sess;
100 	struct act_rule *rule;
101 	struct stksess *ts;
102 	struct stktable *t;
103 	int partial;
104 	int act_flags = 0;
105 
106 	DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%d analysers=%02x\n",
107 		now_ms, __FUNCTION__,
108 		s,
109 		req,
110 		req->rex, req->wex,
111 		req->flags,
112 		req->buf->i,
113 		req->analysers);
114 
115 	/* We don't know whether we have enough data, so must proceed
116 	 * this way :
117 	 * - iterate through all rules in their declaration order
118 	 * - if one rule returns MISS, it means the inspect delay is
119 	 *   not over yet, then return immediately, otherwise consider
120 	 *   it as a non-match.
121 	 * - if one rule returns OK, then return OK
122 	 * - if one rule returns KO, then return KO
123 	 */
124 
125 	if ((req->flags & CF_SHUTR) || buffer_full(req->buf, global.tune.maxrewrite) ||
126 	    !s->be->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms))
127 		partial = SMP_OPT_FINAL;
128 	else
129 		partial = 0;
130 
131 	/* If "the current_rule_list" match the executed rule list, we are in
132 	 * resume condition. If a resume is needed it is always in the action
133 	 * and never in the ACL or converters. In this case, we initialise the
134 	 * current rule, and go to the action execution point.
135 	 */
136 	if (s->current_rule) {
137 		rule = s->current_rule;
138 		s->current_rule = NULL;
139 		if (s->current_rule_list == &s->be->tcp_req.inspect_rules)
140 			goto resume_execution;
141 	}
142 	s->current_rule_list = &s->be->tcp_req.inspect_rules;
143 
144 	list_for_each_entry(rule, &s->be->tcp_req.inspect_rules, list) {
145 		enum acl_test_res ret = ACL_TEST_PASS;
146 
147 		if (rule->cond) {
148 			ret = acl_exec_cond(rule->cond, s->be, sess, s, SMP_OPT_DIR_REQ | partial);
149 			if (ret == ACL_TEST_MISS)
150 				goto missing_data;
151 
152 			ret = acl_pass(ret);
153 			if (rule->cond->pol == ACL_COND_UNLESS)
154 				ret = !ret;
155 		}
156 
157 		if (ret) {
158 			act_flags |= ACT_FLAG_FIRST;
159 resume_execution:
160 			/* we have a matching rule. */
161 			if (rule->action == ACT_ACTION_ALLOW) {
162 				break;
163 			}
164 			else if (rule->action == ACT_ACTION_DENY) {
165 				si_must_kill_conn(chn_prod(req));
166 				channel_abort(req);
167 				channel_abort(&s->res);
168 				req->analysers = 0;
169 
170 				HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
171 				HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
172 				if (sess->listener && sess->listener->counters)
173 					HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
174 
175 				if (!(s->flags & SF_ERR_MASK))
176 					s->flags |= SF_ERR_PRXCOND;
177 				if (!(s->flags & SF_FINST_MASK))
178 					s->flags |= SF_FINST_R;
179 				return 0;
180 			}
181 			else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
182 				/* Note: only the first valid tracking parameter of each
183 				 * applies.
184 				 */
185 				struct stktable_key *key;
186 				struct sample smp;
187 
188 				if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]))
189 					continue;
190 
191 				t = rule->arg.trk_ctr.table.t;
192 				key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | partial, rule->arg.trk_ctr.expr, &smp);
193 
194 				if ((smp.flags & SMP_F_MAY_CHANGE) && !(partial & SMP_OPT_FINAL))
195 					goto missing_data; /* key might appear later */
196 
197 				if (key && (ts = stktable_get_entry(t, key))) {
198 					stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts);
199 					stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
200 					if (sess->fe != s->be)
201 						stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
202 				}
203 			}
204 			else if (rule->action == ACT_TCP_CAPTURE) {
205 				struct sample *key;
206 				struct cap_hdr *h = rule->arg.cap.hdr;
207 				char **cap = s->req_cap;
208 				int len;
209 
210 				key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ | partial, rule->arg.cap.expr, SMP_T_STR);
211 				if (!key)
212 					continue;
213 
214 				if (key->flags & SMP_F_MAY_CHANGE)
215 					goto missing_data;
216 
217 				if (cap[h->index] == NULL)
218 					cap[h->index] = pool_alloc(h->pool);
219 
220 				if (cap[h->index] == NULL) /* no more capture memory */
221 					continue;
222 
223 				len = key->data.u.str.len;
224 				if (len > h->len)
225 					len = h->len;
226 
227 				memcpy(cap[h->index], key->data.u.str.str, len);
228 				cap[h->index][len] = 0;
229 			}
230 			else {
231 				/* Custom keywords. */
232 				if (!rule->action_ptr)
233 					continue;
234 
235 				if (partial & SMP_OPT_FINAL)
236 					act_flags |= ACT_FLAG_FINAL;
237 
238 				switch (rule->action_ptr(rule, s->be, s->sess, s, act_flags)) {
239 				case ACT_RET_ERR:
240 				case ACT_RET_CONT:
241 					continue;
242 				case ACT_RET_STOP:
243 					break;
244 				case ACT_RET_YIELD:
245 					s->current_rule = rule;
246 					goto missing_data;
247 				}
248 				break; /* ACT_RET_STOP */
249 			}
250 		}
251 	}
252 
253 	/* if we get there, it means we have no rule which matches, or
254 	 * we have an explicit accept, so we apply the default accept.
255 	 */
256 	req->analysers &= ~an_bit;
257 	req->analyse_exp = TICK_ETERNITY;
258 	return 1;
259 
260  missing_data:
261 	channel_dont_connect(req);
262 	/* just set the request timeout once at the beginning of the request */
263 	if (!tick_isset(req->analyse_exp) && s->be->tcp_req.inspect_delay)
264 		req->analyse_exp = tick_add(now_ms, s->be->tcp_req.inspect_delay);
265 	return 0;
266 
267 }
268 
269 /* This function performs the TCP response analysis on the current response. It
270  * returns 1 if the processing can continue on next analysers, or zero if it
271  * needs more data, encounters an error, or wants to immediately abort the
272  * response. It relies on buffers flags, and updates s->rep->analysers. The
273  * function may be called for backend rules.
274  */
tcp_inspect_response(struct stream * s,struct channel * rep,int an_bit)275 int tcp_inspect_response(struct stream *s, struct channel *rep, int an_bit)
276 {
277 	struct session *sess = s->sess;
278 	struct act_rule *rule;
279 	int partial;
280 	int act_flags = 0;
281 
282 	DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%d analysers=%02x\n",
283 		now_ms, __FUNCTION__,
284 		s,
285 		rep,
286 		rep->rex, rep->wex,
287 		rep->flags,
288 		rep->buf->i,
289 		rep->analysers);
290 
291 	/* We don't know whether we have enough data, so must proceed
292 	 * this way :
293 	 * - iterate through all rules in their declaration order
294 	 * - if one rule returns MISS, it means the inspect delay is
295 	 *   not over yet, then return immediately, otherwise consider
296 	 *   it as a non-match.
297 	 * - if one rule returns OK, then return OK
298 	 * - if one rule returns KO, then return KO
299 	 */
300 	if ((rep->flags & CF_SHUTR) || buffer_full(rep->buf, global.tune.maxrewrite) ||
301 	    !s->be->tcp_rep.inspect_delay || tick_is_expired(rep->analyse_exp, now_ms))
302 		partial = SMP_OPT_FINAL;
303 	else
304 		partial = 0;
305 
306 	/* If "the current_rule_list" match the executed rule list, we are in
307 	 * resume condition. If a resume is needed it is always in the action
308 	 * and never in the ACL or converters. In this case, we initialise the
309 	 * current rule, and go to the action execution point.
310 	 */
311 	if (s->current_rule) {
312 		rule = s->current_rule;
313 		s->current_rule = NULL;
314 		if (s->current_rule_list == &s->be->tcp_rep.inspect_rules)
315 			goto resume_execution;
316 	}
317 	s->current_rule_list = &s->be->tcp_rep.inspect_rules;
318 
319 	list_for_each_entry(rule, &s->be->tcp_rep.inspect_rules, list) {
320 		enum acl_test_res ret = ACL_TEST_PASS;
321 
322 		if (rule->cond) {
323 			ret = acl_exec_cond(rule->cond, s->be, sess, s, SMP_OPT_DIR_RES | partial);
324 			if (ret == ACL_TEST_MISS) {
325 				/* just set the analyser timeout once at the beginning of the response */
326 				if (!tick_isset(rep->analyse_exp) && s->be->tcp_rep.inspect_delay)
327 					rep->analyse_exp = tick_add(now_ms, s->be->tcp_rep.inspect_delay);
328 				return 0;
329 			}
330 
331 			ret = acl_pass(ret);
332 			if (rule->cond->pol == ACL_COND_UNLESS)
333 				ret = !ret;
334 		}
335 
336 		if (ret) {
337 			act_flags |= ACT_FLAG_FIRST;
338 resume_execution:
339 			/* we have a matching rule. */
340 			if (rule->action == ACT_ACTION_ALLOW) {
341 				break;
342 			}
343 			else if (rule->action == ACT_ACTION_DENY) {
344 				si_must_kill_conn(chn_prod(rep));
345 				channel_abort(rep);
346 				channel_abort(&s->req);
347 				rep->analysers = 0;
348 
349 				HA_ATOMIC_ADD(&s->be->be_counters.denied_resp, 1);
350 				HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_resp, 1);
351 				if (sess->listener && sess->listener->counters)
352 					HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1);
353 
354 				if (!(s->flags & SF_ERR_MASK))
355 					s->flags |= SF_ERR_PRXCOND;
356 				if (!(s->flags & SF_FINST_MASK))
357 					s->flags |= SF_FINST_D;
358 				return 0;
359 			}
360 			else if (rule->action == ACT_TCP_CLOSE) {
361 				chn_prod(rep)->flags |= SI_FL_NOLINGER | SI_FL_NOHALF;
362 				si_must_kill_conn(chn_prod(rep));
363 				si_shutr(chn_prod(rep));
364 				si_shutw(chn_prod(rep));
365 				break;
366 			}
367 			else {
368 				/* Custom keywords. */
369 				if (!rule->action_ptr)
370 					continue;
371 
372 				if (partial & SMP_OPT_FINAL)
373 					act_flags |= ACT_FLAG_FINAL;
374 
375 				switch (rule->action_ptr(rule, s->be, s->sess, s, act_flags)) {
376 				case ACT_RET_ERR:
377 				case ACT_RET_CONT:
378 					continue;
379 				case ACT_RET_STOP:
380 					break;
381 				case ACT_RET_YIELD:
382 					channel_dont_close(rep);
383 					s->current_rule = rule;
384 					if (!tick_isset(rep->analyse_exp) && s->be->tcp_rep.inspect_delay)
385 						rep->analyse_exp = tick_add(now_ms, s->be->tcp_rep.inspect_delay);
386 					return 0;
387 				}
388 				break; /* ACT_RET_STOP */
389 			}
390 		}
391 	}
392 
393 	/* if we get there, it means we have no rule which matches, or
394 	 * we have an explicit accept, so we apply the default accept.
395 	 */
396 	rep->analysers &= ~an_bit;
397 	rep->analyse_exp = TICK_ETERNITY;
398 	return 1;
399 }
400 
401 
402 /* This function performs the TCP layer4 analysis on the current request. It
403  * returns 0 if a reject rule matches, otherwise 1 if either an accept rule
404  * matches or if no more rule matches. It can only use rules which don't need
405  * any data. This only works on connection-based client-facing stream interfaces.
406  */
tcp_exec_l4_rules(struct session * sess)407 int tcp_exec_l4_rules(struct session *sess)
408 {
409 	struct act_rule *rule;
410 	struct stksess *ts;
411 	struct stktable *t = NULL;
412 	struct connection *conn = objt_conn(sess->origin);
413 	int result = 1;
414 	enum acl_test_res ret;
415 
416 	if (!conn)
417 		return result;
418 
419 	list_for_each_entry(rule, &sess->fe->tcp_req.l4_rules, list) {
420 		ret = ACL_TEST_PASS;
421 
422 		if (rule->cond) {
423 			ret = acl_exec_cond(rule->cond, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
424 			ret = acl_pass(ret);
425 			if (rule->cond->pol == ACL_COND_UNLESS)
426 				ret = !ret;
427 		}
428 
429 		if (ret) {
430 			/* we have a matching rule. */
431 			if (rule->action == ACT_ACTION_ALLOW) {
432 				break;
433 			}
434 			else if (rule->action == ACT_ACTION_DENY) {
435 				HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_conn, 1);
436 				if (sess->listener && sess->listener->counters)
437 					HA_ATOMIC_ADD(&sess->listener->counters->denied_conn, 1);
438 
439 				result = 0;
440 				break;
441 			}
442 			else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
443 				/* Note: only the first valid tracking parameter of each
444 				 * applies.
445 				 */
446 				struct stktable_key *key;
447 
448 				if (stkctr_entry(&sess->stkctr[trk_idx(rule->action)]))
449 					continue;
450 
451 				t = rule->arg.trk_ctr.table.t;
452 				key = stktable_fetch_key(t, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
453 
454 				if (key && (ts = stktable_get_entry(t, key)))
455 					stream_track_stkctr(&sess->stkctr[trk_idx(rule->action)], t, ts);
456 			}
457 			else if (rule->action == ACT_TCP_EXPECT_PX) {
458 				conn->flags |= CO_FL_ACCEPT_PROXY;
459 				conn_sock_want_recv(conn);
460 			}
461 			else if (rule->action == ACT_TCP_EXPECT_CIP) {
462 				conn->flags |= CO_FL_ACCEPT_CIP;
463 				conn_sock_want_recv(conn);
464 			}
465 			else {
466 				/* Custom keywords. */
467 				if (!rule->action_ptr)
468 					break;
469 				switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_FLAG_FINAL | ACT_FLAG_FIRST)) {
470 				case ACT_RET_YIELD:
471 					/* yield is not allowed at this point. If this return code is
472 					 * used it is a bug, so I prefer to abort the process.
473 					 */
474 					send_log(sess->fe, LOG_WARNING,
475 					         "Internal error: yield not allowed with tcp-request connection actions.");
476 				case ACT_RET_STOP:
477 					break;
478 				case ACT_RET_CONT:
479 					continue;
480 				case ACT_RET_ERR:
481 					result = 0;
482 					break;
483 				}
484 				break; /* ACT_RET_STOP */
485 			}
486 		}
487 	}
488 	return result;
489 }
490 
491 /* This function performs the TCP layer5 analysis on the current request. It
492  * returns 0 if a reject rule matches, otherwise 1 if either an accept rule
493  * matches or if no more rule matches. It can only use rules which don't need
494  * any data. This only works on session-based client-facing stream interfaces.
495  * An example of valid use case is to track a stick-counter on the source
496  * address extracted from the proxy protocol.
497  */
tcp_exec_l5_rules(struct session * sess)498 int tcp_exec_l5_rules(struct session *sess)
499 {
500 	struct act_rule *rule;
501 	struct stksess *ts;
502 	struct stktable *t = NULL;
503 	int result = 1;
504 	enum acl_test_res ret;
505 
506 	list_for_each_entry(rule, &sess->fe->tcp_req.l5_rules, list) {
507 		ret = ACL_TEST_PASS;
508 
509 		if (rule->cond) {
510 			ret = acl_exec_cond(rule->cond, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
511 			ret = acl_pass(ret);
512 			if (rule->cond->pol == ACL_COND_UNLESS)
513 				ret = !ret;
514 		}
515 
516 		if (ret) {
517 			/* we have a matching rule. */
518 			if (rule->action == ACT_ACTION_ALLOW) {
519 				break;
520 			}
521 			else if (rule->action == ACT_ACTION_DENY) {
522 				HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_sess, 1);
523 				if (sess->listener && sess->listener->counters)
524 					HA_ATOMIC_ADD(&sess->listener->counters->denied_sess, 1);
525 
526 				result = 0;
527 				break;
528 			}
529 			else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
530 				/* Note: only the first valid tracking parameter of each
531 				 * applies.
532 				 */
533 				struct stktable_key *key;
534 
535 				if (stkctr_entry(&sess->stkctr[trk_idx(rule->action)]))
536 					continue;
537 
538 				t = rule->arg.trk_ctr.table.t;
539 				key = stktable_fetch_key(t, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
540 
541 				if (key && (ts = stktable_get_entry(t, key)))
542 					stream_track_stkctr(&sess->stkctr[trk_idx(rule->action)], t, ts);
543 			}
544 			else {
545 				/* Custom keywords. */
546 				if (!rule->action_ptr)
547 					break;
548 				switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_FLAG_FINAL | ACT_FLAG_FIRST)) {
549 				case ACT_RET_YIELD:
550 					/* yield is not allowed at this point. If this return code is
551 					 * used it is a bug, so I prefer to abort the process.
552 					 */
553 					send_log(sess->fe, LOG_WARNING,
554 					         "Internal error: yield not allowed with tcp-request session actions.");
555 				case ACT_RET_STOP:
556 					break;
557 				case ACT_RET_CONT:
558 					continue;
559 				case ACT_RET_ERR:
560 					result = 0;
561 					break;
562 				}
563 				break; /* ACT_RET_STOP */
564 			}
565 		}
566 	}
567 	return result;
568 }
569 
570 /* Parse a tcp-response rule. Return a negative value in case of failure */
tcp_parse_response_rule(char ** args,int arg,int section_type,struct proxy * curpx,struct proxy * defpx,struct act_rule * rule,char ** err,unsigned int where,const char * file,int line)571 static int tcp_parse_response_rule(char **args, int arg, int section_type,
572                                    struct proxy *curpx, struct proxy *defpx,
573                                    struct act_rule *rule, char **err,
574                                    unsigned int where,
575                                    const char *file, int line)
576 {
577 	if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
578 		memprintf(err, "%s %s is only allowed in 'backend' sections",
579 		          args[0], args[1]);
580 		return -1;
581 	}
582 
583 	if (strcmp(args[arg], "accept") == 0) {
584 		arg++;
585 		rule->action = ACT_ACTION_ALLOW;
586 	}
587 	else if (strcmp(args[arg], "reject") == 0) {
588 		arg++;
589 		rule->action = ACT_ACTION_DENY;
590 	}
591 	else if (strcmp(args[arg], "close") == 0) {
592 		arg++;
593 		rule->action = ACT_TCP_CLOSE;
594 	}
595 	else {
596 		struct action_kw *kw;
597 		kw = tcp_res_cont_action(args[arg]);
598 		if (kw) {
599 			arg++;
600 			rule->from = ACT_F_TCP_RES_CNT;
601 			rule->kw = kw;
602 			if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
603 				return -1;
604 		} else {
605 			action_build_list(&tcp_res_cont_keywords, &trash);
606 			memprintf(err,
607 			          "'%s %s' expects 'accept', 'close', 'reject', %s in %s '%s' (got '%s')",
608 			          args[0], args[1], trash.str, proxy_type_str(curpx), curpx->id, args[arg]);
609 			return -1;
610 		}
611 	}
612 
613 	if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
614 		if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
615 			memprintf(err,
616 			          "'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
617 			          args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
618 			return -1;
619 		}
620 	}
621 	else if (*args[arg]) {
622 		memprintf(err,
623 			 "'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
624 			 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
625 		return -1;
626 	}
627 	return 0;
628 }
629 
630 
631 
632 /* Parse a tcp-request rule. Return a negative value in case of failure */
tcp_parse_request_rule(char ** args,int arg,int section_type,struct proxy * curpx,struct proxy * defpx,struct act_rule * rule,char ** err,unsigned int where,const char * file,int line)633 static int tcp_parse_request_rule(char **args, int arg, int section_type,
634                                   struct proxy *curpx, struct proxy *defpx,
635                                   struct act_rule *rule, char **err,
636                                   unsigned int where, const char *file, int line)
637 {
638 	if (curpx == defpx) {
639 		memprintf(err, "%s %s is not allowed in 'defaults' sections",
640 		          args[0], args[1]);
641 		return -1;
642 	}
643 
644 	if (!strcmp(args[arg], "accept")) {
645 		arg++;
646 		rule->action = ACT_ACTION_ALLOW;
647 	}
648 	else if (!strcmp(args[arg], "reject")) {
649 		arg++;
650 		rule->action = ACT_ACTION_DENY;
651 	}
652 	else if (strcmp(args[arg], "capture") == 0) {
653 		struct sample_expr *expr;
654 		struct cap_hdr *hdr;
655 		int kw = arg;
656 		int len = 0;
657 
658 		if (!(curpx->cap & PR_CAP_FE)) {
659 			memprintf(err,
660 			          "'%s %s %s' : proxy '%s' has no frontend capability",
661 			          args[0], args[1], args[kw], curpx->id);
662 			return -1;
663 		}
664 
665 		if (!(where & SMP_VAL_FE_REQ_CNT)) {
666 			memprintf(err,
667 				  "'%s %s' is not allowed in '%s %s' rules in %s '%s'",
668 				  args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
669 			return -1;
670 		}
671 
672 		arg++;
673 
674 		curpx->conf.args.ctx = ARGC_CAP;
675 		expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
676 		if (!expr) {
677 			memprintf(err,
678 			          "'%s %s %s' : %s",
679 			          args[0], args[1], args[kw], *err);
680 			return -1;
681 		}
682 
683 		if (!(expr->fetch->val & where)) {
684 			memprintf(err,
685 			          "'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here",
686 			          args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use));
687 			release_sample_expr(expr);
688 			return -1;
689 		}
690 
691 		if (strcmp(args[arg], "len") == 0) {
692 			arg++;
693 			if (!args[arg]) {
694 				memprintf(err,
695 					  "'%s %s %s' : missing length value",
696 					  args[0], args[1], args[kw]);
697 				release_sample_expr(expr);
698 				return -1;
699 			}
700 			/* we copy the table name for now, it will be resolved later */
701 			len = atoi(args[arg]);
702 			if (len <= 0) {
703 				memprintf(err,
704 					  "'%s %s %s' : length must be > 0",
705 					  args[0], args[1], args[kw]);
706 				release_sample_expr(expr);
707 				return -1;
708 			}
709 			arg++;
710 		}
711 
712 		if (!len) {
713 			memprintf(err,
714 				  "'%s %s %s' : a positive 'len' argument is mandatory",
715 				  args[0], args[1], args[kw]);
716 			free(expr);
717 			return -1;
718 		}
719 
720 		hdr = calloc(1, sizeof(*hdr));
721 		hdr->next = curpx->req_cap;
722 		hdr->name = NULL; /* not a header capture */
723 		hdr->namelen = 0;
724 		hdr->len = len;
725 		hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
726 		hdr->index = curpx->nb_req_cap++;
727 
728 		curpx->req_cap = hdr;
729 		curpx->to_log |= LW_REQHDR;
730 
731 		/* check if we need to allocate an hdr_idx struct for HTTP parsing */
732 		curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
733 
734 		rule->arg.cap.expr = expr;
735 		rule->arg.cap.hdr = hdr;
736 		rule->action = ACT_TCP_CAPTURE;
737 	}
738 	else if (strncmp(args[arg], "track-sc", 8) == 0 &&
739 		 args[arg][9] == '\0' && args[arg][8] >= '0' &&
740 		 args[arg][8] < '0' + MAX_SESS_STKCTR) { /* track-sc 0..9 */
741 		struct sample_expr *expr;
742 		int kw = arg;
743 
744 		arg++;
745 
746 		curpx->conf.args.ctx = ARGC_TRK;
747 		expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
748 		if (!expr) {
749 			memprintf(err,
750 			          "'%s %s %s' : %s",
751 			          args[0], args[1], args[kw], *err);
752 			return -1;
753 		}
754 
755 		if (!(expr->fetch->val & where)) {
756 			memprintf(err,
757 			          "'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here",
758 			          args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use));
759 			release_sample_expr(expr);
760 			return -1;
761 		}
762 
763 		/* check if we need to allocate an hdr_idx struct for HTTP parsing */
764 		curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
765 
766 		if (strcmp(args[arg], "table") == 0) {
767 			arg++;
768 			if (!args[arg]) {
769 				memprintf(err,
770 					  "'%s %s %s' : missing table name",
771 					  args[0], args[1], args[kw]);
772 				release_sample_expr(expr);
773 				return -1;
774 			}
775 			/* we copy the table name for now, it will be resolved later */
776 			rule->arg.trk_ctr.table.n = strdup(args[arg]);
777 			arg++;
778 		}
779 		rule->arg.trk_ctr.expr = expr;
780 		rule->action = ACT_ACTION_TRK_SC0 + args[kw][8] - '0';
781 		rule->check_ptr = check_trk_action;
782 	}
783 	else if (strcmp(args[arg], "expect-proxy") == 0) {
784 		if (strcmp(args[arg+1], "layer4") != 0) {
785 			memprintf(err,
786 				  "'%s %s %s' only supports 'layer4' in %s '%s' (got '%s')",
787 				  args[0], args[1], args[arg], proxy_type_str(curpx), curpx->id, args[arg+1]);
788 			return -1;
789 		}
790 
791 		if (!(where & SMP_VAL_FE_CON_ACC)) {
792 			memprintf(err,
793 				  "'%s %s' is not allowed in '%s %s' rules in %s '%s'",
794 				  args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
795 			return -1;
796 		}
797 
798 		arg += 2;
799 		rule->action = ACT_TCP_EXPECT_PX;
800 	}
801 	else if (strcmp(args[arg], "expect-netscaler-cip") == 0) {
802 		if (strcmp(args[arg+1], "layer4") != 0) {
803 			memprintf(err,
804 				  "'%s %s %s' only supports 'layer4' in %s '%s' (got '%s')",
805 				  args[0], args[1], args[arg], proxy_type_str(curpx), curpx->id, args[arg+1]);
806 			return -1;
807 		}
808 
809 		if (!(where & SMP_VAL_FE_CON_ACC)) {
810 			memprintf(err,
811 				  "'%s %s' is not allowed in '%s %s' rules in %s '%s'",
812 				  args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
813 			return -1;
814 		}
815 
816 		arg += 2;
817 		rule->action = ACT_TCP_EXPECT_CIP;
818 	}
819 	else {
820 		struct action_kw *kw;
821 		if (where & SMP_VAL_FE_CON_ACC) {
822 			/* L4 */
823 			kw = tcp_req_conn_action(args[arg]);
824 			rule->kw = kw;
825 			rule->from = ACT_F_TCP_REQ_CON;
826 		} else if (where & SMP_VAL_FE_SES_ACC) {
827 			/* L5 */
828 			kw = tcp_req_sess_action(args[arg]);
829 			rule->kw = kw;
830 			rule->from = ACT_F_TCP_REQ_SES;
831 		} else {
832 			/* L6 */
833 			kw = tcp_req_cont_action(args[arg]);
834 			rule->kw = kw;
835 			rule->from = ACT_F_TCP_REQ_CNT;
836 		}
837 		if (kw) {
838 			arg++;
839 			if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
840 				return -1;
841 		} else {
842 			if (where & SMP_VAL_FE_CON_ACC)
843 				action_build_list(&tcp_req_conn_keywords, &trash);
844 			else if (where & SMP_VAL_FE_SES_ACC)
845 				action_build_list(&tcp_req_sess_keywords, &trash);
846 			else
847 				action_build_list(&tcp_req_cont_keywords, &trash);
848 			memprintf(err,
849 			          "'%s %s' expects 'accept', 'reject', 'capture', 'expect-proxy', 'expect-netscaler-ip', 'track-sc0' ... 'track-sc%d', %s "
850 			          "in %s '%s' (got '%s').\n",
851 			          args[0], args[1], MAX_SESS_STKCTR-1, trash.str, proxy_type_str(curpx),
852 			          curpx->id, args[arg]);
853 			return -1;
854 		}
855 	}
856 
857 	if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
858 		if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
859 			memprintf(err,
860 			          "'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
861 			          args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
862 			return -1;
863 		}
864 	}
865 	else if (*args[arg]) {
866 		memprintf(err,
867 			 "'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
868 			 args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
869 		return -1;
870 	}
871 	return 0;
872 }
873 
874 /* This function should be called to parse a line starting with the "tcp-response"
875  * keyword.
876  */
tcp_parse_tcp_rep(char ** args,int section_type,struct proxy * curpx,struct proxy * defpx,const char * file,int line,char ** err)877 static int tcp_parse_tcp_rep(char **args, int section_type, struct proxy *curpx,
878                              struct proxy *defpx, const char *file, int line,
879                              char **err)
880 {
881 	const char *ptr = NULL;
882 	unsigned int val;
883 	int warn = 0;
884 	int arg;
885 	struct act_rule *rule;
886 	unsigned int where;
887 	const struct acl *acl;
888 	const char *kw;
889 
890 	if (!*args[1]) {
891 		memprintf(err, "missing argument for '%s' in %s '%s'",
892 		          args[0], proxy_type_str(curpx), curpx->id);
893 		return -1;
894 	}
895 
896 	if (strcmp(args[1], "inspect-delay") == 0) {
897 		if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
898 			memprintf(err, "%s %s is only allowed in 'backend' sections",
899 			          args[0], args[1]);
900 			return -1;
901 		}
902 
903 		if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
904 			memprintf(err,
905 			          "'%s %s' expects a positive delay in milliseconds, in %s '%s'",
906 			          args[0], args[1], proxy_type_str(curpx), curpx->id);
907 			if (ptr)
908 				memprintf(err, "%s (unexpected character '%c')", *err, *ptr);
909 			return -1;
910 		}
911 
912 		if (curpx->tcp_rep.inspect_delay) {
913 			memprintf(err, "ignoring %s %s (was already defined) in %s '%s'",
914 			          args[0], args[1], proxy_type_str(curpx), curpx->id);
915 			return 1;
916 		}
917 		curpx->tcp_rep.inspect_delay = val;
918 		return 0;
919 	}
920 
921 	rule = calloc(1, sizeof(*rule));
922 	LIST_INIT(&rule->list);
923 	arg = 1;
924 	where = 0;
925 
926 	if (strcmp(args[1], "content") == 0) {
927 		arg++;
928 
929 		if (curpx->cap & PR_CAP_FE)
930 			where |= SMP_VAL_FE_RES_CNT;
931 		if (curpx->cap & PR_CAP_BE)
932 			where |= SMP_VAL_BE_RES_CNT;
933 
934 		if (tcp_parse_response_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
935 			goto error;
936 
937 		acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
938 		if (acl) {
939 			if (acl->name && *acl->name)
940 				memprintf(err,
941 					  "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
942 					  acl->name, args[0], args[1], sample_ckp_names(where));
943 			else
944 				memprintf(err,
945 					  "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
946 					  args[0], args[1],
947 					  LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
948 					  sample_ckp_names(where));
949 
950 			warn++;
951 		}
952 		else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
953 			if (acl->name && *acl->name)
954 				memprintf(err,
955 					  "acl '%s' involves keyword '%s' which is incompatible with '%s'",
956 					  acl->name, kw, sample_ckp_names(where));
957 			else
958 				memprintf(err,
959 					  "anonymous acl involves keyword '%s' which is incompatible with '%s'",
960 					  kw, sample_ckp_names(where));
961 			warn++;
962 		}
963 
964 		LIST_ADDQ(&curpx->tcp_rep.inspect_rules, &rule->list);
965 	}
966 	else {
967 		memprintf(err,
968 		          "'%s' expects 'inspect-delay' or 'content' in %s '%s' (got '%s')",
969 		          args[0], proxy_type_str(curpx), curpx->id, args[1]);
970 		goto error;
971 	}
972 
973 	return warn;
974  error:
975 	free(rule);
976 	return -1;
977 }
978 
979 
980 /* This function should be called to parse a line starting with the "tcp-request"
981  * keyword.
982  */
tcp_parse_tcp_req(char ** args,int section_type,struct proxy * curpx,struct proxy * defpx,const char * file,int line,char ** err)983 static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
984                              struct proxy *defpx, const char *file, int line,
985                              char **err)
986 {
987 	const char *ptr = NULL;
988 	unsigned int val;
989 	int warn = 0;
990 	int arg;
991 	struct act_rule *rule;
992 	unsigned int where;
993 	const struct acl *acl;
994 	const char *kw;
995 
996 	if (!*args[1]) {
997 		if (curpx == defpx)
998 			memprintf(err, "missing argument for '%s' in defaults section", args[0]);
999 		else
1000 			memprintf(err, "missing argument for '%s' in %s '%s'",
1001 			          args[0], proxy_type_str(curpx), curpx->id);
1002 		return -1;
1003 	}
1004 
1005 	if (!strcmp(args[1], "inspect-delay")) {
1006 		if (curpx == defpx) {
1007 			memprintf(err, "%s %s is not allowed in 'defaults' sections",
1008 			          args[0], args[1]);
1009 			return -1;
1010 		}
1011 
1012 		if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
1013 			memprintf(err,
1014 			          "'%s %s' expects a positive delay in milliseconds, in %s '%s'",
1015 			          args[0], args[1], proxy_type_str(curpx), curpx->id);
1016 			if (ptr)
1017 				memprintf(err, "%s (unexpected character '%c')", *err, *ptr);
1018 			return -1;
1019 		}
1020 
1021 		if (curpx->tcp_req.inspect_delay) {
1022 			memprintf(err, "ignoring %s %s (was already defined) in %s '%s'",
1023 			          args[0], args[1], proxy_type_str(curpx), curpx->id);
1024 			return 1;
1025 		}
1026 		curpx->tcp_req.inspect_delay = val;
1027 		return 0;
1028 	}
1029 
1030 	rule = calloc(1, sizeof(*rule));
1031 	LIST_INIT(&rule->list);
1032 	arg = 1;
1033 	where = 0;
1034 
1035 	if (strcmp(args[1], "content") == 0) {
1036 		arg++;
1037 
1038 		if (curpx->cap & PR_CAP_FE)
1039 			where |= SMP_VAL_FE_REQ_CNT;
1040 		if (curpx->cap & PR_CAP_BE)
1041 			where |= SMP_VAL_BE_REQ_CNT;
1042 
1043 		if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
1044 			goto error;
1045 
1046 		acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
1047 		if (acl) {
1048 			if (acl->name && *acl->name)
1049 				memprintf(err,
1050 					  "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
1051 					  acl->name, args[0], args[1], sample_ckp_names(where));
1052 			else
1053 				memprintf(err,
1054 					  "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
1055 					  args[0], args[1],
1056 					  LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
1057 					  sample_ckp_names(where));
1058 
1059 			warn++;
1060 		}
1061 		else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
1062 			if (acl->name && *acl->name)
1063 				memprintf(err,
1064 					  "acl '%s' involves keyword '%s' which is incompatible with '%s'",
1065 					  acl->name, kw, sample_ckp_names(where));
1066 			else
1067 				memprintf(err,
1068 					  "anonymous acl involves keyword '%s' which is incompatible with '%s'",
1069 					  kw, sample_ckp_names(where));
1070 			warn++;
1071 		}
1072 
1073 		/* the following function directly emits the warning */
1074 		warnif_misplaced_tcp_cont(curpx, file, line, args[0]);
1075 		LIST_ADDQ(&curpx->tcp_req.inspect_rules, &rule->list);
1076 	}
1077 	else if (strcmp(args[1], "connection") == 0) {
1078 		arg++;
1079 
1080 		if (!(curpx->cap & PR_CAP_FE)) {
1081 			memprintf(err, "%s %s is not allowed because %s %s is not a frontend",
1082 			          args[0], args[1], proxy_type_str(curpx), curpx->id);
1083 			goto error;
1084 		}
1085 
1086 		where |= SMP_VAL_FE_CON_ACC;
1087 
1088 		if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
1089 			goto error;
1090 
1091 		acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
1092 		if (acl) {
1093 			if (acl->name && *acl->name)
1094 				memprintf(err,
1095 					  "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
1096 					  acl->name, args[0], args[1], sample_ckp_names(where));
1097 			else
1098 				memprintf(err,
1099 					  "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
1100 					  args[0], args[1],
1101 					  LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
1102 					  sample_ckp_names(where));
1103 
1104 			warn++;
1105 		}
1106 		else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
1107 			if (acl->name && *acl->name)
1108 				memprintf(err,
1109 					  "acl '%s' involves keyword '%s' which is incompatible with '%s'",
1110 					  acl->name, kw, sample_ckp_names(where));
1111 			else
1112 				memprintf(err,
1113 					  "anonymous acl involves keyword '%s' which is incompatible with '%s'",
1114 					  kw, sample_ckp_names(where));
1115 			warn++;
1116 		}
1117 
1118 		/* the following function directly emits the warning */
1119 		warnif_misplaced_tcp_conn(curpx, file, line, args[0]);
1120 		LIST_ADDQ(&curpx->tcp_req.l4_rules, &rule->list);
1121 	}
1122 	else if (strcmp(args[1], "session") == 0) {
1123 		arg++;
1124 
1125 		if (!(curpx->cap & PR_CAP_FE)) {
1126 			memprintf(err, "%s %s is not allowed because %s %s is not a frontend",
1127 			          args[0], args[1], proxy_type_str(curpx), curpx->id);
1128 			goto error;
1129 		}
1130 
1131 		where |= SMP_VAL_FE_SES_ACC;
1132 
1133 		if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
1134 			goto error;
1135 
1136 		acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
1137 		if (acl) {
1138 			if (acl->name && *acl->name)
1139 				memprintf(err,
1140 					  "acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
1141 					  acl->name, args[0], args[1], sample_ckp_names(where));
1142 			else
1143 				memprintf(err,
1144 					  "anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
1145 					  args[0], args[1],
1146 					  LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
1147 					  sample_ckp_names(where));
1148 			warn++;
1149 		}
1150 		else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
1151 			if (acl->name && *acl->name)
1152 				memprintf(err,
1153 					  "acl '%s' involves keyword '%s' which is incompatible with '%s'",
1154 					  acl->name, kw, sample_ckp_names(where));
1155 			else
1156 				memprintf(err,
1157 					  "anonymous acl involves keyword '%s' which is incompatible with '%s'",
1158 					  kw, sample_ckp_names(where));
1159 			warn++;
1160 		}
1161 
1162 		/* the following function directly emits the warning */
1163 		warnif_misplaced_tcp_sess(curpx, file, line, args[0]);
1164 		LIST_ADDQ(&curpx->tcp_req.l5_rules, &rule->list);
1165 	}
1166 	else {
1167 		if (curpx == defpx)
1168 			memprintf(err,
1169 			          "'%s' expects 'inspect-delay', 'connection', or 'content' in defaults section (got '%s')",
1170 			          args[0], args[1]);
1171 		else
1172 			memprintf(err,
1173 			          "'%s' expects 'inspect-delay', 'connection', or 'content' in %s '%s' (got '%s')",
1174 			          args[0], proxy_type_str(curpx), curpx->id, args[1]);
1175 		goto error;
1176 	}
1177 
1178 	return warn;
1179  error:
1180 	free(rule);
1181 	return -1;
1182 }
1183 
1184 static struct cfg_kw_list cfg_kws = {ILH, {
1185 	{ CFG_LISTEN, "tcp-request",  tcp_parse_tcp_req },
1186 	{ CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
1187 	{ 0, NULL, NULL },
1188 }};
1189 
1190 
1191 __attribute__((constructor))
__tcp_protocol_init(void)1192 static void __tcp_protocol_init(void)
1193 {
1194 	cfg_register_keywords(&cfg_kws);
1195 }
1196 
1197 /*
1198  * Local variables:
1199  *  c-indent-level: 8
1200  *  c-basic-offset: 8
1201  * End:
1202  */
1203