Current File : //root/postfix-3.2.0/src/global/dict_mysql.c |
/*++
/* NAME
/* dict_mysql 3
/* SUMMARY
/* dictionary manager interface to MySQL databases
/* SYNOPSIS
/* #include <dict_mysql.h>
/*
/* 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 '<some username>'
/* 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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <time.h>
#include <mysql.h>
#include <limits.h>
#include <errno.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#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"
/* 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 MYSQL_VERSION_ID >= 50023
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;
/*
* 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;
/*
* 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 MYSQL_VERSION_ID >= 50023
if (dict_mysql->tls_verify_cert != -1)
mysql_options(host->db, MYSQL_OPT_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 MYSQL_VERSION_ID >= 50023
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