driver-mysql.c revision bd63b5b860658b01b1f46f26d406e1e4a9dc019a
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen/* Copyright (c) 2003-2012 Dovecot authors, see the included COPYING file */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "lib.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "ioloop.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "array.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "str.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include "sql-api-private.h"
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef BUILD_MYSQL
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include <stdlib.h>
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include <unistd.h>
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include <time.h>
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef HAVE_ATTR_NULL
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen/* ugly way to tell clang that mysql.h is a system header and we don't want
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen to enable nonnull attributes for it by default.. */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen# 4 "driver-mysql.c" 3
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#endif
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include <mysql.h>
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef HAVE_ATTR_NULL
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen# 4 "driver-mysql.c" 3
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen# line 20
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#endif
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#include <errmsg.h>
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstruct mysql_db {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct sql_db api;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen pool_t pool;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *user, *password, *dbname, *host, *unix_socket;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *ssl_cert, *ssl_key, *ssl_ca, *ssl_ca_path, *ssl_cipher;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *option_file, *option_group;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int port, client_flags;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen time_t last_success;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen MYSQL *mysql;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int next_query_connection;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int ssl_set:1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen};
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstruct mysql_result {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct sql_result api;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen MYSQL_RES *result;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen MYSQL_ROW row;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen MYSQL_FIELD *fields;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int fields_count;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen my_ulonglong affected_rows;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen};
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstruct mysql_transaction_context {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct sql_transaction_context ctx;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen pool_t query_pool;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *error;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int failed:1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen};
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenextern const struct sql_db driver_mysql_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenextern const struct sql_result driver_mysql_result;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenextern const struct sql_result driver_mysql_error_result;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic const char *mysql_prefix(struct mysql_db *db)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return t_strdup_printf("mysql(%s)", db->host);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic int driver_mysql_connect(struct sql_db *_db)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_db *db = (struct mysql_db *)_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *unix_socket, *host;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned long client_flags = db->client_flags;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unsigned int secs_used;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen bool failed;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_CONNECTING);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (*db->host == '/') {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unix_socket = db->host;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen host = NULL;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen } else {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen unix_socket = NULL;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen host = db->host;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (db->option_file != NULL) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_options(db->mysql, MYSQL_READ_DEFAULT_FILE,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->option_file);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_options(db->mysql, MYSQL_READ_DEFAULT_GROUP,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->option_group != NULL ? db->option_group : "client");
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (!db->ssl_set && (db->ssl_ca != NULL || db->ssl_ca_path != NULL)) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef HAVE_MYSQL_SSL
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_ssl_set(db->mysql, db->ssl_key, db->ssl_cert,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->ssl_ca, db->ssl_ca_path
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef HAVE_MYSQL_SSL_CIPHER
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen , db->ssl_cipher
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#endif
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen );
131b0d222ad89b2c5d2b03b865b45cae9e290d68Timo Sirainen db->ssl_set = TRUE;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#else
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_fatal("mysql: SSL support not compiled in "
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen "(remove ssl_ca and ssl_ca_path settings)");
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#endif
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen alarm(SQL_CONNECT_TIMEOUT_SECS);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#ifdef CLIENT_MULTI_RESULTS
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen client_flags |= CLIENT_MULTI_RESULTS;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen#endif
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* CLIENT_MULTI_RESULTS allows the use of stored procedures */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen failed = mysql_real_connect(db->mysql, host, db->user, db->password,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->dbname, db->port, unix_socket,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen client_flags) == NULL;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen secs_used = SQL_CONNECT_TIMEOUT_SECS - alarm(0);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (failed) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* connecting could have taken a while. make sure that any
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen timeouts that get added soon will get a refreshed
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen timestamp. */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen io_loop_time_refresh();
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (db->api.connect_delay < secs_used)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->api.connect_delay = secs_used;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_error("%s: Connect failed to database (%s): %s - "
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen "waiting for %u seconds before retry",
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_prefix(db), db->dbname,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_error(db->mysql), db->api.connect_delay);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return -1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen } else {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_info("%s: Connected to database %s%s", mysql_prefix(db),
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->dbname, db->ssl_set ? " using SSL" : "");
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->last_success = ioloop_time;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_IDLE);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic void driver_mysql_disconnect(struct sql_db *_db ATTR_UNUSED)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic void driver_mysql_parse_connect_string(struct mysql_db *db,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *connect_string)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char *const *args, *name, *value;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen const char **field;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->ssl_cipher = "HIGH";
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen args = t_strsplit_spaces(connect_string, " ");
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen for (; *args != NULL; args++) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen value = strchr(*args, '=');
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (value == NULL) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_fatal("mysql: Missing value in connect string: %s",
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen *args);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen name = t_strdup_until(*args, value);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen value++;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = NULL;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (strcmp(name, "host") == 0 ||
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen strcmp(name, "hostaddr") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->host;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "user") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->user;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "password") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->password;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "dbname") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->dbname;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "port") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->port = atoi(value);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "client_flags") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->client_flags = atoi(value);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "ssl_cert") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->ssl_cert;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "ssl_key") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->ssl_key;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "ssl_ca") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->ssl_ca;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "ssl_ca_path") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->ssl_ca_path;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "ssl_cipher") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->ssl_cipher;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "option_file") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->option_file;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else if (strcmp(name, "option_group") == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen field = &db->option_group;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen else
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_fatal("mysql: Unknown connect string: %s", name);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (field != NULL)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen *field = p_strdup(db->pool, value);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (db->host == NULL)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_fatal("mysql: No hosts given in connect string");
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->mysql = mysql_init(NULL);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (db->mysql == NULL)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_fatal("mysql_init() failed");
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic struct sql_db *driver_mysql_init_v(const char *connect_string)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_db *db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen pool_t pool;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen pool = pool_alloconly_create("mysql driver", 1024);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db = p_new(pool, struct mysql_db, 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->pool = pool;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen db->api = driver_mysql_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen T_BEGIN {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen driver_mysql_parse_connect_string(db, connect_string);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen } T_END;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return &db->api;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic void driver_mysql_deinit_v(struct sql_db *_db)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_db *db = (struct mysql_db *)_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen _db->no_reconnect = TRUE;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_close(db->mysql);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen array_free(&_db->module_contexts);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen pool_unref(&db->pool);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic int driver_mysql_do_query(struct mysql_db *db, const char *query)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (mysql_query(db->mysql, query) == 0)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return 0;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* failed */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen switch (mysql_errno(db->mysql)) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen case CR_SERVER_GONE_ERROR:
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen case CR_SERVER_LOST:
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen break;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen default:
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen break;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return -1;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic const char *
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainendriver_mysql_escape_string(struct sql_db *_db, const char *string)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_db *db = (struct mysql_db *)_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen size_t len = strlen(string);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen char *to;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (_db->state == SQL_DB_STATE_DISCONNECTED) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* try connecting */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen (void)sql_connect(&db->api);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (db->mysql == NULL) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen /* FIXME: we don't have a valid connection, so fallback
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen to using default escaping. the next query will most
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen likely fail anyway so it shouldn't matter that much
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen what we return here.. Anyway, this API needs
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen changing so that the escaping function could already
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen fail the query reliably. */
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen to = t_buffer_get(len * 2 + 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen len = mysql_escape_string(to, string, len);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen t_buffer_alloc(len + 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return to;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen to = t_buffer_get(len * 2 + 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen len = mysql_real_escape_string(db->mysql, to, string, len);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen t_buffer_alloc(len + 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen return to;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic void driver_mysql_exec(struct sql_db *_db, const char *query)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_db *db = (struct mysql_db *)_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen if (driver_mysql_do_query(db, query) < 0) {
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen i_error("%s: Query '%s' failed: %s",
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen mysql_prefix(db), query, mysql_error(db->mysql));
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen }
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic void driver_mysql_query(struct sql_db *db, const char *query,
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_query_callback_t *callback, void *context)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct sql_result *result;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen result = sql_query_s(db, query);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen result->callback = TRUE;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen callback(result, context);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen result->callback = FALSE;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen sql_result_unref(result);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen}
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainenstatic struct sql_result *
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainendriver_mysql_query_s(struct sql_db *_db, const char *query)
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen{
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_db *db = (struct mysql_db *)_db;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen struct mysql_result *result;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen int ret;
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen result = i_new(struct mysql_result, 1);
a49d1c2ca3c134c0c62b37a94936c78e9849e044Timo Sirainen result->api = driver_mysql_result;
if (driver_mysql_do_query(db, query) < 0)
result->api = driver_mysql_error_result;
else {
/* query ok */
result->affected_rows = mysql_affected_rows(db->mysql);
result->result = mysql_store_result(db->mysql);
#ifdef CLIENT_MULTI_RESULTS
/* Because we've enabled CLIENT_MULTI_RESULTS, we need to read
(ignore) extra results - there should not be any.
ret is: -1 = done, >0 = error, 0 = more results. */
while ((ret = mysql_next_result(db->mysql)) == 0) ;
#else
ret = -1;
#endif
if (ret < 0 &&
(result->result != NULL || mysql_errno(db->mysql) == 0)) {
/* ok */
} else {
/* failed */
if (result->result != NULL)
mysql_free_result(result->result);
result->api = driver_mysql_error_result;
}
}
result->api.db = _db;
result->api.refcount = 1;
return &result->api;
}
static void driver_mysql_result_free(struct sql_result *_result)
{
struct mysql_result *result = (struct mysql_result *)_result;
i_assert(_result != &sql_not_connected_result);
if (_result->callback)
return;
if (result->result != NULL)
mysql_free_result(result->result);
i_free(result);
}
static int driver_mysql_result_next_row(struct sql_result *_result)
{
struct mysql_result *result = (struct mysql_result *)_result;
struct mysql_db *db = (struct mysql_db *)_result->db;
int ret;
if (result->result == NULL) {
/* no results */
return 0;
}
result->row = mysql_fetch_row(result->result);
if (result->row != NULL)
ret = 1;
else {
if (mysql_errno(db->mysql) != 0)
return -1;
ret = 0;
}
db->last_success = ioloop_time;
return ret;
}
static void driver_mysql_result_fetch_fields(struct mysql_result *result)
{
if (result->fields != NULL)
return;
result->fields_count = mysql_num_fields(result->result);
result->fields = mysql_fetch_fields(result->result);
}
static unsigned int
driver_mysql_result_get_fields_count(struct sql_result *_result)
{
struct mysql_result *result = (struct mysql_result *)_result;
driver_mysql_result_fetch_fields(result);
return result->fields_count;
}
static const char *
driver_mysql_result_get_field_name(struct sql_result *_result, unsigned int idx)
{
struct mysql_result *result = (struct mysql_result *)_result;
driver_mysql_result_fetch_fields(result);
i_assert(idx < result->fields_count);
return result->fields[idx].name;
}
static int driver_mysql_result_find_field(struct sql_result *_result,
const char *field_name)
{
struct mysql_result *result = (struct mysql_result *)_result;
unsigned int i;
driver_mysql_result_fetch_fields(result);
for (i = 0; i < result->fields_count; i++) {
if (strcmp(result->fields[i].name, field_name) == 0)
return i;
}
return -1;
}
static const char *
driver_mysql_result_get_field_value(struct sql_result *_result,
unsigned int idx)
{
struct mysql_result *result = (struct mysql_result *)_result;
return (const char *)result->row[idx];
}
static const unsigned char *
driver_mysql_result_get_field_value_binary(struct sql_result *_result,
unsigned int idx, size_t *size_r)
{
struct mysql_result *result = (struct mysql_result *)_result;
unsigned long *lengths;
lengths = mysql_fetch_lengths(result->result);
*size_r = lengths[idx];
return (const void *)result->row[idx];
}
static const char *
driver_mysql_result_find_field_value(struct sql_result *result,
const char *field_name)
{
int idx;
idx = driver_mysql_result_find_field(result, field_name);
if (idx < 0)
return NULL;
return driver_mysql_result_get_field_value(result, idx);
}
static const char *const *
driver_mysql_result_get_values(struct sql_result *_result)
{
struct mysql_result *result = (struct mysql_result *)_result;
return (const char *const *)result->row;
}
static const char *driver_mysql_result_get_error(struct sql_result *_result)
{
struct mysql_db *db = (struct mysql_db *)_result->db;
const char *errstr;
unsigned int idle_time;
int err;
err = mysql_errno(db->mysql);
errstr = mysql_error(db->mysql);
if ((err == CR_SERVER_GONE_ERROR || err == CR_SERVER_LOST) &&
db->last_success != 0) {
idle_time = ioloop_time - db->last_success;
errstr = t_strdup_printf("%s (idled for %u secs)",
errstr, idle_time);
}
return errstr;
}
static struct sql_transaction_context *
driver_mysql_transaction_begin(struct sql_db *db)
{
struct mysql_transaction_context *ctx;
ctx = i_new(struct mysql_transaction_context, 1);
ctx->ctx.db = db;
ctx->query_pool = pool_alloconly_create("mysql transaction", 1024);
return &ctx->ctx;
}
static void
driver_mysql_transaction_commit(struct sql_transaction_context *ctx,
sql_commit_callback_t *callback, void *context)
{
const char *error;
if (sql_transaction_commit_s(&ctx, &error) < 0)
callback(error, context);
else
callback(NULL, context);
}
static int ATTR_NULL(3)
transaction_send_query(struct mysql_transaction_context *ctx, const char *query,
unsigned int *affected_rows_r)
{
struct sql_result *_result;
int ret = 0;
if (ctx->failed)
return -1;
_result = sql_query_s(ctx->ctx.db, query);
if (sql_result_next_row(_result) < 0) {
ctx->error = sql_result_get_error(_result);
ctx->failed = TRUE;
ret = -1;
} else if (affected_rows_r != NULL) {
struct mysql_result *result = (struct mysql_result *)_result;
i_assert(result->affected_rows != (my_ulonglong)-1);
*affected_rows_r = result->affected_rows;
}
sql_result_unref(_result);
return ret;
}
static int driver_mysql_try_commit_s(struct mysql_transaction_context *ctx)
{
struct sql_transaction_context *_ctx = &ctx->ctx;
/* try to use a transaction in any case,
even if it's not actually functional. */
if (transaction_send_query(ctx, "BEGIN", NULL) < 0) {
if (_ctx->db->state != SQL_DB_STATE_DISCONNECTED)
return -1;
/* we got disconnected, retry */
return 0;
}
while (_ctx->head != NULL) {
if (transaction_send_query(ctx, _ctx->head->query,
_ctx->head->affected_rows) < 0)
return -1;
_ctx->head = _ctx->head->next;
}
if (transaction_send_query(ctx, "COMMIT", NULL) < 0)
return -1;
return 1;
}
static int
driver_mysql_transaction_commit_s(struct sql_transaction_context *_ctx,
const char **error_r)
{
struct mysql_transaction_context *ctx =
(struct mysql_transaction_context *)_ctx;
struct mysql_db *db = (struct mysql_db *)_ctx->db;
int ret = 1;
*error_r = NULL;
if (_ctx->head != NULL) {
ret = driver_mysql_try_commit_s(ctx);
*error_r = t_strdup(ctx->error);
if (ret == 0) {
i_info("%s: Disconnected from database, "
"retrying commit", db->dbname);
if (sql_connect(_ctx->db) >= 0) {
ctx->failed = FALSE;
ret = driver_mysql_try_commit_s(ctx);
}
}
}
sql_transaction_rollback(&_ctx);
return ret <= 0 ? -1 : 0;
}
static void
driver_mysql_transaction_rollback(struct sql_transaction_context *_ctx)
{
struct mysql_transaction_context *ctx =
(struct mysql_transaction_context *)_ctx;
pool_unref(&ctx->query_pool);
i_free(ctx);
}
static void
driver_mysql_update(struct sql_transaction_context *_ctx, const char *query,
unsigned int *affected_rows)
{
struct mysql_transaction_context *ctx =
(struct mysql_transaction_context *)_ctx;
sql_transaction_add_query(&ctx->ctx, ctx->query_pool,
query, affected_rows);
}
const struct sql_db driver_mysql_db = {
.name = "mysql",
.flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_POOLED,
.v = {
driver_mysql_init_v,
driver_mysql_deinit_v,
driver_mysql_connect,
driver_mysql_disconnect,
driver_mysql_escape_string,
driver_mysql_exec,
driver_mysql_query,
driver_mysql_query_s,
driver_mysql_transaction_begin,
driver_mysql_transaction_commit,
driver_mysql_transaction_commit_s,
driver_mysql_transaction_rollback,
driver_mysql_update
}
};
const struct sql_result driver_mysql_result = {
.v = {
driver_mysql_result_free,
driver_mysql_result_next_row,
driver_mysql_result_get_fields_count,
driver_mysql_result_get_field_name,
driver_mysql_result_find_field,
driver_mysql_result_get_field_value,
driver_mysql_result_get_field_value_binary,
driver_mysql_result_find_field_value,
driver_mysql_result_get_values,
driver_mysql_result_get_error
}
};
static int
driver_mysql_result_error_next_row(struct sql_result *result ATTR_UNUSED)
{
return -1;
}
const struct sql_result driver_mysql_error_result = {
.v = {
driver_mysql_result_free,
driver_mysql_result_error_next_row,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
driver_mysql_result_get_error
},
.failed_try_retry = TRUE
};
const char *driver_mysql_version = DOVECOT_ABI_VERSION;
void driver_mysql_init(void);
void driver_mysql_deinit(void);
void driver_mysql_init(void)
{
sql_driver_register(&driver_mysql_db);
}
void driver_mysql_deinit(void)
{
sql_driver_unregister(&driver_mysql_db);
}
#endif