Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Provides access and control over Neufbox4 devices.
- * Tested with firmware NB4-MAIN-R3.1.10.
- *
- * @require PHP > 5.1.2
- * @author Anael Ollier <nanawel@gmail.com>
- * @version 0.1.0
- * @since 2011-12-25
- */
- class Neufbox4 {
- const CLASS_VERSION = '0.1.1';
- const DEFAULT_HOST = '192.168.1.1';
- const STATUS_CONNECTED = 0;
- const STATUS_CONNECTING = 1;
- const STATUS_UNUSED = 2;
- const STATUS_NOT_CONNECTED = 3;
- const COOKIE_NAME = 'sid';
- const REQUEST_TIMEOUT = 5;
- const LOG_DEBUG = 10;
- const LOG_NOTICE = 20;
- const LOG_WARNING = 30;
- const LOG_ERROR = 40;
- /** @var string */
- protected $_host;
- /** @var string */
- protected $_login;
- /** @var string */
- protected $_password;
- /** @var resource */
- protected $_curl;
- /** @var string */
- protected $_sessionId;
- /** @var boolean */
- public $debug = false;
- /** @var int */
- public $logLevel = self::LOG_DEBUG;
- /**
- *
- * @param string $host The Neufbox4 IP to connect to.
- */
- public function __construct($host = self::DEFAULT_HOST, $logLevel = self::LOG_NOTICE) {
- $this->_host = $host;
- $this->logLevel = $logLevel;
- $this->_curl = curl_init($this->_getUrl('/'));
- $this->log("Initialized new connection to host $host.");
- }
- public function __destruct() {
- curl_close($this->_curl);
- }
- protected function _getUrl($path) {
- return 'http://' . $this->_host . $path;
- }
- /**
- * Creates a new session with stored login/password if
- * there's no current session.
- *
- * @param boolean $force
- */
- protected function _login($force = false) {
- if ($force || !$this->_sessionId) {
- ///////////////////
- // 1. Retrieve challenge (session ID)
- $res = $this->_sendRawRequest('/login', 'post', array('action' => 'challenge'),
- array(
- 'X-Requested-With: XMLHttpRequest',
- 'X-Requested-Handler: ajax',
- )
- );
- if ($res === false) {
- self::throwException('Cannot log in: challenge request failed.');
- }
- if (200 != ($code = $res['info']['http_code'])) {
- self::throwException("Cannot log in: unexpected code HTTP $code returned.");
- }
- elseif ('text/xml' != ($contentType = $res['info']['content_type'])) {
- self::throwException("Cannot log in: unexpected content type \"$contentType\" returned (text/xml expected).");
- }
- $xml = new SimpleXMLElement($res['body']);
- if (! $sid = (string) $xml->challenge) {
- self::throwException('Cannot log in: no challenge found in response body.');
- }
- $this->_sessionId = trim($sid);
- ///////////////////
- // 2. Generate hash for authentication
- $hash = $this->_genLoginHash($sid, $this->_login, $this->_password);
- ///////////////////
- // 3. Log in with calculated hash
- try {
- $res = $this->_sendRawRequest('/login', 'post',
- array(
- 'hash' => $hash,
- 'login' => '',
- 'method' => 'passwd',
- 'password' => '',
- 'zsid' => $sid,
- ),
- array(),
- 'zsid=' . $sid
- );
- }
- catch (Exception $e) {
- self::throwException("Cannot log in: authentication request failed. ({$e->getMessage()})");
- }
- if (200 != ($code = $res['info']['http_code'])) {
- self::throwException("Cannot log in: unexpected code HTTP $code returned while attempting to authenticate.");
- }
- $this->log('Login successful! Session ID: ' . $sid);
- }
- }
- /**
- * Generates the authentication hash based on session ID,
- * login and password.
- *
- * @param string $challenge
- * @param string $login
- * @param string $password
- */
- protected function _genLoginHash($challenge, $login, $password) {
- return hash_hmac('sha256', hash('sha256', $login), $challenge)
- . hash_hmac('sha256', hash('sha256', $password), $challenge);
- }
- /**
- * Helper for building raw cURL requests.
- *
- * @param string $path
- * @param string $method
- * @param array $data
- * @return array
- * @throws Exception if the request failed.
- */
- protected function _sendRawRequest($path = '/', $method = 'get', $data = array(), $headers = null, $cookie = '') {
- curl_setopt($this->_curl, CURLOPT_URL, $this->_getUrl($path));
- if ($cookie) {
- curl_setopt($this->_curl, CURLOPT_COOKIE, $cookie);
- }
- curl_setopt($this->_curl, CURLOPT_TIMEOUT, self::REQUEST_TIMEOUT);
- curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
- if ($method == 'get') {
- curl_setopt($this->_curl, CURLOPT_HTTPGET, true);
- }
- elseif ($method == 'post') {
- curl_setopt($this->_curl, CURLOPT_POST, true);
- $postfields = array();
- foreach($data as $key => $value) {
- $postfields[] = $key . '=' . $value;
- }
- $postfields = implode(';', $postfields);
- curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $postfields);
- }
- if (is_array($headers)) {
- curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
- }
- else {
- curl_setopt($this->_curl, CURLOPT_HTTPHEADER, array());
- }
- curl_setopt($this->_curl, CURLOPT_VERBOSE, $this->debug ? true : false);
- $result = curl_exec($this->_curl);
- if ($result === false) {
- self::throwException('cURL error ' . curl_errno($this->_curl));
- }
- return array(
- 'info' => curl_getinfo($this->_curl),
- 'body' => $result,
- );
- }
- /**
- * Helper for building cURL requests after authentication with
- * the Neufbox.
- *
- * @param string $path
- * @param string $method
- * @param array $data
- * @return array
- * @throws Exception if the request failed.
- */
- protected function _sendRequest($path = '/', $method = 'get', $data = array(), $headers = null) {
- if (!$this->_sessionId) {
- $this->log("No session, initializing...", self::LOG_NOTICE);
- $this->_login();
- }
- $res = $this->_sendRawRequest($path, $method, $data, $headers, self::COOKIE_NAME . '=' . $this->_sessionId);
- // Redirect means that session is invalid
- if ($res['info']['http_code'] == 302) {
- $this->log("Session lost, attempting to renew...", self::LOG_NOTICE);
- // Force new login
- $this->_login(true);
- // Then try the original request again
- $res = $this->_sendRawRequest($path, $method, $data, $headers, self::COOKIE_NAME . '=' . $this->_sessionId);
- if ($res['info']['http_code'] != 200) {
- self::throwException('Cannot reconnect to Neufbox. Aborting.');
- }
- }
- return $res;
- }
- /**
- *
- * @param string $html
- * @param string $encoding
- * @return DOMXPath
- */
- protected function _htmlToDOMXPath($html, $encoding = 'iso-8859-1') {
- $dom = new DOMDocument('1.0', $encoding);
- $dom->loadHTML($html);
- $xpathDom = new DOMXPath($dom);
- return $xpathDom;
- }
- public function login($login, $password) {
- $this->_login = $login;
- $this->_password = $password;
- $this->_login(true);
- }
- public function logout() {
- $this->log('Logging out.', self::LOG_NOTICE);
- $this->_sessionId = null;
- }
- public function getHost() {
- return $this->_host;
- }
- protected function _getStatusFromNodeCss($html, $xpath) {
- $dom = $this->_htmlToDOMXPath($html);
- $entries = $dom->query($xpath);
- if (null === $entries->item(0)) {
- self::throwException('Cannot find node at XPath "' . $xpath . '".');
- }
- $entry = $entries->item(0);
- $status = null;
- switch($entry->attributes->getNamedItem('class')->nodeValue) {
- case 'enabled':
- $status = self::STATUS_CONNECTED;
- break;
- case 'disabled':
- $status = self::STATUS_NOT_CONNECTED;
- break;
- case 'unused':
- $status = self::STATUS_UNUSED;
- break;
- }
- return $status;
- }
- /**
- *
- * @param string $html
- * @param string $xpath XPath to the table holding data to be retrieved
- */
- protected function _getTableDataAsArray($html, $xpath) {
- $dom = $this->_htmlToDOMXPath($html);
- $entries = $dom->query($xpath);
- if (null === $entries->item(0) || $entries->item(0)->nodeName != 'table') {
- self::throwException('Cannot find <table> node at XPath "' . $xpath . '".');
- }
- $entry = $entries->item(0);
- $data = array();
- foreach($entry->childNodes as $childNode) {
- /** $childNode <tr> */
- $label = '';
- $value = '';
- foreach($childNode->childNodes as $node) {
- if ($node->nodeName == 'th') {
- $label = self::_normalizeText($node->textContent);
- }
- if ($node->nodeName == 'td') {
- $value = self::_normalizeText($node->textContent);
- }
- }
- if ($label && $value) {
- $data[$label] = $value;
- }
- }
- return $data;
- }
- /**
- *
- * @param string $html
- * @param string $xpath XPath to the table holding data to be retrieved
- */
- protected function _getTableDataAsArrayWithHeaders($html, $xpath) {
- $dom = $this->_htmlToDOMXPath($html);
- $entries = $dom->query($xpath);
- if (null === $entries->item(0) || $entries->item(0)->nodeName != 'table') {
- self::throwException('Cannot find <table> node at XPath "' . $xpath . '".');
- }
- $entry = $entries->item(0);
- $data = array();
- // Cols
- $cols = array();
- $nodeList = $dom->query($xpath . '/thead/tr/th');
- foreach($nodeList as $node) {
- if ($node->nodeName == 'th') {
- $cols[] = self::_normalizeText($node->textContent);
- }
- }
- // Rows
- $rows = array();
- $rowNodeList = $dom->query($xpath . '/tbody/tr');
- $i = 0;
- foreach($rowNodeList as $rowNode) {
- $i++;
- $j = 0;
- foreach($rowNode->childNodes as $cellNode) {
- if ($cellNode->nodeName == 'td') {
- $colName = isset($cols[$j]) ? $cols[$j++] : "{Column $j}";
- // Normal text node
- if ($value = self::_normalizeText($cellNode->textContent)) {
- $data[$i][$colName] = $value;
- }
- else {
- foreach($cellNode->childNodes as $subCellNode) {
- // Image node: retrieve "alt" attribute as text value
- if ($subCellNode->nodeName == 'img') {
- if ($value = $subCellNode->getAttribute('alt')) {
- $data[$i][$colName] = self::_normalizeText($value);
- }
- }
- }
- }
- // Fallback
- if (!isset($data[$i][$colName])) {
- $data[$i][$colName] = '';
- }
- }
- }
- }
- return $data;
- }
- /**
- *
- * @return int
- */
- public function getIpv4Status() {
- $this->log("Retrieving IPv4 status...");
- $res = $this->_sendRequest('/state');
- return $this->_getStatusFromNodeCss($res['body'], '//td[@id="internet_status"]');
- }
- /**
- *
- * @return int
- */
- public function getIpv6Status() {
- $this->log("Retrieving IPv6 status...");
- $res = $this->_sendRequest('/state');
- return $this->_getStatusFromNodeCss($res['body'], '//td[@id="internet_status_v6"]');
- }
- /**
- *
- * @return int
- */
- public function getPhoneStatus() {
- $this->log("Retrieving phone status...");
- $res = $this->_sendRequest('/state');
- return $this->_getStatusFromNodeCss($res['body'], '//td[@id="voip_status"]');
- }
- /**
- *
- * @return int
- */
- public function getWifiStatus() {
- $this->log("Retrieving Wifi status...");
- $res = $this->_sendRequest('/wifi');
- return $this->_getStatusFromNodeCss($res['body'], '//table[@id="wifi_info"]/*/td[1]');
- }
- /**
- *
- * @return int
- */
- public function getTelevisionStatus() {
- $this->log("Retrieving TV status...");
- $res = $this->_sendRequest('/state');
- return $this->_getStatusFromNodeCss($res['body'], '//td[@id="tv_status"]');
- }
- /**
- *
- * @return array
- */
- public function getModemInfo() {
- $this->log("Retrieving modem info...");
- $res = $this->_sendRequest('/state');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="modem_infos"]');
- }
- /**
- *
- * @return array
- */
- public function getIpv4ConnectionInfo() {
- $this->log("Retrieving IPv4 info...");
- $res = $this->_sendRequest('/state/wan');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="wan_info"]');
- }
- /**
- *
- * @return array
- */
- public function getIpv6ConnectionInfo() {
- $this->log("Retrieving IPv6 info...");
- $res = $this->_sendRequest('/state/wan');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="ipv6_info"]');
- }
- /**
- *
- * @return array
- */
- public function getAdslInfo() {
- $this->log("Retrieving ADSL info...");
- $res = $this->_sendRequest('/state/wan');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="adsl_info"]');
- }
- /**
- *
- * @return array
- */
- public function getPppInfo() {
- $this->log("Retrieving PPP info...");
- $res = $this->_sendRequest('/state/wan');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="ppp_info"]');
- }
- /**
- *
- * @return array
- */
- public function getConnectedHosts() {
- $this->log("Retrieving connected hosts list...");
- $res = $this->_sendRequest('/network');
- return $this->_getTableDataAsArrayWithHeaders($res['body'], '//table[@id="network_clients"]');
- }
- /**
- *
- * @return array
- */
- public function getPortsInfo() {
- $this->log("Retrieving ports info...");
- $res = $this->_sendRequest('/network');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="network_status"]');
- }
- /**
- *
- * @return array
- */
- public function getWifiInfo() {
- $this->log("Retrieving Wifi info...");
- $res = $this->_sendRequest('/wifi');
- return $this->_getTableDataAsArray($res['body'], '//table[@id="wifi_info"]');
- }
- /**
- *
- * @return array
- */
- public function getNatConfig() {
- $this->log("Retrieving NAT configuration...");
- $res = $this->_sendRequest('/network/nat');
- $return = $this->_getTableDataAsArrayWithHeaders($res['body'], '//table[@id="nat_config"]');
- //Remove last line (used to add a new NAT rule from the GUI)
- array_pop($return);
- // Remove the last two columns from rows (used to enable/disable and delete rules from the GUI)
- foreach($return as &$row) {
- array_pop($row);
- array_pop($row);
- }
- return $return;
- }
- /**
- *
- * @return array
- */
- public function getPhoneCallHistory() {
- $this->log("Retrieving phone call history...");
- $res = $this->_sendRequest('/state/voip');
- return $this->_getTableDataAsArrayWithHeaders($res['body'], '//table[@id="call_history_list"]');
- }
- public function getFullReport() {
- $report = array();
- $myMethods = get_class_methods($this);
- $excludedMethods = array('getFullReport');
- sort($myMethods);
- foreach($myMethods as $methodName) {
- if (substr($methodName, 0, 3) == 'get' && !in_array($methodName, $excludedMethods)) {
- $key = self::_uncamelize(substr($methodName, 3));
- $report[$key] = call_user_func(array($this, $methodName));
- }
- }
- return $report;
- }
- public function reboot() {
- $res = $this->_sendRequest('/reboot', 'post', array('submit' => ''));
- if (200 != ($code = $res['info']['http_code'])) {
- self::throwException("Reboot may have failed: unexpected code HTTP $code returned.");
- }
- }
- public function log($msg, $level = self::LOG_DEBUG) {
- if ($level >= $this->logLevel) {
- echo self::formatLog($msg, $level);
- }
- }
- public static function formatLog($msg, $level = self::LOG_DEBUG) {
- switch($level) {
- case self::LOG_NOTICE:
- $level = 'NOTICE';
- break;
- case self::LOG_WARNING:
- $level = 'WARN';
- break;
- case self::LOG_ERROR:
- $level = 'ERROR';
- break;
- default:
- $level = 'DEBUG';
- break;
- }
- return date('Y-m-d H:i:s') . " [$level] " . print_r($msg, true) . "\n";
- }
- public function throwException($msg) {
- $this->log($msg, self::LOG_ERROR);
- throw new Exception($msg);
- }
- protected static function _normalizeText($text) {
- return trim(preg_replace('/\s+/', ' ', str_replace(array("\n", "\r\n"), '', $text)));
- }
- protected function _uncamelize($string) {
- return strtolower(preg_replace('/(.)([A-Z])/', '$1_$2', $string));
- }
- public static function checkRequirements() {
- $classes = array(
- 'DOMDocument',
- 'DOMXPath',
- 'SimpleXMLElement'
- );
- $functions = array(
- 'curl_init'
- );
- if (-1 == version_compare(phpversion(), '5.1.2')) {
- echo "WARNING: PHP 5.1.2 or above is required.\n";
- }
- foreach($classes as $class) {
- if (!class_exists($class)) {
- throw new Exception("Missing required class/library: '$class'. Please check your PHP configuration.");
- }
- }
- foreach($functions as $function) {
- if (!function_exists($function)) {
- throw new Exception("Missing required function/library: '$function'. Please check your PHP configuration.");
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement