/*-
* Copyright (c) 2000-2005 MAEKAWA Masahide <maekawa@cvsync.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <limits.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include "compat_arpa_inet.h"
#include "compat_stdbool.h"
#include "compat_stdint.h"
#include "compat_stdio.h"
#include "compat_inttypes.h"
#include "compat_limits.h"
#include "cvsync.h"
#include "list.h"
#include "logmsg.h"
#include "network.h"
#include "token.h"
#include "defs.h"
static const struct token_keyword access_keywords[] = {
{ "allow", 5, ACL_ALLOW },
{ "always", 6, ACL_ALWAYS },
{ "deny", 4, ACL_DENY },
{ "permit", 6, ACL_ALLOW },
{ "reject", 6, ACL_DENY },
{ NULL, 0, ACL_NOMATCH },
};
void access_open(const char *);
void access_close(struct access_control_args *);
struct aclent *access_match(struct access_control_args *, int, const char *);
struct access_control_args *access_parse(FILE *);
bool access_parse_allow(struct token *, struct aclent *);
bool access_parse_always(struct token *, struct aclent *);
bool access_parse_deny(struct token *, struct aclent *);
bool access_parse_address(char *, const char *, size_t, struct aclent *);
bool access_parse_hostname(char *, const char *, struct aclent *);
bool access_parse_number(char *, const char *, size_t *);
bool access_match_address(struct aclent *, int, const char *);
bool access_match_hostname(struct aclent *, const char *);
bool access_match_ipv4addr(struct aclent *, const void *);
bool access_match_ipv6addr(struct aclent *, const void *);
bool access_set_ipv4addr(struct aclent *, const void *, size_t);
bool access_set_ipv6addr(struct aclent *, const void *, size_t);
static struct server_args **acl;
static struct access_control_args *acl_lists;
static struct list *acl_high;
static char acl_name[PATH_MAX + CVSYNC_NAME_MAX + 1];
static size_t acl_size, acl_actives = 0;
static time_t acl_mtime = 0;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool
access_init(size_t sz)
{
#if !defined(NO_INITSTATE)
static char acl_random_state[256];
#endif /* !defined(NO_INITSTATE) */
struct timeval tv;
if ((acl = malloc(sz * sizeof(*acl))) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
return (false);
}
(void)memset(acl, 0, sz * sizeof(*acl));
if ((acl_high = list_init()) == NULL) {
free(acl);
return (false);
}
(void)gettimeofday(&tv, NULL);
#if !defined(NO_INITSTATE)
initstate((unsigned long)tv.tv_usec, acl_random_state,
sizeof(acl_random_state));
#else /* !defined(NO_INITSTATE) */
srandom((unsigned long)tv.tv_usec);
#endif /* !defined(NO_INITSTATE) */
acl_name[0] = '\0';
acl_size = sz;
return (true);
}
void
access_destroy(void)
{
struct listent *lep;
struct server_args *sa;
size_t i;
pthread_mutex_lock(&mtx);
if (cvsync_isterminated()) {
for (i = 0 ; i < acl_size ; i++) {
if ((sa = acl[i]) == NULL)
continue;
sock_close(sa->sa_socket);
}
for (lep = acl_high->l_head ;
lep != NULL ;
lep = lep->le_next) {
sa = lep->le_elm;
sock_close(sa->sa_socket);
}
}
acl_size = 0;
while (!list_isempty(acl_high) || (acl_actives != 0))
pthread_cond_wait(&cond, &mtx);
pthread_mutex_unlock(&mtx);
list_destroy(acl_high);
free(acl);
access_close(acl_lists);
}
struct server_args *
access_authorize(int sock, struct config *cf)
{
union {
uint32_t v32;
uint8_t v8[4];
} _v;
struct access_control_args aca;
struct aclent *aclp;
struct server_args *sa, *sa_active;
size_t n, i;
int wn;
if (cvsync_isterminated())
return (NULL);
if ((sa = malloc(sizeof(*sa))) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
return (NULL);
}
sa->sa_error = CVSYNC_NO_ERROR;
if (cvsync_isinterrupted()) {
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
if (!sock_getpeeraddr(sock, &sa->sa_family, sa->sa_addr,
sizeof(sa->sa_addr))) {
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
_v.v32 = (uint32_t)random();
wn = snprintf(sa->sa_hostinfo, sizeof(sa->sa_hostinfo),
"[%s] (%02x%02x%02x%02x)", sa->sa_addr,
_v.v8[0], _v.v8[1], _v.v8[2], _v.v8[3]);
if ((wn <= 0) || ((size_t)wn >= sizeof(sa->sa_hostinfo))) {
logmsg_err("ACL: %s", strerror(EINVAL));
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
access_open(cf->cf_access_name);
if ((aclp = access_match(acl_lists, sa->sa_family,
sa->sa_addr)) != NULL) {
sa->sa_status = aclp->acl_status;
} else {
sa->sa_status = ACL_ALLOW;
}
switch (sa->sa_status) {
case ACL_ALLOW:
if (pthread_mutex_lock(&mtx) != 0) {
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
if (acl_actives >= acl_size) {
pthread_mutex_unlock(&mtx);
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_LIMITED;
return (sa);
}
if ((aclp != NULL) && (aclp->acl_max > 0)) {
aca.aca_patterns = aclp;
aca.aca_size = 1;
n = 0;
for (i = 0 ; i < acl_size ; i++) {
if ((sa_active = acl[i]) == NULL)
continue;
if (access_match(&aca, sa_active->sa_family,
sa_active->sa_addr) != NULL) {
if (++n <= aclp->acl_max)
continue;
pthread_mutex_unlock(&mtx);
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_LIMITED;
return (sa);
}
}
}
for (i = 0 ; i < acl_size ; i++) {
if (acl[i] == NULL)
break;
}
sa->sa_id = i;
acl[i] = sa;
acl_actives++;
if (pthread_mutex_unlock(&mtx) != 0) {
acl[i] = NULL;
acl_actives--;
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
break;
case ACL_ALWAYS:
if (pthread_mutex_lock(&mtx) != 0) {
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
if (!list_insert_tail(acl_high, sa)) {
pthread_mutex_unlock(&mtx);
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
if (pthread_mutex_unlock(&mtx) != 0) {
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
break;
case ACL_DENY:
sa->sa_error = CVSYNC_ERROR_DENIED;
break;
default:
sa->sa_status = ACL_DENY;
sa->sa_error = CVSYNC_ERROR_UNAVAIL;
return (sa);
}
sa->sa_socket = sock;
sa->sa_config = cf;
config_acquire(cf);
logmsg("%s Connected (status=%d)", sa->sa_hostinfo, sa->sa_status);
time(&sa->sa_tick);
return (sa);
}
void
access_done(struct server_args *sa)
{
struct config *cf = sa->sa_config;
struct listent *lep;
pthread_mutex_lock(&mtx);
switch (sa->sa_status) {
case ACL_ALLOW:
acl[sa->sa_id] = NULL;
acl_actives--;
break;
case ACL_ALWAYS:
for (lep = acl_high->l_head ;
lep != NULL ;
lep = lep->le_next) {
if (lep->le_elm == sa)
break;
}
if (lep == NULL)
logmsg_err("ACL: not found: %s", sa->sa_hostinfo);
if (!list_remove(acl_high, lep))
logmsg_err("ACL: fail to remove: %s", sa->sa_hostinfo);
break;
case ACL_DENY:
/* Nothing to do. */
break;
default:
break;
}
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);
if (sa->sa_status == ACL_DENY)
logmsg("%s Connection denied", sa->sa_hostinfo);
else
logmsg("%s Connection closed", sa->sa_hostinfo);
sock_close(sa->sa_socket);
free(sa);
config_revoke(cf);
}
void
access_open(const char *fname)
{
struct access_control_args *aca;
struct stat st;
FILE *fp;
int fd;
bool force_load = false;
if (strlen(fname) == 0) {
access_close(acl_lists);
acl_name[0] = '\0';
acl_lists = NULL;
return;
}
if ((strlen(acl_name) == 0) || (strcmp(acl_name, fname) != 0))
force_load = true;
if ((fd = open(fname, O_RDONLY, 0)) == -1) {
if (errno != ENOENT) {
logmsg_err("ACL: %s: %s", fname, strerror(errno));
}
return;
}
if (fstat(fd, &st) == -1) {
logmsg_err("ACL: %s: %s", fname, strerror(errno));
(void)close(fd);
return;
}
if (!force_load && (st.st_mtime == acl_mtime)) {
(void)close(fd);
return;
}
acl_mtime = st.st_mtime;
if (st.st_size == 0) {
(void)close(fd);
aca = NULL;
goto done;
}
if ((fp = fdopen(fd, "r")) == NULL) {
logmsg_err("ACL: %s: %s", fname, strerror(errno));
(void)close(fd);
return;
}
if ((aca = access_parse(fp)) == NULL) {
logmsg_err("ACL: %s: failed to load", fname);
(void)fclose(fp);
return;
}
if (fclose(fp) == EOF) {
logmsg_err("ACL: %s: %s", fname, strerror(errno));
access_close(aca);
return;
}
done:
snprintf(acl_name, sizeof(acl_name), "%s", fname);
access_close(acl_lists);
acl_lists = aca;
logmsg_verbose("ACL: reloaded");
}
void
access_close(struct access_control_args *aca)
{
size_t i;
if (aca == NULL)
return;
if (aca->aca_patterns != NULL) {
for (i = 0 ; i < aca->aca_size ; i++) {
if (aca->aca_patterns[i].acl_addr != NULL)
free(aca->aca_patterns[i].acl_addr);
if (aca->aca_patterns[i].acl_mask != NULL)
free(aca->aca_patterns[i].acl_mask);
}
free(aca->aca_patterns);
}
free(aca);
}
struct aclent *
access_match(struct access_control_args *aca, int af, const char *addr)
{
struct aclent *aclp = NULL;
char host[CVSYNC_MAXHOST];
size_t i;
bool resolved = false;
if ((aca == NULL) || (aca->aca_size == 0))
return (NULL);
for (i = 0 ; i < aca->aca_size ; i++) {
aclp = &aca->aca_patterns[i];
if (aclp->acl_family == AF_UNSPEC) {
if (!resolved) {
sock_resolv_addr(af, addr, host, sizeof(host));
resolved = true;
}
if (strlen(host) == 0)
continue;
if (access_match_hostname(aclp, host))
break;
} else {
if (access_match_address(aclp, af, addr))
break;
}
}
if (i == aca->aca_size)
aclp = NULL;
return (aclp);
}
struct access_control_args *
access_parse(FILE *fp)
{
const struct token_keyword *key;
struct access_control_args *aca;
struct aclent *aclp;
struct token tk;
size_t max = 0;
lineno = 1;
if ((aca = malloc(sizeof(*aca))) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
return (NULL);
}
aca->aca_patterns = NULL;
aca->aca_size = 0;
for (;;) {
if (!token_skip_whitespace(fp)) {
if (feof(fp) == 0) {
access_close(aca);
return (NULL);
}
break;
}
if (aca->aca_size == max) {
struct aclent *newptr;
size_t old = max, new = old + 4;
if ((newptr = malloc(new * sizeof(*newptr))) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
access_close(aca);
return (NULL);
}
if (aca->aca_patterns != NULL) {
(void)memcpy(newptr, aca->aca_patterns,
old * sizeof(*newptr));
free(aca->aca_patterns);
}
aca->aca_patterns = newptr;
max = new;
}
aclp = &aca->aca_patterns[aca->aca_size++];
if ((key = token_get_keyword(fp, access_keywords)) == NULL) {
access_close(aca);
return (NULL);
}
aclp->acl_status = key->type;
if (!token_get_string(fp, &tk)) {
access_close(aca);
return (NULL);
}
switch (aclp->acl_status) {
case ACL_ALLOW:
if (!access_parse_allow(&tk, aclp)) {
logmsg_err("ACL: line %u: %s: invalid "
"address/hostname", lineno,
tk.token);
access_close(aca);
return (NULL);
}
break;
case ACL_ALWAYS:
if (!access_parse_always(&tk, aclp)) {
logmsg_err("ACL: line %u: %s: invalid "
"address/hostname", lineno,
tk.token);
access_close(aca);
return (NULL);
}
break;
case ACL_DENY:
if (!access_parse_deny(&tk, aclp)) {
logmsg_err("ACL: line %u: %s: invalid "
"address/hostname", lineno,
tk.token);
access_close(aca);
return (NULL);
}
break;
default:
access_close(aca);
return (NULL);
}
}
if (aca->aca_size == 0) {
free(aca->aca_patterns);
aca->aca_patterns = NULL;
}
return (aca);
}
bool
access_parse_allow(struct token *tk, struct aclent *aclp)
{
char *sp = tk->token, *bp = sp + tk->length, *sep;
size_t n;
if ((sep = memchr(sp, ',', (size_t)(bp - sp))) != NULL) {
if (sep + 1 >= bp)
return (false);
if (!access_parse_number(sep + 1, bp, &aclp->acl_max))
return (false);
bp = sep;
}
if ((sep = memchr(sp, '/', (size_t)(bp - sp))) != NULL) {
if (sep + 1 >= bp)
return (false);
if (!access_parse_number(sep + 1, bp, &n))
return (false);
return (access_parse_address(sp, sep, n, aclp));
}
if (!access_parse_address(sp, bp, (size_t)-1, aclp))
return (access_parse_hostname(sp, bp, aclp));
return (true);
}
bool
access_parse_always(struct token *tk, struct aclent *aclp)
{
char *sp = tk->token, *bp = sp + tk->length, *sep;
size_t n;
if ((sep = memchr(sp, '/', (size_t)(bp - sp))) != NULL) {
if (sep + 1 >= bp)
return (false);
if (!access_parse_number(sep + 1, bp, &n))
return (false);
return (access_parse_address(sp, sep, n, aclp));
}
if (!access_parse_address(sp, bp, (size_t)-1, aclp))
return (access_parse_hostname(sp, bp, aclp));
return (true);
}
bool
access_parse_deny(struct token *tk, struct aclent *aclp)
{
char *sp = tk->token, *bp = sp + tk->length, *sep;
size_t n;
if ((sep = memchr(sp, '/', (size_t)(bp - sp))) != NULL) {
if (sep + 1 >= bp)
return (false);
if (!access_parse_number(sep + 1, bp, &n))
return (false);
return (access_parse_address(sp, sep, n, aclp));
}
if (!access_parse_address(sp, bp, (size_t)-1, aclp))
return (access_parse_hostname(sp, bp, aclp));
return (true);
}
bool
access_parse_address(char *sp, const char *bp, size_t n, struct aclent *aclp)
{
char addr[CVSYNC_MAXHOST], binaddr[CVSYNC_MAXADDRLEN];
size_t addrlen;
if ((addrlen = bp - sp) >= sizeof(addr))
return (false);
(void)memcpy(addr, sp, addrlen);
addr[addrlen] = '\0';
if (inet_pton(AF_INET, addr, binaddr) == 1) {
aclp->acl_family = AF_INET;
return (access_set_ipv4addr(aclp, binaddr, n));
}
#if defined(AF_INET6)
if (inet_pton(AF_INET6, addr, binaddr) == 1) {
aclp->acl_family = AF_INET6;
return (access_set_ipv6addr(aclp, binaddr, n));
}
#endif /* defined(AF_INET6) */
return (false);
}
bool
access_parse_hostname(char *sp, const char *bp, struct aclent *aclp)
{
if (isdigit((int)(sp[bp - sp - 1])) ||
(memchr(sp, ':', (size_t)(bp - sp)) != NULL)) {
return (false);
}
aclp->acl_family = AF_UNSPEC;
aclp->acl_addrlen = bp - sp;
if ((aclp->acl_addr = malloc(aclp->acl_addrlen + 1)) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
return (false);
}
(void)memcpy(aclp->acl_addr, sp, aclp->acl_addrlen);
((char *)aclp->acl_addr)[aclp->acl_addrlen] = '\0';
aclp->acl_mask = NULL;
return (true);
}
bool
access_parse_number(char *sp, const char *bp, size_t *n)
{
char s[5], *ep;
size_t len;
unsigned long v;
if ((len = bp - sp) >= sizeof(s)) {
logmsg_err("ACL: %.*s: restricted to 1000", len, sp);
return (false);
}
(void)memcpy(s, sp, len);
s[len] = '\0';
errno = 0;
v = strtoul(s, &ep, 0);
if ((ep == NULL) || (*ep != '\0') || ((v == 0) && (errno == EINVAL)) ||
((v == ULONG_MAX) && (errno == ERANGE))) {
logmsg_err("ACL: %s: %s", s, strerror(EINVAL));
return (false);
}
if (v > 1000) {
logmsg_err("ACL: %lu: restricted to 1000", v);
return (false);
}
*n = (size_t)v;
return (true);
}
bool
access_match_address(struct aclent *aclp, int af, const char *addr)
{
uint8_t binaddr[CVSYNC_MAXADDRLEN];
if (af != aclp->acl_family)
return (false);
if (inet_pton(af, addr, binaddr) != 1)
return (false);
switch (af) {
case AF_INET:
return (access_match_ipv4addr(aclp, binaddr));
#if defined(AF_INET6)
case AF_INET6:
return (access_match_ipv6addr(aclp, binaddr));
#endif /* defined(AF_INET6) */
default:
break;
}
return (false);
}
bool
access_match_hostname(struct aclent *aclp, const char *host)
{
if (fnmatch(aclp->acl_addr, host, 0) == FNM_NOMATCH)
return (false);
return (true);
}
bool
access_match_ipv4addr(struct aclent *aclp, const void *addr)
{
uint32_t v4pat = *(uint32_t *)aclp->acl_addr;
uint32_t v4mask = *(uint32_t *)aclp->acl_mask;
uint32_t v4addr = *(const uint32_t *)addr;
if (v4pat != (v4addr & v4mask))
return (false);
return (true);
}
bool
access_match_ipv6addr(struct aclent *aclp, const void *addr)
{
const uint8_t *v6addr = addr;
uint8_t *v6pat = aclp->acl_addr, *v6mask = aclp->acl_mask;
size_t i;
for (i = 0 ; i < aclp->acl_addrlen ; i++) {
if (v6pat[i] != (v6addr[i] & v6mask[i]))
return (false);
}
return (true);
}
bool
access_set_ipv4addr(struct aclent *aclp, const void *addr, size_t netmask)
{
uint32_t v4addr = *(const uint32_t *)addr, v4mask;
size_t addrbits, n;
aclp->acl_addrlen = sizeof(uint32_t);
addrbits = aclp->acl_addrlen * 8;
if (netmask == (size_t)-1)
netmask = addrbits;
if (netmask > addrbits)
return (false);
v4mask = ntohl(INADDR_BROADCAST);
if ((n = addrbits - netmask) > 0) {
v4mask >>= n;
v4mask <<= n;
}
v4mask = htonl(v4mask);
if ((aclp->acl_addr = malloc(aclp->acl_addrlen)) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
return (false);
}
if ((aclp->acl_mask = malloc(aclp->acl_addrlen)) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
free(aclp->acl_addr);
return (false);
}
*(uint32_t *)aclp->acl_addr = v4addr & v4mask;
*(uint32_t *)aclp->acl_mask = v4mask;
return (true);
}
bool
access_set_ipv6addr(struct aclent *aclp, const void *addr, size_t prefixlen)
{
const uint8_t *v6addr = addr;
uint8_t v6mask[16];
size_t addrbits, n, i;
aclp->acl_addrlen = 16;
addrbits = aclp->acl_addrlen * 8;
if (prefixlen == (size_t)-1)
prefixlen = addrbits;
if (prefixlen > addrbits)
return (false);
n = prefixlen / 8;
for (i = 0 ; i < n ; i++)
v6mask[i] = 0xff;
if ((prefixlen % 8) != 0) {
if ((i = 8 - (prefixlen - n * 8)) > 0) {
v6mask[n] = 0xff >> i;
v6mask[n] = v6mask[n] << i;
n++;
}
}
while (n < aclp->acl_addrlen)
v6mask[n++] = 0;
if ((aclp->acl_addr = malloc(aclp->acl_addrlen)) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
return (false);
}
if ((aclp->acl_mask = malloc(aclp->acl_addrlen)) == NULL) {
logmsg_err("ACL: %s", strerror(errno));
free(aclp->acl_addr);
return (false);
}
for (i = 0 ; i < aclp->acl_addrlen ; i++)
((uint8_t *)aclp->acl_addr)[i] = v6addr[i] & v6mask[i];
(void)memcpy(aclp->acl_mask, v6mask, aclp->acl_addrlen);
return (true);
}
syntax highlighted by Code2HTML, v. 0.9.1