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 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 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 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 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 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 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 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 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 359 pgsqldb_clear(void) { 360 if (pgsqldb != NULL) 361 dns_sdb_unregister(&pgsqldb); 362 } 363