/* This file is part of Pies
Copyright (C) 2009 Sergey Poznyakoff
Pies 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.
Pies 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 Pies. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include "pies.h"
#include
#include
#include
#include
#include
#include
#include
#include
struct pies_sockaddr
{
unsigned netmask;
int salen;
struct sockaddr sa;
};
struct acl_entry
{
grecs_locus_t locus;
int allow;
int authenticated;
pies_acl_t acl;
gl_list_t groups;
gl_list_t sockaddrs;
};
struct pies_acl
{
char *name;
grecs_locus_t locus;
gl_list_t list;
};
/* ACL creation */
pies_acl_t
pies_acl_create (const char *name, grecs_locus_t *locus)
{
pies_acl_t acl = xmalloc (sizeof (acl[0]));
acl->name = xstrdup (name);
acl->locus = *locus;
acl->list = gl_list_create_empty(&gl_linked_list_implementation,
NULL,
NULL,
NULL,
false);
return acl;
}
static struct pies_sockaddr *
create_acl_sockaddr (int family, int len)
{
struct pies_sockaddr *p = xzalloc (sizeof (*p));
p->salen = len;
p->sa.sa_family = family;
return p;
}
/* allow|deny [all|authenticated|group ]
[acl ] [from ] */
static int
_parse_token (struct acl_entry *entry, const grecs_value_t *value)
{
if (strcmp (value->v.string, "all") == 0
|| strcmp (value->v.string, "any") == 0)
/* FIXME: Nothing? */ ;
else if (strcmp (value->v.string, "auth") == 0
|| strcmp (value->v.string, "authenticated") == 0)
entry->authenticated = 1;
else
return 1;
return 0;
}
static int
_parse_sockaddr (struct acl_entry *entry, const grecs_value_t *value)
{
struct pies_sockaddr *sptr;
const char *string;
if (assert_grecs_value_type (&entry->locus, value, GRECS_TYPE_STRING))
return 1;
string = value->v.string;
if (string[0] == '/')
{
size_t len;
struct sockaddr_un *s_un;
len = strlen (string);
if (len >= sizeof (s_un->sun_path))
{
grecs_error (&entry->locus, 0,
_("socket name too long: `%s'"), string);
return 1;
}
sptr = create_acl_sockaddr (AF_UNIX, sizeof (s_un));
s_un = (struct sockaddr_un *) &sptr->sa;
memcpy (s_un->sun_path, string, len);
s_un->sun_path[len] = 0;
}
else
{
struct in_addr addr;
struct sockaddr_in *s_in;
char *p = strchr (string, '/');
if (p)
*p = 0;
if (inet_aton (string, &addr) == 0)
{
struct hostent *hp = gethostbyname (string);
if (!hp)
{
grecs_error (&entry->locus, 0,
_("cannot resolve host name: `%s'"), string);
if (p)
*p = '/';
return 1;
}
memcpy (&addr.s_addr, hp->h_addr, sizeof (addr.s_addr));
}
addr.s_addr = ntohl (addr.s_addr);
sptr = create_acl_sockaddr (AF_INET, sizeof (s_in));
s_in = (struct sockaddr_in *) &sptr->sa;
s_in->sin_addr = addr;
if (p)
{
*p++ = '/';
char *q;
unsigned netlen;
netlen = strtoul (p, &q, 10);
if (*q == 0)
{
if (netlen == 0)
sptr->netmask = 0;
else
{
sptr->netmask = 0xfffffffful >> (32 - netlen);
sptr->netmask <<= (32 - netlen);
sptr->netmask = htonl (sptr->netmask);
}
}
else if (*q == '.')
{
struct in_addr addr;
if (inet_aton (p, &addr) == 0)
{
grecs_error (&entry->locus, 0,
_("invalid netmask: `%s'"), p);
return 1;
}
sptr->netmask = addr.s_addr;
}
else
{
grecs_error (&entry->locus, 0, _("invalid netmask: `%s'"), p);
return 1;
}
}
else
sptr->netmask = 0xfffffffful;
}
gl_list_add_last (entry->sockaddrs, sptr);
return 0;
}
static int
_parse_from (struct acl_entry *entry, size_t argc, const grecs_value_t *argv)
{
if (argc == 0)
return 0;
else if (argv->type == GRECS_TYPE_LIST)
{
grecs_error (&entry->locus, 0, _("expected `from', but found list"));
return 1;
}
else if (strcmp (argv->v.string, "from"))
{
grecs_error (&entry->locus, 0, _("expected `from', but found `%s'"),
argv->v.string);
return 1;
}
argc--;
argv++;
if (argc == 0)
{
grecs_error (&entry->locus, 0,
_("unexpected end of statement after `from'"));
return 1;
}
entry->sockaddrs = gl_list_create_empty(&gl_linked_list_implementation,
NULL,
NULL,
NULL,
false);
if (argv->type == GRECS_TYPE_STRING)
{
if (_parse_sockaddr (entry, argv))
return 1;
}
else
{
gl_list_iterator_t itr = gl_list_iterator (argv->v.list);
const void *p;
int rc = 0;
while (gl_list_iterator_next (&itr, &p, NULL))
rc += _parse_sockaddr (entry, (const grecs_value_t*) p);
gl_list_iterator_free (&itr);
if (rc)
return rc;
}
if (argc - 1)
{
grecs_warning (&entry->locus, 0, _("junk after `from' list"));
return 1;
}
return 0;
}
static int
_parse_sub_acl (struct acl_entry *entry, size_t argc, grecs_value_t *argv)
{
if (argc == 0)
return 0;
if (strcmp (argv->v.string, "acl") == 0)
{
argc--;
argv++;
if (argc == 0)
{
grecs_error (&entry->locus, 0,
_("expected ACL name, but found end of statement"));
return 1;
}
if (argv->type != GRECS_TYPE_STRING)
{
grecs_error (&entry->locus, 0,
_("expected string, but found list"));
return 1;
}
entry->acl = pies_acl_lookup (argv->v.string);
if (!entry->acl)
{
grecs_error (&entry->locus, 0, _("ACL not defined: `%s'"),
argv->v.string);
return 1;
}
argc--;
argv++;
}
return _parse_from (entry, argc, argv);
}
static int
_parse_group (struct acl_entry *entry, size_t argc, grecs_value_t * argv)
{
if (strcmp (argv->v.string, "group") == 0)
{
argc--;
argv++;
if (argc == 0)
{
grecs_error (&entry->locus, 0,
_("expected group list, but found end of statement"));
return 1;
}
if (argv->type == GRECS_TYPE_STRING)
{
entry->groups = gl_list_create_empty(&gl_linked_list_implementation,
NULL,
NULL,
NULL,
false);
gl_list_add_last (entry->groups, (void *) argv->v.string);
}
else
entry->groups = argv->v.list;
argc--;
argv++;
}
return _parse_sub_acl (entry, argc, argv);
}
static int
_parse_acl (struct acl_entry *entry, size_t argc, grecs_value_t *argv)
{
if (assert_grecs_value_type (&entry->locus, argv, GRECS_TYPE_STRING))
return 1;
else if (_parse_token (entry, argv) == 0)
return _parse_sub_acl (entry, argc - 1, argv + 1);
else
return _parse_group (entry, argc, argv);
}
int
parse_acl_line (grecs_locus_t *locus, int allow, pies_acl_t acl,
grecs_value_t *value)
{
struct acl_entry *entry = xzalloc (sizeof (*entry));
entry->locus = *locus;
entry->allow = allow;
switch (value->type)
{
case GRECS_TYPE_STRING:
if (_parse_token (entry, value))
{
grecs_error (&entry->locus, 0, _("unknown word `%s'"),
value->v.string);
return 1;
}
break;
case GRECS_TYPE_ARRAY:
if (_parse_acl (entry, value->v.arg.c, value->v.arg.v))
return 1;
break;
case GRECS_TYPE_LIST:
grecs_error (locus, 0, _("unexpected list"));
return 1;
}
gl_list_add_last (acl->list, entry);
return 0;
}
int
acl_section_parser (enum grecs_callback_command cmd,
grecs_locus_t *locus,
void *varptr,
grecs_value_t *value,
void *cb_data)
{
void **pdata = cb_data;
pies_acl_t acl;
switch (cmd)
{
case grecs_callback_section_begin:
if (value->type != GRECS_TYPE_STRING)
grecs_error (locus, 0, _("ACL name must be a string"));
else if (!value->v.string)
grecs_error (locus, 0, _("missing ACL name"));
else
{
grecs_locus_t defn_loc;
acl = pies_acl_create (value->v.string, locus);
if (pies_acl_install (acl, &defn_loc))
{
grecs_error (locus, 0,
_("redefinition of ACL %s"),
value->v.string);
grecs_error (&defn_loc, 0,
_("location of the previous definition"));
return 1;
}
*pdata = acl;
}
break;
case grecs_callback_section_end:
case grecs_callback_set_value:
break;
}
return 0;
}
static int
allow_cb (enum grecs_callback_command cmd,
grecs_locus_t *locus,
void *varptr,
grecs_value_t *value,
void *cb_data)
{
pies_acl_t acl = varptr;
if (cmd != grecs_callback_set_value)
{
grecs_error (locus, 0, _("unexpected block statement"));
return 1;
}
parse_acl_line (locus, 1, acl, value);
return 0;
}
static int
deny_cb (enum grecs_callback_command cmd,
grecs_locus_t *locus,
void *varptr,
grecs_value_t *value,
void *cb_data)
{
pies_acl_t acl = varptr;
if (cmd != grecs_callback_set_value)
{
grecs_error (locus, 0, _("unexpected block statement"));
return 1;
}
parse_acl_line (locus, 0, acl, value);
return 0;
}
struct grecs_keyword acl_keywords[] = {
{ "allow", N_("[all|authenticated|group ] [from ]"),
N_("Allow access"),
grecs_type_string, NULL, 0,
allow_cb },
{ "deny", N_("[all|authenticated|group ] [from ]"),
N_("Deny access"),
grecs_type_string, NULL, 0,
deny_cb },
{ NULL }
};
/* ACL verification */
#define S_UN_NAME(sa, salen) \
((salen < offsetof (struct sockaddr_un,sun_path)) ? "" : (sa)->sun_path)
static int
_check_sockaddr (struct pies_sockaddr *sptr, struct acl_input *input)
{
if (sptr->sa.sa_family != input->addr->sa_family)
return 0;
switch (sptr->sa.sa_family)
{
case AF_INET:
{
struct sockaddr_in *sin_clt = (struct sockaddr_in *) input->addr;
struct sockaddr_in *sin_item = (struct sockaddr_in *) &sptr->sa;
if (ntohl (sin_clt->sin_addr.s_addr) ==
(sin_item->sin_addr.s_addr & sptr->netmask))
return 1;
break;
}
case AF_UNIX:
{
struct sockaddr_un *sun_clt = (struct sockaddr_un *) input->addr;
struct sockaddr_un *sun_item = (struct sockaddr_un *) &sptr->sa;
if (S_UN_NAME (sun_clt, input->addrlen)[0]
&& S_UN_NAME (sun_item, sptr->salen)[0]
&& strcmp (sun_clt->sun_path, sun_item->sun_path) == 0)
return 1;
}
}
return 0;
}
static int
match_group (const char **groups, const char *arg)
{
for (; *groups; groups++)
if (strcmp (*groups, arg) == 0)
return 1;
return 0;
}
static int
_acl_check (struct acl_entry *ent, struct acl_input *input)
{
int result = 1;
if (ent->authenticated)
{
result = input->user != NULL;
if (!result)
return result;
}
if (ent->groups)
{
const void *p;
gl_list_iterator_t itr = gl_list_iterator (ent->groups);
while (result && gl_list_iterator_next (&itr, &p, NULL))
result = match_group (input->groups, p);
gl_list_iterator_free (&itr);
if (!result)
return result;
}
result = pies_acl_check (ent->acl, input, 1);
if (!result)
return result;
if (ent->sockaddrs)
{
const void *p;
gl_list_iterator_t itr = gl_list_iterator (ent->sockaddrs);
result = 0;
while (gl_list_iterator_next (&itr, &p, NULL))
{
if (_check_sockaddr ((struct pies_sockaddr *)p, input))
break;
}
gl_list_iterator_free (&itr);
}
return result;
}
static int
_acl_check_cb (struct acl_entry *ent, struct acl_input *input, int *pres)
{
int result = _acl_check (ent, input);
debug (10, ("%s:%d: %s", ent->locus.file, ent->locus.line,
/* TRANSLATIONS: `MATCHES' is the verb `match' in 2nd person.
E.g., in French: CONCORD AVEC */
result ? _("MATCHES") : _("does not match")));
if (result)
{
*pres = ent->allow;
return 1;
}
return 0;
}
int
pies_acl_check (pies_acl_t acl, struct acl_input *input, int result)
{
if (acl)
{
const void *p;
gl_list_iterator_t itr = gl_list_iterator (acl->list);
while (gl_list_iterator_next (&itr, &p, NULL)
&& _acl_check_cb ((struct acl_entry *)p, input, &result))
;
gl_list_iterator_free (&itr);
}
return result;
}
/* Hash table */
static Hash_table *acl_table;
/* Calculate the hash of a string. */
static size_t
acl_hasher (void const *data, unsigned n_buckets)
{
const struct pies_acl *p = data;
return hash_string (p->name, n_buckets);
}
/* Compare two strings for equality. */
static bool
acl_compare (void const *data1, void const *data2)
{
const struct pies_acl *p1 = data1;
const struct pies_acl *p2 = data2;
return strcasecmp (p1->name, p2->name) == 0;
}
int
pies_acl_install (pies_acl_t acl, grecs_locus_t * locus)
{
pies_acl_t ret;
if (!((acl_table
|| (acl_table = hash_initialize (0, 0,
acl_hasher,
acl_compare, 0)))
&& (ret = hash_insert (acl_table, acl))))
xalloc_die ();
if (ret != acl)
{
if (locus)
*locus = ret->locus;
return 1;
}
return 0;
}
pies_acl_t
pies_acl_lookup (const char *name)
{
struct pies_acl samp;
if (!acl_table)
return NULL;
samp.name = (char *) name;
return hash_lookup (acl_table, &samp);
}