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