PABX da Simples IP
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1378 lines
28 KiB

<?php
/**
* (c) 2007-2009 Chris Maciejewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/**
* PHP SIP UAC class
*
* @ingroup API
* @author Chris Maciejewski <chris@wima.co.uk>
*
* @version SVN: $Id: PhpSIP.class.php 24 2009-11-25 11:39:57Z level7systems $
*/
require_once 'PhpSIP.Exception.php';
class PhpSIP
{
private $debug = false;
/**
* Min port
*/
private $min_port = 5065;
/**
* Max port
*/
private $max_port = 5265;
/**
* Final Response timer (in seconds)
*/
private $fr_timer = 7;
/**
* Lock file
*/
private $lock_file;
/**
* Allowed methods array
*/
private $allowed_methods = array(
"CANCEL","NOTIFY", "INVITE","BYE","REFER","OPTIONS","SUBSCRIBE","MESSAGE"
);
/**
* Dialog established
*/
private $dialog = false;
/**
* The opened socket we listen for incoming SIP messages
*/
private $socket;
/**
* Source IP address
*/
private $src_ip;
/**
* Source IP address
*/
private $user_agent = 'PHP SIP';
/**
* CSeq
*/
private $cseq = 20;
/**
* Source port
*/
private $src_port;
/**
* Call ID
*/
private $call_id;
/**
* Contact
*/
private $contact;
/**
* Request URI
*/
private $uri;
/**
* Request host
*/
private $host;
/**
* Request port
*/
private $port = 5060;
/**
* Outboud SIP proxy
*/
private $proxy;
/**
* Method
*/
private $method;
/**
* Auth username
*/
private $username;
/**
* Auth password
*/
private $password;
/**
* To
*/
private $to;
/**
* To tag
*/
private $to_tag;
/**
* From
*/
private $from;
/**
* From User
*/
private $from_user;
/**
* From tag
*/
private $from_tag;
/**
* Via tag
*/
private $via;
/**
* Content type
*/
private $content_type;
/**
* Body
*/
private $body;
/**
* Received Response
*/
private $response; // whole response body
private $res_code;
private $res_contact;
private $res_cseq_method;
private $res_cseq_number;
/**
* Received Request
*/
private $req_method;
private $req_cseq_method;
private $req_cseq_number;
private $req_contact;
/**
* Authentication
*/
private $auth;
/**
* Routes
*/
private $routes = array();
/**
* Request vias
*/
private $request_via = array();
/**
* Additional headers
*/
private $extra_headers = array();
/**
* Constructor
*
* @param $src_ip Ip address to bind (optional)
*/
public function __construct($src_ip = null)
{
if (!function_exists('socket_create'))
{
throw new PhpSIPException("socket_create() function missing.");
}
if (!$src_ip)
{
// running in a web server
if (isset($_SERVER['SERVER_ADDR']))
{
$src_ip = $_SERVER['SERVER_ADDR'];
}
// running from command line
else
{
$addr = gethostbynamel(php_uname('n'));
if (!is_array($addr) || !isset($addr[0]) || substr($addr[0],0,3) == '127')
{
throw new PhpSIPException("Failed to obtain IP address to bind. Please set bind address manualy.");
}
$src_ip = $addr[0];
}
}
$this->src_ip = $src_ip;
$this->lock_file = rtrim(sys_get_temp_dir(),DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'phpSIP.lock';
$this->createSocket();
}
/**
* Destructor
*/
public function __destruct()
{
$this->closeSocket();
}
/**
* Sets debuggin ON/OFF
*
* @param bool $status
*/
public function setDebug($status = false)
{
$this->debug = $status;
}
/**
* Gets src IP
*
* @return string
*/
public function getSrcIp()
{
return $this->src_ip;
}
/**
* Gets port number to bind
*/
private function getPort()
{
if ($this->min_port > $this->max_port)
{
throw new PhpSIPException ("Min port is bigger than max port.");
}
// waiting until file will be locked for writing
// (1000 milliseconds as timeout)
$fp = @fopen($this->lock_file, 'a+b');
if (!$fp)
{
throw new PhpSIPException ("Failed to open lock file ".$this->lock_file);
}
$startTime = microtime();
do
{
$canWrite = flock($fp, LOCK_EX);
// If lock not obtained sleep for 0 - 100 milliseconds,
// to avoid collision and CPU load
if(!$canWrite) usleep(round(rand(0, 100)*1000));
} while ((!$canWrite)and((microtime()-$startTime) < 1000));
if (!$canWrite)
{
throw new PhpSIPException ("Failed to lock a file in 1000 ms.");
}
//file was locked
$size = filesize($this->lock_file);
if ($size)
{
$contents = fread($fp, $size);
$pids = explode(",",$contents);
}
else
{
$pids = false;
}
ftruncate($fp, 0);
// we are the first one to run, initialize "PID" => "port number" array
if (!$pids)
{
if (!fwrite($fp, $this->min_port))
{
throw new PhpSIPException("Fail to write data to a lock file.");
}
$this->src_port = $this->min_port;
}
// there are other programs running now
else
{
// check if there are any empty ports left
if (count($pids) >= ($this->max_port - $this->min_port))
{
throw new PhpSIPException("No more ports left to bind.");
}
asort($pids,SORT_NUMERIC);
$prev = current($pids);
if ($prev > $this->min_port)
{
$src_port = $this->min_port;
}
else
{
foreach ($pids as $port)
{
if (($port - $prev) > 1)
{
$src_port = $prev + 1;
break;
}
$prev = $port;
}
if (($prev + 1) >= $this->max_port)
{
throw new PhpSIPException("No more ports left to bind. We shouldn't be here!");
}
$src_port = $prev + 1;
}
if (in_array($src_port,$pids))
{
throw new PhpSIPException("Fail to obtain free port number.");
}
$pids[] = $src_port;
if (!fwrite($fp, implode(",",$pids)))
{
throw new PhpSIPException("Failed to write data to lock file.");
}
$this->src_port = $src_port;
}
if (!fclose($fp))
{
throw new PhpSIPException("Failed to close lock_file");
}
}
/**
* Releases port
*/
private function releasePort()
{
// waiting until file will be locked for writing
// (1000 milliseconds as timeout)
$fp = fopen($this->lock_file, 'r+b');
if (!$fp)
{
throw new PhpSIPException("Can't open lock file.");
}
$startTime = microtime();
do
{
$canWrite = flock($fp, LOCK_EX);
// If lock not obtained sleep for 0 - 100 milliseconds,
// to avoid collision and CPU load
if(!$canWrite) usleep(round(rand(0, 100)*1000));
} while ((!$canWrite)and((microtime()-$startTime) < 1000));
if (!$canWrite)
{
throw new PhpSIPException("Failed to lock a file in 1000 ms.");
}
clearstatcache();
$size = filesize($this->lock_file);
$content = fread($fp,$size);
//file was locked
$pids = explode(",",$content);
$key = array_search($this->src_port,$pids);
unset($pids[$key]);
if (count($pids) === 0)
{
if (!fclose($fp))
{
throw new PhpSIPException("Failed to close lock_file");
}
if (!unlink($this->lock_file))
{
throw new PhpSIPException("Failed to delete lock_file.");
}
}
else
{
ftruncate($fp, 0);
if (!fwrite($fp, implode(",",$pids)))
{
throw new PhpSIPException("Failed to save data in lock_file");
}
if (!fclose($fp))
{
throw new PhpSIPException("Failed to close lock_file");
}
}
}
/**
* Adds aditional header
*
* @param string $header
*/
public function addHeader($header)
{
$this->extra_headers[] = $header;
}
/**
* Sets From header
*
* @param string $from
*/
public function setFrom($from)
{
if (preg_match('/<.*>$/',$from))
{
$this->from = $from;
}
else
{
$this->from = '<'.$from.'>';
}
$m = array();
if (!preg_match('/sip:(.*)@/i',$this->from,$m))
{
throw new PhpSIPException('Failed to parse From username.');
}
$this->from_user = $m[1];
}
/**
* Sets method
*
* @param string $method
*/
public function setMethod($method)
{
if (!in_array($method,$this->allowed_methods))
{
throw new PhpSIPException('Invalid method.');
}
$this->method = $method;
if ($method == 'INVITE')
{
$body = "v=0\r\n";
$body.= "o=click2dial 0 0 IN IP4 ".$this->src_ip."\r\n";
$body.= "s=click2dial call\r\n";
$body.= "c=IN IP4 ".$this->src_ip."\r\n";
$body.= "t=0 0\r\n";
$body.= "m=audio 8000 RTP/AVP 0 8 18 3 4 97 98\r\n";
$body.= "a=rtpmap:0 PCMU/8000\r\n";
$body.= "a=rtpmap:18 G729/8000\r\n";
$body.= "a=rtpmap:97 ilbc/8000\r\n";
$body.= "a=rtpmap:98 speex/8000\r\n";
$this->body = $body;
$this->setContentType(null);
}
if ($method == 'REFER')
{
$this->setBody('');
}
if ($method == 'CANCEL')
{
$this->setBody('');
$this->setContentType(null);
}
if ($method == 'MESSAGE')
{
$this->setContentType(null);
}
}
/**
* Sets SIP Proxy
*
* @param $proxy
*/
public function setProxy($proxy)
{
$this->proxy = $proxy;
}
/**
* Sets request URI
*
* @param string $uri
*/
public function setUri($uri)
{
if (strpos($uri,'sip:') === false)
{
throw new PhpSIPException("Only sip: URI supported.");
}
$this->uri = $uri;
$this->to = '<'.$uri.'>';
if ($this->proxy)
{
if (strpos($this->proxy,':'))
{
$temp = explode(":",$this->proxy);
$this->host = $temp[0];
$this->port = $temp[1];
}
else
{
$this->host = $this->proxy;
}
}
else
{
$url = str_replace("sip:","sip://",$uri);
if (!$url = @parse_url($url))
{
throw new PhpSIPException("Failed to parse URI.");
}
$this->host = $url['host'];
if (isset($url['port']))
{
$this->port = $url['port'];
}
}
}
/**
* Sets username
*
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Sets User Agent
*
* @param string $user_agent
*/
public function setUserAgent($user_agent)
{
$this->user_agent = $user_agent;
}
/**
* Sets password
*
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Sends SIP request
*
* @return string Reply
*/
public function send()
{
if (!$this->from)
{
throw new PhpSIPException('Missing From.');
}
if (!$this->method)
{
throw new PhpSIPException('Missing Method.');
}
if (!$this->uri)
{
throw new PhpSIPException('Missing URI.');
}
$data = $this->formatRequest();
$this->sendData($data);
$this->readResponse();
if ($this->method == 'CANCEL' && $this->res_code == '200')
{
$i = 0;
while (substr($this->res_code,0,1) != '4' && $i < 2)
{
$this->readResponse();
$i++;
}
}
if ($this->res_code == '407')
{
$this->cseq++;
$this->auth();
$data = $this->formatRequest();
$this->sendData($data);
$this->readResponse();
}
if ($this->res_code == '401')
{
$this->cseq++;
$this->authWWW();
$data = $this->formatRequest();
$this->sendData($data);
$this->readResponse();
}
if (substr($this->res_code,0,1) == '1')
{
$i = 0;
while (substr($this->res_code,0,1) == '1' && $i < 4)
{
$this->readResponse();
$i++;
}
}
$this->extra_headers = array();
$this->cseq++;
return $this->res_code;
}
/**
* Sends data
*/
private function sendData($data)
{
usleep(10000);
if (!@socket_sendto($this->socket, $data, strlen($data), 0, $this->host, $this->port))
{
$err_no = socket_last_error($this->socket);
throw new PhpSIPException("Failed to send data. ".socket_strerror($err_no));
}
if ($this->debug)
{
$temp = explode("\r\n",$data);
echo "--> ".$temp[0]."\n";
}
}
/**
* Listen for request
*
* @todo This needs to be improved
*/
public function listen($method)
{
$i = 0;
while ($this->req_method != $method)
{
$this->readResponse();
$i++;
if ($i > 5)
{
throw new PhpSIPException("Unexpected request ".$this->req_method."received.");
}
}
}
/**
* Reads response
*/
private function readResponse()
{
$from = "";
$port = 0;
if (!@socket_recvfrom($this->socket, $this->response, 10000, 0, $from, $port))
{
$this->res_code = "No final response in fr_timer seconds.";
return $this->res_code;
}
if ($this->debug)
{
$temp = explode("\r\n",$this->response);
echo "<-- ".$temp[0]."\n";
}
// Response
$result = array();
if (preg_match('/^SIP\/2\.0 ([0-9]{3})/',$this->response,$result))
{
$this->res_code = trim($result[1]);
$res_class = substr($this->res_code,0,1);
if ($res_class == '1' || $res_class == '2')
{
$this->dialog = true;
}
$this->parseResponse();
}
// Request
else
{
$this->parseRequest();
}
}
/**
* Parse Response
*/
private function parseResponse()
{
// To tag
$result = array();
if (preg_match('/^To: .*;tag=(.*)$/im',$this->response,$result))
{
$this->to_tag = trim($result[1]);
}
// Route
$result = array();
if (preg_match_all('/^Record-Route: (.*)$/im',$this->response,$result))
{
foreach ($result[1] as $route)
{
if (!in_array(trim($route),$this->routes))
{
$this->routes[] = trim($route);
}
}
}
// Request via
$result = array();
$this->request_via = array();
if (preg_match_all('/^Via: (.*)$/im',$this->response,$result))
{
foreach ($result[1] as $via)
{
$this->request_via[] = trim($via);
}
}
// Response contact
$result = array();
if (preg_match('/^Contact:.*<(.*)>/im',$this->response,$result))
{
$this->res_contact = trim($result[1]);
$semicolon = strpos($this->res_contact,";");
if ($semicolon !== false)
{
$this->res_contact = substr($this->res_contact,0,$semicolon);
}
}
// Response CSeq method
$result = array();
if (preg_match('/^CSeq: [0-9]+ (.*)$/im',$this->response,$result))
{
$this->res_cseq_method = trim($result[1]);
}
// ACK 2XX-6XX - only invites - RFC3261 17.1.2.1
if ($this->res_cseq_method == 'INVITE' && in_array(substr($this->res_code,0,1),array('2','3','4','5','6')))
{
$this->ack();
}
return $this->res_code;
}
/**
* Parse Request
*/
private function parseRequest()
{
$temp = explode("\r\n",$this->response);
$temp = explode(" ",$temp[0]);
$this->req_method = trim($temp[0]);
// Route
$result = array();
if (preg_match_all('/^Record-Route: (.*)$/im',$this->response,$result))
{
foreach ($result[1] as $route)
{
if (!in_array(trim($route),$this->routes))
{
$this->routes[] = trim($route);
}
}
}
// Request via
$result = array();
$this->request_via = array();
if (preg_match_all('/^Via: (.*)$/im',$this->response,$result))
{
foreach ($result[1] as $via)
{
$this->request_via[] = trim($via);
}
}
// Method contact
$result = array();
if (preg_match('/^Contact: <(.*)>/im',$this->response,$result))
{
$this->req_contact = trim($result[1]);
$semicolon = strpos($this->res_contact,";");
if ($semicolon !== false)
{
$this->res_contact = substr($this->res_contact,0,$semicolon);
}
}
// Response CSeq method
if (preg_match('/^CSeq: [0-9]+ (.*)$/im',$this->response,$result))
{
$this->req_cseq_method = trim($result[1]);
}
// Response CSeq number
if (preg_match('/^CSeq: ([0-9]+) .*$/im',$this->response,$result))
{
$this->req_cseq_number = trim($result[1]);
}
}
/**
* Send Response
*
* @param int $code Response code
* @param string $text Response text
*/
public function reply($code,$text)
{
$r = 'SIP/2.0 '.$code.' '.$text."\r\n";
// Via
foreach ($this->request_via as $via)
{
$r.= 'Via: '.$via."\r\n";
}
// From
$r.= 'From: '.$this->from.';tag='.$this->to_tag."\r\n";
// To
$r.= 'To: '.$this->to.';tag='.$this->from_tag."\r\n";
// Call-ID
$r.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
$r.= 'CSeq: '.$this->req_cseq_number.' '.$this->req_cseq_method."\r\n";
// Max-Forwards
$r.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$r.= 'User-Agent: '.$this->user_agent."\r\n";
// Content-Length
$r.= 'Content-Length: 0'."\r\n";
$r.= "\r\n";
$this->sendData($r);
}
/**
* ACK
*/
private function ack()
{
if ($this->res_cseq_method == 'INVITE' && $this->res_code == '200')
{
$a = 'ACK '.$this->res_contact.' SIP/2.0'."\r\n";
}
else
{
$a = 'ACK '.$this->uri.' SIP/2.0'."\r\n";
}
// Via
$a.= 'Via: '.$this->via."\r\n";
// Route
if ($this->routes)
{
foreach ($this->routes as $route)
{
$a.= 'Route: '.$route."\r\n";
}
}
// From
if (!$this->from_tag) $this->setFromTag();
$a.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n";
// To
if ($this->to_tag)
$a.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n";
else
$a.= 'To: '.$this->to."\r\n";
// Call-ID
if (!$this->call_id) $this->setCallId();
$a.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
$a.= 'CSeq: '.$this->cseq.' ACK'."\r\n";
// Authentication
if ($this->res_code == '200' && $this->auth)
{
$a.= 'Proxy-Authorization: '.$this->auth."\r\n";
}
// Max-Forwards
$a.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$a.= 'User-Agent: '.$this->user_agent."\r\n";
// Content-Length
$a.= 'Content-Length: 0'."\r\n";
$a.= "\r\n";
$this->sendData($a);
}
/**
* Formats SIP request
*
* @return string
*/
private function formatRequest()
{
if (in_array($this->method,array('BYE','REFER','SUBSCRIBE')))
{
$r = $this->method.' '.$this->res_contact.' SIP/2.0'."\r\n";
}
else
{
$r = $this->method.' '.$this->uri.' SIP/2.0'."\r\n";
}
// Via
if ($this->method != 'CANCEL')
{
$this->setVia();
}
$r.= 'Via: '.$this->via."\r\n";
// Route
if ($this->method != 'CANCEL' && $this->routes)
{
foreach ($this->routes as $route)
{
$r.= 'Route: '.$route."\r\n";
}
}
// From
if (!$this->from_tag) $this->setFromTag();
$r.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n";
// To
if (!in_array($this->method,array("INVITE","CANCEL","NOTIFY")) && $this->to_tag)
$r.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n";
else
$r.= 'To: '.$this->to."\r\n";
// Authentication
if ($this->auth)
{
$r.= $this->auth."\r\n";
$this->auth = null;
}
// Call-ID
if (!$this->call_id) $this->setCallId();
$r.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
if ($this->method == 'CANCEL')
{
$this->cseq--;
}
$r.= 'CSeq: '.$this->cseq.' '.$this->method."\r\n";
// Contact
if ($this->method != 'MESSAGE')
{
$r.= 'Contact: <sip:'.$this->from_user.'@'.$this->src_ip.':'.$this->src_port.'>'."\r\n";
}
// Content-Type
if ($this->content_type)
{
$r.= 'Content-Type: '.$this->content_type."\r\n";
}
// Max-Forwards
$r.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$r.= 'User-Agent: '.$this->user_agent."\r\n";
// Additional header
foreach ($this->extra_headers as $header)
{
$r.= $header."\r\n";
}
// Content-Length
$r.= 'Content-Length: '.strlen($this->body)."\r\n";
$r.= "\r\n";
$r.= $this->body;
return $r;
}
/**
* Sets body
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Sets Content Type
*/
public function setContentType($content_type = null)
{
if ($content_type !== null)
{
$this->content_type = $content_type;
}
else
{
switch ($this->method)
{
case 'INVITE':
$this->content_type = 'application/sdp';
break;
case 'MESSAGE':
$this->content_type = 'text/html; charset=utf-8';
break;
default:
$this->content_type = null;
}
}
}
/**
* Sets Via header
*/
private function setVia()
{
$rand = rand(100000,999999);
$this->via = 'SIP/2.0/UDP '.$this->src_ip.':'.$this->src_port.';rport;branch=z9hG4bK'.$rand;
}
/**
* Sets from tag
*/
private function setFromTag()
{
$this->from_tag = rand(10000,99999);
}
/**
* Sets call id
*/
private function setCallId()
{
$this->call_id = md5(uniqid()).'@'.$this->src_ip;
}
/**
* Gets value of the header from the previous request
*
* @param string $name Header name
*
* @return string or false
*/
public function getHeader($name)
{
if (preg_match('/^'.$name.': (.*)$/m',$this->response,$result))
{
return trim($result[1]);
}
else
{
return false;
}
}
/**
* Calculates Digest authentication response
*
*/
private function auth()
{
if (!$this->username)
{
throw new PhpSIPException("Missing username");
}
if (!$this->password)
{
throw new PhpSIPException("Missing password");
}
// realm
$result = array();
if (!preg_match('/^Proxy-Authenticate: .* realm="(.*)"/imU',$this->response, $result))
{
throw new PhpSIPException("Can't find realm in proxy-auth");
}
$realm = $result[1];
// nonce
$result = array();
if (!preg_match('/^Proxy-Authenticate: .* nonce="(.*)"/imU',$this->response, $result))
{
throw new PhpSIPException("Can't find nonce in proxy-auth");
}
$nonce = $result[1];
$ha1 = md5($this->username.':'.$realm.':'.$this->password);
$ha2 = md5($this->method.':'.$this->uri);
$res = md5($ha1.':'.$nonce.':'.$ha2);
$this->auth = 'Proxy-Authorization: Digest username="'.$this->username.'", realm="'.$realm.'", nonce="'.$nonce.'", uri="'.$this->uri.'", response="'.$res.'", algorithm=MD5';
}
/**
* Calculates WWW authorization response
*
*/
private function authWWW()
{
if (!$this->username)
{
throw new PhpSIPException("Missing auth username");
}
if (!$this->password)
{
throw new PhpSIPException("Missing auth password");
}
$qop_present = false;
if (strpos($this->response,'qop=') !== false)
{
$qop_present = true;
// we can only do qop="auth"
if (strpos($this->response,'qop="auth"') === false)
{
throw new PhpSIPException('Only qop="auth" digest authentication supported.');
}
}
// realm
$result = array();
if (!preg_match('/^WWW-Authenticate: .* realm="(.*)"/imU',$this->response, $result))
{
throw new PhpSIPException("Can't find realm in www-auth");
}
$realm = $result[1];
// nonce
$result = array();
if (!preg_match('/^WWW-Authenticate: .* nonce="(.*)"/imU',$this->response, $result))
{
throw new PhpSIPException("Can't find nonce in www-auth");
}
$nonce = $result[1];
$ha1 = md5($this->username.':'.$realm.':'.$this->password);
$ha2 = md5($this->method.':'.$this->uri);
if ($qop_present)
{
$cnonce = md5(time());
$res = md5($ha1.':'.$nonce.':00000001:'.$cnonce.':auth:'.$ha2);
}
else
{
$res = md5($ha1.':'.$nonce.':'.$ha2);
}
$this->auth = 'Authorization: Digest username="'.$this->username.'", realm="'.$realm.'", nonce="'.$nonce.'", uri="'.$this->uri.'", response="'.$res.'", algorithm=MD5';
if ($qop_present)
{
$this->auth.= ', qop="auth", nc="00000001", cnonce="'.$cnonce.'"';
}
}
/**
* Create network socket
*
* @return bool True on success
*/
private function createSocket()
{
$this->getPort();
if (!$this->src_ip)
{
throw new PhpSIPException("Source IP not defined.");
}
if (!$this->socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP))
{
$err_no = socket_last_error($this->socket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!@socket_bind($this->socket, $this->src_ip, $this->src_port))
{
$err_no = socket_last_error($this->socket);
throw new PhpSIPException ("Failed to bind ".$this->src_ip.":".$this->src_port." ".socket_strerror($err_no));
}
if (!@socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>$this->fr_timer,"usec"=>0)))
{
$err_no = socket_last_error($this->socket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!@socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>5,"usec"=>0)))
{
$err_no = socket_last_error($this->socket);
throw new PhpSIPException (socket_strerror($err_no));
}
}
/**
* Close the connection
*
* @return bool True on success
*/
private function closeSocket()
{
$this->releasePort();
socket_close($this->socket);
}
/**
* Resets callid, to/from tags etc.
*
*/
public function newCall()
{
$this->cseq = 20;
$this->call_id = null;
$this->to_tag = null;;
$this->from_tag = null;;
/**
* Body
*/
$this->body = null;
/**
* Received Response
*/
$this->response = null;
$this->res_code = null;
$this->res_contact = null;
$this->res_cseq_method = null;
$this->res_cseq_number = null;
/**
* Received Request
*/
$this->req_method = null;
$this->req_cseq_method = null;
$this->req_cseq_number = null;
$this->req_contact = null;
$this->routes = array();
$this->request_via = array();
}
}
?>