1<?php 2 3/** 4 * 5 * bareos-webui - Bareos Web-Frontend 6 * 7 * @link https://github.com/bareos/bareos for the canonical source repository 8 * @copyright Copyright (c) 2014-2021 Bareos GmbH & Co. KG 9 * @license GNU Affero General Public License (http://www.gnu.org/licenses/) 10 * 11 * This program is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Affero General Public License as published by 13 * the Free Software Foundation, either version 3 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU Affero General Public License for more details. 20 * 21 * You should have received a copy of the GNU Affero General Public License 22 * along with this program. If not, see <http://www.gnu.org/licenses/>. 23 * 24 */ 25 26namespace Bareos\BSock; 27 28class BareosBSock implements BareosBSockInterface 29{ 30 const BNET_TLS_NONE = 0; /* cannot do TLS */ 31 const BNET_TLS_OK = 1; /* can do, but not required on my end */ 32 const BNET_TLS_REQUIRED = 2; /* TLS is required */ 33 34 const BNET_EOD = -1; /* End of data stream, new data may follow */ 35 const BNET_EOD_POLL = -2; /* End of data and poll all in one */ 36 const BNET_STATUS = -3; /* Send full status */ 37 const BNET_TERMINATE = -4; /* Conversation terminated, doing close() */ 38 const BNET_POLL = -5; /* Poll request, I'm hanging on a read */ 39 const BNET_HEARTBEAT = -6; /* Heartbeat Response requested */ 40 const BNET_HB_RESPONSE = -7; /* Only response permited to HB */ 41 const BNET_xxxxxxPROMPT = -8; /* No longer used -- Prompt for subcommand */ 42 const BNET_BTIME = -9; /* Send UTC btime */ 43 const BNET_BREAK = -10; /* Stop current command -- ctl-c */ 44 const BNET_START_SELECT = -11; /* Start of a selection list */ 45 const BNET_END_SELECT = -12; /* End of a select list */ 46 const BNET_INVALID_CMD = -13; /* Invalid command sent */ 47 const BNET_CMD_FAILED = -14; /* Command failed */ 48 const BNET_CMD_OK = -15; /* Command succeeded */ 49 const BNET_CMD_BEGIN = -16; /* Start command execution */ 50 const BNET_MSGS_PENDING = -17; /* Messages pending */ 51 const BNET_MAIN_PROMPT = -18; /* Server ready and waiting */ 52 const BNET_SELECT_INPUT = -19; /* Return selection input */ 53 const BNET_WARNING_MSG = -20; /* Warning message */ 54 const BNET_ERROR_MSG = -21; /* Error message -- command failed */ 55 const BNET_INFO_MSG = -22; /* Info message -- status line */ 56 const BNET_RUN_CMD = -23; /* Run command follows */ 57 const BNET_YESNO = -24; /* Request yes no response */ 58 const BNET_START_RTREE = -25; /* Start restore tree mode */ 59 const BNET_END_RTREE = -26; /* End restore tree mode */ 60 const BNET_SUB_PROMPT = -27; /* Indicate we are at a subprompt */ 61 const BNET_TEXT_INPUT = -28; /* Get text input from user */ 62 63 const DIR_OK_AUTH = "1000 OK auth\n"; 64 const DIR_AUTH_FAILED = "1999 Authorization failed.\n"; 65 66 protected $config = array( 67 'debug' => false, 68 'host' => null, 69 'port' => null, 70 'password' => null, 71 'console_name' => null, 72 'pam_password' => null, 73 'pam_username' => null, 74 'tls_verify_peer' => null, 75 'server_can_do_tls' => null, 76 'server_requires_tls' => null, 77 'client_can_do_tls' => null, 78 'client_requires_tls' => null, 79 'ca_file' => null, 80 'cert_file' => null, 81 'cert_file_passphrase' => null, 82 'allowed_cns' => null, 83 'catalog' => null, 84 ); 85 86 private $socket = null; 87 88 /** 89 * Setter for testing purposes 90 */ 91 public function set_config_param($key, $value) 92 { 93 $this->config[$key] = $value; 94 } 95 96 /** 97 * Authenticate 98 */ 99 public function connect_and_authenticate() 100 { 101 if(self::connect()) { 102 return true; 103 } else { 104 return false; 105 } 106 } 107 108 /** 109 * Set configuration 110 */ 111 private function set_config_keyword($setting, $key) 112 { 113 if (array_key_exists($key, $this->config)) { 114 $this->config[$key] = $setting; 115 } else { 116 throw new \Exception("Illegal parameter $key in /config/autoload/local.php"); 117 } 118 } 119 120 /** 121 * Set user credentials 122 */ 123 public function set_user_credentials($username, $password) 124 { 125 if(!$this->config['console_name']) { 126 $this->config['console_name'] = $username; 127 $this->config['password'] = $password; 128 } 129 130 $this->config['pam_password'] = $password; 131 $this->config['pam_username'] = $username; 132 133 if($this->config['debug']) { 134 // extended debug: print config array 135 var_dump($this->config); 136 } 137 } 138 139 /** 140 * Set the connection configuration 141 * 142 * @param $config 143 */ 144 public function set_config($config) 145 { 146 array_walk($config, array('self', 'set_config_keyword')); 147 148 if($this->config['debug']) { 149 // extended debug: print config array 150 var_dump($this->config); 151 } 152 153 if(!empty($config['console_name'])) { 154 $this->config['console_name'] = $config['console_name']; 155 } 156 if(!empty($config['console_password'])) { 157 $this->config['password'] = $config['console_password']; 158 } 159 } 160 161 /** 162 * Network to host length 163 * 164 * @param $buffer 165 * @return int 166 */ 167 private function ntohl($buffer) 168 { 169 $len = array(); 170 $actual_length = 0; 171 172 $len = unpack('N', $buffer); 173 $actual_length = (float) $len[1]; 174 175 if($actual_length > (float)2147483647) { 176 $actual_length -= (float)"4294967296"; 177 } 178 179 return (int) $actual_length; 180 } 181 182 /** 183 * Replace spaces in a string with the special escape character ^A which is used 184 * to send strings with spaces to specific director commands. 185 * 186 * @param $str 187 * @return string 188 */ 189 private function bash_spaces($str) 190 { 191 $length = strlen($str); 192 $bashed_str = ""; 193 194 for($i = 0; $i < $length; $i++) { 195 if($str[$i] == ' ') { 196 $bashed_str .= '^A'; 197 } else { 198 $bashed_str .= $str[$i]; 199 } 200 } 201 202 return $bashed_str; 203 } 204 205 /** 206 * Send a string over the console socket. 207 * Encode the length as the first 4 bytes of the message and append the string. 208 * 209 * @param $msg 210 * @return boolean 211 */ 212 private function send($msg) 213 { 214 $str_length = 0; 215 $str_length = strlen($msg); 216 $msg = pack('N', $str_length) . $msg; 217 $str_length += 4; 218 while($this->socket && $str_length > 0) { 219 $send = fwrite($this->socket, $msg, $str_length); 220 if($send === 0 || $send === false) { 221 fclose($this->socket); 222 $this->socket = null; 223 return false; 224 } elseif($send < $str_length) { 225 $msg = substr($msg, $send); 226 $str_length -= $send; 227 } else { 228 return true; 229 } 230 } 231 return false; 232 } 233 234 /** 235 * Receive a string over the console socket. 236 * First read first 4 bytes which encoded the length of the string and 237 * the read the actual string. 238 * 239 * @return string 240 */ 241 private function receive($len=0) 242 { 243 $buffer = ""; 244 $msg_len = 0; 245 246 if (!$this->socket) { 247 return $buffer; 248 } 249 250 if ($len === 0) { 251 $buffer = stream_get_contents($this->socket, 4); 252 if($buffer == false){ 253 return false; 254 } 255 $msg_len = self::ntohl($buffer); 256 } else { 257 $msg_len = $len; 258 } 259 260 if ($msg_len > 0) { 261 $buffer = stream_get_contents($this->socket, $msg_len); 262 } 263 264 return $buffer; 265 } 266 267 /** 268 * Special receive function that also knows the different so called BNET signals the 269 * Bareos director can send as part of the data stream. 270 * 271 * @return string 272 */ 273 private function receive_message() 274 { 275 $msg = ""; 276 $buffer = ""; 277 278 if (!$this->socket) { 279 return $msg; 280 } 281 282 while (true) { 283 $buffer = stream_get_contents($this->socket, 4); 284 285 if ($buffer === false) { 286 throw new \Exception("Error reading socket. " . socket_strerror(socket_last_error()) . "\n"); 287 } 288 289 $len = self::ntohl($buffer); 290 291 if ($len === 0) { 292 break; 293 } 294 if ($len > 0) { 295 $msg .= stream_get_contents($this->socket, $len); 296 } elseif ($len < 0) { 297 // signal received 298 switch ($len) { 299 case self::BNET_EOD: 300 if ($this->config['debug']) { 301 echo "Got BNET_EOD\n"; 302 } 303 return $msg; 304 case self::BNET_EOD_POLL: 305 if ($this->config['debug']) { 306 echo "Got BNET_EOD_POLL\n"; 307 } 308 break; 309 case self::BNET_STATUS: 310 if ($this->config['debug']) { 311 echo "Got BNET_STATUS\n"; 312 } 313 break; 314 case self::BNET_TERMINATE: 315 if ($this->config['debug']) { 316 echo "Got BNET_TERMINATE\n"; 317 } 318 break; 319 case self::BNET_POLL: 320 if ($this->config['debug']) { 321 echo "Got BNET_POLL\n"; 322 } 323 break; 324 case self::BNET_HEARTBEAT: 325 if ($this->config['debug']) { 326 echo "Got BNET_HEARTBEAT\n"; 327 } 328 break; 329 case self::BNET_HB_RESPONSE: 330 if ($this->config['debug']) { 331 echo "Got BNET_HB_RESPONSE\n"; 332 } 333 break; 334 case self::BNET_xxxxxxPROMPT: 335 if ($this->config['debug']) { 336 echo "Got BNET_xxxxxxPROMPT\n"; 337 } 338 break; 339 case self::BNET_BTIME: 340 if ($this->config['debug']) { 341 echo "Got BNET_BTIME\n"; 342 } 343 break; 344 case self::BNET_BREAK: 345 if ($this->config['debug']) { 346 echo "Got BNET_BREAK\n"; 347 } 348 break; 349 case self::BNET_START_SELECT: 350 if ($this->config['debug']) { 351 echo "Got BNET_START_SELECT\n"; 352 } 353 break; 354 case self::BNET_END_SELECT: 355 if ($this->config['debug']) { 356 echo "Got BNET_END_SELECT\n"; 357 } 358 break; 359 case self::BNET_INVALID_CMD: 360 if ($this->config['debug']) { 361 echo "Got BNET_INVALID_CMD\n"; 362 } 363 break; 364 case self::BNET_CMD_FAILED: 365 if ($this->config['debug']) { 366 echo "Got BNET_CMD_FAILED\n"; 367 } 368 break; 369 case self::BNET_CMD_OK: 370 if ($this->config['debug']) { 371 echo "Got BNET_CMD_OK\n"; 372 } 373 break; 374 case self::BNET_CMD_BEGIN: 375 if ($this->config['debug']) { 376 echo "Got BNET_CMD_BEGIN\n"; 377 } 378 break; 379 case self::BNET_MSGS_PENDING: 380 if ($this->config['debug']) { 381 echo "Got BNET_MSGS_PENDING\n"; 382 } 383 break; 384 case self::BNET_MAIN_PROMPT: 385 if ($this->config['debug']) { 386 echo "Got BNET_MAIN_PROMPT\n"; 387 } 388 return $msg; 389 case self::BNET_SELECT_INPUT: 390 if ($this->config['debug']) { 391 echo "Got BNET_SELECT_INPUT\n"; 392 } 393 break; 394 case self::BNET_WARNING_MSG: 395 if ($this->config['debug']) { 396 echo "Got BNET_WARNINGS_MSG\n"; 397 } 398 break; 399 case self::BNET_ERROR_MSG: 400 if ($this->config['debug']) { 401 echo "Got BNET_ERROR_MSG\n"; 402 } 403 break; 404 case self::BNET_INFO_MSG: 405 if ($this->config['debug']) { 406 echo "Got BNET_INFO_MSG\n"; 407 } 408 break; 409 case self::BNET_RUN_CMD: 410 if ($this->config['debug']) { 411 echo "Got BNET_RUN_CMD\n"; 412 } 413 break; 414 case self::BNET_YESNO: 415 if ($this->config['debug']) { 416 echo "Got BNET_YESNO\n"; 417 } 418 break; 419 case self::BNET_START_RTREE: 420 if ($this->config['debug']) { 421 echo "Got BNET_START_RTREE\n"; 422 } 423 break; 424 case self::BNET_END_RTREE: 425 if ($this->config['debug']) { 426 echo "Got BNET_END_RTREE\n"; 427 } 428 break; 429 case self::BNET_SUB_PROMPT: 430 if ($this->config['debug']) { 431 echo "Got BNET_SUB_PROMPT\n"; 432 } 433 return $msg; 434 case self::BNET_TEXT_INPUT: 435 if ($this->config['debug']) { 436 echo "Got BNET_TEXT_INPUT\n"; 437 } 438 break; 439 default: 440 throw new \Exception("Received unknown signal " . $len . "\n"); 441 break; 442 } 443 } else { 444 throw new \Exception("Received illegal packet of size " . $len . "\n"); 445 } 446 } 447 448 return $msg; 449 } 450 451 452 /** 453 * Connect to a Bareos Director, authenticate the session and establish TLS if needed. 454 * 455 * @return boolean 456 */ 457 private function connect() 458 { 459 if (!isset($this->config['host']) or !isset($this->config['port'])) { 460 return false; 461 } 462 463 if($this->config['debug']) { 464 error_log("console_name: ".$this->config['console_name']); 465 } 466 467 $port = $this->config['port']; 468 $remote = "tcp://" . $this->config['host'] . ":" . $port; 469 470 // set stream context options 471 $opts = array(); 472 473 // create stream context 474 $context = stream_context_create($opts); 475 476 try { 477 //$this->socket = stream_socket_client($remote, $error, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $context); 478 $this->socket = stream_socket_client($remote, $error, $errstr, 60, STREAM_CLIENT_CONNECT, $context); 479 480 if (!$this->socket) { 481 throw new \Exception("Error: " . $errstr . ", director seems to be down or blocking our request."); 482 } 483 // socket_set_nonblock($this->socket); 484 } 485 catch(\Exception $e) { 486 echo $e->getMessage(); 487 exit; 488 } 489 if($this->config['debug']) { 490 echo "Connected to " . $this->config['host'] . " on port " . $this->config['port'] . "\n"; 491 } 492 493 /* 494 * It only makes sense to setup the whole TLS context when we as client support or 495 * demand a TLS connection. 496 */ 497 if ($this->config['client_can_do_tls'] || $this->config['client_requires_tls']) { 498 /* 499 * We verify the peer ourself so the normal stream layer doesn't need to. 500 * But that does mean we need to capture the certficate. 501 */ 502 $result = stream_context_set_option($context, 'ssl', 'verify_peer', false); 503 $result = stream_context_set_option($context, 'ssl', 'verify_peer_name', false); 504 $result = stream_context_set_option($context, 'ssl', 'capture_peer_cert', true); 505 506 /* 507 * Setup a CA file 508 */ 509 if (!empty($this->config['ca_file'])) { 510 $result = stream_context_set_option($context, 'ssl', 'cafile', $this->config['ca_file']); 511 if ($this->config['tls_verify_peer']) { 512 $result = stream_context_set_option($context, 'ssl', 'verify_peer', true); 513 $result = stream_context_set_option($context, 'ssl', 'verify_peer_name', true); 514 } 515 } else { 516 $result = stream_context_set_option($context, 'ssl', 'allow_self_signed', true); 517 } 518 519 /* 520 * Cert file which needs to contain the client certificate and the key in PEM encoding. 521 */ 522 if (!empty($this->config['cert_file'])) { 523 $result = stream_context_set_option($context, 'ssl', 'local_cert', $this->config['cert_file']); 524 525 /* 526 * Passphrase needed to unlock the above cert file. 527 */ 528 if (!empty($this->config['cert_file_passphrase'])) { 529 $result = stream_context_set_option($context, 'ssl', 'passphrase', $this->config['cert_file_passphrase']); 530 } 531 } 532 } 533 534 if (($this->config['server_can_do_tls'] || $this->config['server_requires_tls']) && 535 ($this->config['client_can_do_tls'] || $this->config['client_requires_tls'])) { 536 537 /* 538 * STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT was introduced with PHP version 539 * 5.6.0. We need to care that calling stream_socket_enable_crypto method 540 * works with versions < 5.6.0 as well. 541 */ 542 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; 543 544 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { 545 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; 546 } 547 548 $result = stream_socket_enable_crypto($this->socket, true, $crypto_method); 549 550 if (!$result) { 551 throw new \Exception("Error in TLS handshake\n"); 552 } 553 554 if ($this->config['tls_verify_peer']) { 555 if (!empty($this->config['allowed_cns'])) { 556 if (!self::tls_postconnect_verify_cn()) { 557 throw new \Exception("Error in TLS postconnect verify CN\n"); 558 } 559 } else { 560 if (!self::tls_postconnect_verify_host()) { 561 throw new \Exception("Error in TLS postconnect verify host\n"); 562 } 563 } 564 } 565 } 566 567 if (!self::login()) { 568 return false; 569 } 570 571 $recv = self::receive(); 572 if($this->config['debug']) { 573 error_log($recv); 574 } 575 576 if (!strncasecmp($recv, "1001", 4)) { 577 $pam_answer = "4002".chr(0x1e).$this->config['pam_username'].chr(0x1e).$this->config['pam_password']; 578 if (!self::send($pam_answer)) { 579 error_log("Send failed for pam credentials"); 580 return false; 581 } 582 $recv = self::receive(); 583 if($this->config['debug']) { 584 error_log($recv); 585 } 586 } 587 588 if (!strncasecmp($recv, "1000", 4)) { 589 $recv = self::receive(); 590 if($this->config['debug']) { 591 error_log($recv); 592 } 593 return true; 594 } 595 596 return false; 597 } 598 599 /** 600 * Disconnect a connected console session 601 * 602 * @return boolean 603 */ 604 public function disconnect() 605 { 606 if ($this->socket != null) { 607 fclose($this->socket); 608 $this->socket = null; 609 if ($this->config['debug']) { 610 echo "Connection to " . $this->config['host'] . " on port " . $this->config['port'] . " closed\n"; 611 } 612 return true; 613 } 614 615 return false; 616 } 617 618 /** 619 * Login into a Bareos Director e.g. authenticate the console session 620 * 621 * @return boolean 622 */ 623 private function login() 624 { 625 include 'version.php'; 626 627 if(isset($this->config['console_name'])) { 628 $bashed_console_name = self::bash_spaces($this->config['console_name']); 629 $DIR_HELLO = "Hello " . $bashed_console_name . " calling version $bareos_full_version\n"; 630 } else { 631 $DIR_HELLO = "Hello *UserAgent* calling\n"; 632 } 633 634 self::send($DIR_HELLO); 635 $recv = self::receive(); 636 637 self::cram_md5_response($recv, $this->config['password']); 638 $recv = self::receive(); 639 640 if(strncasecmp($recv, self::DIR_AUTH_FAILED, strlen(self::DIR_AUTH_FAILED)) == 0) { 641 return false; 642 //throw new \Exception("Failed to authenticate with Director\n"); 643 } elseif(strncasecmp($recv, self::DIR_OK_AUTH, strlen(self::DIR_OK_AUTH)) == 0) { 644 return self::cram_md5_challenge($this->config['password']); 645 } else { 646 return false; 647 //throw new \Exception("Unknown response to authentication by Director $recv\n"); 648 } 649 650 } 651 652 /** 653 * Verify the CN of the certificate against a list of allowed CN names. 654 * 655 * @return boolean 656 */ 657 private function tls_postconnect_verify_cn() 658 { 659 if ($this->socket) { 660 return false; 661 } 662 663 $options = stream_context_get_options($this->socket); 664 665 if (isset($options['ssl']) && isset($options['ssl']['peer_certificate'])) { 666 $cert_data = openssl_x509_parse($options["ssl"]["peer_certificate"]); 667 668 if ($this->config['debug']) { 669 print_r($cert_data); 670 } 671 672 if (isset($cert_data['subject']['CN'])) { 673 $common_names = $cert_data['subject']['CN']; 674 if ($this->config['debug']) { 675 echo("CommonNames: " . $common_names . "\n"); 676 } 677 } 678 679 if (isset($common_names)) { 680 $checks = explode(',', $common_names); 681 682 foreach($checks as $check) { 683 $allowed_cns = explode(',', $this->config['allowed_cns']); 684 foreach($allowed_cns as $allowed_cn) { 685 if (strcasecmp($check, $allowed_cn) == 0) { 686 return true; 687 } 688 } 689 } 690 } 691 } 692 693 return false; 694 } 695 696 /** 697 * Verify TLS names 698 * 699 * @param $names 700 * @return boolean 701 */ 702 private function verify_tls_name($names) 703 { 704 $hostname = $this->config['host']; 705 $checks = explode(',', $names); 706 707 $tmp = explode('.', $hostname); 708 $rev_hostname = array_reverse($tmp); 709 $ok = false; 710 711 foreach($checks as $check) { 712 $tmp = explode(':', $check); 713 714 /* 715 * Candidates must start with DNS: 716 */ 717 if ($tmp[0] != 'DNS') { 718 continue; 719 } 720 721 /* 722 * and have something afterwards 723 */ 724 if (!isset($tmp[1])) { 725 continue; 726 } 727 728 $tmp = explode('.', $tmp[1]); 729 730 /* 731 * "*.com" is not a valid match 732 */ 733 if (count($tmp) < 3) { 734 continue; 735 } 736 737 $cand = array_reverse($tmp); 738 $ok = true; 739 740 foreach($cand as $i => $item) { 741 if (!isset($rev_hostname[$i])) { 742 $ok = false; 743 break; 744 } 745 746 if ($rev_hostname[$i] == $item) { 747 continue; 748 } 749 750 if ($item == '*') { 751 break; 752 } 753 } 754 755 if ($ok) { 756 break; 757 } 758 } 759 760 return $ok; 761 } 762 763 /** 764 * Verify the subjectAltName or CN of the certificate against the hostname we are connecting to. 765 * 766 * @return boolean 767 */ 768 private function tls_postconnect_verify_host() 769 { 770 if (!$this->socket) { 771 return false; 772 } 773 774 $options = stream_context_get_options($this->socket); 775 776 if (isset($options['ssl']) && isset($options['ssl']['peer_certificate'])) { 777 $cert_data = openssl_x509_parse($options["ssl"]["peer_certificate"]); 778 779 if ($this->config['debug']) { 780 print_r($cert_data); 781 } 782 783 /* 784 * Check subjectAltName extensions first. 785 */ 786 if (isset($cert_data['extensions'])) { 787 if (isset($cert_data['extensions']['subjectAltName'])) { 788 $alt_names = $cert_data['extensions']['subjectAltName']; 789 if ($this->config['debug']) { 790 echo("AltNames: " . $alt_names . "\n"); 791 } 792 793 if (self::verify_tls_name($alt_names)) { 794 return true; 795 } 796 } 797 } 798 799 /* 800 * Try verifying against the subject name. 801 */ 802 if (isset($cert_data['subject']['CN'])) { 803 $common_names = "DNS:" . $cert_data['subject']['CN']; 804 if ($this->config['debug']) { 805 echo("CommonNames: " . $common_names . "\n"); 806 } 807 808 if (self::verify_tls_name($common_names)) { 809 return true; 810 } 811 } 812 } 813 814 return false; 815 } 816 817 /** 818 * Perform a CRAM MD5 response 819 * 820 * @param $recv 821 * @param $password 822 * @return boolean 823 */ 824 private function cram_md5_response($recv, $password) 825 { 826 list($chal, $ssl) = sscanf($recv, "auth cram-md5 %s ssl=%d"); 827 828 switch($ssl) { 829 case self::BNET_TLS_OK: 830 $this->config['server_can_do_tls'] = true; 831 break; 832 case self::BNET_TLS_REQUIRED: 833 $this->config['server_requires_tls'] = true; 834 break; 835 default: 836 $this->config['server_can_do_tls'] = false; 837 $this->config['server_requires_tls'] = false; 838 break; 839 } 840 841 $m = hash_hmac('md5', $chal, md5($password), true); 842 $msg = rtrim(base64_encode($m), "="); 843 844 self::send($msg); 845 846 return true; 847 } 848 849 /** 850 * Perform a CRAM MD5 challenge 851 * 852 * @param $password 853 * @return boolean 854 */ 855 private function cram_md5_challenge($password) 856 { 857 $rand = rand(1000000000, 9999999999); 858 $time = time(); 859 $clientname = "php-bsock"; 860 $client = "<" . $rand . "." . $time . "@" . $clientname . ">"; 861 862 if($this->config['client_requires_tls']) { 863 $DIR_AUTH = sprintf("auth cram-md5 %s ssl=%d\n", $client, self::BNET_TLS_REQUIRED); 864 } elseif($this->config['client_can_do_tls']) { 865 $DIR_AUTH = sprintf("auth cram-md5 %s ssl=%d\n", $client, self::BNET_TLS_OK); 866 } else { 867 $DIR_AUTH = sprintf("auth cram-md5 %s ssl=%d\n", $client, self::BNET_TLS_NONE); 868 } 869 870 if(self::send($DIR_AUTH) == true) { 871 $recv = self::receive(); 872 $m = hash_hmac('md5', $client, md5($password), true); 873 874 $b64 = new BareosBase64(); 875 $msg = rtrim( $b64->encode($m, false), "=" ); 876 877 if (self::send(self::DIR_OK_AUTH) == true && strcmp(trim($recv), trim($msg)) == 0) { 878 return true; 879 } else { 880 return false; 881 } 882 } else { 883 return false; 884 } 885 886 } 887 888 /** 889 * Send a single command 890 * 891 * @param $cmd 892 * @param $api 893 * @return string 894 */ 895 public function send_command($cmd, $api=0, $jobid=null) 896 { 897 $result = ""; 898 $debug = ""; 899 900 switch($api) { 901 case 2: 902 // Enable api 2 with compact mode enabled 903 self::send(".api 2 compact=yes"); 904 try { 905 $debug = self::receive_message(); 906 if(!preg_match('/result/', $debug)) { 907 throw new \Exception("Error: API 2 not available on director. 908 Please upgrade to version 15.2.2 or greater and/or compile with jansson support."); 909 } 910 } 911 catch(\Exception $e) { 912 echo $e->getMessage(); 913 exit; 914 } 915 break; 916 case 1: 917 self::send(".api 1"); 918 $debug = self::receive_message(); 919 break; 920 default: 921 self::send(".api 0"); 922 $debug = self::receive_message(); 923 break; 924 } 925 926 if (isset($this->config['catalog'])) { 927 if(self::send("use catalog=" . $this->config['catalog'])) { 928 $debug = self::receive_message(); 929 } 930 } 931 932 if($jobid != null) { 933 if(self::send(".bvfs_update jobid=$jobid")) { 934 $debug = self::receive_message(); 935 } 936 } 937 938 if(self::send($cmd)) { 939 $result = self::receive_message(); 940 } 941 942 return $result; 943 } 944 945 /** 946 * 947 * 948 * @param $type 949 * @param $jobid 950 * @param $client 951 * @param $restoreclient 952 * @param $restorejob 953 * @param $where 954 * @param $fileid 955 * @param $dirid 956 * @param $jobids 957 * 958 * @return string 959 */ 960 public function restore($type=null, $jobid=null, $client=null, $restoreclient=null, $restorejob=null, $where=null, $fileid=null, $dirid=null, $jobids=null, $replace=null) 961 { 962 $result = ""; 963 $debug = ""; 964 $rnd = rand(1000,1000000); 965 966 if(self::send(".api 0")) { 967 $debug = self::receive_message(); 968 } 969 970 if(self::send(".bvfs_update jobid=$jobids")) { 971 $debug = self::receive_message(); 972 } 973 974 if(self::send(".bvfs_restore jobid=$jobids fileid=$fileid dirid=$dirid path=b2000$rnd")) { 975 $debug = self::receive_message(); 976 } 977 978 if(self::send('restore file=?b2000'.$rnd.' client="'.$client.'" restoreclient="'.$restoreclient.'" restorejob="'.$restorejob.'" where="'.$where.'" replace="'.$replace.'" yes')) { 979 $result = self::receive_message(); 980 } 981 982 if(self::send(".bvfs_cleanup path=b2000$rnd")) { 983 $debug = self::receive_message(); 984 } 985 986 return $result; 987 } 988 989} 990?> 991