diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2009-10-12 23:41:21 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2009-10-12 23:42:04 +0300 |
commit | 8a4ba77068e5d7f6eab2cc1c1c10f31dcbccf7a6 (patch) | |
tree | c70f38d943c778684bb9dbfc7db018e7efb21bf1 /src/acl.c | |
parent | af04ed630f18e9003756317cc627a11084d0d59a (diff) | |
download | pies-8a4ba77068e5d7f6eab2cc1c1c10f31dcbccf7a6.tar.gz pies-8a4ba77068e5d7f6eab2cc1c1c10f31dcbccf7a6.tar.bz2 |
Fix make distcheck and check-docs.
* doc/Makefile.am: Fix `check-*' goals.
* doc/pies.texi: Update and rearrange material.
Document new configuration.
* lib/Makefile.am (libpies_a_SOURCES): Remove nls.c
* src/Makefile.am (EXTRA_DIST): Remove pies.rc, add
pp-setup.
(INCLUDES): Add $(top_builddir)/gnu
* src/pies.c: Minor changes.
* src/progman.c: Minor changes.
* README-hacking: New file.
Diffstat (limited to 'src/acl.c')
-rw-r--r-- | src/acl.c | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 0000000..8561967 --- /dev/null +++ b/src/acl.c @@ -0,0 +1,627 @@ +/* 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 <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include "pies.h" +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <hash.h> + +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 <grp: list>] + [acl <name: string>] [from <addr: list>] */ + +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 <grp: list>] [from <addr: list>]"), + N_("Allow access"), + grecs_type_string, NULL, 0, + allow_cb }, + { "deny", N_("[all|authenticated|group <grp: list>] [from <addr: list>]"), + 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); +} |