/src/dovecot/src/lib/event-filter.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "llist.h" |
6 | | #include "str.h" |
7 | | #include "strescape.h" |
8 | | #include "wildcard-match.h" |
9 | | #include "lib-event-private.h" |
10 | | #include "event-filter.h" |
11 | | #include "event-filter-private.h" |
12 | | |
13 | | enum event_filter_code { |
14 | | EVENT_FILTER_CODE_NAME = 'n', |
15 | | EVENT_FILTER_CODE_SOURCE = 's', |
16 | | EVENT_FILTER_CODE_CATEGORY = 'c', |
17 | | EVENT_FILTER_CODE_FIELD = 'f', |
18 | | }; |
19 | | |
20 | | enum cmp_flags { |
21 | | CMP_FLAG_WILDCARD = BIT(1), |
22 | | CMP_FLAG_CASE_SENSITIVE = BIT(2), |
23 | | }; |
24 | | |
25 | | /* map <log type> to <event filter log type & name> */ |
26 | | static const struct log_type_map { |
27 | | enum event_filter_log_type log_type; |
28 | | const char *name; |
29 | | } event_filter_log_type_map[] = { |
30 | | [LOG_TYPE_DEBUG] = { EVENT_FILTER_LOG_TYPE_DEBUG, "debug" }, |
31 | | [LOG_TYPE_INFO] = { EVENT_FILTER_LOG_TYPE_INFO, "info" }, |
32 | | [LOG_TYPE_WARNING] = { EVENT_FILTER_LOG_TYPE_WARNING, "warning" }, |
33 | | [LOG_TYPE_ERROR] = { EVENT_FILTER_LOG_TYPE_ERROR, "error" }, |
34 | | [LOG_TYPE_FATAL] = { EVENT_FILTER_LOG_TYPE_FATAL, "fatal" }, |
35 | | [LOG_TYPE_PANIC] = { EVENT_FILTER_LOG_TYPE_PANIC, "panic" }, |
36 | | }; |
37 | | static_assert_array_size(event_filter_log_type_map, LOG_TYPE_COUNT); |
38 | | |
39 | | struct event_filter_query_internal { |
40 | | struct event_filter_node *expr; |
41 | | void *context; |
42 | | }; |
43 | | |
44 | | static struct event_filter *event_filters = NULL; |
45 | | |
46 | | static struct event_filter *event_filter_create_real(pool_t pool, bool fragment) |
47 | 0 | { |
48 | 0 | struct event_filter *filter; |
49 | |
|
50 | 0 | filter = p_new(pool, struct event_filter, 1); |
51 | 0 | filter->pool = pool; |
52 | 0 | filter->refcount = 1; |
53 | 0 | filter->named_queries_only = TRUE; |
54 | 0 | filter->fragment = fragment; |
55 | 0 | p_array_init(&filter->queries, pool, 4); |
56 | 0 | if (!fragment) |
57 | 0 | DLLIST_PREPEND(&event_filters, filter); |
58 | 0 | return filter; |
59 | 0 | } |
60 | | |
61 | | struct event_filter *event_filter_create(void) |
62 | 0 | { |
63 | 0 | return event_filter_create_real(pool_alloconly_create("event filter", 2048), FALSE); |
64 | 0 | } |
65 | | |
66 | | struct event_filter *event_filter_create_with_pool(pool_t pool) |
67 | 0 | { |
68 | 0 | return event_filter_create_real(pool, FALSE); |
69 | 0 | } |
70 | | |
71 | | struct event_filter *event_filter_create_fragment(pool_t pool) |
72 | 0 | { |
73 | 0 | return event_filter_create_real(pool, TRUE); |
74 | 0 | } |
75 | | |
76 | | void event_filter_ref(struct event_filter *filter) |
77 | 0 | { |
78 | 0 | i_assert(filter->refcount > 0); |
79 | 0 | filter->refcount++; |
80 | 0 | } |
81 | | |
82 | | void event_filter_unref(struct event_filter **_filter) |
83 | 0 | { |
84 | 0 | struct event_filter *filter = *_filter; |
85 | |
|
86 | 0 | if (filter == NULL) |
87 | 0 | return; |
88 | 0 | i_assert(filter->refcount > 0); |
89 | | |
90 | 0 | *_filter = NULL; |
91 | 0 | if (--filter->refcount > 0) |
92 | 0 | return; |
93 | | |
94 | 0 | if (!filter->fragment) { |
95 | 0 | DLLIST_REMOVE(&event_filters, filter); |
96 | | |
97 | | /* fragments' pools are freed by the consumer */ |
98 | 0 | pool_unref(&filter->pool); |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | static const char * |
103 | | wanted_field_value_str(const struct event_field *wanted_field) |
104 | 0 | { |
105 | 0 | switch (wanted_field->value_type) { |
106 | 0 | case EVENT_FIELD_VALUE_TYPE_STR: |
107 | 0 | return wanted_field->value.str; |
108 | 0 | case EVENT_FIELD_VALUE_TYPE_INTMAX: |
109 | 0 | return dec2str(wanted_field->value.intmax); |
110 | 0 | case EVENT_FIELD_VALUE_TYPE_IP: { |
111 | 0 | const char *str = net_ip2addr(&wanted_field->value.ip); |
112 | 0 | if (wanted_field->value.ip_bits == |
113 | 0 | IPADDR_BITS(&wanted_field->value.ip)) |
114 | 0 | return str; |
115 | 0 | return t_strdup_printf("%s/%u", str, wanted_field->value.ip_bits); |
116 | 0 | } |
117 | 0 | case EVENT_FIELD_VALUE_TYPE_TIMEVAL: |
118 | 0 | case EVENT_FIELD_VALUE_TYPE_STRLIST: |
119 | 0 | break; |
120 | 0 | } |
121 | 0 | i_unreached(); |
122 | 0 | } |
123 | | |
124 | | /* |
125 | | * Look for an existing query with the same context pointer and return it. |
126 | | * |
127 | | * If not found, allocate a new internal query and return it. |
128 | | */ |
129 | | static struct event_filter_query_internal * |
130 | | event_filter_get_or_alloc_internal_query(struct event_filter *filter, |
131 | | void *context) |
132 | 0 | { |
133 | 0 | struct event_filter_query_internal *query; |
134 | |
|
135 | 0 | array_foreach_modifiable(&filter->queries, query) { |
136 | 0 | if (query->context == context) |
137 | 0 | return query; |
138 | 0 | } |
139 | | |
140 | | /* no matching context, allocate a new query */ |
141 | 0 | query = array_append_space(&filter->queries); |
142 | 0 | query->context = context; |
143 | 0 | query->expr = NULL; |
144 | |
|
145 | 0 | return query; |
146 | 0 | } |
147 | | |
148 | | static void add_node(pool_t pool, struct event_filter_node **root, |
149 | | struct event_filter_node *new, |
150 | | enum event_filter_node_op op) |
151 | 0 | { |
152 | 0 | struct event_filter_node *parent; |
153 | |
|
154 | 0 | i_assert((op == EVENT_FILTER_OP_AND) || (op == EVENT_FILTER_OP_OR)); |
155 | | |
156 | 0 | if (*root == NULL) { |
157 | 0 | *root = new; |
158 | 0 | return; |
159 | 0 | } |
160 | | |
161 | 0 | parent = p_new(pool, struct event_filter_node, 1); |
162 | 0 | parent->type = EVENT_FILTER_NODE_TYPE_LOGIC; |
163 | 0 | parent->op = op; |
164 | 0 | parent->children[0] = *root; |
165 | 0 | parent->children[1] = new; |
166 | |
|
167 | 0 | *root = parent; |
168 | 0 | } |
169 | | |
170 | | static bool filter_node_requires_event_name(struct event_filter_node *node) |
171 | 0 | { |
172 | 0 | switch (node->op) { |
173 | 0 | case EVENT_FILTER_OP_NOT: |
174 | 0 | return filter_node_requires_event_name(node->children[0]); |
175 | 0 | case EVENT_FILTER_OP_AND: |
176 | 0 | return filter_node_requires_event_name(node->children[0]) || |
177 | 0 | filter_node_requires_event_name(node->children[1]); |
178 | 0 | case EVENT_FILTER_OP_OR: |
179 | 0 | return filter_node_requires_event_name(node->children[0]) && |
180 | 0 | filter_node_requires_event_name(node->children[1]); |
181 | 0 | default: |
182 | 0 | return node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD || |
183 | 0 | node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT; |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | | static int |
188 | | event_filter_parse_real(const char *str, struct event_filter *filter, |
189 | | bool case_sensitive, const char **error_r) |
190 | 0 | { |
191 | 0 | struct event_filter_query_internal *int_query; |
192 | 0 | struct event_filter_parser_state state; |
193 | 0 | int ret; |
194 | |
|
195 | 0 | i_zero(&state); |
196 | 0 | state.input = str; |
197 | 0 | state.len = strlen(str); |
198 | 0 | state.pos = 0; |
199 | 0 | state.pool = filter->pool; |
200 | 0 | state.case_sensitive = case_sensitive; |
201 | |
|
202 | 0 | event_filter_parser_lex_init(&state.scanner); |
203 | 0 | event_filter_parser_set_extra(&state, state.scanner); |
204 | |
|
205 | 0 | ret = event_filter_parser_parse(&state); |
206 | |
|
207 | 0 | event_filter_parser_lex_destroy(state.scanner); |
208 | |
|
209 | 0 | if ((ret == 0) && (state.output != NULL)) { |
210 | | /* success - non-NULL expression */ |
211 | 0 | i_assert(state.error == NULL); |
212 | | |
213 | 0 | int_query = event_filter_get_or_alloc_internal_query(filter, NULL); |
214 | |
|
215 | 0 | add_node(filter->pool, &int_query->expr, state.output, |
216 | 0 | EVENT_FILTER_OP_OR); |
217 | |
|
218 | 0 | filter->named_queries_only = filter->named_queries_only && |
219 | 0 | filter_node_requires_event_name(state.output); |
220 | 0 | } else if (ret != 0) { |
221 | | /* error */ |
222 | 0 | i_assert(state.error != NULL); |
223 | | |
224 | 0 | *error_r = state.error; |
225 | 0 | } |
226 | | |
227 | | /* |
228 | | * Note that success with a NULL expression output is possible, but |
229 | | * turns into a no-op. |
230 | | */ |
231 | | |
232 | 0 | return (ret != 0) ? -1 : 0; |
233 | 0 | } |
234 | | |
235 | | int event_filter_parse(const char *str, struct event_filter *filter, |
236 | | const char **error_r) |
237 | 0 | { |
238 | 0 | int ret; |
239 | 0 | T_BEGIN { |
240 | 0 | ret = event_filter_parse_real(str, filter, FALSE, error_r); |
241 | 0 | } T_END_PASS_STR_IF(ret < 0, error_r); |
242 | 0 | return ret; |
243 | 0 | } |
244 | | |
245 | | int event_filter_parse_case_sensitive(const char *str, |
246 | | struct event_filter *filter, |
247 | | const char **error_r) |
248 | 0 | { |
249 | 0 | int ret; |
250 | 0 | T_BEGIN { |
251 | 0 | ret = event_filter_parse_real(str, filter, TRUE, error_r); |
252 | 0 | } T_END_PASS_STR_IF(ret < 0, error_r); |
253 | 0 | return ret; |
254 | 0 | } |
255 | | |
256 | | static const char * |
257 | | event_filter_node_find_field_exact(struct event_filter_node *node, |
258 | | const char *key, bool op_not, bool *op_not_r) |
259 | 0 | { |
260 | 0 | const char *value; |
261 | |
|
262 | 0 | switch (node->op) { |
263 | 0 | case EVENT_FILTER_OP_CMP_EQ: |
264 | 0 | if (node->type == EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT && |
265 | 0 | strcmp(node->field.key, key) == 0 && |
266 | 0 | node->field.value_type == EVENT_FIELD_VALUE_TYPE_STR) { |
267 | 0 | *op_not_r = op_not; |
268 | 0 | return node->field.value.str; |
269 | 0 | } |
270 | 0 | break; |
271 | 0 | case EVENT_FILTER_OP_AND: |
272 | 0 | case EVENT_FILTER_OP_OR: |
273 | 0 | value = event_filter_node_find_field_exact(node->children[0], |
274 | 0 | key, op_not, op_not_r); |
275 | 0 | if (value != NULL) |
276 | 0 | return value; |
277 | 0 | value = event_filter_node_find_field_exact(node->children[1], |
278 | 0 | key, op_not, op_not_r); |
279 | 0 | if (value != NULL) |
280 | 0 | return value; |
281 | 0 | break; |
282 | 0 | case EVENT_FILTER_OP_NOT: |
283 | 0 | return event_filter_node_find_field_exact(node->children[0], |
284 | 0 | key, !op_not, op_not_r); |
285 | 0 | default: |
286 | 0 | break; |
287 | 0 | } |
288 | 0 | return NULL; |
289 | 0 | } |
290 | | |
291 | | const char *event_filter_find_field_exact(struct event_filter *filter, |
292 | | const char *key, bool *op_not_r) |
293 | 0 | { |
294 | 0 | const struct event_filter_query_internal *query; |
295 | 0 | const char *value; |
296 | |
|
297 | 0 | array_foreach(&filter->queries, query) { |
298 | 0 | value = event_filter_node_find_field_exact(query->expr, key, FALSE, op_not_r); |
299 | 0 | if (value != NULL) |
300 | 0 | return value; |
301 | 0 | } |
302 | 0 | return NULL; |
303 | 0 | } |
304 | | |
305 | | bool event_filter_category_to_log_type(const char *name, |
306 | | enum event_filter_log_type *log_type_r) |
307 | 0 | { |
308 | 0 | unsigned int i; |
309 | |
|
310 | 0 | for (i = 0; i < N_ELEMENTS(event_filter_log_type_map); i++) { |
311 | 0 | if (strcmp(name, event_filter_log_type_map[i].name) == 0) { |
312 | 0 | *log_type_r = event_filter_log_type_map[i].log_type; |
313 | 0 | return TRUE; |
314 | 0 | } |
315 | 0 | } |
316 | 0 | return FALSE; |
317 | 0 | } |
318 | | |
319 | | const char * |
320 | | event_filter_category_from_log_type(enum event_filter_log_type log_type) |
321 | 0 | { |
322 | 0 | unsigned int i; |
323 | |
|
324 | 0 | for (i = 0; i < N_ELEMENTS(event_filter_log_type_map); i++) { |
325 | 0 | if (event_filter_log_type_map[i].log_type == log_type) |
326 | 0 | return event_filter_log_type_map[i].name; |
327 | 0 | } |
328 | 0 | i_unreached(); |
329 | 0 | } |
330 | | |
331 | | static struct event_filter_node * |
332 | | clone_expr(pool_t pool, struct event_filter_node *old) |
333 | 0 | { |
334 | 0 | struct event_filter_node *new; |
335 | |
|
336 | 0 | if (old == NULL) |
337 | 0 | return NULL; |
338 | | |
339 | 0 | new = p_new(pool, struct event_filter_node, 1); |
340 | 0 | new->type = old->type; |
341 | 0 | new->op = old->op; |
342 | 0 | switch (old->type) { |
343 | 0 | case EVENT_FILTER_NODE_TYPE_LOGIC: |
344 | 0 | new->children[0] = clone_expr(pool, old->children[0]); |
345 | 0 | new->children[1] = clone_expr(pool, old->children[1]); |
346 | 0 | break; |
347 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: |
348 | 0 | new->category.log_type = old->category.log_type; |
349 | 0 | new->category.name = p_strdup(pool, old->category.name); |
350 | 0 | new->category.ptr = old->category.ptr; |
351 | 0 | break; |
352 | 0 | default: |
353 | 0 | new->field.key = p_strdup(pool, old->field.key); |
354 | 0 | new->field.value_type = old->field.value_type; |
355 | |
|
356 | 0 | switch (old->field.value_type) { |
357 | 0 | case EVENT_FIELD_VALUE_TYPE_STR: |
358 | 0 | new->field.value.str = p_strdup(pool, old->field.value.str); |
359 | 0 | break; |
360 | 0 | case EVENT_FIELD_VALUE_TYPE_INTMAX: |
361 | 0 | new->field.value.intmax = old->field.value.intmax; |
362 | 0 | break; |
363 | 0 | case EVENT_FIELD_VALUE_TYPE_TIMEVAL: |
364 | 0 | new->field.value.timeval = old->field.value.timeval; |
365 | 0 | break; |
366 | 0 | case EVENT_FIELD_VALUE_TYPE_IP: |
367 | 0 | new->field.value.ip = old->field.value.ip; |
368 | 0 | new->field.value.ip_bits = old->field.value.ip_bits; |
369 | 0 | break; |
370 | 0 | case EVENT_FIELD_VALUE_TYPE_STRLIST: |
371 | 0 | if (array_is_created(&old->field.value.strlist)) { |
372 | 0 | const char *str; |
373 | 0 | p_array_init(&new->field.value.strlist, pool, |
374 | 0 | array_count(&old->field.value.strlist)); |
375 | 0 | array_foreach_elem(&old->field.value.strlist, str) { |
376 | 0 | str = p_strdup(pool, str); |
377 | 0 | array_push_back(&new->field.value.strlist, &str); |
378 | 0 | } |
379 | 0 | } |
380 | 0 | break; |
381 | 0 | } |
382 | 0 | } |
383 | 0 | new->ambiguous_unit = old->ambiguous_unit; |
384 | 0 | new->warned_ambiguous_unit = old->warned_ambiguous_unit; |
385 | 0 | new->warned_type_mismatch = old->warned_type_mismatch; |
386 | 0 | new->warned_string_inequality = old->warned_string_inequality; |
387 | 0 | new->warned_timeval_not_implemented = old->warned_timeval_not_implemented; |
388 | 0 | new->case_sensitive = old->case_sensitive; |
389 | |
|
390 | 0 | return new; |
391 | 0 | } |
392 | | |
393 | | static void |
394 | | event_filter_merge_with_context_internal(struct event_filter *dest, |
395 | | const struct event_filter *src, |
396 | | void *new_context, bool with_context) |
397 | 0 | { |
398 | 0 | const struct event_filter_query_internal *int_query; |
399 | |
|
400 | 0 | array_foreach(&src->queries, int_query) T_BEGIN { |
401 | 0 | void *context = with_context ? new_context : int_query->context; |
402 | 0 | struct event_filter_query_internal *new; |
403 | |
|
404 | 0 | new = event_filter_get_or_alloc_internal_query(dest, context); |
405 | |
|
406 | 0 | add_node(dest->pool, &new->expr, |
407 | 0 | clone_expr(dest->pool, int_query->expr), |
408 | 0 | EVENT_FILTER_OP_OR); |
409 | 0 | dest->named_queries_only = dest->named_queries_only && |
410 | 0 | filter_node_requires_event_name(int_query->expr); |
411 | 0 | } T_END; |
412 | 0 | } |
413 | | |
414 | | bool event_filter_remove_queries_with_context(struct event_filter *filter, |
415 | | void *context) |
416 | 0 | { |
417 | 0 | const struct event_filter_query_internal *int_query; |
418 | 0 | unsigned int idx; |
419 | |
|
420 | 0 | array_foreach(&filter->queries, int_query) { |
421 | 0 | if (int_query->context == context) { |
422 | 0 | idx = array_foreach_idx(&filter->queries, int_query); |
423 | 0 | array_delete(&filter->queries, idx, 1); |
424 | 0 | return TRUE; |
425 | 0 | } |
426 | 0 | } |
427 | 0 | return FALSE; |
428 | 0 | } |
429 | | |
430 | | void event_filter_merge(struct event_filter *dest, |
431 | | const struct event_filter *src) |
432 | 0 | { |
433 | 0 | event_filter_merge_with_context_internal(dest, src, NULL, FALSE); |
434 | 0 | } |
435 | | |
436 | | void event_filter_merge_with_context(struct event_filter *dest, |
437 | | const struct event_filter *src, |
438 | | void *new_context) |
439 | 0 | { |
440 | 0 | event_filter_merge_with_context_internal(dest, src, new_context, TRUE); |
441 | 0 | } |
442 | | |
443 | | static void |
444 | | event_filter_append_escaped(string_t *dest, const char *src, bool wildcard) |
445 | 0 | { |
446 | 0 | if (!wildcard) |
447 | 0 | str_append_escaped(dest, src, strlen(src)); |
448 | 0 | else { |
449 | | /* src is already escaped */ |
450 | 0 | str_append(dest, src); |
451 | 0 | } |
452 | 0 | } |
453 | | |
454 | | static const char * |
455 | | event_filter_export_query_expr_op(enum event_filter_node_op op) |
456 | 0 | { |
457 | 0 | switch (op) { |
458 | 0 | case EVENT_FILTER_OP_AND: |
459 | 0 | case EVENT_FILTER_OP_OR: |
460 | 0 | case EVENT_FILTER_OP_NOT: |
461 | 0 | i_unreached(); |
462 | 0 | case EVENT_FILTER_OP_CMP_EQ: |
463 | 0 | return "="; |
464 | 0 | case EVENT_FILTER_OP_CMP_GT: |
465 | 0 | return ">"; |
466 | 0 | case EVENT_FILTER_OP_CMP_LT: |
467 | 0 | return "<"; |
468 | 0 | case EVENT_FILTER_OP_CMP_GE: |
469 | 0 | return ">="; |
470 | 0 | case EVENT_FILTER_OP_CMP_LE: |
471 | 0 | return "<="; |
472 | 0 | } |
473 | | |
474 | 0 | i_unreached(); |
475 | 0 | } |
476 | | |
477 | | static void |
478 | | event_filter_export_query_expr(const struct event_filter_query_internal *query, |
479 | | struct event_filter_node *node, |
480 | | string_t *dest) |
481 | 0 | { |
482 | 0 | switch (node->type) { |
483 | 0 | case EVENT_FILTER_NODE_TYPE_LOGIC: |
484 | 0 | str_append_c(dest, '('); |
485 | 0 | switch (node->op) { |
486 | 0 | case EVENT_FILTER_OP_AND: |
487 | 0 | event_filter_export_query_expr(query, node->children[0], dest); |
488 | 0 | str_append(dest, " AND "); |
489 | 0 | event_filter_export_query_expr(query, node->children[1], dest); |
490 | 0 | break; |
491 | 0 | case EVENT_FILTER_OP_OR: |
492 | 0 | event_filter_export_query_expr(query, node->children[0], dest); |
493 | 0 | str_append(dest, " OR "); |
494 | 0 | event_filter_export_query_expr(query, node->children[1], dest); |
495 | 0 | break; |
496 | 0 | case EVENT_FILTER_OP_NOT: |
497 | 0 | str_append(dest, "NOT "); |
498 | 0 | event_filter_export_query_expr(query, node->children[0], dest); |
499 | 0 | break; |
500 | 0 | case EVENT_FILTER_OP_CMP_EQ: |
501 | 0 | case EVENT_FILTER_OP_CMP_GT: |
502 | 0 | case EVENT_FILTER_OP_CMP_LT: |
503 | 0 | case EVENT_FILTER_OP_CMP_GE: |
504 | 0 | case EVENT_FILTER_OP_CMP_LE: |
505 | 0 | i_unreached(); |
506 | 0 | } |
507 | 0 | str_append_c(dest, ')'); |
508 | 0 | break; |
509 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: |
510 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: |
511 | 0 | str_append(dest, "event"); |
512 | 0 | str_append(dest, event_filter_export_query_expr_op(node->op)); |
513 | 0 | str_append_c(dest, '"'); |
514 | 0 | event_filter_append_escaped(dest, node->field.value.str, |
515 | 0 | node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD); |
516 | 0 | str_append_c(dest, '"'); |
517 | 0 | break; |
518 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: |
519 | 0 | str_append(dest, "source_location"); |
520 | 0 | str_append(dest, event_filter_export_query_expr_op(node->op)); |
521 | 0 | str_append_c(dest, '"'); |
522 | 0 | event_filter_append_escaped(dest, node->field.value.str, FALSE); |
523 | 0 | str_append_c(dest, '"'); |
524 | 0 | break; |
525 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: |
526 | 0 | str_append(dest, "category"); |
527 | 0 | str_append(dest, event_filter_export_query_expr_op(node->op)); |
528 | 0 | if (node->category.name != NULL) { |
529 | 0 | str_append_c(dest, '"'); |
530 | 0 | event_filter_append_escaped(dest, node->category.name, FALSE); |
531 | 0 | str_append_c(dest, '"'); |
532 | 0 | } else |
533 | 0 | str_append(dest, event_filter_category_from_log_type(node->category.log_type)); |
534 | 0 | break; |
535 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: |
536 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: |
537 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_NUMERIC_WILDCARD: |
538 | 0 | str_append_c(dest, '"'); |
539 | 0 | event_filter_append_escaped(dest, node->field.key, FALSE); |
540 | 0 | str_append_c(dest, '"'); |
541 | 0 | str_append(dest, event_filter_export_query_expr_op(node->op)); |
542 | 0 | str_append_c(dest, '"'); |
543 | 0 | event_filter_append_escaped(dest, |
544 | 0 | wanted_field_value_str(&node->field), |
545 | 0 | node->type != EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT); |
546 | 0 | str_append_c(dest, '"'); |
547 | 0 | break; |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | | static void |
552 | | event_filter_export_query(const struct event_filter_query_internal *query, |
553 | | string_t *dest) |
554 | 0 | { |
555 | 0 | str_append_c(dest, '('); |
556 | 0 | event_filter_export_query_expr(query, query->expr, dest); |
557 | 0 | str_append_c(dest, ')'); |
558 | 0 | } |
559 | | |
560 | | void event_filter_export(struct event_filter *filter, string_t *dest) |
561 | 0 | { |
562 | 0 | const struct event_filter_query_internal *query; |
563 | 0 | bool first = TRUE; |
564 | |
|
565 | 0 | array_foreach(&filter->queries, query) { |
566 | 0 | if (!first) |
567 | 0 | str_append(dest, " OR "); |
568 | 0 | first = FALSE; |
569 | 0 | event_filter_export_query(query, dest); |
570 | 0 | } |
571 | 0 | } |
572 | | |
573 | | struct event_filter_node * |
574 | | event_filter_get_expr_for_testing(struct event_filter *filter, |
575 | | unsigned int *count_r) |
576 | 0 | { |
577 | 0 | const struct event_filter_query_internal *queries; |
578 | |
|
579 | 0 | queries = array_get(&filter->queries, count_r); |
580 | |
|
581 | 0 | return (*count_r == 0) ? NULL : queries[0].expr; |
582 | 0 | } |
583 | | |
584 | | static bool |
585 | | event_category_match(const struct event_category *category, |
586 | | const struct event_category *wanted_category) |
587 | 0 | { |
588 | 0 | for (; category != NULL; category = category->parent) { |
589 | 0 | if (category->internal == wanted_category->internal) |
590 | 0 | return TRUE; |
591 | 0 | } |
592 | 0 | return FALSE; |
593 | 0 | } |
594 | | |
595 | | static bool |
596 | | event_has_category_nonrecursive(struct event *event, |
597 | | struct event_category *wanted_category) |
598 | 0 | { |
599 | 0 | struct event_category *cat; |
600 | |
|
601 | 0 | if (array_is_created(&event->categories)) { |
602 | 0 | array_foreach_elem(&event->categories, cat) { |
603 | 0 | if (event_category_match(cat, wanted_category)) |
604 | 0 | return TRUE; |
605 | 0 | } |
606 | 0 | } |
607 | 0 | return FALSE; |
608 | 0 | } |
609 | | |
610 | | static bool |
611 | | event_has_category(struct event *event, struct event_filter_node *node, |
612 | | enum event_filter_log_type log_type) |
613 | 0 | { |
614 | 0 | struct event_category *wanted_category = node->category.ptr; |
615 | | |
616 | | /* category is a log type */ |
617 | 0 | if (node->category.name == NULL) |
618 | 0 | return (node->category.log_type & log_type) != 0; |
619 | | |
620 | | /* category not registered, therefore the event cannot have it */ |
621 | 0 | if (wanted_category == NULL) |
622 | 0 | return FALSE; |
623 | | |
624 | 0 | while (event != NULL) { |
625 | 0 | if (event_has_category_nonrecursive(event, wanted_category)) |
626 | 0 | return TRUE; |
627 | | /* try also the parent events */ |
628 | 0 | event = event_get_parent(event); |
629 | 0 | } |
630 | | /* check also the global event and its parents */ |
631 | 0 | event = event_get_global(); |
632 | 0 | while (event != NULL) { |
633 | 0 | if (event_has_category_nonrecursive(event, wanted_category)) |
634 | 0 | return TRUE; |
635 | 0 | event = event_get_parent(event); |
636 | 0 | } |
637 | 0 | return FALSE; |
638 | 0 | } |
639 | | |
640 | | static bool |
641 | | cmp_str(const char *value, const char *wanted_value, enum cmp_flags flags) |
642 | 0 | { |
643 | 0 | if ((flags & CMP_FLAG_WILDCARD) != 0) { |
644 | 0 | if ((flags & CMP_FLAG_CASE_SENSITIVE) != 0) |
645 | 0 | return wildcard_match_escaped(value, wanted_value); |
646 | 0 | else |
647 | 0 | return wildcard_match_escaped_icase(value, wanted_value); |
648 | 0 | } else { |
649 | 0 | if ((flags & CMP_FLAG_CASE_SENSITIVE) != 0) |
650 | 0 | return strcmp(value, wanted_value) == 0; |
651 | 0 | else |
652 | 0 | return strcasecmp(value, wanted_value) == 0; |
653 | 0 | } |
654 | 0 | } |
655 | | |
656 | | static bool |
657 | | cmp_value(const char *value, const struct event_field *wanted_field, |
658 | | enum cmp_flags flags) |
659 | 0 | { |
660 | 0 | return cmp_str(value, wanted_field_value_str(wanted_field), flags); |
661 | 0 | } |
662 | | |
663 | | static bool |
664 | | event_match_strlist_recursive(struct event *event, |
665 | | const char *wanted_key, const char *wanted_value, |
666 | | enum cmp_flags cmp_flags, bool *seen) |
667 | 0 | { |
668 | 0 | const struct event_field *field; |
669 | 0 | const char *value; |
670 | |
|
671 | 0 | if (event == NULL) |
672 | 0 | return FALSE; |
673 | | |
674 | 0 | field = event_find_field_nonrecursive(event, wanted_key); |
675 | 0 | if (field != NULL) { |
676 | 0 | i_assert(field->value_type == EVENT_FIELD_VALUE_TYPE_STRLIST); |
677 | 0 | array_foreach_elem(&field->value.strlist, value) { |
678 | 0 | *seen = TRUE; |
679 | 0 | if (cmp_str(value, wanted_value, cmp_flags)) |
680 | 0 | return TRUE; |
681 | 0 | } |
682 | 0 | } |
683 | 0 | return event_match_strlist_recursive(event->parent, wanted_key, |
684 | 0 | wanted_value, cmp_flags, seen); |
685 | 0 | } |
686 | | |
687 | | static bool |
688 | | event_match_strlist(struct event *event, const struct event_field *wanted_field, |
689 | | enum cmp_flags cmp_flags) |
690 | 0 | { |
691 | 0 | const char *wanted_value = wanted_field_value_str(wanted_field); |
692 | 0 | bool seen = FALSE; |
693 | |
|
694 | 0 | if (event_match_strlist_recursive(event, wanted_field->key, wanted_value, |
695 | 0 | cmp_flags, &seen)) |
696 | 0 | return TRUE; |
697 | 0 | if (event_match_strlist_recursive(event_get_global(), |
698 | 0 | wanted_field->key, wanted_value, |
699 | 0 | cmp_flags, &seen)) |
700 | 0 | return TRUE; |
701 | 0 | if (wanted_value[0] == '\0' && !seen) { |
702 | | /* strlist="" matches nonexistent strlist */ |
703 | 0 | return TRUE; |
704 | 0 | } |
705 | 0 | return FALSE; |
706 | |
|
707 | 0 | } |
708 | | |
709 | | static bool |
710 | | event_filter_handle_numeric_operation(enum event_filter_node_op op, |
711 | | intmax_t a, intmax_t b) |
712 | 0 | { |
713 | 0 | switch (op) { |
714 | 0 | case EVENT_FILTER_OP_CMP_EQ: |
715 | 0 | return a == b; |
716 | 0 | case EVENT_FILTER_OP_CMP_GT: |
717 | 0 | return a > b; |
718 | 0 | case EVENT_FILTER_OP_CMP_LT: |
719 | 0 | return a < b; |
720 | 0 | case EVENT_FILTER_OP_CMP_GE: |
721 | 0 | return a >= b; |
722 | 0 | case EVENT_FILTER_OP_CMP_LE: |
723 | 0 | return a <= b; |
724 | 0 | case EVENT_FILTER_OP_AND: |
725 | 0 | case EVENT_FILTER_OP_OR: |
726 | 0 | case EVENT_FILTER_OP_NOT: |
727 | 0 | i_unreached(); |
728 | 0 | } |
729 | 0 | i_unreached(); |
730 | 0 | } |
731 | | |
732 | | static bool |
733 | | event_match_field(struct event *event, struct event_filter_node *node, |
734 | | bool use_strcmp, const char *source_filename, |
735 | | unsigned int source_linenum) |
736 | 0 | { |
737 | 0 | const struct event_field *field; |
738 | 0 | struct event_field duration; |
739 | 0 | bool ret; |
740 | |
|
741 | 0 | const struct event_field *wanted_field = &node->field; |
742 | 0 | if (strcmp(wanted_field->key, "duration") == 0) { |
743 | 0 | uintmax_t duration_value; |
744 | 0 | i_zero(&duration); |
745 | 0 | duration.key = "duration"; |
746 | 0 | duration.value_type = EVENT_FIELD_VALUE_TYPE_INTMAX; |
747 | 0 | event_get_last_duration(event, &duration_value); |
748 | 0 | duration.value.intmax = (intmax_t)duration_value; |
749 | 0 | field = &duration; |
750 | 0 | } else { |
751 | | /* wanted_field has the value in all available formats */ |
752 | 0 | field = event_find_field_recursive(event, wanted_field->key); |
753 | 0 | } |
754 | 0 | if (field == NULL) { |
755 | | /* field="" matches nonexistent field */ |
756 | 0 | return wanted_field->value_type == EVENT_FIELD_VALUE_TYPE_STR && |
757 | 0 | wanted_field->value.str[0] == '\0'; |
758 | 0 | } |
759 | 0 | enum cmp_flags cmp_flags = 0; |
760 | 0 | if (!use_strcmp) |
761 | 0 | cmp_flags |= CMP_FLAG_WILDCARD; |
762 | 0 | if (node->case_sensitive) |
763 | 0 | cmp_flags |= CMP_FLAG_CASE_SENSITIVE; |
764 | |
|
765 | 0 | switch (field->value_type) { |
766 | 0 | case EVENT_FIELD_VALUE_TYPE_STR: |
767 | | /* We only support string equality comparisons. */ |
768 | 0 | if (node->op != EVENT_FILTER_OP_CMP_EQ) { |
769 | | /* Warn about non-equality comparisons. */ |
770 | 0 | if (!node->warned_string_inequality) { |
771 | 0 | const char *name = event->sending_name; |
772 | | /* Use i_warning to prevent event filter recursions. */ |
773 | 0 | i_warning("Event filter for string field '%s' " |
774 | 0 | "only supports equality operation " |
775 | 0 | "'=' not '%s'. (event=%s, source=%s:%u)", |
776 | 0 | wanted_field->key, |
777 | 0 | event_filter_export_query_expr_op(node->op), |
778 | 0 | name != NULL ? name : "", |
779 | 0 | source_filename, source_linenum); |
780 | 0 | node->warned_string_inequality = TRUE; |
781 | 0 | } |
782 | 0 | return FALSE; |
783 | 0 | } |
784 | 0 | if (field->value.str[0] == '\0') { |
785 | | /* field was removed, but it matches field="" filter */ |
786 | 0 | return wanted_field->value_type == EVENT_FIELD_VALUE_TYPE_STR && |
787 | 0 | wanted_field->value.str[0] == '\0'; |
788 | 0 | } |
789 | 0 | T_BEGIN { |
790 | 0 | ret = cmp_value(field->value.str, wanted_field, cmp_flags); |
791 | 0 | } T_END; |
792 | 0 | return ret; |
793 | 0 | case EVENT_FIELD_VALUE_TYPE_INTMAX: |
794 | 0 | if (node->ambiguous_unit) { |
795 | 0 | if (!node->warned_ambiguous_unit) { |
796 | 0 | const char *name = event->sending_name; |
797 | | /* Use i_warning to prevent event filter recursions. */ |
798 | 0 | i_warning("Event filter matches integer field " |
799 | 0 | "'%s' with value that has an " |
800 | 0 | "ambiguous unit '%s'. Please use " |
801 | 0 | "either 'mins' or 'MB' to specify " |
802 | 0 | "interval or size respectively. " |
803 | 0 | "(event=%s, source=%s:%u)", |
804 | 0 | wanted_field->key, |
805 | 0 | wanted_field_value_str(wanted_field), |
806 | 0 | name != NULL ? name : "", |
807 | 0 | source_filename, source_linenum); |
808 | 0 | node->warned_ambiguous_unit = TRUE; |
809 | 0 | } |
810 | 0 | return FALSE; |
811 | 0 | } else if (wanted_field->value_type == EVENT_FIELD_VALUE_TYPE_INTMAX) { |
812 | | /* compare against an integer */ |
813 | 0 | return event_filter_handle_numeric_operation( |
814 | 0 | node->op, field->value.intmax, wanted_field->value.intmax); |
815 | 0 | } else if ((cmp_flags & CMP_FLAG_WILDCARD) == 0 || |
816 | 0 | (node->type != EVENT_FILTER_NODE_TYPE_EVENT_FIELD_NUMERIC_WILDCARD)) { |
817 | 0 | if (!node->warned_type_mismatch) { |
818 | 0 | const char *name = event->sending_name; |
819 | | /* Use i_warning to prevent event filter recursions. */ |
820 | 0 | i_warning("Event filter matches integer field " |
821 | 0 | "'%s' against non-integer value '%s'. " |
822 | 0 | "(event=%s, source=%s:%u)", |
823 | 0 | wanted_field->key, |
824 | 0 | wanted_field_value_str(wanted_field), |
825 | 0 | name != NULL ? name : "", |
826 | 0 | source_filename, source_linenum); |
827 | 0 | node->warned_type_mismatch = TRUE; |
828 | 0 | } |
829 | 0 | return FALSE; |
830 | 0 | } else if (node->op != EVENT_FILTER_OP_CMP_EQ) { |
831 | | /* In this branch a numeric value is matched against a |
832 | | wildcard, which requires an equality operation. */ |
833 | 0 | if (!node->warned_type_mismatch) { |
834 | 0 | const char *name = event->sending_name; |
835 | | /* Use i_warning to prevent event filter recursions. */ |
836 | 0 | i_warning("Event filter matches integer field " |
837 | 0 | "'%s' against wildcard value '%s' " |
838 | 0 | "with an incompatible operation '%s', " |
839 | 0 | "please use '='. (event=%s, " |
840 | 0 | "source=%s:%u)", |
841 | 0 | wanted_field->key, |
842 | 0 | wanted_field_value_str(wanted_field), |
843 | 0 | event_filter_export_query_expr_op(node->op), |
844 | 0 | name != NULL ? name : "", |
845 | 0 | source_filename, source_linenum); |
846 | 0 | node->warned_type_mismatch = TRUE; |
847 | 0 | } |
848 | 0 | return FALSE; |
849 | 0 | } else { |
850 | 0 | char tmp[MAX_INT_STRLEN]; |
851 | 0 | i_snprintf(tmp, sizeof(tmp), "%jd", field->value.intmax); |
852 | 0 | T_BEGIN { |
853 | 0 | ret = wildcard_match_escaped_icase(tmp, |
854 | 0 | wanted_field_value_str(wanted_field)); |
855 | 0 | } T_END; |
856 | 0 | return ret; |
857 | 0 | } |
858 | 0 | case EVENT_FIELD_VALUE_TYPE_TIMEVAL: { |
859 | | /* Filtering for timeval fields is not implemented. */ |
860 | 0 | if (!node->warned_timeval_not_implemented) { |
861 | 0 | const char *name = event->sending_name; |
862 | 0 | i_warning("Event filter for timeval field '%s' is " |
863 | 0 | "currently not implemented. (event=%s, " |
864 | 0 | "source=%s:%u)", |
865 | 0 | wanted_field->key, name != NULL ? name : "", |
866 | 0 | source_filename, source_linenum); |
867 | 0 | node->warned_timeval_not_implemented = TRUE; |
868 | 0 | } |
869 | 0 | return FALSE; |
870 | 0 | } |
871 | 0 | case EVENT_FIELD_VALUE_TYPE_IP: |
872 | 0 | if (node->op != EVENT_FILTER_OP_CMP_EQ) { |
873 | | /* we only support IP equality comparisons */ |
874 | 0 | if (!node->warned_ip_inequality) { |
875 | 0 | const char *name = event->sending_name; |
876 | | /* Use i_warning to prevent event filter recursions. */ |
877 | 0 | i_warning("Event filter for IP field '%s' " |
878 | 0 | "only supports equality operation " |
879 | 0 | "'=' not '%s'. (event=%s, source=%s:%u)", |
880 | 0 | wanted_field->key, |
881 | 0 | event_filter_export_query_expr_op(node->op), |
882 | 0 | name != NULL ? name : "", |
883 | 0 | source_filename, source_linenum); |
884 | 0 | node->warned_ip_inequality = TRUE; |
885 | 0 | } |
886 | 0 | return FALSE; |
887 | 0 | } |
888 | 0 | if (wanted_field->value_type == EVENT_FIELD_VALUE_TYPE_IP) { |
889 | 0 | return net_is_in_network(&field->value.ip, |
890 | 0 | &wanted_field->value.ip, |
891 | 0 | wanted_field->value.ip_bits); |
892 | 0 | } |
893 | 0 | if ((cmp_flags & CMP_FLAG_WILDCARD) == 0) { |
894 | | /* If the matched value was a number, it was already |
895 | | matched in the previous branch. So here we have a |
896 | | non-wildcard IP, which can never be a match to an |
897 | | IP. */ |
898 | 0 | if (!node->warned_type_mismatch) { |
899 | 0 | const char *name = event->sending_name; |
900 | | /* Use i_warning to prevent event filter recursions. */ |
901 | 0 | i_warning("Event filter matches IP field " |
902 | 0 | "'%s' against non-IP value '%s'. " |
903 | 0 | "(event=%s, source=%s:%u)", |
904 | 0 | wanted_field->key, |
905 | 0 | wanted_field_value_str(wanted_field), |
906 | 0 | name != NULL ? name : "", |
907 | 0 | source_filename, source_linenum); |
908 | 0 | node->warned_type_mismatch = TRUE; |
909 | 0 | } |
910 | 0 | return FALSE; |
911 | 0 | } |
912 | 0 | T_BEGIN { |
913 | 0 | ret = wildcard_match_escaped_icase(net_ip2addr(&field->value.ip), |
914 | 0 | wanted_field_value_str(wanted_field)); |
915 | 0 | } T_END; |
916 | 0 | return ret; |
917 | 0 | case EVENT_FIELD_VALUE_TYPE_STRLIST: |
918 | | /* check if the value is (or is not) on the list, |
919 | | only string matching makes sense here. */ |
920 | 0 | if (node->op != EVENT_FILTER_OP_CMP_EQ) { |
921 | 0 | if (!node->warned_string_inequality) { |
922 | 0 | const char *name = event->sending_name; |
923 | 0 | i_warning("Event filter for string list field " |
924 | 0 | "'%s' only supports equality " |
925 | 0 | "operation '=' not '%s'. (event=%s, " |
926 | 0 | "source=%s:%u)", |
927 | 0 | wanted_field->key, |
928 | 0 | event_filter_export_query_expr_op(node->op), |
929 | 0 | name != NULL ? name : "", |
930 | 0 | source_filename, source_linenum); |
931 | 0 | node->warned_string_inequality = TRUE; |
932 | 0 | } |
933 | 0 | return FALSE; |
934 | 0 | } |
935 | 0 | T_BEGIN { |
936 | 0 | ret = event_match_strlist(event, wanted_field, cmp_flags); |
937 | 0 | } T_END; |
938 | 0 | return ret; |
939 | 0 | } |
940 | 0 | i_unreached(); |
941 | 0 | } |
942 | | |
943 | | static bool |
944 | | event_filter_query_match_cmp(struct event_filter_node *node, |
945 | | struct event *event, const char *source_filename, |
946 | | unsigned int source_linenum, |
947 | | enum event_filter_log_type log_type) |
948 | 0 | { |
949 | 0 | i_assert((node->op == EVENT_FILTER_OP_CMP_EQ) || |
950 | 0 | (node->op == EVENT_FILTER_OP_CMP_GT) || |
951 | 0 | (node->op == EVENT_FILTER_OP_CMP_LT) || |
952 | 0 | (node->op == EVENT_FILTER_OP_CMP_GE) || |
953 | 0 | (node->op == EVENT_FILTER_OP_CMP_LE)); |
954 | | |
955 | 0 | switch (node->type) { |
956 | 0 | case EVENT_FILTER_NODE_TYPE_LOGIC: |
957 | 0 | i_unreached(); |
958 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: |
959 | 0 | return (event->sending_name != NULL) && |
960 | 0 | strcmp(event->sending_name, node->field.value.str) == 0; |
961 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: |
962 | 0 | return (event->sending_name != NULL) && |
963 | 0 | wildcard_match_escaped(event->sending_name, |
964 | 0 | node->field.value.str); |
965 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: { |
966 | 0 | bool ret; |
967 | 0 | if (strchr(node->field.value.str, ':') == NULL) |
968 | 0 | ret = strcmp(node->field.value.str, source_filename) == 0; |
969 | 0 | else T_BEGIN { |
970 | 0 | const char *wanted_str = |
971 | 0 | t_strdup_printf("%s:%u", source_filename, source_linenum); |
972 | 0 | ret = strcmp(node->field.value.str, wanted_str) == 0; |
973 | 0 | } T_END; |
974 | 0 | return ret; |
975 | 0 | } |
976 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: |
977 | 0 | return event_has_category(event, node, log_type); |
978 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: |
979 | 0 | return event_match_field(event, node, TRUE, source_filename, source_linenum); |
980 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: |
981 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_NUMERIC_WILDCARD: |
982 | 0 | return event_match_field(event, node, FALSE, source_filename, source_linenum); |
983 | 0 | } |
984 | | |
985 | 0 | i_unreached(); |
986 | 0 | } |
987 | | |
988 | | bool |
989 | | event_filter_query_match_eval(struct event_filter_node *node, |
990 | | struct event *event, const char *source_filename, |
991 | | unsigned int source_linenum, |
992 | | enum event_filter_log_type log_type) |
993 | 0 | { |
994 | 0 | switch (node->op) { |
995 | 0 | case EVENT_FILTER_OP_CMP_EQ: |
996 | 0 | case EVENT_FILTER_OP_CMP_GT: |
997 | 0 | case EVENT_FILTER_OP_CMP_LT: |
998 | 0 | case EVENT_FILTER_OP_CMP_GE: |
999 | 0 | case EVENT_FILTER_OP_CMP_LE: |
1000 | 0 | return event_filter_query_match_cmp(node, event, source_filename, |
1001 | 0 | source_linenum, log_type); |
1002 | 0 | case EVENT_FILTER_OP_AND: |
1003 | 0 | return event_filter_query_match_eval(node->children[0], event, |
1004 | 0 | source_filename, source_linenum, |
1005 | 0 | log_type) && |
1006 | 0 | event_filter_query_match_eval(node->children[1], event, |
1007 | 0 | source_filename, source_linenum, |
1008 | 0 | log_type); |
1009 | 0 | case EVENT_FILTER_OP_OR: |
1010 | 0 | return event_filter_query_match_eval(node->children[0], event, |
1011 | 0 | source_filename, source_linenum, |
1012 | 0 | log_type) || |
1013 | 0 | event_filter_query_match_eval(node->children[1], event, |
1014 | 0 | source_filename, source_linenum, |
1015 | 0 | log_type); |
1016 | 0 | case EVENT_FILTER_OP_NOT: |
1017 | 0 | return !event_filter_query_match_eval(node->children[0], event, |
1018 | 0 | source_filename, source_linenum, |
1019 | 0 | log_type); |
1020 | 0 | } |
1021 | | |
1022 | 0 | i_unreached(); |
1023 | 0 | } |
1024 | | |
1025 | | static bool |
1026 | | event_filter_query_match(const struct event_filter_query_internal *query, |
1027 | | struct event *event, const char *source_filename, |
1028 | | unsigned int source_linenum, |
1029 | | const struct failure_context *ctx) |
1030 | 0 | { |
1031 | 0 | enum event_filter_log_type log_type; |
1032 | |
|
1033 | 0 | i_assert(ctx->type < N_ELEMENTS(event_filter_log_type_map)); |
1034 | 0 | log_type = event_filter_log_type_map[ctx->type].log_type; |
1035 | |
|
1036 | 0 | return event_filter_query_match_eval(query->expr, event, source_filename, |
1037 | 0 | source_linenum, log_type); |
1038 | 0 | } |
1039 | | |
1040 | | static bool |
1041 | | event_filter_match_fastpath(struct event_filter *filter, struct event *event) |
1042 | 0 | { |
1043 | 0 | if (filter->named_queries_only && event->sending_name == NULL) { |
1044 | | /* No debug logging is enabled. Only named events may be wanted |
1045 | | for stats. This event doesn't have a name, so we don't need |
1046 | | to check any further. */ |
1047 | 0 | return FALSE; |
1048 | 0 | } |
1049 | 0 | return TRUE; |
1050 | 0 | } |
1051 | | |
1052 | | bool event_filter_match(struct event_filter *filter, struct event *event, |
1053 | | const struct failure_context *ctx) |
1054 | 0 | { |
1055 | 0 | if (filter == NULL) |
1056 | 0 | return FALSE; |
1057 | 0 | return event_filter_match_source(filter, event, event->source_filename, |
1058 | 0 | event->source_linenum, ctx); |
1059 | 0 | } |
1060 | | |
1061 | | bool event_filter_match_source(struct event_filter *filter, struct event *event, |
1062 | | const char *source_filename, |
1063 | | unsigned int source_linenum, |
1064 | | const struct failure_context *ctx) |
1065 | 0 | { |
1066 | 0 | const struct event_filter_query_internal *query; |
1067 | |
|
1068 | 0 | i_assert(!filter->fragment); |
1069 | | |
1070 | 0 | if (!event_filter_match_fastpath(filter, event)) |
1071 | 0 | return FALSE; |
1072 | | |
1073 | 0 | array_foreach(&filter->queries, query) { |
1074 | 0 | if (event_filter_query_match(query, event, source_filename, |
1075 | 0 | source_linenum, ctx)) |
1076 | 0 | return TRUE; |
1077 | 0 | } |
1078 | 0 | return FALSE; |
1079 | 0 | } |
1080 | | |
1081 | | struct event_filter_match_iter { |
1082 | | struct event_filter *filter; |
1083 | | struct event *event; |
1084 | | const struct failure_context *failure_ctx; |
1085 | | unsigned int idx; |
1086 | | }; |
1087 | | |
1088 | | struct event_filter_match_iter * |
1089 | | event_filter_match_iter_init(struct event_filter *filter, struct event *event, |
1090 | | const struct failure_context *ctx) |
1091 | 0 | { |
1092 | 0 | struct event_filter_match_iter *iter; |
1093 | |
|
1094 | 0 | i_assert(!filter->fragment); |
1095 | | |
1096 | 0 | iter = i_new(struct event_filter_match_iter, 1); |
1097 | 0 | iter->filter = filter; |
1098 | 0 | iter->event = event; |
1099 | 0 | iter->failure_ctx = ctx; |
1100 | 0 | if (!event_filter_match_fastpath(filter, event)) |
1101 | 0 | iter->idx = UINT_MAX; |
1102 | 0 | return iter; |
1103 | 0 | } |
1104 | | |
1105 | | void *event_filter_match_iter_next(struct event_filter_match_iter *iter) |
1106 | 0 | { |
1107 | 0 | const struct event_filter_query_internal *queries; |
1108 | 0 | unsigned int count; |
1109 | |
|
1110 | 0 | queries = array_get(&iter->filter->queries, &count); |
1111 | 0 | while (iter->idx < count) { |
1112 | 0 | const struct event_filter_query_internal *query = |
1113 | 0 | &queries[iter->idx]; |
1114 | |
|
1115 | 0 | iter->idx++; |
1116 | 0 | if (query->context != NULL && |
1117 | 0 | event_filter_query_match(query, iter->event, |
1118 | 0 | iter->event->source_filename, |
1119 | 0 | iter->event->source_linenum, |
1120 | 0 | iter->failure_ctx)) |
1121 | 0 | return query->context; |
1122 | 0 | } |
1123 | 0 | return NULL; |
1124 | 0 | } |
1125 | | |
1126 | | void event_filter_match_iter_deinit(struct event_filter_match_iter **_iter) |
1127 | 0 | { |
1128 | 0 | struct event_filter_match_iter *iter = *_iter; |
1129 | |
|
1130 | 0 | *_iter = NULL; |
1131 | 0 | i_free(iter); |
1132 | 0 | } |
1133 | | |
1134 | | static void |
1135 | | event_filter_query_update_category(struct event_filter_query_internal *query, |
1136 | | struct event_filter_node *node, |
1137 | | struct event_category *category, |
1138 | | bool add) |
1139 | 0 | { |
1140 | 0 | if (node == NULL) |
1141 | 0 | return; |
1142 | | |
1143 | 0 | switch (node->type) { |
1144 | 0 | case EVENT_FILTER_NODE_TYPE_LOGIC: |
1145 | 0 | event_filter_query_update_category(query, node->children[0], category, add); |
1146 | 0 | event_filter_query_update_category(query, node->children[1], category, add); |
1147 | 0 | break; |
1148 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: |
1149 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: |
1150 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: |
1151 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: |
1152 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: |
1153 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_NUMERIC_WILDCARD: |
1154 | 0 | break; |
1155 | 0 | case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: |
1156 | 0 | if (node->category.name == NULL) |
1157 | 0 | break; /* log type */ |
1158 | | |
1159 | 0 | if (add) { |
1160 | 0 | if (node->category.ptr != NULL) |
1161 | 0 | break; |
1162 | | |
1163 | 0 | if (strcmp(node->category.name, category->name) == 0) |
1164 | 0 | node->category.ptr = category; |
1165 | 0 | } else { |
1166 | 0 | if (node->category.ptr == category) |
1167 | 0 | node->category.ptr = NULL; |
1168 | 0 | } |
1169 | 0 | break; |
1170 | 0 | } |
1171 | 0 | } |
1172 | | |
1173 | | static void event_filter_category_registered(struct event_category *category) |
1174 | 1 | { |
1175 | 1 | const bool add = category->internal != NULL; |
1176 | 1 | struct event_filter_query_internal *query; |
1177 | 1 | struct event_filter *filter; |
1178 | | |
1179 | 1 | for (filter = event_filters; filter != NULL; filter = filter->next) { |
1180 | 0 | array_foreach_modifiable(&filter->queries, query) { |
1181 | 0 | event_filter_query_update_category(query, query->expr, |
1182 | 0 | category, add); |
1183 | 0 | } |
1184 | 0 | } |
1185 | 1 | } |
1186 | | |
1187 | | void event_filter_init(void) |
1188 | 5 | { |
1189 | 5 | event_category_register_callback(event_filter_category_registered); |
1190 | 5 | } |
1191 | | |
1192 | | void event_filter_deinit(void) |
1193 | 0 | { |
1194 | 0 | event_category_unregister_callback(event_filter_category_registered); |
1195 | 0 | } |