xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/dict_pgsql.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: dict_pgsql.c,v 1.2 2017/02/14 01:16:45 christos 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 host2.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     static VSTRING *query;
325     static VSTRING *result;
326     int     i;
327     int     j;
328     int     numrows;
329     int     numcols;
330     int     expansion;
331     const char *r;
332     int     domain_rc;
333 
334     dict_pgsql = (DICT_PGSQL *) dict;
335 
336 #define INIT_VSTR(buf, len) do { \
337 	if (buf == 0) \
338 	    buf = vstring_alloc(len); \
339 	VSTRING_RESET(buf); \
340 	VSTRING_TERMINATE(buf); \
341     } while (0)
342 
343     INIT_VSTR(query, 10);
344     INIT_VSTR(result, 10);
345 
346     dict->error = 0;
347 
348     /*
349      * Optionally fold the key.
350      */
351     if (dict->flags & DICT_FLAG_FOLD_FIX) {
352 	if (dict->fold_buf == 0)
353 	    dict->fold_buf = vstring_alloc(10);
354 	vstring_strcpy(dict->fold_buf, name);
355 	name = lowercase(vstring_str(dict->fold_buf));
356     }
357 
358     /*
359      * If there is a domain list for this map, then only search for addresses
360      * in domains on the list. This can significantly reduce the load on the
361      * server.
362      */
363     if ((domain_rc = db_common_check_domain(dict_pgsql->ctx, name)) == 0) {
364 	if (msg_verbose)
365 	    msg_info("%s: Skipping lookup of '%s'", myname, name);
366 	return (0);
367     }
368     if (domain_rc < 0)
369 	DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
370 
371     /*
372      * Suppress the actual lookup if the expansion is empty.
373      *
374      * This initial expansion is outside the context of any specific host
375      * connection, we just want to check the key pre-requisites, so when
376      * quoting happens separately for each connection, we don't bother with
377      * quoting...
378      */
379     if (!db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
380 			  name, 0, query, 0))
381 	return (0);
382 
383     /* do the query - set dict->error & cleanup if there's an error */
384     if ((query_res = plpgsql_query(dict_pgsql, name, query,
385 				   dict_pgsql->dbname,
386 				   dict_pgsql->username,
387 				   dict_pgsql->password)) == 0) {
388 	dict->error = DICT_ERR_RETRY;
389 	return 0;
390     }
391     numrows = PQntuples(query_res);
392     if (msg_verbose)
393 	msg_info("%s: retrieved %d rows", myname, numrows);
394     if (numrows == 0) {
395 	PQclear(query_res);
396 	return 0;
397     }
398     numcols = PQnfields(query_res);
399 
400     for (expansion = i = 0; i < numrows && dict->error == 0; i++) {
401 	for (j = 0; j < numcols; j++) {
402 	    r = PQgetvalue(query_res, i, j);
403 	    if (db_common_expand(dict_pgsql->ctx, dict_pgsql->result_format,
404 				 r, name, result, 0)
405 		&& dict_pgsql->expansion_limit > 0
406 		&& ++expansion > dict_pgsql->expansion_limit) {
407 		msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
408 			 myname, dict_pgsql->parser->name, name);
409 		dict->error = DICT_ERR_RETRY;
410 		break;
411 	    }
412 	}
413     }
414     PQclear(query_res);
415     r = vstring_str(result);
416     return ((dict->error == 0 && *r) ? r : 0);
417 }
418 
419 /* dict_pgsql_check_stat - check the status of a host */
420 
421 static int dict_pgsql_check_stat(HOST *host, unsigned stat, unsigned type,
422 				         time_t t)
423 {
424     if ((host->stat & stat) && (!type || host->type & type)) {
425 	/* try not to hammer the dead hosts too often */
426 	if (host->stat == STATFAIL && host->ts > 0 && host->ts >= t)
427 	    return 0;
428 	return 1;
429     }
430     return 0;
431 }
432 
433 /* dict_pgsql_find_host - find a host with the given status */
434 
435 static HOST *dict_pgsql_find_host(PLPGSQL *PLDB, unsigned stat, unsigned type)
436 {
437     time_t  t;
438     int     count = 0;
439     int     idx;
440     int     i;
441 
442     t = time((time_t *) 0);
443     for (i = 0; i < PLDB->len_hosts; i++) {
444 	if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t))
445 	    count++;
446     }
447 
448     if (count) {
449 	idx = (count > 1) ?
450 	    1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
451 
452 	for (i = 0; i < PLDB->len_hosts; i++) {
453 	    if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t) &&
454 		--idx == 0)
455 		return PLDB->db_hosts[i];
456 	}
457     }
458     return 0;
459 }
460 
461 /* dict_pgsql_get_active - get an active connection */
462 
463 static HOST *dict_pgsql_get_active(PLPGSQL *PLDB, char *dbname,
464 				           char *username, char *password)
465 {
466     const char *myname = "dict_pgsql_get_active";
467     HOST   *host;
468     int     count = RETRY_CONN_MAX;
469 
470     /* try the active connections first; prefer the ones to UNIX sockets */
471     if ((host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL ||
472 	(host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL) {
473 	if (msg_verbose)
474 	    msg_info("%s: found active connection to host %s", myname,
475 		     host->hostname);
476 	return host;
477     }
478 
479     /*
480      * Try the remaining hosts. "count" is a safety net, in case the loop
481      * takes more than RETRY_CONN_INTV and the dead hosts are no longer
482      * skipped.
483      */
484     while (--count > 0 &&
485 	   ((host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
486 					 TYPEUNIX)) != NULL ||
487 	    (host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
488 					 TYPEINET)) != NULL)) {
489 	if (msg_verbose)
490 	    msg_info("%s: attempting to connect to host %s", myname,
491 		     host->hostname);
492 	plpgsql_connect_single(host, dbname, username, password);
493 	if (host->stat == STATACTIVE)
494 	    return host;
495     }
496 
497     /* bad news... */
498     return 0;
499 }
500 
501 /* dict_pgsql_event - callback: close idle connections */
502 
503 static void dict_pgsql_event(int unused_event, void *context)
504 {
505     HOST   *host = (HOST *) context;
506 
507     if (host->db)
508 	plpgsql_close_host(host);
509 }
510 
511 /*
512  * plpgsql_query - process a PostgreSQL query.  Return PGSQL_RES* on success.
513  *			On failure, log failure and try other db instances.
514  *			on failure of all db instances, return 0;
515  *			close unnecessary active connections
516  */
517 
518 static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
519 				        const char *name,
520 				        VSTRING *query,
521 				        char *dbname,
522 				        char *username,
523 				        char *password)
524 {
525     PLPGSQL *PLDB = dict_pgsql->pldb;
526     HOST   *host;
527     PGSQL_RES *res = 0;
528     ExecStatusType status;
529 
530     while ((host = dict_pgsql_get_active(PLDB, dbname, username, password)) != NULL) {
531 
532 	/*
533 	 * The active host is used to escape strings in the context of the
534 	 * active connection's character encoding.
535 	 */
536 	dict_pgsql->active_host = host;
537 	VSTRING_RESET(query);
538 	VSTRING_TERMINATE(query);
539 	db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
540 			 name, 0, query, dict_pgsql_quote);
541 	dict_pgsql->active_host = 0;
542 
543 	/* Check for potential dict_pgsql_quote() failure. */
544 	if (host->stat == STATFAIL) {
545 	    plpgsql_down_host(host);
546 	    continue;
547 	}
548 
549 	/*
550 	 * Submit a command to the server. Be paranoid when processing the
551 	 * result set: try to enumerate every successful case, and reject
552 	 * everything else.
553 	 *
554 	 * From PostgreSQL 8.1.4 docs: (PQexec) returns a PGresult pointer or
555 	 * possibly a null pointer. A non-null pointer will generally be
556 	 * returned except in out-of-memory conditions or serious errors such
557 	 * as inability to send the command to the server.
558 	 */
559 	if ((res = PQexec(host->db, vstring_str(query))) != 0) {
560 
561 	    /*
562 	     * XXX Because non-null result pointer does not imply success, we
563 	     * need to check the command's result status.
564 	     *
565 	     * Section 28.3.1: A result of status PGRES_NONFATAL_ERROR will
566 	     * never be returned directly by PQexec or other query execution
567 	     * functions; results of this kind are instead passed to the
568 	     * notice processor.
569 	     *
570 	     * PGRES_EMPTY_QUERY is being sent by the server when the query
571 	     * string is empty. The sanity-checking done by the Postfix
572 	     * infrastructure makes this case impossible, so we need not
573 	     * handle this situation explicitly.
574 	     */
575 	    switch ((status = PQresultStatus(res))) {
576 	    case PGRES_TUPLES_OK:
577 	    case PGRES_COMMAND_OK:
578 		/* Success. */
579 		if (msg_verbose)
580 		    msg_info("dict_pgsql: successful query from host %s",
581 			     host->hostname);
582 		event_request_timer(dict_pgsql_event, (void *) host,
583 				    IDLE_CONN_INTV);
584 		return (res);
585 	    case PGRES_FATAL_ERROR:
586 		msg_warn("pgsql query failed: fatal error from host %s: %s",
587 			 host->hostname, PQresultErrorMessage(res));
588 		break;
589 	    case PGRES_BAD_RESPONSE:
590 		msg_warn("pgsql query failed: protocol error, host %s",
591 			 host->hostname);
592 		break;
593 	    default:
594 		msg_warn("pgsql query failed: unknown code 0x%lx from host %s",
595 			 (unsigned long) status, host->hostname);
596 		break;
597 	    }
598 	} else {
599 
600 	    /*
601 	     * This driver treats null pointers like fatal, non-null result
602 	     * pointer errors, as suggested by the PostgreSQL 8.1.4
603 	     * documentation.
604 	     */
605 	    msg_warn("pgsql query failed: fatal error from host %s: %s",
606 		     host->hostname, PQerrorMessage(host->db));
607 	}
608 
609 	/*
610 	 * XXX An error occurred. Clean up memory and skip this connection.
611 	 */
612 	if (res != 0)
613 	    PQclear(res);
614 	plpgsql_down_host(host);
615     }
616 
617     return (0);
618 }
619 
620 /*
621  * plpgsql_connect_single -
622  * used to reconnect to a single database when one is down or none is
623  * connected yet. Log all errors and set the stat field of host accordingly
624  */
625 static void plpgsql_connect_single(HOST *host, char *dbname, char *username, char *password)
626 {
627     if ((host->db = PQsetdbLogin(host->name, host->port, NULL, NULL,
628 				 dbname, username, password)) == NULL
629 	|| PQstatus(host->db) != CONNECTION_OK) {
630 	msg_warn("connect to pgsql server %s: %s",
631 		 host->hostname, PQerrorMessage(host->db));
632 	plpgsql_down_host(host);
633 	return;
634     }
635     if (msg_verbose)
636 	msg_info("dict_pgsql: successful connection to host %s",
637 		 host->hostname);
638 
639     /*
640      * XXX Postfix does not send multi-byte characters. The following piece
641      * of code is an explicit statement of this fact, and the database server
642      * should not accept multi-byte information after this point.
643      */
644     if (PQsetClientEncoding(host->db, "LATIN1") != 0) {
645 	msg_warn("dict_pgsql: cannot set the encoding to LATIN1, skipping %s",
646 		 host->hostname);
647 	plpgsql_down_host(host);
648 	return;
649     }
650     /* Success. */
651     host->stat = STATACTIVE;
652 }
653 
654 /* plpgsql_close_host - close an established PostgreSQL connection */
655 
656 static void plpgsql_close_host(HOST *host)
657 {
658     if (host->db)
659 	PQfinish(host->db);
660     host->db = 0;
661     host->stat = STATUNTRIED;
662 }
663 
664 /*
665  * plpgsql_down_host - close a failed connection AND set a "stay away from
666  * this host" timer.
667  */
668 static void plpgsql_down_host(HOST *host)
669 {
670     if (host->db)
671 	PQfinish(host->db);
672     host->db = 0;
673     host->ts = time((time_t *) 0) + RETRY_CONN_INTV;
674     host->stat = STATFAIL;
675     event_cancel_timer(dict_pgsql_event, (void *) host);
676 }
677 
678 /* pgsql_parse_config - parse pgsql configuration file */
679 
680 static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
681 {
682     const char *myname = "pgsql_parse_config";
683     CFG_PARSER *p = dict_pgsql->parser;
684     char   *hosts;
685     VSTRING *query;
686     char   *select_function;
687 
688     dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0);
689     dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0);
690     dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
691     dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
692 
693     /*
694      * XXX: The default should be non-zero for safety, but that is not
695      * backwards compatible.
696      */
697     dict_pgsql->expansion_limit = cfg_get_int(dict_pgsql->parser,
698 					      "expansion_limit", 0, 0, 0);
699 
700     if ((dict_pgsql->query = cfg_get_str(p, "query", 0, 0, 0)) == 0) {
701 
702 	/*
703 	 * No query specified -- fallback to building it from components (
704 	 * old style "select %s from %s where %s" )
705 	 */
706 	query = vstring_alloc(64);
707 	select_function = cfg_get_str(p, "select_function", 0, 0, 0);
708 	if (select_function != 0) {
709 	    vstring_sprintf(query, "SELECT %s('%%s')", select_function);
710 	    myfree(select_function);
711 	} else
712 	    db_common_sql_build_query(query, p);
713 	dict_pgsql->query = vstring_export(query);
714     }
715 
716     /*
717      * Must parse all templates before we can use db_common_expand()
718      */
719     dict_pgsql->ctx = 0;
720     (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx,
721 			   dict_pgsql->query, 1);
722     (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0);
723     db_common_parse_domain(p, dict_pgsql->ctx);
724 
725     /*
726      * Maps that use substring keys should only be used with the full input
727      * key.
728      */
729     if (db_common_dict_partial(dict_pgsql->ctx))
730 	dict_pgsql->dict.flags |= DICT_FLAG_PATTERN;
731     else
732 	dict_pgsql->dict.flags |= DICT_FLAG_FIXED;
733     if (dict_pgsql->dict.flags & DICT_FLAG_FOLD_FIX)
734 	dict_pgsql->dict.fold_buf = vstring_alloc(10);
735 
736     hosts = cfg_get_str(p, "hosts", "", 0, 0);
737 
738     dict_pgsql->hosts = argv_split(hosts, CHARS_COMMA_SP);
739     if (dict_pgsql->hosts->argc == 0) {
740 	argv_add(dict_pgsql->hosts, "localhost", ARGV_END);
741 	argv_terminate(dict_pgsql->hosts);
742 	if (msg_verbose)
743 	    msg_info("%s: %s: no hostnames specified, defaulting to '%s'",
744 		     myname, pgsqlcf, dict_pgsql->hosts->argv[0]);
745     }
746     myfree(hosts);
747 }
748 
749 /* dict_pgsql_open - open PGSQL data base */
750 
751 DICT   *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
752 {
753     DICT_PGSQL *dict_pgsql;
754     CFG_PARSER *parser;
755 
756     /*
757      * Sanity check.
758      */
759     if (open_flags != O_RDONLY)
760 	return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
761 			       "%s:%s map requires O_RDONLY access mode",
762 			       DICT_TYPE_PGSQL, name));
763 
764     /*
765      * Open the configuration file.
766      */
767     if ((parser = cfg_parser_alloc(name)) == 0)
768 	return (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
769 			       "open %s: %m", name));
770 
771     dict_pgsql = (DICT_PGSQL *) dict_alloc(DICT_TYPE_PGSQL, name,
772 					   sizeof(DICT_PGSQL));
773     dict_pgsql->dict.lookup = dict_pgsql_lookup;
774     dict_pgsql->dict.close = dict_pgsql_close;
775     dict_pgsql->dict.flags = dict_flags;
776     dict_pgsql->parser = parser;
777     pgsql_parse_config(dict_pgsql, name);
778     dict_pgsql->active_host = 0;
779     dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts);
780     if (dict_pgsql->pldb == NULL)
781 	msg_fatal("couldn't intialize pldb!\n");
782     dict_pgsql->dict.owner = cfg_get_owner(dict_pgsql->parser);
783     return (DICT_DEBUG (&dict_pgsql->dict));
784 }
785 
786 /* plpgsql_init - initalize a PGSQL database */
787 
788 static PLPGSQL *plpgsql_init(ARGV *hosts)
789 {
790     PLPGSQL *PLDB;
791     int     i;
792 
793     PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL));
794     PLDB->len_hosts = hosts->argc;
795     PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc);
796     for (i = 0; i < hosts->argc; i++)
797 	PLDB->db_hosts[i] = host_init(hosts->argv[i]);
798 
799     return PLDB;
800 }
801 
802 
803 /* host_init - initialize HOST structure */
804 
805 static HOST *host_init(const char *hostname)
806 {
807     const char *myname = "pgsql host_init";
808     HOST   *host = (HOST *) mymalloc(sizeof(HOST));
809     const char *d = hostname;
810 
811     host->db = 0;
812     host->hostname = mystrdup(hostname);
813     host->stat = STATUNTRIED;
814     host->ts = 0;
815 
816     /*
817      * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where
818      * both "inet:" and ":port" are optional.
819      */
820     if (strncmp(d, "unix:", 5) == 0 || strncmp(d, "inet:", 5) == 0)
821 	d += 5;
822     host->name = mystrdup(d);
823     host->port = split_at_right(host->name, ':');
824 
825     /* This is how PgSQL distinguishes between UNIX and INET: */
826     if (host->name[0] && host->name[0] != '/')
827 	host->type = TYPEINET;
828     else
829 	host->type = TYPEUNIX;
830 
831     if (msg_verbose > 1)
832 	msg_info("%s: host=%s, port=%s, type=%s", myname, host->name,
833 		 host->port ? host->port : "",
834 		 host->type == TYPEUNIX ? "unix" : "inet");
835     return host;
836 }
837 
838 /* dict_pgsql_close - close PGSQL data base */
839 
840 static void dict_pgsql_close(DICT *dict)
841 {
842     DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
843 
844     plpgsql_dealloc(dict_pgsql->pldb);
845     cfg_parser_free(dict_pgsql->parser);
846     myfree(dict_pgsql->username);
847     myfree(dict_pgsql->password);
848     myfree(dict_pgsql->dbname);
849     myfree(dict_pgsql->query);
850     myfree(dict_pgsql->result_format);
851     if (dict_pgsql->hosts)
852 	argv_free(dict_pgsql->hosts);
853     if (dict_pgsql->ctx)
854 	db_common_free_ctx(dict_pgsql->ctx);
855     if (dict->fold_buf)
856 	vstring_free(dict->fold_buf);
857     dict_free(dict);
858 }
859 
860 /* plpgsql_dealloc - free memory associated with PLPGSQL close databases */
861 
862 static void plpgsql_dealloc(PLPGSQL *PLDB)
863 {
864     int     i;
865 
866     for (i = 0; i < PLDB->len_hosts; i++) {
867 	event_cancel_timer(dict_pgsql_event, (void *) (PLDB->db_hosts[i]));
868 	if (PLDB->db_hosts[i]->db)
869 	    PQfinish(PLDB->db_hosts[i]->db);
870 	myfree(PLDB->db_hosts[i]->hostname);
871 	myfree(PLDB->db_hosts[i]->name);
872 	myfree((void *) PLDB->db_hosts[i]);
873     }
874     myfree((void *) PLDB->db_hosts);
875     myfree((void *) (PLDB));
876 }
877 
878 #endif
879