1 /* Copyright (c) 2016, 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  "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     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       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       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 *)&current_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         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         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     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     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 *)&current_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