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