aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2020-02-11 15:51:29 +0100
committerSergey Poznyakoff <gray@gnu.org.ua>2020-02-11 15:51:29 +0100
commit38d71b4e92d60b751ada2ee326acc22707aa5900 (patch)
tree94bdf2a2265d4f1e6f533d972c85816c71f4aeb0 /src
downloadping903-38d71b4e92d60b751ada2ee326acc22707aa5900.tar.gz
ping903-38d71b4e92d60b751ada2ee326acc22707aa5900.tar.bz2
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am32
-rw-r--r--src/config.c314
-rw-r--r--src/json.c898
-rw-r--r--src/json.h86
-rw-r--r--src/logger.c135
-rw-r--r--src/main.c297
-rw-r--r--src/ping903.c409
-rw-r--r--src/ping903.h131
-rw-r--r--src/pinger.c568
-rw-r--r--src/remoteip.c379
-rw-r--r--src/wrapacl.c21
12 files changed, 3271 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..deb4593
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1 @@
+/ping903
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..981856f
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,32 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+sbin_PROGRAMS=ping903
+ping903_SOURCES=\
+ main.c\
+ logger.c\
+ ping903.c\
+ ping903.h\
+ pinger.c\
+ config.c\
+ remoteip.c\
+ json.c\
+ json.h
+if COND_TCPD
+ ping903_SOURCES += wrapacl.c
+endif
+AM_YFLAGS = -dtv
+
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..26d858d
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,314 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include "ping903.h"
+#include "json.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(int mode, union cf_callback_arg *arg, void *data)
+{
+ FILE *fp;
+ char buf[1024];
+ unsigned ln = 0;
+ int skip_to_eol = 0;
+ int ret = CF_RET_OK;
+ struct addrinfo hints;
+
+ if (mode != CF_PARSE)
+ return CF_RET_IGNORE;
+
+ 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);
+ }
+
+ 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", arg->input.val, 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", arg->input.val, ln);
+ ret = CF_RET_FAIL;
+ continue;
+ }
+ if (hostaddr_count == hostaddr_max) {
+ hostaddr = e2nrealloc(hostaddr,
+ &hostaddr_max,
+ sizeof(hostaddr[0]));
+ }
+ memset(&hostaddr[hostaddr_count], 0, sizeof(hostaddr[0]));
+ hostaddr[hostaddr_count].name = estrdup(p);
+ hostaddr[hostaddr_count].addr = emalloc(res->ai_addrlen);
+ memcpy(hostaddr[hostaddr_count].addr, res->ai_addr, res->ai_addrlen);
+ hostaddr[hostaddr_count].addrlen = res->ai_addrlen;
+ hostaddr_count++;
+ freeaddrinfo(res);
+ }
+ fclose(fp);
+ return ret;
+}
+
+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 },
+ { "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 },
+ { "data-length", STMT_T_ULONG, &data_length },
+ { NULL }
+};
+
+static struct cf_stmt *
+find_stmt(char const *kw)
+{
+ struct cf_stmt *cf;
+ for (cf = statements; cf->kw; cf++) {
+ if (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);
+ 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:
+ abort();
+
+ 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;
+
+ 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;
+ 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 (stmt_parse(kw, p, file, ln)) {
+ ec = 1;
+ }
+ }
+ fclose(fp);
+ 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:
+ abort();
+
+ case STMT_T_CALLBACK:
+ 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;
+}
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..370d830
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,898 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#include "json.h"
+
+void *
+json_2nrealloc(void *p, size_t *pn, size_t s)
+{
+ size_t n = *pn;
+ char *newp;
+
+ if (!p) {
+ if (!n) {
+ /* The approximate size to use for initial small
+ allocation requests, when the invoking code
+ specifies an old size of zero. 64 bytes is
+ the largest "small" request for the
+ GNU C library malloc. */
+ enum { DEFAULT_MXFAST = 64 };
+
+ n = DEFAULT_MXFAST / s;
+ n += !n;
+ }
+ } else {
+ /* Set N = ceil (1.5 * N) so that progress is made if N == 1.
+ Check for overflow, so that N * S stays in size_t range.
+ The check is slightly conservative, but an exact check isn't
+ worth the trouble. */
+ if ((size_t) -1 / 3 * 2 / s <= n) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ n += (n + 1) / 2;
+ }
+
+ newp = realloc(p, n * s);
+ if (!newp)
+ return NULL;
+ *pn = n;
+ return newp;
+}
+
+static void
+json_format_writez(struct json_format *fmt, char const *str)
+{
+ size_t len = strlen(str);
+ fmt->write(fmt->data, str, len);
+}
+
+static void
+json_format_writec(struct json_format *fmt, char c)
+{
+ fmt->write(fmt->data, &c, 1);
+}
+
+static void
+json_format_indent(struct json_format *fmt, size_t level)
+{
+ level *= fmt->indent;
+ while (level--)
+ json_format_writec(fmt, ' ');
+}
+
+static void
+json_format_delim(struct json_format *fmt, size_t level)
+{
+ json_format_writec(fmt, ',');
+ if (fmt->indent) {
+ json_format_writec(fmt, '\n');
+ json_format_indent(fmt, level);
+ } else
+ json_format_writec(fmt, ' ');
+}
+
+/* General-purpose */
+struct json_value *
+json_value_create(int type)
+{
+ struct json_value *obj = calloc(1, sizeof(*obj));
+ if (obj)
+ obj->type = type;
+ return obj;
+}
+
+typedef void (*json_format_fun)(struct json_format *, struct json_value *, size_t);
+typedef void (*json_free_fun)(struct json_value *);
+
+static void json_format_null(struct json_format *, struct json_value *,
+ size_t level);
+static void json_format_bool(struct json_format *, struct json_value *,
+ size_t level);
+static void json_format_number(struct json_format *, struct json_value *,
+ size_t level);
+static void json_format_string(struct json_format *, struct json_value *,
+ size_t level);
+static void json_format_array(struct json_format *, struct json_value *,
+ size_t level);
+static void json_format_object(struct json_format *, struct json_value *,
+ size_t level);
+
+
+static void json_free_string(struct json_value *);
+static void json_free_array(struct json_value *);
+static void json_free_object(struct json_value *);
+
+static struct json_value_meth {
+ json_format_fun format_fun;
+ json_free_fun free_fun;
+} json_value_meth[] = {
+ [json_null] = { json_format_null, NULL },
+ [json_bool] = { json_format_bool, NULL },
+ [json_number] = { json_format_number, NULL },
+ [json_string] = { json_format_string, json_free_string },
+ [json_array] = { json_format_array, json_free_array },
+ [json_object] = { json_format_object, json_free_object }
+};
+
+static inline int
+is_valid_value(struct json_value *obj)
+{
+ return obj
+ && obj->type >= 0
+ && obj->type < sizeof(json_value_meth)/sizeof(json_value_meth[0]);
+}
+
+void
+json_value_free(struct json_value *obj)
+{
+ if (is_valid_value(obj)) {
+ if (json_value_meth[obj->type].free_fun)
+ json_value_meth[obj->type].free_fun(obj);
+ free(obj);
+ }
+}
+
+int
+json_value_format(struct json_value *obj, struct json_format *fmt, size_t level)
+{
+ ++level;
+ if (!obj) {
+ json_format_null(fmt, obj, level);
+ return 0;
+ }
+ if (is_valid_value(obj)) {
+ json_value_meth[obj->type].format_fun(fmt, obj, level);
+ return 0;
+ }
+ errno = EINVAL;
+ return -1;
+}
+
+/* Null value */
+struct json_value *
+json_new_null(void)
+{
+ return json_value_create(json_null);
+}
+
+static void
+json_format_null(struct json_format *fmt, struct json_value *val, size_t level)
+{
+ json_format_writez(fmt, "null");
+}
+
+/* Bool value */
+struct json_value *
+json_new_bool(int b)
+{
+ struct json_value *j = json_value_create(json_bool);
+ j->v.b = b;
+ return j;
+}
+
+static void
+json_format_bool(struct json_format *fmt, struct json_value *val, size_t level)
+{
+ json_format_writez(fmt, val->v.b ? "true" : "false");
+}
+
+/* Number value */
+struct json_value *
+json_new_number(double n)
+{
+ struct json_value *j = json_value_create(json_number);
+ if (j)
+ j->v.n = n;
+ return j;
+}
+
+static void
+json_format_number(struct json_format *fmt, struct json_value *val, size_t level)
+{
+ char buffer[128];//FIXME
+ if (fmt->precision == -1)
+ snprintf(buffer, sizeof buffer, "%e", val->v.n);
+ else
+ snprintf(buffer, sizeof buffer, "%.*f", fmt->precision,
+ val->v.n);
+ json_format_writez(fmt, buffer);
+}
+
+/* String value */
+struct json_value *
+json_new_string(char const *str)
+{
+ struct json_value *j = json_value_create(json_string);
+ j->v.s = strdup(str);
+ if (!j->v.s) {
+ free(j);
+ j = NULL;
+ }
+ return j;
+}
+
+static void
+json_free_string(struct json_value *val)
+{
+ free(val->v.s);
+}
+
+enum { ESCAPE, UNESCAPE };
+
+static int
+escape(char c, char *o, int un)
+{
+ static char transtab[] = "\\\\\"\"b\bf\fn\nr\rt\t//";
+ char *p;
+
+ for (p = transtab; p[2]; p += 2) {
+ if (p[!un] == c) {
+ *o = p[un];
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void
+json_format_escape(struct json_format *fmt, char const *s)
+{
+ json_format_writec(fmt, '"');
+ for (; *s; s++) {
+ char c;
+ if (!escape(*s, &c, ESCAPE)) {
+ json_format_writec(fmt, '\\');
+ json_format_writec(fmt, c);
+ } else
+ json_format_writec(fmt, *s);
+ }
+ json_format_writec(fmt, '"');
+}
+
+static void
+json_format_string(struct json_format *fmt, struct json_value *val, size_t level)
+{
+ json_format_escape(fmt, val->v.s);
+}
+
+/* Array value */
+struct json_value *
+json_new_array(void)
+{
+ struct json_value *j = json_value_create(json_array);
+ j->v.a = malloc(sizeof(*j->v.a));
+ if (j->v.a) {
+ j->v.a->oc = 0;
+ j->v.a->on = 0;
+ j->v.a->ov = NULL;
+ } else {
+ free(j);
+ j = NULL;
+ }
+ return j;
+}
+
+static void
+json_free_array(struct json_value *val)
+{
+ size_t i;
+
+ for (i = 0; i < val->v.a->oc; i++)
+ json_value_free(val->v.a->ov[i]);
+ free(val->v.a->ov);
+ free(val->v.a);
+}
+
+int
+json_array_expand(struct json_value *jv)
+{
+ size_t i;
+ size_t n = jv->v.a->on;
+ struct json_value **p = json_2nrealloc(jv->v.a->ov,
+ &jv->v.a->on,
+ sizeof(jv->v.a->ov[0]));
+ if (!p)
+ return -1;
+ jv->v.a->ov = p;
+ for (i = jv->v.a->on; i < n; i++)
+ jv->v.a->ov[i] = NULL;
+ return 0;
+}
+
+int
+json_array_insert(struct json_value *jv, size_t idx, struct json_value *v)
+{
+ if (jv->type != json_array) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (jv->v.a->oc <= idx) {
+ while (jv->v.a->on <= idx) {
+ if (json_array_expand(jv))
+ return -1;
+ }
+ jv->v.a->oc = idx + 1;
+ }
+ jv->v.a->ov[idx] = v;
+ return 0;
+}
+
+int
+json_array_append(struct json_value *jv, struct json_value *v)
+{
+ if (jv->type != json_array) {
+ errno = EINVAL;
+ return -1;
+ }
+ return json_array_insert(jv, jv->v.a->oc, v);
+}
+
+int
+json_array_set(struct json_value *jv, size_t idx, struct json_value *v)
+{
+ if (jv->type != json_array) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (idx >= json_array_length(jv)) {
+ errno = ENOENT;
+ return -1;
+ }
+ jv->v.a->ov[idx] = v;
+ return 0;
+}
+
+int
+json_array_get(struct json_value *jv, size_t idx, struct json_value **retval)
+{
+ if (jv->type != json_array) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (idx >= json_array_length(jv)) {
+ errno = ENOENT;
+ return -1;
+ }
+ *retval = jv->v.a->ov[idx];
+ return 0;
+}
+
+static void
+json_format_array(struct json_format *fmt, struct json_value *obj,
+ size_t level)
+{
+ size_t i;
+
+ json_format_writec(fmt, '[');
+ if (obj->v.a->oc) {
+ if (fmt->indent)
+ json_format_writec(fmt, '\n');
+ for (i = 0; i < obj->v.a->oc; i++) {
+ (i ? json_format_delim
+ : json_format_indent)(fmt, level);
+ json_value_format(obj->v.a->ov[i], fmt, level);
+ }
+ if (fmt->indent) {
+ json_format_writec(fmt, '\n');
+ json_format_indent(fmt, level-1);
+ }
+ }
+ json_format_writec(fmt, ']');
+}
+
+/* Object value */
+struct json_value *
+json_new_object(void)
+{
+ struct json_value *jv = json_value_create(json_object);
+ if (!(jv->v.o = calloc(1, sizeof(*jv->v.o)))) {
+ free(jv);
+ return NULL;
+ }
+ return jv;
+}
+
+static void
+json_free_object(struct json_value *val)
+{
+ struct json_pair *p;
+
+ p = val->v.o->head;
+ while (p) {
+ struct json_pair *next = p->next;
+ free(p->k);
+ json_value_free(p->v);
+ p = next;
+ }
+}
+
+static int
+json_object_lookup_or_install(struct json_object *obj, char const *name,
+ int install,
+ struct json_pair **retval)
+{
+ struct json_pair *l, *m;
+ size_t i, n, count;
+
+ if (obj->count == 0)
+ l = NULL;
+ else {
+ l = obj->head;
+
+ if (strcmp(l->k, name) > 0) {
+ l = NULL;
+ } else if (strcmp(obj->tail->k, name) < 0) {
+ l = obj->tail;
+ } else {
+ count = obj->count;
+ while (count) {
+ int c;
+
+ n = count / 2;
+ for (m = l, i = 0; i < n; m = m->next, i++)
+ ;
+
+ c = strcmp(m->k, name);
+ if (c == 0) {
+ *retval = m;
+ return 0;
+ } else if (n == 0) {
+ break;
+ } else if (c < 0) {
+ l = m;
+ count -= n;
+ } else {
+ count = n;
+ }
+ }
+ }
+ }
+
+ if (!install) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ m = malloc(sizeof(*m));
+ if (!m)
+ return -1;
+ m->next = NULL;
+ m->k = strdup(name);
+ if (!m->k) {
+ free(m);
+ return -1;
+ }
+ m->v = NULL;
+
+ if (!l) {
+ if (obj->head == NULL)
+ obj->head = obj->tail = m;
+ else {
+ m->next = obj->head;
+ obj->head = m;
+ }
+ } else {
+ while (l->next && strcmp(l->next->k, name) < 0)
+ l = l->next;
+
+ m->next = l->next;
+ l->next = m;
+ if (!m->next)
+ obj->tail = m;
+ }
+ obj->count++;
+
+ *retval = m;
+ return 0;
+}
+
+int
+json_object_set(struct json_value *obj, char const *name,
+ struct json_value *val)
+{
+ struct json_pair *p;
+ int res;
+
+ if (obj->type != json_object) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ res = json_object_lookup_or_install(obj->v.o, name, 1, &p);
+ if (res)
+ return res;
+
+ json_value_free(p->v);
+ p->v = val;
+ return 0;
+}
+
+int
+json_object_get(struct json_value *obj, char const *name,
+ struct json_value **retval)
+{
+ struct json_pair *p;
+ int res;
+
+ if (obj->type != json_object) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ res = json_object_lookup_or_install(obj->v.o, name, 0, &p);
+ if (res)
+ return res;
+ *retval = p->v;
+ return 0;
+}
+
+static void
+json_format_object(struct json_format *fmt, struct json_value *obj,
+ size_t level)
+{
+ struct json_object *op = obj->v.o;
+ struct json_pair *p;
+ json_format_writec(fmt, '{');
+ if (op->count) {
+ if (fmt->indent)
+ json_format_writec(fmt, '\n');
+ for (p = op->head; p; p = p->next) {
+ (p == op->head
+ ? json_format_indent
+ : json_format_delim)(fmt, level);
+ json_format_escape(fmt, p->k);
+ json_format_writec(fmt, ':');
+ if (fmt->indent)
+ json_format_writec(fmt, ' ');
+ json_value_format(p->v, fmt, level);
+ }
+ if (fmt->indent) {
+ json_format_writec(fmt, '\n');
+ json_format_indent(fmt, level-1);
+ }
+ }
+ json_format_writec(fmt, '}');
+}
+
+/* Parser */
+#define ISSPACE(c) ((c)==' '||(c)=='\t'||(c)=='\n'||(c)=='\r')
+#define ISHEX(c) (strchr("0123456789abcdefABCDEF", c) != NULL)
+#define SKIPWS(s) while (*(s) && ISSPACE(*(s))) (s)++;
+
+struct j_context {
+ struct j_context *next;
+ struct json_value *obj;
+ char *key;
+};
+
+static inline int
+j_context_type(struct j_context *ctx)
+{
+ return ctx ? ctx->obj->type : json_null;
+}
+
+static int
+j_context_push(struct j_context **ctx, int type)
+{
+ struct j_context *cur = malloc(sizeof(*cur));
+ switch (type) {
+ case json_array:
+ cur->obj = json_new_array();
+ break;
+ case json_object:
+ cur->obj = json_new_object();
+ break;
+ default:
+ abort();
+ }
+ if (!cur->obj) {
+ free(cur);
+ return JSON_E_NOMEM;
+ }
+ cur->next = *ctx;
+ cur->key = NULL;
+ *ctx = cur;
+ return JSON_E_NOERR;
+}
+
+static struct json_value *
+j_context_pop(struct j_context **ctx)
+{
+ struct json_value *val;
+ struct j_context *next;
+
+ if (!*ctx)
+ return NULL;
+ val = (*ctx)->obj;
+ free((*ctx)->key);
+ next = (*ctx)->next;
+ free(*ctx);
+ *ctx = next;
+
+ return val;
+}
+
+static int
+utf8_wctomb(char const *u, char r[6])
+{
+ unsigned int wc = strtoul(u, NULL, 16);
+ int count;
+
+ if (wc < 0x80)
+ count = 1;
+ else if (wc < 0x800)
+ count = 2;
+ else if (wc < 0x10000)
+ count = 3;
+ else if (wc < 0x200000)
+ count = 4;
+ else if (wc < 0x4000000)
+ count = 5;
+ else if (wc <= 0x7fffffff)
+ count = 6;
+ else
+ return -1;
+
+ switch (count) {
+ /* Note: code falls through cases! */
+ case 6:
+ r[5] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x4000000;
+ case 5:
+ r[4] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x200000;
+ case 4:
+ r[3] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x10000;
+ case 3:
+ r[2] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x800;
+ case 2:
+ r[1] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0xc0;
+ case 1:
+ r[0] = wc;
+ }
+ return count;
+}
+
+static int
+j_get_text(char const *input, char **retval, char const **endp)
+{
+ size_t len;
+ char const *p;
+ char *q;
+ char *str;
+
+ len = 1;
+ ++input;
+ for (p = input; *p != '"'; p++) {
+ if (*p == '\\') {
+ if (ISHEX(p[1])
+ && ISHEX(p[2])
+ && ISHEX(p[3])
+ && ISHEX(p[4])) {
+ char r[6];
+ int n = utf8_wctomb(p, r);
+ if (n == -1) {
+ *endp = p + 1;
+ return JSON_E_BADSTRING;
+ }
+ p += 5;
+ len += n;
+ continue;
+ } else {
+ p++;
+ }
+ }
+ if (*p == 0)
+ return JSON_E_BADSTRING;
+ len++;
+ }
+
+ str = malloc(len);
+ if (!str)
+ return JSON_E_NOMEM;
+
+ p = input;
+ q = str;
+ while (*p != '"') {
+ if (*p == '\\') {
+ p++;
+ if (escape(*p, q, UNESCAPE) == 0) {
+ q++;
+ p++;
+ } else if (ISHEX(p[0])
+ && ISHEX(p[1])
+ && ISHEX(p[2])
+ && ISHEX(p[3])) {
+ char r[6];
+ int n = utf8_wctomb(p, r);
+ memcpy(q, r, n);
+ p += 4;
+ q += n;
+ } else {
+ *q++ = *p++;
+ }
+ } else {
+ *q++ = *p++;
+ }
+ }
+ *q++ = 0;
+ *endp = p + 1;
+ *retval = str;
+ return 0;
+}
+
+int
+json_parse_string(char const *input, struct json_value **retval, char **endp)
+{
+ struct j_context *ctx = NULL;
+ struct json_value *val;
+ int ecode = JSON_E_NOERR;
+ char *str;
+
+ while (1) {
+ val = NULL;
+
+ SKIPWS(input);
+ switch (*input) {
+ case '[':
+ j_context_push(&ctx, json_array);
+ ++input;
+ continue;
+
+ case ']':
+ if (j_context_type(ctx) == json_array) {
+ val = j_context_pop(&ctx);
+ } else {
+ ecode = JSON_E_BADTOK;
+ goto err;
+ }
+ ++input;
+ break;
+
+ case '{':
+ j_context_push(&ctx, json_object);
+ ++input;
+ continue;
+
+ case '}':
+ if (j_context_type(ctx) == json_object) {
+ val = j_context_pop(&ctx);
+ } else {
+ ecode = JSON_E_BADTOK;
+ goto err;
+ }
+ ++input;
+ break;
+
+ case '"':
+ ecode = j_get_text(input, &str, &input);
+ if (ecode != JSON_E_NOERR)
+ goto err;
+ if (j_context_type(ctx) == json_object && !ctx->key) {
+ ctx->key = str;
+
+ SKIPWS(input);
+ if (*input == ':') {
+ ++input;
+ continue;
+ }
+
+ ecode = JSON_E_BADDELIM;
+ goto err;
+ } else {
+ val = json_value_create(json_string);
+ if (!val)
+ goto err;
+ val->v.s = str;
+ }
+ break;
+
+ case '-':
+ case '+':
+ case '.':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ char *p;
+ double d = strtod(input, &p);
+ if ((input[0] == '-' && d == -HUGE_VAL)
+ || d == HUGE_VAL) {
+ ecode = JSON_E_BADTOK;
+ goto err;
+ }
+ val = json_new_number(d);
+ if (!val) {
+ ecode = JSON_E_NOMEM;
+ goto err;
+ }
+ input = p;
+ break;
+ }
+
+ default:
+ if (strncmp(input, "null", 4) == 0) {
+ val = json_new_null();
+ input += 4;
+ } else if (strncmp(input, "true", 4) == 0) {
+ val = json_new_bool(1);
+ input += 4;
+ } else if (strncmp(input, "false", 5) == 0) {
+ val = json_new_bool(0);
+ input += 5;
+ } else {
+ ecode = JSON_E_BADTOK;
+ goto err;
+ }
+ if (!val) {
+ ecode = JSON_E_NOMEM;
+ goto err;
+ }
+ }
+
+ if (val) {
+ if (ctx) {
+ int rc;
+
+ switch (j_context_type(ctx)) {
+ case json_array:
+ rc = json_array_append(ctx->obj, val);
+ break;
+
+ case json_object:
+ rc = json_object_set(ctx->obj,
+ ctx->key, val);
+ free(ctx->key);
+ ctx->key = NULL;
+ break;
+
+ default:
+ abort();
+ }
+ if (rc) {
+ json_value_free(val);
+ ecode = JSON_E_NOMEM;
+ goto err;
+ }
+ } else {
+ *retval = val;
+ *endp = (char*) input;
+ return 0;
+ }
+ }
+
+ SKIPWS(input);
+ if (*input == ',') {
+ ++input;
+ } else if (*input == ']' || *input == '}') {
+ continue;
+ } else {
+ ecode = JSON_E_BADDELIM;
+ goto err;
+ }
+ }
+
+err:
+ while (j_context_pop(&ctx))
+ ;
+ *endp = (char*) input;
+ return ecode;
+}
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 0000000..8f98368
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,86 @@
+#include <stdlib.h>
+
+enum json_value_type {
+ json_null,
+ json_bool,
+ json_number,
+ json_string,
+ json_array,
+ json_object
+};
+
+struct json_value;
+
+struct json_array {
+ size_t oc;
+ struct json_value **ov;
+ size_t on;
+};
+
+struct json_object {
+ struct json_pair *head;
+ struct json_pair *tail;
+ size_t count;
+};
+
+struct json_value {
+ enum json_value_type type;
+ union {
+ int b; /* json_bool */
+ double n; /* json_number */
+ char *s; /* json_string */
+ struct json_array *a; /* json_array */
+ struct json_object *o; /* json_object */
+ } v;
+};
+
+struct json_pair {
+ struct json_pair *next;
+ char *k;
+ struct json_value *v;
+};
+
+struct json_format
+{
+ size_t indent;
+ int precision;
+ void (*write) (void *, char const *, size_t);
+ void *data;
+};
+
+int json_value_format(struct json_value *obj, struct json_format *fmt,
+ size_t level);
+void json_value_free(struct json_value *);
+
+struct json_value *json_new_null(void);
+struct json_value *json_new_bool(int b);
+struct json_value *json_new_number(double n);
+struct json_value *json_new_string(char const *str);
+
+struct json_value *json_new_object(void);
+int json_object_set(struct json_value *obj, char const *name,
+ struct json_value *val);
+int json_object_get(struct json_value *obj, char const *name,
+ struct json_value **retval);
+
+struct json_value *json_new_array(void);
+static inline size_t json_array_length(struct json_value *j) {
+ return j->v.a->oc;
+}
+int json_array_insert(struct json_value *j, size_t idx, struct json_value *v);
+int json_array_append(struct json_value *j, struct json_value *v);
+int json_array_set(struct json_value *j, size_t idx, struct json_value *v);
+int json_array_get(struct json_value *j, size_t idx,
+ struct json_value **retval);
+
+
+enum {
+ JSON_E_NOERR = 0,
+ JSON_E_NOMEM = -1,
+ JSON_E_BADTOK = -2,
+ JSON_E_BADDELIM = -3,
+ JSON_E_BADSTRING = -4
+};
+
+int json_parse_string(char const *input, struct json_value **retval,
+ char **endp);
diff --git a/src/logger.c b/src/logger.c
new file mode 100644
index 0000000..fe8de2f
--- /dev/null
+++ b/src/logger.c
@@ -0,0 +1,135 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include "ping903.h"
+
+char *progname;
+
+static int facility = LOG_DAEMON;
+
+static void
+vlog_syslog(int prio, char const *fmt, va_list ap)
+{
+ vsyslog(prio, fmt, ap);
+}
+
+static void
+vlog_stderr(int prio, char const *fmt, va_list ap)
+{
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+
+static void (*vlog)(int prio, char const *fmt, va_list ap) = vlog_stderr;
+
+void
+vlogger(int prio, char const *fmt, va_list ap)
+{
+ vlog(prio, fmt, ap);
+}
+
+void
+fatal(char const *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vlog(LOG_CRIT, fmt, ap);
+ va_end(ap);
+}
+
+void
+error(char const *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vlog(LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+void
+info(char const *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vlog(LOG_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void
+syslog_enable(void)
+{
+ openlog(progname, LOG_PID, facility);
+ vlog = vlog_syslog;
+}
+
+static struct facility {
+ char const *s;
+ int n;
+} facilities[] = {
+ { "auth", LOG_AUTH },
+ { "authpriv", LOG_AUTHPRIV },
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+ { "ftp", LOG_FTP },
+ { "kern", LOG_KERN },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ { "news", LOG_NEWS },
+#ifdef LOG_AUTH
+ { "security", LOG_AUTH },
+#endif
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { NULL, -1 }
+};
+
+int
+set_log_facility(char const *arg)
+{
+ struct facility *f;
+ for (f = facilities; f->s; f++)
+ if (strcmp(f->s, arg) == 0)
+ break;
+ if (f->n == -1)
+ return -1;
+ facility = f->n;
+ return 0;
+}
+
+void
+set_progname(char const *arg)
+{
+ char *p = strrchr(arg, '/');
+ if (p)
+ p++;
+ else
+ p = (char*) arg;
+ progname = p;
+}
+
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..3d38ec7
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,297 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+#include "ping903.h"
+#include "json.h"
+
+static char *config_file = "/etc/ping903.conf";
+static int verbose;
+
+
+void
+emalloc_die(void)
+{
+ fatal("not enough memory");
+ exit(2);
+}
+
+void *
+emalloc(size_t s)
+{
+ void *p = malloc(s);
+ if (!p)
+ emalloc_die();
+ return p;
+}
+
+void *
+e2nrealloc(void *p, size_t *pn, size_t s)
+{
+ extern void *json_2nrealloc(void *p, size_t *pn, size_t s);
+ char *n = json_2nrealloc(p, pn, s);
+ if (!n)
+ emalloc_die();
+ return n;
+}
+
+char *
+estrdup(char const *s)
+{
+ return strcpy(emalloc(strlen(s) + 1), s);
+}
+
+enum state {
+ RUNNING, /* Program is running */
+ TERMINATING, /* Program is terminating */
+ CHILD_TERM, /* SIGTERM has been sent to the child */
+ CHILD_KILL
+};
+volatile enum state state;
+
+static void
+sigterm(int sig)
+{
+ if (state == RUNNING)
+ state = TERMINATING;
+}
+
+static void
+sigalrm(int sig)
+{
+ if (state == CHILD_TERM)
+ state = CHILD_KILL;
+}
+
+int fatal_signals[] = {
+ SIGHUP,
+ SIGINT,
+ SIGQUIT,
+ SIGTERM,
+ 0
+};
+
+static void
+sentinel(void)
+{
+ pid_t pid = 0;
+ int i;
+ struct sigaction act;
+ pid_t child_pid = 0;
+ int status;
+
+ act.sa_flags = 0;
+ sigemptyset(&act.sa_mask);
+
+ act.sa_handler = sigalrm;
+ sigaction(SIGALRM, &act, NULL);
+
+ for (i = 0; fatal_signals[i]; i++)
+ sigaddset(&act.sa_mask, fatal_signals[i]);
+ act.sa_handler = sigterm;
+ for (i = 0; fatal_signals[i]; i++)
+ sigaction(fatal_signals[i], &act, NULL);
+
+ while (1) {
+ if (pid == 0) {
+ if (state != RUNNING)
+ break;
+ pid = fork();
+ if (pid == -1) {
+ error("fork: %s", strerror(errno));
+ break;
+ }
+ if (pid == 0) {
+ ping903();
+ exit(0);
+ }
+ }
+
+ if (child_pid > 0) {
+ child_pid = 0;
+ if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
+ if (code || verbose > 1)
+ error("child exited with status %d",
+ code);
+ } else if (WIFSIGNALED(status)) {
+ char const *coremsg = "";
+#ifdef WCOREDUMP
+ if (WCOREDUMP(status))
+ coremsg = " (core dumped)";
+#endif
+ error("child terminated on signal %d%s",
+ WTERMSIG(status), coremsg);
+ } else if (WIFSTOPPED(status)) {
+ error("child stopped on signal %d",
+ WSTOPSIG(status));
+ continue;
+ } else {
+ error("child terminated with unrecognized status %d", status);
+ }
+ /* restart the child */
+ pid = 0;
+ continue;
+ }
+
+ switch (state) {
+ case RUNNING:
+ break;
+
+ case TERMINATING:
+ kill(pid, SIGTERM);
+ alarm(5);
+ state = CHILD_TERM;
+ break;
+ case CHILD_TERM:
+ break;
+
+ case CHILD_KILL:
+ kill(pid, SIGKILL);
+ return;
+ }
+
+ child_pid = wait(&status);
+ if (child_pid == -1) {
+ if (errno != EINTR || verbose > 1)
+ error("wait: %s", strerror(errno));
+ }
+ }
+}
+
+char *pidfile;
+
+void
+pidfile_remove(void)
+{
+ if (pidfile && unlink(pidfile))
+ error("cannot unlink pidfile `%s': %s",
+ pidfile, strerror(errno));
+}
+
+void
+pidfile_create(void)
+{
+ FILE *fp;
+
+ if (!pidfile)
+ return;
+
+ fp = fopen(pidfile, "w");
+ if (!fp) {
+ error("cannot create pidfile `%s': %s",
+ pidfile, strerror(errno));
+ exit(1);
+ }
+ fprintf(fp, "%lu", (unsigned long) getpid());
+ fclose(fp);
+}
+
+/* Check whether pidfile exists and if so, whether its PID is still
+ active. Exit if it is. */
+void
+pidfile_check(void)
+{
+ unsigned long pid;
+ FILE *fp;
+
+ if (!pidfile)
+ return;
+
+ fp = fopen(pidfile, "r");
+
+ if (fp) {
+ if (fscanf(fp, "%lu", &pid) != 1) {
+ error("cannot get pid from pidfile `%s'", pidfile);
+ } else {
+ if (kill(pid, 0) == 0) {
+ error("%s appears to run with pid %lu. "
+ "If it does not, remove `%s' and retry.",
+ progname, pid, pidfile);
+ exit(1);
+ }
+ }
+
+ fclose(fp);
+ if (unlink(pidfile)) {
+ error("cannot unlink pidfile `%s': %s",
+ pidfile, strerror(errno));
+ exit(1);
+ }
+ } else if (errno != ENOENT) {
+ error("cannot open pidfile `%s': %s",
+ pidfile, strerror(errno));
+ exit(1);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int foreground = 0;
+ int single_process = 0;
+
+ set_progname(argv[0]);
+
+ while ((c = getopt(argc, argv, "c:fsv")) != EOF) {
+ switch (c) {
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'f':
+ foreground = 1;
+ break;
+ case 's':
+ single_process = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ exit(1);
+ }
+ }
+
+ if (readconfig(config_file))
+ exit(1);
+
+ pidfile_check();
+
+ if (!foreground) {
+ if (daemon(0, 0)) {
+ error("daemon failed: %s", strerror(errno));
+ exit(1);
+ }
+ syslog_enable();
+ }
+ pidfile_create();
+ if (single_process)
+ ping903();
+ else
+ sentinel();
+
+ pidfile_remove();
+ return 0;
+}
diff --git a/src/ping903.c b/src/ping903.c
new file mode 100644
index 0000000..f98983b
--- /dev/null
+++ b/src/ping903.c
@@ -0,0 +1,409 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <microhttpd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include "ping903.h"
+#include "json.h"
+
+#ifndef DEFAULT_ADDRESS
+# define DEFAULT_ADDRESS "0.0.0.0"
+#endif
+#ifndef DEFAULT_SERVICE
+# define DEFAULT_SERVICE "8080"
+#endif
+
+char *httpd_addr;
+
+static int
+open_node(char const *node, char const *serv, struct sockaddr **saddr)
+{
+ struct addrinfo hints;
+ struct addrinfo *res;
+ int reuse;
+ int fd;
+ int rc;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ rc = getaddrinfo(node, serv, &hints, &res);
+ if (rc) {
+ error("%s: %s", node, gai_strerror(rc));
+ exit(1);
+ }
+
+ fd = socket(res->ai_family, res->ai_socktype, 0);
+ if (fd == -1) {
+ error("socket: %s", strerror(errno));
+ exit(1);
+ }
+ reuse = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ &reuse, sizeof(reuse));
+ if (bind(fd, res->ai_addr, res->ai_addrlen) == -1) {
+ error("bind: %s", strerror(errno));
+ exit(1);
+ }
+
+ if (listen(fd, 8) == -1) {
+ error("listen: %s", strerror(errno));
+ exit(1);
+ }
+
+ if (saddr) {
+ *saddr = emalloc(res->ai_addrlen);
+ memcpy(*saddr, res->ai_addr, res->ai_addrlen);
+ }
+ freeaddrinfo(res);
+
+ return fd;
+}
+
+static int
+open_listener(char const *addr, struct sockaddr **saddr)
+{
+ size_t len;
+ int fd;
+
+ if (!addr)
+ return open_node(DEFAULT_ADDRESS, DEFAULT_SERVICE, saddr);
+
+ len = strcspn(addr, ":");
+ if (len == 0)
+ fd = open_node(DEFAULT_ADDRESS, addr + 1, saddr);
+ else if (addr[len] == 0)
+ fd = open_node(addr, DEFAULT_SERVICE, saddr);
+ else {
+ char *node;
+ node = emalloc(len + 1);
+ memcpy(node, addr, len);
+ node[len] = 0;
+ fd = open_node(node, addr + len + 1, saddr);
+ free(node);
+ }
+ return fd;
+}
+
+static void
+p903_httpd_logger(void *arg, const char *fmt, va_list ap)
+{
+ vlogger(LOG_ERR, fmt, ap);
+}
+
+static void
+p903_httpd_panic(void *cls, const char *file, unsigned int line,
+ const char *reason)
+{
+ if (reason)
+ fatal("%s:%d: MHD PANIC: %s", file, line, reason);
+ else
+ fatal("%s:%d: MHD PANIC", file, line);
+ abort();
+}
+
+#if HAVE_LIBWRAP
+extern int p903_httpd_acl(void *cls, const struct sockaddr *addr,
+ socklen_t addrlen);
+#else
+# define p903_httpd_acl NULL
+#endif
+
+static void
+http_log(struct MHD_Connection *connection,
+ char const *method, char const *url,
+ int status, char const *str)
+{
+ char *ipstr;
+ char const *host, *referer, *user_agent;
+ time_t t;
+ struct tm *tm;
+ char tbuf[30];
+
+ ipstr = get_remote_ip(connection);
+
+ host = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_HOST);
+ referer = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_REFERER);
+ user_agent = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_USER_AGENT);
+ t = time(NULL);
+ tm = localtime(&t);
+ strftime(tbuf, sizeof(tbuf), "[%d/%b/%Y:%H:%M:%S %z]", tm);
+
+ info("%s %s - - %s \"%s %s\" \"%.1024s\" %3d \"%s\" \"%s\"",
+ host, ipstr, tbuf, method, url, str ? str : "", status,
+ referer ? referer : "",
+ user_agent ? user_agent : "");
+ free(ipstr);
+}
+
+
+static int
+http_error(struct MHD_Connection *conn,
+ char const *method, char const *url, int status)
+{
+ int ret;
+ struct MHD_Response *resp =
+ MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ http_log(conn, method, url, status, NULL);
+ ret = MHD_queue_response(conn, status, resp);
+ MHD_destroy_response(resp);
+ return ret;
+}
+
+struct strbuf {
+ char *base;
+ size_t off;
+ size_t size;
+ int err;
+};
+
+static void
+strbuf_writer(void *closure, char const *text, size_t len)
+{
+ extern void *json_2nrealloc(void *p, size_t *pn, size_t s);
+ struct strbuf *sb = closure;
+ if (sb->err)
+ return;
+ while (sb->off + len >= sb->size) {
+ char *p = json_2nrealloc(sb->base, &sb->size, 1);
+ if (!p) {
+ error("not enough memory trying to format reply");
+ sb->err = 1;
+ return;
+ }
+ sb->base = p;
+ }
+ memcpy(sb->base + sb->off, text, len);
+ sb->off += len;
+}
+
+static char *
+json_to_str(struct json_value *obj)
+{
+ struct strbuf sb;
+ struct json_format fmt = {
+ .indent = 0,
+ .precision = 5,
+ .write = strbuf_writer,
+ .data = &sb
+ };
+
+ memset(&sb, 0, sizeof(sb));
+ json_value_format(obj, &fmt, 0);
+ strbuf_writer(&sb, "", 1);
+
+ if (sb.err) {
+ free(sb.base);
+ return NULL;
+ }
+ return sb.base;
+}
+
+static int
+httpd_json_response(struct MHD_Connection *conn,
+ char const *url, char const *method,
+ struct json_value *val)
+{
+ char *reply;
+ struct MHD_Response *resp;
+ int ret;
+
+ reply = json_to_str(val);
+ json_value_free(val);
+ if (!reply)
+ return http_error(conn, method, url,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+
+ resp = MHD_create_response_from_buffer(strlen(reply),
+ reply, MHD_RESPMEM_MUST_COPY);
+ http_log(conn, method, url, MHD_HTTP_OK, reply);
+ free(reply);
+ MHD_add_response_header(resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/json");
+ ret = MHD_queue_response(conn, MHD_HTTP_OK, resp);
+ MHD_destroy_response(resp);
+ return ret;
+}
+
+int
+ept_config(struct MHD_Connection *conn,
+ const char *url, const char *method, const char *suffix)
+{
+ struct json_value *val;
+
+ val = config_to_json();
+ if (!val)
+ return http_error(conn, method, url,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ return httpd_json_response(conn, url, method, val);
+}
+
+int
+ept_host_stat(struct MHD_Connection *conn,
+ const char *url, const char *method, const char *suffix)
+{
+ struct json_value *val;
+ int rc;
+
+ while (*suffix == '/')
+ suffix++;
+
+ if (suffix[0] == 0) {
+ rc = get_all_host_stat(&val);
+ } else {
+ rc = get_hostname_stat(suffix, &val);
+ }
+ if (rc)
+ return http_error(conn, method, url,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+
+ return httpd_json_response(conn, url, method, val);
+}
+
+
+typedef int (*ENDPOINT_HANDLER)(struct MHD_Connection *,
+ const char *, const char *, const char *);
+
+enum {
+ EPT_EXACT = 0x01,
+ EPT_PREFIX = 0x02
+};
+
+struct endpoint {
+ char *url;
+ int flags;
+ char *method;
+ ENDPOINT_HANDLER handler;
+};
+
+static struct endpoint endpoint[] = {
+ { "/config", EPT_EXACT, MHD_HTTP_METHOD_GET, ept_config },
+ { "/host", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_host_stat },
+ { NULL }
+};
+
+static ENDPOINT_HANDLER
+find_endpoint_handler(char const *url, char const *method, char const **suffix)
+{
+ struct endpoint *ept;
+ for (ept = endpoint; ept->url; ept++) {
+ if (strcmp(ept->method, method))
+ continue;
+ if (ept->flags & EPT_EXACT) {
+ if (strcmp(ept->url, url) == 0) {
+ *suffix = url + strlen(url);
+ return ept->handler;
+ }
+ } else {
+ size_t len = strlen(ept->url);
+ if (strncmp(url, ept->url, len) == 0
+ && (url[len] == '/' || url[len] == 0)) {
+ *suffix = url + len;
+ return ept->handler;
+ }
+ }
+ }
+ return NULL;
+}
+
+static int
+p903_httpd_handler(void *cls,
+ struct MHD_Connection *conn,
+ const char *url, const char *method,
+ const char *version,
+ const char *upload_data, size_t *upload_data_size,
+ void **con_cls)
+{
+ ENDPOINT_HANDLER handler;
+ char const *suffix;
+
+ handler = find_endpoint_handler(url, method, &suffix);
+ if (handler)
+ return handler(conn, url, method, suffix);
+
+ if (strcmp(method, MHD_HTTP_METHOD_GET))
+ return http_error(conn, method, url,
+ MHD_HTTP_METHOD_NOT_ALLOWED);
+ return http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
+}
+
+void
+ping903(void)
+{
+ struct MHD_Daemon *mhd;
+ sigset_t sigs;
+ int i;
+ pthread_t tid;
+ int fd = -1;
+ struct sockaddr *server_addr;
+
+ p903_init();
+
+ fd = open_listener(httpd_addr, &server_addr);
+
+ /* Block the 'fatal signals' and SIGPIPE in the handling thread */
+ sigemptyset(&sigs);
+ for (i = 0; fatal_signals[i]; i++)
+ sigaddset(&sigs, fatal_signals[i]);
+ sigaddset(&sigs, SIGPIPE);
+
+ pthread_sigmask(SIG_BLOCK, &sigs, NULL);
+ MHD_set_panic_func(p903_httpd_panic, NULL);
+ mhd = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD
+ | MHD_USE_ERROR_LOG, 0,
+ p903_httpd_acl, server_addr,
+ p903_httpd_handler, NULL,
+ MHD_OPTION_LISTEN_SOCKET, fd,
+#if 0
+ MHD_OPTION_NOTIFY_COMPLETED, p903_httpd_request_completed, NULL,
+#endif
+ MHD_OPTION_EXTERNAL_LOGGER, p903_httpd_logger, NULL,
+ MHD_OPTION_END);
+ /* Unblock only the fatal signals */
+
+ sigdelset(&sigs, SIGPIPE);
+ pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
+
+ pthread_create(&tid, NULL, p903_scheduler, NULL);
+ /* Start Sender thread */
+ pthread_create(&tid, NULL, p903_sender, NULL);
+ /* Start Receiver thread */
+ pthread_create(&tid, NULL, p903_receiver, NULL);
+
+ /* Wait for signal to arrive */
+ sigwait(&sigs, &i);
+ MHD_stop_daemon(mhd);
+}
diff --git a/src/ping903.h b/src/ping903.h
new file mode 100644
index 0000000..e93bf0a
--- /dev/null
+++ b/src/ping903.h
@@ -0,0 +1,131 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+#include <pthread.h>
+#include <stdarg.h>
+#include <microhttpd.h>
+
+void ping903(void);
+
+void vlogger(int prio, char const *fmt, va_list ap);
+void fatal(char const *fmt, ...);
+void error(char const *fmt, ...);
+void info(char const *fmt, ...);
+void syslog_enable(void);
+void set_progname(char const *arg);
+int set_log_facility(char const *arg);
+
+/* Configuration file parser */
+
+/* Callback mode */
+enum {
+ CF_PARSE, /* parse input statement */
+ CF_SERIALIZE /* serialize configuration setting */
+};
+
+union cf_callback_arg {
+ struct json_value *output; /* Output value */
+ struct cf_line {
+ char const *val; /* Statement value (after keyword) */
+ char const *file; /* Statement location: file name */
+ unsigned line; /* ... and input line number */
+ } input;
+};
+
+enum {
+ CF_RET_OK,
+ CF_RET_FAIL,
+ CF_RET_IGNORE
+};
+
+/* Configuration parser callback function */
+typedef int (*CF_CALLBACK)(int mode, union cf_callback_arg *arg, void *data);
+
+/* Built-in statement types */
+enum {
+ STMT_T_STRING,
+ STMT_T_ULONG,
+ STMT_T_BOOL,
+ STMT_T_CALLBACK
+};
+
+int readconfig(char const *file);
+int cf_trusted_ip(int mode, union cf_callback_arg *arg, void *data);
+
+void emalloc_die(void);
+void *emalloc(size_t s);
+char *estrdup(char const *s);
+void *e2nrealloc(void *p, size_t *pn, size_t s);
+
+char *get_remote_ip(struct MHD_Connection *conn);
+
+struct host_stat {
+ struct timeval tv;
+ unsigned long xmit_count;
+ unsigned long recv_count;
+ double tmin; /* minimum round trip time */
+ double tmax; /* maximum round trip time */
+ double avg;
+ double stddev;
+};
+
+static inline int host_stat_is_valid(struct host_stat const *hs) {
+ return hs->tv.tv_sec > 0;
+}
+
+typedef struct hostaddr {
+ char *name;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+
+ /* Send queue */
+ struct hostaddr *next;
+
+ /* Current ping statistics */
+ struct timeval tv;
+ unsigned long xmit_count;
+ unsigned long recv_count;
+ double tmin; /* minimum round trip time */
+ double tmax; /* maximum round trip time */
+ double tsum; /* sum of all times, for doing average */
+ double tsumsq; /* sum of all times squared, for std. dev. */
+
+ /* Last probe statistics */
+ struct host_stat stat_last;
+} HOSTADDR;
+
+extern char *progname;
+extern int fatal_signals[];
+extern char *httpd_addr;
+extern char *pidfile;
+extern unsigned long ping_interval;
+extern unsigned long ping_count;
+extern unsigned long ping_tolerance;
+extern size_t data_length;
+extern HOSTADDR *hostaddr;
+extern size_t hostaddr_count;
+extern size_t hostaddr_max;
+
+struct json_value *config_to_json(void);
+int get_host_stat(HOSTADDR *host, struct json_value **);
+int get_hostname_stat(char const *name, struct json_value **retval);
+int get_all_host_stat(struct json_value **);
+
+void p903_init(void);
+void *p903_sender(void *p);
+void *p903_receiver(void *p);
+void *p903_scheduler(void *p);
+
diff --git a/src/pinger.c b/src/pinger.c
new file mode 100644
index 0000000..c050f81
--- /dev/null
+++ b/src/pinger.c
@@ -0,0 +1,568 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+#include <config.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <pthread.h>
+#include <limits.h>
+#include "ping903.h"
+#include "json.h"
+
+unsigned long ping_interval = 60;
+unsigned long ping_count = 10;
+unsigned long ping_tolerance = 3;
+
+HOSTADDR *hostaddr;
+size_t hostaddr_count;
+size_t hostaddr_max;
+
+static int ping_fd;
+
+static HOSTADDR *sendq_head, *sendq_tail;
+static pthread_mutex_t sendq_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t sendq_cond = PTHREAD_COND_INITIALIZER;
+
+#define ICMP_HEADER_LEN (offsetof(struct icmp, icmp_data))
+#define PING_HEADER_LEN (ICMP_HEADER_LEN+sizeof(struct timeval))
+#define PING_DATALEN (64 - PING_HEADER_LEN)
+
+size_t data_length = PING_DATALEN;
+static unsigned char *data_buffer;
+
+static int ping_ident;
+
+#define MAX_PING_TIMEOUT 10
+enum {
+ MAX_SEQNO = USHRT_MAX,
+ MOD_SEQNO = MAX_SEQNO + 1
+};
+
+struct seqidx {
+ HOSTADDR *host;
+ struct timeval tv;
+};
+
+static struct seqidx *seqidx;
+unsigned short next_seqno;
+
+static int
+seqno_alloc(HOSTADDR *addr, struct timeval *tv)
+{
+ int n = next_seqno;
+ do {
+ if (tv->tv_sec - seqidx[n].tv.tv_sec > MAX_PING_TIMEOUT) {
+ memcpy(&seqidx[n].tv, tv, sizeof(*tv));
+ seqidx[n].host = addr;
+ next_seqno = (n + 1) % MOD_SEQNO;
+ return n;
+ }
+ n = (n + 1) % MOD_SEQNO;
+ } while (n != next_seqno);
+ error("no free sequence numbers");
+ return -1;
+}
+
+static HOSTADDR *
+hostaddr_from_seqno(int seq)
+{
+ HOSTADDR *host = seqidx[seq].host;
+ memset(&seqidx[seq], 0, sizeof(seqidx[seq]));
+ return host;
+}
+
+void
+p903_init(void)
+{
+ struct protoent *proto;
+ int i;
+
+ if (hostaddr_count == 0) {
+ fatal("no host IPs configured, nothing to do?");
+ exit(1);
+ }
+
+ proto = getprotobyname("icmp");
+ if (!proto) {
+ fatal("no entry for icmp in the system protocol database");
+ exit(1);
+ }
+ ping_fd = socket(AF_INET, SOCK_RAW, proto->p_proto);
+ if (ping_fd < 0) {
+ fatal("can't create ICMP socket: %s", strerror(errno));
+ exit(1);
+ }
+
+ ping_ident = getpid() & 0xffff;
+
+ data_buffer = emalloc(data_length);
+ for (i = 0; i < data_length; i++)
+ data_buffer[i] = i;
+
+ seqidx = calloc(MAX_SEQNO + 1, sizeof(seqidx[0]));
+ if (!seqidx)
+ emalloc_die();
+}
+
+void
+sendq_enqueue_unlocked(HOSTADDR *addr)
+{
+ addr->next = NULL;
+ if (sendq_tail)
+ sendq_tail->next = addr;
+ else
+ sendq_head = addr;
+ sendq_tail = addr;
+}
+
+void
+sendq_enqueue(HOSTADDR *addr)
+{
+ pthread_mutex_lock(&sendq_mutex);
+ sendq_enqueue_unlocked(addr);
+ pthread_cond_broadcast(&sendq_cond);
+ pthread_mutex_unlock(&sendq_mutex);
+}
+
+HOSTADDR *
+sendq_dequeue(void)
+{
+ HOSTADDR *host;
+ while (!sendq_head) {
+ pthread_cond_wait(&sendq_cond, &sendq_mutex);
+ }
+ host = sendq_head;
+ sendq_head = host->next;
+ if (sendq_head == NULL)
+ sendq_tail = NULL;
+ host->next = NULL;
+ return host;
+}
+
+unsigned short
+icmp_cksum(unsigned char * addr, int len)
+{
+ register int sum = 0;
+ unsigned short answer = 0;
+ unsigned short *wp;
+
+ for (wp = (unsigned short *) addr; len > 1; wp++, len -= 2)
+ sum += *wp;
+
+ /* Take in an odd byte if present */
+ if (len == 1) {
+ *(unsigned char *) & answer = *(unsigned char *) wp;
+ sum += answer;
+ }
+
+ sum = (sum >> 16) + (sum & 0xffff); /* add high 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+ return answer;
+}
+
+int
+icmp_generic_encode(unsigned char * buffer, size_t bufsize, int type,
+ int ident, int seqno)
+{
+ struct icmp *icmp;
+
+ if (bufsize < ICMP_MINLEN)
+ return -1;
+ icmp = (struct icmp *) buffer;
+ icmp->icmp_type = type;
+ icmp->icmp_code = 0;
+ icmp->icmp_cksum = 0;
+ icmp->icmp_seq = htons(seqno);
+ icmp->icmp_id = htons(ident);
+
+ icmp->icmp_cksum = icmp_cksum(buffer, bufsize);
+ return 0;
+}
+
+int
+icmp_generic_decode(unsigned char * buffer, size_t bufsize,
+ struct ip **ipp, struct icmp ** icmpp)
+{
+ size_t hlen;
+ unsigned short cksum;
+ struct ip *ip;
+ struct icmp *icmp;
+
+ /* IP header */
+ ip = (struct ip *) buffer;
+ hlen = ip->ip_hl << 2;
+ if (bufsize < hlen + ICMP_MINLEN)
+ return -1;
+
+ /* ICMP header */
+ icmp = (struct icmp *) (buffer + hlen);
+
+ /* Prepare return values */
+ *ipp = ip;
+ *icmpp = icmp;
+
+ /* Recompute checksum */
+ cksum = icmp->icmp_cksum;
+ icmp->icmp_cksum = 0;
+ icmp->icmp_cksum = icmp_cksum((unsigned char *) icmp, bufsize - hlen);
+ if (icmp->icmp_cksum != cksum)
+ return 1;
+ icmp->icmp_seq = ntohs(icmp->icmp_seq);
+ icmp->icmp_id = ntohs(icmp->icmp_id);
+
+ return 0;
+}
+
+static void
+send_echo(HOSTADDR *host, unsigned char *ping_buffer)
+{
+ struct icmp *icmp = (struct icmp *) ping_buffer;
+ size_t buflen;
+ ssize_t n;
+ int seqno;
+
+ gettimeofday(&host->tv, NULL);
+ memcpy(icmp->icmp_data, &host->tv, sizeof(host->tv));
+ if (data_buffer)
+ memcpy(icmp->icmp_data + PING_HEADER_LEN, data_buffer,
+ data_length > PING_HEADER_LEN
+ ? data_length - PING_HEADER_LEN
+ : data_length);
+
+ buflen = ICMP_HEADER_LEN + data_length;
+
+ seqno = seqno_alloc(host, &host->tv);
+ if (seqno == -1) {
+ sendq_enqueue_unlocked(host);
+ return;
+ }
+
+ icmp_generic_encode(ping_buffer, buflen, ICMP_ECHO, ping_ident, seqno);
+
+ n = sendto(ping_fd, (char *) ping_buffer, buflen, 0,
+ host->addr, host->addrlen);
+ if (n < 0)
+ error("%s: sendto: %s", host->name, strerror(errno));
+ else {
+ host->xmit_count++;
+ if (n != buflen)
+ error ("ping: wrote %s %d chars, ret=%d\n",
+ host->name, buflen, n);
+ }
+}
+
+void *
+p903_sender(void *p)
+{
+ unsigned char *ping_buffer;
+ ping_buffer = emalloc(sizeof(struct icmp) + data_length);
+
+ pthread_mutex_lock(&sendq_mutex);
+ while (1) {
+ HOSTADDR *host = sendq_dequeue();
+ send_echo(host, ping_buffer);
+ }
+ pthread_mutex_lock(&sendq_mutex);
+ return NULL;
+}
+
+double
+nabs(double a)
+{
+ return (a < 0) ? -a : a;
+}
+
+double
+nsqrt(double a, double prec)
+{
+ double x0, x1;
+
+ if (a < 0)
+ return 0;
+ if (a < prec)
+ return 0;
+ x1 = a / 2;
+ do {
+ x0 = x1;
+ x1 = (x0 + a / x0) / 2;
+ }
+ while (nabs(x1 - x0) > prec);
+ return x1;
+}
+
+static void
+host_stat(HOSTADDR *host, struct timeval const *tv)
+{
+ memcpy(&host->stat_last.tv, tv, sizeof(host->stat_last.tv));
+ host->stat_last.xmit_count = host->xmit_count;
+ host->stat_last.recv_count = host->recv_count;
+ host->stat_last.tmin = host->tmin;
+ host->stat_last.tmax = host->tmax;
+ if (host->recv_count > 0) {
+ double total = host->recv_count; //FIXME: repeat count?
+ double avg = host->tsum / total;
+ double vari = host->tsumsq / total - avg * avg;
+
+ host->stat_last.avg = avg;
+ host->stat_last.stddev = nsqrt(vari, 0.0005);
+ }
+}
+
+/* Reset runtime statistics counters */
+static void
+host_reset(HOSTADDR *host)
+{
+ host->xmit_count = 0;
+ host->recv_count = 0;
+ host->tmin = 999999999.0;
+ host->tmax = 0;
+ host->tsum = 0;
+ host->tsumsq = 0;
+}
+
+int
+get_host_stat(HOSTADDR *host, struct json_value **retval)
+{
+ struct json_value *obj, *v;
+ struct host_stat const *st = &host->stat_last;
+ double ts;
+
+ if (!(obj = json_new_object()))
+ goto err;
+
+ if (!(v = json_new_string(host->name))
+ || json_object_set(obj, "name", v))
+ goto err;
+ if (!(v = json_new_bool(host_stat_is_valid(st)))
+ || json_object_set(obj, "status", v))
+ goto err;
+ ts = (double)host->tv.tv_sec + (double)host->tv.tv_usec / 1000000;
+ if (!(v = json_new_number(ts))
+ || json_object_set(obj, "xmit-timestamp", v))
+ goto err;
+
+ if (host_stat_is_valid(st)) {
+ int is_alive = st->xmit_count - st->recv_count
+ < ping_tolerance;
+
+ /* JSON:
+ { "alive": BOOL,
+ "xmit": NUM,
+ "recv": NUM,
+ "loss": NUM,
+ "min"
+ "max"
+ "avg"
+ "stddev"
+ }
+ */
+ ts = (double)st->tv.tv_sec
+ + (double)st->tv.tv_usec / 1000000;
+ if (!(v = json_new_number(ts))
+ || json_object_set(obj, "recv-timestamp", v))
+ goto err;
+ if (!(v = json_new_bool(is_alive))
+ || json_object_set(obj, "alive", v))
+ goto err;
+ if (!(v = json_new_number(st->xmit_count))
+ || json_object_set(obj, "xmit", v))
+ goto err;
+ if (!(v = json_new_number(st->recv_count))
+ || json_object_set(obj, "recv", v))
+ goto err;
+ if (!(v = json_new_number((double) 100
+ * (st->xmit_count - st->recv_count)
+ / st->xmit_count))
+ || json_object_set(obj, "loss", v))
+ goto err;
+
+ if (st->recv_count > 0) {
+ if (!(v = json_new_number(st->tmin))
+ || json_object_set(obj, "tmin", v))
+ goto err;
+ if (!(v = json_new_number(st->tmax))
+ || json_object_set(obj, "tmax", v))
+ goto err;
+ if (!(v = json_new_number(st->avg))
+ || json_object_set(obj, "avg", v))
+ goto err;
+ if (!(v = json_new_number(st->stddev))
+ || json_object_set(obj, "stddev", v))
+ goto err;
+ }
+ }
+
+ *retval = obj;
+ return 0;
+
+ err:
+ error("out of memory when formatting statistics for %s", host->name);
+ json_value_free(v);
+ json_value_free(obj);
+
+ return -1;
+}
+
+int
+get_hostname_stat(char const *name, struct json_value **retval)
+{
+ size_t i;
+
+ for (i = 0; i < hostaddr_count; i++) {
+ if (strcmp(hostaddr[i].name, name) == 0)
+ return get_host_stat(&hostaddr[i], retval);
+ }
+ *retval = NULL;
+ return 0;
+}
+
+int
+get_all_host_stat(struct json_value **retval)
+{
+ struct json_value *ar;
+ size_t i;
+
+ if (!(ar = json_new_array()))
+ goto err;
+ for (i = 0; i < hostaddr_count; i++) {
+ struct json_value *v;
+ if (get_host_stat(&hostaddr[i], &v))
+ goto err;
+ if (json_array_append(ar, v)) {
+ json_value_free(v);
+ goto err;
+ }
+ }
+ *retval = ar;
+ return 0;
+ err:
+ json_value_free(ar);
+ return -1;
+}
+
+void *
+p903_receiver(void *p)
+{
+ size_t buflen = sizeof(struct icmp) + data_length;
+ unsigned char *ping_buffer;
+
+ ping_buffer = emalloc(buflen);
+
+ while (1) {
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ ssize_t n;
+ int rc;
+ struct icmp *icmp;
+ struct ip *ip;
+ HOSTADDR *host;
+
+ n = recvfrom(ping_fd, ping_buffer, buflen, 0,
+ (struct sockaddr *) &addr, &addrlen);
+ if (n < 0) {
+ error("recvfrom: %s", strerror(errno));
+ continue;
+ }
+ rc = icmp_generic_decode(ping_buffer, n, &ip, &icmp);
+ if (rc < 0) {
+ char hbuf[NI_MAXHOST];
+ if (getnameinfo((struct sockaddr *) &addr,
+ addrlen,
+ hbuf, sizeof(hbuf),
+ NULL, 0, NI_NUMERICHOST)) {
+ error("packet too short (%d bytes)", n);
+ } else {
+ error("packet too short (%d bytes), from %s",
+ n, hbuf);
+ }
+ continue;
+ }
+
+ if (icmp->icmp_type != ICMP_ECHOREPLY
+ || icmp->icmp_id != ping_ident)
+ continue;
+
+ if (rc) {
+ char hbuf[NI_MAXHOST];
+ if (getnameinfo((struct sockaddr *) &addr,
+ addrlen,
+ hbuf, sizeof(hbuf),
+ NULL, 0, NI_NUMERICHOST)) {
+ error("checksum mismatch");
+ } else {
+ error("checksum mismatch from %s", hbuf);
+ }
+ continue;
+ }
+
+ host = hostaddr_from_seqno(icmp->icmp_seq);
+ if (host) {
+ struct timeval tv_now, tv_orig, tv_diff, *tp;
+ double rtt;
+
+// info("got reply for %s", host->name);
+ gettimeofday (&tv_now, NULL);
+
+ tp = (struct timeval *) icmp->icmp_data;
+
+ /* Avoid unaligned data: */
+ memcpy(&tv_orig, tp, sizeof (tv_orig));
+ timersub(&tv_now, &tv_orig, &tv_diff);
+
+ rtt = ((double) tv_diff.tv_sec) * 1000.0 +
+ ((double) tv_diff.tv_usec) / 1000.0;
+ host->tsum += rtt;
+ host->tsumsq += rtt * rtt;
+ if (rtt < host->tmin)
+ host->tmin = rtt;
+ if (rtt > host->tmax)
+ host->tmax = rtt;
+
+ host->recv_count++;
+ if (host->recv_count < ping_count) {
+ sendq_enqueue(host);
+ } else {
+ host_stat(host, &tv_now);
+ }
+ }
+ }
+}
+
+void *
+p903_scheduler(void *p)
+{
+ while (1) {
+ size_t i;
+ for (i = 0; i < hostaddr_count; i++) {
+ host_reset(hostaddr + i);
+ sendq_enqueue(hostaddr + i);
+ }
+ sleep(ping_interval);
+ }
+}
diff --git a/src/remoteip.c b/src/remoteip.c
new file mode 100644
index 0000000..44f0eaf
--- /dev/null
+++ b/src/remoteip.c
@@ -0,0 +1,379 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <microhttpd.h>
+#include "ping903.h"
+#include "json.h"
+
+/* Returns 1 if ADDR is a valid string representation of IPv4 address */
+static int
+str_is_ipv4(const char *addr)
+{
+ int dot_count = 0;
+ int digit_count = 0;
+
+ for (; *addr; addr++) {
+ if (!isascii(*addr))
+ return 0;
+ if (*addr == '.') {
+ if (++dot_count > 3)
+ break;
+ digit_count = 0;
+ }
+ else if (!(isdigit(*addr) && ++digit_count <= 3))
+ return 0;
+ }
+ return (dot_count == 3);
+}
+
+#define PFXSTR_IPV4_MAPPED "::ffff:"
+#define PFXLEN_IPV4_MAPPED (sizeof PFXSTR_IPV4_MAPPED - 1)
+
+static int
+str_is_ipv4mapped(const char *addr)
+{
+ return strlen(addr) > PFXLEN_IPV4_MAPPED
+ && strncasecmp(PFXSTR_IPV4_MAPPED, addr, PFXLEN_IPV4_MAPPED)
+ == 0
+ && str_is_ipv4(addr + PFXLEN_IPV4_MAPPED);
+}
+
+/* Returns 1 if ADDR is a valid IPv6 address */
+static int
+str_is_ipv6(const char *addr)
+{
+ int col_count = 0; /* Number of colons */
+ int dcol = 0; /* Did we encounter a double-colon? */
+ int dig_count = 0; /* Number of digits in the last group */
+
+ for (; *addr; addr++) {
+ if (!isascii (*addr))
+ return 0;
+ else if (isxdigit(*addr)) {
+ if (++dig_count > 4)
+ return 0;
+ } else if (*addr == ':') {
+ if (col_count && dig_count == 0 && ++dcol > 1)
+ return 0;
+ if (++col_count > 7)
+ return 0;
+ dig_count = 0;
+ }
+ else
+ return 0;
+ }
+ return (col_count == 7 || dcol);
+}
+
+/* Max. number of bytes in an IPv6 address */
+#define MU_INADDR_BYTES 16
+
+/* CIDR representation */
+struct cidr {
+ int family; /* Address family */
+ int len; /* Number of bytes in the address */
+ unsigned char address[MU_INADDR_BYTES];
+ unsigned char netmask[MU_INADDR_BYTES];
+ char *str; /* String representation */
+ struct cidr *next; /* Next CIDR in list */
+};
+
+static void
+uint32_to_bytes(unsigned char *bytes, uint32_t u)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ bytes[i] = u & 0xff;
+ u >>= 8;
+ }
+}
+
+static int
+inaddr_to_bytes(int af, void *buf, unsigned char *bytes)
+{
+ uint32_t u;
+
+ switch (af) {
+ case AF_INET:
+ memcpy(&u, buf, sizeof u);
+ uint32_to_bytes (bytes, u);
+ return 4;
+
+ case AF_INET6:
+ memcpy(bytes, buf, 16);
+ return 16;
+ }
+
+ abort();
+}
+
+/* Fill in the BUF (LEN bytes long) with a network mask corresponding to
+ the mask length MASKLEN */
+static void
+masklen_to_netmask(unsigned char *buf, size_t len, size_t masklen)
+{
+ int i, cnt;
+
+ cnt = masklen / 8;
+ for (i = 0; i < cnt; i++)
+ buf[i] = 0xff;
+ if (i == len)
+ return;
+ cnt = 8 - masklen % 8;
+ buf[i++] = (0xff >> cnt) << cnt;
+ for (; i < len; i++)
+ buf[i] = 0;
+}
+
+/* Convert string STR to CIDR. Return 0 on success and -1 on failure. */
+static int
+str_to_cidr(char const *str, struct cidr *cidr,
+ char const *file, unsigned line)
+{
+ int rc;
+ char *ipbuf, *ipstart;
+ union {
+ struct in_addr in;
+ struct in6_addr in6;
+ } inaddr;
+ size_t len;
+ char *p;
+
+ cidr->str = estrdup(str);
+ p = strrchr(str, '/');
+ if (p)
+ len = p - str;
+ else
+ len = strlen (str);
+
+ ipbuf = emalloc(len+1);
+ memcpy(ipbuf, str, len);
+ ipbuf[len] = 0;
+ ipstart = ipbuf;
+
+ if (str_is_ipv4(ipstart))
+ cidr->family = AF_INET;
+ else if (str_is_ipv4mapped(ipstart)) {
+ cidr->family = AF_INET;
+ ipstart += PFXLEN_IPV4_MAPPED;
+ } else if (str_is_ipv6(ipbuf))
+ cidr->family = AF_INET6;
+ else {
+ free(ipbuf);
+ error("%s:%d: invalid CIDR: %s", file, line, str);
+ return -1;
+ }
+
+ rc = inet_pton(cidr->family, ipstart, &inaddr);
+ free(ipbuf);
+
+ if (rc != 1) {
+ if (rc == -1) {
+ error("%s:%d: invalid address family", file, line);
+ return -1;
+ }
+ if (rc == 0) {
+ error("%s:%d: invalid IPv%s address: %s",
+ file, line,
+ cidr->family == AF_INET ? "4" : "6",
+ str);
+ return -1;
+ }
+ }
+
+ cidr->len = inaddr_to_bytes(cidr->family, &inaddr, cidr->address);
+
+ if (p) {
+ char *end;
+ unsigned long masklen;
+
+ p++;
+
+ masklen = strtoul(p, &end, 10);
+ if (*end == 0)
+ masklen_to_netmask(cidr->netmask, cidr->len, masklen);
+ else if ((cidr->family == AF_INET && str_is_ipv4(p))
+ || (cidr->family == AF_INET6 && str_is_ipv6(ipbuf))) {
+ rc = inet_pton(cidr->family, p, &inaddr);
+ if (rc != 1) {
+ error(file, line, "bad netmask: %s",
+ file, line, p);
+ return -1;
+ }
+ inaddr_to_bytes(cidr->family, &inaddr, cidr->netmask);
+ } else {
+ error("%s:%d: bad CIDR (near %s)", file, line, p);
+ return -1;
+ }
+ } else
+ masklen_to_netmask(cidr->netmask, cidr->len, cidr->len * 8);
+ return 0;
+}
+
+/* A is a valid CIDR, and B is a valid IP address represented as CIDR.
+ Return 0 if B falls within A */
+static int
+cidr_match(struct cidr *a, struct cidr *b)
+{
+ int i;
+
+ if (a->family != b->family)
+ return 1;
+ for (i = 0; i < a->len; i++) {
+ if (a->address[i] != (b->address[i] & a->netmask[i]))
+ return 1;
+ }
+ return 0;
+}
+
+struct cidr *trusted_ip_list;
+
+int
+cf_trusted_ip(int mode, union cf_callback_arg *arg, void *data)
+{
+ if (mode == CF_PARSE) {
+ struct cidr *cidr = emalloc(sizeof(cidr[0]));
+
+ if (str_to_cidr(arg->input.val, cidr,
+ arg->input.file, arg->input.line)) {
+ free(cidr);
+ return CF_RET_FAIL;
+ }
+ cidr->next = trusted_ip_list;
+ trusted_ip_list = cidr;
+ } else {
+ struct cidr *cidr;
+ struct json_value *ar = json_new_array();
+
+ for (cidr = trusted_ip_list; cidr; cidr = cidr->next) {
+ struct json_value *v;
+
+ if (!(v = json_new_string(cidr->str)))
+ goto err;
+ if (json_array_append(ar, v)) {
+ json_value_free(v);
+ goto err;
+ }
+ }
+
+ arg->output = ar;
+ return CF_RET_OK;
+ err:
+ json_value_free(ar);
+ return CF_RET_FAIL;
+ }
+ return CF_RET_OK;
+}
+
+static int
+is_trusted_ip(char const *ipstr)
+{
+ struct cidr cidr;
+ struct cidr *p;
+
+ if (str_to_cidr(ipstr, &cidr, __FILE__, __LINE__))
+ return 0;
+ for (p = trusted_ip_list; p; p = p->next) {
+ if (cidr_match(p, &cidr) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static char *
+scan_forwarded_header(char const *hdr)
+{
+ char const *end;
+ char *buf = NULL;
+ size_t bufsize = 0;
+
+ end = hdr + strlen(hdr);
+ while (end > hdr) {
+ char const *p;
+ size_t len, i, j;
+
+ for (p = end - 1; p > hdr && *p != ','; p--)
+ ;
+ len = end - p;
+ if (len + 1 > bufsize) {
+ char *newbuf = realloc(buf, len + 1);
+ if (newbuf) {
+ buf = newbuf;
+ bufsize = len + 1;
+ } else {
+ free(buf);
+ return NULL;
+ }
+ }
+
+ i = 0;
+ j = 0;
+ if (*p == ',')
+ j++;
+ while (j < len && isspace(p[j]))
+ j++;
+ while (j < len) {
+ if (isspace(p[j]))
+ break;
+ buf[i++] = p[j++];
+ }
+ buf[i] = 0;
+
+ if (!is_trusted_ip(buf))
+ return buf;
+
+ end = p;
+ }
+ free(buf);
+ return 0;
+}
+
+char *
+get_remote_ip(struct MHD_Connection *conn)
+{
+ union MHD_ConnectionInfo const *ci;
+ char ipstr[NI_MAXHOST];
+ char const *hdr;
+ char *ret;
+
+ hdr = MHD_lookup_connection_value(conn,
+ MHD_HEADER_KIND,
+ "x-forwarded-from");
+ if (hdr && (ret = scan_forwarded_header(hdr)) != NULL)
+ return ret;
+
+ ci = MHD_get_connection_info(conn,
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS,
+ NULL);
+ if (!ci)
+ return NULL;
+
+ if (getnameinfo(ci->client_addr, sizeof(struct sockaddr),
+ ipstr, sizeof(ipstr), NULL, 0,
+ NI_NUMERICHOST))
+ return NULL;
+ return strdup(ipstr);
+}
diff --git a/src/wrapacl.c b/src/wrapacl.c
new file mode 100644
index 0000000..de6c2dd
--- /dev/null
+++ b/src/wrapacl.c
@@ -0,0 +1,21 @@
+/* This file is part of Ping903
+Copyright (C) 2020 Sergey Poznyakoff
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+*/
+#include <tcpd.h>
+#include <microhttpd.h>
+
+int
+p903_httpd_acl(void *cls, const struct sockaddr *addr, socklen_t addrlen)
+{
+ struct request_info req;
+ request_init(&req,
+ RQ_DAEMON, "ping903",
+ RQ_CLIENT_SIN, addr,
+ RQ_SERVER_SIN, cls,
+ NULL);
+ sock_methods(&req);
+ return hosts_access(&req) ? MHD_YES : MHD_NO;
+}

Return to:

Send suggestions and report system problems to the System administrator.