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 *)¤t_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 *)¤t_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