1 | /* |
2 | Copyright (c) 2007, 2013, Oracle and/or its affiliates. |
3 | Copyright (c) 2008, 2016, MariaDB |
4 | |
5 | This program is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published by |
7 | the Free Software Foundation; version 2 of the License. |
8 | |
9 | This program is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | GNU General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU General Public License |
15 | along with this program; if not, write to the Free Software |
16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
17 | */ |
18 | |
19 | /* |
20 | Functions to autenticate and handle reqests for a connection |
21 | */ |
22 | |
23 | #include "mariadb.h" |
24 | #include "mysqld.h" |
25 | #include "sql_priv.h" |
26 | #ifndef __WIN__ |
27 | #include <netdb.h> // getservbyname, servent |
28 | #endif |
29 | #include "sql_audit.h" |
30 | #include "sql_connect.h" |
31 | #include "probes_mysql.h" |
32 | #include "sql_parse.h" // sql_command_flags, |
33 | // execute_init_command, |
34 | // do_command |
35 | #include "sql_db.h" // mysql_change_db |
36 | #include "hostname.h" // inc_host_errors, ip_to_hostname, |
37 | // reset_host_errors |
38 | #include "sql_acl.h" // acl_getroot, NO_ACCESS, SUPER_ACL |
39 | #include "sql_callback.h" |
40 | #include "wsrep_mysqld.h" |
41 | #include "proxy_protocol.h" |
42 | |
43 | HASH global_user_stats, global_client_stats, global_table_stats; |
44 | HASH global_index_stats; |
45 | /* Protects the above global stats */ |
46 | extern mysql_mutex_t LOCK_global_user_client_stats; |
47 | extern mysql_mutex_t LOCK_global_table_stats; |
48 | extern mysql_mutex_t LOCK_global_index_stats; |
49 | extern vio_keepalive_opts opt_vio_keepalive; |
50 | |
51 | /* |
52 | Get structure for logging connection data for the current user |
53 | */ |
54 | |
55 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
56 | static HASH hash_user_connections; |
57 | |
58 | int get_or_create_user_conn(THD *thd, const char *user, |
59 | const char *host, |
60 | const USER_RESOURCES *mqh) |
61 | { |
62 | int return_val= 0; |
63 | size_t temp_len, user_len; |
64 | char temp_user[USER_HOST_BUFF_SIZE]; |
65 | struct user_conn *uc; |
66 | |
67 | DBUG_ASSERT(user != 0); |
68 | DBUG_ASSERT(host != 0); |
69 | DBUG_ASSERT(thd->user_connect == 0); |
70 | |
71 | user_len= strlen(user); |
72 | temp_len= (strmov(strmov(temp_user, user)+1, host) - temp_user)+1; |
73 | mysql_mutex_lock(&LOCK_user_conn); |
74 | if (!(uc = (struct user_conn *) my_hash_search(&hash_user_connections, |
75 | (uchar*) temp_user, temp_len))) |
76 | { |
77 | /* First connection for user; Create a user connection object */ |
78 | if (!(uc= ((struct user_conn*) |
79 | my_malloc(sizeof(struct user_conn) + temp_len+1, |
80 | MYF(MY_WME))))) |
81 | { |
82 | /* MY_WME ensures an error is set in THD. */ |
83 | return_val= 1; |
84 | goto end; |
85 | } |
86 | uc->user=(char*) (uc+1); |
87 | memcpy(uc->user,temp_user,temp_len+1); |
88 | uc->host= uc->user + user_len + 1; |
89 | uc->len= (uint)temp_len; |
90 | uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0; |
91 | uc->user_resources= *mqh; |
92 | uc->reset_utime= thd->thr_create_utime; |
93 | if (my_hash_insert(&hash_user_connections, (uchar*) uc)) |
94 | { |
95 | /* The only possible error is out of memory, MY_WME sets an error. */ |
96 | my_free(uc); |
97 | return_val= 1; |
98 | goto end; |
99 | } |
100 | } |
101 | thd->user_connect=uc; |
102 | uc->connections++; |
103 | end: |
104 | mysql_mutex_unlock(&LOCK_user_conn); |
105 | return return_val; |
106 | } |
107 | |
108 | |
109 | /* |
110 | check if user has already too many connections |
111 | |
112 | SYNOPSIS |
113 | check_for_max_user_connections() |
114 | thd Thread handle |
115 | uc User connect object |
116 | |
117 | NOTES |
118 | If check fails, we decrease user connection count, which means one |
119 | shouldn't call decrease_user_connections() after this function. |
120 | |
121 | RETURN |
122 | 0 ok |
123 | 1 error |
124 | */ |
125 | |
126 | int check_for_max_user_connections(THD *thd, USER_CONN *uc) |
127 | { |
128 | int error= 1; |
129 | Host_errors errors; |
130 | DBUG_ENTER("check_for_max_user_connections" ); |
131 | |
132 | mysql_mutex_lock(&LOCK_user_conn); |
133 | |
134 | /* Root is not affected by the value of max_user_connections */ |
135 | if (global_system_variables.max_user_connections && |
136 | !uc->user_resources.user_conn && |
137 | global_system_variables.max_user_connections < uc->connections && |
138 | !(thd->security_ctx->master_access & SUPER_ACL)) |
139 | { |
140 | my_error(ER_TOO_MANY_USER_CONNECTIONS, MYF(0), uc->user); |
141 | error=1; |
142 | errors.m_max_user_connection= 1; |
143 | goto end; |
144 | } |
145 | time_out_user_resource_limits(thd, uc); |
146 | if (uc->user_resources.user_conn && |
147 | uc->user_resources.user_conn < uc->connections) |
148 | { |
149 | my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, |
150 | "max_user_connections" , |
151 | (long) uc->user_resources.user_conn); |
152 | error= 1; |
153 | errors.m_max_user_connection= 1; |
154 | goto end; |
155 | } |
156 | if (uc->user_resources.conn_per_hour && |
157 | uc->user_resources.conn_per_hour <= uc->conn_per_hour) |
158 | { |
159 | my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, |
160 | "max_connections_per_hour" , |
161 | (long) uc->user_resources.conn_per_hour); |
162 | error=1; |
163 | errors.m_max_user_connection_per_hour= 1; |
164 | goto end; |
165 | } |
166 | uc->conn_per_hour++; |
167 | error= 0; |
168 | |
169 | end: |
170 | if (unlikely(error)) |
171 | { |
172 | uc->connections--; // no need for decrease_user_connections() here |
173 | /* |
174 | The thread may returned back to the pool and assigned to a user |
175 | that doesn't have a limit. Ensure the user is not using resources |
176 | of someone else. |
177 | */ |
178 | thd->user_connect= NULL; |
179 | } |
180 | mysql_mutex_unlock(&LOCK_user_conn); |
181 | if (unlikely(error)) |
182 | { |
183 | inc_host_errors(thd->main_security_ctx.ip, &errors); |
184 | } |
185 | DBUG_RETURN(error); |
186 | } |
187 | |
188 | |
189 | /* |
190 | Decrease user connection count |
191 | |
192 | SYNOPSIS |
193 | decrease_user_connections() |
194 | uc User connection object |
195 | |
196 | NOTES |
197 | If there is a n user connection object for a connection |
198 | (which only happens if 'max_user_connections' is defined or |
199 | if someone has created a resource grant for a user), then |
200 | the connection count is always incremented on connect. |
201 | |
202 | The user connect object is not freed if some users has |
203 | 'max connections per hour' defined as we need to be able to hold |
204 | count over the lifetime of the connection. |
205 | */ |
206 | |
207 | void decrease_user_connections(USER_CONN *uc) |
208 | { |
209 | DBUG_ENTER("decrease_user_connections" ); |
210 | mysql_mutex_lock(&LOCK_user_conn); |
211 | DBUG_ASSERT(uc->connections); |
212 | if (!--uc->connections && !mqh_used) |
213 | { |
214 | /* Last connection for user; Delete it */ |
215 | (void) my_hash_delete(&hash_user_connections,(uchar*) uc); |
216 | } |
217 | mysql_mutex_unlock(&LOCK_user_conn); |
218 | DBUG_VOID_RETURN; |
219 | } |
220 | |
221 | |
222 | /* |
223 | Reset per-hour user resource limits when it has been more than |
224 | an hour since they were last checked |
225 | |
226 | SYNOPSIS: |
227 | time_out_user_resource_limits() |
228 | thd Thread handler |
229 | uc User connection details |
230 | |
231 | NOTE: |
232 | This assumes that the LOCK_user_conn mutex has been acquired, so it is |
233 | safe to test and modify members of the USER_CONN structure. |
234 | */ |
235 | |
236 | void time_out_user_resource_limits(THD *thd, USER_CONN *uc) |
237 | { |
238 | ulonglong check_time= thd->start_utime; |
239 | DBUG_ENTER("time_out_user_resource_limits" ); |
240 | |
241 | /* If more than a hour since last check, reset resource checking */ |
242 | if (check_time - uc->reset_utime >= 3600000000ULL) |
243 | { |
244 | uc->questions=0; |
245 | uc->updates=0; |
246 | uc->conn_per_hour=0; |
247 | uc->reset_utime= check_time; |
248 | } |
249 | |
250 | DBUG_VOID_RETURN; |
251 | } |
252 | |
253 | /* |
254 | Check if maximum queries per hour limit has been reached |
255 | returns 0 if OK. |
256 | */ |
257 | |
258 | bool check_mqh(THD *thd, uint check_command) |
259 | { |
260 | bool error= 0; |
261 | USER_CONN *uc=thd->user_connect; |
262 | DBUG_ENTER("check_mqh" ); |
263 | DBUG_ASSERT(uc != 0); |
264 | |
265 | mysql_mutex_lock(&LOCK_user_conn); |
266 | |
267 | time_out_user_resource_limits(thd, uc); |
268 | |
269 | /* Check that we have not done too many questions / hour */ |
270 | if (uc->user_resources.questions && |
271 | uc->questions++ >= uc->user_resources.questions) |
272 | { |
273 | my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_queries_per_hour" , |
274 | (long) uc->user_resources.questions); |
275 | error=1; |
276 | goto end; |
277 | } |
278 | if (check_command < (uint) SQLCOM_END) |
279 | { |
280 | /* Check that we have not done too many updates / hour */ |
281 | if (uc->user_resources.updates && |
282 | (sql_command_flags[check_command] & CF_CHANGES_DATA) && |
283 | uc->updates++ >= uc->user_resources.updates) |
284 | { |
285 | my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_updates_per_hour" , |
286 | (long) uc->user_resources.updates); |
287 | error=1; |
288 | goto end; |
289 | } |
290 | } |
291 | end: |
292 | mysql_mutex_unlock(&LOCK_user_conn); |
293 | DBUG_RETURN(error); |
294 | } |
295 | |
296 | #endif /* NO_EMBEDDED_ACCESS_CHECKS */ |
297 | |
298 | /* |
299 | Check for maximum allowable user connections, if the mysqld server is |
300 | started with corresponding variable that is greater then 0. |
301 | */ |
302 | |
303 | extern "C" uchar *get_key_conn(user_conn *buff, size_t *length, |
304 | my_bool not_used __attribute__((unused))) |
305 | { |
306 | *length= buff->len; |
307 | return (uchar*) buff->user; |
308 | } |
309 | |
310 | |
311 | extern "C" void free_user(struct user_conn *uc) |
312 | { |
313 | my_free(uc); |
314 | } |
315 | |
316 | |
317 | void init_max_user_conn(void) |
318 | { |
319 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
320 | my_hash_init(&hash_user_connections, system_charset_info, max_connections, |
321 | 0, 0, (my_hash_get_key) get_key_conn, |
322 | (my_hash_free_key) free_user, 0); |
323 | #endif |
324 | } |
325 | |
326 | |
327 | void free_max_user_conn(void) |
328 | { |
329 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
330 | my_hash_free(&hash_user_connections); |
331 | #endif /* NO_EMBEDDED_ACCESS_CHECKS */ |
332 | } |
333 | |
334 | |
335 | void reset_mqh(LEX_USER *lu, bool get_them= 0) |
336 | { |
337 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
338 | mysql_mutex_lock(&LOCK_user_conn); |
339 | if (lu) // for GRANT |
340 | { |
341 | USER_CONN *uc; |
342 | size_t temp_len=lu->user.length+lu->host.length+2; |
343 | char temp_user[USER_HOST_BUFF_SIZE]; |
344 | |
345 | memcpy(temp_user,lu->user.str,lu->user.length); |
346 | memcpy(temp_user+lu->user.length+1,lu->host.str,lu->host.length); |
347 | temp_user[lu->user.length]='\0'; temp_user[temp_len-1]=0; |
348 | if ((uc = (struct user_conn *) my_hash_search(&hash_user_connections, |
349 | (uchar*) temp_user, |
350 | temp_len))) |
351 | { |
352 | uc->questions=0; |
353 | get_mqh(temp_user,&temp_user[lu->user.length+1],uc); |
354 | uc->updates=0; |
355 | uc->conn_per_hour=0; |
356 | } |
357 | } |
358 | else |
359 | { |
360 | /* for FLUSH PRIVILEGES and FLUSH USER_RESOURCES */ |
361 | for (uint idx=0;idx < hash_user_connections.records; idx++) |
362 | { |
363 | USER_CONN *uc=(struct user_conn *) |
364 | my_hash_element(&hash_user_connections, idx); |
365 | if (get_them) |
366 | get_mqh(uc->user,uc->host,uc); |
367 | uc->questions=0; |
368 | uc->updates=0; |
369 | uc->conn_per_hour=0; |
370 | } |
371 | } |
372 | mysql_mutex_unlock(&LOCK_user_conn); |
373 | #endif /* NO_EMBEDDED_ACCESS_CHECKS */ |
374 | } |
375 | |
376 | /***************************************************************************** |
377 | Handle users statistics |
378 | *****************************************************************************/ |
379 | |
380 | /* 'mysql_system_user' is used for when the user is not defined for a THD. */ |
381 | static const char mysql_system_user[]= "#mysql_system#" ; |
382 | |
383 | // Returns 'user' if it's not NULL. Returns 'mysql_system_user' otherwise. |
384 | static const char * get_valid_user_string(const char* user) |
385 | { |
386 | return user ? user : mysql_system_user; |
387 | } |
388 | |
389 | /* |
390 | Returns string as 'IP' for the client-side of the connection represented by |
391 | 'client'. Does not allocate memory. May return "". |
392 | */ |
393 | |
394 | static const char *get_client_host(THD *client) |
395 | { |
396 | return client->security_ctx->host_or_ip[0] ? |
397 | client->security_ctx->host_or_ip : |
398 | client->security_ctx->host ? client->security_ctx->host : "" ; |
399 | } |
400 | |
401 | extern "C" uchar *get_key_user_stats(USER_STATS *user_stats, size_t *length, |
402 | my_bool not_used __attribute__((unused))) |
403 | { |
404 | *length= user_stats->user_name_length; |
405 | return (uchar*) user_stats->user; |
406 | } |
407 | |
408 | void free_user_stats(USER_STATS* user_stats) |
409 | { |
410 | my_free(user_stats); |
411 | } |
412 | |
413 | void init_user_stats(USER_STATS *user_stats, |
414 | const char *user, |
415 | size_t user_length, |
416 | const char *priv_user, |
417 | uint total_connections, |
418 | uint total_ssl_connections, |
419 | uint concurrent_connections, |
420 | time_t connected_time, |
421 | double busy_time, |
422 | double cpu_time, |
423 | ulonglong bytes_received, |
424 | ulonglong bytes_sent, |
425 | ulonglong binlog_bytes_written, |
426 | ha_rows rows_sent, |
427 | ha_rows rows_read, |
428 | ha_rows rows_inserted, |
429 | ha_rows rows_deleted, |
430 | ha_rows rows_updated, |
431 | ulonglong select_commands, |
432 | ulonglong update_commands, |
433 | ulonglong other_commands, |
434 | ulonglong commit_trans, |
435 | ulonglong rollback_trans, |
436 | ulonglong denied_connections, |
437 | ulonglong lost_connections, |
438 | ulonglong max_statement_time_exceeded, |
439 | ulonglong access_denied_errors, |
440 | ulonglong empty_queries) |
441 | { |
442 | DBUG_ENTER("init_user_stats" ); |
443 | DBUG_PRINT("enter" , ("user: %s priv_user: %s" , user, priv_user)); |
444 | |
445 | user_length= MY_MIN(user_length, sizeof(user_stats->user)-1); |
446 | memcpy(user_stats->user, user, user_length); |
447 | user_stats->user[user_length]= 0; |
448 | user_stats->user_name_length= (uint)user_length; |
449 | strmake_buf(user_stats->priv_user, priv_user); |
450 | |
451 | user_stats->total_connections= total_connections; |
452 | user_stats->total_ssl_connections= total_ssl_connections; |
453 | user_stats->concurrent_connections= concurrent_connections; |
454 | user_stats->connected_time= connected_time; |
455 | user_stats->busy_time= busy_time; |
456 | user_stats->cpu_time= cpu_time; |
457 | user_stats->bytes_received= bytes_received; |
458 | user_stats->bytes_sent= bytes_sent; |
459 | user_stats->binlog_bytes_written= binlog_bytes_written; |
460 | user_stats->rows_sent= rows_sent; |
461 | user_stats->rows_read= rows_read; |
462 | user_stats->rows_inserted= rows_inserted; |
463 | user_stats->rows_deleted= rows_deleted; |
464 | user_stats->rows_updated= rows_updated; |
465 | user_stats->select_commands= select_commands; |
466 | user_stats->update_commands= update_commands; |
467 | user_stats->other_commands= other_commands; |
468 | user_stats->commit_trans= commit_trans; |
469 | user_stats->rollback_trans= rollback_trans; |
470 | user_stats->denied_connections= denied_connections; |
471 | user_stats->lost_connections= lost_connections; |
472 | user_stats->max_statement_time_exceeded= max_statement_time_exceeded; |
473 | user_stats->access_denied_errors= access_denied_errors; |
474 | user_stats->empty_queries= empty_queries; |
475 | DBUG_VOID_RETURN; |
476 | } |
477 | |
478 | |
479 | void init_global_user_stats(void) |
480 | { |
481 | my_hash_init(&global_user_stats, system_charset_info, max_connections, |
482 | 0, 0, (my_hash_get_key) get_key_user_stats, |
483 | (my_hash_free_key) free_user_stats, 0); |
484 | } |
485 | |
486 | void init_global_client_stats(void) |
487 | { |
488 | my_hash_init(&global_client_stats, system_charset_info, max_connections, |
489 | 0, 0, (my_hash_get_key) get_key_user_stats, |
490 | (my_hash_free_key) free_user_stats, 0); |
491 | } |
492 | |
493 | extern "C" uchar *get_key_table_stats(TABLE_STATS *table_stats, size_t *length, |
494 | my_bool not_used __attribute__((unused))) |
495 | { |
496 | *length= table_stats->table_name_length; |
497 | return (uchar*) table_stats->table; |
498 | } |
499 | |
500 | extern "C" void free_table_stats(TABLE_STATS* table_stats) |
501 | { |
502 | my_free(table_stats); |
503 | } |
504 | |
505 | void init_global_table_stats(void) |
506 | { |
507 | my_hash_init(&global_table_stats, system_charset_info, max_connections, |
508 | 0, 0, (my_hash_get_key) get_key_table_stats, |
509 | (my_hash_free_key) free_table_stats, 0); |
510 | } |
511 | |
512 | extern "C" uchar *get_key_index_stats(INDEX_STATS *index_stats, size_t *length, |
513 | my_bool not_used __attribute__((unused))) |
514 | { |
515 | *length= index_stats->index_name_length; |
516 | return (uchar*) index_stats->index; |
517 | } |
518 | |
519 | extern "C" void free_index_stats(INDEX_STATS* index_stats) |
520 | { |
521 | my_free(index_stats); |
522 | } |
523 | |
524 | void init_global_index_stats(void) |
525 | { |
526 | my_hash_init(&global_index_stats, system_charset_info, max_connections, |
527 | 0, 0, (my_hash_get_key) get_key_index_stats, |
528 | (my_hash_free_key) free_index_stats, 0); |
529 | } |
530 | |
531 | |
532 | void free_global_user_stats(void) |
533 | { |
534 | my_hash_free(&global_user_stats); |
535 | } |
536 | |
537 | void free_global_table_stats(void) |
538 | { |
539 | my_hash_free(&global_table_stats); |
540 | } |
541 | |
542 | void free_global_index_stats(void) |
543 | { |
544 | my_hash_free(&global_index_stats); |
545 | } |
546 | |
547 | void free_global_client_stats(void) |
548 | { |
549 | my_hash_free(&global_client_stats); |
550 | } |
551 | |
552 | /* |
553 | Increments the global stats connection count for an entry from |
554 | global_client_stats or global_user_stats. Returns 0 on success |
555 | and 1 on error. |
556 | */ |
557 | |
558 | static bool increment_count_by_name(const char *name, size_t name_length, |
559 | const char *role_name, |
560 | HASH *users_or_clients, THD *thd) |
561 | { |
562 | USER_STATS *user_stats; |
563 | |
564 | if (!(user_stats= (USER_STATS*) my_hash_search(users_or_clients, (uchar*) name, |
565 | name_length))) |
566 | { |
567 | /* First connection for this user or client */ |
568 | if (!(user_stats= ((USER_STATS*) |
569 | my_malloc(sizeof(USER_STATS), |
570 | MYF(MY_WME | MY_ZEROFILL))))) |
571 | return TRUE; // Out of memory |
572 | |
573 | init_user_stats(user_stats, name, name_length, role_name, |
574 | 0, 0, 0, // connections |
575 | 0, 0, 0, // time |
576 | 0, 0, 0, // bytes sent, received and written |
577 | 0, 0, // rows sent and read |
578 | 0, 0, 0, // rows inserted, deleted and updated |
579 | 0, 0, 0, // select, update and other commands |
580 | 0, 0, // commit and rollback trans |
581 | thd->status_var.access_denied_errors, |
582 | 0, // lost connections |
583 | 0, // max query timeouts |
584 | 0, // access denied errors |
585 | 0); // empty queries |
586 | |
587 | if (my_hash_insert(users_or_clients, (uchar*)user_stats)) |
588 | { |
589 | my_free(user_stats); |
590 | return TRUE; // Out of memory |
591 | } |
592 | } |
593 | user_stats->total_connections++; |
594 | if (thd->net.vio && thd->net.vio->type == VIO_TYPE_SSL) |
595 | user_stats->total_ssl_connections++; |
596 | return FALSE; |
597 | } |
598 | |
599 | |
600 | /* |
601 | Increments the global user and client stats connection count. |
602 | |
603 | @param use_lock if true, LOCK_global_user_client_stats will be locked |
604 | |
605 | @retval 0 ok |
606 | @retval 1 error. |
607 | */ |
608 | |
609 | #ifndef EMBEDDED_LIBRARY |
610 | static bool increment_connection_count(THD* thd, bool use_lock) |
611 | { |
612 | const char *user_string= get_valid_user_string(thd->main_security_ctx.user); |
613 | const char *client_string= get_client_host(thd); |
614 | bool return_value= FALSE; |
615 | |
616 | if (!thd->userstat_running) |
617 | return FALSE; |
618 | |
619 | if (use_lock) |
620 | mysql_mutex_lock(&LOCK_global_user_client_stats); |
621 | |
622 | if (increment_count_by_name(user_string, strlen(user_string), user_string, |
623 | &global_user_stats, thd)) |
624 | { |
625 | return_value= TRUE; |
626 | goto end; |
627 | } |
628 | if (increment_count_by_name(client_string, strlen(client_string), |
629 | user_string, &global_client_stats, thd)) |
630 | { |
631 | return_value= TRUE; |
632 | goto end; |
633 | } |
634 | |
635 | end: |
636 | if (use_lock) |
637 | mysql_mutex_unlock(&LOCK_global_user_client_stats); |
638 | return return_value; |
639 | } |
640 | #endif |
641 | |
642 | /* |
643 | Used to update the global user and client stats |
644 | */ |
645 | |
646 | static void update_global_user_stats_with_user(THD *thd, |
647 | USER_STATS *user_stats, |
648 | time_t now) |
649 | { |
650 | DBUG_ASSERT(thd->userstat_running); |
651 | |
652 | user_stats->connected_time+= now - thd->last_global_update_time; |
653 | user_stats->busy_time+= (thd->status_var.busy_time - |
654 | thd->org_status_var.busy_time); |
655 | user_stats->cpu_time+= (thd->status_var.cpu_time - |
656 | thd->org_status_var.cpu_time); |
657 | /* |
658 | This is handle specially as bytes_received is incremented BEFORE |
659 | org_status_var is copied. |
660 | */ |
661 | user_stats->bytes_received+= (thd->org_status_var.bytes_received- |
662 | thd->start_bytes_received); |
663 | user_stats->bytes_sent+= (thd->status_var.bytes_sent - |
664 | thd->org_status_var.bytes_sent); |
665 | user_stats->binlog_bytes_written+= |
666 | (thd->status_var.binlog_bytes_written - |
667 | thd->org_status_var.binlog_bytes_written); |
668 | /* We are not counting rows in internal temporary tables here ! */ |
669 | user_stats->rows_read+= (thd->status_var.rows_read - |
670 | thd->org_status_var.rows_read); |
671 | user_stats->rows_sent+= (thd->status_var.rows_sent - |
672 | thd->org_status_var.rows_sent); |
673 | user_stats->rows_inserted+= (thd->status_var.ha_write_count - |
674 | thd->org_status_var.ha_write_count); |
675 | user_stats->rows_deleted+= (thd->status_var.ha_delete_count - |
676 | thd->org_status_var.ha_delete_count); |
677 | user_stats->rows_updated+= (thd->status_var.ha_update_count - |
678 | thd->org_status_var.ha_update_count); |
679 | user_stats->select_commands+= thd->select_commands; |
680 | user_stats->update_commands+= thd->update_commands; |
681 | user_stats->other_commands+= thd->other_commands; |
682 | user_stats->commit_trans+= (thd->status_var.ha_commit_count - |
683 | thd->org_status_var.ha_commit_count); |
684 | user_stats->rollback_trans+= (thd->status_var.ha_rollback_count + |
685 | thd->status_var.ha_savepoint_rollback_count - |
686 | thd->org_status_var.ha_rollback_count - |
687 | thd->org_status_var. |
688 | ha_savepoint_rollback_count); |
689 | user_stats->access_denied_errors+= |
690 | (thd->status_var.access_denied_errors - |
691 | thd->org_status_var.access_denied_errors); |
692 | user_stats->empty_queries+= (thd->status_var.empty_queries - |
693 | thd->org_status_var.empty_queries); |
694 | |
695 | /* The following can only contain 0 or 1 and then connection ends */ |
696 | user_stats->denied_connections+= thd->status_var.access_denied_errors; |
697 | user_stats->lost_connections+= thd->status_var.lost_connections; |
698 | user_stats->max_statement_time_exceeded+= thd->status_var.max_statement_time_exceeded; |
699 | } |
700 | |
701 | |
702 | /* Updates the global stats of a user or client */ |
703 | void update_global_user_stats(THD *thd, bool create_user, time_t now) |
704 | { |
705 | const char *user_string, *client_string; |
706 | USER_STATS *user_stats; |
707 | size_t user_string_length, client_string_length; |
708 | DBUG_ASSERT(thd->userstat_running); |
709 | |
710 | user_string= get_valid_user_string(thd->main_security_ctx.user); |
711 | user_string_length= strlen(user_string); |
712 | client_string= get_client_host(thd); |
713 | client_string_length= strlen(client_string); |
714 | |
715 | mysql_mutex_lock(&LOCK_global_user_client_stats); |
716 | |
717 | // Update by user name |
718 | if ((user_stats= (USER_STATS*) my_hash_search(&global_user_stats, |
719 | (uchar*) user_string, |
720 | user_string_length))) |
721 | { |
722 | /* Found user. */ |
723 | update_global_user_stats_with_user(thd, user_stats, now); |
724 | } |
725 | else |
726 | { |
727 | /* Create the entry */ |
728 | if (create_user) |
729 | { |
730 | increment_count_by_name(user_string, user_string_length, user_string, |
731 | &global_user_stats, thd); |
732 | } |
733 | } |
734 | |
735 | /* Update by client IP */ |
736 | if ((user_stats= (USER_STATS*)my_hash_search(&global_client_stats, |
737 | (uchar*) client_string, |
738 | client_string_length))) |
739 | { |
740 | // Found by client IP |
741 | update_global_user_stats_with_user(thd, user_stats, now); |
742 | } |
743 | else |
744 | { |
745 | // Create the entry |
746 | if (create_user) |
747 | { |
748 | increment_count_by_name(client_string, client_string_length, |
749 | user_string, &global_client_stats, thd); |
750 | } |
751 | } |
752 | /* Reset variables only used for counting */ |
753 | thd->select_commands= thd->update_commands= thd->other_commands= 0; |
754 | thd->last_global_update_time= now; |
755 | |
756 | mysql_mutex_unlock(&LOCK_global_user_client_stats); |
757 | } |
758 | |
759 | |
760 | /** |
761 | Set thread character set variables from the given ID |
762 | |
763 | @param thd thread handle |
764 | @param cs_number character set and collation ID |
765 | |
766 | @retval 0 OK; character_set_client, collation_connection and |
767 | character_set_results are set to the new value, |
768 | or to the default global values. |
769 | |
770 | @retval 1 error, e.g. the given ID is not supported by parser. |
771 | Corresponding SQL error is sent. |
772 | */ |
773 | |
774 | bool thd_init_client_charset(THD *thd, uint cs_number) |
775 | { |
776 | CHARSET_INFO *cs; |
777 | /* |
778 | Use server character set and collation if |
779 | - opt_character_set_client_handshake is not set |
780 | - client has not specified a character set |
781 | - client character set doesn't exists in server |
782 | */ |
783 | if (!opt_character_set_client_handshake || |
784 | !(cs= get_charset(cs_number, MYF(0)))) |
785 | { |
786 | thd->update_charset(global_system_variables.character_set_client, |
787 | global_system_variables.collation_connection, |
788 | global_system_variables.character_set_results); |
789 | } |
790 | else |
791 | { |
792 | if (!is_supported_parser_charset(cs)) |
793 | { |
794 | /* Disallow non-supported parser character sets: UCS2, UTF16, UTF32 */ |
795 | my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "character_set_client" , |
796 | cs->csname); |
797 | return true; |
798 | } |
799 | thd->update_charset(cs,cs,cs); |
800 | } |
801 | return false; |
802 | } |
803 | |
804 | |
805 | /* |
806 | Initialize connection threads |
807 | */ |
808 | |
809 | #ifndef EMBEDDED_LIBRARY |
810 | bool init_new_connection_handler_thread() |
811 | { |
812 | pthread_detach_this_thread(); |
813 | if (my_thread_init()) |
814 | { |
815 | statistic_increment(aborted_connects,&LOCK_status); |
816 | statistic_increment(connection_errors_internal, &LOCK_status); |
817 | return 1; |
818 | } |
819 | DBUG_EXECUTE_IF("simulate_failed_connection_1" , return(1); ); |
820 | return 0; |
821 | } |
822 | |
823 | /** |
824 | Set client address during authentication. |
825 | |
826 | Initializes THD::main_security_ctx and THD::peer_port. |
827 | Optionally does ip to hostname translation. |
828 | |
829 | @param thd current THD handle |
830 | @param addr peer address (can be NULL, if 'ip' is set) |
831 | @param ip peer address as string (can be NULL if 'addr' is set) |
832 | @param port peer port |
833 | @param check_proxy_networks if true, and host is in |
834 | 'proxy_protocol_networks' list, skip |
835 | "host not privileged" check |
836 | @param[out] host_errors - number of connect |
837 | errors for this host |
838 | |
839 | @retval 0 ok, 1 error |
840 | */ |
841 | int thd_set_peer_addr(THD *thd, |
842 | sockaddr_storage *addr, |
843 | const char *ip, |
844 | uint port, |
845 | bool check_proxy_networks, |
846 | uint *host_errors) |
847 | { |
848 | *host_errors= 0; |
849 | |
850 | thd->peer_port= port; |
851 | |
852 | char ip_string[128]; |
853 | if (!ip) |
854 | { |
855 | void *addr_data; |
856 | if (addr->ss_family == AF_UNIX) |
857 | { |
858 | /* local connection */ |
859 | my_free((void *)thd->main_security_ctx.ip); |
860 | thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host = my_localhost; |
861 | thd->main_security_ctx.ip= 0; |
862 | return 0; |
863 | } |
864 | else if (addr->ss_family == AF_INET) |
865 | addr_data= &((struct sockaddr_in *)addr)->sin_addr; |
866 | else |
867 | addr_data= &((struct sockaddr_in6 *)addr)->sin6_addr; |
868 | if (!inet_ntop(addr->ss_family,addr_data, ip_string, sizeof(ip_string))) |
869 | { |
870 | DBUG_ASSERT(0); |
871 | return 1; |
872 | } |
873 | ip= ip_string; |
874 | } |
875 | |
876 | my_free((void *)thd->main_security_ctx.ip); |
877 | if (!(thd->main_security_ctx.ip = my_strdup(ip, MYF(MY_WME)))) |
878 | { |
879 | /* |
880 | No error accounting per IP in host_cache, |
881 | this is treated as a global server OOM error. |
882 | TODO: remove the need for my_strdup. |
883 | */ |
884 | statistic_increment(aborted_connects, &LOCK_status); |
885 | statistic_increment(connection_errors_internal, &LOCK_status); |
886 | return 1; /* The error is set by my_strdup(). */ |
887 | } |
888 | thd->main_security_ctx.host_or_ip = thd->main_security_ctx.ip; |
889 | if (!(specialflag & SPECIAL_NO_RESOLVE)) |
890 | { |
891 | int rc; |
892 | |
893 | rc = ip_to_hostname(addr, |
894 | thd->main_security_ctx.ip, |
895 | &thd->main_security_ctx.host, |
896 | host_errors); |
897 | |
898 | /* Cut very long hostnames to avoid possible overflows */ |
899 | if (thd->main_security_ctx.host) |
900 | { |
901 | if (thd->main_security_ctx.host != my_localhost) |
902 | ((char*)thd->main_security_ctx.host)[MY_MIN(strlen(thd->main_security_ctx.host), |
903 | HOSTNAME_LENGTH)] = 0; |
904 | thd->main_security_ctx.host_or_ip = thd->main_security_ctx.host; |
905 | } |
906 | |
907 | if (rc == RC_BLOCKED_HOST) |
908 | { |
909 | /* HOST_CACHE stats updated by ip_to_hostname(). */ |
910 | my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip); |
911 | return 1; |
912 | } |
913 | } |
914 | DBUG_PRINT("info" , ("Host: %s ip: %s" , |
915 | (thd->main_security_ctx.host ? |
916 | thd->main_security_ctx.host : "unknown host" ), |
917 | (thd->main_security_ctx.ip ? |
918 | thd->main_security_ctx.ip : "unknown ip" ))); |
919 | if ((!check_proxy_networks || !is_proxy_protocol_allowed((struct sockaddr *) addr)) |
920 | && acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip)) |
921 | { |
922 | /* HOST_CACHE stats updated by acl_check_host(). */ |
923 | my_error(ER_HOST_NOT_PRIVILEGED, MYF(0), |
924 | thd->main_security_ctx.host_or_ip); |
925 | return 1; |
926 | } |
927 | return 0; |
928 | } |
929 | |
930 | /* |
931 | Perform handshake, authorize client and update thd ACL variables. |
932 | |
933 | SYNOPSIS |
934 | check_connection() |
935 | thd thread handle |
936 | |
937 | RETURN |
938 | 0 success, thd is updated. |
939 | 1 error |
940 | */ |
941 | |
942 | static int check_connection(THD *thd) |
943 | { |
944 | uint connect_errors= 0; |
945 | int auth_rc; |
946 | NET *net= &thd->net; |
947 | |
948 | DBUG_PRINT("info" , |
949 | ("New connection received on %s" , vio_description(net->vio))); |
950 | |
951 | #ifdef SIGNAL_WITH_VIO_CLOSE |
952 | thd->set_active_vio(net->vio); |
953 | #endif |
954 | |
955 | if (!thd->main_security_ctx.host) // If TCP/IP connection |
956 | { |
957 | my_bool peer_rc; |
958 | char ip[NI_MAXHOST]; |
959 | uint16 peer_port; |
960 | |
961 | peer_rc= vio_peer_addr(net->vio, ip, &peer_port, NI_MAXHOST); |
962 | |
963 | /* |
964 | =========================================================================== |
965 | DEBUG code only (begin) |
966 | Simulate various output from vio_peer_addr(). |
967 | =========================================================================== |
968 | */ |
969 | |
970 | DBUG_EXECUTE_IF("vio_peer_addr_error" , |
971 | { |
972 | peer_rc= 1; |
973 | } |
974 | ); |
975 | DBUG_EXECUTE_IF("vio_peer_addr_fake_ipv4" , |
976 | { |
977 | struct sockaddr *sa= (sockaddr *) &net->vio->remote; |
978 | sa->sa_family= AF_INET; |
979 | struct in_addr *ip4= &((struct sockaddr_in *) sa)->sin_addr; |
980 | /* See RFC 5737, 192.0.2.0/24 is reserved. */ |
981 | const char* fake= "192.0.2.4" ; |
982 | inet_pton(AF_INET,fake, ip4); |
983 | strcpy(ip, fake); |
984 | peer_rc= 0; |
985 | } |
986 | ); |
987 | |
988 | #ifdef HAVE_IPV6 |
989 | DBUG_EXECUTE_IF("vio_peer_addr_fake_ipv6" , |
990 | { |
991 | struct sockaddr_in6 *sa= (sockaddr_in6 *) &net->vio->remote; |
992 | sa->sin6_family= AF_INET6; |
993 | struct in6_addr *ip6= & sa->sin6_addr; |
994 | /* See RFC 3849, ipv6 2001:DB8::/32 is reserved. */ |
995 | const char* fake= "2001:db8::6:6" ; |
996 | /* inet_pton(AF_INET6, fake, ip6); not available on Windows XP. */ |
997 | ip6->s6_addr[ 0] = 0x20; |
998 | ip6->s6_addr[ 1] = 0x01; |
999 | ip6->s6_addr[ 2] = 0x0d; |
1000 | ip6->s6_addr[ 3] = 0xb8; |
1001 | ip6->s6_addr[ 4] = 0x00; |
1002 | ip6->s6_addr[ 5] = 0x00; |
1003 | ip6->s6_addr[ 6] = 0x00; |
1004 | ip6->s6_addr[ 7] = 0x00; |
1005 | ip6->s6_addr[ 8] = 0x00; |
1006 | ip6->s6_addr[ 9] = 0x00; |
1007 | ip6->s6_addr[10] = 0x00; |
1008 | ip6->s6_addr[11] = 0x00; |
1009 | ip6->s6_addr[12] = 0x00; |
1010 | ip6->s6_addr[13] = 0x06; |
1011 | ip6->s6_addr[14] = 0x00; |
1012 | ip6->s6_addr[15] = 0x06; |
1013 | strcpy(ip, fake); |
1014 | peer_rc= 0; |
1015 | } |
1016 | ); |
1017 | #endif /* HAVE_IPV6 */ |
1018 | |
1019 | /* |
1020 | =========================================================================== |
1021 | DEBUG code only (end) |
1022 | =========================================================================== |
1023 | */ |
1024 | |
1025 | if (peer_rc) |
1026 | { |
1027 | /* |
1028 | Since we can not even get the peer IP address, |
1029 | there is nothing to show in the host_cache, |
1030 | so increment the global status variable for peer address errors. |
1031 | */ |
1032 | statistic_increment(connection_errors_peer_addr, &LOCK_status); |
1033 | my_error(ER_BAD_HOST_ERROR, MYF(0)); |
1034 | return 1; |
1035 | } |
1036 | |
1037 | if (thd_set_peer_addr(thd, &net->vio->remote, ip, peer_port, |
1038 | true, &connect_errors)) |
1039 | return 1; |
1040 | } |
1041 | else /* Hostname given means that the connection was on a socket */ |
1042 | { |
1043 | DBUG_PRINT("info" ,("Host: %s" , thd->main_security_ctx.host)); |
1044 | thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host; |
1045 | thd->main_security_ctx.ip= 0; |
1046 | /* Reset sin_addr */ |
1047 | bzero((char*) &net->vio->remote, sizeof(net->vio->remote)); |
1048 | } |
1049 | vio_keepalive(net->vio, TRUE); |
1050 | vio_set_keepalive_options(net->vio, &opt_vio_keepalive); |
1051 | |
1052 | if (unlikely(thd->packet.alloc(thd->variables.net_buffer_length))) |
1053 | { |
1054 | /* |
1055 | Important note: |
1056 | net_buffer_length is a SESSION variable, |
1057 | so it may be tempting to account OOM conditions per IP in the HOST_CACHE, |
1058 | in case some clients are more demanding than others ... |
1059 | However, this session variable is *not* initialized with a per client |
1060 | value during the initial connection, it is initialized from the |
1061 | GLOBAL net_buffer_length variable from the server. |
1062 | Hence, there is no reason to account on OOM conditions per client IP, |
1063 | we count failures in the global server status instead. |
1064 | */ |
1065 | statistic_increment(aborted_connects,&LOCK_status); |
1066 | statistic_increment(connection_errors_internal, &LOCK_status); |
1067 | return 1; /* The error is set by alloc(). */ |
1068 | } |
1069 | |
1070 | auth_rc= acl_authenticate(thd, 0); |
1071 | if (auth_rc == 0 && connect_errors != 0) |
1072 | { |
1073 | /* |
1074 | A client connection from this IP was successful, |
1075 | after some previous failures. |
1076 | Reset the connection error counter. |
1077 | */ |
1078 | reset_host_connect_errors(thd->main_security_ctx.ip); |
1079 | } |
1080 | |
1081 | return auth_rc; |
1082 | } |
1083 | |
1084 | |
1085 | /* |
1086 | Setup thread to be used with the current thread |
1087 | |
1088 | SYNOPSIS |
1089 | bool setup_connection_thread_globals() |
1090 | thd Thread/connection handler |
1091 | |
1092 | RETURN |
1093 | 0 ok |
1094 | 1 Error (out of memory) |
1095 | In this case we will close the connection and increment status |
1096 | */ |
1097 | |
1098 | bool setup_connection_thread_globals(THD *thd) |
1099 | { |
1100 | if (thd->store_globals()) |
1101 | { |
1102 | close_connection(thd, ER_OUT_OF_RESOURCES); |
1103 | statistic_increment(aborted_connects,&LOCK_status); |
1104 | statistic_increment(connection_errors_internal, &LOCK_status); |
1105 | thd->scheduler->end_thread(thd, 0); |
1106 | return 1; // Error |
1107 | } |
1108 | return 0; |
1109 | } |
1110 | |
1111 | |
1112 | /* |
1113 | Autenticate user, with error reporting |
1114 | |
1115 | SYNOPSIS |
1116 | login_connection() |
1117 | thd Thread handler |
1118 | |
1119 | NOTES |
1120 | Connection is not closed in case of errors |
1121 | |
1122 | RETURN |
1123 | 0 ok |
1124 | 1 error |
1125 | */ |
1126 | |
1127 | bool login_connection(THD *thd) |
1128 | { |
1129 | NET *net= &thd->net; |
1130 | int error= 0; |
1131 | DBUG_ENTER("login_connection" ); |
1132 | DBUG_PRINT("info" , ("login_connection called by thread %lu" , |
1133 | (ulong) thd->thread_id)); |
1134 | |
1135 | /* Use "connect_timeout" value during connection phase */ |
1136 | my_net_set_read_timeout(net, connect_timeout); |
1137 | my_net_set_write_timeout(net, connect_timeout); |
1138 | |
1139 | error= check_connection(thd); |
1140 | thd->protocol->end_statement(); |
1141 | |
1142 | if (unlikely(error)) |
1143 | { // Wrong permissions |
1144 | #ifdef _WIN32 |
1145 | if (vio_type(net->vio) == VIO_TYPE_NAMEDPIPE) |
1146 | my_sleep(1000); /* must wait after eof() */ |
1147 | #endif |
1148 | statistic_increment(aborted_connects,&LOCK_status); |
1149 | error=1; |
1150 | goto exit; |
1151 | } |
1152 | /* Connect completed, set read/write timeouts back to default */ |
1153 | my_net_set_read_timeout(net, thd->variables.net_read_timeout); |
1154 | my_net_set_write_timeout(net, thd->variables.net_write_timeout); |
1155 | |
1156 | /* Updates global user connection stats. */ |
1157 | if (increment_connection_count(thd, TRUE)) |
1158 | { |
1159 | my_error(ER_OUTOFMEMORY, MYF(0), (int) (2*sizeof(USER_STATS))); |
1160 | error= 1; |
1161 | goto exit; |
1162 | } |
1163 | |
1164 | exit: |
1165 | mysql_audit_notify_connection_connect(thd); |
1166 | DBUG_RETURN(error); |
1167 | } |
1168 | |
1169 | |
1170 | /* |
1171 | Close an established connection |
1172 | |
1173 | NOTES |
1174 | This mainly updates status variables |
1175 | */ |
1176 | |
1177 | void end_connection(THD *thd) |
1178 | { |
1179 | NET *net= &thd->net; |
1180 | #ifdef WITH_WSREP |
1181 | if (WSREP(thd)) |
1182 | { |
1183 | wsrep_status_t rcode= wsrep->free_connection(wsrep, thd->thread_id); |
1184 | if (rcode) { |
1185 | WSREP_WARN("wsrep failed to free connection context: %lld code: %d" , |
1186 | (longlong) thd->thread_id, rcode); |
1187 | } |
1188 | } |
1189 | thd->wsrep_client_thread= 0; |
1190 | #endif |
1191 | plugin_thdvar_cleanup(thd); |
1192 | |
1193 | if (thd->user_connect) |
1194 | { |
1195 | /* |
1196 | We decrease this variable early to make it easy to log again quickly. |
1197 | This code is not critical as we will in any case do this test |
1198 | again in thd->cleanup() |
1199 | */ |
1200 | decrease_user_connections(thd->user_connect); |
1201 | /* |
1202 | The thread may returned back to the pool and assigned to a user |
1203 | that doesn't have a limit. Ensure the user is not using resources |
1204 | of someone else. |
1205 | */ |
1206 | thd->user_connect= NULL; |
1207 | } |
1208 | |
1209 | if (unlikely(thd->killed) || (net->error && net->vio != 0)) |
1210 | { |
1211 | statistic_increment(aborted_threads,&LOCK_status); |
1212 | status_var_increment(thd->status_var.lost_connections); |
1213 | } |
1214 | |
1215 | if (likely(!thd->killed) && (net->error && net->vio != 0)) |
1216 | thd->print_aborted_warning(1, thd->get_stmt_da()->is_error() |
1217 | ? thd->get_stmt_da()->message() : ER_THD(thd, ER_UNKNOWN_ERROR)); |
1218 | } |
1219 | |
1220 | |
1221 | /* |
1222 | Initialize THD to handle queries |
1223 | */ |
1224 | |
1225 | void prepare_new_connection_state(THD* thd) |
1226 | { |
1227 | Security_context *sctx= thd->security_ctx; |
1228 | |
1229 | if (thd->client_capabilities & CLIENT_COMPRESS) |
1230 | thd->net.compress=1; // Use compression |
1231 | |
1232 | /* |
1233 | Much of this is duplicated in create_embedded_thd() for the |
1234 | embedded server library. |
1235 | TODO: refactor this to avoid code duplication there |
1236 | */ |
1237 | thd->proc_info= 0; |
1238 | thd->set_command(COM_SLEEP); |
1239 | thd->init_for_queries(); |
1240 | |
1241 | if (opt_init_connect.length && !(sctx->master_access & SUPER_ACL)) |
1242 | { |
1243 | execute_init_command(thd, &opt_init_connect, &LOCK_sys_init_connect); |
1244 | if (unlikely(thd->is_error())) |
1245 | { |
1246 | Host_errors errors; |
1247 | thd->set_killed(KILL_CONNECTION); |
1248 | thd->print_aborted_warning(0, "init_connect command failed" ); |
1249 | sql_print_warning("%s" , thd->get_stmt_da()->message()); |
1250 | |
1251 | /* |
1252 | now let client to send its first command, |
1253 | to be able to send the error back |
1254 | */ |
1255 | NET *net= &thd->net; |
1256 | thd->lex->current_select= 0; |
1257 | my_net_set_read_timeout(net, thd->variables.net_wait_timeout); |
1258 | thd->clear_error(); |
1259 | net_new_transaction(net); |
1260 | ulong packet_length= my_net_read(net); |
1261 | /* |
1262 | If my_net_read() failed, my_error() has been already called, |
1263 | and the main Diagnostics Area contains an error condition. |
1264 | */ |
1265 | if (packet_length != packet_error) |
1266 | my_error(ER_NEW_ABORTING_CONNECTION, MYF(0), |
1267 | thd->thread_id, |
1268 | thd->db.str ? thd->db.str : "unconnected" , |
1269 | sctx->user ? sctx->user : "unauthenticated" , |
1270 | sctx->host_or_ip, "init_connect command failed" ); |
1271 | thd->server_status&= ~SERVER_STATUS_CLEAR_SET; |
1272 | thd->protocol->end_statement(); |
1273 | thd->killed = KILL_CONNECTION; |
1274 | errors.m_init_connect= 1; |
1275 | inc_host_errors(thd->main_security_ctx.ip, &errors); |
1276 | return; |
1277 | } |
1278 | |
1279 | thd->proc_info=0; |
1280 | thd->init_for_queries(); |
1281 | } |
1282 | } |
1283 | |
1284 | |
1285 | /* |
1286 | Thread handler for a connection |
1287 | |
1288 | SYNOPSIS |
1289 | handle_one_connection() |
1290 | arg Connection object (THD) |
1291 | |
1292 | IMPLEMENTATION |
1293 | This function (normally) does the following: |
1294 | - Initialize thread |
1295 | - Initialize THD to be used with this thread |
1296 | - Authenticate user |
1297 | - Execute all queries sent on the connection |
1298 | - Take connection down |
1299 | - End thread / Handle next connection using thread from thread cache |
1300 | */ |
1301 | |
1302 | pthread_handler_t handle_one_connection(void *arg) |
1303 | { |
1304 | CONNECT *connect= (CONNECT*) arg; |
1305 | |
1306 | mysql_thread_set_psi_id(connect->thread_id); |
1307 | |
1308 | do_handle_one_connection(connect); |
1309 | return 0; |
1310 | } |
1311 | |
1312 | bool thd_prepare_connection(THD *thd) |
1313 | { |
1314 | bool rc; |
1315 | lex_start(thd); |
1316 | rc= login_connection(thd); |
1317 | if (rc) |
1318 | return rc; |
1319 | |
1320 | MYSQL_CONNECTION_START(thd->thread_id, &thd->security_ctx->priv_user[0], |
1321 | (char *) thd->security_ctx->host_or_ip); |
1322 | |
1323 | prepare_new_connection_state(thd); |
1324 | #ifdef WITH_WSREP |
1325 | thd->wsrep_client_thread= 1; |
1326 | #endif /* WITH_WSREP */ |
1327 | return FALSE; |
1328 | } |
1329 | |
1330 | bool thd_is_connection_alive(THD *thd) |
1331 | { |
1332 | NET *net= &thd->net; |
1333 | if (likely(!net->error && |
1334 | net->vio != 0 && |
1335 | thd->killed < KILL_CONNECTION)) |
1336 | return TRUE; |
1337 | return FALSE; |
1338 | } |
1339 | |
1340 | |
1341 | void do_handle_one_connection(CONNECT *connect) |
1342 | { |
1343 | ulonglong thr_create_utime= microsecond_interval_timer(); |
1344 | THD *thd; |
1345 | if (connect->scheduler->init_new_connection_thread() || |
1346 | !(thd= connect->create_thd(NULL))) |
1347 | { |
1348 | scheduler_functions *scheduler= connect->scheduler; |
1349 | connect->close_with_error(0, 0, ER_OUT_OF_RESOURCES); |
1350 | scheduler->end_thread(0, 0); |
1351 | return; |
1352 | } |
1353 | |
1354 | /* |
1355 | If a thread was created to handle this connection: |
1356 | increment slow_launch_threads counter if it took more than |
1357 | slow_launch_time seconds to create the thread. |
1358 | */ |
1359 | |
1360 | if (connect->prior_thr_create_utime) |
1361 | { |
1362 | ulong launch_time= (ulong) (thr_create_utime - |
1363 | connect->prior_thr_create_utime); |
1364 | if (launch_time >= slow_launch_time*1000000L) |
1365 | statistic_increment(slow_launch_threads, &LOCK_status); |
1366 | } |
1367 | delete connect; |
1368 | |
1369 | /* Make THD visible in show processlist */ |
1370 | add_to_active_threads(thd); |
1371 | |
1372 | thd->thr_create_utime= thr_create_utime; |
1373 | /* We need to set this because of time_out_user_resource_limits */ |
1374 | thd->start_utime= thr_create_utime; |
1375 | |
1376 | /* |
1377 | handle_one_connection() is normally the only way a thread would |
1378 | start and would always be on the very high end of the stack , |
1379 | therefore, the thread stack always starts at the address of the |
1380 | first local variable of handle_one_connection, which is thd. We |
1381 | need to know the start of the stack so that we could check for |
1382 | stack overruns. |
1383 | */ |
1384 | thd->thread_stack= (char*) &thd; |
1385 | if (setup_connection_thread_globals(thd)) |
1386 | return; |
1387 | |
1388 | for (;;) |
1389 | { |
1390 | bool create_user= TRUE; |
1391 | |
1392 | mysql_socket_set_thread_owner(thd->net.vio->mysql_socket); |
1393 | if (thd_prepare_connection(thd)) |
1394 | { |
1395 | create_user= FALSE; |
1396 | goto end_thread; |
1397 | } |
1398 | |
1399 | while (thd_is_connection_alive(thd)) |
1400 | { |
1401 | mysql_audit_release(thd); |
1402 | if (do_command(thd)) |
1403 | break; |
1404 | } |
1405 | end_connection(thd); |
1406 | |
1407 | #ifdef WITH_WSREP |
1408 | if (WSREP(thd)) |
1409 | { |
1410 | mysql_mutex_lock(&thd->LOCK_thd_data); |
1411 | thd->wsrep_query_state= QUERY_EXITING; |
1412 | mysql_mutex_unlock(&thd->LOCK_thd_data); |
1413 | } |
1414 | #endif |
1415 | end_thread: |
1416 | close_connection(thd); |
1417 | |
1418 | if (thd->userstat_running) |
1419 | update_global_user_stats(thd, create_user, time(NULL)); |
1420 | |
1421 | if (thd->scheduler->end_thread(thd, 1)) |
1422 | return; // Probably no-threads |
1423 | |
1424 | /* |
1425 | If end_thread() returns, this thread has been schedule to |
1426 | handle the next connection. |
1427 | */ |
1428 | thd= current_thd; |
1429 | thd->thread_stack= (char*) &thd; |
1430 | } |
1431 | } |
1432 | #endif /* EMBEDDED_LIBRARY */ |
1433 | |
1434 | |
1435 | /* Handling of CONNECT objects */ |
1436 | |
1437 | /* |
1438 | Close connection without error and delete the connect object |
1439 | This and close_with_error are only called if we didn't manage to |
1440 | create a new thd object. |
1441 | */ |
1442 | |
1443 | void CONNECT::close_and_delete() |
1444 | { |
1445 | DBUG_ENTER("close_and_delete" ); |
1446 | |
1447 | if (vio) |
1448 | vio_close(vio); |
1449 | if (thread_count_incremented) |
1450 | dec_connection_count(scheduler); |
1451 | statistic_increment(connection_errors_internal, &LOCK_status); |
1452 | statistic_increment(aborted_connects,&LOCK_status); |
1453 | |
1454 | delete this; |
1455 | DBUG_VOID_RETURN; |
1456 | } |
1457 | |
1458 | /* |
1459 | Close a connection with a possible error to the end user |
1460 | Alse deletes the connection object, like close_and_delete() |
1461 | */ |
1462 | |
1463 | void CONNECT::close_with_error(uint sql_errno, |
1464 | const char *message, uint close_error) |
1465 | { |
1466 | THD *thd= create_thd(NULL); |
1467 | if (thd) |
1468 | { |
1469 | if (sql_errno) |
1470 | net_send_error(thd, sql_errno, message, NULL); |
1471 | close_connection(thd, close_error); |
1472 | delete thd; |
1473 | set_current_thd(0); |
1474 | } |
1475 | close_and_delete(); |
1476 | } |
1477 | |
1478 | |
1479 | CONNECT::~CONNECT() |
1480 | { |
1481 | if (vio) |
1482 | vio_delete(vio); |
1483 | } |
1484 | |
1485 | |
1486 | /* Reuse or create a THD based on a CONNECT object */ |
1487 | |
1488 | THD *CONNECT::create_thd(THD *thd) |
1489 | { |
1490 | bool res, thd_reused= thd != 0; |
1491 | DBUG_ENTER("create_thd" ); |
1492 | |
1493 | DBUG_EXECUTE_IF("simulate_failed_connection_2" , DBUG_RETURN(0); ); |
1494 | |
1495 | if (thd) |
1496 | { |
1497 | /* reuse old thd */ |
1498 | thd->reset_for_reuse(); |
1499 | /* |
1500 | reset tread_id's, but not thread_dbug_id's as the later isn't allowed |
1501 | to change as there is already structures in thd marked with the old |
1502 | value. |
1503 | */ |
1504 | thd->thread_id= thd->variables.pseudo_thread_id= thread_id; |
1505 | } |
1506 | else if (!(thd= new THD(thread_id))) |
1507 | DBUG_RETURN(0); |
1508 | |
1509 | set_current_thd(thd); |
1510 | res= my_net_init(&thd->net, vio, thd, MYF(MY_THREAD_SPECIFIC)); |
1511 | vio= 0; // Vio now handled by thd |
1512 | |
1513 | if (unlikely(res || thd->is_error())) |
1514 | { |
1515 | if (!thd_reused) |
1516 | delete thd; |
1517 | set_current_thd(0); |
1518 | DBUG_RETURN(0); |
1519 | } |
1520 | |
1521 | init_net_server_extension(thd); |
1522 | |
1523 | thd->security_ctx->host= host; |
1524 | thd->extra_port= extra_port; |
1525 | thd->scheduler= scheduler; |
1526 | thd->real_id= real_id; |
1527 | DBUG_RETURN(thd); |
1528 | } |
1529 | |