1 /* $NetBSD: dict_mysql.c,v 1.1.1.3 2013/01/02 18:58:57 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_mysql 3 6 /* SUMMARY 7 /* dictionary manager interface to MySQL databases 8 /* SYNOPSIS 9 /* #include <dict_mysql.h> 10 /* 11 /* DICT *dict_mysql_open(name, open_flags, dict_flags) 12 /* const char *name; 13 /* int open_flags; 14 /* int dict_flags; 15 /* DESCRIPTION 16 /* dict_mysql_open() creates a dictionary of type 'mysql'. This 17 /* dictionary is an interface for the postfix key->value mappings 18 /* to mysql. The result is a pointer to the installed dictionary, 19 /* or a null pointer in case of problems. 20 /* 21 /* The mysql dictionary can manage multiple connections to different 22 /* sql servers on different hosts. It assumes that the underlying data 23 /* on each host is identical (mirrored) and maintains one connection 24 /* at any given time. If any connection fails, any other available 25 /* ones will be opened and used. The intent of this feature is to eliminate 26 /* a single point of failure for mail systems that would otherwise rely 27 /* on a single mysql server. 28 /* .PP 29 /* Arguments: 30 /* .IP name 31 /* Either the path to the MySQL configuration file (if it starts 32 /* with '/' or '.'), or the prefix which will be used to obtain 33 /* main.cf configuration parameters for this search. 34 /* 35 /* In the first case, the configuration parameters below are 36 /* specified in the file as \fIname\fR=\fIvalue\fR pairs. 37 /* 38 /* In the second case, the configuration parameters are 39 /* prefixed with the value of \fIname\fR and an underscore, 40 /* and they are specified in main.cf. For example, if this 41 /* value is \fImysqlsource\fR, the parameters would look like 42 /* \fImysqlsource_user\fR, \fImysqlsource_table\fR, and so on. 43 /* 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 /* .PP 51 /* Configuration parameters: 52 /* .IP user 53 /* Username for connecting to the database. 54 /* .IP password 55 /* Password for the above. 56 /* .IP dbname 57 /* Name of the database. 58 /* .IP domain 59 /* List of domains the queries should be restricted to. If 60 /* specified, only FQDN addresses whose domain parts matching this 61 /* list will be queried against the SQL database. Lookups for 62 /* partial addresses are also supressed. This can significantly 63 /* reduce the query load on the server. 64 /* .IP query 65 /* Query template, before the query is actually issued, variable 66 /* substitutions are performed. See mysql_table(5) for details. If 67 /* No query is specified, the legacy variables \fItable\fR, 68 /* \fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR 69 /* are used to construct the query template. 70 /* .IP result_format 71 /* The format used to expand results from queries. Substitutions 72 /* are performed as described in mysql_table(5). Defaults to returning 73 /* the lookup result unchanged. 74 /* .IP expansion_limit 75 /* Limit (if any) on the total number of lookup result values. Lookups which 76 /* exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that each 77 /* non-empty (and non-NULL) column of a multi-column result row counts as 78 /* one result. 79 /* .IP table 80 /* When \fIquery\fR is not set, name of the table used to construct the 81 /* query string. This provides compatibility with older releases. 82 /* .IP select_field 83 /* When \fIquery\fR is not set, name of the result field used to 84 /* construct the query string. This provides compatibility with older 85 /* releases. 86 /* .IP where_field 87 /* When \fIquery\fR is not set, name of the where clause field used to 88 /* construct the query string. This provides compatibility with older 89 /* releases. 90 /* .IP additional_conditions 91 /* When \fIquery\fR is not set, additional where clause conditions used 92 /* to construct the query string. This provides compatibility with older 93 /* releases. 94 /* .IP hosts 95 /* List of hosts to connect to. 96 /* .PP 97 /* For example, if you want the map to reference databases of 98 /* the name "your_db" and execute a query like this: select 99 /* forw_addr from aliases where alias like '<some username>' 100 /* against any database called "vmailer_info" located on hosts 101 /* host1.some.domain and host2.some.domain, logging in as user 102 /* "vmailer" and password "passwd" then the configuration file 103 /* should read: 104 /* .PP 105 /* user = vmailer 106 /* .br 107 /* password = passwd 108 /* .br 109 /* dbname = vmailer_info 110 /* .br 111 /* table = aliases 112 /* .br 113 /* select_field = forw_addr 114 /* .br 115 /* where_field = alias 116 /* .br 117 /* hosts = host1.some.domain\fR \fBhost2.some.domain 118 /* .IP additional_conditions 119 /* Backward compatibility when \fIquery\fR is not set, additional 120 /* conditions to the WHERE clause. 121 /* .IP hosts 122 /* List of hosts to connect to. 123 /* .PP 124 /* For example, if you want the map to reference databases of 125 /* the name "your_db" and execute a query like this: select 126 /* forw_addr from aliases where alias like '<some username>' 127 /* against any database called "vmailer_info" located on hosts 128 /* host1.some.domain and host2.some.domain, logging in as user 129 /* "vmailer" and password "passwd" then the configuration file 130 /* should read: 131 /* .PP 132 /* user = vmailer 133 /* .br 134 /* password = passwd 135 /* .br 136 /* dbname = vmailer_info 137 /* .br 138 /* table = aliases 139 /* .br 140 /* select_field = forw_addr 141 /* .br 142 /* where_field = alias 143 /* .br 144 /* hosts = host1.some.domain\fR \fBhost2.some.domain 145 /* .PP 146 /* SEE ALSO 147 /* dict(3) generic dictionary manager 148 /* AUTHOR(S) 149 /* Scott Cotton 150 /* IC Group, Inc. 151 /* scott@icgroup.com 152 /* 153 /* Joshua Marcus 154 /* IC Group, Inc. 155 /* josh@icgroup.com 156 /*--*/ 157 158 /* System library. */ 159 #include "sys_defs.h" 160 161 #ifdef HAS_MYSQL 162 #include <sys/socket.h> 163 #include <netinet/in.h> 164 #include <arpa/inet.h> 165 #include <netdb.h> 166 #include <stdio.h> 167 #include <string.h> 168 #include <stdlib.h> 169 #include <syslog.h> 170 #include <time.h> 171 #include <mysql.h> 172 173 #ifdef STRCASECMP_IN_STRINGS_H 174 #include <strings.h> 175 #endif 176 177 /* Utility library. */ 178 179 #include "dict.h" 180 #include "msg.h" 181 #include "mymalloc.h" 182 #include "argv.h" 183 #include "vstring.h" 184 #include "split_at.h" 185 #include "find_inet.h" 186 #include "myrand.h" 187 #include "events.h" 188 #include "stringops.h" 189 190 /* Global library. */ 191 192 #include "cfg_parser.h" 193 #include "db_common.h" 194 195 /* Application-specific. */ 196 197 #include "dict_mysql.h" 198 199 /* need some structs to help organize things */ 200 typedef struct { 201 MYSQL *db; 202 char *hostname; 203 char *name; 204 unsigned port; 205 unsigned type; /* TYPEUNIX | TYPEINET */ 206 unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */ 207 time_t ts; /* used for attempting reconnection 208 * every so often if a host is down */ 209 } HOST; 210 211 typedef struct { 212 int len_hosts; /* number of hosts */ 213 HOST **db_hosts; /* the hosts on which the databases 214 * reside */ 215 } PLMYSQL; 216 217 typedef struct { 218 DICT dict; 219 CFG_PARSER *parser; 220 char *query; 221 char *result_format; 222 void *ctx; 223 int expansion_limit; 224 char *username; 225 char *password; 226 char *dbname; 227 ARGV *hosts; 228 PLMYSQL *pldb; 229 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 230 HOST *active_host; 231 #endif 232 } DICT_MYSQL; 233 234 #define STATACTIVE (1<<0) 235 #define STATFAIL (1<<1) 236 #define STATUNTRIED (1<<2) 237 238 #define TYPEUNIX (1<<0) 239 #define TYPEINET (1<<1) 240 241 #define RETRY_CONN_MAX 100 242 #define RETRY_CONN_INTV 60 /* 1 minute */ 243 #define IDLE_CONN_INTV 60 /* 1 minute */ 244 245 /* internal function declarations */ 246 static PLMYSQL *plmysql_init(ARGV *); 247 static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *, char *, 248 char *, char *); 249 static void plmysql_dealloc(PLMYSQL *); 250 static void plmysql_close_host(HOST *); 251 static void plmysql_down_host(HOST *); 252 static void plmysql_connect_single(HOST *, char *, char *, char *); 253 static const char *dict_mysql_lookup(DICT *, const char *); 254 DICT *dict_mysql_open(const char *, int, int); 255 static void dict_mysql_close(DICT *); 256 static void mysql_parse_config(DICT_MYSQL *, const char *); 257 static HOST *host_init(const char *); 258 259 /* dict_mysql_quote - escape SQL metacharacters in input string */ 260 261 static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result) 262 { 263 DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; 264 int len = strlen(name); 265 int buflen = 2 * len + 1; 266 267 /* 268 * We won't get integer overflows in 2*len + 1, because Postfix input 269 * keys have reasonable size limits, better safe than sorry. 270 */ 271 if (buflen < len) 272 msg_panic("dict_mysql_quote: integer overflow in 2*%d+1", len); 273 VSTRING_SPACE(result, buflen); 274 275 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 276 if (dict_mysql->active_host) 277 mysql_real_escape_string(dict_mysql->active_host->db, 278 vstring_end(result), name, len); 279 else 280 #endif 281 mysql_escape_string(vstring_end(result), name, len); 282 283 VSTRING_SKIP(result); 284 } 285 286 /* dict_mysql_lookup - find database entry */ 287 288 static const char *dict_mysql_lookup(DICT *dict, const char *name) 289 { 290 const char *myname = "dict_mysql_lookup"; 291 DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; 292 MYSQL_RES *query_res; 293 MYSQL_ROW row; 294 static VSTRING *result; 295 static VSTRING *query; 296 int i; 297 int j; 298 int numrows; 299 int expansion; 300 const char *r; 301 db_quote_callback_t quote_func = dict_mysql_quote; 302 int domain_rc; 303 304 dict->error = 0; 305 306 /* 307 * Optionally fold the key. 308 */ 309 if (dict->flags & DICT_FLAG_FOLD_FIX) { 310 if (dict->fold_buf == 0) 311 dict->fold_buf = vstring_alloc(10); 312 vstring_strcpy(dict->fold_buf, name); 313 name = lowercase(vstring_str(dict->fold_buf)); 314 } 315 316 /* 317 * If there is a domain list for this map, then only search for addresses 318 * in domains on the list. This can significantly reduce the load on the 319 * server. 320 */ 321 if ((domain_rc = db_common_check_domain(dict_mysql->ctx, name)) == 0) { 322 if (msg_verbose) 323 msg_info("%s: Skipping lookup of '%s'", myname, name); 324 return (0); 325 } 326 if (domain_rc < 0) 327 DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0); 328 329 #define INIT_VSTR(buf, len) do { \ 330 if (buf == 0) \ 331 buf = vstring_alloc(len); \ 332 VSTRING_RESET(buf); \ 333 VSTRING_TERMINATE(buf); \ 334 } while (0) 335 336 INIT_VSTR(query, 10); 337 338 /* 339 * Suppress the lookup if the query expansion is empty 340 * 341 * This initial expansion is outside the context of any specific host 342 * connection, we just want to check the key pre-requisites, so when 343 * quoting happens separately for each connection, we don't bother with 344 * quoting... 345 */ 346 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 347 quote_func = 0; 348 #endif 349 if (!db_common_expand(dict_mysql->ctx, dict_mysql->query, 350 name, 0, query, quote_func)) 351 return (0); 352 353 /* do the query - set dict->error & cleanup if there's an error */ 354 if ((query_res = plmysql_query(dict_mysql, name, query, 355 dict_mysql->dbname, 356 dict_mysql->username, 357 dict_mysql->password)) == 0) { 358 dict->error = DICT_ERR_RETRY; 359 return (0); 360 } 361 numrows = mysql_num_rows(query_res); 362 if (msg_verbose) 363 msg_info("%s: retrieved %d rows", myname, numrows); 364 if (numrows == 0) { 365 mysql_free_result(query_res); 366 return 0; 367 } 368 INIT_VSTR(result, 10); 369 370 for (expansion = i = 0; i < numrows && dict->error == 0; i++) { 371 row = mysql_fetch_row(query_res); 372 for (j = 0; j < mysql_num_fields(query_res); j++) { 373 if (db_common_expand(dict_mysql->ctx, dict_mysql->result_format, 374 row[j], name, result, 0) 375 && dict_mysql->expansion_limit > 0 376 && ++expansion > dict_mysql->expansion_limit) { 377 msg_warn("%s: %s: Expansion limit exceeded for key: '%s'", 378 myname, dict_mysql->parser->name, name); 379 dict->error = DICT_ERR_RETRY; 380 break; 381 } 382 } 383 } 384 mysql_free_result(query_res); 385 r = vstring_str(result); 386 return ((dict->error == 0 && *r) ? r : 0); 387 } 388 389 /* dict_mysql_check_stat - check the status of a host */ 390 391 static int dict_mysql_check_stat(HOST *host, unsigned stat, unsigned type, 392 time_t t) 393 { 394 if ((host->stat & stat) && (!type || host->type & type)) { 395 /* try not to hammer the dead hosts too often */ 396 if (host->stat == STATFAIL && host->ts > 0 && host->ts >= t) 397 return 0; 398 return 1; 399 } 400 return 0; 401 } 402 403 /* dict_mysql_find_host - find a host with the given status */ 404 405 static HOST *dict_mysql_find_host(PLMYSQL *PLDB, unsigned stat, unsigned type) 406 { 407 time_t t; 408 int count = 0; 409 int idx; 410 int i; 411 412 t = time((time_t *) 0); 413 for (i = 0; i < PLDB->len_hosts; i++) { 414 if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t)) 415 count++; 416 } 417 418 if (count) { 419 idx = (count > 1) ? 420 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1; 421 422 for (i = 0; i < PLDB->len_hosts; i++) { 423 if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t) && 424 --idx == 0) 425 return PLDB->db_hosts[i]; 426 } 427 } 428 return 0; 429 } 430 431 /* dict_mysql_get_active - get an active connection */ 432 433 static HOST *dict_mysql_get_active(PLMYSQL *PLDB, char *dbname, 434 char *username, char *password) 435 { 436 const char *myname = "dict_mysql_get_active"; 437 HOST *host; 438 int count = RETRY_CONN_MAX; 439 440 /* Try the active connections first; prefer the ones to UNIX sockets. */ 441 if ((host = dict_mysql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL || 442 (host = dict_mysql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL) { 443 if (msg_verbose) 444 msg_info("%s: found active connection to host %s", myname, 445 host->hostname); 446 return host; 447 } 448 449 /* 450 * Try the remaining hosts. "count" is a safety net, in case the loop 451 * takes more than RETRY_CONN_INTV and the dead hosts are no longer 452 * skipped. 453 */ 454 while (--count > 0 && 455 ((host = dict_mysql_find_host(PLDB, STATUNTRIED | STATFAIL, 456 TYPEUNIX)) != NULL || 457 (host = dict_mysql_find_host(PLDB, STATUNTRIED | STATFAIL, 458 TYPEINET)) != NULL)) { 459 if (msg_verbose) 460 msg_info("%s: attempting to connect to host %s", myname, 461 host->hostname); 462 plmysql_connect_single(host, dbname, username, password); 463 if (host->stat == STATACTIVE) 464 return host; 465 } 466 467 /* bad news... */ 468 return 0; 469 } 470 471 /* dict_mysql_event - callback: close idle connections */ 472 473 static void dict_mysql_event(int unused_event, char *context) 474 { 475 HOST *host = (HOST *) context; 476 477 if (host->db) 478 plmysql_close_host(host); 479 } 480 481 /* 482 * plmysql_query - process a MySQL query. Return MYSQL_RES* on success. 483 * On failure, log failure and try other db instances. 484 * on failure of all db instances, return 0; 485 * close unnecessary active connections 486 */ 487 488 static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql, 489 const char *name, 490 VSTRING *query, 491 char *dbname, 492 char *username, 493 char *password) 494 { 495 PLMYSQL *PLDB = dict_mysql->pldb; 496 HOST *host; 497 MYSQL_RES *res = 0; 498 499 while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) { 500 501 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 502 503 /* 504 * The active host is used to escape strings in the context of the 505 * active connection's character encoding. 506 */ 507 dict_mysql->active_host = host; 508 VSTRING_RESET(query); 509 VSTRING_TERMINATE(query); 510 db_common_expand(dict_mysql->ctx, dict_mysql->query, 511 name, 0, query, dict_mysql_quote); 512 dict_mysql->active_host = 0; 513 #endif 514 515 if (!(mysql_query(host->db, vstring_str(query)))) { 516 if ((res = mysql_store_result(host->db)) == 0) { 517 msg_warn("mysql query failed: %s", mysql_error(host->db)); 518 plmysql_down_host(host); 519 } else { 520 if (msg_verbose) 521 msg_info("dict_mysql: successful query from host %s", host->hostname); 522 event_request_timer(dict_mysql_event, (char *) host, IDLE_CONN_INTV); 523 break; 524 } 525 } else { 526 msg_warn("mysql query failed: %s", mysql_error(host->db)); 527 plmysql_down_host(host); 528 } 529 } 530 531 return res; 532 } 533 534 /* 535 * plmysql_connect_single - 536 * used to reconnect to a single database when one is down or none is 537 * connected yet. Log all errors and set the stat field of host accordingly 538 */ 539 static void plmysql_connect_single(HOST *host, char *dbname, char *username, char *password) 540 { 541 if ((host->db = mysql_init(NULL)) == NULL) 542 msg_fatal("dict_mysql: insufficient memory"); 543 if (mysql_real_connect(host->db, 544 (host->type == TYPEINET ? host->name : 0), 545 username, 546 password, 547 dbname, 548 host->port, 549 (host->type == TYPEUNIX ? host->name : 0), 550 0)) { 551 if (msg_verbose) 552 msg_info("dict_mysql: successful connection to host %s", 553 host->hostname); 554 host->stat = STATACTIVE; 555 } else { 556 msg_warn("connect to mysql server %s: %s", 557 host->hostname, mysql_error(host->db)); 558 plmysql_down_host(host); 559 } 560 } 561 562 /* plmysql_close_host - close an established MySQL connection */ 563 static void plmysql_close_host(HOST *host) 564 { 565 mysql_close(host->db); 566 host->db = 0; 567 host->stat = STATUNTRIED; 568 } 569 570 /* 571 * plmysql_down_host - close a failed connection AND set a "stay away from 572 * this host" timer 573 */ 574 static void plmysql_down_host(HOST *host) 575 { 576 mysql_close(host->db); 577 host->db = 0; 578 host->ts = time((time_t *) 0) + RETRY_CONN_INTV; 579 host->stat = STATFAIL; 580 event_cancel_timer(dict_mysql_event, (char *) host); 581 } 582 583 /* mysql_parse_config - parse mysql configuration file */ 584 585 static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) 586 { 587 const char *myname = "mysqlname_parse"; 588 CFG_PARSER *p = dict_mysql->parser; 589 VSTRING *buf; 590 char *hosts; 591 592 dict_mysql->username = cfg_get_str(p, "user", "", 0, 0); 593 dict_mysql->password = cfg_get_str(p, "password", "", 0, 0); 594 dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0); 595 dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); 596 597 /* 598 * XXX: The default should be non-zero for safety, but that is not 599 * backwards compatible. 600 */ 601 dict_mysql->expansion_limit = cfg_get_int(dict_mysql->parser, 602 "expansion_limit", 0, 0, 0); 603 604 if ((dict_mysql->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) { 605 606 /* 607 * No query specified -- fallback to building it from components (old 608 * style "select %s from %s where %s") 609 */ 610 buf = vstring_alloc(64); 611 db_common_sql_build_query(buf, p); 612 dict_mysql->query = vstring_export(buf); 613 } 614 615 /* 616 * Must parse all templates before we can use db_common_expand() 617 */ 618 dict_mysql->ctx = 0; 619 (void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx, 620 dict_mysql->query, 1); 621 (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0); 622 db_common_parse_domain(p, dict_mysql->ctx); 623 624 /* 625 * Maps that use substring keys should only be used with the full input 626 * key. 627 */ 628 if (db_common_dict_partial(dict_mysql->ctx)) 629 dict_mysql->dict.flags |= DICT_FLAG_PATTERN; 630 else 631 dict_mysql->dict.flags |= DICT_FLAG_FIXED; 632 if (dict_mysql->dict.flags & DICT_FLAG_FOLD_FIX) 633 dict_mysql->dict.fold_buf = vstring_alloc(10); 634 635 hosts = cfg_get_str(p, "hosts", "", 0, 0); 636 637 dict_mysql->hosts = argv_split(hosts, " ,\t\r\n"); 638 if (dict_mysql->hosts->argc == 0) { 639 argv_add(dict_mysql->hosts, "localhost", ARGV_END); 640 argv_terminate(dict_mysql->hosts); 641 if (msg_verbose) 642 msg_info("%s: %s: no hostnames specified, defaulting to '%s'", 643 myname, mysqlcf, dict_mysql->hosts->argv[0]); 644 } 645 myfree(hosts); 646 } 647 648 /* dict_mysql_open - open MYSQL data base */ 649 650 DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags) 651 { 652 DICT_MYSQL *dict_mysql; 653 CFG_PARSER *parser; 654 655 /* 656 * Sanity checks. 657 */ 658 if (open_flags != O_RDONLY) 659 return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags, 660 "%s:%s map requires O_RDONLY access mode", 661 DICT_TYPE_MYSQL, name)); 662 663 /* 664 * Open the configuration file. 665 */ 666 if ((parser = cfg_parser_alloc(name)) == 0) 667 return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags, 668 "open %s: %m", name)); 669 670 dict_mysql = (DICT_MYSQL *) dict_alloc(DICT_TYPE_MYSQL, name, 671 sizeof(DICT_MYSQL)); 672 dict_mysql->dict.lookup = dict_mysql_lookup; 673 dict_mysql->dict.close = dict_mysql_close; 674 dict_mysql->dict.flags = dict_flags; 675 dict_mysql->parser = parser; 676 mysql_parse_config(dict_mysql, name); 677 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 678 dict_mysql->active_host = 0; 679 #endif 680 dict_mysql->pldb = plmysql_init(dict_mysql->hosts); 681 if (dict_mysql->pldb == NULL) 682 msg_fatal("couldn't intialize pldb!\n"); 683 dict_mysql->dict.owner = cfg_get_owner(dict_mysql->parser); 684 return (DICT_DEBUG (&dict_mysql->dict)); 685 } 686 687 /* 688 * plmysql_init - initalize a MYSQL database. 689 * Return NULL on failure, or a PLMYSQL * on success. 690 */ 691 static PLMYSQL *plmysql_init(ARGV *hosts) 692 { 693 PLMYSQL *PLDB; 694 int i; 695 696 if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == 0) 697 msg_fatal("mymalloc of pldb failed"); 698 699 PLDB->len_hosts = hosts->argc; 700 if ((PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc)) == 0) 701 return (0); 702 for (i = 0; i < hosts->argc; i++) 703 PLDB->db_hosts[i] = host_init(hosts->argv[i]); 704 705 return PLDB; 706 } 707 708 709 /* host_init - initialize HOST structure */ 710 static HOST *host_init(const char *hostname) 711 { 712 const char *myname = "mysql host_init"; 713 HOST *host = (HOST *) mymalloc(sizeof(HOST)); 714 const char *d = hostname; 715 char *s; 716 717 host->db = 0; 718 host->hostname = mystrdup(hostname); 719 host->port = 0; 720 host->stat = STATUNTRIED; 721 host->ts = 0; 722 723 /* 724 * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where 725 * both "inet:" and ":port" are optional. 726 */ 727 if (strncmp(d, "unix:", 5) == 0) { 728 d += 5; 729 host->type = TYPEUNIX; 730 } else { 731 if (strncmp(d, "inet:", 5) == 0) 732 d += 5; 733 host->type = TYPEINET; 734 } 735 host->name = mystrdup(d); 736 if ((s = split_at_right(host->name, ':')) != 0) 737 host->port = ntohs(find_inet_port(s, "tcp")); 738 if (strcasecmp(host->name, "localhost") == 0) { 739 /* The MySQL way: this will actually connect over the UNIX socket */ 740 myfree(host->name); 741 host->name = 0; 742 host->type = TYPEUNIX; 743 } 744 if (msg_verbose > 1) 745 msg_info("%s: host=%s, port=%d, type=%s", myname, 746 host->name ? host->name : "localhost", 747 host->port, host->type == TYPEUNIX ? "unix" : "inet"); 748 return host; 749 } 750 751 /* dict_mysql_close - close MYSQL database */ 752 753 static void dict_mysql_close(DICT *dict) 754 { 755 DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; 756 757 plmysql_dealloc(dict_mysql->pldb); 758 cfg_parser_free(dict_mysql->parser); 759 myfree(dict_mysql->username); 760 myfree(dict_mysql->password); 761 myfree(dict_mysql->dbname); 762 myfree(dict_mysql->query); 763 myfree(dict_mysql->result_format); 764 if (dict_mysql->hosts) 765 argv_free(dict_mysql->hosts); 766 if (dict_mysql->ctx) 767 db_common_free_ctx(dict_mysql->ctx); 768 if (dict->fold_buf) 769 vstring_free(dict->fold_buf); 770 dict_free(dict); 771 } 772 773 /* plmysql_dealloc - free memory associated with PLMYSQL close databases */ 774 static void plmysql_dealloc(PLMYSQL *PLDB) 775 { 776 int i; 777 778 for (i = 0; i < PLDB->len_hosts; i++) { 779 event_cancel_timer(dict_mysql_event, (char *) (PLDB->db_hosts[i])); 780 if (PLDB->db_hosts[i]->db) 781 mysql_close(PLDB->db_hosts[i]->db); 782 myfree(PLDB->db_hosts[i]->hostname); 783 if (PLDB->db_hosts[i]->name) 784 myfree(PLDB->db_hosts[i]->name); 785 myfree((char *) PLDB->db_hosts[i]); 786 } 787 myfree((char *) PLDB->db_hosts); 788 myfree((char *) (PLDB)); 789 } 790 791 #endif 792