/* * Copyright (c) 2018 Florian Obser * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WAIT_CONNECT 1 #define WAIT_CONNECT_SUCCEED 2 #define WAIT_BANNER 3 #define WRITE_LIST 4 #define WAIT_LIST 5 #define FETCH_ELEMENT 6 #define WAIT_FETCHED_DATA 7 #define BANNER_PREFIX "# munin node at " struct munin { SLIST_ENTRY(munin) entry; struct event ev; struct kreq *r; struct sockaddr_storage ss; int s; int state; char hbuf[NI_MAXHOST]; char *data; ssize_t write_size; char *hostname; char *list; char *current_metric; }; SLIST_HEAD(munin_head, munin); struct munin_head munins; void write_handler(int, short, void *); void read_handler(int, short, void *); void export(struct munin *); void khttp_log(struct kreq *, char *, ...); void khttp_log(struct kreq *r, char *fmt, ...) { va_list ap; int ret; char *str; va_start(ap, fmt); ret = vasprintf(&str, fmt, ap); va_end(ap); if (ret >= 0) { khttp_puts(r, "# "); khttp_puts(r, str); khttp_putc(r, '\n'); } free(str); } void export(struct munin *munin) { char *d, *line, *k, *v, *str; d = munin->data; while((line = strsep(&d, "\n")) != NULL) { if (*line == '\0') continue; /* two adjacent delimiters */ if (*line == '#') { asprintf(&str, "# skipping %s metric comment: %s\n", munin->current_metric, line); khttp_puts(munin->r, str); free(str); continue; } if (line[0] == '.' && line[1] == '\0') break; /* munin EOF marker */ if ((v = strchr(line, ' ')) == NULL) { asprintf(&str, "# skipping malformed line: %s\n", line); khttp_puts(munin->r, str); free(str); continue; } else *v++ = '\0'; if ((k = strstr(line, ".value")) != NULL) *k = '\0'; k = line; asprintf(&str, "# TYPE %s_%s counter\n%s_%s{hostname=\"%s\"} %s\n", munin->current_metric, k, munin->current_metric, k, munin->hostname, v); khttp_puts(munin->r, str); free(str); } } void read_handler(int fd, short events, void *arg) { struct munin *munin, *i, *t; ssize_t n; char buf[4096], *tmp, *str; munin = (struct munin*)arg; n = read(fd, buf, sizeof(buf) - 1); switch (n) { case -1: switch (errno) { case EINTR: case EAGAIN: return; default: asprintf(&str, "# read: %s\n", strerror(errno)); khttp_puts(munin->r, str); free(str); goto fail; } break; case 0: asprintf(&str, "# read: closed connection\n"); khttp_puts(munin->r, str); free(str); goto fail; default: break; } buf[n] = '\0'; if (munin->data == NULL) munin->data = strdup(buf); else { tmp = munin->data; asprintf(&munin->data, "%s%s", munin->data, buf); free(tmp); } switch (munin->state) { case WAIT_CONNECT_SUCCEED: munin->state = WAIT_BANNER; /* connection succeeded, canceling others */ SLIST_FOREACH_SAFE(i, &munins, entry, t) { if (i != munin) { event_del(&i->ev); close(i->s); SLIST_REMOVE(&munins, i, munin, entry); free(i); } } /* fall through */ case WAIT_BANNER: if ((tmp = strchr(munin->data, '\n')) != NULL) { *tmp = '\0'; if (strncmp(BANNER_PREFIX, munin->data, sizeof(BANNER_PREFIX) - 1) == 0) { munin->hostname = strdup(munin->data + (sizeof(BANNER_PREFIX) - 1)); asprintf(&str, "#hostname: %s\n", munin->hostname); khttp_puts(munin->r, str); free(str); } free(munin->data); munin->data = strdup("list\n"); munin->write_size = sizeof("list\n") - 1; munin->state = WRITE_LIST; event_del(&munin->ev); event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, write_handler, munin); event_add(&munin->ev, NULL); } break; case WAIT_LIST: /* XXX ignore comment lines */ if ((tmp = strchr(munin->data, '\n')) != NULL) { *tmp = '\0'; munin->list = strdup(munin->data); munin->state = FETCH_ELEMENT; event_del(&munin->ev); event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, write_handler, munin); event_add(&munin->ev, NULL); } break; case WAIT_FETCHED_DATA: if ((munin->data[0] == '.' && munin->data[1] == '\n') || strstr(munin->data, "\n.\n") != NULL) { export(munin); free(munin->data); free(munin->current_metric); munin->state = FETCH_ELEMENT; event_del(&munin->ev); event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, write_handler, munin); event_add(&munin->ev, NULL); } break; default: asprintf(&str, "# %s: unknown state: %d\n", __func__, munin->state); khttp_puts(munin->r, str); free(str); exit(1); break; } return; fail: event_del(&munin->ev); close(fd); SLIST_REMOVE(&munins, munin, munin, entry); free(munin); } void write_handler(int fd, short events, void *arg) { struct munin *munin; int error = 0; socklen_t len = sizeof(error); char *tmp, *str; munin = (struct munin*)arg; switch (munin->state) { case WAIT_CONNECT: if (getsockopt(munin->s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { asprintf(&str, "# getsockopt: %s\n", strerror(errno)); khttp_puts(munin->r, str); free(str); goto fail; } if (error != 0) { errno = error; asprintf(&str, "# connect: %s\n", strerror(errno)); khttp_puts(munin->r, str); free(str); goto fail; } event_del(&munin->ev); munin->state = WAIT_CONNECT_SUCCEED; event_set(&munin->ev, munin->s, EV_READ | EV_PERSIST, read_handler, munin); event_add(&munin->ev, NULL); break; case WRITE_LIST: if (write(fd, munin->data, munin->write_size) != munin->write_size) { asprintf(&str, "# write: %s\n", strerror(errno)); khttp_puts(munin->r, str); free(str); goto fail; } free(munin->data); munin->data = NULL; munin->write_size = -1; event_del(&munin->ev); munin->state = WAIT_LIST; event_set(&munin->ev, munin->s, EV_READ | EV_PERSIST, read_handler, munin); event_add(&munin->ev, NULL); break; case FETCH_ELEMENT: tmp = strsep(&munin->list, " \t"); if (tmp == NULL) { free(munin->list); goto fail; // XXX this is success, we are done! } else if (tmp == '\0') break; /* two adjacent delimiters */ munin->current_metric = strdup(tmp); munin->write_size = asprintf(&munin->data, "fetch %s\n", munin->current_metric); if (write(fd, munin->data, munin->write_size) != munin->write_size) { asprintf(&str, "# write: %s\n", strerror(errno)); khttp_puts(munin->r, str); free(str); goto fail; } free(munin->data); munin->data = NULL; munin->write_size = -1; event_del(&munin->ev); munin->state = WAIT_FETCHED_DATA; event_set(&munin->ev, munin->s, EV_READ | EV_PERSIST, read_handler, munin); event_add(&munin->ev, NULL); break; default: asprintf(&str, "# %s: unknown state: %d\n", __func__, munin->state); khttp_puts(munin->r, str); free(str); exit(1); break; } return; fail: event_del(&munin->ev); close(fd); SLIST_REMOVE(&munins, munin, munin, entry); free(munin); } int main(int argc, char **argv) { struct kreq r; struct addrinfo hints, *res, *res0; struct munin *munin; int error; char *host = NULL; if (pledge("stdio dns inet proc", "") == -1) err(1, "pledge"); if (KCGI_OK != khttp_parse(&r, NULL, 0, NULL, 0, 0)) return(EXIT_FAILURE); if (pledge("stdio dns inet", "") == -1) err(1, "pledge"); if (r.fullpath != NULL) { if (r.fullpath[0] == '/') host = r.fullpath + 1; else host = r.fullpath; } else { khttp_head(&r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_500]); khttp_head(&r, kresps[KRESP_CONTENT_TYPE], "%s", "text/plain"); khttp_body(&r); khttp_puts(&r, "# missing host\n"); khttp_free(&r); return (EXIT_SUCCESS); } SLIST_INIT(&munins); event_init(); khttp_head(&r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); khttp_head(&r, kresps[KRESP_CONTENT_TYPE], "%s", "text/plain"); khttp_body(&r); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(host, "4949", &hints, &res0); if (error) { khttp_log(&r, "getaddrinfo: %s", gai_strerror(error)); goto out; } for (res = res0; res; res = res->ai_next) { if ((munin = calloc(1, sizeof(*munin))) == NULL) { khttp_log(&r, "malloc: %s", strerror(errno)); goto out; } error = getnameinfo(res->ai_addr, res->ai_addr->sa_len, munin->hbuf, sizeof(munin->hbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV); if (error) { khttp_log(&r, "could not get numeric hostname: %s", gai_strerror(error)); goto out; } memcpy(&munin->ss, res->ai_addr, res->ai_addrlen); if ((munin->s = socket(res->ai_family, res->ai_socktype | SOCK_NONBLOCK, res->ai_protocol)) == -1) { khttp_log(&r, "socket: %s", strerror(errno)); goto out; } munin->r = &r; munin->state = WAIT_CONNECT; event_set(&munin->ev, munin->s, EV_WRITE | EV_PERSIST, write_handler, munin); event_add(&munin->ev, NULL); SLIST_INSERT_HEAD(&munins, munin, entry); } freeaddrinfo(res0); if (pledge("stdio inet", "") == -1) err(1, "pledge"); SLIST_FOREACH(munin, &munins, entry) { if (connect(munin->s, (struct sockaddr *)&munin->ss, munin->ss.ss_len) == -1) if (errno != EINPROGRESS) { khttp_log(&r, "connect: %s", strerror(errno)); goto out; } } if (pledge("stdio", "") == -1) err(1, "pledge"); event_dispatch(); khttp_puts(&r, "# OK\n"); out: khttp_free(&r); return (EXIT_SUCCESS); }