1<?php 2namespace IXR\Client; 3 4use IXR\Exception\ClientException; 5use IXR\Message\Message; 6use IXR\Request\Request; 7 8/** 9 * Client for communicating with a XML-RPC Server over HTTPS. 10 * 11 * @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/) 12 * @version 0.2.0 26May2005 08:34 +0800 13 * @copyright (c) 2004-2005 Jason Stirk 14 * @package IXR 15 */ 16class ClientSSL extends Client 17{ 18 /** 19 * Filename of the SSL Client Certificate 20 * @access private 21 * @since 0.1.0 22 * @var string 23 */ 24 private $_certFile; 25 26 /** 27 * Filename of the SSL CA Certificate 28 * @access private 29 * @since 0.1.0 30 * @var string 31 */ 32 private $_caFile; 33 34 /** 35 * Filename of the SSL Client Private Key 36 * @access private 37 * @since 0.1.0 38 * @var string 39 */ 40 private $_keyFile; 41 42 /** 43 * Passphrase to unlock the private key 44 * @access private 45 * @since 0.1.0 46 * @var string 47 */ 48 private $_passphrase; 49 50 /** 51 * Constructor 52 * @param string $server URL of the Server to connect to 53 * @since 0.1.0 54 */ 55 public function __construct($server, $path = false, $port = 443, $timeout = false, $timeout_io = null) 56 { 57 parent::__construct($server, $path, $port, $timeout, $timeout_io); 58 $this->useragent = 'The Incutio XML-RPC PHP Library for SSL'; 59 60 // Set class fields 61 $this->_certFile = false; 62 $this->_caFile = false; 63 $this->_keyFile = false; 64 $this->_passphrase = ''; 65 } 66 67 /** 68 * Set the client side certificates to communicate with the server. 69 * 70 * @since 0.1.0 71 * @param string $certificateFile Filename of the client side certificate to use 72 * @param string $keyFile Filename of the client side certificate's private key 73 * @param string $keyPhrase Passphrase to unlock the private key 74 * @throws ClientException 75 */ 76 public function setCertificate($certificateFile, $keyFile, $keyPhrase = '') 77 { 78 // Check the files all exist 79 if (is_file($certificateFile)) { 80 $this->_certFile = $certificateFile; 81 } else { 82 throw new ClientException('Could not open certificate: ' . $certificateFile); 83 } 84 85 if (is_file($keyFile)) { 86 $this->_keyFile = $keyFile; 87 } else { 88 throw new ClientException('Could not open private key: ' . $keyFile); 89 } 90 91 $this->_passphrase = (string)$keyPhrase; 92 } 93 94 public function setCACertificate($caFile) 95 { 96 if (is_file($caFile)) { 97 $this->_caFile = $caFile; 98 } else { 99 throw new ClientException('Could not open CA certificate: ' . $caFile); 100 } 101 } 102 103 /** 104 * Sets the connection timeout (in seconds) 105 * @param int $newTimeOut Timeout in seconds 106 * @returns void 107 * @since 0.1.2 108 */ 109 public function setTimeOut($newTimeOut) 110 { 111 $this->timeout = (int)$newTimeOut; 112 } 113 114 /** 115 * Returns the connection timeout (in seconds) 116 * @returns int 117 * @since 0.1.2 118 */ 119 public function getTimeOut() 120 { 121 return $this->timeout; 122 } 123 124 /** 125 * Set the query to send to the XML-RPC Server 126 * @since 0.1.0 127 */ 128 public function query() 129 { 130 $args = func_get_args(); 131 $method = array_shift($args); 132 $request = new Request($method, $args); 133 $length = $request->getLength(); 134 $xml = $request->getXml(); 135 136 $this->debugOutput('<pre>' . htmlspecialchars($xml) . PHP_EOL . '</pre>'); 137 138 //This is where we deviate from the normal query() 139 //Rather than open a normal sock, we will actually use the cURL 140 //extensions to make the calls, and handle the SSL stuff. 141 142 $curl = curl_init('https://' . $this->server . $this->path); 143 144 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 145 146 //Since 23Jun2004 (0.1.2) - Made timeout a class field 147 curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->timeout); 148 if (null !== $this->timeout_io) { 149 curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout_io); 150 } 151 152 if ($this->debug) { 153 curl_setopt($curl, CURLOPT_VERBOSE, 1); 154 } 155 156 curl_setopt($curl, CURLOPT_HEADER, 1); 157 curl_setopt($curl, CURLOPT_POST, 1); 158 curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); 159 if($this->port !== 443) { 160 curl_setopt($curl, CURLOPT_PORT, $this->port); 161 } 162 curl_setopt($curl, CURLOPT_HTTPHEADER, [ 163 "Content-Type: text/xml", 164 "Content-length: {$length}" 165 ]); 166 167 // Process the SSL certificates, etc. to use 168 if (!($this->_certFile === false)) { 169 // We have a certificate file set, so add these to the cURL handler 170 curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile); 171 curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile); 172 173 if ($this->debug) { 174 $this->debugOutput('SSL Cert at : ' . $this->_certFile); 175 $this->debugOutput('SSL Key at : ' . $this->_keyFile); 176 } 177 178 // See if we need to give a passphrase 179 if (!($this->_passphrase === '')) { 180 curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase); 181 } 182 183 if ($this->_caFile === false) { 184 // Don't verify their certificate, as we don't have a CA to verify against 185 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); 186 } else { 187 // Verify against a CA 188 curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile); 189 } 190 } 191 192 // Call cURL to do it's stuff and return us the content 193 $contents = curl_exec($curl); 194 curl_close($curl); 195 196 // Check for 200 Code in $contents 197 if (!strstr($contents, '200 OK')) { 198 //There was no "200 OK" returned - we failed 199 return $this->handleError(-32300, 'transport error - HTTP status code was not 200'); 200 } 201 202 if ($this->debug) { 203 $this->debugOutput('<pre>' . htmlspecialchars($contents) . PHP_EOL . '</pre>'); 204 } 205 // Now parse what we've got back 206 // Since 20Jun2004 (0.1.1) - We need to remove the headers first 207 // Why I have only just found this, I will never know... 208 // So, remove everything before the first < 209 $contents = substr($contents, strpos($contents, '<')); 210 211 $this->message = new Message($contents); 212 if (!$this->message->parse()) { 213 // XML error 214 return $this->handleError(-32700, 'parse error. not well formed'); 215 } 216 // Is the message a fault? 217 if ($this->message->messageType == 'fault') { 218 return $this->handleError($this->message->faultCode, $this->message->faultString); 219 } 220 221 // Message must be OK 222 return true; 223 } 224 225 /** 226 * Debug output, if debug is enabled 227 * @param $message 228 */ 229 private function debugOutput($message) 230 { 231 if ($this->debug) { 232 echo $message . PHP_EOL; 233 } 234 } 235} 236