Expand PMF_FN_* macros.
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / global / dict_mysql.c
blobf1d4d3eb338f1a8fde10a50d882e30c28270e5d7
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* dict_mysql 3
6 /* SUMMARY
7 /* dictionary manager interface to MySQL databases
8 /* SYNOPSIS
9 /* #include <dict_mysql.h>
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.
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.
35 /* In the first case, the configuration parameters below are
36 /* specified in the file as \fIname\fR=\fBvalue\fR pairs.
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.
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:
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
157 /* Joshua Marcus
158 /* IC Group, Inc.
159 /* josh@icgroup.com
160 /*--*/
162 /* System library. */
163 #include "sys_defs.h"
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>
177 #ifdef STRCASECMP_IN_STRINGS_H
178 #include <strings.h>
179 #endif
181 /* Utility library. */
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"
194 /* Global library. */
196 #include "cfg_parser.h"
197 #include "db_common.h"
199 /* Application-specific. */
201 #include "dict_mysql.h"
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;
215 typedef struct {
216 int len_hosts; /* number of hosts */
217 HOST **db_hosts; /* the hosts on which the databases
218 * reside */
219 } PLMYSQL;
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;
238 #define STATACTIVE (1<<0)
239 #define STATFAIL (1<<1)
240 #define STATUNTRIED (1<<2)
242 #define TYPEUNIX (1<<0)
243 #define TYPEINET (1<<1)
245 #define RETRY_CONN_MAX 100
246 #define RETRY_CONN_INTV 60 /* 1 minute */
247 #define IDLE_CONN_INTV 60 /* 1 minute */
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 *);
263 /* dict_mysql_quote - escape SQL metacharacters in input string */
265 static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result)
267 DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
268 int len = strlen(name);
269 int buflen = 2*len + 1;
272 * We won't get integer overflows in 2*len + 1, because Postfix
273 * input keys have reasonable size limits, better safe than sorry.
275 if (buflen < len)
276 msg_panic("dict_mysql_quote: integer overflow in 2*%d+1", len);
277 VSTRING_SPACE(result, buflen);
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);
287 VSTRING_SKIP(result);
290 /* dict_mysql_lookup - find database entry */
292 static const char *dict_mysql_lookup(DICT *dict, const char *name)
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;
308 dict_errno = 0;
311 * Optionally fold the key.
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));
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.
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);
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)
338 INIT_VSTR(query, 10);
341 * Suppress the lookup if the query expansion is empty
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...
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);
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);
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;
372 INIT_VSTR(result, 10);
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;
388 mysql_free_result(query_res);
389 r = vstring_str(result);
390 return ((dict_errno == 0 && *r) ? r : 0);
393 /* dict_mysql_check_stat - check the status of a host */
395 static int dict_mysql_check_stat(HOST *host, unsigned stat, unsigned type,
396 time_t t)
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;
404 return 0;
407 /* dict_mysql_find_host - find a host with the given status */
409 static HOST *dict_mysql_find_host(PLMYSQL *PLDB, unsigned stat, unsigned type)
411 time_t t;
412 int count = 0;
413 int idx;
414 int i;
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++;
422 if (count) {
423 idx = (count > 1) ?
424 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
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];
432 return 0;
435 /* dict_mysql_get_active - get an active connection */
437 static HOST *dict_mysql_get_active(PLMYSQL *PLDB, char *dbname,
438 char *username, char *password)
440 const char *myname = "dict_mysql_get_active";
441 HOST *host;
442 int count = RETRY_CONN_MAX;
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;
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.
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;
471 /* bad news... */
472 return 0;
475 /* dict_mysql_event - callback: close idle connections */
477 static void dict_mysql_event(int unused_event, char *context)
479 HOST *host = (HOST *) context;
481 if (host->db)
482 plmysql_close_host(host);
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
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)
499 PLMYSQL *PLDB = dict_mysql->pldb;
500 HOST *host;
501 MYSQL_RES *res = 0;
503 while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) {
505 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
507 * The active host is used to escape strings in the
508 * context of the active connection's character encoding.
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
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;
528 } else {
529 msg_warn("mysql query failed: %s", mysql_error(host->db));
530 plmysql_down_host(host);
534 return res;
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
542 static void plmysql_connect_single(HOST *host, char *dbname, char *username, char *password)
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);
565 /* plmysql_close_host - close an established MySQL connection */
566 static void plmysql_close_host(HOST *host)
568 mysql_close(host->db);
569 host->db = 0;
570 host->stat = STATUNTRIED;
574 * plmysql_down_host - close a failed connection AND set a "stay away from
575 * this host" timer
577 static void plmysql_down_host(HOST *host)
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);
586 /* mysql_parse_config - parse mysql configuration file */
588 static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
590 const char *myname = "mysqlname_parse";
591 CFG_PARSER *p;
592 VSTRING *buf;
593 int i;
594 char *hosts;
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);
602 * XXX: The default should be non-zero for safety, but that is not
603 * backwards compatible.
605 dict_mysql->expansion_limit = cfg_get_int(dict_mysql->parser,
606 "expansion_limit", 0, 0, 0);
608 if ((dict_mysql->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) {
610 * No query specified -- fallback to building it from components
611 * (old style "select %s from %s where %s")
613 buf = vstring_alloc(64);
614 db_common_sql_build_query(buf, p);
615 dict_mysql->query = vstring_export(buf);
619 * Must parse all templates before we can use db_common_expand()
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);
628 * Maps that use substring keys should only be used with the full
629 * input key.
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);
638 hosts = cfg_get_str(p, "hosts", "", 0, 0);
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]);
648 myfree(hosts);
651 /* dict_mysql_open - open MYSQL data base */
653 DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags)
655 DICT_MYSQL *dict_mysql;
658 * Sanity checks.
660 if (open_flags != O_RDONLY)
661 msg_fatal("%s:%s map requires O_RDONLY access mode",
662 DICT_TYPE_MYSQL, name);
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));
680 * plmysql_init - initalize a MYSQL database.
681 * Return NULL on failure, or a PLMYSQL * on success.
683 static PLMYSQL *plmysql_init(ARGV *hosts)
685 PLMYSQL *PLDB;
686 int i;
688 if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == 0)
689 msg_fatal("mymalloc of pldb failed");
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]);
697 return PLDB;
701 /* host_init - initialize HOST structure */
702 static HOST *host_init(const char *hostname)
704 const char *myname = "mysql host_init";
705 HOST *host = (HOST *) mymalloc(sizeof(HOST));
706 const char *d = hostname;
707 char *s;
709 host->db = 0;
710 host->hostname = mystrdup(hostname);
711 host->port = 0;
712 host->stat = STATUNTRIED;
713 host->ts = 0;
716 * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where
717 * both "inet:" and ":port" are optional.
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;
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;
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;
744 /* dict_mysql_close - close MYSQL database */
746 static void dict_mysql_close(DICT *dict)
748 int i;
749 DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict;
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);
767 /* plmysql_dealloc - free memory associated with PLMYSQL close databases */
768 static void plmysql_dealloc(PLMYSQL *PLDB)
770 int i;
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]);
781 myfree((char *) PLDB->db_hosts);
782 myfree((char *) (PLDB));
785 #endif