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