#!/usr/bin/php -q This script is only meant to run at the command line.'); } /* we are not talking to the browser */ $no_http_headers = true; /* let's report all errors */ error_reporting(E_ALL); /* allow the script to hang around waiting for connections. */ set_time_limit(0); /* Turn on implicit output flushing so we see what we're getting * as it comes in. */ ob_implicit_flush(); /* Start Initialization Section */ include_once('./include/global.php'); include_once('./lib/rrd.php'); include_once('./lib/poller.php'); include_once('./lib/boost.php'); /* suppress warnings */ error_reporting(0); /* install the boost error handler */ set_error_handler('boost_error_handler'); /* in UNIX/LINUX we change the effective user of the process to the * cacti users specified in the /include/global.php file. */ $username = read_config_option('boost_server_effective_user'); if (function_exists('posix_getpwnam')) { if (strlen($username)) { $user_info = posix_getpwnam($username); if (strlen($user_info['uid'])) { posix_setuid($user_info['uid']); } } } elseif ($username != '') { boost_svr_log("INFO: no support for switching the effective user\n"); } /* setup global variables */ $listen_port = read_config_option('boost_server_listen_port'); $rrd_path = read_config_option('path_rrdtool'); $rrd_update_path = read_config_option('boost_path_rrdupdate'); $php_binary_path = read_config_option('path_php_binary'); $log_file = read_config_option('path_cactilog'); if (strstr(PHP_OS, 'WIN')) { $eol = "\r\n"; }else{ $eol = "\n"; } /* create a streaming socket, of type TCP/IP */ $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (!socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1)) { boost_svr_log('WARNING: ' . socket_strerror(socket_last_error())); } /*---------------------------------------------------------------------------*/ /* "bind" the socket to the address to "localhost", on port $port */ /* so this means that all connections on this port are now our resposibility */ /* to send/recv data, disconnect, etc.. */ /*---------------------------------------------------------------------------*/ socket_set_block($sock); if (!socket_bind($sock, 0, $listen_port)) { boost_svr_log("FATAL: Socket bind to port '$listen_port' Failed, Socket likely in use!"); exit -1; } /* start listen for connections */ socket_listen($sock); /* list of all the clients that are connected to the server */ $clients = array($sock); /* keep a buffer of commands and hosts that come into the sockets */ $commands = array(); $hosts = array(); /* give the debugging developer some indication that we are working */ boost_svr_log('Cacti RRDtool Service Started'); /* initially, we will block indefinately waiting for a client to connect. */ $wait_value = NULL; /* initially, there are not rrdupdates in process */ $rrdupdates_in_process = array(); /* start and endless loop waiting for clients to connect and issue commands */ while (true) { if (read_config_option('boost_server_multiprocess', TRUE)) { $multiprocess = TRUE; }else{ $multiprocess = FALSE; } /* if there were call's to generate rrdupdates, then let's see if any finished */ if (sizeof($rrdupdates_in_process)) { $finished_rrds = db_fetch_assoc('SELECT * FROM poller_output_boost_processes'); if (sizeof($finished_rrds)) { foreach($finished_rrds as $finished_rrd) { /* remove the finished entry from the table */ db_execute("DELETE FROM poller_output_boost_processes WHERE sock_int_value='" . $finished_rrd['sock_int_value'] . "'"); /* send the output back to the cleint. make sure we mask errors from broken connections */ @socket_write($rrdupdates_in_process[$finished_rrd['sock_int_value']]['socket'], $finished_rrd['status'] . $eol, strlen($finished_rrd['status'] . $eol)); if (substr_count($finished_rrd['status'], 'OK') == 0) { cacti_log($finished_rrd['status'], true, 'BOOST_SERVER'); }else{ echo date('Y:m:d H:i:s') . " - RRDUpdate OK Message: '" . $finished_rrd['status'] . "'" . $eol; } /* remove the item from the rrdupdates_in_process array. make sure we mask errors from broken connections */ unset($rrdupdates_in_process[$finished_rrd['sock_int_value']]); } } if (sizeof($rrdupdates_in_process)) { usleep(100000); $wait_value = 0; }else{ $wait_value = NULL; } } /* initialize the rrdtool command if this is a single process boost server */ global $rrdtool_pipe; if (!$multiprocess) { if (empty($rrdtool_pipe)) { $rrdtool_pipe = rrd_init(); } }else{ if (!empty($rrdtool_pipe)) { rrd_close($rrdtool_pipe); } } /* create a copy, so $clients doesn't get modified by socket_select() */ $read = $clients; /* get a list of all the clients that have data to be read from */ /* if there are no clients with data, go to next iteration */ /* in windows, we read one character at a time */ if (socket_select($read, $write = NULL, $except = NULL, $wait_value) < 1) continue; /* re-connect to the database server */ db_close(); db_connect_real($database_hostname, $database_username, $database_password, $database_default, $database_type, $database_port); /* check if there is a client trying to connect */ if (in_array($sock, $read)) { /* accept the client, and add him to the $clients array */ $clients[] = $newsock = socket_accept($sock); socket_getpeername($newsock, $ip); boost_svr_log("Host Connected '$ip'"); /* remove the listening socket from the clients-with-data array */ $key = array_search($sock, $read); unset($read[$key]); /* store the peer's ip address in the hosts array */ $key = array_search($newsock, $clients); $hosts[$key] = $ip; /* verify that the client is authorized */ $valid_hosts = explode(',', read_config_option('boost_server_clients', TRUE)); if (!in_array($ip, $valid_hosts)) { $connect_hostname = gethostbyaddr($ip); if (!in_array($connect_hostname, $valid_hosts)) { close_connection($newsock, "WARNING: Host '$ip' Not Authorized."); }else{ boost_svr_log("Host Validated '$connect_hostname'"); } }else{ boost_svr_log("Host Validated '$ip'"); } } /* loop through all the clients that have data to read from */ foreach ($read as $read_sock) { if (!isset($command[intval($read_sock)])) { $command[intval($read_sock)] = ''; } /* read until newline or 100k bytes */ $data = @socket_read($read_sock, 100000); /* check if the client is disconnected */ if (ord($data) == 0) { /* remove client for $clients array */ close_connection($read_sock, 'WARNING: Broken connection detected'); /* let's make sure we don't enter into a race conditiion waiting on an rrdupdate that * may never happen. */ if (isset($rrdupdates_in_process[intval($read_sock)])) { unset($rrdupdates_in_process[intval($read_sock)]); } /* continue to the next client to read from, if any */ continue; }else if (ord($data) == 3 || ord($data) == 17) { /* client is attempting to disconnect, so do it */ /* write message to the socket and close */ close_connection($read_sock, 'WARNING: Host broke connection'); /* let's make sure we don't enter into a race conditiion waiting on an rrdupdate that * may never happen. */ if (isset($rrdupdates_in_process[intval($read_sock)])) { unset($rrdupdates_in_process[intval($read_sock)]); } }else if (ord($data) == 13) { $response = run_command($read_sock,$command[intval($read_sock)], $multiprocess); $command[intval($read_sock)] = ''; }else if (strlen($data) > 1) { $response = run_command($read_sock,$data, $multiprocess); $command[intval($read_sock)] = ''; }else{ $command[intval($read_sock)] .= $data; } } } /* close the rrdtool command if this is single process boost */ if (!read_config_option('boost_server_multiprocess')) { rrd_close($rrdtool_pipe); } /* close the listening socket */ socket_close($sock); /*---------------------------------------------------------------------------*/ /* this function will perform the appropriate RRDtool command and then */ /* continue. */ /*---------------------------------------------------------------------------*/ function run_command($socket, $command, $multiprocess) { global $config, $eol, $rrdtool_pipe, $rrd_path, $php_binary_path, $rrd_update_path, $rrdupdates_in_process; $output = 'OK'; /* process the command, don't accept bad commands */ if (substr_count(strtolower($command), 'quit')) { close_connection($socket, 'Host Disconnect Request Received.'); return 'OK'; }elseif (substr_count(strtolower(substr($command,0,10)), 'update')) { /* ok to run */ }elseif (substr_count(strtolower(substr($command,0,10)), 'graph')) { /* ok to run */ }elseif (substr_count(strtolower(substr($command,0,10)), 'tune')) { /* ok to run */ }elseif (substr_count(strtolower(substr($command,0,10)), 'create')) { /* ok to run, check for structured paths */ if (read_config_option('extended_paths') == 'on') { $parts = explode(' ', $command); $data_source_path = $parts[1]; if (!is_dir(dirname($data_source_path))) { if (mkdir(dirname($data_source_path), 0775)) { if ($config['cacti_server_os'] != 'win32') { $owner_id = fileowner($config['rra_path']); $group_id = filegroup($config['rra_path']); if ((chown(dirname($data_source_path), $owner_id)) && (chgrp(dirname($data_source_path), $group_id))) { /* permissions set ok */ }else{ cacti_log("ERROR: Unable to set directory permissions for '" . dirname($data_source_path) . "'", FALSE); } } }else{ cacti_log("ERROR: Unable to create directory '" . dirname($data_source_path) . "'", FALSE); } } } }elseif (substr_count(strtolower(substr($command,0,10)), 'status')) { close_connection($socket, 'Server Status OK'); return 'OK'; }else{ close_connection($socket, "WARNING: Unknown RRD Command '" . $command . "' This activity will be logged!! Goodbye."); return 'OK'; } boost_svr_log("RRD Command '" . $command . "'"); /* update/create the rrd */ if (!$multiprocess) { boost_rrdtool_execute_internal($command, false, RRDTOOL_OUTPUT_STDOUT, 'BOOST SERVER'); }else{ /* store the correct information in the array */ $rrdupdates_in_process[intval($socket)]['socket'] = $socket; if ((strlen($rrd_update_path)) && (!substr_count($command, 'create '))) { $command = str_replace('update ', '', $command); exec_background($php_binary_path, 'boost_rrdupdate.php ' . intval($socket) . ' ' . $rrd_update_path . ' ' . $command); }else{ exec_background($php_binary_path, 'boost_rrdupdate.php ' . intval($socket) . ' ' . $rrd_path . ' ' . $command); } } /* send the output back to the cleint if not multiprocess */ if (!$multiprocess) { socket_write($socket, $output . $eol, strlen($output . $eol)); } } /*---------------------------------------------------------------------------*/ /* this function logs entries to the log file */ /*---------------------------------------------------------------------------*/ function boost_svr_log($message) { global $log_file, $eol; if (strlen($message)) { if ((substr_count($message, 'ERROR')) || (substr_count($message, 'WARNING')) || (substr_count($message, 'FATAL')) || (read_config_option('poller_verbosity') >= POLLER_VERBOSITY_HIGH)) { cacti_log($message, true, 'BOOST SERVER'); }else{ echo date('Y:m:d H:i:s') . ' - ' . $message . $eol; } } } /*---------------------------------------------------------------------------*/ /* this function will close a connection to a peer. Plain and simple */ /*---------------------------------------------------------------------------*/ function close_connection($socket, $message = '') { global $clients, $hosts; /* remove the socket from the connected clients list */ $key = array_search($socket, $clients); unset($clients[$key]); /* echo the output message to the screen */ if (strlen($message)) { boost_svr_log($message . " '" . $hosts[$key] . "'"); @socket_write($socket, $message); } /* close the socket of the peer */ socket_close($socket); /* remove the ip from the hosts list */ unset($hosts[$key]); }