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