s4-dns: use ldb hooks for samba extensions in dlz_bind9
[metze/samba/wip.git] / source4 / dns_server / dlz_bind9.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    bind9 dlz driver for Samba
5
6    Copyright (C) 2010 Andrew Tridgell
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "talloc.h"
24 #include "param/param.h"
25 #include "lib/events/events.h"
26 #include "dsdb/samdb/samdb.h"
27 #include "dsdb/common/util.h"
28 #include "auth/session.h"
29 #include "auth/gensec/gensec.h"
30 #include "gen_ndr/ndr_dnsp.h"
31 #include "lib/cmdline/popt_common.h"
32 #include "lib/cmdline/popt_credentials.h"
33 #include "ldb_module.h"
34 #include "dlz_bind9.h"
35
36 struct dlz_bind9_data {
37         struct ldb_context *samdb;
38         struct tevent_context *ev_ctx;
39         struct loadparm_context *lp;
40
41         /* helper functions from the dlz_dlopen driver */
42         void (*log)(int level, const char *fmt, ...);
43         isc_result_t (*putrr)(dns_sdlzlookup_t *handle, const char *type,
44                               dns_ttl_t ttl, const char *data);
45         isc_result_t (*putnamedrr)(dns_sdlzlookup_t *handle, const char *name,
46                                    const char *type, dns_ttl_t ttl, const char *data);
47 };
48
49 /*
50   return the version of the API
51  */
52 _PUBLIC_ int dlz_version(unsigned int *flags)
53 {
54         return DLZ_DLOPEN_VERSION;
55 }
56
57 /*
58    remember a helper function from the bind9 dlz_dlopen driver
59  */
60 static void b9_add_helper(struct dlz_bind9_data *state, const char *helper_name, void *ptr)
61 {
62         if (strcmp(helper_name, "log") == 0) {
63                 state->log = ptr;
64         }
65         if (strcmp(helper_name, "putrr") == 0) {
66                 state->putrr = ptr;
67         }
68         if (strcmp(helper_name, "putnamedrr") == 0) {
69                 state->putnamedrr = ptr;
70         }
71 }
72
73 /*
74   format a record for bind9
75  */
76 static bool b9_format(struct dlz_bind9_data *state,
77                       TALLOC_CTX *mem_ctx,
78                       struct dnsp_DnssrvRpcRecord *rec,
79                       const char **type, const char **data)
80 {
81         switch (rec->wType) {
82         case DNS_TYPE_A:
83                 *type = "a";
84                 *data = rec->data.ipv4;
85                 break;
86
87         case DNS_TYPE_AAAA:
88                 *type = "aaaa";
89                 *data = rec->data.ipv6;
90                 break;
91
92         case DNS_TYPE_CNAME:
93                 *type = "cname";
94                 *data = rec->data.cname;
95                 break;
96
97         case DNS_TYPE_TXT:
98                 *type = "txt";
99                 *data = rec->data.txt;
100                 break;
101
102         case DNS_TYPE_PTR:
103                 *type = "ptr";
104                 *data = rec->data.ptr;
105                 break;
106
107         case DNS_TYPE_SRV:
108                 *type = "srv";
109                 *data = talloc_asprintf(mem_ctx, "%u %u %u %s",
110                                         rec->data.srv.wPriority,
111                                         rec->data.srv.wWeight,
112                                         rec->data.srv.wPort,
113                                         rec->data.srv.nameTarget);
114                 break;
115
116         case DNS_TYPE_MX:
117                 *type = "mx";
118                 *data = talloc_asprintf(mem_ctx, "%u %s",
119                                         rec->data.srv.wPriority,
120                                         rec->data.srv.nameTarget);
121                 break;
122
123         case DNS_TYPE_HINFO:
124                 *type = "hinfo";
125                 *data = talloc_asprintf(mem_ctx, "%s %s",
126                                         rec->data.hinfo.cpu,
127                                         rec->data.hinfo.os);
128                 break;
129
130         case DNS_TYPE_NS:
131                 *type = "ns";
132                 *data = rec->data.ns;
133                 break;
134
135         case DNS_TYPE_SOA:
136                 *type = "soa";
137                 *data = talloc_asprintf(mem_ctx, "%s %s %u %u %u %u %u",
138                                         rec->data.soa.mname,
139                                         rec->data.soa.rname,
140                                         rec->data.soa.serial,
141                                         rec->data.soa.refresh,
142                                         rec->data.soa.retry,
143                                         rec->data.soa.expire,
144                                         rec->data.soa.minimum);
145                 break;
146
147         default:
148                 state->log(ISC_LOG_ERROR, "samba b9_putrr: unhandled record type %u",
149                            rec->wType);
150                 return false;
151         }
152
153         return true;
154 }
155
156 /*
157   send a resource recond to bind9
158  */
159 static isc_result_t b9_putrr(struct dlz_bind9_data *state,
160                              void *handle, struct dnsp_DnssrvRpcRecord *rec,
161                              const char **types)
162 {
163         isc_result_t result;
164         const char *type, *data;
165         TALLOC_CTX *tmp_ctx = talloc_new(state);
166
167         if (!b9_format(state, tmp_ctx, rec, &type, &data)) {
168                 return ISC_R_FAILURE;
169         }
170
171         if (data == NULL) {
172                 talloc_free(tmp_ctx);
173                 return ISC_R_NOMEMORY;
174         }
175
176         if (types) {
177                 int i;
178                 for (i=0; types[i]; i++) {
179                         if (strcmp(types[i], type) == 0) break;
180                 }
181                 if (types[i] == NULL) {
182                         /* skip it */
183                         return ISC_R_SUCCESS;
184                 }
185         }
186
187         result = state->putrr(handle, type, rec->dwTtlSeconds, data);
188         if (result != ISC_R_SUCCESS) {
189                 state->log(ISC_LOG_ERROR, "Failed to put rr");
190         }
191         talloc_free(tmp_ctx);
192         return result;
193 }
194
195
196 /*
197   send a named resource recond to bind9
198  */
199 static isc_result_t b9_putnamedrr(struct dlz_bind9_data *state,
200                                   void *handle, const char *name,
201                                   struct dnsp_DnssrvRpcRecord *rec)
202 {
203         isc_result_t result;
204         const char *type, *data;
205         TALLOC_CTX *tmp_ctx = talloc_new(state);
206
207         if (!b9_format(state, tmp_ctx, rec, &type, &data)) {
208                 return ISC_R_FAILURE;
209         }
210
211         if (data == NULL) {
212                 talloc_free(tmp_ctx);
213                 return ISC_R_NOMEMORY;
214         }
215
216         result = state->putnamedrr(handle, name, type, rec->dwTtlSeconds, data);
217         if (result != ISC_R_SUCCESS) {
218                 state->log(ISC_LOG_ERROR, "Failed to put named rr '%s'", name);
219         }
220         talloc_free(tmp_ctx);
221         return result;
222 }
223
224 struct b9_options {
225         const char *url;
226 };
227
228 /*
229    parse options
230  */
231 static isc_result_t parse_options(struct dlz_bind9_data *state,
232                                   unsigned int argc, char *argv[],
233                                   struct b9_options *options)
234 {
235         int opt;
236         poptContext pc;
237         struct poptOption long_options[] = {
238                 { "url",       'H', POPT_ARG_STRING, &options->url, 0, "database URL", "URL" },
239                 { NULL }
240         };
241         struct poptOption **popt_options;
242         int ret;
243
244         popt_options = ldb_module_popt_options(state->samdb);
245         (*popt_options) = long_options;
246
247         ret = ldb_modules_hook(state->samdb, LDB_MODULE_HOOK_CMDLINE_OPTIONS);
248         if (ret != LDB_SUCCESS) {
249                 state->log(ISC_LOG_ERROR, "dlz samba: failed cmdline hook");
250                 return ISC_R_FAILURE;
251         }
252
253         pc = poptGetContext("dlz_bind9", argc, (const char **)argv, *popt_options,
254                             POPT_CONTEXT_KEEP_FIRST);
255
256         while ((opt = poptGetNextOpt(pc)) != -1) {
257                 switch (opt) {
258                 default:
259                         state->log(ISC_LOG_ERROR, "dlz samba: Invalid option %s: %s",
260                                    poptBadOption(pc, 0), poptStrerror(opt));
261                         return ISC_R_FAILURE;
262                 }
263         }
264
265         ret = ldb_modules_hook(state->samdb, LDB_MODULE_HOOK_CMDLINE_PRECONNECT);
266         if (ret != LDB_SUCCESS) {
267                 state->log(ISC_LOG_ERROR, "dlz samba: failed cmdline preconnect");
268                 return ISC_R_FAILURE;
269         }
270
271         return ISC_R_SUCCESS;
272 }
273
274
275 /*
276   called to initialise the driver
277  */
278 _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
279                                  unsigned int argc, char *argv[],
280                                  void *driverarg, void **dbdata, ...)
281 {
282         struct dlz_bind9_data *state;
283         const char *helper_name;
284         va_list ap;
285         isc_result_t result;
286         TALLOC_CTX *tmp_ctx;
287         int ret;
288         struct ldb_dn *dn;
289         struct b9_options options;
290
291         ZERO_STRUCT(options);
292
293         state = talloc_zero(NULL, struct dlz_bind9_data);
294         if (state == NULL) {
295                 return ISC_R_NOMEMORY;
296         }
297
298         tmp_ctx = talloc_new(state);
299
300         /* fill in the helper functions */
301         va_start(ap, dbdata);
302         while ((helper_name = va_arg(ap, const char *)) != NULL) {
303                 b9_add_helper(state, helper_name, va_arg(ap, void*));
304         }
305         va_end(ap);
306
307         state->ev_ctx = s4_event_context_init(state);
308         if (state->ev_ctx == NULL) {
309                 result = ISC_R_NOMEMORY;
310                 goto failed;
311         }
312
313         state->samdb = ldb_init(state, state->ev_ctx);
314         if (state->samdb == NULL) {
315                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed to create ldb");
316                 result = ISC_R_FAILURE;
317                 goto failed;
318         }
319
320         result = parse_options(state, argc, argv, &options);
321         if (result != ISC_R_SUCCESS) {
322                 goto failed;
323         }
324
325         state->lp = loadparm_init_global(true);
326         if (state->lp == NULL) {
327                 result = ISC_R_NOMEMORY;
328                 goto failed;
329         }
330
331         if (options.url == NULL) {
332                 options.url = talloc_asprintf(tmp_ctx, "ldapi://%s",
333                                               private_path(tmp_ctx, state->lp, "ldap_priv/ldapi"));
334                 if (options.url == NULL) {
335                         result = ISC_R_NOMEMORY;
336                         goto failed;
337                 }
338         }
339
340         ret = ldb_connect(state->samdb, options.url, 0, NULL);
341         if (ret == -1) {
342                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed to connect to %s - %s",
343                            options.url, ldb_errstring(state->samdb));
344                 result = ISC_R_FAILURE;
345                 goto failed;
346         }
347
348         ret = ldb_modules_hook(state->samdb, LDB_MODULE_HOOK_CMDLINE_POSTCONNECT);
349         if (ret != LDB_SUCCESS) {
350                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed postconnect for %s - %s",
351                            options.url, ldb_errstring(state->samdb));
352                 result = ISC_R_FAILURE;
353                 goto failed;
354         }
355
356         dn = ldb_get_default_basedn(state->samdb);
357         if (dn == NULL) {
358                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Unable to get basedn for %s - %s",
359                            options.url, ldb_errstring(state->samdb));
360                 result = ISC_R_FAILURE;
361                 goto failed;
362         }
363
364         state->log(ISC_LOG_INFO, "samba dlz_bind9: started for DN %s",
365                    ldb_dn_get_linearized(dn));
366
367         *dbdata = state;
368
369         talloc_free(tmp_ctx);
370         return ISC_R_SUCCESS;
371
372 failed:
373         talloc_free(state);
374         return result;
375 }
376
377 /*
378   shutdown the backend
379  */
380 _PUBLIC_ void dlz_destroy(void *driverarg, void *dbdata)
381 {
382         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
383         state->log(ISC_LOG_INFO, "samba dlz_bind9: shutting down");
384         talloc_free(state);
385 }
386
387
388 /*
389   see if we handle a given zone
390  */
391 _PUBLIC_ isc_result_t dlz_findzonedb(void *driverarg, void *dbdata, const char *name)
392 {
393         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
394         if (strcasecmp(lpcfg_dnsdomain(state->lp), name) == 0) {
395                 return ISC_R_SUCCESS;
396         }
397         return ISC_R_NOTFOUND;
398 }
399
400
401 /*
402   lookup one record
403  */
404 static isc_result_t dlz_lookup_types(struct dlz_bind9_data *state,
405                                      const char *zone, const char *name,
406                                      void *driverarg, dns_sdlzlookup_t *lookup,
407                                      const char **types)
408 {
409         struct ldb_dn *dn;
410         TALLOC_CTX *tmp_ctx = talloc_new(state);
411         const char *attrs[] = { "dnsRecord", NULL };
412         int ret, i;
413         struct ldb_result *res;
414         struct ldb_message_element *el;
415
416         dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
417         if (dn == NULL) {
418                 talloc_free(tmp_ctx);
419                 return ISC_R_NOMEMORY;
420         }
421
422         if (!ldb_dn_add_child_fmt(dn, "DC=%s,DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones",
423                                   name, zone)) {
424                 talloc_free(tmp_ctx);
425                 return ISC_R_NOMEMORY;
426         }
427
428         ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
429                          attrs, "objectClass=dnsNode");
430         if (ret != LDB_SUCCESS) {
431                 talloc_free(tmp_ctx);
432                 return ISC_R_NOTFOUND;
433         }
434
435         el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
436         if (el == NULL || el->num_values == 0) {
437                 state->log(ISC_LOG_INFO, "failed to find %s",
438                            ldb_dn_get_linearized(dn));
439                 talloc_free(tmp_ctx);
440                 return ISC_R_NOTFOUND;
441         }
442
443         for (i=0; i<el->num_values; i++) {
444                 struct dnsp_DnssrvRpcRecord rec;
445                 enum ndr_err_code ndr_err;
446                 isc_result_t result;
447
448                 ndr_err = ndr_pull_struct_blob(&el->values[i], tmp_ctx, &rec,
449                                                (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
450                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
451                         state->log(ISC_LOG_ERROR, "samba dlz_bind9: failed to parse dnsRecord for %s",
452                                    ldb_dn_get_linearized(dn));
453                         talloc_free(tmp_ctx);
454                         return ISC_R_FAILURE;
455                 }
456
457                 result = b9_putrr(state, lookup, &rec, types);
458                 if (result != ISC_R_SUCCESS) {
459                         talloc_free(tmp_ctx);
460                         return result;
461                 }
462         }
463
464         talloc_free(tmp_ctx);
465         return ISC_R_SUCCESS;
466 }
467
468 /*
469   lookup one record
470  */
471 _PUBLIC_ isc_result_t dlz_lookup(const char *zone, const char *name, void *driverarg,
472                                  void *dbdata, dns_sdlzlookup_t *lookup)
473 {
474         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
475         return dlz_lookup_types(state, zone, name, driverarg, lookup, NULL);
476 }
477
478
479 /*
480   see if a zone transfer is allowed
481  */
482 _PUBLIC_ isc_result_t dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name,
483                                        const char *client)
484 {
485         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
486
487         if (strcasecmp(lpcfg_dnsdomain(state->lp), name) == 0) {
488                 /* TODO: check an ACL here? client is the IP of the requester */
489                 state->log(ISC_LOG_INFO, "samba dlz_bind9: allowing zone transfer for '%s' by '%s'",
490                            name, client);
491                 return ISC_R_SUCCESS;
492         }
493         return ISC_R_NOTFOUND;
494 }
495
496 /*
497   perform a zone transfer
498  */
499 _PUBLIC_ isc_result_t dlz_allnodes(const char *zone, void *driverarg, void *dbdata,
500                                    dns_sdlzallnodes_t *allnodes)
501 {
502         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
503         const char *attrs[] = { "dnsRecord", NULL };
504         int ret, i, j;
505         struct ldb_dn *dn;
506         struct ldb_result *res;
507         TALLOC_CTX *tmp_ctx = talloc_new(state);
508
509
510         dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
511         if (dn == NULL) {
512                 talloc_free(tmp_ctx);
513                 return ISC_R_NOMEMORY;
514         }
515
516         if (!ldb_dn_add_child_fmt(dn, "DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones", zone)) {
517                 talloc_free(tmp_ctx);
518                 return ISC_R_NOMEMORY;
519         }
520
521         ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
522                          attrs, "objectClass=dnsNode");
523         if (ret != LDB_SUCCESS) {
524                 talloc_free(tmp_ctx);
525                 return ISC_R_NOTFOUND;
526         }
527
528         for (i=0; i<res->count; i++) {
529                 struct ldb_message_element *el;
530                 TALLOC_CTX *el_ctx = talloc_new(tmp_ctx);
531                 const char *rdn, *name;
532                 const struct ldb_val *v;
533
534                 el = ldb_msg_find_element(res->msgs[i], "dnsRecord");
535                 if (el == NULL || el->num_values == 0) {
536                         state->log(ISC_LOG_INFO, "failed to find dnsRecord for %s",
537                                    ldb_dn_get_linearized(dn));
538                         talloc_free(el_ctx);
539                         continue;
540                 }
541
542                 v = ldb_dn_get_rdn_val(res->msgs[i]->dn);
543                 if (v == NULL) {
544                         state->log(ISC_LOG_INFO, "failed to find RDN for %s",
545                                    ldb_dn_get_linearized(dn));
546                         talloc_free(el_ctx);
547                         continue;
548                 }
549
550                 rdn = talloc_strndup(el_ctx, (char *)v->data, v->length);
551                 if (rdn == NULL) {
552                         talloc_free(tmp_ctx);
553                         return ISC_R_NOMEMORY;
554                 }
555
556                 if (strcmp(rdn, "@") == 0) {
557                         name = zone;
558                 } else {
559                         name = talloc_asprintf(el_ctx, "%s.%s", rdn, zone);
560                 }
561                 if (name == NULL) {
562                         talloc_free(tmp_ctx);
563                         return ISC_R_NOMEMORY;
564                 }
565
566                 for (j=0; j<el->num_values; j++) {
567                         struct dnsp_DnssrvRpcRecord rec;
568                         enum ndr_err_code ndr_err;
569                         isc_result_t result;
570
571                         ndr_err = ndr_pull_struct_blob(&el->values[j], el_ctx, &rec,
572                                                        (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
573                         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
574                                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: failed to parse dnsRecord for %s",
575                                            ldb_dn_get_linearized(dn));
576                                 talloc_free(el_ctx);
577                                 continue;
578                         }
579
580                         result = b9_putnamedrr(state, allnodes, name, &rec);
581                         if (result != ISC_R_SUCCESS) {
582                                 talloc_free(el_ctx);
583                                 continue;
584                         }
585                 }
586         }
587
588         talloc_free(tmp_ctx);
589
590         return ISC_R_SUCCESS;
591 }