1 /* Copyright (c) 2015, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #define MYSQL_SERVER
24 #include <my_global.h>
25 #include <mysql/plugin_audit.h>
26 #include <m_string.h>
27 #include <sql_class.h>
28 #include <hash.h>
29 #include <sstream>
30 #include <errmsg.h>
31 #include <mysql/service_locking.h>
32 #include <locking_service.h>
33
34 #ifdef WIN32
35 #define PLUGIN_EXPORT extern "C" __declspec(dllexport)
36 #else
37 #define PLUGIN_EXPORT extern "C"
38 #endif
39
40 // This global value is initiated with 1 and the corresponding session
41 // value with 0. Hence Every session must compare its tokens with the
42 // global values when it runs its very first query.
43 static volatile int64 session_number= 1;
44 static size_t vtoken_string_length;
45
46 // This flag is for memory management when the variable
47 // is updated for the first time.
48 struct version_token_st {
49 LEX_STRING token_name;
50 LEX_STRING token_val;
51 };
52
53
54 #define VTOKEN_LOCKS_NAMESPACE "version_token_locks"
55
56 #define LONG_TIMEOUT ((ulong) 3600L*24L*365L)
57
58 static HASH version_tokens_hash;
59
60 /**
61 Utility class implementing an atomic boolean on top of an int32
62
63 The mysys lib does not support atomic booleans.
64 */
65 class atomic_boolean
66 {
67 /** constants for true and false */
68 static const int m_true, m_false;
69 /** storage for the boolean's current value */
70 volatile int32 m_value;
71 public:
72
73 /**
74 Constructs a new atomic_boolean.
75
76 @param value The value to initialize the boolean with.
77 */
atomic_boolean(bool value=false)78 atomic_boolean(bool value= false) : m_value(value ? m_true : m_false)
79 {}
80
81 /**
82 Checks if the atomic boolean has a certain value
83
84 if used without an argument checks if the atomic boolean is on.
85
86 @param value the value to check for
87 @retval true the atomic boolean value matches the argument value
88 @retval false the atomic boolean value is different from the argument value
89 */
is_set(bool value=true)90 bool is_set(bool value= true)
91 {
92 int32 cmp= value ? m_true : m_false, actual_value;
93
94 actual_value= my_atomic_load32(&m_value);
95
96 return actual_value == cmp;
97 }
98
99 /**
100 Sets a new value for the atomic boolean
101
102 @param new value
103 */
set(bool new_value)104 void set(bool new_value)
105 {
106 int32 new_val= new_value ? m_true : m_false;
107 my_atomic_store32(&m_value, new_val);
108 }
109 };
110
111 const int atomic_boolean::m_true= 0;
112 const int atomic_boolean::m_false= 1;
113
114
115 /**
116 State of the version tokens hash global structure
117
118 Needed since both the UDFs and the plugin are using the global
119 and thus it can't be freed until the last UDF or plugin has been unloaded.
120 */
121 static atomic_boolean version_tokens_hash_inited;
122
123 static MYSQL_THDVAR_ULONG(session_number,
124 PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
125 "Version number to assist with session tokens check",
126 NULL, NULL, 0L, 0, ((ulong) -1), 0);
127
128
update_session_version_tokens(MYSQL_THD thd,struct st_mysql_sys_var * var,void * var_ptr,const void * save)129 static void update_session_version_tokens(MYSQL_THD thd,
130 struct st_mysql_sys_var *var,
131 void *var_ptr, const void *save)
132 {
133 THDVAR(thd, session_number)= 0;
134 *(char **) var_ptr= *(char **) save;
135 }
136
137
138
139 static MYSQL_THDVAR_STR(session,
140 PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
141 "Holds the session value for version tokens",
142 NULL, update_session_version_tokens, NULL);
143
144
145 // Lock to be used for global variable hash.
146 mysql_rwlock_t LOCK_vtoken_hash;
147
148 PSI_memory_key key_memory_vtoken;
149
150 #ifdef HAVE_PSI_INTERFACE
151 PSI_rwlock_key key_LOCK_vtoken_hash;
152
153 static PSI_rwlock_info all_vtoken_rwlocks[]=
154 {
155 {&key_LOCK_vtoken_hash, "LOCK_vtoken_hash", 0},
156 };
157
158 static PSI_memory_info all_vtoken_memory[]=
159 {
160 {&key_memory_vtoken, "vtoken", 0}
161 };
162
163 // Function to register the lock
vtoken_init_psi_keys(void)164 void vtoken_init_psi_keys(void)
165 {
166 const char* category= "vtoken";
167 int count;
168
169 if (PSI_server == NULL)
170 return;
171
172 count= array_elements(all_vtoken_rwlocks);
173 PSI_server->register_rwlock(category, all_vtoken_rwlocks, count);
174
175 count= array_elements(all_vtoken_memory);
176 PSI_server->register_memory(category, all_vtoken_memory, count);
177 }
178
179 #endif /* HAVE_PSI_INTERFACE */
180
is_blank_string(char * input)181 static bool is_blank_string(char *input)
182 {
183 LEX_STRING input_lex;
184 input_lex.str= input;
185 input_lex.length= strlen(input);
186
187 trim_whitespace(&my_charset_bin, &input_lex);
188
189 if (input_lex.length == 0)
190 return true;
191 else
192 return false;
193 }
194
195
196 static uchar *version_token_get_key(const char *entry MY_ATTRIBUTE((unused)),
197 size_t *length MY_ATTRIBUTE((unused)),
198 my_bool not_used MY_ATTRIBUTE((unused)));
199
set_vtoken_string_length()200 static void set_vtoken_string_length()
201 {
202 version_token_st *token_obj;
203 int i= 0;
204 size_t str_size= 0;
205 while ((token_obj= (version_token_st *) my_hash_element(&version_tokens_hash, i)))
206 {
207 if ((token_obj->token_name).str)
208 str_size= str_size+ (token_obj->token_name).length;
209
210 if ((token_obj->token_val).str)
211 str_size= str_size+ (token_obj->token_val).length;
212
213 str_size+= 2;
214
215 i++;
216 }
217 vtoken_string_length= str_size;
218 }
219
220
221 // UDF
222 PLUGIN_EXPORT my_bool version_tokens_set_init(UDF_INIT *initid, UDF_ARGS *args,
223 char *message);
224 PLUGIN_EXPORT char *version_tokens_set(UDF_INIT *initid, UDF_ARGS *args,
225 char *result, unsigned long *length,
226 char *null_value, char *error);
227
228 PLUGIN_EXPORT my_bool version_tokens_show_init(UDF_INIT *initid, UDF_ARGS *args,
229 char *message);
230 PLUGIN_EXPORT void version_tokens_show_deinit(UDF_INIT *initid);
231 PLUGIN_EXPORT char *version_tokens_show(UDF_INIT *initid, UDF_ARGS *args,
232 char *result, unsigned long *length,
233 char *null_value, char *error);
234
235 PLUGIN_EXPORT my_bool version_tokens_edit_init(UDF_INIT *initid, UDF_ARGS *args,
236 char *message);
237 PLUGIN_EXPORT char *version_tokens_edit(UDF_INIT *initid, UDF_ARGS *args,
238 char *result, unsigned long *length,
239 char *null_value, char *error);
240
241 PLUGIN_EXPORT my_bool version_tokens_delete_init(UDF_INIT *initid,
242 UDF_ARGS *args, char *message);
243 PLUGIN_EXPORT char *version_tokens_delete(UDF_INIT *initid, UDF_ARGS *args,
244 char *result, unsigned long *length,
245 char *null_value, char *error);
246 PLUGIN_EXPORT my_bool version_tokens_lock_shared_init(
247 UDF_INIT *initid, UDF_ARGS *args, char *message);
248 PLUGIN_EXPORT long long version_tokens_lock_shared(
249 UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);
250 PLUGIN_EXPORT my_bool version_tokens_lock_exclusive_init(
251 UDF_INIT *initid, UDF_ARGS *args, char *message);
252 PLUGIN_EXPORT long long version_tokens_lock_exclusive(
253 UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);
254 PLUGIN_EXPORT my_bool version_tokens_unlock_init(
255 UDF_INIT *initid, UDF_ARGS *args, char *message);
256 PLUGIN_EXPORT long long version_tokens_unlock(
257 UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);
258
259 enum command {
260 SET_VTOKEN= 0,
261 EDIT_VTOKEN,
262 CHECK_VTOKEN
263 };
264
265 /**
266 @brief Parses the list of version tokens and either updates the global
267 list with the input or checks the input against the global according
268 to which function the caller is.
269
270 @param input [IN] List of semicolon separated token name/value pairs
271 @param enum command [IN] Helps determining the caller function.
272
273 @return (error) -1 in case of error.
274 @return (success) Number of tokens updated/set on EDIT_VTOKEN and SET_VTOKEN.
275 0 in case of CHECK_VTOKEN.
276
277 TODO: Add calls to get_lock services in CHECK_VTOKEN.
278 */
parse_vtokens(char * input,enum command type)279 static int parse_vtokens(char *input, enum command type)
280 {
281 char *token, *lasts_token= NULL;
282 const char *separator= ";";
283 int result= 0;
284 THD *thd= current_thd;
285 ulonglong thd_session_number= THDVAR(thd, session_number);
286 ulonglong tmp_token_number= (ulonglong)
287 my_atomic_load64((volatile int64 *) &session_number);
288
289 bool vtokens_unchanged= (thd_session_number == tmp_token_number);
290
291 token= my_strtok_r(input, separator, &lasts_token);
292
293 while (token)
294 {
295 const char *equal= "=";
296 char *lasts_val= NULL;
297 LEX_STRING token_name, token_val;
298
299 if (is_blank_string(token))
300 {
301 token= my_strtok_r(NULL, separator, &lasts_token);
302 continue;
303 }
304
305 token_name.str= my_strtok_r(token, equal, &lasts_val);
306 token_val.str= lasts_val;
307
308 token_name.length= token_name.str ? strlen(token_name.str) : 0;
309 token_val.length= lasts_val ? strlen(lasts_val):0;
310 trim_whitespace(&my_charset_bin, &token_name);
311 trim_whitespace(&my_charset_bin, &token_val);
312
313 if ((token_name.length == 0) || (token_val.length == 0))
314 {
315 switch (type)
316 {
317 case CHECK_VTOKEN:
318 {
319 if (!thd->get_stmt_da()->is_set())
320 thd->get_stmt_da()->set_error_status(ER_ACCESS_DENIED_ERROR,
321 "Empty version token "
322 "name/value encountered",
323 "42000");
324 return -1;
325 }
326 case SET_VTOKEN:
327 case EDIT_VTOKEN:
328 {
329 push_warning(thd, Sql_condition::SL_WARNING, 42000,
330 "Invalid version token pair encountered. The list "
331 "provided is only partially updated.");
332 }
333 }
334 return result;
335 }
336
337 if (token_name.length > 64)
338 {
339 switch (type)
340 {
341 case CHECK_VTOKEN:
342 {
343 if (!thd->get_stmt_da()->is_set())
344 thd->get_stmt_da()->set_error_status(ER_ACCESS_DENIED_ERROR,
345 "Lengthy version token name "
346 "encountered. Maximum length "
347 "allowed for a token name is "
348 "64 characters.",
349 "42000");
350 return -1;
351 }
352 case SET_VTOKEN:
353 case EDIT_VTOKEN:
354 {
355 push_warning(thd, Sql_condition::SL_WARNING, 42000,
356 "Lengthy version token name encountered. Maximum length "
357 "allowed for a token name is 64 characters. The list "
358 "provided is only partially updated.");
359 }
360 }
361 return result;
362 }
363
364 switch (type) {
365
366 case SET_VTOKEN:
367 case EDIT_VTOKEN:
368 {
369 char *name= NULL, *val= NULL;
370 size_t name_len, val_len;
371
372 name_len= token_name.length;
373 val_len= token_val.length;
374 version_token_st *v_token= NULL;
375
376 if (!my_multi_malloc(key_memory_vtoken, MYF(0),
377 &v_token, sizeof(version_token_st), &name, name_len,
378 &val, val_len, NULL))
379 {
380 push_warning(thd, Sql_condition::SL_WARNING, CR_OUT_OF_MEMORY,
381 "Not enough memory available");
382 return result;
383 }
384
385 memcpy(name, token_name.str, name_len);
386 memcpy(val, token_val.str, val_len);
387 v_token->token_name.str= name;
388 v_token->token_val.str= val;
389 v_token->token_name.length= name_len;
390 v_token->token_val.length= val_len;
391
392 if (my_hash_insert(&version_tokens_hash, (uchar *) v_token))
393 {
394 version_token_st *tmp= (version_token_st *)
395 my_hash_search(&version_tokens_hash,
396 (uchar *) name, name_len);
397
398 if (tmp)
399 {
400 my_hash_delete(&version_tokens_hash, (uchar *) tmp);
401 }
402 my_hash_insert(&version_tokens_hash, (uchar *) v_token);
403 }
404 result++;
405 }
406 break;
407
408 case CHECK_VTOKEN:
409 {
410 version_token_st *token_obj;
411 char error_str[MYSQL_ERRMSG_SIZE];
412 if (!mysql_acquire_locking_service_locks(thd, VTOKEN_LOCKS_NAMESPACE,
413 (const char **) &(token_name.str), 1,
414 LOCKING_SERVICE_READ, LONG_TIMEOUT) &&
415 !vtokens_unchanged)
416 {
417 if ((token_obj= (version_token_st *)my_hash_search(&version_tokens_hash,
418 (const uchar*) token_name.str,
419 token_name.length)))
420 {
421 if ((token_obj->token_val.length != token_val.length) ||
422 (memcmp(token_obj->token_val.str, token_val.str, token_val.length) != 0))
423 {
424
425 if (!thd->get_stmt_da()->is_set())
426 {
427 my_snprintf(error_str, sizeof(error_str),
428 ER(ER_VTOKEN_PLUGIN_TOKEN_MISMATCH),
429 (int) token_name.length, token_name.str,
430 (int) token_obj->token_val.length,
431 token_obj->token_val.str);
432
433 thd->get_stmt_da()->set_error_status(ER_VTOKEN_PLUGIN_TOKEN_MISMATCH,
434 (const char *) error_str,
435 "42000");
436 }
437 return -1;
438 }
439 }
440 else
441 {
442 if (!thd->get_stmt_da()->is_set())
443 {
444 my_snprintf(error_str, sizeof(error_str),
445 ER(ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND),
446 (int) token_name.length, token_name.str);
447
448 thd->get_stmt_da()->set_error_status(ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND,
449 (const char *) error_str,
450 "42000");
451 }
452 return -1;
453 }
454 }
455 }
456 }
457 token= my_strtok_r(NULL, separator, &lasts_token);
458 }
459
460 if (type == CHECK_VTOKEN)
461 {
462 THDVAR(thd, session_number)= (long) tmp_token_number;
463 }
464
465 return result;
466 }
467
468
469 /**
470 Audit API entry point for the version token pluign
471
472 Plugin audit function to compare session version tokens with
473 the global ones.
474 At the start of each query (MYSQL_AUDIT_GENERAL_LOG
475 currently) if there's a session version token vector it will
476 acquire the GET_LOCK shared locks for the session version tokens
477 and then will try to find them in the global version lock and
478 compare their values with the ones found. Throws errors if not
479 found or the version values do not match. See parse_vtokens().
480 At query end (MYSQL_AUDIT_GENERAL_STATUS currently) it releases
481 the GET_LOCK shared locks it has aquired.
482
483 @param thd The current thread
484 @param event_class audit API event class
485 @param event pointer to the audit API event data
486 */
version_token_check(MYSQL_THD thd,mysql_event_class_t event_class,const void * event)487 static int version_token_check(MYSQL_THD thd,
488 mysql_event_class_t event_class,
489 const void *event)
490 {
491 char *sess_var;
492
493 const struct mysql_event_general *event_general=
494 (const struct mysql_event_general *) event;
495 const uchar *command= (const uchar *) event_general->general_command.str;
496 size_t length= event_general->general_command.length;
497
498 assert(event_class == MYSQL_AUDIT_GENERAL_CLASS);
499
500 switch (event_general->event_subclass)
501 {
502 case MYSQL_AUDIT_GENERAL_LOG:
503 {
504 /* Ignore all commands but COM_QUERY and COM_STMT_PREPARE */
505 if (0 != my_charset_latin1.coll->strnncoll(&my_charset_latin1,
506 command, length,
507 (const uchar *) STRING_WITH_LEN("Query"),
508 0) &&
509 0 != my_charset_latin1.coll->strnncoll(&my_charset_latin1,
510 command, length,
511 (const uchar *) STRING_WITH_LEN("Prepare"),
512 0))
513 return 0;
514
515
516 if (THDVAR(thd, session))
517 sess_var= my_strndup(key_memory_vtoken,
518 THDVAR(thd, session),
519 strlen(THDVAR(thd, session)),
520 MYF(MY_FAE));
521 else
522 return 0;
523
524 // Lock the hash before checking for values.
525 mysql_rwlock_rdlock(&LOCK_vtoken_hash);
526
527 parse_vtokens(sess_var, CHECK_VTOKEN);
528
529 // Unlock hash
530 mysql_rwlock_unlock(&LOCK_vtoken_hash);
531 my_free(sess_var);
532 break;
533 }
534 case MYSQL_AUDIT_GENERAL_STATUS:
535 {
536 /*
537 Release locks only if the session variable is set.
538
539 This relies on the fact that MYSQL_AUDIT_GENERAL_STATUS
540 is always generated at the end of query execution.
541 */
542 if (THDVAR(thd, session))
543 mysql_release_locking_service_locks(NULL, VTOKEN_LOCKS_NAMESPACE);
544 break;
545 }
546 default:
547 break;
548 }
549
550 return 0;
551 }
552
553
554 /**
555 Helper class to dispose of the rwlocks at DLL/so unload.
556
557 We can't release the rwlock at plugin or UDF unload since we're using it
558 to synchronize both.
559
560 So we need to rely on the shared object deinitialization function to
561 dispose of lock up.
562
563 For that we declare a helper class with a destructor that disposes of the
564 global object and declare one global variable @ref cleanup_lock of that
565 class and expect the C library to call the destructor when unloading
566 the DLL/so.
567 */
568 class vtoken_lock_cleanup
569 {
570 atomic_boolean activated;
571 public:
vtoken_lock_cleanup()572 vtoken_lock_cleanup()
573 {};
~vtoken_lock_cleanup()574 ~vtoken_lock_cleanup()
575 {
576 if (activated.is_set())
577 mysql_rwlock_destroy(&LOCK_vtoken_hash);
578 }
activate()579 void activate()
580 {
581 activated.set(true);
582 }
583
is_active()584 bool is_active()
585 {
586 return activated.is_set();
587 }
588 };
589
590 /**
591 A single global variable to invoke the destructor.
592 See @ref vtoken_lock_cleanup.
593 */
594 static vtoken_lock_cleanup cleanup_lock;
595
596
597
598 static struct st_mysql_audit version_token_descriptor=
599 {
600 MYSQL_AUDIT_INTERFACE_VERSION, /* interface version */
601 NULL, /* release_thd() */
602 version_token_check, /* event_notify() */
603 { (unsigned long) MYSQL_AUDIT_GENERAL_ALL, } /* class mask */
604 };
605
606 /** Plugin init. */
version_tokens_init(void * arg MY_ATTRIBUTE ((unused)))607 static int version_tokens_init(void *arg MY_ATTRIBUTE((unused)))
608 {
609 #ifdef HAVE_PSI_INTERFACE
610 // Initialize psi keys.
611 vtoken_init_psi_keys();
612 #endif /* HAVE_PSI_INTERFACE */
613
614 // Initialize hash.
615 my_hash_init(&version_tokens_hash,
616 &my_charset_bin,
617 4, 0, 0, (my_hash_get_key) version_token_get_key,
618 my_free, HASH_UNIQUE,
619 key_memory_vtoken);
620 version_tokens_hash_inited.set(true);
621
622 if (!cleanup_lock.is_active())
623 {
624 // Lock for hash.
625 mysql_rwlock_init(key_LOCK_vtoken_hash, &LOCK_vtoken_hash);
626 // Lock for version number.
627 cleanup_lock.activate();
628 }
629 return 0;
630 }
631
632 /** Plugin deinit. */
version_tokens_deinit(void * arg MY_ATTRIBUTE ((unused)))633 static int version_tokens_deinit(void *arg MY_ATTRIBUTE((unused)))
634 {
635 mysql_rwlock_wrlock(&LOCK_vtoken_hash);
636 if (version_tokens_hash.records)
637 my_hash_reset(&version_tokens_hash);
638
639 my_hash_free(&version_tokens_hash);
640 version_tokens_hash_inited.set(false);
641 mysql_rwlock_unlock(&LOCK_vtoken_hash);
642
643 return 0;
644 }
645
646 static struct st_mysql_sys_var* system_variables[]={
647 MYSQL_SYSVAR(session_number),
648 MYSQL_SYSVAR(session),
649 NULL
650 };
651
652
653 // Declare plugin
mysql_declare_plugin(version_tokens)654 mysql_declare_plugin(version_tokens)
655 {
656 MYSQL_AUDIT_PLUGIN, /* type */
657 &version_token_descriptor, /* descriptor */
658 "version_tokens", /* name */
659 "Oracle Corp", /* author */
660 "version token check", /* description */
661 PLUGIN_LICENSE_GPL,
662 version_tokens_init, /* init function (when loaded) */
663 version_tokens_deinit, /* deinit function (when unloaded) */
664 0x0101, /* version */
665 NULL, /* status variables */
666 system_variables, /* system variables */
667 NULL,
668 0
669 }
670 mysql_declare_plugin_end;
671
672
673 /**
674 A function to check if the hash is inited and generate an error.
675
676 To be called while holding LOCK_vtoken_hash
677
678 @param function the UDF function name for the error message
679 @param error the UDF error pointer to set
680 @retval false hash not initialized. Error set. Bail out.
681 @retval true All good. Go on.
682 */
683
is_hash_inited(const char * function,char * error)684 static bool is_hash_inited(const char *function, char *error)
685 {
686
687 if (!version_tokens_hash_inited.is_set())
688 {
689 my_error(ER_CANT_INITIALIZE_UDF, MYF(0), function,
690 "version_token plugin is not installed.");
691 *error= 1;
692 return false;
693 }
694 return true;
695 }
696
697
698
699 /*
700 Below is the UDF for setting global list of version tokens.
701 Input must be provided as semicolon separated tokens.
702
703 Function signature:
704
705 VERSION_TOKENS_SET(tokens_list varchar)
706 */
707
version_tokens_set_init(UDF_INIT * initid,UDF_ARGS * args,char * message)708 PLUGIN_EXPORT my_bool version_tokens_set_init(UDF_INIT* initid, UDF_ARGS* args,
709 char* message)
710 {
711 THD *thd= current_thd;
712
713 if (!(thd->security_context()->check_access(SUPER_ACL)))
714 {
715 my_stpcpy(message, "The user is not privileged to use this function.");
716 return true;
717 }
718
719 if (!version_tokens_hash_inited.is_set())
720 {
721 my_stpcpy(message, "version_token plugin is not installed.");
722 return true;
723 }
724
725 if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
726 {
727 my_stpcpy(message, "Wrong arguments provided for the function.");
728 return true;
729 }
730
731 return false;
732 }
733
version_tokens_set(UDF_INIT * initid,UDF_ARGS * args,char * result,unsigned long * length,char * null_value,char * error)734 PLUGIN_EXPORT char *version_tokens_set(UDF_INIT *initid, UDF_ARGS *args,
735 char *result, unsigned long *length,
736 char *null_value, char *error)
737 {
738 char *hash_str;
739 int len= args->lengths[0];
740 int vtokens_count= 0;
741 std::stringstream ss;
742
743 mysql_rwlock_wrlock(&LOCK_vtoken_hash);
744 if (!is_hash_inited("version_tokens_set", error))
745 {
746 mysql_rwlock_unlock(&LOCK_vtoken_hash);
747 return NULL;
748 }
749 if (len > 0)
750 {
751 // Separate copy for values to be inserted in hash.
752 hash_str= (char *)my_malloc(key_memory_vtoken,
753 len+1, MYF(MY_WME));
754
755 if (!hash_str)
756 {
757 *error= 1;
758 mysql_rwlock_unlock(&LOCK_vtoken_hash);
759 return NULL;
760 }
761 memcpy(hash_str, args->args[0], len);
762 hash_str[len]= 0;
763
764 // Hash built with its own copy of string.
765 if (version_tokens_hash.records)
766 my_hash_reset(&version_tokens_hash);
767 vtokens_count= parse_vtokens(hash_str, SET_VTOKEN);
768 ss << vtokens_count << " version tokens set.";
769
770 my_free(hash_str);
771
772 }
773 else
774 {
775 if (version_tokens_hash.records)
776 my_hash_reset(&version_tokens_hash);
777 ss << "Version tokens list cleared.";
778 }
779 set_vtoken_string_length();
780
781 my_atomic_add64((volatile int64 *) &session_number, 1);
782
783 mysql_rwlock_unlock(&LOCK_vtoken_hash);
784
785 ss.getline(result, MAX_FIELD_WIDTH, '\0');
786 *length= (unsigned long) ss.gcount();
787
788 return result;
789 }
790
791
792 /*
793 Below is the UDF for updating global version tokens.
794 Tokens to be updated must be provided as semicolon
795 separated tokens.
796
797 Function signature:
798
799 VERSION_TOKENS_EDIT(tokens_list varchar)
800 */
801
version_tokens_edit_init(UDF_INIT * initid,UDF_ARGS * args,char * message)802 PLUGIN_EXPORT my_bool version_tokens_edit_init(UDF_INIT *initid, UDF_ARGS *args,
803 char *message)
804 {
805 THD *thd= current_thd;
806
807 if (!version_tokens_hash_inited.is_set())
808 {
809 my_stpcpy(message, "version_token plugin is not installed.");
810 return true;
811 }
812
813 if (!(thd->security_context()->check_access(SUPER_ACL)))
814 {
815 my_stpcpy(message, "The user is not privileged to use this function.");
816 return true;
817 }
818
819 if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
820 {
821 my_stpcpy(message, "Wrong arguments provided for the function.");
822 return true;
823 }
824
825 return false;
826 }
827
version_tokens_edit(UDF_INIT * initid,UDF_ARGS * args,char * result,unsigned long * length,char * null_value,char * error)828 PLUGIN_EXPORT char *version_tokens_edit(UDF_INIT *initid, UDF_ARGS *args,
829 char *result, unsigned long *length,
830 char *null_value, char *error)
831 {
832 char *hash_str;
833 int len= args->lengths[0];
834 std::stringstream ss;
835 int vtokens_count= 0;
836
837 if (len > 0)
838 {
839 // Separate copy for values to be inserted in hash.
840 hash_str= (char *)my_malloc(key_memory_vtoken,
841 len+1, MYF(MY_WME));
842
843 if (!hash_str)
844 {
845 *error= 1;
846 return NULL;
847 }
848 memcpy(hash_str, args->args[0], len);
849 hash_str[len]= 0;
850
851 // Hash built with its own copy of string.
852 mysql_rwlock_wrlock(&LOCK_vtoken_hash);
853 if (!is_hash_inited("version_tokens_edit", error))
854 {
855 mysql_rwlock_unlock(&LOCK_vtoken_hash);
856 return NULL;
857 }
858
859 vtokens_count= parse_vtokens(hash_str, EDIT_VTOKEN);
860
861 set_vtoken_string_length();
862
863 if (vtokens_count)
864 my_atomic_add64((volatile int64 *) &session_number, 1);
865
866 mysql_rwlock_unlock(&LOCK_vtoken_hash);
867 my_free(hash_str);
868 }
869 ss << vtokens_count << " version tokens updated.";
870 ss.getline(result, MAX_FIELD_WIDTH, '\0');
871 *length= (unsigned long) ss.gcount();
872
873 return result;
874 }
875
876
877 /*
878 Below is the UDF for deleting selected global version tokens.
879 Names of tokens to be deleted must be provided in a semicolon separated list.
880
881 Function signature:
882
883 VERSION_TOKENS_DELETE(tokens_list varchar)
884 */
885
version_tokens_delete_init(UDF_INIT * initid,UDF_ARGS * args,char * message)886 PLUGIN_EXPORT my_bool version_tokens_delete_init(UDF_INIT *initid,
887 UDF_ARGS *args, char *message)
888 {
889 THD *thd= current_thd;
890
891 if (!version_tokens_hash_inited.is_set())
892 {
893 my_stpcpy(message, "version_token plugin is not installed.");
894 return true;
895 }
896
897 if (!(thd->security_context()->check_access(SUPER_ACL)))
898 {
899 my_stpcpy(message, "The user is not privileged to use this function.");
900 return true;
901 }
902
903 if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
904 {
905 my_stpcpy(message, "Wrong arguments provided for the function.");
906 return true;
907 }
908
909 return false;
910 }
911
version_tokens_delete(UDF_INIT * initid,UDF_ARGS * args,char * result,unsigned long * length,char * null_value,char * error)912 PLUGIN_EXPORT char *version_tokens_delete(UDF_INIT *initid, UDF_ARGS *args,
913 char *result, unsigned long *length,
914 char *null_value, char *error)
915 {
916 const char *arg= args->args[0];
917 std::stringstream ss;
918 int vtokens_count= 0;
919
920 if (args->lengths[0] > 0)
921 {
922 char *input;
923 const char *separator= ";";
924 char *token, *lasts_token= NULL;
925
926 if (NULL == (input= my_strdup(key_memory_vtoken, arg, MYF(MY_WME))))
927 {
928 *error= 1;
929 return NULL;
930 }
931
932 mysql_rwlock_wrlock(&LOCK_vtoken_hash);
933 if (!is_hash_inited("version_tokens_delete", error))
934 {
935 mysql_rwlock_unlock(&LOCK_vtoken_hash);
936 return NULL;
937 }
938
939 token= my_strtok_r(input, separator, &lasts_token);
940
941 while (token)
942 {
943 version_token_st *tmp;
944 LEX_STRING st={ token, strlen(token) };
945
946 trim_whitespace(&my_charset_bin, &st);
947
948 if (st.length)
949 {
950 tmp= (version_token_st *) my_hash_search(&version_tokens_hash,
951 (uchar *) st.str,
952 st.length);
953 if (tmp)
954 {
955 my_hash_delete(&version_tokens_hash, (uchar *) tmp);
956 vtokens_count++;
957 }
958 }
959
960 token= my_strtok_r(NULL, separator, &lasts_token);
961 }
962
963 set_vtoken_string_length();
964
965 if (vtokens_count)
966 {
967 my_atomic_add64((volatile int64 *) &session_number, 1);
968 }
969
970 mysql_rwlock_unlock(&LOCK_vtoken_hash);
971 my_free(input);
972 }
973
974 ss << vtokens_count << " version tokens deleted.";
975
976 ss.getline(result, MAX_FIELD_WIDTH, '\0');
977 *length= (unsigned long) ss.gcount();
978 return result;
979 }
980
981
982 /*
983 Below is the UDF for showing the existing list of global version tokens.
984 Names of tokens will be returned in a semicolon separated list.
985
986 Function signature:
987
988 VERSION_TOKENS_SHOW()
989 */
990
version_tokens_show_init(UDF_INIT * initid,UDF_ARGS * args,char * message)991 PLUGIN_EXPORT my_bool version_tokens_show_init(UDF_INIT *initid, UDF_ARGS *args,
992 char *message)
993 {
994 int i= 0;
995 size_t str_size= 0;
996 char *result_str;
997 version_token_st *token_obj;
998 THD *thd= current_thd;
999
1000 if (!(thd->security_context()->check_access(SUPER_ACL)))
1001 {
1002 my_stpcpy(message, "The user is not privileged to use this function.");
1003 return true;
1004 }
1005
1006 if (args->arg_count != 0)
1007 {
1008 my_stpcpy(message, "This function does not take any arguments.");
1009 return true;
1010 }
1011
1012 mysql_rwlock_rdlock(&LOCK_vtoken_hash);
1013 if (!version_tokens_hash_inited.is_set())
1014 {
1015 my_stpcpy(message, "version_token plugin is not installed.");
1016 mysql_rwlock_unlock(&LOCK_vtoken_hash);
1017 return true;
1018 }
1019
1020 str_size= vtoken_string_length;
1021
1022 if (str_size)
1023 {
1024 str_size++;
1025 initid->ptr= (char *)my_malloc(key_memory_vtoken,
1026 str_size, MYF(MY_WME));
1027
1028 if (initid->ptr == NULL)
1029 {
1030 my_stpcpy(message, "Not enough memory available.");
1031 mysql_rwlock_unlock(&LOCK_vtoken_hash);
1032 return true;
1033 }
1034
1035 result_str= initid->ptr;
1036
1037 i= 0;
1038
1039 while ((token_obj= (version_token_st *) my_hash_element(&version_tokens_hash, i)))
1040 {
1041 memcpy(result_str, (token_obj->token_name).str, (token_obj->token_name).length);
1042
1043 result_str+= (token_obj->token_name).length;
1044
1045 memcpy(result_str, "=", 1);
1046
1047 result_str++;
1048
1049 memcpy(result_str, (token_obj->token_val).str, (token_obj->token_val).length);
1050
1051 result_str+= (token_obj->token_val).length;
1052
1053 memcpy(result_str, ";", 1);
1054
1055 result_str++;
1056
1057 i++;
1058 }
1059
1060 initid->ptr[str_size-1]= '\0';
1061 }
1062 else
1063 initid->ptr= NULL;
1064 mysql_rwlock_unlock(&LOCK_vtoken_hash);
1065
1066 return false;
1067 }
1068
version_tokens_show_deinit(UDF_INIT * initid)1069 PLUGIN_EXPORT void version_tokens_show_deinit(UDF_INIT *initid)
1070 {
1071 if (initid->ptr)
1072 my_free(initid->ptr);
1073 }
1074
version_tokens_show(UDF_INIT * initid,UDF_ARGS * args,char * result,unsigned long * length,char * null_value,char * error)1075 PLUGIN_EXPORT char *version_tokens_show(UDF_INIT *initid, UDF_ARGS *args,
1076 char *result, unsigned long *length,
1077 char *null_value, char *error)
1078 {
1079 char *result_str= initid->ptr;
1080 *length= 0;
1081
1082 if (!result_str)
1083 return NULL;
1084
1085 *length= (unsigned long) strlen(result_str);
1086
1087 return result_str;
1088 }
1089
init_acquire(UDF_INIT * initid,UDF_ARGS * args,char * message)1090 static inline my_bool init_acquire(UDF_INIT *initid, UDF_ARGS *args, char *message)
1091 {
1092 initid->maybe_null= FALSE;
1093 initid->decimals= 0;
1094 initid->max_length= 1;
1095 initid->ptr= NULL;
1096 initid->const_item= 0;
1097 initid->extension= NULL;
1098
1099 THD *thd= current_thd;
1100
1101 if (!(thd->security_context()->check_access(SUPER_ACL)))
1102 {
1103 my_stpcpy(message, "The user is not privileged to use this function.");
1104 return true;
1105 }
1106
1107 // At least two arguments - lock, timeout
1108 if (args->arg_count < 2)
1109 {
1110 strcpy(message,
1111 "Requires at least two arguments: (lock(...),timeout).");
1112 return TRUE;
1113 }
1114
1115 // Timeout is the last argument, should be INT
1116 if (args->arg_type[args->arg_count - 1] != INT_RESULT)
1117 {
1118 strcpy(message, "Wrong argument type - expected integer.");
1119 return TRUE;
1120 }
1121
1122 // All other arguments should be strings
1123 for (size_t i= 0; i < (args->arg_count - 1); i++)
1124 {
1125 if (args->arg_type[i] != STRING_RESULT)
1126 {
1127 strcpy(message, "Wrong argument type - expected string.");
1128 return TRUE;
1129 }
1130 }
1131
1132 return FALSE;
1133 }
1134
version_tokens_lock_shared_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1135 PLUGIN_EXPORT my_bool version_tokens_lock_shared_init(
1136 UDF_INIT *initid, UDF_ARGS *args, char *message)
1137 {
1138 return init_acquire(initid, args, message);
1139 }
1140
1141
version_tokens_lock_shared(UDF_INIT * initid,UDF_ARGS * args,char * is_null,char * error)1142 PLUGIN_EXPORT long long version_tokens_lock_shared(
1143 UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
1144 {
1145 long long timeout=
1146 args->args[args->arg_count - 1] ? // Null ?
1147 *((long long *) args->args[args->arg_count - 1]) : -1;
1148
1149 if (timeout < 0)
1150 {
1151 my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "timeout",
1152 "version_tokens_lock_shared");
1153 *error= 1;
1154 }
1155
1156 // For the UDF 1 == success, 0 == failure.
1157 return !acquire_locking_service_locks(NULL, VTOKEN_LOCKS_NAMESPACE,
1158 const_cast<const char**>(&args->args[0]),
1159 args->arg_count - 1,
1160 LOCKING_SERVICE_READ, (unsigned long) timeout);
1161 }
1162
1163
version_tokens_lock_exclusive_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1164 PLUGIN_EXPORT my_bool version_tokens_lock_exclusive_init(
1165 UDF_INIT *initid, UDF_ARGS *args, char *message)
1166 {
1167 return init_acquire(initid, args, message);
1168 }
1169
1170
version_tokens_lock_exclusive(UDF_INIT * initid,UDF_ARGS * args,char * is_null,char * error)1171 PLUGIN_EXPORT long long version_tokens_lock_exclusive(
1172 UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
1173 {
1174 long long timeout=
1175 args->args[args->arg_count - 1] ? // Null ?
1176 *((long long *) args->args[args->arg_count - 1]) : -1;
1177
1178 if (timeout < 0)
1179 {
1180 my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "timeout",
1181 "version_tokens_lock_exclusive");
1182 *error= 1;
1183 }
1184
1185 // For the UDF 1 == success, 0 == failure.
1186 return !acquire_locking_service_locks(NULL, VTOKEN_LOCKS_NAMESPACE,
1187 const_cast<const char**>(&args->args[0]),
1188 args->arg_count - 1,
1189 LOCKING_SERVICE_WRITE, (unsigned long) timeout);
1190 }
1191
version_tokens_unlock_init(UDF_INIT * initid,UDF_ARGS * args,char * message)1192 PLUGIN_EXPORT my_bool version_tokens_unlock_init(
1193 UDF_INIT *initid, UDF_ARGS *args, char *message)
1194 {
1195 THD *thd= current_thd;
1196
1197 if (!(thd->security_context()->check_access(SUPER_ACL)))
1198 {
1199 my_stpcpy(message, "The user is not privileged to use this function.");
1200 return true;
1201 }
1202
1203 if (args->arg_count != 0)
1204 {
1205 strcpy(message, "Requires no arguments.");
1206 return TRUE;
1207 }
1208
1209 return FALSE;
1210 }
1211
1212
version_tokens_unlock(UDF_INIT * initid,UDF_ARGS * args,char * is_null,char * error)1213 long long version_tokens_unlock(UDF_INIT *initid, UDF_ARGS *args,
1214 char *is_null, char *error)
1215 {
1216 // For the UDF 1 == success, 0 == failure.
1217 return !release_locking_service_locks(NULL, VTOKEN_LOCKS_NAMESPACE);
1218 }
1219
1220
1221
version_token_get_key(const char * entry,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))1222 static uchar *version_token_get_key(const char *entry, size_t *length,
1223 my_bool not_used MY_ATTRIBUTE((unused)))
1224 {
1225 char *key;
1226 key= (((version_token_st *) entry)->token_name).str;
1227 *length= (((version_token_st *) entry)->token_name).length;
1228 return (uchar *) key;
1229 }
1230