/*
* libvirt_nss_leases.c: Name Service Switch plugin lease file parser
*
* Copyright (C) 2019 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "libvirt_nss_leases.h"
#include "libvirt_nss.h"
enum {
FIND_LEASES_STATE_START,
FIND_LEASES_STATE_LIST,
FIND_LEASES_STATE_ENTRY,
};
typedef struct {
const char *name;
char **macs;
size_t nmacs;
int state;
unsigned long long now;
int af;
bool *found;
leaseAddress **addrs;
size_t *naddrs;
char *key;
struct {
unsigned long long expiry;
char *ipaddr;
char *macaddr;
char *hostname;
} entry;
} findLeasesParser;
static int
appendAddr(const char *name __attribute__((unused)),
leaseAddress **tmpAddress,
size_t *ntmpAddress,
const char *ipAddr,
long long expirytime,
int af)
{
int family;
size_t i;
struct addrinfo hints = {0};
struct addrinfo *res = NULL;
union {
struct sockaddr sa;
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
} sa;
unsigned char addr[16];
int err;
leaseAddress *newAddr;
DEBUG("IP address: %s", ipAddr);
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
if ((err = getaddrinfo(ipAddr, NULL, &hints, &res)) != 0) {
ERROR("Cannot parse socket address '%s': %s",
ipAddr, gai_strerror(err));
return -1;
}
if (!res) {
ERROR("No resolved address for '%s'", ipAddr);
return -1;
}
family = res->ai_family;
memcpy(&sa, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (family == AF_INET) {
memcpy(addr, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr));
} else if (family == AF_INET6) {
memcpy(addr, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr));
} else {
DEBUG("Skipping unexpected family %d", family);
return 0;
}
if (af != AF_UNSPEC && af != family) {
DEBUG("Skipping address which family is %d, %d requested", family, af);
return 0;
}
for (i = 0; i < *ntmpAddress; i++) {
if (family == AF_INET) {
if ((*tmpAddress)[i].af == AF_INET &&
memcmp((*tmpAddress)[i].addr,
&sa.sin.sin_addr,
sizeof(sa.sin.sin_addr)) == 0) {
DEBUG("IP address already in the list");
return 0;
}
} else {
if ((*tmpAddress)[i].af == AF_INET6 &&
memcmp((*tmpAddress)[i].addr,
&sa.sin6.sin6_addr,
sizeof(sa.sin6.sin6_addr)) == 0) {
DEBUG("IP address already in the list");
return 0;
}
}
}
newAddr = realloc(*tmpAddress, sizeof(*newAddr) * (*ntmpAddress + 1));
if (!newAddr) {
ERROR("Out of memory");
return -1;
}
*tmpAddress = newAddr;
(*tmpAddress)[*ntmpAddress].expirytime = expirytime;
(*tmpAddress)[*ntmpAddress].af = family;
if (family == AF_INET)
memcpy((*tmpAddress)[*ntmpAddress].addr,
&sa.sin.sin_addr,
sizeof(sa.sin.sin_addr));
else
memcpy((*tmpAddress)[*ntmpAddress].addr,
&sa.sin6.sin6_addr,
sizeof(sa.sin6.sin6_addr));
(*ntmpAddress)++;
return 0;
}
static int
findLeasesParserInteger(void *ctx,
long long val)
{
findLeasesParser *parser = ctx;
DEBUG("Parse int state=%d '%lld' (map key '%s')",
parser->state, val, NULLSTR(parser->key));
if (!parser->key)
return 0;
if (parser->state == FIND_LEASES_STATE_ENTRY) {
if (strcmp(parser->key, "expiry-time"))
return 0;
parser->entry.expiry = val;
} else {
return 0;
}
return 1;
}
static int
findLeasesParserString(void *ctx,
const unsigned char *stringVal,
size_t stringLen)
{
findLeasesParser *parser = ctx;
DEBUG("Parse string state=%d '%.*s' (map key '%s')",
parser->state, (int)stringLen, (const char *)stringVal,
NULLSTR(parser->key));
if (!parser->key)
return 0;
if (parser->state == FIND_LEASES_STATE_ENTRY) {
if (!strcmp(parser->key, "ip-address")) {
if (!(parser->entry.ipaddr = strndup((char *)stringVal, stringLen)))
return 0;
} else if (!strcmp(parser->key, "mac-address")) {
if (!(parser->entry.macaddr = strndup((char *)stringVal, stringLen)))
return 0;
} else if (!strcmp(parser->key, "hostname")) {
if (!(parser->entry.hostname = strndup((char *)stringVal, stringLen)))
return 0;
} else {
return 1;
}
} else {
return 0;
}
return 1;
}
static int
findLeasesParserMapKey(void *ctx,
const unsigned char *stringVal,
size_t stringLen)
{
findLeasesParser *parser = ctx;
DEBUG("Parse map key state=%d '%.*s'",
parser->state, (int)stringLen, (const char *)stringVal);
free(parser->key);
if (!(parser->key = strndup((char *)stringVal, stringLen)))
return 0;
return 1;
}
static int
findLeasesParserStartMap(void *ctx)
{
findLeasesParser *parser = ctx;
DEBUG("Parse start map state=%d", parser->state);
if (parser->state != FIND_LEASES_STATE_LIST)
return 0;
free(parser->key);
parser->key = NULL;
parser->state = FIND_LEASES_STATE_ENTRY;
return 1;
}
static int
findLeasesParserEndMap(void *ctx)
{
findLeasesParser *parser = ctx;
size_t i;
bool found = false;
DEBUG("Parse end map state=%d", parser->state);
if (parser->entry.macaddr == NULL)
return 0;
if (parser->state != FIND_LEASES_STATE_ENTRY)
return 0;
if (parser->nmacs) {
DEBUG("Check %zu macs", parser->nmacs);
for (i = 0; i < parser->nmacs && !found; i++) {
DEBUG("Check mac '%s' vs '%s'", parser->macs[i], NULLSTR(parser->entry.macaddr));
if (parser->entry.macaddr && !strcmp(parser->macs[i], parser->entry.macaddr))
found = true;
}
} else {
DEBUG("Check name '%s' vs '%s'", parser->name, NULLSTR(parser->entry.hostname));
if (parser->entry.hostname && !strcmp(parser->name, parser->entry.hostname))
found = true;
}
DEBUG("Found %d", found);
if (parser->entry.expiry != 0 &&
parser->entry.expiry < parser->now) {
DEBUG("Entry expired at %llu vs now %llu",
parser->entry.expiry, parser->now);
found = false;
}
if (!parser->entry.ipaddr)
found = false;
if (found) {
*parser->found = true;
if (appendAddr(parser->name,
parser->addrs, parser->naddrs,
parser->entry.ipaddr,
parser->entry.expiry,
parser->af) < 0)
return 0;
}
free(parser->entry.macaddr);
free(parser->entry.ipaddr);
free(parser->entry.hostname);
parser->entry.expiry = 0;
parser->entry.macaddr = NULL;
parser->entry.ipaddr = NULL;
parser->entry.hostname = NULL;
parser->state = FIND_LEASES_STATE_LIST;
return 1;
}
static int
findLeasesParserStartArray(void *ctx)
{
findLeasesParser *parser = ctx;
DEBUG("Parse start array state=%d", parser->state);
if (parser->state == FIND_LEASES_STATE_START) {
parser->state = FIND_LEASES_STATE_LIST;
} else {
return 0;
}
return 1;
}
static int
findLeasesParserEndArray(void *ctx)
{
findLeasesParser *parser = ctx;
DEBUG("Parse end array state=%d", parser->state);
if (parser->state == FIND_LEASES_STATE_LIST)
parser->state = FIND_LEASES_STATE_START;
else
return 0;
return 1;
}
int
findLeases(const char *file,
const char *name,
char **macs,
size_t nmacs,
int af,
time_t now,
leaseAddress **addrs,
size_t *naddrs,
bool *found)
{
int fd = -1;
int ret = -1;
const yajl_callbacks parserCallbacks = {
NULL, /* null */
NULL, /* bool */
findLeasesParserInteger,
NULL, /* double */
NULL, /* number */
findLeasesParserString,
findLeasesParserStartMap,
findLeasesParserMapKey,
findLeasesParserEndMap,
findLeasesParserStartArray,
findLeasesParserEndArray,
};
findLeasesParser parserState = {
.name = name,
.macs = macs,
.nmacs = nmacs,
.af = af,
.now = now,
.found = found,
.addrs = addrs,
.naddrs = naddrs,
};
yajl_handle parser = NULL;
char line[1024];
ssize_t nreadTotal = 0;
int rv;
if ((fd = open(file, O_RDONLY)) < 0) {
ERROR("Cannot open %s", file);
goto cleanup;
}
parser = yajl_alloc(&parserCallbacks, NULL, &parserState);
if (!parser) {
ERROR("Unable to create JSON parser");
goto cleanup;
}
while (1) {
rv = read(fd, line, sizeof(line));
if (rv < 0)
goto cleanup;
if (rv == 0)
break;
nreadTotal += rv;
if (yajl_parse(parser, (const unsigned char *)line, rv) !=
yajl_status_ok) {
unsigned char *err = yajl_get_error(parser, 1,
(const unsigned char*)line, rv);
ERROR("Parse failed %s", (const char *) err);
yajl_free_error(parser, err);
goto cleanup;
}
}
if (nreadTotal > 0 &&
yajl_complete_parse(parser) != yajl_status_ok) {
ERROR("Parse failed %s",
yajl_get_error(parser, 1, NULL, 0));
goto cleanup;
}
ret = 0;
cleanup:
if (ret != 0) {
free(*addrs);
*addrs = NULL;
*naddrs = 0;
}
if (parser)
yajl_free(parser);
free(parserState.entry.ipaddr);
free(parserState.entry.macaddr);
free(parserState.entry.hostname);
free(parserState.key);
if (fd != -1)
close(fd);
return ret;
}