3434#
3535# #############################################################################
3636
37+ use Fcntl;
3738use File::Basename;
3839use Getopt::Long;
3940use Sys::Hostname;
@@ -70,6 +71,9 @@ Usage: $0 [OPTIONS]
7071 --help Display this help and exit.
7172 --ldata=path The path to the MySQL data directory. Same as --datadir.
7273 --no-defaults Don't read default options from any option file.
74+ --random-passwords Create and set a random password for all root accounts
75+ and set the "password expired" flag,
76+ remove the anonymous accounts.
7377 --rpm For internal use. This option is used by RPM files
7478 during the MySQL installation process.
7579 --skip-name-resolve Use IP addresses rather than hostnames when creating
@@ -132,8 +136,9 @@ sub parse_arguments
132136 " verbose" ,
133137 " rpm" ,
134138 " help" ,
139+ " random-passwords" ,
135140
136- # These options will also be pased to mysqld.
141+ # These options will also be pased to mysqld.
137142 " defaults-file=s" ,
138143 " defaults-extra-file=s" ,
139144 " no-defaults" ,
@@ -147,7 +152,7 @@ sub parse_arguments
147152 # the install package. See top-level 'dist-hook' make target.
148153 #
149154 # --windows is a deprecated alias
150- " cross-bootstrap|windows" , # FIXME undocumented, even needed?
155+ " cross-bootstrap|windows" ,
151156 ) or usage();
152157
153158 usage() if $opt -> {help };
@@ -249,6 +254,121 @@ sub quote_options {
249254 return join (" " , @cmd );
250255}
251256
257+ # #############################################################################
258+ #
259+ # Simple escape mechanism (\-escape any ' and \), suitable for two contexts:
260+ # - single-quoted SQL strings
261+ # - single-quoted option values on the right hand side of = in my.cnf
262+ #
263+ # #############################################################################
264+
265+ # (Function and comment copied from 'mysql_secure_installation')
266+
267+ # These two contexts don't handle escapes identically. SQL strings allow
268+ # quoting any character (\C => C, for any C), but my.cnf parsing allows
269+ # quoting only \, ' or ". For example, password='a\b' quotes a 3-character
270+ # string in my.cnf, but a 2-character string in SQL.
271+ #
272+ # This simple escape works correctly in both places.
273+
274+ # FIXME: What about double quote in password? Not handled here - not needed?
275+
276+ sub basic_single_escape {
277+ my ($str ) = @_ ;
278+ # Inside a character class, \ is not special; this escapes both \ and '
279+ $str =~ s / ([\' ])/ \\ $1 / g ;
280+ return $str ;
281+ }
282+
283+ # #############################################################################
284+ #
285+ # Handle the files with confidential contents
286+ #
287+ # #############################################################################
288+
289+ my $secret_file ; # full path name of the confidential file
290+ my $escaped_password ; # the password, with special characters escaped
291+
292+ sub ensure_secret_file {
293+ $secret_file = $ENV {HOME } . " /.mysql_secret" ;
294+
295+ # Create safe files to avoid leaking info to other users
296+ # Loop may be extended if we need more ...
297+ foreach my $file ( $secret_file ) {
298+ next if -f $file ; # Already exists
299+ local *FILE;
300+ sysopen (FILE, $file , O_CREAT, 0600)
301+ or die " ERROR: can't create $file : $! " ;
302+ close FILE;
303+ }
304+ }
305+
306+ # #############################################################################
307+ #
308+ # Append an arbitrary number of lines to an existing file
309+ #
310+ # #############################################################################
311+
312+ sub append_file {
313+ my $file = shift ;
314+ -f $file or die " ERROR: file is missing \" $file \" : $! " ;
315+ open (FILE, " >>$file " ) or die " ERROR: can't append to file \" $file \" : $! " ;
316+ foreach my $line ( @_ ) {
317+ print FILE $line , " \n " ; # Add EOL char
318+ }
319+ close FILE;
320+ }
321+
322+ # #############################################################################
323+ #
324+ # Inform the user about the generated random password
325+ #
326+ # #############################################################################
327+
328+ sub tell_root_password {
329+ my $now = localtime (); # scalar context = printable string
330+
331+ # Now, we need to tell the user the new root password.
332+ # We use "append_file" to protect the user in case they are doing multiple
333+ # installations intermixed with backups and restores.
334+ # While this would be really bad practice, it still might happen.
335+ # As long as this file is not destroyed, the time stamps may rescue them.
336+ # Having the comment and the password on the same line makes it easier
337+ # to automatically extract the password (automated testing!), and the final
338+ # empty line is for better redability.
339+ append_file($secret_file ,
340+ " # The random password set for the root user at $now (local time): " .
341+ $escaped_password ,
342+ " " );
343+ print " A random root password has been set. You will find it in '$secret_file '.\n " ;
344+ }
345+
346+ # #############################################################################
347+ #
348+ # Generate a random password
349+ #
350+ # #############################################################################
351+
352+ sub generate_random_password {
353+ # Short term:
354+ # On (at least) Linux and Solaris, a "random" device is available, use it:
355+ # cat /dev/urandom | tr -dc "[:alnum:]" | fold -w 8 | head -1
356+ # Note: There is no guarantee the results will pass a validation checker
357+ # as resulted from WL#2739
358+ # http://wl.no.oracle.com/worklo/?tid=2739
359+ # http://dev.mysql.com/doc/refman/5.6/en/validate-password-plugin.html
360+ #
361+ # Long term:
362+ # Password generated via Perl module "String::MkPasswd" available at
363+ # http://search.cpan.org/~cgrau/String-MkPasswd/bin/mkpasswd.pl
364+ # Using it got approved recently.
365+ #
366+ my $password = ` cat /dev/urandom | tr -dc "[:alnum:]" | fold -w 8 | head -1` ;
367+ chomp ($password );
368+ return $password ;
369+ }
370+
371+
252372# #############################################################################
253373#
254374# Ok, let's go. We first need to parse arguments which are required by
@@ -312,6 +432,31 @@ $opt = {}; # Reset the arguments FIXME ?
312432parse_arguments($opt , @default_options );
313433parse_arguments($opt , ' PICK-ARGS-FROM-ARGV' , @ARGV );
314434
435+ # ----------------------------------------------------------------------
436+ # Create a random password for root, if requested and implemented
437+ # ----------------------------------------------------------------------
438+
439+ if ( $opt -> {' random-passwords' } ) {
440+ # Add other non-working OS like this: $^O =~ m/^(solaris|linux|freebsd|darwin)$/
441+ # Issue 1: random password creation
442+ # Issue 2: confidential file
443+ if ( $^O =~ m / ^(MSWin32)$ / ) {
444+ print " Random password not yet implemented for $^O - option will be ignored\n " ;
445+ delete $opt -> {' random-passwords' };
446+ } else {
447+ ensure_secret_file();
448+ my $password = generate_random_password();
449+ if ( $password ) {
450+ # "true" means "string is non-empty"
451+ $escaped_password = basic_single_escape($password );
452+ } else {
453+ # Whatever the reason (missing "/dev/urandom"), an empty password is bad
454+ print " Could not generate a random password - not setting one\n " ;
455+ delete $opt -> {' random-passwords' };
456+ }
457+ }
458+ }
459+
315460# ----------------------------------------------------------------------
316461# Configure paths to support files
317462# ----------------------------------------------------------------------
@@ -368,8 +513,9 @@ if ( $opt->{srcdir} )
368513my $fill_help_tables = " $pkgdatadir /fill_help_tables.sql" ;
369514my $create_system_tables = " $pkgdatadir /mysql_system_tables.sql" ;
370515my $fill_system_tables = " $pkgdatadir /mysql_system_tables_data.sql" ;
516+ my $security_commands = " $pkgdatadir /mysql_security_commands.sql" ;
371517
372- foreach my $f ( $fill_help_tables ,$create_system_tables ,$fill_system_tables )
518+ foreach my $f ( $fill_help_tables , $create_system_tables , $fill_system_tables , $security_commands )
373519{
374520 -f $f or cannot_find_file($f );
375521}
@@ -483,6 +629,21 @@ if ( open(PIPE, "| $mysqld_install_cmd_line") )
483629
484630 print PIPE $_ ;
485631 }
632+
633+ if ( $opt -> {' random-passwords' } )
634+ {
635+ open (SQL3, $security_commands )
636+ or error($opt ," can't open $security_commands for reading: $! " );
637+ while ( <SQL3> )
638+ {
639+ # using the implicit variable $_ !
640+ s / ABC123xyz/ $escaped_password / e ; # Replace placeholder by random password
641+ print PIPE $_ ;
642+ }
643+ close SQL3;
644+ tell_root_password();
645+ }
646+
486647 close PIPE;
487648 close SQL;
488649 close SQL2;
@@ -525,22 +686,43 @@ if ( open(PIPE, "| $mysqld_install_cmd_line") )
525686 # set a password after installing the data files on the real host system.
526687 # At this point, there is no end user, so it does not make sense to print
527688 # this reminder.
528- report($opt ,
529- " PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQL root USER !" ,
530- " To do so, start the server, then issue the following commands:" ,
531- " " ,
532- " $bindir /mysqladmin -u root password 'new-password'" ,
533- " $bindir /mysqladmin -u root -h $hostname password 'new-password'" ,
534- " " ,
535- " Alternatively you can run:" ,
536- " " ,
537- " $bindir /mysql_secure_installation" ,
538- " " ,
539- " which will also give you the option of removing the test" ,
540- " databases and anonymous user created by default. This is" ,
541- " strongly recommended for production servers." ,
542- " " ,
543- " See the manual for more instructions." );
689+ if ( $opt -> {' random-passwords' } ) {
690+ report($opt ,
691+ " A RANDOM PASSWORD HAS BEEN SET FOR THE MySQL root USER !" ,
692+ " You will find that password in '$secret_file '." ,
693+ " " ,
694+ " You must change that password on your first connect," ,
695+ " no other statement but 'SET PASSWORD' will be accepted." ,
696+ " See the manual for the semantics of the 'password expired' flag." ,
697+ " " ,
698+ " Also, the account for the anonymous user has been removed." ,
699+ " " ,
700+ " In addition, you can run:" ,
701+ " " ,
702+ " $bindir /mysql_secure_installation" ,
703+ " " ,
704+ " which will also give you the option of removing the test database." ,
705+ " This is strongly recommended for production servers." ,
706+ " " ,
707+ " See the manual for more instructions." );
708+ } else {
709+ report($opt ,
710+ " PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQL root USER !" ,
711+ " To do so, start the server, then issue the following commands:" ,
712+ " " ,
713+ " $bindir /mysqladmin -u root password 'new-password'" ,
714+ " $bindir /mysqladmin -u root -h $hostname password 'new-password'" ,
715+ " " ,
716+ " Alternatively you can run:" ,
717+ " " ,
718+ " $bindir /mysql_secure_installation" ,
719+ " " ,
720+ " which will also give you the option of removing the test" ,
721+ " databases and anonymous user created by default. This is" ,
722+ " strongly recommended for production servers." ,
723+ " " ,
724+ " See the manual for more instructions." );
725+ }
544726
545727 if ( !$opt -> {rpm } )
546728 {
0 commit comments