elseif (!empty($db_config['server'])) {
$host = $db_config['server'];
}
- $conn = @mysqli_connect($host, $db_config['username'], $db_config['password'], $db_config['database'], !empty($db_config['port']) ? $db_config['port'] : NULL);
+ if (empty($db_config['ssl_params'])) {
+ $conn = @mysqli_connect($host, $db_config['username'], $db_config['password'], $db_config['database'], !empty($db_config['port']) ? $db_config['port'] : NULL);
+ }
+ else {
+ $conn = NULL;
+ $init = mysqli_init();
+ mysqli_ssl_set(
+ $init,
+ $db_config['ssl_params']['key'] ?? NULL,
+ $db_config['ssl_params']['cert'] ?? NULL,
+ $db_config['ssl_params']['ca'] ?? NULL,
+ $db_config['ssl_params']['capath'] ?? NULL,
+ $db_config['ssl_params']['cipher'] ?? NULL
+ );
+ if (@mysqli_real_connect($init, $host, $db_config['username'], $db_config['password'], $db_config['database'], (!empty($db_config['port']) ? $db_config['port'] : NULL), NULL, MYSQLI_CLIENT_SSL)) {
+ $conn = $init;
+ }
+ }
return $conn;
}
$expectedKeys = array('server', 'username', 'password', 'database');
sort($expectedKeys);
if ($keys !== $expectedKeys) {
- $e->addError('database', $dbField, sprintf("The database credentials for \"%s\" should be specified as (%s) not (%s)",
- $dbField,
- implode(',', $expectedKeys),
- implode(',', $keys)
- ));
- $errors++;
+ // if it failed it might be because of the optional ssl parameters
+ $expectedKeys[] = 'ssl_params';
+ sort($expectedKeys);
+ if ($keys !== $expectedKeys) {
+ $e->addError('database', $dbField, sprintf("The database credentials for \"%s\" should be specified as (%s) not (%s)",
+ $dbField,
+ implode(',', $expectedKeys),
+ implode(',', $keys)
+ ));
+ $errors++;
+ }
}
foreach ($db as $k => $v) {
if ($k === 'password' && empty($v)) {
$e->addWarning('database', "$dbField.$k", "The property \"$dbField.$k\" is blank. This may be correct in some controlled environments; it could also be a mistake or a symptom of an insecure configuration.");
}
- elseif (!is_scalar($v)) {
+ elseif ($k !== 'ssl_params' && !is_scalar($v)) {
$e->addError('database', "$dbField.$k", "The property \"$dbField.$k\" is not well-formed.");
$errors++;
}
'username' => $model->db['username'],
'password' => $model->db['password'],
'database' => $model->db['database'],
+ 'ssl_params' => $model->db['ssl_params'] ?? NULL,
));
_corereqadapter_addMessages($e, 'database', $dbMsgs);
});
$params['dbPass'] = addslashes($m->db['password']);
$params['dbHost'] = addslashes($m->db['server']);
$params['dbName'] = addslashes($m->db['database']);
+ // The '&' prefix is awkward, but we don't know what's already in the file.
+ // At the time of writing, it has ?new_link=true. If that is removed,
+ // then need to update this.
+ // The PHP_QUERY_RFC3986 is important because PEAR::DB will interpret plus
+ // signs as a reference to its old DSN format and mangle the DSN, so we
+ // need to use %20 for spaces.
+ $params['dbSSL'] = empty($m->db['ssl_params']) ? '' : addslashes('&' . http_build_query($m->db['ssl_params'], '', '&', PHP_QUERY_RFC3986));
$params['cms'] = addslashes($m->cms);
$params['CMSdbUser'] = addslashes($m->cmsDb['username']);
$params['CMSdbPass'] = addslashes($m->cmsDb['password']);
public static function parseDsn($dsn) {
$parsed = parse_url($dsn);
return array(
- 'server' => self::encodeHostPort($parsed['host'], $parsed['port']),
+ 'server' => self::encodeHostPort($parsed['host'], $parsed['port'] ?? NULL),
'username' => $parsed['user'] ?: NULL,
'password' => $parsed['pass'] ?: NULL,
'database' => $parsed['path'] ? ltrim($parsed['path'], '/') : NULL,
+ 'ssl_params' => self::parseSSL($parsed['query'] ?? NULL),
);
}
/**
+ * @todo Is this used anywhere? It doesn't support SSL as-is.
* Convert an datasource from array notation to URL notation.
*
* @param array $db
*/
public static function softConnect($db) {
list($host, $port) = self::decodeHostPort($db['server']);
- $conn = @mysqli_connect($host, $db['username'], $db['password'], $db['database'], $port);
+ if (empty($db['ssl_params'])) {
+ $conn = @mysqli_connect($host, $db['username'], $db['password'], $db['database'], $port);
+ }
+ else {
+ $conn = NULL;
+ $init = mysqli_init();
+ mysqli_ssl_set(
+ $init,
+ $db['ssl_params']['key'] ?? NULL,
+ $db['ssl_params']['cert'] ?? NULL,
+ $db['ssl_params']['ca'] ?? NULL,
+ $db['ssl_params']['capath'] ?? NULL,
+ $db['ssl_params']['cipher'] ?? NULL
+ );
+ // @todo socket parameter, but if you're using sockets do you need SSL?
+ if (@mysqli_real_connect($init, $host, $db['username'], $db['password'], $db['database'], $port, NULL, MYSQLI_CLIENT_SSL)) {
+ $conn = $init;
+ }
+ }
return $conn;
}
return $host . ($port ? (':' . $port) : '');
}
+ /**
+ * For SSL you can have client certificates, which has some required and
+ * optional parameters, or you can have anonymous SSL, which just requires
+ * some indication that you want that.
+ *
+ * @param string $query_string
+ * @return array
+ */
+ public static function parseSSL($query_string) {
+ if (empty($query_string)) {
+ return [];
+ }
+ parse_str($query_string, $parsed_query);
+ $sensible_parameters = [
+ // ssl=1 alone means no client certificate - it's not a real mysqli option
+ 'ssl' => NULL,
+ 'key' => NULL,
+ 'cert' => NULL,
+ 'ca' => NULL,
+ 'capath' => NULL,
+ 'cipher' => NULL,
+ ];
+ // Only want to include a param if it's in our list of sensibles, e.g.
+ // we don't want new_link=true.
+ return array_intersect_key($parsed_query, $sensible_parameters);
+ }
+
/**
* @param array $db
* @param string $SQLcontent
define('CIVICRM_DSN', $GLOBALS['_CV']['TEST_DB_DSN']);
}
else {
- define('CIVICRM_DSN', 'mysql://%%dbUser%%:%%dbPass%%@%%dbHost%%/%%dbName%%?new_link=true');
+ define('CIVICRM_DSN', 'mysql://%%dbUser%%:%%dbPass%%@%%dbHost%%/%%dbName%%?new_link=true%%dbSSL%%');
}
}
--- /dev/null
+<?php
+namespace Civi\Setup;
+
+/**
+ * Class DbUtilTest
+ * @package Civi\Setup
+ * @group headless
+ */
+class DbUtilTest extends \CiviUnitTestCase {
+
+ /**
+ * Test parseSSL
+ * @dataProvider queryStringProvider
+ * @param string $input
+ * @param array $expected
+ */
+ public function testParseSSL(string $input, array $expected) {
+ $this->assertSame($expected, \Civi\Setup\DbUtil::parseSSL($input));
+ }
+
+ /**
+ * Data provider for testParseSSL
+ * @return array
+ */
+ public function queryStringProvider():array {
+ return [
+ ['', []],
+ ['new_link=true', []],
+ ['ssl=1', ['ssl' => '1']],
+ ['new_link=true&ssl=1', ['ssl' => '1']],
+ ['ca=%2Ftmp%2Fcacert.crt', ['ca' => '/tmp/cacert.crt']],
+ [
+ 'ca=%2Ftmp%2Fcacert.crt&cert=%2Ftmp%2Fcert.crt&key=%2Ftmp%2Fmy.key',
+ [
+ 'ca' => '/tmp/cacert.crt',
+ 'cert' => '/tmp/cert.crt',
+ 'key' => '/tmp/my.key',
+ ],
+ ],
+ [
+ 'ca=%2Fpath%20with%20spaces%2Fcacert.crt',
+ [
+ 'ca' => '/path with spaces/cacert.crt',
+ ],
+ ],
+ ['cipher=aes', ['cipher' => 'aes']],
+ ['capath=%2Ftmp', ['capath' => '/tmp']],
+ [
+ 'cipher=aes&capath=%2Ftmp&food=banana',
+ [
+ 'cipher' => 'aes',
+ 'capath' => '/tmp',
+ ],
+ ],
+ ['food=banana&cipher=aes', ['cipher' => 'aes']],
+ ];
+ }
+
+}