aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2020-03-07 09:48:38 +0200
committerSergey Poznyakoff <gray@gnu.org>2020-03-07 12:13:39 +0200
commitf6769c969ee779c43ab0df18f7388b9d5a307f96 (patch)
tree8d976ee0f482d33528898ebe89723f6cb02adec4
parent417659b757afc85fc437f3da462c09a4b86c1282 (diff)
downloadping903-f6769c969ee779c43ab0df18f7388b9d5a307f96.tar.gz
ping903-f6769c969ee779c43ab0df18f7388b9d5a307f96.tar.bz2
Simplify entry point structure.
The /ip entry point is gone. The /hosts entry point accepts new query arguments: (1) select=LIST, which introduces a list of IP addresses (hostnames) to query and (2) attr=LIST - a list of attributes to return in each stat object. In both cases, LIST is a comma-separated list of values. The "select" argument can be used together with explicit host (/hosts/IP?select=LIST), which is equivalent to /hosts/?select=IP,LIST. The /match entry point takes the "select" query argument as well. It return array of match objects. Each match object contains at least the following attributes: - name: original host name used in the request - hosts: array of monitored host names or IPs corresponding to that name, (can be empty). If an error occurred (e.g. host name cannot be resolved) the "error" attribute contains the textual description of the error. The ping903q utility now accepts one or more IP addresses as arguments in all modes, except nagios check. The statistics or matches for each IP are returned separately. * README: Document changes. * doc/ping903q.1: Likewise. * src/ping903.c (http_log): Log query arguments, if any. (ept_host_stat): Accept query arguments: select and attr. Return array of stat objects. (ept_ip_stat): Remove. (ept_ip_match): Accept query arguments: select. Rewrite output. (endpoint): Remove /ip * src/ping903.h (get_ipaddr_stat): Remove. (get_host_matches): Change signature. * src/ping903q.c (HTTP_BUF_INITIALIZER): New macro. (http_buf_init, http_buf_free): New functions. (query_host,match_host): Removed. (query_hosts,match_hosts): Removed. (query_host_nagios): New function. * src/pinger.c (get_ipaddr_stat): Remove. (hostping_match): New function. (get_host_matches): Rewrite.
-rw-r--r--.gitignore1
-rw-r--r--README77
-rw-r--r--doc/ping903q.112
-rw-r--r--src/ping903.c366
-rw-r--r--src/ping903.h4
-rw-r--r--src/ping903q.c416
-rw-r--r--src/pinger.c182
7 files changed, 789 insertions, 269 deletions
diff --git a/.gitignore b/.gitignore
index 1beb4b8..6bc59e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
ChangeLog
Makefile
Makefile.in
+TAGS
aclocal.m4
autom4te.cache/
compile
diff --git a/README b/README
index 2e9486f..8f8e672 100644
--- a/README
+++ b/README
@@ -138,7 +138,12 @@ formatted in a ping(8)-like manner:
10 packets transmitted, 10 received, 0% packet loss, time 9414ms
rtt min/avg/max/mdev = 41.212/41.265/41.374/0.046 ms
-You can check the current status of all hosts by running
+In both cases, any number of IP addresses can be given. E.g. the
+following command will returns statistics for two IPs:
+
+ $ ping903q -v 203.0.113.1 203.0.113.5
+
+To check the current status of all hosts, run
$ ping903q
@@ -211,19 +216,21 @@ following attributes is returned:
ATTR is one of the attributes discussed above. Returned is the value
of that attribute.
-** /host/NAME
+** /host/[NAME]?[select=HOSTLIST][attr=ATTRLIST]
-NAME is the IP address or hostname. The server will look up this
-string in the list of configured hosts and, if found, return the
-statistics information for that host. Note that NAME is treated as a
-character string and must coincide exactly with the IP or hostname as
-it was supplied in configuration. In particular, if a host was
-specified by its symbolic DNS name in the configuration, exactly that
-name must be used in URL to obtain statistics for that host. If you
-wish to use IP, see the "/ip" or "/match" endpoints, discussed below.
+NAME is the IP address or hostname and HOSTLIST is a comma-separated
+list of such names. If NAME is supplied, it is added at the beginning
+of HOSTLIST and the hosts in HOSTLIST are looked up in the list of
+hosts being monitored. Note that NAME is treated as a character
+string and must coincide exactly with the IP or hostname as it was
+supplied in configuration. In particular, if a host was specified by
+its symbolic DNS name in the configuration, exactly that name must be
+used in URL to obtain statistics for that host. If you wish to use
+IP, see the "/match" endpoint, discussed below.
-On success, a JSON object is returned. The following keys are defined
-in that object:
+The return value is a JSON array whose elements correspond to the
+entries in HOSTLIST (after addition of NAME, if given). Each element
+is an object with the following attributes:
- "name": string
@@ -306,6 +313,7 @@ Example of the returned JSON for a reachable host:
{
"alive":true,
"avg":25.85150,
+ "dup":0.00000,
"loss":0.00000,
"name":"203.0.113.1",
"recv":10.00000,
@@ -327,27 +335,50 @@ Example of the returned JSON for an unreachable host:
"xmit-timestamp":1581666176.01373
}
+The "attr" request argument allows you to specify attributes in the
+"stat" object that you are interested in. It is a comma-separated
+list of attribute names. If given, each returned "stat" object will
+contain only elements from that list.
+
** /host
Return statistics for all monitored hosts. The result is returned as
-an array of JSON objects described above.
+an array of JSON "stat" objects (described above).
This is an experimental endpoint. Be careful with it, as it may cause
considerable strain on the server.
-** /ip/ADDR
+** /match/[HOST]?[select=HOSTLIST]
+
+Return monitored names that correspond to HOST or HOSTLIST. HOSTLIST
+is a comma-separated list of host names or IPv4 addresses, HOST is a
+single such address. Both HOST and HOSTLIST can be supplied:
+/match/HOST?select=HOSTLIST is equivalent to /match?select=HOST,HOSTLIST.
+
+Each name in the resulting HOSTLIST is resolved and monitored hosts
+with IPs matching any of its IPv4 addresses are returned as an
+array of JSON objects. Each element in the array describes a single
+host from the HOSTLIST and has the following attributes:
+
+- "name": string
+
+Original name from the HOSTLIST to which this object corresponds.
+
+- "hosts": array of strings
+
+Array of monitored names corresponding to this "name". Each name
+from the array can be used as argument in a GET request to the
+"/host" endpoint.
+
+This array is empty if none of the IP addresses of this "name" are
+monitored by the server.
-Request statistics about a particular IP address. The response is the
-same as for </host/NAME>. Use this API if hostnames are used in your
-ip-list and you need to request statistics using an IP as opposed to
-the hostname.
+If any error occurred during processing, the following attribute is
+added as well:
-** /match/NAME-OR-IP
+- "error": string
-Return host names that correspond to NAME-OR-IP (a JSON array of
-strings). If no matches found, empty array is returned. Multiple
-entries can be returned if NAME-OR-IP is a hostname that has multiple
-DNS A records, several of which are registered in the ip-list.
+Textual description of the error.
** /config
diff --git a/doc/ping903q.1 b/doc/ping903q.1
index 84bd850..c724043 100644
--- a/doc/ping903q.1
+++ b/doc/ping903q.1
@@ -13,7 +13,7 @@
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with Ping903. If not, see <http://www.gnu.org/licenses/>.
-.TH PING903Q 1 "March 5, 2020" "PING903Q" "User Commands"
+.TH PING903Q 1 "March 6, 2020" "PING903Q" "User Commands"
.SH NAME
ping903q \- ping903 query tool
.SH SYNOPSIS
@@ -21,7 +21,7 @@ ping903q \- ping903 query tool
[\fB\-hVv\fR]\
[\fB\-f \fIFILE\fR]\
[\fB\-R \fIREALM\fR]\
- [\fIIP\fR]
+ [\fIIP\fR...]
.PP
\fBping903q\fR\
[\fB\-N\fR]\
@@ -37,10 +37,10 @@ ping903q \- ping903 query tool
[\fB\-R \fIREALM\fR]\
\fB\-m\fR
.SH DESCRIPTION
-Queries monitoring statistics from the \fBping903\fR daemon. When
-used with a single argument (\fIIP\fR), displays information about
-this particular IP address. Used without arguments, displays
-statistics about all IP addresses monitored by the running daemon.
+Queries monitoring statistics from the \fBping903\fR daemon. Used
+with argumentst (\fIIP\fR...), displays information about the supplied
+IP addresses. Used without arguments, displays statistics about all
+IP addresses monitored by the running daemon.
.PP
By default a one-line summary is displayed, which informs about the IP
and its current status ("alive" vs. "not alive"). Additional
diff --git a/src/ping903.c b/src/ping903.c
index 5a0994f..b4ea906 100644
--- a/src/ping903.c
+++ b/src/ping903.c
@@ -15,6 +15,7 @@
along with Ping903. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
+#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
@@ -37,6 +38,65 @@ int httpd_access_log = 0;
int httpd_log_verbose = 0;
unsigned int httpd_backlog_size = SOMAXCONN;
+
+struct strentry {
+ struct strentry *next;
+ char str[1];
+};
+
+struct strlist {
+ struct strentry *head, *tail;
+};
+
+static int
+strlist_add(struct strlist *slist, const char *str, size_t len)
+{
+ struct strentry *sent = malloc(sizeof(*sent) + len);
+ if (!sent)
+ return -1;
+
+ memcpy(sent->str, str, len);
+ sent->str[len] = 0;
+ sent->next = NULL;
+
+ if (slist->tail)
+ slist->tail->next = sent;
+ else
+ slist->head = sent;
+ slist->tail = sent;
+ return 0;
+}
+
+static void
+strlist_free(struct strlist *slist)
+{
+ struct strentry *sent = slist->head;
+ while (sent) {
+ struct strentry *next = sent->next;
+ free(sent);
+ sent = next;
+ }
+ slist->head = slist->tail = NULL;
+}
+
+static int
+strlist_from_csv(struct strlist *slist, const char *s)
+{
+ while (*s) {
+ size_t n = strcspn(s, ",");
+ if (n > 0) {
+ if (strlist_add(slist, s, n)) {
+ strlist_free(slist);
+ return -1;
+ }
+ }
+ s += n;
+ if (*s)
+ s++;
+ }
+ return 0;
+}
+
static int
open_node(char const *node, char const *serv, struct sockaddr **saddr)
{
@@ -132,6 +192,21 @@ extern int p903_httpd_acl(void *cls, const struct sockaddr *addr,
# define p903_httpd_acl NULL
#endif
+
+static int
+reassemble_get_args(void *cls, enum MHD_ValueKind kind,
+ const char *key, const char *value)
+{
+ struct strlist *slist = cls;
+ strlist_add(slist, "&", 1);
+ strlist_add(slist, key, strlen(key));
+ if (value) {
+ strlist_add(slist, "=", 1);
+ strlist_add(slist, value, strlen(value));
+ }
+ return MHD_YES;
+}
+
static void
http_log(struct MHD_Connection *connection,
char const *method, char const *url,
@@ -142,7 +217,9 @@ http_log(struct MHD_Connection *connection,
time_t t;
struct tm *tm;
char tbuf[30];
-
+ struct strlist slist = { NULL, NULL };
+ char *reqs = NULL;
+
if (!httpd_access_log)
return;
if (!httpd_log_verbose)
@@ -162,10 +239,32 @@ http_log(struct MHD_Connection *connection,
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,
+ MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND,
+ &reassemble_get_args, &slist);
+ if (slist.head) {
+ size_t len = 0;
+ struct strentry *sent;
+ char *p;
+
+ for (sent = slist.head; sent; sent = sent->next)
+ len += strlen(sent->str);
+ p = malloc(len + 1);
+ if (p) {
+ reqs = p;
+ *p++ = '?';
+ for (sent = slist.head->next; sent; sent = sent->next)
+ p += strlen(strcpy(p, sent->str));
+ *p = 0;
+ }
+ strlist_free(&slist);
+ }
+
+ info("%s %s - - %s \"%s %s%s\" \"%.1024s\" %3d \"%s\" \"%s\"",
+ host, ipstr, tbuf, method, url, reqs ? reqs : "",
+ str ? str : "", status,
referer ? referer : "",
user_agent ? user_agent : "");
+ free(reqs);
free(ipstr);
}
@@ -493,6 +592,116 @@ ept_ip_post(struct MHD_Connection *conn,
err_text, err_pos);
}
+struct host_stat_args_closure {
+ struct strlist hostlist;
+ struct strlist attrlist;
+ int err;
+};
+
+static int
+collect_host_stat_args(void *cls, enum MHD_ValueKind kind,
+ const char *key, const char *value)
+{
+ struct host_stat_args_closure *clos = cls;
+
+ if (strcmp(key, "select") == 0 && value) {
+ if (strlist_from_csv(&clos->hostlist, value)) {
+ clos->err = 1;
+ return MHD_NO;
+ }
+ } else if (strcmp(key, "attr") == 0 && value) {
+ if (strlist_from_csv(&clos->attrlist, value)) {
+ clos->err = 1;
+ return MHD_NO;
+ }
+ } else {
+ clos->err = 1;
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+static void
+host_stat_args_free(struct host_stat_args_closure *clos)
+{
+ strlist_free(&clos->hostlist);
+ strlist_free(&clos->attrlist);
+}
+
+static int
+get_selected_host_stat(struct strlist const *slist,
+ struct json_value **ret_arr)
+{
+ struct json_value *arr, *jv;
+ struct strentry *sent;
+
+ if (!(arr = json_new_array()))
+ return GET_NOMEM;
+ for (sent = slist->head; sent; sent = sent->next) {
+ switch (get_hostname_stat(sent->str, &jv)) {
+ case GET_OK:
+ break;
+
+ case GET_NOENT:
+ jv = json_new_null();
+ break;
+
+ case GET_NOMEM:
+ jv = NULL;
+ break;
+ }
+ if (jv) {
+ if (json_array_append(arr, jv) == 0)
+ continue;
+ json_value_free(jv);
+ }
+ /* Error: */
+ json_value_free(arr);
+ return -1;
+ }
+ *ret_arr = arr;
+ return 0;
+}
+
+static int
+attrlist_flt(char const *kw, struct json_value *val, void *data)
+{
+ struct strlist *slist = data;
+ struct strentry *sent;
+
+ for (sent = slist->head; sent; sent = sent->next) {
+ if (strcmp(kw, sent->str) == 0)
+ return 0;
+ }
+ return 1;
+}
+
+static int
+attrlist_extract(struct json_value *val, struct strlist *slist)
+{
+ if (val && slist->head) {
+ switch (val->type) {
+ case json_array: {
+ size_t i;
+
+ for (i = 0; i < val->v.a->oc; i++) {
+ if (attrlist_extract(val->v.a->ov[i], slist))
+ return -1;
+ }
+ break;
+ }
+ case json_object:
+ return json_object_filter(val, attrlist_flt, slist);
+ break;
+
+ default:
+ /* Leave scalar values unchanged */
+ break;
+ }
+ }
+ return 0;
+}
+
static int
ept_host_stat(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
@@ -500,26 +709,41 @@ ept_host_stat(struct MHD_Connection *conn,
{
struct json_value *val;
int rc, ret;
-
+ struct host_stat_args_closure clos;
+
while (*suffix == '/')
suffix++;
-
- if (suffix[0] == 0) {
- rc = get_all_host_stat(&val);
+
+ memset(&clos, 0, sizeof(clos));
+ if (*suffix && strlist_add(&clos.hostlist, suffix, strlen(suffix)))
+ return http_response(conn, method, url,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ MHD_get_connection_values(conn, MHD_GET_ARGUMENT_KIND,
+ &collect_host_stat_args, &clos);
+ if (clos.err) {
+ host_stat_args_free(&clos);
+ return http_response(conn, method, url, MHD_HTTP_BAD_REQUEST);
+ }
+
+ if (clos.hostlist.head) {
+ rc = get_selected_host_stat(&clos.hostlist, &val);
} else {
- rc = get_hostname_stat(suffix, &val);
+ rc = get_all_host_stat(&val);
}
+
+ if (attrlist_extract(val, &clos.attrlist)) {
+ json_value_free(val);
+ rc = GET_NOMEM;
+ }
+
+ host_stat_args_free(&clos);
+
switch (rc) {
case GET_OK:
+ case GET_NOENT:
ret = httpd_json_response(conn, url, method, MHD_HTTP_OK, val);
break;
- case GET_NOENT:
- ret = http_response_detail(conn, method, url,
- MHD_HTTP_NOT_FOUND,
- "No such host", 0);
- break;
-
default:
ret = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
@@ -529,82 +753,63 @@ ept_host_stat(struct MHD_Connection *conn,
}
static int
-ept_ip_stat(struct MHD_Connection *conn,
- const char *url, const char *method, const char *suffix,
- struct json_value *unused)
-{
- struct json_value *val;
- int rc;
-
- while (*suffix == '/')
- suffix++;
-
- if (suffix[0] == 0) {
- return http_response(conn, method, url, MHD_HTTP_FORBIDDEN);
- } else {
- int ret;
- struct addrinfo hints, *res;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- hints.ai_protocol = IPPROTO_TCP;
- rc = getaddrinfo(suffix, NULL, &hints, &res);
- if (rc)
- return http_response(conn, method, url,
- MHD_HTTP_NOT_FOUND);
- rc = get_ipaddr_stat(res->ai_addr, res->ai_addrlen, &val);
- switch (rc) {
- case GET_OK:
- ret = httpd_json_response(conn, url, method,
- MHD_HTTP_OK, val);
- break;
-
- case GET_NOENT:
- ret = http_response_detail(conn, method, url,
- MHD_HTTP_NOT_FOUND,
- "No host matches IP", 0);
- break;
-
- default:
- ret = http_response(conn, method, url,
- MHD_HTTP_INTERNAL_SERVER_ERROR);
- break;
-
- }
- freeaddrinfo(res);
- return ret;
- }
-}
-
-static int
ept_ip_match(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
- struct json_value *val;
- int rc;
- int ret;
- struct addrinfo hints, *res;
+ struct json_value *ar;
+ struct host_stat_args_closure clos;
+ struct strentry *ent;
while (*suffix == '/')
suffix++;
- if (suffix[0] == 0)
+ memset(&clos, 0, sizeof(clos));
+ if (*suffix && strlist_add(&clos.hostlist, suffix, strlen(suffix)))
+ return http_response(conn, method, url,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ MHD_get_connection_values(conn, MHD_GET_ARGUMENT_KIND,
+ &collect_host_stat_args, &clos);
+ if (clos.err || clos.attrlist.head) {
+ host_stat_args_free(&clos);
+ return http_response(conn, method, url, MHD_HTTP_BAD_REQUEST);
+ }
+ if (!clos.hostlist.head)
return http_response(conn, method, url, MHD_HTTP_FORBIDDEN);
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- hints.ai_protocol = IPPROTO_TCP;
- rc = getaddrinfo(suffix, NULL, &hints, &res);
- if (rc)
- return http_response(conn, method, url, MHD_HTTP_NOT_FOUND);
- rc = get_host_matches(&res, &val);
- if (rc)
- ret = http_response(conn, method, url,
- MHD_HTTP_INTERNAL_SERVER_ERROR);
- else
- ret = httpd_json_response(conn, url, method, MHD_HTTP_OK, val);
- freeaddrinfo(res);
- return ret;
+ if (!(ar = json_new_array()))
+ goto err;
+ for (ent = clos.hostlist.head; ent; ent = ent->next) {
+ struct json_value *obj, *jv;
+
+ if (!(obj = json_new_object()))
+ goto err;
+ if (json_array_append(ar, obj)) {
+ json_value_free(obj);
+ goto err;
+ }
+ if (!(jv = json_new_string(ent->str))
+ || json_object_set(obj, "name", jv)) {
+ json_value_free(jv);
+ goto err;
+ }
+ if (!(jv = json_new_array())
+ || json_object_set(obj, "hosts", jv)) {
+ json_value_free(jv);
+ goto err;
+ }
+ }
+
+ host_stat_args_free(&clos);
+
+ if (get_host_matches(ar) == 0)
+ return httpd_json_response(conn, url, method, MHD_HTTP_OK, ar);
+
+err:
+ host_stat_args_free(&clos);
+ json_value_free(ar);
+ return http_response(conn, method, url,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
}
enum auth_type {
@@ -939,7 +1144,6 @@ static struct endpoint endpoint[] = {
{ "/config/ip-list", EPT_PREFIX, MHD_HTTP_METHOD_DELETE, ept_ip_delete },
{ "/config", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_config },
{ "/host", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_host_stat },
- { "/ip", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_ip_stat },
{ "/match", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_ip_match },
{ NULL }
};
diff --git a/src/ping903.h b/src/ping903.h
index 3e88c17..f0065f3 100644
--- a/src/ping903.h
+++ b/src/ping903.h
@@ -169,11 +169,9 @@ enum {
functions. */
};
int get_hostname_stat(char const *name, struct json_value **retval);
-int get_ipaddr_stat(struct sockaddr *sa, int salen, struct json_value **retval);
int get_all_hosts(struct json_value **);
int get_all_host_stat(struct json_value **);
-struct addrinfo;
-int get_host_matches(struct addrinfo **aip, struct json_value **retval);
+int get_host_matches(struct json_value *);
int file_read_ip_list(FILE *fp, char const *fname);
diff --git a/src/ping903q.c b/src/ping903q.c
index 64c17bb..8b51367 100644
--- a/src/ping903q.c
+++ b/src/ping903q.c
@@ -186,6 +186,8 @@ struct http_buf {
size_t len;
};
+#define HTTP_BUF_INITIALIZER { NULL, 0, 0 }
+
static char const *
http_resp_get_header(struct http_resp *resp, char const *name)
{
@@ -206,6 +208,29 @@ http_resp_get_header(struct http_resp *resp, char const *name)
}
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;
@@ -314,10 +339,9 @@ static void
http_recv(struct http_resp *resp)
{
enum input_state { is_initial, is_headers, is_content } state = is_initial;
- struct http_buf hbuf;
+ struct http_buf hbuf = HTTP_BUF_INITIALIZER;
char const *hval;
- memset(&hbuf, 0, sizeof(hbuf));
http_resp_reset(resp);
while (state != is_content) {
http_readline(&hbuf);
@@ -353,6 +377,7 @@ http_recv(struct http_resp *resp)
}
}
}
+ http_buf_free(&hbuf);
hval = http_resp_get_header(resp, "content-length");
if (hval) {
@@ -711,7 +736,7 @@ ejson_get(struct json_value *obj, char *name, int type)
}
static int
-print_host_status(struct json_value *obj, void *unused)
+print_host_status(struct json_value *obj)
{
struct json_value *jv;
int alive;
@@ -755,102 +780,238 @@ print_host_status(struct json_value *obj, void *unused)
return alive ? EX_NAGIOS_OK : EX_NAGIOS_CRITICAL;
}
-static int
-unknown_host_status(char const *host, char const *msg)
+static char *
+argvjoin(int argc, char **argv)
{
- abend("%s: %s", host, msg);
- return EX_NAGIOS_UNKNOWN;
+ int i;
+ size_t len = argc - 1;
+ char *ret, *p, *q;
+
+ 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 *
-resolve_host(char const *name)
+urlformat(char const *baseurl, char const *param, char **argv)
{
- struct addrinfo hints, *res;
- char hbuf[NI_MAXHOST];
- int rc;
+ size_t len;
+ char *buf;
+ int i;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- hints.ai_protocol = IPPROTO_TCP;
- rc = getaddrinfo(name, NULL, &hints, &res);
- if (rc)
- abend("%s: %s", name, gai_strerror(rc));
- if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf),
- NULL, 0, NI_NUMERICHOST)) {
- /* shouldn't happen */
- abend("%s: can't resolve hostname", name);
+ 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;
+ }
}
- freeaddrinfo(res);
- return estrdup(hbuf);
+
+ 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_host(char const *name,
- int (*report)(struct json_value *, void *),
- int (*unknown)(char const*, char const*),
- void *report_data)
+query_hosts(int argc, char **argv)
{
int rc;
struct http_resp resp;
struct json_value *obj;
- char url[1024];
+ char *url;
+ char *reqargv[NARG_MAX];
char const *hval;
char *p;
- char *ipstr = resolve_host(name);
- ssize_t n;
-
+ 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) {
- n = snprintf(url, sizeof(url), "/ip/%s", ipstr);
- } else {
- n = snprintf(url, sizeof(url), "/host/%s", name);
+ 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);
}
- if (n < 0 || n == sizeof(url)) {
- abend("bad host name or IP");
- }
- http_resp_init(&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:
- exit(unknown(name, "host is not monitored"));
+ abend("%s: host is not monitored", argv[0]);
default:
- exit(unknown(name, resp.reason));
+ 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")) {
- exit(unknown(name, "missing or unsupported content type"));
+ 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_null)
- exit(unknown(name, "host is not monitored"));
- if (obj->type != json_object)
- exit(unknown(name, "returned entity has wrong type"));
- exit(report(obj, report_data));
+
+ 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_host(char const *name)
+match_hosts(int argc, char **argv)
{
int rc;
struct http_resp resp;
- struct json_value *obj;
- char url[1024];
+ struct json_value *ar;
+ char *url;
char const *hval;
char *p;
- ssize_t n;
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]);
- n = snprintf(url, sizeof(url), "/match/%s", name);
- if (n == -1 || n == sizeof(url)) {
- abend("url buffer overflow");
- }
http_resp_init(&resp);
http_query("GET", url, std_headers, &resp);
if (resp.code != 200) {
@@ -862,29 +1023,59 @@ match_host(char const *name)
abend("missing or unsupported content type");
}
- rc = json_parse_string(resp.content, &obj, &p);
+ rc = json_parse_string(resp.content, &ar, &p);
if (rc != JSON_E_NOERR)
abend("%s near %s", json_strerror(rc), p);
- if (obj->type != json_array)
+ if (ar->type != json_array)
abend("returned entity has wrong type");
- len = json_array_length(obj);
- if (json_array_length(obj) == 0) {
- if (verbose)
- error("no matching hosts found");
- exit(EX_NAGIOS_CRITICAL);
- }
-
+ len = json_array_length(ar);
for (i = 0; i < len; i++) {
- struct json_value *jv;
- if (json_array_get(obj, i, &jv)) {
- abend("can't get element %lu", (unsigned long) i);
- }
- if (jv->type != json_string)
- abend("bad type of element %lu", (unsigned long) i);
- printf("%s\n", jv->v.s);
+ 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);
}
- exit(EX_NAGIOS_OK);
+ if (count[1] == 0)
+ exit(EX_NAGIOS_OK);
+ if (count[0] == 0)
+ exit(EX_NAGIOS_CRITICAL);
+ exit(EX_NAGIOS_WARNING);
}
static void
@@ -924,7 +1115,7 @@ query_all(void)
abend("can't get element %lu", (unsigned long) i);
}
if (jv->type == json_object)
- count[print_host_status(jv, NULL) != EX_NAGIOS_OK]++;
+ count[print_host_status(jv) != EX_NAGIOS_OK]++;
}
if (count[1] == 0)
exit(EX_NAGIOS_OK);
@@ -1017,9 +1208,8 @@ nagios_format_output(int status, double loss, double rta,
}
static int
-nagios_check(struct json_value *obj, void *data)
+nagios_check(struct json_value *obj, struct nagios_check_data *cd)
{
- struct nagios_check_data *cd = data;
struct json_value *jv;
char const *name;
int status;
@@ -1068,11 +1258,73 @@ nagios_unknown(char const *name, char const *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);
+