1#!/bin/sh 2 3# Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved. 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; version 2 of the License. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program; if not, write to the Free Software 16# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA 17 18config=".my.cnf.$$" 19command=".mysql.$$" 20output=".my.output.$$" 21 22trap "interrupt" 1 2 3 6 15 23 24rootpass="" 25echo_n= 26echo_c= 27basedir= 28defaults_file= 29defaults_extra_file= 30no_defaults= 31 32parse_arg() 33{ 34 echo "$1" | sed -e 's/^[^=]*=//' 35} 36 37parse_arguments() 38{ 39 # We only need to pass arguments through to the server if we don't 40 # handle them here. So, we collect unrecognized options (passed on 41 # the command line) into the args variable. 42 pick_args= 43 if test "$1" = PICK-ARGS-FROM-ARGV 44 then 45 pick_args=1 46 shift 47 fi 48 49 for arg 50 do 51 case "$arg" in 52 --basedir=*) basedir=`parse_arg "$arg"` ;; 53 --defaults-file=*) defaults_file="$arg" ;; 54 --defaults-extra-file=*) defaults_extra_file="$arg" ;; 55 --no-defaults) no_defaults="$arg" ;; 56 *) 57 if test -n "$pick_args" 58 then 59 # This sed command makes sure that any special chars are quoted, 60 # so the arg gets passed exactly to the server. 61 # XXX: This is broken; true fix requires using eval and proper 62 # quoting of every single arg ($basedir, $ldata, etc.) 63 #args="$args "`echo "$arg" | sed -e 's,\([^a-zA-Z0-9_.-]\),\\\\\1,g'` 64 args="$args $arg" 65 fi 66 ;; 67 esac 68 done 69} 70 71# Try to find a specific file within --basedir which can either be a binary 72# release or installed source directory and return the path. 73find_in_basedir() 74{ 75 return_dir=0 76 found=0 77 case "$1" in 78 --dir) 79 return_dir=1; shift 80 ;; 81 esac 82 83 file=$1; shift 84 85 for dir in "$@" 86 do 87 if test -f "$basedir/$dir/$file" 88 then 89 found=1 90 if test $return_dir -eq 1 91 then 92 echo "$basedir/$dir" 93 else 94 echo "$basedir/$dir/$file" 95 fi 96 break 97 fi 98 done 99 100 if test $found -eq 0 101 then 102 # Test if command is in PATH 103 $file --no-defaults --version > /dev/null 2>&1 104 status=$? 105 if test $status -eq 0 106 then 107 echo $file 108 fi 109 fi 110} 111 112cannot_find_file() 113{ 114 echo 115 echo "FATAL ERROR: Could not find $1" 116 117 shift 118 if test $# -ne 0 119 then 120 echo 121 echo "The following directories were searched:" 122 echo 123 for dir in "$@" 124 do 125 echo " $dir" 126 done 127 fi 128 129 echo 130 echo "If you compiled from source, you need to run 'make install' to" 131 echo "copy the software into the correct location ready for operation." 132 echo 133 echo "If you are using a binary release, you must either be at the top" 134 echo "level of the extracted archive, or pass the --basedir option" 135 echo "pointing to that location." 136 echo 137} 138 139# Ok, let's go. We first need to parse arguments which are required by 140# my_print_defaults so that we can execute it first, then later re-parse 141# the command line to add any extra bits that we need. 142parse_arguments PICK-ARGS-FROM-ARGV "$@" 143 144# 145# We can now find my_print_defaults. This script supports: 146# 147# --srcdir=path pointing to compiled source tree 148# --basedir=path pointing to installed binary location 149# 150# or default to compiled-in locations. 151# 152 153if test -n "$basedir" 154then 155 print_defaults=`find_in_basedir my_print_defaults bin extra` 156 echo "print: $print_defaults" 157 if test -z "$print_defaults" 158 then 159 cannot_find_file my_print_defaults $basedir/bin $basedir/extra 160 exit 1 161 fi 162 mysql_command=`find_in_basedir mysql bin` 163 if test -z "$mysql_command" 164 then 165 cannot_find_file mysql $basedir/bin 166 exit 1 167 fi 168else 169 print_defaults="@bindir@/my_print_defaults" 170 mysql_command="@bindir@/mysql" 171fi 172 173if test ! -x "$print_defaults" 174then 175 cannot_find_file "$print_defaults" 176 exit 1 177fi 178 179if test ! -x "$mysql_command" 180then 181 cannot_find_file "$mysql_command" 182 exit 1 183fi 184 185# Now we can get arguments from the group [client] and [client-server] 186# in the my.cfg file, then re-run to merge with command line arguments. 187parse_arguments `$print_defaults $defaults_file $defaults_extra_file $no_defaults client client-server client-mariadb` 188parse_arguments PICK-ARGS-FROM-ARGV "$@" 189 190set_echo_compat() { 191 case `echo "testing\c"`,`echo -n testing` in 192 *c*,-n*) echo_n= echo_c= ;; 193 *c*,*) echo_n=-n echo_c= ;; 194 *) echo_n= echo_c='\c' ;; 195 esac 196} 197 198validate_reply () { 199 ret=0 200 if [ -z "$1" ]; then 201 reply=y 202 return $ret 203 fi 204 case $1 in 205 y|Y|yes|Yes|YES) reply=y ;; 206 n|N|no|No|NO) reply=n ;; 207 *) ret=1 ;; 208 esac 209 return $ret 210} 211 212prepare() { 213 touch $config $command 214 chmod 600 $config $command 215} 216 217do_query() { 218 echo "$1" >$command 219 #sed 's,^,> ,' < $command # Debugging 220 $mysql_command --defaults-file=$config $defaults_extra_file $no_defaults $args <$command >$output 221 return $? 222} 223 224# Simple escape mechanism (\-escape any ' and \), suitable for two contexts: 225# - single-quoted SQL strings 226# - single-quoted option values on the right hand side of = in my.cnf 227# 228# These two contexts don't handle escapes identically. SQL strings allow 229# quoting any character (\C => C, for any C), but my.cnf parsing allows 230# quoting only \, ' or ". For example, password='a\b' quotes a 3-character 231# string in my.cnf, but a 2-character string in SQL. 232# 233# This simple escape works correctly in both places. 234basic_single_escape () { 235 # The quoting on this sed command is a bit complex. Single-quoted strings 236 # don't allow *any* escape mechanism, so they cannot contain a single 237 # quote. The string sed gets (as argv[1]) is: s/\(['\]\)/\\\1/g 238 # 239 # Inside a character class, \ and ' are not special, so the ['\] character 240 # class is balanced and contains two characters. 241 echo "$1" | sed 's/\(['"'"'\]\)/\\\1/g' 242} 243 244# 245# create a simple my.cnf file to be able to pass the root password to the mysql 246# client without putting it on the command line 247# 248make_config() { 249 echo "# mysql_secure_installation config file" >$config 250 echo "[mysql]" >>$config 251 echo "user=root" >>$config 252 esc_pass=`basic_single_escape "$rootpass"` 253 echo "password='$esc_pass'" >>$config 254 #sed 's,^,> ,' < $config # Debugging 255 256 if test -n "$defaults_file" 257 then 258 dfile=`parse_arg "$defaults_file"` 259 cat "$dfile" >>$config 260 fi 261} 262 263get_root_password() { 264 status=1 265 while [ $status -eq 1 ]; do 266 stty -echo 267 echo $echo_n "Enter current password for root (enter for none): $echo_c" 268 read password 269 echo 270 stty echo 271 if [ "x$password" = "x" ]; then 272 emptypass=1 273 else 274 emptypass=0 275 fi 276 rootpass=$password 277 make_config 278 do_query "show create user root@localhost" 279 status=$? 280 done 281 if grep -q unix_socket $output; then 282 emptypass=0 283 fi 284 echo "OK, successfully used password, moving on..." 285 echo 286} 287 288set_root_password() { 289 stty -echo 290 echo $echo_n "New password: $echo_c" 291 read password1 292 echo 293 echo $echo_n "Re-enter new password: $echo_c" 294 read password2 295 echo 296 stty echo 297 298 if [ "$password1" != "$password2" ]; then 299 echo "Sorry, passwords do not match." 300 echo 301 return 1 302 fi 303 304 if [ "$password1" = "" ]; then 305 echo "Sorry, you can't use an empty password here." 306 echo 307 return 1 308 fi 309 310 esc_pass=`basic_single_escape "$password1"` 311 do_query "UPDATE mysql.global_priv SET priv=json_set(priv, '$.plugin', 'mysql_native_password', '$.authentication_string', PASSWORD('$esc_pass')) WHERE User='root';" 312 if [ $? -eq 0 ]; then 313 echo "Password updated successfully!" 314 echo "Reloading privilege tables.." 315 reload_privilege_tables 316 if [ $? -eq 1 ]; then 317 clean_and_exit 318 fi 319 echo 320 rootpass=$password1 321 make_config 322 else 323 echo "Password update failed!" 324 clean_and_exit 325 fi 326 327 return 0 328} 329 330remove_anonymous_users() { 331 do_query "DELETE FROM mysql.global_priv WHERE User='';" 332 if [ $? -eq 0 ]; then 333 echo " ... Success!" 334 else 335 echo " ... Failed!" 336 clean_and_exit 337 fi 338 339 return 0 340} 341 342remove_remote_root() { 343 do_query "DELETE FROM mysql.global_priv WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');" 344 if [ $? -eq 0 ]; then 345 echo " ... Success!" 346 else 347 echo " ... Failed!" 348 fi 349} 350 351remove_test_database() { 352 echo " - Dropping test database..." 353 do_query "DROP DATABASE IF EXISTS test;" 354 if [ $? -eq 0 ]; then 355 echo " ... Success!" 356 else 357 echo " ... Failed! Not critical, keep moving..." 358 fi 359 360 echo " - Removing privileges on test database..." 361 do_query "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'" 362 if [ $? -eq 0 ]; then 363 echo " ... Success!" 364 else 365 echo " ... Failed! Not critical, keep moving..." 366 fi 367 368 return 0 369} 370 371reload_privilege_tables() { 372 do_query "FLUSH PRIVILEGES;" 373 if [ $? -eq 0 ]; then 374 echo " ... Success!" 375 return 0 376 else 377 echo " ... Failed!" 378 return 1 379 fi 380} 381 382interrupt() { 383 echo 384 echo "Aborting!" 385 echo 386 cleanup 387 stty echo 388 exit 1 389} 390 391cleanup() { 392 echo "Cleaning up..." 393 rm -f $config $command $output 394} 395 396# Remove the files before exiting. 397clean_and_exit() { 398 cleanup 399 exit 1 400} 401 402# The actual script starts here 403 404prepare 405set_echo_compat 406 407echo 408echo "NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB" 409echo " SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!" 410echo 411echo "In order to log into MariaDB to secure it, we'll need the current" 412echo "password for the root user. If you've just installed MariaDB, and" 413echo "haven't set the root password yet, you should just press enter here." 414echo 415 416get_root_password 417 418 419# 420# Set the root password 421# 422 423echo "Setting the root password or using the unix_socket ensures that nobody" 424echo "can log into the MariaDB root user without the proper authorisation." 425echo 426 427while true ; do 428 if [ $emptypass -eq 1 ]; then 429 echo $echo_n "Enable unix_socket authentication? [Y/n] $echo_c" 430 else 431 echo "You already have your root account protected, so you can safely answer 'n'." 432 echo 433 echo $echo_n "Switch to unix_socket authentication [Y/n] $echo_c" 434 fi 435 read reply 436 validate_reply $reply && break 437done 438 439if [ "$reply" = "n" ]; then 440 echo " ... skipping." 441else 442 emptypass=0 443 do_query "UPDATE mysql.global_priv SET priv=json_set(priv, '$.password_last_changed', UNIX_TIMESTAMP(), '$.plugin', 'mysql_native_password', '$.authentication_string', 'invalid', '$.auth_or', json_array(json_object(), json_object('plugin', 'unix_socket'))) WHERE User='root';" 444 if [ $? -eq 0 ]; then 445 echo "Enabled successfully!" 446 echo "Reloading privilege tables.." 447 reload_privilege_tables 448 if [ $? -eq 1 ]; then 449 clean_and_exit 450 fi 451 echo 452 else 453 echo "Failed!" 454 clean_and_exit 455 fi 456fi 457echo 458 459while true ; do 460 if [ $emptypass -eq 1 ]; then 461 echo $echo_n "Set root password? [Y/n] $echo_c" 462 else 463 echo "You already have your root account protected, so you can safely answer 'n'." 464 echo 465 echo $echo_n "Change the root password? [Y/n] $echo_c" 466 fi 467 read reply 468 validate_reply $reply && break 469done 470 471if [ "$reply" = "n" ]; then 472 echo " ... skipping." 473else 474 status=1 475 while [ $status -eq 1 ]; do 476 set_root_password 477 status=$? 478 done 479fi 480echo 481 482 483# 484# Remove anonymous users 485# 486 487echo "By default, a MariaDB installation has an anonymous user, allowing anyone" 488echo "to log into MariaDB without having to have a user account created for" 489echo "them. This is intended only for testing, and to make the installation" 490echo "go a bit smoother. You should remove them before moving into a" 491echo "production environment." 492echo 493 494while true ; do 495 echo $echo_n "Remove anonymous users? [Y/n] $echo_c" 496 read reply 497 validate_reply $reply && break 498done 499if [ "$reply" = "n" ]; then 500 echo " ... skipping." 501else 502 remove_anonymous_users 503fi 504echo 505 506 507# 508# Disallow remote root login 509# 510 511echo "Normally, root should only be allowed to connect from 'localhost'. This" 512echo "ensures that someone cannot guess at the root password from the network." 513echo 514while true ; do 515 echo $echo_n "Disallow root login remotely? [Y/n] $echo_c" 516 read reply 517 validate_reply $reply && break 518done 519if [ "$reply" = "n" ]; then 520 echo " ... skipping." 521else 522 remove_remote_root 523fi 524echo 525 526 527# 528# Remove test database 529# 530 531echo "By default, MariaDB comes with a database named 'test' that anyone can" 532echo "access. This is also intended only for testing, and should be removed" 533echo "before moving into a production environment." 534echo 535 536while true ; do 537 echo $echo_n "Remove test database and access to it? [Y/n] $echo_c" 538 read reply 539 validate_reply $reply && break 540done 541 542if [ "$reply" = "n" ]; then 543 echo " ... skipping." 544else 545 remove_test_database 546fi 547echo 548 549 550# 551# Reload privilege tables 552# 553 554echo "Reloading the privilege tables will ensure that all changes made so far" 555echo "will take effect immediately." 556echo 557 558while true ; do 559 echo $echo_n "Reload privilege tables now? [Y/n] $echo_c" 560 read reply 561 validate_reply $reply && break 562done 563 564if [ "$reply" = "n" ]; then 565 echo " ... skipping." 566else 567 reload_privilege_tables 568fi 569echo 570 571cleanup 572 573echo 574echo "All done! If you've completed all of the above steps, your MariaDB" 575echo "installation should now be secure." 576echo 577echo "Thanks for using MariaDB!" 578