/* * Copyright (C) 2013 - Alejandro T. Colombini * * 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 3 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, see . */ #define MYSQL_ABI_CHECK #include #include #include #include #include #include #define CONFIG_FILE _CONFIG_DIR"/proxy-auth.ini" #define SQL_FILE _SQL_DIR"/proxy-auth.sql" typedef struct { gboolean initialized; gchar * socket; gchar * host; gchar * user; gchar * pass; gchar * schema; guint port; GRegex * regex; gchar * queryt; gsize queryt_len; gint pool_size; gint max_connections; GAsyncQueue * conn_pool; GMutex mutex; guint last_error; gboolean debug; } ProxyAuth; typedef struct { MYSQL * conn; gchar * user; gulong user_len; gchar * pass; gulong pass_len; } RegexData; ProxyAuth * proxy_auth_new() { ProxyAuth * self = g_new(ProxyAuth, 1); self->initialized = FALSE; g_mutex_init(&self->mutex); return self; } void proxy_auth_deinit(ProxyAuth * self) { if (!self->initialized) return; g_free(self->socket); g_free(self->host); g_free(self->user); g_free(self->pass); g_free(self->schema); g_free(self->queryt); if (self->regex) g_regex_unref(self->regex); // FIXME: Connection can't be closed because mysql_close() causes MySQL // process crash with signal 11 when the process is shutdown. // MySQL version 5.5.40 //if (self->conn_pool) // g_async_queue_unref(self->conn_pool); } void proxy_auth_free(ProxyAuth * self) { g_return_if_fail(self != NULL); proxy_auth_deinit(self); g_mutex_clear(&self->mutex); g_free(self); } gboolean proxy_auth_init(ProxyAuth * self) { g_return_val_if_fail(self != NULL, FALSE); gboolean res = FALSE; GError * error = NULL; proxy_auth_deinit(self); self->initialized = TRUE; self->socket = NULL; self->host = NULL; self->user = NULL; self->pass = NULL; self->schema = NULL; self->queryt = NULL; self->queryt_len = 0; self->regex = NULL; self->conn_pool = NULL; self->last_error = 0; self->debug = FALSE; mysql_library_init(0, NULL, NULL); // Reading the configuration file GKeyFile * key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, CONFIG_FILE, G_KEY_FILE_NONE, &error)) { g_warning("ProxyAuth: Can't open configuration file: %s", error->message); goto end; } self->socket = NULL; self->host = g_key_file_get_string(key_file, "db", "host", NULL); if (!self->host) self->socket = g_key_file_get_string(key_file, "db", "socket", NULL); self->user = g_key_file_get_string(key_file, "db", "user", NULL); self->pass = g_key_file_get_string(key_file, "db", "pass", NULL); self->schema = g_key_file_get_string(key_file, "db", "schema", NULL); self->port = (guint) g_key_file_get_integer(key_file, "db", "port", NULL); self->max_connections = g_key_file_get_integer(key_file, "db", "maxConnections", NULL); self->debug = g_key_file_get_boolean(key_file, "db", "debug", NULL); // Reading the query template if (!g_file_get_contents(SQL_FILE, &self->queryt, &self->queryt_len, &error)) { g_warning("ProxyAuth: Can't read the query template: %s", error->message); goto end; } // Creates the regular expression self->regex = g_regex_new("#\\w+", G_REGEX_OPTIMIZE | G_REGEX_MULTILINE, 0, &error); if (error) { g_warning("ProxyAuth: Can't create the regex: %s", error->message); goto end; } // Creates the connection pool self->conn_pool = g_async_queue_new_full((GDestroyNotify) mysql_close); self->pool_size = 0; self->last_error = 0; if (self->max_connections < 1) self->max_connections = 1; res = TRUE; end: g_clear_error(&error); g_key_file_free(key_file); return res; } static gboolean proxy_auth_regex_func(const GMatchInfo * info, GString * res, gpointer data) { RegexData * regex_data = (RegexData *) data; gchar * match = g_match_info_fetch(info, 0); gchar * str = NULL; gulong str_len; if (!g_strcmp0(match, "#user")) { str = regex_data->user; str_len = regex_data->user_len; } else if (!g_strcmp0(match, "#pass")) { str = regex_data->pass; str_len = regex_data->pass_len; } if (str) { unsigned long scaped_len; char escaped_str[str_len * 2 + 1]; g_string_append_c(res, '\''); // FIXME: mysql_real_escape_string() causes MySQL process crash with signal 11 //scaped_len = mysql_real_escape_string(regex_data->conn, escaped_str, str, str_len); scaped_len = mysql_escape_string(escaped_str, str, str_len); g_string_append_len(res, escaped_str,(gssize) scaped_len); g_string_append_c(res, '\''); } else g_string_append(res, match); g_free(match); return FALSE; } static gboolean proxy_auth_reconnect(ProxyAuth * self, MYSQL * conn) { g_return_val_if_fail(self != NULL, FALSE); gboolean connected = mysql_real_connect(conn, self->host, self->user, self->pass, self->schema, self->port, self->socket, 0) != NULL; guint conn_error = mysql_errno(conn); if (conn_error && self->last_error != conn_error) g_warning("ProxyAuth: Can't connect to database: %s", mysql_error(conn)); else if (mysql_set_character_set(conn, "latin1")) g_warning("ProxyAuth: Can't set character set: %s", mysql_error(conn)); self->last_error = conn_error; return connected; } int proxy_auth_authenticate(ProxyAuth * self, MYSQL_PLUGIN_VIO * vio, MYSQL_SERVER_AUTH_INFO * info) { g_return_val_if_fail(self != NULL, CR_ERROR); int res = CR_ERROR; gchar * query = NULL; GError * error = NULL; unsigned char * pkt; // Check for the username int user_len = info->user_name_length; if (info->user_name == NULL &&(user_len = vio->read_packet(vio, &pkt)) < 0) return CR_ERROR; if (user_len > MYSQL_USERNAME_LENGTH) return CR_ERROR; // Read the password and check if it's valid int pass_len = vio->read_packet(vio, &pkt) - 1; if (pass_len < 0) return CR_ERROR; if (!pass_len || *pkt == '\0') { info->password_used = PASSWORD_USED_NO; return CR_ERROR; } char pass[pass_len + 1]; memcpy(pass, pkt, pass_len); pass[pass_len] = '\0'; info->password_used = PASSWORD_USED_YES; // Gets a connection from the pool MYSQL * conn = (MYSQL *) g_async_queue_try_pop(self->conn_pool); if (!conn) { g_mutex_lock(&self->mutex); if (self->pool_size < self->max_connections) { self->pool_size++; g_mutex_unlock(&self->mutex); conn = mysql_init(NULL); if (!proxy_auth_reconnect(self, conn)) { g_mutex_lock(&self->mutex); self->pool_size--; g_mutex_unlock(&self->mutex); mysql_close(conn); conn = NULL; goto end; } } else { g_mutex_unlock(&self->mutex); conn = (MYSQL *) g_async_queue_pop(self->conn_pool); } } switch (mysql_errno(conn)) { case CR_SERVER_LOST: case CR_SERVER_GONE_ERROR: if (!proxy_auth_reconnect(self, conn)) goto end; } // Replaces the user and the pass on the query template RegexData regex_data; regex_data.conn = conn; regex_data.user = info->user_name; regex_data.user_len = user_len; regex_data.pass = pass; regex_data.pass_len = pass_len; query = g_regex_replace_eval(self->regex, self->queryt, self->queryt_len, 0, 0, proxy_auth_regex_func, ®ex_data, &error); if (error) { g_warning("ProxyAuth: Can't evaluate regex: %s", error->message); goto end; } // Sends the query to the database if (self->debug) g_message("ProxyAuth: Query: %s", query); MYSQL_RES * result; if (!mysql_query(conn, query) &&(result = mysql_store_result(conn))) { MYSQL_ROW row = mysql_fetch_row(result); if (row && row[0]) { unsigned long row_len = mysql_fetch_lengths(result)[0]; if (row_len > 0 && row_len <= MYSQL_USERNAME_LENGTH) { strcpy(info->external_user, info->user_name); strncpy(info->authenticated_as, row[0], row_len); info->authenticated_as[row_len] = '\0'; res = CR_OK; if (self->debug) g_message("ProxyAuth: Proxy user: %s", info->authenticated_as); } } mysql_free_result(result); } else g_warning("ProxyAuth: Error executing query: %s", mysql_error(conn)); end: if (conn) g_async_queue_push(self->conn_pool, conn); g_clear_error(&error); g_free(query); return res; } ProxyAuth * pauth = NULL; static int proxy_auth_plugin_main(MYSQL_PLUGIN_VIO * vio, MYSQL_SERVER_AUTH_INFO * info) { return proxy_auth_authenticate(pauth, vio, info); } static int proxy_auth_plugin_init() { pauth = proxy_auth_new(); return proxy_auth_init(pauth) ? 0 : 1; } static int proxy_auth_plugin_deinit() { proxy_auth_free(pauth); return 0; } int generate_auth_string_hash( char *outbuf, unsigned int *buflen, const char *inbuf, unsigned int inbuflen) { if (*buflen < inbuflen) return 1; strncpy(outbuf, inbuf, inbuflen); *buflen = strlen(inbuf); return 0; } int validate_auth_string_hash( char* const inbuf __attribute__((unused)), unsigned int buflen __attribute__((unused))) { return 0; } int set_salt( const char* password __attribute__((unused)), unsigned int password_len __attribute__((unused)), unsigned char* salt __attribute__((unused)), unsigned char* salt_len) { *salt_len = 0; return 0; } static struct st_mysql_auth proxy_auth_handler = { MYSQL_AUTHENTICATION_INTERFACE_VERSION, "mysql_clear_password", // Cleartext plugin required in the client proxy_auth_plugin_main, generate_auth_string_hash, validate_auth_string_hash, set_salt, AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE }; mysql_declare_plugin(proxy_auth) { MYSQL_AUTHENTICATION_PLUGIN, &proxy_auth_handler, "proxy_auth", "Alejandro T. Colombini", "Proxy user authentication server-side plugin", PLUGIN_LICENSE_GPL, proxy_auth_plugin_init, proxy_auth_plugin_deinit, 0x0100, // version 1.0, NULL, NULL, NULL, 0, } mysql_declare_plugin_end;