1 /* Copyright (c) 2010, 2021, Oracle and/or its affiliates.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 #include "sql_alter.h"
24 
25 #include "auth_common.h"                     // check_access
26 #include "sql_table.h"                       // mysql_alter_table,
27                                              // mysql_exchange_partition
28 #include "sql_base.h"                        // open_temporary_tables
29 #include "log.h"
30 
31 bool has_external_data_or_index_dir(partition_info &pi);
32 
Alter_info(const Alter_info & rhs,MEM_ROOT * mem_root)33 Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root)
34   :drop_list(rhs.drop_list, mem_root),
35   alter_list(rhs.alter_list, mem_root),
36   key_list(rhs.key_list, mem_root),
37   alter_rename_key_list(rhs.alter_rename_key_list, mem_root),
38   create_list(rhs.create_list, mem_root),
39   flags(rhs.flags),
40   keys_onoff(rhs.keys_onoff),
41   partition_names(rhs.partition_names, mem_root),
42   num_parts(rhs.num_parts),
43   requested_algorithm(rhs.requested_algorithm),
44   requested_lock(rhs.requested_lock),
45   with_validation(rhs.with_validation)
46 {
47   /*
48     Make deep copies of used objects.
49     This is not a fully deep copy - clone() implementations
50     of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec
51     do not copy string constants. At the same length the only
52     reason we make a copy currently is that ALTER/CREATE TABLE
53     code changes input Alter_info definitions, but string
54     constants never change.
55   */
56   list_copy_and_replace_each_value(drop_list, mem_root);
57   list_copy_and_replace_each_value(alter_list, mem_root);
58   list_copy_and_replace_each_value(key_list, mem_root);
59   list_copy_and_replace_each_value(alter_rename_key_list, mem_root);
60   list_copy_and_replace_each_value(create_list, mem_root);
61   /* partition_names are not deeply copied currently */
62 }
63 
64 
set_requested_algorithm(const LEX_STRING * str)65 bool Alter_info::set_requested_algorithm(const LEX_STRING *str)
66 {
67   // To avoid adding new keywords to the grammar, we match strings here.
68   if (!my_strcasecmp(system_charset_info, str->str, "INPLACE"))
69     requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE;
70   else if (!my_strcasecmp(system_charset_info, str->str, "COPY"))
71     requested_algorithm= ALTER_TABLE_ALGORITHM_COPY;
72   else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
73     requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
74   else
75     return true;
76   return false;
77 }
78 
79 
set_requested_lock(const LEX_STRING * str)80 bool Alter_info::set_requested_lock(const LEX_STRING *str)
81 {
82   // To avoid adding new keywords to the grammar, we match strings here.
83   if (!my_strcasecmp(system_charset_info, str->str, "NONE"))
84     requested_lock= ALTER_TABLE_LOCK_NONE;
85   else if (!my_strcasecmp(system_charset_info, str->str, "SHARED"))
86     requested_lock= ALTER_TABLE_LOCK_SHARED;
87   else if (!my_strcasecmp(system_charset_info, str->str, "EXCLUSIVE"))
88     requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE;
89   else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
90     requested_lock= ALTER_TABLE_LOCK_DEFAULT;
91   else
92     return true;
93   return false;
94 }
95 
96 
Alter_table_ctx()97 Alter_table_ctx::Alter_table_ctx()
98   : datetime_field(NULL), error_if_not_empty(false),
99     tables_opened(0),
100     db(NULL), table_name(NULL), alias(NULL),
101     new_db(NULL), new_name(NULL), new_alias(NULL)
102 #ifndef NDEBUG
103     , tmp_table(false)
104 #endif
105 {
106 }
107 
108 
Alter_table_ctx(THD * thd,TABLE_LIST * table_list,uint tables_opened_arg,const char * new_db_arg,const char * new_name_arg)109 Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list,
110                                  uint tables_opened_arg,
111                                  const char *new_db_arg,
112                                  const char *new_name_arg)
113   : datetime_field(NULL), error_if_not_empty(false),
114     tables_opened(tables_opened_arg),
115     new_db(new_db_arg), new_name(new_name_arg)
116 #ifndef NDEBUG
117     , tmp_table(false)
118 #endif
119 {
120   /*
121     Assign members db, table_name, new_db and new_name
122     to simplify further comparisions: we want to see if it's a RENAME
123     later just by comparing the pointers, avoiding the need for strcmp.
124   */
125   db= table_list->db;
126   table_name= table_list->table_name;
127   alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
128 
129   if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
130     new_db= db;
131 
132   if (new_name)
133   {
134     DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name));
135 
136     if (lower_case_table_names == 1) // Convert new_name/new_alias to lower case
137     {
138       my_casedn_str(files_charset_info, const_cast<char*>(new_name));
139       new_alias= new_name;
140     }
141     else if (lower_case_table_names == 2) // Convert new_name to lower case
142     {
143       my_stpcpy(new_alias_buff, new_name);
144       new_alias= (const char*)new_alias_buff;
145       my_casedn_str(files_charset_info, const_cast<char*>(new_name));
146     }
147     else
148       new_alias= new_name; // LCTN=0 => case sensitive + case preserving
149 
150     if (!is_database_changed() &&
151         !my_strcasecmp(table_alias_charset, new_name, table_name))
152     {
153       /*
154         Source and destination table names are equal:
155         make is_table_renamed() more efficient.
156       */
157       new_alias= table_name;
158       new_name= table_name;
159     }
160   }
161   else
162   {
163     new_alias= alias;
164     new_name= table_name;
165   }
166 
167   my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%x", tmp_file_prefix,
168               current_pid, thd->thread_id());
169   /* Safety fix for InnoDB */
170   if (lower_case_table_names)
171     my_casedn_str(files_charset_info, tmp_name);
172 
173   if (table_list->table->s->tmp_table == NO_TMP_TABLE)
174   {
175     build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
176 
177     build_table_filename(new_path, sizeof(new_path) - 1, new_db, new_name, "", 0);
178 
179     build_table_filename(new_filename, sizeof(new_filename) - 1,
180                          new_db, new_name, reg_ext, 0);
181 
182     build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db, tmp_name, "",
183                          FN_IS_TMP);
184   }
185   else
186   {
187     /*
188       We are not filling path, new_path and new_filename members if
189       we are altering temporary table as these members are not used in
190       this case. This fact is enforced with assert.
191     */
192     build_tmptable_filename(thd, tmp_path, sizeof(tmp_path));
193 #ifndef NDEBUG
194     tmp_table= true;
195 #endif
196   }
197 }
198 
199 
execute(THD * thd)200 bool Sql_cmd_alter_table::execute(THD *thd)
201 {
202   LEX *lex= thd->lex;
203   /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
204   SELECT_LEX *select_lex= lex->select_lex;
205   /* first table of first SELECT_LEX */
206   TABLE_LIST *first_table= select_lex->get_table_list();
207   /*
208     Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
209     so we have to use a copy of this structure to make execution
210     prepared statement- safe. A shallow copy is enough as no memory
211     referenced from this structure will be modified.
212     @todo move these into constructor...
213   */
214   HA_CREATE_INFO create_info(lex->create_info);
215   Alter_info alter_info(lex->alter_info, thd->mem_root);
216   ulong priv=0;
217   ulong priv_needed= ALTER_ACL;
218   bool result;
219 
220   DBUG_ENTER("Sql_cmd_alter_table::execute");
221 
222   if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
223     DBUG_RETURN(TRUE);
224 
225   {
226     partition_info *part_info= thd->lex->part_info;
227     if (part_info != NULL && has_external_data_or_index_dir(*part_info) &&
228         check_access(thd, FILE_ACL, any_db, NULL, NULL, FALSE, FALSE))
229 
230       DBUG_RETURN(TRUE);
231   }
232   /*
233     We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well
234     as for RENAME TO, as being done by SQLCOM_RENAME_TABLE
235   */
236   if (alter_info.flags & (Alter_info::ALTER_DROP_PARTITION |
237                           Alter_info::ALTER_RENAME))
238     priv_needed|= DROP_ACL;
239 
240   /* Must be set in the parser */
241   assert(select_lex->db);
242   assert(!(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION));
243   assert(!(alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION));
244   if (check_access(thd, priv_needed, first_table->db,
245                    &first_table->grant.privilege,
246                    &first_table->grant.m_internal,
247                    0, 0) ||
248       check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db,
249                    &priv,
250                    NULL, /* Don't use first_tab->grant with sel_lex->db */
251                    0, 0))
252     DBUG_RETURN(TRUE);                  /* purecov: inspected */
253 
254   /* If it is a merge table, check privileges for merge children. */
255   if (create_info.merge_list.first)
256   {
257     /*
258       The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
259       underlying base tables, even if there are temporary tables with the same
260       names.
261 
262       From user's point of view, it might look as if the user must have these
263       privileges on temporary tables to create a merge table over them. This is
264       one of two cases when a set of privileges is required for operations on
265       temporary tables (see also CREATE TABLE).
266 
267       The reason for this behavior stems from the following facts:
268 
269         - For merge tables, the underlying table privileges are checked only
270           at CREATE TABLE / ALTER TABLE time.
271 
272           In other words, once a merge table is created, the privileges of
273           the underlying tables can be revoked, but the user will still have
274           access to the merge table (provided that the user has privileges on
275           the merge table itself).
276 
277         - Temporary tables shadow base tables.
278 
279           I.e. there might be temporary and base tables with the same name, and
280           the temporary table takes the precedence in all operations.
281 
282         - For temporary MERGE tables we do not track if their child tables are
283           base or temporary. As result we can't guarantee that privilege check
284           which was done in presence of temporary child will stay relevant later
285           as this temporary table might be removed.
286 
287       If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
288       the underlying *base* tables, it would create a security breach as in
289       Bug#12771903.
290     */
291 
292     if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
293                            create_info.merge_list.first, FALSE, UINT_MAX, FALSE))
294       DBUG_RETURN(TRUE);
295   }
296 
297   if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
298     DBUG_RETURN(TRUE);                  /* purecov: inspected */
299 
300   if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL))
301   {
302     // Rename of table
303     TABLE_LIST tmp_table;
304 
305     tmp_table.table_name= lex->name.str;
306     tmp_table.db= select_lex->db;
307     tmp_table.grant.privilege= priv;
308     if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE,
309                     UINT_MAX, FALSE))
310       DBUG_RETURN(TRUE);                  /* purecov: inspected */
311   }
312 
313   /* Don't yet allow changing of symlinks with ALTER TABLE */
314   if (create_info.data_file_name)
315     push_warning_printf(thd, Sql_condition::SL_WARNING,
316                         WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
317                         "DATA DIRECTORY");
318   if (create_info.index_file_name)
319     push_warning_printf(thd, Sql_condition::SL_WARNING,
320                         WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
321                         "INDEX DIRECTORY");
322   create_info.data_file_name= create_info.index_file_name= NULL;
323 
324   thd->enable_slow_log= opt_log_slow_admin_statements;
325 
326   /* Push Strict_error_handler for alter table*/
327   Strict_error_handler strict_handler;
328   if (!thd->lex->is_ignore() && thd->is_strict_mode())
329     thd->push_internal_handler(&strict_handler);
330 
331   Partition_in_shared_ts_error_handler partition_in_shared_ts_handler;
332   thd->push_internal_handler(&partition_in_shared_ts_handler);
333   result= mysql_alter_table(thd, select_lex->db, lex->name.str,
334                             &create_info, first_table, &alter_info);
335   thd->pop_internal_handler();
336 
337   if (!thd->lex->is_ignore() && thd->is_strict_mode())
338     thd->pop_internal_handler();
339   DBUG_RETURN(result);
340 }
341 
342 
execute(THD * thd)343 bool Sql_cmd_discard_import_tablespace::execute(THD *thd)
344 {
345   /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
346   SELECT_LEX *select_lex= thd->lex->select_lex;
347   /* first table of first SELECT_LEX */
348   TABLE_LIST *table_list= select_lex->get_table_list();
349 
350   if (check_access(thd, ALTER_ACL, table_list->db,
351                    &table_list->grant.privilege,
352                    &table_list->grant.m_internal,
353                    0, 0))
354     return true;
355 
356   if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false))
357     return true;
358 
359   thd->enable_slow_log= opt_log_slow_admin_statements;
360 
361   /*
362     Check if we attempt to alter mysql.slow_log or
363     mysql.general_log table and return an error if
364     it is the case.
365     TODO: this design is obsolete and will be removed.
366   */
367   enum_log_table_type table_kind=
368     query_logger.check_if_log_table(table_list, false);
369 
370   if (table_kind != QUERY_LOG_NONE)
371   {
372     /* Disable alter of enabled query log tables */
373     if (query_logger.is_log_table_enabled(table_kind))
374     {
375       my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
376       return true;
377     }
378   }
379 
380   /*
381     Add current database to the list of accessed databases
382     for this statement. Needed for MTS.
383   */
384   thd->add_to_binlog_accessed_dbs(table_list->db);
385 
386   return
387     mysql_discard_or_import_tablespace(thd, table_list,
388                                        m_tablespace_op == DISCARD_TABLESPACE);
389 }
390