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 119typedef 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 129typedef struct { 130 int len_hosts; /* number of hosts */ 131 HOST **db_hosts; /* hosts on which databases reside */ 132} PLPGSQL; 133 134typedef 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 */ 156static PLPGSQL *plpgsql_init(ARGV *); 157static PGSQL_RES *plpgsql_query(DICT_PGSQL *, const char *, VSTRING *, char *, 158 char *, char *, char *); 159static void plpgsql_dealloc(PLPGSQL *); 160static void plpgsql_close_host(HOST *); 161static void plpgsql_down_host(HOST *); 162static void plpgsql_connect_single(HOST *, char *, char *, char *, char *); 163static const char *dict_pgsql_lookup(DICT *, const char *); 164DICT *dict_pgsql_open(const char *, int, int); 165static void dict_pgsql_close(DICT *); 166static HOST *host_init(const char *); 167 168/* dict_pgsql_quote - escape SQL metacharacters in input string */ 169 170static 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 251static 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 367static 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 381static 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 409static 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 452static 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 467static 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 */ 575static 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 604static 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 */ 616static 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 628static 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 700DICT *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 737static 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 754static 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 802static 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 825static 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