summaryrefslogtreecommitdiff
path: root/sql/sql_trigger.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_trigger.cc')
-rw-r--r--sql/sql_trigger.cc318
1 files changed, 282 insertions, 36 deletions
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index dbad8dcffb5..84cc79ee4dc 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -32,15 +32,36 @@ const char * const triggers_file_ext= ".TRG";
*/
static File_option triggers_file_parameters[]=
{
- {{(char*)"triggers", 8},
+ {
+ { (char *) STRING_WITH_LEN("triggers") },
offsetof(class Table_triggers_list, definitions_list),
- FILE_OPTIONS_STRLIST},
- {{(char*)"sql_modes", 13},
+ FILE_OPTIONS_STRLIST
+ },
+ {
+ /*
+ FIXME: Length specified for "sql_modes" key is erroneous, problem caused
+ by this are reported as BUG#14090 and should be fixed ASAP.
+ */
+ { (char *) "sql_modes", 13 },
offsetof(class Table_triggers_list, definition_modes_list),
- FILE_OPTIONS_ULLLIST},
- {{0, 0}, 0, FILE_OPTIONS_STRING}
+ FILE_OPTIONS_ULLLIST
+ },
+ {
+ { (char *) STRING_WITH_LEN("definers") },
+ offsetof(class Table_triggers_list, definers_list),
+ FILE_OPTIONS_STRLIST
+ },
+ { { 0, 0 }, 0, FILE_OPTIONS_STRING }
};
+/*
+ This must be kept up to date whenever a new option is added to the list
+ above, as it specifies the number of required parameters of the trigger in
+ .trg file.
+*/
+
+static const int TRG_NUM_REQUIRED_PARAMETERS= 4;
+static const int TRG_MAX_VERSIONS= 3;
/*
Structure representing contents of .TRN file which are used to support
@@ -58,9 +79,16 @@ const char * const trigname_file_ext= ".TRN";
static File_option trigname_file_parameters[]=
{
- {{(char*)"trigger_table", 15}, offsetof(struct st_trigname, trigger_table),
- FILE_OPTIONS_ESTRING},
- {{0, 0}, 0, FILE_OPTIONS_STRING}
+ {
+ /*
+ FIXME: Length specified for "trigger_table" key is erroneous, problem
+ caused by this are reported as BUG#14090 and should be fixed ASAP.
+ */
+ { (char *) "trigger_table", 15 },
+ offsetof(struct st_trigname, trigger_table),
+ FILE_OPTIONS_ESTRING
+ },
+ { { 0, 0 }, 0, FILE_OPTIONS_STRING }
};
@@ -104,6 +132,9 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
{
TABLE *table;
bool result= TRUE;
+ LEX_STRING definer_user;
+ LEX_STRING definer_host;
+
DBUG_ENTER("mysql_create_or_drop_trigger");
/*
@@ -131,9 +162,12 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
But a trigger can in theory be used to do nasty things (if it supported
DROP for example) so we do the check for privileges. For now there is
already a stronger test right above; but when this stronger test will
- be removed, the test below will hold.
+ be removed, the test below will hold. Because triggers have the same
+ nature as functions regarding binlogging: their body is implicitely
+ binlogged, so they share the same danger, so trust_function_creators
+ applies to them too.
*/
- if (!trust_routine_creators && mysql_bin_log.is_open() &&
+ if (!trust_function_creators && mysql_bin_log.is_open() &&
!(thd->security_ctx->master_access & SUPER_ACL))
{
my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0));
@@ -184,7 +218,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
}
result= (create ?
- table->triggers->create_trigger(thd, tables):
+ table->triggers->create_trigger(thd, tables, &definer_user, &definer_host):
table->triggers->drop_trigger(thd, tables));
end:
@@ -192,17 +226,30 @@ end:
start_waiting_global_read_lock(thd);
if (!result)
+ {
+ if (mysql_bin_log.is_open())
{
- if (mysql_bin_log.is_open())
+ thd->clear_error();
+
+ String log_query(thd->query, thd->query_length, system_charset_info);
+
+ if (create)
{
- thd->clear_error();
- /* Such a statement can always go directly to binlog, no trans cache */
- Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE);
- mysql_bin_log.write(&qinfo);
+ log_query.set((char *) 0, 0, system_charset_info); /* reset log_query */
+
+ log_query.append("CREATE ");
+ append_definer(thd, &log_query, &definer_user, &definer_host);
+ log_query.append(thd->lex->trigger_definition_begin);
}
- send_ok(thd);
+
+ /* Such a statement can always go directly to binlog, no trans cache. */
+ Query_log_event qinfo(thd, log_query.ptr(), log_query.length(), 0, FALSE);
+ mysql_bin_log.write(&qinfo);
}
+ send_ok(thd);
+ }
+
DBUG_RETURN(result);
}
@@ -212,15 +259,26 @@ end:
SYNOPSIS
create_trigger()
- thd - current thread context (including trigger definition in LEX)
- tables - table list containing one open table for which trigger is
- created.
+ thd - current thread context (including trigger definition in
+ LEX)
+ tables - table list containing one open table for which the
+ trigger is created.
+ definer_user - [out] after a call it points to 0-terminated string,
+ which contains user name part of the actual trigger
+ definer. The caller is responsible to provide memory for
+ storing LEX_STRING object.
+ definer_host - [out] after a call it points to 0-terminated string,
+ which contains host name part of the actual trigger
+ definer. The caller is responsible to provide memory for
+ storing LEX_STRING object.
RETURN VALUE
False - success
True - error
*/
-bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
+bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
+ LEX_STRING *definer_user,
+ LEX_STRING *definer_host)
{
LEX *lex= thd->lex;
TABLE *table= tables->table;
@@ -229,6 +287,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
LEX_STRING dir, file, trigname_file;
LEX_STRING *trg_def, *name;
ulonglong *trg_sql_mode;
+ char trg_definer_holder[HOSTNAME_LENGTH + USERNAME_LENGTH + 2];
+ LEX_STRING *trg_definer;
Item_trigger_field *trg_field;
struct st_trigname trigname;
@@ -250,6 +310,31 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
}
/*
+ Definer attribute of the Lex instance is always set in sql_yacc.yy when
+ trigger is created.
+ */
+
+ DBUG_ASSERT(lex->definer);
+
+ /*
+ If the specified definer differs from the current user, we should check
+ that the current user has SUPER privilege (in order to create trigger
+ under another user one must have SUPER privilege).
+ */
+
+ if (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) ||
+ my_strcasecmp(system_charset_info,
+ lex->definer->host.str,
+ thd->security_ctx->priv_host))
+ {
+ if (check_global_access(thd, SUPER_ACL))
+ {
+ my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER");
+ return TRUE;
+ }
+ }
+
+ /*
Let us check if all references to fields in old/new versions of row in
this trigger are ok.
@@ -318,15 +403,39 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
definitions_list.push_back(trg_def, &table->mem_root) ||
!(trg_sql_mode= (ulonglong*)alloc_root(&table->mem_root,
sizeof(ulonglong))) ||
- definition_modes_list.push_back(trg_sql_mode, &table->mem_root))
+ definition_modes_list.push_back(trg_sql_mode, &table->mem_root) ||
+ !(trg_definer= (LEX_STRING*) alloc_root(&table->mem_root,
+ sizeof(LEX_STRING))) ||
+ definers_list.push_back(trg_definer, &table->mem_root))
goto err_with_cleanup;
trg_def->str= thd->query;
trg_def->length= thd->query_length;
*trg_sql_mode= thd->variables.sql_mode;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (!is_acl_user(lex->definer->host.str,
+ lex->definer->user.str))
+ {
+ push_warning_printf(thd,
+ MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_NO_SUCH_USER,
+ ER(ER_NO_SUCH_USER),
+ lex->definer->user.str,
+ lex->definer->host.str);
+ }
+#endif /* NO_EMBEDDED_ACCESS_CHECKS */
+
+ *definer_user= lex->definer->user;
+ *definer_host= lex->definer->host;
+
+ trg_definer->str= trg_definer_holder;
+ trg_definer->length= strxmov(trg_definer->str, definer_user->str, "@",
+ definer_host->str, NullS) - trg_definer->str;
+
if (!sql_create_definition_file(&dir, &file, &triggers_file_type,
- (gptr)this, triggers_file_parameters, 3))
+ (gptr)this, triggers_file_parameters,
+ TRG_MAX_VERSIONS))
return 0;
err_with_cleanup:
@@ -403,12 +512,14 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
List_iterator_fast<LEX_STRING> it_name(names_list);
List_iterator<LEX_STRING> it_def(definitions_list);
List_iterator<ulonglong> it_mod(definition_modes_list);
+ List_iterator<LEX_STRING> it_definer(definers_list);
char path[FN_REFLEN];
while ((name= it_name++))
{
it_def++;
it_mod++;
+ it_definer++;
if (my_strcasecmp(table_alias_charset, lex->spname->m_name.str,
name->str) == 0)
@@ -419,6 +530,7 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
*/
it_def.remove();
it_mod.remove();
+ it_definer.remove();
if (definitions_list.is_empty())
{
@@ -446,7 +558,7 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
if (sql_create_definition_file(&dir, &file, &triggers_file_type,
(gptr)this, triggers_file_parameters,
- 3))
+ TRG_MAX_VERSIONS))
return 1;
}
@@ -568,7 +680,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
DBUG_RETURN(0);
/*
- File exists so we got to load triggers
+ File exists so we got to load triggers.
FIXME: A lot of things to do here e.g. how about other funcs and being
more paranoical ?
*/
@@ -584,13 +696,16 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
DBUG_RETURN(1);
/*
- We don't have sql_modes in old versions of .TRG file, so we should
- initialize list for safety.
+ We don't have the following attributes in old versions of .TRG file, so
+ we should initialize the list for safety:
+ - sql_modes;
+ - definers;
*/
triggers->definition_modes_list.empty();
+ triggers->definers_list.empty();
if (parser->parse((gptr)triggers, &table->mem_root,
- triggers_file_parameters, 2))
+ triggers_file_parameters, TRG_NUM_REQUIRED_PARAMETERS))
DBUG_RETURN(1);
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
@@ -612,7 +727,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
DBUG_RETURN(1); // EOM
}
*trg_sql_mode= global_system_variables.sql_mode;
- while ((trg_create_str= it++))
+ while (it++)
{
if (triggers->definition_modes_list.push_back(trg_sql_mode,
&table->mem_root))
@@ -623,8 +738,43 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
it.rewind();
}
+ if (triggers->definers_list.is_empty() &&
+ !triggers->definitions_list.is_empty())
+ {
+ /*
+ It is old file format => we should fill list of definers.
+
+ If there is no definer information, we should not switch context to
+ definer when checking privileges. I.e. privileges for such triggers
+ are checked for "invoker" rather than for "definer".
+ */
+
+ LEX_STRING *trg_definer;
+
+ if (! (trg_definer= (LEX_STRING*)alloc_root(&table->mem_root,
+ sizeof(LEX_STRING))))
+ DBUG_RETURN(1); // EOM
+
+ trg_definer->str= "";
+ trg_definer->length= 0;
+
+ while (it++)
+ {
+ if (triggers->definers_list.push_back(trg_definer,
+ &table->mem_root))
+ {
+ DBUG_RETURN(1); // EOM
+ }
+ }
+
+ it.rewind();
+ }
+
DBUG_ASSERT(triggers->definition_modes_list.elements ==
triggers->definitions_list.elements);
+ DBUG_ASSERT(triggers->definers_list.elements ==
+ triggers->definitions_list.elements);
+
table->triggers= triggers;
/*
@@ -647,6 +797,8 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
char *trg_name_buff;
List_iterator_fast<ulonglong> itm(triggers->definition_modes_list);
+ List_iterator_fast<LEX_STRING> it_definer(triggers->
+ definers_list);
LEX *old_lex= thd->lex, lex;
ulong save_sql_mode= thd->variables.sql_mode;
@@ -659,22 +811,55 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
while ((trg_create_str= it++))
{
trg_sql_mode= itm++;
+ LEX_STRING *trg_definer= it_definer++;
thd->variables.sql_mode= (ulong)*trg_sql_mode;
lex_start(thd, (uchar*)trg_create_str->str, trg_create_str->length);
if (yyparse((void *)thd) || thd->is_fatal_error)
{
/*
- Free lex associated resources
+ Free lex associated resources.
QQ: Do we really need all this stuff here ?
*/
delete lex.sphead;
goto err_with_lex_cleanup;
}
- lex.sphead->m_sql_mode= *trg_sql_mode;
+ lex.sphead->set_info(0, 0, &lex.sp_chistics, *trg_sql_mode);
+
triggers->bodies[lex.trg_chistics.event]
[lex.trg_chistics.action_time]= lex.sphead;
+
+ if (!trg_definer->length)
+ {
+ /*
+ This trigger was created/imported from the previous version of
+ MySQL, which does not support triggers definers. We should emit
+ warning here.
+ */
+
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
+ (const char*) db,
+ (const char*) lex.sphead->m_name.str);
+
+ /*
+ Set definer to the '' to correct displaying in the information
+ schema.
+ */
+
+ lex.sphead->set_definer("", 0);
+
+ /*
+ Triggers without definer information are executed under the
+ authorization of the invoker.
+ */
+
+ lex.sphead->m_chistics->suid= SP_IS_NOT_SUID;
+ }
+ else
+ lex.sphead->set_definer(trg_definer->str, trg_definer->length);
+
if (triggers->names_list.push_back(&lex.sphead->m_name,
&table->mem_root))
goto err_with_lex_cleanup;
@@ -701,6 +886,10 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
trg_field= trg_field->next_trg_field)
trg_field->setup_field(thd, table);
+ triggers->m_spec_var_used[lex.trg_chistics.event]
+ [lex.trg_chistics.action_time]=
+ lex.trg_table_fields.first ? TRUE : FALSE;
+
lex_end(&lex);
}
thd->db= save_db.str;
@@ -744,6 +933,9 @@ err_with_lex_cleanup:
name - returns name of trigger
stmt - returns statement of trigger
sql_mode - returns sql_mode of trigger
+ definer_user - returns definer/creator of trigger. The caller is
+ responsible to allocate enough space for storing definer
+ information.
RETURN VALUE
False - success
@@ -754,7 +946,8 @@ bool Table_triggers_list::get_trigger_info(THD *thd, trg_event_type event,
trg_action_time_type time_type,
LEX_STRING *trigger_name,
LEX_STRING *trigger_stmt,
- ulong *sql_mode)
+ ulong *sql_mode,
+ LEX_STRING *definer)
{
sp_head *body;
DBUG_ENTER("get_trigger_info");
@@ -763,6 +956,18 @@ bool Table_triggers_list::get_trigger_info(THD *thd, trg_event_type event,
*trigger_name= body->m_name;
*trigger_stmt= body->m_body;
*sql_mode= body->m_sql_mode;
+
+ if (body->m_chistics->suid == SP_IS_NOT_SUID)
+ {
+ definer->str[0]= 0;
+ definer->length= 0;
+ }
+ else
+ {
+ definer->length= strxmov(definer->str, body->m_definer_user.str, "@",
+ body->m_definer_host.str, NullS) - definer->str;
+ }
+
DBUG_RETURN(0);
}
DBUG_RETURN(1);
@@ -898,8 +1103,9 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
bool old_row_is_record1)
{
int res= 0;
+ sp_head *sp_trigger= bodies[event][time_type];
- if (bodies[event][time_type])
+ if (sp_trigger)
{
Sub_statement_state statement_state;
@@ -914,14 +1120,54 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
old_field= table->field;
}
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context *save_ctx;
+
+ if (sp_change_security_context(thd, sp_trigger, &save_ctx))
+ return TRUE;
+
/*
- FIXME: We should juggle with security context here (because trigger
- should be invoked with creator rights).
+ NOTE: TRIGGER_ACL should be used below.
*/
+ if (check_global_access(thd, SUPER_ACL))
+ {
+ sp_restore_security_context(thd, save_ctx);
+ return TRUE;
+ }
+
+ /*
+ If the trigger uses special variables (NEW/OLD), check that we have
+ SELECT and UPDATE privileges on the subject table.
+ */
+
+ if (is_special_var_used(event, time_type))
+ {
+ TABLE_LIST table_list;
+ bzero((char *) &table_list, sizeof (table_list));
+ table_list.db= (char *) table->s->db;
+ table_list.db_length= strlen(table_list.db);
+ table_list.table_name= (char *) table->s->table_name;
+ table_list.table_name_length= strlen(table_list.table_name);
+ table_list.alias= (char *) table->alias;
+ table_list.table= table;
+
+ if (check_table_access(thd, SELECT_ACL | UPDATE_ACL, &table_list, 0))
+ {
+ sp_restore_security_context(thd, save_ctx);
+ return TRUE;
+ }
+ }
+
+#endif // NO_EMBEDDED_ACCESS_CHECKS
+
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
- res= bodies[event][time_type]->execute_function(thd, 0, 0, 0);
+ res= sp_trigger->execute_function(thd, 0, 0, 0);
thd->restore_sub_statement_state(&statement_state);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ sp_restore_security_context(thd, save_ctx);
+#endif // NO_EMBEDDED_ACCESS_CHECKS
}
return res;