/** * Create a new PHPSession object using the provided options (if any) * * @param array $options An optional array of ini options to set * * @throws ConfigurationError * @see http://php.net/manual/en/session.configuration.php */ public function __construct(array $options = null) { if ($options !== null) { $options = array_merge(self::$defaultCookieOptions, $options); } else { $options = self::$defaultCookieOptions; } if (array_key_exists('test_session_name', $options)) { $this->sessionName = $options['test_session_name']; unset($options['test_session_name']); } foreach ($options as $sessionVar => $value) { if (ini_set("session." . $sessionVar, $value) === false) { Logger::warning('Could not set php.ini setting %s = %s. This might affect your sessions behaviour.', $sessionVar, $value); } } $sessionSavePath = session_save_path() ?: sys_get_temp_dir(); if (session_module_name() === 'files' && !is_writable($sessionSavePath)) { throw new ConfigurationError("Can't save session, path '{$sessionSavePath}' is not writable."); } if ($this->exists()) { // We do not want to start a new session here if there is not any $this->read(); } }
/** * Register all custom user backends from all loaded modules */ protected static function registerCustomUserBackends() { if (static::$customBackends !== null) { return; } static::$customBackends = array(); $providedBy = array(); foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { foreach ($module->getUserBackends() as $identifier => $className) { if (array_key_exists($identifier, $providedBy)) { Logger::warning('Cannot register user backend of type "%s" provided by module "%s".' . ' The type is already provided by module "%s"', $identifier, $module->getName(), $providedBy[$identifier]); } elseif (in_array($identifier, static::$defaultBackends)) { Logger::warning('Cannot register user backend of type "%s" provided by module "%s".' . ' The type is a default type provided by Icinga Web 2', $identifier, $module->getName()); } else { $providedBy[$identifier] = $module->getName(); static::$customBackends[$identifier] = $className; } } } }
/** * Run the given LDAP query and return the resulting entries * * This utilizes paged search requests as defined in RFC 2696. * * @param LdapQuery $query The query to fetch results with * @param array $fields Request these attributes instead of the ones registered in the given query * @param int $pageSize The maximum page size, defaults to self::PAGE_SIZE * * @return array * * @throws LdapException In case an error occured while fetching the results */ protected function runPagedQuery(LdapQuery $query, array $fields = null, $pageSize = null) { if ($pageSize === null) { $pageSize = static::PAGE_SIZE; } $limit = $query->getLimit(); $offset = $query->hasOffset() ? $query->getOffset() : 0; if ($fields === null) { $fields = $query->getColumns(); } $ds = $this->getConnection(); $serverSorting = false; //$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID); if (!$serverSorting && $query->hasOrder() && !empty($fields)) { foreach ($query->getOrder() as $rule) { if (!in_array($rule[0], $fields, true)) { $fields[] = $rule[0]; } } } $unfoldAttribute = $query->getUnfoldAttribute(); if ($unfoldAttribute) { foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) { $fieldKey = array_search($filterColumn, $fields, true); if ($fieldKey === false || is_string($fieldKey)) { $fields[] = $filterColumn; } } } $count = 0; $cookie = ''; $entries = array(); do { // Do not request the pagination control as a critical extension, as we want the // server to return results even if the paged search request cannot be satisfied ldap_control_paged_result($ds, $pageSize, false, $cookie); if ($serverSorting && $query->hasOrder()) { ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array(array('oid' => LdapCapabilities::LDAP_SERVER_SORT_OID, 'value' => $this->encodeSortRules($query->getOrder())))); } $results = $this->ldapSearch($query, array_values($fields), 0, $serverSorting && $limit ? $offset + $limit : 0); if ($results === false) { if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) { break; } throw new LdapException('LDAP query "%s" (base %s) failed. Error: %s', (string) $query, $query->getBase() ?: $this->getDn(), ldap_error($ds)); } elseif (ldap_count_entries($ds, $results) === 0) { if (in_array(ldap_errno($ds), array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED), true)) { Logger::warning('Unable to request more than %u results. Does the server allow paged search requests? (%s)', $count, ldap_error($ds)); } break; } $entry = ldap_first_entry($ds, $results); do { if ($unfoldAttribute) { $rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute); if (is_array($rows)) { // TODO: Register the DN the same way as a section name in the ArrayDatasource! foreach ($rows as $row) { if ($query->getFilter()->matches($row)) { $count += 1; if (!$serverSorting || $offset === 0 || $offset < $count) { $entries[] = $row; } if ($serverSorting && $limit > 0 && $limit === count($entries)) { break; } } } } else { $count += 1; if (!$serverSorting || $offset === 0 || $offset < $count) { $entries[ldap_get_dn($ds, $entry)] = $rows; } } } else { $count += 1; if (!$serverSorting || $offset === 0 || $offset < $count) { $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields); } } } while ((!$serverSorting || $limit === 0 || $limit !== count($entries)) && ($entry = ldap_next_entry($ds, $entry))); if (false === @ldap_control_paged_result_response($ds, $results, $cookie)) { // If the page size is greater than or equal to the sizeLimit value, the server should ignore the // control as the request can be satisfied in a single page: https://www.ietf.org/rfc/rfc2696.txt // This applies no matter whether paged search requests are permitted or not. You're done once you // got everything you were out for. if ($serverSorting && count($entries) !== $limit) { // The server does not support pagination, but still returned a response by ignoring the // pagedResultsControl. We output a warning to indicate that the pagination control was ignored. Logger::warning('Unable to request paged LDAP results. Does the server allow paged search requests?'); } } ldap_free_result($results); } while ($cookie && (!$serverSorting || $limit === 0 || count($entries) < $limit)); if ($cookie) { // A sequence of paged search requests is abandoned by the client sending a search request containing a // pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by // the server: https://www.ietf.org/rfc/rfc2696.txt ldap_control_paged_result($ds, 0, false, $cookie); // Returns no entries, due to the page size ldap_search($ds, $query->getBase() ?: $this->getDn(), (string) $query); } if (!$serverSorting && $query->hasOrder()) { uasort($entries, array($query, 'compare')); if ($limit && $count > $limit) { $entries = array_splice($entries, $query->hasOffset() ? $query->getOffset() : 0, $limit); } } return $entries; }
/** * Search for deleted properties and use the editor to delete these entries * * @param Config $oldconfig The config representing the state before the change * @param Config $newconfig The config representing the state after the change * @param Document $doc * * @throws ProgrammingError */ protected function diffPropertyDeletions(Config $oldconfig, Config $newconfig, Document $doc) { // Iterate over all properties in the old configuration file and remove those that don't // exist in the new config foreach ($oldconfig->toArray() as $section => $directives) { if (!is_array($directives)) { Logger::warning('Section-less property ' . (string) $directives . ' was ignored.'); continue; } if ($newconfig->hasSection($section)) { $newSection = $newconfig->getSection($section); $oldDomSection = $doc->getSection($section); foreach ($directives as $key => $value) { if ($value instanceof ConfigObject) { throw new ProgrammingError('Cannot diff recursive configs'); } if (null === $newSection->get($key) && $oldDomSection->hasDirective($key)) { $oldDomSection->removeDirective($key); } } } else { $doc->removeSection($section); } } }
/** * Register module * * @return bool */ public function register() { if ($this->registered) { return true; } $this->registerAutoloader(); try { $this->launchRunScript(); } catch (Exception $e) { Logger::warning('Launching the run script %s for module %s failed with the following exception: %s', $this->runScript, $this->name, $e->getMessage()); return false; } $this->registerWebIntegration(); $this->registered = true; return true; }
/** * Detect installed modules from every path provided in modulePaths * * @param array $availableDirs Installed modules location * * @return $this */ public function detectInstalledModules(array $availableDirs = null) { $modulePaths = $availableDirs !== null ? $availableDirs : $this->modulePaths; foreach ($modulePaths as $basedir) { $canonical = realpath($basedir); if ($canonical === false) { Logger::warning('Module path "%s" does not exist', $basedir); continue; } if (!is_dir($canonical)) { Logger::error('Module path "%s" is not a directory', $canonical); continue; } if (!is_readable($canonical)) { Logger::error('Module path "%s" is not readable', $canonical); continue; } if (($dh = opendir($canonical)) !== false) { while (($file = readdir($dh)) !== false) { if ($file[0] === '.') { continue; } if (is_dir($canonical . '/' . $file)) { if (!array_key_exists($file, $this->installedBaseDirs)) { $this->installedBaseDirs[$file] = $canonical . '/' . $file; } else { Logger::debug('Module "%s" already exists in installation path "%s" and is ignored.', $canonical . '/' . $file, $this->installedBaseDirs[$file]); } } } closedir($dh); } } ksort($this->installedBaseDirs); return $this; }
/** * Read the ini file contained in a string and return a mutable DOM that can be used * to change the content of an INI file. * * @param $str A string containing the whole ini file * * @return Document The mutable DOM object. * @throws ConfigurationError In case the file is not parseable */ public static function parseIni($str) { $doc = new Document(); $sec = null; $dir = null; $coms = array(); $state = self::LINE_START; $escaping = null; $token = ''; $line = 0; for ($i = 0; $i < strlen($str); $i++) { $s = $str[$i]; switch ($state) { case self::LINE_START: if (ctype_space($s)) { continue; } switch ($s) { case '[': $state = self::SECTION; break; case ';': $state = self::COMMENT; break; default: $state = self::DIRECTIVE_KEY; $token = $s; break; } break; case self::ESCAPE: $token .= $s; $state = $escaping; $escaping = null; break; case self::SECTION: if ($s === "\n") { self::throwParseError('Unterminated SECTION', $line); } elseif ($s === '\\') { $state = self::ESCAPE; $escaping = self::SECTION; } elseif ($s !== ']') { $token .= $s; } else { $sec = new Section($token); $sec->setCommentsPre($coms); $doc->addSection($sec); $dir = null; $coms = array(); $state = self::LINE_END; $token = ''; } break; case self::DIRECTIVE_KEY: if ($s !== '=') { $token .= $s; } else { $dir = new Directive($token); $dir->setCommentsPre($coms); if (isset($sec)) { $sec->addDirective($dir); } else { Logger::warning(sprintf('Ini parser warning: section-less directive "%s" ignored. (l. %d)', $token, $line)); } $coms = array(); $state = self::DIRECTIVE_VALUE_START; $token = ''; } break; case self::DIRECTIVE_VALUE_START: if (ctype_space($s)) { continue; } elseif ($s === '"') { $state = self::DIRECTIVE_VALUE_QUOTED; } else { $state = self::DIRECTIVE_VALUE; $token = $s; } break; case self::DIRECTIVE_VALUE: /* Escaping non-quoted values is not supported by php_parse_ini, it might be reasonable to include in case we are switching completely our own parser implementation */ if ($s === "\n" || $s === ";") { $dir->setValue($token); $token = ''; if ($s === "\n") { $state = self::LINE_START; $line++; } elseif ($s === ';') { $state = self::COMMENT; } } else { $token .= $s; } break; case self::DIRECTIVE_VALUE_QUOTED: if ($s === '\\') { $state = self::ESCAPE; $escaping = self::DIRECTIVE_VALUE_QUOTED; } elseif ($s !== '"') { $token .= $s; } else { $dir->setValue($token); $token = ''; $state = self::LINE_END; } break; case self::COMMENT: case self::COMMENT_END: if ($s !== "\n") { $token .= $s; } else { $com = new Comment(); $com->setContent($token); $token = ''; // Comments at the line end belong to the current line's directive or section. Comments // on empty lines belong to the next directive that shows up. if ($state === self::COMMENT_END) { if (isset($dir)) { $dir->setCommentPost($com); } else { $sec->setCommentPost($com); } } else { $coms[] = $com; } $state = self::LINE_START; $line++; } break; case self::LINE_END: if ($s === "\n") { $state = self::LINE_START; $line++; } elseif ($s === ';') { $state = self::COMMENT_END; } break; } } // process the last token switch ($state) { case self::COMMENT: case self::COMMENT_END: $com = new Comment(); $com->setContent($token); if ($state === self::COMMENT_END) { if (isset($dir)) { $dir->setCommentPost($com); } else { $sec->setCommentPost($com); } } else { $coms[] = $com; } break; case self::DIRECTIVE_VALUE: $dir->setValue($token); $sec->addDirective($dir); break; case self::ESCAPE: case self::DIRECTIVE_VALUE_QUOTED: case self::DIRECTIVE_KEY: case self::SECTION: self::throwParseError('File ended in unterminated state ' . $state, $line); } if (!empty($coms)) { $doc->setCommentsDangling($coms); } return $doc; }
/** * Add a notification message * * @param string $message * @param string $type */ protected function addMessage($message, $type = self::INFO) { if ($this->isCli) { $msg = sprintf('[%s] %s', $type, $message); switch ($type) { case self::INFO: case self::SUCCESS: Logger::info($msg); break; case self::ERROR: Logger::error($msg); break; case self::WARNING: Logger::warning($msg); break; } } else { $this->messages[] = (object) array('type' => $type, 'message' => $message); } }
/** * Prepare and establish a connection with the LDAP server * * @return resource A positive LDAP link identifier * * @throws LdapException In case the connection is not possible */ protected function prepareNewConnection() { if ($this->encryption === static::STARTTLS || $this->encryption === static::LDAPS) { $this->prepareTlsEnvironment(); } $hostname = $this->hostname; if ($this->encryption === static::LDAPS) { $hostname = 'ldaps://' . $hostname; } $ds = ldap_connect($hostname, $this->port); try { $this->capabilities = $this->discoverCapabilities($ds); $this->discoverySuccess = true; } catch (LdapException $e) { Logger::debug($e); Logger::warning('LADP discovery failed, assuming default LDAP capabilities.'); $this->capabilities = new Capability(); // create empty default capabilities $this->discoverySuccess = false; } if ($this->encryption === static::STARTTLS) { $force_tls = false; if ($this->capabilities->hasStartTls()) { if (@ldap_start_tls($ds)) { Logger::debug('LDAP STARTTLS succeeded'); } else { Logger::error('LDAP STARTTLS failed: %s', ldap_error($ds)); throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds)); } } elseif ($force_tls) { throw new LdapException('STARTTLS is required but not announced by %s', $this->hostname); } else { Logger::warning('LDAP STARTTLS enabled but not announced'); } } // ldap_rename requires LDAPv3: if ($this->capabilities->hasLdapV3()) { if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) { throw new LdapException('LDAPv3 is required'); } } else { // TODO: remove this -> FORCING v3 for now ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); Logger::warning('No LDAPv3 support detected'); } // Not setting this results in "Operations error" on AD when using the whole domain as search base ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); // ldap_set_option($ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER); return $ds; }