/* 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
#include
#include
#include
#include "json.h"
#include "defs.h"
#include "basicauth.h"
static char *config_file = DEFAULT_CONFIG_FILE;
struct addrinfo *addr;
FILE *http;
int verbose;
int resolve_ip;
int nodata_ok = 1;
char *nagios_prefix_format = "PING";
char const http_version[] = "HTTP/1.1";
char *cred_file_name;
char *nodename, *service;
enum {
EX_NAGIOS_OK = 0,
EX_NAGIOS_WARNING = 1,
EX_NAGIOS_CRITICAL = 2,
EX_NAGIOS_UNKNOWN = 3,
EX_NAGIOS_DEPENDENT = 4
};
static char const *status_str[] = {
"OK",
"WARNING",
"CRITICAL",
"UNKNOWN",
"DEPENDENT"
};
static void
abend(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vlogger(LOG_CRIT, fmt, ap);
va_end(ap);
exit(EX_NAGIOS_UNKNOWN);
}
static char *
read_listen(char **ret_service)
{
FILE *fp;
int ln = 0;
char buf[1024];
int skip_to_eol = 0;
char *retval = NULL;
*ret_service = NULL;
fp = fopen(config_file, "r");
if (!fp) {
if (errno == ENOENT)
return NULL;
else
abend("can't open %s: %s",
config_file, strerror(errno));
}
while (fgets(buf, sizeof(buf), fp)) {
int len;
char *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", config_file, ln);
skip_to_eol = 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 (strncmp(p, "listen", 6) == 0) {
char *q;
p += 6;
while (*p && (*p == ' '||*p == '\t'))
p++;
retval = estrdup(p);
q = strchr(retval, ':');
if (q) {
*q++ = 0;
if (*q) {
*ret_service = estrdup(q);
}
}
break;
}
}
fclose(fp);
return retval;
}
static void
http_reconnect(void)
{
int fd;
fd = socket(addr->ai_family, addr->ai_socktype, 0);
if (fd == -1)
abend("socket: %s", strerror(errno));
if (connect(fd, (struct sockaddr *)addr->ai_addr, addr->ai_addrlen))
abend("failed to connect: %s", strerror(errno));
if (http)
fclose(http);
http = fdopen(fd, "w+");
if (http == NULL)
abend("failed to open socket: %s", strerror(errno));
}
static void
http_connect(char *node, char *service)
{
struct addrinfo hints;
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, service, &hints, &addr);
if (rc)
abend("%s: %s", node, gai_strerror(rc));
http_reconnect();
}
struct http_resp {
int code; /* HTTP response code */
char *reason; /* Human-readable status */
char **hv; /* Headers */
size_t hc; /* Number of headers in hv */
size_t hn; /* Max capacity of hv */
char *content;
size_t content_length;
};
struct http_buf {
char *base;
size_t size;
size_t len;
};
#define HTTP_BUF_INITIALIZER { NULL, 0, 0 }
static char const *
http_resp_get_header(struct http_resp *resp, char const *name)
{
int i;
size_t len = strlen(name);
for (i = 0; i < resp->hc; i++) {
if (strlen(resp->hv[i]) > len
&& resp->hv[i][len] == ':'
&& strncasecmp(resp->hv[i], name, len) == 0) {
char const *ret = resp->hv[i] + len + 1;
while (*ret && (*ret == ' ' || *ret == '\t'))
ret++;
return ret;
}
}
return NULL;
}
static void
http_buf_init(struct http_buf *hbuf)
{
memset(hbuf, 0, sizeof(*hbuf));
}
static void
http_buf_free(struct http_buf *hbuf)
{
free(hbuf->base);
http_buf_init(hbuf);
}
static void
http_buf_add(struct http_buf *hbuf, char const *str, size_t len)
{
while (hbuf->len + len >= hbuf->size) {
hbuf->base = e2nrealloc(hbuf->base, &hbuf->size, 1);
}
memcpy(hbuf->base + hbuf->len, str, len);
hbuf->len += len;
}
static void
http_readline(struct http_buf *hbuf)
{
hbuf->len = 0;
while (1) {
if (hbuf->len == hbuf->size) {
hbuf->base = e2nrealloc(hbuf->base, &hbuf->size, 1);
}
if (fgets(hbuf->base + hbuf->len, hbuf->size, http) == NULL) {
if (feof(http))
abend("connection closed prematurely");
else
abend("error reading from socket: %s",
strerror(errno));
}
hbuf->len += strlen(hbuf->base + hbuf->len);
if (hbuf->base[hbuf->len-1] == '\n') {
hbuf->base[--hbuf->len] = 0;
if (hbuf->base[hbuf->len-1] == '\r')
hbuf->base[--hbuf->len] = 0;
break;
}
}
}
static int
split3(const char *input, char *res[3], int flag)
{
size_t len;
char *p;
p = strchr(input, ' ');
if (!p)
return 1;
len = p - input;
res[0] = malloc(len + 1);
if (!res[0])
return -1;
memcpy(res[0], input, len);
res[0][len] = 0;
input = p + 1;
p = (flag ? strrchr : strchr)(input, ' ');
if (!p)
return 1;
len = p - input;
res[1] = malloc(len + 1);
if (!res[1])
return -1;
memcpy(res[1], input, len);
res[1][len] = 0;
res[2] = strdup(p + 1);
if (!res[2])
return -1;
return 0;
}
int
strsplit3(const char *input, char *result[3], int flag)
{
char *tmp[3] = { NULL, NULL, NULL };
int rc = split3(input, tmp, flag);
if (rc) {
int ec = errno;
free(tmp[0]);
free(tmp[1]);
free(tmp[2]);
errno = ec;
} else {
result[0] = tmp[0];
result[1] = tmp[1];
result[2] = tmp[2];
}
return rc;
}
static void
http_resp_init(struct http_resp *resp)
{
memset(resp, 0, sizeof(*resp));
}
static void
http_resp_reset(struct http_resp *resp)
{
size_t i;
resp->code = 0;
free(resp->reason);
resp->reason = NULL;
for (i = 0; i < resp->hc; i++) {
free(resp->hv[i]);
resp->hv[i] = NULL;
}
resp->hc = 0;
free(resp->content);
resp->content = NULL;
resp->content_length = 0;
}
static void
http_recv(struct http_resp *resp)
{
enum input_state { is_initial, is_headers, is_content } state = is_initial;
struct http_buf hbuf = HTTP_BUF_INITIALIZER;
char const *hval;
http_resp_reset(resp);
while (state != is_content) {
http_readline(&hbuf);
if (state == is_initial) {
char *res[3];
if (strsplit3(hbuf.base, res, 0)) {
abend("malformed HTTP response");
}
if (strcmp(res[0], http_version)) {
abend("unsupported HTTP version: %s", res[0]);
}
free(res[0]);
resp->code = atoi(res[1]);
if (resp->code <= 0 || resp->code > 559) {
abend("bad response code: %s", res[1]);
}
free(res[1]);
resp->reason = res[2];
state = is_headers;
} else if (state == is_headers) {
if (resp->hc == resp->hn) {
resp->hv = json_2nrealloc(resp->hv,
&resp->hn,
sizeof(resp->hv[0]));
}
if (hbuf.len > 0) {
resp->hv[resp->hc++] = estrdup(hbuf.base);
} else {
resp->hv[resp->hc] = NULL;
state = is_content;
}
}
}
http_buf_free(&hbuf);
hval = http_resp_get_header(resp, "content-length");
if (hval) {
char *p;
unsigned long len = strtoul(hval, &p, 10);
if (*p) {
abend("malformed content-length");
}
resp->content = emalloc(len + 1);
resp->content_length = len;
p = resp->content;
while (len) {
ssize_t n = fread(p, 1, len, http);
if (n == -1) {
abend("socket read error: %s",
strerror(errno));
}
if (n == 0) {
abend("short read from socket");
}
len -= n;
p += n;
}
*p = 0;
}
hval = http_resp_get_header(resp, "connection");
if (hval) {
if (strcasecmp(hval, "close") == 0) {
fclose(http);
http = NULL;
} else if (strcasecmp(hval, "keep-alive") != 0) {
abend("unsupported Connection: %s", hval);
}
}
}
static void
http_query_primitive(char const *meth, char const *url, char **hdr)
{
size_t i;
int host = 0;
if (!http)
http_reconnect();
fprintf(http, "%s %s HTTP/1.1\r\n", meth, url);
if (hdr) {
for (i = 0; hdr[i]; i++) {
fprintf(http, "%s\r\n", hdr[i]);
if (!host && strncasecmp(hdr[i], "host:", 5) == 0)
host = 1;
}
}
if (!host)
fprintf(http, "Host: localhost\r\n");
fprintf(http, "\r\n");
}
static char *std_headers[] = {
"Accept: application/json",
"User-Agent: ping903q (" PACKAGE_STRING ")",
NULL,
NULL,
};
/* Place where to insert the Authorization header to std_headers */
#define AUTH_HDR_IDX 2
#define BASICPREF "Basic realm=\""
#define BASICLEN (sizeof(BASICPREF)-1)
static char const *
get_cred_file_name(void)
{
if (!cred_file_name) {
char const *home = getenv("HOME");
size_t len = strlen(home) + sizeof(CRED_FILE_NAME);
cred_file_name = emalloc(len + 1);
strcpy(cred_file_name, home);
strcat(cred_file_name, "/");
strcat(cred_file_name, CRED_FILE_NAME);
}
return cred_file_name;
}
static int
addrinfocmp(struct addrinfo *aptr, struct addrinfo *bptr)
{
for (; aptr; aptr = aptr->ai_next) {
for (; bptr; bptr = bptr->ai_next) {
if (aptr->ai_addrlen == bptr->ai_addrlen
&& memcmp(aptr->ai_addr, bptr->ai_addr,
bptr->ai_addrlen) == 0)
return 0;
}
}
return 1;
}
static int
nodecmp(char const *a, char const *b)
{
struct addrinfo hints, *ares, *bres;
int rc;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo(a, NULL, &hints, &ares);
if (rc)
return -1;
rc = getaddrinfo(b, NULL, &hints, &bres);
if (rc) {
freeaddrinfo(ares);
return -1;
}
rc = addrinfocmp(ares, bres);
freeaddrinfo(ares);
freeaddrinfo(bres);
return rc;
}
static int
machinecmp(char const *pat)
{
if (strcmp(pat, "*") == 0)
return 0;
if (pat[0] == '*' && pat[1] == ':') {
pat += 2;
} else {
size_t len = strcspn(pat, ":");
if (!(len == strlen(nodename)
&& memcmp(pat, nodename, len) == 0)) {
int rc;
char *pcopy = emalloc(len + 1);
memcpy(pcopy, pat, len);
pcopy[len] = 0;
rc = nodecmp(pcopy, nodename);
free(pcopy);
if (rc)
return -1;
}
pat += len;
if (*pat == 0)
return 0;
pat++;
}
if (strcmp(pat, "*") == 0)
return 0;
return strcmp(pat, service);
}
#define HTTP_AUTHORIZATION "Authorization: Basic "
#define HTTP_AUTHORIZATION_LEN (sizeof(HTTP_AUTHORIZATION)-1)
static int
get_realm_creds(char const *realm, char **retval)
{
size_t len;
char const *cfname;
FILE *fp;
char buf[1024];
int ln = 0;
int skip_to_eol = 0;
int ret;
cfname = get_cred_file_name();
fp = fopen(cfname, "r");
if (!fp)
return -1;
ret = -1;
while (fgets(buf, sizeof(buf), fp)) {
char *p;
int ac;
char **av;
char *endp;
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", cfname, ln);
skip_to_eol = 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;
switch (strsplit(p, 4, &ac, &av, &endp)) {
case STRSPLIT_OK:
if (!*endp)
break;
/* fall through */
case STRSPLIT_ERR:
error("%s:%d: syntax error near %s",
cfname, ln, endp);
argcv_free(ac, av);
goto err;
case STRSPLIT_NOMEM:
emalloc_die();
}
if (ac != 4) {
error("%s:%d: not enough fields", cfname, ln);
argcv_free(ac, av);
goto err;
}
if (machinecmp(av[0]) == 0
&& (strcmp(av[1], "*") == 0
|| strcmp(av[1], realm) == 0)) {
len = strlen(av[2]) + strlen(av[3]) + 2;
char *plaintext;
unsigned char *b64;
size_t b64_len;
char *hdr;
/* Create plaintext auth string */
plaintext = emalloc(len);
strcpy(plaintext, av[2]);
strcat(plaintext, ":");
strcat(plaintext, av[3]);
/* Encode it to base64 */
if (base64_encode((unsigned char const *)plaintext,
len, &b64, &b64_len))
emalloc_die();
free(plaintext);
/* Create Authorization header */
hdr = emalloc(HTTP_AUTHORIZATION_LEN
+ b64_len
+ 1);
strcpy(hdr, HTTP_AUTHORIZATION);
strcat(hdr, (char*) b64);
free(b64);
/* Return it */
*retval = hdr;
ret = 0;
argcv_free(ac, av);
break;
} else {
argcv_free(ac, av);
}
}
err:
fclose(fp);
return ret;
}
static int
get_auth_creds(char const *auth, char **retval)
{
char *realm;
size_t len;
int rc;
int i;
if (!auth || strncmp(auth, BASICPREF, BASICLEN))
return -1;
auth += BASICLEN;
len = strlen(auth);
if (len == 0 || auth[len-1] != '"')
return -1;
len--;
if (auth[len-1] == '\\')
return -1;
realm = emalloc(len + 1);
memcpy(realm, auth, len);
for (i = 0; i < len; ) {
if (*auth == '\\')
auth++;
realm[i++] = *auth++;
}
realm[i] = 0;
rc = get_realm_creds(realm, retval);
free(realm);
return rc;
}
static int
has_authorization(char **hdr)
{
size_t i;
if (!hdr)
return 0;
for (i = 0; hdr[i]; i++)
if (strncmp(hdr[i], HTTP_AUTHORIZATION,
HTTP_AUTHORIZATION_LEN) == 0)
return 1;
return 0;
}
static void
http_query(char const *meth, char const *url, char **hdr,
struct http_resp *resp)
{
http_query_primitive(meth, url, hdr);
http_recv(resp);
if (resp->code != 200) {
if (resp->code == 401 && !has_authorization(hdr)) {
char *creds;
char const *auth =
http_resp_get_header(resp,
"WWW-Authenticate");
if (get_auth_creds(auth, &creds) == 0) {
char **new_hdr;
size_t i, nhdr;
for (nhdr = 0; hdr[nhdr]; nhdr++)
;
new_hdr = ecalloc(nhdr + 2,
sizeof(new_hdr[0]));
for (i = 0; i < nhdr; i++)
new_hdr[i] = hdr[i];
new_hdr[i] = creds;
new_hdr[i+1] = NULL;
http_query_primitive(meth, url, new_hdr);
http_recv(resp);
free(new_hdr);
free(creds);
}
}
}
}
static struct json_value *
ejson_get(struct json_value *obj, char *name, int type)
{
struct json_value *jv;
if (json_object_get(obj, name, &jv)) {
abend("no \"%s\" member in the response", name);
}
if (jv->type != type) {
abend("\"%s\" member has wrong type", name);
}
return jv;
}
static int
print_host_status(struct json_value *obj)
{
struct json_value *jv;
int alive;
char const *name;
jv = ejson_get(obj, "name", json_string);
name = jv->v.s;
jv = ejson_get(obj, "status", json_string);
if (strcmp(jv->v.s, "init") == 0) {
printf("%s: no data available yet; try later\n", name);
return EX_NAGIOS_UNKNOWN;
} else if (strcmp(jv->v.s, "invalid") == 0) {
printf("no response from %s\n", name);
return EX_NAGIOS_CRITICAL;
}
/* Status values "pending" and "valid" are OK */
alive = ejson_get(obj, "alive", json_bool)->v.b;
printf("%s is %s\n", name, alive ? "alive" : "not alive");
if (verbose) {
double xmit = ejson_get(obj, "xmit", json_number)->v.n;
double recv = ejson_get(obj, "recv", json_number)->v.n;
double start_ts = ejson_get(obj, "start-timestamp",
json_number)->v.n;
double stop_ts = ejson_get(obj, "stop-timestamp",
json_number)->v.n;
printf("--- %s ping statistics ---\n", name);
printf("%lu packets transmitted, %lu received, %d%% packet loss, time %.0fms\n",
(unsigned long) xmit, (unsigned long) recv,
(int)(100 * (xmit - recv) / xmit),
(stop_ts - start_ts) * 1000);
if (!json_object_get(obj, "stddev", &jv))
printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n",
ejson_get(obj, "tmin", json_number)->v.n,
ejson_get(obj, "avg", json_number)->v.n,
ejson_get(obj, "tmax", json_number)->v.n,
ejson_get(obj, "stddev", json_number)->v.n);
}
return alive ? EX_NAGIOS_OK : EX_NAGIOS_CRITICAL;
}
static char *
argvjoin(int argc, char **argv)
{
int i;
size_t len;
char *ret, *p, *q;
if (argc == 0)
return NULL;
len = argc - 1;
for (i = 0; i < argc; i++)
len += strlen(argv[i]);
ret = emalloc(len + 1);
p = ret;
for (i = 0; i < argc; i++) {
q = argv[i];
while ((*p++ = *q++))
;
p[-1] = ',';
}
p[-1] = 0;
return ret;
}
enum {
ARG_SELECT,
ARG_ATTR,
NARG_MAX
};
static char const *arg_str[] = {
"select=",
"attr=",
};
static char *
urlformat(char const *baseurl, char const *param, char **argv)
{
size_t len;
char *buf;
int i;
len = strlen(baseurl);
if (param)
len += strlen(param) + 1;
if (argv) {
for (i = 0; i < NARG_MAX; i++) {
if (argv[i])
len += strlen(arg_str[i]) + strlen(argv[i]) + 1;
}
}
buf = emalloc(len + 1);
strcpy(buf, baseurl);
if (param) {
strcat(buf, "/");
strcat(buf, param);
}
if (argv) {
char const *delim[] = { "?", "&" };
for (i = 0; i < NARG_MAX; i++) {
if (argv[i]) {
strcat(buf, delim[!!i]);
strcat(buf, arg_str[i]);
strcat(buf, argv[i]);
}
}
}
return buf;
}
static void
query_hosts(int argc, char **argv)
{
int rc;
struct http_resp resp;
struct json_value *obj;
char *url;
char *reqargv[NARG_MAX];
char const *hval;
char *p;
size_t i;
size_t count[] = { 0, 0 };
memset(reqargv, 0, sizeof(reqargv));
reqargv[ARG_SELECT] = argvjoin(argc, argv);
http_resp_init(&resp);
if (resolve_ip) {
size_t len;
struct json_value *ar;
struct http_buf hbuf = HTTP_BUF_INITIALIZER;
url = urlformat("/match", NULL, reqargv);
free(reqargv[ARG_SELECT]);
http_query("GET", url, std_headers, &resp);
if (resp.code != 200) {
abend("%s", resp.reason);
}
hval = http_resp_get_header(&resp, "content-type");
if (!hval || strcmp(hval, "application/json")) {
abend("missing or unsupported content type");
}
rc = json_parse_string(resp.content, &ar, &p);
if (rc != JSON_E_NOERR)
abend("%s near %s", json_strerror(rc), p);
if (ar->type != json_array)
abend("returned entity has wrong type");
len = json_array_length(ar);
for (i = 0; i < len; i++) {
struct json_value *obj, *hosts, *err;
char const *name;
if (json_array_get(ar, i, &obj))
abend("can't get element %zu", i);
name = ejson_get(obj, "name", json_string)->v.s;
if (json_object_get(obj, "error", &err) == 0) {
error("%s: %s", name, err->v.s);
count[0]++;
} else if (json_object_get(obj, "hosts", &hosts) == 0
&& hosts->type == json_array) {
size_t hlen, j;
hlen = json_array_length(hosts);
if (hlen == 0) {
error("%s: host is not monitored",
name);
count[0]++;
} else {
for (j = 0; j < hlen; j++) {
struct json_value *jv;
if (!json_array_get(hosts, j, &jv)
&& jv->type == json_string) {
if (j)
http_buf_add(&hbuf, ",", 1);
http_buf_add(&hbuf, jv->v.s, strlen(jv->v.s));
}
}
}
} else
abend("malformed element %zu", i);
}
json_value_free(ar);
if (hbuf.len == 0)
exit(EX_NAGIOS_CRITICAL);
http_buf_add(&hbuf, "", 1);
reqargv[ARG_SELECT] = hbuf.base ;
http_resp_reset(&resp);
}
url = urlformat("/host", NULL, reqargv);
free(reqargv[ARG_SELECT]);
http_query("GET", url, std_headers, &resp);
free(url);
switch (resp.code) {
case 200:
break;
case 404:
abend("%s: host is not monitored", argv[0]);
default:
if (argc == 1)
abend("%s: %s", argv[0], resp.reason);
else
abend("%s", resp.reason);
}
hval = http_resp_get_header(&resp, "content-type");
if (!hval || strcmp(hval, "application/json")) {
abend("missing or unsupported content type");
}
rc = json_parse_string(resp.content, &obj, &p);
if (rc != JSON_E_NOERR)
abend("%s near %s", json_strerror(rc), p);
if (obj->type != json_array)
abend("returned entity has wrong type");
for (i = 0; i < json_array_length(obj); i++) {
struct json_value *jv;
if (json_array_get(obj, i, &jv))
abend("can't get value %d: %s", i, strerror(errno));
switch (jv->type) {
case json_object:
count[print_host_status(jv) != EX_NAGIOS_OK]++;
break;
case json_null:
error("%s: host is not monitored", argv[i]);
count[0]++;
break;
default:
abend("returned entity is malformed");
}
}
if (count[1] == 0)
exit(EX_NAGIOS_OK);
if (count[0] == 0)
exit(EX_NAGIOS_CRITICAL);
exit(EX_NAGIOS_WARNING);
}
static void
match_hosts(int argc, char **argv)
{
int rc;
struct http_resp resp;
struct json_value *ar;
char *url;
char const *hval;
char *p;
size_t i, len;
char *reqargv[NARG_MAX];
int count[2] = { 0, 0 };
memset(reqargv, 0, sizeof(reqargv));
reqargv[ARG_SELECT] = argvjoin(argc, argv);
url = urlformat("/match", NULL, reqargv);
free(reqargv[ARG_SELECT]);
http_resp_init(&resp);
http_query("GET", url, std_headers, &resp);
if (resp.code != 200) {
abend("%s", resp.reason);
}
hval = http_resp_get_header(&resp, "content-type");
if (!hval || strcmp(hval, "application/json")) {
abend("missing or unsupported content type");
}
rc = json_parse_string(resp.content, &ar, &p);
if (rc != JSON_E_NOERR)
abend("%s near %s", json_strerror(rc), p);
if (ar->type != json_array)
abend("returned entity has wrong type");
len = json_array_length(ar);
for (i = 0; i < len; i++) {
char const *name;
struct json_value *obj, *hosts, *err;
if (json_array_get(ar, i, &obj))
abend("can't get element %zu", i);
if (obj->type != json_object)
abend("bad type of element %zu", i);
name = ejson_get(obj, "name", json_string)->v.s;
if (json_object_get(obj, "error", &err) == 0) {
error("%s: %s", name, err->v.s);
count[1]++;
} else if (json_object_get(obj, "hosts", &hosts) == 0) {
size_t hlen;
if (hosts->type != json_array)
abend("malformed element %zu", i);
hlen = json_array_length(hosts);
if (hlen == 0) {
error("%s: no matches", name);
count[1]++;
} else {
size_t j;
printf("%s", name);
for (j = 0; j < hlen; j++) {
struct json_value *jv;
if (json_array_get(hosts, j, &jv))
abend("can't get host "
"element %zu", j);
else if (jv->type != json_string)
abend("bad type of host "
"element %zu", j);
else
printf(" %s", jv->v.s);
}
putchar('\n');
count[0]++;
}
} else
abend("malformed element %zu", i);
}
if (count[1] == 0)
exit(EX_NAGIOS_OK);
if (count[0] == 0)
exit(EX_NAGIOS_CRITICAL);
exit(EX_NAGIOS_WARNING);
}
struct nagios_threshold {
double round_trip;
double loss_pct;
};
struct nagios_check_data {
struct nagios_threshold wth;
struct nagios_threshold cth;
};
static void
parse_nagios_threshold(char const *val, struct nagios_threshold *tp)
{
char *p;
double d;
d = strtod(val, &p);
if (d <= 0 || d == HUGE_VAL) {
abend("invalid threshold: %s", val);
}
tp->round_trip = d;
if (*p != ',') {
abend("threshold missing percentage: %s", val);
}
d = strtod(p + 1, &p);
if (d < 0 || (d == 0 && errno) || d > 100 || *p != '%') {
abend("invalid threshold: %s", val);
}
tp->loss_pct = d;
}
static void
print_perfdata(double rta, double pl, struct nagios_check_data *cd)
{
printf("|rta=%.3fms;%.3f;%.3f;%.3f pl=%.2f%%;%.2f;%.2f;%d\n",
rta, cd->wth.round_trip, cd->cth.round_trip, 0.0,
pl, cd->wth.loss_pct, cd->cth.loss_pct, 0);
}
static inline int newstatus(int old, int new) {
return old > new ? old : new;
}
/* Format initial prefix */
static void
nagios_format_prefix(char const *name, int status)
{
char *p;
size_t len;
p = nagios_prefix_format;
while (*p) {
len = strcspn(p, "%");
fwrite(p, len, 1, stdout);
p += len;
if (p[0]) {
if (p[1] == 'h') {
fwrite(name, strlen(name), 1, stdout);
p += 2;
} else {
fputc(*p++, stdout);
if (*p)
fputc(*p++, stdout);
}
}
}
printf(" %s ", status_str[status]);
}
static void
nagios_format_output(int status, double loss, double rta,
struct nagios_check_data *cd, char const *name)
{
nagios_format_prefix(name, status);
if (loss >= 100) {
printf("%s", "- Packet loss = 100%");
rta = cd->cth.round_trip;
} else {
printf("Packet loss = %.0f%%, RTA = %.2f ms", loss, rta);
}
print_perfdata(rta, loss, cd);
}
static int
nagios_check(struct json_value *obj, struct nagios_check_data *cd)
{
struct json_value *jv;
char const *name;
int status;
double rta, loss;
jv = ejson_get(obj, "name", json_string);
name = jv->v.s;
jv = ejson_get(obj, "status", json_string);
if (strcmp(jv->v.s, "init") == 0) {
if (nodata_ok) {
status = EX_NAGIOS_OK;
nagios_format_prefix(name, status);
printf("- %s\n", "waiting for data to arrive");
return status;
}
status = EX_NAGIOS_UNKNOWN;
} else if (strcmp(jv->v.s, "invalid") == 0
|| json_object_get(obj, "stddev", &jv)) {
loss = 100;
rta = cd->cth.round_trip;
status = EX_NAGIOS_CRITICAL;
} else {
status = EX_NAGIOS_OK;
rta = ejson_get(obj, "avg", json_number)->v.n;
if (rta >= cd->cth.round_trip)
status = newstatus(status, EX_NAGIOS_CRITICAL);
if (rta >= cd->wth.round_trip)
status = newstatus(status, EX_NAGIOS_WARNING);
loss = ejson_get(obj, "loss", json_number)->v.n;
if (loss >= cd->cth.loss_pct)
status = newstatus(status, EX_NAGIOS_CRITICAL);
if (loss >= cd->wth.loss_pct)
status = newstatus(status, EX_NAGIOS_WARNING);
}
nagios_format_output(status, loss, rta, cd, name);
return status;
}
static int
nagios_unknown(char const *name, char const *msg)
{
nagios_format_prefix(name, EX_NAGIOS_UNKNOWN);
printf("- %s\n", msg);
return EX_NAGIOS_UNKNOWN;
}
static void
query_host_nagios(char const *host, struct nagios_check_data *chkdata)
{
int rc;
struct http_resp resp;
struct json_value *obj, *jv;
char *url;
char const *hval;
char *p;
url = urlformat("/host", host, NULL);
http_resp_init(&resp);
http_query("GET", url, std_headers, &resp);
free(url);
switch (resp.code) {
case 200:
break;
case 404:
exit(nagios_unknown(host, "host is not monitored"));
default:
exit(nagios_unknown(host, resp.reason));
}
hval = http_resp_get_header(&resp, "content-type");
if (!hval || strcmp(hval, "application/json")) {
exit(nagios_unknown(host,
"missing or unsupported content type"));
}
rc = json_parse_string(resp.content, &obj, &p);
if (rc != JSON_E_NOERR)
abend("%s near %s", json_strerror(rc), p);
switch (obj->type) {
case json_null:
exit(nagios_unknown(host, "host is not monitored"));
case json_array:
break;
default:
exit(nagios_unknown(host, "returned entity has wrong type"));
}
if (json_array_get(obj, 0, &jv)) {
if (errno != ENOENT)
abend("can't get array element: %s", strerror(errno));
exit(nagios_unknown(host, "empty array returned"));
}
switch (jv->type) {
case json_object:
rc = nagios_check(jv, chkdata);
break;
case json_null:
rc = nagios_unknown(host, "host is not monitored");
break;
default:
rc = nagios_unknown(host, "returned entity is malformed");
}
exit(rc);
}
void
usage(void)
{
printf("Usage: %s [-ahrVv] [-f FILE] [HOST...]\n", progname);
printf(" or: %s [-N] [-p PREFIX] -H HOST -c RTA,PCT%% -w RTA,PCT%%\n",
progname);
printf(" or: %s -m HOST\n", progname);
printf("Query ping903 daemon.\n");
printf("\n");
printf("Options:\n\n");
printf(" -a query statistics for all monitored hosts\n");
printf(" -f FILE read configuration from FILE\n");
printf(" -h print this help test\n");
printf(" -R REALM use credentials for this authentication realm\n");
printf(" -r resolve HOST to IP address\n");
printf(" -V print program version and exit\n");
printf(" -v additional verbosity\n");
printf("\nNagios check mode:\n\n");
printf(" -H HOST query statistics for this host\n");
printf(" -c RTA,PCT%% set critical threshold\n");
printf(" -w RTA,PCT%% set warning threshold\n");
printf("\n (all three must be given in this mode)\n\n");
printf(" -N treat initial state as UNKNOWN, "
"instead of OK\n");
printf(" -p PREFIX prefix output with this string, "
"instead of the default \"PING\"\n");
printf("\nMatch mode:\n\n");
printf(" -m check whether HOST is monitored\n");
printf("\n");
printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL);
}
void
version(void)
{
printf("%s (%s) %s\n", progname, PACKAGE_NAME, PACKAGE_VERSION);
printf("%s", COPYLEFT);
}
enum {
MODE_DEFAULT,
MODE_NAGIOS_CHECK,
MODE_MATCH
};
static int mode = MODE_DEFAULT;
static int mode_opt = 0;
static inline void
setmode(int newmode, int opt)
{
if (mode != MODE_DEFAULT && mode != newmode)
abend("option -%d conflicts with -%d", opt, mode_opt);
mode = newmode;
mode_opt = opt;
}
int
main(int argc, char **argv)
{
int c;
char *p;
char const *host = NULL;
int a_opt = 0;
char *c_opt = NULL;
char *w_opt = NULL;
char *realm = NULL;
struct nagios_check_data chkdata;
set_progname(argv[0]);
if (argc == 2) {
if (strcmp(argv[1], "--help") == 0) {
usage();
exit(0);
}
if (strcmp(argv[1], "--version") == 0) {
version();
exit(0);
}
}
while ((c = getopt(argc, argv, "ac:f:H:hNmp:R:rVvw:")) != EOF) {
switch (c) {
case 'a':
a_opt = 1;
break;
case 'c':
c_opt = optarg;
setmode(MODE_NAGIOS_CHECK, c);
break;
case 'f':
config_file = optarg;
break;
case 'H':
host = optarg;
setmode(MODE_NAGIOS_CHECK, c);
break;
case 'h':
usage();
exit(0);
case 'N':
nodata_ok = 0;
break;
case 'm':
setmode(MODE_MATCH, c);
break;
case 'p':
nagios_prefix_format = optarg;
break;
case 'R':
realm = optarg;
break;
case 'r':
resolve_ip = 1;
break;
case 'V':
version();
exit(0);
case 'v':
verbose++;
break;
case 'w':
w_opt = optarg;
setmode(MODE_NAGIOS_CHECK, c);
break;
default:
exit(EX_NAGIOS_UNKNOWN);
}
}
argc -= optind;
argv += optind;
switch (mode) {
case MODE_DEFAULT:
if (argc == 0 && !a_opt)
abend("either give at least one argument "
"or use -a option");
break;
case MODE_NAGIOS_CHECK:
if (argc != 0) {
abend("bad number of arguments");
}
if (!host)
abend("hostname missing; use -H option");
if (!c_opt)
abend("critical threshold missing; use -c option");
parse_nagios_threshold(c_opt, &chkdata.cth);
if (!w_opt)
abend("warning threshold missing; use -w option");
parse_nagios_threshold(w_opt, &chkdata.wth);
break;
case MODE_MATCH:
if (argc == 0)
abend("-m requires one or more command line arguments");
break;
}
p = nodename = read_listen(&service);
if (!nodename || !nodename[0])
nodename = DEFAULT_ADDRESS;
if (!service)
service = DEFAULT_SERVICE;
http_connect(nodename, service);
free(p);
if (realm) {
char *auth_hdr;
if (get_realm_creds(realm, &auth_hdr) == 0) {
std_headers[AUTH_HDR_IDX] = auth_hdr;
} else
error("can't find credentials for realm %s at %s:%s",
realm, nodename, service);
}
switch (mode) {
case MODE_DEFAULT:
query_hosts(argc, argv);
break;
case MODE_NAGIOS_CHECK:
query_host_nagios(host, &chkdata);
break;
case MODE_MATCH:
match_hosts(argc, argv);
}
exit(EX_NAGIOS_UNKNOWN);
}