/* This file is part of varnish-mib -*- c -*-
Copyright (C) 2014-2015 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
#define ISSPACE(c) ((c)==' '||(c)=='\t'||(c)=='\n')
static unsigned vcli_timeout = 5;
void
varnish_vcli_timeout_parser(const char *token, char *line)
{
varnish_mib_timeout_parser(token, line, &vcli_timeout);
}
#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(("varnish_mib:vcli", "<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(("varnish_mib:vcli", ">>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(struct sockaddr_in *sa, const char *connstr)
{
int fd = socket(sa->sin_family, SOCK_STREAM, 0);
struct timeval start, connect_tv;
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, sizeof(*sa));
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;
}
}
snmp_log(LOG_ERR, "cannot connect to %s: %s\n",
connstr, strerror(errno));
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;
}
int
vcli_connect(struct vsm *vsm, struct vcli_conn *conn)
{
struct sockaddr_in vcli_sa;
char *portstr, *p;
unsigned long n;
short pn;
struct hostent *hp;
char *T_arg = NULL, *S_arg = NULL;
memset(conn, 0, sizeof(*conn));
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;
}
S_arg = VSM_Dup(vsm, "Arg", "-S");
if (!S_arg) {
free(T_arg);
snmp_log(LOG_ERR, "no -S arg in shared memory\n");
return SNMP_ERR_GENERR;
}
DEBUGMSGTL(("varnish_mib:vcli", "-T '%s'\n", (char*) T_arg));
for (portstr = T_arg; !ISSPACE(*portstr); portstr++)
;
if (!*portstr) {
snmp_log(LOG_ERR, "unrecognized -T arg: %s\n", T_arg);
free(T_arg);
free(S_arg);
return SNMP_ERR_GENERR;
}
for (*portstr++ = 0; ISSPACE(*portstr); portstr++)
;
n = pn = strtoul(portstr, &p, 0);
if (n != pn || (*p && !ISSPACE(*p))) {
snmp_log(LOG_ERR, "unrecognized -T arg: %s\n", T_arg);
free(T_arg);
free(S_arg);
return SNMP_ERR_GENERR;
}
hp = gethostbyname(T_arg);
if (!hp) {
snmp_log(LOG_ERR, "unknown host name %s\n", T_arg);
free(T_arg);
free(S_arg);
return SNMP_ERR_GENERR;
}
vcli_sa.sin_family = hp->h_addrtype;
if (vcli_sa.sin_family != AF_INET) {
snmp_log(LOG_ERR, "unknown host name %s\n", T_arg);
free(T_arg);
free(S_arg);
return SNMP_ERR_GENERR;
}
memmove(&vcli_sa.sin_addr, hp->h_addr, 4);
vcli_sa.sin_port = htons(pn);
conn->fd = open_socket(&vcli_sa, T_arg);
free(T_arg);
if (conn->fd == -1) {
free(S_arg);
return SNMP_ERR_GENERR;
}
DEBUGMSGTL(("varnish_mib:vcli", "-S '%s'\n", S_arg));
conn->secret = S_arg;
if (vcli_handshake(conn)) {
vcli_disconnect(conn);
return SNMP_ERR_GENERR;
}
return SNMP_ERR_NOERROR;
}