<?php
if(!isset($_cfg['server']['port'])) $_cfg['server']['port'] = '6677';
if(!isset($_cfg['server']['listen_address'])) $_cfg['server']['listen_address'] = '192.168.1.6';
if(!isset($_cfg['server']['basedir'])) $_cfg['server']['basedir'] = '';
if(!isset($_cfg['server']['max_clients'])) $_cfg['server']['max_clients'] = '100';
//if(!isset($_cfg['server']['keep_alive'])) $_cfg['server']['keep_alive'] = true;
//if(!isset($_cfg['server']['keep_alive_timeout'])) $_cfg['server']['keep_alive_timeout'] = '15';
if(!isset($_cfg['server']['timeout'])) $_cfg['server']['timeout'] = '100';
if(!isset($_cfg['server']['sysname'])) $_cfg['server']['sysname'] = 'bibivu';
if(!isset($_cfg['server']['listen_queue'])) $_cfg['server']['listen_queue'] = '30';
if(!isset($_cfg['server']['httpd_user'])) $_cfg['server']['httpd_user'] = 'bibivu';
if(!isset($_cfg['server']['httpd_pid'])) $_cfg['server']['httpd_pid'] = '/var/run/bibivu/bibivu-httpd.pid';
if(!isset($_cfg['server']['daemon'])) $_cfg['server']['daemon'] = '0';
if(!isset($_cfg['server']['logfile'])) $_cfg['server']['logfile'] = ini_get('error_log');
if(!isset($_cfg['server']['function_log'])) $_cfg['server']['function_log'] = '';
if(!isset($_cfg['server']['verbose'])) $_cfg['server']['verbose'] = false;
if(phpversion()>=5){
require_once realpath(dirname(__FILE__).'/php5x/'.basename(__FILE__));
}else{
require_once realpath(dirname(__FILE__).'/php4x/'.basename(__FILE__));
}
class _bib_server extends _bib_server_vars{
function _bib_server($_cfg){
$this->_cfg['basedir'] = $_cfg['server']['basedir'];
$this->_cfg['port'] = $_cfg['server']['port'];
$this->_cfg['listen_address'] = $_cfg['server']['listen_address'];
$this->_cfg['max_clients'] = $_cfg['server']['max_clients']>2?$_cfg['server']['max_clients']:3;
// $this->_cfg['keep_alive'] = $_cfg['server']['keep_alive'];
// $this->_cfg['keep_alive_timeout'] = $_cfg['server']['keep_alive_timeout'];
$this->_cfg['timeout'] = $_cfg['server']['timeout'];
$this->_cfg['sysname'] = $_cfg['server']['sysname'];
$this->_cfg['httpd_user'] = $_cfg['server']['httpd_user'];
$this->_cfg['httpd_pid'] = $_cfg['server']['httpd_pid'];
$this->_cfg['daemon'] = $_cfg['server']['daemon'];
$this->_cfg['function_log'] = $_cfg['server']['function_log'];
$this->_cfg['listen_queue'] = $_cfg['server']['listen_queue'];
$this->_cfg['logfile'] = $_cfg['server']['logfile'];
$this->_cfg['verbose'] = $_cfg['server']['verbose'];
$error = array();
if ((int)substr(preg_replace("/[^\d]*/", '', phpversion()), 0, 3) < 410)
$error[] = "PHP Error: You must be running PHP version 4.1 or higher.\n";
if (!extension_loaded('posix'))
$error[] = "PHP Error: The POSIX module is required (configure PHP '--enable-posix')\n";
if (!extension_loaded('pcntl'))
$error[] = "PHP Error: The Process Control module is required (configure PHP '--enable-pcntl')\n";
if (!extension_loaded('sockets'))
$error[] = "PHP Error: The Sockets module is required (configure PHP '--enable-sockets')\n";
if (!empty($error)) {
$this->write_log(2,0,'bibivu-httpd',implode("\n", $error),0,1);
exit;
}
$this->_init();
}
function write_log($level, $id, $extra, $log_msg, $remote_ip) {
if (!$this->_cfg['daemon']) {
$msg = '('.$extra.') '.$log_msg."\n";
if (php_sapi_name() == 'cli') {
fwrite(STDERR,$msg);
} else {
echo $msg;
}
}
// file system
if ($this->_cfg['logfile'] != '') {
// open file as r/w & point at the end of the file
$logfile=fopen($this->_cfg['logfile'], 'a+');
fwrite($logfile,$level.'|'.$id.'|'.$extra.'|'.$log_msg.'|'.$remote_ip.'|'.date('YmdHis')."\n");
fclose ($logfile);
}
if($this->_cfg['function_log']!='' && is_callable($this->_cfg['function_log'])){
call_user_func_array($this->_cfg['function_log'],func_get_args());
}
}
function _init(){
/* Run from root only */
if (posix_getuid() != 0) {
$this->write_log(1,0,'bibivu-httpd',"This program must be started as root (uid 0).\n",0);
exit();
}
/* check if the user that we need to be Exist, and get the info*/
if (!($this->_cfg['user'] = posix_getpwnam($this->_cfg['httpd_user'])) || !($group = posix_getgrgid($this->_cfg['user']['gid']))) {
$this->write_log(0,0,'bibivu-httpd','User '.$this->_cfg['httpd_user'].' does not exist.',0);
exit();
}
//starting the socket
$this->start_server();
/* change to a user with lower permissions than root. We should do this as soon as possible. */
// pcntl_setpriority(-20);
posix_setgid($this->_cfg['user']['gid']);
posix_setuid($this->_cfg['user']['uid']);
/* define max length to use when reading from sockets/files */
$this->MAX_LENGTH = 2*4096;
/* php 4.3 pcntl doesn't work without ticks declaration */
declare(ticks=1);
/* which functions should we run when we catch a posix signal
* stop_server() before shutdown, or run reload_server() on SIGHUP */
pcntl_signal(SIGHUP, array($this,'reload_server')); // sig# 1
pcntl_signal(SIGINT, array($this,'stop_server')); // sig# 2
pcntl_signal(SIGTERM, array($this,'stop_server')); // sig# 15
/* increase our memory limit to 16 megabytes (standard is 8M) */
ini_set('memory_limit', '16M');
/* CLI automatically sets time limit to unlimited, but we need this for CGI */
set_time_limit(0);
/* fork the script into a background process */
if ($this->_cfg['daemon'] === 1) {
$pid = pcntl_fork();
if ($pid == -1) {
$this->write_log(0,0,'bibivu-httpd','Failed to spawn a daemon',0,1);
exit(0);
} elseif ($pid) {
/* kill parent and return success */
$this->write_log(0,0,'bibivu-httpd','Daemon started, killing the Main process',0,1);
exit(0);
}
/* become session leader / detach from the controlling terminal */
if (!posix_setsid()) {
$this->write_log(0,0,'bibivu-httpd',"Could not detach from terminal",0,1);
exit(1);
} else
$this->write_log(2,0,'bibivu-httpd',"bibivu-httpd daemon has been started.",0,1);
}
$this->set_pid_file(getmypid());
$this->http_status_map=array(
'ok' => '200 OK',
'redirect' => '302 Found',
'forbidden' => '403 Forbidden',
'not found' => '404 Not Found',
'default' => '400 Bad Request',
);
/* set some initial status variables */
$this->httpd_status['started'] = $this->get_microtime();
$this->httpd_status['hits'] = 0;
$this->httpd_status['connections'] = 0;
$this->httpd_status['sent'] = 0;
$this->httpd_status['child'] = 0;
/* initialize the client socket array */
// $this->csocket = array();
$this->cdata = array();
}
function doit(){
//this function needs to check for a connection via socket
//and when receive a new connection, fork the script and load the required file
while(true){
//check for any incoming comincations
//close any old child connections
if (isset($this->csocket) && is_resource($this->csocket)) {
//there is a connection running
if (($this->get_microtime() - $this->cdata['established']) > $this->_cfg['timeout']) {
//timeout
$this->close_client();
exit(0); //exit the child
}
}
$set_r = array($this->lsocket);
$ready = @socket_select($set_r, $set_w = NULL, $set_e = NULL, $to_sec = 1);
// if ($ready == 0 || $ready === false) {
/* there are zero updates on the listening socket, or we caught a posix signal */
// continue;
// }
/* if there is an update on the listening socket create a new connection */
if (($ready != 0 && $ready !== false) && in_array($this->lsocket, $set_r)) {
// if (in_array($this->lsocket, $set_r)) {
//now I fork
$this->httpd_status['connections']++;
$this->httpd_status['child']++;
if($this->_cfg['verbose'])
$this->write_log(0,0,'bibivu-httpd','Fork child: '.$this->httpd_status['child'].'/'.$this->_cfg['max_clients'],0,1);
$pid = pcntl_fork();
if ($pid == -1) {
$this->write_log(0,0,'bibivu-httpd','Failed to spawn a daemon',0,1);
exit(1);
} elseif ($pid) {
//wait for a new connection
$ret = 0;
if($this->httpd_status['child']>$this->_cfg['max_clients']){
$ret = pcntl_wait($status, WUNTRACED);
$this->httpd_status['child']--;
}
usleep(10000);
} else {
$this->new_client();
//exit the child
// if($this->_cfg['verbose'])
// $this->write_log(0,0,'bibivu-httpd','::Exiting: '.posix_getpid(),0,1);
exit(0);
}
}
$ret = pcntl_wait($status, WNOHANG);
if($ret>0 && $this->_cfg['verbose']){
$this->httpd_status['child']--;
$this->write_log(0,0,'bibivu-httpd','::::back from: '.$ret.' - '.$this->httpd_status['child'].'/'.$this->_cfg['max_clients'],0,1);
}
}
}
function new_client(){
set_time_limit(1); //two seconds to open the connection
if($this->open_client()){
set_time_limit(5);
$this->talk_client();
$this->close_client();
}
}
function talk_client(){
/* read data from the client into a buffer. if read fails close the connection */
$this->cdata['clength'] = 0;
$this->cdata['hlength'] = 0;
while($data = @socket_read($this->csocket, $this->MAX_LENGTH)) {
$this->cdata['buffer'] .= $data;
/* check for http header double carriage return / line feed */
if (($this->cdata['hlength'] = strpos($this->cdata['buffer'], "\r\n\r\n")) !== false) {
$this->cdata['hlength'] += 4;
/* According to what I've read in the HTTP RFC's (rfc1945, rfc2068, rfc2616)
* all client requests with a body, i.e. POSTs, are REQUIRED to have the
* Content-Length header. This would also apply for HTTP 1.1, despite
* chunking. So I guess we'll just make sure all the data arrived here. */
if ($this->cdata['clength'] = stristr($this->cdata['buffer'], 'Content-Length:')) {
$this->cdata['clength'] = substr($this->cdata['clength'], 0, strpos($this->cdata['clength'], "\r\n"));
$this->cdata['clength'] = explode(':', $this->cdata['clength']);
$this->cdata['clength'] = trim($this->cdata['clength'][1]);
}
if (strlen($this->cdata['buffer']) >= ($this->cdata['hlength'] + $this->cdata['clength'])) {
//received everything
break;
}
} else {
//header not received
// return;
}
/* If the full body content hasn't arrived yet, skip the next part. */
}
/* this is where we parse the request headers */
$request = $this->parse_request($this->cdata['buffer']);
/* request has been parsed, erase the buffer. */
$this->cdata['buffer'] = '';
unset($this->cdata['hlength']);
unset($this->cdata['clength']);
/* build response array based on the request array */
$response = $this->build_response($request);
/* then send our response */
$this->send_response($response, $request);
/* close the connection if we're done */
//log which file was requested
if($this->_cfg['verbose'])
$this->write_log(3,0,$this->cdata['remote_ip'],'file requested: '.str_replace($this->_cfg['basedir'],'',$response['translated_file']).' at '.date('g:i:s a', $this->cdata['established']),0);
if ($response['connection_close']) {
$this->close_client();
exit(0);
}
}
function start_server() {
/* open a socket to listen for connections on */
/* create a listening socket */
if (!$this->lsocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) {
$this->write_log(0,0,'bibivu-httpd','Failed to create a listening socket because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
exit();
}
/* able to reuse our listening socket */
if (!socket_set_option($this->lsocket, SOL_SOCKET, SO_REUSEADDR, 1)) {
$this->write_log(0,0,'bibivu-httpd','Failed to make listening address reusable because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
exit();
}
/* setting other options */
if (!socket_set_option($this->lsocket, SOL_SOCKET, SO_SNDTIMEO, array('sec'=>10, 'usec'=>100)) || //time before timeout
!socket_set_option($this->lsocket, SOL_SOCKET, SO_SNDBUF, 100*1024)
) {
$this->write_log(0,0,'bibivu-httpd','Failed to set options for the socket: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
exit();
}
/* make our socket non-blocking */
if (!socket_set_block($this->lsocket)) {
$this->write_log(0,0,'bibivu-httpd','Failed to set listening socket to blocking because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
exit();
}
/* bind the listening socket to the specified address and port */
if (!socket_bind($this->lsocket, $this->_cfg['listen_address'], $this->_cfg['port'])) {
$this->write_log(0,0,'bibivu-httpd','Failed to bind to '.$this->_cfg['listen_address'].' on port '.$this->_cfg['port']." because: ".socket_strerror(socket_last_error($this->lsocket)),0,1);
exit();
}
/* start listening on our socket */
if (!@socket_listen($this->lsocket, $this->_cfg['listen_queue'])) {
$this->write_log(0,0,'bibivu-httpd','Failed to start listening on socket because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
exit();
}
$this->write_log(2,0,'bibivu-httpd',"bibivu-httpd daemon has been started.",0);
return $this->lsocket;
}
function stop_server($signo='') {
/* shutdown the listening socket and close all client sockets. */
// shut down client sockets
$this->close_client();
// shut down listening socket
@socket_shutdown($this->lsocket, 2);
@socket_close($this->lsocket);
if (file_exists($this->_cfg['httpd_pid']) && !unlink($this->_cfg['httpd_pid'])) {
$this->write_log(1,0,'bibivu-httpd','Unable to remove '.$this->_cfg['httpd_pid'],0);
}
$this->write_log(2,0,'bibivu-httpd',"bibivu-httpd daemon has been stopped.",0);
exit;
}
function reload_server($signo='') {
/* on SIGHUP we reload the server configuration */
/* reset configuration */
//still to finish..
//now doesn't wotk
$this->_bib_server($GLOBALS['_cfg']);
$this->write_log(2,0,'bibivu-httpd',"SIGHUP received, server was reloaded.",0);
}
function open_client() {
/* open a connection between the server and a client machine.*/
/* find an open slot and fill it with the new connection.*/
if (!$this->csocket = @socket_accept($this->lsocket)) {
$this->write_log(1,0,'bibivu-httpd','Client failed to connect because: '.socket_strerror(socket_last_error($this->lsocket)).'['.socket_last_error($this->lsocket).']',0);
exit(1);
return false;
}
/* get ip and port of the client and local server */
if (!@socket_getpeername($this->csocket, $this->cdata['remote_ip'], $this->cdata['remote_port'])) {
$this->close_client();
exit(1);
return false;
}
if (!@socket_getsockname($this->csocket, $this->cdata['local_ip'], $this->cdata['local_port'])) {
$this->close_client();
exit(1);
return false;
}
/* assign initial client data */
$this->cdata['established'] = $this->get_microtime();
$this->cdata['buffer'] = '';
if($this->_cfg['verbose'])
$this->write_log(3,0,$this->cdata['remote_ip'],'connected to '.$this->cdata['local_ip'].' on port '.$this->cdata['local_port'].' at '.date('g:i:s a', $this->cdata['established']),0);
return true;
}
function close_client() {
/* close a connection between the server and a client machine. */
/* if still connected shutdown and close the socket */
// if (isset($this->csocket) && is_resource($this->csocket)) {
@socket_shutdown($this->csocket, 2);
@socket_close($this->csocket);
// } else {
// $this->write_log(1,0,$this->cdata['remote_ip'],'Socket not found while attempting to close the connection',0);
// }
if (isset($this->csocket)) {
unset($this->csocket);
}
/* clean out data associated with the socket */
if (!empty($this->cdata)) {
if($this->_cfg['verbose'])
$this->write_log(3,0,$this->cdata['remote_ip'],'disconnected from '.$this->cdata['local_ip'].' on port '.$this->cdata['local_port'].' at '.date('g:i:s a', $this->get_microtime()),0);
unset($this->cdata);
}
/*now I can exit the child*/
// exit(0);
}
function parse_request(&$data) {
/******************************************************************
* take the raw request data from a client and return something like:
*
* $request
* (
* [method] => GET
* [protocol] => HTTP/1.1
* [file] => test.php
* [connection_close] => false
* [get]
* (
* [key1] => val1
* [key2] => val2
* )
* )
*******************************************************************/
$data = explode("\r\n\r\n", $data, 2);
$header = explode("\r\n", $data[0]);
/* if using ssl grab the custom SSL_Remote_IP: header from stunnel.
To make sure it isn't faked by the client we only accept the first instance
of this header, directly on 'top'.
*/
// if ($this->_cfg['ssl'])
// $request['remote_ip'] = trim(array_shift($header));
$request_line = explode(' ', $header[0]);
unset($header[0]);
/* make sure it's a valid HTTP request */
if (!isset($request_line[2]) || strpos($request_line[2], "HTTP/") === false) {
return false;
}
$request['method'] = $request_line[0];
$request['protocol'] = $request_line[2];
$request['cookie'] = array();
$request['connection_close'] = false;
/* Here we're going through the headers one by one
* looking for the few that we actually care about */
foreach ($header as $val) {
if (stristr($val, 'User-Agent:')) {
/* pass user-agent string on to use
* with the $_SERVER variable */
$val = substr($val, 11);
$request['user_agent'] = trim($val);
} elseif (stristr($val, 'Accept-Language:')) {
/* pass accept-language string on to
* use with the $_SERVER variable */
$val = substr($val, 16);
$request['language'] = trim($val);
} elseif (stristr($val, 'Cookie:')) {
/* parse any cookies */
$val = substr($val, 7);
$request['cookie'] = $this->parse_query(trim($val), '; ');
continue;
} elseif (stristr($val, 'Connection: close')) {
/* see if they want us to close the connection */
$request['connection_close'] = true;
continue;
} elseif (stristr($val, 'Content-Type:')) {
/* get content-type */
$val = substr($val, 13);
$request['content_type'] = trim($val);
continue;
}
}
/* save requested URI for $_SERVER */
$request['uri'] = $request_line[1];
/* split any GET querries from the requested path */
$path_query = explode('?', $request_line[1]);
$request['file'] = $path_query[0];
/* save query string for $_SERVER */
$request['query_string'] = '';
if (isset($path_query[1])) {
$request['query_string'] = $path_query[1];
}
// initialize file, post, and get data arrays
$request['get'] = array();
$request['post'] = array();
$request['files'] = array();
/* determine if there are POST or GET queries, and parse them */
if (isset($path_query[1]) && strpos($path_query[1], '=') !== false) {
$request['get'] = $this->parse_query($path_query[1]);
}
// check for included request body
if (isset($data[1]) && isset($request['content_type'])) {
if (strpos($request['content_type'], 'application/x-www-form-urlencoded') !== false) {
// normal form
$request['post'] = $this->parse_query($data[1]);
} elseif (strpos($request['content_type'], 'multipart/form-data') !== false) {
// rfc 2388 covers multipart/form-data
$boundary = explode(';', $request['content_type']);
$boundary = explode('=', $boundary[1]);
$boundary = trim($boundary[1]);
$this->parse_multi_part($data[1], $boundary, $request['post'], $request['files']);
}
}
return $request;
}
function build_response($request) {
/******************************************************************
* build the array that we will base our response on:
*
* $response
* (
* [status] => ok
* [protocol] => HTTP/1.1
* [translated_file] => /home/bibivu/web/index.php
* [file_type] => php
* [connection_close] => false
* )
*******************************************************************/
$response['connection_close'] = true;
/*
$response['connection_close'] = false;
// the http 1.1 protocol uses keep alive connections. We should
// close a socket only when it has timed out, or one of the
// following conditions is met.
if (!$request) {
// we received a request we don't understand
$response['connection_close'] = true;
} elseif ($request['protocol'] !== 'HTTP/1.1') {
// they are using a http 1.0 or unknown protocol
$response['connection_close'] = true;
} elseif ($request['connection_close']) {
// they asked us to close it with a Connection: close header
$response['connection_close'] = true;
} elseif (!$this->_cfg['keep_alive']) {
// keep alive is turned off in the configuration file
$response['connection_close'] = true;
}
*/
/* if the request we got is invalid respond as such */
if (!$request) {
$response['protocol'] = 'HTTP/1.1';
$response['status'] = 'malformed';
return $response;
}
if ($request['protocol'] == 'HTTP/1.1') {
/* If they're using HTTP version 1.1 then respond with 1.1 */
$response['protocol'] = 'HTTP/1.1';
} else {
/* If they're using any other version drop to 1.0 compatibility */
$response['protocol'] = 'HTTP/1.0';
}
/* extremely basic security check, remove any ../ from requested file
* so they can't ascend into forbidden directories */
$response['translated_file'] = str_replace('../', '', $request['file']);
/* remove leading/trailing slash(es) */
$response['translated_file'] = trim($response['translated_file'], '/');
/* append default path from config file */
$response['translated_file'] = $this->_cfg['basedir'].$response['translated_file'];
/* load index.php for directories */
if (is_readable($response['translated_file']) && is_dir($response['translated_file'])) {
$response['translated_file'] = rtrim($response['translated_file'], '/').'/index.php';
}
/* Get a file's extension by grabbing the characters
* from after the last period to the last character. */
$response['file_type'] = substr($response['translated_file'], (strrpos($response['translated_file'], '.') + 1));
/* we're working with a lot of files that may change */
clearstatcache();
/* see if requested file exists and can be served */
if (basename($response['translated_file']) == '--status') {
$response['status'] = 'status';
} elseif (!file_exists($response['translated_file'])) {
$response['status'] = 'not found';
} elseif (!is_readable($response['translated_file'])) {
$response['status'] = 'forbidden';
} elseif (!is_dir($response['translated_file'])) {
$response['status'] = 'ok';
/* change to file's working directory */
chdir(dirname($response['translated_file']));
}
return $response;
}
function build_headers($response) {
/* compile an appropriate list of headers to send to client */
/* Status line */
if (!in_array($response['status'], array_keys($this->http_status_map))) {
$response['status']='default';
}
$headers = $response['protocol'].' '.$this->http_status_map[$response['status']]."\r\n";
/* Date line, Sat, 06 Sep 2014 23:50:08 GMT*/
$headers .= 'Date: '.gmdate("D, d M Y H:i:s T")."\r\n";
/* Server line */
$headers .= "Server: bibivu-httpd - web://cp \r\n";
$headers .= 'X-Powered-By: PHP/'.phpversion()."\r\n";
/* Add content length */
$headers .= 'Content-length: '.$response['content_length']."\r\n";
/* Connection close header */
if ($response['connection_close'] && $response['protocol'] == 'HTTP/1.1') {
$headers .= "Connection: close\r\n";
}
/* MIME type */
if ($response['status'] !== 'ok') {
$headers .= "Content-type: text/html\r\n";
} else {
$headers .= 'Content-type: '.$this->mime_type($response['file_type'])."\r\n";
}
$headers .= "\r\n";
return $headers;
}
function send_response($response, $request) {
/* handle the client's request */
if ($response['status'] == 'ok' && $response['file_type'] == 'php') {
/* if the requested resource is a php file. */
/*
$pid = pcntl_fork();
if ($pid == -1) {
$this->write_log(0,0,$this->cdata['remote_ip'],'Failed to spawn a daemon',0,1);
exit;
} elseif ($pid) {
exit;
}
*/
$content = $this->load_page($this->cdata, $response, $request);
// add up the content-length
$response['content_length'] = intval(strlen($content));
$headers = $this->build_headers($response);
if (@socket_write($this->csocket, $headers.$content) === false) {
$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
$this->close_client();
exit();
}
// echo socket_strerror(socket_last_error($this->csocket))."\n";
} elseif ($response['status'] == 'ok') {
/* if the requested resource is a normal file and can be returned */
$response['content_length'] = filesize($response['translated_file']);
// $response['content_length'] = strlen($response['translated_file']);
$headers = $this->build_headers($response);
if (@socket_write($this->csocket[$slot], $headers) === false) {
$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
$this->close_client();
exit();
} else {
$this->socket_write_file($response['translated_file'], $this->cdata);
}
} elseif ($response['status'] == 'forbidden') {
/* if the requested resource isn't readable */
$content = "<html><head>\r\n";
$content .= "<title>403 Forbidden</title>\r\n";
$content .= "</head><body>\r\n";
$content .= "<h1>Forbidden</h1>\r\n";
$content .= "<p>You do not have permission to access $request[file].</p>\r\n";
$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\r\n";
$content .= "</body></html>";
$response['content_length'] = strlen($content);
$headers = $this->build_headers($response);
if (@socket_write($this->csocket, $headers.$content) === false) {
$this->write_log(0,0,$this->cdata['remote_ip'],"Failed to send because: ".socket_strerror(socket_last_error($this->csocket[$slot])),0);
$this->close_client();
exit();
}
} elseif ($response['status'] == 'not found') {
/* if the requested resource doesn't exist */
$content = "<html><head>\r\n";
$content .= "<title>404 Not Found</title>\r\n";
$content .= "</head><body>\r\n";
$content .= "<h1>Not Found</h1>\r\n";
$content .= "<p>The requested URL $request[file] was not found.</p>\r\n";
$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\r\n";
$content .= "</body></html>";
$response['content_length'] = strlen($content);
$headers = $this->build_headers($response);
if (@socket_write($this->csocket, $headers.$content) === false) {
$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
$this->close_client();
exit();
}
} elseif ($response['status'] == 'status') {
/* they requested server status */
/* convert uptime into days, hours, minutes, and seconds */
$uptime = $this->get_microtime() - $this->httpd_status['started'];
$uptime_str = '';
if (($days = floor($uptime / 86400)) > 0) $uptime_str .= "$days days";
if (($hours = floor(($uptime % 86400) / (3600))) > 0) $uptime_str .= " $hours hours";
if (($mins = floor((($uptime % 86400) % 3600) / 60)) > 0) $uptime_str .= " $mins min";
if (($secs = floor((($uptime % 86400) % 3600) % 60)) > 0) $uptime_str .= " $secs sec";
/* convert bytes sent into terra, giga, mega, kilo, or bytes */
$sent_str = ($this->httpd_status['sent']);
// Count number of code lines in web://cp
/*
$numlines = 0;
$dir = $this->dirlist($this->_cfg['basedir'].'/server', 'file');
do {
if (strstr(current($dir),'.php') && !strstr(current($dir),'phpmyadmin') && !strstr(current($dir),'phppgadmin') && !strstr(current($dir),'errors'))
$numlines += count(file(current($dir)));
} while (next($dir));
$dir = $this->dirlist($this->_cfg['basedir'].'/web', 'file');
do {
if (strstr(current($dir),'.php') && !strstr(current($dir),'phpmyadmin') && !strstr(current($dir),'phppgadmin') && !strstr(current($dir),'errors'))
$numlines += count(file(current($dir)));
} while (next($dir));
*/
$content = "<html><head>\n";
$content .= "<title>web://bibivu status</title>\n";
$content .= "</head><body>\n";
$content .= "<h1>web://bibivu status</h1>\n";
// $content .= "<b>Started:</b> ".date('r', $this->httpd_status['started'])."<br />\n";
$content .= "<b>Uptime:</b> $uptime_str<br />\n";
$content .= "<b>on IP:</b> ".$this->_cfg['listen_address'].' : '.$this->_cfg['port']."<br />\n";
// $content .= "<b>Data Served:</b> $sent_str<br />\n";
// $content .= "<b>Hits:</b> ".$this->httpd_status['hits']."<br />\n";
$content .= "<b>Connections:</b> ".$this->httpd_status['connections']."<br />\n";
$content .= "<b>Childrens:</b> ".$this->httpd_status['child'].'/'.$this->_cfg['max_clients']."<br />\n";
$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\n";
$content .= "</body></html>";
$response['content_length'] = strlen($content);
$headers = $this->build_headers($response);
if (@socket_write($this->csocket, $headers.$content) === false) {
$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
$this->close_client();
exit();
}
} else {
/* if we had an invalid request */
$content = "<html><head>\r\n";
$content .= "<title>400 Bad Request</title>\r\n";
$content .= "</head><body>\r\n";
$content .= "<h1>Bad Request</h1>\r\n";
$content .= "<p>You sent a malformed header. Goodbye.</p>\r\n";
$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\r\n";
$content .= "</body></html>";
$response['content_length'] = strlen($content);
$headers = $this->build_headers($response);
if (@socket_write($this->csocket, $headers.$content) === false) {
$this->write_log(0,0,$this->cdata['remote_ip'],'Failed to send to because: '.socket_strerror(socket_last_error($this->csocket)),0);
$this->close_client();
exit();
}
}
/* update internal statistics */
$this->httpd_status['hits']++;
$this->httpd_status['sent'] += strlen($headers) + $response['content_length'];
// log this request:
// $this->log_http_request($this->cdata, $response, $request);
}
function load_page($client, $response, $request) {
/* Interpret a seperate php file and return it in a variable. */
/* copy globals, work around for not being able to use $GLOBALS directly due to recursion */
$_SERVER = array();
$_REQUEST = array();
/* pass HTTP_SERVER_VARS array to subscript */
$_SERVER['HTTP_HOST'] = $this->_cfg['sysname'];
$_SERVER['HTTP_USER_AGENT'] = (isset($request['user_agent']) ? $request['user_agent'] : '');
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = (isset($request['language']) ? $request['language'] : '');
$_SERVER['SERVER_SOFTWARE'] = 'bibivu-httpd php/'.phpversion();
$_SERVER['BIBIVU-HTTPD'] = true;
$_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
$_SERVER['SERVER_ADDR'] = $client['local_ip'];
$_SERVER['SERVER_PORT'] = $this->_cfg['port'];
$_SERVER['REMOTE_ADDR'] = $client['remote_ip'];
$_SERVER['REMOTE_PORT'] = $client['remote_port'];
$_SERVER['SERVER_PROTOCOL'] = $response['protocol'];
$_SERVER['DOCUMENT_ROOT'] = $this->_cfg['basedir'];
$_SERVER['SCRIPT_FILENAME'] = $response['translated_file'];
$_SERVER['REQUEST_METHOD'] = $request['method'];
$_SERVER['QUERY_STRING'] = $request['query_string'];
$_SERVER['REQUEST_URI'] = $request['uri'];
$_SERVER['SCRIPT_NAME'] = substr($response['translated_file'], strlen($this->_cfg['basedir'])-1);
$_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'];
// $HTTP_SERVER_VARS = $_SERVER;
/* pass cookie array to subscript */
$_COOKIE = $request['cookie'];
// $HTTP_COOKIE_VARS = $_COOKIE;
$_REQUEST = array_merge($_REQUEST, $_COOKIE);
/* pass post array to subscript */
$_POST = $request['post'];
// $HTTP_POST_VARS = $_POST;
$_REQUEST = array_merge($_REQUEST, $_POST);
/* pass get array to subscript */
$_GET = $request['get'];
// $HTTP_GET_VARS = $_GET;
$_REQUEST = array_merge($_REQUEST, $_GET);
/* pass HTTP_ENV_VARS array to subscript */
// $HTTP_ENV_VARS = $_ENV;
/* pass files array to subscript */
$_FILES = $request['files'];
// $HTTP_POST_FILES = $_FILES;
/* set error reporting to the php.ini default */
restore_error_handler();
$default_error_level = get_cfg_var('error_reporting');
$error_level = error_reporting($default_error_level);
/* evaluate the requested script and cache it to a variable. */
if($this->_cfg['verbose'])
$this->write_log(3,0,$this->cdata['remote_ip'],'PHP PAGE:'.$response['translated_file'],0);
ob_start();
$result = include $response['translated_file'];
$page = ob_get_contents();
while(ob_get_level()>0){
ob_end_clean();
}
/* restore error reporting */
set_error_handler(array($this,'error_handler'));
error_reporting($error_level);
/* See if there is anything returned from running the script. */
if (strlen($result) > 1) {
/* if so, print it out */
$page .= $result;
}
/* remove temporary files that were uploaded by client */
foreach($_FILES as $val) {
/* make sure that this script can still access the file */
if (is_writable($val['tmp_name'])) {
unlink($val['tmp_name']);
}
}
/* return header info by reference */
// $header_redirect = (isset($GLOBALS['header_redirect']) ? $GLOBALS['header_redirect'] : false);
// $send_headers = (isset($GLOBALS['send_headers']) ? $GLOBALS['send_headers'] : false);
return $page;
}
function socket_write_file($file, $client) {
/* send a file line by line. */
$fp = fopen($file, 'rb');
while(!feof($fp)) {
if (@socket_write($this->csocket, fread($fp, $this->MAX_LENGTH)) === false) {
$this->write_log(0,0,$this->cdata['remote_ip'],'Failed to send to '.$client['remote_ip'].' because: '.socket_strerror(socket_last_error($this->csocket)),0);
$this->close_client();
exit();
}
}
fclose($fp);
}
function mime_type($file_type) {
/* return the correct mime type based on the file type. */
switch($file_type) {
case 'php':
case 'html':
return 'text/html';
case 'jpg':
case 'jpeg':
return 'image/jpeg';
case 'png':
return 'image/png';
case 'gif':
return 'image/gif';
default:
return 'text/plain';
}
}
function parse_query($query_line, $delimiter='&') {
/* take a query line from a POST, GET or COOKIE and return an
* array holding each query name and value */
if ($delimiter !== '&') {
$query_line = str_replace($delimiter, '&', $query_line);
}
parse_str(trim($query_line), $result);
return $result;
}
function parse_multi_part(&$body, $boundary, &$post, &$files) {
/* This function will parse posted multipart form data
* into temporary files or post queries which will be
* passed back by reference */
// split the multiple parts into an array
$body = preg_split("/(\r\n)?--$boundary(--)?(\r\n)?/", $body, -1, PREG_SPLIT_NO_EMPTY);
// cycle through the parts and parse each one
foreach($body as $part) {
$part = explode("\r\n\r\n", $part);
if (count($part) < 2) {
$part[1] = '';
}
// separate the part's headers into an array
$part[0] = explode("\r\n", $part[0]);
foreach($part[0] as $val) {
$arr = explode(':', $val, 2);
if (count($arr) > 1) {
$mime_header[strtolower($arr[0])] = trim($arr[1]);
}
}
// separate all attributes of the content disposition into an array
if (isset($mime_header['content-disposition'])) {
$attribute = explode(';', $mime_header['content-disposition']);
foreach ($attribute as $val) {
$arr = explode('=', trim($val));
if (count($arr) > 1) {
$cont_disp[strtolower($arr[0])] = trim($arr[1], '"');
}
}
}
// check and see if this part is a file
if (!empty($cont_disp['filename'])) {
// generate a temporary name and get the size
$tmp_name = '/tmp/php'.uniqid(getmypid());
$size = strlen($part[1]);
// add it and it's associated data to the files array that we're going to need
$files[$cont_disp['name']]['name'] = $cont_disp['filename'];
$files[$cont_disp['name']]['tmp_name'] = $tmp_name;
$files[$cont_disp['name']]['size'] = $size;
if (isset($mime_header['content-type'])) {
$files[$cont_disp['name']]['type'] = $mime_header['content-type'];
}
// save the uploaded file to the tmp directory
// we will delete the file when execution of this
// request is finished.
if ($fp = fopen($tmp_name, 'w')) {
fwrite($fp, $part[1], $size);
fclose($fp);
}
// if it's not a file it must be a posted query
// } elseif (!empty($part[1])) {
} else{
if (isset($cont_disp['name'])) {
$post[$cont_disp['name']] = $part[1];
}
}
}
}
function parse_multi_part_old(&$body, $boundary, &$post, &$files) {
/* This function will parse posted multipart form data
* into temporary files or post queries which will be
* passed back by reference */
// split the multiple parts into an array
$body = preg_split('/(\r\n)?--'.$boundary.'(--)?(\r\n)?/', $body, -1, PREG_SPLIT_NO_EMPTY);
// $this->write_log(2,0,'bibivu-httpd',$body."\n".'--'.$boundary,0,1);
// $body = explode('--'.$boundary, $body);
// cycle through the parts and parse each one
foreach($body as $part) {
$part = ltrim($part);
$part = explode("\r\n\r\n", $part);
if (count($part) < 2) {
if($part[0]==0){
//nothing to do here
continue;
}
$part[1] = '';
} else {
$part[1] = substr($part[1],0,-2);
}
// separate the part's headers into an array
$part[0] = explode("\r\n", $part[0]);
foreach($part[0] as $val) {
$arr = explode(':', $val, 2);
if (count($arr) > 1) {
$mime_header[strtolower($arr[0])] = trim($arr[1]);
}
}
// separate all attributes of the content disposition into an array
if (isset($mime_header['content-disposition'])) {
$attribute = explode('; ', $mime_header['content-disposition']);
foreach ($attribute as $val) {
$arr = explode('=', trim($val));
if (count($arr) > 1) {
$cont_disp[strtolower($arr[0])] = trim($arr[1], '"');
}
}
}
// check and see if this part is a file
if (!empty($cont_disp['filename'])) {
// generate a temporary name and get the size
$tmp_name = '/tmp/php'.uniqid(getmypid());
$size = strlen($part[1]);
// add it and it's associated data to the files array that we're going to need
$files[$cont_disp['name']]['name'] = $cont_disp['filename'];
$files[$cont_disp['name']]['tmp_name'] = $tmp_name;
$files[$cont_disp['name']]['size'] = $size;
if (isset($mime_header['content-type'])) {
$files[$cont_disp['name']]['type'] = $mime_header['content-type'];
}
// save the uploaded file to the tmp directory
// we will delete the file when execution of this
// request is finished.
if ($fp = fopen($tmp_name, 'w')) {
fwrite($fp, $part[1], $size);
fclose($fp);
}
// if it's not a file it must be a posted query
} elseif (!empty($part[1])) {
//need to split the arrays
if (isset($cont_disp['name'])){
if(strpos($cont_disp['name'],'[')!==false){
$start = strpos($cont_disp['name'],'[');
$end = strpos($cont_disp['name'],']');
$len = $end-$start;
if(strpos($cont_disp['name'],'[]')!==false){
$post[substr($cont_disp['name'],0,$start)][] = $part[1];
} else {
$index = substr($cont_disp['name'],$start,$len);
}
} else {
//normal variable
$post[$cont_disp['name']] = $part[1];
}
}
}
}
// var_dump($post);
}
function set_pid_file($pid) {
/* handle pid file details */
/* check for an existing pid file */
if (file_exists($this->_cfg['httpd_pid']) && !is_dir($this->_cfg['httpd_pid'])) {
$fp = fopen($this->_cfg['httpd_pid'], 'r');
$old_pid = fgets($fp, $this->MAX_LENGTH);
fclose($fp);
/* if there is a corresponding process, cancel execution. */
if ($old_pid > 0 && posix_kill($old_pid, 0)) {
$this->write_log(0,0,$this->cdata['remote_ip'],"Server already running with PID: $old_pid",0);
exit;
}
}
/* create a pid file */
if ($fp = fopen($this->_cfg['httpd_pid'], 'w')) {
fputs($fp, $pid);
fclose($fp);
/* make sure we can delete the pid file later */
chown($this->_cfg['httpd_pid'], $this->_cfg['user']['uid']);
chgrp($this->_cfg['httpd_pid'], $this->_cfg['user']['gid']);
chmod($this->_cfg['httpd_pid'], 0644);
} else {
$this->write_log(0,0,$this->cdata['remote_ip'],'Unable to write to '.$this->_cfg['httpd_pid'],0);
exit;
}
}
function error_handler($errno, $errstr, $errfile, $errline) {
// make sure there is no \ sign before the error line
if (error_reporting()) {
switch($errno) {
// Fatal Errors
case 1:
case 256:
$this->write_log(0,0,$this->cdata['remote_ip'],"Fatal Error ($errno) in file $errfile($errline): $errstr",0);
break;
// Warning & errors
case 2:
case 4:
case 8:
case 512:
case 1024:
$this->write_log(1,0,$this->cdata['remote_ip'],"Error ($errno) in file $errfile($errline): $errstr",0);
break;
// anything else
default:
$this->write_log(1,0,$this->cdata['remote_ip'],"Error unknown ($errno) in file $errfile($errline): $errstr",0);
}
}
}
function get_microtime() {
/* used to calculate timeouts, also useful for testing */
list($usec, $sec) = explode(' ', microtime());
return ((float)$usec + (float)$sec);
}
}
?>