/* This file is part of Ping903 Copyright (C) 2020 Sergey Poznyakoff Ping903 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Ping903 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ping903. If not, see . */ #include #include #include #include #include #include #include #include #include #include "ping903.h" #include "json.h" #include "defs.h" static int get_num(char const *str, unsigned long *retval, char const *file, unsigned line) { unsigned long n; char *p; errno = 0; n = strtoul(str, &p, 10); if (errno || *p) { error("%s:%d: invalid numeric value", file, line); return -1; } *retval = n; return 0; } static int cf_ip_list_parse(union cf_callback_arg *arg, void *data) { FILE *fp; int rc; fp = fopen(arg->input.val, "r"); if (!fp) { error("%s:%d: can't open file %s: %s", arg->input.file, arg->input.line, arg->input.val, strerror(errno)); exit(1); } rc = file_read_ip_list(fp, arg->input.val); fclose(fp); return rc; } int file_read_ip_list(FILE *fp, char const *fname) { char buf[1024]; unsigned ln = 0; int skip_to_eol = 0; int ret = CF_RET_OK; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_protocol = IPPROTO_TCP; while (fgets(buf, sizeof(buf), fp)) { int len; char *p; struct addrinfo *res; int rc; ln++; len = strlen(buf); if (len == 0) continue; if (skip_to_eol) { skip_to_eol = buf[len-1] != '\n'; continue; } if (buf[len-1] == '\n') buf[--len] = 0; else if (!feof(fp)) { error("%s:%d: line too long", fname, ln); skip_to_eol = 1; ret = CF_RET_FAIL; continue; } while (len > 0 && isspace(buf[len-1])) buf[--len] = 0; if (len == 0) continue; for (p = buf; (*p == ' ' || *p == '\t'); p++) ; if (*p == 0 || *p == '#') continue; rc = getaddrinfo(p, NULL, &hints, &res); if (rc) { error("%s:%d: invalid IP address", fname, ln); ret = CF_RET_FAIL; continue; } rc = pinger_host_add(p, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); if (rc) { error("%s:%d: out of memory", fname, ln); ret = CF_RET_FAIL; break; } } return ret; } static int cf_ip_list_serialize(union cf_callback_arg *arg, void *data) { if (get_all_hosts(&arg->output)) return CF_RET_FAIL; return CF_RET_OK; } static int cf_ip_list(int mode, union cf_callback_arg *arg, void *data) { switch (mode) { case CF_PARSE: return cf_ip_list_parse(arg, data); case CF_SERIALIZE: return cf_ip_list_serialize(arg, data); } abort(); } static int cf_ip_list_heredoc(int mode, union cf_callback_arg *arg, void *data) { struct addrinfo hints, *res; int rc; if (mode == CF_SERIALIZE) return CF_RET_IGNORE; switch (arg->heredoc.op) { case CF_HEREDOC_START: case CF_HEREDOC_STOP: break; case CF_HEREDOC_NEXT: memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_protocol = IPPROTO_TCP; rc = getaddrinfo(arg->heredoc.val, NULL, &hints, &res); if (rc) { error("%s:%d: invalid IP address", arg->heredoc.file, arg->heredoc.line); return CF_RET_FAIL; } rc = pinger_host_add(arg->heredoc.val, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); if (rc) { error("%s:%d: out of memory", arg->heredoc.file, arg->heredoc.line); return CF_RET_FAIL; } } return CF_RET_OK; } struct cf_stmt { char const *kw; int type; void *data; CF_CALLBACK callback; }; struct cf_stmt statements[] = { { "listen", STMT_T_STRING, &httpd_addr }, { "pidfile", STMT_T_STRING, &pidfile }, { "trusted-ip", STMT_T_CALLBACK, NULL, cf_trusted_ip }, { "trusted-ip", STMT_T_HEREDOC, NULL, cf_trusted_ip_heredoc }, { "probe-interval", STMT_T_ULONG, &probe_interval }, { "ping-interval", STMT_T_ULONG, &ping_interval }, { "ping-count", STMT_T_ULONG, &ping_count }, { "tolerance", STMT_T_ULONG, &ping_tolerance }, { "ip-list", STMT_T_CALLBACK, NULL, cf_ip_list }, { "ip-list", STMT_T_HEREDOC, NULL, cf_ip_list_heredoc }, { "data-length", STMT_T_ULONG, &data_length }, { "syslog-facility", STMT_T_CALLBACK, NULL, cf_syslog_facility }, { "access-log", STMT_T_BOOL, &httpd_access_log }, { "access-log-verbose", STMT_T_BOOL, &httpd_log_verbose }, { "http-backlog-size", STMT_T_ULONG, &httpd_backlog_size }, { "auth", STMT_T_CALLBACK, NULL, cf_auth }, { NULL } }; static struct cf_stmt * find_stmt(char const *kw, int heredoc) { struct cf_stmt *cf; for (cf = statements; cf->kw; cf++) { if (((heredoc && cf->type == STMT_T_HEREDOC) || (!heredoc && cf->type != STMT_T_HEREDOC)) && strcmp(cf->kw, kw) == 0) return cf; } return NULL; } static int stmt_parse(char const *kw, char const *val, char const *file, unsigned line) { struct cf_stmt *cf = find_stmt(kw, 0); union cf_callback_arg arg; if (!cf) { error("%s:%d: unrecognized keyword", file, line); return -1; } switch (cf->type) { case STMT_T_STRING: *(char**)cf->data = estrdup(val); break; case STMT_T_ULONG: return get_num(val, cf->data, file, line); case STMT_T_BOOL: { static char *t_val[] = { "1", "t", "true", "yes", "on", NULL }; static char *f_val[] = { "0", "f", "nil", "false", "no", "off", NULL }; int i; for (i = 0; t_val[i]; i++) { if (strcasecmp(t_val[i], val) == 0) { *(int*)cf->data = 1; return 0; } } for (i = 0; f_val[i]; i++) { if (strcasecmp(f_val[i], val) == 0) { *(int*)cf->data = 0; return 0; } } error("%s:%d: not a valid boolean value", file, line); return -1; } case STMT_T_CALLBACK: arg.input.val = val; arg.input.file = file; arg.input.line = line; return cf->callback(CF_PARSE, &arg, cf->data); default: abort(); } return 0; } int readconfig(char const *file) { FILE *fp; char buf[1024]; unsigned ln = 0; int skip_to_eol = 0; int ec = 0; struct { char *eot; CF_CALLBACK callback; void *data; unsigned line; } heredoc = { .eot = NULL }; fp = fopen(file, "r"); if (!fp) { error("can't open file %s: %s", file, strerror(errno)); exit(1); } while (fgets(buf, sizeof(buf), fp)) { int len; char *kw, *p; ln++; len = strlen(buf); if (len == 0) continue; if (skip_to_eol) { skip_to_eol = buf[len-1] != '\n'; continue; } if (buf[len-1] == '\n') buf[--len] = 0; else if (!feof(fp)) { error("%s:%d: line too long", file, ln); skip_to_eol = 1; ec = 1; continue; } while (len > 0 && isspace(buf[len-1])) buf[--len] = 0; if (len == 0) continue; for (p = buf; (*p == ' ' || *p == '\t'); p++) ; if (*p == 0 || *p == '#') continue; if (heredoc.eot) { union cf_callback_arg arg; arg.heredoc.file = file; arg.heredoc.line = ln; if (strcmp(heredoc.eot, p) == 0) { arg.heredoc.op = CF_HEREDOC_STOP; if (heredoc.callback(CF_PARSE, &arg, heredoc.data)) ec = 1; free(heredoc.eot); heredoc.eot = NULL; } else { arg.heredoc.op = CF_HEREDOC_NEXT; arg.heredoc.val = p; if (heredoc.callback(CF_PARSE, &arg, heredoc.data)) ec = 1; } continue; } kw = p; while (*p && isascii(*p) && (isalnum(*p) || *p == '_' || *p == '-')) p++; if (*p) *p++ = 0; while (*p == ' ' || *p == '\t') p++; if (!*p) { error("%s:%d: malformed statement", file, ln); ec = 1; continue; } if (p[0] == '<' && p[1] == '<') { p += 2; struct cf_stmt *cf = find_stmt(kw, 1); if (cf) { union cf_callback_arg arg; arg.heredoc.op = CF_HEREDOC_START; arg.heredoc.file = file; arg.heredoc.line = ln; heredoc.callback = cf->callback; heredoc.data = cf->data; heredoc.line = ln; heredoc.eot = estrdup(p); if (heredoc.callback(CF_PARSE, &arg, heredoc.data)) ec = 1; } else { error("%s:%d: unknown heredoc statement", file, ln); ec = 1; } continue; } if (stmt_parse(kw, p, file, ln)) { ec = 1; } } fclose(fp); if (heredoc.eot) { error("%s:%d: heredoc not closed", file, heredoc.line); ec = 1; } if ((double) ping_count * ping_interval >= probe_interval) { error("configuration error: ping_count * ping_interval >= probe_interval"); ec = 1; } return ec; } struct json_value * config_to_json(void) { struct cf_stmt *cf; struct json_value *obj = json_new_object(); struct json_value *vn; union cf_callback_arg arg; if (!obj) goto err; for (cf = statements; cf->kw; cf++) { switch (cf->type) { case STMT_T_STRING: if (*(char**)cf->data == NULL) vn = json_new_null(); else vn = json_new_string(*(char**)cf->data); break; case STMT_T_ULONG: vn = json_new_number(*(unsigned long*)cf->data); break; case STMT_T_BOOL: vn = json_new_bool(*(int*)cf->data); break; case STMT_T_CALLBACK: case STMT_T_HEREDOC: arg.output = NULL; switch (cf->callback(CF_SERIALIZE, &arg, cf->data)) { case CF_RET_OK: vn = arg.output; break; case CF_RET_IGNORE: continue; default: goto err; } } if (json_object_set(obj, cf->kw, vn)) { json_value_free(vn); goto err; } } return obj; err: json_value_free(obj); return NULL; }