/* * Copyright (c) 2008-2024 OARC, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #ifdef __FreeBSD__ #define _WITH_GETLINE #endif #include "parse_conf.h" #include "config_hooks.h" #include "dns_message.h" #include "compat.h" #include "client_subnet_index.h" #if defined(HAVE_LIBGEOIP) && defined(HAVE_GEOIP_H) #define HAVE_GEOIP 1 #include #endif #if defined(HAVE_LIBMAXMINDDB) && defined(HAVE_MAXMINDDB_H) #define HAVE_MAXMINDDB 1 #include #endif #include #include #include #include #define PARSE_CONF_EINVAL -2 #define PARSE_CONF_ERROR -1 #define PARSE_CONF_OK 0 #define PARSE_CONF_LAST 1 #define PARSE_CONF_COMMENT 2 #define PARSE_CONF_EMPTY 3 #define PARSE_MAX_ARGS 64 typedef enum conf_token_type conf_token_type_t; enum conf_token_type { TOKEN_END = 0, TOKEN_STRING, TOKEN_NUMBER, TOKEN_STRINGS, TOKEN_NUMBERS, TOKEN_ANY }; typedef struct conf_token conf_token_t; struct conf_token { conf_token_type_t type; const char* token; size_t length; }; typedef struct conf_token_syntax conf_token_syntax_t; struct conf_token_syntax { const char* token; int (*parse)(const conf_token_t* tokens); const conf_token_type_t syntax[PARSE_MAX_ARGS]; }; int parse_conf_token(char** conf, size_t* length, conf_token_t* token) { int quoted = 0, end = 0; if (!conf || !*conf || !length || !token) { return PARSE_CONF_EINVAL; } if (!*length) { return PARSE_CONF_ERROR; } if (**conf == ' ' || **conf == '\t' || **conf == ';' || !**conf || **conf == '\n' || **conf == '\r') { return PARSE_CONF_ERROR; } if (**conf == '#') { return PARSE_CONF_COMMENT; } if (**conf == '"') { quoted = 1; (*conf)++; (*length)--; token->type = TOKEN_STRING; } else { token->type = TOKEN_NUMBER; } token->token = *conf; token->length = 0; for (; **conf && length; (*conf)++, (*length)--) { if (quoted && **conf == '"') { end = 1; quoted = 0; continue; } else if ((!quoted || end) && (**conf == ' ' || **conf == '\t' || **conf == ';')) { while (length && (**conf == ' ' || **conf == '\t')) { (*conf)++; (*length)--; } if (**conf == ';') { return PARSE_CONF_LAST; } return PARSE_CONF_OK; } else if (end || **conf == '\n' || **conf == '\r' || !**conf) { return PARSE_CONF_ERROR; } if (**conf < '0' || **conf > '9') { token->type = TOKEN_STRING; } token->length++; } return PARSE_CONF_ERROR; } int parse_conf_interface(const conf_token_t* tokens) { char* interface = strndup(tokens[1].token, tokens[1].length); int ret; if (!interface) { errno = ENOMEM; return -1; } ret = open_interface(interface); free(interface); return ret == 1 ? 0 : 1; } int parse_conf_run_dir(const conf_token_t* tokens) { char* run_dir = strndup(tokens[1].token, tokens[1].length); int ret; if (!run_dir) { errno = ENOMEM; return -1; } ret = set_run_dir(run_dir); free(run_dir); return ret == 1 ? 0 : 1; } int parse_conf_minfree_bytes(const conf_token_t* tokens) { char* minfree_bytes = strndup(tokens[1].token, tokens[1].length); int ret; if (!minfree_bytes) { errno = ENOMEM; return -1; } ret = set_minfree_bytes(minfree_bytes); free(minfree_bytes); return ret == 1 ? 0 : 1; } int parse_conf_pid_file(const conf_token_t* tokens) { char* pid_file = strndup(tokens[1].token, tokens[1].length); int ret; if (!pid_file) { errno = ENOMEM; return -1; } ret = set_pid_file(pid_file); free(pid_file); return ret == 1 ? 0 : 1; } int parse_conf_statistics_interval(const conf_token_t* tokens) { char* statistics_interval = strndup(tokens[1].token, tokens[1].length); int ret; if (!statistics_interval) { errno = ENOMEM; return -1; } ret = set_statistics_interval(statistics_interval); free(statistics_interval); return ret == 1 ? 0 : 1; } int parse_conf_local_address(const conf_token_t* tokens) { char* local_address = strndup(tokens[1].token, tokens[1].length); char* local_mask = 0; int ret; if (!local_address) { errno = ENOMEM; return -1; } if (tokens[2].token != TOKEN_END && !(local_mask = strndup(tokens[2].token, tokens[2].length))) { free(local_address); errno = ENOMEM; return -1; } ret = add_local_address(local_address, local_mask); free(local_address); free(local_mask); return ret == 1 ? 0 : 1; } int parse_conf_bpf_program(const conf_token_t* tokens) { char* bpf_program = strndup(tokens[1].token, tokens[1].length); int ret; if (!bpf_program) { errno = ENOMEM; return -1; } ret = set_bpf_program(bpf_program); free(bpf_program); return ret == 1 ? 0 : 1; } int parse_conf_dataset(const conf_token_t* tokens) { char* name = strndup(tokens[1].token, tokens[1].length); char* layer = strndup(tokens[2].token, tokens[2].length); char* dim1_name = strndup(tokens[3].token, tokens[3].length); char* dim1_indexer; char* dim2_name = strndup(tokens[4].token, tokens[4].length); char* dim2_indexer; char* filter = strndup(tokens[5].token, tokens[5].length); int ret; dataset_opt opts; size_t i; if (!name || !layer || !dim1_name || !dim2_name || !filter) { free(name); free(layer); free(dim1_name); free(dim2_name); free(filter); errno = ENOMEM; return -1; } opts.min_count = 0; // min cell count to report opts.max_cells = 0; // max 2nd dim cells to print for (i = 6; tokens[i].type != TOKEN_END; i++) { char* opt = strndup(tokens[i].token, tokens[i].length); char* arg; ret = 0; if (!opt) { errno = ENOMEM; ret = -1; } else if (!(arg = strchr(opt, '='))) { ret = 1; } else { *arg = 0; arg++; if (!*arg) { ret = 1; } else if (!strcmp(opt, "min-count")) { opts.min_count = atoi(arg); } else if (!strcmp(opt, "max-cells")) { opts.max_cells = atoi(arg); } else { ret = 1; } } free(opt); if (ret) { free(name); free(layer); free(dim1_name); free(dim2_name); free(filter); return ret; } } if (!(dim1_indexer = strchr(dim1_name, ':')) || !(dim2_indexer = strchr(dim2_name, ':'))) { ret = 1; } else { *dim1_indexer = *dim2_indexer = 0; dim1_indexer++; dim2_indexer++; ret = add_dataset(name, layer, dim1_name, dim1_indexer, dim2_name, dim2_indexer, filter, opts); } free(name); free(layer); free(dim1_name); free(dim2_name); free(filter); return ret == 1 ? 0 : 1; } int parse_conf_bpf_vlan_tag_byte_order(const conf_token_t* tokens) { char* bpf_vlan_tag_byte_order = strndup(tokens[1].token, tokens[1].length); int ret; if (!bpf_vlan_tag_byte_order) { errno = ENOMEM; return -1; } ret = set_bpf_vlan_tag_byte_order(bpf_vlan_tag_byte_order); free(bpf_vlan_tag_byte_order); return ret == 1 ? 0 : 1; } int parse_conf_output_format(const conf_token_t* tokens) { char* output_format = strndup(tokens[1].token, tokens[1].length); int ret; if (!output_format) { errno = ENOMEM; return -1; } ret = set_output_format(output_format); free(output_format); return ret == 1 ? 0 : 1; } int parse_conf_match_vlan(const conf_token_t* tokens) { int ret = 0; size_t i; for (i = 1; tokens[i].type != TOKEN_END; i++) { char* match_vlan = strndup(tokens[i].token, tokens[i].length); if (!match_vlan) { errno = ENOMEM; return -1; } ret = set_match_vlan(match_vlan); free(match_vlan); if (ret != 1) { break; } } return ret == 1 ? 0 : 1; } int parse_conf_qname_filter(const conf_token_t* tokens) { char* name = strndup(tokens[1].token, tokens[1].length); char* re = strndup(tokens[2].token, tokens[2].length); int ret; if (!name || !re) { free(name); free(re); errno = ENOMEM; return -1; } ret = add_qname_filter(name, re); free(name); free(re); return ret == 1 ? 0 : 1; } int parse_conf_dump_reports_on_exit(const conf_token_t* tokens) { set_dump_reports_on_exit(); return 0; } #ifdef HAVE_GEOIP int parse_conf_geoip_options(const conf_token_t* tokens, int* options) { size_t i; for (i = 2; tokens[i].type != TOKEN_END; i++) { if (!strncmp(tokens[i].token, "STANDARD", tokens[i].length)) { *options |= GEOIP_STANDARD; } else if (!strncmp(tokens[i].token, "MEMORY_CACHE", tokens[i].length)) { *options |= GEOIP_MEMORY_CACHE; } else if (!strncmp(tokens[i].token, "CHECK_CACHE", tokens[i].length)) { *options |= GEOIP_CHECK_CACHE; } else if (!strncmp(tokens[i].token, "INDEX_CACHE", tokens[i].length)) { *options |= GEOIP_INDEX_CACHE; } else if (!strncmp(tokens[i].token, "MMAP_CACHE", tokens[i].length)) { *options |= GEOIP_MMAP_CACHE; } else { return 1; } } return 0; } #endif int parse_conf_geoip_v4_dat(const conf_token_t* tokens) { #ifdef HAVE_GEOIP char* geoip_v4_dat = strndup(tokens[1].token, tokens[1].length); int ret, options = 0; if (!geoip_v4_dat) { errno = ENOMEM; return -1; } if ((ret = parse_conf_geoip_options(tokens, &options))) { free(geoip_v4_dat); return ret; } ret = set_geoip_v4_dat(geoip_v4_dat, options); free(geoip_v4_dat); return ret == 1 ? 0 : 1; #else fprintf(stderr, "GeoIP support not built in!\n"); return 1; #endif } int parse_conf_geoip_v6_dat(const conf_token_t* tokens) { #ifdef HAVE_GEOIP char* geoip_v6_dat = strndup(tokens[1].token, tokens[1].length); int ret, options = 0; if (!geoip_v6_dat) { errno = ENOMEM; return -1; } if ((ret = parse_conf_geoip_options(tokens, &options))) { free(geoip_v6_dat); return ret; } ret = set_geoip_v6_dat(geoip_v6_dat, options); free(geoip_v6_dat); return ret == 1 ? 0 : 1; #else fprintf(stderr, "GeoIP support not built in!\n"); return 1; #endif } int parse_conf_geoip_asn_v4_dat(const conf_token_t* tokens) { #ifdef HAVE_GEOIP char* geoip_asn_v4_dat = strndup(tokens[1].token, tokens[1].length); int ret, options = 0; if (!geoip_asn_v4_dat) { errno = ENOMEM; return -1; } if ((ret = parse_conf_geoip_options(tokens, &options))) { free(geoip_asn_v4_dat); return ret; } ret = set_geoip_asn_v4_dat(geoip_asn_v4_dat, options); free(geoip_asn_v4_dat); return ret == 1 ? 0 : 1; #else fprintf(stderr, "GeoIP support not built in!\n"); return 1; #endif } int parse_conf_geoip_asn_v6_dat(const conf_token_t* tokens) { #ifdef HAVE_GEOIP char* geoip_asn_v6_dat = strndup(tokens[1].token, tokens[1].length); int ret, options = 0; if (!geoip_asn_v6_dat) { errno = ENOMEM; return -1; } if ((ret = parse_conf_geoip_options(tokens, &options))) { free(geoip_asn_v6_dat); return ret; } ret = set_geoip_asn_v6_dat(geoip_asn_v6_dat, options); free(geoip_asn_v6_dat); return ret == 1 ? 0 : 1; #else fprintf(stderr, "GeoIP support not built in!\n"); return 1; #endif } int parse_conf_asn_indexer_backend(const conf_token_t* tokens) { if (!strncmp(tokens[1].token, "geoip", tokens[1].length)) { #ifdef HAVE_GEOIP return set_asn_indexer_backend(geoip_backend_libgeoip) == 1 ? 0 : 1; #else fprintf(stderr, "GeoIP support not built in!\n"); #endif } else if (!strncmp(tokens[1].token, "maxminddb", tokens[1].length)) { #ifdef HAVE_MAXMINDDB return set_asn_indexer_backend(geoip_backend_libmaxminddb) == 1 ? 0 : 1; #else fprintf(stderr, "MaxMind DB support not built in!\n"); #endif } return 1; } int parse_conf_country_indexer_backend(const conf_token_t* tokens) { if (!strncmp(tokens[1].token, "geoip", tokens[1].length)) { #ifdef HAVE_GEOIP return set_country_indexer_backend(geoip_backend_libgeoip) == 1 ? 0 : 1; #else fprintf(stderr, "GeoIP support not built in!\n"); #endif } else if (!strncmp(tokens[1].token, "maxminddb", tokens[1].length)) { #ifdef HAVE_MAXMINDDB return set_country_indexer_backend(geoip_backend_libmaxminddb) == 1 ? 0 : 1; #else fprintf(stderr, "MaxMind DB support not built in!\n"); #endif } return 1; } int parse_conf_maxminddb_asn(const conf_token_t* tokens) { #ifdef HAVE_MAXMINDDB char* maxminddb_asn = strndup(tokens[1].token, tokens[1].length); int ret; if (!maxminddb_asn) { errno = ENOMEM; return -1; } ret = set_maxminddb_asn(maxminddb_asn); free(maxminddb_asn); return ret == 1 ? 0 : 1; #else fprintf(stderr, "MaxMind DB support not built in!\n"); return 1; #endif } int parse_conf_maxminddb_country(const conf_token_t* tokens) { #ifdef HAVE_MAXMINDDB char* maxminddb_country = strndup(tokens[1].token, tokens[1].length); int ret; if (!maxminddb_country) { errno = ENOMEM; return -1; } ret = set_maxminddb_country(maxminddb_country); free(maxminddb_country); return ret == 1 ? 0 : 1; #else fprintf(stderr, "MaxMind DB support not built in!\n"); return 1; #endif } int parse_conf_pcap_buffer_size(const conf_token_t* tokens) { char* pcap_buffer_size = strndup(tokens[1].token, tokens[1].length); int ret; if (!pcap_buffer_size) { errno = ENOMEM; return -1; } ret = set_pcap_buffer_size(pcap_buffer_size); free(pcap_buffer_size); return ret == 1 ? 0 : 1; } int parse_conf_no_wait_interval(const conf_token_t* tokens) { set_no_wait_interval(); return 0; } int parse_conf_pcap_thread_timeout(const conf_token_t* tokens) { char* timeout = strndup(tokens[1].token, tokens[1].length); int ret; if (!timeout) { errno = ENOMEM; return -1; } ret = set_pt_timeout(timeout); free(timeout); return ret == 1 ? 0 : 1; } int parse_conf_drop_ip_fragments(const conf_token_t* tokens) { set_drop_ip_fragments(); return 0; } int parse_conf_client_v4_mask(const conf_token_t* tokens) { char* mask = strndup(tokens[1].token, tokens[1].length); int ret; if (!mask) { errno = ENOMEM; return -1; } ret = client_subnet_v4_mask_set(mask); free(mask); return ret == 1 ? 0 : 1; } int parse_conf_client_v6_mask(const conf_token_t* tokens) { char* mask = strndup(tokens[1].token, tokens[1].length); int ret; if (!mask) { errno = ENOMEM; return -1; } ret = client_subnet_v6_mask_set(mask); free(mask); return ret == 1 ? 0 : 1; } int parse_conf_dns_port(const conf_token_t* tokens) { char* dns_port = strndup(tokens[1].token, tokens[1].length); int ret; if (!dns_port) { errno = ENOMEM; return -1; } ret = set_dns_port(dns_port); free(dns_port); return ret == 1 ? 0 : 1; } int parse_conf_response_time_mode(const conf_token_t* tokens) { char* s = strndup(tokens[1].token, tokens[1].length); int ret; if (!s) { errno = ENOMEM; return -1; } ret = set_response_time_mode(s); free(s); return ret == 1 ? 0 : 1; } int parse_conf_response_time_max_queries(const conf_token_t* tokens) { char* s = strndup(tokens[1].token, tokens[1].length); int ret; if (!s) { errno = ENOMEM; return -1; } ret = set_response_time_max_queries(s); free(s); return ret == 1 ? 0 : 1; } int parse_conf_response_time_full_mode(const conf_token_t* tokens) { char* s = strndup(tokens[1].token, tokens[1].length); int ret; if (!s) { errno = ENOMEM; return -1; } ret = set_response_time_full_mode(s); free(s); return ret == 1 ? 0 : 1; } int parse_conf_response_time_max_seconds(const conf_token_t* tokens) { char* s = strndup(tokens[1].token, tokens[1].length); int ret; if (!s) { errno = ENOMEM; return -1; } ret = set_response_time_max_seconds(s); free(s); return ret == 1 ? 0 : 1; } int parse_conf_response_time_max_sec_mode(const conf_token_t* tokens) { char* s = strndup(tokens[1].token, tokens[1].length); int ret; if (!s) { errno = ENOMEM; return -1; } ret = set_response_time_max_sec_mode(s); free(s); return ret == 1 ? 0 : 1; } int parse_conf_response_time_bucket_size(const conf_token_t* tokens) { char* s = strndup(tokens[1].token, tokens[1].length); int ret; if (!s) { errno = ENOMEM; return -1; } ret = set_response_time_bucket_size(s); free(s); return ret == 1 ? 0 : 1; } int parse_conf_dnstap_file(const conf_token_t* tokens) { char* file = strndup(tokens[1].token, tokens[1].length); int ret; if (!file) { errno = ENOMEM; return -1; } ret = open_dnstap(dnstap_via_file, file, 0, 0, 0, 0); free(file); return ret == 1 ? 0 : 1; } int parse_conf_dnstap_unixsock(const conf_token_t* tokens) { char* unixsock = strndup(tokens[1].token, tokens[1].length); char* user = 0; char* group = 0; char* umask = 0; int ret; if (!unixsock) { errno = ENOMEM; return -1; } if (tokens[2].type != TOKEN_END) { int t = 2; if (tokens[t].token[0] != '0') { if (!(user = strndup(tokens[t].token, tokens[t].length))) { free(unixsock); errno = ENOMEM; return -1; } if ((group = strchr(user, ':'))) { *group = 0; group++; } t++; } if (tokens[t].type != TOKEN_END) { if (!(umask = strndup(tokens[t].token, tokens[t].length))) { free(unixsock); free(user); errno = ENOMEM; return -1; } } } ret = open_dnstap(dnstap_via_unixsock, unixsock, 0, user, group, umask); free(unixsock); free(user); free(umask); return ret == 1 ? 0 : 1; } int parse_conf_dnstap_tcp(const conf_token_t* tokens) { char* host = strndup(tokens[1].token, tokens[1].length); char* port = strndup(tokens[2].token, tokens[2].length); int ret; if (!host || !port) { free(host); free(port); errno = ENOMEM; return -1; } ret = open_dnstap(dnstap_via_tcp, host, port, 0, 0, 0); free(host); free(port); return ret == 1 ? 0 : 1; } int parse_conf_dnstap_udp(const conf_token_t* tokens) { char* host = strndup(tokens[1].token, tokens[1].length); char* port = strndup(tokens[2].token, tokens[2].length); int ret; if (!host || !port) { free(host); free(port); errno = ENOMEM; return -1; } ret = open_dnstap(dnstap_via_udp, host, port, 0, 0, 0); free(host); free(port); return ret == 1 ? 0 : 1; } int parse_conf_dnstap_network(const conf_token_t* tokens) { extern char* dnstap_network_ip4; extern char* dnstap_network_ip6; extern int dnstap_network_port; char* port = strndup(tokens[3].token, tokens[3].length); if (dnstap_network_ip4) free(dnstap_network_ip4); dnstap_network_ip4 = strndup(tokens[1].token, tokens[1].length); if (dnstap_network_ip6) free(dnstap_network_ip6); dnstap_network_ip6 = strndup(tokens[2].token, tokens[2].length); if (!dnstap_network_ip4 || !dnstap_network_ip6 || !port) { errno = ENOMEM; free(port); return -1; } dnstap_network_port = atoi(port); free(port); return dnstap_network_port < 0 ? 1 : 0; } int parse_conf_knowntlds_file(const conf_token_t* tokens) { char* file = strndup(tokens[1].token, tokens[1].length); int ret; if (!file) { errno = ENOMEM; return -1; } ret = load_knowntlds(file); free(file); return ret == 1 ? 0 : 1; } int parse_conf_tld_list(const conf_token_t* tokens) { char* file = strndup(tokens[1].token, tokens[1].length); int ret; if (!file) { errno = ENOMEM; return -1; } ret = load_tld_list(file); free(file); return ret == 1 ? 0 : 1; } int parse_conf_output_user(const conf_token_t* tokens) { char* user = strndup(tokens[1].token, tokens[1].length); int ret; if (!user) { errno = ENOMEM; return -1; } ret = set_output_user(user); free(user); return ret == 1 ? 0 : 1; } int parse_conf_output_group(const conf_token_t* tokens) { char* group = strndup(tokens[1].token, tokens[1].length); int ret; if (!group) { errno = ENOMEM; return -1; } ret = set_output_group(group); free(group); return ret == 1 ? 0 : 1; } int parse_conf_output_mod(const conf_token_t* tokens) { char* mod = strndup(tokens[1].token, tokens[1].length); int ret; if (!mod) { errno = ENOMEM; return -1; } ret = set_output_mod(mod); free(mod); return ret == 1 ? 0 : 1; } static conf_token_syntax_t _syntax[] = { { "interface", parse_conf_interface, { TOKEN_STRING, TOKEN_END } }, { "run_dir", parse_conf_run_dir, { TOKEN_STRING, TOKEN_END } }, { "minfree_bytes", parse_conf_minfree_bytes, { TOKEN_NUMBER, TOKEN_END } }, { "pid_file", parse_conf_pid_file, { TOKEN_STRING, TOKEN_END } }, { "statistics_interval", parse_conf_statistics_interval, { TOKEN_NUMBER, TOKEN_END } }, { "local_address", parse_conf_local_address, { TOKEN_STRING, TOKEN_ANY, TOKEN_END } }, { "bpf_program", parse_conf_bpf_program, { TOKEN_STRING, TOKEN_END } }, { "dataset", parse_conf_dataset, { TOKEN_STRING, TOKEN_STRING, TOKEN_STRING, TOKEN_STRING, TOKEN_STRING, TOKEN_STRINGS, TOKEN_END } }, { "bpf_vlan_tag_byte_order", parse_conf_bpf_vlan_tag_byte_order, { TOKEN_STRING, TOKEN_END } }, { "output_format", parse_conf_output_format, { TOKEN_STRING, TOKEN_END } }, { "match_vlan", parse_conf_match_vlan, { TOKEN_NUMBER, TOKEN_NUMBERS, TOKEN_END } }, { "qname_filter", parse_conf_qname_filter, { TOKEN_STRING, TOKEN_STRING, TOKEN_END } }, { "dump_reports_on_exit", parse_conf_dump_reports_on_exit, { TOKEN_END } }, { "geoip_v4_dat", parse_conf_geoip_v4_dat, { TOKEN_STRING, TOKEN_STRINGS, TOKEN_END } }, { "geoip_v6_dat", parse_conf_geoip_v6_dat, { TOKEN_STRING, TOKEN_STRINGS, TOKEN_END } }, { "geoip_asn_v4_dat", parse_conf_geoip_asn_v4_dat, { TOKEN_STRING, TOKEN_STRINGS, TOKEN_END } }, { "geoip_asn_v6_dat", parse_conf_geoip_asn_v6_dat, { TOKEN_STRING, TOKEN_STRINGS, TOKEN_END } }, { "pcap_buffer_size", parse_conf_pcap_buffer_size, { TOKEN_NUMBER, TOKEN_END } }, { "no_wait_interval", parse_conf_no_wait_interval, { TOKEN_END } }, { "pcap_thread_timeout", parse_conf_pcap_thread_timeout, { TOKEN_NUMBER, TOKEN_END } }, { "drop_ip_fragments", parse_conf_drop_ip_fragments, { TOKEN_END } }, { "client_v4_mask", parse_conf_client_v4_mask, { TOKEN_STRING, TOKEN_END } }, { "client_v6_mask", parse_conf_client_v6_mask, { TOKEN_STRING, TOKEN_END } }, { "asn_indexer_backend", parse_conf_asn_indexer_backend, { TOKEN_STRING, TOKEN_END } }, { "country_indexer_backend", parse_conf_country_indexer_backend, { TOKEN_STRING, TOKEN_END } }, { "maxminddb_asn", parse_conf_maxminddb_asn, { TOKEN_STRING, TOKEN_END } }, { "maxminddb_country", parse_conf_maxminddb_country, { TOKEN_STRING, TOKEN_END } }, { "dns_port", parse_conf_dns_port, { TOKEN_NUMBER, TOKEN_END } }, { "response_time_mode", parse_conf_response_time_mode, { TOKEN_STRING, TOKEN_END } }, { "response_time_max_queries", parse_conf_response_time_max_queries, { TOKEN_NUMBER, TOKEN_END } }, { "response_time_full_mode", parse_conf_response_time_full_mode, { TOKEN_STRING, TOKEN_END } }, { "response_time_max_seconds", parse_conf_response_time_max_seconds, { TOKEN_NUMBER, TOKEN_END } }, { "response_time_max_sec_mode", parse_conf_response_time_max_sec_mode, { TOKEN_STRING, TOKEN_END } }, { "response_time_bucket_size", parse_conf_response_time_bucket_size, { TOKEN_NUMBER, TOKEN_END } }, { "dnstap_file", parse_conf_dnstap_file, { TOKEN_STRING, TOKEN_END } }, { "dnstap_unixsock", parse_conf_dnstap_unixsock, { TOKEN_ANY, TOKEN_END } }, { "dnstap_tcp", parse_conf_dnstap_tcp, { TOKEN_STRING, TOKEN_NUMBER, TOKEN_END } }, { "dnstap_udp", parse_conf_dnstap_udp, { TOKEN_STRING, TOKEN_NUMBER, TOKEN_END } }, { "dnstap_network", parse_conf_dnstap_network, { TOKEN_STRING, TOKEN_STRING, TOKEN_NUMBER, TOKEN_END } }, { "knowntlds_file", parse_conf_knowntlds_file, { TOKEN_STRING, TOKEN_END } }, { "tld_list", parse_conf_tld_list, { TOKEN_STRING, TOKEN_END } }, { "output_user", parse_conf_output_user, { TOKEN_STRING, TOKEN_END } }, { "output_group", parse_conf_output_group, { TOKEN_STRING, TOKEN_END } }, { "output_mod", parse_conf_output_mod, { TOKEN_NUMBER, TOKEN_END } }, { 0, 0, { TOKEN_END } } }; int parse_conf_tokens(const conf_token_t* tokens, size_t token_size, size_t line) { const conf_token_syntax_t* syntax; const conf_token_type_t* type; size_t i; if (!tokens || !token_size) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Internal error, please report!\n", line); return 1; } if (tokens[0].type != TOKEN_STRING) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Wrong first token, expected a string\n", line); return 1; } for (syntax = _syntax; syntax->token; syntax++) { if (!strncmp(tokens[0].token, syntax->token, tokens[0].length)) { break; } } if (!syntax->token) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Unknown configuration option: ", line); fwrite(tokens[0].token, tokens[0].length, 1, stderr); fprintf(stderr, "\n"); return 1; } for (type = syntax->syntax, i = 1; *type != TOKEN_END && i < token_size; i++) { if (*type == TOKEN_STRINGS) { if (tokens[i].type != TOKEN_STRING) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Wrong token for argument %zu, expected a string\n", line, i); return 1; } continue; } if (*type == TOKEN_NUMBERS) { if (tokens[i].type != TOKEN_NUMBER) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Wrong token for argument %zu, expected a number\n", line, i); return 1; } continue; } if (*type == TOKEN_ANY) { if (tokens[i].type != TOKEN_STRING && tokens[i].type != TOKEN_NUMBER) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Wrong token for argument %zu, expected a string or number\n", line, i); return 1; } continue; } if (tokens[i].type != *type) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Wrong token for argument %zu", line, i); if (*type == TOKEN_STRING) { fprintf(stderr, ", expected a string\n"); } else if (*type == TOKEN_NUMBER) { fprintf(stderr, ", expected a number\n"); } else { fprintf(stderr, "\n"); } return 1; } type++; } if (syntax->parse) { int ret = syntax->parse(tokens); if (ret < 0) { char errbuf[512]; fprintf(stderr, "CONFIG ERROR [line:%zu]: %s\n", line, dsc_strerror(errno, errbuf, sizeof(errbuf))); } if (ret > 0) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Unable to configure ", line); fwrite(tokens[0].token, tokens[0].length, 1, stderr); fprintf(stderr, "\n"); } return ret ? 1 : 0; } return 0; } int parse_conf(const char* file) { FILE* fp; char* buffer = 0; size_t bufsize = 0; char* buf; size_t s, i, line = 0; conf_token_t tokens[PARSE_MAX_ARGS]; int ret, ret2; if (!file) { return 1; } if (!(fp = fopen(file, "r"))) { return 1; } while ((ret2 = getline(&buffer, &bufsize, fp)) > 0 && buffer) { memset(tokens, 0, sizeof(conf_token_t) * PARSE_MAX_ARGS); line++; /* * Go to the first non white-space character */ ret = PARSE_CONF_OK; for (buf = buffer, s = bufsize; *buf && s; buf++, s--) { if (*buf != ' ' && *buf != '\t') { if (*buf == '\n' || *buf == '\r') { ret = PARSE_CONF_EMPTY; } break; } } /* * Parse all the tokens */ for (i = 0; i < PARSE_MAX_ARGS && ret == PARSE_CONF_OK; i++) { ret = parse_conf_token(&buf, &s, &tokens[i]); } if (ret == PARSE_CONF_COMMENT) { /* * Line ended with comment, reduce the number of tokens */ i--; if (!i) { /* * Comment was the only token so the line is empty */ continue; } } else if (ret == PARSE_CONF_EMPTY) { continue; } else if (ret == PARSE_CONF_OK) { if (i > 0 && tokens[0].type == TOKEN_STRING) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Too many arguments for ", line); fwrite(tokens[0].token, tokens[0].length, 1, stderr); fprintf(stderr, " at line %zu\n", line); } else { fprintf(stderr, "CONFIG ERROR [line:%zu]: Too many arguments at line %zu\n", line, line); } free(buffer); fclose(fp); return 1; } else if (ret != PARSE_CONF_LAST) { if (i > 0 && tokens[0].type == TOKEN_STRING) { fprintf(stderr, "CONFIG ERROR [line:%zu]: Invalid syntax for ", line); fwrite(tokens[0].token, tokens[0].length, 1, stderr); fprintf(stderr, " at line %zu\n", line); } else { fprintf(stderr, "CONFIG ERROR [line:%zu]: Invalid syntax at line %zu\n", line, line); } free(buffer); fclose(fp); return 1; } /* * Configure using the tokens */ if (parse_conf_tokens(tokens, i, line)) { free(buffer); fclose(fp); return 1; } } if (ret2 < 0) { long pos; char errbuf[512]; pos = ftell(fp); if (fseek(fp, 0, SEEK_END)) { fprintf(stderr, "CONFIG ERROR [line:%zu]: fseek(): %s\n", line, dsc_strerror(errno, errbuf, sizeof(errbuf))); } else if (ftell(fp) < pos) { fprintf(stderr, "CONFIG ERROR [line:%zu]: getline(): %s\n", line, dsc_strerror(errno, errbuf, sizeof(errbuf))); } } free(buffer); fclose(fp); return 0; }