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