/* Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl Modified 2004 by Mikhail Ramendik mr@ramendik.ru (MR) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * * mysql driver file. * functions for connecting and talking to the Mysql database */ #include "dbmail.h" #include "mysql.h" #define DB_MYSQL_STANDARD_PORT 3306 const char * db_get_sql(sql_fragment_t frag) { switch(frag) { case SQL_TO_CHAR: return "DATE_FORMAT(%s, '%%Y-%%m-%%d %%T')"; break; case SQL_TO_DATE: return "'%s'"; break; case SQL_CURRENT_TIMESTAMP: return "CURRENT_TIMESTAMP"; break; case SQL_REPLYCACHE_EXPIRE: return "NOW() - INTERVAL %d DAY"; break; case SQL_BINARY: return "BINARY"; break; case SQL_REGEXP: return "REGEXP"; break; case SQL_SENSITIVE_LIKE: return "LIKE BINARY"; break; case SQL_INSENSITIVE_LIKE: return "LIKE"; break; case SQL_ENCODE_ESCAPE: return "%s"; break; } return NULL; } static MYSQL conn; /**< MySQL database connection */ static MYSQL_RES *res = NULL; /**< MySQL result set */ static MYSQL_RES *msgbuf_res = NULL; /**< MySQL result set for msgbuf */ static MYSQL_RES *stored_res = NULL; /**< MySQL result set backup */ static MYSQL_ROW last_row; /**< MySQL result row */ /* Additions by MR */ static int res_changed = 1; /* result set changed */ static unsigned last_row_number = 0; /* the number of the row in last_row */ /* database parameters */ db_param_t _db_params; /* static functions, only used locally */ /* Check that the database and connection collations match. * I don't think we need to worry about the server collation. */ static int db_mysql_check_collations(void) { char the_query[DEF_QUERYSIZE]; char *collation[3][2]; int collations_match = 0; int i, j; if (strlen(_db_params.encoding) > 0) { snprintf(the_query, DEF_QUERYSIZE, "SET NAMES %s", _db_params.encoding); if (db_query(the_query) == DM_EQUERY) { trace(TRACE_ERROR, "%s,%s: error setting collation", __FILE__, __func__); return DM_EQUERY; } db_free_result(); } snprintf(the_query, DEF_QUERYSIZE, "SHOW VARIABLES LIKE 'collation_%%'"); if (db_query(the_query) == DM_EQUERY) { trace(TRACE_ERROR, "%s,%s: error getting collation variables for database", __FILE__, __func__); return DM_EQUERY; } for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) collation[i][j] = strdup(db_get_result(i, j)); /* Sane indentation goes insane here. */ for (i = 0; i < 3; i++) if (strcmp(collation[i][0], "collation_database") == 0) { for (j = 0; j < 3; j++) if (strcmp(collation[j][0], "collation_connection") == 0) { trace(TRACE_DEBUG, "%s,%s: does [%s:%s] match [%s:%s]?", __FILE__, __func__, collation[i][0], collation[i][1], collation[j][0], collation[j][1]); if (strcmp(collation[i][1], collation[j][1]) == 0) { collations_match = 1; break; } } if (collations_match) break; } db_free_result(); for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) free(collation[i][j]); if (!collations_match) { trace(TRACE_ERROR, "%s,%s: collation mismatch, your MySQL configuration specifies a" " different charset than the data currently in your DBMail database.", __FILE__, __func__); return DM_EQUERY; } return DM_SUCCESS; } int db_connect() { char *sock = NULL; /* connect */ mysql_init(&conn); /* auto re-connect */ conn.reconnect = 1; /* use the standard MySQL port by default */ if (_db_params.port == 0) _db_params.port = DB_MYSQL_STANDARD_PORT; /* issue an error if a connection to a MySQL instance * on the localhost is requested, but no sqlsocket is given */ if (strncmp(_db_params.host, "localhost", FIELDSIZE) == 0 || _db_params.host == NULL) { if (strlen(_db_params.sock) == 0) { trace(TRACE_WARNING, "%s,%s: MySQL host is set to " "localhost, but no mysql_socket has been set. " "Use sqlsocket=... in dbmail.conf. Connecting " "will be attempted using the default socket.", __FILE__, __func__); sock = NULL; } else sock = _db_params.sock; } if (mysql_real_connect(&conn, _db_params.host, _db_params.user, _db_params.pass, _db_params.db, _db_params.port, sock, 0) == NULL) { trace(TRACE_ERROR, "%s,%s: mysql_real_connect failed: %s", __FILE__, __func__, mysql_error(&conn)); return DM_EQUERY; } #ifdef mysql_errno if (mysql_errno(&conn)) { trace(TRACE_ERROR, "%s,%s: mysql_real_connect failed: %s", __FILE__, __func__, mysql_error(&conn)); return DM_EQUERY; } #endif if (db_mysql_check_collations() == DM_EQUERY) return DM_EQUERY; return DM_SUCCESS; } unsigned db_num_rows() { /* mysql_store_result can return NULL. If this is * true, res will be zero, and naturally num_rows * should return DM_SUCCESS */ if (!res) return DM_SUCCESS; return mysql_num_rows(res); } unsigned db_num_fields() { if (!res) return DM_SUCCESS; return mysql_num_fields(res); } void db_free_result() { if (res) mysql_free_result(res); res = NULL; res_changed = 1; } const char *db_get_result(unsigned row, unsigned field) { char *result; if (!res) { trace(TRACE_WARNING, "%s,%s: result set is null\n", __FILE__, __func__); return NULL; } if ((row >= db_num_rows()) || (field >= db_num_fields())) { trace(TRACE_WARNING, "%s, %s: " "row = %u, field = %u, bigger than size of result set", __FILE__, __func__, row, field); return NULL; } if (res_changed) { mysql_data_seek(res, row); last_row = mysql_fetch_row(res); } else { if (row == last_row_number+1) last_row = mysql_fetch_row(res); else if (row != last_row_number) { mysql_data_seek(res, row); last_row = mysql_fetch_row(res); }; }; res_changed = 0; last_row_number = row; if (last_row == NULL) { trace(TRACE_WARNING, "%s,%s: row is NULL\n", __FILE__, __func__); return NULL; } result = last_row[field]; if (result == NULL) trace(TRACE_WARNING, "%s,%s: result is null\n", __FILE__, __func__); return result; } int db_disconnect() { if (res) db_free_result(); mysql_close(&conn); return DM_SUCCESS; } int db_check_connection() { if (mysql_ping(&conn)) { if (db_connect() < 0) { trace(TRACE_ERROR, "%s,%s: unable to connect to " "database.", __FILE__, __func__); return DM_EQUERY; } } return DM_SUCCESS; } u64_t db_insert_result(const char *sequence_identifier UNUSED) { u64_t insert_result; insert_result = mysql_insert_id(&conn); return insert_result; } int db_query(const char *q) { unsigned querysize = 0; assert(q); querysize = (unsigned) strlen(q); if (querysize == 0) { trace(TRACE_ERROR, "%s,%s: empty query: [%d]", __FILE__, __func__, querysize); return DM_EQUERY; } if (db_check_connection() < 0) return DM_EQUERY; trace(TRACE_DEBUG, "%s,%s: query [%s]", __FILE__, __func__, q); if (mysql_real_query(&conn, q, querysize)) { trace(TRACE_ERROR, "%s,%s: [%s] [%s]", __FILE__, __func__, mysql_error(&conn), q); return DM_EQUERY; } if(res != NULL) db_free_result(); res = mysql_store_result(&conn); res_changed = 1; return DM_SUCCESS; } unsigned long db_escape_string(char *to, const char *from, unsigned long length) { return mysql_real_escape_string(&conn, to, from, length); } unsigned long db_escape_binary(char *to, const char *from, unsigned long length) { return db_escape_string(to,from,length); } int db_do_cleanup(const char **tables, int num) { char the_query[DEF_QUERYSIZE]; int i; int result = 0; for (i = 0; i < num; i++) { snprintf(the_query, DEF_QUERYSIZE, "OPTIMIZE TABLE %s%s", _db_params.pfx,tables[i]); if (db_query(the_query) == DM_EQUERY) { trace(TRACE_ERROR, "%s,%s: error optimizing table [%s%s]", __FILE__, __func__, _db_params.pfx,tables[i]); result = DM_EQUERY; } db_free_result(); } return result; } u64_t db_get_length(unsigned row, unsigned field) { if (!res) { trace(TRACE_WARNING, "%s,%s: result set is null\n", __FILE__, __func__); return DM_EQUERY; } res_changed=1; if ((row >= db_num_rows()) || (field >= db_num_fields())) { trace(TRACE_ERROR, "%s, %s: " "row = %u, field = %u, bigger than size of result set", __FILE__, __func__, row, field); return DM_EQUERY; } mysql_data_seek(res, row); last_row = mysql_fetch_row(res); if (last_row == NULL) { trace(TRACE_ERROR, "%s,%s: last_row = NULL", __FILE__, __func__); return (u64_t) 0; } return (u64_t) mysql_fetch_lengths(res)[field]; } u64_t db_get_affected_rows() { return (u64_t) mysql_affected_rows(&conn); } void db_use_msgbuf_result() { stored_res = res; res = msgbuf_res; res_changed = 1; /*MR*/ } void db_store_msgbuf_result() { msgbuf_res = res; res = stored_res; res_changed = 1; /*MR*/ } void *db_get_result_set() { return (void *) res; } void db_set_result_set(void *the_result_set) { res = (MYSQL_RES *) the_result_set; res_changed = 1; /*MR*/ }