@@ -0,0 +1,225 @@
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2018 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/

if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}

/**
* GLPI security key
**/
class GLPIKey {
/**
* Key file path.
*
* @var string
*/
private $keyfile;

/**
* List of crypted DB fields.
*
* @var array
*/
protected $fields = [
'glpi_mailcollectors.passwd',
'glpi_authldaps.rootdn_passwd'
];

/**
* List of crypted configuration values.
* Each key corresponds to a configuration context, and contains list of configs names.
*
* @var array
*/
protected $configs = [
'core' => [
'smtp_passwd',
'proxy_passwd'
]
];

public function __construct() {
$this->keyfile = GLPI_CONFIG_DIR . '/glpi.key';
}

/**
* Check if GLPI security key used for decryptable passwords exists
*
* @return string
*/
public function keyExists() {
return file_exists($this->keyfile) && !empty($this->get());
}

/**
* Get GLPI security key used for decryptable passwords
*
* @return string
*/
public function get() {
$key = GLPIKEY;
if (file_exists($this->keyfile)) {
//load key from existing config file
$key = file_get_contents($this->keyfile);
}
return $key;
}

/**
* Generate GLPI security key used for decryptable passwords
* and update values in DB if necessary.
*
* @return boolean
*/
public function generate() {
global $DB;

$current_key = file_exists($this->keyfile) ? $this->get() : GLPIKEY;

$success = (bool)file_put_contents($this->keyfile, Toolbox::getRandomString(50));
if (!$success) {
return false;
}

if ($DB instanceof DBmysql) {
return $this->migrateFieldsInDb($current_key)
&& $this->migrateConfigsInDb($current_key);
}

return true;
}

/**
* Get fields
*
* @return array
*/
public function getFields() {
global $PLUGIN_HOOKS;

$fields = $this->fields;
if (isset($PLUGIN_HOOKS['secured_fields'])) {
foreach ($PLUGIN_HOOKS['secured_fields'] as $plugfields) {
$fields = array_merge($fields, $plugfields);
}
}

return $fields;
}

/**
* Get configs
*
* @return array
*/
public function getConfigs() {
global $PLUGIN_HOOKS;

$configs = $this->configs;

if (isset($PLUGIN_HOOKS['secured_configs'])) {
foreach ($PLUGIN_HOOKS['secured_configs'] as $plugin => $plugconfigs) {
$configs['plugin:' . $plugin] = $plugconfigs;
}
}

return $configs;
}

/**
* Migrate fields in database
*
* @param string $current_key Current key
*
* @return void
*/
protected function migrateFieldsInDb($current_key) {
global $DB;

$success = true;

foreach ($this->getFields() as $field) {
list($table, $column) = explode('.', $field);

$iterator = $DB->request([
'SELECT' => ['id', $column],
'FROM' => $table
]);

while ($success && $row = $iterator->next()) {
$pass = Toolbox::encrypt(Toolbox::decrypt($row[$column], $current_key));
$success = $DB->update(
$table,
[$field => $pass],
['id' => $row['id']]
);
}
}

return $success;
}

/**
* Migrate configurations in database
*
* @param string $current_key Current key
*
* @return boolean
*/
protected function migrateConfigsInDb($current_key) {
global $DB;

$success = true;

foreach ($this->getConfigs() as $context => $names) {
$iterator = $DB->request([
'FROM' => Config::getTable(),
'WHERE' => [
'context' => $context,
'name' => $names
]
]);

while ($success && $row = $iterator->next()) {
$pass = Toolbox::encrypt(Toolbox::decrypt($row['value'], $current_key));
$success = $DB->update(
Config::getTable(),
['value' => $pass],
['id' => $row['id']]
);
}
}

return $success;
}
}
@@ -65,7 +65,7 @@ function __construct() {
if ($CFG_GLPI['smtp_username'] != '') {
$this->SMTPAuth = true;
$this->Username = $CFG_GLPI['smtp_username'];
$this->Password = Toolbox::decrypt($CFG_GLPI['smtp_passwd'], GLPIKEY);
$this->Password = Toolbox::decrypt($CFG_GLPI['smtp_passwd']);
}

if ($CFG_GLPI['smtp_mode'] == MAIL_SMTPSSL) {
@@ -145,7 +145,7 @@ function prepareInputForUpdate($input) {
if (empty($input["passwd"])) {
unset($input["passwd"]);
} else {
$input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]), GLPIKEY);
$input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]));
}
}

@@ -174,7 +174,7 @@ function prepareInputForAdd($input) {
if (empty($input["passwd"])) {
unset($input["passwd"]);
} else {
$input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]), GLPIKEY);
$input["passwd"] = Toolbox::encrypt(stripslashes($input["passwd"]));
}
}

@@ -1218,14 +1218,14 @@ function connect() {

if ($this->fields['use_kerberos']) {
$this->marubox = @imap_open($this->fields['host'], $this->fields['login'],
Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
Toolbox::decrypt($this->fields['passwd']),
CL_EXPUNGE, 1);
} else {
$try_options = [['DISABLE_AUTHENTICATOR' => 'GSSAPI'],
['DISABLE_AUTHENTICATOR' => 'PLAIN']];
foreach ($try_options as $option) {
$this->marubox = @imap_open($this->fields['host'], $this->fields['login'],
Toolbox::decrypt($this->fields['passwd'], GLPIKEY),
Toolbox::decrypt($this->fields['passwd']),
CL_EXPUNGE, 1, $option);
if (is_resource($this->marubox)) {
break;
@@ -880,8 +880,7 @@ static function getRSSFeed($url, $cache_duration = DAY_TIMESTAMP) {
if (!empty($CFG_GLPI["proxy_user"])) {
$prx_opt[CURLOPT_HTTPAUTH] = CURLAUTH_ANYSAFE;
$prx_opt[CURLOPT_PROXYUSERPWD] = $CFG_GLPI["proxy_user"].":".
Toolbox::decrypt($CFG_GLPI["proxy_passwd"],
GLPIKEY);
Toolbox::decrypt($CFG_GLPI["proxy_passwd"]);
}
$feed->set_curl_options($prx_opt);
}
@@ -239,7 +239,15 @@ static function decodeFromUtf8($string, $to_charset = "ISO-8859-1") {
*
* @return encrypted string
**/
static function encrypt($string, $key) {
static function encrypt($string, $key = null) {

if ($key === null) {
$key = self::getGlpiSecKey();
}

if ($key === GLPIKEY && !defined('TU_USER')) {
self::deprecated('Using GLPIKEY is not secure!');
}

$result = '';
for ($i=0; $i<strlen($string); $i++) {
@@ -260,7 +268,11 @@ static function encrypt($string, $key) {
*
* @return decrypted string
**/
static function decrypt($string, $key) {
static function decrypt($string, $key = null) {

if ($key === null) {
$key = self::getGlpiSecKey();
}

$result = '';
$string = base64_decode($string);
@@ -275,6 +287,19 @@ static function decrypt($string, $key) {
return Toolbox::unclean_cross_side_scripting_deep($result);
}

/**
* Get GLPI security key used for decryptable passwords
*
* Will read key from config/glpi.key if present.
* For 9.4 branch, this will defaults to GLPIKEY.
*
* @return string
*/
public static function getGlpiSecKey() {
$glpikey = new GLPIKey();
return $glpikey->get();
}


/**
* Prevent from XSS
@@ -1737,7 +1762,7 @@ public static function callCurl($url, array $eopts = [], &$msgerr = null) {
if (!empty($CFG_GLPI["proxy_user"])) {
$opts += [
CURLOPT_PROXYAUTH => CURLAUTH_BASIC,
CURLOPT_PROXYUSERPWD => $CFG_GLPI["proxy_user"] . ":" . self::decrypt($CFG_GLPI["proxy_passwd"], GLPIKEY),
CURLOPT_PROXYUSERPWD => $CFG_GLPI["proxy_user"] . ":" . self::decrypt($CFG_GLPI["proxy_passwd"]),
];
}

@@ -163,7 +163,8 @@ public function doUpdates($current_version = null) {
$updir = __DIR__ . "/../install/";

if (isCommandLine() && version_compare($current_version, '0.72.3', 'lt')) {
die('Upgrade from command line is not supported before 0.72.3!');
echo 'Upgrade from command line is not supported before 0.72.3!';
die(1);
}

// Update process desactivate all plugins
@@ -500,6 +501,12 @@ public function doUpdates($current_version = null) {
$crontask_telemetry->getFromDBbyName("Telemetry", "telemetry");
$crontask_telemetry->resetDate();
$crontask_telemetry->resetState();

//generate security key if missing, and update db
$glpikey = new GLPIKey();
if (!$glpikey->keyExists() && !$glpikey->generate()) {
$this->migration->displayWarning(__('Unable to create security key file!'), true);
}
}

/**
@@ -375,6 +375,18 @@ function next_form() {
Html::closeForm();
}

//create security key
$glpikey = new GLPIKey();
$secured = $glpikey->keyExists();
if (!$secured) {
$secured = $glpikey->generate();
}
if (!$secured) {
echo "<p><strong>".__('Security key cannot be generated!')."</strong></p>";
prev_form($host, $user, $password);
return;
}

//Check if the port is in url
$hostport = explode(":", $host);
if (count($hostport) < 2) {
@@ -104,8 +104,7 @@
foreach ($ldap_methods as $method) {
echo " ".$method['name'];
if (AuthLDAP::tryToConnectToServer($method, $method["rootdn"],
Toolbox::decrypt($method["rootdn_passwd"],
GLPIKEY))) {
Toolbox::decrypt($method["rootdn_passwd"]))) {
echo "_OK";
} else {
echo "_PROBLEM";
@@ -159,16 +159,16 @@ public function testPrepareInputForUpdate() {
$input = ['name' => 'ldap', 'rootdn_passwd' => $password];
$result = $ldap->prepareInputForUpdate($input);

//Expected value to be encrypted using GLPIKEY key
$expected = \Toolbox::encrypt(stripslashes($password), GLPIKEY);
//Expected value to be encrypted using current key
$expected = \Toolbox::encrypt(stripslashes($password));
$this->string($result['rootdn_passwd'])->isIdenticalTo($expected);

$password = 'tot\'o';
$input = ['name' => 'ldap', 'rootdn_passwd' => $password];
$result = $ldap->prepareInputForUpdate($input);

//Expected value to be encrypted using GLPIKEY key
$expected = \Toolbox::encrypt(stripslashes($password), GLPIKEY);
//Expected value to be encrypted using current key
$expected = \Toolbox::encrypt(stripslashes($password));
$this->string($result['rootdn_passwd'])->isIdenticalTo($expected);

$input['_blank_passwd'] = 1;