mariadb/sql/sql_connect.cc
unknown 2a0d2fef51 Bug#12713 "Error in a stored function called from a SELECT doesn't
cause ROLLBACK of statement", part 1. Review fixes.

Do not send OK/EOF packets to the client until we reached the end of 
the current statement.
This is a consolidation, to keep the functionality that is shared by all 
SQL statements in one place in the server.
Currently this functionality includes:
- close_thread_tables()
- log_slow_statement().

After this patch and the subsequent patch for Bug#12713, it shall also include:
- ha_autocommit_or_rollback()
- net_end_statement()
- query_cache_end_of_result().

In future it may also include:
- mysql_reset_thd_for_next_command().


include/mysql_com.h:
  Rename now unused members of NET: no_send_ok, no_send_error, report_error.
  These were server-specific variables related to the client/server
  protocol. They have been made obsolete by this patch.
  
  Previously the same members of NET were used to store the error message
  both on the client and on the server. 
  The error message was stored in net.last_error (client: mysql->net.last_error,
  server: thd->net.last_error).
  The error code was stored in net.last_errno (client: mysql->net.last_errno,
  server: thd->net.last_errno).
  The server error code and message are now stored elsewhere 
  (in the Diagnostics_area), thus NET members are no longer used by the
  server.
  Rename last_error to client_last_error, last_errno to client_last_errno
  to avoid potential bugs introduced by merges.
include/mysql_h.ic:
  Update the ABI file to reflect a rename. 
  Renames do not break the binary compatibility.
libmysql/libmysql.c:
  Rename last_error to client_last_error, last_errno to client_last_errno.
  This is necessary to ensure no unnoticed bugs introduced by merged
  changesets.
  
  Remove net.report_error, net.no_send_ok, net.no_send_error.
libmysql/manager.c:
  Rename net.last_errno to net.client_last_errno.
libmysqld/lib_sql.cc:
  Rename net.last_errno to net.client_last_errno.
  
  Update the embedded implementation of the client-server protocol to
  reflect the refactoring of protocol.cc.
libmysqld/libmysqld.c:
  Rename net.last_errno to net.client_last_errno.
mysql-test/r/events.result:
  Update to reflect the change in mysql_rm_db(). Now we drop stored
  routines and events for a given database name only if there
  is a directory for this database name. ha_drop_database() and
  query_cache_invalidate() are called likewise. 
  Previously we would attempt to drop routines/events even if database
  directory was not found (it worked, since routines and events are stored
  in tables). This fixes Bug 29958 "Weird message on DROP DATABASE if mysql.proc
  does not exist".
  The change was done because the previous code used to call send_ok()
  twice, which led to an assertion failure when asserts against it were
  added by this patch.
mysql-test/r/grant.result:
  Fix the patch for Bug 16470, now FLUSH PRIVILEGES produces an error 
  if mysql.procs_priv is missing.
  This fixes the assert that send_ok() must not called after send_error()
  (the original patch for Bug 16470 was prone to this).
mysql-test/suite/rpl/r/rpl_row_tabledefs_2myisam.result:
  Produce a more detailed error message.
mysql-test/suite/rpl/r/rpl_row_tabledefs_3innodb.result:
  Produce a more detailed error message.
mysql-test/t/grant.test:
  Update the test, now FLUSH PRIVILEGES returns an error if mysql.procs_priv
  is missing.
server-tools/instance-manager/mysql_connection.cc:
  Rename net.last_errno to net.client_last_errno.
sql/ha_ndbcluster_binlog.cc:
  Add asserts. 
  
  Use getters to access statement status information.
  
  Add a comment why run_query() is broken. Reset the diagnostics area
  in the end of run_query() to fulfill the invariant that the diagnostics_area
  is never assigned twice per statement (see the comment in the code
  when this can happen). We still do not clear thd->is_fatal_error and
  thd->is_slave_error, which may lead to bugs, I consider the whole affair
  as something to be dealt with separately.
sql/ha_partition.cc:
  fatal_error() doesn't set an error by itself. Perhaps we should
  remove this method altogether and instead add a flag to my_error 
  to set thd->is_fatal_error property.
  
  Meanwhile, this change is a part of inspection made to the entire source
  code with the goal to ensure that fatal_error()
  is always accompanied by my_error().
sql/item_func.cc:
  There is no net.last_error anymore. Remove the obsolete assignment.
sql/log_event.cc:
  Use getters to access statement error status information.
sql/log_event_old.cc:
  Use getters to access statement error status information.
sql/mysqld.cc:
  Previously, if a continue handler for an error was found, my_message_sql() 
  would not set an error in THD. Since the current statement
  must be aborted in any case, find_handler() had a hack to assign 
  thd->net.report_error to 1.
  
  Remove this hack. Set an error in my_message_sql() even if the continue
  handler is found. The error will be cleared anyway when the handler
  is executed. This is one action among many in this patch to ensure the 
  invariant that whenever thd->is_error() is TRUE, we have a message in 
  thd->main_da.message().
sql/net_serv.cc:
  Use a full-blown my_error() in net_serv.cc to report an error,
  instead of just setting net->last_errno. This ensures the invariant that
  whenever thd->is_error() returns TRUE, we have a message in 
  thd->main_da.message().
  
  Remove initialization of removed NET members.
sql/opt_range.cc:
  Use my_error() instead of just raising thd->net.report_error. 
  This ensures the invariant that whenever thd->is_error() returns TRUE, 
  there is a message in thd->main_da.message().
sql/opt_sum.cc:
  Move invocation of fatal_error() right next to the place where
  we set the error message. That makes it easier to track that whenever
  fatal_error() is called, there is a message in THD.
sql/protocol.cc:
  Rename send_ok() and send_eof() to net_send_ok() and net_send_eof() 
  respectively. These functions write directly to the network and are not 
  for use anywhere outside the client/server protocol code. 
  
  Remove the code that was responsible for cases when either there is 
  no error code, or no error message, or both.
  Instead the calling code ensures that they are always present. Asserts
  are added to enforce the invariant.
  
  Instead of a direct access to thd->server_status and thd->total_warn_count
  use function parameters, since these from now on don't always come directly
  from THD.
  
  Introduce net_end_statement(), the single-entry-point replacement API for 
  send_ok(), send_eof() and net_send_error().
  
  Implement Protocol::end_partial_result_set to use in select_send::abort()
  when there is a continue handler.
sql/protocol.h:
  Update declarations.
sql/repl_failsafe.cc:
  Use getters to access statement status information in THD.
  Rename net.last_error to net.client_last_error.
sql/rpl_record.cc:
  Set an error message in prepare_record() if there is no default
  value for the field -- later we do print this message to the client.
sql/rpl_rli.cc:
  Use getters to access statement status information in THD.
sql/slave.cc:
  In create_table_from_dump() (a common function that is used in 
  LOAD MASTER TABLE SQL statement and COM_LOAD_MASTER_DATA), instead of hacks
  with no_send_ok, clear the diagnostics area when mysql_rm_table() succeeded.
  
  Update has_temporary_error() to work correctly when no error is set.
  This is the case when Incident_log_event is executed: it always returns
  an error but does not set an error message.
  
  Use getters to access error status information.
sql/sp_head.cc:
  Instead of hacks with no_send_error, work through the diagnostics area 
  interface to suppress sending of OK/ERROR packets to the client.
  
  Move query_cache_end_of_result before log_slow_statement(), similarly
  to how it's done in dispatch_command().
sql/sp_rcontext.cc:
  Remove hacks with assignment of thd->net.report_error, they are not
  necessary any more (see the changes in mysqld.cc).
sql/sql_acl.cc:
  Use getters to access error status information in THD.
sql/sql_base.cc:
  Access thd->main_da.sql_errno() only if there is an error. This fixes
  a bug when auto-discovery, that was effectively disabled under pre-locking.
sql/sql_binlog.cc:
  Remove hacks with no_send_ok/no_send_error, they are not necessary 
  anymore: the caller is responsible for network communication.
sql/sql_cache.cc:
  Disable sending of OK/ERROR/EOF packet in the end of dispatch_command
  if the response has been served from the query cache. This raises the 
  question whether we should store EOF packet in the query cache at all,
  or generate it anew for each statement (we should generate it anew), but
  this is to be addressed separately.
sql/sql_class.cc:
  Implement class Diagnostics_area. Please see comments in sql_class.h
  for details.
  
  Fix a subtle coding mistake in select_send::send_data: when on slave, 
  an error in Item::send() was ignored.
  The problem became visible due to asserts that the diagnostics area is
  never double assigned.
  
  Remove initialization of removed NET members.
  
  In select_send::abort() do not call select_send::send_eof(). This is
  not inheritance-safe. Even if a stored procedure continue handler is
  found, the current statement is aborted, not succeeded.
  Instead introduce a Protocol API to send the required response, 
  Protocol::end_partial_result_set().
  
  This simplifies implementation of select_send::send_eof(). No need
  to add more asserts that there is no error, there is an assert inside
  Diagnostics_area::set_ok_status() already.
  
  Leave no trace of no_send_* in the code.
sql/sql_class.h:
  Declare class Diagnostics_area. 
  
  Remove the hack with no_send_ok from
  Substatement_state.
  
  Provide inline implementations of send_ok/send_eof.
  
  Add commetns.
sql/sql_connect.cc:
  Remove hacks with no_send_error. 
  
  Since now an error in THD is always set if net->error, it's not necessary
  to check both net->error and thd->is_error() in the do_command loop.
  
  Use thd->main_da.message() instead of net->last_errno.
  
  Remove the hack with is_slave_error in sys_init_connect. Since now we do not
  reset the diagnostics area in net_send_error (it's reset at the beginning
  of the next statement), we can access it safely even after 
  execute_init_command.
sql/sql_db.cc:
  Update the code to satisfy the invariant that the diagnostics area is never
  assigned twice.
  Incidentally, this fixes Bug 29958 "Weird message on DROP DATABASE if 
  mysql.proc does not exist".
sql/sql_delete.cc:
  Change multi-delete to abort in abort(), as per select_send protocol.
  Fixes the merge error with the test for Bug 29136
sql/sql_derived.cc:
  Use getters to access error information.
sql/sql_insert.cc:
  Use getters to access error information.
sql-common/client.c:
  Rename last_error to client_last_error, last_errno to client_last_errno.
sql/sql_parse.cc:
  Remove hacks with no_send_error. Deploy net_end_statement().
  
  The story of COM_SHUTDOWN is interesting. Long story short, the server 
  would become on its death's door, and only no_send_ok/no_send_error assigned
  by send_ok()/net_send_error() would hide its babbling from the client.
  
  First of all, COM_QUIT does not require a response. So, the comment saying
  "Let's send a response to possible COM_QUIT" is not only groundless 
  (even mysqladmin shutdown/mysql_shutdown() doesn't send COM_QUIT after 
  COM_SHUTDOWN), it's plainly incorrect.
  
  Secondly, besides this additional 'OK' packet to respond to a hypothetical
  COM_QUIT, there was the following code in dispatch_command():
  
  if (thd->killed)
    thd->send_kill_message();
  if (thd->is_error()
    net_send_error(thd);
  
  This worked out really funny for the thread through which COM_SHUTDOWN
  was delivered: we would get COM_SHUTDOWN, say okay, say okay again, 
  kill everybody, get the kill signal ourselves, and then attempt to say 
  "Server shutdown in progress" to the client that is very likely long gone.
  
  This all became visible when asserts were added that the Diagnostics_area
  is not assigned twice.
  
  Move query_cache_end_of_result() to the end of dispatch_command(), since
  net_send_eof() has been moved there. This is safe, query_cache_end_of_result()
  is a no-op if there is no started query in the cache.
  
  Consistently use select_send interface to call abort() or send_eof()
  depending on the operation result.
  
  Remove thd->fatal_error() from reset_master(), it was a no-op. 
  in hacks with no_send_error woudl save us
  from complete breakage of the client/server protocol.
  
  Consistently use select_send::abort() whenever there is an error, 
  and select_send::send_eof() in case of success.
  The issue became visible due to added asserts.
sql/sql_partition.cc:
  Always set an error in THD whenever there is a call to fatal_error().
sql/sql_prepare.cc:
  Deploy class Diagnostics_area.
  Remove the unnecessary juggling with the protocol in 
  Select_fetch_protocol_binary::send_eof(). EOF packet format is 
  protocol-independent.
sql/sql_select.cc:
  Call fatal_error() directly in opt_sum_query.
  Call my_error() whenever we call thd->fatal_error().
sql/sql_servers.cc:
  Use getters to access error information in THD.
sql/sql_show.cc:
  Use getters to access error information in THD.
  
  Add comments.
  
  Call my_error() whenever we call fatal_error().
sql/sql_table.cc:
  Replace hacks with no_send_ok with the interface of the diagnostics area.
  
  Clear the error if ENOENT error in ha_delete_table().
sql/sql_update.cc:
  Introduce multi_update::abort(), which is the proper way to abort a
  multi-update. This fixes the merge conflict between this patch and
  the patch for Bug 29136.
sql/table.cc:
  Use a getter to access error information in THD.
sql/tztime.cc:
  Use a getter to access error information in THD.
2007-12-12 18:21:01 +03:00

1139 lines
34 KiB
C++

/* Copyright (C) 2007 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
Functions to autenticate and handle reqests for a connection
*/
#include "mysql_priv.h"
#ifdef HAVE_OPENSSL
/*
Without SSL the handshake consists of one packet. This packet
has both client capabilites and scrambled password.
With SSL the handshake might consist of two packets. If the first
packet (client capabilities) has CLIENT_SSL flag set, we have to
switch to SSL and read the second packet. The scrambled password
is in the second packet and client_capabilites field will be ignored.
Maybe it is better to accept flags other than CLIENT_SSL from the
second packet?
*/
#define SSL_HANDSHAKE_SIZE 2
#define NORMAL_HANDSHAKE_SIZE 6
#define MIN_HANDSHAKE_SIZE 2
#else
#define MIN_HANDSHAKE_SIZE 6
#endif /* HAVE_OPENSSL */
#ifdef __WIN__
static void test_signal(int sig_ptr)
{
#if !defined( DBUG_OFF)
MessageBox(NULL,"Test signal","DBUG",MB_OK);
#endif
#if defined(OS2)
fprintf(stderr, "Test signal %d\n", sig_ptr);
fflush(stderr);
#endif
}
static void init_signals(void)
{
int signals[7] = {SIGINT,SIGILL,SIGFPE,SIGSEGV,SIGTERM,SIGBREAK,SIGABRT } ;
for (int i=0 ; i < 7 ; i++)
signal( signals[i], test_signal) ;
}
#endif
/*
Get structure for logging connection data for the current user
*/
#ifndef NO_EMBEDDED_ACCESS_CHECKS
static HASH hash_user_connections;
static int get_or_create_user_conn(THD *thd, const char *user,
const char *host,
USER_RESOURCES *mqh)
{
int return_val= 0;
uint temp_len, user_len;
char temp_user[USER_HOST_BUFF_SIZE];
struct user_conn *uc;
DBUG_ASSERT(user != 0);
DBUG_ASSERT(host != 0);
user_len= strlen(user);
temp_len= (strmov(strmov(temp_user, user)+1, host) - temp_user)+1;
(void) pthread_mutex_lock(&LOCK_user_conn);
if (!(uc = (struct user_conn *) hash_search(&hash_user_connections,
(uchar*) temp_user, temp_len)))
{
/* First connection for user; Create a user connection object */
if (!(uc= ((struct user_conn*)
my_malloc(sizeof(struct user_conn) + temp_len+1,
MYF(MY_WME)))))
{
/* MY_WME ensures an error is set in THD. */
return_val= 1;
goto end;
}
uc->user=(char*) (uc+1);
memcpy(uc->user,temp_user,temp_len+1);
uc->host= uc->user + user_len + 1;
uc->len= temp_len;
uc->connections= uc->questions= uc->updates= uc->conn_per_hour= 0;
uc->user_resources= *mqh;
uc->reset_utime= thd->thr_create_utime;
if (my_hash_insert(&hash_user_connections, (uchar*) uc))
{
/* The only possible error is out of memory, MY_WME sets an error. */
my_free((char*) uc,0);
return_val= 1;
goto end;
}
}
thd->user_connect=uc;
uc->connections++;
end:
(void) pthread_mutex_unlock(&LOCK_user_conn);
return return_val;
}
/*
check if user has already too many connections
SYNOPSIS
check_for_max_user_connections()
thd Thread handle
uc User connect object
NOTES
If check fails, we decrease user connection count, which means one
shouldn't call decrease_user_connections() after this function.
RETURN
0 ok
1 error
*/
static
int check_for_max_user_connections(THD *thd, USER_CONN *uc)
{
int error=0;
DBUG_ENTER("check_for_max_user_connections");
(void) pthread_mutex_lock(&LOCK_user_conn);
if (max_user_connections && !uc->user_resources.user_conn &&
max_user_connections < (uint) uc->connections)
{
my_error(ER_TOO_MANY_USER_CONNECTIONS, MYF(0), uc->user);
error=1;
goto end;
}
time_out_user_resource_limits(thd, uc);
if (uc->user_resources.user_conn &&
uc->user_resources.user_conn < uc->connections)
{
my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user,
"max_user_connections",
(long) uc->user_resources.user_conn);
error= 1;
goto end;
}
if (uc->user_resources.conn_per_hour &&
uc->user_resources.conn_per_hour <= uc->conn_per_hour)
{
my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user,
"max_connections_per_hour",
(long) uc->user_resources.conn_per_hour);
error=1;
goto end;
}
uc->conn_per_hour++;
end:
if (error)
uc->connections--; // no need for decrease_user_connections() here
(void) pthread_mutex_unlock(&LOCK_user_conn);
DBUG_RETURN(error);
}
/*
Decrease user connection count
SYNOPSIS
decrease_user_connections()
uc User connection object
NOTES
If there is a n user connection object for a connection
(which only happens if 'max_user_connections' is defined or
if someone has created a resource grant for a user), then
the connection count is always incremented on connect.
The user connect object is not freed if some users has
'max connections per hour' defined as we need to be able to hold
count over the lifetime of the connection.
*/
void decrease_user_connections(USER_CONN *uc)
{
DBUG_ENTER("decrease_user_connections");
(void) pthread_mutex_lock(&LOCK_user_conn);
DBUG_ASSERT(uc->connections);
if (!--uc->connections && !mqh_used)
{
/* Last connection for user; Delete it */
(void) hash_delete(&hash_user_connections,(uchar*) uc);
}
(void) pthread_mutex_unlock(&LOCK_user_conn);
DBUG_VOID_RETURN;
}
/*
Reset per-hour user resource limits when it has been more than
an hour since they were last checked
SYNOPSIS:
time_out_user_resource_limits()
thd Thread handler
uc User connection details
NOTE:
This assumes that the LOCK_user_conn mutex has been acquired, so it is
safe to test and modify members of the USER_CONN structure.
*/
void time_out_user_resource_limits(THD *thd, USER_CONN *uc)
{
ulonglong check_time= thd->start_utime;
DBUG_ENTER("time_out_user_resource_limits");
/* If more than a hour since last check, reset resource checking */
if (check_time - uc->reset_utime >= LL(3600000000))
{
uc->questions=1;
uc->updates=0;
uc->conn_per_hour=0;
uc->reset_utime= check_time;
}
DBUG_VOID_RETURN;
}
/*
Check if maximum queries per hour limit has been reached
returns 0 if OK.
*/
bool check_mqh(THD *thd, uint check_command)
{
bool error= 0;
USER_CONN *uc=thd->user_connect;
DBUG_ENTER("check_mqh");
DBUG_ASSERT(uc != 0);
(void) pthread_mutex_lock(&LOCK_user_conn);
time_out_user_resource_limits(thd, uc);
/* Check that we have not done too many questions / hour */
if (uc->user_resources.questions &&
uc->questions++ >= uc->user_resources.questions)
{
my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_questions",
(long) uc->user_resources.questions);
error=1;
goto end;
}
if (check_command < (uint) SQLCOM_END)
{
/* Check that we have not done too many updates / hour */
if (uc->user_resources.updates &&
(sql_command_flags[check_command] & CF_CHANGES_DATA) &&
uc->updates++ >= uc->user_resources.updates)
{
my_error(ER_USER_LIMIT_REACHED, MYF(0), uc->user, "max_updates",
(long) uc->user_resources.updates);
error=1;
goto end;
}
}
end:
(void) pthread_mutex_unlock(&LOCK_user_conn);
DBUG_RETURN(error);
}
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
/**
Check if user exist and password supplied is correct.
@param thd thread handle, thd->security_ctx->{host,user,ip} are used
@param command originator of the check: now check_user is called
during connect and change user procedures; used for
logging.
@param passwd scrambled password received from client
@param passwd_len length of scrambled password
@param db database name to connect to, may be NULL
@param check_count TRUE if establishing a new connection. In this case
check that we have not exceeded the global
max_connections limist
@note Host, user and passwd may point to communication buffer.
Current implementation does not depend on that, but future changes
should be done with this in mind; 'thd' is INOUT, all other params
are 'IN'.
@retval 0 OK; thd->security_ctx->user/master_access/priv_user/db_access and
thd->db are updated; OK is sent to the client.
@retval 1 error, e.g. access denied or handshake error, not sent to
the client. A message is pushed into the error stack.
*/
int
check_user(THD *thd, enum enum_server_command command,
const char *passwd, uint passwd_len, const char *db,
bool check_count)
{
DBUG_ENTER("check_user");
LEX_STRING db_str= { (char *) db, db ? strlen(db) : 0 };
/*
Clear thd->db as it points to something, that will be freed when
connection is closed. We don't want to accidentally free a wrong
pointer if connect failed. Also in case of 'CHANGE USER' failure,
current database will be switched to 'no database selected'.
*/
thd->reset_db(NULL, 0);
#ifdef NO_EMBEDDED_ACCESS_CHECKS
thd->main_security_ctx.master_access= GLOBAL_ACLS; // Full rights
/* Change database if necessary */
if (db && db[0])
{
if (mysql_change_db(thd, &db_str, FALSE))
DBUG_RETURN(1);
}
send_ok(thd);
DBUG_RETURN(0);
#else
my_bool opt_secure_auth_local;
pthread_mutex_lock(&LOCK_global_system_variables);
opt_secure_auth_local= opt_secure_auth;
pthread_mutex_unlock(&LOCK_global_system_variables);
/*
If the server is running in secure auth mode, short scrambles are
forbidden.
*/
if (opt_secure_auth_local && passwd_len == SCRAMBLE_LENGTH_323)
{
my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE));
DBUG_RETURN(1);
}
if (passwd_len != 0 &&
passwd_len != SCRAMBLE_LENGTH &&
passwd_len != SCRAMBLE_LENGTH_323)
{
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
DBUG_RETURN(1);
}
USER_RESOURCES ur;
int res= acl_getroot(thd, &ur, passwd, passwd_len);
#ifndef EMBEDDED_LIBRARY
if (res == -1)
{
/*
This happens when client (new) sends password scrambled with
scramble(), but database holds old value (scrambled with
scramble_323()). Here we please client to send scrambled_password
in old format.
*/
NET *net= &thd->net;
if (opt_secure_auth_local)
{
my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0),
thd->main_security_ctx.user,
thd->main_security_ctx.host_or_ip);
general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE),
thd->main_security_ctx.user,
thd->main_security_ctx.host_or_ip);
DBUG_RETURN(1);
}
/* We have to read very specific packet size */
if (send_old_password_request(thd) ||
my_net_read(net) != SCRAMBLE_LENGTH_323 + 1)
{
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
DBUG_RETURN(1);
}
/* Final attempt to check the user based on reply */
/* So as passwd is short, errcode is always >= 0 */
res= acl_getroot(thd, &ur, (char *) net->read_pos, SCRAMBLE_LENGTH_323);
}
#endif /*EMBEDDED_LIBRARY*/
/* here res is always >= 0 */
if (res == 0)
{
if (!(thd->main_security_ctx.master_access &
NO_ACCESS)) // authentication is OK
{
DBUG_PRINT("info",
("Capabilities: %lu packet_length: %ld Host: '%s' "
"Login user: '%s' Priv_user: '%s' Using password: %s "
"Access: %lu db: '%s'",
thd->client_capabilities,
thd->max_client_packet_length,
thd->main_security_ctx.host_or_ip,
thd->main_security_ctx.user,
thd->main_security_ctx.priv_user,
passwd_len ? "yes": "no",
thd->main_security_ctx.master_access,
(thd->db ? thd->db : "*none*")));
if (check_count)
{
VOID(pthread_mutex_lock(&LOCK_thread_count));
bool count_ok= thread_count <= max_connections + delayed_insert_threads
|| (thd->main_security_ctx.master_access & SUPER_ACL);
VOID(pthread_mutex_unlock(&LOCK_thread_count));
if (!count_ok)
{ // too many connections
my_error(ER_CON_COUNT_ERROR, MYF(0));
DBUG_RETURN(1);
}
}
/*
Log the command before authentication checks, so that the user can
check the log for the tried login tried and also to detect
break-in attempts.
*/
general_log_print(thd, command,
(thd->main_security_ctx.priv_user ==
thd->main_security_ctx.user ?
(char*) "%s@%s on %s" :
(char*) "%s@%s as anonymous on %s"),
thd->main_security_ctx.user,
thd->main_security_ctx.host_or_ip,
db ? db : (char*) "");
/*
This is the default access rights for the current database. It's
set to 0 here because we don't have an active database yet (and we
may not have an active database to set.
*/
thd->main_security_ctx.db_access=0;
/* Don't allow user to connect if he has done too many queries */
if ((ur.questions || ur.updates || ur.conn_per_hour || ur.user_conn ||
max_user_connections) &&
get_or_create_user_conn(thd,
(opt_old_style_user_limits ? thd->main_security_ctx.user :
thd->main_security_ctx.priv_user),
(opt_old_style_user_limits ? thd->main_security_ctx.host_or_ip :
thd->main_security_ctx.priv_host),
&ur))
{
/* The error is set by get_or_create_user_conn(). */
DBUG_RETURN(1);
}
if (thd->user_connect &&
(thd->user_connect->user_resources.conn_per_hour ||
thd->user_connect->user_resources.user_conn ||
max_user_connections) &&
check_for_max_user_connections(thd, thd->user_connect))
{
/* The error is set in check_for_max_user_connections(). */
DBUG_RETURN(1);
}
/* Change database if necessary */
if (db && db[0])
{
if (mysql_change_db(thd, &db_str, FALSE))
{
/* mysql_change_db() has pushed the error message. */
if (thd->user_connect)
decrease_user_connections(thd->user_connect);
DBUG_RETURN(1);
}
}
send_ok(thd);
thd->password= test(passwd_len); // remember for error messages
/* Ready to handle queries */
DBUG_RETURN(0);
}
}
else if (res == 2) // client gave short hash, server has long hash
{
my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE));
DBUG_RETURN(1);
}
my_error(ER_ACCESS_DENIED_ERROR, MYF(0),
thd->main_security_ctx.user,
thd->main_security_ctx.host_or_ip,
passwd_len ? ER(ER_YES) : ER(ER_NO));
general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR),
thd->main_security_ctx.user,
thd->main_security_ctx.host_or_ip,
passwd_len ? ER(ER_YES) : ER(ER_NO));
DBUG_RETURN(1);
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
}
/*
Check for maximum allowable user connections, if the mysqld server is
started with corresponding variable that is greater then 0.
*/
extern "C" uchar *get_key_conn(user_conn *buff, size_t *length,
my_bool not_used __attribute__((unused)))
{
*length= buff->len;
return (uchar*) buff->user;
}
extern "C" void free_user(struct user_conn *uc)
{
my_free((char*) uc,MYF(0));
}
void init_max_user_conn(void)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
(void) hash_init(&hash_user_connections,system_charset_info,max_connections,
0,0,
(hash_get_key) get_key_conn, (hash_free_key) free_user,
0);
#endif
}
void free_max_user_conn(void)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
hash_free(&hash_user_connections);
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
}
void reset_mqh(LEX_USER *lu, bool get_them= 0)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
(void) pthread_mutex_lock(&LOCK_user_conn);
if (lu) // for GRANT
{
USER_CONN *uc;
uint temp_len=lu->user.length+lu->host.length+2;
char temp_user[USER_HOST_BUFF_SIZE];
memcpy(temp_user,lu->user.str,lu->user.length);
memcpy(temp_user+lu->user.length+1,lu->host.str,lu->host.length);
temp_user[lu->user.length]='\0'; temp_user[temp_len-1]=0;
if ((uc = (struct user_conn *) hash_search(&hash_user_connections,
(uchar*) temp_user, temp_len)))
{
uc->questions=0;
get_mqh(temp_user,&temp_user[lu->user.length+1],uc);
uc->updates=0;
uc->conn_per_hour=0;
}
}
else
{
/* for FLUSH PRIVILEGES and FLUSH USER_RESOURCES */
for (uint idx=0;idx < hash_user_connections.records; idx++)
{
USER_CONN *uc=(struct user_conn *) hash_element(&hash_user_connections,
idx);
if (get_them)
get_mqh(uc->user,uc->host,uc);
uc->questions=0;
uc->updates=0;
uc->conn_per_hour=0;
}
}
(void) pthread_mutex_unlock(&LOCK_user_conn);
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
}
void thd_init_client_charset(THD *thd, uint cs_number)
{
/*
Use server character set and collation if
- opt_character_set_client_handshake is not set
- client has not specified a character set
- client character set is the same as the servers
- client character set doesn't exists in server
*/
if (!opt_character_set_client_handshake ||
!(thd->variables.character_set_client= get_charset(cs_number, MYF(0))) ||
!my_strcasecmp(&my_charset_latin1,
global_system_variables.character_set_client->name,
thd->variables.character_set_client->name))
{
thd->variables.character_set_client=
global_system_variables.character_set_client;
thd->variables.collation_connection=
global_system_variables.collation_connection;
thd->variables.character_set_results=
global_system_variables.character_set_results;
}
else
{
thd->variables.character_set_results=
thd->variables.collation_connection=
thd->variables.character_set_client;
}
}
/*
Initialize connection threads
*/
bool init_new_connection_handler_thread()
{
pthread_detach_this_thread();
#if defined(__WIN__)
init_signals();
#else
/* Win32 calls this in pthread_create */
if (my_thread_init())
return 1;
#endif /* __WIN__ */
return 0;
}
/*
Perform handshake, authorize client and update thd ACL variables.
SYNOPSIS
check_connection()
thd thread handle
RETURN
0 success, OK is sent to user, thd is updated.
-1 error, which is sent to user
> 0 error code (not sent to user)
*/
#ifndef EMBEDDED_LIBRARY
static int check_connection(THD *thd)
{
uint connect_errors= 0;
NET *net= &thd->net;
ulong pkt_len= 0;
char *end;
DBUG_PRINT("info",
("New connection received on %s", vio_description(net->vio)));
#ifdef SIGNAL_WITH_VIO_CLOSE
thd->set_active_vio(net->vio);
#endif
if (!thd->main_security_ctx.host) // If TCP/IP connection
{
char ip[30];
if (vio_peer_addr(net->vio, ip, &thd->peer_port))
{
my_error(ER_BAD_HOST_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(MY_WME))))
return 1; /* The error is set by my_strdup(). */
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip;
vio_in_addr(net->vio,&thd->remote.sin_addr);
if (!(specialflag & SPECIAL_NO_RESOLVE))
{
vio_in_addr(net->vio,&thd->remote.sin_addr);
thd->main_security_ctx.host=
ip_to_hostname(&thd->remote.sin_addr, &connect_errors);
/* Cut very long hostnames to avoid possible overflows */
if (thd->main_security_ctx.host)
{
if (thd->main_security_ctx.host != my_localhost)
thd->main_security_ctx.host[min(strlen(thd->main_security_ctx.host),
HOSTNAME_LENGTH)]= 0;
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host;
}
if (connect_errors > max_connect_errors)
{
my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
}
DBUG_PRINT("info",("Host: %s ip: %s",
(thd->main_security_ctx.host ?
thd->main_security_ctx.host : "unknown host"),
(thd->main_security_ctx.ip ?
thd->main_security_ctx.ip : "unknown ip")));
if (acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip))
{
my_error(ER_HOST_NOT_PRIVILEGED, MYF(0),
thd->main_security_ctx.host_or_ip);
return 1;
}
}
else /* Hostname given means that the connection was on a socket */
{
DBUG_PRINT("info",("Host: %s", thd->main_security_ctx.host));
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host;
thd->main_security_ctx.ip= 0;
/* Reset sin_addr */
bzero((char*) &thd->remote, sizeof(thd->remote));
}
vio_keepalive(net->vio, TRUE);
{
/* buff[] needs to big enough to hold the server_version variable */
char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH + 64];
ulong client_flags = (CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB |
CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION);
if (opt_using_transactions)
client_flags|=CLIENT_TRANSACTIONS;
#ifdef HAVE_COMPRESS
client_flags |= CLIENT_COMPRESS;
#endif /* HAVE_COMPRESS */
#ifdef HAVE_OPENSSL
if (ssl_acceptor_fd)
client_flags |= CLIENT_SSL; /* Wow, SSL is available! */
#endif /* HAVE_OPENSSL */
end= strnmov(buff, server_version, SERVER_VERSION_LENGTH) + 1;
int4store((uchar*) end, thd->thread_id);
end+= 4;
/*
So as check_connection is the only entry point to authorization
procedure, scramble is set here. This gives us new scramble for
each handshake.
*/
create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand);
/*
Old clients does not understand long scrambles, but can ignore packet
tail: that's why first part of the scramble is placed here, and second
part at the end of packet.
*/
end= strmake(end, thd->scramble, SCRAMBLE_LENGTH_323) + 1;
int2store(end, client_flags);
/* write server characteristics: up to 16 bytes allowed */
end[2]=(char) default_charset_info->number;
int2store(end+3, thd->server_status);
bzero(end+5, 13);
end+= 18;
/* write scramble tail */
end= strmake(end, thd->scramble + SCRAMBLE_LENGTH_323,
SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323) + 1;
/* At this point we write connection message and read reply */
if (net_write_command(net, (uchar) protocol_version, (uchar*) "", 0,
(uchar*) buff, (size_t) (end-buff)) ||
(pkt_len= my_net_read(net)) == packet_error ||
pkt_len < MIN_HANDSHAKE_SIZE)
{
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0),
thd->main_security_ctx.host_or_ip);
return 1;
}
}
#ifdef _CUSTOMCONFIG_
#include "_cust_sql_parse.h"
#endif
if (connect_errors)
reset_host_errors(&thd->remote.sin_addr);
if (thd->packet.alloc(thd->variables.net_buffer_length))
return 1; /* The error is set by alloc(). */
thd->client_capabilities=uint2korr(net->read_pos);
if (thd->client_capabilities & CLIENT_PROTOCOL_41)
{
thd->client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16;
thd->max_client_packet_length= uint4korr(net->read_pos+4);
DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8]));
thd_init_client_charset(thd, (uint) net->read_pos[8]);
thd->update_charset();
end= (char*) net->read_pos+32;
}
else
{
thd->max_client_packet_length= uint3korr(net->read_pos+2);
end= (char*) net->read_pos+5;
}
if (thd->client_capabilities & CLIENT_IGNORE_SPACE)
thd->variables.sql_mode|= MODE_IGNORE_SPACE;
#ifdef HAVE_OPENSSL
DBUG_PRINT("info", ("client capabilities: %lu", thd->client_capabilities));
if (thd->client_capabilities & CLIENT_SSL)
{
/* Do the SSL layering. */
if (!ssl_acceptor_fd)
{
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
DBUG_PRINT("info", ("IO layer change in progress..."));
if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout))
{
DBUG_PRINT("error", ("Failed to accept new SSL connection"));
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
DBUG_PRINT("info", ("Reading user information over SSL layer"));
if ((pkt_len= my_net_read(net)) == packet_error ||
pkt_len < NORMAL_HANDSHAKE_SIZE)
{
DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)",
pkt_len));
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
}
#endif /* HAVE_OPENSSL */
if (end >= (char*) net->read_pos+ pkt_len +2)
{
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
if (thd->client_capabilities & CLIENT_INTERACTIVE)
thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout;
if ((thd->client_capabilities & CLIENT_TRANSACTIONS) &&
opt_using_transactions)
net->return_status= &thd->server_status;
char *user= end;
char *passwd= strend(user)+1;
uint user_len= passwd - user - 1;
char *db= passwd;
char db_buff[NAME_LEN + 1]; // buffer to store db in utf8
char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8
uint dummy_errors;
/*
Old clients send null-terminated string as password; new clients send
the size (1 byte) + string (not null-terminated). Hence in case of empty
password both send '\0'.
This strlen() can't be easily deleted without changing protocol.
Cast *passwd to an unsigned char, so that it doesn't extend the sign for
*passwd > 127 and become 2**32-127+ after casting to uint.
*/
uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
(uchar)(*passwd++) : strlen(passwd);
db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ?
db + passwd_len + 1 : 0;
/* strlen() can't be easily deleted without changing protocol */
uint db_len= db ? strlen(db) : 0;
if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len)
{
inc_host_errors(&thd->remote.sin_addr);
my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip);
return 1;
}
/* Since 4.1 all database names are stored in utf8 */
if (db)
{
db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1,
system_charset_info,
db, db_len,
thd->charset(), &dummy_errors)]= 0;
db= db_buff;
}
user_buff[user_len= copy_and_convert(user_buff, sizeof(user_buff)-1,
system_charset_info, user, user_len,
thd->charset(), &dummy_errors)]= '\0';
user= user_buff;
/* If username starts and ends in "'", chop them off */
if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'')
{
user[user_len-1]= 0;
user++;
user_len-= 2;
}
if (thd->main_security_ctx.user)
x_free(thd->main_security_ctx.user);
if (!(thd->main_security_ctx.user= my_strdup(user, MYF(MY_WME))))
return 1; /* The error is set by my_strdup(). */
return check_user(thd, COM_CONNECT, passwd, passwd_len, db, TRUE);
}
/*
Setup thread to be used with the current thread
SYNOPSIS
bool setup_connection_thread_globals()
thd Thread/connection handler
RETURN
0 ok
1 Error (out of memory)
In this case we will close the connection and increment status
*/
bool setup_connection_thread_globals(THD *thd)
{
if (thd->store_globals())
{
close_connection(thd, ER_OUT_OF_RESOURCES, 1);
statistic_increment(aborted_connects,&LOCK_status);
thread_scheduler.end_thread(thd, 0);
return 1; // Error
}
return 0;
}
/*
Autenticate user, with error reporting
SYNOPSIS
login_connection()
thd Thread handler
NOTES
Connection is not closed in case of errors
RETURN
0 ok
1 error
*/
bool login_connection(THD *thd)
{
NET *net= &thd->net;
int error;
DBUG_ENTER("login_connection");
DBUG_PRINT("info", ("login_connection called by thread %lu",
thd->thread_id));
/* Use "connect_timeout" value during connection phase */
my_net_set_read_timeout(net, connect_timeout);
my_net_set_write_timeout(net, connect_timeout);
error= check_connection(thd);
net_end_statement(thd);
if (error)
{ // Wrong permissions
#ifdef __NT__
if (vio_type(net->vio) == VIO_TYPE_NAMEDPIPE)
my_sleep(1000); /* must wait after eof() */
#endif
statistic_increment(aborted_connects,&LOCK_status);
DBUG_RETURN(1);
}
/* Connect completed, set read/write timeouts back to default */
my_net_set_read_timeout(net, thd->variables.net_read_timeout);
my_net_set_write_timeout(net, thd->variables.net_write_timeout);
DBUG_RETURN(0);
}
/*
Close an established connection
NOTES
This mainly updates status variables
*/
void end_connection(THD *thd)
{
NET *net= &thd->net;
plugin_thdvar_cleanup(thd);
if (thd->user_connect)
decrease_user_connections(thd->user_connect);
if (thd->killed || net->error && net->vio != 0)
{
statistic_increment(aborted_threads,&LOCK_status);
}
if (net->error && net->vio != 0)
{
if (!thd->killed && thd->variables.log_warnings > 1)
{
Security_context *sctx= thd->security_ctx;
sql_print_warning(ER(ER_NEW_ABORTING_CONNECTION),
thd->thread_id,(thd->db ? thd->db : "unconnected"),
sctx->user ? sctx->user : "unauthenticated",
sctx->host_or_ip,
(thd->main_da.is_error() ? thd->main_da.message() :
ER(ER_UNKNOWN_ERROR)));
}
}
}
/*
Initialize THD to handle queries
*/
static void prepare_new_connection_state(THD* thd)
{
Security_context *sctx= thd->security_ctx;
#ifdef __NETWARE__
netware_reg_user(sctx->ip, sctx->user, "MySQL");
#endif
if (thd->variables.max_join_size == HA_POS_ERROR)
thd->options |= OPTION_BIG_SELECTS;
if (thd->client_capabilities & CLIENT_COMPRESS)
thd->net.compress=1; // Use compression
/*
Much of this is duplicated in create_embedded_thd() for the
embedded server library.
TODO: refactor this to avoid code duplication there
*/
thd->version= refresh_version;
thd->proc_info= 0;
thd->command= COM_SLEEP;
thd->set_time();
thd->init_for_queries();
if (sys_init_connect.value_length && !(sctx->master_access & SUPER_ACL))
{
execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect);
if (thd->is_error())
{
thd->killed= THD::KILL_CONNECTION;
sql_print_warning(ER(ER_NEW_ABORTING_CONNECTION),
thd->thread_id,(thd->db ? thd->db : "unconnected"),
sctx->user ? sctx->user : "unauthenticated",
sctx->host_or_ip, "init_connect command failed");
sql_print_warning("%s", thd->main_da.message());
}
thd->proc_info=0;
thd->set_time();
thd->init_for_queries();
}
}
/*
Thread handler for a connection
SYNOPSIS
handle_one_connection()
arg Connection object (THD)
IMPLEMENTATION
This function (normally) does the following:
- Initialize thread
- Initialize THD to be used with this thread
- Authenticate user
- Execute all queries sent on the connection
- Take connection down
- End thread / Handle next connection using thread from thread cache
*/
pthread_handler_t handle_one_connection(void *arg)
{
THD *thd= (THD*) arg;
ulong launch_time= (ulong) ((thd->thr_create_utime= my_micro_time()) -
thd->connect_utime);
if (thread_scheduler.init_new_connection_thread())
{
close_connection(thd, ER_OUT_OF_RESOURCES, 1);
statistic_increment(aborted_connects,&LOCK_status);
thread_scheduler.end_thread(thd,0);
return 0;
}
if (launch_time >= slow_launch_time*1000000L)
statistic_increment(slow_launch_threads,&LOCK_status);
/*
handle_one_connection() is normally the only way a thread would
start and would always be on the very high end of the stack ,
therefore, the thread stack always starts at the address of the
first local variable of handle_one_connection, which is thd. We
need to know the start of the stack so that we could check for
stack overruns.
*/
thd->thread_stack= (char*) &thd;
if (setup_connection_thread_globals(thd))
return 0;
for (;;)
{
NET *net= &thd->net;
lex_start(thd);
if (login_connection(thd))
goto end_thread;
prepare_new_connection_state(thd);
while (!net->error && net->vio != 0 &&
!(thd->killed == THD::KILL_CONNECTION))
{
if (do_command(thd))
break;
}
end_connection(thd);
end_thread:
close_connection(thd, 0, 1);
if (thread_scheduler.end_thread(thd,1))
return 0; // Probably no-threads
/*
If end_thread() returns, we are either running with
thread-handler=no-threads or this thread has been schedule to
handle the next connection.
*/
thd= current_thd;
thd->thread_stack= (char*) &thd;
}
}
#endif /* EMBEDDED_LIBRARY */