diff --git a/lib/resolveCache.php b/lib/resolveCache.php new file mode 100644 index 0000000..76c54fa --- /dev/null +++ b/lib/resolveCache.php @@ -0,0 +1,81 @@ +_cache_file = $filename; + if (file_exists($this->_cache_file)) { + $this->_cache = unserialize(file_get_contents($this->_cache_file)); + $this->_isDirty = false; + } else { + $this->_isDirty = true; + } + } + + function __destruct() { + if ($this->_isDirty) { + /*if (!is_writable($this->_cache_file)) { + log_error_and_throw("Could not write to file '".$this->_cache_file."' at Resolver::destruct"); + }*/ + if (!file_put_contents($this->_cache_file, serialize($this->_cache))) { + log_error_and_throw("Could not write to file '".$this->_cache_file."' at Resolver::destruct"); + } + } + } + + public function isDirty() { + return $this->_isDirty; + } + + public function check($filename) { + return array_key_exists($filename, $this->_cache); + } + + public function addFile($filename, $realpath) { + if ($this->check($filename)) + log_error("Duplicate file:$filename"); /* should we prevent this ? */ + $this->_cache[$filename] = $realpath; + $this->_isDirty =true; + } + + public function removeFile($filename) { + if ($this->check($filename)) { + unset($this->_cache[$filename]); + $this->_isDirty = true; + } + } + + public function getPath($filename) { + if ($this->check($filename)) { + return $this->_cache[$filename]; + } + return false; + } + + protected function printCache() { + print_r($this->_cache); + } +} + +/* +class sqliteCache extends resolveCache { + function __construct() { + } + function __destruct() { + } + public function addFile($filename, $realpath); + public function removeFile($filename); + public function check($filename); + public function getPath($filename); +} +*/ +?> \ No newline at end of file diff --git a/lib/resolver.php b/lib/resolver.php index 579b10b..ccf9621 100644 --- a/lib/resolver.php +++ b/lib/resolver.php @@ -1,6 +1,7 @@ config = $config; - //$this->logger = $logger; - if(file_exists($this->config['main']['cache_filename'])) { - $this->cache = unserialize(file_get_contents($config['main']['cache_filename'])); - } else { - $this->buildCleanCache(); + $this->cache = new fileCache($this->config['main']['cache_filename']); + if ($this->cache->isDirty()) { + $this->rebuildCache(); } } - function __destruct() { - // $this->printCache() - if ($this->isDirty) { - if (!file_put_contents($this->config['main']['cache_filename'], serialize($this->cache))) { - $this->log_error_and_throw("Could not write to file '".$this->config['cache_filename']."' at Resolver::destruct"); - } - } - } - function log_error_and_throw($message) { - global $logger; - $logger->log('LOG_ERROR', $message); - throw new Exception($message); - } - function log_debug($message) { - global $logger; - $logger->log('LOG_DEBUG', $message); - } - function searchForFile($filename) { + + public function searchForFile($filename) { foreach($this->config['subdirs'] as $key => $value) { if ($key === "firmware" || $key === "tftproot" ) { continue; } $path = realpath($this->config['main']['base_path'] . "/" . $value['path'] . "/$filename"); if (file_exists($path)) { - $this-> addFile($filename, $path); + $this->cache->addFile($filename, $path); return $path; } } - $this->log_error_and_throw("File '$filename' does not exist"); + log_error("File '$filename' does not exist"); + return ResolveResult::FileNotFound; } - function buildCleanCache() { + + public function rebuildCache() { + log_debug("Rebuilding Cache, standby..."); foreach($this->config['subdirs'] as $key =>$value) { if ($key === "tftproot") { continue; @@ -71,60 +66,62 @@ class Resolver { foreach ($iterator as $file) { if ($file->isFile()) { if ($value['strip']) { - $this->addFile($file->getFileName(), $file->getPathname()); + $this->cache->addFile($file->getFileName(), $file->getPathname()); } else { $subdir = basename(dirname($file->getPathname())); - $this->addFile('$subpath/'.$file->getFileName(), $file->getPathname()); + $this->cache->addFile('$subpath/'.$file->getFileName(), $file->getPathname()); } } } } $this->isDirty = TRUE; } - function addFile($requestpath, $truepath) { - //$this->logger->log('LOG_DEBUG', "Adding $requestpath"); - $this->log_debug("Adding $requestpath"); - $this->cache[$requestpath] = $truepath; - $this->isDirty =TRUE; - } - function removeFile($requestpath) { - $this->log_debug("Removing $hash"); - unset($this->cache[$requestpath]); - $this->isDirty = TRUE; - } - function validateRequest($request) { + + public function validateRequest($request) { /* make sure request does not startwith or contain: "/", "../" or "/./" */ /* make sure request only starts with filename or one of $config[$subdir]['locale'] or $config[$subdir]['wallpaper'] */ /* check uri/url decode */ - if (!is_string($request)) { - $this->log_error_and_throw("Request is not a string"); + if (!$request || empty($request)) { + log_error("Request is empty"); + return ResolveResult::EmptyRequest; } - $this->log_debug($request . ":" . escapeshellarg($request) . ":" . utf8_urldecode($request) . "\n"); + if (!is_string($request)) { + log_error("Request is not a string"); + return ResolveResult::RequestNotAString; + } + log_debug($request . ":" . escapeshellarg($request) . ":" . utf8_urldecode($request) . "\n"); $escaped_request = escapeshellarg(utf8_urldecode($request)); if ($escaped_request !== "'" . $request . "'") { - $this->log_error_and_throw("Request '$request' contains invalid characters"); + log_error("Request '$request' contains invalid characters"); + return ResolveResult::RequestContainsInvalidChar; } if (strstr($escaped_request, "..")) { - $this->log_error_and_throw("Request '$request' containst '..'"); + log_error("Request '$request' contains '..'"); + return ResolveResult::RequestContainsPathWalk; } + return ResolveResult::Ok; } - function resolve($request) /* canthrow */ { - $this->validateRequest($request); + + public function resolve($request) /* canthrow */ { $path = ''; - if (array_key_exists($request, $this->cache)) { - if ($path = $this->cache[$request]) { - if (!file_exists($path)) { - $this->removeFile($request); - $this->log_error_and_throw("File '$request' does not exist on FS"); - } - return $path; + $result = $this->validateRequest($request); + if ($result !== ResolveResult::Ok) { + return $result; + } + if (($path = $this->cache->getPath($request))) { + if (!file_exists($path)) { + $this->cache->removeFile($request); + log_error("File '$request' does not exist on FS"); + return ResolveResult::FileNotFound; } + return $path; } if ($this->searchForFile($request)) { - return $this->cache[$request]; + $path = $this->cache->getPath($request); } return $path; } + /* temporary */ function printCache() { print_r($this->cache); @@ -134,32 +131,36 @@ class Resolver { // Testing if(defined('STDIN') ) { $resolver = new Resolver($config); - $test_cases = Array( - Array('request' => 'jar70sccp.9-4-2ES26.sbn', 'expected' => '/tftpboot/firmware/7970/jar70sccp.9-4-2ES26.sbn', 'throws' => FALSE), - Array('request' => 'Russian_Russian_Federation/be-sccp.jar', 'expected' => '/tftpboot/locales/languages/Russian_Russian_Federation/be-sccp.jar', 'throws' => FALSE), - Array('request' => 'Spain/g3-tones.xml', 'expected' => '/tftpboot/locales/countries/Spain/g3-tones.xml', 'throws' => FALSE), - Array('request' => '320x196x4/Chan-SCCP-b.png', 'expected' => '/tftpboot/wallpapers/320x196x4/Chan-SCCP-b.png', 'throws' => FALSE), - Array('request' => 'XMLDefault.cnf.xml', 'expected' => '/tftpboot/settings/bak/XMLDefault.cnf.xml', 'throws' => FALSE), - Array('request' => '../XMLDefault.cnf.xml', 'expected' => '', 'throws' => TRUE), - Array('request' => 'XMLDefault.cnf.xml/../../text.xml', 'expected' => '', 'throws' => TRUE), - + Array('request' => 'jar70sccp.9-4-2ES26.sbn', 'expected' => '/tftpboot/firmware/7970/jar70sccp.9-4-2ES26.sbn'), + Array('request' => 'Russian_Russian_Federation/be-sccp.jar', 'expected' => '/tftpboot/locales/languages/Russian_Russian_Federation/be-sccp.jar'), + Array('request' => 'Spain/g3-tones.xml', 'expected' => '/tftpboot/locales/countries/Spain/g3-tones.xml'), + Array('request' => '320x196x4/Chan-SCCP-b.png', 'expected' => '/tftpboot/wallpapers/320x196x4/Chan-SCCP-b.png'), + Array('request' => 'XMLDefault.cnf.xml', 'expected' => '/tftpboot/settings/bak/XMLDefault.cnf.xml'), + Array('request' => '../XMLDefault.cnf.xml', 'expected' => ResolveResult::RequestContainsPathWalk), + Array('request' => 'XMLDefault.cnf.xml/../../text.xml', 'expected' => ResolveResult::RequestContainsPathWalk), ); foreach($test_cases as $test) { try { $result = $resolver->resolve($test['request']); - if ($result !== $base_path . $test['expected']) { - print("Error: expected result does not match what we got\n"); - print("request:'".$test['request']."', result:'" . $base_path . $test['expected'] . "'\n"); + if (is_string($result)) { + if ($result === $base_path . $test['expected']) { + print("'" . $test['request'] . "' => '" . $result . "'\n"); + continue; + } } else { - print("'" . $test['request'] . "' => '" . $result . "'\n"); + if ($result === $test['expected']) { + print("'" . $test['request'] . "' => '" . $result . "'\n"); + continue; + } } + print("Error: expected result does not match what we got\n"); + print("request:'".$test['request']."'\n"); + print("expected:'" . $base_path . $test['expected'] . "'\n"); + print("result:'" . $result . "'\n\n"); } catch (Exception $e) { - if (!$test['throws']) { - print("Error: request was expected to throw: $e\n"); - } else { - print("'" . $test['request'] . "' => throws error as expected\n"); - } + print("'" . $test['request'] . "' => throws error as expected\n"); + print("Exception: " . $e->getMessage() . "\n"); } } unset($resolver); diff --git a/lib/utils.php b/lib/utils.php index e884a95..2b7ce85 100644 --- a/lib/utils.php +++ b/lib/utils.php @@ -50,4 +50,19 @@ function parse_ini_file_multi($file, $process_sections = false, $scanner_mode = } return $data; } + +function log_debug($message) { + global $logger; + $logger->log('LOG_DEBUG', $message); +} + +function log_error($message) { + global $logger; + $logger->log('LOG_ERROR', $message); +} + +function log_error_and_throw($message) { + log_error($message); + throw new Exception($message); +} ?> diff --git a/tftp_provisioner.php b/tftp_provisioner.php index 8d21f94..a33f692 100755 --- a/tftp_provisioner.php +++ b/tftp_provisioner.php @@ -8,7 +8,7 @@ class TFTPProvisioner extends TFTPServer { private $_debug; private $_resolver; - private $_filename; + private $_settings_path; function __construct($server_url, $config, $logger = NULL, $debug = false) { @@ -20,42 +20,32 @@ class TFTPProvisioner extends TFTPServer $this->_debug = $debug; $this->max_put_size = 60000000; $this->_resolver = new Resolver($config); - } - - public function exists($peer, $req_filename) - { - if (($this->_filename = $this->_resolver->resolve($req_filename))) { - return file_exists($this->_filename); - } - return false; - } - - public function readable($peer, $req_filename) - { - return is_readable($this->_filename); - } - - public function get($peer, $req_filename, $mode) - { - return file_get_contents($this->_filename); + $this->_settings_path = $this->_config['main']['base_path'] . DIRECTORY_SEPARATOR + . $this->_config['subdirs']['settings']['path'] . DIRECTORY_SEPARATOR; } public function writable($peer, $req_filename) { - // check $req_filename starts with 'settings/' (SPA phones can write to tftpboot) - $settings_path = $this->_config['main']['base_path'] . DIRECTORY_SEPARATOR - . $this->_config['subdirs']['settings']['path'] . DIRECTORY_SEPARATOR; - $filename = $settings_path . basename($req_filename); - if (is_writable($filename) || (!file_exists($filename) && is_writable($settings_path))) { - $this->_filename = $filename; - return true; + $filename = $this->_settings_path . basename($req_filename); + if (file_exists($filename)) { + return is_writable($filename); } + return is_writable($this->_settings_path); + } + + public function get($peer, $req_filename, $mode) + { + $filename = $this->_resolver->resolve($req_filename); + if (file_exists($filename) && is_readable($filename)) + return file_get_contents($filename); return false; } - public function put($peer, $filename, $mode, $content) + public function put($peer, $req_filename, $mode, $content) { - return file_put_contents($this->_filename, $content); + // (SPA phones can write to tftpboot -> redirect PUT request to 'settings' folder) + $filename = $this->_settings_path . basename($req_filename); + return file_put_contents($filename, $content); } /*