diff --git a/src/FusionDirectory/Cli/Application.php b/src/FusionDirectory/Cli/Application.php index 653e21dbea3d4c9f45e1d462c76d61989a069f6e..2c2d4d04c36a44c7c6aac97398fe9b75c6b2f5a7 100644 --- a/src/FusionDirectory/Cli/Application.php +++ b/src/FusionDirectory/Cli/Application.php @@ -28,19 +28,19 @@ class Application { /** * @var array<string,array> - * Options this application supports. Should be filled in constructor. + * Options this application supports. Should be filled in constructor's child. */ - protected $options = []; + protected $options = []; /** * @var array<string,array> - * Arguments this application supports. Should be filled in constructor. + * Arguments this application supports. Should be filled in constructor's child. */ - protected $args = []; + protected array $args = []; /** * @var array<string,mixed> - * Result of options parsing + * Result of options parsing. */ - protected $getopt = []; + protected array $getopt = []; public function __construct () { @@ -52,29 +52,34 @@ class Application */ protected function usage (array $argv): void { - echo 'Usage: '.$argv[0].' --'.str_replace(':', ' VALUE', implode(' --', array_keys($this->options))).' '.strtoupper(implode(' ', array_keys($this->args)))."\n\n"; + echo 'Usage: ' . $argv[0] . ' --' . str_replace(':', ' VALUE', implode(' --', array_keys($this->options))) . ' ' . strtoupper(implode(' ', array_keys($this->args))) . "\n\n"; + foreach ($this->options as $opt => $infos) { - printf("\t--%-25s\t%s\n", $opt.(isset($infos['short']) ? ', -'.$infos['short'] : ''), $infos['help']); + printf("\t--%-25s\t%s\n", $opt . (isset($infos['short']) ? ', -' . $infos['short'] : ''), $infos['help']); } + foreach ($this->args as $arg => $infos) { printf("\t%-25s:\t%s\n", strtoupper($arg), $infos['help']); } + exit(1); } /** - * Parse arguments - * @param array<string> $argv + * @param array $argv + * @param int $optind + * @return void */ protected function parseArgs (array $argv, int $optind): void { + // optind comes from setopt method, counting the matches in existing options and arguments passed. If not equals, print usage. if ((count($argv) - $optind) != count($this->args)) { $this->usage($argv); } $argv = array_slice($argv, $optind); - foreach ($this->args as $arg => &$infos) { + foreach ($this->args as &$infos) { if ($infos['handler'] == '…') { /* All the last args */ $infos['value'] = $argv; @@ -88,20 +93,17 @@ class Application } /** - * Parse options and arguments from $argv - * @param array<string> $argv - * - * @return array<string,mixed> + * @return string + * Note : This method is used to format the available options in children applications. Create short formats. + * This class is responsible to output the helpers info and thus receive options from children. */ - protected function parseOptionsAndArgs (array $argv): array + protected function generateShortOptions (): string { - /* Parse options */ - $shortOptions = implode('', array_map( - function (string $key, array $infos): string - { + return implode('', array_map( + function (string $key, array $infos): string { if (isset($infos['short'])) { if (substr($key, -1) === ':') { - return $infos['short'].':'; + return $infos['short'] . ':'; } else { return $infos['short']; } @@ -112,73 +114,154 @@ class Application array_keys($this->options), array_values($this->options) )); - $getopt = getopt($shortOptions, array_keys($this->options), $optind); + } + + /** + * Note : Simply output usage if required after verifying existing potential invalid arguments + * Receive option index (returned by getopt method) and the arguments passed to the script + */ + protected function verifyArguments ($optind, $argv, $shortOptions) + { for ($i = 0; $i < $optind; $i++) { + if (($argv[$i][0] === '-') && ($argv[$i] !== '--')) { if (preg_match('/^--(.+)$/', $argv[$i], $m)) { - if (!isset($this->options[$m[1]]) && !isset($this->options[$m[1].':'])) { - echo 'Unrecognized option '.$argv[$i]."\n"; + if (!isset($this->options[$m[1]]) && !isset($this->options[$m[1] . ':'])) { + + echo 'Unrecognized option ' . $argv[$i] . "\n"; $this->usage($argv); } } elseif (preg_match('/^-(.+)$/', $argv[$i], $m)) { $shorts = str_split($m[1]); + foreach ($shorts as $short) { if (strpos($shortOptions, $short) === FALSE) { - echo 'Unrecognized option -'.$short."\n"; + echo 'Unrecognized option -' . $short . "\n"; + $this->usage($argv); } } } else { - echo 'Failed to parse option '.$argv[$i]."\n"; + echo 'Failed to parse option ' . $argv[$i] . "\n"; + $this->usage($argv); } } } - foreach ($this->options as $key => $option) { - if (substr($key, -1) !== ':') { - if (isset($getopt[$key])) { - if (is_array($getopt[$key])) { - $getopt[$key] = count($getopt[$key]); - } else { - $getopt[$key] = 1; - } - } else { - $getopt[$key] = 0; - } - if (isset($option['short']) && isset($getopt[$option['short']])) { - if (is_array($getopt[$option['short']])) { - $getopt[$key] += count($getopt[$option['short']]); - } else { - $getopt[$key]++; - } - unset($getopt[$option['short']]); - } + } + + /** + * @return void + * Logic is that every options that are matched by an arguments will have an incremental int increased from zero. + * This allows to have a final array of arguments (validated) on which we can work on to execute related functions. + */ + private function handleShortOptions ($key, $option, &$getopt): void + { + if (isset($option['short']) && isset($getopt[$option['short']])) { + if (is_array($getopt[$option['short']])) { + $getopt[$key] += count($getopt[$option['short']]); + } else { - $key = substr($key, 0, -1); - if (isset($option['short']) && isset($getopt[$option['short']])) { - if (!isset($getopt[$key])) { - $getopt[$key] = $getopt[$option['short']]; - } else { - if (is_string($getopt[$key])) { - $getopt[$key] = [$getopt[$key]]; - } - if (is_array($getopt[$option['short']])) { - $getopt[$key] = array_merge($getopt[$key], $getopt[$option['short']]); - } else { - $getopt[$key][] = $getopt[$option['short']]; - } - } - } + $getopt[$key]++; } - if (isset($getopt[$key])) { + + unset($getopt[$option['short']]); + } + } + + /** + * @return void + * Note : This method, like the handleShortOptions - incremented the integer. Arguments requiring data. + */ + private function processNonValueOption ($key, $option, &$getopt): void + { + if (!isset($getopt[$key])) { + $getopt[$key] = 0; + } elseif (is_array($getopt[$key])) { + $getopt[$key] = count($getopt[$key]); + } else { + $getopt[$key] = 1; + } + + // Simply handle the short options + $this->handleShortOptions($key, $option, $getopt); + } + + /** + * @return void + * Note : Method which process arguments requiring data input. (See processNonValueOption for non data arguments). + */ + private function processValueOption ($key, $option, &$getopt): void + { + $key = substr($key, 0, -1); + + // Simply handle short option with value below + if (isset($option['short']) && isset($getopt[$option['short']])) { + if (!isset($getopt[$key])) { + + $getopt[$key] = $getopt[$option['short']]; + } else { + if (is_string($getopt[$key])) { $getopt[$key] = [$getopt[$key]]; } - if (isset($option['handler'])) { - $getopt[$key] = $option['handler']($getopt[$key]); + + if (is_array($getopt[$option['short']])) { + $getopt[$key] = array_merge($getopt[$key], $getopt[$option['short']]); + } else { + + $getopt[$key][] = $getopt[$option['short']]; } } } + } + + /** + * @return void + * Note: We are putting a numerical value on arguments allowing better error handling. + * Note 2: getopt is passed by reference. Keep this function private. + */ + private function processOptions ($key, $option, &$getopt): void + { + if (substr($key, -1) !== ':') { + // Process non-value options (No added path or file or options to the arguments set). + $this->processNonValueOption($key, $option, $getopt); + } else { + // Process value options (argument provided + data). + $this->processValueOption($key, $option, $getopt); + } + + if (isset($getopt[$key])) { + if (is_string($getopt[$key])) { + $getopt[$key] = [$getopt[$key]]; + } + if (isset($option['handler'])) { + $getopt[$key] = $option['handler']($getopt[$key]); + } + } + } + + /** + * @param array $argv + * @return array + * Note : This is the main method to verify arguments passed and generated a final array which summarize valid arguments + * passed. + */ + protected function parseOptionsAndArgs (array $argv): array + { + // Parse into a short format, options received by children applications. + $shortOptions = $this->generateShortOptions(); + + // using getopt method, arguments passed are matched to existing preset long/short options. + $getopt = getopt($shortOptions, array_keys($this->options), $optind); + + // getopt does not return wrong arguments passed, therefore below verification allows to indicate it. + $this->verifyArguments($optind, $argv, $shortOptions); + + foreach ($this->options as $key => $option) { + // process each options retrieved by getopt + $this->processOptions($key, $option, $getopt); + } /* Parse arguments */ $this->parseArgs($argv, $optind); @@ -193,16 +276,18 @@ class Application { foreach ($this->getopt as $key => $value) { if (isset($this->options[$key]['command']) && ($value > 0)) { + call_user_func([$this, $this->options[$key]['command']]); - } elseif (isset($this->options[$key.':']['command'])) { - call_user_func([$this, $this->options[$key.':']['command']], $value); + } elseif (isset($this->options[$key . ':']['command'])) { + + call_user_func([$this, $this->options[$key . ':']['command']], $value); } } } /** * Main function. - * By default only parse options and arguments, and print help if needed. + * By default, only parse options and arguments, and print help if needed. * Extend if you want to call runCommands. * @param array<string> $argv */ @@ -230,10 +315,10 @@ class Application /* Remove the \n at the end of $input */ $line = trim($line); - if (in_array(strtolower($line), ['yes','y'])) { + if (in_array(strtolower($line), ['yes', 'y'])) { $return = TRUE; break; - } elseif (in_array(strtolower($line), ['no','n'])) { + } elseif (in_array(strtolower($line), ['no', 'n'])) { $return = FALSE; break; } @@ -249,7 +334,7 @@ class Application if ($defaultAnswer != '') { $thingToAsk .= " [$defaultAnswer]"; } - echo $thingToAsk.":\n"; + echo $thingToAsk . ":\n"; if ($hidden) { /* FIXME maybe find a better way */ diff --git a/src/FusionDirectory/Cli/FusionDirectory.php b/src/FusionDirectory/Cli/FusionDirectory.php new file mode 100644 index 0000000000000000000000000000000000000000..47c8a0f863b81c1a916536a683b710375f625a70 --- /dev/null +++ b/src/FusionDirectory/Cli/FusionDirectory.php @@ -0,0 +1,229 @@ +<?php +/* + This code is part of ldap-config-manager (https://www.fusiondirectory.org/) + + Copyright (C) 2020-2024 FusionDirectory + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace FusionDirectory\Cli; + +use Exception; +use SimpleXMLElement; +use SodiumException; + +/** + * Base class for interacting with FusionDirectory specifics + */ +class FusionDirectory extends Application +{ + /** + * Current values of variables + * @var array<string,string> + */ + protected $vars; + protected $configFilePath; + /** + * @var string path to the FusionDirectory secrets file containing the key to decrypt passwords + */ + protected string $secretsFilePath; + + public function __construct () + { + parent::__construct(); + + // Variables to be set during script calling. + $this->vars = [ + 'fd_home' => '/usr/share/fusiondirectory', + 'fd_config_dir' => '/etc/fusiondirectory', + 'config_file' => 'fusiondirectory.conf', + 'secrets_file' => 'fusiondirectory.secrets', + 'fd_cache' => '/var/cache/fusiondirectory', + 'fd_smarty_path' => '/usr/share/php/smarty3/Smarty.class.php', + 'fd_spool_dir' => '/var/spool/fusiondirectory', + 'locale_dir' => 'locale', + 'class_cache' => 'class.cache', + 'locale_cache_dir' => 'locale', + 'tmp_dir' => 'tmp', + 'fai_log_dir' => 'fai', + 'template_dir' => 'template' + ]; + } + + /** + * @return array[] + */ + public function getVarOptions (): array + { + return [ + 'list-vars' => [ + 'help' => 'List an example of a possible var to give to --set-var followed by --write-vars', + 'command' => 'cmdListVars', + ], + 'set-var:' => [ + 'help' => 'Set the variable value', + 'command' => 'cmdSetVar', + ], + ]; + } + + + /** + * Read variables.inc file from FusionDirectory and update variables accordingly + */ + protected function readFusionDirectoryVariablesFile (): void + { + if ($this->verbose()) { + printf('Reading vars from %s' . "\n", $this->vars['fd_home'] . '/include/variables.inc'); + } + require_once($this->vars['fd_home'] . '/include/variables.inc'); + + $fd_cache = $this->removeFinalSlash(CACHE_DIR); + $varsToSet = [ + 'fd_config_dir' => $this->removeFinalSlash(CONFIG_DIR), + 'config_file' => $this->removeFinalSlash(CONFIG_FILE), + 'fd_smarty_path' => $this->removeFinalSlash(SMARTY), + 'fd_spool_dir' => $this->removeFinalSlash(SPOOL_DIR), + 'fd_cache' => $fd_cache, + 'locale_cache_dir' => $this->removeFinalSlash(str_replace($fd_cache . '/', '', LOCALE_DIR)), + 'tmp_dir' => $this->removeFinalSlash(str_replace($fd_cache . '/', '', TEMP_DIR)), + 'template_dir' => $this->removeFinalSlash(str_replace($fd_cache . '/', '', CONFIG_TEMPLATE_DIR)), + 'fai_log_dir' => $this->removeFinalSlash(str_replace($fd_cache . '/', '', FAI_LOG_DIR)), + 'class_cache' => $this->removeFinalSlash(CLASS_CACHE), + ]; + foreach ($varsToSet as $var => $value) { + if (isset($this->vars[$var])) { + $this->vars[$var] = $value; + } + } + } + + + /** + * @return void + * Only print an example of variables that can be changed. + */ + protected function cmdListVars (): void + { + foreach ($this->vars as $key => $value) { + printf("%-20s [%s]\n", $key, $value); + } + } + + + /** + * @param string $var + * @return void + * @throws Exception + * Note : This allows to set var in the variables.php file in FD. + * This method needs rework as multiple variables are not received, only a string. + * (logic of multiple should be defined in parent class) + */ + protected function cmdSetVar (string $var): void + { + $varsToSet = []; + if (preg_match('/^([^=]+)=(.+)$/', $var, $m)) { + if (isset($this->vars[strtolower($m[1])])) { + $varsToSet[strtolower($m[1])] = $m[2]; + } else { + throw new Exception('Var "' . $m[1] . '" does not exists. Use --list-vars to get the list of vars.'); + } + } else { + throw new Exception('Incorrect syntax for --set-var: "' . $var . '". Use var=value'); + } + + if (isset($varsToSet['fd_home'])) { + if ($this->verbose()) { + printf('Setting var %s to "%s"' . "\n", 'fd_home', $this->removeFinalSlash($varsToSet['fd_home'])); + } + $this->vars['fd_home'] = $this->removeFinalSlash($varsToSet['fd_home']); + } + $this->readFusionDirectoryVariablesFile(); + unset($varsToSet['fd_home']); + foreach ($varsToSet as $var => $value) { + if ($this->verbose()) { + printf('Setting var %s to "%s"' . "\n", $var, $value); + } + $this->vars[$var] = $value; + } + } + + /** + * Load locations information from FusionDirectory configuration file + * @return array<array{tls: bool, uri: string, base: string, bind_dn: string, bind_pwd: string}> locations + * @throws SodiumException + * @throws Exception + */ + protected function loadFusionDirectoryConfigurationFile (): array + { + if ($this->verbose()) { + printf('Loading configuration file from %s' . "\n", $this->configFilePath); + } + + $secret = NULL; + if (file_exists($this->secretsFilePath)) { + if ($this->verbose()) { + printf('Using secrets file %s' . "\n", $this->secretsFilePath); + } + $lines = file($this->secretsFilePath, FILE_SKIP_EMPTY_LINES); + if ($lines === FALSE) { + throw new Exception('Could not open "' . $this->secretsFilePath . '"'); + } + foreach ($lines as $line) { + if (preg_match('/RequestHeader set FDKEY ([^ \n]+)\n/', $line, $m)) { + $secret = sodium_base642bin($m[1], SODIUM_BASE64_VARIANT_ORIGINAL, ''); + break; + } + } + } + + // Note: this function is case sensitive with xml tags and attributes, FD is not + $xml = new SimpleXMLElement($this->configFilePath, 0, TRUE); + $locations = []; + foreach ($xml->main->location as $loc) { + $ref = $loc->referral[0]; + $location = [ + 'tls' => (isset($loc['ldapTLS']) && (strcasecmp((string)$loc['ldapTLS'], 'TRUE') === 0)), + 'uri' => (string)$ref['URI'], + 'base' => (string)($ref['base'] ?? $loc['base'] ?? ''), + 'bind_dn' => (string)$ref['adminDn'], + 'bind_pwd' => (string)$ref['adminPassword'], + ]; + if ($location['base'] === '') { + if (preg_match('|^(.*)/([^/]+)$|', $location['uri'], $m)) { + /* Format from FD<1.3 */ + $location['uri'] = $m[1]; + $location['base'] = $m[2]; + } else { + throw new Exception('"' . $location['uri'] . '" does not contain any base!'); + } + } + if ($secret !== NULL) { + $location['bind_pwd'] = SecretBox::decrypt($location['bind_pwd'], $secret); + } + $locations[(string)$loc['name']] = $location; + if ($this->verbose()) { + printf('Found location %s (%s)' . "\n", (string)$loc['name'], $location['uri']); + } + } + if (count($locations) < 1) { + throw new Exception('No location found in configuration file'); + } + + return $locations; + } + +} \ No newline at end of file diff --git a/src/FusionDirectory/Cli/LdapApplication.php b/src/FusionDirectory/Cli/LdapApplication.php index 875640f76152b7525e6f5eda9e1c846a080a2561..a6cc067f3ddd6e2e52265a1ac1643b23de925b42 100644 --- a/src/FusionDirectory/Cli/LdapApplication.php +++ b/src/FusionDirectory/Cli/LdapApplication.php @@ -21,32 +21,33 @@ namespace FusionDirectory\Cli; -use \FusionDirectory\Ldap; +use FusionDirectory\Ldap; +use SodiumException; /** * Base class for cli applications that needs an LDAP connection from fusiondirectory configuration file */ -class LdapApplication extends Application +class LdapApplication extends FusionDirectory { /** * @var Ldap\Link|null */ - protected $ldap = NULL; + protected ?Ldap\Link $ldap = NULL; /** * @var string Ldap tree base */ - protected $base; + protected string $base; - /** - * @var string path to the FusionDirectory configuration file - */ - protected $configFilePath; + // /** + // * @var string path to the FusionDirectory configuration file + // */ + // protected string $configFilePath; - /** - * @var string path to the FusionDirectory secrets file containing the key to decrypt passwords - */ - protected $secretsFilePath; + // /** + // * @var string path to the FusionDirectory secrets file containing the key to decrypt passwords + // */ + // protected string $secretsFilePath; /** * May not be needed once SecretBox situation clears up @@ -58,36 +59,36 @@ class LdapApplication extends Application { parent::__construct(); - $this->options = [ - 'ldapuri:' => [ - 'help' => 'URI to connect to, defaults to configuration file value', + $this->options = [ + 'ldapuri:' => [ + 'help' => 'URI to connect to, defaults to configuration file value', ], - 'binddn:' => [ - 'help' => 'DN to bind with, defaults to configuration file value', + 'binddn:' => [ + 'help' => 'DN to bind with, defaults to configuration file value', ], - 'bindpwd:' => [ - 'help' => 'Password to bind with, defaults to configuration file value', + 'bindpwd:' => [ + 'help' => 'Password to bind with, defaults to configuration file value', ], - 'saslmech:' => [ - 'help' => 'SASL mech, activates SASL if specified', + 'saslmech:' => [ + 'help' => 'SASL mech, activates SASL if specified', ], - 'saslrealm:' => [ - 'help' => 'SASL realm', + 'saslrealm:' => [ + 'help' => 'SASL realm', ], - 'saslauthcid:' => [ - 'help' => 'SASL authcid', + 'saslauthcid:' => [ + 'help' => 'SASL authcid', ], - 'saslauthzid:' => [ - 'help' => 'SASL authzid', + 'saslauthzid:' => [ + 'help' => 'SASL authzid', ], - 'yes' => [ - 'help' => 'Answer yes to all questions', + 'yes' => [ + 'help' => 'Answer yes to all questions', ], - 'verbose' => [ - 'help' => 'Verbose output', + 'verbose' => [ + 'help' => 'Verbose output', ], - 'help' => [ - 'help' => 'Show this help', + 'help' => [ + 'help' => 'Show this help', ], ]; } @@ -95,6 +96,8 @@ class LdapApplication extends Application /** * Read FusionDirectory configuration file, and open a connection to the LDAP server * If there already is a connection opened, do nothing + * @throws Ldap\Exception + * @throws SodiumException */ protected function readFusionDirectoryConfigurationFileAndConnectToLdap (): void { @@ -102,11 +105,11 @@ class LdapApplication extends Application return; } - $locations = $this->loadFusionDirectoryConfigurationFile(); - $location = (string)key($locations); + $locations = $this->loadFusionDirectoryConfigurationFile(); + $location = (string)key($locations); if (count($locations) > 1) { /* Give the choice between locations to user */ - $question = 'There are several locations in your config file, which one should be used: ('.implode(',', array_keys($locations)).')'; + $question = 'There are several locations in your config file, which one should be used: (' . implode(',', array_keys($locations)) . ')'; do { $answer = $this->askUserInput($question, $location); } while (!isset($locations[$answer])); @@ -115,7 +118,7 @@ class LdapApplication extends Application $config = $locations[$location]; if ($this->verbose()) { - printf('Connecting to LDAP at %s'."\n", $this->getopt['ldapuri'][0] ?? $config['uri']); + printf('Connecting to LDAP at %s' . "\n", $this->getopt['ldapuri'][0] ?? $config['uri']); } $this->ldap = new Ldap\Link($this->getopt['ldapuri'][0] ?? $config['uri']); if (($this->getopt['saslmech'][0] ?? '') === '') { @@ -133,71 +136,4 @@ class LdapApplication extends Application $this->base = $config['base']; } - - /** - * Load locations information from FusionDirectory configuration file - * @return array<array{tls: bool, uri: string, base: string, bind_dn: string, bind_pwd: string}> locations - */ - protected function loadFusionDirectoryConfigurationFile (): array - { - if ($this->verbose()) { - printf('Loading configuration file from %s'."\n", $this->configFilePath); - } - - $secret = NULL; - if (file_exists($this->secretsFilePath)) { - if ($this->verbose()) { - printf('Using secrets file %s'."\n", $this->secretsFilePath); - } - $lines = file($this->secretsFilePath, FILE_SKIP_EMPTY_LINES); - if ($lines === FALSE) { - throw new \Exception('Could not open "'.$this->secretsFilePath.'"'); - } - foreach ($lines as $line) { - if (preg_match('/RequestHeader set FDKEY ([^ \n]+)\n/', $line, $m)) { - $secret = \sodium_base642bin($m[1], SODIUM_BASE64_VARIANT_ORIGINAL); - break; - } - } - } - - // FIXME this function is case sensitive with xml tags and attributes, FD is not - $xml = new \SimpleXMLElement($this->configFilePath, 0, TRUE); - $locations = []; - foreach ($xml->main->location as $loc) { - $ref = $loc->referral[0]; - $location = [ - 'tls' => (isset($loc['ldapTLS']) && (strcasecmp((string)$loc['ldapTLS'], 'TRUE') === 0)), - 'uri' => (string)$ref['URI'], - 'base' => (string)($ref['base'] ?? $loc['base'] ?? '' ), - 'bind_dn' => (string)$ref['adminDn'], - 'bind_pwd' => (string)$ref['adminPassword'], - ]; - if ($location['base'] === '') { - if (preg_match('|^(.*)/([^/]+)$|', $location['uri'], $m)) { - /* Format from FD<1.3 */ - $location['uri'] = $m[1]; - $location['base'] = $m[2]; - } else { - throw new \Exception('"'.$location['uri'].'" does not contain any base!'); - } - } - if ($secret !== NULL) { - if (!class_exists('SecretBox')) { - /* Temporary hack waiting for core namespace/autoload refactor */ - require_once($this->vars['fd_home'].'/include/SecretBox.inc'); - } - $location['bind_pwd'] = SecretBox::decrypt($location['bind_pwd'], $secret); - } - $locations[(string)$loc['name']] = $location; - if ($this->verbose()) { - printf('Found location %s (%s)'."\n", (string)$loc['name'], $location['uri']); - } - } - if (count($locations) < 1) { - throw new \Exception('No location found in configuration file'); - } - - return $locations; - } } diff --git a/src/FusionDirectory/Cli/SecretBox.php b/src/FusionDirectory/Cli/SecretBox.php index d795c04a6fd365df997b3e56dec0decdf8484ec3..0f2608be7dedfe3750ca37da14165f62532d5556 100644 --- a/src/FusionDirectory/Cli/SecretBox.php +++ b/src/FusionDirectory/Cli/SecretBox.php @@ -21,6 +21,18 @@ namespace FusionDirectory\Cli; +use Exception; +use SodiumException; +use function random_bytes; +use function sodium_base642bin; +use function sodium_bin2base64; +use function sodium_crypto_secretbox; +use function sodium_crypto_secretbox_keygen; +use function sodium_crypto_secretbox_open; +use function sodium_memzero; +use function sodium_pad; +use function sodium_unpad; + class SecretBox { /** @@ -32,7 +44,7 @@ class SecretBox */ public static function generateSecretKey (): string { - return \sodium_crypto_secretbox_keygen(); + return sodium_crypto_secretbox_keygen(); } /** @@ -43,23 +55,23 @@ class SecretBox * @param string $message - message to encrypt * @param string $secret_key - encryption key * @param int $block_size - pad the message by $block_size byte chunks to conceal encrypted data size. must match between encrypt/decrypt! - * @see decrypt() * @see https://github.com/jedisct1/libsodium/issues/392 + * @see decrypt() */ public static function encrypt (string $message, string $secret_key, int $block_size = 1): string { /* Create a nonce for this operation. it will be stored and recovered in the message itself */ - $nonce = \random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); + $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); /* Pad to $block_size byte chunks (enforce 512 byte limit) */ - $padded_message = \sodium_pad($message, $block_size <= 512 ? $block_size : 512); + $padded_message = sodium_pad($message, min($block_size, 512)); /* Encrypt message and combine with nonce */ - $cipher = \sodium_bin2base64($nonce . \sodium_crypto_secretbox($padded_message, $nonce, $secret_key), SODIUM_BASE64_VARIANT_ORIGINAL); + $cipher = sodium_bin2base64($nonce . sodium_crypto_secretbox($padded_message, $nonce, $secret_key), SODIUM_BASE64_VARIANT_ORIGINAL); /* Cleanup */ - \sodium_memzero($message); - \sodium_memzero($secret_key); + sodium_memzero($message); + sodium_memzero($secret_key); return $cipher; } @@ -70,16 +82,18 @@ class SecretBox * Use libsodium to decrypt an encrypted string * * @param int $block_size - pad the message by $block_size byte chunks to conceal encrypted data size. must match between encrypt/decrypt! - * @see encrypt() + * @throws SodiumException + * @throws Exception * @see https://github.com/jedisct1/libsodium/issues/392 + * @see encrypt() */ public static function decrypt (string $encrypted, string $secret_key, int $block_size = 1): string { /* Unpack base64 message */ - $decoded = \sodium_base642bin($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL); + $decoded = sodium_base642bin($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL, ''); if (mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) { - throw new \Exception('The message was truncated'); + throw new Exception('The message was truncated'); } /* Pull nonce and ciphertext out of unpacked message */ @@ -87,18 +101,18 @@ class SecretBox $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, NULL, '8bit'); /* Decrypt it and account for extra padding from $block_size (enforce 512 byte limit) */ - $decrypted_padded_message = \sodium_crypto_secretbox_open($ciphertext, $nonce, $secret_key); + $decrypted_padded_message = sodium_crypto_secretbox_open($ciphertext, $nonce, $secret_key); /* Check for encryption failures */ if ($decrypted_padded_message === FALSE) { - throw new \Exception('The message was tampered with in transit'); + throw new Exception('The message was tampered with in transit'); } - $message = \sodium_unpad($decrypted_padded_message, $block_size <= 512 ? $block_size : 512); + $message = sodium_unpad($decrypted_padded_message, min($block_size, 512)); /* Cleanup */ - \sodium_memzero($encrypted); - \sodium_memzero($secret_key); + sodium_memzero($encrypted); + sodium_memzero($secret_key); return $message; } diff --git a/src/FusionDirectory/Cli/VarHandling.php b/src/FusionDirectory/Cli/VarHandling.php deleted file mode 100644 index d5fb5634e6dd39f9329926d46ce5d39c1183857a..0000000000000000000000000000000000000000 --- a/src/FusionDirectory/Cli/VarHandling.php +++ /dev/null @@ -1,129 +0,0 @@ -<?php -/* - This code is part of FusionDirectory (https://www.fusiondirectory.org/) - - Copyright (C) 2020-2021 FusionDirectory - - This program 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. - - This program 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 this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -namespace FusionDirectory\Cli; - -/** - * Trait for cli tools using a variable system through their options - */ -trait VarHandling -{ - /** - * Current values of variables - * @var array<string,string> - */ - protected $vars; - - /** - * Get the options related to variables handling - * @return array<string,array<string,string>> - */ - protected function getVarOptions (): array - { - return [ - 'list-vars' => [ - 'help' => 'List possible vars to give --set-var', - 'command' => 'cmdListVars', - ], - 'set-var:' => [ - 'help' => 'Set the variable value', - 'command' => 'cmdSetVar', - ], - ]; - } - - abstract protected function verbose (): bool; - - /** - * Read variables.inc file from FusionDirectory and update variables accordingly - */ - protected function readFusionDirectoryVariablesFile (): void - { - if ($this->verbose()) { - printf('Reading vars from %s'."\n", $this->vars['fd_home'].'/include/variables.inc'); - } - require_once($this->vars['fd_home'].'/include/variables.inc'); - - $fd_cache = LdapApplication::removeFinalSlash(CACHE_DIR); - $varsToSet = [ - 'fd_config_dir' => LdapApplication::removeFinalSlash(CONFIG_DIR), - 'config_file' => LdapApplication::removeFinalSlash(CONFIG_FILE), - 'fd_smarty_path' => LdapApplication::removeFinalSlash(SMARTY), - 'fd_spool_dir' => LdapApplication::removeFinalSlash(SPOOL_DIR), - 'fd_cache' => $fd_cache, - 'locale_cache_dir' => LdapApplication::removeFinalSlash(str_replace($fd_cache.'/', '', LOCALE_DIR)), - 'tmp_dir' => LdapApplication::removeFinalSlash(str_replace($fd_cache.'/', '', TEMP_DIR)), - 'template_dir' => LdapApplication::removeFinalSlash(str_replace($fd_cache.'/', '', CONFIG_TEMPLATE_DIR)), - 'fai_log_dir' => LdapApplication::removeFinalSlash(str_replace($fd_cache.'/', '', FAI_LOG_DIR)), - 'class_cache' => LdapApplication::removeFinalSlash(CLASS_CACHE), - ]; - foreach ($varsToSet as $var => $value) { - if (isset($this->vars[$var])) { - $this->vars[$var] = $value; - } - } - } - - /** - * Output variables and their current values - */ - protected function cmdListVars (): void - { - foreach ($this->vars as $key => $value) { - printf("%-20s [%s]\n", $key, $value); - } - } - - /** - * Set variables values - * @param array<string> $vars - */ - protected function cmdSetVar (array $vars): void - { - $varsToSet = []; - foreach ($vars as $var) { - if (preg_match('/^([^=]+)=(.+)$/', $var, $m)) { - if (isset($this->vars[strtolower($m[1])])) { - $varsToSet[strtolower($m[1])] = $m[2]; - } else { - throw new \Exception('Var "'.$m[1].'" does not exists. Use --list-vars to get the list of vars.'); - } - } else { - throw new \Exception('Incorrect syntax for --set-var: "'.$var.'". Use var=value'); - } - } - - if (isset($varsToSet['fd_home'])) { - if ($this->verbose()) { - printf('Setting var %s to "%s"'."\n", 'fd_home', LdapApplication::removeFinalSlash($varsToSet['fd_home'])); - } - $this->vars['fd_home'] = LdapApplication::removeFinalSlash($varsToSet['fd_home']); - } - $this->readFusionDirectoryVariablesFile(); - unset($varsToSet['fd_home']); - foreach ($varsToSet as $var => $value) { - if ($this->verbose()) { - printf('Setting var %s to "%s"'."\n", $var, $value); - } - $this->vars[$var] = $value; - } - } -} diff --git a/src/FusionDirectory/Rest/WebServiceCall.php b/src/FusionDirectory/Rest/WebServiceCall.php index 047023d522956209ff2ad4763e89c8fbd39b8a6b..fcc48e9881c558bae2bdbe908cfa90afae3834df 100644 --- a/src/FusionDirectory/Rest/WebServiceCall.php +++ b/src/FusionDirectory/Rest/WebServiceCall.php @@ -1,5 +1,6 @@ <?php +namespace FusionDirectory\Rest; class WebServiceCall { private $URL, $method, $token; //String