├── LICENSE ├── PhpSIP.Exception.php ├── PhpSIP.class.php ├── README.md └── examples ├── dtmf.php ├── listen.php ├── message.php ├── notify.php └── options.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Level 7 Systems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PhpSIP.Exception.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | class PhpSIPException extends Exception 33 | { 34 | public function __construct($message, $code = 0) 35 | { 36 | parent::__construct($message,$code); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /PhpSIP.class.php: -------------------------------------------------------------------------------- 1 | 31 | * 32 | */ 33 | require_once 'PhpSIP.Exception.php'; 34 | 35 | class PhpSIP 36 | { 37 | private $debug = false; 38 | 39 | /** 40 | * Min port 41 | */ 42 | private $min_port = 5065; 43 | 44 | /** 45 | * Max port 46 | */ 47 | private $max_port = 5265; 48 | 49 | /** 50 | * Final Response timer (in ms) 51 | */ 52 | private $fr_timer = 10000; 53 | 54 | /** 55 | * Lock file 56 | */ 57 | private $lock_file = '/tmp/PhpSIP.lock'; 58 | 59 | /** 60 | * Persistent Lock file 61 | */ 62 | private $persistent_lock_file = true; 63 | 64 | /** 65 | * Allowed methods array 66 | */ 67 | private $allowed_methods = array( 68 | "CANCEL","NOTIFY", "INVITE","BYE","REFER","OPTIONS","SUBSCRIBE","MESSAGE", "PUBLISH", "REGISTER", "INFO" 69 | ); 70 | 71 | private $server_mode = false; 72 | 73 | /** 74 | * Dialog established 75 | */ 76 | private $dialog = false; 77 | 78 | /** 79 | * The opened socket we listen for incoming SIP messages 80 | */ 81 | private $socket; 82 | 83 | /** 84 | * Source IP address 85 | */ 86 | private $src_ip; 87 | 88 | /** 89 | * Source IP address 90 | */ 91 | private $user_agent = 'PHP SIP'; 92 | 93 | /** 94 | * CSeq 95 | */ 96 | private $cseq = 20; 97 | 98 | /** 99 | * Source port 100 | */ 101 | private $src_port; 102 | 103 | /** 104 | * Call ID 105 | */ 106 | private $call_id; 107 | 108 | /** 109 | * Contact 110 | */ 111 | private $contact; 112 | 113 | /** 114 | * Request URI 115 | */ 116 | private $uri; 117 | 118 | /** 119 | * Request host 120 | */ 121 | private $host; 122 | 123 | /** 124 | * Request port 125 | */ 126 | private $port = 5060; 127 | 128 | /** 129 | * Outboud SIP proxy 130 | */ 131 | private $proxy; 132 | 133 | /** 134 | * Method 135 | */ 136 | private $method; 137 | 138 | /** 139 | * Auth username 140 | */ 141 | private $username; 142 | 143 | /** 144 | * Auth password 145 | */ 146 | private $password; 147 | 148 | /** 149 | * To 150 | */ 151 | private $to; 152 | 153 | /** 154 | * To tag 155 | */ 156 | private $to_tag; 157 | 158 | /** 159 | * From 160 | */ 161 | private $from; 162 | 163 | /** 164 | * From User 165 | */ 166 | private $from_user; 167 | 168 | /** 169 | * From tag 170 | */ 171 | private $from_tag; 172 | 173 | /** 174 | * Via tag 175 | */ 176 | private $via; 177 | 178 | /** 179 | * Content type 180 | */ 181 | private $content_type; 182 | 183 | /** 184 | * Body 185 | */ 186 | private $body; 187 | 188 | /** 189 | * Received SIP message 190 | */ 191 | private $rx_msg; 192 | 193 | /** 194 | * Received Response 195 | */ 196 | private $res_code; 197 | private $res_contact; 198 | private $res_cseq_method; 199 | private $res_cseq_number; 200 | 201 | /** 202 | * Received Request 203 | */ 204 | private $req_method; 205 | private $req_cseq_method; 206 | private $req_cseq_number; 207 | private $req_contact; 208 | private $req_from; 209 | private $req_from_tag; 210 | private $req_to; 211 | private $req_to_tag; 212 | 213 | /** 214 | * Authentication 215 | */ 216 | private $auth; 217 | 218 | /** 219 | * Routes 220 | */ 221 | private $routes = array(); 222 | 223 | /** 224 | * Record-route 225 | */ 226 | private $record_route = array(); 227 | 228 | /** 229 | * Request vias 230 | */ 231 | private $request_via = array(); 232 | 233 | /** 234 | * Additional headers 235 | */ 236 | private $extra_headers = array(); 237 | 238 | /** 239 | * Constructor 240 | * 241 | * @param $src_ip Ip address to bind (optional) 242 | */ 243 | public function __construct($src_ip = null, $src_port = null, $fr_timer = null) 244 | { 245 | if (!function_exists('socket_create')) 246 | { 247 | throw new PhpSIPException("socket_create() function missing."); 248 | } 249 | 250 | if ($src_ip) 251 | { 252 | if (!preg_match('/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/', $src_ip)) 253 | { 254 | throw new PhpSIPException("Invalid src_ip $src_ip"); 255 | } 256 | } 257 | else 258 | { 259 | // running in a web server 260 | if (isset($_SERVER['SERVER_ADDR'])) 261 | { 262 | $src_ip = $_SERVER['SERVER_ADDR']; 263 | } 264 | // running from command line 265 | else 266 | { 267 | $addr = gethostbynamel(php_uname('n')); 268 | 269 | if (!is_array($addr) || !isset($addr[0]) || substr($addr[0],0,3) == '127') 270 | { 271 | throw new PhpSIPException("Failed to obtain IP address to bind. Please set bind address manualy."); 272 | } 273 | 274 | $src_ip = $addr[0]; 275 | } 276 | } 277 | 278 | $this->src_ip = $src_ip; 279 | 280 | if ($src_port) 281 | { 282 | if (!preg_match('/^[0-9]+$/',$src_port)) 283 | { 284 | throw new PhpSIPException("Invalid src_port $src_port"); 285 | } 286 | 287 | $this->src_port = $src_port; 288 | $this->lock_file = null; 289 | } 290 | 291 | if ($fr_timer) 292 | { 293 | if (!preg_match('/^[0-9]+$/',$fr_timer)) 294 | { 295 | throw new PhpSIPException("Invalid fr_timer $fr_timer"); 296 | } 297 | 298 | $this->fr_timer = $fr_timer; 299 | } 300 | 301 | $this->createSocket(); 302 | } 303 | 304 | /** 305 | * Destructor 306 | */ 307 | public function __destruct() 308 | { 309 | $this->closeSocket(); 310 | } 311 | 312 | /** 313 | * Sets debuggin ON/OFF 314 | * 315 | * @param bool $status 316 | */ 317 | public function setDebug($status = false) 318 | { 319 | $this->debug = $status; 320 | } 321 | 322 | /** 323 | * Gets src IP 324 | * 325 | * @return string 326 | */ 327 | public function getSrcIp() 328 | { 329 | return $this->src_ip; 330 | } 331 | 332 | /** 333 | * Gets port number to bind 334 | */ 335 | private function getPort() 336 | { 337 | if ($this->src_port) 338 | { 339 | return true; 340 | } 341 | 342 | if ($this->min_port > $this->max_port) 343 | { 344 | throw new PhpSIPException ("Min port is bigger than max port."); 345 | } 346 | 347 | $fp = @fopen($this->lock_file, 'a+'); 348 | 349 | if (!$fp) 350 | { 351 | throw new PhpSIPException ("Failed to open lock file ".$this->lock_file); 352 | } 353 | 354 | $canWrite = flock($fp, LOCK_EX); 355 | 356 | if (!$canWrite) 357 | { 358 | throw new PhpSIPException ("Failed to lock a file in 1000 ms."); 359 | } 360 | 361 | //file was locked 362 | clearstatcache(); 363 | $size = filesize($this->lock_file); 364 | 365 | if ($size) 366 | { 367 | $contents = fread($fp, $size); 368 | 369 | $ports = explode(",",$contents); 370 | } 371 | else 372 | { 373 | $ports = false; 374 | } 375 | 376 | ftruncate($fp, 0); 377 | rewind($fp); 378 | 379 | // we are the first one to run, initialize "PID" => "port number" array 380 | if (!$ports) 381 | { 382 | if (!fwrite($fp, $this->min_port)) 383 | { 384 | throw new PhpSIPException("Fail to write data to a lock file."); 385 | } 386 | 387 | $this->src_port = $this->min_port; 388 | } 389 | // there are other programs running now 390 | else 391 | { 392 | $src_port = null; 393 | 394 | for ($i = $this->min_port; $i <= $this->max_port; $i++) 395 | { 396 | if (!in_array($i,$ports)) 397 | { 398 | $src_port = $i; 399 | break; 400 | } 401 | } 402 | 403 | if (!$src_port) 404 | { 405 | throw new PhpSIPException("No more ports left to bind."); 406 | } 407 | 408 | $ports[] = $src_port; 409 | 410 | if (!fwrite($fp, implode(",",$ports))) 411 | { 412 | throw new PhpSIPException("Failed to write data to lock file."); 413 | } 414 | 415 | $this->src_port = $src_port; 416 | } 417 | 418 | if (!fclose($fp)) 419 | { 420 | throw new PhpSIPException("Failed to close lock_file"); 421 | } 422 | 423 | } 424 | 425 | /** 426 | * Releases port 427 | */ 428 | private function releasePort() 429 | { 430 | if ($this->lock_file === null) 431 | { 432 | return true; 433 | } 434 | 435 | $fp = fopen($this->lock_file, 'r+'); 436 | 437 | if (!$fp) 438 | { 439 | throw new PhpSIPException("Can't open lock file."); 440 | } 441 | 442 | $canWrite = flock($fp, LOCK_EX); 443 | 444 | if (!$canWrite) 445 | { 446 | throw new PhpSIPException("Failed to lock a file in 1000 ms."); 447 | } 448 | 449 | clearstatcache(); 450 | 451 | $size = filesize($this->lock_file); 452 | $content = fread($fp,$size); 453 | 454 | //file was locked 455 | $ports = explode(",",$content); 456 | 457 | $key = array_search($this->src_port,$ports); 458 | 459 | unset($ports[$key]); 460 | 461 | if (!$this->persistent_lock_file && count($ports) === 0) 462 | { 463 | if (!fclose($fp)) 464 | { 465 | throw new PhpSIPException("Failed to close lock_file"); 466 | } 467 | 468 | if (!unlink($this->lock_file)) 469 | { 470 | throw new PhpSIPException("Failed to delete lock_file."); 471 | } 472 | } 473 | else 474 | { 475 | ftruncate($fp, 0); 476 | rewind($fp); 477 | 478 | if ($ports && !fwrite($fp, implode(",",$ports))) 479 | { 480 | throw new PhpSIPException("Failed to save data in lock_file"); 481 | } 482 | 483 | flock($fp, LOCK_UN); 484 | 485 | if (!fclose($fp)) 486 | { 487 | throw new PhpSIPException("Failed to close lock_file"); 488 | } 489 | } 490 | } 491 | 492 | /** 493 | * Adds aditional header 494 | * 495 | * @param string $header 496 | */ 497 | public function addHeader($header) 498 | { 499 | $this->extra_headers[] = $header; 500 | } 501 | 502 | /** 503 | * Sets From header 504 | * 505 | * @param string $from 506 | */ 507 | public function setFrom($from) 508 | { 509 | if (preg_match('/<.*>$/',$from)) 510 | { 511 | $this->from = $from; 512 | } 513 | else 514 | { 515 | $this->from = '<'.$from.'>'; 516 | } 517 | 518 | $m = array(); 519 | if (!preg_match('/sip:(.*)@/i',$this->from,$m)) 520 | { 521 | throw new PhpSIPException('Failed to parse From username.'); 522 | } 523 | 524 | $this->from_user = $m[1]; 525 | } 526 | 527 | /** 528 | * Sets To header 529 | * 530 | * @param string $to 531 | */ 532 | public function setTo($to) 533 | { 534 | if (preg_match('/<.*>$/',$to)) 535 | { 536 | $this->to = $to; 537 | } 538 | else 539 | { 540 | $this->to = '<'.$to.'>'; 541 | } 542 | } 543 | 544 | /** 545 | * Sets method 546 | * 547 | * @param string $method 548 | */ 549 | public function setMethod($method) 550 | { 551 | if (!in_array($method,$this->allowed_methods)) 552 | { 553 | throw new PhpSIPException('Invalid method.'); 554 | } 555 | 556 | $this->method = $method; 557 | 558 | if ($method == 'INVITE') 559 | { 560 | $body = "v=0\r\n"; 561 | $body.= "o=click2dial 0 0 IN IP4 ".$this->src_ip."\r\n"; 562 | $body.= "s=click2dial call\r\n"; 563 | $body.= "c=IN IP4 ".$this->src_ip."\r\n"; 564 | $body.= "t=0 0\r\n"; 565 | $body.= "m=audio 8000 RTP/AVP 0 8 18 3 4 97 98\r\n"; 566 | $body.= "a=rtpmap:0 PCMU/8000\r\n"; 567 | $body.= "a=rtpmap:18 G729/8000\r\n"; 568 | $body.= "a=rtpmap:97 ilbc/8000\r\n"; 569 | $body.= "a=rtpmap:98 speex/8000\r\n"; 570 | 571 | $this->body = $body; 572 | 573 | $this->setContentType(null); 574 | } 575 | 576 | if ($method == 'REFER') 577 | { 578 | $this->setBody(''); 579 | } 580 | 581 | if ($method == 'CANCEL') 582 | { 583 | $this->setBody(''); 584 | $this->setContentType(null); 585 | } 586 | 587 | if ($method == 'MESSAGE' && !$this->content_type) 588 | { 589 | $this->setContentType(null); 590 | } 591 | } 592 | 593 | /** 594 | * Sets SIP Proxy 595 | * 596 | * @param $proxy 597 | */ 598 | public function setProxy($proxy) 599 | { 600 | $this->proxy = $proxy; 601 | 602 | if (strpos($this->proxy,':')) 603 | { 604 | $temp = explode(":",$this->proxy); 605 | 606 | if (!preg_match('/^[0-9]+$/',$temp[1])) 607 | { 608 | throw new PhpSIPException("Invalid port number ".$temp[1]); 609 | } 610 | 611 | $this->host = $temp[0]; 612 | $this->port = $temp[1]; 613 | } 614 | else 615 | { 616 | $this->host = $this->proxy; 617 | } 618 | } 619 | 620 | /** 621 | * Sets Contact header 622 | * 623 | * @param $v 624 | */ 625 | public function setContact($v) 626 | { 627 | $this->contact = $v; 628 | } 629 | 630 | /** 631 | * Sets request URI 632 | * 633 | * @param string $uri 634 | */ 635 | public function setUri($uri) 636 | { 637 | if (strpos($uri,'sip:') === false) 638 | { 639 | throw new PhpSIPException("Only sip: URI supported."); 640 | } 641 | 642 | if (!$this->proxy && strpos($uri,'transport=tcp') !== false) 643 | { 644 | throw new PhpSIPException("Only UDP transport supported."); 645 | } 646 | 647 | $this->uri = $uri; 648 | 649 | if (!$this->to) 650 | { 651 | $this->to = '<'.$uri.'>'; 652 | } 653 | 654 | if ($this->proxy) 655 | { 656 | if (strpos($this->proxy,':')) 657 | { 658 | $temp = explode(":",$this->proxy); 659 | 660 | $this->host = $temp[0]; 661 | $this->port = $temp[1]; 662 | } 663 | else 664 | { 665 | $this->host = $this->proxy; 666 | } 667 | } 668 | else 669 | { 670 | $uri = ($t_pos = strpos($uri,";")) ? substr($uri,0,$t_pos) : $uri; 671 | 672 | $url = str_replace("sip:","sip://",$uri); 673 | 674 | if (!$url = @parse_url($url)) 675 | { 676 | throw new PhpSIPException("Failed to parse URI '$url'."); 677 | } 678 | 679 | $this->host = $url['host']; 680 | 681 | if (isset($url['port'])) 682 | { 683 | $this->port = $url['port']; 684 | } 685 | } 686 | } 687 | 688 | /** 689 | * Sets username 690 | * 691 | * @param string $username 692 | */ 693 | public function setUsername($username) 694 | { 695 | $this->username = $username; 696 | } 697 | 698 | /** 699 | * Sets User Agent 700 | * 701 | * @param string $user_agent 702 | */ 703 | public function setUserAgent($user_agent) 704 | { 705 | $this->user_agent = $user_agent; 706 | } 707 | 708 | /** 709 | * Sets password 710 | * 711 | * @param string $password 712 | */ 713 | public function setPassword($password) 714 | { 715 | $this->password = $password; 716 | } 717 | 718 | /** 719 | * Sends SIP Request 720 | * 721 | * @return string Reply 722 | */ 723 | public function send() 724 | { 725 | if (!$this->from) 726 | { 727 | throw new PhpSIPException('Missing From.'); 728 | } 729 | 730 | if (!$this->method) 731 | { 732 | throw new PhpSIPException('Missing Method.'); 733 | } 734 | 735 | if (!$this->uri) 736 | { 737 | throw new PhpSIPException('Missing URI.'); 738 | } 739 | 740 | $data = $this->formatRequest(); 741 | 742 | $this->sendData($data); 743 | 744 | $this->readMessage(); 745 | 746 | if ($this->method == 'CANCEL' && $this->res_code == '200') 747 | { 748 | $i = 0; 749 | while (substr($this->res_code,0,1) != '4' && $i < 2) 750 | { 751 | $this->readMessage(); 752 | $i++; 753 | } 754 | } 755 | 756 | if ($this->res_code == '407') 757 | { 758 | $this->cseq++; 759 | 760 | $this->auth(); 761 | 762 | $data = $this->formatRequest(); 763 | 764 | $this->sendData($data); 765 | 766 | $this->readMessage(); 767 | } 768 | 769 | if ($this->res_code == '401') 770 | { 771 | $this->cseq++; 772 | 773 | $this->authWWW(); 774 | 775 | $data = $this->formatRequest(); 776 | 777 | $this->sendData($data); 778 | 779 | $this->readMessage(); 780 | } 781 | 782 | if (substr($this->res_code,0,1) == '1' && $this->res_code != '183') 783 | { 784 | $i = 0; 785 | while (substr($this->res_code,0,1) == '1' && $this->res_code != '183' && $i < 4) 786 | { 787 | $this->readMessage(); 788 | $i++; 789 | } 790 | } 791 | 792 | $this->extra_headers = array(); 793 | $this->cseq++; 794 | 795 | return $this->res_code; 796 | } 797 | 798 | /** 799 | * Sends data 800 | */ 801 | private function sendData($data) 802 | { 803 | if (!$this->host) 804 | { 805 | throw new PhpSIPException("Can't send data, host undefined"); 806 | } 807 | 808 | if (!$this->port) 809 | { 810 | throw new PhpSIPException("Can't send data, host undefined"); 811 | } 812 | 813 | if (!$data) 814 | { 815 | throw new PhpSIPException("Can't send - empty data"); 816 | } 817 | 818 | if (preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->host)) 819 | { 820 | $ip_address = $this->host; 821 | } 822 | else 823 | { 824 | $ip_address = gethostbyname($this->host); 825 | 826 | if ($ip_address == $this->host) 827 | { 828 | throw new PhpSIPException("DNS resolution of ".$this->host." failed"); 829 | } 830 | } 831 | 832 | if (!@socket_sendto($this->socket, $data, strlen($data), 0, $ip_address, $this->port)) 833 | { 834 | $err_no = socket_last_error($this->socket); 835 | throw new PhpSIPException("Failed to send data to ".$ip_address.":".$this->port.". Source IP ".$this->src_ip.", source port: ".$this->src_port.". ".socket_strerror($err_no)); 836 | } 837 | 838 | if ($this->debug) 839 | { 840 | $temp = explode("\r\n",$data); 841 | 842 | echo "--> ".$temp[0]."\n"; 843 | } 844 | } 845 | 846 | /** 847 | * Listen for request 848 | * 849 | * @param mixed $method string or array 850 | */ 851 | public function listen($methods) 852 | { 853 | if (!is_array($methods)) 854 | { 855 | $methods = array($methods); 856 | } 857 | 858 | if ($this->debug) 859 | { 860 | echo "Listenning for ".implode(", ",$methods)."\n"; 861 | } 862 | 863 | if ($this->server_mode) 864 | { 865 | while (!in_array($this->req_method, $methods)) 866 | { 867 | $this->readMessage(); 868 | 869 | if ($this->rx_msg && !in_array($this->req_method, $methods)) 870 | { 871 | $this->reply(200,'OK'); 872 | } 873 | } 874 | } 875 | else 876 | { 877 | $i = 0; 878 | $this->req_method = null; 879 | 880 | while (!in_array($this->req_method, $methods)) 881 | { 882 | $this->readMessage(); 883 | 884 | $i++; 885 | 886 | if ($i > 5) 887 | { 888 | throw new PhpSIPException("Unexpected request ".$this->req_method." received."); 889 | } 890 | } 891 | } 892 | } 893 | 894 | /** 895 | * Sets server mode 896 | * 897 | * @param bool $status 898 | */ 899 | public function setServerMode($v) 900 | { 901 | if (!@socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>0,"usec"=>0))) 902 | { 903 | $err_no = socket_last_error($this->socket); 904 | throw new PhpSIPException (socket_strerror($err_no)); 905 | } 906 | 907 | $this->server_mode = $v; 908 | } 909 | 910 | /** 911 | * Reads incoming SIP message 912 | */ 913 | private function readMessage() 914 | { 915 | $from = ""; 916 | $port = 0; 917 | $this->rx_msg = null; 918 | 919 | if (!@socket_recvfrom($this->socket, $this->rx_msg, 10000, 0, $from, $port)) 920 | { 921 | $this->res_code = "No final response in ".round($this->fr_timer/1000,3)." seconds. (".socket_last_error($this->socket).")"; 922 | return $this->res_code; 923 | } 924 | 925 | if ($this->debug) 926 | { 927 | $temp = explode("\r\n",$this->rx_msg); 928 | 929 | echo "<-- ".$temp[0]."\n"; 930 | } 931 | 932 | // Response 933 | $m = array(); 934 | if (preg_match('/^SIP\/2\.0 ([0-9]{3})/', $this->rx_msg, $m)) 935 | { 936 | $this->res_code = trim($m[1]); 937 | 938 | $this->parseResponse(); 939 | } 940 | // Request 941 | else 942 | { 943 | $this->parseRequest(); 944 | } 945 | 946 | // is diablog establised? 947 | if (in_array(substr($this->res_code,0,1),array("1","2")) && $this->from_tag && $this->to_tag && $this->call_id) 948 | { 949 | if ($this->debug && !$this->dialog) 950 | { 951 | echo " New dialog: ".$this->from_tag.'.'.$this->to_tag.'.'.$this->call_id."\n"; 952 | } 953 | 954 | $this->dialog = $this->from_tag.'.'.$this->to_tag.'.'.$this->call_id; 955 | } 956 | } 957 | 958 | /** 959 | * Parse Response 960 | */ 961 | private function parseResponse() 962 | { 963 | // Request via 964 | $m = array(); 965 | $this->req_via = array(); 966 | 967 | if (preg_match_all('/^Via: (.*)$/im', $this->rx_msg, $m)) 968 | { 969 | foreach ($m[1] as $via) 970 | { 971 | $this->req_via[] = trim($via); 972 | } 973 | } 974 | 975 | // Routes 976 | $this->parseRecordRoute(); 977 | 978 | // To tag 979 | $m = array(); 980 | if (preg_match('/^To: .*;tag=(.*)$/im', $this->rx_msg, $m)) 981 | { 982 | $this->to_tag = trim($m[1]); 983 | } 984 | 985 | // Response contact 986 | $this->res_contact = $this->parseContact(); 987 | 988 | // Response CSeq method 989 | $this->res_cseq_method = $this->parseCSeqMethod(); 990 | 991 | // ACK 2XX-6XX - only invites - RFC3261 17.1.2.1 992 | if ($this->res_cseq_method == 'INVITE' && in_array(substr($this->res_code,0,1),array('2','3','4','5','6'))) 993 | { 994 | $this->ack(); 995 | } 996 | 997 | return $this->res_code; 998 | } 999 | 1000 | /** 1001 | * Parse Request 1002 | */ 1003 | private function parseRequest() 1004 | { 1005 | $temp = explode("\r\n",$this->rx_msg); 1006 | $temp = explode(" ",$temp[0]); 1007 | 1008 | $this->req_method = trim($temp[0]); 1009 | 1010 | // Routes 1011 | $this->parseRecordRoute(); 1012 | 1013 | // Request via 1014 | $m = array(); 1015 | $this->req_via = array(); 1016 | if (preg_match_all('/^Via: (.*)$/im',$this->rx_msg,$m)) 1017 | { 1018 | if ($this->server_mode) 1019 | { 1020 | // set $this->host to top most via 1021 | $m2 = array(); 1022 | if (preg_match('/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/',$m[1][0],$m2)) 1023 | { 1024 | $this->host = $m2[0]; 1025 | } 1026 | } 1027 | 1028 | foreach ($m[1] as $via) 1029 | { 1030 | $this->req_via[] = trim($via); 1031 | } 1032 | } 1033 | 1034 | // Request contact 1035 | $this->req_contact = $this->parseContact(); 1036 | 1037 | // Request CSeq method 1038 | $this->req_cseq_method = $this->parseCSeqMethod(); 1039 | 1040 | // Request CSeq number 1041 | $m = array(); 1042 | 1043 | if (preg_match('/^CSeq: ([0-9]+)/im', $this->rx_msg, $m)) 1044 | { 1045 | $this->req_cseq_number = trim($m[1]); 1046 | } 1047 | 1048 | // Request From 1049 | $m = array(); 1050 | if (preg_match('/^From: (.*)/im', $this->rx_msg, $m)) 1051 | { 1052 | $this->req_from = (strpos($m[1],';')) ? substr($m[1],0,strpos($m[1],';')) : $m[1]; 1053 | } 1054 | 1055 | // Request From tag 1056 | $m = array(); 1057 | if (preg_match('/^From:.*;tag=(.*)$/im', $this->rx_msg, $m)) 1058 | { 1059 | $this->req_from_tag = trim($m[1]); 1060 | } 1061 | 1062 | // Request To 1063 | $m = array(); 1064 | if (preg_match('/^To: (.*)/im', $this->rx_msg, $m)) 1065 | { 1066 | $this->req_to = (strpos($m[1],';')) ? substr($m[1],0,strpos($m[1],';')) : $m[1]; 1067 | } 1068 | 1069 | // Request To tag 1070 | $m = array(); 1071 | if (preg_match('/^To:.*;tag=(.*)$/im', $this->rx_msg, $m)) 1072 | { 1073 | $this->req_to_tag = trim($m[1]); 1074 | } 1075 | else 1076 | { 1077 | $this->req_to_tag = rand(10000,99999); 1078 | } 1079 | 1080 | // Call-id 1081 | if (!$this->call_id) 1082 | { 1083 | $m = array(); 1084 | if (preg_match('/^Call-ID:(.*)$/im', $this->rx_msg, $m)) 1085 | { 1086 | $this->call_id = trim($m[1]); 1087 | } 1088 | } 1089 | } 1090 | 1091 | /** 1092 | * Send Response 1093 | * 1094 | * @param int $code Response code 1095 | * @param string $text Response text 1096 | */ 1097 | public function reply($code, $text) 1098 | { 1099 | $r = 'SIP/2.0 '.$code.' '.$text."\r\n"; 1100 | 1101 | // Via 1102 | foreach ($this->req_via as $via) 1103 | { 1104 | $r.= 'Via: '.$via."\r\n"; 1105 | } 1106 | 1107 | // Record-route 1108 | foreach ($this->record_route as $record_route) 1109 | { 1110 | $r.= 'Record-Route: '.$record_route."\r\n"; 1111 | } 1112 | 1113 | // From 1114 | $r.= 'From: '.$this->req_from.';tag='.$this->req_from_tag."\r\n"; 1115 | 1116 | // To 1117 | $r.= 'To: '.$this->req_to.';tag='.$this->req_to_tag."\r\n"; 1118 | 1119 | // Call-ID 1120 | $r.= 'Call-ID: '.$this->call_id."\r\n"; 1121 | 1122 | //CSeq 1123 | $r.= 'CSeq: '.$this->req_cseq_number.' '.$this->req_cseq_method."\r\n"; 1124 | 1125 | // Max-Forwards 1126 | $r.= 'Max-Forwards: 70'."\r\n"; 1127 | 1128 | // User-Agent 1129 | $r.= 'User-Agent: '.$this->user_agent."\r\n"; 1130 | 1131 | // Content-Length 1132 | $r.= 'Content-Length: 0'."\r\n"; 1133 | $r.= "\r\n"; 1134 | 1135 | $this->sendData($r); 1136 | } 1137 | 1138 | /** 1139 | * ACK 1140 | */ 1141 | private function ack() 1142 | { 1143 | if ($this->res_cseq_method == 'INVITE' && $this->res_code == '200') 1144 | { 1145 | $a = 'ACK '.$this->res_contact.' SIP/2.0'."\r\n"; 1146 | } 1147 | else 1148 | { 1149 | $a = 'ACK '.$this->uri.' SIP/2.0'."\r\n"; 1150 | } 1151 | 1152 | // Via 1153 | $a.= 'Via: '.$this->via."\r\n"; 1154 | 1155 | // Route 1156 | if ($this->routes) 1157 | { 1158 | $a.= 'Route: '.implode(",",array_reverse($this->routes))."\r\n"; 1159 | } 1160 | 1161 | // From 1162 | if (!$this->from_tag) 1163 | { 1164 | $this->from_tag = rand(10000,99999); 1165 | } 1166 | 1167 | $a.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n"; 1168 | 1169 | // To 1170 | if ($this->to_tag) 1171 | { 1172 | $a.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n"; 1173 | } 1174 | else 1175 | { 1176 | $a.= 'To: '.$this->to."\r\n"; 1177 | } 1178 | 1179 | // Call-ID 1180 | if (!$this->call_id) 1181 | { 1182 | $this->setCallId(); 1183 | } 1184 | 1185 | $a.= 'Call-ID: '.$this->call_id."\r\n"; 1186 | 1187 | //CSeq 1188 | $a.= 'CSeq: '.$this->cseq.' ACK'."\r\n"; 1189 | 1190 | // Authentication 1191 | if ($this->res_code == '200' && $this->auth) 1192 | { 1193 | $a.= 'Proxy-Authorization: '.$this->auth."\r\n"; 1194 | } 1195 | 1196 | // Max-Forwards 1197 | $a.= 'Max-Forwards: 70'."\r\n"; 1198 | 1199 | // User-Agent 1200 | $a.= 'User-Agent: '.$this->user_agent."\r\n"; 1201 | 1202 | // Content-Length 1203 | $a.= 'Content-Length: 0'."\r\n"; 1204 | $a.= "\r\n"; 1205 | 1206 | $this->sendData($a); 1207 | } 1208 | 1209 | /** 1210 | * Formats SIP request 1211 | * 1212 | * @return string 1213 | */ 1214 | private function formatRequest() 1215 | { 1216 | if ($this->res_contact && in_array($this->method,array('BYE','REFER','SUBSCRIBE'))) 1217 | { 1218 | $r = $this->method.' '.$this->res_contact.' SIP/2.0'."\r\n"; 1219 | } 1220 | else 1221 | { 1222 | $r = $this->method.' '.$this->uri.' SIP/2.0'."\r\n"; 1223 | } 1224 | 1225 | // Via 1226 | if ($this->method != 'CANCEL') 1227 | { 1228 | $this->setVia(); 1229 | } 1230 | 1231 | $r.= 'Via: '.$this->via."\r\n"; 1232 | 1233 | // Route 1234 | if ($this->method != 'CANCEL' && $this->routes) 1235 | { 1236 | $r.= 'Route: '.implode(",",array_reverse($this->routes))."\r\n"; 1237 | } 1238 | 1239 | // From 1240 | if (!$this->from_tag) 1241 | { 1242 | $this->from_tag = rand(10000,99999); 1243 | } 1244 | 1245 | $r.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n"; 1246 | 1247 | // To 1248 | if ($this->to_tag && !in_array($this->method,array("INVITE","CANCEL","NOTIFY","REGISTER"))) 1249 | { 1250 | $r.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n"; 1251 | } 1252 | else 1253 | { 1254 | $r.= 'To: '.$this->to."\r\n"; 1255 | } 1256 | 1257 | // Authentication 1258 | if ($this->auth) 1259 | { 1260 | $r.= $this->auth."\r\n"; 1261 | $this->auth = null; 1262 | } 1263 | 1264 | // Call-ID 1265 | if (!$this->call_id) 1266 | { 1267 | $this->setCallId(); 1268 | } 1269 | 1270 | $r.= 'Call-ID: '.$this->call_id."\r\n"; 1271 | 1272 | //CSeq 1273 | if ($this->method == 'CANCEL') 1274 | { 1275 | $this->cseq--; 1276 | } 1277 | 1278 | $r.= 'CSeq: '.$this->cseq.' '.$this->method."\r\n"; 1279 | 1280 | // Contact 1281 | if ($this->contact) 1282 | { 1283 | if (substr($this->contact,0,1) == "<") { 1284 | $r.= 'Contact: '.$this->contact."\r\n"; 1285 | } else { 1286 | $r.= 'Contact: <'.$this->contact.'>'."\r\n"; 1287 | } 1288 | } 1289 | else if ($this->method != 'MESSAGE') 1290 | { 1291 | $r.= 'Contact: from_user.'@'.$this->src_ip.':'.$this->src_port.'>'."\r\n"; 1292 | } 1293 | 1294 | // Content-Type 1295 | if ($this->content_type) 1296 | { 1297 | $r.= 'Content-Type: '.$this->content_type."\r\n"; 1298 | } 1299 | 1300 | // Max-Forwards 1301 | $r.= 'Max-Forwards: 70'."\r\n"; 1302 | 1303 | // User-Agent 1304 | $r.= 'User-Agent: '.$this->user_agent."\r\n"; 1305 | 1306 | // Additional header 1307 | foreach ($this->extra_headers as $header) 1308 | { 1309 | $r.= $header."\r\n"; 1310 | } 1311 | 1312 | // Content-Length 1313 | $r.= 'Content-Length: '.strlen($this->body)."\r\n"; 1314 | $r.= "\r\n"; 1315 | $r.= $this->body; 1316 | 1317 | return $r; 1318 | } 1319 | 1320 | /** 1321 | * Sets body 1322 | */ 1323 | public function setBody($body) 1324 | { 1325 | $this->body = $body; 1326 | } 1327 | 1328 | /** 1329 | * Sets Content Type 1330 | */ 1331 | public function setContentType($content_type = null) 1332 | { 1333 | if ($content_type !== null) 1334 | { 1335 | $this->content_type = $content_type; 1336 | } 1337 | else 1338 | { 1339 | switch ($this->method) 1340 | { 1341 | case 'INVITE': 1342 | $this->content_type = 'application/sdp'; 1343 | break; 1344 | case 'MESSAGE': 1345 | $this->content_type = 'text/html; charset=utf-8'; 1346 | break; 1347 | default: 1348 | $this->content_type = null; 1349 | } 1350 | } 1351 | } 1352 | 1353 | /** 1354 | * Sets Via header 1355 | */ 1356 | private function setVia() 1357 | { 1358 | $rand = rand(100000,999999); 1359 | $this->via = 'SIP/2.0/UDP '.$this->src_ip.':'.$this->src_port.';rport;branch=z9hG4bK'.$rand; 1360 | } 1361 | 1362 | /** 1363 | * Sets from tag 1364 | * 1365 | * @param string $v 1366 | */ 1367 | public function setFromTag($v) 1368 | { 1369 | $this->from_tag = $v; 1370 | } 1371 | 1372 | /** 1373 | * Sets to tag 1374 | * 1375 | * @param string $v 1376 | */ 1377 | public function setToTag($v) 1378 | { 1379 | $this->to_tag = $v; 1380 | } 1381 | 1382 | /** 1383 | * Sets cseq 1384 | * 1385 | * @param string $v 1386 | */ 1387 | public function setCseq($v) 1388 | { 1389 | $this->cseq = $v; 1390 | } 1391 | 1392 | /** 1393 | * Sets call id 1394 | * 1395 | * @param string $v 1396 | */ 1397 | public function setCallId($v = null) 1398 | { 1399 | if ($v) 1400 | { 1401 | $this->call_id = $v; 1402 | } 1403 | else 1404 | { 1405 | $this->call_id = md5(uniqid()).'@'.$this->src_ip; 1406 | } 1407 | } 1408 | 1409 | /** 1410 | * Gets value of the header from the previous request 1411 | * 1412 | * @param string $name Header name 1413 | * 1414 | * @return string or false 1415 | */ 1416 | public function getHeader($name) 1417 | { 1418 | $m = array(); 1419 | 1420 | if (preg_match('/^'.$name.': (.*)$/im', $this->rx_msg, $m)) 1421 | { 1422 | return trim($m[1]); 1423 | } 1424 | else 1425 | { 1426 | return false; 1427 | } 1428 | } 1429 | 1430 | /** 1431 | * Gets body from previous request 1432 | * 1433 | * @return string 1434 | */ 1435 | public function getBody() 1436 | { 1437 | $temp = explode("\r\n\r\n",$this->rx_msg); 1438 | 1439 | if (!isset($temp[1])) 1440 | { 1441 | return ''; 1442 | } 1443 | 1444 | return $temp[1]; 1445 | } 1446 | 1447 | /** 1448 | * Calculates Digest authentication response 1449 | * 1450 | */ 1451 | private function auth() 1452 | { 1453 | if (!$this->username) 1454 | { 1455 | throw new PhpSIPException("Missing username"); 1456 | } 1457 | 1458 | if (!$this->password) 1459 | { 1460 | throw new PhpSIPException("Missing password"); 1461 | } 1462 | 1463 | // realm 1464 | $m = array(); 1465 | if (!preg_match('/^Proxy-Authenticate: .* realm="(.*)"/imU',$this->rx_msg, $m)) 1466 | { 1467 | throw new PhpSIPException("Can't find realm in proxy-auth"); 1468 | } 1469 | 1470 | $realm = $m[1]; 1471 | 1472 | // nonce 1473 | $m = array(); 1474 | if (!preg_match('/^Proxy-Authenticate: .* nonce="(.*)"/imU',$this->rx_msg, $m)) 1475 | { 1476 | throw new PhpSIPException("Can't find nonce in proxy-auth"); 1477 | } 1478 | 1479 | $nonce = $m[1]; 1480 | 1481 | $ha1 = md5($this->username.':'.$realm.':'.$this->password); 1482 | $ha2 = md5($this->method.':'.$this->uri); 1483 | 1484 | $res = md5($ha1.':'.$nonce.':'.$ha2); 1485 | 1486 | $this->auth = 'Proxy-Authorization: Digest username="'.$this->username.'", realm="'.$realm.'", nonce="'.$nonce.'", uri="'.$this->uri.'", response="'.$res.'", algorithm=MD5'; 1487 | } 1488 | 1489 | /** 1490 | * Calculates WWW authorization response 1491 | * 1492 | */ 1493 | private function authWWW() 1494 | { 1495 | if (!$this->username) 1496 | { 1497 | throw new PhpSIPException("Missing auth username"); 1498 | } 1499 | 1500 | if (!$this->password) 1501 | { 1502 | throw new PhpSIPException("Missing auth password"); 1503 | } 1504 | 1505 | $qop_present = false; 1506 | if (strpos($this->rx_msg,'qop=') !== false) 1507 | { 1508 | $qop_present = true; 1509 | 1510 | // we can only do qop="auth" 1511 | if (strpos($this->rx_msg,'qop="auth"') === false) 1512 | { 1513 | throw new PhpSIPException('Only qop="auth" digest authentication supported.'); 1514 | } 1515 | } 1516 | 1517 | // realm 1518 | $m = array(); 1519 | if (!preg_match('/^WWW-Authenticate: .* realm="(.*)"/imU',$this->rx_msg, $m)) 1520 | { 1521 | throw new PhpSIPException("Can't find realm in www-auth"); 1522 | } 1523 | 1524 | $realm = $m[1]; 1525 | 1526 | // nonce 1527 | $m = array(); 1528 | if (!preg_match('/^WWW-Authenticate: .* nonce="(.*)"/imU',$this->rx_msg, $m)) 1529 | { 1530 | throw new PhpSIPException("Can't find nonce in www-auth"); 1531 | } 1532 | 1533 | $nonce = $m[1]; 1534 | 1535 | $ha1 = md5($this->username.':'.$realm.':'.$this->password); 1536 | $ha2 = md5($this->method.':'.$this->uri); 1537 | 1538 | if ($qop_present) 1539 | { 1540 | $cnonce = md5(time()); 1541 | 1542 | $res = md5($ha1.':'.$nonce.':00000001:'.$cnonce.':auth:'.$ha2); 1543 | } 1544 | else 1545 | { 1546 | $res = md5($ha1.':'.$nonce.':'.$ha2); 1547 | } 1548 | 1549 | $this->auth = 'Authorization: Digest username="'.$this->username.'", realm="'.$realm.'", nonce="'.$nonce.'", uri="'.$this->uri.'", response="'.$res.'", algorithm=MD5'; 1550 | 1551 | if ($qop_present) 1552 | { 1553 | $this->auth.= ', qop="auth", nc="00000001", cnonce="'.$cnonce.'"'; 1554 | } 1555 | } 1556 | 1557 | /** 1558 | * Create network socket 1559 | * 1560 | * @return bool True on success 1561 | */ 1562 | private function createSocket() 1563 | { 1564 | $this->getPort(); 1565 | 1566 | if (!$this->src_ip) 1567 | { 1568 | throw new PhpSIPException("Source IP not defined."); 1569 | } 1570 | 1571 | if (!$this->socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) 1572 | { 1573 | $err_no = socket_last_error($this->socket); 1574 | throw new PhpSIPException (socket_strerror($err_no)); 1575 | } 1576 | 1577 | if (!@socket_bind($this->socket, $this->src_ip, $this->src_port)) 1578 | { 1579 | $err_no = socket_last_error($this->socket); 1580 | throw new PhpSIPException ("Failed to bind ".$this->src_ip.":".$this->src_port." ".socket_strerror($err_no)); 1581 | } 1582 | 1583 | $microseconds = $this->fr_timer * 1000; 1584 | 1585 | $usec = $microseconds % 1000000; 1586 | 1587 | $sec = floor($microseconds / 1000000); 1588 | 1589 | if (!@socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>$sec,"usec"=>$usec))) 1590 | { 1591 | $err_no = socket_last_error($this->socket); 1592 | throw new PhpSIPException (socket_strerror($err_no)); 1593 | } 1594 | 1595 | if (!@socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>5,"usec"=>0))) 1596 | { 1597 | $err_no = socket_last_error($this->socket); 1598 | throw new PhpSIPException (socket_strerror($err_no)); 1599 | } 1600 | } 1601 | 1602 | /** 1603 | * Close the connection 1604 | * 1605 | * @return bool True on success 1606 | */ 1607 | private function closeSocket() 1608 | { 1609 | socket_close($this->socket); 1610 | 1611 | $this->releasePort(); 1612 | } 1613 | 1614 | /** 1615 | * Resets callid, to/from tags etc. 1616 | * 1617 | */ 1618 | public function newCall() 1619 | { 1620 | $this->cseq = 20; 1621 | $this->call_id = null; 1622 | $this->to = null; 1623 | $this->to_tag = null; 1624 | $this->from = null; 1625 | $this->from_tag = null; 1626 | 1627 | /** 1628 | * Body 1629 | */ 1630 | $this->body = null; 1631 | 1632 | /** 1633 | * Received Response 1634 | */ 1635 | $this->rx_msg = null; 1636 | $this->res_code = null; 1637 | $this->res_contact = null; 1638 | $this->res_cseq_method = null; 1639 | $this->res_cseq_number = null; 1640 | 1641 | /** 1642 | * Received Request 1643 | */ 1644 | $this->req_via = array(); 1645 | $this->req_method = null; 1646 | $this->req_cseq_method = null; 1647 | $this->req_cseq_number = null; 1648 | $this->req_contact = null; 1649 | $this->req_from = null; 1650 | $this->req_from_tag = null; 1651 | $this->req_to = null; 1652 | $this->req_to_tag = null; 1653 | 1654 | $this->routes = array(); 1655 | } 1656 | 1657 | /** 1658 | * Parses Record-Route header 1659 | * 1660 | * @return array 1661 | */ 1662 | private function parseRecordRoute() 1663 | { 1664 | $this->record_route = array(); 1665 | 1666 | $m = array(); 1667 | 1668 | if (preg_match_all('/^Record-Route: (.*)$/im', $this->rx_msg, $m)) 1669 | { 1670 | foreach ($m[1] as $route_header) 1671 | { 1672 | $this->record_route[] = $route_header; 1673 | 1674 | foreach (explode(",",$route_header) as $route) 1675 | { 1676 | if (!in_array(trim($route), $this->routes)) 1677 | { 1678 | $this->routes[] = trim($route); 1679 | } 1680 | } 1681 | } 1682 | } 1683 | } 1684 | 1685 | /** 1686 | * Parses Contact header 1687 | * 1688 | * @return string ro null 1689 | */ 1690 | private function parseContact() 1691 | { 1692 | $output = null; 1693 | 1694 | $m = array(); 1695 | 1696 | if (preg_match('/^Contact:.*<(.*)>/im', $this->rx_msg, $m)) 1697 | { 1698 | $output = trim($m[1]); 1699 | 1700 | $semicolon = strpos($output, ";"); 1701 | 1702 | if ($semicolon !== false) 1703 | { 1704 | $output = substr($output, 0, $semicolon); 1705 | } 1706 | } 1707 | 1708 | return $output; 1709 | } 1710 | 1711 | /** 1712 | * Parse METHOD from CSeq header 1713 | * 1714 | * @return string or null 1715 | */ 1716 | private function parseCSeqMethod() 1717 | { 1718 | $output = null; 1719 | 1720 | $m = array(); 1721 | 1722 | if (preg_match('/^CSeq: [0-9]+ (.*)$/im', $this->rx_msg, $m)) 1723 | { 1724 | $output = trim($m[1]); 1725 | } 1726 | 1727 | return $output; 1728 | } 1729 | } 1730 | 1731 | ?> 1732 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # PHP SIP User Agent class 3 | 4 | ## Introduction 5 | 6 | This is a PHP implementation of a simple [SIP](http://en.wikipedia.org/wiki/Session_Initiation_Protocol) user agent (UAS / UAC). PHP SIP is not a VoIP phone. It is not possible to make "voip calls" with this tool - only SIP signalling is supported - no audio available. 7 | 8 | Usage examples: 9 | 10 | * "click to call" in a web page - see Tutorial 11 | * send SIMPLE instant messages 12 | * send Message Waiting Indication NOTIFY 13 | * send messages to any SIP destination 14 | * SIP functional testing 15 | * more... 16 | 17 | ## Features 18 | 19 | * symmetric signalling 20 | * send/receive SIP message concurrently 21 | * add any header to the request 22 | * interpret and react to the response 23 | * Linux and Windows compatible 24 | 25 | ## Known Bugs/Limitations 26 | 27 | * only UDP transport 28 | * not fully RFC compliant 29 | * probably more... please help improve this project and report any issues [here](https://github.com/level7systems/php-sip/issues) 30 | 31 | ## Code examples 32 | Instant MESSAGE 33 | 34 | ```php 35 | 36 | try { 37 | $api = new PhpSIP('172.30.30.1'); // IP we will bind to $api->setMethod('MESSAGE'); 38 | $api->setFrom('sip:john@sip.domain.com'); 39 | $api->setUri('sip:anna@sip.domain.com'); 40 | $api->setBody('Hi, can we meet at 5pm today?'); 41 | 42 | $res = $api->send(); echo "res1: $res\n"; 43 | 44 | } catch (Exception $e) { 45 | 46 | echo $e->getMessage()."\n"; 47 | } 48 | 49 | ?> 50 | ``` 51 | 52 | Send NOTIFY to resync Linksys phone 53 | 54 | ```php 55 | try { 56 | $api = new PhpSIP('172.30.30.1'); // IP we will bind to 57 | $api->setUsername('10000'); // authentication username 58 | $api->setPassword('secret'); // authentication password // 59 | $api->setProxy('some_ip_here'); 60 | $api->addHeader('Event: resync'); 61 | $api->setMethod('NOTIFY'); 62 | $api->setFrom('sip:10000@sip.domain.com'); 63 | $api->setUri('sip:10000@sip.domain.com'); 64 | $res = $api->send(); 65 | 66 | echo "res1: $res\n"; 67 | 68 | } catch (Exception $e) { 69 | 70 | echo $e->getMessage()."\n"; } 71 | 72 | ?> 73 | ``` 74 | -------------------------------------------------------------------------------- /examples/dtmf.php: -------------------------------------------------------------------------------- 1 | setDebug(true); 8 | $api->setUsername('ua1'); 9 | $api->setPassword('xxxxxxxx'); 10 | $api->setProxy('192.168.1.1'); 11 | $api->setMethod('INVITE'); 12 | $api->setFrom('sip:ua1@192.168.1.1'); 13 | $api->setUri('sip:ua2@192.168.1.1'); 14 | $res = $api->send(); 15 | echo "response: $res\n"; 16 | 17 | usleep(100000); 18 | 19 | $api->setMethod('INFO'); 20 | $api->setContentType('application/dtmf-relay'); 21 | $api->setBody('Signal=*'."\r\n".'Duration=160'); 22 | $res = $api->send(); 23 | echo "response: $res\n"; 24 | 25 | usleep(100000); 26 | 27 | $api->setMethod('INFO'); 28 | $api->setContentType('application/dtmf-relay'); 29 | $api->setBody('Signal=0'."\r\n".'Duration=160'); 30 | $res = $api->send(); 31 | echo "response: $res\n"; 32 | 33 | usleep(100000); 34 | 35 | $api->setMethod('BYE'); 36 | $res = $api->send(); 37 | echo "response: $res\n"; 38 | 39 | } catch (Exception $e) { 40 | 41 | echo $e; 42 | 43 | } 44 | 45 | ?> 46 | -------------------------------------------------------------------------------- /examples/listen.php: -------------------------------------------------------------------------------- 1 | setDebug(true); 10 | $api->setServerMode(true); 11 | $api->listen(['MESSAGE']); 12 | 13 | echo "MESSAGE received\n"; 14 | 15 | $api->reply(200,'OK'); 16 | 17 | } catch (Exception $e) { 18 | 19 | echo $e; 20 | } 21 | 22 | ?> 23 | -------------------------------------------------------------------------------- /examples/message.php: -------------------------------------------------------------------------------- 1 | setMethod('MESSAGE'); 8 | $api->setFrom('sip:10000@127.0.0.1'); 9 | $api->setUri('sip:10000@192.168.5.65:5061'); // <- CHANGE IP address here 10 | $api->setBody('Hello world'); 11 | $res = $api->send(); 12 | 13 | echo "response: $res\n"; 14 | 15 | } catch (Exception $e) { 16 | 17 | echo $e; 18 | } 19 | 20 | ?> 21 | -------------------------------------------------------------------------------- /examples/notify.php: -------------------------------------------------------------------------------- 1 | setUsername('10000'); // authentication username 10 | $api->setPassword('secret'); // authentication password 11 | // $api->setProxy('some_ip_here'); 12 | $api->addHeader('Event: resync'); 13 | $api->setMethod('NOTIFY'); 14 | $api->setFrom('sip:10000@sip.domain.com'); 15 | $api->setUri('sip:10000@sip.domain.com'); 16 | $res = $api->send(); 17 | 18 | echo "response: $res\n"; 19 | 20 | } catch (Exception $e) { 21 | 22 | echo $e; 23 | } 24 | 25 | ?> 26 | -------------------------------------------------------------------------------- /examples/options.php: -------------------------------------------------------------------------------- 1 | setDebug(true); 12 | $api->setMethod('OPTIONS'); 13 | $api->setFrom('sip:anonymous@localhost'); 14 | $api->setUri('sip:test@eu.sip.ssl7.net'); 15 | $res = $api->send(); 16 | 17 | echo "response: $res\n"; 18 | 19 | } catch (Exception $e) { 20 | 21 | echo $e; 22 | } 23 | 24 | ?> 25 | --------------------------------------------------------------------------------