/* This file is part of Ping903
Copyright (C) 2020-2023 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;
}
char *rbt_insert_result_str[] = {
[RBT_LOOKUP_SUCCESS] = "hostname or IP address already exists",
[RBT_LOOKUP_NOENT] = "inserted successfully",
[RBT_LOOKUP_FAILURE] = "out of memory"
};
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 != RBT_LOOKUP_NOENT) {
error("%s:%d: %s", fname, ln,
rbt_insert_result_str[rc]);
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 != RBT_LOOKUP_NOENT) {
error("%s:%d: %s",
arg->heredoc.file, arg->heredoc.line,
rbt_insert_result_str[rc]);
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;
}