1 <?php
2 3 4 5 6 7 8 9 10 11 12 13
14
15 require_once(dirname(__FILE__) . '/KerioApiInterface.php');
16 require_once(dirname(__FILE__) . '/KerioApiSocket.php');
17 require_once(dirname(__FILE__) . '/KerioApiException.php');
18
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
46 class KerioApi implements KerioApiInterface {
47
48 49 50
51 const CRLF = "\r\n";
52
53 54 55
56 const HTTP_SERVER_OK = 200;
57
58 59 60 61
62 public $name = 'Kerio APIs Client Library for PHP';
63
64 65 66 67
68 public $version = '1.4.0.234';
69
70 71 72 73
74 private $debug = FALSE;
75
76 77 78 79
80 private $requestId = 0;
81
82 83 84 85
86 protected $hostname = '';
87
88 89 90 91
92 protected $token = '';
93
94 95 96 97
98 protected $cookies = '';
99
100 101 102 103
104 protected $application = array('name' => '', 'vendor' => '', 'version' => '');
105
106 107 108 109
110 protected $jsonRpc = array('version' => '', 'port' => '', 'api' => '');
111
112 113 114 115
116 protected = array();
117
118 119 120 121
122 private $socketHandler = '';
123
124 125 126 127
128 private $timeout = '';
129
130 131 132 133 134 135 136 137 138
139 public function __construct($name, $vendor, $version) {
140 $this->checkPhpEnvironment();
141 $this->setApplication($name, $vendor, $version);
142 $this->setJsonRpc($this->jsonRpc['version'], $this->jsonRpc['port'], $this->jsonRpc['api']);
143 }
144
145 146 147 148 149 150
151 private function checkPhpEnvironment() {
152 if (version_compare(PHP_VERSION, '5.1.0', '<')) {
153 die(sprintf('<h1>kerio-api-php error</h1>Minimum PHP version required is 5.1.0. Your installation is %s.<br>Please, upgrade your PHP installation.', phpversion()));
154 }
155 if (FALSE === function_exists('openssl_open')) {
156 die('<h1>kerio-api-php error</h1>Your PHP installation does not have OpenSSL enabled.<br>To configure OpenSSL support in PHP, please edit your php.ini config file and enable row with php_openssl module, e.g. extension=php_openssl.dll<br>For more information see <a href="http://www.php.net/manual/en/openssl.installation.php">http://www.php.net/manual/en/openssl.installation.php</a>.');
157 }
158 if (FALSE === function_exists('json_decode')) {
159 die('<h1>kerio-api-php error</h1>Your PHP installation does not have JSON enabled.<br>To configure JSON support in PHP, please edit your php.ini config file and enable row with php_json module, e.g. extension=php_json.dll<br>For more information see <a href="http://www.php.net/manual/en/json.installation.php">http://www.php.net/manual/en/json.installation.php</a>.');
160 }
161 }
162
163 164 165 166 167 168 169 170 171
172 private function setApplication($name, $vendor, $version) {
173 if (empty($name) && empty($vendor) && empty($version)) {
174 throw new KerioApiException('Application not defined.');
175 }
176 else {
177 $this->debug(sprintf("Registering application '%s' by '%s' version '%s'<br>", $name, $vendor, $version));
178 $this->application = array(
179 'name' => $name,
180 'vendor' => $vendor,
181 'version' => $version
182 );
183 }
184 }
185
186 187 188 189 190 191
192 public final function getApplication() {
193 return $this->application;
194 }
195
196 197 198 199 200 201 202 203 204 205
206 public final function setJsonRpc($version, $port, $api) {
207 if (empty($version) && empty($port) && empty($api)) {
208 throw new KerioApiException('JSON-RPC not defined.');
209 }
210 else {
211 $this->debug(sprintf("Registering JSON-RPC %s on %s using port %d", $version, $api, $port));
212 $this->jsonRpc = array(
213 'version' => $version,
214 'port' => $port,
215 'api' => $api
216 );
217 }
218 }
219
220 221 222 223 224 225
226 public final function getJsonRpc() {
227 return $this->jsonRpc;
228 }
229
230 231 232 233 234 235
236 public final function setDebug($boolean) {
237 $this->debug = (bool) $boolean;
238 }
239
240 241 242 243 244 245
246 public final function getDebug() {
247 return $this->debug;
248 }
249
250 251 252 253 254 255 256
257 public function debug($message, $css = 'debug') {
258 if ($this->debug) {
259 printf('<div class="%s">%s</div>%s', $css, $message, "\n");
260 }
261 }
262
263 264 265 266 267 268
269 public function getApiVersion() {
270 $method = 'Version.getApiVersion';
271 $response = $this->sendRequest($method);
272 return $response['apiVersion'];
273 }
274
275 276 277 278 279 280 281 282 283 284
285 public function login($hostname, $username, $password) {
286 $this->clean();
287
288 if (empty($hostname)) {
289 throw new KerioApiException('Cannot login. Hostname not set.');
290 }
291 elseif (empty($username)) {
292 throw new KerioApiException('Cannot login. Username not set.');
293 }
294 elseif (empty($this->application)) {
295 throw new KerioApiException('Cannot login. Application not defined.');
296 }
297
298 $this->setHostname($hostname);
299
300 $method = 'Session.login';
301 $params = array(
302 'userName' => $username,
303 'password' => $password,
304 'application' => $this->application
305 );
306
307 $response = $this->sendRequest($method, $params);
308 return $response;
309 }
310
311 312 313 314 315 316 317
318 public function logout() {
319 $method = 'Session.logout';
320 $response = $this->sendRequest($method);
321 $this->clean();
322 return $response;
323 }
324
325 326 327 328 329 330
331 public function clean() {
332 if ($this->token) {
333 $this->debug('Removing X-Token.');
334 $this->token = '';
335 }
336 if ($this->cookies) {
337 $this->debug('Removing Cookies.');
338 $this->cookies = '';
339 }
340 $this->hostname = '';
341 $this->socketHandler = '';
342 }
343
344 345 346 347 348 349 350 351
352 protected function getHttpRequest($method, $body) {
353
354 $this->headers = array();
355 $bodyRequest = '';
356 $fullRequest = '';
357
358
359 switch ($method) {
360 case 'POST':
361 $bodyRequest = $this->getHttpPostRequest($body);
362 break;
363 case 'GET':
364 $bodyRequest = $this->getHttpGetRequest($body);
365 break;
366 case 'PUT':
367 $bodyRequest = $this->getHttpPutRequest($body);
368 break;
369 default:
370 throw new KerioApiException('Cannot send request, unknown method.');
371 }
372
373
374 $port = ($this->jsonRpc['port'] == 443)
375 ? ''
376 : sprintf(':%d', $this->jsonRpc['port']);
377
378
379 $this->headers['Host:'] = sprintf('%s%s', $this->hostname, $port);
380 $this->headers['Content-Length:'] = strlen($bodyRequest);
381 $this->headers['Connection:'] = 'close';
382
383
384 if ($this->token) {
385 $this->headers['Cookie:'] = $this->cookies;
386 $this->headers['X-Token:'] = $this->token;
387 }
388
389
390 foreach ($this->headers as $item => $value){
391 $fullRequest .= $item . ' ' . $value . self::CRLF;
392 }
393 $fullRequest .= self::CRLF;
394 $fullRequest .= $bodyRequest;
395
396
397 return $fullRequest;
398 }
399
400 401 402 403 404 405
406 protected function getHttpPostRequest($data) {
407 $this->headers['POST'] = sprintf('%s HTTP/1.1', $this->jsonRpc['api']);
408 $this->headers['Accept:'] = 'application/json-rpc';
409 $this->headers['Content-Type:'] = 'application/json-rpc; charset=UTF-8';
410 $this->headers['User-Agent:'] = sprintf('%s/%s', $this->name, $this->version);
411
412 return str_replace(array("\r", "\r\n", "\n", "\t"), '', $data) . self::CRLF;
413 }
414
415 416 417 418 419 420
421 protected function getHttpGetRequest($data) {
422 $this->headers['GET'] = sprintf('%s HTTP/1.1', $data);
423 $this->headers['Accept:'] = '*/*';
424
425 return $data . self::CRLF;
426 }
427
428 429 430 431 432 433
434 protected function getHttpPutRequest($data) {
435 $boundary = sprintf('---------------------%s', substr(md5(rand(0,32000)), 0, 10));
436
437 $this->headers['POST'] = sprintf('%s%s HTTP/1.1', $this->jsonRpc['api'], 'upload/');
438 $this->headers['Accept:'] = '*/*';
439 $this->headers['Content-Type:'] = sprintf('multipart/form-data; boundary=%s', $boundary);
440
441 $body = '--' . $boundary . self::CRLF;
442 $body .= 'Content-Disposition: form-data; name="unknown"; filename="newFile.bin"' . self::CRLF;
443 $body .= self::CRLF;
444 $body .= $data . self::CRLF;
445 $body .= '--' . $boundary . '--' . self::CRLF;
446
447 return $body;
448 }
449
450 451 452 453 454 455 456 457
458 public function sendRequest($method, $params = '') {
459 $request = array(
460 'jsonrpc' => $this->jsonRpc['version'],
461 'id' => $this->getRequestId(),
462 'token' => $this->token,
463 'method' => $method,
464 'params' => $params
465 );
466
467 if (empty($this->token)) {
468 unset($request['token']);
469 }
470 if (empty($params)) {
471 unset($request['params']);
472 }
473
474 $json_request = json_encode($request);
475
476
477 $json_response = $this->send('POST', $json_request);
478
479
480 $response = json_decode($json_response, TRUE);
481 return $response['result'];
482 }
483
484 485 486 487 488 489
490 public function sendRequestJson($json) {
491 return $this->send('POST', $json);
492 }
493
494 495 496 497 498 499 500 501
502 protected function send($method, $data) {
503 if (empty($this->hostname)) {
504 throw new KerioApiException('Cannot send data before login.');
505 }
506
507
508 $request = $this->getHttpRequest($method, $data);
509 $this->debug(sprintf("→ Raw request:\n<pre>%s</pre>", $request));
510
511
512 $this->socketHandler = new KerioApiSocket($this->hostname, $this->jsonRpc['port'], $this->timeout);
513
514
515 $rawResponse = $this->socketHandler->send($request);
516 $this->debug(sprintf("← Raw response:\n<pre>%s</pre>", $rawResponse));
517
518
519 $headers = $this->socketHandler->getHeaders();
520 $body = $this->socketHandler->getBody();
521 $this->checkHttpResponse(self::HTTP_SERVER_OK, $headers);
522
523
524 $response = stripslashes($body);
525 $response = json_decode($body, TRUE);
526 if (($method == 'POST') && empty($response)) {
527 throw new KerioApiException('Invalid JSON data, cannot parse response.');
528 }
529
530
531 if (empty($this->token)) {
532 if (isset($response['result']['token'])) {
533 $this->setToken($response['result']['token']);
534 }
535 }
536
537
538 if (isset($response['error'])) {
539 if (FALSE === empty($response['error'])) {
540 $message = $response['error']['message'];
541 $code = $response['error']['code'];
542 $params = (isset($response['error']['data']))
543 ? $response['error']['data']['messageParameters']['positionalParameters']
544 : '';
545 throw new KerioApiException($message, $code, $params, $data, $body);
546 }
547 }
548 elseif (isset($response['result']['errors'])) {
549 if (FALSE === empty($response['result']['errors'])) {
550 $message = $response['result']['errors'][0]['message'];
551 $code = $response['result']['errors'][0]['code'];
552 $params = $response['result']['errors'][0]['messageParameters']['positionalParameters'];
553 throw new KerioApiException($message, $code, $params, $data, $body);
554 }
555 }
556
557
558 if (empty($this->cookies)) {
559 $this->setCookieFromHeaders($headers);
560 }
561
562
563 return $body;
564 }
565
566 567 568 569 570 571 572 573 574
575 public function downloadFile($url, $directory, $filename = '') {
576 $saveAs = (empty($filename)) ? 'file.bin' : $filename;
577 $saveAs = sprintf('%s/%s', $directory, $filename);
578
579 $data = $this->send('GET', $url);
580
581 $this->debug(sprintf('Saving file %s', $saveAs));
582 if (FALSE === @file_put_contents($saveAs, $data)) {
583 throw new KerioApiException(sprintf('Unable to save file %s', $saveAs));
584 }
585 return TRUE;
586 }
587
588 589 590 591 592 593
594 public function getFile($url) {
595 return $this->send('GET', $url);
596 }
597
598 599 600 601 602 603 604 605
606 public function uploadFile($filename, $id = null) {
607 $data = @file_get_contents($filename);
608
609 if ($data) {
610 $this->debug(sprintf('Uploading file %s', $filename));
611 $json_response = $this->send('PUT', $data);
612 }
613 else {
614 throw new KerioApiException(sprintf('Unable to open file %s', $filename));
615 }
616
617 $response = json_decode($json_response, TRUE);
618 return $response['result'];
619 }
620
621 622 623 624 625 626 627 628
629 protected function checkHttpResponse($code, $headers) {
630 preg_match('#HTTP/\d+\.\d+ (\d+) (.+)#', $headers, $result);
631 switch ($result[1]) {
632 case $code:
633 return TRUE;
634 default:
635 $remote = sprintf('https://%s:%d%s', $this->hostname, $this->jsonRpc['port'], $this->jsonRpc['api']);
636 throw new KerioApiException(sprintf('%d - %s on remote server %s', $result[1], $result[2], $remote));
637 }
638 }
639
640 641 642 643 644 645
646 public function setHostname($hostname) {
647 $hostname = preg_split('/:/', $hostname);
648 $this->hostname = $hostname[0];
649 if (isset($hostname[1])) {
650 $this->setJsonRpc($this->jsonRpc['version'], $hostname[1], $this->jsonRpc['api']);
651 }
652 }
653
654 655 656 657 658 659
660 private function getRequestId() {
661 $this->requestId++;
662 return $this->requestId;
663 }
664
665 666 667 668 669 670
671 protected function setToken($token) {
672 $this->debug(sprintf('Setting X-Token %s.', $token));
673 $this->token = $token;
674 }
675
676 677 678 679 680 681
682 public function getToken() {
683 return $this->token;
684 }
685
686 687 688 689 690 691
692 protected function setCookie($cookies) {
693 $this->cookies = $cookies;
694 }
695
696 697 698 699 700 701
702 public function getCookie() {
703 return $this->cookies;
704 }
705
706 707 708 709 710 711
712 private function ($headers) {
713 foreach (explode("\n", $headers) as $line) {
714 if (preg_match_all('/Set-Cookie:\s(\w*)=(\w*)/', $line, $result)) {
715 foreach ($result[1] as $index => $cookie) {
716 $this->debug(sprintf('Setting %s=%s.', $cookie, $result[2][$index]));
717 $this->setCookie(sprintf('%s %s=%s;', $this->getCookie(), $cookie, $result[2][$index]));
718 }
719 }
720 }
721 }
722
723 724 725 726 727 728
729 public function setTimeout($timeout) {
730 $this->timeout = (integer) $timeout;
731 }
732 }
733