From 0a46531f5615aa3bea269c5787babffea01358b5 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Mon, 6 Apr 2009 11:06:18 +0200 Subject: Add update ACL capabilities. --- src/Makefile.am | 28 +++- src/acl.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/acl.h | 26 ++++ src/ldap_helper.c | 93 +++++++++---- 4 files changed, 505 insertions(+), 28 deletions(-) create mode 100644 src/acl.c create mode 100644 src/acl.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index ffa5499..f5cf9da 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,11 +3,31 @@ lib_LTLIBRARIES = libdnsldap.la LIBMAJOR = 1 LIBMINOR = 0 -HDRS = cache.h ldap_convert.h ldap_helper.h log.h rdlist.h semaphore.h \ - settings.h str.h util.h zone_manager.h +HDRS = \ + acl.h \ + cache.h \ + ldap_convert.h \ + ldap_helper.h \ + log.h \ + rdlist.h \ + semaphore.h \ + settings.h \ + str.h \ + util.h \ + zone_manager.h -libdnsldap_la_SOURCES = $(HDRS) cache.c ldap_convert.c ldap_driver.c \ - ldap_helper.c log.c rdlist.c semaphore.c settings.c str.c \ +libdnsldap_la_SOURCES = \ + $(HDRS) \ + acl.c \ + cache.c \ + ldap_convert.c \ + ldap_driver.c \ + ldap_helper.c \ + log.c \ + rdlist.c \ + semaphore.c \ + settings.c \ + str.c \ zone_manager.c libdnsldap_la_CFLAGS = -Wall -Wextra -pedantic -std=c99 diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 0000000..64f4060 --- /dev/null +++ b/src/acl.c @@ -0,0 +1,386 @@ +/* Authors: Martin Nagy + * + * Copyright (C) 2009 Red Hat + * see file 'COPYING' for use and warranty information + * + * This program 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; version 2 only + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * For portions of the code (see bellow): + * + * Copyright (C) 2004-2008 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2001-2003 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "str.h" +#include "util.h" +#include "log.h" + +static cfg_type_t *update_policy; + +static cfg_type_t * +get_type_from_tuplefield(const cfg_type_t *cfg_type, const char *name) +{ + cfg_type_t *ret = NULL; + const cfg_tuplefielddef_t *field; + + REQUIRE(cfg_type != NULL && cfg_type->of != NULL); + REQUIRE(name != NULL); + + field = (cfg_tuplefielddef_t *)cfg_type->of; + for (int i = 0; field[i].name != NULL; i++) { + if (!strcmp(field[i].name, name)) { + ret = field[i].type; + break; + } + } + + return ret; +} + +static cfg_type_t * +get_type_from_clause(const cfg_clausedef_t *clause, const char *name) +{ + cfg_type_t *ret = NULL; + + REQUIRE(clause != NULL); + REQUIRE(name != NULL); + + for (int i = 0; clause[i].name != NULL; i++) { + if (!strcmp(clause[i].name, name)) { + ret = clause[i].type; + break; + } + } + + return ret; +} + +static cfg_type_t * +get_type_from_clause_array(const cfg_type_t *cfg_type, const char *name) +{ + cfg_type_t *ret = NULL; + const cfg_clausedef_t **clauses; + + REQUIRE(cfg_type != NULL && cfg_type->of != NULL); + REQUIRE(name != NULL); + + clauses = (const cfg_clausedef_t **)cfg_type->of; + for (int i = 0; clauses[i] != NULL; i++) { + ret = get_type_from_clause(clauses[i], name); + if (ret != NULL) + break; + } + + return ret; +} + +static cfg_type_t * +get_update_policy_type(void) +{ + cfg_type_t *type; + + type = &cfg_type_namedconf; + type = get_type_from_clause_array(type, "zone"); + type = get_type_from_tuplefield(type, "options"); + type = get_type_from_clause_array(type, "update-policy"); + //type = (cfg_type_t *)type->of; + + return type; +} + +static isc_result_t +parse(cfg_parser_t *parser, const char *string, cfg_obj_t **objp) +{ + isc_result_t result; + isc_buffer_t buffer; + size_t string_len; + cfg_obj_t *ret = NULL; + + REQUIRE(parser != NULL); + REQUIRE(string != NULL); + REQUIRE(objp != NULL && *objp == NULL); + + /* FIXME: Only do this once. */ + update_policy = get_update_policy_type(); + + string_len = strlen(string); + isc_buffer_init(&buffer, string, string_len); + isc_buffer_add(&buffer, string_len); + + result = cfg_parse_buffer(parser, &buffer, update_policy, &ret); + + if (result == ISC_R_SUCCESS) + *objp = ret; + + return result; +} + +/* + * The rest of the code in this file is either copied from, or based on code + * from ISC BIND, file bin/named/config.c. + */ + +#define MATCH(string_rep, return_val) \ + do { \ + if (!strcasecmp(str, string_rep)) { \ + return return_val; \ + } \ + } while (0) + +static isc_boolean_t +get_mode(const cfg_obj_t *obj) +{ + const char *str; + + obj = cfg_tuple_get(obj, "mode"); + str = cfg_obj_asstring(obj); + + MATCH("grant", ISC_TRUE); + MATCH("deny", ISC_FALSE); + + INSIST(0); + /* Not reached. */ + return ISC_FALSE; +} + +static unsigned int +get_match_type(const cfg_obj_t *obj) +{ + const char *str; + + obj = cfg_tuple_get(obj, "matchtype"); + str = cfg_obj_asstring(obj); + + MATCH("name", DNS_SSUMATCHTYPE_NAME); + MATCH("subdomain", DNS_SSUMATCHTYPE_SUBDOMAIN); + MATCH("wildcard", DNS_SSUMATCHTYPE_WILDCARD); + MATCH("self", DNS_SSUMATCHTYPE_SELF); + MATCH("selfsub", DNS_SSUMATCHTYPE_SELFSUB); + MATCH("selfwild", DNS_SSUMATCHTYPE_SELFWILD); + MATCH("ms-self", DNS_SSUMATCHTYPE_SELFMS); + MATCH("krb5-self", DNS_SSUMATCHTYPE_SELFKRB5); + MATCH("ms-subdomain", DNS_SSUMATCHTYPE_SUBDOMAINMS); + MATCH("krb5-subdomain", DNS_SSUMATCHTYPE_SUBDOMAINKRB5); + MATCH("tcp-self", DNS_SSUMATCHTYPE_TCPSELF); + MATCH("6to4-self", DNS_SSUMATCHTYPE_6TO4SELF); + + INSIST(0); + /* Not reached. */ + return DNS_SSUMATCHTYPE_NAME; +} + +static isc_result_t +get_fixed_name(const cfg_obj_t *obj, const char *name, dns_fixedname_t *fname) +{ + isc_result_t result; + isc_buffer_t buf; + const char *str; + + REQUIRE(fname != NULL); + + obj = cfg_tuple_get(obj, name); + str = cfg_obj_asstring(obj); + + isc_buffer_init(&buf, str, strlen(str)); + isc_buffer_add(&buf, strlen(str)); + + dns_fixedname_init(fname); + + result = dns_name_fromtext(dns_fixedname_name(fname), &buf, + dns_rootname, ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) + log_error("'%s' is not a valid name", str); + + return result; +} + +static unsigned int +count_list_elements(const cfg_obj_t *list) +{ + const cfg_listelt_t *el; + unsigned int ret = 0; + + for (el = cfg_list_first(list); el != NULL; el = cfg_list_next(el)) + ret++; + + return ret; +} + +static isc_result_t +get_types(isc_mem_t *mctx, const cfg_obj_t *obj, dns_rdatatype_t **typesp, + unsigned int *np) +{ + isc_result_t result; + unsigned int i; + unsigned int n = 0; + const cfg_listelt_t *el; + dns_rdatatype_t *types = NULL; + + REQUIRE(obj != NULL); + REQUIRE(typesp != NULL && *typesp == NULL); + REQUIRE(np != NULL); + + obj = cfg_tuple_get(obj, "types"); + + n = count_list_elements(obj); + if (n > 0) + CHECKED_MEM_GET(mctx, types, n * sizeof(dns_rdatatype_t)); + + i = 0; + for (el = cfg_list_first(obj); el != NULL; el = cfg_list_next(el)) { + const cfg_obj_t *typeobj; + const char *str; + isc_textregion_t r; + + INSIST(i < n); + + typeobj = cfg_listelt_value(el); + str = cfg_obj_asstring(typeobj); + DE_CONST(str, r.base); + r.length = strlen(str); + + result = dns_rdatatype_fromtext(&types[i++], &r); + if (result != ISC_R_SUCCESS) { + log_error("'%s' is not a valid type", str); + goto cleanup; + } + } + INSIST(i == n); + + *typesp = types; + *np = n; + return result; + +cleanup: + SAFE_MEM_PUT(mctx, types, n * sizeof(dns_rdatatype_t)); + + return result; +} + +isc_result_t +acl_configure_zone_ssutable(const char *policy_str, dns_zone_t *zone) +{ + isc_result_t result = ISC_R_SUCCESS; + cfg_parser_t *parser = NULL; + const cfg_listelt_t *el; + cfg_obj_t *policy = NULL; + dns_ssutable_t *table = NULL; + ld_string_t *new_policy_str = NULL; + isc_mem_t *mctx; + + REQUIRE(zone != NULL); + + log_func_enter(); + + mctx = dns_zone_getmctx(zone); + + if (policy_str == NULL) + goto cleanup; + + CHECK(str_new(mctx, &new_policy_str)); + CHECK(str_sprintf(new_policy_str, "{ %s }", policy_str)); + + CHECK(cfg_parser_create(mctx, dns_lctx, &parser)); + result = parse(parser, str_buf(new_policy_str), &policy); + + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "failed to parse policy string"); + goto cleanup; + } + + CHECK(dns_ssutable_create(mctx, &table)); + + for (el = cfg_list_first(policy); el != NULL; el = cfg_list_next(el)) { + const cfg_obj_t *stmt; + isc_boolean_t grant; + unsigned int match_type; + dns_fixedname_t fname, fident; + dns_rdatatype_t *types; + unsigned int n; + + types = NULL; + + stmt = cfg_listelt_value(el); + grant = get_mode(stmt); + match_type = get_match_type(stmt); + + CHECK(get_fixed_name(stmt, "identity", &fident)); + CHECK(get_fixed_name(stmt, "name", &fname)); + CHECK(get_types(mctx, stmt, &types, &n)); + + result = dns_ssutable_addrule(table, grant, + dns_fixedname_name(&fident), + match_type, + dns_fixedname_name(&fname), + n, types); + + SAFE_MEM_PUT(mctx, types, n * sizeof(dns_rdatatype_t)); + if (result != ISC_R_SUCCESS) + goto cleanup; + + } + + cleanup: + if (result == ISC_R_SUCCESS) + dns_zone_setssutable(zone, table); + + str_destroy(&new_policy_str); + if (policy != NULL) + cfg_obj_destroy(parser, &policy); + if (parser != NULL) + cfg_parser_destroy(&parser); + if (table != NULL) + dns_ssutable_detach(&table); + + log_func_exit_result(result); + + return result; +} diff --git a/src/acl.h b/src/acl.h new file mode 100644 index 0000000..3b3e8ea --- /dev/null +++ b/src/acl.h @@ -0,0 +1,26 @@ +/* Authors: Martin Nagy + * + * Copyright (C) 2009 Red Hat + * see file 'COPYING' for use and warranty information + * + * This program 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; version 2 only + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LD_ACL_H_ +#define _LD_ACL_H_ + +isc_result_t +acl_configure_zone_ssutable(const char *policy_str, dns_zone_t *zone); + +#endif /* !_LD_ACL_H_ */ diff --git a/src/ldap_helper.c b/src/ldap_helper.c index 60b2675..99a8fab 100644 --- a/src/ldap_helper.c +++ b/src/ldap_helper.c @@ -43,6 +43,7 @@ #include #include +#include "acl.h" #include "ldap_convert.h" #include "ldap_helper.h" #include "log.h" @@ -144,6 +145,7 @@ struct ldap_instance { struct ldap_entry { LDAPMessage *entry; + char *dn; ldap_attribute_t *last_attr; ldap_attribute_list_t attributes; LINK(ldap_entry_t) link; @@ -186,7 +188,8 @@ static isc_result_t new_ldap_instance(ldap_db_t *ldap_db, ldap_instance_t **ldap_instp); static void destroy_ldap_instance(ldap_instance_t **ldap_instp); static isc_result_t add_or_modify_zone(ldap_db_t *ldap_db, const char *dn, - const char *db_name, dns_zonemgr_t *zmgr); + const char *db_name, const char *update_str, + dns_zonemgr_t *zmgr); static isc_result_t findrdatatype_or_create(isc_mem_t *mctx, ldapdb_rdatalist_t *rdatalist, ldap_entry_t *entry, @@ -222,9 +225,12 @@ static void free_query_cache(ldap_instance_t *inst); static void free_ldap_attributes(isc_mem_t *mctx, ldap_entry_t *entry); static void free_ldap_values(isc_mem_t *mctx, ldap_attribute_t *attr); +static const char * get_dn(ldap_instance_t *ldap_inst, ldap_entry_t *entry); +#if 0 static const LDAPMessage *next_entry(ldap_instance_t *inst); static const char *get_dn(ldap_instance_t *inst); +#endif static ldap_instance_t * get_connection(ldap_db_t *ldap_db); static void put_connection(ldap_instance_t *ldap_inst); @@ -459,8 +465,9 @@ refresh_zones_from_ldap(ldap_db_t *ldap_db, const char *name, { isc_result_t result = ISC_R_SUCCESS; ldap_instance_t *ldap_inst; + ldap_entry_t *entry; char *attrs[] = { - "idnsName", NULL + "idnsName", "idnsUpdatePolicy", NULL }; REQUIRE(ldap_db != NULL); @@ -471,10 +478,30 @@ refresh_zones_from_ldap(ldap_db_t *ldap_db, const char *name, ldap_inst = get_connection(ldap_db); ldap_query(ldap_inst, str_buf(ldap_db->base), LDAP_SCOPE_SUBTREE, - attrs, 0, "(objectClass=idnsZone)"); + attrs, 0, "(&(objectClass=idnsZone)(idnsZoneActive=True))"); + CHECK(cache_query_results(ldap_inst)); - while (next_entry(ldap_inst)) - CHECK(add_or_modify_zone(ldap_db, get_dn(ldap_inst), name, zmgr)); + for (entry = HEAD(ldap_inst->ldap_entries); + entry != NULL; + entry = NEXT(entry, link)) { + const char *dn; + const char *update_str = NULL; + ldap_value_list_t values; + + dn = get_dn(ldap_inst, entry); + + /* Look if there's an update policy. */ + result = get_values(entry, "idnsUpdatePolicy", &values); + if (result == ISC_R_SUCCESS) + update_str = HEAD(values)->value; + + result = add_or_modify_zone(ldap_db, dn, name, update_str, + zmgr); + + /* TODO: move this to the add_or_modify_zone() */ + if (result != ISC_R_SUCCESS) + log_error("failed to add/modify zone %s", dn); + } cleanup: put_connection(ldap_inst); @@ -484,6 +511,20 @@ cleanup: return result; } +static const char * +get_dn(ldap_instance_t *ldap_inst, ldap_entry_t *entry) +{ + if (entry->dn) { + ldap_memfree(entry->dn); + entry->dn = NULL; + } + if (ldap_inst->handle) + entry->dn = ldap_get_dn(ldap_inst->handle, entry->entry); + + return entry->dn; +} + +#if 0 static const char * get_dn(ldap_instance_t *inst) { @@ -498,7 +539,7 @@ get_dn(ldap_instance_t *inst) return inst->dn; } - +#endif void string_deleter(void *arg1, void *arg2) @@ -568,14 +609,10 @@ add_zone_dn(ldap_db_t *ldap_db, dns_name_t *name, const char *dn) /* Now add it. */ CHECK(dns_rbt_addname(rbt, name, (void *)new_dn)); - RWUNLOCK(&ldap_db->zone_rwlock, isc_rwlocktype_write); - - return ISC_R_SUCCESS; - cleanup: RWUNLOCK(&ldap_db->zone_rwlock, isc_rwlocktype_write); - if (new_dn) + if (result != ISC_R_SUCCESS && new_dn != NULL) isc_mem_free(ldap_db->mctx, new_dn); return result; @@ -584,12 +621,11 @@ cleanup: /* FIXME: Better error handling. */ static isc_result_t add_or_modify_zone(ldap_db_t *ldap_db, const char *dn, const char *db_name, - dns_zonemgr_t *zmgr) + const char *update_str, dns_zonemgr_t *zmgr) { isc_result_t result; dns_zone_t *zone; dns_name_t name; - dns_acl_t *updateacl = NULL; const char *argv[2]; REQUIRE(ldap_db != NULL); @@ -613,12 +649,6 @@ add_or_modify_zone(ldap_db_t *ldap_db, const char *dn, const char *db_name, dns_zone_setclass(zone, dns_rdataclass_in); dns_zone_settype(zone, dns_zone_master); CHECK(dns_zone_setdbtype(zone, 2, argv)); - - /* XXX Temporary set update ACLs to any */ - CHECK(dns_acl_any(ldap_db->mctx, &updateacl)); - dns_zone_setupdateacl(zone, updateacl); - dns_acl_detach(&updateacl); - CHECK(dns_zonemgr_managezone(zmgr, zone)); CHECK(dns_view_addzone(ldap_db->view, zone)); CHECK(add_zone_dn(ldap_db, &name, dn)); @@ -626,6 +656,9 @@ add_or_modify_zone(ldap_db_t *ldap_db, const char *dn, const char *db_name, goto cleanup; } + /* Set simple update table. */ + CHECK(acl_configure_zone_ssutable(update_str, zone)); + /* * ACLs: * dns_zone_setqueryacl() @@ -1322,6 +1355,8 @@ free_query_cache(ldap_instance_t *inst) next = NEXT(entry, link); UNLINK(inst->ldap_entries, entry, link); free_ldap_attributes(inst->database->mctx, entry); + if (entry->dn != NULL) + ldap_memfree(entry->dn); isc_mem_put(inst->database->mctx, entry, sizeof(*entry)); entry = next; } @@ -1360,6 +1395,7 @@ free_ldap_values(isc_mem_t *mctx, ldap_attribute_t *attr) } } +#if 0 /* FIXME: this function is obsolete, remove. */ static const LDAPMessage * next_entry(ldap_instance_t *inst) @@ -1378,6 +1414,7 @@ next_entry(ldap_instance_t *inst) return inst->entry; } +#endif #if 0 /* FIXME: Not tested. */ @@ -1536,11 +1573,16 @@ ldap_modify_do(ldap_instance_t *ldap_inst, const char *dn, LDAPMod **mods) REQUIRE(dn != NULL); REQUIRE(mods != NULL); - log_debug(2, "writing to to '%s'", dn); + log_debug(2, "writing to '%s'", dn); ret = ldap_modify_ext_s(ldap_inst->handle, dn, mods, NULL, NULL); if (ret != LDAP_SUCCESS) { - log_error("error writing to ldap: %s", ldap_err2string(ret)); + int err_code; + + ldap_get_option(ldap_inst->handle, LDAP_OPT_RESULT_CODE, + &err_code); + log_error("error writing to ldap: %s", + ldap_err2string(err_code)); return ISC_R_FAILURE; } @@ -1678,11 +1720,14 @@ modify_ldap_common(dns_name_t *owner, ldap_db_t *ldap_db, { isc_result_t result; isc_mem_t *mctx; - ldap_instance_t *ldap_inst; + ldap_instance_t *ldap_inst = NULL; ld_string_t *owner_dn = NULL; - LDAPMod *change[2]; + LDAPMod *change[2] = { NULL, NULL }; - change[0] = change[1] = NULL; + if (rdlist->type == dns_rdatatype_soa) { + result = ISC_R_SUCCESS; + goto cleanup; + } mctx = ldap_db->mctx; ldap_inst = get_connection(ldap_db); -- cgit