1 /*
2 * Copyright (c) 2002-2017 Balabit
3 * Copyright (c) 1998-2012 Balázs Scheidler
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * As an additional exemption you are allowed to compile & link against the
20 * OpenSSL libraries as published by the OpenSSL project. See the file
21 * COPYING for details.
22 *
23 */
24
25 #include "cfg-parser.h"
26 #include "cfg-grammar.h"
27
28 #include <string.h>
29 #include <stdlib.h>
30
31 extern int main_debug;
32
33 /* defined in the parser */
34 int main_parse(CfgLexer *lexer, gpointer *dummy, gpointer arg);
35
36 /*
37 * PLEASE: if at all possible avoid adding keywords here, as these
38 * keywords might collide with user-defined identifiers (e.g. source,
39 * destination & block names).
40 *
41 * Add the keyword & the token to the parser of the module in question
42 * instead, even if there are multiple modules using the same
43 * keywords.
44 *
45 * Tokens (e.g. KW_xxx) associated with core keywords (e.g. those
46 * listed here) should be declared only once and only in cfg-grammar.y
47 * and NOT in external modules.
48 */
49 static CfgLexerKeyword main_keywords[] =
50 {
51 /* statements */
52 { "source", KW_SOURCE },
53 { "filter", KW_FILTER },
54 { "parser", KW_PARSER },
55 { "rewrite", KW_REWRITE },
56 { "destination", KW_DESTINATION },
57 { "log", KW_LOG },
58 { "junction", KW_JUNCTION },
59 { "channel", KW_CHANNEL },
60 { "options", KW_OPTIONS },
61 { "include", KW_INCLUDE, },
62 { "block", KW_BLOCK },
63 { "if", KW_IF },
64 { "else", KW_ELSE },
65 { "elif", KW_ELIF },
66
67 /* source or destination items */
68 { "internal", KW_INTERNAL },
69
70 /* value pairs */
71 { "value_pairs", KW_VALUE_PAIRS },
72 { "exclude", KW_EXCLUDE },
73 { "pair", KW_PAIR },
74 { "key", KW_KEY },
75 { "scope", KW_SCOPE },
76 { "rekey", KW_REKEY },
77 { "shift", KW_SHIFT },
78 { "shift_levels", KW_SHIFT_LEVELS },
79 { "add_prefix", KW_ADD_PREFIX },
80 { "replace", KW_REPLACE_PREFIX, KWS_OBSOLETE, "replace_prefix" },
81 { "replace_prefix", KW_REPLACE_PREFIX },
82
83 /* option items */
84 { "flags", KW_FLAGS },
85 { "pad_size", KW_PAD_SIZE },
86 { "truncate_size", KW_TRUNCATE_SIZE },
87 { "mark_freq", KW_MARK_FREQ },
88 { "mark", KW_MARK_FREQ, KWS_OBSOLETE, "mark_freq" },
89 { "mark_mode", KW_MARK_MODE },
90 { "stats_freq", KW_STATS_FREQ },
91 { "stats_lifetime", KW_STATS_LIFETIME },
92 { "stats_level", KW_STATS_LEVEL },
93 { "stats", KW_STATS_FREQ, KWS_OBSOLETE, "stats_freq" },
94 { "stats_max_dynamics", KW_STATS_MAX_DYNAMIC },
95 { "min_iw_size_per_reader", KW_MIN_IW_SIZE_PER_READER },
96 { "flush_lines", KW_FLUSH_LINES },
97 { "flush_timeout", KW_FLUSH_TIMEOUT, KWS_OBSOLETE, "Some drivers support batch-timeout() instead that you can specify at the destination level." },
98 { "suppress", KW_SUPPRESS },
99 { "sync_freq", KW_FLUSH_LINES, KWS_OBSOLETE, "flush_lines" },
100 { "sync", KW_FLUSH_LINES, KWS_OBSOLETE, "flush_lines" },
101 { "long_hostnames", KW_CHAIN_HOSTNAMES, KWS_OBSOLETE, "chain_hostnames" },
102 { "chain_hostnames", KW_CHAIN_HOSTNAMES },
103 { "normalize_hostnames", KW_NORMALIZE_HOSTNAMES },
104 { "keep_hostname", KW_KEEP_HOSTNAME },
105 { "check_hostname", KW_CHECK_HOSTNAME },
106 { "bad_hostname", KW_BAD_HOSTNAME },
107 { "custom_domain", KW_CUSTOM_DOMAIN },
108 { "keep_timestamp", KW_KEEP_TIMESTAMP },
109 { "encoding", KW_ENCODING },
110 { "ts_format", KW_TS_FORMAT },
111 { "frac_digits", KW_FRAC_DIGITS },
112 { "time_zone", KW_TIME_ZONE },
113 { "recv_time_zone", KW_RECV_TIME_ZONE },
114 { "send_time_zone", KW_SEND_TIME_ZONE },
115 { "local_time_zone", KW_LOCAL_TIME_ZONE },
116 { "format", KW_FORMAT },
117 { "use_time_recvd", KW_USE_TIME_RECVD, KWS_OBSOLETE, "Use R_ or S_ prefixed macros in templates or keep_timestamp(no)" },
118 { "use_fqdn", KW_USE_FQDN },
119 { "use_dns", KW_USE_DNS },
120 { "time_reopen", KW_TIME_REOPEN },
121 { "time_reap", KW_TIME_REAP },
122 { "time_sleep", KW_TIME_SLEEP, KWS_OBSOLETE, "time_sleep() has been deprecated" },
123 { "file_template", KW_FILE_TEMPLATE },
124 { "proto_template", KW_PROTO_TEMPLATE },
125 { "default_level", KW_DEFAULT_SEVERITY },
126 { "default_priority", KW_DEFAULT_SEVERITY },
127 { "default_severity", KW_DEFAULT_SEVERITY },
128 { "default_facility", KW_DEFAULT_FACILITY },
129 { "threaded", KW_THREADED },
130 { "use_rcptid", KW_USE_RCPTID, KWS_OBSOLETE, "This has been deprecated, try use_uniqid() instead" },
131 { "use_uniqid", KW_USE_UNIQID },
132
133 { "log_fifo_size", KW_LOG_FIFO_SIZE },
134 { "log_fetch_limit", KW_LOG_FETCH_LIMIT },
135 { "log_iw_size", KW_LOG_IW_SIZE },
136 { "log_msg_size", KW_LOG_MSG_SIZE },
137 { "trim_large_messages", KW_TRIM_LARGE_MESSAGES },
138 { "log_prefix", KW_LOG_PREFIX, KWS_OBSOLETE, "program_override" },
139 { "program_override", KW_PROGRAM_OVERRIDE },
140 { "host_override", KW_HOST_OVERRIDE },
141 { "throttle", KW_THROTTLE },
142
143 { "create_dirs", KW_CREATE_DIRS },
144 { "optional", KW_OPTIONAL },
145
146 { "owner", KW_OWNER },
147 { "group", KW_GROUP },
148 { "perm", KW_PERM },
149 { "dir_owner", KW_DIR_OWNER },
150 { "dir_group", KW_DIR_GROUP },
151 { "dir_perm", KW_DIR_PERM },
152 { "template", KW_TEMPLATE },
153 { "template_escape", KW_TEMPLATE_ESCAPE },
154 { "template_function", KW_TEMPLATE_FUNCTION },
155 { "on_error", KW_ON_ERROR },
156 { "persist_only", KW_PERSIST_ONLY },
157 { "dns_cache_hosts", KW_DNS_CACHE_HOSTS },
158 { "dns_cache", KW_DNS_CACHE },
159 { "dns_cache_size", KW_DNS_CACHE_SIZE },
160 { "dns_cache_expire", KW_DNS_CACHE_EXPIRE },
161 { "dns_cache_expire_failed", KW_DNS_CACHE_EXPIRE_FAILED },
162 {
163 "pass_unix_credentials", KW_PASS_UNIX_CREDENTIALS, KWS_OBSOLETE,
164 "The use of pass-unix-credentials() has been deprecated in " VERSION_3_35 " in favour of "
165 "the 'so-passcred()' source option or the 'ignore-aux-data' source flag"
166 },
167
168 { "persist_name", KW_PERSIST_NAME, VERSION_VALUE_3_8 },
169
170 { "retries", KW_RETRIES },
171 { "workers", KW_WORKERS },
172 { "batch_lines", KW_BATCH_LINES },
173 { "batch_timeout", KW_BATCH_TIMEOUT },
174
175 { "read_old_records", KW_READ_OLD_RECORDS},
176 { "use_syslogng_pid", KW_USE_SYSLOGNG_PID },
177 { "fetch_no_data_delay", KW_FETCH_NO_DATA_DELAY},
178 /* filter items */
179 { "type", KW_TYPE },
180 { "tags", KW_TAGS },
181
182 /* on/off switches */
183 { "yes", KW_YES },
184 { "on", KW_YES },
185 { "no", KW_NO },
186 { "off", KW_NO },
187 /* rewrite rules */
188 { "condition", KW_CONDITION },
189 { "value", KW_VALUE },
190
191 { NULL, 0 }
192 };
193
194
195 CfgParser main_parser =
196 {
197 #if SYSLOG_NG_ENABLE_DEBUG
198 .debug_flag = &main_debug,
199 #endif
200 .name = "config",
201 .context = LL_CONTEXT_ROOT,
202 .keywords = main_keywords,
203 .parse = main_parse,
204 };
205
CFG_PARSER_IMPLEMENT_LEXER_BINDING(main_,MAIN_,gpointer *)206 CFG_PARSER_IMPLEMENT_LEXER_BINDING(main_, MAIN_, gpointer *)
207
208 /* display CONTEXT lines before and after the offending line */
209 #define CONTEXT 5
210
211 static void
212 _format_source_prefix(gchar *line_prefix, gsize line_prefix_len, gint lineno, gboolean error_location)
213 {
214 g_snprintf(line_prefix, line_prefix_len, "%d", lineno);
215 if (error_location)
216 {
217 for (gint i = strlen(line_prefix); i < 6; i++)
218 g_strlcat(line_prefix, "-", line_prefix_len);
219
220 g_strlcat(line_prefix, ">", line_prefix_len);
221 }
222 }
223
224 static void
_print_underline(const gchar * line,gint whitespace_before,gint number_of_carets)225 _print_underline(const gchar *line, gint whitespace_before, gint number_of_carets)
226 {
227 for (gint i = 0; line[i] && i < whitespace_before; i++)
228 {
229 fprintf(stderr, "%c", line[i] == '\t' ? '\t' : ' ');
230 }
231
232 /* NOTE: sometimes the yylloc has zero characters, print a caret even in
233 * this case, that's why i == 0 is there */
234
235 for (gint i = 0; i == 0 || i < number_of_carets; i++)
236 fprintf(stderr, "^");
237 fprintf(stderr, "\n");
238 }
239
240 static void
_print_underlined_source_block(const CFG_LTYPE * yylloc,gchar ** lines,gint error_index)241 _print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gint error_index)
242 {
243 gint line_ndx;
244 gchar line_prefix[12];
245 gint error_length = yylloc->last_line - yylloc->first_line + 1;
246
247 for (line_ndx = 0; lines[line_ndx]; line_ndx++)
248 {
249 gint lineno = yylloc->first_line + line_ndx - error_index;
250 const gchar *line = lines[line_ndx];
251 gint line_len = strlen(line);
252 gboolean line_ends_with_newline = line_len > 0 && line[line_len - 1] == '\n';
253
254 _format_source_prefix(line_prefix, sizeof(line_prefix), lineno,
255 line_ndx >= error_index && line_ndx < error_index + error_length);
256
257 fprintf(stderr, "%-8s%s%s", line_prefix, line, line_ends_with_newline ? "" : "\n");
258
259 if (line_ndx == error_index)
260 {
261 /* print the underline right below the source line we just printed */
262 fprintf(stderr, "%-8s", line_prefix);
263
264 gboolean multi_line = yylloc->first_line != yylloc->last_line;
265
266 _print_underline(line, yylloc->first_column - 1,
267 multi_line ? strlen(&line[yylloc->first_column]) + 1
268 : yylloc->last_column - yylloc->first_column);
269 }
270 else if (line_ndx >= error_index + CONTEXT)
271 break;
272 }
273 }
274
275 static void
_report_file_location(const gchar * filename,const CFG_LTYPE * yylloc)276 _report_file_location(const gchar *filename, const CFG_LTYPE *yylloc)
277 {
278 FILE *f;
279 gint lineno = 0;
280 gsize buflen = 65520;
281 gchar *buf = g_malloc(buflen);
282 GPtrArray *context = g_ptr_array_new();
283 gint error_index = 0;
284
285 f = fopen(filename, "r");
286 if (f)
287 {
288 while (fgets(buf, buflen, f))
289 {
290 lineno++;
291 if (lineno > (gint) yylloc->first_line + CONTEXT)
292 break;
293 else if (lineno < (gint) yylloc->first_line - CONTEXT)
294 continue;
295 else if (lineno == yylloc->first_line)
296 error_index = context->len;
297 g_ptr_array_add(context, g_strdup(buf));
298 }
299 /* NOTE: do we have the appropriate number of lines? */
300 if (lineno <= yylloc->first_line)
301 goto exit;
302 g_ptr_array_add(context, NULL);
303 fclose(f);
304 }
305 _print_underlined_source_block(yylloc, (gchar **) context->pdata, error_index);
306
307 exit:
308 g_free(buf);
309 g_ptr_array_foreach(context, (GFunc) g_free, NULL);
310 g_ptr_array_free(context, TRUE);
311 }
312
313 static void
_report_buffer_location(const gchar * buffer_content,const CFG_LTYPE * yylloc)314 _report_buffer_location(const gchar *buffer_content, const CFG_LTYPE *yylloc)
315 {
316 gchar **lines = g_strsplit(buffer_content, "\n", yylloc->first_line + CONTEXT + 1);
317 gint num_lines = g_strv_length(lines);
318
319 if (num_lines <= yylloc->first_line)
320 goto exit;
321
322 gint start = yylloc->first_line - 1 - CONTEXT;
323 gint error_index = CONTEXT;
324 if (start < 0)
325 {
326 error_index += start;
327 start = 0;
328 }
329 _print_underlined_source_block(yylloc, &lines[start], error_index);
330
331 exit:
332 g_strfreev(lines);
333 }
334
335 void
report_syntax_error(CfgLexer * lexer,const CFG_LTYPE * yylloc,const char * what,const char * msg,gboolean in_main_grammar)336 report_syntax_error(CfgLexer *lexer, const CFG_LTYPE *yylloc, const char *what, const char *msg,
337 gboolean in_main_grammar)
338 {
339 CfgIncludeLevel *level = yylloc->level, *from;
340
341 for (from = level; from >= lexer->include_stack; from--)
342 {
343 const CFG_LTYPE *from_lloc;
344
345 if (from == level)
346 {
347 /* the location on the initial level is the one we get as
348 * argument, instead of what we have in the lexer's state. This
349 * is because the lexer might be one token in advance (because of
350 * LALR) and the grammar is kind enough to pass us the original
351 * location. */
352
353 from_lloc = yylloc;
354 fprintf(stderr, "Error parsing %s, %s in %s:%d:%d-%d:%d:\n",
355 what,
356 msg,
357 from_lloc->level->name,
358 from_lloc->first_line,
359 from_lloc->first_column,
360 from_lloc->last_line,
361 from_lloc->last_column);
362 }
363 else
364 {
365 from_lloc = &from->lloc;
366 fprintf(stderr, "Included from %s:%d:%d-%d:%d:\n", from->name,
367 from_lloc->first_line,
368 from_lloc->first_column,
369 from_lloc->last_line,
370 from_lloc->last_column);
371 }
372 if (from->include_type == CFGI_FILE)
373 {
374 _report_file_location(from->name, from_lloc);
375 }
376 else if (from->include_type == CFGI_BUFFER)
377 {
378 _report_buffer_location(from->buffer.original_content, from_lloc);
379 }
380 fprintf(stderr, "\n");
381 }
382
383 if (in_main_grammar)
384 fprintf(stderr, "\nsyslog-ng documentation: %s\n"
385 "contact: %s\n", PRODUCT_DOCUMENTATION, PRODUCT_CONTACT);
386
387 }
388
389 /* the debug flag for the main parser will be used for all parsers */
390 extern int cfg_parser_debug;
391
392
393 gboolean
cfg_parser_parse(CfgParser * self,CfgLexer * lexer,gpointer * instance,gpointer arg)394 cfg_parser_parse(CfgParser *self, CfgLexer *lexer, gpointer *instance, gpointer arg)
395 {
396 enum { OK, ERROR, MEMORY_EXHAUSTED } parse_result;
397 gboolean success;
398
399 if (cfg_parser_debug)
400 {
401 fprintf(stderr, "\n\nStarting parser %s\n", self->name);
402 }
403 if (self->debug_flag)
404 (*self->debug_flag) = cfg_parser_debug;
405 cfg_lexer_push_context(lexer, self->context, self->keywords, self->name);
406 parse_result = self->parse(lexer, instance, arg);
407 success = (parse_result == OK);
408 cfg_lexer_pop_context(lexer);
409 if (cfg_parser_debug)
410 {
411 fprintf(stderr, "\nStopping parser %s, result: %d\n", self->name, parse_result);
412 }
413 if (parse_result == MEMORY_EXHAUSTED)
414 {
415 fprintf(stderr,
416 "\nToo many tokens found during parsing, consider increasing YYMAXDEPTH in lib/cfg-grammar.y and recompiling.\n");
417 }
418 return success;
419 }
420
421 void
cfg_parser_cleanup(CfgParser * self,gpointer instance)422 cfg_parser_cleanup(CfgParser *self, gpointer instance)
423 {
424 if (instance && self->cleanup)
425 self->cleanup(instance);
426 }
427
428
429 /*
430 * This function can be used to parse flags in a flags(...) option. It
431 * makes it quite easy to write a flags parser by specifying the
432 * operations to be performed in a getopt-like array.
433 */
434 gboolean
cfg_process_flag(CfgFlagHandler * handlers,gpointer base,const gchar * flag_)435 cfg_process_flag(CfgFlagHandler *handlers, gpointer base, const gchar *flag_)
436 {
437 gint h;
438 gchar flag[32];
439
440 for (h = 0; flag_[h] && h < sizeof(flag); h++)
441 {
442 if (flag_[h] == '_')
443 flag[h] = '-';
444 else
445 flag[h] = flag_[h];
446 }
447 flag[h] = 0;
448
449 for (h = 0; handlers[h].name; h++)
450 {
451 CfgFlagHandler *handler = &handlers[h];
452
453 if (strcmp(handlers[h].name, flag) == 0)
454 {
455 guint32 *field = ((guint32 *) (((gchar *) base) + handler->ofs));
456 switch (handler->op)
457 {
458 case CFH_SET:
459 /* this works if handler->mask is unset and handler->param is a single bit only */
460
461 if (handler->mask)
462 *field = ((*field) & ~handler->mask) | handler->param;
463 else
464 *field = (*field) | handler->param;
465 return TRUE;
466 case CFH_CLEAR:
467 /* set the bitfield to zero */
468
469 if (handler->mask)
470 *field = (*field) & ~handler->mask;
471 else
472 *field = (*field) & ~handler->param;
473 return TRUE;
474 default:
475 g_assert_not_reached();
476 break;
477 }
478 }
479 }
480 return FALSE;
481 }
482
483 gboolean
cfg_process_yesno(const gchar * yesno)484 cfg_process_yesno(const gchar *yesno)
485 {
486 if (strcasecmp(yesno, "yes") == 0 || atoi(yesno) > 0)
487 return TRUE;
488 return FALSE;
489 }
490