#!/usr/bin/php
<? /*
// File: webcp-httpd.php
// Purpose: web://cp HTTP Daemon
// Creation: 2003-01-25
// Author: Jonathan Haskins <hide@address.com>
*/
/*********************************************
* Main
*********************************************/
error_reporting(E_ALL);
/* change to the script directory since php CLI doesn't do it automatically */
chdir(dirname(__FILE__));
/* include configuration file and functions */
include('webcp-httpd-functions.inc.phps');
include('../web/functions.inc.phps');
include('../web/config.inc.php');
set_error_handler('error_handler');
/* Make sure the php CLI/CGI has the needed modules */
$i = 0;
unset($error);
if ((int)substr(preg_replace("/[^\d]*/", '', phpversion()), 0, 3) < 410)
$error[++$i] = "PHP Error: You must be running PHP version 4.1 or higher.\n";
if (!extension_loaded('mysql'))
$error[++$i] = "PHP Error: The MySQL module is required (configure PHP '--with-mysql=/usr')\n";
if (!extension_loaded('posix'))
$error[++$i] = "PHP Error: The POSIX module is required (configure PHP '--enable-posix')\n";
if (!extension_loaded('pcre'))
$error[++$i] = "PHP Error: The Perl Compatible Regular Expressions module is required (configure PHP '--with-pcre-regex')\n";
if (!extension_loaded('pcntl'))
$error[++$i] = "PHP Error: The Process Control module is required (configure PHP '--enable-pcntl')\n";
if (!extension_loaded('sockets'))
$error[++$i] = "PHP Error: The Sockets module is required (configure PHP '--enable-sockets')\n";
if ($cfg['httpd_mode'] != 'webcp')
$error[++$i] = "Configuration Error: The setting \$cfg['httpd_mode'] must be set to 'webcp'\n";
if (isset($error)) {
webcp_log(2,0,'webcp-httpd',implode("\n", $error),0,1);
exit;
}
/* Run from root only */
if (posix_getuid() != 0) {
webcp_log(1,0,'webcp-httpd',"This program must be started as root (uid 0).\n",0,1);
exit;
}
/* Check if we are run interactively or with the -d switch; keep on going or become a daemon */
$args = trim(next($HTTP_SERVER_VARS["argv"]));
if ($args == '-d' OR $args == '--daemon') {
/* do not echo to terminal */
$echo = 0;
} else {
webcp_log(3,0,'webcp-httpd',"web://cp ".$cfg['webcp']." httpd daemon usage:
webcp-httpd.php (run interactively)
webcp-httpd.php -i (run interactively)
webcp-httpd.php -d, --daemon (run as daemon)
webcp-httpd.php -h, --help (display this help notice)\n\n",0,1);
if ($args == '-h' OR $args == '--help') {
exit;
} else {
webcp_log(3,0,'webcp-httpd',"\n\n Running web://cp ".$cfg['webcp']." httpd daemon".
" interactively\n use ./webcp-httpd.php -h for help, CTRL-C to exit\n".
"==================================================\n\n",0,1);
$echo = 1;
}
}
unset($args);
/* get user uid and gid from username */
if (!($user = posix_getpwnam($cfg['httpd_user'])) || !($group = posix_getgrgid($user['gid']))) {
webcp_log(0,0,'webcp-httpd',"User $cfg[httpd_user] does not exist.",0,$echo);
exit;
}
/* open a listening socket that is bound to an address and port */
$lsocket = start_server();
/* if ssl is on then start stunnel so we can accept ssl connections */
if ($cfg['ssl']) {
switch ($cfg['osversion']) {
case 'RedHat9.0':
//spit out simple stunnel.conf file for redhat 9
$fp = fopen($cfg['basedir'].'/httpd/conf/stunnel4.conf', 'w');
fwrite($fp, "######################################\n".
"# this file is auto generated by webcp\n".
"######################################\n\n".
"# permissions on cert should be set to 0600\n".
"cert = $cfg[ssl_cert]\n".
"setuid = $user[name]\n".
"setgid = $group[name]\n".
"output = /var/log/secure\n".
"pid = $cfg[httpsd_pid]\n\n".
"[webcp]\n".
"accept = $cfg[port]\n".
"TIMEOUTclose = 0\n".
"exec = $cfg[basedir]/server/ssl_in.php\n".
"\n");
fclose($fp);
$stunnel = $cfg['prog']['stunnel']." $cfg[basedir]/httpd/conf/stunnel4.conf 2>&1";
break;
default:
$stunnel = $cfg['prog']['stunnel'].' '.
"-p $cfg[ssl_cert] ".
"-d $cfg[listen_address]:$cfg[port] ".
"-l $cfg[basedir]/server/ssl_in.php ".
"-s $user[name] ".
"-g $group[name] ".
"-P $cfg[httpsd_pid] ".
"2>&1";
}
/* run stunnel and exit on error */
exec($stunnel, $stunnel_output, $stunnel_return);
if ($stunnel_return !== 0) {
webcp_log(0,0,'webcp-httpd',"stunnel had a problem.",0,$echo);
stop_server();
}
webcp_log(2,0,'webcp-httpd',"stunnel is listening on port $cfg[port] and communicating with webcp through port $cfg[ssl_port]",0,$echo);
}
/* change to a user with lower permissions than root. We should do this as soon as possible. */
posix_setgid($user['gid']);
posix_setuid($user['uid']);
/* define max length to use when reading from sockets/files */
define("MAX_LENGTH", 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, "reload_server"); // sig# 1
pcntl_signal(SIGINT, "stop_server"); // sig# 2
pcntl_signal(SIGTERM, "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);
/* setup a database connection */
db_connect($cfg['dbhost'], $cfg['dbname'], $cfg['dbuser'], $cfg['dbpass']);
/* fork the script into a background process */
if ($echo === 0) {
$pid = pcntl_fork();
if ($pid == -1) {
webcp_log(0,0,'webcp-httpd',"Failed to spawn a daemon",0,1);
exit;
} elseif ($pid) {
/* kill parent and return success */
return 0;
}
/* become session leader / detach from the controlling terminal */
if (!posix_setsid()) {
webcp_log(0,0,'webcp-httpd',"Could not detach from terminal",0,1);
exit;
} else
webcp_log(2,0,'webcp-httpd',"Webcp-httpd daemon has been started.",0,1);
}
/* create pid file */
set_pid_file(getmypid());
// an array mapping short http status strings to long http strings
$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 */
$httpd_status['started'] = get_microtime();
$httpd_status['hits'] = 0;
$httpd_status['connections'] = 0;
$httpd_status['sent'] = 0;
/* initialize the client socket array */
$csocket = array();
/* enter an endless loop */
while (true) {
/* close any sockets that have timed out */
foreach($csocket as $slot => $socket) {
if (!empty($cfg['timeout']) && (get_microtime() - $cdata[$slot]['established']) > $cfg['timeout']) {
close_client($slot);
} elseif ($cfg['keep_alive'] && !$cfg['ssl']) {
if (get_microtime() > $cdata[$slot]['keep_alive']) {
close_client($slot);
}
}
}
/* build array consisting of sockets we want to get updates for */
$set_r = array($lsocket);
$set_r = array_merge($set_r, $csocket);
/* $set_r will be an array holding the sockets that we have received data on.
* $ready will be the number of sockets that we have recieved data on. */
$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 (in_array($lsocket, $set_r)) {
open_client($lsocket);
}
/* start loop over if there isn't anymore updated sockets */
if ($ready-- <= 0) {
continue;
}
/* otherwise check client sockets for incoming data */
foreach ($csocket as $slot => $socket) {
if (!in_array($socket, $set_r)) {
continue;
}
/* read data from the client into a buffer. if read fails close the connection */
if (!$cdata[$slot]['buffer'] .= @socket_read($socket, MAX_LENGTH)) {
close_client($slot);
} else {
/* since we got a response, renew our keepalive timeout */
if ($cfg['keep_alive'] && !$cfg['ssl']) {
$cdata[$slot]['keep_alive'] = get_microtime() + $cfg['keep_alive_timeout'];
}
/* check for http header double carriage return / line feed */
if (!isset($cdata[$slot]['hlength'])) {
if (($cdata[$slot]['hlength'] = strpos($cdata[$slot]['buffer'], "\r\n\r\n")) !== false) {
$cdata[$slot]['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 ($cdata[$slot]['clength'] = stristr($cdata[$slot]['buffer'], 'Content-Length:')) {
$cdata[$slot]['clength'] = substr($cdata[$slot]['clength'], 0, strpos($cdata[$slot]['clength'], "\r\n"));
$cdata[$slot]['clength'] = explode(':', $cdata[$slot]['clength']);
$cdata[$slot]['clength'] = trim($cdata[$slot]['clength'][1]);
}
if (!isset($cdata[$slot]['clength']))
$cdata[$slot]['clength'] = 0;
} else {
/* we don't have a header length yet */
continue;
}
}
/* If the full body content hasn't arrived yet, skip the next part. */
if (strlen($cdata[$slot]['buffer']) < ($cdata[$slot]['hlength'] + $cdata[$slot]['clength'])) {
continue;
}
/* this is where we parse the request headers */
$request = parse_request($cdata[$slot]['buffer']);
/* request has been parsed, erase the buffer. */
$cdata[$slot]['buffer'] = '';
unset($cdata[$slot]['hlength']);
unset($cdata[$slot]['clength']);
/* build response array based on the request array */
$response = build_response($request);
/* then send our response */
send_response($slot, $response, $request);
/* close the connection if we're done */
if ($response['connection_close']) {
close_client($slot);
}
}
/* stop checking the client sockets if there isn't anymore updated sockets */
if ($ready-- <= 0) {
break;
}
}
}
?>