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