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