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