bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
d13a8e21656e1b7005e094f7b9c9e3611105c648Timo Sirainen/* ugly way to tell clang that mysql.h is a system header and we don't want
d13a8e21656e1b7005e094f7b9c9e3611105c648Timo Sirainen to enable nonnull attributes for it by default.. */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen const char *user, *password, *dbname, *host, *unix_socket;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *ssl_cert, *ssl_key, *ssl_ca, *ssl_ca_path, *ssl_cipher;
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen unsigned int connect_timeout, read_timeout, write_timeout;
0db5b158a00c08955bdacc99b1e2cd1ec07f4311Timo Sirainenextern const struct sql_result driver_mysql_result;
0db5b158a00c08955bdacc99b1e2cd1ec07f4311Timo Sirainenextern const struct sql_result driver_mysql_error_result;
5fbf8719b9ef072295c16bc4492f9f0ece92117dTimo Sirainenstatic const char *mysql_prefix(struct mysql_db *db)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic int driver_mysql_connect(struct sql_db *_db)
4e9070b89501c0ce7ad6aaf4f5e49aba1ee56decTimo Sirainen unsigned long client_flags = db->client_flags;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED);
b57cd9928909b51fa473c3eea81442e296006438Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_CONNECTING);
6920cf9a4d7bb8fd1a5b5bd25fafc8dd0cb9817cTimo Sirainen /* assume option_file overrides the host, or if not we'll just
6920cf9a4d7bb8fd1a5b5bd25fafc8dd0cb9817cTimo Sirainen connect to localhost */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen mysql_options(db->mysql, MYSQL_READ_DEFAULT_FILE,
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen mysql_options(db->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &db->connect_timeout);
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen mysql_options(db->mysql, MYSQL_OPT_READ_TIMEOUT, &db->read_timeout);
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen mysql_options(db->mysql, MYSQL_OPT_WRITE_TIMEOUT, &db->write_timeout);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen mysql_options(db->mysql, MYSQL_READ_DEFAULT_GROUP,
57ca8cc86193103127066c724815e7e7a24926dbTimo Sirainen db->option_group != NULL ? db->option_group : "client");
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen if (!db->ssl_set && (db->ssl_ca != NULL || db->ssl_ca_path != NULL)) {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen mysql_ssl_set(db->mysql, db->ssl_key, db->ssl_cert,
09d0ffb0ddfbebb4a04b377b9f879c05749de54fTimo Sirainen mysql_options(db->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
6bca3405636e3ec95724350c3a10d6fcb737782aTimo Sirainen "(remove ssl_ca and ssl_ca_path settings)");
e8fd206cf9dca263278efba21864606126fc29b8Timo Sirainen /* CLIENT_MULTI_RESULTS allows the use of stored procedures */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen failed = mysql_real_connect(db->mysql, host, db->user, db->password,
685a84a0ccb55cb9359dcd57d9eb3b6b836034a2Timo Sirainen /* connecting could have taken a while. make sure that any
685a84a0ccb55cb9359dcd57d9eb3b6b836034a2Timo Sirainen timeouts that get added soon will get a refreshed
685a84a0ccb55cb9359dcd57d9eb3b6b836034a2Timo Sirainen timestamp. */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
5fbf8719b9ef072295c16bc4492f9f0ece92117dTimo Sirainen i_error("%s: Connect failed to database (%s): %s - "
6bca3405636e3ec95724350c3a10d6fcb737782aTimo Sirainen "waiting for %u seconds before retry",
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen mysql_error(db->mysql), db->api.connect_delay);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_IDLE);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic void driver_mysql_disconnect(struct sql_db *_db ATTR_UNUSED)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void driver_mysql_parse_connect_string(struct mysql_db *db,
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen db->connect_timeout = SQL_CONNECT_TIMEOUT_SECS;
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen db->read_timeout = MYSQL_DEFAULT_READ_TIMEOUT_SECS;
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen db->write_timeout = MYSQL_DEFAULT_WRITE_TIMEOUT_SECS;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen args = t_strsplit_spaces(connect_string, " ");
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen i_fatal("mysql: Missing value in connect string: %s",
009217abb57a24a4076092e8e4e165545747839eStephan Bosch i_fatal("mysql: Invalid port number: %s", value);
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch } else if (strcmp(name, "client_flags") == 0) {
e180615c1db31c8a6f6a586ae40b1cfc2d6ee725Timo Sirainen if (str_to_uint(value, &db->client_flags) < 0)
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch i_fatal("mysql: Invalid client flags: %s", value);
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen } else if (strcmp(name, "connect_timeout") == 0) {
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen if (str_to_uint(value, &db->connect_timeout) < 0)
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen i_fatal("mysql: Invalid read_timeout: %s", value);
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen } else if (strcmp(name, "read_timeout") == 0) {
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen if (str_to_uint(value, &db->read_timeout) < 0)
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen i_fatal("mysql: Invalid read_timeout: %s", value);
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen } else if (strcmp(name, "write_timeout") == 0) {
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen if (str_to_uint(value, &db->write_timeout) < 0)
2d33429390199b9dfbbdaace1b48626cc3a7a42bTimo Sirainen i_fatal("mysql: Invalid read_timeout: %s", value);
09d0ffb0ddfbebb4a04b377b9f879c05749de54fTimo Sirainen else if (strcmp(name, "ssl_verify_server_cert") == 0) {
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen i_fatal("mysql: Unknown connect string: %s", name);
6920cf9a4d7bb8fd1a5b5bd25fafc8dd0cb9817cTimo Sirainen if (db->host == NULL && db->option_file == NULL)
6bca3405636e3ec95724350c3a10d6fcb737782aTimo Sirainen i_fatal("mysql: No hosts given in connect string");
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainenstatic struct sql_db *driver_mysql_init_v(const char *connect_string)
bc27fcd011f86208feaf73da9778a66ac7d7d3abTimo Sirainen pool = pool_alloconly_create("mysql driver", 1024);
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen driver_mysql_parse_connect_string(db, connect_string);
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainenstatic void driver_mysql_deinit_v(struct sql_db *_db)
9222c96ab9f42b25d5d5260f9e7a42c694715b0aTimo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic int driver_mysql_do_query(struct mysql_db *db, const char *query)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED);
13a8c553f293349248b161ff851743498916e26eTimo Sirainenstatic const char *
24ce0c343cefe54af841871fa39dbc3464028b06Timo Sirainendriver_mysql_escape_string(struct sql_db *_db, const char *string)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen if (_db->state == SQL_DB_STATE_DISCONNECTED) {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* try connecting */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* FIXME: we don't have a valid connection, so fallback
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen to using default escaping. the next query will most
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen likely fail anyway so it shouldn't matter that much
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen what we return here.. Anyway, this API needs
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen changing so that the escaping function could already
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen fail the query reliably. */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen len = mysql_real_escape_string(db->mysql, to, string, len);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void driver_mysql_exec(struct sql_db *_db, const char *query)
5fbf8719b9ef072295c16bc4492f9f0ece92117dTimo Sirainen mysql_prefix(db), query, mysql_error(db->mysql));
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenstatic void driver_mysql_query(struct sql_db *db, const char *query,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen sql_query_callback_t *callback, void *context)
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenstatic struct sql_result *
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainendriver_mysql_query_s(struct sql_db *_db, const char *query)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen /* query ok */
f1ea65eb88b4f67e572c4640882bdf00fe3a2186Timo Sirainen result->affected_rows = mysql_affected_rows(db->mysql);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen result->result = mysql_store_result(db->mysql);
e8fd206cf9dca263278efba21864606126fc29b8Timo Sirainen /* Because we've enabled CLIENT_MULTI_RESULTS, we need to read
e8fd206cf9dca263278efba21864606126fc29b8Timo Sirainen (ignore) extra results - there should not be any.
e8fd206cf9dca263278efba21864606126fc29b8Timo Sirainen ret is: -1 = done, >0 = error, 0 = more results. */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen while ((ret = mysql_next_result(db->mysql)) == 0) ;
a5be5a0d074ed0f3e4106612a2792e143a43efc6Timo Sirainen (result->result != NULL || mysql_errno(db->mysql) == 0)) {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainenstatic void driver_mysql_result_free(struct sql_result *_result)
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
3656c91dcb8336814bebd4500e81c3dde25233e6Timo Sirainen i_assert(_result != &sql_not_connected_result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic int driver_mysql_result_next_row(struct sql_result *_result)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct mysql_db *db = (struct mysql_db *)_result->db;
fedb73c7e918653877286ede0fe18029b3cce7d3Timo Sirainen /* no results */
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen result->row = mysql_fetch_row(result->result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void driver_mysql_result_fetch_fields(struct mysql_result *result)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen result->fields_count = mysql_num_fields(result->result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen result->fields = mysql_fetch_fields(result->result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic unsigned int
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainendriver_mysql_result_get_fields_count(struct sql_result *_result)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic const char *
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainendriver_mysql_result_get_field_name(struct sql_result *_result, unsigned int idx)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic int driver_mysql_result_find_field(struct sql_result *_result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int i;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (strcmp(result->fields[i].name, field_name) == 0)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic const char *
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainendriver_mysql_result_get_field_value(struct sql_result *_result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen unsigned int idx)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainenstatic const unsigned char *
b3f46fa8a890527996fd0288cf964b7f3567cd22Timo Sirainendriver_mysql_result_get_field_value_binary(struct sql_result *_result,
b3f46fa8a890527996fd0288cf964b7f3567cd22Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
b3f46fa8a890527996fd0288cf964b7f3567cd22Timo Sirainen lengths = mysql_fetch_lengths(result->result);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic const char *
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainendriver_mysql_result_find_field_value(struct sql_result *result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen idx = driver_mysql_result_find_field(result, field_name);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return driver_mysql_result_get_field_value(result, idx);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic const char *const *
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainendriver_mysql_result_get_values(struct sql_result *_result)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
6bca3405636e3ec95724350c3a10d6fcb737782aTimo Sirainenstatic const char *driver_mysql_result_get_error(struct sql_result *_result)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct mysql_db *db = (struct mysql_db *)_result->db;
933de3f374e6971f50308cb7bea13a51a45287bfTimo Sirainen if ((err == CR_SERVER_GONE_ERROR || err == CR_SERVER_LOST) &&
3ec16f23d3c94c225e5b74d84c6a69064922cd9dTimo Sirainen errstr = t_strdup_printf("%s (idled for %u secs)",
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainendriver_mysql_transaction_begin(struct sql_db *db)
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen ctx = i_new(struct mysql_transaction_context, 1);
7d075009a641d88a45940238676883a8eaf1507bTimo Sirainen ctx->query_pool = pool_alloconly_create("mysql transaction", 1024);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainendriver_mysql_transaction_commit(struct sql_transaction_context *ctx,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen sql_commit_callback_t *callback, void *context)
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen if (sql_transaction_commit_s(&ctx, &error) < 0)
59811ccfeb7dccc51e3be50c299c2ed853a1e2f3Timo Sirainentransaction_send_query(struct mysql_transaction_context *ctx, const char *query,
f1ea65eb88b4f67e572c4640882bdf00fe3a2186Timo Sirainen struct mysql_result *result = (struct mysql_result *)_result;
f1ea65eb88b4f67e572c4640882bdf00fe3a2186Timo Sirainen i_assert(result->affected_rows != (my_ulonglong)-1);
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainenstatic int driver_mysql_try_commit_s(struct mysql_transaction_context *ctx)
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen struct sql_transaction_context *_ctx = &ctx->ctx;
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen /* try to use a transaction in any case,
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen even if it's not actually functional. */
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen if (transaction_send_query(ctx, "BEGIN", NULL) < 0) {
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen if (_ctx->db->state != SQL_DB_STATE_DISCONNECTED)
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen /* we got disconnected, retry */
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen if (transaction_send_query(ctx, _ctx->head->query,
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen if (transaction_send_query(ctx, "COMMIT", NULL) < 0)
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainendriver_mysql_transaction_commit_s(struct sql_transaction_context *_ctx,
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen const char **error_r)
a343811459d1d2259e668f369646a20f02301c2cTimo Sirainen struct mysql_db *db = (struct mysql_db *)_ctx->db;
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainendriver_mysql_transaction_rollback(struct sql_transaction_context *_ctx)
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainendriver_mysql_update(struct sql_transaction_context *_ctx, const char *query,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen sql_transaction_add_query(&ctx->ctx, ctx->query_pool,
b87761f9bbef949f31dae297e619ac3f5e9c2b2eTimo Sirainenstatic const char *
b87761f9bbef949f31dae297e619ac3f5e9c2b2eTimo Sirainendriver_mysql_escape_blob(struct sql_db *_db ATTR_UNUSED,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen .flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_POOLED,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .transaction_begin = driver_mysql_transaction_begin,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .transaction_commit = driver_mysql_transaction_commit,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .transaction_commit_s = driver_mysql_transaction_commit_s,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .transaction_rollback = driver_mysql_transaction_rollback,
0db5b158a00c08955bdacc99b1e2cd1ec07f4311Timo Sirainenconst struct sql_result driver_mysql_result = {
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .get_fields_count = driver_mysql_result_get_fields_count,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .get_field_name = driver_mysql_result_get_field_name,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .get_field_value = driver_mysql_result_get_field_value,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .get_field_value_binary = driver_mysql_result_get_field_value_binary,
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .find_field_value = driver_mysql_result_find_field_value,
43d32cbe60fdaef2699d99f1ca259053e9350411Timo Sirainendriver_mysql_result_error_next_row(struct sql_result *result ATTR_UNUSED)
0db5b158a00c08955bdacc99b1e2cd1ec07f4311Timo Sirainenconst struct sql_result driver_mysql_error_result = {
ef597c4619eb021563f659b886c67762fce7a817Timo Sirainen .next_row = driver_mysql_result_error_next_row,