1 /*
2 Copyright (c) 2012, 2021, Oracle and/or its affiliates.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is also distributed with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have included with MySQL.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License, version 2.0, for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 // C headers
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <spawn.h>
34 #include <pthread.h>
35 #include <sys/types.h>
36 #include <sys/wait.h>
37
38 // MySQL headers
39 #include "my_global.h"
40 #include "my_default.h"
41 #include "my_getopt.h"
42 #include "welcome_copyright_notice.h"
43 #include "mysql_version.h"
44 #include "auth_utils.h"
45 #include "path.h"
46 #include "logger.h"
47 #include "infix_ostream_it.h"
48 #include "my_dir.h"
49
50 // Additional C++ headers
51 #include <string>
52 #include <algorithm>
53 #include <locale>
54 #include <iostream>
55 #include <fstream>
56 #include <iterator>
57 #include <vector>
58 #include <sstream>
59 #include <map>
60 #include <iomanip>
61
62 using namespace std;
63
64 #include "../scripts/sql_commands_system_tables.h"
65 #include "../scripts/sql_commands_system_data.h"
66 #include "../scripts/sql_commands_help_data.h"
67 #include "../scripts/sql_commands_sys_schema.h"
68
69 #define PROGRAM_NAME "mysql_install_db"
70 #define MYSQLD_EXECUTABLE "mysqld"
71 #define MAX_MYSQLD_ARGUMENTS 10
72 #define MAX_USER_NAME_LEN 32
73
74 char *opt_euid= 0;
75 char *opt_basedir= 0;
76 char *opt_datadir= 0;
77 char *opt_adminlogin= 0;
78 char *opt_loginpath= 0;
79 char default_loginpath[]= "client";
80 char *opt_sqlfile= 0;
81 char default_adminuser[]= "root";
82 char *opt_adminuser= 0;
83 char default_adminhost[]= "localhost";
84 char *opt_adminhost= 0;
85 char default_authplugin[]= "mysql_native_password";
86 char *opt_authplugin= 0;
87 char *opt_mysqldfile= 0;
88 char *opt_randpwdfile= 0;
89 char default_randpwfile[]= ".mysql_secret";
90 char *opt_langpath= 0;
91 char *opt_lang= 0;
92 char default_lang[]= "en_US";
93 char *opt_defaults_file= 0;
94 char *opt_def_extra_file= 0;
95 char *opt_builddir= 0;
96 char *opt_srcdir= 0;
97 my_bool opt_no_defaults= FALSE;
98 my_bool opt_insecure= FALSE;
99 my_bool opt_verbose= FALSE;
100 my_bool opt_ssl= FALSE;
101 my_bool opt_skipsys= FALSE;
102
103 /**
104 Connection options.
105 @note first element must be 'help' and last element must be
106 the end token: {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
107 */
108 static struct my_option my_connection_options[]=
109 {
110 {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG,
111 NO_ARG, 0, 0, 0, 0, 0, 0},
112 {"user", 'u', "The effective user id used when executing the bootstrap "
113 "sequence.", &opt_euid, &opt_euid, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0,
114 0, 0, 0},
115 {"builddir", 0, "For use with --srcdir and out-of-source builds. Set this to "
116 "the location of the directory where the built files reside.",
117 &opt_builddir, 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
118 {"srcdir", 0, "For internal use. This option specifies the directory under"
119 " which mysql_install_db looks for support files such as the error"
120 " message file and the file for populating the help tables.", &opt_srcdir,
121 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
122 {"basedir", 0, "The path to the MySQL installation directory.",
123 &opt_basedir, 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
124 {"datadir", 0, "The path to the MySQL data directory.", &opt_datadir,
125 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
126 {"login-path", 0, "Set the credential category to use with the MySQL password"
127 " store when setting default credentials. This option takes precedence over "
128 "admin-user, admin-host options.",
129 &opt_loginpath, 0, 0, GET_STR_ALLOC, REQUIRED_ARG,
130 (longlong)&default_loginpath, 0, 0, 0, 0, 0},
131 {"login-file", 0, "Use the MySQL password store at the specified location "
132 " to set the default password. This option takes precedence over admin-user, "
133 "admin-host options. Use the login-path option to change the default "
134 "credential category (default is 'client').",
135 &opt_adminlogin, 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
136 {"extra-sql-file", 'f', "Optional SQL file to execute during bootstrap.",
137 &opt_sqlfile, 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
138 {"admin-user", 0, "Username part of the default admin account.",
139 &opt_adminuser, 0, 0, GET_STR_ALLOC, REQUIRED_ARG,
140 (longlong)&default_adminuser, 0, 0, 0, 0, 0},
141 {"admin-host", 0, "Hostname part of the default admin account.",
142 &opt_adminhost, 0, 0, GET_STR_ALLOC,REQUIRED_ARG,
143 (longlong)&default_adminhost, 0, 0, 0, 0, 0},
144 {"admin-auth-plugin", 0, "Plugin to use for the default admin account.",
145 &opt_authplugin, 0, 0, GET_STR_ALLOC, REQUIRED_ARG,
146 (longlong)&default_authplugin, 0, 0, 0, 0, 0},
147 {"admin-require-ssl", 0, "Require SSL/TLS for the default admin account.",
148 &opt_ssl, 0, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
149 {"mysqld-file", 0, "Qualified path to the mysqld binary.", &opt_mysqldfile,
150 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
151 {"random-password-file", 0, "Specifies the qualified path to the "
152 ".mysql_secret temporary password file.", &opt_randpwdfile,
153 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0,
154 0, 0, 0, 0, 0},
155 {"insecure", 0, "Disables random passwords for the default admin account.",
156 &opt_insecure, 0, 0, GET_BOOL, NO_ARG, 0, 0, 0,
157 0, 0, 0},
158 {"verbose", 'v', "Be more verbose when running program.",
159 &opt_verbose, 0, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
160 {"version", 'V', "Print program version and exit.",
161 &opt_verbose, 0, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
162 {"lc-messages-dir", 'l', "Specifies the path to the language files.",
163 &opt_langpath, 0, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
164 {"lc-messages", 0, "Specifies the language to use.", &opt_lang,
165 0, 0, GET_STR_ALLOC, REQUIRED_ARG, (longlong)&default_lang, 0, 0, 0, 0, 0},
166 {"skip-sys-schema", 0, "Skip installation of the sys schema.",
167 &opt_skipsys, 0, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
168 /* End token */
169 {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
170 };
171
172 Log info(cout,"NOTE");
173 Log error(cerr,"ERROR");
174 Log warning(cout, "WARNING");
175
176 /**
177 Escapes quotes and backslash.
178 @param str The string which needs to be quoted
179 @note This is not a replacement for the mysql_real_escape_string() function
180 as it only does what is necessary for this application.
181 @return A quoted copy of the original string.
182 */
escape_string(string str)183 string escape_string(string str)
184 {
185 string esc("'\"\\");
186 for(string::iterator it= esc.begin(); it != esc.end(); ++it)
187 {
188 string::size_type idx= 0;
189 while ((idx= str.find(*it, idx)) != string::npos)
190 {
191 str.insert(idx, 1, '\\');
192 idx +=2;
193 }
194 }
195 return str;
196 }
197
198 /**
199
200 */
201 struct Proxy_user
202 {
Proxy_userProxy_user203 Proxy_user(string opt_host, string opt_user) : host(opt_host), user(opt_user)
204 {}
205
206 string host;
207 string user;
to_strProxy_user208 void to_str(string *sql)
209 {
210 sql->clear();
211 sql->append("INSERT INTO proxies_priv VALUES ('");
212 sql->append(escape_string(host)).
213 append("','");
214 sql->append(escape_string(user)).
215 append("','','',TRUE,'',now());\n");
216 }
217
218 };
219
220 /**
221 A trivial container for attributes associated with the creation of a MySQL
222 user.
223 */
224 struct Sql_user
225 {
Sql_userSql_user226 Sql_user(string opt_host,
227 string opt_user,
228 string opt_password,
229 Access_privilege opt_priv,
230 string opt_ssl_type,
231 string opt_ssl_cipher,
232 string opt_x509_issuer,
233 string opt_x509_subject,
234 int opt_max_questions,
235 int opt_max_updates,
236 int opt_max_connections,
237 int opt_max_user_connections,
238 string opt_plugin,
239 string opt_authentication_string,
240 bool opt_password_expired,
241 int opt_password_lifetime) :
242 host(opt_host),
243 user(opt_user),
244 password(opt_password),
245 priv(opt_priv),
246 ssl_type(opt_ssl_type),
247 ssl_cipher(opt_ssl_cipher),
248 x509_issuer(opt_x509_issuer),
249 x509_subject(opt_x509_subject),
250 max_questions(opt_max_questions),
251 max_updates(opt_max_updates),
252 max_connections(opt_max_connections),
253 max_user_connections(opt_max_user_connections),
254 plugin(opt_plugin),
255 authentication_string(opt_authentication_string),
256 password_expired(opt_password_expired),
257 password_lifetime(opt_password_lifetime) {}
258
259 string host;
260 string user;
261 string password;
262 Access_privilege priv;
263 string ssl_type;
264 string ssl_cipher;
265 string x509_issuer;
266 string x509_subject;
267 int max_questions;
268 int max_updates;
269 int max_connections;
270 int max_user_connections;
271 string plugin;
272 string authentication_string;
273 bool password_expired;
274 int password_lifetime;
275
to_sqlSql_user276 void to_sql(string *cmdstr)
277 {
278 stringstream set_passcmd,ss, flush_priv;
279 ss << "INSERT INTO mysql.user VALUES ("
280 << "'" << escape_string(host) << "','" << escape_string(user) << "',";
281
282 uint64_t acl= priv.to_int();
283 for(int i= 0; i< NUM_ACLS; ++i)
284 {
285 if( (acl & (1L << i)) != 0 )
286 ss << "'Y',";
287 else
288 ss << "'N',";
289 }
290 ss << "'" << escape_string(ssl_type) << "',"
291 << "'" << escape_string(ssl_cipher) << "',"
292 << "'" << escape_string(x509_issuer) << "',"
293 << "'" << escape_string(x509_subject) << "',"
294 << max_questions << ","
295 << max_updates << ","
296 << max_connections << ","
297 << max_user_connections << ","
298 << "'" << plugin << "',";
299 ss << "'',";
300 if (password_expired)
301 ss << "'Y',";
302 else
303 ss << "'N',";
304 ss << "now(), NULL, 'N');\n";
305
306 flush_priv << "FLUSH PRIVILEGES;\n";
307
308 if (password_expired)
309 {
310 set_passcmd << "ALTER USER '" << escape_string(user) << "'@'"
311 << escape_string(host) << "' IDENTIFIED BY '"
312 << escape_string(password) << "' PASSWORD EXPIRE;\n";
313 }
314 cmdstr->append(ss.str()).append(flush_priv.str()).append(set_passcmd.str());
315 }
316
317 };
318
319 static const char *load_default_groups[]= { PROGRAM_NAME, 0 };
320
print_version(const string & p)321 void print_version(const string &p)
322 {
323 cout << p
324 << " Ver " << MYSQL_SERVER_VERSION << ", for "
325 << SYSTEM_TYPE << " on "
326 << MACHINE_TYPE << "\n";
327 }
328
usage(const string & p)329 void usage(const string &p)
330 {
331 print_version(p);
332 cout << ORACLE_WELCOME_COPYRIGHT_NOTICE("2015") << endl
333 << "MySQL Database Deployment Utility." << endl
334 << "Usage: "
335 << p
336 << " [OPTIONS]" << endl;
337 my_print_help(my_connection_options);
338 cout << endl
339 << "The following options may be given as the first argument:"
340 << endl
341 << "--print-defaults Print the program argument list and exit."
342 << endl
343 << "--no-defaults Don't read default options from any option file,"
344 << endl
345 << " except for login file."
346 << endl
347 << "--defaults-file=# Only read default options from the given file #."
348 << endl
349 << "--defaults-extra-file=# Read this file after the global files are read."
350 << endl;
351 my_print_variables(my_connection_options);
352 }
353
354
355 extern "C" my_bool
my_arguments_get_one_option(int optid,const struct my_option * opt MY_ATTRIBUTE ((unused)),char * argument)356 my_arguments_get_one_option(int optid,
357 const struct my_option *opt MY_ATTRIBUTE((unused)),
358 char *argument)
359 {
360 switch(optid)
361 {
362 case '?':
363 usage(PROGRAM_NAME);
364 exit(0);
365 case 'V':
366 print_version(PROGRAM_NAME);
367 exit(0);
368 }
369 return 0;
370 }
371
372
373 /**
374 The string class will break if constructed with a NULL pointer. This wrapper
375 provides a systematic protection when importing char pointers.
376 */
create_string(char * ptr)377 string create_string(char *ptr)
378 {
379 if (ptr)
380 return string(ptr);
381 else
382 return string("");
383 }
384
385 template <class InputIterator, class UnaryPredicate >
my_all_of(InputIterator first,InputIterator last,UnaryPredicate pred)386 bool my_all_of(InputIterator first, InputIterator last, UnaryPredicate pred)
387 {
388 while (first != last)
389 {
390 if (!pred(*first)) return false;
391 ++first;
392 }
393 return true;
394 }
395
my_legal_username_chars(const char & c)396 bool my_legal_username_chars(const char &c)
397 {
398 return isalnum(c) || c == '_';
399 }
400
my_legal_hostname_chars(const char & c)401 bool my_legal_hostname_chars(const char &c)
402 {
403 return isalnum(c) || c == '_' || c == '.';
404 }
405
my_legal_plugin_chars(const char & c)406 bool my_legal_plugin_chars(const char &c)
407 {
408 return isalnum(c) || c == '_';
409 }
410
411 // defined in auth_utils.cc
412 extern const string g_allowed_pwd_chars;
413
my_legal_password(const char & c)414 bool my_legal_password(const char &c)
415 {
416 return (get_allowed_pwd_chars().find(c) != string::npos);
417 }
418
419 /**
420 Verify that the default admin account follows the recommendations and
421 restrictions.
422 */
assert_valid_root_account(const string & username,const string & host,const string & plugin,bool ssl)423 bool assert_valid_root_account(const string &username, const string &host,
424 const string &plugin, bool ssl)
425 {
426 if( username.length() > MAX_USER_NAME_LEN || username.length() < 1)
427 {
428 error << "Username must be between 1 and "
429 << MAX_USER_NAME_LEN
430 << " characters in length."
431 << endl;
432 return false;
433 }
434 if (!my_all_of(username.begin(), username.end(), my_legal_username_chars) ||
435 !my_all_of(host.begin(), host.end(), my_legal_hostname_chars))
436 {
437 error << "Recommended practice is to use only alpha-numericals in "
438 "the user / host name."
439 << endl;
440 return false;
441 }
442 if (!my_all_of(plugin.begin(), plugin.end(), my_legal_plugin_chars))
443 {
444 error << "Only use alpha-numericals in the the plugin name."
445 << endl;
446 return false;
447 }
448 if (plugin != "mysql_native_password" && plugin != "sha256_password")
449 {
450 error << "Unsupported authentication plugin specified."
451 << endl;
452 return false;
453 }
454 return true;
455 }
456
assert_valid_datadir(const string & datadir,Path * target)457 bool assert_valid_datadir(const string &datadir, Path *target)
458 {
459 if (datadir.length() == 0)
460 {
461 error << "The data directory needs to be specified."
462 << endl;
463 return false;
464 }
465
466 target->append(datadir);
467
468 if (target->exists())
469 {
470 if (!target->empty())
471 {
472 error << "The data directory '"
473 << datadir.c_str()
474 << "' already exist and is not empty." << endl;
475 return false;
476 }
477 }
478 return true;
479 }
480
481 class File_exists
482 {
483 public:
File_exists(const string * file,Path * qp)484 File_exists(const string *file, Path *qp) : m_file(file), m_qpath(qp)
485 {}
486
operator ()(const Path & path)487 bool operator()(const Path &path)
488 {
489 Path tmp_path(path);
490 tmp_path.filename(*m_file);
491 if (tmp_path.exists())
492 {
493 m_qpath->path(path);
494 m_qpath->filename(*m_file);
495 return true;
496 }
497 return false;
498 }
499
500 private:
501 const string *m_file;
502 Path *m_qpath;
503 };
504
505
506 /**
507 Given a list of search paths; find the file.
508 If search_paths=0 then the filename is considered to be a qualified path.
509 If filename is empty then the qpath will be the first directory which
510 is found.
511 @param filename The file to look for
512 @search_paths paths to search
513 @qpath[out] The qualified path to the first found file
514
515 @return true if a file is found, false if not.
516 */
517
locate_file(const string & filename,vector<Path> * search_paths,Path * qpath)518 bool locate_file(const string &filename, vector<Path > *search_paths,
519 Path *qpath)
520 {
521 if (search_paths == 0)
522 {
523 MY_STAT s;
524 if (my_stat(filename.c_str(), &s, MYF(0)) == NULL)
525 return false;
526 qpath->qpath(filename);
527 }
528 else
529 {
530 vector<Path>::iterator qpath_it=
531 find_if(search_paths->begin(), search_paths->end(),
532 File_exists(&filename, qpath));
533 if (qpath_it == search_paths->end())
534 return false;
535 }
536 return true;
537 }
538
add_standard_search_paths(vector<Path> * spaths)539 void add_standard_search_paths(vector<Path > *spaths)
540 {
541 Path p;
542 if (!p.path_getcwd())
543 warning << "Can't determine current working directory." << endl;
544
545 spaths->push_back(p);
546 spaths->push_back(Path(p).append("/bin"));
547 spaths->push_back(Path("/usr/bin"));
548 spaths->push_back(Path("/usr/local/bin"));
549 spaths->push_back(Path("/opt/mysql/bin"));
550 #ifdef INSTALL_SBINDIR
551 spaths->push_back(Path(INSTALL_SBINDIR));
552 #endif
553 #ifdef INSTALL_BINDIR
554 spaths->push_back(Path(INSTALL_BINDIR));
555 #endif
556
557 }
558
559 /**
560 Attempts to locate the mysqld file.
561 If opt_mysqldfile is specified then the this assumed to be a correct qualified
562 path to the mysqld executable.
563 If opt_basedir is specified then opt_basedir+"/bin" is assumed to be a
564 candidate path for the mysqld executable.
565 If opt_srcdir is set then opt_srcdir+"/bin" is assumed to be a
566 candidate path for the mysqld executable.
567 If opt_builddir is set then opt_builddir+"/sql" is assumed to be a
568 candidate path for the mysqld executable.
569
570 If the executable isn't found in any of these locations,
571 attempt to search the local directory and "bin" and "sbin" subdirectories.
572 Finally check "/usr/bin","/usr/sbin", "/usr/local/bin","/usr/local/sbin",
573 "/opt/mysql/bin","/opt/mysql/sbin"
574
575 */
assert_mysqld_exists(const string & opt_mysqldfile,const string & opt_basedir,const string & opt_builddir,const string & opt_srcdir,Path * qpath)576 bool assert_mysqld_exists(const string &opt_mysqldfile,
577 const string &opt_basedir,
578 const string &opt_builddir,
579 const string &opt_srcdir,
580 Path *qpath)
581 {
582 vector<Path > spaths;
583 if (opt_mysqldfile.length() > 0)
584 {
585 /* Use explicit option to file mysqld */
586 if (!locate_file(opt_mysqldfile, 0, qpath))
587 {
588 error << "No such file: " << opt_mysqldfile << endl;
589 return false;
590 }
591 }
592 else
593 {
594 if (opt_basedir.length() > 0)
595 {
596 spaths.push_back(Path(opt_basedir).
597 append("bin"));
598 /* cater for RPM installs : mysqld in sbin */
599 spaths.push_back(Path(opt_basedir).
600 append("sbin"));
601 }
602 if (opt_builddir.length() > 0)
603 {
604 spaths.push_back(Path(opt_builddir).
605 append("sql"));
606 }
607
608 add_standard_search_paths(&spaths);
609
610 if (!locate_file(MYSQLD_EXECUTABLE, &spaths, qpath))
611 {
612 error << "Can't locate the server executable (mysqld)." << endl;
613 info << "The following paths were searched: ";
614 copy(spaths.begin(), spaths.end(),
615 infix_ostream_iterator<Path >(info, ", "));
616 info << endl;
617 return false;
618 }
619 }
620 return true;
621 }
622
623
assert_valid_language_directory(const string & opt_langpath,const string & opt_basedir,const string & opt_builddir,const string & opt_srcdir,Path * language_directory)624 bool assert_valid_language_directory(const string &opt_langpath,
625 const string &opt_basedir,
626 const string &opt_builddir,
627 const string &opt_srcdir,
628 Path *language_directory)
629 {
630 vector<Path > search_paths;
631 bool found_subdir= false;
632 if (opt_langpath.length() > 0)
633 {
634 search_paths.push_back(opt_langpath);
635 }
636 else
637 {
638 if(opt_basedir.length() > 0)
639 {
640 Path ld(opt_basedir);
641 ld.append("/share/english");
642 search_paths.push_back(ld);
643
644 /* cater for RPMs */
645 Path ld2(opt_basedir);
646 ld2.append("/share/mysql/english");
647 search_paths.push_back(ld2);
648 }
649 if (opt_builddir.length() > 0)
650 {
651 Path ld(opt_builddir);
652 ld.append("/sql/share/english");
653 search_paths.push_back(ld);
654 }
655 if (opt_srcdir.length() > 0)
656 {
657 Path ld(opt_srcdir);
658 ld.append("/sql/share/english");
659 search_paths.push_back(ld);
660 }
661 search_paths.push_back(Path("/usr/share/mysql/english"));
662 search_paths.push_back(Path("/opt/mysql/share/english"));
663 #ifdef INSTALL_MYSQLSHAREDIR
664 search_paths.push_back(Path(INSTALL_MYSQLSHAREDIR).append("/english"));
665 #endif
666 found_subdir= true;
667 }
668
669 if (!locate_file("", &search_paths, language_directory))
670 {
671 error << "Can't locate the language directory." << endl;
672 info << "Attempted the following paths: ";
673 copy(search_paths.begin(), search_paths.end(),
674 infix_ostream_iterator<Path>(info, ", "));
675 info << endl;
676 return false;
677 }
678 if (found_subdir)
679 language_directory->up();
680 return true;
681 }
682
683 /**
684 Parse the login.cnf file and extract the missing admin credentials.
685 If any of adminuser or adminhost contains information, it won't be overwritten
686 by new data. Password is always updated.
687
688 @return Error
689 @retval ALL_OK Reporting success
690 @retval ERR_FILE File not found
691 @retval ERR_ENCRYPTION Error while decrypting
692 @retval ERR_SYNTAX Error while parsing
693 */
get_admin_credentials(const string & opt_adminlogin,const string & login_path,string * adminuser,string * adminhost,string * password)694 int get_admin_credentials(const string &opt_adminlogin,
695 const string &login_path,
696 string *adminuser,
697 string *adminhost,
698 string *password)
699 {
700 Path path;
701 int ret= ERR_OTHER;
702 if (!path.qpath(opt_adminlogin) || !path.exists())
703 return ERR_FILE;
704
705 ifstream fin(opt_adminlogin.c_str(), ifstream::binary);
706 stringstream sout;
707 if (decrypt_login_cnf_file(fin, sout) != ALL_OK)
708 return ERR_ENCRYPTION;
709
710 map<string, string > options;
711
712 if ((ret= parse_cnf_file(sout, &options, login_path)) != ALL_OK)
713 return ret;
714
715 for( map<string, string >::iterator it= options.begin();
716 it != options.end(); ++it)
717 {
718 if (it->first == "user")
719 *adminuser= it->second;
720 if (it->first == "host")
721 *adminhost= it->second;
722 if (it->first == "password")
723 *password= it->second;
724 }
725 return ALL_OK;
726 }
727
create_ssl_policy(string * ssl_type,string * ssl_cipher,string * x509_issuer,string * x509_subject)728 void create_ssl_policy(string *ssl_type, string *ssl_cipher,
729 string *x509_issuer, string *x509_subject)
730 {
731 /* TODO set up a specific SSL restriction on the default account */
732 *ssl_type= "ANY";
733 *ssl_cipher= "";
734 *x509_issuer= "";
735 *x509_subject= "";
736 }
737
738 #define READ_BUFFER_SIZE 2048
739 #define TIMEOUT_IN_SEC 30
740
741 class Process_reader
742 {
743 public:
Process_reader(string * buffer)744 Process_reader(string *buffer) : m_buffer(buffer)
745 {}
operator ()(int fh)746 bool operator()(int fh)
747 {
748 errno= 0;
749 char ch[READ_BUFFER_SIZE];
750 ssize_t n= 1;
751 int select_ret= 0;
752 fd_set rd, ex;
753 struct timeval tm;
754 tm.tv_sec = TIMEOUT_IN_SEC;
755 tm.tv_usec = 0;
756
757 int flags = fcntl(fh, F_GETFL, 0);
758 fcntl(fh, F_SETFL, flags | O_NONBLOCK);
759 FD_ZERO(&rd);
760 FD_ZERO(&ex);
761 FD_SET(fh, &rd);
762 FD_SET(fh, &ex);
763 errno= 0;
764 /* Wait for something to read */
765 if ((select_ret= select(fh + 1, &rd, NULL, &ex, &tm)) == 0)
766 {
767 /* if 30 s passed we attempt to read anyway */
768 warning << "select() timed out." << endl;
769 }
770 /* Read any error reports from the child process */
771 while((n= read(fh, ch, READ_BUFFER_SIZE)) > 0 && ch[0] != 0 && errno == 0)
772 {
773 m_buffer->append(ch, n);
774 }
775
776 return true;
777 }
778
779 private:
780 string *m_buffer;
781
782 };
783
784 struct Delimiter_parser
785 {
Delimiter_parserDelimiter_parser786 Delimiter_parser() : m_delimiter(";") {}
~Delimiter_parserDelimiter_parser787 ~Delimiter_parser() {}
788
789 bool operator()(std::string &line);
790 private:
791 string m_agg;
792 string m_delimiter;
793 };
794
operator ()(std::string & line)795 bool Delimiter_parser::operator()(std::string &line)
796 {
797 if (line.empty() || line.size() == m_delimiter.size())
798 return false;
799 const string delimiter_tok("delimiter ");
800
801 string::size_type pos= line.find(delimiter_tok);
802 string::size_type curr_del_pos= line.find(m_delimiter);
803
804 if (pos != string::npos && curr_del_pos != string::npos)
805 {
806 /* replace old delimiter with new */
807 m_delimiter= line.substr(pos+delimiter_tok.size(), curr_del_pos - (pos+delimiter_tok.size()));
808 line.clear();
809 return false;
810 }
811
812 if (curr_del_pos != string::npos)
813 {
814 line.erase(curr_del_pos, m_delimiter.length());
815 line.append(";\n");
816 line= m_agg.append(line);
817 m_agg.clear();
818 }
819 else
820 {
821 m_agg.append(line.append(" "));
822 line.clear();
823 return false;
824 }
825 return true; /* line has delimiter */
826 }
827
828 struct Comment_extractor
829 {
Comment_extractorComment_extractor830 Comment_extractor() : m_in_comment(false) {}
~Comment_extractorComment_extractor831 ~Comment_extractor() {}
832
833 bool operator()(string &line);
834 private:
835 bool m_in_comment;
836 };
837
operator ()(string & line)838 bool Comment_extractor::operator ()(string& line)
839 {
840
841 /* Are we in a multi comment? */
842 if (m_in_comment)
843 {
844 string::size_type i= line.find("*/");
845 if (i != string::npos)
846 {
847 m_in_comment= false;
848 string::iterator b= line.begin();
849 advance(b, i+2);
850 line.erase(line.begin(), b);
851 if (line.empty())
852 return true;
853 }
854 else
855 {
856 /* We're still in a multi comment clear the line */
857 line.clear();
858 }
859 }
860 else
861 {
862 string::size_type i= line.find("--");
863 if (i != string::npos)
864 {
865 string::iterator it= line.begin();
866 advance(it, i);
867 line.erase(it,line.end());
868 return true;
869 }
870 i= line.find("/*");
871 if (i != string::npos)
872 {
873 m_in_comment= true;
874 string::iterator a= line.begin();
875 advance(a, i);
876 string::iterator b;
877 string::size_type j= line.find("*/");
878 if (j != string::npos)
879 {
880 b= line.begin();
881 advance(b, j+2);
882 m_in_comment= false;
883 }
884 else
885 b= line.end();
886 line.erase(a, b);
887 if (line.empty())
888 return true;
889 }
890 }
891
892 return m_in_comment;
893 }
894
895 class Process_writer
896 {
897 public:
Process_writer(Sql_user * user,const string & opt_sqlfile)898 Process_writer(Sql_user *user, const string &opt_sqlfile) : m_user(user),
899 m_opt_sqlfile(opt_sqlfile) {}
operator ()(int fh)900 bool operator()(int fh)
901 {
902 errno= 0;
903 info << "Creating system tables...";
904
905 string create_db("CREATE DATABASE mysql;\n");
906 string use_db("USE mysql;\n");
907 // ssize_t write() may be declared with attribute warn_unused_result
908 size_t w1= write(fh, create_db.c_str(), create_db.length());
909 size_t w2= write(fh, use_db.c_str(), use_db.length());
910 if (w1 != create_db.length() || w2 != use_db.length())
911 {
912 info << "failed." << endl;
913 return false;
914 }
915
916 unsigned s= 0;
917 s= sizeof(mysql_system_tables)/sizeof(*mysql_system_tables);
918 for(unsigned i=0, n= 1; i< s && errno != EPIPE && n != 0 &&
919 mysql_system_tables[i] != NULL; ++i)
920 {
921 n= write(fh, mysql_system_tables[i],
922 strlen(mysql_system_tables[i]));
923 }
924 if (errno != 0)
925 {
926 info << "failed." << endl;
927 return false;
928 }
929 else
930 info << "done." << endl;
931
932 info << "Filling system tables with data...";
933 s= sizeof(mysql_system_data)/sizeof(*mysql_system_data);
934 for(unsigned i=0, n= 1; i< s && errno != EPIPE && n != 0 &&
935 mysql_system_data[i] != NULL; ++i)
936 {
937 n= write(fh, mysql_system_data[i],
938 strlen(mysql_system_data[i]));
939 }
940 if (errno != 0)
941 {
942 info << "failed." << endl;
943 return false;
944 }
945 else
946 info << "done." << endl;
947
948 info << "Filling help table with data...";
949 s= sizeof(fill_help_tables)/sizeof(*fill_help_tables);
950 for(unsigned i=0, n= 1; i< s && errno != EPIPE && n != 0 &&
951 fill_help_tables[i] != NULL; ++i)
952 {
953 n= write(fh, fill_help_tables[i],
954 strlen(fill_help_tables[i]));
955 }
956 if (errno != 0)
957 {
958 info << "failed." << endl;
959 return false;
960 }
961 else
962 info << "done." << endl;
963
964 info << "Creating user for internal session service...";
965
966 string create_session_serv_user(
967 "INSERT IGNORE INTO mysql.user VALUES ('localhost','mysql.session',"
968 "'N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','Y','N',"
969 "'N','N','N','N','N','N','N','N','N','N','N','N','','','','',0,0,0,0,"
970 "'mysql_native_password','*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE',"
971 "'N',CURRENT_TIMESTAMP,NULL,'Y');\n");
972 string select_table_priv(
973 "INSERT IGNORE INTO mysql.tables_priv VALUES ('localhost', 'mysql',"
974 " 'mysql.session', 'user', 'root@localhost', CURRENT_TIMESTAMP,"
975 " 'Select', '');\n"
976 );
977 string select_db_priv(
978 "INSERT IGNORE INTO mysql.db VALUES ('localhost', 'performance_schema',"
979 " 'mysql.session','Y','N','N','N','N','N','N','N','N','N','N','N',"
980 "'N','N','N','N','N','N','N');\n"
981 );
982
983 w1= write(fh, create_session_serv_user.c_str(),
984 create_session_serv_user.length());
985 w2= write(fh, select_table_priv.c_str(), select_table_priv.length());
986 size_t w3= write(fh, select_db_priv.c_str(), select_db_priv.length());
987
988 if (w1 != create_session_serv_user.length() ||
989 w2 != select_table_priv.length() ||
990 w3 != select_db_priv.length())
991 {
992 info << "failed." << endl;
993 return false;
994 }
995 else
996 info << "done." << endl;
997
998 info << "Creating default user " << m_user->user << "@"
999 << m_user->host
1000 << endl;
1001 string create_user_cmd;
1002 m_user->to_sql(&create_user_cmd);
1003 w1= write(fh, create_user_cmd.c_str(), create_user_cmd.length());
1004 if (w1 !=create_user_cmd.length() || errno != 0)
1005 return false;
1006 info << "Creating default proxy " << m_user->user << "@"
1007 << m_user->host
1008 << endl;
1009 Proxy_user proxy_user(m_user->host, m_user->user);
1010 string create_proxy_cmd;
1011 proxy_user.to_str(&create_proxy_cmd);
1012 w1= write(fh, create_proxy_cmd.c_str(), create_proxy_cmd.length());
1013 if (w1 != create_proxy_cmd.length() || errno != 0)
1014 return false;
1015
1016 if (!opt_skipsys)
1017 {
1018 info << "Creating sys schema" << endl;
1019 s= sizeof(mysql_sys_schema)/sizeof(*mysql_sys_schema);
1020 for(unsigned i=0, n= 1; i< s && errno != EPIPE && n != 0 &&
1021 mysql_sys_schema[i] != NULL; ++i)
1022 {
1023 n= write(fh, mysql_sys_schema[i],
1024 strlen(mysql_sys_schema[i]));
1025 }
1026 if (errno != 0)
1027 {
1028 info << "failed." << endl;
1029 return false;
1030 }
1031 else
1032 info << "done." << endl;
1033 }
1034
1035 /* Execute optional SQL from a file */
1036 if (m_opt_sqlfile.length() > 0)
1037 {
1038 Path extra_sql;
1039 extra_sql.qpath(m_opt_sqlfile);
1040 if (!extra_sql.exists())
1041 {
1042 warning << "No such file '" << extra_sql.to_str() << "' "
1043 << "(skipping)"
1044 << endl;
1045 } else
1046 {
1047 info << "Executing extra SQL commands from " << extra_sql.to_str()
1048 << endl;
1049 ifstream fin(extra_sql.to_str().c_str());
1050 string sql_command;
1051 string default_se_command("SET default_storage_engine=INNODB;\n");
1052 int n= write(fh, default_se_command.c_str(), default_se_command.length());
1053 Comment_extractor strip_comments;
1054 Delimiter_parser check_delimiters;
1055 while (!getline(fin, sql_command).eof() && errno != EPIPE &&
1056 n != 0)
1057 {
1058 bool is_comment= strip_comments(sql_command);
1059 if (!is_comment)
1060 {
1061 bool has_delimiter= check_delimiters(sql_command);
1062 if (!has_delimiter)
1063 continue;
1064 n= write(fh, sql_command.c_str(), sql_command.length());
1065 }
1066 }
1067 fin.close();
1068 }
1069 }
1070 return true;
1071 }
1072 private:
1073 Sql_user *m_user;
1074 string m_opt_sqlfile;
1075 };
1076
1077 struct Reader_thd_command_st
1078 {
1079 Process_reader *reader_functor;
1080 int read_hndl;
1081 };
1082
reader_func_adaptor(void * f)1083 static void *reader_func_adaptor(void *f)
1084 {
1085 Reader_thd_command_st *cmd= static_cast<Reader_thd_command_st*>(f);
1086 (*cmd->reader_functor)(cmd->read_hndl);
1087 return NULL;
1088 }
1089
1090
1091 template <typename Reader_func_t, typename Writer_func_t,
1092 typename Fwd_iterator >
process_execute(const string & exec,Fwd_iterator begin,Fwd_iterator end,Reader_func_t reader,Writer_func_t writer)1093 bool process_execute(const string &exec, Fwd_iterator begin,
1094 Fwd_iterator end, Reader_func_t reader,
1095 Writer_func_t writer)
1096 {
1097 pid_t child;
1098 bool retval= true;
1099 int read_pipe[2];
1100 int write_pipe[2];
1101 posix_spawn_file_actions_t spawn_action;
1102 char *execve_args[MAX_MYSQLD_ARGUMENTS];
1103
1104 /*
1105 Disable any signal handler for broken pipes and check for EPIPE during
1106 IO instead.
1107 */
1108 signal(SIGPIPE, SIG_IGN);
1109 if (pipe(read_pipe) < 0)
1110 {
1111 return false;
1112 }
1113 if (pipe(write_pipe) < 0)
1114 {
1115 ::close(read_pipe[0]);
1116 ::close(read_pipe[1]);
1117 return false;
1118 }
1119
1120 posix_spawn_file_actions_init(&spawn_action);
1121 /* target process std input (0) reads from our write_pipe */
1122 posix_spawn_file_actions_adddup2(&spawn_action, write_pipe[0], 0);
1123 /* target process shouldn't attempt to write to this pipe */
1124 posix_spawn_file_actions_addclose(&spawn_action, write_pipe[1]);
1125
1126 /* target process output (1) is mapped to our read_pipe */
1127 posix_spawn_file_actions_adddup2(&spawn_action, read_pipe[1], 2);
1128 /* target process shouldn't attempt to read from this pipe */
1129 posix_spawn_file_actions_addclose(&spawn_action, read_pipe[0]);
1130
1131 /*
1132 We need to copy the strings or spawn will fail
1133 */
1134 char *local_filename= strdup(exec.c_str());
1135 execve_args[0]= local_filename;
1136 int i= 1;
1137 for(Fwd_iterator it= begin;
1138 it!= end && i < MAX_MYSQLD_ARGUMENTS-1;)
1139 {
1140 execve_args[i]= strdup(const_cast<char *>((*it).c_str()));
1141 ++it;
1142 ++i;
1143 }
1144 execve_args[i]= 0;
1145
1146 int ret= posix_spawnp(&child, (const char *)execve_args[0], &spawn_action,
1147 NULL, execve_args, NULL);
1148
1149 /* This end is for the target process to read from */
1150 ::close(write_pipe[0]);
1151 /* This end is for the target process to write to */
1152 ::close(read_pipe[1]);
1153
1154 if (ret != 0)
1155 {
1156 /* always failure if we get here! */
1157 error << "Child process: " << exec <<
1158 " exited with return value " << ret << endl;
1159 exit(1);
1160 }
1161 else
1162 {
1163 pthread_t thd_id;
1164 Reader_thd_command_st cmd;
1165 cmd.read_hndl= read_pipe[0];
1166 cmd.reader_functor= &reader;
1167 sigset_t set;
1168 sigemptyset(&set);
1169 sigaddset(&set, SIGPIPE);
1170 pthread_sigmask(SIG_BLOCK, &set, NULL);
1171 pthread_create(&thd_id, NULL, reader_func_adaptor, &cmd);
1172 if (!writer(write_pipe[1]) || errno != 0)
1173 {
1174 error << "Child process: " << exec <<
1175 "terminated prematurely with errno= "
1176 << errno
1177 << endl;
1178 retval= false;
1179 }
1180 // join with read thread
1181 void *ret= NULL;
1182 pthread_cancel(thd_id); // break select()
1183 pthread_join(thd_id, &ret);
1184 }
1185
1186 while(i > 0)
1187 {
1188 free(execve_args[i]);
1189 --i;
1190 }
1191 ::close(write_pipe[1]);
1192 ::close(read_pipe[0]);
1193
1194 /* Wait for the child to die */
1195 int signal= 0;
1196 waitpid(child, &signal, 0);
1197
1198 posix_spawn_file_actions_destroy(&spawn_action);
1199 free(local_filename);
1200 return retval;
1201 }
1202
generate_password_file(Path & pwdfile,const string & adminuser,const string & adminhost,const string & password)1203 int generate_password_file(Path &pwdfile, const string &adminuser,
1204 const string &adminhost,
1205 const string &password)
1206 {
1207
1208 /*
1209 The format of the password file is
1210 ['#'][bytes]['\n']['password bytes']['\n']|[EOF])
1211 */
1212 ofstream fout;
1213 mode_t old_mask= umask(~(S_IRWXU));
1214 fout.open(pwdfile.to_str().c_str());
1215 if (!fout.is_open())
1216 {
1217 umask(old_mask);
1218 return ERR_FILE;
1219 }
1220
1221 fout << "# Password set for user '"
1222 << adminuser << "@" << adminhost << "' at "
1223 << Datetime() << "\n"
1224 << password << "\n";
1225 fout.close();
1226 info << "done." << endl;
1227 umask(old_mask);
1228 return ALL_OK;
1229 }
1230
connection_options_sorter(const void * a,const void * b)1231 int connection_options_sorter(const void *a, const void *b)
1232 {
1233 return strcmp(static_cast<const my_option*>(a)->name,
1234 static_cast<const my_option *>(b)->name);
1235 }
1236
is_prefix(const char * s,const char * t)1237 static int is_prefix(const char *s, const char *t)
1238 {
1239 while (*t)
1240 if (*s++ != *t++) return 0;
1241 return 1;
1242 }
1243
real_get_defaults_options(int argc,char ** argv,my_bool * no_defaults,char ** defaults,char ** extra_defaults,char ** group_suffix,char ** login_path)1244 static int real_get_defaults_options(int argc, char **argv,
1245 my_bool *no_defaults,
1246 char **defaults,
1247 char **extra_defaults,
1248 char **group_suffix,
1249 char **login_path)
1250 {
1251 char **argv_it= argv;
1252 int org_argc= argc, prev_argc= 0, default_option_count= 0;
1253
1254 while (argc >= 2 && argc != prev_argc)
1255 {
1256 /* Skip program name or previously handled argument */
1257 argv_it++;
1258 prev_argc= argc; /* To check if we found */
1259 /* --no-defaults is always the first option. */
1260 if (is_prefix(*argv_it,"--no-defaults") && ! default_option_count)
1261 {
1262 argc--;
1263 default_option_count ++;
1264 *no_defaults= TRUE;
1265 continue;
1266 }
1267 if (!*defaults && is_prefix(*argv_it, "--defaults-file="))
1268 {
1269 *defaults= *argv_it + sizeof("--defaults-file=")-1;
1270 argc--;
1271 default_option_count ++;
1272 continue;
1273 }
1274 if (!*extra_defaults && is_prefix(*argv_it, "--defaults-extra-file="))
1275 {
1276 *extra_defaults= *argv_it + sizeof("--defaults-extra-file=")-1;
1277 argc--;
1278 default_option_count ++;
1279 continue;
1280 }
1281 if (!*group_suffix && is_prefix(*argv_it, "--defaults-group-suffix="))
1282 {
1283 *group_suffix= *argv_it + sizeof("--defaults-group-suffix=")-1;
1284 argc--;
1285 default_option_count ++;
1286 continue;
1287 }
1288 if (!*login_path && is_prefix(*argv_it, "--login-path="))
1289 {
1290 *login_path= *argv_it + sizeof("--login-path=")-1;
1291 argc--;
1292 default_option_count ++;
1293 continue;
1294 }
1295 }
1296 return org_argc - argc;
1297 }
1298
1299
1300 class Resource_releaser
1301 {
1302 char **m_argv;
1303
1304 public:
Resource_releaser(char ** argv)1305 explicit Resource_releaser(char **argv)
1306 : m_argv(argv) { }
1307
~Resource_releaser()1308 ~Resource_releaser()
1309 {
1310 free_defaults(m_argv);
1311 my_cleanup_options(my_connection_options);
1312 }
1313 };
1314
1315
main(int argc,char * argv[])1316 int main(int argc,char *argv[])
1317 {
1318 /*
1319 In order to use the mysys library and the program option library
1320 we need to call the MY_INIT() macro.
1321 */
1322 MY_INIT(argv[0]);
1323
1324 char *dummy= 0; // ignore group suffix when transferring to mysqld
1325 /* Remember the defaults argument so we later can pass these to mysqld */
1326 int default_opt_used= real_get_defaults_options(argc, argv,
1327 &opt_no_defaults,
1328 &opt_defaults_file,
1329 &opt_def_extra_file,
1330 &dummy,
1331 &opt_loginpath);
1332 #ifdef __WIN__
1333 /* Convert command line parameters from UTF16LE to UTF8MB4. */
1334 my_win_translate_command_line_args(&my_charset_utf8mb4_bin, &argc, &argv);
1335 #endif
1336
1337 my_getopt_use_args_separator= TRUE;
1338 if (load_defaults("my", load_default_groups, &argc, &argv))
1339 return 1;
1340
1341 // Remember to call free_defaults() and my_cleanup_options()
1342 Resource_releaser resource_releaser(argv);
1343
1344 // Assert that the help messages are in sorted order
1345 // except that --help must be the first element and 0 must indicate the end.
1346 my_qsort(my_connection_options + 1,
1347 sizeof(my_connection_options)/sizeof(my_option) - 2,
1348 sizeof(my_option),
1349 connection_options_sorter);
1350
1351 int rc= 0;
1352 if ((rc= handle_options(&argc, &argv, my_connection_options,
1353 my_arguments_get_one_option)))
1354 {
1355 error << "Unrecognized options" << endl;
1356 return 1;
1357 }
1358
1359 warning << "mysql_install_db is deprecated. ";
1360 warning << "Please consider switching to mysqld --initialize" << endl;
1361
1362 bool expire_password= !opt_insecure;
1363 string adminuser(create_string(opt_adminuser));
1364 string adminhost(create_string(opt_adminhost));
1365 string authplugin(create_string(opt_authplugin));
1366 string password;
1367 string basedir(create_string(opt_basedir));
1368 string srcdir(create_string(opt_srcdir));
1369 string builddir(create_string(opt_builddir));
1370
1371 if (opt_verbose != 1)
1372 {
1373 info.enabled(false);
1374 }
1375
1376 if (default_opt_used > 0)
1377 {
1378 if (opt_defaults_file != 0)
1379 {
1380 info << "Using default values from " << opt_defaults_file << endl;
1381 }
1382 else
1383 if (!opt_no_defaults)
1384 {
1385 info << "Using default values from my.cnf" << endl;
1386 }
1387 if (opt_def_extra_file != 0)
1388 {
1389 info << "Using additional default values from " << endl;
1390 }
1391 }
1392
1393 /*
1394 1. Verify all option parameters
1395 2. Create missing directories
1396 3. Compose mysqld start string
1397 4. Execute mysqld
1398 5. Exit
1399 */
1400
1401 if (opt_adminlogin)
1402 {
1403 info << "Reading the login config file "
1404 << opt_adminlogin
1405 << " for default account credentials using login-path = "
1406 << opt_loginpath
1407 << endl;
1408 int ret= get_admin_credentials(create_string(opt_adminlogin),
1409 create_string(opt_loginpath),
1410 &adminuser,
1411 &adminhost,
1412 &password);
1413 switch(ret)
1414 {
1415 case ALL_OK: expire_password= false;
1416 if (password.length() == 0 && !opt_insecure)
1417 {
1418 error << "Password is specified as empty! You need to use the "
1419 "--insecure option" << endl;
1420 return 1;
1421 }
1422 break;
1423 case ERR_FILE:
1424 error << "Can't read the login config file: "
1425 << opt_adminlogin << endl;
1426 return 1;
1427 case ERR_ENCRYPTION:
1428 error << "Failed to decrypt the login config file: "
1429 << opt_adminlogin << endl;
1430 return 1;
1431 case ERR_NO_SUCH_CATEGORY:
1432 error << "Failed to locate login-path '"
1433 << opt_loginpath << "' "
1434 << "in the login config file '"
1435 << opt_adminlogin << "' " << endl;
1436 return 1;
1437 case ERR_SYNTAX:
1438 default:
1439 error << "Failed to parse the login config file: "
1440 << opt_adminlogin << endl;
1441 return 1;
1442
1443 }
1444 }
1445
1446 if (!assert_valid_root_account(adminuser,
1447 adminhost,
1448 create_string(opt_authplugin),
1449 opt_ssl))
1450 {
1451 /* Subroutine reported error */
1452 return 1;
1453 }
1454
1455
1456 Path data_directory;
1457 if (!assert_valid_datadir(create_string(opt_datadir), &data_directory))
1458 {
1459 /* Subroutine reported error */
1460 return 1;
1461 }
1462
1463 Path language_directory;
1464 if (!assert_valid_language_directory(create_string(opt_langpath),
1465 basedir,
1466 builddir,
1467 srcdir,
1468 &language_directory))
1469 {
1470 /* Subroutine reported error */
1471 return 1;
1472 }
1473
1474 Path mysqld_exec;
1475 if( !assert_mysqld_exists(create_string(opt_mysqldfile),
1476 basedir,
1477 builddir,
1478 srcdir,
1479 &mysqld_exec))
1480 {
1481 /* Subroutine reported error */
1482 return 1;
1483 }
1484
1485 if (opt_def_extra_file)
1486 {
1487 Path def_extra_file;
1488 def_extra_file.qpath(opt_def_extra_file);
1489 if (!def_extra_file.exists())
1490 {
1491 warning << "Can't open extra defaults file '"
1492 << opt_def_extra_file
1493 << "' (skipping)" << endl;
1494 opt_def_extra_file= NULL;
1495 }
1496 }
1497
1498 if (opt_defaults_file)
1499 {
1500 opt_no_defaults= FALSE;
1501 Path defaults_file;
1502 defaults_file.qpath(opt_defaults_file);
1503 if (!defaults_file.exists())
1504 {
1505 error << "Can't open defaults file '"
1506 << defaults_file
1507 << "'" << endl;
1508 opt_defaults_file= NULL;
1509 return 1;
1510 }
1511 }
1512
1513 if (data_directory.exists())
1514 {
1515 info << "Using existing directory "
1516 << data_directory
1517 << endl;
1518 }
1519 else
1520 {
1521 info << "Creating data directory "
1522 << data_directory << endl;
1523 mode_t old_mask= umask(0);
1524 if (my_mkdir(data_directory.to_str().c_str(),
1525 S_IRWXU | S_IRGRP | S_IXGRP, MYF(MY_WME)))
1526 {
1527 error << "Failed to create the data directory '"
1528 << data_directory << "'" << endl;
1529 umask(old_mask);
1530 return 1;
1531 }
1532 umask(old_mask);
1533 }
1534
1535 /* Generate a random password is no password was found previously */
1536 if (password.length() == 0 && !opt_insecure)
1537 {
1538 Path randpwdfile;
1539 if (opt_randpwdfile != 0)
1540 {
1541 randpwdfile.qpath(opt_randpwdfile);
1542 }
1543 else
1544 {
1545 randpwdfile.get_homedir();
1546 randpwdfile.filename(default_randpwfile);
1547 }
1548 info << "Generating random password to "
1549 << randpwdfile << "...";
1550 generate_password(&password,12);
1551 if (generate_password_file(randpwdfile, adminuser, adminhost,
1552 password) != ALL_OK)
1553 {
1554 error << "Can't create password file "
1555 << randpwdfile
1556 << endl;
1557 return 1;
1558 }
1559 }
1560
1561 if (opt_euid && geteuid() == 0)
1562 {
1563 struct passwd *pwd;
1564 info << "Setting file ownership to " << opt_euid
1565 << endl;
1566 pwd= getpwnam(opt_euid); /* Try getting UID for username */
1567 if (pwd == NULL)
1568 {
1569 error << "Failed to verify user id '" << opt_euid
1570 << "'. Does it exist?" << endl;
1571 return 1;
1572 }
1573 if (chown(data_directory.to_str().c_str(), pwd->pw_uid, pwd->pw_gid) != 0)
1574 {
1575 error << "Failed to set file ownership for "
1576 << data_directory.to_str()
1577 << " to (" << pwd->pw_uid << ", " << pwd->pw_gid << ")"
1578 << endl;
1579 }
1580 if (setegid(pwd->pw_gid) != 0)
1581 {
1582 warning << "Failed to set effective group id to " << pwd->pw_gid
1583 << endl;
1584 }
1585 if (seteuid(pwd->pw_uid) != 0)
1586 {
1587 warning << "Failed to set effective user id to " << pwd->pw_uid
1588 << endl;
1589 }
1590 }
1591 else
1592 opt_euid= 0;
1593 vector<string> command_line;
1594 if (opt_no_defaults == TRUE && opt_defaults_file == NULL &&
1595 opt_def_extra_file == NULL)
1596 command_line.push_back(string("--no-defaults"));
1597 if (opt_defaults_file != NULL)
1598 command_line.push_back(string("--defaults-file=")
1599 .append(opt_defaults_file));
1600 if (opt_def_extra_file != NULL)
1601 command_line.push_back(string("--defaults-extra-file=")
1602 .append(opt_def_extra_file));
1603 command_line.push_back(string("--bootstrap"));
1604 command_line.push_back(string("--datadir=")
1605 .append(data_directory.to_str()));
1606 command_line.push_back(string("--lc-messages-dir=")
1607 .append(language_directory.to_str()));
1608 command_line.push_back(string("--lc-messages=")
1609 .append(create_string(opt_lang)));
1610 if (basedir.length() > 0)
1611 command_line.push_back(string("--basedir=")
1612 .append(basedir));
1613
1614 string ssl_type;
1615 string ssl_cipher;
1616 string x509_issuer;
1617 string x509_subject;
1618 if (opt_ssl == true)
1619 create_ssl_policy(&ssl_type, &ssl_cipher, &x509_issuer, &x509_subject);
1620 info << "Executing " << mysqld_exec.to_str() << " ";
1621 copy(command_line.begin(), command_line.end(),
1622 infix_ostream_iterator<Path>(info, " "));
1623 info << endl;
1624 Sql_user user(adminhost,
1625 adminuser,
1626 password,
1627 Access_privilege(Access_privilege::acl_all()),
1628 ssl_type, // ssl_type
1629 ssl_cipher, // ssl_cipher
1630 x509_issuer, // x509_issuer
1631 x509_subject, // x509_subject
1632 0, // max_questions
1633 0, // max updates
1634 0, // max connections
1635 0, // max user connections
1636 create_string(opt_authplugin),
1637 string(""),
1638 expire_password,
1639 0);
1640 string output;
1641 bool success= process_execute(mysqld_exec.to_str(),
1642 command_line.begin(),
1643 command_line.end(),
1644 Process_reader(&output),
1645 Process_writer(&user,create_string(opt_sqlfile)));
1646 if (!success)
1647 {
1648 error << "Failed to execute " << mysqld_exec.to_str() << " ";
1649 copy(command_line.begin(), command_line.end(),
1650 infix_ostream_iterator<Path>(error, " "));
1651 error << endl;
1652 cerr << "-- server log begin --" << endl;
1653 cerr << output << endl;
1654 cerr << "-- server log end --" << endl;
1655 return 1;
1656 }
1657 else if (output.find("ERROR") != string::npos)
1658 {
1659 error << "The bootstrap log isn't empty:"
1660 << endl
1661 << output
1662 << endl;
1663 }
1664 else if (output.size() > 0 &&
1665 output.find_first_not_of(" \t\n\r") != string::npos)
1666 {
1667 warning << "The bootstrap log isn't empty:"
1668 << endl
1669 << output
1670 << endl;
1671 }
1672 else
1673 {
1674 info << "Success!"
1675 << endl;
1676 }
1677
1678 return 0;
1679 }
1680