17f8f2456SAndreas Gohr<?php 27f8f2456SAndreas Gohrnamespace IXR\Client; 37f8f2456SAndreas Gohr 47f8f2456SAndreas Gohruse IXR\Exception\ClientException; 57f8f2456SAndreas Gohruse IXR\Message\Message; 67f8f2456SAndreas Gohruse IXR\Request\Request; 77f8f2456SAndreas Gohr 87f8f2456SAndreas Gohr/** 97f8f2456SAndreas Gohr * Client for communicating with a XML-RPC Server over HTTPS. 107f8f2456SAndreas Gohr * 117f8f2456SAndreas Gohr * @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/) 127f8f2456SAndreas Gohr * @version 0.2.0 26May2005 08:34 +0800 137f8f2456SAndreas Gohr * @copyright (c) 2004-2005 Jason Stirk 147f8f2456SAndreas Gohr * @package IXR 157f8f2456SAndreas Gohr */ 167f8f2456SAndreas Gohrclass ClientSSL extends Client 177f8f2456SAndreas Gohr{ 187f8f2456SAndreas Gohr /** 197f8f2456SAndreas Gohr * Filename of the SSL Client Certificate 207f8f2456SAndreas Gohr * @access private 217f8f2456SAndreas Gohr * @since 0.1.0 227f8f2456SAndreas Gohr * @var string 237f8f2456SAndreas Gohr */ 247f8f2456SAndreas Gohr private $_certFile; 257f8f2456SAndreas Gohr 267f8f2456SAndreas Gohr /** 277f8f2456SAndreas Gohr * Filename of the SSL CA Certificate 287f8f2456SAndreas Gohr * @access private 297f8f2456SAndreas Gohr * @since 0.1.0 307f8f2456SAndreas Gohr * @var string 317f8f2456SAndreas Gohr */ 327f8f2456SAndreas Gohr private $_caFile; 337f8f2456SAndreas Gohr 347f8f2456SAndreas Gohr /** 357f8f2456SAndreas Gohr * Filename of the SSL Client Private Key 367f8f2456SAndreas Gohr * @access private 377f8f2456SAndreas Gohr * @since 0.1.0 387f8f2456SAndreas Gohr * @var string 397f8f2456SAndreas Gohr */ 407f8f2456SAndreas Gohr private $_keyFile; 417f8f2456SAndreas Gohr 427f8f2456SAndreas Gohr /** 437f8f2456SAndreas Gohr * Passphrase to unlock the private key 447f8f2456SAndreas Gohr * @access private 457f8f2456SAndreas Gohr * @since 0.1.0 467f8f2456SAndreas Gohr * @var string 477f8f2456SAndreas Gohr */ 487f8f2456SAndreas Gohr private $_passphrase; 497f8f2456SAndreas Gohr 507f8f2456SAndreas Gohr /** 517f8f2456SAndreas Gohr * Constructor 527f8f2456SAndreas Gohr * @param string $server URL of the Server to connect to 537f8f2456SAndreas Gohr * @since 0.1.0 547f8f2456SAndreas Gohr */ 557f8f2456SAndreas Gohr public function __construct($server, $path = false, $port = 443, $timeout = false, $timeout_io = null) 567f8f2456SAndreas Gohr { 577f8f2456SAndreas Gohr parent::__construct($server, $path, $port, $timeout, $timeout_io); 587f8f2456SAndreas Gohr $this->useragent = 'The Incutio XML-RPC PHP Library for SSL'; 597f8f2456SAndreas Gohr 607f8f2456SAndreas Gohr // Set class fields 617f8f2456SAndreas Gohr $this->_certFile = false; 627f8f2456SAndreas Gohr $this->_caFile = false; 637f8f2456SAndreas Gohr $this->_keyFile = false; 647f8f2456SAndreas Gohr $this->_passphrase = ''; 657f8f2456SAndreas Gohr } 667f8f2456SAndreas Gohr 677f8f2456SAndreas Gohr /** 687f8f2456SAndreas Gohr * Set the client side certificates to communicate with the server. 697f8f2456SAndreas Gohr * 707f8f2456SAndreas Gohr * @since 0.1.0 717f8f2456SAndreas Gohr * @param string $certificateFile Filename of the client side certificate to use 727f8f2456SAndreas Gohr * @param string $keyFile Filename of the client side certificate's private key 737f8f2456SAndreas Gohr * @param string $keyPhrase Passphrase to unlock the private key 747f8f2456SAndreas Gohr * @throws ClientException 757f8f2456SAndreas Gohr */ 767f8f2456SAndreas Gohr public function setCertificate($certificateFile, $keyFile, $keyPhrase = '') 777f8f2456SAndreas Gohr { 787f8f2456SAndreas Gohr // Check the files all exist 797f8f2456SAndreas Gohr if (is_file($certificateFile)) { 807f8f2456SAndreas Gohr $this->_certFile = $certificateFile; 817f8f2456SAndreas Gohr } else { 827f8f2456SAndreas Gohr throw new ClientException('Could not open certificate: ' . $certificateFile); 837f8f2456SAndreas Gohr } 847f8f2456SAndreas Gohr 857f8f2456SAndreas Gohr if (is_file($keyFile)) { 867f8f2456SAndreas Gohr $this->_keyFile = $keyFile; 877f8f2456SAndreas Gohr } else { 887f8f2456SAndreas Gohr throw new ClientException('Could not open private key: ' . $keyFile); 897f8f2456SAndreas Gohr } 907f8f2456SAndreas Gohr 917f8f2456SAndreas Gohr $this->_passphrase = (string)$keyPhrase; 927f8f2456SAndreas Gohr } 937f8f2456SAndreas Gohr 947f8f2456SAndreas Gohr public function setCACertificate($caFile) 957f8f2456SAndreas Gohr { 967f8f2456SAndreas Gohr if (is_file($caFile)) { 977f8f2456SAndreas Gohr $this->_caFile = $caFile; 987f8f2456SAndreas Gohr } else { 997f8f2456SAndreas Gohr throw new ClientException('Could not open CA certificate: ' . $caFile); 1007f8f2456SAndreas Gohr } 1017f8f2456SAndreas Gohr } 1027f8f2456SAndreas Gohr 1037f8f2456SAndreas Gohr /** 1047f8f2456SAndreas Gohr * Sets the connection timeout (in seconds) 1057f8f2456SAndreas Gohr * @param int $newTimeOut Timeout in seconds 1067f8f2456SAndreas Gohr * @returns void 1077f8f2456SAndreas Gohr * @since 0.1.2 1087f8f2456SAndreas Gohr */ 1097f8f2456SAndreas Gohr public function setTimeOut($newTimeOut) 1107f8f2456SAndreas Gohr { 1117f8f2456SAndreas Gohr $this->timeout = (int)$newTimeOut; 1127f8f2456SAndreas Gohr } 1137f8f2456SAndreas Gohr 1147f8f2456SAndreas Gohr /** 1157f8f2456SAndreas Gohr * Returns the connection timeout (in seconds) 1167f8f2456SAndreas Gohr * @returns int 1177f8f2456SAndreas Gohr * @since 0.1.2 1187f8f2456SAndreas Gohr */ 1197f8f2456SAndreas Gohr public function getTimeOut() 1207f8f2456SAndreas Gohr { 1217f8f2456SAndreas Gohr return $this->timeout; 1227f8f2456SAndreas Gohr } 1237f8f2456SAndreas Gohr 1247f8f2456SAndreas Gohr /** 1257f8f2456SAndreas Gohr * Set the query to send to the XML-RPC Server 1267f8f2456SAndreas Gohr * @since 0.1.0 1277f8f2456SAndreas Gohr */ 1287f8f2456SAndreas Gohr public function query() 1297f8f2456SAndreas Gohr { 1307f8f2456SAndreas Gohr $args = func_get_args(); 1317f8f2456SAndreas Gohr $method = array_shift($args); 1327f8f2456SAndreas Gohr $request = new Request($method, $args); 1337f8f2456SAndreas Gohr $length = $request->getLength(); 1347f8f2456SAndreas Gohr $xml = $request->getXml(); 1357f8f2456SAndreas Gohr 1367f8f2456SAndreas Gohr $this->debugOutput('<pre>' . htmlspecialchars($xml) . PHP_EOL . '</pre>'); 1377f8f2456SAndreas Gohr 1387f8f2456SAndreas Gohr //This is where we deviate from the normal query() 1397f8f2456SAndreas Gohr //Rather than open a normal sock, we will actually use the cURL 1407f8f2456SAndreas Gohr //extensions to make the calls, and handle the SSL stuff. 1417f8f2456SAndreas Gohr 1427f8f2456SAndreas Gohr $curl = curl_init('https://' . $this->server . $this->path); 1437f8f2456SAndreas Gohr 1447f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 1457f8f2456SAndreas Gohr 1467f8f2456SAndreas Gohr //Since 23Jun2004 (0.1.2) - Made timeout a class field 1477f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->timeout); 1487f8f2456SAndreas Gohr if (null !== $this->timeout_io) { 1497f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout_io); 1507f8f2456SAndreas Gohr } 1517f8f2456SAndreas Gohr 1527f8f2456SAndreas Gohr if ($this->debug) { 1537f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_VERBOSE, 1); 1547f8f2456SAndreas Gohr } 1557f8f2456SAndreas Gohr 1567f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_HEADER, 1); 1577f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_POST, 1); 1587f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); 1597f8f2456SAndreas Gohr if($this->port !== 443) { 1607f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_PORT, $this->port); 1617f8f2456SAndreas Gohr } 1627f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_HTTPHEADER, [ 1637f8f2456SAndreas Gohr "Content-Type: text/xml", 1647f8f2456SAndreas Gohr "Content-length: {$length}" 1657f8f2456SAndreas Gohr ]); 1667f8f2456SAndreas Gohr 1677f8f2456SAndreas Gohr // Process the SSL certificates, etc. to use 1687f8f2456SAndreas Gohr if (!($this->_certFile === false)) { 1697f8f2456SAndreas Gohr // We have a certificate file set, so add these to the cURL handler 1707f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile); 1717f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile); 1727f8f2456SAndreas Gohr 1737f8f2456SAndreas Gohr if ($this->debug) { 1747f8f2456SAndreas Gohr $this->debugOutput('SSL Cert at : ' . $this->_certFile); 1757f8f2456SAndreas Gohr $this->debugOutput('SSL Key at : ' . $this->_keyFile); 1767f8f2456SAndreas Gohr } 1777f8f2456SAndreas Gohr 1787f8f2456SAndreas Gohr // See if we need to give a passphrase 1797f8f2456SAndreas Gohr if (!($this->_passphrase === '')) { 1807f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase); 1817f8f2456SAndreas Gohr } 1827f8f2456SAndreas Gohr 1837f8f2456SAndreas Gohr if ($this->_caFile === false) { 1847f8f2456SAndreas Gohr // Don't verify their certificate, as we don't have a CA to verify against 1857f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); 1867f8f2456SAndreas Gohr } else { 1877f8f2456SAndreas Gohr // Verify against a CA 1887f8f2456SAndreas Gohr curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile); 1897f8f2456SAndreas Gohr } 1907f8f2456SAndreas Gohr } 1917f8f2456SAndreas Gohr 1927f8f2456SAndreas Gohr // Call cURL to do it's stuff and return us the content 1937f8f2456SAndreas Gohr $contents = curl_exec($curl); 1947f8f2456SAndreas Gohr curl_close($curl); 1957f8f2456SAndreas Gohr 1967f8f2456SAndreas Gohr // Check for 200 Code in $contents 197*934f970aSAndreas Gohr if (!strstr($contents, '200 OK') && !strstr($contents, 'HTTP/2 200')) { 1987f8f2456SAndreas Gohr //There was no "200 OK" returned - we failed 1997f8f2456SAndreas Gohr return $this->handleError(-32300, 'transport error - HTTP status code was not 200'); 2007f8f2456SAndreas Gohr } 2017f8f2456SAndreas Gohr 2027f8f2456SAndreas Gohr if ($this->debug) { 2037f8f2456SAndreas Gohr $this->debugOutput('<pre>' . htmlspecialchars($contents) . PHP_EOL . '</pre>'); 2047f8f2456SAndreas Gohr } 2057f8f2456SAndreas Gohr // Now parse what we've got back 2067f8f2456SAndreas Gohr // Since 20Jun2004 (0.1.1) - We need to remove the headers first 2077f8f2456SAndreas Gohr // Why I have only just found this, I will never know... 2087f8f2456SAndreas Gohr // So, remove everything before the first < 2097f8f2456SAndreas Gohr $contents = substr($contents, strpos($contents, '<')); 2107f8f2456SAndreas Gohr 2117f8f2456SAndreas Gohr $this->message = new Message($contents); 2127f8f2456SAndreas Gohr if (!$this->message->parse()) { 2137f8f2456SAndreas Gohr // XML error 2147f8f2456SAndreas Gohr return $this->handleError(-32700, 'parse error. not well formed'); 2157f8f2456SAndreas Gohr } 2167f8f2456SAndreas Gohr // Is the message a fault? 2177f8f2456SAndreas Gohr if ($this->message->messageType == 'fault') { 2187f8f2456SAndreas Gohr return $this->handleError($this->message->faultCode, $this->message->faultString); 2197f8f2456SAndreas Gohr } 2207f8f2456SAndreas Gohr 2217f8f2456SAndreas Gohr // Message must be OK 2227f8f2456SAndreas Gohr return true; 2237f8f2456SAndreas Gohr } 2247f8f2456SAndreas Gohr 2257f8f2456SAndreas Gohr /** 2267f8f2456SAndreas Gohr * Debug output, if debug is enabled 2277f8f2456SAndreas Gohr * @param $message 2287f8f2456SAndreas Gohr */ 2297f8f2456SAndreas Gohr private function debugOutput($message) 2307f8f2456SAndreas Gohr { 2317f8f2456SAndreas Gohr if ($this->debug) { 2327f8f2456SAndreas Gohr echo $message . PHP_EOL; 2337f8f2456SAndreas Gohr } 2347f8f2456SAndreas Gohr } 2357f8f2456SAndreas Gohr} 236