/* This file is part of GNU Pies Copyright (C) 2009, 2010, 2011 Sergey Poznyakoff GNU 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. GNU 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 GNU Pies. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include "pies.h" #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; struct grecs_list *groups; struct grecs_list *sockaddrs; }; struct pies_acl { char *name; grecs_locus_t locus; struct grecs_list *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 = grecs_list_create (); return acl; } void pies_acl_free (pies_acl_t acl) { free (acl->name); grecs_list_free (acl->list); free (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; } grecs_list_append (entry->sockaddrs, sptr); return 0; } static int _parse_from (struct acl_entry *entry, size_t argc, grecs_value_t **argv) { if (argc == 0) return 0; else if (argv[0]->type == GRECS_TYPE_LIST) { grecs_error (&entry->locus, 0, _("expected `from', but found list")); return 1; } else if (strcmp (argv[0]->v.string, "from")) { grecs_error (&entry->locus, 0, _("expected `from', but found `%s'"), argv[0]->v.string); return 1; } argc--; argv++; if (argc == 0) { grecs_error (&entry->locus, 0, _("unexpected end of statement after `from'")); return 1; } entry->sockaddrs = grecs_list_create (); if (argv[0]->type == GRECS_TYPE_STRING) { if (_parse_sockaddr (entry, argv[0])) return 1; } else { int rc = 0; struct grecs_list_entry *ep; for (ep = argv[0]->v.list->head; ep; ep = ep->next) rc += _parse_sockaddr (entry, (const grecs_value_t*) ep->data); 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[0]->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[0]->type != GRECS_TYPE_STRING) { grecs_error (&entry->locus, 0, _("expected string, but found list")); return 1; } entry->acl = pies_acl_lookup (argv[0]->v.string); if (!entry->acl) { grecs_error (&entry->locus, 0, _("ACL not defined: `%s'"), argv[0]->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[0]->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[0]->type == GRECS_TYPE_STRING) { entry->groups = grecs_list_create (); grecs_list_append (entry->groups, xstrdup (argv[0]->v.string)); } else entry->groups = argv[0]->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[0], GRECS_TYPE_STRING)) return 1; else if (_parse_token (entry, argv[0]) == 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; } grecs_list_append (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; 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 && (acl = pies_acl_install (acl)) == NULL) 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[] = { /* TRANSLATORS: only words within angle brackets are translatable */ { "allow", N_("[all|authenticated|group ] [from ]"), N_("Allow access"), grecs_type_string, NULL, 0, allow_cb }, /* TRANSLATORS: only words within angle brackets are translatable */ { "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) { struct grecs_list_entry *ep; for (ep = ent->groups->head; result && ep; ep = ep->next) result = match_group (input->groups, ep->data); if (!result) return result; } result = pies_acl_check (ent->acl, input, 1); if (!result) return result; if (ent->sockaddrs) { struct grecs_list_entry *ep; result = 0; for (ep = ent->sockaddrs->head; ep; ep = ep->next) { result = _check_sockaddr ((struct pies_sockaddr *)ep->data, input); if (result) break; } } 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.beg.file, ent->locus.beg.line, /* TRANSLATORS: `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) { struct grecs_list_entry *ep; for (ep = acl->list->head; ep; ep = ep->next) if (_acl_check_cb ((struct acl_entry *)ep->data, input, &result)) break; } return result; } /* Hash table */ static struct grecs_symtab *acl_table; /* Calculate the hash of a string. */ static unsigned acl_hasher (void *data, unsigned long n_buckets) { const struct pies_acl *p = data; return grecs_hash_string (p->name, n_buckets); } /* Compare two strings for equality. */ static int 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); } static int acl_copy (void *a, void *b) { const struct pies_acl *pb = b; memcpy (a, b, sizeof (struct pies_acl)); memset (b, 0, sizeof (struct pies_acl)); return 0; } static void acl_free_entry (void *p) { pies_acl_free (p); } pies_acl_t pies_acl_install (pies_acl_t acl) { pies_acl_t ret; int install = 1; if (!acl_table) { acl_table = grecs_symtab_create(sizeof (struct pies_acl), acl_hasher, acl_compare, acl_copy, NULL, acl_free_entry); if (!acl_table) xalloc_die (); } ret = grecs_symtab_lookup_or_install (acl_table, acl, &install); if (!ret) { logmsg (LOG_ERR, _("cannot install acl: %s"), strerror (errno)); exit (1); } if (!install) { grecs_error (&acl->locus, 0, _("redefinition of ACL %s"), ret->name); grecs_error (&ret->locus, 0, _("location of the previous definition")); ret = NULL; } pies_acl_free (acl); return ret; } pies_acl_t pies_acl_lookup (const char *name) { struct pies_acl samp; if (!acl_table) return NULL; samp.name = (char *) name; return grecs_symtab_lookup_or_install (acl_table, &samp, NULL); }