// Compute DSN.
global $databases;
+ $ssl_params = \Civi\Setup\DrupalUtil::guessSslParams($databases['default']['default']);
$model->db = $model->cmsDb = array(
'server' => \Civi\Setup\DbUtil::encodeHostPort($databases['default']['default']['host'], $databases['default']['default']['port'] ?: NULL),
'username' => $databases['default']['default']['username'],
'password' => $databases['default']['default']['password'],
'database' => $databases['default']['default']['database'],
+ 'ssl_params' => empty($ssl_params) ? NULL : $ssl_params,
);
// Compute URLs
// Compute DSN.
global $databases;
+ $ssl_params = \Civi\Setup\DrupalUtil::guessSslParams($databases['default']['default']);
$model->db = $model->cmsDb = array(
'server' => \Civi\Setup\DbUtil::encodeHostPort($databases['default']['default']['host'], $databases['default']['default']['port'] ?: NULL),
'username' => $databases['default']['default']['username'],
'password' => $databases['default']['default']['password'],
'database' => $databases['default']['default']['database'],
+ 'ssl_params' => empty($ssl_params) ? NULL : $ssl_params,
);
// Compute cmsBaseUrl.
// Compute DSN.
$connectionOptions = \Drupal::database()->getConnectionOptions();
+ $ssl_params = \Civi\Setup\DrupalUtil::guessSslParams($connectionOptions);
$model->db = $model->cmsDb = array(
'server' => \Civi\Setup\DbUtil::encodeHostPort($connectionOptions['host'], $connectionOptions['port'] ?: NULL),
'username' => $connectionOptions['username'],
'password' => $connectionOptions['password'],
'database' => $connectionOptions['database'],
+ 'ssl_params' => empty($ssl_params) ? NULL : $ssl_params,
);
// Compute cmsBaseUrl.
$params['CMSdbPass'] = addslashes($m->cmsDb['password']);
$params['CMSdbHost'] = addslashes($m->cmsDb['server']);
$params['CMSdbName'] = addslashes($m->cmsDb['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['CMSdbSSL'] = empty($m->cmsDb['ssl_params']) ? '' : addslashes('&' . http_build_query($m->cmsDb['ssl_params'], '', '&', PHP_QUERY_RFC3986));
$params['siteKey'] = addslashes($m->siteKey);
$extraSettings = array();
*/
}
+ /**
+ * Guess if the CMS is using SSL for MySQL and what the corresponding
+ * parameters should be for PEAR::DB.
+ *
+ * Not all combinations will work. See the install docs for a list of known
+ * configurations that do. We don't enforce that here since we're just
+ * trying to guess a default based on what they already have.
+ *
+ * @param array $cmsDatabaseParams
+ * The contents of the section from drupal's settings.php where it defines
+ * the $database array, usually under 'default'.
+ * @return array
+ * The corresponding guessed params for PEAR::DB.
+ */
+ public static function guessSslParams(array $cmsDatabaseParams):array {
+ // If the pdo-mysql extension isn't loaded or they have nothing in drupal
+ // config for pdo, then we're done. PDO isn't required for Civi, but note
+ // the references to PDO constants below would fail and they obviously
+ // wouldn't have them in drupal config then.
+ if (empty($cmsDatabaseParams['pdo']) || !extension_loaded('pdo_mysql')) {
+ return [];
+ }
+
+ $pdo = $cmsDatabaseParams['pdo'];
+
+ $pdo_map = [
+ \PDO::MYSQL_ATTR_SSL_CA => 'ca',
+ \PDO::MYSQL_ATTR_SSL_KEY => 'key',
+ \PDO::MYSQL_ATTR_SSL_CERT => 'cert',
+ \PDO::MYSQL_ATTR_SSL_CAPATH => 'capath',
+ \PDO::MYSQL_ATTR_SSL_CIPHER => 'cipher',
+ ];
+
+ $ssl_params = [];
+
+ // If they have one set in drupal config and it's a string, then copy
+ // it over verbatim.
+ foreach ($pdo_map as $pdo_name => $ssl_name) {
+ if (!empty($pdo[$pdo_name]) && is_string($pdo[$pdo_name])) {
+ $ssl_params[$ssl_name] = $pdo[$pdo_name];
+ }
+ }
+
+ // No client certificate or server verification, but want SSL. Return our
+ // made-up indicator ssl=1 that isn't a real mysqli option but which we
+ // recognize. It's possible they have other params set too which we pass
+ // along from above, but that may not be compatible but it's up to them.
+ if (($pdo[\PDO::MYSQL_ATTR_SSL_CA] ?? NULL) === TRUE && ($pdo[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] ?? NULL) === FALSE) {
+ $ssl_params['ssl'] = 1;
+ }
+
+ return $ssl_params;
+ }
+
}
* define( 'CIVICRM_UF_DSN', 'mysql://cms_db_username:cms_db_password@db_server/cms_database?new_link=true');
*/
if (!defined('CIVICRM_UF_DSN') && CIVICRM_UF !== 'UnitTests') {
- define( 'CIVICRM_UF_DSN' , 'mysql://%%CMSdbUser%%:%%CMSdbPass%%@%%CMSdbHost%%/%%CMSdbName%%?new_link=true');
+ define( 'CIVICRM_UF_DSN' , 'mysql://%%CMSdbUser%%:%%CMSdbPass%%@%%CMSdbHost%%/%%CMSdbName%%?new_link=true%%CMSdbSSL%%');
}
// %%extraSettings%%
--- /dev/null
+<?php
+namespace Civi\Setup;
+
+/**
+ * Class DrupalUtilTest
+ * @package Civi\Setup
+ * @group headless
+ */
+class DrupalUtilTest extends \CiviUnitTestCase {
+
+ /**
+ * Test guessSslParams
+ * @dataProvider pdoParamsProvider
+ * @param array $input
+ * @param array $expected
+ */
+ public function testGuessSslParams(array $input, array $expected) {
+ $this->assertSame($expected, \Civi\Setup\DrupalUtil::guessSslParams($input));
+ }
+
+ /**
+ * Data provider for testGuessSslParams
+ * @return array
+ */
+ public function pdoParamsProvider():array {
+ return [
+ 'empty' => [[], []],
+ 'empty2' => [['pdo' => []], []],
+ 'no client certificate, no server verification' => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_CA => TRUE,
+ \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => FALSE,
+ ],
+ ],
+ ['ssl' => 1],
+ ],
+ 'typical client certificate with server verification' => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cacert.crt',
+ \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key',
+ \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt',
+ ],
+ ],
+ [
+ 'ca' => '/tmp/cacert.crt',
+ 'key' => '/tmp/my.key',
+ 'cert' => '/tmp/cert.crt',
+ ],
+ ],
+ 'client certificate, no server verification' => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => FALSE,
+ \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key',
+ \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt',
+ ],
+ ],
+ [
+ 'key' => '/tmp/my.key',
+ 'cert' => '/tmp/cert.crt',
+ ],
+ ],
+ 'self-signed client certificate with server verification' => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cert.crt',
+ \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key',
+ \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt',
+ ],
+ ],
+ [
+ 'ca' => '/tmp/cert.crt',
+ 'key' => '/tmp/my.key',
+ 'cert' => '/tmp/cert.crt',
+ ],
+ ],
+ 'Not sure what would happen in practice but is all the string params' => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cacert.crt',
+ \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key',
+ \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt',
+ \PDO::MYSQL_ATTR_SSL_CAPATH => '/tmp/cacert_folder',
+ \PDO::MYSQL_ATTR_SSL_CIPHER => 'aes',
+ ],
+ ],
+ [
+ 'ca' => '/tmp/cacert.crt',
+ 'key' => '/tmp/my.key',
+ 'cert' => '/tmp/cert.crt',
+ 'capath' => '/tmp/cacert_folder',
+ 'cipher' => 'aes',
+ ],
+ ],
+ 'Ditto, but showing extra ones should get ignored' => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_CA => '/tmp/cacert.crt',
+ \PDO::MYSQL_ATTR_SSL_KEY => '/tmp/my.key',
+ \PDO::MYSQL_ATTR_SSL_CERT => '/tmp/cert.crt',
+ \PDO::MYSQL_ATTR_SSL_CAPATH => '/tmp/cacert_folder',
+ \PDO::MYSQL_ATTR_SSL_CIPHER => 'aes',
+ 'fourteen' => 'the number fourteen',
+ ],
+ ],
+ [
+ 'ca' => '/tmp/cacert.crt',
+ 'key' => '/tmp/my.key',
+ 'cert' => '/tmp/cert.crt',
+ 'capath' => '/tmp/cacert_folder',
+ 'cipher' => 'aes',
+ ],
+ ],
+ "some windows paths shouldn't get mangled" => [
+ [
+ 'pdo' => [
+ \PDO::MYSQL_ATTR_SSL_CA => 'C:/Program Files/MariaDB 10.3/data/cacert.crt',
+ \PDO::MYSQL_ATTR_SSL_KEY => 'C:/Program Files/MariaDB 10.3/data/my.key',
+ \PDO::MYSQL_ATTR_SSL_CERT => 'C:\\Program Files\\MariaDB 10.3\\data\\cert.crt',
+ ],
+ ],
+ [
+ 'ca' => 'C:/Program Files/MariaDB 10.3/data/cacert.crt',
+ 'key' => 'C:/Program Files/MariaDB 10.3/data/my.key',
+ 'cert' => 'C:\\Program Files\\MariaDB 10.3\\data\\cert.crt',
+ ],
+ ],
+ ];
+ }
+
+}