aboutsummaryrefslogtreecommitdiff
path: root/src/acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/acl.c')
-rw-r--r--src/acl.c627
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);
+}

Return to:

Send suggestions and report system problems to the System administrator.