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