/*++ /* NAME /* dict_mysql 3 /* SUMMARY /* dictionary manager interface to MySQL databases /* SYNOPSIS /* #include /* /* DICT *dict_mysql_open(name, open_flags, dict_flags) /* const char *name; /* int open_flags; /* int dict_flags; /* DESCRIPTION /* dict_mysql_open() creates a dictionary of type 'mysql'. This /* dictionary is an interface for the postfix key->value mappings /* to mysql. The result is a pointer to the installed dictionary, /* or a null pointer in case of problems. /* /* The mysql dictionary can manage multiple connections to different /* sql servers on different hosts. It assumes that the underlying data /* on each host is identical (mirrored) and maintains one connection /* at any given time. If any connection fails, any other available /* ones will be opened and used. The intent of this feature is to eliminate /* a single point of failure for mail systems that would otherwise rely /* on a single mysql server. /* .PP /* Arguments: /* .IP name /* Either the path to the MySQL configuration file (if it starts /* with '/' or '.'), or the prefix which will be used to obtain /* main.cf configuration parameters for this search. /* /* In the first case, the configuration parameters below are /* specified in the file as \fIname\fR=\fIvalue\fR pairs. /* /* In the second case, the configuration parameters are /* prefixed with the value of \fIname\fR and an underscore, /* and they are specified in main.cf. For example, if this /* value is \fImysqlsource\fR, the parameters would look like /* \fImysqlsource_user\fR, \fImysqlsource_table\fR, and so on. /* /* .IP other_name /* reference for outside use. /* .IP open_flags /* Must be O_RDONLY. /* .IP dict_flags /* See dict_open(3). /* .PP /* Configuration parameters: /* .IP user /* Username for connecting to the database. /* .IP password /* Password for the above. /* .IP dbname /* Name of the database. /* .IP domain /* List of domains the queries should be restricted to. If /* specified, only FQDN addresses whose domain parts matching this /* list will be queried against the SQL database. Lookups for /* partial addresses are also suppressed. This can significantly /* reduce the query load on the server. /* .IP query /* Query template, before the query is actually issued, variable /* substitutions are performed. See mysql_table(5) for details. If /* No query is specified, the legacy variables \fItable\fR, /* \fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR /* are used to construct the query template. /* .IP result_format /* The format used to expand results from queries. Substitutions /* are performed as described in mysql_table(5). Defaults to returning /* the lookup result unchanged. /* .IP expansion_limit /* Limit (if any) on the total number of lookup result values. Lookups which /* exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that each /* non-empty (and non-NULL) column of a multi-column result row counts as /* one result. /* .IP table /* When \fIquery\fR is not set, name of the table used to construct the /* query string. This provides compatibility with older releases. /* .IP select_field /* When \fIquery\fR is not set, name of the result field used to /* construct the query string. This provides compatibility with older /* releases. /* .IP where_field /* When \fIquery\fR is not set, name of the where clause field used to /* construct the query string. This provides compatibility with older /* releases. /* .IP additional_conditions /* When \fIquery\fR is not set, additional where clause conditions used /* to construct the query string. This provides compatibility with older /* releases. /* .IP hosts /* List of hosts to connect to. /* .IP option_file /* Read options from the given file instead of the default my.cnf /* location. /* .IP option_group /* Read options from the given group. /* .IP require_result_set /* Require that every query produces a result set. /* .IP tls_cert_file /* File containing client's X509 certificate. /* .IP tls_key_file /* File containing the private key corresponding to \fItls_cert_file\fR. /* .IP tls_CAfile /* File containing certificates for all of the X509 Certification /* Authorities the client will recognize. Takes precedence over /* \fItls_CApath\fR. /* .IP tls_CApath /* Directory containing X509 Certification Authority certificates /* in separate individual files. /* .IP tls_verify_cert /* Verify that the server's name matches the common name of the /* certificate. /* .PP /* For example, if you want the map to reference databases of /* the name "your_db" and execute a query like this: select /* forw_addr from aliases where alias like '' /* against any database called "vmailer_info" located on hosts /* host1.some.domain and host2.some.domain, logging in as user /* "vmailer" and password "passwd" then the configuration file /* should read: /* .PP /* user = vmailer /* .br /* password = passwd /* .br /* dbname = vmailer_info /* .br /* table = aliases /* .br /* select_field = forw_addr /* .br /* where_field = alias /* .br /* hosts = host1.some.domain host2.some.domain /* .PP /* SEE ALSO /* dict(3) generic dictionary manager /* AUTHOR(S) /* Scott Cotton, Joshua Marcus /* IC Group, Inc. /* scott@icgroup.com /* /* Liviu Daia /* Institute of Mathematics of the Romanian Academy /* P.O. BOX 1-764 /* RO-014700 Bucharest, ROMANIA /* /* John Fawcett /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include "sys_defs.h" #ifdef HAS_MYSQL #include #include #include #include #include #include #include #include #include #include #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include "dict.h" #include "msg.h" #include "mymalloc.h" #include "argv.h" #include "vstring.h" #include "split_at.h" #include "find_inet.h" #include "myrand.h" #include "events.h" #include "stringops.h" /* Global library. */ #include "cfg_parser.h" #include "db_common.h" /* Application-specific. */ #include "dict_mysql.h" /* MySQL 8.x API change */ #if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50023 #define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_VERIFY_SERVER_CERT #elif MYSQL_VERSION_ID >= 80000 #define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_MODE #endif /* need some structs to help organize things */ typedef struct { MYSQL *db; char *hostname; char *name; unsigned port; unsigned type; /* TYPEUNIX | TYPEINET */ unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */ time_t ts; /* used for attempting reconnection * every so often if a host is down */ } HOST; typedef struct { int len_hosts; /* number of hosts */ HOST **db_hosts; /* the hosts on which the databases * reside */ } PLMYSQL; typedef struct { DICT dict; CFG_PARSER *parser; char *query; char *result_format; char *option_file; char *option_group; void *ctx; int expansion_limit; char *username; char *password; char *dbname; ARGV *hosts; PLMYSQL *pldb; #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 HOST *active_host; char *tls_cert_file; char *tls_key_file; char *tls_CAfile; char *tls_CApath; char *tls_ciphers; #if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) int tls_verify_cert; #endif #endif int require_result_set; } DICT_MYSQL; #define STATACTIVE (1<<0) #define STATFAIL (1<<1) #define STATUNTRIED (1<<2) #define TYPEUNIX (1<<0) #define TYPEINET (1<<1) #define RETRY_CONN_MAX 100 #define RETRY_CONN_INTV 60 /* 1 minute */ #define IDLE_CONN_INTV 60 /* 1 minute */ /* internal function declarations */ static PLMYSQL *plmysql_init(ARGV *); static int plmysql_query(DICT_MYSQL *, const char *, VSTRING *, MYSQL_RES **); static void plmysql_dealloc(PLMYSQL *); static void plmysql_close_host(HOST *); static void plmysql_down_host(HOST *); static void plmysql_connect_single(DICT_MYSQL *, HOST *); static const char *dict_mysql_lookup(DICT *, const char *); DICT *dict_mysql_open(const char *, int, int); static void dict_mysql_close(DICT *); static void mysql_parse_config(DICT_MYSQL *, const char *); static HOST *host_init(const char *); /* dict_mysql_quote - escape SQL metacharacters in input string */ static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result) { DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; int len = strlen(name); int buflen; /* * We won't get integer overflows in 2*len + 1, because Postfix input * keys have reasonable size limits, better safe than sorry. */ if (len > (INT_MAX - VSTRING_LEN(result) - 1) / 2) msg_panic("dict_mysql_quote: integer overflow in %lu+2*%d+1", (unsigned long) VSTRING_LEN(result), len); buflen = 2 * len + 1; VSTRING_SPACE(result, buflen); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 if (dict_mysql->active_host) mysql_real_escape_string(dict_mysql->active_host->db, vstring_end(result), name, len); else #endif mysql_escape_string(vstring_end(result), name, len); VSTRING_SKIP(result); } /* dict_mysql_lookup - find database entry */ static const char *dict_mysql_lookup(DICT *dict, const char *name) { const char *myname = "dict_mysql_lookup"; DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; MYSQL_RES *query_res; MYSQL_ROW row; static VSTRING *result; static VSTRING *query; int i; int j; int numrows; int expansion; const char *r; db_quote_callback_t quote_func = dict_mysql_quote; int domain_rc; dict->error = 0; /* * Don't frustrate future attempts to make Postfix UTF-8 transparent. */ #ifdef SNAPSHOT if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 && !valid_utf8_string(name, strlen(name))) { if (msg_verbose) msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'", myname, dict_mysql->parser->name, name); return (0); } #endif /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } /* * If there is a domain list for this map, then only search for addresses * in domains on the list. This can significantly reduce the load on the * server. */ if ((domain_rc = db_common_check_domain(dict_mysql->ctx, name)) == 0) { if (msg_verbose) msg_info("%s: Skipping lookup of '%s'", myname, name); return (0); } if (domain_rc < 0) { msg_warn("%s:%s 'domain' pattern match failed for '%s'", dict->type, dict->name, name); DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0); } #define INIT_VSTR(buf, len) do { \ if (buf == 0) \ buf = vstring_alloc(len); \ VSTRING_RESET(buf); \ VSTRING_TERMINATE(buf); \ } while (0) INIT_VSTR(query, 10); /* * Suppress the lookup if the query expansion is empty * * This initial expansion is outside the context of any specific host * connection, we just want to check the key pre-requisites, so when * quoting happens separately for each connection, we don't bother with * quoting... */ #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 quote_func = 0; #endif if (!db_common_expand(dict_mysql->ctx, dict_mysql->query, name, 0, query, quote_func)) return (0); /* do the query - set dict->error & cleanup if there's an error */ if (plmysql_query(dict_mysql, name, query, &query_res) == 0) { dict->error = DICT_ERR_RETRY; return (0); } if (query_res == 0) return (0); numrows = mysql_num_rows(query_res); if (msg_verbose) msg_info("%s: retrieved %d rows", myname, numrows); if (numrows == 0) { mysql_free_result(query_res); return 0; } INIT_VSTR(result, 10); for (expansion = i = 0; i < numrows && dict->error == 0; i++) { row = mysql_fetch_row(query_res); for (j = 0; j < mysql_num_fields(query_res); j++) { if (db_common_expand(dict_mysql->ctx, dict_mysql->result_format, row[j], name, result, 0) && dict_mysql->expansion_limit > 0 && ++expansion > dict_mysql->expansion_limit) { msg_warn("%s: %s: Expansion limit exceeded for key: '%s'", myname, dict_mysql->parser->name, name); dict->error = DICT_ERR_RETRY; break; } } } mysql_free_result(query_res); r = vstring_str(result); return ((dict->error == 0 && *r) ? r : 0); } /* dict_mysql_check_stat - check the status of a host */ static int dict_mysql_check_stat(HOST *host, unsigned stat, unsigned type, time_t t) { if ((host->stat & stat) && (!type || host->type & type)) { /* try not to hammer the dead hosts too often */ if (host->stat == STATFAIL && host->ts > 0 && host->ts >= t) return 0; return 1; } return 0; } /* dict_mysql_find_host - find a host with the given status */ static HOST *dict_mysql_find_host(PLMYSQL *PLDB, unsigned stat, unsigned type) { time_t t; int count = 0; int idx; int i; t = time((time_t *) 0); for (i = 0; i < PLDB->len_hosts; i++) { if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t)) count++; } if (count) { idx = (count > 1) ? 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1; for (i = 0; i < PLDB->len_hosts; i++) { if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t) && --idx == 0) return PLDB->db_hosts[i]; } } return 0; } /* dict_mysql_get_active - get an active connection */ static HOST *dict_mysql_get_active(DICT_MYSQL *dict_mysql) { const char *myname = "dict_mysql_get_active"; PLMYSQL *PLDB = dict_mysql->pldb; HOST *host; int count = RETRY_CONN_MAX; /* Try the active connections first; prefer the ones to UNIX sockets. */ if ((host = dict_mysql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL || (host = dict_mysql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL) { if (msg_verbose) msg_info("%s: found active connection to host %s", myname, host->hostname); return host; } /* * Try the remaining hosts. "count" is a safety net, in case the loop * takes more than RETRY_CONN_INTV and the dead hosts are no longer * skipped. */ while (--count > 0 && ((host = dict_mysql_find_host(PLDB, STATUNTRIED | STATFAIL, TYPEUNIX)) != NULL || (host = dict_mysql_find_host(PLDB, STATUNTRIED | STATFAIL, TYPEINET)) != NULL)) { if (msg_verbose) msg_info("%s: attempting to connect to host %s", myname, host->hostname); plmysql_connect_single(dict_mysql, host); if (host->stat == STATACTIVE) return host; } /* bad news... */ return 0; } /* dict_mysql_event - callback: close idle connections */ static void dict_mysql_event(int unused_event, void *context) { HOST *host = (HOST *) context; if (host->db) plmysql_close_host(host); } /* * plmysql_query - process a MySQL query. Return 'true' on success. * On failure, log failure and try other db instances. * on failure of all db instances, return 'false'; * close unnecessary active connections */ static int plmysql_query(DICT_MYSQL *dict_mysql, const char *name, VSTRING *query, MYSQL_RES **result) { HOST *host; MYSQL_RES *first_result = 0; int query_error = 1; /* * Helper to avoid spamming the log with warnings. */ #define SET_ERROR_AND_WARN_ONCE(err, ...) \ do { \ if (err == 0) { \ err = 1; \ msg_warn(__VA_ARGS__); \ } \ } while (0) while ((host = dict_mysql_get_active(dict_mysql)) != NULL) { #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 /* * The active host is used to escape strings in the context of the * active connection's character encoding. */ dict_mysql->active_host = host; VSTRING_RESET(query); VSTRING_TERMINATE(query); db_common_expand(dict_mysql->ctx, dict_mysql->query, name, 0, query, dict_mysql_quote); dict_mysql->active_host = 0; #endif query_error = 0; errno = 0; /* * The query must complete. */ if (mysql_query(host->db, vstring_str(query)) != 0) { query_error = 1; msg_warn("%s:%s: query failed: %s", dict_mysql->dict.type, dict_mysql->dict.name, mysql_error(host->db)); } /* * Collect all result sets to avoid synchronization errors. */ else { int next_res_status; do { MYSQL_RES *temp_result; /* * Keep the first result set. Reject multiple result sets. */ if ((temp_result = mysql_store_result(host->db)) != 0) { if (first_result == 0) { first_result = temp_result; } else { SET_ERROR_AND_WARN_ONCE(query_error, "%s:%s: query failed: multiple result sets " "returning data are not supported", dict_mysql->dict.type, dict_mysql->dict.name); mysql_free_result(temp_result); } } /* * No result: the mysql_field_count() function must return 0 * to indicate that mysql_store_result() completed normally. */ else if (mysql_field_count(host->db) != 0) { SET_ERROR_AND_WARN_ONCE(query_error, "%s:%s: query failed (mysql_store_result): %s", dict_mysql->dict.type, dict_mysql->dict.name, mysql_error(host->db)); } /* * Are there more results? -1 = no, 0 = yes, > 0 = error. */ if ((next_res_status = mysql_next_result(host->db)) > 0) { SET_ERROR_AND_WARN_ONCE(query_error, "%s:%s: query failed (mysql_next_result): %s", dict_mysql->dict.type, dict_mysql->dict.name, mysql_error(host->db)); } } while (next_res_status == 0); /* * Enforce the require_result_set setting. */ if (first_result == 0 && dict_mysql->require_result_set) { SET_ERROR_AND_WARN_ONCE(query_error, "%s:%s: query failed: query returned no result set" "(require_result_set = yes)", dict_mysql->dict.type, dict_mysql->dict.name); } } /* * See what we got. */ if (query_error) { plmysql_down_host(host); if (errno == 0) errno = ENOTSUP; if (first_result) { mysql_free_result(first_result); first_result = 0; } } else { if (msg_verbose) msg_info("%s:%s: successful query result from host %s", dict_mysql->dict.type, dict_mysql->dict.name, host->hostname); event_request_timer(dict_mysql_event, (void *) host, IDLE_CONN_INTV); break; } } *result = first_result; return (query_error == 0); } /* * plmysql_connect_single - * used to reconnect to a single database when one is down or none is * connected yet. Log all errors and set the stat field of host accordingly */ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host) { if ((host->db = mysql_init(NULL)) == NULL) msg_fatal("dict_mysql: insufficient memory"); if (dict_mysql->option_file) mysql_options(host->db, MYSQL_READ_DEFAULT_FILE, dict_mysql->option_file); if (dict_mysql->option_group && dict_mysql->option_group[0]) mysql_options(host->db, MYSQL_READ_DEFAULT_GROUP, dict_mysql->option_group); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 if (dict_mysql->tls_key_file || dict_mysql->tls_cert_file || dict_mysql->tls_CAfile || dict_mysql->tls_CApath || dict_mysql->tls_ciphers) mysql_ssl_set(host->db, dict_mysql->tls_key_file, dict_mysql->tls_cert_file, dict_mysql->tls_CAfile, dict_mysql->tls_CApath, dict_mysql->tls_ciphers); #if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) if (dict_mysql->tls_verify_cert != -1) mysql_options(host->db, DICT_MYSQL_SSL_VERIFY_SERVER_CERT, &dict_mysql->tls_verify_cert); #endif #endif if (mysql_real_connect(host->db, (host->type == TYPEINET ? host->name : 0), dict_mysql->username, dict_mysql->password, dict_mysql->dbname, host->port, (host->type == TYPEUNIX ? host->name : 0), CLIENT_MULTI_RESULTS)) { if (msg_verbose) msg_info("dict_mysql: successful connection to host %s", host->hostname); host->stat = STATACTIVE; } else { msg_warn("connect to mysql server %s: %s", host->hostname, mysql_error(host->db)); plmysql_down_host(host); } } /* plmysql_close_host - close an established MySQL connection */ static void plmysql_close_host(HOST *host) { mysql_close(host->db); host->db = 0; host->stat = STATUNTRIED; } /* * plmysql_down_host - close a failed connection AND set a "stay away from * this host" timer */ static void plmysql_down_host(HOST *host) { mysql_close(host->db); host->db = 0; host->ts = time((time_t *) 0) + RETRY_CONN_INTV; host->stat = STATFAIL; event_cancel_timer(dict_mysql_event, (void *) host); } /* mysql_parse_config - parse mysql configuration file */ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) { const char *myname = "mysql_parse_config"; CFG_PARSER *p = dict_mysql->parser; VSTRING *buf; char *hosts; dict_mysql->username = cfg_get_str(p, "user", "", 0, 0); dict_mysql->password = cfg_get_str(p, "password", "", 0, 0); dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0); dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0); dict_mysql->option_group = cfg_get_str(p, "option_group", "client", 0, 0); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0); dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0); dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0); dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0); dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0); #if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1); #endif #endif dict_mysql->require_result_set = cfg_get_bool(p, "require_result_set", 1); /* * XXX: The default should be non-zero for safety, but that is not * backwards compatible. */ dict_mysql->expansion_limit = cfg_get_int(dict_mysql->parser, "expansion_limit", 0, 0, 0); if ((dict_mysql->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) { /* * No query specified -- fallback to building it from components (old * style "select %s from %s where %s") */ buf = vstring_alloc(64); db_common_sql_build_query(buf, p); dict_mysql->query = vstring_export(buf); } /* * Must parse all templates before we can use db_common_expand() */ dict_mysql->ctx = 0; (void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx, dict_mysql->query, 1); (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0); db_common_parse_domain(p, dict_mysql->ctx); /* * Maps that use substring keys should only be used with the full input * key. */ if (db_common_dict_partial(dict_mysql->ctx)) dict_mysql->dict.flags |= DICT_FLAG_PATTERN; else dict_mysql->dict.flags |= DICT_FLAG_FIXED; if (dict_mysql->dict.flags & DICT_FLAG_FOLD_FIX) dict_mysql->dict.fold_buf = vstring_alloc(10); hosts = cfg_get_str(p, "hosts", "", 0, 0); dict_mysql->hosts = argv_split(hosts, CHARS_COMMA_SP); if (dict_mysql->hosts->argc == 0) { argv_add(dict_mysql->hosts, "localhost", ARGV_END); argv_terminate(dict_mysql->hosts); if (msg_verbose) msg_info("%s: %s: no hostnames specified, defaulting to '%s'", myname, mysqlcf, dict_mysql->hosts->argv[0]); } myfree(hosts); } /* dict_mysql_open - open MYSQL data base */ DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags) { DICT_MYSQL *dict_mysql; CFG_PARSER *parser; /* * Sanity checks. */ if (open_flags != O_RDONLY) return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags, "%s:%s map requires O_RDONLY access mode", DICT_TYPE_MYSQL, name)); /* * Open the configuration file. */ if ((parser = cfg_parser_alloc(name)) == 0) return (dict_surrogate(DICT_TYPE_MYSQL, name, open_flags, dict_flags, "open %s: %m", name)); dict_mysql = (DICT_MYSQL *) dict_alloc(DICT_TYPE_MYSQL, name, sizeof(DICT_MYSQL)); dict_mysql->dict.lookup = dict_mysql_lookup; dict_mysql->dict.close = dict_mysql_close; dict_mysql->dict.flags = dict_flags; dict_mysql->parser = parser; mysql_parse_config(dict_mysql, name); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 dict_mysql->active_host = 0; #endif dict_mysql->pldb = plmysql_init(dict_mysql->hosts); if (dict_mysql->pldb == NULL) msg_fatal("couldn't initialize pldb!\n"); dict_mysql->dict.owner = cfg_get_owner(dict_mysql->parser); return (DICT_DEBUG (&dict_mysql->dict)); } /* * plmysql_init - initialize a MYSQL database. * Return NULL on failure, or a PLMYSQL * on success. */ static PLMYSQL *plmysql_init(ARGV *hosts) { PLMYSQL *PLDB; int i; if ((PLDB = (PLMYSQL *) mymalloc(sizeof(PLMYSQL))) == 0) msg_fatal("mymalloc of pldb failed"); PLDB->len_hosts = hosts->argc; if ((PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc)) == 0) return (0); for (i = 0; i < hosts->argc; i++) PLDB->db_hosts[i] = host_init(hosts->argv[i]); return PLDB; } /* host_init - initialize HOST structure */ static HOST *host_init(const char *hostname) { const char *myname = "mysql host_init"; HOST *host = (HOST *) mymalloc(sizeof(HOST)); const char *d = hostname; char *s; host->db = 0; host->hostname = mystrdup(hostname); host->port = 0; host->stat = STATUNTRIED; host->ts = 0; /* * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where * both "inet:" and ":port" are optional. */ if (strncmp(d, "unix:", 5) == 0) { d += 5; host->type = TYPEUNIX; } else { if (strncmp(d, "inet:", 5) == 0) d += 5; host->type = TYPEINET; } host->name = mystrdup(d); if ((s = split_at_right(host->name, ':')) != 0) host->port = ntohs(find_inet_port(s, "tcp")); if (strcasecmp(host->name, "localhost") == 0) { /* The MySQL way: this will actually connect over the UNIX socket */ myfree(host->name); host->name = 0; host->type = TYPEUNIX; } if (msg_verbose > 1) msg_info("%s: host=%s, port=%d, type=%s", myname, host->name ? host->name : "localhost", host->port, host->type == TYPEUNIX ? "unix" : "inet"); return host; } /* dict_mysql_close - close MYSQL database */ static void dict_mysql_close(DICT *dict) { DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; plmysql_dealloc(dict_mysql->pldb); cfg_parser_free(dict_mysql->parser); myfree(dict_mysql->username); myfree(dict_mysql->password); myfree(dict_mysql->dbname); myfree(dict_mysql->query); myfree(dict_mysql->result_format); if (dict_mysql->option_file) myfree(dict_mysql->option_file); if (dict_mysql->option_group) myfree(dict_mysql->option_group); #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 if (dict_mysql->tls_key_file) myfree(dict_mysql->tls_key_file); if (dict_mysql->tls_cert_file) myfree(dict_mysql->tls_cert_file); if (dict_mysql->tls_CAfile) myfree(dict_mysql->tls_CAfile); if (dict_mysql->tls_CApath) myfree(dict_mysql->tls_CApath); if (dict_mysql->tls_ciphers) myfree(dict_mysql->tls_ciphers); #endif if (dict_mysql->hosts) argv_free(dict_mysql->hosts); if (dict_mysql->ctx) db_common_free_ctx(dict_mysql->ctx); if (dict->fold_buf) vstring_free(dict->fold_buf); dict_free(dict); } /* plmysql_dealloc - free memory associated with PLMYSQL close databases */ static void plmysql_dealloc(PLMYSQL *PLDB) { int i; for (i = 0; i < PLDB->len_hosts; i++) { event_cancel_timer(dict_mysql_event, (void *) (PLDB->db_hosts[i])); if (PLDB->db_hosts[i]->db) mysql_close(PLDB->db_hosts[i]->db); myfree(PLDB->db_hosts[i]->hostname); if (PLDB->db_hosts[i]->name) myfree(PLDB->db_hosts[i]->name); myfree((void *) PLDB->db_hosts[i]); } myfree((void *) PLDB->db_hosts); myfree((void *) (PLDB)); } #endif