diff --git a/plugins/password/drivers/directadmin.php b/plugins/password/drivers/directadmin.php index e1f649b0e..fb1bf75a9 100644 --- a/plugins/password/drivers/directadmin.php +++ b/plugins/password/drivers/directadmin.php @@ -5,10 +5,10 @@ * * Driver to change passwords via DirectAdmin Control Panel * - * @version 2.1 - * @author Victor Benincasa + * @version 2.2 + * @author Victor Benincasa * - * Copyright (C) 2005-2013, The Roundcube Dev Team + * Copyright (C) 2005-2018, The Roundcube Dev Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,427 +79,426 @@ class rcube_directadmin_password * * Very, very basic usage: * $Socket = new HTTPSocket; - * echo $Socket->get('http://user:pass@somehost.com:2222/CMD_API_SOMEAPI?query=string&this=that'); + * echo $Socket->get('http://user:pass@somesite.com/somedir/some.file?query=string&this=that'); * * @author Phi1 'l0rdphi1' Stier - * @updates 2.7 and 2.8 by Victor Benincasa * @package HTTPSocket - * @version 2.8 - */ -class HTTPSocket { - - var $version = '2.8'; - - /* all vars are private except $error, $query_cache, and $doFollowLocationHeader */ - - var $method = 'GET'; - - var $remote_host; - var $remote_port; - var $remote_uname; - var $remote_passwd; - - var $result; - var $result_header; - var $result_body; - var $result_status_code; - - var $lastTransferSpeed; - - var $bind_host; - - var $error = array(); - var $warn = array(); - var $query_cache = array(); - - var $doFollowLocationHeader = TRUE; - var $redirectURL; - - var $extra_headers = array(); - - /** - * Create server "connection". - * - */ - function connect($host, $port = '' ) - { - if (!is_numeric($port)) - { - $port = 2222; - } - - $this->remote_host = $host; - $this->remote_port = $port; - } - - function bind( $ip = '' ) - { - if ( $ip == '' ) - { - $ip = $_SERVER['SERVER_ADDR']; - } - - $this->bind_host = $ip; - } - - /** - * Change the method being used to communicate. - * - * @param string|null request method. supports GET, POST, and HEAD. default is GET - */ - function set_method( $method = 'GET' ) - { - $this->method = strtoupper($method); - } - - /** - * Specify a username and password. - * - * @param string|null username. default is null - * @param string|null password. default is null - */ - function set_login( $uname = '', $passwd = '' ) - { - if ( strlen($uname) > 0 ) - { - $this->remote_uname = $uname; - } - - if ( strlen($passwd) > 0 ) - { - $this->remote_passwd = $passwd; - } - - } - - /** - * Query the server - * - * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too. - * @param string|array query to pass to url - * @param int if connection KB/s drops below value here, will drop connection - */ - function query( $request, $content = '', $doSpeedCheck = 0 ) - { - $this->error = $this->warn = array(); - $this->result_status_code = NULL; - - // is our request a http(s):// ... ? - if (preg_match('/^(http|https):\/\//i',$request)) - { - $location = parse_url($request); - $this->connect($location['host'],$location['port']); - $this->set_login($location['user'],$location['pass']); + * @version 3.0.2 - $request = $location['path']; - $content = $location['query']; + * 3.0.2 + * added longer curl timeouts - if ( strlen($request) < 1 ) - { - $request = '/'; - } - - } + * 3.0.1 + * support for tcp:// conversion to http:// - $array_headers = array( - 'User-Agent' => "HTTPSocket/$this->version", - 'Host' => ( $this->remote_port == 80 ? parse_url($this->remote_host,PHP_URL_HOST) : parse_url($this->remote_host,PHP_URL_HOST).":".$this->remote_port ), - 'Accept' => '*/*', - 'Connection' => 'Close' ); - - foreach ( $this->extra_headers as $key => $value ) - { - $array_headers[$key] = $value; - } - - $this->result = $this->result_header = $this->result_body = ''; - - // was content sent as an array? if so, turn it into a string - if (is_array($content)) - { - $pairs = array(); - - foreach ( $content as $key => $value ) - { - $pairs[] = "$key=".urlencode($value); - } - - $content = join('&',$pairs); - unset($pairs); - } + * 3.0.0 + * swapped to use curl to address ssl certificate issues with php 5.6 - $OK = TRUE; + * 2.7.2 + * added x-use-https header check + * added max number of location redirects + * added custom settable message if x-use-https is found, so users can be told where to set their scripts + * if a redirect host is https, add ssl:// to remote_host - // instance connection - if ($this->bind_host) - { - $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - socket_bind($socket,$this->bind_host); + * 2.7.1 + * added isset to headers['location'], line 306 - if (!@socket_connect($socket,$this->remote_host,$this->remote_port)) - { - $OK = FALSE; - } - - } - else - { - $socket = @fsockopen( $this->remote_host, $this->remote_port, $sock_errno, $sock_errstr, 10 ); - } - - if ( !$socket || !$OK ) - { - $this->error[] = "Can't create socket connection to $this->remote_host:$this->remote_port."; - return 0; - } - - // if we have a username and password, add the header - if ( isset($this->remote_uname) && isset($this->remote_passwd) ) - { - $array_headers['Authorization'] = 'Basic '.base64_encode("$this->remote_uname:$this->remote_passwd"); - } - - // for DA skins: if $this->remote_passwd is NULL, try to use the login key system - if ( isset($this->remote_uname) && $this->remote_passwd == NULL ) - { - $array_headers['Cookie'] = "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}"; - } - - // if method is POST, add content length & type headers - if ( $this->method == 'POST' ) - { - $array_headers['Content-type'] = 'application/x-www-form-urlencoded'; - $array_headers['Content-length'] = strlen($content); - } - // else method is GET or HEAD. we don't support anything else right now. - else - { - if ($content) - { - $request .= "?$content"; - } - } - - // prepare query - $query = "$this->method $request HTTP/1.0\r\n"; - foreach ( $array_headers as $key => $value ) - { - $query .= "$key: $value\r\n"; - } - $query .= "\r\n"; - - // if POST we need to append our content - if ( $this->method == 'POST' && $content ) - { - $query .= "$content\r\n\r\n"; - } - - // query connection - if ($this->bind_host) - { - socket_write($socket,$query); - - // now load results - while ( $out = socket_read($socket,2048) ) - { - $this->result .= $out; - } - } - else - { - fwrite( $socket, $query, strlen($query) ); - - // now load results - $this->lastTransferSpeed = 0; - $status = socket_get_status($socket); - $startTime = time(); - $length = 0; - while ( !feof($socket) && !$status['timed_out'] ) - { - $chunk = fgets($socket,1024); - $length += strlen($chunk); - $this->result .= $chunk; - - $elapsedTime = time() - $startTime; - - if ( $elapsedTime > 0 ) - { - $this->lastTransferSpeed = ($length/1024)/$elapsedTime; - } - - if ( $doSpeedCheck > 0 && $elapsedTime > 5 && $this->lastTransferSpeed < $doSpeedCheck ) - { - $this->warn[] = "kB/s for last 5 seconds is below 50 kB/s (~".( ($length/1024)/$elapsedTime )."), dropping connection..."; - $this->result_status_code = 503; - break; - } - - } - - if ( $this->lastTransferSpeed == 0 ) - { - $this->lastTransferSpeed = $length/1024; - } - - } - - list($this->result_header,$this->result_body) = preg_split("/\r\n\r\n/",$this->result,2); - - if ($this->bind_host) - { - socket_close($socket); - } - else - { - fclose($socket); - } - - $this->query_cache[] = $query; - - - $headers = $this->fetch_header(); - - // what return status did we get? - if (!$this->result_status_code) - { - preg_match("#HTTP/1\.. (\d+)#",$headers[0],$matches); - $this->result_status_code = $matches[1]; - } - - // did we get the full file? - if ( !empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body) ) - { - $this->result_status_code = 206; - } - - // now, if we're being passed a location header, should we follow it? - if ($this->doFollowLocationHeader) - { - if ($headers['location']) - { - $this->redirectURL = $headers['location']; - $this->query($headers['location']); - } - } - } - - function getTransferSpeed() - { - return $this->lastTransferSpeed; - } - - /** - * The quick way to get a URL's content :) - * - * @param string URL - * @param boolean return as array? (like PHP's file() command) - * @return string result body - */ - function get($location, $asArray = FALSE ) - { - $this->query($location); - - if ( $this->get_status_code() == 200 ) - { - if ($asArray) - { - return preg_split("/\n/",$this->fetch_body()); - } - - return $this->fetch_body(); - } - - return FALSE; - } - - /** - * Returns the last status code. - * 200 = OK; - * 403 = FORBIDDEN; - * etc. - * - * @return int status code - */ - function get_status_code() - { - return $this->result_status_code; - } - - /** - * Adds a header, sent with the next query. - * - * @param string header name - * @param string header value - */ - function add_header($key,$value) - { - $this->extra_headers[$key] = $value; - } - - /** - * Clears any extra headers. - * - */ - function clear_headers() - { - $this->extra_headers = array(); - } - - /** - * Return the result of a query. - * - * @return string result - */ - function fetch_result() - { - return $this->result; - } - - /** - * Return the header of result (stuff before body). - * - * @param string (optional) header to return - * @return array result header - */ - function fetch_header( $header = '' ) - { - $array_headers = preg_split("/\r\n/",$this->result_header); - $array_return = array( 0 => $array_headers[0] ); - unset($array_headers[0]); - - foreach ( $array_headers as $pair ) - { - list($key,$value) = preg_split("/: /",$pair,2); - $array_return[strtolower($key)] = $value; - } - - if ( $header != '' ) - { - return $array_return[strtolower($header)]; - } - - return $array_return; - } - - /** - * Return the body of result (stuff after header). - * - * @return string result body - */ - function fetch_body() - { - return $this->result_body; - } + */ +class HTTPSocket { - /** - * Return parsed body in array format. - * - * @return array result parsed - */ - function fetch_parsed_body() - { - parse_str($this->result_body,$x); - return $x; - } + var $version = '3.0.2'; + + /* all vars are private except $error, $query_cache, and $doFollowLocationHeader */ + + var $method = 'GET'; + + var $remote_host; + var $remote_port; + var $remote_uname; + var $remote_passwd; + + var $result; + var $result_header; + var $result_body; + var $result_status_code; + + var $lastTransferSpeed; + + var $bind_host; + + var $error = array(); + var $warn = array(); + var $query_cache = array(); + + var $doFollowLocationHeader = TRUE; + var $redirectURL; + var $max_redirects = 5; + var $ssl_setting_message = 'DirectAdmin appears to be using SSL. Change your script to connect to ssl://'; + + var $extra_headers = array(); + + /** + * Create server "connection". + * + */ + function connect($host, $port = '' ) + { + if (!is_numeric($port)) + { + $port = 2222; + } + + $this->remote_host = $host; + $this->remote_port = $port; + } + + function bind( $ip = '' ) + { + if ( $ip == '' ) + { + $ip = $_SERVER['SERVER_ADDR']; + } + + $this->bind_host = $ip; + } + + /** + * Change the method being used to communicate. + * + * @param string|null request method. supports GET, POST, and HEAD. default is GET + */ + function set_method( $method = 'GET' ) + { + $this->method = strtoupper($method); + } + + /** + * Specify a username and password. + * + * @param string|null username. default is null + * @param string|null password. default is null + */ + function set_login( $uname = '', $passwd = '' ) + { + if ( strlen($uname) > 0 ) + { + $this->remote_uname = $uname; + } + + if ( strlen($passwd) > 0 ) + { + $this->remote_passwd = $passwd; + } + + } + + /** + * Query the server + * + * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too. + * @param string|array query to pass to url + * @param int if connection KB/s drops below value here, will drop connection + */ + function query( $request, $content = '', $doSpeedCheck = 0 ) + { + $this->error = $this->warn = array(); + $this->result_status_code = NULL; + + $is_ssl = FALSE; + + // is our request a http:// ... ? + if (preg_match('!^http://!i',$request) || preg_match('!^https://!i',$request)) + { + $location = parse_url($request); + if (preg_match('!^https://!i',$request)) + { + $this->connect('https://'.$location['host'],$location['port']); + } + else + $this->connect('http://'.$location['host'],$location['port']); + + $this->set_login($location['user'],$location['pass']); + + $request = $location['path']; + $content = $location['query']; + + if ( strlen($request) < 1 ) + { + $request = '/'; + } + + } + + if (preg_match('!^ssl://!i', $this->remote_host)) + $this->remote_host = 'https://'.substr($this->remote_host, 6); + + if (preg_match('!^tcp://!i', $this->remote_host)) + $this->remote_host = 'http://'.substr($this->remote_host, 6); + + if (preg_match('!^https://!i', $this->remote_host)) + $is_ssl = TRUE; + + $array_headers = array( + 'Host' => ( $this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port" ), + 'Accept' => '*/*', + 'Connection' => 'Close' ); + + foreach ( $this->extra_headers as $key => $value ) + { + $array_headers[$key] = $value; + } + + $this->result = $this->result_header = $this->result_body = ''; + + // was content sent as an array? if so, turn it into a string + if (is_array($content)) + { + $pairs = array(); + + foreach ( $content as $key => $value ) + { + $pairs[] = "$key=".urlencode($value); + } + + $content = join('&',$pairs); + unset($pairs); + } + + $OK = TRUE; + + if ($this->method == 'GET') + $request .= '?'.$content; + + $ch = curl_init($this->remote_host.':'.$this->remote_port.$request); + + if ($is_ssl) + { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //1 + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //2 + //curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + } + + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); + curl_setopt($ch, CURLOPT_USERAGENT, "HTTPSocket/$this->version"); + curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 100); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_HEADER, 1); + + curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 512); + curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 120); + + //if ($this->doFollowLocationHeader) + //{ + // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE); + // curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects); + //} + + // instance connection + if ($this->bind_host) + { + curl_setopt($ch, CURLOPT_INTERFACE, $this->bind_host); + } + + // if we have a username and password, add the header + if ( isset($this->remote_uname) && isset($this->remote_passwd) ) + { + curl_setopt($ch, CURLOPT_USERPWD, $this->remote_uname.':'.$this->remote_passwd); + } + + // for DA skins: if $this->remote_passwd is NULL, try to use the login key system + if ( isset($this->remote_uname) && $this->remote_passwd == NULL ) + { + $array_headers['Cookie'] = "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}"; + } + + // if method is POST, add content length & type headers + if ( $this->method == 'POST' ) + { + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $content); + + //$array_headers['Content-type'] = 'application/x-www-form-urlencoded'; + $array_headers['Content-length'] = strlen($content); + } + + curl_setopt($ch, CURLOPT_HTTPHEADER, $array_headers); + + + if( !($this->result = curl_exec($ch)) ) + { + $this->error[] .= curl_error($ch); + $OK = FALSE; + } + + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $this->result_header = substr($this->result, 0, $header_size); + $this->result_body = substr($this->result, $header_size); + $this->result_status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + $this->lastTransferSpeed = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD) / 1024; + + curl_close($ch); + + $this->query_cache[] = $this->remote_host.':'.$this->remote_port.$request; + + $headers = $this->fetch_header(); + + // did we get the full file? + if ( !empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body) ) + { + $this->result_status_code = 206; + } + + // now, if we're being passed a location header, should we follow it? + if ($this->doFollowLocationHeader) + { + //dont bother if we didn't even setup the script correctly + if (isset($headers['x-use-https']) && $headers['x-use-https']=='yes') + die($this->ssl_setting_message); + + if (isset($headers['location'])) + { + if ($this->max_redirects <= 0) + die("Too many redirects on: ".$headers['location']); + + $this->max_redirects--; + $this->redirectURL = $headers['location']; + $this->query($headers['location']); + } + } + + } + + function getTransferSpeed() + { + return $this->lastTransferSpeed; + } + + /** + * The quick way to get a URL's content :) + * + * @param string URL + * @param boolean return as array? (like PHP's file() command) + * @return string result body + */ + function get($location, $asArray = FALSE ) + { + $this->query($location); + + if ( $this->get_status_code() == 200 ) + { + if ($asArray) + { + return preg_split("/\n/",$this->fetch_body()); + } + + return $this->fetch_body(); + } + + return FALSE; + } + + /** + * Returns the last status code. + * 200 = OK; + * 403 = FORBIDDEN; + * etc. + * + * @return int status code + */ + function get_status_code() + { + return $this->result_status_code; + } + + /** + * Adds a header, sent with the next query. + * + * @param string header name + * @param string header value + */ + function add_header($key,$value) + { + $this->extra_headers[$key] = $value; + } + + /** + * Clears any extra headers. + * + */ + function clear_headers() + { + $this->extra_headers = array(); + } + + /** + * Return the result of a query. + * + * @return string result + */ + function fetch_result() + { + return $this->result; + } + + /** + * Return the header of result (stuff before body). + * + * @param string (optional) header to return + * @return array result header + */ + function fetch_header( $header = '' ) + { + $array_headers = preg_split("/\r\n/",$this->result_header); + + $array_return = array( 0 => $array_headers[0] ); + unset($array_headers[0]); + + foreach ( $array_headers as $pair ) + { + if ($pair == '' || $pair == "\r\n") continue; + list($key,$value) = preg_split("/: /",$pair,2); + $array_return[strtolower($key)] = $value; + } + + if ( $header != '' ) + { + return $array_return[strtolower($header)]; + } + + return $array_return; + } + + /** + * Return the body of result (stuff after header). + * + * @return string result body + */ + function fetch_body() + { + return $this->result_body; + } + + /** + * Return parsed body in array format. + * + * @return array result parsed + */ + function fetch_parsed_body() + { + parse_str($this->result_body,$x); + return $x; + } + + + /** + * Set a specifc message on how to change the SSL setting, in the event that it's not set correctly. + */ + function set_ssl_setting_message($str) + { + $this->ssl_setting_message = $str; + } }