1 /* Copyright (c) 2017, 2019, 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 "log.h"
24 #include "my_default.h"                 // my_getopt_use_args_separator
25 #include "migrate_keyring.h"
26 #include "mysqld.h"
27 #include "mysqld_error.h"
28 #include "sql_plugin.h"                 // plugin_early_load_one
29 #include "violite.h"
30 
31 /**
32   Standard constructor
33 */
34 
Migrate_keyring()35 Migrate_keyring::Migrate_keyring()
36 {
37   m_source_plugin_handle= NULL;
38   m_destination_plugin_handle= NULL;
39   mysql= NULL;
40 }
41 
42 /**
43   This function does the following:
44     1. Validate all keyring migration specific options.
45     2. Get a connection handle by connecting to server if connection
46        specific options are set.
47 
48    @param [in] argc               Pointer to argc of original program
49    @param [in] argv               Pointer to argv of original program
50    @param [in] source_plugin      Pointer to source plugin option
51    @param [in] destination_plugin Pointer to destination plugin option
52    @param [in] user               User to login to server
53    @param [in] host               Host on which to connect to server
54    @param [in] password           Password used to connect to server
55    @param [in] socket             The socket file to use for connection
56    @param [in] port               Port number to use for connection
57 
58    @return 0 Success
59    @return 1 Failure
60 
61 */
init(int argc,char ** argv,char * source_plugin,char * destination_plugin,char * user,char * host,char * password,char * socket,ulong port)62 bool Migrate_keyring::init(int  argc,
63                            char **argv,
64                            char *source_plugin,
65                            char *destination_plugin,
66                            char *user, char *host,
67                            char *password, char *socket,
68                            ulong port)
69 {
70   DBUG_ENTER("Migrate_keyring::init");
71 
72   std::size_t found= std::string::npos;
73   string equal("=");
74   string so(".so");
75   string dll(".dll");
76 
77   if (!source_plugin)
78   {
79     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
80              "Invalid --keyring-migration-source option.");
81     DBUG_RETURN(true);
82   }
83   if (!destination_plugin)
84   {
85     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
86              "Invalid --keyring-migration-destination option.");
87     DBUG_RETURN(true);
88   }
89   m_source_plugin_option= source_plugin;
90   m_destination_plugin_option= destination_plugin;
91 
92   /* extract plugin name from the specified source plugin option */
93   if ((found= m_source_plugin_option.find(equal)) != std::string::npos)
94     m_source_plugin_name= m_source_plugin_option.substr(0, found);
95   else if ((found= m_source_plugin_option.find(so)) != std::string::npos)
96     m_source_plugin_name= m_source_plugin_option.substr(0, found);
97   else if ((found= m_source_plugin_option.find(dll)) != std::string::npos)
98     m_source_plugin_name= m_source_plugin_option.substr(0, found);
99   else
100   {
101     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
102              "Invalid source plugin option value.");
103     DBUG_RETURN(true);
104   }
105 
106   /* extract plugin name from the specified destination plugin option */
107   if ((found= m_destination_plugin_option.find(equal)) != std::string::npos)
108     m_destination_plugin_name= m_destination_plugin_option.substr(0, found);
109   else if ((found= m_destination_plugin_option.find(so)) != std::string::npos)
110     m_destination_plugin_name= m_destination_plugin_option.substr(0, found);
111   else if ((found= m_destination_plugin_option.find(dll)) != std::string::npos)
112     m_destination_plugin_name= m_destination_plugin_option.substr(0, found);
113   else
114   {
115     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
116              "Invalid destination plugin option value.");
117     DBUG_RETURN(true);
118   }
119 
120   /* if connect options are provided then initiate connection */
121   if (migrate_connect_options)
122   {
123     ssl_start();
124     /* initiate connection */
125     mysql= mysql_init(NULL);
126 
127     enum mysql_ssl_mode ssl_mode= SSL_MODE_REQUIRED;
128     mysql_options(mysql, MYSQL_OPT_SSL_MODE, &ssl_mode);
129     mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
130     mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name",
131                    "mysqld");
132     mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_role",
133                    "keyring_migration_tool");
134 
135     if (!mysql_real_connect(mysql, host, user, password, "",
136       port, socket, 0))
137     {
138       my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
139                "Connection to server failed. Please check connection specific "
140                "option values.");
141       DBUG_RETURN(true);
142     }
143   }
144 
145   m_argc= argc;
146   m_argv= new char *[m_argc + 2];  // 1st for extra option and 2nd for NULL
147   for (int cnt= 0; cnt < m_argc; ++cnt) {
148     m_argv[cnt]= argv[cnt];
149   }
150   /* add --loose_<plugin_name>_open_mode=1 option */
151   m_internal_option= "--loose_" + m_source_plugin_name + "_open_mode=1";
152   m_argv[m_argc]= const_cast<char *>(m_internal_option.c_str());
153   /* update m_argc, m_argv */
154   m_argv[++m_argc]= NULL;
155   DBUG_RETURN(false);
156 }
157 
158 /**
159   This function does the following in sequence:
160     1. Load source plugin.
161     2. Load destination plugin.
162     3. Disable access to keyring service APIs.
163     4. Fetch all keys from source plugin and upon
164        sucess store in destination plugin.
165     5. Enable access to keyring service APIs.
166     6. Unload source plugin.
167     7. Unload destination plugin.
168 
169   NOTE: In case there is any error while fetching keys from source plugin,
170   this function would remove all keys stored as part of fetch.
171 
172   @return 0 Success
173   @return 1 Failure
174 */
execute()175 bool Migrate_keyring::execute()
176 {
177   DBUG_ENTER("Migrate_keyring::execute");
178 
179   char **tmp_m_argv;
180 
181   /* Disable access to keyring service APIs */
182   if (migrate_connect_options && disable_keyring_operations())
183       goto error;
184 
185   /* Load source plugin. */
186   if (load_plugin(SOURCE_PLUGIN))
187   {
188     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
189              "Failed to initialize source keyring");
190     goto error;
191   }
192 
193   /* Load destination source plugin. */
194   if (load_plugin(DESTINATION_PLUGIN))
195   {
196     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
197              "Failed to initialize destination keyring");
198     goto error;
199   }
200 
201   /* skip program name */
202   m_argc--;
203   /* We use a tmp ptr instead of m_argv since if the latter gets changed, we
204    * lose access to the alloced mem and hence there would be leak */
205   tmp_m_argv= m_argv + 1;
206   /* check for invalid options */
207   if (m_argc > 1)
208   {
209     struct my_option no_opts[]=
210     {
211       {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
212     };
213     my_getopt_skip_unknown= 0;
214     my_getopt_use_args_separator= true;
215     if (handle_options(&m_argc, &tmp_m_argv, no_opts, NULL))
216       unireg_abort(MYSQLD_ABORT_EXIT);
217 
218     if (m_argc > 1)
219     {
220       sql_print_error("Please specify options specific to keyring migration. Any "
221                       "additional options can be ignored. NOTE: Although some options "
222                       "are valid, migration tool can still report error example: plugin "
223                       "variables for which plugin is not loaded yet.");
224       unireg_abort(MYSQLD_ABORT_EXIT);
225     }
226   }
227 
228   /* Fetch all keys from source plugin and store into destination plugin. */
229   if (fetch_and_store_keys())
230     goto error;
231 
232   /* Enable access to keyring service APIs */
233   if (migrate_connect_options)
234     enable_keyring_operations();
235 
236   DBUG_RETURN(false);
237 
238 error:
239   /*
240    Enable keyring_operations in case of error
241   */
242   if (migrate_connect_options)
243     enable_keyring_operations();
244   DBUG_RETURN(true);
245 }
246 
247 /**
248   Load plugin.
249 
250   @param [in] plugin_type        Indicates what plugin to be loaded
251 
252   @return 0 Success
253   @return 1 Failure
254 */
load_plugin(enum_plugin_type plugin_type)255 bool Migrate_keyring::load_plugin(enum_plugin_type plugin_type)
256 {
257   DBUG_ENTER("Migrate_keyring::load_plugin");
258 
259   char* keyring_plugin= NULL;
260   char* plugin_name= NULL;
261   bool is_source_plugin= 0;
262 
263   if (plugin_type == SOURCE_PLUGIN)
264     is_source_plugin= 1;
265 
266   if (is_source_plugin)
267   {
268     keyring_plugin= const_cast<char *>(m_source_plugin_option.c_str());
269     plugin_name= const_cast<char *>(m_source_plugin_name.c_str());
270   }
271   else
272   {
273     keyring_plugin= const_cast<char *>(m_destination_plugin_option.c_str());
274     plugin_name= const_cast<char *>(m_destination_plugin_name.c_str());
275   }
276 
277   if (plugin_early_load_one(&m_argc, m_argv, keyring_plugin))
278     goto error;
279   else
280   {
281     /* set plugin handle */
282     plugin_ref plugin;
283     plugin= my_plugin_lock_by_name(0, to_lex_cstring(plugin_name),
284                                    MYSQL_KEYRING_PLUGIN);
285     if (plugin == NULL)
286       goto error;
287 
288     if (is_source_plugin)
289       m_source_plugin_handle= (st_mysql_keyring*)plugin_decl(plugin)->info;
290     else
291       m_destination_plugin_handle= (st_mysql_keyring*)plugin_decl(plugin)->info;
292 
293     plugin_unlock(0, plugin);
294   }
295   DBUG_RETURN(false);
296 
297 error:
298   if (is_source_plugin)
299     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
300              "Failed to load source keyring plugin.");
301   else
302     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
303              "Failed to load destination keyring plugin.");
304   DBUG_RETURN(true);
305 }
306 
307 /**
308   This function does the following in sequence:
309     1. Initialize key iterator which will make iterator to position itself
310        inorder to fetch a key.
311     2. Using iterator get key ID and user name.
312     3. Fetch the key information using key ID and user name.
313     4. Store the fetched key into destination plugin.
314     5. In case of errors remove keys from destination plugin.
315 
316   @return 0 Success
317   @return 1 Failure
318 */
fetch_and_store_keys()319 bool Migrate_keyring::fetch_and_store_keys()
320 {
321   DBUG_ENTER("Migrate_keyring::fetch_keys");
322 
323   bool error= FALSE;
324   char key_id[MAX_KEY_LEN]= { 0 };
325   char user_id[USERNAME_LENGTH]= { 0 };
326   void *key=NULL;
327   size_t key_len= 0;
328   char *key_type= NULL;
329   void *key_iterator= NULL;
330 
331   m_source_plugin_handle->mysql_key_iterator_init(&key_iterator);
332   if (key_iterator == NULL)
333   {
334     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
335              "Initializing source keyring iterator failed.");
336     DBUG_RETURN(true);
337   }
338   while(!error)
339   {
340     if (m_source_plugin_handle->
341       mysql_key_iterator_get_key(key_iterator, key_id, user_id))
342       break;
343 
344     /* using key_info metadata fetch the actual key */
345     if (m_source_plugin_handle->mysql_key_fetch(key_id,
346                                                 &key_type,
347                                                 user_id,
348                                                 &key,
349                                                 &key_len))
350     {
351       /* fetch failed */
352       string errmsg= "Fetching key (" + string(key_id) +
353         ") from source plugin failed.";
354       my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
355                errmsg.c_str());
356       error= TRUE;
357     }
358     else /* store the fetched key into destination plugin */
359     {
360       if (m_destination_plugin_handle->mysql_key_store(key_id, key_type,
361           user_id, key, key_len))
362       {
363         string errmsg= "Storing key (" + string(key_id) +
364           ") into destination plugin failed.";
365         my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
366                  errmsg.c_str());
367         error= TRUE;
368       }
369       else
370       {
371         /*
372          keep track of keys stored in successfully so that they can be
373          removed in case of error.
374         */
375         Key_info ki(key_id, user_id);
376         m_source_keys.push_back(ki);
377       }
378     }
379     if (key)
380       my_free((char*)key);
381     if (key_type)
382       my_free(key_type);
383   }
384   if (error)
385   {
386     /* something went wrong remove keys from destination plugin. */
387     while (m_source_keys.size())
388     {
389       Key_info ki= m_source_keys.back();
390       if (m_destination_plugin_handle->mysql_key_remove(
391         ki.m_key_id, ki.m_user_id))
392       {
393         string errmsg= "Removing key (" + string(ki.m_key_id) +
394           ") from destination plugin failed.";
395         my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
396                  errmsg.c_str());
397       }
398       m_source_keys.pop_back();
399     }
400   }
401   m_source_plugin_handle->mysql_key_iterator_deinit(key_iterator);
402   DBUG_RETURN(error);
403 }
404 
405 /**
406   Disable variable @@keyring_operations.
407 
408   @return 0 Success
409   @return 1 Failure
410 */
disable_keyring_operations()411 bool Migrate_keyring::disable_keyring_operations()
412 {
413   DBUG_ENTER("Migrate_keyring::disable_keyring_operations");
414   const char query[]= "SET GLOBAL KEYRING_OPERATIONS=0";
415   if (mysql && mysql_real_query(mysql, query, strlen(query)))
416   {
417     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
418              "Failed to disable keyring_operations variable.");
419     DBUG_RETURN(true);
420   }
421   DBUG_RETURN(false);
422 }
423 
424 /**
425   Enable variable @@keyring_operations.
426 
427   @return 0 Success
428   @return 1 Failure
429 */
enable_keyring_operations()430 bool Migrate_keyring::enable_keyring_operations()
431 {
432   DBUG_ENTER("Migrate_keyring::enable_keyring_operations");
433   const char query[]= "SET GLOBAL KEYRING_OPERATIONS=1";
434   if (mysql && mysql_real_query(mysql, query, strlen(query)))
435   {
436     my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
437              "Failed to enable keyring_operations variable.");
438     DBUG_RETURN(true);
439   }
440   DBUG_RETURN(false);
441 }
442 
443 /**
444   Standard destructor to close connection handle.
445 */
~Migrate_keyring()446 Migrate_keyring::~Migrate_keyring()
447 {
448   if (mysql)
449   {
450     delete[] m_argv;
451     m_argv= NULL;
452     mysql_close(mysql);
453     mysql= NULL;
454     if (migrate_connect_options)
455       vio_end();
456   }
457 }
458