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