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