aboutsummaryrefslogtreecommitdiff
path: root/mysql/sql-common/client_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'mysql/sql-common/client_plugin.c')
-rw-r--r--mysql/sql-common/client_plugin.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/mysql/sql-common/client_plugin.c b/mysql/sql-common/client_plugin.c
new file mode 100644
index 0000000..c7dfda9
--- /dev/null
+++ b/mysql/sql-common/client_plugin.c
@@ -0,0 +1,592 @@
+/* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
+
+ 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; version 2 of the License.
+
+ 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+/**
+ @file
+
+ Support code for the client side (libmysql) plugins
+
+ Client plugins are somewhat different from server plugins, they are simpler.
+
+ They do not need to be installed or in any way explicitly loaded on the
+ client, they are loaded automatically on demand.
+ One client plugin per shared object, soname *must* match the plugin name.
+
+ There is no reference counting and no unloading either.
+*/
+
+#include <my_global.h>
+#include "mysql.h"
+#include <my_sys.h>
+#include <m_string.h>
+#include <my_thread.h>
+
+#include <sql_common.h>
+#include "errmsg.h"
+#include <mysql/client_plugin.h>
+
+#ifdef HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#if defined(CLIENT_PROTOCOL_TRACING)
+#include <mysql/plugin_trace.h>
+#endif
+
+PSI_memory_key key_memory_root;
+PSI_memory_key key_memory_load_env_plugins;
+
+#ifdef HAVE_PSI_INTERFACE
+PSI_mutex_key key_mutex_LOCK_load_client_plugin;
+
+static PSI_mutex_info all_client_plugin_mutexes[]=
+{
+ {&key_mutex_LOCK_load_client_plugin, "LOCK_load_client_plugin", PSI_FLAG_GLOBAL}
+};
+
+static PSI_memory_info all_client_plugin_memory[]=
+{
+ {&key_memory_root, "root", PSI_FLAG_GLOBAL},
+ {&key_memory_load_env_plugins, "load_env_plugins", PSI_FLAG_GLOBAL}
+};
+
+static void init_client_plugin_psi_keys()
+{
+ const char* category= "sql";
+ int count;
+
+ count= array_elements(all_client_plugin_mutexes);
+ mysql_mutex_register(category, all_client_plugin_mutexes, count);
+
+ count= array_elements(all_client_plugin_memory);
+ mysql_memory_register(category, all_client_plugin_memory, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+struct st_client_plugin_int {
+ struct st_client_plugin_int *next;
+ void *dlhandle;
+ struct st_mysql_client_plugin *plugin;
+};
+
+static my_bool initialized= 0;
+static MEM_ROOT mem_root;
+
+static const char *plugin_declarations_sym= "_mysql_client_plugin_declaration_";
+static uint plugin_version[MYSQL_CLIENT_MAX_PLUGINS]=
+{
+ 0, /* these two are taken by Connector/C */
+ 0, /* these two are taken by Connector/C */
+ MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION,
+ MYSQL_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION,
+};
+
+/*
+ Loaded plugins are stored in a linked list.
+ The list is append-only, the elements are added to the head (like in a stack).
+ The elements are added under a mutex, but the list can be read and traversed
+ without any mutex because once an element is added to the list, it stays
+ there. The main purpose of a mutex is to prevent two threads from
+ loading the same plugin twice in parallel.
+*/
+struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS];
+static mysql_mutex_t LOCK_load_client_plugin;
+
+static int is_not_initialized(MYSQL *mysql, const char *name)
+{
+ if (initialized)
+ return 0;
+
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD,
+ unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD),
+ name, "not initialized");
+ return 1;
+}
+
+/**
+ finds a plugin in the list
+
+ @param name plugin name to search for
+ @param type plugin type
+
+ @note this does NOT necessarily need a mutex, take care!
+
+ @retval a pointer to a found plugin or 0
+*/
+static struct st_mysql_client_plugin *
+find_plugin(const char *name, int type)
+{
+ struct st_client_plugin_int *p;
+
+ DBUG_ASSERT(initialized);
+ DBUG_ASSERT(type >= 0 && type < MYSQL_CLIENT_MAX_PLUGINS);
+ if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS)
+ return 0;
+
+ for (p= plugin_list[type]; p; p= p->next)
+ {
+ if (strcmp(p->plugin->name, name) == 0)
+ return p->plugin;
+ }
+ return NULL;
+}
+
+/**
+ verifies the plugin and adds it to the list
+
+ @param mysql MYSQL structure (for error reporting)
+ @param plugin plugin to install
+ @param dlhandle a handle to the shared object (returned by dlopen)
+ or 0 if the plugin was not dynamically loaded
+ @param argc number of arguments in the 'va_list args'
+ @param args arguments passed to the plugin initialization function
+
+ @retval a pointer to an installed plugin or 0
+*/
+static struct st_mysql_client_plugin *
+do_add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin,
+ void *dlhandle,
+ int argc, va_list args)
+{
+ const char *errmsg;
+ struct st_client_plugin_int plugin_int, *p;
+ char errbuf[1024];
+
+ DBUG_ASSERT(initialized);
+
+ plugin_int.plugin= plugin;
+ plugin_int.dlhandle= dlhandle;
+
+ if (plugin->type >= MYSQL_CLIENT_MAX_PLUGINS)
+ {
+ errmsg= "Unknown client plugin type";
+ goto err1;
+ }
+
+ if (plugin->interface_version < plugin_version[plugin->type] ||
+ (plugin->interface_version >> 8) >
+ (plugin_version[plugin->type] >> 8))
+ {
+ errmsg= "Incompatible client plugin interface";
+ goto err1;
+ }
+
+#if defined(CLIENT_PROTOCOL_TRACING) && !defined(MYSQL_SERVER)
+ /*
+ If we try to load a protocol trace plugin but one is already
+ loaded (global trace_plugin pointer is not NULL) then we ignore
+ the new trace plugin and give error. This is done before the
+ new plugin gets initialized.
+ */
+ if (plugin->type == MYSQL_CLIENT_TRACE_PLUGIN && NULL != trace_plugin)
+ {
+ errmsg= "Can not load another trace plugin while one is already loaded";
+ goto err1;
+ }
+#endif
+
+ /* Call the plugin initialization function, if any */
+ if (plugin->init && plugin->init(errbuf, sizeof(errbuf), argc, args))
+ {
+ errmsg= errbuf;
+ goto err1;
+ }
+
+ p= (struct st_client_plugin_int *)
+ memdup_root(&mem_root, &plugin_int, sizeof(plugin_int));
+
+ if (!p)
+ {
+ errmsg= "Out of memory";
+ goto err2;
+ }
+
+ mysql_mutex_assert_owner(&LOCK_load_client_plugin);
+
+ p->next= plugin_list[plugin->type];
+ plugin_list[plugin->type]= p;
+ net_clear_error(&mysql->net);
+
+#if defined(CLIENT_PROTOCOL_TRACING) && !defined(MYSQL_SERVER)
+ /*
+ If loaded plugin is a protocol trace one, then set the global
+ trace_plugin pointer to point at it. When trace_plugin is not NULL,
+ each new connection will be traced using the plugin pointed by it
+ (see MYSQL_TRACE_STAGE() macro in libmysql/mysql_trace.h).
+ */
+ if (plugin->type == MYSQL_CLIENT_TRACE_PLUGIN)
+ {
+ trace_plugin = (struct st_mysql_client_plugin_TRACE*)plugin;
+ }
+#endif
+
+ return plugin;
+
+err2:
+ if (plugin->deinit)
+ plugin->deinit();
+err1:
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
+ ER(CR_AUTH_PLUGIN_CANNOT_LOAD), plugin->name,
+ errmsg);
+ if (dlhandle)
+ dlclose(dlhandle);
+ return NULL;
+}
+
+
+static struct st_mysql_client_plugin *
+add_plugin_noargs(MYSQL *mysql, struct st_mysql_client_plugin *plugin,
+ void *dlhandle,
+ int argc, ...)
+{
+ struct st_mysql_client_plugin *retval= NULL;
+ va_list ap;
+ va_start(ap, argc);
+ retval= do_add_plugin(mysql, plugin, dlhandle, argc, ap);
+ va_end(ap);
+ return retval;
+}
+
+
+static struct st_mysql_client_plugin *
+add_plugin_withargs(MYSQL *mysql, struct st_mysql_client_plugin *plugin,
+ void *dlhandle,
+ int argc, va_list args)
+{
+ return do_add_plugin(mysql, plugin, dlhandle, argc, args);
+}
+
+
+
+/**
+ Loads plugins which are specified in the environment variable
+ LIBMYSQL_PLUGINS.
+
+ Multiple plugins must be separated by semicolon. This function doesn't
+ return or log an error.
+
+ The function is be called by mysql_client_plugin_init
+
+ @todo
+ Support extended syntax, passing parameters to plugins, for example
+ LIBMYSQL_PLUGINS="plugin1(param1,param2);plugin2;..."
+ or
+ LIBMYSQL_PLUGINS="plugin1=int:param1,str:param2;plugin2;..."
+*/
+static void load_env_plugins(MYSQL *mysql)
+{
+ char *plugs, *free_env, *s= getenv("LIBMYSQL_PLUGINS");
+ char *enable_cleartext_plugin= getenv("LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN");
+
+ if (enable_cleartext_plugin && strchr("1Yy", enable_cleartext_plugin[0]))
+ libmysql_cleartext_plugin_enabled= 1;
+
+ /* no plugins to load */
+ if(!s)
+ return;
+
+ free_env= plugs= my_strdup(key_memory_load_env_plugins,
+ s, MYF(MY_WME));
+
+ do {
+ if ((s= strchr(plugs, ';')))
+ *s= '\0';
+ mysql_load_plugin(mysql, plugs, -1, 0);
+ plugs= s + 1;
+ } while (s);
+
+ my_free(free_env);
+
+}
+
+
+/********** extern functions to be used by libmysql *********************/
+
+/**
+ Initializes the client plugin layer.
+
+ This function must be called before any other client plugin function.
+
+ @retval 0 successful
+ @retval != 0 error occured
+*/
+int mysql_client_plugin_init()
+{
+ MYSQL mysql;
+ struct st_mysql_client_plugin **builtin;
+
+ if (initialized)
+ return 0;
+
+#ifdef HAVE_PSI_INTERFACE
+ init_client_plugin_psi_keys();
+#endif /* HAVE_PSI_INTERFACE */
+
+ memset(&mysql, 0, sizeof(mysql)); /* dummy mysql for set_mysql_extended_error */
+
+ mysql_mutex_init(key_mutex_LOCK_load_client_plugin,
+ &LOCK_load_client_plugin, MY_MUTEX_INIT_SLOW);
+ init_alloc_root(key_memory_root, &mem_root, 128, 128);
+
+ memset(&plugin_list, 0, sizeof(plugin_list));
+
+ initialized= 1;
+
+ mysql_mutex_lock(&LOCK_load_client_plugin);
+
+ for (builtin= mysql_client_builtins; *builtin; builtin++)
+ add_plugin_noargs(&mysql, *builtin, 0, 0);
+
+ mysql_mutex_unlock(&LOCK_load_client_plugin);
+
+ load_env_plugins(&mysql);
+
+ mysql_close_free(&mysql);
+
+ return 0;
+}
+
+/**
+ Deinitializes the client plugin layer.
+
+ Unloades all client plugins and frees any associated resources.
+*/
+void mysql_client_plugin_deinit()
+{
+ int i;
+ struct st_client_plugin_int *p;
+
+ if (!initialized)
+ return;
+
+ for (i=0; i < MYSQL_CLIENT_MAX_PLUGINS; i++)
+ for (p= plugin_list[i]; p; p= p->next)
+ {
+ if (p->plugin->deinit)
+ p->plugin->deinit();
+ if (p->dlhandle)
+ dlclose(p->dlhandle);
+ }
+
+ memset(&plugin_list, 0, sizeof(plugin_list));
+ initialized= 0;
+ free_root(&mem_root, MYF(0));
+ mysql_mutex_destroy(&LOCK_load_client_plugin);
+}
+
+
+/************* public facing functions, for client consumption *********/
+
+/* see <mysql/client_plugin.h> for a full description */
+struct st_mysql_client_plugin *
+mysql_client_register_plugin(MYSQL *mysql,
+ struct st_mysql_client_plugin *plugin)
+{
+ if (is_not_initialized(mysql, plugin->name))
+ return NULL;
+
+ mysql_mutex_lock(&LOCK_load_client_plugin);
+
+ /* make sure the plugin wasn't loaded meanwhile */
+ if (find_plugin(plugin->name, plugin->type))
+ {
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD,
+ unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD),
+ plugin->name, "it is already loaded");
+ plugin= NULL;
+ }
+ else
+ plugin= add_plugin_noargs(mysql, plugin, 0, 0);
+
+ mysql_mutex_unlock(&LOCK_load_client_plugin);
+ return plugin;
+}
+
+/* see <mysql/client_plugin.h> for a full description */
+struct st_mysql_client_plugin *
+mysql_load_plugin_v(MYSQL *mysql, const char *name, int type,
+ int argc, va_list args)
+{
+ const char *errmsg;
+ char dlpath[FN_REFLEN+1];
+ void *sym, *dlhandle;
+ struct st_mysql_client_plugin *plugin;
+ const char *plugindir;
+#ifdef _WIN32
+ char win_errormsg[2048];
+#endif
+
+ DBUG_ENTER ("mysql_load_plugin_v");
+ DBUG_PRINT ("entry", ("name=%s type=%d int argc=%d", name, type, argc));
+ if (is_not_initialized(mysql, name))
+ {
+ DBUG_PRINT ("leave", ("mysql not initialized"));
+ DBUG_RETURN (NULL);
+ }
+
+ mysql_mutex_lock(&LOCK_load_client_plugin);
+
+ /* make sure the plugin wasn't loaded meanwhile */
+ if (type >= 0 && find_plugin(name, type))
+ {
+ errmsg= "it is already loaded";
+ goto err;
+ }
+
+ if (mysql->options.extension && mysql->options.extension->plugin_dir)
+ {
+ plugindir= mysql->options.extension->plugin_dir;
+ }
+ else
+ {
+ plugindir= getenv("LIBMYSQL_PLUGIN_DIR");
+ if (!plugindir)
+ {
+ plugindir= PLUGINDIR;
+ }
+ }
+
+ /* Compile dll path */
+ strxnmov(dlpath, sizeof(dlpath) - 1,
+ plugindir, "/",
+ name, SO_EXT, NullS);
+
+ DBUG_PRINT ("info", ("dlopeninig %s", dlpath));
+ /* Open new dll handle */
+ if (!(dlhandle= dlopen(dlpath, RTLD_NOW)))
+ {
+#if defined(__APPLE__)
+ /* Apple supports plugins with .so also, so try this as well */
+ strxnmov(dlpath, sizeof(dlpath) - 1,
+ plugindir, "/",
+ name, ".so", NullS);
+ if ((dlhandle= dlopen(dlpath, RTLD_NOW)))
+ goto have_plugin;
+#endif
+
+#ifdef _WIN32
+ /* There should be no win32 calls between failed dlopen() and GetLastError() */
+ if(FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, GetLastError(), 0, win_errormsg, 2048, NULL))
+ errmsg= win_errormsg;
+ else
+ errmsg= "";
+#else
+ errmsg= dlerror();
+#endif
+ DBUG_PRINT ("info", ("failed to dlopen"));
+ goto err;
+ }
+
+#if defined(__APPLE__)
+have_plugin:
+#endif
+ if (!(sym= dlsym(dlhandle, plugin_declarations_sym)))
+ {
+ errmsg= "not a plugin";
+ dlclose(dlhandle);
+ goto err;
+ }
+
+ plugin= (struct st_mysql_client_plugin*)sym;
+
+ if (type >=0 && type != plugin->type)
+ {
+ errmsg= "type mismatch";
+ goto err;
+ }
+
+ if (strcmp(name, plugin->name))
+ {
+ errmsg= "name mismatch";
+ goto err;
+ }
+
+ if (type < 0 && find_plugin(name, plugin->type))
+ {
+ errmsg= "it is already loaded";
+ goto err;
+ }
+
+ plugin= add_plugin_withargs(mysql, plugin, dlhandle, argc, args);
+
+ mysql_mutex_unlock(&LOCK_load_client_plugin);
+
+ DBUG_PRINT ("leave", ("plugin loaded ok"));
+ DBUG_RETURN (plugin);
+
+err:
+ mysql_mutex_unlock(&LOCK_load_client_plugin);
+ DBUG_PRINT ("leave", ("plugin load error : %s", errmsg));
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
+ ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, errmsg);
+ DBUG_RETURN (NULL);
+}
+
+/* see <mysql/client_plugin.h> for a full description */
+struct st_mysql_client_plugin *
+mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...)
+{
+ struct st_mysql_client_plugin *p;
+ va_list args;
+ va_start(args, argc);
+ p= mysql_load_plugin_v(mysql, name, type, argc, args);
+ va_end(args);
+ return p;
+}
+
+/* see <mysql/client_plugin.h> for a full description */
+struct st_mysql_client_plugin *
+mysql_client_find_plugin(MYSQL *mysql, const char *name, int type)
+{
+ struct st_mysql_client_plugin *p;
+
+ DBUG_ENTER ("mysql_client_find_plugin");
+ DBUG_PRINT ("entry", ("name=%s, type=%d", name, type));
+ if (is_not_initialized(mysql, name))
+ DBUG_RETURN (NULL);
+
+ if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS)
+ {
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
+ ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name,
+ "invalid type");
+ }
+
+ if ((p= find_plugin(name, type)))
+ {
+ DBUG_PRINT ("leave", ("found %p", p));
+ DBUG_RETURN (p);
+ }
+
+ /* not found, load it */
+ p= mysql_load_plugin(mysql, name, type, 0);
+ DBUG_PRINT ("leave", ("loaded %p", p));
+ DBUG_RETURN (p);
+}
+
+
+/* see <mysql/client_plugin.h> for a full description */
+int mysql_plugin_options(struct st_mysql_client_plugin *plugin,
+ const char *option,
+ const void *value)
+{
+ DBUG_ENTER("mysql_plugin_options");
+ /* does the plugin support options call? */
+ if (!plugin || !plugin->options)
+ DBUG_RETURN(1);
+ DBUG_RETURN(plugin->options(option, value));
+}