Initial checkin of resolver.php

- use \\ instead of just \ in FileName
- Use file_put_contents instead of open/write
- Use file_exist instead of stat
- Added /lib directory
- Moved /tftpboot/index.cfg -> /config.ini
- Moved /tftpboot/resolver.php -> /lib/resolver.php
- Added /lib/config.php
  - include /lib/config.php in resolver.php and index.php
- Changed $config array
- Remove print_r($config['main']['base_path']) from config.php
- Add isValidRequest() function
- Use Boolean in tree_base data
- Simplify config['subdirs'] substitution
- Add lib/utils.php file
- Added simple shell/utf/html escape checking
- Added a collection of test cases (we need some more escape checking ones)
- Added lib/logger.php (copied from tftpserver.php, so that it can be reused for that).
- Clarify config.ini logformat
- Update logger implementation
- Replaced index.php with version that uses lib/resolver.php
- Replaced ../etc/nginx/sites-available/tftpboot Example file
This commit is contained in:
Diederik de Groot
2020-02-15 19:30:00 +01:00
parent d5bdcd4c30
commit a4ebaee776
9 changed files with 454 additions and 570 deletions

17
config.ini Normal file
View File

@@ -0,0 +1,17 @@
[main]
debug = on ; The output in the browser window for more information
cache_filename = "/tmp/provision_sccp_resolver.cache"
default_language = English_United_States
log_type = SYSLOG ; SYSLOG|STDERR|STDOUT|NULL|FILE
log_level = LOG_INFO ; LOG_EMERG|LOG_ALERT|LOG_CRIT|LOG_ERR|LOG_WARNING|LOG_NOTICE|LOG_INFO|LOG_DEBUG
;log_filename = provision.log ; only in case of log_type = FILE
[subdirs]
tftproot = tftpboot
firmware = firmware
settings = settings
wallpapers = wallpapers
ringtones = ringtones
locales = locales
countries = countries
languages = languages

View File

@@ -1,260 +1,29 @@
server {
listen 6970;
server_name tftp.servername.org;
#root /tftpboot;
root /data/development/sccp/sources/tftp/tftpboot;
index XMLDefault.cnf.xml;
listen 6970;
server_name tftp.servername.org;
root /data/development/sccp/sources/tftp/tftpboot;
# Normal Logging
#access_log /var/log/nginx/tftp.access.log;
#error_log /var/log/nginx/tftp.error.log;
# Normal Logging
access_log /var/log/nginx/tftp1.access.log;
error_log /var/log/nginx/tftp1.error.log;
# Debug Rewrite Rules
rewrite_log on;
access_log /var/log/nginx/tftp.access.log;
error_log /var/log/nginx/tftp.error.log notice;
index index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location / {
# settings
rewrite ^/((.*)(\.cnf\.xml|\.tlv|authorized_keys)(\.enc)?(\.sgn)?)$ /settings/$1 last;
location / {
try_files $uri $uri/ /index.php$is_args$args;
rewrite ^/(.*)$ /index.php?filename=$1 last;
}
# firmware
rewrite ^/((.*).(bin|bin.|loads|LOADS|sbn|SBN|sb2|sbin|zz|zup)(\.sgn)?)$ /firmware/$1 last;
# locales
rewrite ^/(.*)/((.*)-tones\.xml(\.sgn)?)$ /locales/countries/$1/$2 last;
rewrite ^/(.*)/((.*)(-dictionary(.*)\.xml)(\.sgn)?)$ /locales/languages/$1/$2 last;
rewrite ^/(.*)/((.*)(-kate(.*)\.xml)(\.sgn)?)$ /locales/languages/$1/$2 last;
rewrite ^/(.*)/((.*)(-sccp\.jar)(\.sgn)?)$ /locales/languages/$1/$2 last;
rewrite ^/(.*)/((.*)(-font\.xml)(\.sgn)?)$ /locales/languages/$1/$2 last;
rewrite ^/(.*)/(CIPC_Locale\.(.*))(\.sgn)?)$ /locales/languages/$1/$2 last;
rewrite ^/(.*)/((.*)(-tones.xml)(\.sgn)?)$ /locales/countries/$1/$2 last;
# ringtones
rewrite ^/([D|d]istinctive)?([R|r]ing[L|l]ist)\.(xml|XML)(\.sgn)?$ /ringtones/ringlist.xml last;
#rewrite ^/((.*)(\.raw|\.pcm)(\.sgn))?$ /ringtones/$1 last;
# wallpapers
rewrite ^/Desktops/(.*)/((.*)(\.sgn)?)$ /wallpapers/$1/$2 last;
try_files $uri $uri/ =404;
autoindex off;
}
# settings
location /settings {
try_files $uri $uri/ =404;
autoindex off;
}
# firmware
location /firmware {
# 6901 firmware (java-based)
rewrite ^/firmware/(APP6901SCCP.+\.sgn)$ /firmware/6901/$1 last;
rewrite ^/firmware/(KNL6901SCCP.+\.sgn)$ /firmware/6901/$1 last;
rewrite ^/firmware/(SCCP6901.+\.loads)$ /firmware/6901/$1 last;
# 6911 firmware (java-based)
rewrite ^/firmware/(APP6911SCCP.+\.sgn)$ /firmware/6911/$1 last;
rewrite ^/firmware/(BFS6911SCCP.+\.sgn)$ /firmware/6911/$1 last;
rewrite ^/firmware/(KNL6911SCCP.+\.sgn)$ /firmware/6911/$1 last;
rewrite ^/firmware/(SCCP6911.+\.loads)$ /firmware/6911/$1 last;
# 6921 firmware (java-based)
rewrite ^/firmware/(BOOT69xx.+\.sgn)$ /firmware/6921/$1 last;
rewrite ^/firmware/(DSP69xx.+\.sgn)$ /firmware/6921/$1 last;
rewrite ^/firmware/(SCCP69xx.+\.loads)$ /firmware/6921/$1 last;
rewrite ^/firmware/(SCCP69xx.+\.sgn)$ /firmware/6921/$1 last;
# 6945 firmware (java-based)
rewrite ^/firmware/(SCCP6945.+\.sgn)$ /firmware/6945/$1 last;
rewrite ^/firmware/(SCCP6945.+\.loads)$ /firmware/6945/$1 last;
# 69xx firmware (java-based)
rewrite ^/firmware/(BOOT69xx.+\.sgn)$ /firmware/69xx/$1 last;
rewrite ^/firmware/(DSP69xx.+\.sgn)$ /firmware/69xx/$1 last;
rewrite ^/firmware/(SCCP69xx.+\.loads)$ /firmware/69xx/$1 last;
rewrite ^/firmware/(SCCP69xx.+\.sgn)$ /firmware/69xx/$1 last;
# 7902 firmware (ancient)
rewrite ^/firmware/(CP7902080002SCCP060817A.sbin)$ /firmware/7902/$1 last;
# 7905 firmware (ancient)
rewrite ^/firmware/(CP7905080003SCCP070409A.sbin)$ /firmware/7905/$1 last;
rewrite ^/firmware/(CP7905080003SCCP070409A.zup)$ /firmware/7905/$1 last;
# 7906_7911 firmware (java-based)
rewrite ^/firmware/(apps11.+\.sbn)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(cnu11.+\.sbn)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(cvm11sccp.+\.sbn)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(dsp11.+\.sbn)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(jar11sccp.+\.sbn)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(SCCP11.+\.loads)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(term06.+\.loads)$ /firmware/7906_7911/$1 last;
rewrite ^/firmware/(term11.+\.loads)$ /firmware/7906_7911/$1 last;
# 7910 firmware (ancient)
rewrite ^/firmware/(P00405000700.bin)$ /firmware/7910/$1 last;
rewrite ^/firmware/(P00405000700.sbn)$ /firmware/7910/$1 last;
# 7912 firmware (ancient)
rewrite ^/firmware/(CP7912080004SCCP080108A.sbin)$ /firmware/7912/$1 last;
# 7915 firmware (ancient)
rewrite ^/firmware/(B015-1-0-4.SBN)$ /firmware/7915/$1 last;
# 7916 firmware (ancient)
rewrite ^/firmware/(B016-1-0-4.SBN)$ /firmware/7916/$1 last;
# 7920 firmware (ancient)
rewrite ^/firmware/(cmterm_7920.4.0-03-02.bin)$ /firmware/7920/$1 last;
# 7921_7925 firmware (ancient)
rewrite ^/firmware/(APPS-1.4.3.4.SBN)$ /firmware/7921_7925/$1 last;
rewrite ^/firmware/(CP7921G-1.4.3.4.LOADS)$ /firmware/7921_7925/$1 last;
rewrite ^/firmware/(GUI-1.4.3.4.SBN)$ /firmware/7921_7925/$1 last;
rewrite ^/firmware/(SYS-1.4.3.4.SBN)$ /firmware/7921_7925/$1 last;
rewrite ^/firmware/(TNUX-1.4.3.4.SBN)$ /firmware/7921_7925/$1 last;
rewrite ^/firmware/(TNUXR-1.4.3.4.SBN)$ /firmware/7921_7925/$1 last;
rewrite ^/firmware/(WLAN-1.4.3.4.SBN)$ /firmware/7921_7925/$1 last;
# 7926 firmware (ancient)
rewrite ^/firmware/(APPSS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(CP7926G-1.4.1SR1.LOADS)$ /firmware/7926/$1 last;
rewrite ^/firmware/(EA15FW-BF3-220.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(GUIS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(JSYSS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(JUIS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(SYSS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(TNUXRS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(TNUXS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
rewrite ^/firmware/(WLANS-1.4.1SR1.SBN)$ /firmware/7926/$1 last;
# 7931 firmware (java-based)
rewrite ^/firmware/(apps31.+\.sbn)$ /firmware/7931/$1 last;
rewrite ^/firmware/(cnu31.+\.sbn)$ /firmware/7931/$1 last;
rewrite ^/firmware/(cvm31sccp.+\.sbn)$ /firmware/7931/$1 last;
rewrite ^/firmware/(dsp31.+\.sbn)$ /firmware/7931/$1 last;
rewrite ^/firmware/(jar31sccp.+\.sbn)$ /firmware/7931/$1 last;
rewrite ^/firmware/(SCCP31.+\.loads)$ /firmware/7931/$1 last;
rewrite ^/firmware/(term31.+\.loads)$ /firmware/7931/$1 last;
# 7935 firmware (ancient)
rewrite ^/firmware/(P00503021900.bin)$ /firmware/7935/$1 last;
# 7936 firmware (ancient)
rewrite ^/firmware/(cmterm_7936.3-3-21-0.bin)$ /firmware/7936/$1 last;
# 7937 firmware (ancient)
rewrite ^/firmware/(apps37sccp.1-4-5-7.bin)$ /firmware/7937/$1 last;
# 7940_7960 firmware (ancient)
rewrite ^/firmware/(P0030801SR.+\.bin)$ /firmware/7940_7960/$1 last;
rewrite ^/firmware/(P0030801SR.+\.loads)$ /firmware/7940_7960/$1 last;
rewrite ^/firmware/(P0030801SR.+\.sb2)$ /firmware/7940_7960/$1 last;
rewrite ^/firmware/(P0030801SR.+\.sbn)$ /firmware/7940_7960/$1 last;
# 7941_7961 firmware (java-based)
rewrite ^/firmware/(apps41.+\.sbn)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(cnu41.+\.sbn)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(cvm41sccp.+\.sbn)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(dsp41.+\.sbn)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(jar41sccp.+\.sbn)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(SCCP41.+\.loads)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(term41.+\.loads)$ /firmware/7941_7961/$1 last;
rewrite ^/firmware/(term61.+\.loads)$ /firmware/7941_7961/$1 last;
# 7942_7962 firmware (java-based)
rewrite ^/firmware/(apps42.+\.sbn)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(cnu42.+\.sbn)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(cvm42sccp.+\.sbn)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(dsp42.+\.sbn)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(jar42sccp.+\.sbn)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(SCCP42.+\.loads)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(term42.+\.loads)$ /firmware/7942_7962/$1 last;
rewrite ^/firmware/(term62.+\.loads)$ /firmware/7942_7962/$1 last;
# 7945_7965 firmware (java-based)
rewrite ^/firmware/(apps45.+\.sbn)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(cnu45.+\.sbn)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(cvm45sccp.+\.sbn)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(dsp45.+\.sbn)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(jar45sccp.+\.sbn)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(SCCP45.+\.loads)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(term45.+\.loads)$ /firmware/7945_7965/$1 last;
rewrite ^/firmware/(term65.+\.loads)$ /firmware/7945_7965/$1 last;
# 7970_7971 firmware (java-based)
rewrite ^/firmware/(apps70.+\.sbn)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(cnu70.+\.sbn)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(cvm70sccp.+\.sbn)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(dsp70.+\.sbn)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(jar70sccp.+\.sbn)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(SCCP70.+\.loads)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(term70.+\.loads)$ /firmware/7970_7971/$1 last;
rewrite ^/firmware/(term71.+\.loads)$ /firmware/7970_7971/$1 last;
# 7975 firmware (java-based)
rewrite ^/firmware/(apps75.+\.sbn)$ /firmware/7975/$1 last;
rewrite ^/firmware/(cnu75.+\.sbn)$ /firmware/7975/$1 last;
rewrite ^/firmware/(cvm75sccp.+\.sbn)$ /firmware/7975/$1 last;
rewrite ^/firmware/(dsp75.+\.sbn)$ /firmware/7975/$1 last;
rewrite ^/firmware/(jar75sccp.+\.sbn)$ /firmware/7975/$1 last;
rewrite ^/firmware/(SCCP75.+\.loads)$ /firmware/7975/$1 last;
rewrite ^/firmware/(term75.+\.loads)$ /firmware/7975/$1 last;
# 7985 firmware (ancient)
rewrite ^/firmware/(cmterm_7985.4-1-7-0.bin)$ /firmware/7985/$1 last;
# 894x firmware (java-based)
rewrite ^/firmware/(BOOT894x.+\.sgn)$ /firmware/894x/$1 last;
rewrite ^/firmware/(SCCP894x.+\.sgn)$ /firmware/894x/$1 last;
rewrite ^/firmware/(SCCP894x.+\.loads)$ /firmware/894x/$1 last;
# ata186 firmware (ancient)
rewrite ^/firmware/(ATA030204SCCP090202A.zup)$ /firmware/ata186/$1 last;
# ata188 firmware (ancient)
rewrite ^/firmware/(ATA030204SCCP090202A.zup)$ /firmware/ata188/$1 last;
# SPA50x
rewrite ^/firmware/(spa50x.+\.bin)$ /firmware/spa50x/$1 last;
rewrite ^/firmware/(spa51x.+\.bin)$ /firmware/spa51x/$1 last;
rewrite ^/firmware/(spa515.+\.bin)$ /firmware/spa525/$1 last;
rewrite ^/firmware/(spa941.+\.bin)$ /firmware/spa941/$1 last;
try_files $uri $uri/ =404;
autoindex off;
}
# locales
location /locales {
try_files $uri $uri/ =404;
autoindex off;
}
# ringtones
location /ringtones {
try_files $uri $uri/ =404;
autoindex off;
}
# wallpapers
location /wallpapers {
try_files $uri $uri/ =404;
autoindex off;
}
# Deny access to .htaccess
location ~ /\.ht {
deny all;
}
error_page 404 /;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
error_page 404 /;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

80
lib/config.php Normal file
View File

@@ -0,0 +1,80 @@
<?php
include_once("logger.php");
$base_path = !empty($_SERVER['DOCUMENT_ROOT']) ? realpath($_SERVER['DOCUMENT_ROOT'] . "/../"): realpath(getcwd()."/../");
$base_config = Array(
'main' => Array(
'debug' => 1,
'default_language' => 'English_United_States',
'log_type' => "NULL",
'log_level' => LOG_EMERG
),
'subdirs' => Array(
'tftproot' => 'tftpboot',
'firmware' => 'firmware',
'settings' => 'settings',
'wallpapers' => 'wallpapers',
'ringtones' => 'ringtones',
'locales' => 'locales',
'countries' => 'countries',
'languages' => 'languages',
)
);
$tree_base = Array(
'settings' => array('path' => 'tftproot', "strip" => TRUE),
'wallpapers' => array('path' => 'tftproot', "strip" => FALSE),
'ringtones' => array('path' => 'tftproot', "strip" => TRUE),
'locales' => array('path' => 'tftproot', "strip" => TRUE),
'firmware' => array('path' => 'tftproot', "strip" => TRUE),
'languages' => array('path' => 'locales', "strip" => FALSE),
'countries' => array('path' => 'locales', "strip" => FALSE),
'default_language' => array('path' => 'locales', "strip" => TRUE),
);
# Merge config
$ini_array = parse_ini_file('../config.ini', TRUE, INI_SCANNER_TYPED);
if (!empty($ini_array)) {
$config = array_merge($base_config, $ini_array);
}
# build new config['subdirs'] paths substituting bases from tree_base
foreach ($tree_base as $key => $value) {
$tmp = $config;
if (!empty($tmp['subdirs'][$key])) {
if (substr($tmp['subdirs'][$key], 0, 1) !== "/") {
if (is_array($tmp['subdirs'][$value['path']])) {
$path = $tmp['subdirs'][$value['path']]['path'].'/'.$tmp['subdirs'][$key];
} else {
$path = $tmp['subdirs'][$value['path']].'/'.$tmp['subdirs'][$key];
}
}
$config['subdirs'][$key] = array('path' => $path, 'strip' => $value['strip']);
}
}
$config['main']['base_path'] = $base_path;
$config['main']['tftproot'] = (!empty($config['main']['tftproot'])) ? $base_path . "tftpboot" : '/tftpboot';
switch($config['main']['log_type']) {
case 'SYSLOG':
$logger = new Logger_Syslog($config['main']['log_level']);
break;
case 'FILE':
if (!isempty($config['main']['log_file'])) {
$logger = new Logger_Filename($config['main']['log_level'], $config['main']['log_file']);
}
break;
case 'STDOUT':
$logger = new Logger_Stdout($config['main']['log_level']);
break;
case 'STDERR':
$logger = new Logger_Stderr($config['main']['log_level']);
break;
default:
$logger = new Logger_Null($config['main']['log_level']);
}
# Fixup debug
$print_debug = (!empty($config['main']['debug'])) ? $config['main']['debug'] : 'off';
$print_debug = ($print_debug == 1) ? 'on' : $print_debug;
?>

94
lib/logger.php Normal file
View File

@@ -0,0 +1,94 @@
<?php
/* Note about the Logger class:
* The "priority" and "minimum should be one of the constants used for syslog.
* See: http://php.net/manual/en/function.syslog.php
* They are: LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE,
* LOG_INFO, LOG_DEBUG
* Note that LOG_EMERG, LOG_ALERT, and LOG_CRIT are not really relevant to a
* tftp server - these represent instability in the entire operating system.
* Note that the number they are represented by are in reverse order -
* LOG_EMERG is the lowest, LOG_DEBUG the highest.
*/
abstract class Logger
{
function __construct($minimum)
{
$this->minimum = $minimum;
}
function shouldlog($priority)
{
// Note: this looks reversed, but is correct
// the priority must be AT LEAST the minimum,
// because higher priorities represent lower numbers.
return $priority <= $this->minimum;
}
abstract function log($priority, $message);
}
class Logger_Null extends Logger
{
function log($priority, $message)
{
}
}
class Logger_Syslog extends Logger
{
function log($priority, $message)
{
if($this->shouldlog($priority))
syslog($priority,$message);
}
}
class Logger_Filehandle extends Logger
{
private $priority_map = array(
LOG_DEBUG => "D",
LOG_INFO => "I",
LOG_NOTICE => "N",
LOG_WARNING => "W",
LOG_ERR => "E",
LOG_CRIT => "C",
LOG_ALERT => "A",
LOG_EMERG => "!"
);
function __construct($minimum, $filehandle, $dateformat = "r")
{
$this->filehandle = $filehandle;
$this->dateformat = $dateformat;
return parent::__construct($minimum);
}
function log($priority, $message)
{
if($this->shouldlog($priority))
fwrite($this->filehandle, date($this->dateformat) . ": " . $this->priority_map[$priority] . " $message\n");
}
}
class Logger_Filename extends Logger_Filehandle
{
function __construct($minimum, $filename, $dateformat = "r")
{
return parent::__construct($minimum, fopen($filename, "a"), $dateformat);
}
}
class Logger_Stderr extends Logger_Filehandle
{
function __construct($minimum, $dateformat = "r")
{
return parent::__construct($minimum, STDERR, $dateformat);
}
}
class Logger_Stdout extends Logger_Filehandle
{
function __construct($minimum, $dateformat = "r")
{
return parent::__construct($minimum, STDOUT, $dateformat);
}
}
?>

168
lib/resolver.php Normal file
View File

@@ -0,0 +1,168 @@
<?php
include_once("config.php");
include_once("utils.php");
/* Todo:
✔️ setup logging
✔️ read config.file
✔? improve error handling
✔️? secure urlencoding/urldecoding
✔️? don't allow browsing
- check source ip-range
- check HTTPHeader for known BrowserTypes
- Could use some more test-cases, especially error ones
*/
class Resolver {
private $isDirty = FALSE;
private $cache = array();
private $config;
//private $logger;
function __construct($config) {
//global $logger;
$this->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();
}
}
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) {
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);
return $path;
}
}
$this->log_error_and_throw("File '$filename' does not exist");
}
function buildCleanCache() {
foreach($this->config['subdirs'] as $key =>$value) {
if ($key === "tftproot") {
continue;
}
$path = $this->config['main']['base_path'] . "/" . $value['path'] . "/";
$dir_iterator = new RecursiveDirectoryIterator($path);
$iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
if ($file->isFile()) {
if ($value['strip']) {
$this->addFile($file->getFileName(), $file->getPathname());
} else {
$subdir = basename(dirname($file->getPathname()));
$this->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) {
/* 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");
}
$this->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");
}
if (strstr($escaped_request, "..")) {
$this->log_error_and_throw("Request '$request' containst '..'");
}
}
function resolve($request) /* canthrow */ {
$this->validateRequest($request);
$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;
}
}
if ($this->searchForFile($request)) {
return $this->cache[$request];
}
return $path;
}
/* temporary */
function printCache() {
print_r($this->cache);
}
}
// 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),
);
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");
} else {
print("'" . $test['request'] . "' => '" . $result . "'\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");
}
}
}
unset($resolver);
#unlink($CACHEFILE_NAME);
}
?>

10
lib/utils.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
//
// Helper functions
//
function utf8_urldecode($str) {
$str = preg_replace("/%u([0-9a-f]{3,4})/i","&#x\\1;",urldecode($str));
return html_entity_decode($str,null,'UTF-8');;
}
?>

View File

@@ -1,13 +0,0 @@
[main]
debug = on ; The output in the browser window for more information
tftproot = /tftpboot
;default_language = English_United_States
firmware = firmware
settings = settings
wallpapers = wallpapers
ringtones = ringtones
locales = locales
countries = countries
languages = languages
;log = /tftpboot/provision.log

View File

@@ -1,308 +1,67 @@
<?php
//
// Written by Alex / github.com/PhantomVI
//
include_once("../lib/config.php");
include_once("../lib/resolver.php");
$request = $_REQUEST ?? null;
$base_path = realpath($_SERVER['DOCUMENT_ROOT']);
$base_config = Array(
'debug' => 1,
'tftproot' => $base_path,
'default_language' => 'English_United_States',
'firmware' => 'firmware', 'settings' => 'settings', 'wallpapers' => 'wallpapers', 'ringtones' => 'ringtones',
'locales' => 'locales', 'countries' => 'countries', 'languages' => 'languages'
);
$base_tree = Array('settings' => 'tftproot', 'wallpapers' => 'tftproot', 'ringtones'=>'tftproot',
'locales' => 'tftproot', 'firmware' => 'tftproot', 'languages' => 'locales',
'countries' => 'locales', 'default_language' => 'locales');
# Merge config
$ini_array = parse_ini_file('index.cnf');
if (!empty($ini_array)) {
$config = array_merge($base_config, $ini_array);
function send_fallback_html($message) {
while (ob_get_level()) {ob_end_clean();}
if (ob_get_length() === false) {
ob_start();
header('Content-Description: README');
header('Content-Type: text/html');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
}
$content="
<html>
<header>
</header>
<body>
<h1>provision_sccp</h1>
<p>Request:" . json_encode($request) . "</p>
<p>Message:" . $message . "</p>
</body>
</html>
";
print ($content);
ob_flush();
flush();
}
foreach ($base_tree as $key => $value) {
if (!empty($config[$key])) {
if (substr($config[$key],0,1) != "/") {
$config[$key] = $config[$value].'/'.$config[$key];
}
}
function sendfile($file) {
if (file_exists($file)) {
while (ob_get_level()) {ob_end_clean();}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
/* want to stream out, so don't use file_get_contents() in this case */
if ($fd = fopen($file, 'rb')) {
while (!feof($fd)) {
print fread($fd, 1024);
}
fclose($fd);
}
}
}
#$config['tftproot'] = (!empty($config['tftproot'])) ? $config['tftproot'] : '/tftpboot';
# Fixup debug
$print_debug = (!empty($config['debug'])) ? $config['debug'] : 'off';
$print_debug = ($print_debug == 1) ? 'on' : $print_debug;
# Parse request
$request = $_REQUEST;
$req_file = !empty($request['id']) ? $request['id'] : '';
# directory content extensions map
$fw_suffix = array('.bin', '.loads', '.sbn', '.sb2', '.sbin', '.zz', '.zup', '.loads');
//$settings_suffix = array('cnf.xml');
$ringtones_list = array('distinctive.xml', 'ringlist.xml','distinctiveringlist.xml');
$ringtones_suffix = array('.raw', '.pcm', '.rwb');
$locale_list = array('-dictionary.', 'dictionary-ext.', '-dictionary.utf-8.', '-kate.xml', '-font.xml', '-font.dat','-tones.xml',
'be-sccp.jar', 'tc-sccp.jar', 'td-sccp.jar', 'ipc-sccp.jar', 'mk-sccp.jar', '_locale.loads', 'i-button-help.xml',
'tc-sip.jar', 'td-sipp.jar');
# Show debug output
if ($print_debug == 'on') {
print_r("<br> Config:<pre>");
print_r($config);
print("</pre>");
print_r("<br> Request:<br><pre>");
print_r($request);
print("</pre>");
if (!$request || empty($request) || !array_key_exists('filename',$request) || empty($request['filename'])) {
send_fallback_html("Empty request sent");
exit();
}
# Start parsing the request
$req_file_full_path = '' ;
if (!empty($req_file)) {
$signed = FALSE;
$req_data_ar = explode('/', $req_file);
$req_data_len = count($req_data_ar) - 1;
$orig_req_file_name = end($req_data_ar);
$req_file_name = $orig_req_file_name;
if (strpos('.sgn;', strtolower($orig_req_file_name).';') !== FALSE) { // handle signed files
$signed = TRUE;
$req_file_name = basename($orig_req_file_name, '.sgn'); // strip signed part
}
if (file_exists($config['tftproot'].'/'.$req_file_name)) // prevent "/../...//" browsing - (eliminate back door)
{
$req_file_full_path = $config['tftproot'].'/'.$req_file_name;
}
else
{
$tmp_file = explode('.', $req_file_name);
if (strpos_array($req_file_name, $fw_suffix,'any') !== FALSE) { // Firmware file was requested
$firmware_list = find_all_files($config['firmware']);
$pos2 = strpos_array($firmware_list, $req_file_name, 'any'); // case unsensitive
if ($pos2 !== FALSE) { // Request Firmware
$req_file_full_path = $firmware_list[$pos2];
}
if ($print_debug == 'on'){ print_r('<br>Requested Firmware: '. $req_file_full_path. '<br>');}
}
else
{
$tmp_file = '';
//if (strpos_array($req_file_name, $settings_suffix, 'any') !== FALSE) { // Request Settings
if (strpos(strtolower($req_file_name), '.cnf.xml') !== FALSE) { // Request Settings
$tmp_file = $config['settings'].'/'.$req_file_name;
}
else if (strpos(strtolower($req_file), 'desktops/') !== FALSE) { // Request Wallpapers
$tmp_file = $config['wallpapers'].'/'. $req_data_ar[$req_data_len-1].'/'. $req_file_name;
}
else if (strpos_array($ringtones_list, $req_file_name, 'any') !== FALSE) { // Request RingTones
$tmp_file = $config['ringtones'].'/'.$req_file_name;
if (!file_exists($tmp_file)) {
$tmp_file = $config['ringtones'].'/ringlist.xml';
}
}
else if(strpos_array($req_file_name, $ringtones_suffix,'any') !== FALSE) { // Firmware file was requested
$tmp_file = $config['ringtones'].'/'.$req_file_name;
}
else if (strpos_array($req_file, $locale_list, 'any') !== FALSE) { // Request Languages
if (!empty($req_data_ar[$req_data_len-1])) {
$tmp_file = $config['languages'].'/'. $req_data_ar[$req_data_len-1].'/'. $req_file_name;
} else {
$tmp_file = $config['default_language'].'/'. $req_file_name;
}
}
/*
else if (strpos(strtolower($req_file), '-tones.xml') !== FALSE) { // Request Countries
$tmp_file = $config['countries'].'/'. $req_data_ar[$req_data_len-1].'/'. $req_data_ar[$req_data_len];
}
else if (strpos(strtolower($req_file), '-dictionary.') !== FALSE) { // Request Countries
$tmp_file = $config['languages'].'/'. $req_data_ar[$req_data_len-1].'/'. $req_data_ar[$req_data_len];
}
else if (strpos_array($req_file, $locale_list, 'any') !== FALSE) { // Request Languages
$tmp_file = $config['languages'].'/'. $req_data_ar[$req_data_len-1].'/'. $req_data_ar[$req_data_len];
}
else if (strpos(strtolower($req_file), '-dictionary.jar') !== FALSE) { // Request Countries
$tmp_file = $config['languages'].'/'. $req_data_ar[$req_data_len-1].'/'. $req_data_ar[$req_data_len];
}
*/
if ($print_debug == 'on'){ print_r('<br>File : '. $orig_req_file_name. ' not found.<br>');}
if (empty($tmp_file)) {
if (!empty($config['log'])) { to_log(array('GET ('.$req_file.'):'.$orig_req_file_name, 'no match found'),'E',$config['log']); }
header("HTTP/1.0 404 Not Found");
die('ERROR: no match found.');
}
$req_file_full_path = $tmp_file;
}
}
if (!empty($config['log'])) { to_log(array('GET ('.$req_file.') :'.$orig_req_file_name, 'Remap :'.$req_file_full_path),'i',$config['log']); }
if (!empty($req_file_full_path)) {
if ($signed) {
$req_file_full_path .= '.sgn';
}
if (!file_exists($req_file_full_path)) {
header("HTTP/1.0 404 Not Found");
die('Could not find:'. $req_file_full_path);
}
if ($print_debug == 'on'){ print_r('<br>Returning: '. $req_file_full_path. '<br>');}
file_force_download($req_file_full_path);
}
}
/*
* Helper functiosn
*/
function file_force_download($file) {
if (file_exists($file)) {
if (ob_get_level()) {
ob_end_clean();
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
if ($fd = fopen($file, 'rb')) {
while (!feof($fd)) {
print fread($fd, 1024);
}
fclose($fd);
}
exit;
}
try {
$req_filename=$request['filename'];
$resolver = new Resolver($config);
if (($filename = $resolver->resolve($req_filename))) {
sendfile($filename);
}
unset($resolver);
} catch(Exception $e) {
send_fallback_html($e->getMessage());
}
/*
* Founds any string from array in array
*/
function strpos_array($haystack, $needles, $mode='any') {
if (is_array($needles)) {
foreach ($needles as $str) {
$pos = strpos_array($haystack, $str, $mode);
if ($pos !== FALSE) {
return $pos;
}
}
} else {
if (is_array($haystack)) {
foreach ($haystack as $key => $subtr) {
$pos = strpos_array($subtr, $needles, $mode);
if ($pos !== FALSE) {
return $key;
}
}
return FALSE;
} else {
if ($mode == 'any') {
return strpos(strtolower($haystack), strtolower($needles));
} else {
if ($mode == 'full') {
if ($haystack == $needles) {
return 0;
} else {
return FALSE;
}
} else {
return strpos($haystack, $needles);
}
}
}
}
return FALSE;
}
function to_log($text, $level='i', $file) {
switch (strtolower($level)) {
case 'e':
case 'error':
$level='ERROR';
break;
case 'i':
case 'info':
$level='INFO';
break;
case 'd':
case 'debug':
$level='DEBUG';
break;
default:
$level='INFO';
}
if (is_array($text)) {
$to_log = '';
foreach ($text as $value) {
$to_log .= $value."\t";
}
} else {
$to_log .= $text;
}
$_txt = date('d.m.Y h:i:s')."\t[".$level."]\t ".$to_log."\n";
if (empty($file)) {
error_log($_txt, 0);
} else {
error_log($_txt, 3, $file);
}
}
function find_all_files($dir, $file_mask=null, $mode='full'){
$result = NULL;
if (empty($dir) || (!file_exists($dir))) {
return $result;
}
$root = scandir($dir);
foreach($root as $value) {
if($value === '.' || $value === '..') {continue;}
if(is_file("$dir/$value")) {
$filter = FALSE;
if (!empty($file_mask)) {
if (is_array($file_mask)) {
foreach ($file_mask as $k){
if (strpos(strtolower($value), strtolower($k)) !== FALSE) {$filter = true;}
}
} else {
if (strpos(strtolower($value), strtolower($file_mask)) !== FALSE) {$filter = true;}
}
} else {$filter = true;}
if ($filter) {
if ($mode=='fileonly'){
$result[]="$value";
} else {
$result[]="$dir/$value";
}
} else {$result[]=null;}
continue;
}
$sub_fiend = find_all_files("$dir/$value", $file_mask, $mode);
if (!empty($sub_fiend)) {
foreach($sub_fiend as $sub_value) {
if (!empty($sub_value)) {
$result[]=$sub_value;
}
}
}
}
return $result;
}
?>

View File

@@ -6,7 +6,7 @@ if [ ! -z "`ls *.pcm 2>/dev/null`" ]; then
basename=`basename ${filename} .pcm`
echo -e "\t<Ring>" >>$outfile
echo -e "\t\t<DisplayName>${basename}</DisplayName>" >>$outfile
echo -e "\t\t<FileName>ringtones\${filename}</FileName>" >>$outfile
echo -e "\t\t<FileName>ringtones\\${filename}</FileName>" >>$outfile
echo -e "\t</Ring>" >>$outfile
done
fi
@@ -15,7 +15,7 @@ if [ ! -z "`ls *.raw 2>/dev/null`" ]; then
basename=`basename ${filename} .raw`
echo -e "\t<Ring>" >>$outfile
echo -e "\t\t<DisplayName>${basename}</DisplayName>" >>$outfile
echo -e "\t\t<FileName>ringtones\${filename}</FileName>" >>$outfile
echo -e "\t\t<FileName>ringtones\\${filename}</FileName>" >>$outfile
echo -e "\t</Ring>" >>$outfile
done
fi