1/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
2 Copyright (c) 2010, 2011, Oracle and/or its affiliates.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
16
17/**
18 @file
19
20 Support code for the client side (libmysql) plugins
21
22 Client plugins are somewhat different from server plugins, they are simpler.
23
24 They do not need to be installed or in any way explicitly loaded on the
25 client, they are loaded automatically on demand.
26 One client plugin per shared object, soname *must* match the plugin name.
27
28 There is no reference counting and no unloading either.
29*/
30
31#if defined(_MSC_VER)
32/* Silence warnings about variable 'unused' being used. */
33#define FORCE_INIT_OF_VARS 1
34#endif
35
36#include <my_global.h>
37#include "mysql.h"
38#include <my_sys.h>
39#include <m_string.h>
40#include <my_pthread.h>
41
42#include <sql_common.h>
43#include "errmsg.h"
44#include <mysql/client_plugin.h>
45
46struct st_client_plugin_int {
47 struct st_client_plugin_int *next;
48 void *dlhandle;
49 struct st_mysql_client_plugin *plugin;
50};
51
52static my_bool initialized= 0;
53static MEM_ROOT mem_root;
54
55#define plugin_declarations_sym "_mysql_client_plugin_declaration_"
56
57static uint plugin_version[MYSQL_CLIENT_MAX_PLUGINS]=
58{
59 0, /* these two are taken by Connector/C */
60 0, /* these two are taken by Connector/C */
61 MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION
62};
63
64/*
65 Loaded plugins are stored in a linked list.
66 The list is append-only, the elements are added to the head (like in a stack).
67 The elements are added under a mutex, but the list can be read and traversed
68 without any mutex because once an element is added to the list, it stays
69 there. The main purpose of a mutex is to prevent two threads from
70 loading the same plugin twice in parallel.
71*/
72struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS];
73static mysql_mutex_t LOCK_load_client_plugin;
74
75static int is_not_initialized(MYSQL *mysql, const char *name)
76{
77 DBUG_ENTER("is_not_initialized");
78
79 if (initialized)
80 DBUG_RETURN(0);
81
82 set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD,
83 unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD),
84 name, "not initialized");
85 DBUG_RETURN(1);
86}
87
88/**
89 finds a plugin in the list
90
91 @param name plugin name to search for
92 @param type plugin type
93
94 @note this does NOT necessarily need a mutex, take care!
95
96 @retval a pointer to a found plugin or 0
97*/
98static struct st_mysql_client_plugin *
99find_plugin(const char *name, int type)
100{
101 struct st_client_plugin_int *p;
102 DBUG_ENTER("find_plugin");
103
104 DBUG_ASSERT(initialized);
105 DBUG_ASSERT(type >= 0 && type < MYSQL_CLIENT_MAX_PLUGINS);
106 if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS)
107 DBUG_RETURN(0);
108
109 for (p= plugin_list[type]; p; p= p->next)
110 {
111 if (strcmp(p->plugin->name, name) == 0)
112 DBUG_RETURN(p->plugin);
113 }
114 DBUG_RETURN(NULL);
115}
116
117/**
118 verifies the plugin and adds it to the list
119
120 @param mysql MYSQL structure (for error reporting)
121 @param plugin plugin to install
122 @param dlhandle a handle to the shared object (returned by dlopen)
123 or 0 if the plugin was not dynamically loaded
124 @param argc number of arguments in the 'va_list args'
125 @param args arguments passed to the plugin initialization function
126
127 @retval a pointer to an installed plugin or 0
128*/
129static struct st_mysql_client_plugin *
130add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle,
131 int argc, va_list args)
132{
133 const char *errmsg;
134 struct st_client_plugin_int plugin_int, *p;
135 char errbuf[1024];
136 DBUG_ENTER("add_plugin");
137
138 DBUG_ASSERT(initialized);
139
140 plugin_int.plugin= plugin;
141 plugin_int.dlhandle= dlhandle;
142
143 if (plugin->type >= MYSQL_CLIENT_MAX_PLUGINS)
144 {
145 errmsg= "Unknown client plugin type";
146 goto err1;
147 }
148
149 if (plugin->interface_version < plugin_version[plugin->type] ||
150 (plugin->interface_version >> 8) >
151 (plugin_version[plugin->type] >> 8))
152 {
153 errmsg= "Incompatible client plugin interface";
154 goto err1;
155 }
156
157 /* Call the plugin initialization function, if any */
158 if (plugin->init && plugin->init(errbuf, sizeof(errbuf), argc, args))
159 {
160 errmsg= errbuf;
161 goto err1;
162 }
163
164 p= (struct st_client_plugin_int *)
165 memdup_root(&mem_root, &plugin_int, sizeof(plugin_int));
166
167 if (!p)
168 {
169 errmsg= "Out of memory";
170 goto err2;
171 }
172
173 mysql_mutex_assert_owner(&LOCK_load_client_plugin);
174
175 p->next= plugin_list[plugin->type];
176 plugin_list[plugin->type]= p;
177 net_clear_error(&mysql->net);
178
179 DBUG_RETURN(plugin);
180
181err2:
182 if (plugin->deinit)
183 plugin->deinit();
184err1:
185 set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
186 ER(CR_AUTH_PLUGIN_CANNOT_LOAD), plugin->name,
187 errmsg);
188 if (dlhandle)
189 (void)dlclose(dlhandle);
190 DBUG_RETURN(NULL);
191}
192
193/**
194 Loads plugins which are specified in the environment variable
195 LIBMYSQL_PLUGINS.
196
197 Multiple plugins must be separated by semicolon. This function doesn't
198 return or log an error.
199
200 The function is be called by mysql_client_plugin_init
201
202 @todo
203 Support extended syntax, passing parameters to plugins, for example
204 LIBMYSQL_PLUGINS="plugin1(param1,param2);plugin2;..."
205 or
206 LIBMYSQL_PLUGINS="plugin1=int:param1,str:param2;plugin2;..."
207*/
208static void load_env_plugins(MYSQL *mysql)
209{
210 char *plugs, *free_env, *s= getenv("LIBMYSQL_PLUGINS");
211 DBUG_ENTER("load_env_plugins");
212
213 /* no plugins to load */
214 if (!s)
215 DBUG_VOID_RETURN;
216
217 free_env= plugs= my_strdup(s, MYF(MY_WME));
218
219 do {
220 if ((s= strchr(plugs, ';')))
221 *s= '\0';
222 mysql_load_plugin(mysql, plugs, -1, 0);
223 plugs= s + 1;
224 } while (s);
225
226 my_free(free_env);
227 DBUG_VOID_RETURN;
228}
229
230/********** extern functions to be used by libmysql *********************/
231
232/**
233 Initializes the client plugin layer.
234
235 This function must be called before any other client plugin function.
236
237 @retval 0 successful
238 @retval != 0 error occurred
239*/
240int mysql_client_plugin_init()
241{
242 MYSQL mysql;
243 struct st_mysql_client_plugin **builtin;
244 va_list unused;
245 DBUG_ENTER("mysql_client_plugin_init");
246 LINT_INIT_STRUCT(unused);
247
248 if (initialized)
249 DBUG_RETURN(0);
250
251 bzero(&mysql, sizeof(mysql)); /* dummy mysql for set_mysql_extended_error */
252
253 mysql_mutex_init(0, &LOCK_load_client_plugin, MY_MUTEX_INIT_SLOW);
254 init_alloc_root(&mem_root, "client_plugin", 128, 128, MYF(0));
255
256 bzero(&plugin_list, sizeof(plugin_list));
257
258 initialized= 1;
259
260 mysql_mutex_lock(&LOCK_load_client_plugin);
261
262 for (builtin= mysql_client_builtins; *builtin; builtin++)
263 add_plugin(&mysql, *builtin, 0, 0, unused);
264
265 mysql_mutex_unlock(&LOCK_load_client_plugin);
266
267 load_env_plugins(&mysql);
268
269 DBUG_RETURN(0);
270}
271
272/**
273 Deinitializes the client plugin layer.
274
275 Unloades all client plugins and frees any associated resources.
276*/
277void mysql_client_plugin_deinit()
278{
279 int i;
280 struct st_client_plugin_int *p;
281 DBUG_ENTER("mysql_client_plugin_deinit");
282
283 if (!initialized)
284 DBUG_VOID_RETURN;
285
286 for (i=0; i < MYSQL_CLIENT_MAX_PLUGINS; i++)
287 for (p= plugin_list[i]; p; p= p->next)
288 {
289 if (p->plugin->deinit)
290 p->plugin->deinit();
291 if (p->dlhandle)
292 (void)dlclose(p->dlhandle);
293 }
294
295 bzero(&plugin_list, sizeof(plugin_list));
296 initialized= 0;
297 free_root(&mem_root, MYF(0));
298 mysql_mutex_destroy(&LOCK_load_client_plugin);
299 DBUG_VOID_RETURN;
300}
301
302/************* public facing functions, for client consumption *********/
303
304/* see <mysql/client_plugin.h> for a full description */
305struct st_mysql_client_plugin *
306mysql_client_register_plugin(MYSQL *mysql,
307 struct st_mysql_client_plugin *plugin)
308{
309 va_list unused;
310 DBUG_ENTER("mysql_client_register_plugin");
311 LINT_INIT_STRUCT(unused);
312
313 if (is_not_initialized(mysql, plugin->name))
314 DBUG_RETURN(NULL);
315
316 mysql_mutex_lock(&LOCK_load_client_plugin);
317
318 /* make sure the plugin wasn't loaded meanwhile */
319 if (find_plugin(plugin->name, plugin->type))
320 {
321 set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD,
322 unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD),
323 plugin->name, "it is already loaded");
324 plugin= NULL;
325 }
326 else
327 plugin= add_plugin(mysql, plugin, 0, 0, unused);
328
329 mysql_mutex_unlock(&LOCK_load_client_plugin);
330 DBUG_RETURN(plugin);
331}
332
333/* see <mysql/client_plugin.h> for a full description */
334struct st_mysql_client_plugin *
335mysql_load_plugin_v(MYSQL *mysql, const char *name, int type,
336 int argc, va_list args)
337{
338 const char *errmsg;
339 char dlpath[FN_REFLEN+1];
340 void *sym, *dlhandle;
341 struct st_mysql_client_plugin *plugin;
342 DBUG_ENTER("mysql_load_plugin_v");
343
344 DBUG_PRINT ("entry", ("name=%s type=%d int argc=%d", name, type, argc));
345 if (is_not_initialized(mysql, name))
346 {
347 DBUG_PRINT ("leave", ("mysql not initialized"));
348 DBUG_RETURN (NULL);
349 }
350
351 mysql_mutex_lock(&LOCK_load_client_plugin);
352
353 /* make sure the plugin wasn't loaded meanwhile */
354 if (type >= 0 && find_plugin(name, type))
355 {
356 errmsg= "it is already loaded";
357 goto err;
358 }
359
360 /* Compile dll path */
361 strxnmov(dlpath, sizeof(dlpath) - 1,
362 mysql->options.extension && mysql->options.extension->plugin_dir ?
363 mysql->options.extension->plugin_dir : PLUGINDIR, "/",
364 name, SO_EXT, NullS);
365
366 DBUG_PRINT ("info", ("dlopeninig %s", dlpath));
367 /* Open new dll handle */
368 if (!(dlhandle= dlopen(dlpath, RTLD_NOW)))
369 {
370 DBUG_PRINT ("info", ("failed to dlopen"));
371 errmsg= dlerror();
372 goto err;
373 }
374
375 if (!(sym= dlsym(dlhandle, plugin_declarations_sym)))
376 {
377 errmsg= "not a plugin";
378 goto errc;
379 }
380
381 plugin= (struct st_mysql_client_plugin*)sym;
382
383 if (type >=0 && type != plugin->type)
384 {
385 errmsg= "type mismatch";
386 goto errc;
387 }
388
389 if (strcmp(name, plugin->name))
390 {
391 errmsg= "name mismatch";
392 goto errc;
393 }
394
395 if (type < 0 && find_plugin(name, plugin->type))
396 {
397 errmsg= "it is already loaded";
398 goto errc;
399 }
400
401 plugin= add_plugin(mysql, plugin, dlhandle, argc, args);
402
403 mysql_mutex_unlock(&LOCK_load_client_plugin);
404
405 DBUG_PRINT ("leave", ("plugin loaded ok"));
406 DBUG_RETURN (plugin);
407
408errc:
409 dlclose(dlhandle);
410err:
411 mysql_mutex_unlock(&LOCK_load_client_plugin);
412 DBUG_PRINT ("leave", ("plugin load error : %s", errmsg));
413 set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
414 ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, errmsg);
415 DBUG_RETURN (NULL);
416}
417
418/* see <mysql/client_plugin.h> for a full description */
419struct st_mysql_client_plugin *
420mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...)
421{
422 struct st_mysql_client_plugin *p;
423 va_list args;
424 DBUG_ENTER("mysql_load_plugin");
425
426 va_start(args, argc);
427 p= mysql_load_plugin_v(mysql, name, type, argc, args);
428 va_end(args);
429 DBUG_RETURN(p);
430}
431
432/* see <mysql/client_plugin.h> for a full description */
433struct st_mysql_client_plugin *
434mysql_client_find_plugin(MYSQL *mysql, const char *name, int type)
435{
436 struct st_mysql_client_plugin *p;
437 DBUG_ENTER("mysql_client_find_plugin");
438
439 DBUG_PRINT ("entry", ("name=%s, type=%d", name, type));
440 if (is_not_initialized(mysql, name))
441 DBUG_RETURN (NULL);
442
443 if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS)
444 {
445 set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate,
446 ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name,
447 "invalid type");
448 }
449
450 if ((p= find_plugin(name, type)))
451 {
452 DBUG_PRINT ("leave", ("found %p", p));
453 DBUG_RETURN (p);
454 }
455
456 /* not found, load it */
457 p= mysql_load_plugin(mysql, name, type, 0);
458 DBUG_PRINT ("leave", ("loaded %p", p));
459 DBUG_RETURN (p);
460}
461
462
463/* see <mysql/client_plugin.h> for a full description */
464int mysql_plugin_options(struct st_mysql_client_plugin *plugin,
465 const char *option,
466 const void *value)
467{
468 DBUG_ENTER("mysql_plugin_options");
469 /* does the plugin support options call? */
470 if (!plugin || !plugin->options)
471 DBUG_RETURN(1);
472 DBUG_RETURN(plugin->options(option, value));
473}
474