diff options
-rw-r--r-- | source4/dns_server/dns_server.c | 133 | ||||
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/dns_notify.c | 448 | ||||
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/samba_dsdb.c | 3 | ||||
-rwxr-xr-x | source4/dsdb/samdb/ldb_modules/wscript_build_server | 9 | ||||
-rw-r--r-- | source4/librpc/idl/irpc.idl | 11 |
5 files changed, 565 insertions, 39 deletions
diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c index f1a4c4c747..3e18287bfa 100644 --- a/source4/dns_server/dns_server.c +++ b/source4/dns_server/dns_server.c @@ -45,6 +45,8 @@ #include "lib/util/tevent_werror.h" #include "auth/auth.h" #include "auth/credentials/credentials.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "lib/messaging/irpc.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_DNS @@ -761,16 +763,90 @@ static struct dns_server_tkey_store *tkey_store_init(TALLOC_CTX *mem_ctx, return buffer; } +static NTSTATUS dns_server_reload_zones(struct dns_server *dns) +{ + int ret; + static const char * const attrs[] = { "name", NULL}; + struct ldb_result *res; + int i; + struct dns_server_zone *new_list = NULL; + struct dns_server_zone *old_list = NULL; + struct dns_server_zone *old_zone; + + // TODO: this search does not work against windows + ret = dsdb_search(dns->samdb, dns, &res, NULL, LDB_SCOPE_SUBTREE, + attrs, DSDB_SEARCH_SEARCH_ALL_PARTITIONS, "(objectClass=dnsZone)"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + TYPESAFE_QSORT(res->msgs, res->count, dns_server_sort_zones); + + for (i=0; i < res->count; i++) { + struct dns_server_zone *z; + + z = talloc_zero(dns, struct dns_server_zone); + if (z == NULL) { + return NT_STATUS_NO_MEMORY; + } + + z->name = ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL); + z->dn = talloc_move(z, &res->msgs[i]->dn); + /* + * Ignore the RootDNSServers zone and zones that we don't support yet + * RootDNSServers should never be returned (Windows DNS server don't) + * ..TrustAnchors should never be returned as is, (Windows returns + * TrustAnchors) and for the moment we don't support DNSSEC so we'd better + * not return this zone. + */ + if ((strcmp(z->name, "RootDNSServers") == 0) || + (strcmp(z->name, "..TrustAnchors") == 0)) + { + DEBUG(10, ("Ignoring zone %s\n", z->name)); + talloc_free(z); + continue; + } + DLIST_ADD_END(new_list, z, NULL); + } + + old_list = dns->zones; + dns->zones = new_list; + while ((old_zone = DLIST_TAIL(old_list)) != NULL) { + DLIST_REMOVE(old_list, old_zone); + talloc_free(old_zone); + } + + return NT_STATUS_OK; +} + +/** + * Called when the internal DNS server should reload the zones from DB, for + * example, when zones are added or deleted through RPC or replicated by + * inbound DRS. + */ +static NTSTATUS dns_reload_zones(struct irpc_message *msg, + struct dnssrv_reload_dns_zones *r) +{ + struct dns_server *dns; + + dns = talloc_get_type(msg->private_data, struct dns_server); + if (dns == NULL) { + r->out.result = NT_STATUS_INTERNAL_ERROR; + return NT_STATUS_INTERNAL_ERROR; + } + + r->out.result = dns_server_reload_zones(dns); + + return NT_STATUS_OK; +} + static void dns_task_init(struct task_server *task) { struct dns_server *dns; NTSTATUS status; struct interface *ifaces = NULL; int ret; - struct ldb_result *res; - static const char * const attrs[] = { "name", NULL}; static const char * const attrs_none[] = { NULL}; - unsigned int i; struct ldb_message *dns_acc; char *hostname_lower; char *dns_spn; @@ -866,48 +942,29 @@ static void dns_task_init(struct task_server *task) return; } - // TODO: this search does not work against windows - ret = dsdb_search(dns->samdb, dns, &res, NULL, LDB_SCOPE_SUBTREE, - attrs, DSDB_SEARCH_SEARCH_ALL_PARTITIONS, "(objectClass=dnsZone)"); - if (ret != LDB_SUCCESS) { - task_server_terminate(task, - "dns: failed to look up root DNS zones", - true); + status = dns_server_reload_zones(dns); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, "dns: failed to load DNS zones", true); return; } - TYPESAFE_QSORT(res->msgs, res->count, dns_server_sort_zones); - - for (i=0; i < res->count; i++) { - struct dns_server_zone *z; - - z = talloc_zero(dns, struct dns_server_zone); - if (z == NULL) { - task_server_terminate(task, "dns failed to allocate memory", true); - } + status = dns_startup_interfaces(dns, ifaces); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, "dns failed to setup interfaces", true); + return; + } - z->name = ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL); - z->dn = talloc_move(z, &res->msgs[i]->dn); - /* - * Ignore the RootDNSServers zone and zones that we don't support yet - * RootDNSServers should never be returned (Windows DNS server don't) - * ..TrustAnchors should never be returned as is, (Windows returns - * TrustAnchors) and for the moment we don't support DNSSEC so we'd better - * not return this zone. - */ - if ((strcmp(z->name, "RootDNSServers") == 0) || - (strcmp(z->name, "..TrustAnchors") == 0)) - { - DEBUG(10, ("Ignoring zone %s\n", z->name)); - talloc_free(z); - continue; - } - DLIST_ADD_END(dns->zones, z, NULL); + /* Setup the IRPC interface and register handlers */ + status = irpc_add_name(task->msg_ctx, "dnssrv"); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate(task, "dns: failed to register IRPC name", true); + return; } - status = dns_startup_interfaces(dns, ifaces); + status = IRPC_REGISTER(task->msg_ctx, irpc, DNSSRV_RELOAD_DNS_ZONES, + dns_reload_zones, dns); if (!NT_STATUS_IS_OK(status)) { - task_server_terminate(task, "dns failed to setup interfaces", true); + task_server_terminate(task, "dns: failed to setup reload handler", true); return; } } diff --git a/source4/dsdb/samdb/ldb_modules/dns_notify.c b/source4/dsdb/samdb/ldb_modules/dns_notify.c new file mode 100644 index 0000000000..28a1be960c --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/dns_notify.c @@ -0,0 +1,448 @@ +/* + ldb database library + + Copyright (C) Samuel Cabrero <samuelcabrero@kernevil.me> 2014 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. +*/ + +/* + * Name: ldb + * + * Component: ldb dns_notify module + * + * Description: Notify the DNS server when zones are changed, either by direct + * RPC management calls or DRS inbound replication. + * + * Author: Samuel Cabrero <samuelcabrero@kernevil.me> + */ + +#include "includes.h" +#include "ldb_module.h" +#include "dsdb/samdb/ldb_modules/util.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/proto.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "param/param.h" +#include "dlinklist.h" + +struct dns_notify_watched_dn { + struct dns_notify_watched_dn *next, *prev; + struct ldb_dn *dn; +}; + +struct dns_notify_private { + struct dns_notify_watched_dn *watched; + bool reload_zones; +}; + +struct dns_notify_dnssrv_state { + struct imessaging_context *msg_ctx; + struct dnssrv_reload_dns_zones r; +}; + +static void dns_notify_dnssrv_done(struct tevent_req *req) +{ + NTSTATUS status; + struct dns_notify_dnssrv_state *state; + + state = tevent_req_callback_data(req, struct dns_notify_dnssrv_state); + + status = dcerpc_dnssrv_reload_dns_zones_r_recv(req, state); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("%s: Error notifiying dns server: %s\n", + __func__, nt_errstr(status))); + } + imessaging_cleanup(state->msg_ctx); + + talloc_free(req); + talloc_free(state); +} + +static void dns_notify_dnssrv_send(struct ldb_module *module) +{ + struct ldb_context *ldb; + struct loadparm_context *lp_ctx; + struct dns_notify_dnssrv_state *state; + struct dcerpc_binding_handle *handle; + struct tevent_req *req; + + ldb = ldb_module_get_ctx(module); + + lp_ctx = ldb_get_opaque(ldb, "loadparm"); + if (lp_ctx == NULL) { + return; + } + + state = talloc_zero(module, struct dns_notify_dnssrv_state); + if (state == NULL) { + return; + } + + /* Initialize messaging client */ + state->msg_ctx = imessaging_client_init(state, lp_ctx, + ldb_get_event_context(ldb)); + if (state->msg_ctx == NULL) { + ldb_asprintf_errstring(ldb, "Failed to generate client messaging context in %s", + lpcfg_imessaging_path(state, lp_ctx)); + talloc_free(state); + return; + } + + /* Get a handle to notify the DNS server */ + handle = irpc_binding_handle_by_name(state, state->msg_ctx, + "dnssrv", + &ndr_table_irpc); + if (handle == NULL) { + imessaging_cleanup(state->msg_ctx); + talloc_free(state); + return; + } + + /* Send the notifications */ + req = dcerpc_dnssrv_reload_dns_zones_r_send(state, + ldb_get_event_context(ldb), + handle, + &state->r); + if (req == NULL) { + imessaging_cleanup(state->msg_ctx); + talloc_free(state); + return; + } + tevent_req_set_callback(req, dns_notify_dnssrv_done, state); +} + +static int dns_notify_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_context *ldb; + struct dns_notify_private *data; + struct dns_notify_watched_dn *w; + struct dsdb_schema *schema; + const struct dsdb_class *objectclass; + + if (ldb_dn_is_special(req->op.add.message->dn)) { + return ldb_next_request(module, req); + } + + if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + data = talloc_get_type(ldb_module_get_private(module), + struct dns_notify_private); + if (data == NULL) { + return ldb_operr(ldb); + } + + for (w = data->watched; w; w = w->next) { + if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) { + schema = dsdb_get_schema(ldb, req); + if (schema == NULL) { + return ldb_operr(ldb); + } + + objectclass = dsdb_get_structural_oc_from_msg(schema, req->op.add.message); + if (objectclass == NULL) { + return ldb_operr(ldb); + } + + if (strcasecmp(objectclass->lDAPDisplayName, "dnsZone") == 0) { + data->reload_zones = true; + break; + } + } + } + + return ldb_next_request(module, req); +} + +static int dns_notify_modify(struct ldb_module *module, struct ldb_request *req) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_context *ldb; + struct dns_notify_private *data; + struct dns_notify_watched_dn *w; + struct ldb_dn *dn; + struct ldb_result *res; + struct dsdb_schema *schema; + const struct dsdb_class *objectclass; + int ret; + + if (ldb_dn_is_special(req->op.mod.message->dn)) { + return ldb_next_request(module, req); + } + + if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + data = talloc_get_type(ldb_module_get_private(module), + struct dns_notify_private); + if (data == NULL) { + return ldb_operr(ldb); + } + + tmp_ctx = talloc_new(module); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + for (w = data->watched; w; w = w->next) { + if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) { + dn = ldb_dn_copy(tmp_ctx, req->op.mod.message->dn); + + ret = dsdb_module_search_dn(module, tmp_ctx, &res, dn, NULL, + DSDB_FLAG_NEXT_MODULE | + DSDB_SEARCH_SHOW_RECYCLED | + DSDB_SEARCH_REVEAL_INTERNALS | + DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req); + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "%s: Failed to modify %s, because we failed to find it: %s\n", + __func__, + ldb_dn_get_linearized(dn), + ldb_errstring(ldb_module_get_ctx(module))); + talloc_free(tmp_ctx); + return ret; + } + + schema = dsdb_get_schema(ldb, req); + if (schema == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]); + if (objectclass == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + if (strcasecmp(objectclass->lDAPDisplayName, "dnsZone") == 0) { + data->reload_zones = true; + break; + } + } + } + + talloc_free(tmp_ctx); + return ldb_next_request(module, req); +} + +static int dns_notify_delete(struct ldb_module *module, struct ldb_request *req) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_context *ldb; + struct dns_notify_private *data; + struct dns_notify_watched_dn *w; + struct ldb_dn *old_dn; + struct ldb_result *res; + struct dsdb_schema *schema; + const struct dsdb_class *objectclass; + int ret; + + if (ldb_dn_is_special(req->op.del.dn)) { + return ldb_next_request(module, req); + } + + if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) { + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + data = talloc_get_type(ldb_module_get_private(module), + struct dns_notify_private); + if (data == NULL) { + return ldb_operr(ldb); + } + + tmp_ctx = talloc_new(module); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + for (w = data->watched; w; w = w->next) { + if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) { + old_dn = ldb_dn_copy(tmp_ctx, req->op.del.dn); + ret = dsdb_module_search_dn(module, tmp_ctx, &res, old_dn, NULL, + DSDB_FLAG_NEXT_MODULE | + DSDB_SEARCH_SHOW_RECYCLED | + DSDB_SEARCH_REVEAL_INTERNALS | + DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req); + if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), + "%s: Failed to delete %s, because we failed to find it: %s\n", + __func__, + ldb_dn_get_linearized(old_dn), + ldb_errstring(ldb_module_get_ctx(module))); + talloc_free(tmp_ctx); + return ret; + } + + schema = dsdb_get_schema(ldb, req); + if (schema == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]); + if (objectclass == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + + if (strcasecmp(objectclass->lDAPDisplayName, "dnsZone") == 0) { + data->reload_zones = true; + break; + } + } + } + + talloc_free(tmp_ctx); + return ldb_next_request(module, req); +} + +static int dns_notify_start_trans(struct ldb_module *module) +{ + struct ldb_context *ldb; + struct dns_notify_private *data; + + ldb = ldb_module_get_ctx(module); + data = talloc_get_type(ldb_module_get_private(module), + struct dns_notify_private); + if (data == NULL) { + return ldb_operr(ldb); + } + + data->reload_zones = false; + + return ldb_next_start_trans(module); +} + +static int dns_notify_end_trans(struct ldb_module *module) +{ + struct ldb_context *ldb; + struct dns_notify_private *data; + int ret; + + ldb = ldb_module_get_ctx(module); + data = talloc_get_type(ldb_module_get_private(module), + struct dns_notify_private); + if (data == NULL) { + return ldb_operr(ldb); + } + + ret = ldb_next_end_trans(module); + if (ret == LDB_SUCCESS) { + if (data->reload_zones) { + dns_notify_dnssrv_send(module); + } + } + + return ret; +} + +static int dns_notify_del_trans(struct ldb_module *module) +{ + struct ldb_context *ldb; + struct dns_notify_private *data; + + ldb = ldb_module_get_ctx(module); + data = talloc_get_type(ldb_module_get_private(module), + struct dns_notify_private); + if (data == NULL) { + return ldb_operr(ldb); + } + + data->reload_zones = false; + + return ldb_next_del_trans(module); +} + +static int dns_notify_init(struct ldb_module *module) +{ + struct ldb_context *ldb; + struct dns_notify_private *data; + struct dns_notify_watched_dn *watched; + struct ldb_dn *domain_dn; + struct ldb_dn *forest_dn; + + ldb = ldb_module_get_ctx(module); + + data = talloc_zero(module, struct dns_notify_private); + if (data == NULL) { + return ldb_oom(ldb); + } + + domain_dn = ldb_get_default_basedn(ldb); + forest_dn = ldb_get_root_basedn(ldb); + + /* Register hook on domain partition */ + watched = talloc_zero(data, struct dns_notify_watched_dn); + if (watched == NULL) { + talloc_free(data); + return ldb_oom(ldb); + } + watched->dn = ldb_dn_new_fmt(watched, ldb, + "CN=MicrosoftDNS,CN=System,%s", + ldb_dn_get_linearized(domain_dn)); + if (watched->dn == NULL) { + talloc_free(data); + return ldb_oom(ldb); + } + DLIST_ADD(data->watched, watched); + + /* Check for DomainDnsZones partition and register hook */ + watched = talloc_zero(data, struct dns_notify_watched_dn); + if (watched == NULL) { + talloc_free(data); + return ldb_oom(ldb); + } + watched->dn = ldb_dn_new_fmt(watched, ldb, "CN=MicrosoftDNS,DC=DomainDnsZones,%s", ldb_dn_get_linearized(forest_dn)); + DLIST_ADD(data->watched, watched); + + /* Check for ForestDnsZones partition and register hook */ + watched = talloc_zero(data, struct dns_notify_watched_dn); + if (watched == NULL) { + talloc_free(data); + return ldb_oom(ldb); + } + watched->dn = ldb_dn_new_fmt(watched, ldb, "CN=MicrosoftDNS,DC=ForestDnsZones,%s", ldb_dn_get_linearized(forest_dn)); + DLIST_ADD(data->watched, watched); + + ldb_module_set_private(module, data); + + return ldb_next_init(module); +} + +static const struct ldb_module_ops ldb_dns_notify_module_ops = { + .name = "dns_notify", + .init_context = dns_notify_init, + .add = dns_notify_add, + .modify = dns_notify_modify, + .del = dns_notify_delete, + .start_transaction = dns_notify_start_trans, + .end_transaction = dns_notify_end_trans, + .del_transaction = dns_notify_del_trans, +}; + +int ldb_dns_notify_module_init(const char *version) +{ + LDB_MODULE_CHECK_VERSION(version); + return ldb_register_module(&ldb_dns_notify_module_ops); +} diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c index 086b11fb54..26c583ef58 100644 --- a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c +++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c @@ -302,7 +302,8 @@ static int samba_dsdb_init(struct ldb_module *module) const char *extended_dn_module_openldap = "extended_dn_out_openldap"; const char *extended_dn_in_module = "extended_dn_in"; - static const char *modules_list2[] = {"show_deleted", + static const char *modules_list2[] = {"dns_notify", + "show_deleted", "new_partition", "partition", NULL }; diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server index 0307aeaf9f..8848fd2c77 100755 --- a/source4/dsdb/samdb/ldb_modules/wscript_build_server +++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server @@ -364,3 +364,12 @@ bld.SAMBA_MODULE('ldb_dirsync', internal_module=False, deps='talloc samba-security samdb DSDB_MODULE_HELPERS' ) + +bld.SAMBA_MODULE('ldb_dns_notify', + source='dns_notify.c', + subsystem='ldb', + init_function='ldb_dns_notify_module_init', + module_init_name='ldb_init_module', + internal_module=False, + deps='talloc samdb DSDB_MODULE_HELPERS MESSAGING RPC_NDR_IRPC' + )
\ No newline at end of file diff --git a/source4/librpc/idl/irpc.idl b/source4/librpc/idl/irpc.idl index 6a55eef953..65ae4b6c5d 100644 --- a/source4/librpc/idl/irpc.idl +++ b/source4/librpc/idl/irpc.idl @@ -207,4 +207,15 @@ import "misc.idl", "security.idl", "nbt.idl", "netlogon.idl", "server_id.idl"; [in] uint32 dns_ttl, [in,out,ref] NL_DNS_NAME_INFO_ARRAY *dns_names ); + + /****************************************************** + * Management calls for the dns server + ******************************************************/ + /** + * Force internal DNS server to reload the DNS zones. + * + * Called when zones are added or deleted through RPC + * or replicated by DRS. + */ + NTSTATUS dnssrv_reload_dns_zones(); } |