s4-dns: a dlz module for bind9
[samba.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 "dsdb/samdb/samdb.h"
26 #include "dsdb/common/util.h"
27 #include "auth/session.h"
28 #include "gen_ndr/ndr_dnsp.h"
29 #include "dlz_bind9.h"
30
31 struct dlz_bind9_data {
32         struct ldb_context *samdb;
33         struct tevent_context *ev_ctx;
34         struct loadparm_context *lp;
35
36         /* helper functions from the dlz_dlopen driver */
37         void (*log)(int level, const char *fmt, ...);
38         isc_result_t (*putrr)(dns_sdlzlookup_t *handle, const char *type,
39                               dns_ttl_t ttl, const char *data);
40         isc_result_t (*putnamedrr)(dns_sdlzlookup_t *handle, const char *name,
41                                    const char *type, dns_ttl_t ttl, const char *data);
42 };
43
44 /*
45   return the version of the API
46  */
47 _PUBLIC_ int dlz_version(void)
48 {
49         return DLZ_DLOPEN_VERSION;
50 }
51
52 /*
53    remember a helper function from the bind9 dlz_dlopen driver
54  */
55 static void b9_add_helper(struct dlz_bind9_data *state, const char *helper_name, void *ptr)
56 {
57         if (strcmp(helper_name, "log") == 0) {
58                 state->log = ptr;
59         }
60         if (strcmp(helper_name, "putrr") == 0) {
61                 state->putrr = ptr;
62         }
63         if (strcmp(helper_name, "putnamedrr") == 0) {
64                 state->putnamedrr = ptr;
65         }
66 }
67
68 /*
69   format a record for bind9
70  */
71 static bool b9_format(struct dlz_bind9_data *state,
72                       TALLOC_CTX *mem_ctx,
73                       struct dnsp_DnssrvRpcRecord *rec,
74                       const char **type, const char **data)
75 {
76         switch (rec->wType) {
77         case DNS_TYPE_A:
78                 *type = "a";
79                 *data = rec->data.ipv4;
80                 break;
81
82         case DNS_TYPE_AAAA:
83                 *type = "aaaa";
84                 *data = rec->data.ipv6;
85                 break;
86
87         case DNS_TYPE_CNAME:
88                 *type = "cname";
89                 *data = rec->data.cname;
90                 break;
91
92         case DNS_TYPE_TXT:
93                 *type = "txt";
94                 *data = rec->data.txt;
95                 break;
96
97         case DNS_TYPE_PTR:
98                 *type = "ptr";
99                 *data = rec->data.ptr;
100                 break;
101
102         case DNS_TYPE_SRV:
103                 *type = "srv";
104                 *data = talloc_asprintf(mem_ctx, "%u %u %u %s",
105                                         rec->data.srv.wPriority,
106                                         rec->data.srv.wWeight,
107                                         rec->data.srv.wPort,
108                                         rec->data.srv.nameTarget);
109                 break;
110
111         case DNS_TYPE_MX:
112                 *type = "mx";
113                 *data = talloc_asprintf(mem_ctx, "%u %s",
114                                         rec->data.srv.wPriority,
115                                         rec->data.srv.nameTarget);
116                 break;
117
118         case DNS_TYPE_HINFO:
119                 *type = "hinfo";
120                 *data = talloc_asprintf(mem_ctx, "%s %s",
121                                         rec->data.hinfo.cpu,
122                                         rec->data.hinfo.os);
123                 break;
124
125         case DNS_TYPE_NS:
126                 *type = "ns";
127                 *data = rec->data.ns;
128                 break;
129
130         case DNS_TYPE_SOA:
131                 *type = "soa";
132                 *data = talloc_asprintf(mem_ctx, "%s %s %u %u %u %u %u",
133                                         rec->data.soa.mname,
134                                         rec->data.soa.rname,
135                                         rec->data.soa.serial,
136                                         rec->data.soa.refresh,
137                                         rec->data.soa.retry,
138                                         rec->data.soa.expire,
139                                         rec->data.soa.minimum);
140                 break;
141
142         default:
143                 state->log(ISC_LOG_ERROR, "samba b9_putrr: unhandled record type %u",
144                            rec->wType);
145                 return false;
146         }
147
148         return true;
149 }
150
151 /*
152   send a resource recond to bind9
153  */
154 static isc_result_t b9_putrr(struct dlz_bind9_data *state,
155                              void *handle, struct dnsp_DnssrvRpcRecord *rec,
156                              const char **types)
157 {
158         isc_result_t result;
159         const char *type, *data;
160         TALLOC_CTX *tmp_ctx = talloc_new(state);
161
162         if (!b9_format(state, tmp_ctx, rec, &type, &data)) {
163                 return ISC_R_FAILURE;
164         }
165
166         if (data == NULL) {
167                 talloc_free(tmp_ctx);
168                 return ISC_R_NOMEMORY;
169         }
170
171         if (types) {
172                 int i;
173                 for (i=0; types[i]; i++) {
174                         if (strcmp(types[i], type) == 0) break;
175                 }
176                 if (types[i] == NULL) {
177                         /* skip it */
178                         return ISC_R_SUCCESS;
179                 }
180         }
181
182         /* FIXME: why does dlz insist on all TTL values being the same
183            for the same name? */
184         result = state->putrr(handle, type, /* rec->dwTtlSeconds */ 900, data);
185         if (result != ISC_R_SUCCESS) {
186                 state->log(ISC_LOG_ERROR, "Failed to put rr");
187         }
188         talloc_free(tmp_ctx);
189         return result;
190 }
191
192
193 /*
194   send a named resource recond to bind9
195  */
196 static isc_result_t b9_putnamedrr(struct dlz_bind9_data *state,
197                                   void *handle, const char *name,
198                                   struct dnsp_DnssrvRpcRecord *rec)
199 {
200         isc_result_t result;
201         const char *type, *data;
202         TALLOC_CTX *tmp_ctx = talloc_new(state);
203
204         if (!b9_format(state, tmp_ctx, rec, &type, &data)) {
205                 return ISC_R_FAILURE;
206         }
207
208         if (data == NULL) {
209                 talloc_free(tmp_ctx);
210                 return ISC_R_NOMEMORY;
211         }
212
213         /* FIXME: why does dlz insist on all TTL values being the same
214            for the same name? */
215         result = state->putnamedrr(handle, name, type, /* rec->dwTtlSeconds */ 900, data);
216         if (result != ISC_R_SUCCESS) {
217                 state->log(ISC_LOG_ERROR, "Failed to put named rr '%s'", name);
218         }
219         talloc_free(tmp_ctx);
220         return result;
221 }
222
223
224 /*
225   called to initialise the driver
226  */
227 _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
228                                  unsigned int argc, char *argv[],
229                                  void *driverarg, void **dbdata, ...)
230 {
231         struct dlz_bind9_data *state;
232         const char *helper_name;
233         va_list ap;
234         isc_result_t result;
235         const char *url;
236         TALLOC_CTX *tmp_ctx;
237         int ret;
238         struct ldb_dn *dn;
239
240         state = talloc_zero(NULL, struct dlz_bind9_data);
241         if (state == NULL) {
242                 return ISC_R_NOMEMORY;
243         }
244
245         tmp_ctx = talloc_new(state);
246
247         /* fill in the helper functions */
248         va_start(ap, dbdata);
249         while ((helper_name = va_arg(ap, const char *)) != NULL) {
250                 b9_add_helper(state, helper_name, va_arg(ap, void*));
251         }
252         va_end(ap);
253
254         state->lp = loadparm_init_global(true);
255         if (state->lp == NULL) {
256                 result = ISC_R_NOMEMORY;
257                 goto failed;
258         }
259
260         state->ev_ctx = tevent_context_init(state);
261         if (state->ev_ctx == NULL) {
262                 result = ISC_R_NOMEMORY;
263                 goto failed;
264         }
265
266         state->samdb = ldb_init(state, state->ev_ctx);
267         if (state->samdb == NULL) {
268                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed to create ldb");
269                 result = ISC_R_FAILURE;
270                 goto failed;
271         }
272
273         url = talloc_asprintf(tmp_ctx, "ldapi://%s",
274                               private_path(tmp_ctx, state->lp, "ldap_priv/ldapi"));
275         if (url == NULL) {
276                 result = ISC_R_NOMEMORY;
277                 goto failed;
278         }
279
280         ret = ldb_connect(state->samdb, url, 0, NULL);
281         if (ret == -1) {
282                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed to connect to %s - %s",
283                            url, ldb_errstring(state->samdb));
284                 result = ISC_R_FAILURE;
285                 goto failed;
286         }
287
288         dn = ldb_get_default_basedn(state->samdb);
289         if (dn == NULL) {
290                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: Unable to get basedn for %s - %s",
291                            url, ldb_errstring(state->samdb));
292                 result = ISC_R_FAILURE;
293                 goto failed;
294         }
295
296         state->log(ISC_LOG_INFO, "samba dlz_bind9: started for DN %s",
297                    ldb_dn_get_linearized(dn));
298
299         *dbdata = state;
300
301         talloc_free(tmp_ctx);
302         return ISC_R_SUCCESS;
303
304 failed:
305         talloc_free(state);
306         return result;
307 }
308
309 /*
310   shutdown the backend
311  */
312 _PUBLIC_ void dlz_destroy(void *driverarg, void *dbdata)
313 {
314         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
315         state->log(ISC_LOG_INFO, "samba dlz_bind9: shutting down");
316         talloc_free(state);
317 }
318
319
320 /*
321   see if we handle a given zone
322  */
323 _PUBLIC_ isc_result_t dlz_findzonedb(void *driverarg, void *dbdata, const char *name)
324 {
325         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
326         if (strcasecmp(lpcfg_dnsdomain(state->lp), name) == 0) {
327                 return ISC_R_SUCCESS;
328         }
329         return ISC_R_NOTFOUND;
330 }
331
332
333 /*
334   lookup one record
335  */
336 _PUBLIC_ isc_result_t dlz_lookup_types(struct dlz_bind9_data *state,
337                                        const char *zone, const char *name,
338                                        void *driverarg, dns_sdlzlookup_t *lookup,
339                                        const char **types)
340 {
341         struct ldb_dn *dn;
342         TALLOC_CTX *tmp_ctx = talloc_new(state);
343         const char *attrs[] = { "dnsRecord", NULL };
344         int ret, i;
345         struct ldb_result *res;
346         struct ldb_message_element *el;
347
348         dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
349         if (dn == NULL) {
350                 talloc_free(tmp_ctx);
351                 return ISC_R_NOMEMORY;
352         }
353
354         if (!ldb_dn_add_child_fmt(dn, "DC=%s,DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones",
355                                   name, zone)) {
356                 talloc_free(tmp_ctx);
357                 return ISC_R_NOMEMORY;
358         }
359
360         ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
361                          attrs, "objectClass=dnsNode");
362         if (ret != LDB_SUCCESS) {
363                 talloc_free(tmp_ctx);
364                 return ISC_R_NOTFOUND;
365         }
366
367         el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
368         if (el == NULL || el->num_values == 0) {
369                 state->log(ISC_LOG_INFO, "failed to find %s",
370                            ldb_dn_get_linearized(dn));
371                 talloc_free(tmp_ctx);
372                 return ISC_R_NOTFOUND;
373         }
374
375         for (i=0; i<el->num_values; i++) {
376                 struct dnsp_DnssrvRpcRecord rec;
377                 enum ndr_err_code ndr_err;
378                 isc_result_t result;
379
380                 ndr_err = ndr_pull_struct_blob(&el->values[i], tmp_ctx, &rec,
381                                                (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
382                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
383                         state->log(ISC_LOG_ERROR, "samba dlz_bind9: failed to parse dnsRecord for %s",
384                                    ldb_dn_get_linearized(dn));
385                         talloc_free(tmp_ctx);
386                         return ISC_R_FAILURE;
387                 }
388
389                 result = b9_putrr(state, lookup, &rec, types);
390                 if (result != ISC_R_SUCCESS) {
391                         talloc_free(tmp_ctx);
392                         return result;
393                 }
394         }
395
396         talloc_free(tmp_ctx);
397         return ISC_R_SUCCESS;
398 }
399
400 /*
401   lookup one record
402  */
403 _PUBLIC_ isc_result_t dlz_lookup(const char *zone, const char *name, void *driverarg,
404                                  void *dbdata, dns_sdlzlookup_t *lookup)
405 {
406         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
407         return dlz_lookup_types(state, zone, name, driverarg, lookup, NULL);
408 }
409
410
411 /*
412   see if a zone transfer is allowed
413  */
414 _PUBLIC_ isc_result_t dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name,
415                                        const char *client)
416 {
417         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
418
419         if (strcasecmp(lpcfg_dnsdomain(state->lp), name) == 0) {
420                 /* TODO: check an ACL here? client is the IP of the requester */
421                 state->log(ISC_LOG_INFO, "samba dlz_bind9: allowing zone transfer for '%s' by '%s'",
422                            name, client);
423                 return ISC_R_SUCCESS;
424         }
425         return ISC_R_NOTFOUND;
426 }
427
428 /*
429   perform a zone transfer
430  */
431 _PUBLIC_ isc_result_t dlz_allnodes(const char *zone, void *driverarg, void *dbdata,
432                                    dns_sdlzallnodes_t *allnodes)
433 {
434         struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
435         const char *attrs[] = { "dnsRecord", NULL };
436         int ret, i, j;
437         struct ldb_dn *dn;
438         struct ldb_result *res;
439         TALLOC_CTX *tmp_ctx = talloc_new(state);
440
441
442         dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
443         if (dn == NULL) {
444                 talloc_free(tmp_ctx);
445                 return ISC_R_NOMEMORY;
446         }
447
448         if (!ldb_dn_add_child_fmt(dn, "DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones", zone)) {
449                 talloc_free(tmp_ctx);
450                 return ISC_R_NOMEMORY;
451         }
452
453         ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
454                          attrs, "objectClass=dnsNode");
455         if (ret != LDB_SUCCESS) {
456                 talloc_free(tmp_ctx);
457                 return ISC_R_NOTFOUND;
458         }
459
460         for (i=0; i<res->count; i++) {
461                 struct ldb_message_element *el;
462                 TALLOC_CTX *el_ctx = talloc_new(tmp_ctx);
463                 const char *rdn, *name;
464                 const struct ldb_val *v;
465
466                 el = ldb_msg_find_element(res->msgs[i], "dnsRecord");
467                 if (el == NULL || el->num_values == 0) {
468                         state->log(ISC_LOG_INFO, "failed to find dnsRecord for %s",
469                                    ldb_dn_get_linearized(dn));
470                         talloc_free(el_ctx);
471                         continue;
472                 }
473
474                 v = ldb_dn_get_rdn_val(res->msgs[i]->dn);
475                 if (v == NULL) {
476                         state->log(ISC_LOG_INFO, "failed to find RDN for %s",
477                                    ldb_dn_get_linearized(dn));
478                         talloc_free(el_ctx);
479                         continue;
480                 }
481
482                 rdn = talloc_strndup(el_ctx, (char *)v->data, v->length);
483                 if (rdn == NULL) {
484                         talloc_free(tmp_ctx);
485                         return ISC_R_NOMEMORY;
486                 }
487
488                 if (strcmp(rdn, "@") == 0) {
489                         name = zone;
490                 } else {
491                         name = talloc_asprintf(el_ctx, "%s.%s", rdn, zone);
492                 }
493                 if (name == NULL) {
494                         talloc_free(tmp_ctx);
495                         return ISC_R_NOMEMORY;
496                 }
497
498                 for (j=0; j<el->num_values; j++) {
499                         struct dnsp_DnssrvRpcRecord rec;
500                         enum ndr_err_code ndr_err;
501                         isc_result_t result;
502
503                         ndr_err = ndr_pull_struct_blob(&el->values[j], el_ctx, &rec,
504                                                        (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
505                         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
506                                 state->log(ISC_LOG_ERROR, "samba dlz_bind9: failed to parse dnsRecord for %s",
507                                            ldb_dn_get_linearized(dn));
508                                 talloc_free(el_ctx);
509                                 continue;
510                         }
511
512                         result = b9_putnamedrr(state, allnodes, name, &rec);
513                         if (result != ISC_R_SUCCESS) {
514                                 talloc_free(el_ctx);
515                                 continue;
516                         }
517                 }
518         }
519
520         talloc_free(tmp_ctx);
521
522         return ISC_R_SUCCESS;
523 }