1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('Validation', 'Utility');
20:
21: 22: 23: 24: 25: 26: 27:
28: class CakeSocket {
29:
30: 31: 32: 33: 34:
35: public $description = 'Remote DataSource Network Socket Interface';
36:
37: 38: 39: 40: 41:
42: protected $_baseConfig = array(
43: 'persistent' => false,
44: 'host' => 'localhost',
45: 'protocol' => 'tcp',
46: 'port' => 80,
47: 'timeout' => 30,
48: 'cryptoType' => 'tls',
49: );
50:
51: 52: 53: 54: 55:
56: public $config = array();
57:
58: 59: 60: 61: 62:
63: public $connection = null;
64:
65: 66: 67: 68: 69:
70: public $connected = false;
71:
72: 73: 74: 75: 76:
77: public $lastError = array();
78:
79: 80: 81: 82: 83:
84: public $encrypted = false;
85:
86: 87: 88: 89: 90:
91: protected $_encryptMethods = array(
92:
93: 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT,
94: 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
95: 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
96: 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT,
97: 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER,
98: 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER,
99: 'sslv23_server' => STREAM_CRYPTO_METHOD_SSLv23_SERVER,
100: 'tls_server' => STREAM_CRYPTO_METHOD_TLS_SERVER,
101:
102: );
103:
104: 105: 106: 107: 108: 109:
110: protected $_connectionErrors = array();
111:
112: 113: 114: 115: 116: 117:
118: public function __construct($config = array()) {
119: $this->config = array_merge($this->_baseConfig, $config);
120:
121: $this->_addTlsVersions();
122: }
123:
124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136:
137: protected function _addTlsVersions() {
138: $conditionalCrypto = array(
139: 'tlsv1_1_client' => 'STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT',
140: 'tlsv1_2_client' => 'STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT',
141: 'tlsv1_1_server' => 'STREAM_CRYPTO_METHOD_TLSv1_1_SERVER',
142: 'tlsv1_2_server' => 'STREAM_CRYPTO_METHOD_TLSv1_2_SERVER'
143: );
144: foreach ($conditionalCrypto as $key => $const) {
145: if (defined($const)) {
146: $this->_encryptMethods[$key] = constant($const);
147: }
148: }
149:
150:
151: if (isset($this->_encryptMethods['tlsv1_2_client'])) {
152: $this->_encryptMethods['tls_client'] = STREAM_CRYPTO_METHOD_TLS_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
153: }
154: if (isset($this->_encryptMethods['tlsv1_2_server'])) {
155: $this->_encryptMethods['tls_server'] = STREAM_CRYPTO_METHOD_TLS_SERVER | STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
156: }
157:
158: }
159:
160: 161: 162: 163: 164: 165:
166: public function connect() {
167: if ($this->connection) {
168: $this->disconnect();
169: }
170:
171: $hasProtocol = strpos($this->config['host'], '://') !== false;
172: if ($hasProtocol) {
173: list($this->config['protocol'], $this->config['host']) = explode('://', $this->config['host']);
174: }
175: $scheme = null;
176: if (!empty($this->config['protocol'])) {
177: $scheme = $this->config['protocol'] . '://';
178: }
179: if (!empty($this->config['proxy'])) {
180: $scheme = 'tcp://';
181: }
182:
183: $host = $this->config['host'];
184: if (isset($this->config['request']['uri']['host'])) {
185: $host = $this->config['request']['uri']['host'];
186: }
187: $this->_setSslContext($host);
188:
189: if (!empty($this->config['context'])) {
190: $context = stream_context_create($this->config['context']);
191: } else {
192: $context = stream_context_create();
193: }
194:
195: $connectAs = STREAM_CLIENT_CONNECT;
196: if ($this->config['persistent']) {
197: $connectAs |= STREAM_CLIENT_PERSISTENT;
198: }
199:
200: set_error_handler(array($this, '_connectionErrorHandler'));
201: $this->connection = stream_socket_client(
202: $scheme . $this->config['host'] . ':' . $this->config['port'],
203: $errNum,
204: $errStr,
205: $this->config['timeout'],
206: $connectAs,
207: $context
208: );
209: restore_error_handler();
210:
211: if (!empty($errNum) || !empty($errStr)) {
212: $this->setLastError($errNum, $errStr);
213: throw new SocketException($errStr, $errNum);
214: }
215:
216: if (!$this->connection && $this->_connectionErrors) {
217: $message = implode("\n", $this->_connectionErrors);
218: throw new SocketException($message, E_WARNING);
219: }
220:
221: $this->connected = is_resource($this->connection);
222: if ($this->connected) {
223: stream_set_timeout($this->connection, $this->config['timeout']);
224:
225: if (!empty($this->config['request']) &&
226: $this->config['request']['uri']['scheme'] === 'https' &&
227: !empty($this->config['proxy'])
228: ) {
229: $req = array();
230: $req[] = 'CONNECT ' . $this->config['request']['uri']['host'] . ':' .
231: $this->config['request']['uri']['port'] . ' HTTP/1.1';
232: $req[] = 'Host: ' . $this->config['host'];
233: $req[] = 'User-Agent: php proxy';
234: if (!empty($this->config['proxyauth'])) {
235: $req[] = 'Proxy-Authorization: ' . $this->config['proxyauth'];
236: }
237:
238: fwrite($this->connection, implode("\r\n", $req) . "\r\n\r\n");
239:
240: while (!feof($this->connection)) {
241: $s = rtrim(fgets($this->connection, 4096));
242: if (preg_match('/^$/', $s)) {
243: break;
244: }
245: }
246:
247: $this->enableCrypto($this->config['cryptoType'], 'client');
248: }
249: }
250: return $this->connected;
251: }
252:
253: 254: 255: 256: 257: 258:
259: protected function _setSslContext($host) {
260: foreach ($this->config as $key => $value) {
261: if (substr($key, 0, 4) !== 'ssl_') {
262: continue;
263: }
264: $contextKey = substr($key, 4);
265: if (empty($this->config['context']['ssl'][$contextKey])) {
266: $this->config['context']['ssl'][$contextKey] = $value;
267: }
268: unset($this->config[$key]);
269: }
270: if (version_compare(PHP_VERSION, '5.3.2', '>=')) {
271: if (!isset($this->config['context']['ssl']['SNI_enabled'])) {
272: $this->config['context']['ssl']['SNI_enabled'] = true;
273: }
274: if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
275: if (empty($this->config['context']['ssl']['peer_name'])) {
276: $this->config['context']['ssl']['peer_name'] = $host;
277: }
278: } else {
279: if (empty($this->config['context']['ssl']['SNI_server_name'])) {
280: $this->config['context']['ssl']['SNI_server_name'] = $host;
281: }
282: }
283: }
284: if (empty($this->config['context']['ssl']['cafile'])) {
285: $this->config['context']['ssl']['cafile'] = CAKE . 'Config' . DS . 'cacert.pem';
286: }
287: if (!empty($this->config['context']['ssl']['verify_host'])) {
288: $this->config['context']['ssl']['CN_match'] = $host;
289: }
290: unset($this->config['context']['ssl']['verify_host']);
291: }
292:
293: 294: 295: 296: 297: 298: 299: 300: 301: 302:
303: protected function _connectionErrorHandler($code, $message) {
304: $this->_connectionErrors[] = $message;
305: }
306:
307: 308: 309: 310: 311:
312: public function context() {
313: if (!$this->connection) {
314: return null;
315: }
316: return stream_context_get_options($this->connection);
317: }
318:
319: 320: 321: 322: 323:
324: public function host() {
325: if (Validation::ip($this->config['host'])) {
326: return gethostbyaddr($this->config['host']);
327: }
328: return gethostbyaddr($this->address());
329: }
330:
331: 332: 333: 334: 335:
336: public function address() {
337: if (Validation::ip($this->config['host'])) {
338: return $this->config['host'];
339: }
340: return gethostbyname($this->config['host']);
341: }
342:
343: 344: 345: 346: 347:
348: public function addresses() {
349: if (Validation::ip($this->config['host'])) {
350: return array($this->config['host']);
351: }
352: return gethostbynamel($this->config['host']);
353: }
354:
355: 356: 357: 358: 359:
360: public function lastError() {
361: if (!empty($this->lastError)) {
362: return $this->lastError['num'] . ': ' . $this->lastError['str'];
363: }
364: return null;
365: }
366:
367: 368: 369: 370: 371: 372: 373:
374: public function setLastError($errNum, $errStr) {
375: $this->lastError = array('num' => $errNum, 'str' => $errStr);
376: }
377:
378: 379: 380: 381: 382: 383:
384: public function write($data) {
385: if (!$this->connected) {
386: if (!$this->connect()) {
387: return false;
388: }
389: }
390: $totalBytes = strlen($data);
391: for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) {
392: $rv = fwrite($this->connection, substr($data, $written));
393: if ($rv === false || $rv === 0) {
394: return $written;
395: }
396: }
397: return $written;
398: }
399:
400: 401: 402: 403: 404: 405: 406:
407: public function read($length = 1024) {
408: if (!$this->connected) {
409: if (!$this->connect()) {
410: return false;
411: }
412: }
413:
414: if (!feof($this->connection)) {
415: $buffer = fread($this->connection, $length);
416: $info = stream_get_meta_data($this->connection);
417: if ($info['timed_out']) {
418: $this->setLastError(E_WARNING, __d('cake_dev', 'Connection timed out'));
419: return false;
420: }
421: return $buffer;
422: }
423: return false;
424: }
425:
426: 427: 428: 429: 430:
431: public function disconnect() {
432: if (!is_resource($this->connection)) {
433: $this->connected = false;
434: return true;
435: }
436: $this->connected = !fclose($this->connection);
437:
438: if (!$this->connected) {
439: $this->connection = null;
440: }
441: return !$this->connected;
442: }
443:
444: 445: 446:
447: public function __destruct() {
448: $this->disconnect();
449: }
450:
451: 452: 453: 454: 455: 456:
457: public function reset($state = null) {
458: if (empty($state)) {
459: static $initalState = array();
460: if (empty($initalState)) {
461: $initalState = get_class_vars(__CLASS__);
462: }
463: $state = $initalState;
464: }
465:
466: foreach ($state as $property => $value) {
467: $this->{$property} = $value;
468: }
469: return true;
470: }
471:
472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482:
483: public function enableCrypto($type, $clientOrServer = 'client', $enable = true) {
484: if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) {
485: throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen'));
486: }
487: $enableCryptoResult = false;
488: try {
489: $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable,
490: $this->_encryptMethods[$type . '_' . $clientOrServer]);
491: } catch (Exception $e) {
492: $this->setLastError(null, $e->getMessage());
493: throw new SocketException($e->getMessage());
494: }
495: if ($enableCryptoResult === true) {
496: $this->encrypted = $enable;
497: return true;
498: }
499: $errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket');
500: $this->setLastError(null, $errorMessage);
501: throw new SocketException($errorMessage);
502: }
503: }
504: