1 /* $NetBSD: dlz_perl_driver.c,v 1.1.1.3 2014/12/10 03:34:31 christos Exp $ */
4 * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the
8 * above copyright notice and this permission notice appear in all
11 * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
12 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
13 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
14 * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
16 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
17 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
18 * USE OR PERFORMANCE OF THIS SOFTWARE.
20 * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
21 * conceived and contributed by Rob Butler.
23 * Permission to use, copy, modify, and distribute this software for any
24 * purpose with or without fee is hereby granted, provided that the
25 * above copyright notice and this permission notice appear in all
28 * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
29 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
31 * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
32 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
33 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
34 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
35 * USE OR PERFORMANCE OF THIS SOFTWARE.
39 * Copyright (C) 1999-2001 Internet Software Consortium.
41 * Permission to use, copy, modify, and distribute this software for any
42 * purpose with or without fee is hereby granted, provided that the above
43 * copyright notice and this permission notice appear in all copies.
45 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
46 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
48 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
49 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
50 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
51 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
52 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
56 * Copyright (C) 2009-2012 John Eaglesham
58 * Permission to use, copy, modify, and distribute this software for any
59 * purpose with or without fee is hereby granted, provided that the above
60 * copyright notice and this permission notice appear in all copies.
62 * THE SOFTWARE IS PROVIDED "AS IS" AND JOHN EAGLESHAM
63 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
64 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
65 * JOHN EAGLESHAM BE LIABLE FOR ANY SPECIAL, DIRECT,
66 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
67 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
68 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
69 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
80 #include <dlz_minimal.h>
82 #include "dlz_perl_driver.h"
84 /* Enable debug logging? */
86 #define carp(...) cd->log(ISC_LOG_INFO, __VA_ARGS__);
92 /* This is a pretty terrible work-around for handling HUP/rndc reconfig, but
93 * the way BIND/DLZ handles reloads causes it to create a second back end
94 * before removing the first. In the case of a single global interpreter,
95 * serious problems arise. We can hack around this, but it's much better to do
96 * it properly and link against a perl compiled with multiplicity. */
97 static PerlInterpreter
*global_perl
= NULL
;
98 static int global_perl_dont_free
= 0;
101 typedef struct config_data
{
102 PerlInterpreter
*perl
;
106 /* Functions given to us by bind9 */
108 dns_sdlz_putrr_t
*putrr
;
109 dns_sdlz_putnamedrr_t
*putnamedrr
;
110 dns_dlz_writeablezone_t
*writeable_zone
;
113 /* Note, this code generates warnings due to lost type qualifiers. This code
114 * is (almost) verbatim from perlembed, and is known to work correctly despite
117 EXTERN_C
void xs_init (pTHX
);
118 EXTERN_C
void boot_DynaLoader (pTHX_ CV
* cv
);
119 EXTERN_C
void boot_DLZ_Perl__clientinfo (pTHX_ CV
* cv
);
120 EXTERN_C
void boot_DLZ_Perl (pTHX_ CV
* cv
);
124 char *file
= __FILE__
;
127 /* DynaLoader is a special case */
128 newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader
, file
);
129 newXS("DLZ_Perl::clientinfo::bootstrap", boot_DLZ_Perl__clientinfo
, file
);
130 newXS("DLZ_Perl::bootstrap", boot_DLZ_Perl
, file
);
138 * remember a helper function, from the bind9 dlz_dlopen driver
140 static void b9_add_helper(config_data_t
*state
,
141 const char *helper_name
, void *ptr
)
143 if (strcmp(helper_name
, "log") == 0)
145 if (strcmp(helper_name
, "putrr") == 0)
147 if (strcmp(helper_name
, "putnamedrr") == 0)
148 state
->putnamedrr
= ptr
;
149 if (strcmp(helper_name
, "writeable_zone") == 0)
150 state
->writeable_zone
= ptr
;
153 int dlz_version(unsigned int *flags
) {
154 return DLZ_DLOPEN_VERSION
;
157 isc_result_t
dlz_allnodes(const char *zone
, void *dbdata
,
158 dns_sdlzallnodes_t
*allnodes
)
160 config_data_t
*cd
= (config_data_t
*) dbdata
;
169 PerlInterpreter
*my_perl
= cd
->perl
;
173 PERL_SET_CONTEXT(cd
->perl
);
178 XPUSHs(cd
->perl_class
);
179 XPUSHs(sv_2mortal(newSVpv(zone
, 0)));
182 carp("DLZ Perl: Calling allnodes for zone %s", zone
);
183 rrcount
= call_method("allnodes", G_ARRAY
|G_EVAL
);
184 carp("DLZ Perl: Call to allnodes returned rrcount of %i", rrcount
);
190 cd
->log(ISC_LOG_ERROR
, "DLZ Perl: allnodes for zone %s died in eval: %s", zone
, SvPV_nolen(ERRSV
));
191 retval
= ISC_R_FAILURE
;
192 goto CLEAN_UP_AND_RETURN
;
196 retval
= ISC_R_NOTFOUND
;
197 goto CLEAN_UP_AND_RETURN
;
200 retval
= ISC_R_SUCCESS
;
202 while (r
++ < rrcount
) {
205 (!SvROK(record_ref
)) ||
206 (SvTYPE(SvRV(record_ref
)) != SVt_PVAV
)
208 cd
->log(ISC_LOG_ERROR
,
209 "DLZ Perl: allnodes for zone %s "
210 "returned an invalid value "
211 "(expected array of arrayrefs)",
213 retval
= ISC_R_FAILURE
;
217 record_ref
= SvRV(record_ref
);
219 rr_name
= av_fetch((AV
*) record_ref
, 0, 0);
220 rr_type
= av_fetch((AV
*) record_ref
, 1, 0);
221 rr_ttl
= av_fetch((AV
*) record_ref
, 2, 0);
222 rr_data
= av_fetch((AV
*) record_ref
, 3, 0);
224 if (rr_name
== NULL
|| rr_type
== NULL
||
225 rr_ttl
== NULL
|| rr_data
== NULL
)
227 cd
->log(ISC_LOG_ERROR
,
228 "DLZ Perl: allnodes for zone %s "
229 "returned an array that was missing data",
231 retval
= ISC_R_FAILURE
;
235 carp("DLZ Perl: Got record %s/%s = %s",
236 SvPV_nolen(*rr_name
), SvPV_nolen(*rr_type
),
237 SvPV_nolen(*rr_data
));
238 retval
= cd
->putnamedrr(allnodes
,
239 SvPV_nolen(*rr_name
),
240 SvPV_nolen(*rr_type
),
241 SvIV(*rr_ttl
), SvPV_nolen(*rr_data
));
242 if (retval
!= ISC_R_SUCCESS
) {
243 cd
->log(ISC_LOG_ERROR
,
244 "DLZ Perl: putnamedrr in allnodes "
245 "for zone %s failed with code %i "
246 "(did lookup return invalid record data?)",
257 carp("DLZ Perl: Returning from allnodes, r = %i, retval = %i",
264 dlz_allowzonexfr(void *dbdata
, const char *name
, const char *client
) {
265 config_data_t
*cd
= (config_data_t
*) dbdata
;
269 PerlInterpreter
*my_perl
= cd
->perl
;
273 PERL_SET_CONTEXT(cd
->perl
);
278 XPUSHs(cd
->perl_class
);
279 XPUSHs(sv_2mortal(newSVpv(name
, 0)));
280 XPUSHs(sv_2mortal(newSVpv(client
, 0)));
283 r
= call_method("allowzonexfr", G_SCALAR
|G_EVAL
);
288 * On error there's an undef at the top of the stack. Pop
289 * it away so we don't leave junk on the stack for the next
293 cd
->log(ISC_LOG_ERROR
,
294 "DLZ Perl: allowzonexfr died in eval: %s",
296 retval
= ISC_R_FAILURE
;
298 /* Client returned nothing -- zone not found. */
299 retval
= ISC_R_NOTFOUND
;
301 /* Once again, clean out the stack when possible. */
303 cd
->log(ISC_LOG_ERROR
,
304 "DLZ Perl: allowzonexfr returned too many parameters!");
305 retval
= ISC_R_FAILURE
;
308 * Client returned true/false -- we're authoritative for
313 retval
= ISC_R_SUCCESS
;
315 retval
= ISC_R_NOPERM
;
324 #if DLZ_DLOPEN_VERSION < 3
326 dlz_findzonedb(void *dbdata
, const char *name
)
329 dlz_findzonedb(void *dbdata
, const char *name
,
330 dns_clientinfomethods_t
*methods
,
331 dns_clientinfo_t
*clientinfo
)
334 config_data_t
*cd
= (config_data_t
*) dbdata
;
338 PerlInterpreter
*my_perl
= cd
->perl
;
341 #if DLZ_DLOPEN_VERSION >= 3
347 carp("DLZ Perl: findzone looking for '%s'", name
);
349 PERL_SET_CONTEXT(cd
->perl
);
354 XPUSHs(cd
->perl_class
);
355 XPUSHs(sv_2mortal(newSVpv(name
, 0)));
358 r
= call_method("findzone", G_SCALAR
|G_EVAL
);
363 * On error there's an undef at the top of the stack. Pop
364 * it away so we don't leave junk on the stack for the next
368 cd
->log(ISC_LOG_ERROR
,
369 "DLZ Perl: findzone died in eval: %s",
371 retval
= ISC_R_FAILURE
;
373 retval
= ISC_R_FAILURE
;
375 /* Once again, clean out the stack when possible. */
377 cd
->log(ISC_LOG_ERROR
,
378 "DLZ Perl: findzone returned too many parameters!");
379 retval
= ISC_R_FAILURE
;
383 retval
= ISC_R_SUCCESS
;
385 retval
= ISC_R_NOTFOUND
;
395 #if DLZ_DLOPEN_VERSION == 1
397 dlz_lookup(const char *zone
, const char *name
,
398 void *dbdata
, dns_sdlzlookup_t
*lookup
)
401 dlz_lookup(const char *zone
, const char *name
,
402 void *dbdata
, dns_sdlzlookup_t
*lookup
,
403 dns_clientinfomethods_t
*methods
,
404 dns_clientinfo_t
*clientinfo
)
408 config_data_t
*cd
= (config_data_t
*) dbdata
;
410 dlz_perl_clientinfo_opaque opaque
;
416 PerlInterpreter
*my_perl
= cd
->perl
;
419 #if DLZ_DLOPEN_VERSION >= 2
425 PERL_SET_CONTEXT(cd
->perl
);
429 opaque
.methods
= methods
;
430 opaque
.clientinfo
= clientinfo
;
433 XPUSHs(cd
->perl_class
);
434 XPUSHs(sv_2mortal(newSVpv(name
, 0)));
435 XPUSHs(sv_2mortal(newSVpv(zone
, 0)));
436 XPUSHs(sv_2mortal(newSViv((IV
)&opaque
)));
439 carp("DLZ Perl: Searching for name %s in zone %s", name
, zone
);
440 rrcount
= call_method("lookup", G_ARRAY
|G_EVAL
);
441 carp("DLZ Perl: Call to lookup returned %i", rrcount
);
447 cd
->log(ISC_LOG_ERROR
, "DLZ Perl: lookup died in eval: %s",
449 retval
= ISC_R_FAILURE
;
450 goto CLEAN_UP_AND_RETURN
;
454 retval
= ISC_R_NOTFOUND
;
455 goto CLEAN_UP_AND_RETURN
;
458 retval
= ISC_R_SUCCESS
;
460 while (r
++ < rrcount
) {
462 if ((!SvROK(record_ref
)) ||
463 (SvTYPE(SvRV(record_ref
)) != SVt_PVAV
))
465 cd
->log(ISC_LOG_ERROR
,
466 "DLZ Perl: lookup returned an "
467 "invalid value (expected array of arrayrefs)!");
468 retval
= ISC_R_FAILURE
;
472 record_ref
= SvRV(record_ref
);
474 rr_type
= av_fetch((AV
*) record_ref
, 0, 0);
475 rr_ttl
= av_fetch((AV
*) record_ref
, 1, 0);
476 rr_data
= av_fetch((AV
*) record_ref
, 2, 0);
478 if (rr_type
== NULL
|| rr_ttl
== NULL
|| rr_data
== NULL
) {
479 cd
->log(ISC_LOG_ERROR
,
480 "DLZ Perl: lookup for record %s in "
481 "zone %s returned an array that was "
482 "missing data", name
, zone
);
483 retval
= ISC_R_FAILURE
;
487 carp("DLZ Perl: Got record %s = %s",
488 SvPV_nolen(*rr_type
), SvPV_nolen(*rr_data
));
489 retval
= cd
->putrr(lookup
, SvPV_nolen(*rr_type
),
490 SvIV(*rr_ttl
), SvPV_nolen(*rr_data
));
492 if (retval
!= ISC_R_SUCCESS
) {
493 cd
->log(ISC_LOG_ERROR
,
494 "DLZ Perl: putrr for lookup of %s in "
495 "zone %s failed with code %i "
496 "(did lookup return invalid record data?)",
507 carp("DLZ Perl: Returning from lookup, r = %i, retval = %i", r
, retval
);
514 missing_perl_method(const char *perl_class_name
, PerlInterpreter
*my_perl
)
516 missing_perl_method(const char *perl_class_name
)
519 const int BUF_LEN
= 64; /* Should be big enough, right? hah */
520 char full_name
[BUF_LEN
];
521 const char *methods
[] = { "new", "findzone", "lookup", NULL
};
524 while( methods
[i
] != NULL
) {
525 snprintf(full_name
, BUF_LEN
, "%s::%s",
526 perl_class_name
, methods
[i
]);
528 if (get_cv(full_name
, 0) == NULL
) {
538 dlz_create(const char *dlzname
, unsigned int argc
, char *argv
[],
542 char *init_args
[] = { NULL
, NULL
};
543 char *perlrun
[] = { "", NULL
, "dlz perl", NULL
};
544 char *perl_class_name
;
547 const char *helper_name
;
548 const char *missing_method_name
;
549 char *call_argv_args
= NULL
;
551 PerlInterpreter
*my_perl
;
554 cd
= malloc(sizeof(config_data_t
));
556 return (ISC_R_NOMEMORY
);
558 memset(cd
, 0, sizeof(config_data_t
));
560 /* fill in the helper functions */
561 va_start(ap
, dbdata
);
562 while ((helper_name
= va_arg(ap
, const char *)) != NULL
) {
563 b9_add_helper(cd
, helper_name
, va_arg(ap
, void*));
568 cd
->log(ISC_LOG_ERROR
,
569 "DLZ Perl '%s': Missing script argument.",
571 return (ISC_R_FAILURE
);
575 cd
->log(ISC_LOG_ERROR
,
576 "DLZ Perl '%s': Missing class name argument.",
578 return (ISC_R_FAILURE
);
580 perl_class_name
= argv
[2];
582 cd
->log(ISC_LOG_INFO
, "DLZ Perl '%s': Loading '%s' from location '%s'",
583 dlzname
, perl_class_name
, argv
[1], argc
);
588 * PERL_SET_CONTEXT not needed here as we're guaranteed to
589 * have an implicit context thanks to an undefined
592 PL_perl_destruct_level
= 1;
593 perl_destruct(global_perl
);
594 perl_free(global_perl
);
596 global_perl_dont_free
= 1;
600 cd
->perl
= perl_alloc();
601 if (cd
->perl
== NULL
) {
603 return (ISC_R_FAILURE
);
608 PERL_SET_CONTEXT(cd
->perl
);
611 * We will re-create the interpreter during an rndc reconfig, so we
612 * must set this variable per perlembed in order to insure we can
613 * clean up Perl at a later time.
615 PL_perl_destruct_level
= 1;
616 perl_construct(cd
->perl
);
617 PL_exit_flags
|= PERL_EXIT_DESTRUCT_END
;
618 /* Prevent crashes from clients writing to $0 */
621 cd
->perl_source
= strdup(argv
[1]);
622 if (cd
->perl_source
== NULL
) {
624 return (ISC_R_NOMEMORY
);
627 perlrun
[1] = cd
->perl_source
;
628 if (perl_parse(cd
->perl
, xs_init
, 3, perlrun
, (char **)NULL
)) {
629 cd
->log(ISC_LOG_ERROR
,
630 "DLZ Perl '%s': Failed to parse Perl script, aborting",
632 goto CLEAN_UP_PERL_AND_FAIL
;
635 /* Let Perl know about our callbacks. */
636 call_argv("DLZ_Perl::clientinfo::bootstrap",
637 G_DISCARD
|G_NOARGS
, &call_argv_args
);
638 call_argv("DLZ_Perl::bootstrap",
639 G_DISCARD
|G_NOARGS
, &call_argv_args
);
642 * Run the script. We don't really need to do this since we have
643 * the init callback, but there's not really a downside either.
645 if (perl_run(cd
->perl
)) {
646 cd
->log(ISC_LOG_ERROR
,
647 "DLZ Perl '%s': Script exited with an error, aborting",
649 goto CLEAN_UP_PERL_AND_FAIL
;
653 if (missing_method_name
= missing_perl_method(perl_class_name
, my_perl
))
655 if (missing_method_name
= missing_perl_method(perl_class_name
))
658 cd
->log(ISC_LOG_ERROR
,
659 "DLZ Perl '%s': Missing required function '%s', "
660 "aborting", dlzname
, missing_method_name
);
661 goto CLEAN_UP_PERL_AND_FAIL
;
669 XPUSHs(sv_2mortal(newSVpv(perl_class_name
, 0)));
671 /* Build flattened hash of config info. */
672 XPUSHs(sv_2mortal(newSVpv("log_context", 0)));
673 XPUSHs(sv_2mortal(newSViv((IV
)cd
->log
)));
675 /* Argument to pass to new? */
677 XPUSHs(sv_2mortal(newSVpv("argv", 0)));
678 XPUSHs(sv_2mortal(newSVpv(argv
[3], 0)));
683 r
= call_method("new", G_EVAL
|G_SCALAR
);
687 if (r
) cd
->perl_class
= SvREFCNT_inc(POPs
);
695 cd
->log(ISC_LOG_ERROR
,
696 "DLZ Perl '%s': new died in eval: %s",
697 dlzname
, SvPV_nolen(ERRSV
));
698 goto CLEAN_UP_PERL_AND_FAIL
;
701 if (!r
|| !sv_isobject(cd
->perl_class
)) {
702 cd
->log(ISC_LOG_ERROR
,
703 "DLZ Perl '%s': new failed to return a blessed object",
705 goto CLEAN_UP_PERL_AND_FAIL
;
711 global_perl
= cd
->perl
;
713 return (ISC_R_SUCCESS
);
715 CLEAN_UP_PERL_AND_FAIL
:
716 PL_perl_destruct_level
= 1;
717 perl_destruct(cd
->perl
);
719 free(cd
->perl_source
);
721 return (ISC_R_FAILURE
);
724 void dlz_destroy(void *dbdata
) {
725 config_data_t
*cd
= (config_data_t
*) dbdata
;
727 PerlInterpreter
*my_perl
= cd
->perl
;
730 cd
->log(ISC_LOG_INFO
, "DLZ Perl: Unloading driver.");
733 if (!global_perl_dont_free
) {
735 PERL_SET_CONTEXT(cd
->perl
);
736 PL_perl_destruct_level
= 1;
737 perl_destruct(cd
->perl
);
740 global_perl_dont_free
= 0;
745 free(cd
->perl_source
);