xref: /minix3/external/bsd/bind/dist/contrib/sdb/pgsql/pgsqldb.c (revision 00b67f09dd46474d133c95011a48590a8e8f94c7)
1 /*	$NetBSD: pgsqldb.c,v 1.4 2014/12/10 04:37:57 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2004, 2007, 2011, 2014  Internet Systems Consortium, Inc. ("ISC")
5  * Copyright (C) 2000, 2001  Internet Software Consortium.
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /* Id: pgsqldb.c,v 1.17 2011/10/11 23:46:45 tbox Exp  */
21 
22 #include <config.h>
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 
28 #include <pgsql/libpq-fe.h>
29 
30 #include <isc/mem.h>
31 #include <isc/print.h>
32 #include <isc/result.h>
33 #include <isc/util.h>
34 
35 #include <dns/sdb.h>
36 #include <dns/result.h>
37 
38 #include <named/globals.h>
39 
40 #include "pgsqldb.h"
41 
42 /*
43  * A simple database driver that interfaces to a PostgreSQL database.  This
44  * is not complete, and not designed for general use.  It opens one
45  * connection to the database per zone, which is inefficient.  It also may
46  * not handle quoting correctly.
47  *
48  * The table must contain the fields "name", "rdtype", and "rdata", and
49  * is expected to contain a properly constructed zone.  The program "zonetodb"
50  * creates such a table.
51  */
52 
53 static dns_sdbimplementation_t *pgsqldb = NULL;
54 
55 struct dbinfo {
56 	PGconn *conn;
57 	char *database;
58 	char *table;
59 	char *host;
60 	char *user;
61 	char *passwd;
62 };
63 
64 static void
65 pgsqldb_destroy(const char *zone, void *driverdata, void **dbdata);
66 
67 /*
68  * Canonicalize a string before writing it to the database.
69  * "dest" must be an array of at least size 2*strlen(source) + 1.
70  */
71 static void
quotestring(const char * source,char * dest)72 quotestring(const char *source, char *dest) {
73 	while (*source != 0) {
74 		if (*source == '\'')
75 			*dest++ = '\'';
76 		/* SQL doesn't treat \ as special, but PostgreSQL does */
77 		else if (*source == '\\')
78 			*dest++ = '\\';
79 		*dest++ = *source++;
80 	}
81 	*dest++ = 0;
82 }
83 
84 /*
85  * Connect to the database.
86  */
87 static isc_result_t
db_connect(struct dbinfo * dbi)88 db_connect(struct dbinfo *dbi) {
89 	dbi->conn = PQsetdbLogin(dbi->host, NULL, NULL, NULL, dbi->database,
90 				 dbi->user, dbi->passwd);
91 
92 	if (PQstatus(dbi->conn) == CONNECTION_OK)
93 		return (ISC_R_SUCCESS);
94 	else
95 		return (ISC_R_FAILURE);
96 }
97 
98 /*
99  * Check to see if the connection is still valid.  If not, attempt to
100  * reconnect.
101  */
102 static isc_result_t
maybe_reconnect(struct dbinfo * dbi)103 maybe_reconnect(struct dbinfo *dbi) {
104 	if (PQstatus(dbi->conn) == CONNECTION_OK)
105 		return (ISC_R_SUCCESS);
106 
107 	return (db_connect(dbi));
108 }
109 
110 /*
111  * This database operates on absolute names.
112  *
113  * Queries are converted into SQL queries and issued synchronously.  Errors
114  * are handled really badly.
115  */
116 #ifdef DNS_CLIENTINFO_VERSION
117 static isc_result_t
pgsqldb_lookup(const char * zone,const char * name,void * dbdata,dns_sdblookup_t * lookup,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)118 pgsqldb_lookup(const char *zone, const char *name, void *dbdata,
119 	       dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods,
120 	       dns_clientinfo_t *clientinfo)
121 #else
122 static isc_result_t
123 pgsqldb_lookup(const char *zone, const char *name, void *dbdata,
124 	       dns_sdblookup_t *lookup)
125 #endif /* DNS_CLIENTINFO_VERSION */
126 {
127 	isc_result_t result;
128 	struct dbinfo *dbi = dbdata;
129 	PGresult *res;
130 	char str[1500];
131 	char *canonname;
132 	int i;
133 
134 	UNUSED(zone);
135 #ifdef DNS_CLIENTINFO_VERSION
136 	UNUSED(methods);
137 	UNUSED(clientinfo);
138 #endif /* DNS_CLIENTINFO_VERSION */
139 
140 	canonname = isc_mem_get(ns_g_mctx, strlen(name) * 2 + 1);
141 	if (canonname == NULL)
142 		return (ISC_R_NOMEMORY);
143 	quotestring(name, canonname);
144 	snprintf(str, sizeof(str),
145 		 "SELECT TTL,RDTYPE,RDATA FROM \"%s\" WHERE "
146 		 "lower(NAME) = lower('%s')", dbi->table, canonname);
147 	isc_mem_put(ns_g_mctx, canonname, strlen(name) * 2 + 1);
148 
149 	result = maybe_reconnect(dbi);
150 	if (result != ISC_R_SUCCESS)
151 		return (result);
152 
153 	res = PQexec(dbi->conn, str);
154 	if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) {
155 		PQclear(res);
156 		return (ISC_R_FAILURE);
157 	}
158 	if (PQntuples(res) == 0) {
159 		PQclear(res);
160 		return (ISC_R_NOTFOUND);
161 	}
162 
163 	for (i = 0; i < PQntuples(res); i++) {
164 		char *ttlstr = PQgetvalue(res, i, 0);
165 		char *type = PQgetvalue(res, i, 1);
166 		char *data = PQgetvalue(res, i, 2);
167 		dns_ttl_t ttl;
168 		char *endp;
169 		ttl = strtol(ttlstr, &endp, 10);
170 		if (*endp != '\0') {
171 			PQclear(res);
172 			return (DNS_R_BADTTL);
173 		}
174 		result = dns_sdb_putrr(lookup, type, ttl, data);
175 		if (result != ISC_R_SUCCESS) {
176 			PQclear(res);
177 			return (ISC_R_FAILURE);
178 		}
179 	}
180 
181 	PQclear(res);
182 	return (ISC_R_SUCCESS);
183 }
184 
185 /*
186  * Issue an SQL query to return all nodes in the database and fill the
187  * allnodes structure.
188  */
189 static isc_result_t
pgsqldb_allnodes(const char * zone,void * dbdata,dns_sdballnodes_t * allnodes)190 pgsqldb_allnodes(const char *zone, void *dbdata, dns_sdballnodes_t *allnodes) {
191 	struct dbinfo *dbi = dbdata;
192 	PGresult *res;
193 	isc_result_t result;
194 	char str[1500];
195 	int i;
196 
197 	UNUSED(zone);
198 
199 	snprintf(str, sizeof(str),
200 		 "SELECT TTL,NAME,RDTYPE,RDATA FROM \"%s\" ORDER BY NAME",
201 		 dbi->table);
202 
203 	result = maybe_reconnect(dbi);
204 	if (result != ISC_R_SUCCESS)
205 		return (result);
206 
207 	res = PQexec(dbi->conn, str);
208 	if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ) {
209 		PQclear(res);
210 		return (ISC_R_FAILURE);
211 	}
212 	if (PQntuples(res) == 0) {
213 		PQclear(res);
214 		return (ISC_R_NOTFOUND);
215 	}
216 
217 	for (i = 0; i < PQntuples(res); i++) {
218 		char *ttlstr = PQgetvalue(res, i, 0);
219 		char *name = PQgetvalue(res, i, 1);
220 		char *type = PQgetvalue(res, i, 2);
221 		char *data = PQgetvalue(res, i, 3);
222 		dns_ttl_t ttl;
223 		char *endp;
224 		ttl = strtol(ttlstr, &endp, 10);
225 		if (*endp != '\0') {
226 			PQclear(res);
227 			return (DNS_R_BADTTL);
228 		}
229 		result = dns_sdb_putnamedrr(allnodes, name, type, ttl, data);
230 		if (result != ISC_R_SUCCESS) {
231 			PQclear(res);
232 			return (ISC_R_FAILURE);
233 		}
234 	}
235 
236 	PQclear(res);
237 	return (ISC_R_SUCCESS);
238 }
239 
240 /*
241  * Create a connection to the database and save any necessary information
242  * in dbdata.
243  *
244  * argv[0] is the name of the database
245  * argv[1] is the name of the table
246  * argv[2] (if present) is the name of the host to connect to
247  * argv[3] (if present) is the name of the user to connect as
248  * argv[4] (if present) is the name of the password to connect with
249  */
250 static isc_result_t
pgsqldb_create(const char * zone,int argc,char ** argv,void * driverdata,void ** dbdata)251 pgsqldb_create(const char *zone, int argc, char **argv,
252 	       void *driverdata, void **dbdata)
253 {
254 	struct dbinfo *dbi;
255 	isc_result_t result;
256 
257 	UNUSED(zone);
258 	UNUSED(driverdata);
259 
260 	if (argc < 2)
261 		return (ISC_R_FAILURE);
262 
263 	dbi = isc_mem_get(ns_g_mctx, sizeof(struct dbinfo));
264 	if (dbi == NULL)
265 		return (ISC_R_NOMEMORY);
266 	dbi->conn = NULL;
267 	dbi->database = NULL;
268 	dbi->table = NULL;
269 	dbi->host = NULL;
270 	dbi->user = NULL;
271 	dbi->passwd = NULL;
272 
273 #define STRDUP_OR_FAIL(target, source)				\
274 	do {							\
275 		target = isc_mem_strdup(ns_g_mctx, source);	\
276 		if (target == NULL) {				\
277 			result = ISC_R_NOMEMORY;		\
278 			goto cleanup;				\
279 		}						\
280 	} while (/*CONSTCOND*/0);
281 
282 	STRDUP_OR_FAIL(dbi->database, argv[0]);
283 	STRDUP_OR_FAIL(dbi->table, argv[1]);
284 	if (argc > 2)
285 		STRDUP_OR_FAIL(dbi->host, argv[2]);
286 	if (argc > 3)
287 		STRDUP_OR_FAIL(dbi->user, argv[3]);
288 	if (argc > 4)
289 		STRDUP_OR_FAIL(dbi->passwd, argv[4]);
290 
291 	result = db_connect(dbi);
292 	if (result != ISC_R_SUCCESS)
293 		goto cleanup;
294 
295 	*dbdata = dbi;
296 	return (ISC_R_SUCCESS);
297 
298  cleanup:
299 	pgsqldb_destroy(zone, driverdata, (void **)&dbi);
300 	return (result);
301 }
302 
303 /*
304  * Close the connection to the database.
305  */
306 static void
pgsqldb_destroy(const char * zone,void * driverdata,void ** dbdata)307 pgsqldb_destroy(const char *zone, void *driverdata, void **dbdata) {
308 	struct dbinfo *dbi = *dbdata;
309 
310 	UNUSED(zone);
311 	UNUSED(driverdata);
312 
313 	if (dbi->conn != NULL)
314 		PQfinish(dbi->conn);
315 	if (dbi->database != NULL)
316 		isc_mem_free(ns_g_mctx, dbi->database);
317 	if (dbi->table != NULL)
318 		isc_mem_free(ns_g_mctx, dbi->table);
319 	if (dbi->host != NULL)
320 		isc_mem_free(ns_g_mctx, dbi->host);
321 	if (dbi->user != NULL)
322 		isc_mem_free(ns_g_mctx, dbi->user);
323 	if (dbi->passwd != NULL)
324 		isc_mem_free(ns_g_mctx, dbi->passwd);
325 	if (dbi->database != NULL)
326 		isc_mem_free(ns_g_mctx, dbi->database);
327 	isc_mem_put(ns_g_mctx, dbi, sizeof(struct dbinfo));
328 }
329 
330 /*
331  * Since the SQL database corresponds to a zone, the authority data should
332  * be returned by the lookup() function.  Therefore the authority() function
333  * is NULL.
334  */
335 static dns_sdbmethods_t pgsqldb_methods = {
336 	pgsqldb_lookup,
337 	NULL, /* authority */
338 	pgsqldb_allnodes,
339 	pgsqldb_create,
340 	pgsqldb_destroy,
341 	NULL /* lookup2 */
342 };
343 
344 /*
345  * Wrapper around dns_sdb_register().
346  */
347 isc_result_t
pgsqldb_init(void)348 pgsqldb_init(void) {
349 	unsigned int flags;
350 	flags = 0;
351 	return (dns_sdb_register("pgsql", &pgsqldb_methods, NULL, flags,
352 				 ns_g_mctx, &pgsqldb));
353 }
354 
355 /*
356  * Wrapper around dns_sdb_unregister().
357  */
358 void
pgsqldb_clear(void)359 pgsqldb_clear(void) {
360 	if (pgsqldb != NULL)
361 		dns_sdb_unregister(&pgsqldb);
362 }
363