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 | |
51 | struct st_client_plugin_int { |
52 | struct st_client_plugin_int *next; |
53 | void *dlhandle; |
54 | struct st_mysql_client_plugin *plugin; |
55 | }; |
56 | |
57 | static my_bool initialized= 0; |
58 | static MA_MEM_ROOT mem_root; |
59 | |
60 | static 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 | |
79 | struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS + MARIADB_CLIENT_MAX_PLUGINS]; |
80 | #ifdef THREAD |
81 | static 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 | |
91 | struct 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 | |
103 | static 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 | |
114 | static 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 | |
123 | static 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 | */ |
141 | static 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 | |
175 | static struct st_mysql_client_plugin * |
176 | add_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 | |
219 | err2: |
220 | if (plugin->deinit) |
221 | plugin->deinit(); |
222 | err1: |
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 | |
247 | static 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 | |
278 | int 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 | |
315 | void 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 */ |
341 | struct st_mysql_client_plugin * STDCALL |
342 | mysql_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 */ |
370 | struct st_mysql_client_plugin * STDCALL |
371 | mysql_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 | |
468 | err: |
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 */ |
479 | struct st_mysql_client_plugin * STDCALL |
480 | mysql_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 */ |
491 | struct st_mysql_client_plugin * STDCALL |
492 | mysql_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 | |