- Copied github.com/tm1000/tftpserver/tftpserver.php over to lib/tftpserver.php
- Replaced logging functions with lib/logger.php - First step for adding templated settings/file where placeholders can be filled out. - Add 'settings' structure to config.ini - Add 'settings' multidimensional config parser to lib/utils.php - Added simple test implementation of tftpserver - Renamed lib/tftpserver.php to lib/tftp.php - Fixed error output from lib/tftp.php - Note: current simple tftpserver.php test implementation stores/read files - from memory (not fs). So you need to put a file, before you can get that file back. - Cleanup some small config details - First simple implementation of tftp_provisioner.php
This commit is contained in:
40
config.ini
40
config.ini
@@ -1,10 +1,19 @@
|
||||
[main]
|
||||
debug = on ; The output in the browser window for more information
|
||||
debug = TRUE ; 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
|
||||
auto_generate_settings = FALSE
|
||||
auto_sign = FALSE
|
||||
auto_encrypt = FALSE
|
||||
|
||||
[security]
|
||||
cert_ca = NULL
|
||||
cert_priv = NULL
|
||||
cert_pub = NULL
|
||||
hash = NULL
|
||||
|
||||
[subdirs]
|
||||
tftproot = tftpboot
|
||||
@@ -15,3 +24,32 @@ ringtones = ringtones
|
||||
locales = locales
|
||||
countries = countries
|
||||
languages = languages
|
||||
|
||||
[settings]
|
||||
sshUserId = cisco
|
||||
sshPassword = cisco
|
||||
ipAddress = ipv4|ipv6 ; ipv4 | ipv4 | ipv4|ipv6 | ipv6|ipv4
|
||||
datetime.template = M/D/YA
|
||||
datetime.timezone = W. Europe Standard/Daylight Time
|
||||
datetime.ipaddress = 10.x.x.x
|
||||
datetime.mode = Unicast
|
||||
members.myhost.hostname = myhost.domain.com
|
||||
members.myhost.ipv4 = 10.x.x.x
|
||||
members.myhost.ipv6 = 2001:470::x:x
|
||||
members.myhost.port = 2000
|
||||
;srts.
|
||||
;common.
|
||||
;vendor.
|
||||
locale.country = United_States
|
||||
locale.language = English_United_States
|
||||
locale.langcode = en_US
|
||||
locale.charset = utf-8
|
||||
urls.security = FALSE
|
||||
urls.information = NULL
|
||||
urls.authentication = NULL
|
||||
urls.services = NULL
|
||||
urls.direcory = NULL
|
||||
urls.messages = NULL
|
||||
urls.proxyserver = NULL
|
||||
;vpn.
|
||||
;phoneservices.
|
||||
|
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
include_once("logger.php");
|
||||
include_once("utils.php");
|
||||
//$base_path = !empty($_SERVER['DOCUMENT_ROOT']) ? realpath($_SERVER['DOCUMENT_ROOT'] . "/.."): realpath(getcwd() . "/..");
|
||||
$base_path = realpath(__DIR__ . DIRECTORY_SEPARATOR . "..");
|
||||
|
||||
$base_path = !empty($_SERVER['DOCUMENT_ROOT']) ? realpath($_SERVER['DOCUMENT_ROOT'] . "/../"): realpath(getcwd()."/../");
|
||||
$base_config = Array(
|
||||
'main' => Array(
|
||||
'debug' => 1,
|
||||
'debug' => true,
|
||||
'default_language' => 'English_United_States',
|
||||
'log_type' => "NULL",
|
||||
'log_level' => LOG_EMERG
|
||||
'log_level' => 'LOG_EMERG'
|
||||
),
|
||||
'subdirs' => Array(
|
||||
'tftproot' => 'tftpboot',
|
||||
@@ -17,22 +19,63 @@ $base_config = Array(
|
||||
'ringtones' => 'ringtones',
|
||||
'locales' => 'locales',
|
||||
'countries' => 'countries',
|
||||
'languages' => 'languages',
|
||||
'languages' => 'languages'
|
||||
),
|
||||
'security' => Array(
|
||||
'cert_ca' => NULL,
|
||||
'cert_priv' => NULL,
|
||||
'cert_pub' => NULL,
|
||||
'hash' => NULL
|
||||
),
|
||||
'settings' => Array(
|
||||
'sshUserId' => 'cisco',
|
||||
'sshPassword' => 'cisco',
|
||||
'ipAddress' => '0',
|
||||
'datetime' => Array(
|
||||
'template' => 'M/D/YA',
|
||||
'timezone' => 'W. Europe Standard/Daylight Time',
|
||||
'ipaddress' => '10.x.x.x',
|
||||
'mode' => 'Unicast'
|
||||
),
|
||||
'members' => Array(
|
||||
'myhost' => Array(
|
||||
'hostname' => 'myhost.domain.com',
|
||||
'ipv4' => '10.x.x.x',
|
||||
'ipv6' => '2001:470::x:x',
|
||||
'port' => '2000'
|
||||
)
|
||||
),
|
||||
'locale' => Array(
|
||||
'country' => 'United_States',
|
||||
'language' => 'English_United_States',
|
||||
'langcode' => 'en_US',
|
||||
'charset' => 'utf-8'
|
||||
),
|
||||
'urls' => Array(
|
||||
'security' => false,
|
||||
'information' => NULL,
|
||||
'authentication' => NULL,
|
||||
'services' => NULL,
|
||||
'direcory' => NULL,
|
||||
'messages' => NULL,
|
||||
'proxyserver' => NULL
|
||||
)
|
||||
)
|
||||
);
|
||||
$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),
|
||||
'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);
|
||||
//$ini_array = parse_ini_file("$base_path/config.ini", true, INI_SCANNER_TYPED);
|
||||
$ini_array = parse_ini_file_multi("$base_path/config.ini", true, INI_SCANNER_TYPED);
|
||||
if (!empty($ini_array)) {
|
||||
$config = array_merge($base_config, $ini_array);
|
||||
}
|
||||
@@ -77,4 +120,10 @@ switch($config['main']['log_type']) {
|
||||
# Fixup debug
|
||||
$print_debug = (!empty($config['main']['debug'])) ? $config['main']['debug'] : 'off';
|
||||
$print_debug = ($print_debug == 1) ? 'on' : $print_debug;
|
||||
|
||||
if(defined('STDIN') ) {
|
||||
print_r($config);
|
||||
//var_dump($config);
|
||||
}
|
||||
|
||||
?>
|
||||
|
782
lib/tftp.php
Normal file
782
lib/tftp.php
Normal file
@@ -0,0 +1,782 @@
|
||||
<?php
|
||||
/*
|
||||
* PHP TFTP Server
|
||||
*
|
||||
* Copyright (c) 2011 <mattias.wadman@gmail.com>
|
||||
*
|
||||
* MIT License:
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*
|
||||
* Extend TFTPServer class and then call loop method with UDP URL.
|
||||
* Possible methods to override:
|
||||
* exists($peer, $filename)
|
||||
* Check if file exist, default always true.
|
||||
* readable($peer, $filename)
|
||||
* Check if file is readable, default always true.
|
||||
* get($peer, $filename, $mode)
|
||||
* Return content of file, default always false.
|
||||
* Only called if both exists and readable returns true.
|
||||
* writable($peer, $filename)
|
||||
* Check if file is writable, default always false.
|
||||
* put($peer, $filename, $mode, $content)
|
||||
* Write content to file.
|
||||
* Only falled if both exists and writable returns true.
|
||||
*
|
||||
* $peer is $ip:$port, source ip and port of client
|
||||
* $filename is filename specified by client
|
||||
* $mode is probably "octet" or "netascii"
|
||||
* $content is file content
|
||||
*
|
||||
* The server support multiple concurrent read and writes, but the method calls
|
||||
* are serialized, so make sure to return quickly.
|
||||
*
|
||||
* TODO:
|
||||
* select must handle EINTR, how?
|
||||
* multiple recv per select?
|
||||
*
|
||||
*/
|
||||
|
||||
// origin: https://github.com/tm1000/tftpserver
|
||||
include_once("logger.php");
|
||||
|
||||
class TFTPOpcode
|
||||
{
|
||||
public static function name($v)
|
||||
{
|
||||
static $names = array(TFTPOpcode::RRQ => "RRQ",
|
||||
TFTPOpcode::WRQ => "WRQ",
|
||||
TFTPOpcode::DATA => "DATA",
|
||||
TFTPOpcode::ACK => "ACK",
|
||||
TFTPOpcode::ERROR => "ERROR",
|
||||
TFTPOpcode::OACK => "OACK");
|
||||
if(isset($names[$v]))
|
||||
return $names[$v];
|
||||
else
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
const RRQ = 1; // read request
|
||||
const WRQ = 2; // write request
|
||||
const DATA = 3; // send data
|
||||
const ACK = 4; // ack data
|
||||
const ERROR = 5;
|
||||
const OACK = 6; // option ack, instead of first ACK/DATA
|
||||
}
|
||||
|
||||
class TFTPError
|
||||
{
|
||||
const NOT_DEFINED = 0; // see error message instead of error code
|
||||
const FILE_NOT_FOUND = 1;
|
||||
const ACCESS_VIOLATION = 2;
|
||||
const DISK_FULL = 3;
|
||||
const ILLEGAL_OPERATION = 4;
|
||||
const UNKNOWN_TID = 5; // unknown transfer (id is ip:port pair)
|
||||
const FILE_ALREADY_EXISTS = 6;
|
||||
const NO_SUCH_USER = 7;
|
||||
const OACK_FAILURE = 8;
|
||||
}
|
||||
|
||||
class TFTPTransferState
|
||||
{
|
||||
const READY = 1;
|
||||
const SENDING_WAIT_OACK = 2;
|
||||
const SENDING = 3;
|
||||
const RECEIVING = 4;
|
||||
const TERMINATING = 5;
|
||||
}
|
||||
|
||||
abstract class TFTPTransfer {
|
||||
public $state;
|
||||
public $peer;
|
||||
public $retransmit_timeout;
|
||||
public $block_size;
|
||||
public $tsize;
|
||||
protected $_server; // TFTPServer reference
|
||||
|
||||
function __construct($server, $peer, $extensions)
|
||||
{
|
||||
$this->state = TFTPTransferState::READY;
|
||||
$this->peer = $peer;
|
||||
$this->retransmit_timeout = $server->retransmit_timeout;
|
||||
$this->block_size = $server->block_size;
|
||||
$this->tsize = 0;
|
||||
$this->_server = $server;
|
||||
|
||||
if(isset($extensions["timeout"])) {
|
||||
$timeout = (int)$extensions["timeout"];
|
||||
if($timeout > 0 && $timeout < 256)
|
||||
$this->retransmit_timeout = $timeout;
|
||||
}
|
||||
|
||||
if(isset($extensions["blksize"])) {
|
||||
$blksize = (int)$extensions["blksize"];
|
||||
if($blksize > 0 && $blksize <= $server->max_block_size)
|
||||
$this->block_size = $blksize;
|
||||
}
|
||||
|
||||
// tsize is only checked for in write transfers
|
||||
}
|
||||
|
||||
protected function log_debug($message)
|
||||
{
|
||||
$this->_server->log_debug($this->peer, $message);
|
||||
}
|
||||
|
||||
protected function log_info($message)
|
||||
{
|
||||
$this->_server->log_info($this->peer, $message);
|
||||
}
|
||||
|
||||
protected function log_warning($message)
|
||||
{
|
||||
$this->_server->log_warning($this->peer, $message);
|
||||
}
|
||||
|
||||
protected function log_error($message)
|
||||
{
|
||||
$this->_server->log_error($this->peer, $message);
|
||||
}
|
||||
|
||||
protected function terminal_info($error, $message)
|
||||
{
|
||||
$this->log_info($message);
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
return TFTPServer::packet_error($error, $message);
|
||||
}
|
||||
|
||||
protected function terminal_error($op, $error, $message)
|
||||
{
|
||||
$this->log_debug("$op: $message");
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
return TFTPServer::packet_error($error, $message);
|
||||
}
|
||||
|
||||
protected function illegal_operation($op, $message = "Illegal operation")
|
||||
{
|
||||
return $this->terminal_error($op, TFTPError::ILLEGAL_OPERATION, $message);
|
||||
}
|
||||
|
||||
public function rrq($filename, $mode)
|
||||
{
|
||||
return $this->illegal_operation("RRQ");
|
||||
}
|
||||
|
||||
public function wrq($filename, $mode)
|
||||
{
|
||||
return $this->illegal_operation("WRQ");
|
||||
}
|
||||
|
||||
public function data($block, $data)
|
||||
{
|
||||
return $this->illegal_operation("DATA");
|
||||
}
|
||||
|
||||
public function ack($block)
|
||||
{
|
||||
return $this->illegal_operation("ACK");
|
||||
}
|
||||
|
||||
public function error($error, $message)
|
||||
{
|
||||
$this->log_debug("ERROR: $error: $message");
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
}
|
||||
|
||||
protected function use_extensions() {
|
||||
return
|
||||
$this->retransmit_timeout != $this->_server->retransmit_timeout ||
|
||||
$this->block_size != $this->_server->block_size ||
|
||||
$this->tsize != 0;
|
||||
}
|
||||
|
||||
protected function packet_oack() {
|
||||
$options = array();
|
||||
|
||||
if($this->retransmit_timeout != $this->_server->retransmit_timeout)
|
||||
$options["timeout"] = (string)$this->retransmit_timeout;
|
||||
|
||||
if($this->block_size != $this->_server->block_size)
|
||||
$options["blksize"] = (string)$this->block_size;
|
||||
|
||||
if($this->tsize != 0)
|
||||
$options["tsize"] = (string)$this->tsize;
|
||||
|
||||
return TFTPServer::packet_oack($options);
|
||||
}
|
||||
}
|
||||
|
||||
class TFTPReadTransfer extends TFTPTransfer {
|
||||
private $_last_recv_ack;
|
||||
private $_last_sent_data;
|
||||
private $_buffer;
|
||||
private $_block;
|
||||
private $_last_block;
|
||||
|
||||
function __construct($server, $peer, $extensions)
|
||||
{
|
||||
parent::__construct($server, $peer, $extensions);
|
||||
$this->_last_recv_ack = time();
|
||||
$this->_last_sent_data = $this->_last_recv_ack;
|
||||
$this->_buffer = false;
|
||||
$this->_block = 1;
|
||||
$this->_last_block = 1;
|
||||
|
||||
$this->log_debug("new read transfer");
|
||||
}
|
||||
|
||||
private function current_block()
|
||||
{
|
||||
return substr($this->_buffer,
|
||||
($this->_block - 1) * $this->block_size,
|
||||
$this->block_size);
|
||||
}
|
||||
|
||||
private function packet_data_current()
|
||||
{
|
||||
$this->_last_sent_data = time();
|
||||
|
||||
if($this->state == TFTPTransferState::SENDING_WAIT_OACK)
|
||||
return $this->packet_oack();
|
||||
else
|
||||
return TFTPServer::packet_data($this->_block, $this->current_block());
|
||||
}
|
||||
|
||||
public function rrq($filename, $mode)
|
||||
{
|
||||
$this->log_debug("RRQ: filename $filename in $mode mode");
|
||||
|
||||
if($this->state != TFTPTransferState::READY)
|
||||
return $this->illegal_operation("RRQ", "Not in ready state");
|
||||
|
||||
if(!$this->_server->exists($this->peer, $filename))
|
||||
return $this->terminal_info(TFTPError::FILE_NOT_FOUND,
|
||||
"File $filename does not exist");
|
||||
|
||||
if(!$this->_server->readable($this->peer, $filename))
|
||||
return $this->terminal_info(TFTPError::ACCESS_VIOLATION,
|
||||
"File $filename is not readable");
|
||||
|
||||
$this->_buffer = $this->_server->get($this->peer, $filename, $mode);
|
||||
if($this->_buffer === false)
|
||||
return $this->terminal_info(TFTPError::FILE_NOT_FOUND,
|
||||
"Failed to read $filename");
|
||||
|
||||
$this->log_info("Reading $filename (" .
|
||||
strlen($this->_buffer) . " bytes)");
|
||||
|
||||
if($this->use_extensions())
|
||||
$this->state = TFTPTransferState::SENDING_WAIT_OACK;
|
||||
else
|
||||
$this->state = TFTPTransferState::SENDING;
|
||||
$this->_last_block = floor(strlen($this->_buffer) /
|
||||
$this->block_size) + 1;
|
||||
|
||||
$this->log_debug("RRQ: send first block or OACK");
|
||||
return $this->packet_data_current();
|
||||
}
|
||||
|
||||
public function ack($block)
|
||||
{
|
||||
if($this->state == TFTPTransferState::SENDING_WAIT_OACK) {
|
||||
if($block != 0) {
|
||||
$this->log_debug("ACK: waiting OACK ACK got block $block");
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->state = TFTPTransferState::SENDING;
|
||||
$this->log_debug("ACK: got OACK ACK, send first block");
|
||||
return $this->packet_data_current();
|
||||
}
|
||||
|
||||
if($this->state != TFTPTransferState::SENDING)
|
||||
return $this->illegal_operation("ACK", "Not in sending state");
|
||||
|
||||
$this->log_debug("ACK: block $block");
|
||||
$this->_last_recv_ack = time();
|
||||
|
||||
if($block < $this->_block) {
|
||||
$this->log_debug("ACK: duplicate block $block");
|
||||
// just ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
if($block > $this->_last_block)
|
||||
return $this->illegal_operation("ACK",
|
||||
"Block $block outside " .
|
||||
"range 1-{$this->_last_block}");
|
||||
|
||||
if($block == $this->_last_block) {
|
||||
$this->log_debug("ACK: last block, done");
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
return false;
|
||||
}
|
||||
|
||||
// move to next block
|
||||
$this->_block = $block + 1;
|
||||
|
||||
$this->log_debug("ACK: sending block {$this->_block}");
|
||||
return $this->packet_data_current();
|
||||
}
|
||||
|
||||
public function retransmit($now)
|
||||
{
|
||||
if($now - $this->_last_recv_ack > $this->_server->timeout) {
|
||||
$this->log_debug("retransmit: timeout");
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
return false;
|
||||
}
|
||||
|
||||
if($now - $this->_last_sent_data > $this->retransmit_timeout) {
|
||||
$this->log_debug("retransmit: resending block {$this->_block} or OACK");
|
||||
return $this->packet_data_current();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class TFTPWriteTransfer extends TFTPTransfer {
|
||||
private $_last_sent_ack;
|
||||
private $_last_recv_data;
|
||||
private $_buffer;
|
||||
private $_buffer_size;
|
||||
private $_next_block;
|
||||
private $_filename;
|
||||
private $_mode;
|
||||
|
||||
function __construct($server, $peer, $extensions)
|
||||
{
|
||||
parent::__construct($server, $peer, $extensions);
|
||||
$this->_last_sent_ack = time();
|
||||
$this->_last_recv_data = $this->_last_sent_ack;
|
||||
$this->_buffer = array();
|
||||
$this->_buffer_size = 0;
|
||||
$this->_last_recv_block = 0;
|
||||
$this->_filename = false;
|
||||
$this->_mode = false;
|
||||
|
||||
if(isset($extensions["tsize"]))
|
||||
$this->tsize = (int)$extensions["tsize"];
|
||||
|
||||
$this->log_debug("new write transfer");
|
||||
}
|
||||
|
||||
private function packet_ack_current()
|
||||
{
|
||||
$this->_last_sent_ack = time();
|
||||
|
||||
if($this->_last_recv_block == 0 && $this->use_extensions())
|
||||
return $this->packet_oack();
|
||||
else
|
||||
return TFTPServer::packet_ack($this->_last_recv_block);
|
||||
}
|
||||
|
||||
public function wrq($filename, $mode)
|
||||
{
|
||||
$this->log_debug("WRQ: filename $filename in $mode mode");
|
||||
|
||||
if($this->state != TFTPTransferState::READY)
|
||||
return $this->illegal_operation("WRQ", "Not in ready state");
|
||||
|
||||
if(!$this->_server->writable($this->peer, $filename))
|
||||
return $this->terminal_info(TFTPError::ACCESS_VIOLATION,
|
||||
"File $filename is not writable");
|
||||
|
||||
if($this->tsize != 0 && $this->tsize > $this->_server->max_put_size)
|
||||
return $this->terminal_info(TFTPError::DISK_FULL,
|
||||
"File too big, " .
|
||||
$this->tsize . "(tsize) > " .
|
||||
$this->_server->max_put_size);
|
||||
|
||||
$this->state = TFTPTransferState::RECEIVING;
|
||||
$this->_filename = $filename;
|
||||
$this->_mode = $mode;
|
||||
$this->_last_sent_ack = time();
|
||||
|
||||
$this->log_debug("WRQ: ack request");
|
||||
if($this->use_extensions())
|
||||
return $this->packet_oack();
|
||||
else
|
||||
return TFTPServer::packet_ack(0);
|
||||
}
|
||||
|
||||
public function data($block, $data)
|
||||
{
|
||||
if($this->state != TFTPTransferState::RECEIVING)
|
||||
return $this->illegal_operation("DATA", "Not in receiving state");
|
||||
|
||||
$this->log_debug("DATA: block $block");
|
||||
$this->last_recv_data = time();
|
||||
|
||||
if($block <= $this->_last_recv_block) {
|
||||
$this->log_debug("DATA: duplicate block $block");
|
||||
// just ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
if($block != $this->_last_recv_block + 1)
|
||||
return $this->illegal_operation("DATA",
|
||||
"Expected block " .
|
||||
($this->_last_recv_block + 1) .
|
||||
" got $block");
|
||||
|
||||
$this->_last_recv_block = $block;
|
||||
$this->_last_recv_data = time();
|
||||
array_push($this->_buffer, $data);
|
||||
$this->_buffer_size += strlen($data);
|
||||
|
||||
if($this->_buffer_size > $this->_server->max_put_size)
|
||||
return $this->terminal_info(TFTPError::DISK_FULL,
|
||||
"File too big, " .
|
||||
$this->_buffer_size . " > " .
|
||||
$this->_server->max_put_size);
|
||||
|
||||
if(strlen($data) < $this->block_size) {
|
||||
$this->log_debug("DATA: last, done");
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
$this->log_info("Writing {$this->_filename} " .
|
||||
"({$this->_buffer_size} bytes)");
|
||||
$this->_server->put($this->peer, $this->_filename, $this->_mode,
|
||||
implode("", $this->_buffer));
|
||||
return $this->packet_ack_current();
|
||||
}
|
||||
|
||||
$this->log_debug("DATA: ack block $block");
|
||||
return $this->packet_ack_current();
|
||||
}
|
||||
|
||||
public function retransmit($now)
|
||||
{
|
||||
if($now - $this->_last_recv_data > $this->_server->timeout) {
|
||||
$this->log_debug("retransmit: timeout");
|
||||
$this->state = TFTPTransferState::TERMINATING;
|
||||
return false;
|
||||
}
|
||||
|
||||
if($now - $this->_last_sent_ack > $this->retransmit_timeout) {
|
||||
$this->log_debug("retransmit: reack block {$this->_last_recv_block}");
|
||||
return $this->packet_ack_current();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class TFTPServer {
|
||||
public $block_size = 512;
|
||||
public $max_block_size = 65464; // max block size from rfc2348
|
||||
public $timeout = 10;
|
||||
public $retransmit_timeout = 1;
|
||||
public $max_put_size = 10485760; // 10 Mibi
|
||||
private $_socket_url;
|
||||
private $_socket;
|
||||
private $_transfers = array();
|
||||
private $_logger = NULL;
|
||||
|
||||
function __construct($socket_url, $logger = NULL)
|
||||
{
|
||||
$this->_socket_url = $socket_url;
|
||||
$this->_logger = $logger;
|
||||
}
|
||||
|
||||
public function exists($peer, $filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function readable($peer, $filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get($peer, $filename, $mode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function writable($peer, $filename)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function put($peer, $filename, $mode, $content)
|
||||
{
|
||||
}
|
||||
|
||||
public function logger_log($priority, $message) {
|
||||
if($this->_logger === NULL)
|
||||
return;
|
||||
|
||||
$this->_logger->log($priority, $message);
|
||||
}
|
||||
|
||||
public function log_debug($peer, $message)
|
||||
{
|
||||
$this->logger_log(LOG_DEBUG, "$peer $message");
|
||||
}
|
||||
|
||||
public function log_info($peer, $message)
|
||||
{
|
||||
$this->logger_log(LOG_INFO, "$peer $message");
|
||||
}
|
||||
|
||||
public function log_warning($peer, $message)
|
||||
{
|
||||
$this->logger_log(LOG_WARNING, "$peer $message");
|
||||
}
|
||||
|
||||
public function log_error($peer, $message)
|
||||
{
|
||||
$this->logger_log(LOG_ERR, "$peer $message");
|
||||
}
|
||||
|
||||
public static function packet_ack($block)
|
||||
{
|
||||
return pack("nn", TFTPOpcode::ACK, $block);
|
||||
}
|
||||
|
||||
public static function packet_data($block, $data)
|
||||
{
|
||||
return pack("nn", TFTPOpcode::DATA, $block) . $data;
|
||||
}
|
||||
|
||||
public static function packet_error($code, $message = "")
|
||||
{
|
||||
return pack("nn", TFTPOpcode::ERROR, $code) . $message . "\0";
|
||||
}
|
||||
|
||||
public static function packet_oack($options)
|
||||
{
|
||||
$data = "";
|
||||
foreach($options as $key => $value)
|
||||
$data .= "$key\0$value\0";
|
||||
return pack("n", TFTPOpcode::OACK) . $data;
|
||||
}
|
||||
|
||||
public static function escape_string($str)
|
||||
{
|
||||
$b = "";
|
||||
$l = strlen($str);
|
||||
for($i = 0; $i < $l; $i++) {
|
||||
$c = $str[$i];
|
||||
if(ctype_print($c))
|
||||
$b .= $c;
|
||||
else
|
||||
$b .= sprintf("\\x%'02x", ord($c));
|
||||
}
|
||||
|
||||
return $b;
|
||||
}
|
||||
|
||||
public function loop(&$error = false, $user = null)
|
||||
{
|
||||
$this->_socket =
|
||||
stream_socket_server($this->_socket_url, $errno, $errstr,
|
||||
STREAM_SERVER_BIND);
|
||||
if(!$this->_socket) {
|
||||
if($error !== false)
|
||||
$error = "$errno: $errstr";
|
||||
return false;
|
||||
}
|
||||
|
||||
if($user != null) {
|
||||
posix_seteuid($user["uid"]);
|
||||
posix_setegid($user["gid"]);
|
||||
}
|
||||
|
||||
stream_set_blocking($this->_socket, false);
|
||||
|
||||
return $this->loop_ex();
|
||||
}
|
||||
|
||||
private function loop_ex()
|
||||
{
|
||||
$now = $last = time();
|
||||
|
||||
while(true) {
|
||||
$read = array($this->_socket);
|
||||
//$r = stream_select($read, $write = null, $excpt = null, 1);
|
||||
$write = null;
|
||||
$except = null;
|
||||
$r = stream_select($read, $write, $excpt, 1);
|
||||
|
||||
if($r === false) {
|
||||
$this->log_error("server", "select returned false");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(count($read) > 0) {
|
||||
$packet = stream_socket_recvfrom($this->_socket,
|
||||
65535, // max udp packet size
|
||||
0, // no flags
|
||||
$peer);
|
||||
// ipv6 hack, convert to [host]:port format
|
||||
if(strpos($peer, ".") === false) {
|
||||
$portpos = strrpos($peer, ":");
|
||||
$host = substr($peer, 0, $portpos);
|
||||
$port = substr($peer, $portpos + 1);
|
||||
$peer = "[$host]:$port";
|
||||
}
|
||||
$this->log_debug($peer, "request: " . strlen($packet). " bytes");
|
||||
$this->log_debug($peer, "request: " .
|
||||
TFTPServer::escape_string($packet));
|
||||
$reply = $this->request($peer, $packet);
|
||||
if($reply !== false) {
|
||||
$this->log_debug($peer, "reply: " .
|
||||
TFTPServer::escape_string($reply));
|
||||
stream_socket_sendto($this->_socket, $reply, 0, $peer);
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
if($now != $last) {
|
||||
$last = $now;
|
||||
$this->retransmit($now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function retransmit($now)
|
||||
{
|
||||
foreach($this->_transfers as $peer => $transfer) {
|
||||
$reply = $transfer->retransmit($now);
|
||||
if($reply !== false) {
|
||||
$this->log_debug($peer, "resend: " .
|
||||
TFTPServer::escape_string($reply));
|
||||
stream_socket_sendto($this->_socket, $reply, 0, $peer);
|
||||
}
|
||||
|
||||
if($transfer->state == TFTPTransferState::TERMINATING)
|
||||
unset($this->_transfers[$peer]);
|
||||
}
|
||||
}
|
||||
|
||||
private function request($peer, $packet)
|
||||
{
|
||||
if(strlen($packet) < 4) {
|
||||
$this->log_debug($peer, "request: short packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
$reply = false;
|
||||
$transfer = false;
|
||||
if(isset($this->_transfers[$peer])) {
|
||||
$this->log_debug($peer, "request: existing transfer");
|
||||
$transfer = $this->_transfers[$peer];
|
||||
}
|
||||
|
||||
$fields = unpack("n", $packet);
|
||||
$op = $fields[1];
|
||||
$this->log_debug($peer, "request: opcode " .
|
||||
TFTPOpcode::name($op) . " ($op)");
|
||||
switch($op) {
|
||||
case TFTPOpcode::WRQ:
|
||||
case TFTPOpcode::RRQ:
|
||||
$a = explode("\0", substr($packet, 2));
|
||||
if(count($a) < 3 || $a[count($a) - 1] != "") {
|
||||
$this->log_warning($peer, "request: malformed " .
|
||||
TFTPOpcode::name($op));
|
||||
return false;
|
||||
}
|
||||
|
||||
$rawexts = array_slice($a, 2, -1);
|
||||
|
||||
// Cisco IP Phone 7941 (and possibly others) return an extra null
|
||||
// at the end; a breach of RFC rfc2347. This is a workaround.
|
||||
// If odd count strip last and continue if empty, else warn and ignore
|
||||
if(count($rawexts) % 2 != 0) {
|
||||
if(array_pop($rawexts)!="") {
|
||||
$this->log_warning($peer, "request: malformed extension " .
|
||||
"key/value pairs " . TFTPOpcode::name($op));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$extensions = array();
|
||||
foreach(array_chunk($rawexts, 2) as $pair)
|
||||
$extensions[strtolower($pair[0])] = $pair[1];
|
||||
|
||||
if($transfer === false) {
|
||||
if($op == TFTPOpcode::RRQ)
|
||||
$transfer = new TFTPReadTransfer($this, $peer, $extensions);
|
||||
else
|
||||
$transfer = new TFTPWriteTransfer($this, $peer, $extensions);
|
||||
|
||||
$this->_transfers[$peer] = $transfer;
|
||||
}
|
||||
|
||||
if($op == TFTPOpcode::RRQ)
|
||||
$reply = $transfer->rrq($a[0], $a[1]);
|
||||
else
|
||||
$reply = $transfer->wrq($a[0], $a[1]);
|
||||
|
||||
break;
|
||||
case TFTPOpcode::ACK:
|
||||
if(strlen($packet) != 4) {
|
||||
$this->log_warning($peer, "request: malformed ACK");
|
||||
return false;
|
||||
}
|
||||
|
||||
$a = unpack("n", substr($packet, 2));
|
||||
if($transfer === false) {
|
||||
// do not warn, some clients like BSD tftp sends ack on read error
|
||||
$this->log_debug($peer, "request: ack from unknwon peer");
|
||||
} else
|
||||
$reply = $transfer->ack($a[1]);
|
||||
break;
|
||||
case TFTPOpcode::DATA:
|
||||
if(strlen($packet) < 4) {
|
||||
$this->log_warning($peer, "request: malformed DATA");
|
||||
return false;
|
||||
}
|
||||
|
||||
$a = unpack("n", substr($packet, 2));
|
||||
$data = substr($packet, 4, strlen($packet) - 4);
|
||||
if($transfer === false) {
|
||||
$this->log_warning($peer, "request: data from unknwon peer");
|
||||
$reply = TFTPServer::packet_error(TFTPError::UNKNOWN_TID,
|
||||
"Unknown TID for DATA");
|
||||
} else
|
||||
$reply = $transfer->data($a[1], $data);
|
||||
break;
|
||||
case TFTPOpcode::ERROR:
|
||||
$a = unpack("n", substr($packet, 2, 2));
|
||||
$message = substr($packet, 4, strlen($packet) - 5);
|
||||
|
||||
if($transfer === false)
|
||||
$this->log_warning($peer, "request: error from unknwon peer, " .
|
||||
"{$a[1]}:$message");
|
||||
else
|
||||
$transfer->error($a[1], $message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if($transfer !== false &&
|
||||
$transfer->state == TFTPTransferState::TERMINATING) {
|
||||
$this->log_debug($peer, "request: terminating");
|
||||
unset($this->_transfers[$transfer->peer]);
|
||||
}
|
||||
|
||||
return $reply;
|
||||
}
|
||||
}
|
||||
?>
|
@@ -7,4 +7,47 @@ 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');;
|
||||
}
|
||||
|
||||
function parse_ini_file_multi($file, $process_sections = false, $scanner_mode = INI_SCANNER_NORMAL) {
|
||||
$explode_str = '.';
|
||||
$escape_char = "'";
|
||||
// load ini file the normal way
|
||||
$data = parse_ini_file($file, $process_sections, $scanner_mode);
|
||||
if (!$process_sections) {
|
||||
$data = array($data);
|
||||
}
|
||||
foreach ($data as $section_key => $section) {
|
||||
// loop inside the section
|
||||
foreach ($section as $key => $value) {
|
||||
if (strpos($key, $explode_str)) {
|
||||
if (substr($key, 0, 1) !== $escape_char) {
|
||||
// key has a dot. Explode on it, then parse each subkeys
|
||||
// and set value at the right place thanks to references
|
||||
$sub_keys = explode($explode_str, $key);
|
||||
$subs =& $data[$section_key];
|
||||
foreach ($sub_keys as $sub_key) {
|
||||
if (!isset($subs[$sub_key])) {
|
||||
$subs[$sub_key] = [];
|
||||
}
|
||||
$subs =& $subs[$sub_key];
|
||||
}
|
||||
// set the value at the right place
|
||||
$subs = $value;
|
||||
// unset the dotted key, we don't need it anymore
|
||||
unset($data[$section_key][$key]);
|
||||
}
|
||||
// we have escaped the key, so we keep dots as they are
|
||||
else {
|
||||
$new_key = trim($key, $escape_char);
|
||||
$data[$section_key][$new_key] = $value;
|
||||
unset($data[$section_key][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$process_sections) {
|
||||
$data = $data[0];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
?>
|
||||
|
101
tftp_provisioner.php
Executable file
101
tftp_provisioner.php
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
require_once("lib/tftp.php");
|
||||
require_once("lib/config.php");
|
||||
require_once("lib/resolver.php");
|
||||
|
||||
class TFTPProvisioner extends TFTPServer
|
||||
{
|
||||
private $_debug;
|
||||
private $_resolver;
|
||||
private $_filename;
|
||||
|
||||
function __construct($server_url, $config, $logger = NULL, $debug = false)
|
||||
{
|
||||
$this->_config = $config;
|
||||
if (!$logger) {
|
||||
$logger = new Logger_NULL('LOG_ERROR');
|
||||
}
|
||||
parent::__construct($server_url, $logger);
|
||||
$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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function put($peer, $filename, $mode, $content)
|
||||
{
|
||||
return file_put_contents($this->_filename, $content);
|
||||
}
|
||||
|
||||
/*
|
||||
* STDOUT Log functions
|
||||
*/
|
||||
private function log($peer, $level, $message)
|
||||
{
|
||||
echo(date("H:i:s") . " $level $peer $message\n");
|
||||
}
|
||||
|
||||
public function log_debug($peer, $message)
|
||||
{
|
||||
if($this->_debug)
|
||||
$this->log($peer, "D", $message);
|
||||
}
|
||||
|
||||
public function log_info($peer, $message)
|
||||
{
|
||||
$this->log($peer, "I", $message);
|
||||
}
|
||||
|
||||
public function log_warning($peer, $message)
|
||||
{
|
||||
$this->log($peer, "W", $message);
|
||||
}
|
||||
|
||||
public function log_error($peer, $message)
|
||||
{
|
||||
$this->log($peer, "E", $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$host = "127.0.0.1";
|
||||
$port = 10069;
|
||||
$url = "udp://$host:$port";
|
||||
|
||||
echo "\nStarting TFTP Provisioner...\n";
|
||||
$server = new TFTPProvisioner($url, $config, $logger);
|
||||
if(!$server->loop($error))
|
||||
die("$error\n");
|
||||
?>
|
||||
|
Reference in New Issue
Block a user