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