1 /*
2 * Copyright (c) 2015, 2021, Oracle and/or its affiliates.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2.0,
6 * as published by the Free Software Foundation.
7 *
8 * This program is also distributed with certain software (including
9 * but not limited to OpenSSL) that is licensed under separate terms,
10 * as designated in a particular file or component or in included license
11 * documentation. The authors of MySQL hereby grant you an additional
12 * permission to link the program and your derivative works with the
13 * separately licensed software that they have included with MySQL.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License, version 2.0, for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23 * 02110-1301 USA
24 */
25
26 #include <algorithm>
27 #include "mysql/plugin.h"
28 #include "sql_data_context.h"
29 #include "sql_user_require.h"
30 #include "mysql/service_command.h"
31 #include "streaming_command_delegate.h"
32 #include "buffering_command_delegate.h"
33 #include "mysql_variables.h"
34 #include "query_string_builder.h"
35
36 #include "xpl_log.h"
37 #include "xpl_error.h"
38 #include "notices.h"
39
40 #include "user_verification_helper.h"
41
42
43 using namespace xpl;
44
init(const int client_port,const ngs::Connection_type type)45 ngs::Error_code Sql_data_context::init(const int client_port, const ngs::Connection_type type)
46 {
47 ngs::Error_code error = init();
48 if (error)
49 return error;
50
51 if ((error = set_connection_type(type)))
52 return error;
53
54 if (0 != srv_session_info_set_client_port(m_mysql_session, client_port))
55 return ngs::Error_code(ER_X_SESSION, "Could not set session client port");
56
57 return ngs::Error_code();
58 }
59
60
init()61 ngs::Error_code Sql_data_context::init()
62 {
63 m_mysql_session = srv_session_open(&Sql_data_context::default_completion_handler, this);
64 log_debug("sqlsession init: %p [%i]", m_mysql_session, m_mysql_session ? srv_session_info_get_session_id(m_mysql_session) : -1);
65 if (!m_mysql_session)
66 {
67 if (ER_SERVER_ISNT_AVAILABLE == m_last_sql_errno)
68 return ngs::Error_code(ER_SERVER_ISNT_AVAILABLE, "Server API not ready");
69 log_error("Could not open internal MySQL session");
70 return ngs::Error_code(ER_X_SESSION, "Could not open session");
71 }
72 return ngs::Error_code();
73 }
74
deinit()75 void Sql_data_context::deinit()
76 {
77 if (m_mysql_session)
78 {
79 srv_session_detach(m_mysql_session);
80
81 log_debug("sqlsession deinit: %p [%i]", m_mysql_session, srv_session_info_get_session_id(m_mysql_session));
82 srv_session_close(m_mysql_session);
83 m_mysql_session = NULL;
84 }
85
86 #ifdef HAVE_PSI_THREAD_INTERFACE
87 PSI_THREAD_CALL(delete_current_thread)();
88
89 PSI_thread *psi= PSI_THREAD_CALL(new_thread) (KEY_thread_x_worker, NULL, 0);
90 PSI_THREAD_CALL(set_thread_os_id)(psi);
91 PSI_THREAD_CALL(set_thread)(psi);
92 #endif /* HAVE_PSI_THREAD_INTERFACE */
93 }
94
95
kill_completion_handler(void * ctx,unsigned int sql_errno,const char * err_msg)96 static void kill_completion_handler(void *ctx, unsigned int sql_errno, const char *err_msg)
97 {
98 log_warning("Kill client: %i %s", sql_errno, err_msg);
99 }
100
101
kill()102 bool Sql_data_context::kill()
103 {
104 if (srv_session_server_is_available())
105 {
106 log_debug("sqlsession init (for kill): %p [%i]", m_mysql_session, m_mysql_session ? srv_session_info_get_session_id(m_mysql_session) : -1);
107 MYSQL_SESSION session = srv_session_open(kill_completion_handler, NULL);
108 bool ok = false;
109 if (session)
110 {
111 MYSQL_SECURITY_CONTEXT scontext;
112
113 if (thd_get_security_context(srv_session_info_get_thd(session), &scontext))
114 log_warning("Could not get security context for session");
115 else {
116 const char *user = MYSQL_SESSION_USER;
117 const char *host = MYSQLXSYS_HOST;
118 if (security_context_lookup(scontext, user, host, NULL, NULL))
119 log_warning("Unable to switch security context to root");
120 else
121 {
122 COM_DATA data;
123 Callback_command_delegate deleg;
124 Query_string_builder qb;
125 qb.put("KILL ").put(mysql_session_id());
126
127 data.com_query.query = (char*)qb.get().c_str();
128 data.com_query.length = static_cast<unsigned int>(qb.get().length());
129
130 if (!command_service_run_command(session, COM_QUERY, &data,
131 mysqld::get_charset_utf8mb4_general_ci(), deleg.callbacks(),
132 deleg.representation(), &deleg))
133 {
134 if (!deleg.get_error())
135 ok = true;
136 else
137 log_info("Kill client: %i %s", deleg.get_error().error, deleg.get_error().message.c_str());
138 }
139 }
140 }
141 srv_session_close(session);
142 }
143 return ok;
144 }
145 return false;
146 }
147
148
set_connection_type(const ngs::Connection_type type)149 ngs::Error_code Sql_data_context::set_connection_type(const ngs::Connection_type type)
150 {
151 enum_vio_type vio_type = ngs::Connection_type_helper::convert_type(type);
152
153 if (NO_VIO_TYPE == vio_type)
154 return ngs::Error(ER_X_SESSION, "Connection type not known. type=%i", (int)type);
155
156 if (0 != srv_session_info_set_connection_type(m_mysql_session, vio_type))
157 return ngs::Error_code(ER_X_SESSION, "Could not set session connection type");
158
159 return ngs::Error_code();
160 }
161
wait_api_ready(ngs::function<bool ()> exiting)162 bool Sql_data_context::wait_api_ready(ngs::function<bool()> exiting)
163 {
164 bool result = is_api_ready();
165
166 while (!result && !exiting())
167 {
168 my_sleep(250000); // wait for 0.25s
169
170 result = is_api_ready();
171 }
172
173 return result;
174 }
175
176
detach()177 void Sql_data_context::detach()
178 {
179 if (m_mysql_session)
180 srv_session_detach(m_mysql_session);
181 }
182
183
~Sql_data_context()184 Sql_data_context::~Sql_data_context()
185 {
186 if (m_mysql_session)
187 log_debug("sqlsession deinit~: %p [%i]", m_mysql_session, srv_session_info_get_session_id(m_mysql_session));
188 if (m_mysql_session && srv_session_close(m_mysql_session))
189 log_warning("Error closing SQL session");
190 }
191
192
switch_to_local_user(const std::string & user)193 void Sql_data_context::switch_to_local_user(const std::string &user)
194 {
195 ngs::Error_code error = switch_to_user(user.c_str(), "localhost", NULL, NULL);
196 if (error)
197 throw error;
198 }
199
200
authenticate(const char * user,const char * host,const char * ip,const char * db,On_user_password_hash password_hash_cb,bool allow_expired_passwords,ngs::IOptions_session_ptr & options_session,const ngs::Connection_type type)201 ngs::Error_code Sql_data_context::authenticate(const char *user, const char *host, const char *ip,
202 const char *db, On_user_password_hash password_hash_cb,
203 bool allow_expired_passwords, ngs::IOptions_session_ptr &options_session, const ngs::Connection_type type)
204 {
205 ngs::Error_code error = switch_to_user(user, host, ip, db);
206
207 if (error)
208 {
209 return ngs::Error(ER_NO_SUCH_USER, "Invalid user or password");
210 }
211
212 std::string authenticated_user_name = get_authenticated_user_name();
213 std::string authenticated_user_host = get_authenticated_user_host();
214
215 error = switch_to_user(MYSQL_SESSION_USER, MYSQLXSYS_HOST, NULL, NULL);
216
217 if (error) {
218 log_error("Unable to switch context to user %s", MYSQL_SESSION_USER);
219 return error;
220 }
221
222 if (!is_acl_disabled())
223 {
224 User_verification_helper user_verification(password_hash_cb, options_session, type);
225
226 error = user_verification.verify_mysql_account(
227 *this,
228 authenticated_user_name,
229 authenticated_user_host);
230 }
231
232 if (error.error == ER_MUST_CHANGE_PASSWORD_LOGIN)
233 {
234 m_password_expired = true;
235
236 // password is expired, client doesn't support it and server wants us to disconnect these users
237 if (error.severity == ngs::Error_code::FATAL && !allow_expired_passwords)
238 return error;
239
240 // if client supports expired password mode, then pwd expired is not fatal
241 // we send a notice and move on
242 notices::send_account_expired(proto());
243 }
244 else if (error)
245 return error;
246
247 error = switch_to_user(user, host, ip, db);
248
249 if (!error)
250 {
251 if (db && *db)
252 {
253 COM_DATA data;
254
255 data.com_init_db.db_name = db;
256 data.com_init_db.length = static_cast<unsigned long>(strlen(db));
257
258 m_callback_delegate.reset();
259
260 if (command_service_run_command(m_mysql_session, COM_INIT_DB, &data, mysqld::get_charset_utf8mb4_general_ci(),
261 m_callback_delegate.callbacks(), m_callback_delegate.representation(), &m_callback_delegate))
262 return ngs::Error_code(ER_NO_DB_ERROR, "Could not set database");
263 error = m_callback_delegate.get_error();
264 }
265
266 std::string user_name = get_user_name();
267 std::string host_or_ip = get_host_or_ip();
268
269 #ifdef HAVE_PSI_THREAD_INTERFACE
270 PSI_THREAD_CALL(set_thread_account) (
271 user_name.c_str(), user_name.length(),
272 host_or_ip.c_str(), host_or_ip.length());
273 #endif // HAVE_PSI_THREAD_INTERFACE
274
275 return error;
276 }
277
278 log_error("Unable to switch context to user %s", user);
279
280 return error;
281 }
282
283 template<typename Result_type>
get_security_context_value(MYSQL_THD thd,const char * option,Result_type & result)284 bool get_security_context_value(MYSQL_THD thd, const char *option, Result_type &result)
285 {
286 MYSQL_SECURITY_CONTEXT scontext;
287
288 if (thd_get_security_context(thd, &scontext))
289 return false;
290
291 return FALSE == security_context_get_option(scontext, option, &result);
292 }
293
is_acl_disabled()294 bool Sql_data_context::is_acl_disabled()
295 {
296 MYSQL_LEX_CSTRING value;
297
298 if (get_security_context_value(get_thd(), "priv_user", value))
299 {
300 return 0 != value.length &&
301 NULL != strstr(value.str, "skip-grants ");
302 }
303
304 return false;
305 }
306
has_authenticated_user_a_super_priv() const307 bool Sql_data_context::has_authenticated_user_a_super_priv() const
308 {
309 my_svc_bool value = 0;
310 if (get_security_context_value(get_thd(), "privilege_super", value))
311 return value != 0;
312
313 return false;
314 }
315
get_user_name() const316 std::string Sql_data_context::get_user_name() const
317 {
318 MYSQL_LEX_CSTRING result;
319
320 if (get_security_context_value(get_thd(), "user", result))
321 return result.str;
322
323 return "";
324 }
325
get_host_or_ip() const326 std::string Sql_data_context::get_host_or_ip() const
327 {
328 MYSQL_LEX_CSTRING result;
329
330 if (get_security_context_value(get_thd(), "host_or_ip", result))
331 return result.str;
332
333 return "";
334 }
335
get_authenticated_user_name() const336 std::string Sql_data_context::get_authenticated_user_name() const
337 {
338 MYSQL_LEX_CSTRING result;
339
340 if (get_security_context_value(get_thd(), "priv_user", result))
341 return result.str;
342
343 return "";
344 }
345
get_authenticated_user_host() const346 std::string Sql_data_context::get_authenticated_user_host() const
347 {
348 MYSQL_LEX_CSTRING result;
349
350 if (get_security_context_value(get_thd(), "priv_host", result))
351 return result.str;
352
353 return "";
354 }
355
switch_to_user(const char * username,const char * hostname,const char * address,const char * db)356 ngs::Error_code Sql_data_context::switch_to_user(
357 const char *username,
358 const char *hostname,
359 const char *address,
360 const char *db)
361 {
362 MYSQL_SECURITY_CONTEXT scontext;
363 m_auth_ok = false;
364
365 if (thd_get_security_context(get_thd(), &scontext))
366 return ngs::Fatal(ER_X_SERVICE_ERROR, "Error getting security context for session");
367
368 // security_context_lookup - doesn't make a copy of username, hostname, addres or db
369 // thus we need to make a copy of them and pass our pointers
370 // to security_context_lookup
371 m_username = username ? username : "";
372 m_hostname = hostname ? hostname : "";
373 m_address = address ? address : "";
374 m_db = db ? db : "";
375
376 log_debug("Switching security context to user %s@%s [%s]", username, hostname, address);
377 if (security_context_lookup(scontext, m_username.c_str(), m_hostname.c_str(), m_address.c_str(), m_db.c_str()))
378 {
379 return ngs::Fatal(ER_X_SERVICE_ERROR, "Unable to switch context to user %s", username);
380 }
381
382 m_auth_ok = true;
383
384 return ngs::Success();
385 }
386
387
execute_kill_sql_session(uint64_t mysql_session_id)388 ngs::Error_code Sql_data_context::execute_kill_sql_session(uint64_t mysql_session_id)
389 {
390 Query_string_builder qb;
391 qb.put("KILL ").put(mysql_session_id);
392 Sql_data_context::Result_info r_info;
393
394 return execute_sql_no_result(qb.get().data(), qb.get().length(), r_info);
395 }
396
397
execute_sql(Command_delegate & deleg,const char * sql,size_t length,Sql_data_context::Result_info & r_info)398 ngs::Error_code Sql_data_context::execute_sql(
399 Command_delegate &deleg,
400 const char *sql,
401 size_t length,
402 Sql_data_context::Result_info &r_info)
403 {
404 if (!m_auth_ok && !m_query_without_authentication)
405 throw std::logic_error("Attempt to execute query in non-authenticated session");
406
407 COM_DATA data;
408
409 data.com_query.query = sql;
410 data.com_query.length = static_cast<unsigned int>(length);
411
412 deleg.reset();
413
414 if (command_service_run_command(m_mysql_session, COM_QUERY, &data, mysqld::get_charset_utf8mb4_general_ci(),
415 deleg.callbacks(), deleg.representation(), &deleg))
416 {
417 log_debug("Error running command: %s (%i %s)", sql, m_last_sql_errno, m_last_sql_error.c_str());
418 return ngs::Error_code(ER_X_SERVICE_ERROR, "Internal error executing query");
419 }
420
421 if (m_password_expired && !deleg.get_error())
422 {
423 // if a SQL command succeeded while password is expired, it means the user probably changed the password
424 // we run a command to check just in case... (some commands are still allowed in expired password mode)
425 Callback_command_delegate d;
426 data.com_query.query = "select 1";
427 data.com_query.length = static_cast<unsigned int>(strlen(data.com_query.query));
428 if (false == command_service_run_command(m_mysql_session, COM_QUERY, &data, mysqld::get_charset_utf8mb4_general_ci(),
429 d.callbacks(), d.representation(), &d) && !d.get_error())
430 {
431 m_password_expired = false;
432 }
433 }
434
435 if (is_killed())
436 throw ngs::Fatal(ER_QUERY_INTERRUPTED, "Query execution was interrupted");
437
438 r_info.last_insert_id = deleg.last_insert_id();
439 r_info.num_warnings = deleg.statement_warn_count();
440 r_info.affected_rows = deleg.affected_rows();
441 r_info.message = deleg.message();
442 r_info.server_status = deleg.server_status();
443
444 return deleg.get_error();
445 }
446
447
execute_sql_no_result(const char * sql,std::size_t sql_len,Sql_data_context::Result_info & r_info)448 ngs::Error_code Sql_data_context::execute_sql_no_result(const char *sql, std::size_t sql_len,
449 Sql_data_context::Result_info &r_info)
450 {
451 m_callback_delegate.set_callbacks(Callback_command_delegate::Start_row_callback(),
452 Callback_command_delegate::End_row_callback());
453 return execute_sql(m_callback_delegate, sql, sql_len, r_info);
454 }
455
456
execute_sql_and_collect_results(const char * sql,std::size_t sql_len,std::vector<Command_delegate::Field_type> & r_types,Buffering_command_delegate::Resultset & r_rows,Result_info & r_info)457 ngs::Error_code Sql_data_context::execute_sql_and_collect_results(const char *sql, std::size_t sql_len,
458 std::vector<Command_delegate::Field_type> &r_types,
459 Buffering_command_delegate::Resultset &r_rows,
460 Result_info &r_info)
461 {
462 ngs::Error_code error = execute_sql(m_buffering_delegate, sql, sql_len, r_info);
463 if (!error)
464 {
465 r_types = m_buffering_delegate.get_field_types();
466 r_rows = m_buffering_delegate.resultset();
467 }
468 return error;
469 }
470
execute_sql_and_process_results(const char * sql,std::size_t sql_len,const Callback_command_delegate::Start_row_callback & start_row,const Callback_command_delegate::End_row_callback & end_row,Sql_data_context::Result_info & r_info)471 ngs::Error_code Sql_data_context::execute_sql_and_process_results(const char *sql, std::size_t sql_len,
472 const Callback_command_delegate::Start_row_callback &start_row,
473 const Callback_command_delegate::End_row_callback &end_row,
474 Sql_data_context::Result_info &r_info)
475 {
476 m_callback_delegate.set_callbacks(start_row, end_row);
477 return execute_sql(m_callback_delegate, sql, sql_len, r_info);
478 }
479
480
execute_sql_and_stream_results(const char * sql,std::size_t sql_len,bool compact_metadata,Result_info & r_info)481 ngs::Error_code Sql_data_context::execute_sql_and_stream_results(const char *sql, std::size_t sql_len,
482 bool compact_metadata, Result_info &r_info)
483 {
484 m_streaming_delegate.set_compact_metadata(compact_metadata);
485 return execute_sql(m_streaming_delegate, sql, sql_len, r_info);
486 }
487
488
default_completion_handler(void * ctx,unsigned int sql_errno,const char * err_msg)489 void Sql_data_context::default_completion_handler(void *ctx, unsigned int sql_errno, const char *err_msg)
490 {
491 Sql_data_context *self = (Sql_data_context*)ctx;
492 self->m_last_sql_errno = sql_errno;
493 self->m_last_sql_error = err_msg ? err_msg : "";
494 }
495
496
is_killed()497 bool Sql_data_context::is_killed()
498 {
499 return srv_session_info_killed(m_mysql_session);
500 }
501
502
is_api_ready()503 bool Sql_data_context::is_api_ready()
504 {
505 return 0 != srv_session_server_is_available();
506 }
507
508
mysql_session_id() const509 uint64_t Sql_data_context::mysql_session_id() const
510 {
511 return srv_session_info_get_session_id(m_mysql_session);
512 }
513
514
get_thd() const515 MYSQL_THD Sql_data_context::get_thd() const
516 {
517 return srv_session_info_get_thd(m_mysql_session);
518 }
519