From a44a924a407ae1f1135a14ee750468e6fc88d9b0 Mon Sep 17 00:00:00 2001
From: unknown <anozdrin@mysql.com>
Date: Wed, 1 Mar 2006 14:13:07 +0300
Subject: [PATCH] Fix for BUG#16266: Definer is not fully qualified error
 during replication.

The idea of the fix is to extend support of non-SUID triggers for backward
compatibility. Formerly non-SUID triggers were appeared when "new" server
is being started against "old" database. Now, they are also created when
"new" slave receives updates from "old" master.


mysql-test/r/rpl_trigger.result:
  Updated the result file with the results of the test for BUG#16266.
mysql-test/t/rpl_trigger.test:
  Added the test case for BUG#16266.
sql/mysql_priv.h:
  Added an utility operation to be used from sql_yacc.yy.
sql/sql_parse.cc:
  Add a utility operation to be used from sql_yacc.yy.
sql/sql_trigger.cc:
  Extend support of non-SUID triggers.
sql/sql_view.cc:
  Initialize LEX::definer if DEFINER-clause is missing.
sql/sql_yacc.yy:
  Extended support of non-SUID triggers.
mysql-test/std_data/bug16266.000001:
  A new binlog file for testing a patch for BUG#16266.
---
 mysql-test/r/rpl_trigger.result     |  41 +++++++++++
 mysql-test/std_data/bug16266.000001 | Bin 0 -> 532 bytes
 mysql-test/t/rpl_trigger.test       |  74 +++++++++++++++++++
 sql/mysql_priv.h                    |   1 +
 sql/sql_parse.cc                    |  30 +++++++-
 sql/sql_trigger.cc                  | 108 +++++++++++++++++++++-------
 sql/sql_view.cc                     |  20 ++++++
 sql/sql_yacc.yy                     |  42 ++++-------
 8 files changed, 264 insertions(+), 52 deletions(-)
 create mode 100644 mysql-test/std_data/bug16266.000001

diff --git a/mysql-test/r/rpl_trigger.result b/mysql-test/r/rpl_trigger.result
index ccd880c1e0..185862097b 100644
--- a/mysql-test/r/rpl_trigger.result
+++ b/mysql-test/r/rpl_trigger.result
@@ -855,3 +855,44 @@ f3
 drop trigger trg11;
 drop table t21,t31;
 drop table t11;
+STOP SLAVE;
+FLUSH LOGS;
+RESET SLAVE;
+START SLAVE;
+SELECT MASTER_POS_WAIT('master-bin.000001', 513) >= 0;
+MASTER_POS_WAIT('master-bin.000001', 513) >= 0
+1
+SHOW TABLES;
+Tables_in_test
+t1
+t2
+SHOW TRIGGERS;
+Trigger	Event	Table	Statement	Timing	Created	sql_mode	Definer
+trg1	INSERT	t1	INSERT INTO t2 VALUES(CURRENT_USER())	AFTER	NULL		
+SELECT * FROM t1;
+c
+1
+SELECT * FROM t2;
+s
+@
+INSERT INTO t1 VALUES(2);
+SELECT * FROM t1;
+c
+1
+2
+SELECT * FROM t2;
+s
+@
+root@localhost
+DROP TRIGGER trg1;
+Warnings:
+Warning	1454	No definer attribute for trigger 'test'.'trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger.
+DROP TABLE t1;
+DROP TABLE t2;
+STOP SLAVE;
+RESET SLAVE;
+SHOW TABLES;
+Tables_in_test
+SHOW TRIGGERS;
+Trigger	Event	Table	Statement	Timing	Created	sql_mode	Definer
+RESET MASTER;
diff --git a/mysql-test/std_data/bug16266.000001 b/mysql-test/std_data/bug16266.000001
new file mode 100644
index 0000000000000000000000000000000000000000..1b24d231511ff1505b232ef852b6473852afd480
GIT binary patch
literal 532
zcmeyDl$p2U!XIaTMg|6kI3P{}Vg?2l22(u)Jwr3yl+>isblsf%bg(d90%VM^1up{!
zgAfA?5C}0acr&m_Z3PN5flLnt;_YA)Ks*p&VPKE~QVt9ZFcAhe=HikR77m~x91JC?
z#U%{RL9UJ=t_mTJPCl**C59Tw3Z8x;ng?NqMg#Q=fpx=-Al*<Sjba67562)4BLf3X
z%||e!X8?^}MYhpFp6>3hK?)^B>4pl9ZXrOXr(dvZP>6!RAJ7{LZvH_Eu8z(g3PJwi
zFd3l7{1r-!6v7;RLS2J3oI`_xT>V1gLxI{fG&Nts+!YRV7b6ppfQK3}UWVEZvBMB%
UhoPp&#Xru>AYTdtu?W~$0PX--`v3p{

literal 0
HcmV?d00001

diff --git a/mysql-test/t/rpl_trigger.test b/mysql-test/t/rpl_trigger.test
index f3db1cb584..90822e0654 100644
--- a/mysql-test/t/rpl_trigger.test
+++ b/mysql-test/t/rpl_trigger.test
@@ -162,6 +162,7 @@ use test;
 drop table t1,t2;
 drop database other;
 
+
 #
 # Test specific triggers including SELECT into var with replication
 # BUG#13227:
@@ -257,6 +258,79 @@ while ($rnd)
 }
 
 
+#
+# BUG#16266: Definer is not fully qualified error during replication.
+#
+# The idea of this test is to emulate replication of a trigger from the old
+# master (master w/o "DEFINER in triggers" support) to the new slave and check
+# that:
+#   1. the trigger on the slave will be replicated w/o errors;
+#   2. the trigger on the slave will be non-SUID (will have no DEFINER);
+#   3. the trigger can be activated later on the slave w/o errors.
+#
+# In order to emulate this kind of replication, we make the slave playing the binlog,
+# recorded by 5.0.16 master. This binlog contains the following statements:
+#   CREATE TABLE t1(c INT);
+#   CREATE TABLE t2(s CHAR(200));
+#   CREATE TRIGGER trg1 AFTER INSERT ON t1
+#     FOR EACH ROW
+#       INSERT INTO t2 VALUES(CURRENT_USER());
+#   INSERT INTO t1 VALUES(1);
+#
+
+# 1. Check that the trigger's replication is succeeded.
+
+# Stop the slave.
+
+connection slave;
+STOP SLAVE;
+
+# Replace master's binlog.
+
+connection master;
+FLUSH LOGS;
+exec cp $MYSQL_TEST_DIR/std_data/bug16266.000001 $MYSQLTEST_VARDIR/log/master-bin.000001;
+
+# Make the slave to replay the new binlog.
+
+connection slave;
+RESET SLAVE;
+START SLAVE;
+
+SELECT MASTER_POS_WAIT('master-bin.000001', 513) >= 0;
+
+# Check that the replication succeeded.
+
+SHOW TABLES;
+SHOW TRIGGERS;
+SELECT * FROM t1;
+SELECT * FROM t2;
+
+# 2. Check that the trigger is non-SUID on the slave;
+# 3. Check that the trigger can be activated on the slave.
+
+INSERT INTO t1 VALUES(2);
+
+SELECT * FROM t1;
+SELECT * FROM t2;
+
+# That's all, cleanup.
+
+DROP TRIGGER trg1;
+DROP TABLE t1;
+DROP TABLE t2;
+
+STOP SLAVE;
+RESET SLAVE;
+
+# The master should be clean.
+
+connection master;
+SHOW TABLES;
+SHOW TRIGGERS;
+
+RESET MASTER;
+
 
 #
 # End of tests
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 2c817ae54c..9559b0be76 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -531,6 +531,7 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables,
                            TABLE_LIST *create_table);
 
 bool get_default_definer(THD *thd, LEX_USER *definer);
+LEX_USER *create_default_definer(THD *thd);
 LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name);
 
 enum enum_mysql_completiontype {
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index b2066953cf..0494ccf985 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -7206,6 +7206,34 @@ bool get_default_definer(THD *thd, LEX_USER *definer)
 }
 
 
+/*
+  Create default definer for the specified THD. Also check that the current
+  user is conformed to the definers requirements.
+
+  SYNOPSIS
+    create_default_definer()
+    thd         [in] thread handler
+
+  RETURN
+    On success, return a valid pointer to the created and initialized
+    LEX_USER, which contains definer information.
+    On error, return 0.
+*/
+
+LEX_USER *create_default_definer(THD *thd)
+{
+  LEX_USER *definer;
+
+  if (! (definer= (LEX_USER*) thd->alloc(sizeof(LEX_USER))))
+    return 0;
+
+  if (get_default_definer(thd, definer))
+    return 0;
+
+  return definer;
+}
+
+
 /*
   Create definer with the given user and host names. Also check that the user
   and host names satisfy definers requirements.
@@ -7218,7 +7246,7 @@ bool get_default_definer(THD *thd, LEX_USER *definer)
 
   RETURN
     On success, return a valid pointer to the created and initialized
-    LEX_STRING, which contains definer information.
+    LEX_USER, which contains definer information.
     On error, return 0.
 */
 
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index 4c3ae5c032..9ccc70d8cc 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -264,7 +264,17 @@ end:
         log_query.set((char *) 0, 0, system_charset_info); /* reset log_query */
 
         log_query.append(STRING_WITH_LEN("CREATE "));
-        append_definer(thd, &log_query, &definer_user, &definer_host);
+
+        if (definer_user.str && definer_host.str)
+        {
+          /*
+            Append definer-clause if the trigger is SUID (a usual trigger in
+            new MySQL versions).
+          */
+
+          append_definer(thd, &log_query, &definer_user, &definer_host);
+        }
+
         log_query.append(thd->lex->trigger_definition_begin);
       }
 
@@ -289,17 +299,30 @@ end:
                      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
+      definer_user - [out] after a call it points to 0-terminated string or
+                     contains the NULL-string:
+                       - 0-terminated is returned if the trigger is SUID. The
+                         string contains user name part of the actual trigger
+                         definer.
+                       - NULL-string is returned if the trigger is non-SUID.
+                     Anyway, 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
+      definer_host - [out] after a call it points to 0-terminated string or
+                     contains the NULL-string:
+                       - 0-terminated string is returned if the trigger is
+                         SUID. The string contains host name part of the
+                         actual trigger definer.
+                       - NULL-string is returned if the trigger is non-SUID.
+                     Anyway, the caller is responsible to provide memory for
                      storing LEX_STRING object.
 
   NOTE
-    Assumes that trigger name is fully qualified.
+    - Assumes that trigger name is fully qualified.
+    - NULL-string means the following LEX_STRING instance:
+      { str = 0; length = 0 }.
+    - In other words, definer_user and definer_host should contain
+      simultaneously NULL-strings (non-SUID/old trigger) or valid strings
+      (SUID/new trigger).
 
   RETURN VALUE
     False - success
@@ -336,12 +359,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
     return 1;
   }
 
-  /*
-    Definer attribute of the Lex instance is always set in sql_yacc.yy when
-    trigger is created.
-  */
+  if (!lex->definer)
+  {
+    /*
+      DEFINER-clause is missing.
 
-  DBUG_ASSERT(lex->definer);
+      If we are in slave thread, this means that we received CREATE TRIGGER
+      from the master, that does not support definer in triggers. So, we
+      should mark this trigger as non-SUID. Note that this does not happen
+      when we parse triggers' definitions during opening .TRG file.
+      LEX::definer is ignored in that case.
+
+      Otherwise, we should use CURRENT_USER() as definer.
+
+      NOTE: when CREATE TRIGGER statement is allowed to be executed in PS/SP,
+      it will be required to create the definer below in persistent MEM_ROOT
+      of PS/SP.
+    */
+
+    if (!thd->slave_thread)
+    {
+      if (!(lex->definer= create_default_definer(thd)))
+        return 1;
+    }
+  }
 
   /*
     If the specified definer differs from the current user, we should check
@@ -349,10 +390,11 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
     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 (lex->definer &&
+      (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))
     {
@@ -446,8 +488,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
   *trg_sql_mode= thd->variables.sql_mode;
 
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
-  if (!is_acl_user(lex->definer->host.str,
-                   lex->definer->user.str))
+  if (lex->definer && !is_acl_user(lex->definer->host.str,
+                                   lex->definer->user.str))
   {
     push_warning_printf(thd,
                         MYSQL_ERROR::WARN_LEVEL_NOTE,
@@ -458,12 +500,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
   }
 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
 
-  *definer_user= lex->definer->user;
-  *definer_host= lex->definer->host;
+  if (lex->definer)
+  {
+    /* SUID trigger. */
+
+    *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;
+    trg_definer->str= trg_definer_holder;
+    trg_definer->length= strxmov(trg_definer->str, definer_user->str, "@",
+                                 definer_host->str, NullS) - trg_definer->str;
+  }
+  else
+  {
+    /* non-SUID trigger. */
+
+    definer_user->str= 0;
+    definer_user->length= 0;
+
+    definer_host->str= 0;
+    definer_host->length= 0;
+
+    trg_definer->str= (char*) "";
+    trg_definer->length= 0;
+  }
 
   if (!sql_create_definition_file(&dir, &file, &triggers_file_type,
                                   (gptr)this, triggers_file_parameters, 0))
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 4f62a80cfd..2178b5d00a 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -208,6 +208,26 @@ bool mysql_create_view(THD *thd,
   if (mode != VIEW_CREATE_NEW)
     sp_cache_invalidate();
 
+  if (!lex->definer)
+  {
+    /*
+      DEFINER-clause is missing; we have to create default definer in
+      persistent arena to be PS/SP friendly.
+    */
+
+    Query_arena original_arena;
+    Query_arena *ps_arena = thd->activate_stmt_arena_if_needed(&original_arena);
+
+    if (!(lex->definer= create_default_definer(thd)))
+      res= TRUE;
+
+    if (ps_arena)
+      thd->restore_active_arena(ps_arena, &original_arena);
+
+    if (res)
+      goto err;
+  }
+
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
   /*
     check definer of view:
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index d6d2939bed..7e93b9c8e7 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -778,7 +778,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
 
 %type <symbol> FUNC_ARG0 FUNC_ARG1 FUNC_ARG2 FUNC_ARG3 keyword keyword_sp
 
-%type <lex_user> user grant_user get_definer
+%type <lex_user> user grant_user
 
 %type <charset>
 	opt_collate
@@ -8960,41 +8960,29 @@ subselect_end:
 	};
 
 definer:
-	get_definer
+	/* empty */
 	{
-	  THD *thd= YYTHD;
-	  
-	  if (! (thd->lex->definer= create_definer(thd, &$1->user, &$1->host)))
-	    YYABORT;
+          /*
+            We have to distinguish missing DEFINER-clause from case when
+            CURRENT_USER specified as definer explicitly in order to properly
+            handle CREATE TRIGGER statements which come to replication thread
+            from older master servers (i.e. to create non-suid trigger in this
+            case).
+           */
+          YYTHD->lex->definer= 0;
 	}
-	;
-
-get_definer:
-	opt_current_definer
+	| DEFINER_SYM EQ CURRENT_USER optional_braces
 	{
-	  THD *thd= YYTHD;
-          
-	  if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
-	    YYABORT;
-
-	  if (get_default_definer(thd, $$))
-	    YYABORT;
+          if (! (YYTHD->lex->definer= create_default_definer(YYTHD)))
+            YYABORT;
 	}
 	| DEFINER_SYM EQ ident_or_text '@' ident_or_text
 	{
-	  if (!($$=(LEX_USER*) YYTHD->alloc(sizeof(st_lex_user))))
-	    YYABORT;
-
-	  $$->user= $3;
-	  $$->host= $5;
+          if (!(YYTHD->lex->definer= create_definer(YYTHD, &$3, &$5)))
+            YYABORT;
 	}
 	;
 
-opt_current_definer:
-	/* empty */
-	| DEFINER_SYM EQ CURRENT_USER optional_braces
-	;
-
 /**************************************************************************
 
  CREATE VIEW statement options.
-- 
2.30.9