/* This file is part of varnish-mib -*- c -*- Copyright (C) 2014-2019 Sergey Poznyakoff Varnish-mib 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. Varnish-mib 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 varnish-mib. If not, see . */ #include "varnish_mib.h" #include #include #include #include #include #include #include #include struct vcli_sockaddr { socklen_t len; struct sockaddr addr[1]; }; #define ISSPACE(c) ((c)==' '||(c)=='\t'||(c)=='\n') vcli_sockaddr_t vcli_parse_sockaddr(char const *arg) { char *node; size_t n; int rc; struct addrinfo *res; char const *p; vcli_sockaddr_t sa; n = strcspn(arg, ": \t"); node = malloc(n + 1); if (!node) { snmp_log(LOG_ERR, "out of memory\n"); return NULL; } memcpy(node, arg, n); node[n] = 0; p = arg + n; if (*p == ':') p++; else while (*p && ISSPACE(*p)) p++; if (!p) p = "6082"; rc = getaddrinfo(node, p, NULL, &res); free(node); if (rc) { snmp_log(LOG_ERR, "can't resolve %s\n", arg); return NULL; } sa = malloc(sizeof(*sa) - sizeof(sa->addr) + res->ai_addrlen); if (!sa) { snmp_log(LOG_ERR, "out of memory\n"); return NULL; } sa->len = res->ai_addrlen; memcpy(sa->addr, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); return sa; } vcli_sockaddr_t vcli_sockaddr_dup(vcli_sockaddr_t src) { vcli_sockaddr_t dst; dst = malloc(sizeof(*dst) - sizeof(dst->addr) + src->len); memcpy(dst, src, src->len); return dst; } #define VCLI_INIT_ALLOC 16 static int vcli_alloc(struct vcli_conn *conn, size_t size) { char *p; if (size < conn->bufmax) return 0; p = realloc(conn->base, size); if (!p) { snmp_log(LOG_ERR, "out of memory\n"); return -1; } conn->base = p; conn->bufmax = size; return 0; } static int vcli_extra_size(struct vcli_conn *conn, size_t incr) { if (incr + conn->bufsize > conn->bufmax) { size_t size; if (conn->bufmax == 0) size = incr > VCLI_INIT_ALLOC ? incr : VCLI_INIT_ALLOC; else { for (size = conn->bufmax; size < conn->bufsize + incr; size *= 2) if (size <= conn->bufmax) { snmp_log(LOG_ERR, "out of memory\n"); return -1; } } return vcli_alloc(conn, size); } return 0; } static int vcli_read(struct vcli_conn *conn, size_t size) { fd_set rd; time_t start; struct timeval tv; long ttl; int ret = 0; int rc; ++size; rc = vcli_alloc(conn, size + 1); if (rc) return SNMP_ERR_GENERR; conn->bufsize = 0; time(&start); while (size) { FD_ZERO(&rd); FD_SET(conn->fd, &rd); ttl = vcli_timeout - (time(NULL) - start); if (ttl <= 0) { snmp_log(LOG_ERR, "timed out reading from varnish\n"); ret = -1; break; } tv.tv_sec = ttl; tv.tv_usec = 0; rc = select(conn->fd + 1, &rd, NULL, NULL, &tv); if (rc < 0) { if (errno == EINTR || errno == EAGAIN) continue; snmp_log(LOG_ERR, "select: %s\n", strerror(errno)); ret = -1; break; } if (FD_ISSET(conn->fd, &rd)) { int n; if (ioctl(conn->fd, FIONREAD, &n) < 0) { snmp_log(LOG_ERR, "ioctl: %s\n", strerror(errno)); ret = -1; break; } if (n > size) n = size; rc = read(conn->fd, conn->base + conn->bufsize, n); if (rc > 0) { conn->bufsize += rc; size -= rc; } else if (rc == 0 || errno == EINTR || errno == EAGAIN) { continue; } else { snmp_log(LOG_ERR, "read: %s\n", strerror(errno)); ret = -1; break; } } } if (ret == 0) { if (conn->bufsize == 0) ret = -1; conn->base[conn->bufsize] = 0; DEBUGMSGTL(("vcli:transcript", "<base)); } return ret; } static int vcli_getline(struct vcli_conn *conn) { fd_set rd; time_t start; struct timeval tv; long ttl; int ret = 0; int rc; conn->bufsize = 0; time(&start); while (1) { FD_ZERO(&rd); FD_SET(conn->fd, &rd); ttl = vcli_timeout - (time(NULL) - start); if (ttl <= 0) { snmp_log(LOG_ERR, "timed out reading from varnish\n"); ret = -1; break; } tv.tv_sec = ttl; tv.tv_usec = 0; rc = select(conn->fd + 1, &rd, NULL, NULL, &tv); if (rc < 0) { if (errno == EINTR || errno == EAGAIN) continue; snmp_log(LOG_ERR, "select: %s\n", strerror(errno)); ret = -1; break; } if (FD_ISSET(conn->fd, &rd)) { char c; rc = read(conn->fd, &c, 1); if (rc == 1) { if (vcli_extra_size(conn, 1)) { ret = -1; break; } conn->base[conn->bufsize++] = c; if (c == '\n') break; } else if (rc == 0 || errno == EINTR || errno == EAGAIN) { continue; } else { snmp_log(LOG_ERR, "read: %s\n", strerror(errno)); ret = -1; break; } } } if (ret == 0) { if (conn->bufsize == 0) ret = -1; else if (conn->base[conn->bufsize-1] == '\n') { conn->bufsize--; if (conn->base[conn->bufsize-1] == '\r') conn->bufsize--; } conn->base[conn->bufsize] = 0; } return ret; } int vcli_write(struct vcli_conn *conn) { size_t size; DEBUGMSGTL(("vcli:transcript", ">>varnish: %s\n", conn->base)); for (size = 0; size < conn->bufsize; ) { int n = write(conn->fd, conn->base + size, conn->bufsize - size); if (n < 0) { snmp_log(LOG_ERR, "write error: %s\n", strerror(errno)); return -1; } size += n; } return 0; } int vcli_read_response(struct vcli_conn *conn) { char *p; unsigned long n; if (vcli_getline(conn)) { vcli_disconnect(conn); return SNMP_ERR_GENERR; } n = strtoul(conn->base, &p, 10); conn->resp = n; if (!ISSPACE(*p)) { snmp_log(LOG_ERR, "unrecognized response from Varnish: %s\n", conn->base); return SNMP_ERR_GENERR; } while (*p && ISSPACE(*p)) ++p; n = strtoul(p, &p, 10); if (n > 0) return vcli_read(conn, n); return 0; } int vcli_vasprintf(struct vcli_conn *conn, const char *fmt, va_list ap) { int rc = 0; rc = vcli_alloc(conn, VCLI_INIT_ALLOC); if (rc) return -1; for (;;) { va_list aq; ssize_t n; va_copy(aq, ap); n = vsnprintf(conn->base, conn->bufmax, fmt, aq); va_end(aq); if (n < 0 || n >= conn->bufmax || !memchr(conn->base, '\0', n + 1)) { size_t newlen = conn->bufmax * 2; if (newlen < conn->bufmax) { snmp_log(LOG_ERR, "out of memory\n"); rc = -1; break; } rc = vcli_alloc(conn, newlen); if (rc) break; } else { conn->bufsize = n; break; } } return rc; } int vcli_asprintf(struct vcli_conn *conn, const char *fmt, ...) { int rc; va_list ap; va_start(ap, fmt); rc = vcli_vasprintf(conn, fmt, ap); va_end(ap); return rc; } static int open_socket(vcli_sockaddr_t sa) { int fd; struct timeval start, connect_tv; fd = socket(sa->addr->sa_family, SOCK_STREAM, 0); if (fd == -1) { snmp_log(LOG_ERR, "socket: %s\n", strerror(errno)); return -1; } connect_tv.tv_sec = vcli_timeout; connect_tv.tv_usec = 0; gettimeofday(&start, NULL); for (;;) { int rc = connect(fd, sa->addr, sa->len); if (rc == 0) break; else if (errno == ECONNREFUSED) { struct timeval tv, now; fd_set rset, wset, xset; gettimeofday(&now, NULL); timersub(&now, &start, &tv); if (timercmp(&tv, &connect_tv, < )) { FD_ZERO(&rset); FD_SET(fd, &rset); FD_ZERO(&wset); FD_SET(fd, &wset); FD_ZERO(&xset); FD_SET(fd, &xset); select(fd + 1, &rset, &wset, &xset, &tv); continue; } } else { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; int ec = errno; if (getnameinfo(sa->addr, sa->len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) snmp_log(LOG_ERR, "cannot connect to %s:%s: %s\n", hbuf, sbuf, strerror(ec)); else snmp_log(LOG_ERR, "cannot connect to varnish: %s\n", strerror(ec)); close(fd); return -1; } } return fd; } void vcli_disconnect(struct vcli_conn *conn) { close(conn->fd); free(conn->base); free(conn->secret); memset(conn, 0, sizeof(*conn)); } static int vcli_handshake(struct vcli_conn *conn) { if (vcli_read_response(conn)) return 1; if (conn->resp == CLIS_AUTH) { char buf[CLI_AUTH_RESPONSE_LEN + 1]; char *p = strchr(conn->base, '\n'); if (!p) { snmp_log(LOG_ERR, "unrecognized response from Varnish: %s\n", conn->base); return SNMP_ERR_GENERR; } *p = 0; if (varnish_auth_response(conn->secret, conn->base, buf)) return 1; if (vcli_asprintf(conn, "auth %s\n", buf) || vcli_write(conn)) return 1; if (vcli_read_response(conn)) return 1; } if (conn->resp != CLIS_OK) { snmp_log(LOG_ERR, "Varnish connection rejected: %u %s\n", conn->resp, conn->base); return 1; } if (vcli_asprintf(conn, "ping\n") || vcli_write(conn)) return 1; if (vcli_read_response(conn)) return 1; if (conn->resp != CLIS_OK || strstr(conn->base, "PONG") == NULL) { snmp_log(LOG_ERR, "expected PONG, but got %s\n", conn->base); return 1; } return 0; } static int parse_connection(struct vsm *vsm, vcli_sockaddr_t *addr, char **secret) { if (vcli_sockaddr) { DEBUGMSGTL(("vcli", "using configured sockaddr\n")); *addr = vcli_sockaddr_dup(vcli_sockaddr); } else { char *T_arg, *p; T_arg = VSM_Dup(vsm, "Arg", "-T"); if (!T_arg) { snmp_log(LOG_ERR, "no -T arg in shared memory\n"); return SNMP_ERR_GENERR; } p = T_arg + strlen(T_arg) - 1; if (*p == '\n') *p = 0; DEBUGMSGTL(("vcli", "-T '%s'\n", T_arg)); *addr = vcli_parse_sockaddr(T_arg); free(T_arg); } if (!*addr) return SNMP_ERR_GENERR; if (vcli_secret) { DEBUGMSGTL(("vcli", "using configured secret\n")); *secret = strdup(vcli_secret); if (!*secret) { snmp_log(LOG_ERR, "out of memory"); return SNMP_ERR_GENERR; } } else { char *S_arg = VSM_Dup(vsm, "Arg", "-S"); if (!S_arg) { snmp_log(LOG_ERR, "no -S arg in shared memory\n"); return SNMP_ERR_GENERR; } DEBUGMSGTL(("vcli", "-S '%s'\n", S_arg)); *secret = S_arg; } return 0; } int vcli_connect(struct vsm *vsm, struct vcli_conn *conn) { vcli_sockaddr_t addr = NULL; char *secret = NULL; int rc; rc = parse_connection(vsm, &addr, &secret); if (rc == 0) { memset(conn, 0, sizeof(*conn)); conn->fd = open_socket(addr); if (conn->fd == -1) rc = SNMP_ERR_GENERR; else { conn->secret = secret; secret = NULL; if (vcli_handshake(conn)) { vcli_disconnect(conn); rc = SNMP_ERR_GENERR; } } } free(addr); free(secret); return rc; }