<?php

/* ##########################

  Devart HttpTunnel v1.69.    

  HTTP tunnel script.    

  This script allows you to manage server even if the corresponding port is blocked or remote access to server is not allowed.    

  ##########################
*/
if ( !function_exists('sys_get_temp_dir')) {
    function sys_get_temp_dir() {
        if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); }
        if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); }
        if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); }
        $tempfile=tempnam(__FILE__,'');
        if (file_exists($tempfile)) {
          unlink($tempfile);
            return realpath(dirname($tempfile));
        }
            return false;
    }
}

$tmp_dir = sys_get_temp_dir();
if(!$tmp_dir){
    define('SYSTEM_TMP_DIR', '');
}
else{
    $last_symbol = substr($tmp_dir, -1);
    if($last_symbol == DIRECTORY_SEPARATOR){
        define('SYSTEM_TMP_DIR', $tmp_dir);
    }
    else{
        define('SYSTEM_TMP_DIR', $tmp_dir . DIRECTORY_SEPARATOR);
    }
}

$SUB_DIRECTORY = SYSTEM_TMP_DIR . 'tunnel_files';
$LOG_FILE_NAME = 'httptunnel_server.log';
$CONN_FILE_NAME = '_connections.id.php';
$LOGFILE = $SUB_DIRECTORY . '/' . $LOG_FILE_NAME;
$CONN_FILE = $SUB_DIRECTORY . '/' . $CONN_FILE_NAME;

$LOG = 0;       // Set to "0" to disable logging
$LOG_DEBUG = 0; // Set to "0" to disable additional debug logging
$LOGFILEHANDLE = 0;
$MAXLOGSIZE = "4000000";
$LIFETIME = 180; // script lifetime in seconds. If script was started and got no client within that time - it exits.
$READ_WRITE_ATTEMPTS = 100;
$CREATE_CLIENT_SOCKET_ATTEMPTS = 3;
$IPC_TMPDIR = "/tmp/"; 

global $SUB_DIRECTORY;

function checkFunctionExists($functionName) {
  if (!function_exists($functionName)) {
    echo "Required function <b>$functionName</b> does not exist.</br>";
    return false;
  }
  
  return true;
}

// Creates connection temporary  file if not exists and checks permission to write
function CreateAndCheckConnFile($fileName) {

    global $SUB_DIRECTORY;

  if (file_exists($fileName)){
      $newFile = @fopen($fileName, 'a');
      if($newFile)
         fclose($newFile);
      else
         echo "<b>Error</b>: Failed to open ($fileName) file: Permission denied.";
          
  }
  else{
      if(!is_dir($SUB_DIRECTORY)){
          mkdir($SUB_DIRECTORY);
      }
      $newFile = @fopen($fileName, 'w');
      if($newFile){
         fwrite($newFile, "<?php echo 'Devart HTTP tunnel temporary file.'; exit; ?>\r\n"); // forbid viewing this file through browser
         fclose($newFile);
      }
      else
         echo "<b>Error</b>: Failed to create ($fileName) file: Permission denied.";
      }
        
  if(!$newFile)
    exit;
}

if (!isset($_REQUEST["a"])) {  // query from browser
  
  echo "Devart HttpTunnel v1.69<br />";
  
  $functionList = array(
    // "set_time_limit",
    "stream_socket_server",
    "stream_socket_client",
    "stream_socket_get_name",
    "stream_set_blocking",
    "stream_socket_accept",
    "stream_select"
  );
  
  $exist = true;
  foreach($functionList as $functionName) {
    $result = checkFunctionExists($functionName);
    $exist = $exist && $result;
  }
  
  if ($exist)
    CreateAndCheckConnFile($CONN_FILE);

  if ($exist) {
    echo "Tunnel script is installed correctly. <br />You can establish connections through the HTTP tunnel.";
    if ($LOG==1)
      echo "<br /> <br /><b>Loging is enabled.</b><br />Log files are located in the tunnel_files folder, which, in its turn, is located in the temporary folder of the operating system: " .$LOGFILE;
  }
  else
    echo "Required PHP functions listed above are not available. Tunneling script will not work without these functions. Please read PHP manuals about how to install listed functions.";

  exit;
}

function myErrorHandler($errno, $errstr, $errfile, $errline) {
  switch ($errno) {
  case E_ERROR:
    $errfile = preg_replace('|^.*[\\\\/]|','',$errfile);
    echo $ERRSTR."Error in line $errline of file $errfile: [$errno] $errstr\n";
    exit;
  }
}

function shutdown () {
  global $ipsock, $rmsock, $outcount, $incount, $td, $te, $sockname, $useunix;
  
  if (connection_status() & 1) { // ABORTED
    logline ($_SERVER["REMOTE_ADDR"].": Irregular tunnel disconnect -> disconnecting server");
    logline ($_SERVER["REMOTE_ADDR"].": Sent ".$outcount." bytes, received ".$incount." bytes");
  } elseif (connection_status() & 2) { // TIMEOUT
    logline ($_SERVER["REMOTE_ADDR"].": PHP script timeout -> disconnecting server");
    logline ($_SERVER["REMOTE_ADDR"].": Sent ".$outcount." bytes, received ".$incount." bytes");
  }
  
  if ($ipsock) fclose($ipsock);
  if ($rmsock) fclose($rmsock);
}

function logline ($msg) {
  log_line_to_file(0, $msg);
}

function logdebug($msg) {
  log_line_to_file(1, $msg);
}

function logerr($msg) {
  global $ERRSTR;
  logline($msg);
  echo $ERRSTR;
  echo $msg;
}

function log_line_to_file ($debug, $msg) {
  global $LOG, $LOG_DEBUG, $MAXLOGSIZE, $LOGFILE, $LOGFILEHANDLE;
  if ($LOG && ((! $debug) || $LOG_DEBUG)) {
    $LOGFILEHANDLE=fopen ($LOGFILE, "a");
    if ($LOGFILEHANDLE) {
      fwrite ($LOGFILEHANDLE, date("d.m.Y H:i:s")." - $msg\r\n");
      $lstat=fstat($LOGFILEHANDLE);
      if ($lstat["size"]>$MAXLOGSIZE) rotatelog();
      fclose($LOGFILEHANDLE);
    }
  }
}

function rotatelog() {
  global $LOG, $MAXLOGSIZE, $LOGFILE, $LOGFILEHANDLE;
  if ($LOG) {
    fwrite ($LOGFILEHANDLE, date("d.m.Y H:i:s")." - Logfile reached maximum size ($MAXLOGSIZE)- rotating.\r\n");
    fclose ($LOGFILEHANDLE);
		rename ($LOGFILE, substr_replace($LOGFILE,md5(microtime()),-3).".log");
    $LOGFILEHANDLE=fopen ($LOGFILE, "a");
    if (!$LOGFILEHANDLE)
      $LOG=0;
    else 
      fwrite ($LOGFILEHANDLE, date("d.m.Y H:i:s")." - Opening new Logfile.\r\n");
  }
}

function create_client_socket() {
  global $CREATE_CLIENT_SOCKET_ATTEMPTS, $_REQUEST, $IPC_TMPDIR;
  
  if (!isset($_REQUEST["port"])) {
    echo $ERRSTR."Port not set.";
    return 0;
  }
  
  $port = $_REQUEST["port"];
  $useunix = in_array("unix", stream_get_transports());
  
  $retryCount = 0;
  do {
    if ($retryCount > 0) {
      usleep(10000); // 10ms
      logdebug("Attempt to create client socket #".($retryCount + 1)." ErrorCode:".$errcode." ErrorMessage:".$errmessage." Port:".$port);
    }
        
    if ($useunix) { // this is for the unix socket type
      $sockname = preg_replace('/\\\\/','/', $IPC_TMPDIR."tun$port.sock");
      $client = stream_socket_client("unix://".$sockname, $errcode, $errmessage);
    }
    else {
      $client = stream_socket_client("tcp://127.0.0.1:".$port, $errcode, $errmessage);
    }
    $retryCount = $retryCount + 1;
  } while(!$client && $retryCount < $CREATE_CLIENT_SOCKET_ATTEMPTS);

  if ($client) {
    stream_set_blocking($client, 1);
  }
  return $client;
}

function send_server_script_message($command) {
  global $_REQUEST;
  
  $client = create_client_socket();
  if (!$client) {
    logerr("Failed to create client socket");
    return FALSE;
  }
  if (fwrite($client, $command, 1) === FALSE) {
    logerr("Failed to send message to server script.");
    fclose($client);
    return FALSE;
  }
  fclose($client);
  return TRUE;
}

function increase_script_lifetime() {
  global $LIFETIME;
  if (function_exists("set_time_limit")) {
    set_time_limit($LIFETIME);
    logdebug("Script liftetime incremented with $LIFETIME");
  }
}

function write_to_socket($socket, $buffer, $count) {
  global $READ_WRITE_ATTEMPTS;
  
  $totalCount = 0;
  $retryCount = 0;
  
  do {
    if ($retryCount > 0)
      usleep(10000); // 10ms
    
    if (!$socket)
      break;
    
    $written = fwrite($socket, $buffer, $count);
    $buffer = substr($buffer, $written);
    $totalCount += $written;
    
    if ($retryCount > 0)
      logdebug("Attempt to write #".($retryCount + 1)." Write: ".$written);
    
    if ($written <= 0)
      $retryCount = $retryCount + 1;
    
  } while($totalCount < $count && $retryCount < $READ_WRITE_ATTEMPTS);
  
  if ($totalCount != $count)
    logline("ERROR: Failed to write to socket $count bytes, $totalCount actually written.");
  
  return $totalCount;
}

// reads specified byte count from socket
function read_from_socket($socket, &$buffer, $count) {
  global $READ_WRITE_ATTEMPTS;
  
  $totalCount = 0;
  $retryCount = 0;
  
  $buffer = "";
  $readBuffer;
  
  do {
    if ($retryCount > 0)
      usleep(10000); // 10ms
    
    if (!$socket)
      break;
    
    $readBuffer = fread($socket, $count);
    $read = strlen($readBuffer);
    $buffer = $buffer.$readBuffer;
    
    if ($retryCount > 0)
      logdebug("Attempt to read #".($retryCount + 1)." Read: ".$read);
    
    $totalCount += $read;
    if ($read <= 0)
      $retryCount = $retryCount + 1;
    
  } while($totalCount < $count && $retryCount < $READ_WRITE_ATTEMPTS);
  
  if ($totalCount != $count)
    logerr("Failed to read from socket $count bytes, $totalCount actually read.");
  
  return $totalCount;
}

// packet:  size of data count |                      data count | data
// lengths:             1 byte | up to 255 bytes, typically 1 - 5| up to $MaxCount
function write_data_packet($socket, &$buffer, $count) {
  
  $countLength = strlen($count);
  // write length of data count digit
  write_to_socket($socket, $countLength, 1);
  // write data count
  write_to_socket($socket, $count, $countLength);
  // write data
  $writeCount = write_to_socket($socket, $buffer, $count);
  if ($writeCount == $count)
    return $writeCount;
  else
    return 0;
}

function read_data_packet($socket, &$buffer) {
  
  // obtain data length digit length
  read_from_socket($socket, $countSize, 1);
  // read data length
  read_from_socket($socket, $readCount, $countSize);
  $expectedReadCount = $readCount;
  // read data
  $readCount = read_from_socket($socket, $buffer, $readCount);
  if ($readCount == $expectedReadCount)
    return $readCount;
  else
    return FALSE;
}

// Start of the tunnel script
$isServer = FALSE;

if (version_compare("5.0.0", phpversion())==1) die ("Only PHP 5 or above supported");
error_reporting(0);
set_error_handler("myErrorHandler");
register_shutdown_function ("shutdown");
// no-cache
Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
Header("Cache-Control: no-cache, must-revalidate");
Header("Pragma: no-cache"); // HTTP/1.1 
Header("Last-Modified: ".gmdate("D, d M Y H:i:s")."GMT");
Header("Content-Type: application/octet-stream");

ob_implicit_flush();

// Maximum bytes to read at once
$MaxReadCount = 16*1024;

// operation success identification
$OKSTR = "OK:";
$ERRSTR = "ER:";

$CONN_FILE_MAXSIZE = 100;

// Primary tunnel connection
// need the following REQUEST vars:
// a: "c"
// s: remote server name
// p: remote server port

// every operation output at least first three chars identifying the success of operation: "OK:" if succeeded, "ER:" if not.
//

if ($_REQUEST["a"]=="c") {  // run server script
  $isServer=TRUE;
  // clear log
  if ($LOG_DEBUG) {
    // truncate log file
    $logfile = fopen($LOGFILE, 'w');
    fclose($logfile);
  }

  $useunix = in_array("unix", stream_get_transports());
  $dad = $_REQUEST["s"];
  $dpo = $_REQUEST["p"];

  // open the interprocess socket
  $errno = 0;
  $errstr = "";
  $ipsock = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);

  if (!$ipsock) {
    logerr("stream_socket_server() failed: reason:".$errno." ".$errstr);
    exit;
  }

  $port = stream_socket_get_name($ipsock, false);
  $port = preg_replace('/^.*?:/','', $port);
  
  if ($useunix) { // this is for the unix socket type
    if ($ipsock) fclose($ipsock);
    $ipsock = 0;
    $sockname = preg_replace('/\\\\/','/', $IPC_TMPDIR."tun$port.sock");
    unlink($sockname);
    if (file_exists($sockname)) {
      logerr("Temporary UNIX socket exists and cannot be deleted ($sockname)");
      exit;
    }
    $ipsock = stream_socket_server("unix://".$sockname, $errno, $errstr);
    if (!$ipsock) {
      logerr("stream_socket_server() failed: reason:".$errno." ".$errstr);
      exit;
    }
  }

  stream_set_blocking($ipsock, 1);
  
  // open the remote socket
  logline("Connecting to remote  $dad: $dpo");
  $rmsock = stream_socket_client("tcp://".$dad.":".$dpo, $errno, $errstr);
  
  if (!$rmsock) {
    logerr("Failed to create remote socket at $dad: $dpo. ".$errno." ".$errstr);
    exit;
  }
  else {
    if (isset($_REQUEST["nonblock"]))
      $block = 0;
    else
      $block = 1;
    stream_set_blocking($rmsock, $block);
    logline("Connected to remote  $dad: $dpo");
  }
  
  // write connection identificator to file. Echo'ing is not appropriate in case of antiviral software, it would be blocked until script finishes
  $newConnFile = FALSE;
  $connFileMode = "a";
  
  if (file_exists($CONN_FILE)) {
    $connFile = fopen($CONN_FILE, "r");
    $lstat=fstat($connFile);
    fclose($connFile);
    if ($lstat["size"]>$CONN_FILE_MAXSIZE) {
      $connFileMode = "w";
      $newConnFile = TRUE;
    }
  }
  else {
    $newConnFile = TRUE;
  }
  
  $connFile = fopen($CONN_FILE, $connFileMode);
  if ($connFile) {
    if ($newConnFile) {
      fwrite($connFile, "<?php echo 'Devart HTTP tunnel temporary file.'; exit; ?>\r\n"); // forbid viewing this file through browser
    }
    $connectionId = str_replace("_", " ", $_REQUEST["id"]);
    fwrite ($connFile, $connectionId." ".$port."\r\n");
    fclose($connFile);
  }
  else {
    logerr("Failed to create connection temporary file.");
    exit;
  }
  
  if (function_exists("set_time_limit")) {
    set_time_limit($LIFETIME);
  }
    
  $exit = false;
  $buffer = array();
  $countBuffer = array();
  
  while (!$exit) {
    logdebug("Waiting for client...");
    $client = stream_socket_accept($ipsock);
    logline("Client accepted");
    if ($client === FALSE) {
      logline("ERROR: Bad client.");
      continue;
    }
    // read command
    $count = read_from_socket($client, $buffer, 1);
    if ($count == 0) {
      logline("Error reading client command.");
      $exit = true;
    }
    
    logdebug("Read from client ($count): ".$buffer[0]);
    
    $command = $buffer[0];
    
    increase_script_lifetime();
    
    if ($command == "x") {  // close
      logline("Shutting down on client request.");
      $exit = true;  // shutdown
    }
    else if ($command == "r") { // read
      if (!$rmsock) {
        logline("ERROR: rmsock is off");
        $exit = true;
        break;
      }
      
      $readCount = 0;
      $buffer = fread($rmsock, $MaxReadCount);
      if ($buffer === FALSE) {
        logline("ERROR: Remote server disconnected.");
        $exit = true;
        break;
      }
      else {
        $readCount = strlen($buffer);
        logline("Read from remote:($readCount)");
      }
      
      if ($readCount >= 0) {
        if ($readCount == 0)
          logline("Nothing to read from remote.");
        
        $writeCount = write_data_packet($client, $buffer, $readCount);
        logdebug("Write to client($writeCount): $buffer");
        if ($readCount > 0 && $writeCount == 0) {
          logerr("Failed to write to client.");
          $exit = true;
        }
      }
    }
    else if ($command == "s") { // select
      if (!$rmsock) {
        logline("ERROR: rmsock is off");
        $exit = true;
        break;
      }

      $readSocks = array($ipsock, $rmsock);
	    $writeSocks = NULL;
	    $exceptSocks = NULL;
      if (!stream_select($readSocks, $writeSocks, $exceptSocks, NULL)) 
        $res = $ERRSTR;
      else 
        if (in_array($rmsock, $readSocks))
          $res = $OKSTR;
        else
          $res = $ERRSTR;
      $writeCount = write_to_socket($client, $res, strlen($res));
      logdebug("Write to client($writeCount): $res");
    }
    else if ($command == "w") { // write
      if (!$rmsock) {
        logline("ERROR: rmsock is off");
        $exit = true;
        break;
      }
      $readCount = read_data_packet($client, $buffer);
      logline("Read from client: $readCount");
      if ($readCount > 0) {
        $writeCount = write_to_socket($rmsock, $buffer, $readCount);
        logdebug("Write to remote($writeCount): $buffer");
      }
    }
    else if ($command == "l") {   // increment lease time
      logline("Lease time increased.");
    }
    else if ($command == "t") {  // test connection command
      $writeCount = write_to_socket($client, $OKSTR, strlen($OKSTR));
      if ($writeCount == 0)
        $exit = true;
    }
    else {
      logline("ERROR: Unknown command: $command. Exiting.");
      $exit = true;
    }
  }
  
  logline("Server script closed.");
  exit;
}

if ($_REQUEST["a"]=="r") {  // read
  
  $client = create_client_socket();
  if (!$client) {
    logerr("Failed to connect to server script.");
    exit;
  }
  
  logdebug("Client: Reading from server script");
  
  if (write_to_socket($client, "r", 1) == 0) { // write "Read" command
    logerr("Write to server script failed.");
    fclose($client);
    exit;
  }
  
  $buffer;
  $readCount = read_data_packet($client, $buffer);
  if ($readCount === FALSE) {
    logerr("Failed to read response from server script.");
    fclose($client);
    exit;
  }
  
  $totalCount = strlen($OKSTR) + $readCount;
  
  $outputStr = $OKSTR.$buffer;
  
  header("Content-Length: ".$totalCount);
  header("Content-Type: application/octet-stream");
  
  logline("Client: Read from server $readCount");
  echo $outputStr;
  
  fclose($client);
  exit;
}

if ($_REQUEST["a"]=="s") {  // select
  
  $client = create_client_socket();
  if (!$client) {
    logerr("Failed to connect to server script.");
    exit;
  }
  
  logdebug("Client: Selecting from server script");
  
  if (write_to_socket($client, "s", 1) == 0) { // write "Select" command
    logerr("Write to server script failed.");
    fclose($client);
    exit;
  }

  $buffer;
  $count = read_from_socket($client, $buffer, 3);
  if ($count < 3) {
    logerr("Failed to read response from server script.");
    fclose($client);
    exit;
  }

  fclose($client);
  echo $buffer;
  exit;
}

if ($_REQUEST["a"]=="w") {  // write
  
  $client = create_client_socket();
  if (!$client) {
    logerr("Failed to connect to server script.");
    exit;
  }
  
  $postBody= isset($_POST['base64body'])?base64_decode($_POST['base64body']):file_get_contents("php://input");  // Retrieve RAW POST data	
  $writeData = $postBody;
  $expectedWriteCount = strlen($writeData);
  $writeCount = write_to_socket($client, "w", 1);  // indicate that this is the "Write" command
  if ($writeCount > 0)
    $writeCount = write_data_packet($client, $writeData, $expectedWriteCount);
  
  if ($writeCount == 0) {
    logerr("Write to server script failed.");
    fclose($client);
    exit;
  }
  
  logdebug("Client: Written $writeCount");
  
  fclose($client);
  echo $OKSTR;
  exit;
}

if ($_REQUEST["a"]=="x") {  // close
  
  echo $OKSTR."Shutted down.";
  send_server_script_message("x");
  exit;
}

if ($_REQUEST["a"] == "l") { // increment server script lease time
  
  if (send_server_script_message("l"))
    echo $OKSTR."Incremented server script lease time.";
  exit;
}

if ($_REQUEST["a"] == "t") { // test newly created connection
  
  $connectionId = str_replace("_", " ", $_REQUEST["id"]);
  logline($connectionId);
  $connections = file_get_contents($CONN_FILE);
  
  if ($connections === FALSE) {
    logerr("Failed to open $CONN_FILE.");
    exit;
  }
  
  $lines = explode("\r\n", $connections);
  
  // skip first line
  for($i = 1; $i < count($lines); ++$i) {
    $line = $lines[$i];
    $pos = strpos($line, $connectionId);
    if ($pos === FALSE)
      continue;
    
    if ($pos === 0) {  // starts with
      $parts = explode(" ", $line);
      if (count($parts) != 3) {
        echo "Invalid connection record";
        exit;
      }
      
      echo $OKSTR.$parts[2]."\n"."$LIFETIME\n";
      exit;
    }
  }
  
  logerr("Connection entry not found.");
  exit;
}

logerr("Invalid tunneling script parameter: ".$_REQUEST["a"]);

?>