28a1be960cac0eefaeba73a9d9eade3965ea8762
[obnox/samba/samba-obnox.git] / source4 / dsdb / samdb / ldb_modules / dns_notify.c
1 /*
2    ldb database library
3
4    Copyright (C) Samuel Cabrero <samuelcabrero@kernevil.me> 2014
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21  *  Name: ldb
22  *
23  *  Component: ldb dns_notify module
24  *
25  *  Description: Notify the DNS server when zones are changed, either by direct
26  *               RPC management calls or DRS inbound replication.
27  *
28  *  Author: Samuel Cabrero <samuelcabrero@kernevil.me>
29  */
30
31 #include "includes.h"
32 #include "ldb_module.h"
33 #include "dsdb/samdb/ldb_modules/util.h"
34 #include "dsdb/samdb/samdb.h"
35 #include "dsdb/common/proto.h"
36 #include "librpc/gen_ndr/ndr_irpc.h"
37 #include "lib/messaging/irpc.h"
38 #include "librpc/gen_ndr/ndr_irpc_c.h"
39 #include "param/param.h"
40 #include "dlinklist.h"
41
42 struct dns_notify_watched_dn {
43         struct dns_notify_watched_dn *next, *prev;
44         struct ldb_dn *dn;
45 };
46
47 struct dns_notify_private {
48         struct dns_notify_watched_dn *watched;
49         bool reload_zones;
50 };
51
52 struct dns_notify_dnssrv_state {
53         struct imessaging_context *msg_ctx;
54         struct dnssrv_reload_dns_zones r;
55 };
56
57 static void dns_notify_dnssrv_done(struct tevent_req *req)
58 {
59         NTSTATUS status;
60         struct dns_notify_dnssrv_state *state;
61
62         state = tevent_req_callback_data(req, struct dns_notify_dnssrv_state);
63
64         status = dcerpc_dnssrv_reload_dns_zones_r_recv(req, state);
65         if (!NT_STATUS_IS_OK(status)) {
66                 DEBUG(1, ("%s: Error notifiying dns server: %s\n",
67                       __func__, nt_errstr(status)));
68         }
69         imessaging_cleanup(state->msg_ctx);
70
71         talloc_free(req);
72         talloc_free(state);
73 }
74
75 static void dns_notify_dnssrv_send(struct ldb_module *module)
76 {
77         struct ldb_context *ldb;
78         struct loadparm_context *lp_ctx;
79         struct dns_notify_dnssrv_state *state;
80         struct dcerpc_binding_handle *handle;
81         struct tevent_req *req;
82
83         ldb = ldb_module_get_ctx(module);
84
85         lp_ctx = ldb_get_opaque(ldb, "loadparm");
86         if (lp_ctx == NULL) {
87                 return;
88         }
89
90         state = talloc_zero(module, struct dns_notify_dnssrv_state);
91         if (state == NULL) {
92                 return;
93         }
94
95         /* Initialize messaging client */
96         state->msg_ctx = imessaging_client_init(state, lp_ctx,
97                                                 ldb_get_event_context(ldb));
98         if (state->msg_ctx == NULL) {
99                 ldb_asprintf_errstring(ldb, "Failed to generate client messaging context in %s",
100                                        lpcfg_imessaging_path(state, lp_ctx));
101                 talloc_free(state);
102                 return;
103         }
104
105         /* Get a handle to notify the DNS server */
106         handle = irpc_binding_handle_by_name(state, state->msg_ctx,
107                                              "dnssrv",
108                                              &ndr_table_irpc);
109         if (handle == NULL) {
110                 imessaging_cleanup(state->msg_ctx);
111                 talloc_free(state);
112                 return;
113         }
114
115         /* Send the notifications */
116         req = dcerpc_dnssrv_reload_dns_zones_r_send(state,
117                                                     ldb_get_event_context(ldb),
118                                                     handle,
119                                                     &state->r);
120         if (req == NULL) {
121                 imessaging_cleanup(state->msg_ctx);
122                 talloc_free(state);
123                 return;
124         }
125         tevent_req_set_callback(req, dns_notify_dnssrv_done, state);
126 }
127
128 static int dns_notify_add(struct ldb_module *module, struct ldb_request *req)
129 {
130         struct ldb_context *ldb;
131         struct dns_notify_private *data;
132         struct dns_notify_watched_dn *w;
133         struct dsdb_schema *schema;
134         const struct dsdb_class *objectclass;
135
136         if (ldb_dn_is_special(req->op.add.message->dn)) {
137                 return ldb_next_request(module, req);
138         }
139
140         if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
141                 return ldb_next_request(module, req);
142         }
143
144         ldb = ldb_module_get_ctx(module);
145         data = talloc_get_type(ldb_module_get_private(module),
146                                struct dns_notify_private);
147         if (data == NULL) {
148                 return ldb_operr(ldb);
149         }
150
151         for (w = data->watched; w; w = w->next) {
152                 if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) {
153                         schema = dsdb_get_schema(ldb, req);
154                         if (schema == NULL) {
155                                 return ldb_operr(ldb);
156                         }
157
158                         objectclass = dsdb_get_structural_oc_from_msg(schema, req->op.add.message);
159                         if (objectclass == NULL) {
160                                 return ldb_operr(ldb);
161                         }
162
163                         if (strcasecmp(objectclass->lDAPDisplayName, "dnsZone") == 0) {
164                                 data->reload_zones = true;
165                                 break;
166                         }
167                 }
168         }
169
170         return ldb_next_request(module, req);
171 }
172
173 static int dns_notify_modify(struct ldb_module *module, struct ldb_request *req)
174 {
175         TALLOC_CTX *tmp_ctx;
176         struct ldb_context *ldb;
177         struct dns_notify_private *data;
178         struct dns_notify_watched_dn *w;
179         struct ldb_dn *dn;
180         struct ldb_result *res;
181         struct dsdb_schema *schema;
182         const struct dsdb_class *objectclass;
183         int ret;
184
185         if (ldb_dn_is_special(req->op.mod.message->dn)) {
186                 return ldb_next_request(module, req);
187         }
188
189         if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
190                 return ldb_next_request(module, req);
191         }
192
193         ldb = ldb_module_get_ctx(module);
194         data = talloc_get_type(ldb_module_get_private(module),
195                                struct dns_notify_private);
196         if (data == NULL) {
197                 return ldb_operr(ldb);
198         }
199
200         tmp_ctx = talloc_new(module);
201         if (tmp_ctx == NULL) {
202                 return ldb_oom(ldb);
203         }
204
205         for (w = data->watched; w; w = w->next) {
206                 if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) {
207                         dn = ldb_dn_copy(tmp_ctx, req->op.mod.message->dn);
208
209                         ret = dsdb_module_search_dn(module, tmp_ctx, &res, dn, NULL,
210                                                     DSDB_FLAG_NEXT_MODULE |
211                                                     DSDB_SEARCH_SHOW_RECYCLED |
212                                                     DSDB_SEARCH_REVEAL_INTERNALS |
213                                                     DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req);
214                         if (ret != LDB_SUCCESS) {
215                                 ldb_asprintf_errstring(ldb_module_get_ctx(module),
216                                                        "%s: Failed to modify %s, because we failed to find it: %s\n",
217                                                        __func__,
218                                                        ldb_dn_get_linearized(dn),
219                                                        ldb_errstring(ldb_module_get_ctx(module)));
220                                 talloc_free(tmp_ctx);
221                                 return ret;
222                         }
223
224                         schema = dsdb_get_schema(ldb, req);
225                         if (schema == NULL) {
226                                 talloc_free(tmp_ctx);
227                                 return ldb_operr(ldb);
228                         }
229
230                         objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]);
231                         if (objectclass == NULL) {
232                                 talloc_free(tmp_ctx);
233                                 return ldb_operr(ldb);
234                         }
235
236                         if (strcasecmp(objectclass->lDAPDisplayName, "dnsZone") == 0) {
237                                 data->reload_zones = true;
238                                 break;
239                         }
240                 }
241         }
242
243         talloc_free(tmp_ctx);
244         return ldb_next_request(module, req);
245 }
246
247 static int dns_notify_delete(struct ldb_module *module, struct ldb_request *req)
248 {
249         TALLOC_CTX *tmp_ctx;
250         struct ldb_context *ldb;
251         struct dns_notify_private *data;
252         struct dns_notify_watched_dn *w;
253         struct ldb_dn *old_dn;
254         struct ldb_result *res;
255         struct dsdb_schema *schema;
256         const struct dsdb_class *objectclass;
257         int ret;
258
259         if (ldb_dn_is_special(req->op.del.dn)) {
260                 return ldb_next_request(module, req);
261         }
262
263         if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
264                 return ldb_next_request(module, req);
265         }
266
267         ldb = ldb_module_get_ctx(module);
268         data = talloc_get_type(ldb_module_get_private(module),
269                                struct dns_notify_private);
270         if (data == NULL) {
271                 return ldb_operr(ldb);
272         }
273
274         tmp_ctx = talloc_new(module);
275         if (tmp_ctx == NULL) {
276                 return ldb_oom(ldb);
277         }
278
279         for (w = data->watched; w; w = w->next) {
280                 if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) {
281                         old_dn = ldb_dn_copy(tmp_ctx, req->op.del.dn);
282                         ret = dsdb_module_search_dn(module, tmp_ctx, &res, old_dn, NULL,
283                                                     DSDB_FLAG_NEXT_MODULE |
284                                                     DSDB_SEARCH_SHOW_RECYCLED |
285                                                     DSDB_SEARCH_REVEAL_INTERNALS |
286                                                     DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req);
287                         if (ret != LDB_SUCCESS) {
288                                 ldb_asprintf_errstring(ldb_module_get_ctx(module),
289                                                        "%s: Failed to delete %s, because we failed to find it: %s\n",
290                                                        __func__,
291                                                        ldb_dn_get_linearized(old_dn),
292                                                        ldb_errstring(ldb_module_get_ctx(module)));
293                                 talloc_free(tmp_ctx);
294                                 return ret;
295                         }
296
297                         schema = dsdb_get_schema(ldb, req);
298                         if (schema == NULL) {
299                                 talloc_free(tmp_ctx);
300                                 return ldb_operr(ldb);
301                         }
302
303                         objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]);
304                         if (objectclass == NULL) {
305                                 talloc_free(tmp_ctx);
306                                 return ldb_operr(ldb);
307                         }
308
309                         if (strcasecmp(objectclass->lDAPDisplayName, "dnsZone") == 0) {
310                                 data->reload_zones = true;
311                                 break;
312                         }
313                 }
314         }
315
316         talloc_free(tmp_ctx);
317         return ldb_next_request(module, req);
318 }
319
320 static int dns_notify_start_trans(struct ldb_module *module)
321 {
322         struct ldb_context *ldb;
323         struct dns_notify_private *data;
324
325         ldb = ldb_module_get_ctx(module);
326         data = talloc_get_type(ldb_module_get_private(module),
327                                struct dns_notify_private);
328         if (data == NULL) {
329                 return ldb_operr(ldb);
330         }
331
332         data->reload_zones = false;
333
334         return ldb_next_start_trans(module);
335 }
336
337 static int dns_notify_end_trans(struct ldb_module *module)
338 {
339         struct ldb_context *ldb;
340         struct dns_notify_private *data;
341         int ret;
342
343         ldb = ldb_module_get_ctx(module);
344         data = talloc_get_type(ldb_module_get_private(module),
345                                struct dns_notify_private);
346         if (data == NULL) {
347                 return ldb_operr(ldb);
348         }
349
350         ret = ldb_next_end_trans(module);
351         if (ret == LDB_SUCCESS) {
352                 if (data->reload_zones) {
353                         dns_notify_dnssrv_send(module);
354                 }
355         }
356
357         return ret;
358 }
359
360 static int dns_notify_del_trans(struct ldb_module *module)
361 {
362         struct ldb_context *ldb;
363         struct dns_notify_private *data;
364
365         ldb = ldb_module_get_ctx(module);
366         data = talloc_get_type(ldb_module_get_private(module),
367                                struct dns_notify_private);
368         if (data == NULL) {
369                 return ldb_operr(ldb);
370         }
371
372         data->reload_zones = false;
373
374         return ldb_next_del_trans(module);
375 }
376
377 static int dns_notify_init(struct ldb_module *module)
378 {
379         struct ldb_context *ldb;
380         struct dns_notify_private *data;
381         struct dns_notify_watched_dn *watched;
382         struct ldb_dn *domain_dn;
383         struct ldb_dn *forest_dn;
384
385         ldb = ldb_module_get_ctx(module);
386
387         data = talloc_zero(module, struct dns_notify_private);
388         if (data == NULL) {
389                 return ldb_oom(ldb);
390         }
391
392         domain_dn = ldb_get_default_basedn(ldb);
393         forest_dn = ldb_get_root_basedn(ldb);
394
395         /* Register hook on domain partition */
396         watched = talloc_zero(data, struct dns_notify_watched_dn);
397         if (watched == NULL) {
398                 talloc_free(data);
399                 return ldb_oom(ldb);
400         }
401         watched->dn = ldb_dn_new_fmt(watched, ldb,
402                                      "CN=MicrosoftDNS,CN=System,%s",
403                                      ldb_dn_get_linearized(domain_dn));
404         if (watched->dn == NULL) {
405                 talloc_free(data);
406                 return ldb_oom(ldb);
407         }
408         DLIST_ADD(data->watched, watched);
409
410         /* Check for DomainDnsZones partition and register hook */
411         watched = talloc_zero(data, struct dns_notify_watched_dn);
412         if (watched == NULL) {
413                 talloc_free(data);
414                 return ldb_oom(ldb);
415         }
416         watched->dn = ldb_dn_new_fmt(watched, ldb, "CN=MicrosoftDNS,DC=DomainDnsZones,%s", ldb_dn_get_linearized(forest_dn));
417         DLIST_ADD(data->watched, watched);
418
419         /* Check for ForestDnsZones partition and register hook */
420         watched = talloc_zero(data, struct dns_notify_watched_dn);
421         if (watched == NULL) {
422                 talloc_free(data);
423                 return ldb_oom(ldb);
424         }
425         watched->dn = ldb_dn_new_fmt(watched, ldb, "CN=MicrosoftDNS,DC=ForestDnsZones,%s", ldb_dn_get_linearized(forest_dn));
426         DLIST_ADD(data->watched, watched);
427
428         ldb_module_set_private(module, data);
429
430         return ldb_next_init(module);
431 }
432
433 static const struct ldb_module_ops ldb_dns_notify_module_ops = {
434         .name              = "dns_notify",
435         .init_context      = dns_notify_init,
436         .add               = dns_notify_add,
437         .modify            = dns_notify_modify,
438         .del               = dns_notify_delete,
439         .start_transaction = dns_notify_start_trans,
440         .end_transaction   = dns_notify_end_trans,
441         .del_transaction   = dns_notify_del_trans,
442 };
443
444 int ldb_dns_notify_module_init(const char *version)
445 {
446         LDB_MODULE_CHECK_VERSION(version);
447         return ldb_register_module(&ldb_dns_notify_module_ops);
448 }