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, version 2.0, 7# as published by the Free Software Foundation. 8# 9# This program is also distributed with certain software (including 10# but not limited to OpenSSL) that is licensed under separate terms, 11# as designated in a particular file or component or in included license 12# documentation. The authors of MySQL hereby grant you an additional 13# permission to link the program and your derivative works with the 14# separately licensed software that they have included with MySQL. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License, version 2.0, for more details. 20# 21# You should have received a copy of the GNU General Public License 22# along with this program; if not, write to the Free Software 23# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 24 25config=".my.cnf.$$" 26command=".mysql.$$" 27mysql_client="" 28 29trap "interrupt" 1 2 3 6 15 30 31rootpass="" 32echo_n= 33echo_c= 34 35set_echo_compat() { 36 case `echo "testing\c"`,`echo -n testing` in 37 *c*,-n*) echo_n= echo_c= ;; 38 *c*,*) echo_n=-n echo_c= ;; 39 *) echo_n= echo_c='\c' ;; 40 esac 41} 42 43validate_reply () { 44 ret=0 45 if [ -z "$1" ]; then 46 reply=y 47 return $ret 48 fi 49 case $1 in 50 y|Y|yes|Yes|YES) reply=y ;; 51 n|N|no|No|NO) reply=n ;; 52 *) ret=1 ;; 53 esac 54 return $ret 55} 56 57prepare() { 58 touch $config $command 59 chmod 600 $config $command 60} 61 62find_mysql_client() 63{ 64 for n in ./bin/mysql mysql 65 do 66 $n --no-defaults --help > /dev/null 2>&1 67 status=$? 68 if test $status -eq 0 69 then 70 mysql_client=$n 71 return 72 fi 73 done 74 echo "Can't find a 'mysql' client in PATH or ./bin" 75 exit 1 76} 77 78do_query() { 79 echo "$1" >$command 80 #sed 's,^,> ,' < $command # Debugging 81 $mysql_client --defaults-file=$config <$command 82 return $? 83} 84 85# Simple escape mechanism (\-escape any ' and \), suitable for two contexts: 86# - single-quoted SQL strings 87# - single-quoted option values on the right hand side of = in my.cnf 88# 89# These two contexts don't handle escapes identically. SQL strings allow 90# quoting any character (\C => C, for any C), but my.cnf parsing allows 91# quoting only \, ' or ". For example, password='a\b' quotes a 3-character 92# string in my.cnf, but a 2-character string in SQL. 93# 94# This simple escape works correctly in both places. 95basic_single_escape () { 96 # The quoting on this sed command is a bit complex. Single-quoted strings 97 # don't allow *any* escape mechanism, so they cannot contain a single 98 # quote. The string sed gets (as argv[1]) is: s/\(['\]\)/\\\1/g 99 # 100 # Inside a character class, \ and ' are not special, so the ['\] character 101 # class is balanced and contains two characters. 102 echo "$1" | sed 's/\(['"'"'\]\)/\\\1/g' 103} 104 105make_config() { 106 echo "# mysql_secure_installation config file" >$config 107 echo "[mysql]" >>$config 108 echo "user=root" >>$config 109 esc_pass=`basic_single_escape "$rootpass"` 110 echo "password='$esc_pass'" >>$config 111 #sed 's,^,> ,' < $config # Debugging 112} 113 114get_root_password() { 115 status=1 116 while [ $status -eq 1 ]; do 117 stty -echo 118 echo $echo_n "Enter current password for root (enter for none): $echo_c" 119 read password 120 echo 121 stty echo 122 if [ "x$password" = "x" ]; then 123 hadpass=0 124 else 125 hadpass=1 126 fi 127 rootpass=$password 128 make_config 129 do_query "" 130 status=$? 131 done 132 echo "OK, successfully used password, moving on..." 133 echo 134} 135 136set_root_password() { 137 stty -echo 138 echo $echo_n "New password: $echo_c" 139 read password1 140 echo 141 echo $echo_n "Re-enter new password: $echo_c" 142 read password2 143 echo 144 stty echo 145 146 if [ "$password1" != "$password2" ]; then 147 echo "Sorry, passwords do not match." 148 echo 149 return 1 150 fi 151 152 if [ "$password1" = "" ]; then 153 echo "Sorry, you can't use an empty password here." 154 echo 155 return 1 156 fi 157 158 esc_pass=`basic_single_escape "$password1"` 159 160 # attempt to lift the password expiration flag for root first 161 do_query "SET PASSWORD=PASSWORD('$esc_pass');" 162 if [ $? -ne 0 ]; then 163 echo "root password update failed!" 164 clean_and_exit 165 fi 166 167 # now since the password has changed, lets use the new one. 168 rootpass=$password1 169 make_config 170 171 # set password for all root users 172 # do the old password 173 do_query "SET @@old_passwords=1; UPDATE mysql.user SET Password=PASSWORD('$esc_pass'), password_expired='N' WHERE User='root' and plugin = 'mysql_old_password';" 174 if [ $? -ne 0 ]; then 175 echo "old password update failed!" 176 clean_and_exit 177 fi 178 179 # do the native password 180 do_query "SET @@old_passwords=0; UPDATE mysql.user SET Password=PASSWORD('$esc_pass'), password_expired='N' WHERE User='root' and plugin in ('', 'mysql_native_password');" 181 if [ $? -ne 0 ]; then 182 echo "native password update failed!" 183 clean_and_exit 184 fi 185 186 # do the sha256 password 187 do_query "SET @@old_passwords=2; UPDATE mysql.user SET authentication_string=PASSWORD('$esc_pass'), password_expired='N' WHERE User='root' and plugin = 'sha256_password';" 188 if [ $? -ne 0 ]; then 189 echo "sha256 password update failed!" 190 clean_and_exit 191 fi 192 193 echo "Password updated successfully!" 194 echo "Reloading privilege tables.." 195 reload_privilege_tables 196 if [ $? -eq 1 ]; then 197 clean_and_exit 198 fi 199 echo 200 201 return 0 202} 203 204remove_anonymous_users() { 205 do_query "DELETE FROM mysql.user WHERE User='';" 206 if [ $? -eq 0 ]; then 207 echo " ... Success!" 208 else 209 echo " ... Failed!" 210 clean_and_exit 211 fi 212 213 return 0 214} 215 216remove_remote_root() { 217 do_query "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');" 218 if [ $? -eq 0 ]; then 219 echo " ... Success!" 220 else 221 echo " ... Failed!" 222 fi 223} 224 225remove_test_database() { 226 echo " - Dropping test database..." 227 do_query "DROP DATABASE test;" 228 if [ $? -eq 0 ]; then 229 echo " ... Success!" 230 else 231 echo " ... Failed! Not critical, keep moving..." 232 fi 233 234 echo " - Removing privileges on test database..." 235 do_query "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'" 236 if [ $? -eq 0 ]; then 237 echo " ... Success!" 238 else 239 echo " ... Failed! Not critical, keep moving..." 240 fi 241 242 return 0 243} 244 245reload_privilege_tables() { 246 do_query "FLUSH PRIVILEGES;" 247 if [ $? -eq 0 ]; then 248 echo " ... Success!" 249 return 0 250 else 251 echo " ... Failed!" 252 return 1 253 fi 254} 255 256interrupt() { 257 echo 258 echo "Aborting!" 259 echo 260 cleanup 261 stty echo 262 exit 1 263} 264 265cleanup() { 266 echo "Cleaning up..." 267 rm -f $config $command 268} 269 270# Remove the files before exiting. 271clean_and_exit() { 272 cleanup 273 exit 1 274} 275 276# The actual script starts here 277 278prepare 279find_mysql_client 280set_echo_compat 281 282echo 283echo 284echo 285echo 286echo "NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MySQL" 287echo " SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!" 288echo 289echo 290 291echo "In order to log into MySQL to secure it, we'll need the current" 292echo "password for the root user. If you've just installed MySQL, and" 293echo "you haven't set the root password yet, the password will be blank," 294echo "so you should just press enter here." 295echo 296 297get_root_password 298 299 300# 301# Set the root password 302# 303 304echo "Setting the root password ensures that nobody can log into the MySQL" 305echo "root user without the proper authorisation." 306echo 307 308while true ; do 309 if [ $hadpass -eq 0 ]; then 310 echo $echo_n "Set root password? [Y/n] $echo_c" 311 else 312 echo "You already have a root password set, so you can safely answer 'n'." 313 echo 314 echo $echo_n "Change the root password? [Y/n] $echo_c" 315 fi 316 read reply 317 validate_reply $reply && break 318done 319 320if [ "$reply" = "n" ]; then 321 echo " ... skipping." 322else 323 status=1 324 while [ $status -eq 1 ]; do 325 set_root_password 326 status=$? 327 done 328fi 329echo 330 331 332# 333# Remove anonymous users 334# 335 336echo "By default, a MySQL installation has an anonymous user, allowing anyone" 337echo "to log into MySQL without having to have a user account created for" 338echo "them. This is intended only for testing, and to make the installation" 339echo "go a bit smoother. You should remove them before moving into a" 340echo "production environment." 341echo 342 343while true ; do 344 echo $echo_n "Remove anonymous users? [Y/n] $echo_c" 345 read reply 346 validate_reply $reply && break 347done 348if [ "$reply" = "n" ]; then 349 echo " ... skipping." 350else 351 remove_anonymous_users 352fi 353echo 354 355 356# 357# Disallow remote root login 358# 359 360echo "Normally, root should only be allowed to connect from 'localhost'. This" 361echo "ensures that someone cannot guess at the root password from the network." 362echo 363while true ; do 364 echo $echo_n "Disallow root login remotely? [Y/n] $echo_c" 365 read reply 366 validate_reply $reply && break 367done 368if [ "$reply" = "n" ]; then 369 echo " ... skipping." 370else 371 remove_remote_root 372fi 373echo 374 375 376# 377# Remove test database 378# 379 380echo "By default, MySQL comes with a database named 'test' that anyone can" 381echo "access. This is also intended only for testing, and should be removed" 382echo "before moving into a production environment." 383echo 384 385while true ; do 386 echo $echo_n "Remove test database and access to it? [Y/n] $echo_c" 387 read reply 388 validate_reply $reply && break 389done 390 391if [ "$reply" = "n" ]; then 392 echo " ... skipping." 393else 394 remove_test_database 395fi 396echo 397 398 399# 400# Reload privilege tables 401# 402 403echo "Reloading the privilege tables will ensure that all changes made so far" 404echo "will take effect immediately." 405echo 406 407while true ; do 408 echo $echo_n "Reload privilege tables now? [Y/n] $echo_c" 409 read reply 410 validate_reply $reply && break 411done 412 413if [ "$reply" = "n" ]; then 414 echo " ... skipping." 415else 416 reload_privilege_tables 417fi 418echo 419 420cleanup 421 422echo 423echo 424echo 425echo "All done! If you've completed all of the above steps, your MySQL" 426echo "installation should now be secure." 427echo 428echo "Thanks for using MySQL!" 429echo 430echo 431 432 433