1/*
2 * Copyright (C) 2004, 2007, 2012  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000, 2001  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id$ */
19
20/*
21 * A simple database driver that calls a Tcl procedure to define
22 * the contents of the DNS namespace.  The procedure is loaded
23 * from the file lookup.tcl; look at the comments there for
24 * more information.
25 */
26
27#include <config.h>
28
29#include <string.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <sys/stat.h>
33
34#include <isc/mem.h>
35#include <isc/print.h>
36#include <isc/result.h>
37#include <isc/util.h>
38
39#include <dns/log.h>
40#include <dns/sdb.h>
41
42#include <named/globals.h>
43
44#include <tcl.h>
45
46#include <tcldb.h>
47
48#define CHECK(op)						\
49	do { result = (op);					\
50		if (result != ISC_R_SUCCESS) return (result);	\
51	} while (0)
52
53typedef struct tcldb_driver {
54	isc_mem_t *mctx;
55	Tcl_Interp *interp;
56} tcldb_driver_t;
57
58static tcldb_driver_t *the_driver = NULL;
59
60static dns_sdbimplementation_t *tcldb = NULL;
61
62static isc_result_t
63tcldb_driver_create(isc_mem_t *mctx, tcldb_driver_t **driverp) {
64	int tclres;
65	isc_result_t result = ISC_R_SUCCESS;
66	tcldb_driver_t *driver = isc_mem_get(mctx, sizeof(tcldb_driver_t));
67	if (driver == NULL)
68		return (ISC_R_NOMEMORY);
69	driver->mctx = mctx;
70	driver->interp = Tcl_CreateInterp();
71
72	tclres = Tcl_EvalFile(driver->interp, (char *) "lookup.tcl");
73	if (tclres != TCL_OK) {
74		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
75			      DNS_LOGMODULE_SDB, ISC_LOG_ERROR,
76			      "initializing tcldb: "
77			      "loading 'lookup.tcl' failed: %s",
78			      driver->interp->result);
79		result = ISC_R_FAILURE;
80		goto cleanup;
81	}
82	*driverp = driver;
83	return (ISC_R_SUCCESS);
84
85 cleanup:
86	isc_mem_put(mctx, driver, sizeof(tcldb_driver_t));
87	return (result);
88
89}
90
91static void
92tcldb_driver_destroy(tcldb_driver_t **driverp) {
93	tcldb_driver_t *driver = *driverp;
94	Tcl_DeleteInterp(driver->interp);
95	isc_mem_put(driver->mctx, driver, sizeof(tcldb_driver_t));
96}
97
98/*
99 * Perform a lookup, by invoking the Tcl procedure "lookup".
100 */
101static isc_result_t
102tcldb_lookup(const char *zone, const char *name, void *dbdata,
103	      dns_sdblookup_t *lookup)
104{
105	isc_result_t result = ISC_R_SUCCESS;
106	int tclres;
107	int rrc;	/* RR count */
108	char **rrv;	/* RR vector */
109	int i;
110	char *cmdv[3];
111	char *cmd;
112
113	tcldb_driver_t *driver = (tcldb_driver_t *) dbdata;
114
115	cmdv[0] = "lookup";
116	cmdv[1] = zone;
117	cmdv[2] = name;
118	cmd = Tcl_Merge(3, cmdv);
119	tclres = Tcl_Eval(driver->interp, cmd);
120	Tcl_Free(cmd);
121
122	if (tclres != TCL_OK) {
123		isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
124			      DNS_LOGMODULE_SDB, ISC_LOG_ERROR,
125			      "zone '%s': tcl lookup function failed: %s",
126			      zone, driver->interp->result);
127		return (ISC_R_FAILURE);
128	}
129
130	if (strcmp(driver->interp->result, "NXDOMAIN") == 0) {
131		result = ISC_R_NOTFOUND;
132		goto fail;
133	}
134
135	tclres = Tcl_SplitList(driver->interp, driver->interp->result,
136			       &rrc, &rrv);
137	if (tclres != TCL_OK)
138		goto malformed;
139
140	for (i = 0; i < rrc; i++) {
141		isc_result_t tmpres;
142		int fieldc;	/* Field count */
143		char **fieldv;	/* Field vector */
144		tclres = Tcl_SplitList(driver->interp, rrv[i],
145				       &fieldc, &fieldv);
146		if (tclres != TCL_OK) {
147			tmpres = ISC_R_FAILURE;
148			goto failrr;
149		}
150		if (fieldc != 3)
151			goto malformed;
152		tmpres = dns_sdb_putrr(lookup, fieldv[0], atoi(fieldv[1]),
153				       fieldv[2]);
154		Tcl_Free((char *) fieldv);
155	failrr:
156		if (tmpres != ISC_R_SUCCESS)
157			result = tmpres;
158	}
159	Tcl_Free((char *) rrv);
160	if (result == ISC_R_SUCCESS)
161		return (result);
162
163 malformed:
164	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
165		      DNS_LOGMODULE_SDB, ISC_LOG_ERROR,
166		      "zone '%s': "
167		      "malformed return value from tcl lookup function: %s",
168		      zone, driver->interp->result);
169	result = ISC_R_FAILURE;
170 fail:
171	return (result);
172}
173
174/*
175 * Set up per-zone state.  In our case, the database arguments of the
176 * zone are collected into a Tcl list and assigned to an element of
177 * the global array "dbargs".
178 */
179static isc_result_t
180tcldb_create(const char *zone, int argc, char **argv,
181	     void *driverdata, void **dbdata)
182{
183	tcldb_driver_t *driver = (tcldb_driver_t *) driverdata;
184
185	char *list = Tcl_Merge(argc, argv);
186
187	Tcl_SetVar2(driver->interp, (char *) "dbargs", (char *) zone, list, 0);
188
189	Tcl_Free(list);
190
191	*dbdata = driverdata;
192
193	return (ISC_R_SUCCESS);
194}
195
196/*
197 * This driver does not support zone transfer, so allnodes() is NULL.
198 */
199static dns_sdbmethods_t tcldb_methods = {
200	tcldb_lookup,
201	NULL, /* authority */
202	NULL, /* allnodes */
203	tcldb_create,
204	NULL /* destroy */
205};
206
207/*
208 * Initialize the tcldb driver.
209 */
210isc_result_t
211tcldb_init(void) {
212	isc_result_t result;
213	int flags = DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA;
214
215	result = tcldb_driver_create(ns_g_mctx, &the_driver);
216	if (result != ISC_R_SUCCESS)
217		return (result);
218
219	return (dns_sdb_register("tcl", &tcldb_methods, the_driver, flags,
220				 ns_g_mctx, &tcldb));
221}
222
223/*
224 * Wrapper around dns_sdb_unregister().
225 */
226void
227tcldb_clear(void) {
228	if (tcldb != NULL)
229		dns_sdb_unregister(&tcldb);
230	if (the_driver != NULL)
231		tcldb_driver_destroy(&the_driver);
232}
233