/*
* 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 "libvirt_nss_leases.h"
#include "libvirt_nss.h"
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;
}
/**
* findLeaseInJSON
*
* @jobj: the json object containing the leases
* @name: the requested hostname (optional if a MAC address is present)
* @macs: the array of MAC addresses we're matching (optional if we have a hostname)
* @nmacs: the size of the MAC array
* @af: the requested address family
* @now: current time (to eliminate expired leases)
* @addrs: the returned matching addresses
* @naddrs: size of the returned array
* @found: whether a match was found
*
* Returns 0 even if nothing was found
* -1 on error
*/
static int
findLeaseInJSON(json_object *jobj,
const char *name,
char **macs,
size_t nmacs,
int af,
time_t now,
leaseAddress **addrs,
size_t *naddrs,
bool *found)
{
size_t i;
int len;
if (!json_object_is_type(jobj, json_type_array)) {
ERROR("parsed JSON does not contain the leases array");
return -1;
}
len = json_object_array_length(jobj);
for (i = 0; i < len; i++) {
json_object *lease = NULL;
json_object *expiry = NULL;
json_object *ipobj = NULL;
unsigned long long expiryTime;
const char *ipaddr;
lease = json_object_array_get_idx(jobj, i);
if (macs) {
const char *macAddr;
bool match = false;
json_object *val;
size_t j;
val = json_object_object_get(lease, "mac-address");
if (!val)
continue;
macAddr = json_object_get_string(val);
if (!macAddr)
continue;
for (j = 0; j < nmacs; j++) {
if (strcmp(macs[j], macAddr) == 0) {
match = true;
break;
}
}
if (!match)
continue;
} else {
const char *leaseName;
json_object *val;
val = json_object_object_get(lease, "hostname");
if (!val)
continue;
leaseName = json_object_get_string(val);
if (!leaseName)
continue;
if (strcasecmp(leaseName, name) != 0)
continue;
}
expiry = json_object_object_get(lease, "expiry-time");
if (!expiry) {
ERROR("Missing expiry time for %s", name);
return -1;
}
expiryTime = json_object_get_uint64(expiry);
if (expiryTime > 0 && expiryTime < now) {
DEBUG("Skipping expired lease for %s", name);
continue;
}
ipobj = json_object_object_get(lease, "ip-address");
if (!ipobj) {
DEBUG("Missing IP address for %s", name);
continue;
}
ipaddr = json_object_get_string(ipobj);
DEBUG("Found record for %s", name);
*found = true;
if (appendAddr(name,
addrs, naddrs,
ipaddr,
expiryTime,
af) < 0)
return -1;
}
return 0;
}
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;
json_object *jobj = NULL;
json_tokener *tok = NULL;
enum json_tokener_error jerr;
int jsonflags = JSON_TOKENER_STRICT | JSON_TOKENER_VALIDATE_UTF8;
char line[1024];
ssize_t nreadTotal = 0;
int rv;
if ((fd = open(file, O_RDONLY)) < 0) {
ERROR("Cannot open %s", file);
goto cleanup;
}
tok = json_tokener_new();
if (!tok) {
ERROR("failed to create JSON tokener");
goto cleanup;
}
json_tokener_set_flags(tok, jsonflags);
do {
rv = read(fd, line, sizeof(line));
if (rv < 0)
goto cleanup;
if (rv == 0)
break;
nreadTotal += rv;
jobj = json_tokener_parse_ex(tok, line, rv);
jerr = json_tokener_get_error(tok);
} while (jerr == json_tokener_continue);
if (jerr == json_tokener_continue) {
ERROR("Cannot parse %s: incomplete json found", file);
goto cleanup;
}
if (nreadTotal > 0 && jerr != json_tokener_success) {
ERROR("Cannot parse %s: %s", file, json_tokener_error_desc(jerr));
goto cleanup;
}
ret = findLeaseInJSON(jobj, name, macs, nmacs, af, now,
addrs, naddrs, found);
cleanup:
json_object_put(jobj);
if (tok)
json_tokener_free(tok);
if (ret != 0) {
free(*addrs);
*addrs = NULL;
*naddrs = 0;
}
if (fd != -1)
close(fd);
return ret;
}