This repository has been archived on 2024-01-15. You can view files and clone it, but cannot push or open issues or pull requests.
vn-mysql/src/proxy-auth/proxy-auth.c

469 lines
10 KiB
C
Raw Normal View History

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#define MYSQL_ABI_CHECK
#include <stdio.h>
#include <string.h>
#include <mysql.h>
#include <mysql/errmsg.h>
#include <mysql/plugin_auth.h>
#include <glib-unix.h>
#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;
2015-09-20 12:18:40 +00:00
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, &regex_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;