/* 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 = name ? xstrdup (name) : NULL; 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); } } 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; } #define ACL_TAG_NONE 0 #define ACL_TAG_IGNORE 1 #define ACL_TAG_OPTIONAL 2 #define ACL_TAG_REQUIRED 3 int _acl_common_section_parser (enum grecs_callback_command cmd, grecs_locus_t *locus, grecs_value_t *value, pies_acl_t *pacl, int flag) { pies_acl_t acl; grecs_locus_t defn_loc; const char *tag = NULL; int has_value = 0; switch (cmd) { case grecs_callback_section_begin: if (value) { if (value->type != GRECS_TYPE_STRING) { grecs_error (locus, 0, _("ACL name must be a string")); return 1; } has_value = value->v.string != NULL; } if (has_value) { switch (flag) { case ACL_TAG_NONE: grecs_error (locus, 0, _("ACL name is not expected")); return 1; case ACL_TAG_IGNORE: grecs_warning (locus, 0, _("ACL name is ignored")); break; case ACL_TAG_OPTIONAL: case ACL_TAG_REQUIRED: tag = value->v.string; } } else if (flag == ACL_TAG_REQUIRED) { grecs_error (locus, 0, _("missing ACL name")); return 1; } acl = pies_acl_create (tag, locus); if (tag && 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; } if (pacl) *pacl = acl; break; case grecs_callback_section_end: case grecs_callback_set_value: break; } return 0; } int acl_section_parser (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { int rc = _acl_common_section_parser (cmd, locus, value, varptr, ACL_TAG_NONE); if (rc == 0) *(void**)cb_data = *(pies_acl_t*)varptr; return rc; } int defacl_section_parser (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { return _acl_common_section_parser (cmd, locus, value, cb_data, ACL_TAG_REQUIRED); } 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 (sin_item->sin_addr.s_addr == (ntohl (sin_clt->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)) { result = _check_sockaddr ((struct pies_sockaddr *)p, input); if (result) 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 (1, ("%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, size_t 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); }