Location: PHPKode > projects > web-cp - Web Hosting Control Panel > webcp/server/webcp-httpd.php
#!/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;
		}
	}
}
?>
Return current item: web-cp - Web Hosting Control Panel