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