xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/dict_pgsql.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: dict_pgsql.c,v 1.1.1.3 2013/01/02 18:58:57 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_pgsql 3
6 /* SUMMARY
7 /*	dictionary manager interface to PostgreSQL databases
8 /* SYNOPSIS
9 /*	#include <dict_pgsql.h>
10 /*
11 /*	DICT	*dict_pgsql_open(name, open_flags, dict_flags)
12 /*	const char *name;
13 /*	int	open_flags;
14 /*	int	dict_flags;
15 /* DESCRIPTION
16 /*	dict_pgsql_open() creates a dictionary of type 'pgsql'.  This
17 /*	dictionary is an interface for the postfix key->value mappings
18 /*	to pgsql.  The result is a pointer to the installed dictionary,
19 /*	or a null pointer in case of problems.
20 /*
21 /*	The pgsql dictionary can manage multiple connections to
22 /*	different sql servers for the same database.  It assumes that
23 /*	the underlying data on each server is identical (mirrored) and
24 /*	maintains one connection at any given time.  If any connection
25 /*	fails,  any other available ones will be opened and used.
26 /*	The intent of this feature is to eliminate a single point of
27 /*	failure for mail systems that would otherwise rely on a single
28 /*	pgsql server.
29 /* .PP
30 /*	Arguments:
31 /* .IP name
32 /*	Either the path to the PostgreSQL configuration file (if it
33 /*	starts with '/' or '.'), or the prefix which will be used to
34 /*	obtain main.cf configuration parameters for this search.
35 /*
36 /*	In the first case, the configuration parameters below are
37 /*	specified in the file as \fIname\fR=\fIvalue\fR pairs.
38 /*
39 /*	In the second case, the configuration parameters are
40 /*	prefixed with the value of \fIname\fR and an underscore,
41 /*	and they are specified in main.cf.  For example, if this
42 /*	value is \fIpgsqlsource\fR, the parameters would look like
43 /*	\fIpgsqlsource_user\fR, \fIpgsqlsource_table\fR, and so on.
44 /* .IP other_name
45 /*	reference for outside use.
46 /* .IP open_flags
47 /*	Must be O_RDONLY.
48 /* .IP dict_flags
49 /*	See dict_open(3).
50 /*
51 /* .PP
52 /*	Configuration parameters:
53 /* .IP user
54 /*	Username for connecting to the database.
55 /* .IP password
56 /*	Password for the above.
57 /* .IP dbname
58 /*	Name of the database.
59 /* .IP query
60 /*	Query template. If not defined a default query template is constructed
61 /*	from the legacy \fIselect_function\fR or failing that the \fItable\fR,
62 /*	\fIselect_field\fR, \fIwhere_field\fR, and \fIadditional_conditions\fR
63 /*	parameters. Before the query is issues, variable substitutions are
64 /*	performed. See pgsql_table(5).
65 /* .IP domain
66 /*	List of domains the queries should be restricted to.  If
67 /*	specified, only FQDN addresses whose domain parts matching this
68 /*	list will be queried against the SQL database.  Lookups for
69 /*	partial addresses are also supressed.  This can significantly
70 /*	reduce the query load on the server.
71 /* .IP result_format
72 /*	The format used to expand results from queries.  Substitutions
73 /*	are performed as described in pgsql_table(5). Defaults to returning
74 /*	the lookup result unchanged.
75 /* .IP expansion_limit
76 /*	Limit (if any) on the total number of lookup result values. Lookups which
77 /*	exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that each
78 /*	non-empty (and non-NULL) column of a multi-column result row counts as
79 /*	one result.
80 /* .IP select_function
81 /*	When \fIquery\fR is not defined, the function to be used instead of
82 /*	the default query based on the legacy \fItable\fR, \fIselect_field\fR,
83 /*	\fIwhere_field\fR, and \fIadditional_conditions\fR parameters.
84 /* .IP table
85 /*	When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
86 /*	FROM table used to construct the default query template, see pgsql_table(5).
87 /* .IP select_field
88 /*	When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
89 /*	SELECT field used to construct the default query template, see pgsql_table(5).
90 /* .IP where_field
91 /*	When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
92 /*	WHERE field used to construct the default query template, see pgsql_table(5).
93 /* .IP additional_conditions
94 /*	When \fIquery\fR and \fIselect_function\fR are not defined, the name of the
95 /*	additional text to add to the WHERE field in the default query template (this
96 /*	usually begins with "and") see pgsql_table(5).
97 /* .IP hosts
98 /*	List of hosts to connect to.
99 /* .PP
100 /*	For example, if you want the map to reference databases of
101 /*	the name "your_db" and execute a query like this: select
102 /*	forw_addr from aliases where alias like '<some username>'
103 /*	against any database called "postfix_info" located on hosts
104 /*	host1.some.domain and host2.some.domain, logging in as user
105 /*	"postfix" and password "passwd" then the configuration file
106 /*	should read:
107 /* .PP
108 /*	user = postfix
109 /* .br
110 /*	password = passwd
111 /* .br
112 /*	dbname = postfix_info
113 /* .br
114 /*	table = aliases
115 /* .br
116 /*	select_field = forw_addr
117 /* .br
118 /*	where_field = alias
119 /* .br
120 /*	hosts = host1.some.domain\fR \fBhost2.some.domain
121 /* .PP
122 /* SEE ALSO
123 /*	dict(3) generic dictionary manager
124 /* AUTHOR(S)
125 /*	Aaron Sethman
126 /*	androsyn@ratbox.org
127 /*
128 /*	Based upon dict_mysql.c by
129 /*
130 /*	Scott Cotton
131 /*	IC Group, Inc.
132 /*	scott@icgroup.com
133 /*
134 /*	Joshua Marcus
135 /*	IC Group, Inc.
136 /*	josh@icgroup.com
137 /*--*/
138 
139 /* System library. */
140 
141 #include "sys_defs.h"
142 
143 #ifdef HAS_PGSQL
144 #include <sys/socket.h>
145 #include <netinet/in.h>
146 #include <arpa/inet.h>
147 #include <netdb.h>
148 #include <stdio.h>
149 #include <string.h>
150 #include <stdlib.h>
151 #include <syslog.h>
152 #include <time.h>
153 
154 #include <postgres_ext.h>
155 #include <libpq-fe.h>
156 
157 /* Utility library. */
158 
159 #include "dict.h"
160 #include "msg.h"
161 #include "mymalloc.h"
162 #include "argv.h"
163 #include "vstring.h"
164 #include "split_at.h"
165 #include "find_inet.h"
166 #include "myrand.h"
167 #include "events.h"
168 #include "stringops.h"
169 
170 /* Global library. */
171 
172 #include "cfg_parser.h"
173 #include "db_common.h"
174 
175 /* Application-specific. */
176 
177 #include "dict_pgsql.h"
178 
179 #define STATACTIVE			(1<<0)
180 #define STATFAIL			(1<<1)
181 #define STATUNTRIED			(1<<2)
182 
183 #define TYPEUNIX			(1<<0)
184 #define TYPEINET			(1<<1)
185 
186 #define RETRY_CONN_MAX			100
187 #define RETRY_CONN_INTV			60	/* 1 minute */
188 #define IDLE_CONN_INTV			60	/* 1 minute */
189 
190 typedef struct {
191     PGconn *db;
192     char   *hostname;
193     char   *name;
194     char   *port;
195     unsigned type;			/* TYPEUNIX | TYPEINET */
196     unsigned stat;			/* STATUNTRIED | STATFAIL | STATCUR */
197     time_t  ts;				/* used for attempting reconnection */
198 } HOST;
199 
200 typedef struct {
201     int     len_hosts;			/* number of hosts */
202     HOST  **db_hosts;			/* hosts on which databases reside */
203 } PLPGSQL;
204 
205 typedef struct {
206     DICT    dict;
207     CFG_PARSER *parser;
208     char   *query;
209     char   *result_format;
210     void   *ctx;
211     int     expansion_limit;
212     char   *username;
213     char   *password;
214     char   *dbname;
215     char   *table;
216     ARGV   *hosts;
217     PLPGSQL *pldb;
218     HOST   *active_host;
219 } DICT_PGSQL;
220 
221 
222 /* Just makes things a little easier for me.. */
223 #define PGSQL_RES PGresult
224 
225 /* internal function declarations */
226 static PLPGSQL *plpgsql_init(ARGV *);
227 static PGSQL_RES *plpgsql_query(DICT_PGSQL *, const char *, VSTRING *, char *,
228 				        char *, char *);
229 static void plpgsql_dealloc(PLPGSQL *);
230 static void plpgsql_close_host(HOST *);
231 static void plpgsql_down_host(HOST *);
232 static void plpgsql_connect_single(HOST *, char *, char *, char *);
233 static const char *dict_pgsql_lookup(DICT *, const char *);
234 DICT   *dict_pgsql_open(const char *, int, int);
235 static void dict_pgsql_close(DICT *);
236 static HOST *host_init(const char *);
237 
238 /* dict_pgsql_quote - escape SQL metacharacters in input string */
239 
240 static void dict_pgsql_quote(DICT *dict, const char *name, VSTRING *result)
241 {
242     DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
243     HOST   *active_host = dict_pgsql->active_host;
244     char   *myname = "dict_pgsql_quote";
245     size_t  len = strlen(name);
246     size_t  buflen = 2 * len + 1;
247     int     err = 1;
248 
249     if (active_host == 0)
250 	msg_panic("%s: bogus dict_pgsql->active_host", myname);
251 
252     /*
253      * We won't get arithmetic overflows in 2*len + 1, because Postfix input
254      * keys have reasonable size limits, better safe than sorry.
255      */
256     if (buflen <= len)
257 	msg_panic("%s: arithmetic overflow in 2*%lu+1",
258 		  myname, (unsigned long) len);
259 
260     /*
261      * XXX Workaround: stop further processing when PQescapeStringConn()
262      * (below) fails. A more proper fix requires invasive changes, not
263      * suitable for a stable release.
264      */
265     if (active_host->stat == STATFAIL)
266 	return;
267 
268     /*
269      * Escape the input string, using PQescapeStringConn(), because the older
270      * PQescapeString() is not safe anymore, as stated by the documentation.
271      *
272      * From current libpq (8.1.4) documentation:
273      *
274      * PQescapeStringConn writes an escaped version of the from string to the to
275      * buffer, escaping special characters so that they cannot cause any
276      * harm, and adding a terminating zero byte.
277      *
278      * ...
279      *
280      * The parameter from points to the first character of the string that is to
281      * be escaped, and the length parameter gives the number of bytes in this
282      * string. A terminating zero byte is not required, and should not be
283      * counted in length.
284      *
285      * ...
286      *
287      * (The parameter) to shall point to a buffer that is able to hold at least
288      * one more byte than twice the value of length, otherwise the behavior
289      * is undefined.
290      *
291      * ...
292      *
293      * If the error parameter is not NULL, then *error is set to zero on
294      * success, nonzero on error ... The output string is still generated on
295      * error, but it can be expected that the server will reject it as
296      * malformed. On error, a suitable message is stored in the conn object,
297      * whether or not error is NULL.
298      */
299     VSTRING_SPACE(result, buflen);
300     PQescapeStringConn(active_host->db, vstring_end(result), name, len, &err);
301     if (err == 0) {
302 	VSTRING_SKIP(result);
303     } else {
304 
305 	/*
306 	 * PQescapeStringConn() failed. According to the docs, we still have
307 	 * a valid, null-terminated output string, but we need not rely on
308 	 * this behavior.
309 	 */
310 	msg_warn("dict pgsql: (host %s) cannot escape input string: %s",
311 		 active_host->hostname, PQerrorMessage(active_host->db));
312 	active_host->stat = STATFAIL;
313 	VSTRING_TERMINATE(result);
314     }
315 }
316 
317 /* dict_pgsql_lookup - find database entry */
318 
319 static const char *dict_pgsql_lookup(DICT *dict, const char *name)
320 {
321     const char *myname = "dict_pgsql_lookup";
322     PGSQL_RES *query_res;
323     DICT_PGSQL *dict_pgsql;
324     PLPGSQL *pldb;
325     static VSTRING *query;
326     static VSTRING *result;
327     int     i;
328     int     j;
329     int     numrows;
330     int     numcols;
331     int     expansion;
332     const char *r;
333     int     domain_rc;
334 
335     dict_pgsql = (DICT_PGSQL *) dict;
336     pldb = dict_pgsql->pldb;
337 
338 #define INIT_VSTR(buf, len) do { \
339 	if (buf == 0) \
340 	    buf = vstring_alloc(len); \
341 	VSTRING_RESET(buf); \
342 	VSTRING_TERMINATE(buf); \
343     } while (0)
344 
345     INIT_VSTR(query, 10);
346     INIT_VSTR(result, 10);
347 
348     dict->error = 0;
349 
350     /*
351      * Optionally fold the key.
352      */
353     if (dict->flags & DICT_FLAG_FOLD_FIX) {
354 	if (dict->fold_buf == 0)
355 	    dict->fold_buf = vstring_alloc(10);
356 	vstring_strcpy(dict->fold_buf, name);
357 	name = lowercase(vstring_str(dict->fold_buf));
358     }
359 
360     /*
361      * If there is a domain list for this map, then only search for addresses
362      * in domains on the list. This can significantly reduce the load on the
363      * server.
364      */
365     if ((domain_rc = db_common_check_domain(dict_pgsql->ctx, name)) == 0) {
366 	if (msg_verbose)
367 	    msg_info("%s: Skipping lookup of '%s'", myname, name);
368 	return (0);
369     }
370     if (domain_rc < 0)
371 	DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
372 
373     /*
374      * Suppress the actual lookup if the expansion is empty.
375      *
376      * This initial expansion is outside the context of any specific host
377      * connection, we just want to check the key pre-requisites, so when
378      * quoting happens separately for each connection, we don't bother with
379      * quoting...
380      */
381     if (!db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
382 			  name, 0, query, 0))
383 	return (0);
384 
385     /* do the query - set dict->error & cleanup if there's an error */
386     if ((query_res = plpgsql_query(dict_pgsql, name, query,
387 				   dict_pgsql->dbname,
388 				   dict_pgsql->username,
389 				   dict_pgsql->password)) == 0) {
390 	dict->error = DICT_ERR_RETRY;
391 	return 0;
392     }
393     numrows = PQntuples(query_res);
394     if (msg_verbose)
395 	msg_info("%s: retrieved %d rows", myname, numrows);
396     if (numrows == 0) {
397 	PQclear(query_res);
398 	return 0;
399     }
400     numcols = PQnfields(query_res);
401 
402     for (expansion = i = 0; i < numrows && dict->error == 0; i++) {
403 	for (j = 0; j < numcols; j++) {
404 	    r = PQgetvalue(query_res, i, j);
405 	    if (db_common_expand(dict_pgsql->ctx, dict_pgsql->result_format,
406 				 r, name, result, 0)
407 		&& dict_pgsql->expansion_limit > 0
408 		&& ++expansion > dict_pgsql->expansion_limit) {
409 		msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
410 			 myname, dict_pgsql->parser->name, name);
411 		dict->error = DICT_ERR_RETRY;
412 		break;
413 	    }
414 	}
415     }
416     PQclear(query_res);
417     r = vstring_str(result);
418     return ((dict->error == 0 && *r) ? r : 0);
419 }
420 
421 /* dict_pgsql_check_stat - check the status of a host */
422 
423 static int dict_pgsql_check_stat(HOST *host, unsigned stat, unsigned type,
424 				         time_t t)
425 {
426     if ((host->stat & stat) && (!type || host->type & type)) {
427 	/* try not to hammer the dead hosts too often */
428 	if (host->stat == STATFAIL && host->ts > 0 && host->ts >= t)
429 	    return 0;
430 	return 1;
431     }
432     return 0;
433 }
434 
435 /* dict_pgsql_find_host - find a host with the given status */
436 
437 static HOST *dict_pgsql_find_host(PLPGSQL *PLDB, unsigned stat, unsigned type)
438 {
439     time_t  t;
440     int     count = 0;
441     int     idx;
442     int     i;
443 
444     t = time((time_t *) 0);
445     for (i = 0; i < PLDB->len_hosts; i++) {
446 	if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t))
447 	    count++;
448     }
449 
450     if (count) {
451 	idx = (count > 1) ?
452 	    1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
453 
454 	for (i = 0; i < PLDB->len_hosts; i++) {
455 	    if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t) &&
456 		--idx == 0)
457 		return PLDB->db_hosts[i];
458 	}
459     }
460     return 0;
461 }
462 
463 /* dict_pgsql_get_active - get an active connection */
464 
465 static HOST *dict_pgsql_get_active(PLPGSQL *PLDB, char *dbname,
466 				           char *username, char *password)
467 {
468     const char *myname = "dict_pgsql_get_active";
469     HOST   *host;
470     int     count = RETRY_CONN_MAX;
471 
472     /* try the active connections first; prefer the ones to UNIX sockets */
473     if ((host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL ||
474 	(host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL) {
475 	if (msg_verbose)
476 	    msg_info("%s: found active connection to host %s", myname,
477 		     host->hostname);
478 	return host;
479     }
480 
481     /*
482      * Try the remaining hosts. "count" is a safety net, in case the loop
483      * takes more than RETRY_CONN_INTV and the dead hosts are no longer
484      * skipped.
485      */
486     while (--count > 0 &&
487 	   ((host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
488 					 TYPEUNIX)) != NULL ||
489 	    (host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
490 					 TYPEINET)) != NULL)) {
491 	if (msg_verbose)
492 	    msg_info("%s: attempting to connect to host %s", myname,
493 		     host->hostname);
494 	plpgsql_connect_single(host, dbname, username, password);
495 	if (host->stat == STATACTIVE)
496 	    return host;
497     }
498 
499     /* bad news... */
500     return 0;
501 }
502 
503 /* dict_pgsql_event - callback: close idle connections */
504 
505 static void dict_pgsql_event(int unused_event, char *context)
506 {
507     HOST   *host = (HOST *) context;
508 
509     if (host->db)
510 	plpgsql_close_host(host);
511 }
512 
513 /*
514  * plpgsql_query - process a PostgreSQL query.  Return PGSQL_RES* on success.
515  *			On failure, log failure and try other db instances.
516  *			on failure of all db instances, return 0;
517  *			close unnecessary active connections
518  */
519 
520 static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
521 				        const char *name,
522 				        VSTRING *query,
523 				        char *dbname,
524 				        char *username,
525 				        char *password)
526 {
527     PLPGSQL *PLDB = dict_pgsql->pldb;
528     HOST   *host;
529     PGSQL_RES *res = 0;
530     ExecStatusType status;
531 
532     while ((host = dict_pgsql_get_active(PLDB, dbname, username, password)) != NULL) {
533 
534 	/*
535 	 * The active host is used to escape strings in the context of the
536 	 * active connection's character encoding.
537 	 */
538 	dict_pgsql->active_host = host;
539 	VSTRING_RESET(query);
540 	VSTRING_TERMINATE(query);
541 	db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
542 			 name, 0, query, dict_pgsql_quote);
543 	dict_pgsql->active_host = 0;
544 
545 	/* Check for potential dict_pgsql_quote() failure. */
546 	if (host->stat == STATFAIL) {
547 	    plpgsql_down_host(host);
548 	    continue;
549 	}
550 
551 	/*
552 	 * Submit a command to the server. Be paranoid when processing the
553 	 * result set: try to enumerate every successful case, and reject
554 	 * everything else.
555 	 *
556 	 * From PostgreSQL 8.1.4 docs: (PQexec) returns a PGresult pointer or
557 	 * possibly a null pointer. A non-null pointer will generally be
558 	 * returned except in out-of-memory conditions or serious errors such
559 	 * as inability to send the command to the server.
560 	 */
561 	if ((res = PQexec(host->db, vstring_str(query))) != 0) {
562 
563 	    /*
564 	     * XXX Because non-null result pointer does not imply success, we
565 	     * need to check the command's result status.
566 	     *
567 	     * Section 28.3.1: A result of status PGRES_NONFATAL_ERROR will
568 	     * never be returned directly by PQexec or other query execution
569 	     * functions; results of this kind are instead passed to the
570 	     * notice processor.
571 	     *
572 	     * PGRES_EMPTY_QUERY is being sent by the server when the query
573 	     * string is empty. The sanity-checking done by the Postfix
574 	     * infrastructure makes this case impossible, so we need not
575 	     * handle this situation explicitly.
576 	     */
577 	    switch ((status = PQresultStatus(res))) {
578 	    case PGRES_TUPLES_OK:
579 	    case PGRES_COMMAND_OK:
580 		/* Success. */
581 		if (msg_verbose)
582 		    msg_info("dict_pgsql: successful query from host %s",
583 			     host->hostname);
584 		event_request_timer(dict_pgsql_event, (char *) host,
585 				    IDLE_CONN_INTV);
586 		return (res);
587 	    case PGRES_FATAL_ERROR:
588 		msg_warn("pgsql query failed: fatal error from host %s: %s",
589 			 host->hostname, PQresultErrorMessage(res));
590 		break;
591 	    case PGRES_BAD_RESPONSE:
592 		msg_warn("pgsql query failed: protocol error, host %s",
593 			 host->hostname);
594 		break;
595 	    default:
596 		msg_warn("pgsql query failed: unknown code 0x%lx from host %s",
597 			 (unsigned long) status, host->hostname);
598 		break;
599 	    }
600 	} else {
601 
602 	    /*
603 	     * This driver treats null pointers like fatal, non-null result
604 	     * pointer errors, as suggested by the PostgreSQL 8.1.4
605 	     * documentation.
606 	     */
607 	    msg_warn("pgsql query failed: fatal error from host %s: %s",
608 		     host->hostname, PQerrorMessage(host->db));
609 	}
610 
611 	/*
612 	 * XXX An error occurred. Clean up memory and skip this connection.
613 	 */
614 	if (res != 0)
615 	    PQclear(res);
616 	plpgsql_down_host(host);
617     }
618 
619     return (0);
620 }
621 
622 /*
623  * plpgsql_connect_single -
624  * used to reconnect to a single database when one is down or none is
625  * connected yet. Log all errors and set the stat field of host accordingly
626  */
627 static void plpgsql_connect_single(HOST *host, char *dbname, char *username, char *password)
628 {
629     if ((host->db = PQsetdbLogin(host->name, host->port, NULL, NULL,
630 				 dbname, username, password)) == NULL
631 	|| PQstatus(host->db) != CONNECTION_OK) {
632 	msg_warn("connect to pgsql server %s: %s",
633 		 host->hostname, PQerrorMessage(host->db));
634 	plpgsql_down_host(host);
635 	return;
636     }
637     if (msg_verbose)
638 	msg_info("dict_pgsql: successful connection to host %s",
639 		 host->hostname);
640 
641     /*
642      * XXX Postfix does not send multi-byte characters. The following piece
643      * of code is an explicit statement of this fact, and the database server
644      * should not accept multi-byte information after this point.
645      */
646     if (PQsetClientEncoding(host->db, "LATIN1") != 0) {
647 	msg_warn("dict_pgsql: cannot set the encoding to LATIN1, skipping %s",
648 		 host->hostname);
649 	plpgsql_down_host(host);
650 	return;
651     }
652     /* Success. */
653     host->stat = STATACTIVE;
654 }
655 
656 /* plpgsql_close_host - close an established PostgreSQL connection */
657 
658 static void plpgsql_close_host(HOST *host)
659 {
660     if (host->db)
661 	PQfinish(host->db);
662     host->db = 0;
663     host->stat = STATUNTRIED;
664 }
665 
666 /*
667  * plpgsql_down_host - close a failed connection AND set a "stay away from
668  * this host" timer.
669  */
670 static void plpgsql_down_host(HOST *host)
671 {
672     if (host->db)
673 	PQfinish(host->db);
674     host->db = 0;
675     host->ts = time((time_t *) 0) + RETRY_CONN_INTV;
676     host->stat = STATFAIL;
677     event_cancel_timer(dict_pgsql_event, (char *) host);
678 }
679 
680 /* pgsql_parse_config - parse pgsql configuration file */
681 
682 static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
683 {
684     const char *myname = "pgsql_parse_config";
685     CFG_PARSER *p = dict_pgsql->parser;
686     char   *hosts;
687     VSTRING *query;
688     char   *select_function;
689 
690     dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0);
691     dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0);
692     dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
693     dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
694 
695     /*
696      * XXX: The default should be non-zero for safety, but that is not
697      * backwards compatible.
698      */
699     dict_pgsql->expansion_limit = cfg_get_int(dict_pgsql->parser,
700 					      "expansion_limit", 0, 0, 0);
701 
702     if ((dict_pgsql->query = cfg_get_str(p, "query", 0, 0, 0)) == 0) {
703 
704 	/*
705 	 * No query specified -- fallback to building it from components (
706 	 * old style "select %s from %s where %s" )
707 	 */
708 	query = vstring_alloc(64);
709 	select_function = cfg_get_str(p, "select_function", 0, 0, 0);
710 	if (select_function != 0) {
711 	    vstring_sprintf(query, "SELECT %s('%%s')", select_function);
712 	    myfree(select_function);
713 	} else
714 	    db_common_sql_build_query(query, p);
715 	dict_pgsql->query = vstring_export(query);
716     }
717 
718     /*
719      * Must parse all templates before we can use db_common_expand()
720      */
721     dict_pgsql->ctx = 0;
722     (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx,
723 			   dict_pgsql->query, 1);
724     (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0);
725     db_common_parse_domain(p, dict_pgsql->ctx);
726 
727     /*
728      * Maps that use substring keys should only be used with the full input
729      * key.
730      */
731     if (db_common_dict_partial(dict_pgsql->ctx))
732 	dict_pgsql->dict.flags |= DICT_FLAG_PATTERN;
733     else
734 	dict_pgsql->dict.flags |= DICT_FLAG_FIXED;
735     if (dict_pgsql->dict.flags & DICT_FLAG_FOLD_FIX)
736 	dict_pgsql->dict.fold_buf = vstring_alloc(10);
737 
738     hosts = cfg_get_str(p, "hosts", "", 0, 0);
739 
740     dict_pgsql->hosts = argv_split(hosts, " ,\t\r\n");
741     if (dict_pgsql->hosts->argc == 0) {
742 	argv_add(dict_pgsql->hosts, "localhost", ARGV_END);
743 	argv_terminate(dict_pgsql->hosts);
744 	if (msg_verbose)
745 	    msg_info("%s: %s: no hostnames specified, defaulting to '%s'",
746 		     myname, pgsqlcf, dict_pgsql->hosts->argv[0]);
747     }
748     myfree(hosts);
749 }
750 
751 /* dict_pgsql_open - open PGSQL data base */
752 
753 DICT   *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
754 {
755     DICT_PGSQL *dict_pgsql;
756     CFG_PARSER *parser;
757 
758     /*
759      * Sanity check.
760      */
761     if (open_flags != O_RDONLY)
762 	return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
763 			       "%s:%s map requires O_RDONLY access mode",
764 			       DICT_TYPE_PGSQL, name));
765 
766     /*
767      * Open the configuration file.
768      */
769     if ((parser = cfg_parser_alloc(name)) == 0)
770 	return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
771 			       "open %s: %m", name));
772 
773     dict_pgsql = (DICT_PGSQL *) dict_alloc(DICT_TYPE_PGSQL, name,
774 					   sizeof(DICT_PGSQL));
775     dict_pgsql->dict.lookup = dict_pgsql_lookup;
776     dict_pgsql->dict.close = dict_pgsql_close;
777     dict_pgsql->dict.flags = dict_flags;
778     dict_pgsql->parser = parser;
779     pgsql_parse_config(dict_pgsql, name);
780     dict_pgsql->active_host = 0;
781     dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts);
782     if (dict_pgsql->pldb == NULL)
783 	msg_fatal("couldn't intialize pldb!\n");
784     dict_pgsql->dict.owner = cfg_get_owner(dict_pgsql->parser);
785     return (DICT_DEBUG (&dict_pgsql->dict));
786 }
787 
788 /* plpgsql_init - initalize a PGSQL database */
789 
790 static PLPGSQL *plpgsql_init(ARGV *hosts)
791 {
792     PLPGSQL *PLDB;
793     int     i;
794 
795     PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL));
796     PLDB->len_hosts = hosts->argc;
797     PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc);
798     for (i = 0; i < hosts->argc; i++)
799 	PLDB->db_hosts[i] = host_init(hosts->argv[i]);
800 
801     return PLDB;
802 }
803 
804 
805 /* host_init - initialize HOST structure */
806 
807 static HOST *host_init(const char *hostname)
808 {
809     const char *myname = "pgsql host_init";
810     HOST   *host = (HOST *) mymalloc(sizeof(HOST));
811     const char *d = hostname;
812 
813     host->db = 0;
814     host->hostname = mystrdup(hostname);
815     host->stat = STATUNTRIED;
816     host->ts = 0;
817 
818     /*
819      * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where
820      * both "inet:" and ":port" are optional.
821      */
822     if (strncmp(d, "unix:", 5) == 0 || strncmp(d, "inet:", 5) == 0)
823 	d += 5;
824     host->name = mystrdup(d);
825     host->port = split_at_right(host->name, ':');
826 
827     /* This is how PgSQL distinguishes between UNIX and INET: */
828     if (host->name[0] && host->name[0] != '/')
829 	host->type = TYPEINET;
830     else
831 	host->type = TYPEUNIX;
832 
833     if (msg_verbose > 1)
834 	msg_info("%s: host=%s, port=%s, type=%s", myname, host->name,
835 		 host->port ? host->port : "",
836 		 host->type == TYPEUNIX ? "unix" : "inet");
837     return host;
838 }
839 
840 /* dict_pgsql_close - close PGSQL data base */
841 
842 static void dict_pgsql_close(DICT *dict)
843 {
844     DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
845 
846     plpgsql_dealloc(dict_pgsql->pldb);
847     cfg_parser_free(dict_pgsql->parser);
848     myfree(dict_pgsql->username);
849     myfree(dict_pgsql->password);
850     myfree(dict_pgsql->dbname);
851     myfree(dict_pgsql->query);
852     myfree(dict_pgsql->result_format);
853     if (dict_pgsql->hosts)
854 	argv_free(dict_pgsql->hosts);
855     if (dict_pgsql->ctx)
856 	db_common_free_ctx(dict_pgsql->ctx);
857     if (dict->fold_buf)
858 	vstring_free(dict->fold_buf);
859     dict_free(dict);
860 }
861 
862 /* plpgsql_dealloc - free memory associated with PLPGSQL close databases */
863 
864 static void plpgsql_dealloc(PLPGSQL *PLDB)
865 {
866     int     i;
867 
868     for (i = 0; i < PLDB->len_hosts; i++) {
869 	event_cancel_timer(dict_pgsql_event, (char *) (PLDB->db_hosts[i]));
870 	if (PLDB->db_hosts[i]->db)
871 	    PQfinish(PLDB->db_hosts[i]->db);
872 	myfree(PLDB->db_hosts[i]->hostname);
873 	myfree(PLDB->db_hosts[i]->name);
874 	myfree((char *) PLDB->db_hosts[i]);
875     }
876     myfree((char *) PLDB->db_hosts);
877     myfree((char *) (PLDB));
878 }
879 
880 #endif
881