/** * Handles incoming SIP messages * * @param $peer client address to send to * @param $msg the raw SIP message * @return true if message was handled */ function handle_message($peer, $msg) { $pos = strpos($msg, "\r\n\r\n"); $head = explode("\r\n", trim(substr($msg, 0, $pos)), 2); $mime = mimeParseHeader($head[1]); $body = trim(substr($msg, $pos)); $tmp = explode(' ', $head[0]); //INVITE sip:xxx@10.10.10.240 SIP/2.0 if ($tmp[2] != 'SIP/2.0') { //XXX return error code! echo "ERROR: Unsupported SIP version: " . $tmp[2] . "\n"; return false; } switch ($tmp[0]) { case 'INVITE': $sip_type = SIP_INVITE; break; case 'ACK': $sip_type = SIP_ACK; break; case 'BYE': $sip_type = SIP_BYE; break; case 'OPTIONS': $sip_type = SIP_OPTIONS; break; case 'CANCEL': $sip_type = SIP_CANCEL; break; case 'REGISTER': $sip_type = SIP_REGISTER; break; default: echo "Unknown SIP command: " . $tmp[0] . "\n"; return false; } switch ($sip_type) { case SIP_INVITE: //echo "Recieved SIP INVITE from ".$peer."\n"; //Send "100 TRYING" response $res = $this->send_message(SIP_TRYING, $peer, $mime); //Send "180 RINGING" response $res = $this->send_message(SIP_RINGING, $peer, $mime); //Send "200 OK" response if ($mime['Content-Type'] != 'application/sdp') { echo "ERROR: DIDNT GET SDP FROM CLIENT INVITE MSG\n"; //FIXME how to handle. hangup? break; } echo "Forwarding SIP media streams to IP: " . $this->dst_ip . "\n"; $port = $this->allocate_port($peer); $res_sdp = generate_sdp($body, $this->dst_ip, $port); $res = $this->send_message(SIP_OK, $peer, $mime, $res_sdp); echo "SDP FROM CALLER:\n" . $body . "\n\n"; echo "SDP TO CALLER & STREAMING SERVER:\n" . $res_sdp . "\n"; $sdp_file = '/tmp/sip_tmp.sdp'; file_put_contents($sdp_file, $res_sdp); //NOTE: playback live stream locally with VLC: //exec('/home/ml/scripts/compile/vlc-git/vlc -vvv '.$sdp_file); exec('ffplay ' . $sdp_file); //XXX dump rtp stream (command not working!!!) //exec('ffmpeg -i '.$sdp_file.' -vcodec copy -f raw /tmp/out.h263'); break; case SIP_BYE: echo "Recieved SIP BYE from " . $peer . "\n"; //Send "200 OK" response $res = $this->send_message(SIP_OK, $peer, $mime); $this->free_port($peer); break; case SIP_ACK: //echo "Recieved SIP ACK from ".$peer."\n"; //we dont send a response on this message break; case SIP_OPTIONS: //FIXME more parameters should be set in the OK response (???) echo "Recieved SIP OPTIONS from " . $peer . "\n"; $res = $this->send_message(SIP_OK, $peer, $mime); break; //We are acting a SIP Registrar //We are acting a SIP Registrar case SIP_REGISTER: echo "Recieved SIP REGISTER from " . $peer . "\n"; if (empty($mime['Authorization'])) { //FIXME sip bindings (how does that work?!) RFC 3261: 10.3 echo "Sending auth request!\n"; $res = $this->send_message(SIP_UNAUTHORIZED, $peer, $mime); break; } $pos = strpos($mime['Authorization'], ' '); $auth_type = substr($mime['Authorization'], 0, $pos); $auth_response = substr($mime['Authorization'], $pos + 1); if ($auth_type != "Digest") { //FIXME: send error code! 400 Bad request (???) echo "ERROR: SIP/2.0 only support Digest authentication\n"; break; } //RFC 2617: "3.2.2.1 Request-Digest" $chal = MimeReader::parseAuthRequest($auth_response); if ($chal['algorithm'] != "MD5" || $chal['realm'] != $this->auth_realm || $chal['nonce'] != $this->get_nonce($peer) || $chal['opaque'] != md5($this->auth_opaque) || empty($chal['username'])) { //FIXME: send error code! 400 Bad request (???) echo "ERROR: Authentication details for user " . $chal['username'] . " is invalid!\n"; break; } $check = $this->auth_default_handler($chal['username'], $this->auth_realm, $chal['uri'], $chal['nonce'], $chal['response']); if ($check) { echo "DEBUG: Authentication of user " . $chal['username'] . " SUCCESSFUL!\n"; $res = $this->send_message(SIP_OK, $peer, $mime); } else { //FIXME: send error code! 403 Forbidden (???) echo "ERROR: Authentication of user " . $chal['username'] . " FAILED!\n"; } break; case SIP_CANCEL: echo "Recieved SIP CANCEL from " . $peer . "\n"; //FIXME what should we respond? break; default: echo "Unknown SIP message type\n"; return false; } return true; }
function _AUTH() { if (isset($this->ability['AUTH']['DIGEST-MD5'])) { //XXX: this implementation might be buggy in regards to charset (non latin1 letters in username / password) $this->write('AUTH DIGEST-MD5'); if ($this->status != 334) { echo "smtp->_AUTH() DIGEST-MD5 [" . $this->status . "]: " . $this->lastreply . ln(); return false; } //echo "challenge: ".base64_decode($this->lastreply)."\n"; $chal = MimeReader::parseAuthRequest(base64_decode($this->lastreply)); if (empty($chal['qop'])) { $chal['qop'] = 'auth'; } //default if ($chal['algorithm'] != 'md5-sess') { echo "smtp->_AUTH() DIGEST-MD5 unknown algorithm: " . $chal['algorithm'] . ln(); return false; } //RFC 2831 @ 2.1.2.1 $nc = '00000001'; //number of times same nonce has been used $cnonce = md5(mt_rand() . microtime()); $digest_uri = 'smtp/' . $this->servername; $a1 = md5($this->username . ':' . $chal['realm'] . ':' . $this->password, true) . ':' . $chal['nonce'] . ':' . $cnonce . (!empty($chal['authzid']) ? ':' . $chal['authzid'] : ''); $a2 = 'AUTHENTICATE:' . $digest_uri . ($chal['qop'] != 'auth' ? ':00000000000000000000000000000000' : ''); $response = md5(md5($a1) . ':' . $chal['nonce'] . ':' . $nc . ':' . $cnonce . ':' . $chal['qop'] . ':' . md5($a2)); $cmd = 'charset=' . $chal['charset'] . ',' . 'username="******",' . 'realm="' . $chal['realm'] . '",' . 'nonce="' . $chal['nonce'] . '",' . 'nc=' . $nc . ',' . 'cnonce="' . $cnonce . '",' . 'digest-uri="' . $digest_uri . '",' . 'response=' . $response . ',' . 'qop=' . $chal['qop']; $this->write(base64_encode($cmd)); if ($this->status != 334) { echo "smtp->_AUTH() DIGEST-MD5 challenge [" . $this->status . "]: " . $this->lastreply . ln(); return false; } //XXX validate server response, RFC 2831 @ 2.1.3 //echo "response: ".base64_decode($this->lastreply)."\n"; $this->write('NOOP'); //FIXME figure out why we need to send 1 more command to get the 235 status if ($this->status != 235) { echo "smtp->_AUTH() DIGEST-MD5 auth failed [" . $this->status . "]: " . $this->lastreply . ln(); return false; } return true; } if (isset($this->ability['AUTH']['CRAM-MD5'])) { $this->write('AUTH CRAM-MD5'); if ($this->status != 334) { echo "smtp->_AUTH() CRAM-MD5 [" . $this->status . "]: " . $this->lastreply . ln(); return false; } $digest = hash_hmac('md5', base64_decode($this->lastreply), $this->password); $this->write(base64_encode($this->username . ' ' . $digest)); if ($this->status != 235) { echo "smtp->_AUTH() CRAM-MD5 challenge [" . $this->status . "]: " . $this->lastreply . ln(); return false; } return true; } if (isset($this->ability['AUTH']['LOGIN'])) { $this->write('AUTH LOGIN'); if ($this->status != 334) { echo "smtp->_AUTH() LOGIN [" . $this->status . "]: " . $this->lastreply . ln(); return false; } $this->write(base64_encode($this->username)); if ($this->status != 334) { echo "smtp->_AUTH() LOGIN username [" . $this->status . "]: " . $this->lastreply . ln(); return false; } $this->write(base64_encode($this->password)); if ($this->status != 235) { echo "smtp->_AUTH() LOGIN password [" . $this->status . "]: " . $this->lastreply . ln(); return false; } return true; } //d($this->ability); //Defaults to "AUTH PLAIN" if the server is not ESMTP-capable (FIXME: verify with non-capable server) $this->write('AUTH PLAIN'); if ($this->status == 503) { return true; } //authentication not enabled if ($this->status != 334) { throw new \Exception("smtp->_AUTH() PLAIN [" . $this->status . "]: " . $this->lastreply); } $cmd = base64_encode(chr(0) . $this->username . chr(0) . $this->password); $this->write($cmd); if ($this->status != 235) { echo "smtp->_AUTH() PLAIN error [" . $this->status . "]: " . $this->lastreply . ln(); return false; } return true; }