No empty .Rs/.Re
[netbsd-mini2440.git] / external / bsd / bind / dist / contrib / dlz / drivers / dlz_postgres_driver.c
blob0bbea7838a2d091ba7620d2cf8df45e793c0858d
1 /* $NetBSD$ */
3 /*
4 * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the
8 * above copyright notice and this permission notice appear in all
9 * copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
12 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
13 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
14 * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
16 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
17 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
18 * USE OR PERFORMANCE OF THIS SOFTWARE.
20 * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
21 * conceived and contributed by Rob Butler.
23 * Permission to use, copy, modify, and distribute this software for any
24 * purpose with or without fee is hereby granted, provided that the
25 * above copyright notice and this permission notice appear in all
26 * copies.
28 * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
29 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
31 * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
32 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
33 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
34 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
35 * USE OR PERFORMANCE OF THIS SOFTWARE.
39 * Copyright (C) 1999-2001 Internet Software Consortium.
41 * Permission to use, copy, modify, and distribute this software for any
42 * purpose with or without fee is hereby granted, provided that the above
43 * copyright notice and this permission notice appear in all copies.
45 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
46 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
48 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
49 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
50 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
51 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
52 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
55 #ifdef DLZ_POSTGRES
57 #include <config.h>
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
62 #include <dns/log.h>
63 #include <dns/sdlz.h>
64 #include <dns/result.h>
66 #include <isc/mem.h>
67 #include <isc/platform.h>
68 #include <isc/print.h>
69 #include <isc/result.h>
70 #include <isc/string.h>
71 #include <isc/util.h>
73 #include <named/globals.h>
75 #include <dlz/sdlz_helper.h>
76 #include <dlz/dlz_postgres_driver.h>
78 /* temporarily include time. */
79 #include <time.h>
81 #include <libpq-fe.h>
83 static dns_sdlzimplementation_t *dlz_postgres = NULL;
85 #define dbc_search_limit 30
86 #define ALLNODES 1
87 #define ALLOWXFR 2
88 #define AUTHORITY 3
89 #define FINDZONE 4
90 #define LOOKUP 5
93 * Private methods
96 /* ---------------
97 * Escaping arbitrary strings to get valid SQL strings/identifiers.
99 * Replaces "\\" with "\\\\" and "'" with "''".
100 * length is the length of the buffer pointed to by
101 * from. The buffer at to must be at least 2*length + 1 characters
102 * long. A terminating NUL character is written.
104 * NOTICE!!!
105 * This function was borrowed directly from PostgreSQL's libpq.
106 * The function was originally called PQescapeString and renamed
107 * to postgres_makesafe to avoid a naming collision.
108 * PQescapeString is a new function made available in Postgres 7.2.
109 * For some reason the function is not properly exported on Win32
110 * builds making the function unavailable on Windows. Also, since
111 * this function is new it would require building this driver with
112 * the libpq 7.2. By borrowing this function the Windows problem
113 * is solved, and the dependence on libpq 7.2 is removed. Libpq is
114 * still required of course, but an older version should work now too.
116 * The copyright statements from the original file containing this
117 * function are included below:
118 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
119 * Portions Copyright (c) 1994, Regents of the University of California
120 * ---------------
123 static size_t
124 postgres_makesafe(char *to, const char *from, size_t length)
126 const char *source = from;
127 char *target = to;
128 unsigned int remaining = length;
130 while (remaining > 0)
132 switch (*source)
134 case '\\':
135 *target = '\\';
136 target++;
137 *target = '\\';
138 /* target and remaining are updated below. */
139 break;
141 case '\'':
142 *target = '\'';
143 target++;
144 *target = '\'';
145 /* target and remaining are updated below. */
146 break;
148 default:
149 *target = *source;
150 /* target and remaining are updated below. */
152 source++;
153 target++;
154 remaining--;
157 /* Write the terminating NUL character. */
158 *target = '\0';
160 return target - to;
163 #ifdef ISC_PLATFORM_USETHREADS
166 * Properly cleans up a list of database instances.
167 * This function is only used when the driver is compiled for
168 * multithreaded operation.
170 static void
171 postgres_destroy_dblist(db_list_t *dblist)
174 dbinstance_t *ndbi = NULL;
175 dbinstance_t *dbi = NULL;
177 /* get the first DBI in the list */
178 ndbi = ISC_LIST_HEAD(*dblist);
180 /* loop through the list */
181 while (ndbi != NULL) {
182 dbi = ndbi;
183 /* get the next DBI in the list */
184 ndbi = ISC_LIST_NEXT(dbi, link);
185 /* release DB connection */
186 if (dbi->dbconn != NULL)
187 PQfinish((PGconn *) dbi->dbconn);
188 /* release all memory that comprised a DBI */
189 destroy_sqldbinstance(dbi);
191 /* release memory for the list structure */
192 isc_mem_put(ns_g_mctx, dblist, sizeof(db_list_t));
196 * Loops through the list of DB instances, attempting to lock
197 * on the mutex. If successful, the DBI is reserved for use
198 * and the thread can perform queries against the database.
199 * If the lock fails, the next one in the list is tried.
200 * looping continues until a lock is obtained, or until
201 * the list has been searched dbc_search_limit times.
202 * This function is only used when the driver is compiled for
203 * multithreaded operation.
206 static dbinstance_t *
207 postgres_find_avail_conn(db_list_t *dblist)
209 dbinstance_t *dbi = NULL;
210 dbinstance_t *head;
211 int count = 0;
213 /* get top of list */
214 head = dbi = ISC_LIST_HEAD(*dblist);
216 /* loop through list */
217 while (count < dbc_search_limit) {
218 /* try to lock on the mutex */
219 if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS)
220 return dbi; /* success, return the DBI for use. */
222 /* not successful, keep trying */
223 dbi = ISC_LIST_NEXT(dbi, link);
225 /* check to see if we have gone to the top of the list. */
226 if (dbi == NULL) {
227 count++;
228 dbi = head;
231 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
232 DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
233 "Postgres driver unable to find available connection "
234 "after searching %d times",
235 count);
236 return NULL;
239 #endif /* ISC_PLATFORM_USETHREADS */
242 * Allocates memory for a new string, and then constructs the new
243 * string by "escaping" the input string. The new string is
244 * safe to be used in queries. This is necessary because we cannot
245 * be sure of what types of strings are passed to us, and we don't
246 * want special characters in the string causing problems.
249 static char *
250 postgres_escape_string(const char *instr) {
252 char *outstr;
253 unsigned int len;
255 if (instr == NULL)
256 return NULL;
258 len = strlen(instr);
260 outstr = isc_mem_allocate(ns_g_mctx ,(2 * len * sizeof(char)) + 1);
261 if (outstr == NULL)
262 return NULL;
264 postgres_makesafe(outstr, instr, len);
265 /* PQescapeString(outstr, instr, len); */
267 return outstr;
271 * This function is the real core of the driver. Zone, record
272 * and client strings are passed in (or NULL is passed if the
273 * string is not available). The type of query we want to run
274 * is indicated by the query flag, and the dbdata object is passed
275 * passed in to. dbdata really holds either:
276 * 1) a list of database instances (in multithreaded mode) OR
277 * 2) a single database instance (in single threaded mode)
278 * The function will construct the query and obtain an available
279 * database instance (DBI). It will then run the query and hopefully
280 * obtain a result set. Postgres is nice, in that once the result
281 * set is returned, we can make the db connection available for another
282 * thread to use, while this thread continues on. So, the DBI is made
283 * available ASAP by unlocking the instance_lock after we have cleaned
284 * it up properly.
286 static isc_result_t
287 postgres_get_resultset(const char *zone, const char *record,
288 const char *client, unsigned int query,
289 void *dbdata, PGresult **rs)
291 isc_result_t result;
292 dbinstance_t *dbi = NULL;
293 char *querystring = NULL;
294 unsigned int i = 0;
295 unsigned int j = 0;
297 /* temporarily get a unique thread # */
298 unsigned int dlz_thread_num = 1+(int) (1000.0*rand()/(RAND_MAX+1.0));
300 REQUIRE(*rs == NULL);
302 #if 0
303 /* temporary logging message */
304 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
305 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
306 "%d Getting DBI", dlz_thread_num);
307 #endif
309 /* get db instance / connection */
310 #ifdef ISC_PLATFORM_USETHREADS
312 /* find an available DBI from the list */
313 dbi = postgres_find_avail_conn((db_list_t *) dbdata);
315 #else /* ISC_PLATFORM_USETHREADS */
318 * only 1 DBI - no need to lock instance lock either
319 * only 1 thread in the whole process, no possible contention.
321 dbi = (dbinstance_t *) dbdata;
323 #endif /* ISC_PLATFORM_USETHREADS */
325 #if 0
326 /* temporary logging message */
327 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
328 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
329 "%d Got DBI - checking query", dlz_thread_num);
330 #endif
332 /* if DBI is null, can't do anything else */
333 if (dbi == NULL) {
334 result = ISC_R_FAILURE;
335 goto cleanup;
338 /* what type of query are we going to run? */
339 switch(query) {
340 case ALLNODES:
342 * if the query was not passed in from the config file
343 * then we can't run it. return not_implemented, so
344 * it's like the code for that operation was never
345 * built into the driver.... AHHH flexibility!!!
347 if (dbi->allnodes_q == NULL) {
348 result = ISC_R_NOTIMPLEMENTED;
349 goto cleanup;
351 break;
352 case ALLOWXFR:
353 /* same as comments as ALLNODES */
354 if (dbi->allowxfr_q == NULL) {
355 result = ISC_R_NOTIMPLEMENTED;
356 goto cleanup;
358 break;
359 case AUTHORITY:
360 /* same as comments as ALLNODES */
361 if (dbi->authority_q == NULL) {
362 result = ISC_R_NOTIMPLEMENTED;
363 goto cleanup;
365 break;
366 case FINDZONE:
367 /* this is required. It's the whole point of DLZ! */
368 if (dbi->findzone_q == NULL) {
369 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
370 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
371 "No query specified for findzone. "
372 "Findzone requires a query");
373 result = ISC_R_FAILURE;
374 goto cleanup;
376 break;
377 case LOOKUP:
378 /* this is required. It's also a major point of DLZ! */
379 if (dbi->lookup_q == NULL) {
380 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
381 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
382 "No query specified for lookup. "
383 "Lookup requires a query");
384 result = ISC_R_FAILURE;
385 goto cleanup;
387 break;
388 default:
390 * this should never happen. If it does, the code is
391 * screwed up!
393 UNEXPECTED_ERROR(__FILE__, __LINE__,
394 "Incorrect query flag passed to "
395 "postgres_get_resultset");
396 result = ISC_R_UNEXPECTED;
397 goto cleanup;
400 #if 0
401 /* temporary logging message */
402 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
403 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
404 "%d checked query", dlz_thread_num);
405 #endif
408 * was a zone string passed? If so, make it safe for use in
409 * queries.
411 if (zone != NULL) {
412 dbi->zone = postgres_escape_string(zone);
413 if (dbi->zone == NULL) {
414 result = ISC_R_NOMEMORY;
415 goto cleanup;
417 } else { /* no string passed, set the string pointer to NULL */
418 dbi->zone = NULL;
421 #if 0
422 /* temporary logging message */
423 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
424 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
425 "%d did zone", dlz_thread_num);
426 #endif
429 * was a record string passed? If so, make it safe for use in
430 * queries.
432 if (record != NULL) {
433 dbi->record = postgres_escape_string(record);
434 if (dbi->record == NULL) {
435 result = ISC_R_NOMEMORY;
436 goto cleanup;
438 } else { /* no string passed, set the string pointer to NULL */
439 dbi->record = NULL;
443 #if 0
444 /* temporary logging message */
445 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
446 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
447 "%d did record", dlz_thread_num);
448 #endif
451 * was a client string passed? If so, make it safe for use in
452 * queries.
454 if (client != NULL) {
455 dbi->client = postgres_escape_string(client);
456 if (dbi->client == NULL) {
457 result = ISC_R_NOMEMORY;
458 goto cleanup;
460 } else { /* no string passed, set the string pointer to NULL */
461 dbi->client = NULL;
464 #if 0
465 /* temporary logging message */
466 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
467 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
468 "%d did client", dlz_thread_num);
469 #endif
472 * what type of query are we going to run?
473 * this time we build the actual query to run.
475 switch(query) {
476 case ALLNODES:
477 querystring = build_querystring(ns_g_mctx, dbi->allnodes_q);
478 break;
479 case ALLOWXFR:
480 querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q);
481 break;
482 case AUTHORITY:
483 querystring = build_querystring(ns_g_mctx, dbi->authority_q);
484 break;
485 case FINDZONE:
486 querystring = build_querystring(ns_g_mctx, dbi->findzone_q);
487 break;
488 case LOOKUP:
489 querystring = build_querystring(ns_g_mctx, dbi->lookup_q);
490 break;
491 default:
493 * this should never happen. If it does, the code is
494 * screwed up!
496 UNEXPECTED_ERROR(__FILE__, __LINE__,
497 "Incorrect query flag passed to "
498 "postgres_get_resultset");
499 result = ISC_R_UNEXPECTED;
500 goto cleanup;
503 #if 0
504 /* temporary logging message */
505 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
506 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
507 "%d built query", dlz_thread_num);
508 #endif
510 /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */
511 if (querystring == NULL) {
512 result = ISC_R_NOMEMORY;
513 goto cleanup;
516 #if 0
517 /* temporary logging message */
518 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
519 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
520 "%d query is '%s'", dlz_thread_num, querystring);
521 #endif
524 * output the full query string during debug so we can see
525 * what lame error the query has.
527 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
528 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
529 "\nQuery String: %s\n", querystring);
531 /* attempt query up to 3 times. */
532 for (j=0; j < 3; j++) {
533 #if 0
534 /* temporary logging message */
535 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
536 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
537 "%d executing query for %d time",
538 dlz_thread_num, j);
539 #endif
540 /* try to get result set */
541 *rs = PQexec((PGconn *)dbi->dbconn, querystring );
542 result = ISC_R_SUCCESS;
544 * if result set is null, reset DB connection, max 3
545 * attempts.
547 for (i=0; *rs == NULL && i < 3; i++) {
548 #if 0
549 /* temporary logging message */
550 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
551 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
552 "%d resetting connection",
553 dlz_thread_num);
554 #endif
555 result = ISC_R_FAILURE;
556 PQreset((PGconn *) dbi->dbconn);
557 /* connection ok, break inner loop */
558 if (PQstatus((PGconn *) dbi->dbconn) == CONNECTION_OK)
559 break;
561 /* result set ok, break outter loop */
562 if (PQresultStatus(*rs) == PGRES_TUPLES_OK) {
563 #if 0
564 /* temporary logging message */
565 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
566 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
567 "%d rs ok", dlz_thread_num);
568 #endif
569 break;
570 } else {
571 /* we got a result set object, but it's not right. */
572 #if 0
573 /* temporary logging message */
574 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
575 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
576 "%d clearing rs", dlz_thread_num);
577 #endif
578 PQclear(*rs); /* get rid of it */
579 /* in case this was the last attempt */
580 result = ISC_R_FAILURE;
584 cleanup:
585 /* it's always good to cleanup after yourself */
587 #if 0
588 /* temporary logging message */
589 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
590 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
591 "%d cleaning up", dlz_thread_num);
592 #endif
594 /* if we couldn't even allocate DBI, just return NULL */
595 if (dbi == NULL)
596 return ISC_R_FAILURE;
598 /* free dbi->zone string */
599 if (dbi->zone != NULL)
600 isc_mem_free(ns_g_mctx, dbi->zone);
602 /* free dbi->record string */
603 if (dbi->record != NULL)
604 isc_mem_free(ns_g_mctx, dbi->record);
606 /* free dbi->client string */
607 if (dbi->client != NULL)
608 isc_mem_free(ns_g_mctx, dbi->client);
610 #ifdef ISC_PLATFORM_USETHREADS
612 #if 0
613 /* temporary logging message */
614 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
615 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
616 "%d unlocking mutex", dlz_thread_num);
617 #endif
619 /* release the lock so another thread can use this dbi */
620 isc_mutex_unlock(&dbi->instance_lock);
622 #endif /* ISC_PLATFORM_USETHREADS */
624 /* release query string */
625 if (querystring != NULL)
626 isc_mem_free(ns_g_mctx, querystring );
628 #if 0
629 /* temporary logging message */
630 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
631 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
632 "%d returning", dlz_thread_num);
633 #endif
635 /* return result */
636 return result;
640 * The processing of result sets for lookup and authority are
641 * exactly the same. So that functionality has been moved
642 * into this function to minimize code.
645 static isc_result_t
646 postgres_process_rs(dns_sdlzlookup_t *lookup, PGresult *rs)
648 isc_result_t result;
649 unsigned int i;
650 unsigned int rows;
651 unsigned int fields;
652 unsigned int j;
653 unsigned int len;
654 char *tmpString;
655 char *endp;
656 int ttl;
658 rows = PQntuples(rs); /* how many rows in result set */
659 fields = PQnfields(rs); /* how many columns in result set */
660 for (i=0; i < rows; i++) {
661 switch(fields) {
662 case 1:
664 * one column in rs, it's the data field. use
665 * default type of A record, and default TTL
666 * of 86400
668 result = dns_sdlz_putrr(lookup, "a", 86400,
669 PQgetvalue(rs, i, 0));
670 break;
671 case 2:
672 /* two columns, data field, and data type.
673 * use default TTL of 86400.
675 result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 0),
676 86400, PQgetvalue(rs, i, 1));
677 break;
678 case 3:
679 /* three columns, all data no defaults.
680 * convert text to int, make sure it worked
681 * right.
683 ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10);
684 if (*endp != '\0' || ttl < 0) {
685 isc_log_write(dns_lctx,
686 DNS_LOGCATEGORY_DATABASE,
687 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
688 "Postgres driver ttl must be "
689 "a positive number");
691 result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 1),
692 ttl, PQgetvalue(rs, i, 2));
693 break;
694 default:
696 * more than 3 fields, concatenate the last
697 * ones together. figure out how long to make
698 * string
700 for (j=2, len=0; j < fields; j++) {
701 len += strlen(PQgetvalue(rs, i, j)) + 1;
704 * allocate string memory, allow for NULL to
705 * term string
707 tmpString = isc_mem_allocate(ns_g_mctx, len + 1);
708 if (tmpString == NULL) {
709 /* major bummer, need more ram */
710 isc_log_write(dns_lctx,
711 DNS_LOGCATEGORY_DATABASE,
712 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
713 "Postgres driver unable to "
714 "allocate memory for "
715 "temporary string");
716 PQclear(rs);
717 return (ISC_R_FAILURE); /* Yeah, I'd say! */
719 /* copy field to tmpString */
720 strcpy(tmpString, PQgetvalue(rs, i, 2));
722 * concat the rest of fields together, space
723 * between each one.
725 for (j=3; j < fields; j++) {
726 strcat(tmpString, " ");
727 strcat(tmpString, PQgetvalue(rs, i, j));
729 /* convert text to int, make sure it worked right */
730 ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10);
731 if (*endp != '\0' || ttl < 0) {
732 isc_log_write(dns_lctx,
733 DNS_LOGCATEGORY_DATABASE,
734 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
735 "Postgres driver ttl must be "
736 "a postive number");
738 /* ok, now tell Bind about it. */
739 result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 1),
740 ttl, tmpString);
741 /* done, get rid of this thing. */
742 isc_mem_free(ns_g_mctx, tmpString);
744 /* I sure hope we were successful */
745 if (result != ISC_R_SUCCESS) {
746 /* nope, get rid of the Result set, and log a msg */
747 PQclear(rs);
748 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
749 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
750 "dns_sdlz_putrr returned error. "
751 "Error code was: %s",
752 isc_result_totext(result));
753 return (ISC_R_FAILURE);
757 /* free result set memory */
758 PQclear(rs);
760 /* if we did return results, we are successful */
761 if (rows > 0)
762 return (ISC_R_SUCCESS);
764 /* empty result set, no data found */
765 return (ISC_R_NOTFOUND);
769 * SDLZ interface methods
772 /*% determine if the zone is supported by (in) the database */
774 static isc_result_t
775 postgres_findzone(void *driverarg, void *dbdata, const char *name)
777 isc_result_t result;
778 PGresult *rs = NULL;
779 unsigned int rows;
780 UNUSED(driverarg);
782 /* run the query and get the result set from the database. */
783 result = postgres_get_resultset(name, NULL, NULL,
784 FINDZONE, dbdata, &rs);
785 /* if we didn't get a result set, log an err msg. */
786 if (result != ISC_R_SUCCESS) {
787 if (rs != NULL)
788 PQclear(rs);
789 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
790 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
791 "Postgres driver unable to return "
792 "result set for findzone query");
793 return (ISC_R_FAILURE);
795 /* count how many rows in result set */
796 rows = PQntuples(rs);
797 /* get rid of result set, we are done with it. */
798 PQclear(rs);
800 /* if we returned any rows, zone is supported. */
801 if (rows > 0)
802 return (ISC_R_SUCCESS);
804 /* no rows returned, zone is not supported. */
805 return (ISC_R_NOTFOUND);
808 /*% Determine if the client is allowed to perform a zone transfer */
809 static isc_result_t
810 postgres_allowzonexfr(void *driverarg, void *dbdata, const char *name,
811 const char *client)
813 isc_result_t result;
814 PGresult *rs = NULL;
815 unsigned int rows;
816 UNUSED(driverarg);
818 /* first check if the zone is supported by the database. */
819 result = postgres_findzone(driverarg, dbdata, name);
820 if (result != ISC_R_SUCCESS)
821 return (ISC_R_NOTFOUND);
824 * if we get to this point we know the zone is supported by
825 * the database the only questions now are is the zone
826 * transfer is allowed for this client and did the config file
827 * have an allow zone xfr query.
829 * Run our query, and get a result set from the database.
831 result = postgres_get_resultset(name, NULL, client,
832 ALLOWXFR, dbdata, &rs);
833 /* if we get "not implemented", send it along. */
834 if (result == ISC_R_NOTIMPLEMENTED)
835 return result;
836 /* if we didn't get a result set, log an err msg. */
837 if (result != ISC_R_SUCCESS) {
838 if (rs != NULL)
839 PQclear(rs);
840 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
841 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
842 "Postgres driver unable to return "
843 "result set for allow xfr query");
844 return (ISC_R_FAILURE);
846 /* count how many rows in result set */
847 rows = PQntuples(rs);
848 /* get rid of result set, we are done with it. */
849 PQclear(rs);
851 /* if we returned any rows, zone xfr is allowed. */
852 if (rows > 0)
853 return (ISC_R_SUCCESS);
855 /* no rows returned, zone xfr not allowed */
856 return (ISC_R_NOPERM);
860 * If the client is allowed to perform a zone transfer, the next order of
861 * business is to get all the nodes in the zone, so bind can respond to the
862 * query.
864 static isc_result_t
865 postgres_allnodes(const char *zone, void *driverarg, void *dbdata,
866 dns_sdlzallnodes_t *allnodes)
868 isc_result_t result;
869 PGresult *rs = NULL;
870 unsigned int i;
871 unsigned int rows;
872 unsigned int fields;
873 unsigned int j;
874 unsigned int len;
875 char *tmpString;
876 char *endp;
877 int ttl;
879 UNUSED(driverarg);
881 /* run the query and get the result set from the database. */
882 result = postgres_get_resultset(zone, NULL, NULL,
883 ALLNODES, dbdata, &rs);
884 /* if we get "not implemented", send it along */
885 if (result == ISC_R_NOTIMPLEMENTED)
886 return result;
887 /* if we didn't get a result set, log an err msg. */
888 if (result != ISC_R_SUCCESS) {
889 if (rs != NULL)
890 PQclear(rs);
891 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
892 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
893 "Postgres driver unable to return "
894 "result set for all nodes query");
895 return (ISC_R_FAILURE);
898 rows = PQntuples(rs); /* how many rows in result set */
899 fields = PQnfields(rs); /* how many columns in result set */
900 for (i=0; i < rows; i++) {
901 if (fields < 4) { /* gotta have at least 4 columns */
902 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
903 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
904 "Postgres driver too few fields "
905 "returned by all nodes query");
907 /* convert text to int, make sure it worked right */
908 ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10);
909 if (*endp != '\0' || ttl < 0) {
910 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
911 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
912 "Postgres driver ttl must be "
913 "a postive number");
915 if (fields == 4) {
916 /* tell Bind about it. */
917 result = dns_sdlz_putnamedrr(allnodes,
918 PQgetvalue(rs, i, 2),
919 PQgetvalue(rs, i, 1),
920 ttl,
921 PQgetvalue(rs, i, 3));
922 } else {
924 * more than 4 fields, concatonat the last
925 * ones together. figure out how long to make
926 * string
928 for (j=3, len=0; j < fields; j++) {
929 len += strlen(PQgetvalue(rs, i, j)) + 1;
931 /* allocate memory, allow for NULL to term string */
932 tmpString = isc_mem_allocate(ns_g_mctx, len + 1);
933 if (tmpString == NULL) { /* we need more ram. */
934 isc_log_write(dns_lctx,
935 DNS_LOGCATEGORY_DATABASE,
936 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
937 "Postgres driver unable to "
938 "allocate memory for "
939 "temporary string");
940 PQclear(rs);
941 return (ISC_R_FAILURE);
943 /* copy this field to tmpString */
944 strcpy(tmpString, PQgetvalue(rs, i, 3));
945 /* concatonate the rest, with spaces between */
946 for (j=4; j < fields; j++) {
947 strcat(tmpString, " ");
948 strcat(tmpString, PQgetvalue(rs, i, j));
950 /* tell Bind about it. */
951 result = dns_sdlz_putnamedrr(allnodes,
952 PQgetvalue(rs, i, 2),
953 PQgetvalue(rs, i, 1),
954 ttl, tmpString);
955 isc_mem_free(ns_g_mctx, tmpString);
957 /* if we weren't successful, log err msg */
958 if (result != ISC_R_SUCCESS) {
959 PQclear(rs);
960 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
961 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
962 "dns_sdlz_putnamedrr returned error. "
963 "Error code was: %s",
964 isc_result_totext(result));
965 return (ISC_R_FAILURE);
969 /* free result set memory */
970 PQclear(rs);
972 /* if we did return results, we are successful */
973 if (rows > 0)
974 return (ISC_R_SUCCESS);
976 /* empty result set, no data found */
977 return (ISC_R_NOTFOUND);
981 * if the lookup function does not return SOA or NS records for the zone,
982 * use this function to get that information for Bind.
985 static isc_result_t
986 postgres_authority(const char *zone, void *driverarg, void *dbdata,
987 dns_sdlzlookup_t *lookup)
989 isc_result_t result;
990 PGresult *rs = NULL;
992 UNUSED(driverarg);
994 /* run the query and get the result set from the database. */
995 result = postgres_get_resultset(zone, NULL, NULL,
996 AUTHORITY, dbdata, &rs);
997 /* if we get "not implemented", send it along */
998 if (result == ISC_R_NOTIMPLEMENTED)
999 return result;
1000 /* if we didn't get a result set, log an err msg. */
1001 if (result != ISC_R_SUCCESS) {
1002 if (rs != NULL)
1003 PQclear(rs);
1004 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1005 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1006 "Postgres driver unable to return "
1007 "result set for authority query");
1008 return (ISC_R_FAILURE);
1011 * lookup and authority result sets are processed in the same
1012 * manner postgres_process_rs does the job for both
1013 * functions.
1015 return postgres_process_rs(lookup, rs);
1018 /*% if zone is supported, lookup up a (or multiple) record(s) in it */
1019 static isc_result_t
1020 postgres_lookup(const char *zone, const char *name, void *driverarg,
1021 void *dbdata, dns_sdlzlookup_t *lookup)
1023 isc_result_t result;
1024 PGresult *rs = NULL;
1026 UNUSED(driverarg);
1028 /* run the query and get the result set from the database. */
1029 result = postgres_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs);
1030 /* if we didn't get a result set, log an err msg. */
1031 if (result != ISC_R_SUCCESS) {
1032 if (rs != NULL)
1033 PQclear(rs);
1034 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1035 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1036 "Postgres driver unable to "
1037 "return result set for lookup query");
1038 return (ISC_R_FAILURE);
1041 * lookup and authority result sets are processed in the same
1042 * manner postgres_process_rs does the job for both functions.
1044 return postgres_process_rs(lookup, rs);
1048 * create an instance of the driver. Remember, only 1 copy of the driver's
1049 * code is ever loaded, the driver has to remember which context it's
1050 * operating in. This is done via use of the dbdata argument which is
1051 * passed into all query functions.
1053 static isc_result_t
1054 postgres_create(const char *dlzname, unsigned int argc, char *argv[],
1055 void *driverarg, void **dbdata)
1057 isc_result_t result;
1058 dbinstance_t *dbi = NULL;
1059 unsigned int j;
1061 #ifdef ISC_PLATFORM_USETHREADS
1062 /* if multi-threaded, we need a few extra variables. */
1063 int dbcount;
1064 db_list_t *dblist = NULL;
1065 int i;
1066 char *endp;
1068 #endif /* ISC_PLATFORM_USETHREADS */
1070 UNUSED(driverarg);
1071 UNUSED(dlzname);
1073 /* seed random # generator */
1074 srand( (unsigned)time( NULL ) );
1077 #ifdef ISC_PLATFORM_USETHREADS
1078 /* if debugging, let user know we are multithreaded. */
1079 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1080 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
1081 "Postgres driver running multithreaded");
1082 #else /* ISC_PLATFORM_USETHREADS */
1083 /* if debugging, let user know we are single threaded. */
1084 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1085 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
1086 "Postgres driver running single threaded");
1087 #endif /* ISC_PLATFORM_USETHREADS */
1089 /* verify we have at least 5 arg's passed to the driver */
1090 if (argc < 5) {
1091 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1092 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1093 "Postgres driver requires at least "
1094 "4 command line args.");
1095 return (ISC_R_FAILURE);
1098 /* no more than 8 arg's should be passed to the driver */
1099 if (argc > 8) {
1100 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1101 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1102 "Postgres driver cannot accept more than "
1103 "7 command line args.");
1104 return (ISC_R_FAILURE);
1107 /* multithreaded build can have multiple DB connections */
1108 #ifdef ISC_PLATFORM_USETHREADS
1110 /* check how many db connections we should create */
1111 dbcount = strtol(argv[1], &endp, 10);
1112 if (*endp != '\0' || dbcount < 0) {
1113 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1114 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1115 "Postgres driver database connection count "
1116 "must be positive.");
1117 return (ISC_R_FAILURE);
1120 /* allocate memory for database connection list */
1121 dblist = isc_mem_get(ns_g_mctx, sizeof(db_list_t));
1122 if (dblist == NULL)
1123 return (ISC_R_NOMEMORY);
1125 /* initialize DB connection list */
1126 ISC_LIST_INIT(*dblist);
1129 * create the appropriate number of database instances (DBI)
1130 * append each new DBI to the end of the list
1132 for (i=0; i < dbcount; i++) {
1134 #endif /* ISC_PLATFORM_USETHREADS */
1136 /* how many queries were passed in from config file? */
1137 switch(argc) {
1138 case 5:
1139 result = build_sqldbinstance(ns_g_mctx, NULL, NULL,
1140 NULL, argv[3], argv[4],
1141 NULL, &dbi);
1142 break;
1143 case 6:
1144 result = build_sqldbinstance(ns_g_mctx, NULL, NULL,
1145 argv[5], argv[3], argv[4],
1146 NULL, &dbi);
1147 break;
1148 case 7:
1149 result = build_sqldbinstance(ns_g_mctx, argv[6], NULL,
1150 argv[5], argv[3], argv[4],
1151 NULL, &dbi);
1152 break;
1153 case 8:
1154 result = build_sqldbinstance(ns_g_mctx, argv[6],
1155 argv[7], argv[5], argv[3],
1156 argv[4], NULL, &dbi);
1157 break;
1158 default:
1159 /* not really needed, should shut up compiler. */
1160 result = ISC_R_FAILURE;
1164 if (result == ISC_R_SUCCESS) {
1165 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1166 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1167 "Postgres driver created database "
1168 "instance object.");
1169 } else { /* unsuccessful?, log err msg and cleanup. */
1170 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1171 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1172 "Postgres driver could not create "
1173 "database instance object.");
1174 goto cleanup;
1177 #ifdef ISC_PLATFORM_USETHREADS
1179 /* when multithreaded, build a list of DBI's */
1180 ISC_LINK_INIT(dbi, link);
1181 ISC_LIST_APPEND(*dblist, dbi, link);
1183 #endif
1185 /* create and set db connection */
1186 dbi->dbconn = PQconnectdb(argv[2]);
1188 * if db connection cannot be created, log err msg and
1189 * cleanup.
1191 if (dbi->dbconn == NULL) {
1192 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1193 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1194 "Postgres driver could not allocate "
1195 "memory for database connection");
1196 goto cleanup;
1199 /* if we cannot connect the first time, try 3 more times. */
1200 for (j = 0;
1201 PQstatus((PGconn *) dbi->dbconn) != CONNECTION_OK &&
1202 j < 3;
1203 j++)
1204 PQreset((PGconn *) dbi->dbconn);
1207 #ifdef ISC_PLATFORM_USETHREADS
1210 * if multi threaded, let user know which connection
1211 * failed. user could be attempting to create 10 db
1212 * connections and for some reason the db backend only
1213 * allows 9
1215 if (PQstatus((PGconn *) dbi->dbconn) != CONNECTION_OK) {
1216 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1217 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1218 "Postgres driver failed to create "
1219 "database connection number %u "
1220 "after 4 attempts",
1221 i + 1);
1222 goto cleanup;
1225 /* set DBI = null for next loop through. */
1226 dbi = NULL;
1227 } /* end for loop */
1229 /* set dbdata to the list we created. */
1230 *dbdata = dblist;
1232 #else /* ISC_PLATFORM_USETHREADS */
1233 /* if single threaded, just let user know we couldn't connect. */
1234 if (PQstatus((PGconn *) dbi->dbconn) != CONNECTION_OK) {
1235 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1236 DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1237 "Postgres driver failed to create database "
1238 "connection after 4 attempts");
1239 goto cleanup;
1243 * single threaded build can only use 1 db connection, return
1244 * it via dbdata
1246 *dbdata = dbi;
1248 #endif /* ISC_PLATFORM_USETHREADS */
1250 /* hey, we got through all of that ok, return success. */
1251 return(ISC_R_SUCCESS);
1253 cleanup:
1255 #ifdef ISC_PLATFORM_USETHREADS
1257 * if multithreaded, we could fail because only 1 connection
1258 * couldn't be made. We should cleanup the other successful
1259 * connections properly.
1261 postgres_destroy_dblist(dblist);
1263 #else /* ISC_PLATFORM_USETHREADS */
1264 if (dbi != NULL)
1265 destroy_sqldbinstance(dbi);
1267 #endif /* ISC_PLATFORM_USETHREADS */
1268 return(ISC_R_FAILURE);
1272 * destroy an instance of the driver. Remember, only 1 copy of the driver's
1273 * code is ever loaded, the driver has to remember which context it's
1274 * operating in. This is done via use of the dbdata argument.
1275 * so we really only need to clean it up since we are not using driverarg.
1277 static void
1278 postgres_destroy(void *driverarg, void *dbdata)
1281 #ifdef ISC_PLATFORM_USETHREADS
1283 UNUSED(driverarg);
1284 /* cleanup the list of DBI's */
1285 postgres_destroy_dblist((db_list_t *) dbdata);
1287 #else /* ISC_PLATFORM_USETHREADS */
1289 dbinstance_t *dbi;
1291 UNUSED(driverarg);
1293 dbi = (dbinstance_t *) dbdata;
1295 /* release DB connection */
1296 if (dbi->dbconn != NULL)
1297 PQfinish((PGconn *) dbi->dbconn);
1299 /* destroy single DB instance */
1300 destroy_sqldbinstance(dbi);
1302 #endif /* ISC_PLATFORM_USETHREADS */
1305 /* pointers to all our runtime methods. */
1306 /* this is used during driver registration */
1307 /* i.e. in dlz_postgres_init below. */
1308 static dns_sdlzmethods_t dlz_postgres_methods = {
1309 postgres_create,
1310 postgres_destroy,
1311 postgres_findzone,
1312 postgres_lookup,
1313 postgres_authority,
1314 postgres_allnodes,
1315 postgres_allowzonexfr
1319 * Wrapper around dns_sdlzregister().
1321 isc_result_t
1322 dlz_postgres_init(void) {
1323 isc_result_t result;
1326 * Write debugging message to log
1328 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1329 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1330 "Registering DLZ postgres driver.");
1333 * Driver is always threadsafe. When multithreaded all
1334 * functions use multithreaded code. When not multithreaded,
1335 * all functions can only be entered once, but only 1 thread
1336 * of operation is available in Bind. So everything is still
1337 * threadsafe.
1339 result = dns_sdlzregister("postgres", &dlz_postgres_methods, NULL,
1340 DNS_SDLZFLAG_RELATIVEOWNER |
1341 DNS_SDLZFLAG_RELATIVERDATA |
1342 DNS_SDLZFLAG_THREADSAFE,
1343 ns_g_mctx, &dlz_postgres);
1344 /* if we can't register the driver, there are big problems. */
1345 if (result != ISC_R_SUCCESS) {
1346 UNEXPECTED_ERROR(__FILE__, __LINE__,
1347 "dns_sdlzregister() failed: %s",
1348 isc_result_totext(result));
1349 result = ISC_R_UNEXPECTED;
1353 return result;
1357 * Wrapper around dns_sdlzunregister().
1359 void
1360 dlz_postgres_clear(void) {
1363 * Write debugging message to log
1365 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1366 DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1367 "Unregistering DLZ postgres driver.");
1369 /* unregister the driver. */
1370 if (dlz_postgres != NULL)
1371 dns_sdlzunregister(&dlz_postgres);
1374 #endif