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