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 | struct st_client_plugin_int { |
48 | struct st_client_plugin_int *next; |
49 | void *dlhandle; |
50 | struct st_mysql_client_plugin *plugin; |
51 | }; |
52 | |
53 | static my_bool initialized= 0; |
54 | static MA_MEM_ROOT mem_root; |
55 | |
56 | static 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 | |
75 | struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS + MARIADB_CLIENT_MAX_PLUGINS]; |
76 | #ifdef THREAD |
77 | static 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 | |
85 | struct 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 | |
95 | static 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 | |
106 | static 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 | |
115 | static 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 | */ |
133 | static 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 | |
167 | static struct st_mysql_client_plugin * |
168 | add_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 | |
214 | err2: |
215 | if (plugin->deinit) |
216 | plugin->deinit(); |
217 | err1: |
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 | |
242 | static 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 | |
273 | int 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 | |
310 | void 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 */ |
336 | struct st_mysql_client_plugin * STDCALL |
337 | mysql_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 */ |
365 | struct st_mysql_client_plugin * STDCALL |
366 | mysql_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 | |
456 | err: |
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 */ |
465 | struct st_mysql_client_plugin * STDCALL |
466 | mysql_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 */ |
477 | struct st_mysql_client_plugin * STDCALL |
478 | mysql_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 | |