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