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