1 /* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
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 #include "plugin/connection_control/connection_delay.h"
24 
25 #include <mysql/psi/mysql_thread.h>
26 #include <time.h>
27 
28 #include "m_ctype.h" /* my_charset_bin */
29 #include "my_compiler.h"
30 #include "my_dbug.h"
31 #include "my_systime.h"
32 #include "mysqld_error.h"
33 #include "plugin/connection_control/connection_control.h"
34 #include "plugin/connection_control/security_context_wrapper.h"
35 #include "sql/current_thd.h" /* current_thd */
36 #include "sql/item_cmpfunc.h"
37 #include "sql/sql_class.h" /* THD, Security context */
38 
39 /* Forward declaration */
40 bool schema_table_store_record(THD *thd, TABLE *table);
41 
42 void thd_enter_cond(void *opaque_thd, mysql_cond_t *cond, mysql_mutex_t *mutex,
43                     const PSI_stage_info *stage, PSI_stage_info *old_stage,
44                     const char *src_function, const char *src_file,
45                     int src_line);
46 
47 void thd_exit_cond(void *opaque_thd, const PSI_stage_info *stage,
48                    const char *src_function, const char *src_file,
49                    int src_line);
50 
51 struct st_mysql_information_schema connection_control_failed_attempts_view = {
52     MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION};
53 
54 /** Fields of information_schema.connection_control_failed_attempts */
55 static ST_FIELD_INFO failed_attempts_view_fields[] = {
56     {"USERHOST", USERNAME_LENGTH + HOSTNAME_LENGTH + 6, MYSQL_TYPE_STRING, 0,
57      MY_I_S_UNSIGNED, nullptr, 0},
58     {"FAILED_ATTEMPTS", 16, MYSQL_TYPE_LONG, 0, MY_I_S_UNSIGNED, nullptr, 0},
59     {nullptr, 0, MYSQL_TYPE_NULL, 0, 0, nullptr, 0}};
60 
61 namespace connection_control {
62 
63 /** constants/variables declared in connection_delay_interfaces.h */
64 
65 int64 DEFAULT_THRESHOLD = 3;
66 int64 MIN_THRESHOLD = 0;
67 int64 DISABLE_THRESHOLD = 0;
68 int64 MAX_THRESHOLD = INT_MAX32;
69 
70 int64 DEFAULT_MAX_DELAY = INT_MAX32;
71 int64 DEFAULT_MIN_DELAY = 1000;
72 int64 MIN_DELAY = 1000;
73 int64 MAX_DELAY = INT_MAX32;
74 
75 /** variables used by connection_delay.cc */
76 static mysql_rwlock_t connection_event_delay_lock;
77 
78 static opt_connection_control opt_enums[] = {OPT_FAILED_CONNECTIONS_THRESHOLD,
79                                              OPT_MIN_CONNECTION_DELAY,
80                                              OPT_MAX_CONNECTION_DELAY};
81 size_t opt_enums_size = 3;
82 
83 static stats_connection_control status_vars_enums[] = {
84     STAT_CONNECTION_DELAY_TRIGGERED};
85 size_t status_vars_enums_size = 1;
86 
87 static Connection_delay_action *g_max_failed_connection_handler = nullptr;
88 
89 Sql_string I_S_CONNECTION_CONTROL_FAILED_ATTEMPTS_USERHOST(
90     "information_schema.connection_control_failed_login_attempts.userhost");
91 
92 /**
93   Helper function for Connection_delay_event::reset_all
94 
95   @returns 1 to indicate that entry is a match
96 */
match_all_entries(const uchar *)97 int match_all_entries(const uchar *) { return 1; }
98 
99 /**
100   Callback function for LF hash to get key information
101 
102   Returns a pointer to a buffer which represent a key in the hash.
103   The function does NOT calculate a hash.
104   The function is called during lf_hash_insert(). The buffer is
105   fed to an internal calc_hash() which use the defined charset to
106   calculate a hash from the key buffer (in most cases a murmur)
107 
108   @param [in] el        Pointer to an element in the hash
109   @param [out] length   The length of the key belonging to the element
110 
111   @returns Pointer to key buffer
112 */
113 
connection_delay_event_hash_key(const uchar * el,size_t * length)114 const uchar *connection_delay_event_hash_key(const uchar *el, size_t *length) {
115   const Connection_event_record *const *entry;
116   const Connection_event_record *entry_info;
117   entry = reinterpret_cast<const Connection_event_record *const *>(el);
118   DBUG_ASSERT(entry != nullptr);
119   entry_info = *entry;
120   *length = entry_info->get_length();
121   return (const_cast<uchar *>(entry_info->get_userhost()));
122 }
123 
124 /**
125   Constructor for Connection_delay_event
126 
127   Initialize LF hash.
128 */
129 
Connection_delay_event()130 Connection_delay_event::Connection_delay_event() {
131   lf_hash_init(&m_entries, sizeof(Connection_event_record **), LF_HASH_UNIQUE,
132                0, /* key offset */
133                0, /* key length not used */
134                connection_delay_event_hash_key, &my_charset_bin);
135 }
136 
137 /**
138   Creates or updates an entry in hash
139 
140   @param [in] s    User information in '<user'@'<host>' format
141 
142   @returns status of insertion/update
143     @retval false  Insertion/Update successful
144     @retval true   Failed to insert/update an entry
145 */
146 
create_or_update_entry(const Sql_string & s)147 bool Connection_delay_event::create_or_update_entry(const Sql_string &s) {
148   Connection_event_record **searched_entry = nullptr;
149   Connection_event_record *searched_entry_info = nullptr;
150   Connection_event_record *new_entry = nullptr;
151   int insert_status;
152   DBUG_TRACE;
153 
154   LF_PINS *pins = lf_hash_get_pins(&m_entries);
155   if (unlikely(pins == nullptr)) return true;
156 
157   searched_entry = reinterpret_cast<Connection_event_record **>(
158       lf_hash_search(&m_entries, pins, s.c_str(), s.length()));
159 
160   if (searched_entry && (searched_entry != MY_LF_ERRPTR)) {
161     /* We found an entry, so increment the count */
162     searched_entry_info = *searched_entry;
163     DBUG_ASSERT(searched_entry_info != nullptr);
164     searched_entry_info->inc_count();
165     lf_hash_search_unpin(pins);
166     lf_hash_put_pins(pins);
167     return false;
168   } else {
169     /* No entry found, so try to add new entry */
170     lf_hash_search_unpin(pins);
171     new_entry = new Connection_event_record(s);
172 
173     insert_status = lf_hash_insert(&m_entries, pins, &new_entry);
174 
175     if (likely(insert_status == 0)) {
176       lf_hash_put_pins(pins);
177       return false;
178     } else {
179       /*
180         OOM. We are likely in bigger trouble than just
181         failing to insert an entry in hash.
182       */
183       lf_hash_put_pins(pins);
184       delete new_entry;
185       new_entry = nullptr;
186       return true;
187     }
188   }
189 }
190 
191 /**
192   Resets count stored against given user entry
193 
194   @param [in] s    User information in '<user'@'<host>' format
195 
196   @returns status of reset operation
197     @retval false Reset successful
198     @retval true Failed to find given entry
199 */
200 
remove_entry(const Sql_string & s)201 bool Connection_delay_event::remove_entry(const Sql_string &s) {
202   Connection_event_record **searched_entry = nullptr;
203   Connection_event_record *searched_entry_info = nullptr;
204   DBUG_TRACE;
205 
206   LF_PINS *pins = lf_hash_get_pins(&m_entries);
207 
208   searched_entry = reinterpret_cast<Connection_event_record **>(
209       lf_hash_search(&m_entries, pins, s.c_str(), s.length()));
210 
211   if (searched_entry && searched_entry != MY_LF_ERRPTR) {
212     searched_entry_info = *searched_entry;
213     DBUG_ASSERT(searched_entry_info != nullptr);
214     int rc = lf_hash_delete(&m_entries, pins, s.c_str(), s.length());
215     lf_hash_search_unpin(pins);
216     lf_hash_put_pins(pins);
217     if (rc == 0) {
218       /* free memory upon successful deletion */
219       delete searched_entry_info;
220     }
221     return rc != 0;
222   } else {
223     /* No entry found. */
224     lf_hash_search_unpin(pins);
225     lf_hash_put_pins(pins);
226     return true;
227   }
228 }
229 
230 /**
231   Retrieve stored value for given user entry
232 
233   @param [in] s        User information in '<user'@'<host>' format
234   @param value [out]   Buffer to hold value stored against given user
235 
236   @returns whether given entry is present in hash or not
237     @retval false  Entry found. Corresponding value is copied in value buffer.
238     @retval true   No matching entry found. value buffer should not be used.
239 */
240 
match_entry(const Sql_string & s,void * value)241 bool Connection_delay_event::match_entry(const Sql_string &s, void *value) {
242   Connection_event_record **searched_entry = nullptr;
243   Connection_event_record *searched_entry_info = nullptr;
244   int64 count = DISABLE_THRESHOLD;
245   bool error = true;
246   DBUG_TRACE;
247 
248   LF_PINS *pins = lf_hash_get_pins(&m_entries);
249 
250   searched_entry = reinterpret_cast<Connection_event_record **>(
251       lf_hash_search(&m_entries, pins, s.c_str(), s.length()));
252 
253   if (searched_entry && searched_entry != MY_LF_ERRPTR) {
254     searched_entry_info = *searched_entry;
255     count = searched_entry_info->get_count();
256     error = false;
257   }
258 
259   lf_hash_search_unpin(pins);
260   lf_hash_put_pins(pins);
261   *(reinterpret_cast<int64 *>(value)) = count;
262 
263   return error;
264 }
265 
266 /**
267   Delete all entries from hash and free memory
268 */
269 
reset_all()270 void Connection_delay_event::reset_all() {
271   Connection_event_record **searched_entry = nullptr;
272   DBUG_TRACE;
273   LF_PINS *pins = lf_hash_get_pins(&m_entries);
274 
275   do {
276     /* match anything */
277     searched_entry = reinterpret_cast<Connection_event_record **>(
278         lf_hash_random_match(&m_entries, pins, match_all_entries, 0));
279 
280     if (searched_entry != nullptr && searched_entry != MY_LF_ERRPTR &&
281         (*searched_entry) &&
282         !lf_hash_delete(&m_entries, pins, (*searched_entry)->get_userhost(),
283                         (*searched_entry)->get_length())) {
284       delete (*searched_entry);
285       *searched_entry = nullptr;
286     } else {
287       /* Failed to delete didn't remove any pins */
288       lf_hash_search_unpin(pins);
289     }
290   } while (searched_entry != nullptr);
291 
292   lf_hash_put_pins(pins);
293 }
294 
295 /** This works because connction_delay_IS_table is protected by wrlock */
296 static TABLE *connection_delay_IS_table;
set_connection_delay_IS_table(TABLE * t)297 void set_connection_delay_IS_table(TABLE *t) { connection_delay_IS_table = t; }
298 
299 /**
300   Function to populate information_schema view.
301 
302   @param [in] ptr  Entry from LF hash
303 
304   @returns status of row insertion
305     @retval 0 Success
306     @retval 1 Error
307 */
308 
connection_delay_IS_table_writer(const uchar * ptr)309 int connection_delay_IS_table_writer(const uchar *ptr) {
310   /* Always return "no match" so that we go through all entries */
311   THD *thd = current_thd;
312   const Connection_event_record *const *entry;
313   const Connection_event_record *entry_info;
314   entry = reinterpret_cast<const Connection_event_record *const *>(ptr);
315   entry_info = *entry;
316   connection_delay_IS_table->field[0]->store((char *)entry_info->get_userhost(),
317                                              entry_info->get_length(),
318                                              system_charset_info);
319   connection_delay_IS_table->field[1]->store(entry_info->get_count(), true);
320   if (schema_table_store_record(thd, connection_delay_IS_table)) return 1;
321   /* Always return "no match" so that we go over all entries in the hash */
322   return 0;
323 }
324 
325 /**
326   Function to dump LF hash data to IS table.
327 
328   @param [in] tables Handle to
329                      information_schema.connection_control_failed_attempts
330 */
331 
fill_IS_table(TABLE_LIST * tables)332 void Connection_delay_event::fill_IS_table(TABLE_LIST *tables) {
333   DBUG_TRACE;
334   TABLE *table = tables->table;
335   set_connection_delay_IS_table(table);
336   LF_PINS *pins = lf_hash_get_pins(&m_entries);
337   void *key = nullptr;
338 
339   do {
340     key =
341         lf_hash_random_match(&m_entries, pins,
342                              /* Functor: match anything and store the fields */
343                              connection_delay_IS_table_writer, 0);
344     /* Always unpin after lf_hash_random_match() */
345     lf_hash_search_unpin(pins);
346   } while (key != nullptr);
347 
348   lf_hash_put_pins(pins);
349 }
350 
351 /**
352   Connection_delay_action Constructor.
353 
354   @param [in] threshold         Defines a threshold after which wait is
355   triggered
356   @param [in] min_delay         Lower cap on wait
357   @param [in] max_delay         Upper cap on wait
358   @param [in] sys_vars          System variables
359   @param [in] sys_vars_size     Size of sys_vars array
360   @param [in] status_vars       Status variables
361   @param [in] status_vars_size  Size of status_vars array
362   @param [in] lock              RW lock handle
363 */
364 
Connection_delay_action(int64 threshold,int64 min_delay,int64 max_delay,opt_connection_control * sys_vars,size_t sys_vars_size,stats_connection_control * status_vars,size_t status_vars_size,mysql_rwlock_t * lock)365 Connection_delay_action::Connection_delay_action(
366     int64 threshold, int64 min_delay, int64 max_delay,
367     opt_connection_control *sys_vars, size_t sys_vars_size,
368     stats_connection_control *status_vars, size_t status_vars_size,
369     mysql_rwlock_t *lock)
370     : m_threshold(threshold),
371       m_min_delay(min_delay),
372       m_max_delay(max_delay),
373       m_lock(lock) {
374   if (sys_vars_size) {
375     for (uint i = 0; i < sys_vars_size; ++i) m_sys_vars.push_back(sys_vars[i]);
376   }
377 
378   if (status_vars_size) {
379     for (uint i = 0; i < status_vars_size; ++i)
380       m_stats_vars.push_back(status_vars[i]);
381   }
382 }
383 
384 /**
385   Create hash key of the format '<user>'@'<host>'.
386   Policy:
387   1. Use proxy_user information if available. Else if,
388   2. Use priv_user/priv_host if either of them is not empty. Else,
389   3. Use user/host
390 
391   @param [in] thd        THD pointer for getting security context
392   @param [out] s         Hash key is stored here
393 */
394 
make_hash_key(MYSQL_THD thd,Sql_string & s)395 void Connection_delay_action::make_hash_key(MYSQL_THD thd, Sql_string &s) {
396   /* Our key for hash will be of format : '<user>'@'<host>' */
397 
398   /* If proxy_user is set then use it directly for lookup */
399   Security_context_wrapper sctx_wrapper(thd);
400   const char *proxy_user = sctx_wrapper.get_proxy_user();
401   if (proxy_user && *proxy_user) {
402     s.append(proxy_user);
403   } /* else if priv_user and/or priv_host is set, then use them */
404   else {
405     const char *priv_user = sctx_wrapper.get_priv_user();
406     const char *priv_host = sctx_wrapper.get_priv_host();
407     if (*priv_user || *priv_host) {
408       s.append("'");
409 
410       if (*priv_user) s.append(priv_user);
411 
412       s.append("'@'");
413 
414       if (*priv_host) s.append(priv_host);
415 
416       s.append("'");
417     } else {
418       const char *user = sctx_wrapper.get_user();
419       const char *host = sctx_wrapper.get_host();
420       const char *ip = sctx_wrapper.get_ip();
421 
422       s.append("'");
423 
424       if (user && *user) s.append(user);
425 
426       s.append("'@'");
427 
428       if (host && *host)
429         s.append(host);
430       else if (ip && *ip)
431         s.append(ip);
432 
433       s.append("'");
434     }
435   }
436 }
437 
438 /**
439   Wait till the wait_time expires or thread is killed
440 
441   @param [in] thd        Handle to MYSQL_THD object
442   @param [in] wait_time  Maximum time to wait
443 */
444 
conditional_wait(MYSQL_THD thd,ulonglong wait_time)445 void Connection_delay_action::conditional_wait(MYSQL_THD thd,
446                                                ulonglong wait_time) {
447   DBUG_TRACE;
448 
449   /** mysql_cond_timedwait requires wait time in timespec format */
450   struct timespec abstime;
451   /** Since we get wait_time in milliseconds, convert it to nanoseconds */
452   set_timespec_nsec(&abstime, wait_time * 1000000ULL);
453 
454   /** PSI_stage_info for thd_enter_cond/thd_exit_cond */
455   PSI_stage_info old_stage;
456 
457   /** Initialize mutex required for mysql_cond_timedwait */
458   mysql_mutex_t connection_delay_mutex;
459   mysql_mutex_init(key_connection_delay_mutex, &connection_delay_mutex,
460                    MY_MUTEX_INIT_FAST);
461 
462   /* Initialize condition to wait for */
463   mysql_cond_t connection_delay_wait_condition;
464   mysql_cond_init(key_connection_delay_wait, &connection_delay_wait_condition);
465 
466   /** Register wait condition with THD */
467   mysql_mutex_lock(&connection_delay_mutex);
468   thd_enter_cond(thd, &connection_delay_wait_condition, &connection_delay_mutex,
469                  &stage_waiting_in_connection_control_plugin, &old_stage,
470                  __func__, __FILE__, __LINE__);
471 
472   /*
473     At this point, thread is essentially going to sleep till
474     timeout. If admin issues KILL statement for this THD,
475     there is no point keeping this thread in sleep mode only
476     to wake up to be terminated. Hence, in case of KILL,
477     we will return control to server without worring about
478     wait_time.
479   */
480   mysql_cond_timedwait(&connection_delay_wait_condition,
481                        &connection_delay_mutex, &abstime);
482 
483   /* Finish waiting and deregister wait condition */
484   mysql_mutex_unlock(&connection_delay_mutex);
485   thd_exit_cond(thd, &stage_waiting_in_connection_control_plugin, __func__,
486                 __FILE__, __LINE__);
487 
488   /* Cleanup */
489   mysql_mutex_destroy(&connection_delay_mutex);
490   mysql_cond_destroy(&connection_delay_wait_condition);
491 }
492 
493 /**
494   @brief  Handle a connection event and if requried,
495   wait for random amount of time before returning.
496 
497   We only care about CONNECT and CHANGE_USER sub events.
498 
499   @param [in] thd                THD pointer
500   @param [in] coordinator        Connection_event_coordinator
501   @param [in] connection_event   Connection event to be handled
502   @param [in] error_handler      Error handler object
503 
504   @returns status of connection event handling
505     @retval false  Successfully handled an event.
506     @retval true   Something went wrong.
507                    error_buffer may contain details.
508 */
509 
notify_event(MYSQL_THD thd,Connection_event_coordinator_services * coordinator,const mysql_event_connection * connection_event,Error_handler * error_handler)510 bool Connection_delay_action::notify_event(
511     MYSQL_THD thd, Connection_event_coordinator_services *coordinator,
512     const mysql_event_connection *connection_event,
513     Error_handler *error_handler) {
514   DBUG_TRACE;
515   bool error = false;
516   unsigned int subclass = connection_event->event_subclass;
517   Connection_event_observer *self = this;
518 
519   if (subclass != MYSQL_AUDIT_CONNECTION_CONNECT &&
520       subclass != MYSQL_AUDIT_CONNECTION_CHANGE_USER)
521     return error;
522 
523   RD_lock rd_lock(m_lock);
524 
525   int64 threshold = this->get_threshold();
526 
527   /* If feature was disabled, return */
528   if (threshold <= DISABLE_THRESHOLD) return error;
529 
530   int64 current_count = 0;
531   bool user_present = false;
532   Sql_string userhost;
533 
534   make_hash_key(thd, userhost);
535 
536   DBUG_PRINT("info", ("Connection control : Connection event lookup for: %s",
537                       userhost.c_str()));
538 
539   /* Cache current failure count */
540   user_present = m_userhost_hash.match_entry(userhost, (void *)&current_count)
541                      ? false
542                      : true;
543 
544   if (current_count >= threshold || current_count < 0) {
545     /*
546       If threshold is crosed, regardless of connection success
547       or failure, wait for (current_count + 1) - threshold seconds
548       Note that current_count is not yet updated in hash. So we
549       have to consider current connection as well - Hence the usage
550       of current_count + 1.
551     */
552     ulonglong wait_time = get_wait_time((current_count + 1) - threshold);
553 
554     if ((error = coordinator->notify_status_var(
555              &self, STAT_CONNECTION_DELAY_TRIGGERED, ACTION_INC))) {
556       error_handler->handle_error(
557           ER_CONN_CONTROL_STAT_CONN_DELAY_TRIGGERED_UPDATE_FAILED);
558     }
559     /*
560       Invoking sleep while holding read lock on Connection_delay_action
561       would block access to cache data through IS table.
562     */
563     rd_lock.unlock();
564     conditional_wait(thd, wait_time);
565     rd_lock.lock();
566   }
567 
568   if (connection_event->status) {
569     /*
570       Connection failure.
571       Add new entry to hash or increment failed connection count
572       for an existing entry
573     */
574     if (m_userhost_hash.create_or_update_entry(userhost)) {
575       error_handler->handle_error(
576           ER_CONN_CONTROL_FAILED_TO_UPDATE_CONN_DELAY_HASH, userhost.c_str());
577       error = true;
578     }
579   } else {
580     /*
581       Successful connection.
582       delete entry for given account from the hash
583     */
584     if (user_present) {
585       (void)m_userhost_hash.remove_entry(userhost);
586     }
587   }
588 
589   return error;
590 }
591 
592 /**
593   Notification of a change in system variable value
594 
595   @param [in] coordinator        Handle to coordinator
596   @param [in] variable           Enum of variable
597   @param [in] new_value          New value for variable
598   @param [in] error_handler      Error handler object
599 
600   @returns processing status
601     @retval false  Change in variable value processed successfully
602     @retval true Error processing new value.
603                   error_buffer may contain more details.
604 */
605 
notify_sys_var(Connection_event_coordinator_services * coordinator,opt_connection_control variable,void * new_value,Error_handler * error_handler)606 bool Connection_delay_action::notify_sys_var(
607     Connection_event_coordinator_services *coordinator,
608     opt_connection_control variable, void *new_value,
609     Error_handler *error_handler) {
610   DBUG_TRACE;
611   bool error = true;
612   Connection_event_observer *self = this;
613 
614   WR_lock wr_lock(m_lock);
615 
616   switch (variable) {
617     case OPT_FAILED_CONNECTIONS_THRESHOLD: {
618       int64 new_threshold = *(static_cast<int64 *>(new_value));
619       DBUG_ASSERT(new_threshold >= DISABLE_THRESHOLD);
620       set_threshold(new_threshold);
621 
622       if ((error = coordinator->notify_status_var(
623                &self, STAT_CONNECTION_DELAY_TRIGGERED, ACTION_RESET))) {
624         error_handler->handle_error(
625             ER_CONN_CONTROL_STAT_CONN_DELAY_TRIGGERED_RESET_FAILED);
626       }
627       break;
628     }
629     case OPT_MIN_CONNECTION_DELAY:
630     case OPT_MAX_CONNECTION_DELAY: {
631       int64 new_delay = *(static_cast<int64 *>(new_value));
632       if ((error =
633                set_delay(new_delay, (variable == OPT_MIN_CONNECTION_DELAY)))) {
634         error_handler->handle_error(
635             ER_CONN_CONTROL_FAILED_TO_SET_CONN_DELAY,
636             (variable == OPT_MIN_CONNECTION_DELAY) ? "min" : "max");
637       }
638       break;
639     }
640     default:
641       /* Should never reach here. */
642       DBUG_ASSERT(false);
643       error_handler->handle_error(ER_CONN_CONTROL_INVALID_CONN_DELAY_TYPE);
644   };
645   return error;
646 }
647 
648 /**
649   Subscribe with coordinator for connection events
650 
651   @param [in] coordinator  Handle to Connection_event_coordinator_services
652                            for registration
653 */
init(Connection_event_coordinator_services * coordinator)654 void Connection_delay_action::init(
655     Connection_event_coordinator_services *coordinator) {
656   DBUG_TRACE;
657   DBUG_ASSERT(coordinator);
658   bool retval;
659   Connection_event_observer *subscriber = this;
660   WR_lock wr_lock(m_lock);
661   retval = coordinator->register_event_subscriber(&subscriber, &m_sys_vars,
662                                                   &m_stats_vars);
663   DBUG_ASSERT(!retval);
664   if (retval) retval = false; /* Make compiler happy */
665 }
666 
667 /**
668   Clear data from Connection_delay_action
669 */
670 
deinit()671 void Connection_delay_action::deinit() {
672   mysql_rwlock_wrlock(m_lock);
673   m_userhost_hash.reset_all();
674   m_sys_vars.clear();
675   m_stats_vars.clear();
676   m_threshold = DISABLE_THRESHOLD;
677   mysql_rwlock_unlock(m_lock);
678   m_lock = nullptr;
679 }
680 
681 /**
682   Get user information from "where userhost = <value>"
683 
684   @param [in] cond        Equality condition structure
685   @param [out] eq_arg     Sql_string handle to store user information
686   @param [in] field_name  userhost field
687 
688   @returns whether a value was found or not
689     @retval false Found a value. Check eq_arg
690     @retval true Error.
691 */
692 
get_equal_condition_argument(Item * cond,Sql_string * eq_arg,const Sql_string & field_name)693 static bool get_equal_condition_argument(Item *cond, Sql_string *eq_arg,
694                                          const Sql_string &field_name) {
695   if (cond != nullptr && cond->type() == Item::FUNC_ITEM) {
696     Item_func *func = static_cast<Item_func *>(cond);
697     if (func != nullptr && func->functype() == Item_func::EQ_FUNC) {
698       Item_func_eq *eq_func = static_cast<Item_func_eq *>(func);
699       if (eq_func->arguments()[0]->type() == Item::FIELD_ITEM &&
700           my_strcasecmp(system_charset_info,
701                         eq_func->arguments()[0]->full_name(),
702                         field_name.c_str()) == 0) {
703         char buff[1024];
704         String *res;
705         String filter(buff, sizeof(buff), system_charset_info);
706         if (eq_func->arguments()[1] != nullptr &&
707             (res = eq_func->arguments()[1]->val_str(&filter))) {
708           eq_arg->append(res->c_ptr_safe(), res->length());
709           return false;
710         }
711       }
712     }
713   }
714   return true;
715 }
716 
717 /**
718   Function to fill information_schema.connection_control_failed_attempts.
719 
720   Handles query with equality condition.
721   For full scan, calls Connection_delay_event::fill_IS_table()
722 
723   Permission : SUPER_ACL is required.
724 
725   @param [in] thd     THD handle.
726   @param [in] tables  Handle to
727                       information_schema.connection_control_failed_attempts.
728   @param [in] cond    Condition if any.
729 */
730 
fill_IS_table(THD * thd,TABLE_LIST * tables,Item * cond)731 void Connection_delay_action::fill_IS_table(THD *thd, TABLE_LIST *tables,
732                                             Item *cond) {
733   DBUG_TRACE;
734   Security_context_wrapper sctx_wrapper(thd);
735   if (!(sctx_wrapper.is_super_user() || sctx_wrapper.is_connection_admin()))
736     return;
737   WR_lock wr_lock(m_lock);
738   Sql_string eq_arg;
739   if (cond != nullptr &&
740       !get_equal_condition_argument(
741           cond, &eq_arg, I_S_CONNECTION_CONTROL_FAILED_ATTEMPTS_USERHOST)) {
742     int64 current_count = 0;
743     if (m_userhost_hash.match_entry(eq_arg, (void *)&current_count)) {
744       /* There are no matches given the condition */
745       return;
746     } else {
747       /* There is exactly one matching userhost entry */
748       TABLE *table = tables->table;
749       table->field[0]->store(eq_arg.c_str(), eq_arg.length(),
750                              system_charset_info);
751       table->field[1]->store(current_count, true);
752       schema_table_store_record(thd, table);
753     }
754   } else
755     m_userhost_hash.fill_IS_table(tables);
756 }
757 
758 /**
759   Initializes required objects for handling connection events.
760 
761   @param [in] coordinator    Connection_event_coordinator_services handle.
762 */
763 
init_connection_delay_event(Connection_event_coordinator_services * coordinator,Error_handler * error_handler)764 bool init_connection_delay_event(
765     Connection_event_coordinator_services *coordinator,
766     Error_handler *error_handler) {
767   /*
768     1. Initialize lock(s)
769   */
770   mysql_rwlock_init(key_connection_event_delay_lock,
771                     &connection_event_delay_lock);
772   g_max_failed_connection_handler = new Connection_delay_action(
773       g_variables.failed_connections_threshold,
774       g_variables.min_connection_delay, g_variables.max_connection_delay,
775       opt_enums, opt_enums_size, status_vars_enums, status_vars_enums_size,
776       &connection_event_delay_lock);
777   if (!g_max_failed_connection_handler) {
778     error_handler->handle_error(ER_CONN_CONTROL_DELAY_ACTION_INIT_FAILED);
779     return true;
780   }
781   g_max_failed_connection_handler->init(coordinator);
782 
783   return false;
784 }
785 
786 /**
787   Deinitializes objects and frees associated memory.
788 */
789 
deinit_connection_delay_event()790 void deinit_connection_delay_event() {
791   if (g_max_failed_connection_handler) delete g_max_failed_connection_handler;
792   g_max_failed_connection_handler = nullptr;
793   mysql_rwlock_destroy(&connection_event_delay_lock);
794   return;
795 }
796 }  // namespace connection_control
797 
798 /**
799   Function to fill information_schema.connection_control_failed_attempts.
800 
801   @param [in] thd     THD handle.
802   @param [in] tables  Handle to
803                       information_schema.connection_control_failed_attempts.
804   @param [in] cond    Condition if any.
805 
806   @returns Always returns false.
807 */
808 
fill_failed_attempts_view(THD * thd,TABLE_LIST * tables,Item * cond)809 int fill_failed_attempts_view(THD *thd, TABLE_LIST *tables, Item *cond) {
810   if (connection_control::g_max_failed_connection_handler)
811     connection_control::g_max_failed_connection_handler->fill_IS_table(
812         thd, tables, cond);
813   return false;
814 }
815 
816 /**
817   View init function
818 
819   @param [in] ptr    Handle to
820                      information_schema.connection_control_failed_attempts.
821 
822   @returns Always returns 0.
823 */
824 
connection_control_failed_attempts_view_init(void * ptr)825 int connection_control_failed_attempts_view_init(void *ptr) {
826   ST_SCHEMA_TABLE *schema_table = (ST_SCHEMA_TABLE *)ptr;
827 
828   schema_table->fields_info = failed_attempts_view_fields;
829   schema_table->fill_table = fill_failed_attempts_view;
830   return 0;
831 }
832