diff --git a/includes/admin-page.css b/includes/admin-page.css new file mode 100644 index 0000000..132edd4 --- /dev/null +++ b/includes/admin-page.css @@ -0,0 +1,12 @@ +#dashboard_right_now .securedbconnection-ssl span:before { + content: "\f160"; +} + +#dashboard_right_now .securedbconnection-nossl span:before { + content: "\f528"; + color: #d54e21; +} + +#dashboard_right_now .securedbconnection-nossl { + color: #d54e21; +} diff --git a/includes/db.php b/includes/db.php new file mode 100644 index 0000000..b41f928 --- /dev/null +++ b/includes/db.php @@ -0,0 +1,175 @@ +is_mysql = true; + + /* + * Deprecated in 3.9+ when using MySQLi. No equivalent + * $new_link parameter exists for mysqli_* functions. + */ + $new_link = defined( 'MYSQL_NEW_LINK' ) ? MYSQL_NEW_LINK : true; + $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0; + + if ( $this->use_mysqli ) { + $this->dbh = mysqli_init(); + + // mysqli_real_connect doesn't support the host param including a port or socket + // like mysql_connect does. This duplicates how mysql_connect detects a port and/or socket file. + $port = null; + $socket = null; + $host = $this->dbhost; + $port_or_socket = strstr( $host, ':' ); + if ( ! empty( $port_or_socket ) ) { + $host = substr( $host, 0, strpos( $host, ':' ) ); + $port_or_socket = substr( $port_or_socket, 1 ); + if ( 0 !== strpos( $port_or_socket, '/' ) ) { + $port = intval( $port_or_socket ); + $maybe_socket = strstr( $port_or_socket, ':' ); + if ( ! empty( $maybe_socket ) ) { + $socket = substr( $maybe_socket, 1 ); + } + } else { + $socket = $port_or_socket; + } + } + + // Set SSL certs if we want to use secure DB connections + $ssl_opts = array( + 'KEY' => ( defined( 'MYSQL_SSL_KEY' ) && is_file( MYSQL_SSL_KEY ) ) ? MYSQL_SSL_KEY : null, + 'CERT' => ( defined( 'MYSQL_SSL_CERT' ) && is_file( MYSQL_SSL_CERT ) ) ? MYSQL_SSL_CERT : null, + 'CA' => ( defined( 'MYSQL_SSL_CA' ) && is_file( MYSQL_SSL_CA ) ) ? MYSQL_SSL_CA : null, + 'CA_PATH' => ( defined( 'MYSQL_SSL_CA_PATH' ) && is_dir ( MYSQL_SSL_CA_PATH ) ) ? MYSQL_SSL_CA_PATH : null, + 'CIPHER' => ( defined( 'MYSQL_SSL_CIPHER' ) && !empty ( MYSQL_SSL_CIPHER ) ) ? MYSQL_SSL_CIPHER : null, + ); + $ssl_opts_set = false; + foreach ( $ssl_opts as $ssl_opt_val ) { + if ( !is_null( $ssl_opt_val ) ) { + $ssl_opts_set = true; + break; + } + } + if ( $ssl_opts_set ) { + mysqli_ssl_set( + $this->dbh, + $ssl_opts[ 'KEY' ], + $ssl_opts[ 'CERT' ], + $ssl_opts[ 'CA' ], + $ssl_opts[ 'CA_PATH' ], + $ssl_opts[ 'CIPHER' ] + ); + } + + if ( WP_DEBUG ) { + mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags ); + } else { + @mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags ); + } + + if ( $this->dbh->connect_errno ) { + $this->dbh = null; + + /* It's possible ext/mysqli is misconfigured. Fall back to ext/mysql if: + * - We haven't previously connected, and + * - WP_USE_EXT_MYSQL isn't set to false, and + * - ext/mysql is loaded. + */ + $attempt_fallback = true; + + if ( $this->has_connected ) { + $attempt_fallback = false; + } elseif ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) { + $attempt_fallback = false; + } elseif ( ! function_exists( 'mysql_connect' ) ) { + $attempt_fallback = false; + } + + if ( $attempt_fallback ) { + $this->use_mysqli = false; + return $this->db_connect( $allow_bail ); + } + } + } else { + if ( WP_DEBUG ) { + $this->dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags ); + } else { + $this->dbh = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags ); + } + } + + if ( ! $this->dbh && $allow_bail ) { + wp_load_translations_early(); + + // Load custom DB error template, if present. + if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) { + require_once( WP_CONTENT_DIR . '/db-error.php' ); + die(); + } + + $message = '

' . __( 'Error establishing a database connection' ) . "

\n"; + + $message .= '

' . sprintf( + /* translators: 1: wp-config.php. 2: database host */ + __( 'This either means that the username and password information in your %1$s file is incorrect or we can’t contact the database server at %2$s. This could mean your host’s database server is down.' ), + 'wp-config.php', + '' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '' + ) . "

\n"; + + $message .= "\n"; + + $message .= '

' . sprintf( + /* translators: %s: support forums URL */ + __( 'If you’re unsure what these terms mean you should probably contact your host. If you still need help you can always visit the WordPress Support Forums.' ), + __( 'https://wordpress.org/support/' ) + ) . "

\n"; + + $this->bail( $message, 'db_connect_fail' ); + + return false; + } elseif ( $this->dbh ) { + if ( ! $this->has_connected ) { + $this->init_charset(); + } + + $this->has_connected = true; + + $this->set_charset( $this->dbh ); + + $this->ready = true; + $this->set_sql_mode(); + $this->select( $this->dbname, $this->dbh ); + + return true; + } + + return false; + } + +} + +$wpdb = new wpdb_ssl( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST ); diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..d5496dc --- /dev/null +++ b/readme.txt @@ -0,0 +1,70 @@ +=== Secure DB Connection === +Contributors: HypertextRanch +Tags: db, mysql, secure, encrypted, ssl +Requires at least: 3.9 +Tested up to: 4.6 +Stable tag: 1.0 +License: GPLv3 +License URI: http://www.gnu.org/licenses/gpl-3.0.html + +Sets SSL keys and certs for encrypted database connections. + +== Description == + +Depending on the MySQL server setup the SSL certs used may not be in the trusted store, if that's the case `mysqli_ssl_set()` needs to be called to set custom keys and certs before connect. This Plugin adds a custom DB class that allows these settings to be configured via custom constants. + +This plugin will also add a custom item on the "At a Glance" section of the Dashboard to show if the `$wpdb` connection is secure or not. + +Also find me on [GitHub](https://github.com/xyu/secure-db-connection). + += Configuration Parameters = + +To adjust the configuration, define any of the following applicable constants in your `wp-config.php` file. + + * `MYSQL_SSL_KEY` [default: not set] + + The path name to the key file. (RSA Key) + + * `MYSQL_SSL_CERT` [default: not set] + + The path name to the certificate file. + + * `MYSQL_SSL_CA` [default: not set] + + The path name to the certificate authority file. + + * `MYSQL_SSL_CA_PATH` [default: not set] + + The pathname to a directory that contains trusted SSL CA certificates in PEM format. + + * `MYSQL_SSL_CIPHER` [default: not set] + + A list of allowable ciphers to use for SSL encryption + += Turning on SSL = + +Once SSL keys and certs have been configured you via the defines above define an WP core constant to pass a use SSL flag to the mysqli client also in your `wp-config.php` file. + +``` +define( 'MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL ); +``` + +If you are using the MySQL Native Driver and MySQL 5.6 or later `mysqli_real_connect()` will verify the server SSL certificate before connecting. If the SSL cert installed on the MySQL server your are connecting to is not valid PHP will refuse to connect. A flag was added to disable server certificate validation. If your server has an invalid certificate turn on SSL and turn off validation like so: + +``` +define( 'MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT ); +``` + +== Installation == + +For detailed installation instructions, please read the [standard installation procedure for WordPress plugins](http://codex.wordpress.org/Managing_Plugins#Installing_Plugins). + +1. Install and activate plugin. +1. Symlink or copy the `db.php` file from the `/plugins/secure-db-connection/includes/` directory to the `/wp-content/` directory. +1. Set the relevant defines in your `wp-config.php` file. + +== Changelog == + += 1.0 = + + * Initial release diff --git a/secure-db-connection.php b/secure-db-connection.php new file mode 100644 index 0000000..a69ea8a --- /dev/null +++ b/secure-db-connection.php @@ -0,0 +1,81 @@ +init(); + } + + public function init() { + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_filter( 'dashboard_glance_items', array( $this, 'dashboard_glance_items' ) ); + } + + public function admin_enqueue_scripts( $hook_suffix ) { + if ( "index.php" === $hook_suffix ) { + $plugin = get_plugin_data( __FILE__ ); + wp_enqueue_style( + 'secure-db-connection', + plugin_dir_url( __FILE__ ) . 'includes/admin-page.css', + null, + $plugin[ 'Version' ] + ); + } + } + + /** + * Add to Dashboard At a Glance echo instead of return to hack in + * custom styles. + */ + public function dashboard_glance_items( $elements ) { + if ( current_user_can( 'administrator' ) ) { + $status = $this->_getConnStatus(); + + if ( empty( $status['ssl_cipher'] ) ) { + printf( + '
  • %s
  • ', + "Connection to MySQL is in plain text", + 'MySQL Unencrypted' + ); + } else { + printf( + '
  • %s
  • ', + "Connection to MySQL is SSL ({$status['ssl_version']}) encrypted via {$status['ssl_cipher']}", + "MySQL Secured" + ); + } + } + + return $elements; + } + + private function _getConnStatus() { + global $wpdb; + + $results = $wpdb->get_results( + "SHOW SESSION STATUS WHERE variable_name IN ( 'Ssl_cipher', 'Ssl_version' )" + ); + + $return = array(); + foreach ( $results as $row ) { + $key = strtolower( $row->Variable_name ); + $return[ $key ] = $row->Value; + } + return $return; + } + +} + +new WP_SecureDBConnection();