PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/jaxl.php

https://github.com/onurdegerli/JAXL
PHP | 816 lines | 532 code | 118 blank | 166 comment | 110 complexity | 2e317515cab0a33382bce21dd97287c2 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Jaxl (Jabber XMPP Library)
  4. *
  5. * Copyright (c) 2009-2012, Abhinav Singh <me@abhinavsingh.com>.
  6. * All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * * Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * * Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * * Neither the name of Abhinav Singh nor the names of his
  21. * contributors may be used to endorse or promote products derived
  22. * from this software without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  30. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  31. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
  33. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  34. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35. * POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. */
  38. date_default_timezone_set("UTC");
  39. declare(ticks = 1);
  40. define('JAXL_CWD', dirname(__FILE__));
  41. require_once JAXL_CWD.'/core/jaxl_exception.php';
  42. require_once JAXL_CWD.'/core/jaxl_cli.php';
  43. require_once JAXL_CWD.'/core/jaxl_loop.php';
  44. require_once JAXL_CWD.'/xmpp/xmpp_stream.php';
  45. require_once JAXL_CWD.'/core/jaxl_event.php';
  46. require_once JAXL_CWD.'/core/jaxl_logger.php';
  47. require_once JAXL_CWD.'/core/jaxl_socket_server.php';
  48. /**
  49. *
  50. * @author abhinavsingh
  51. */
  52. class RosterItem {
  53. public $jid = null;
  54. public $subscription = null;
  55. public $groups = array();
  56. public $resources = array();
  57. public $vcard = null;
  58. public function __construct($jid, $subscription, $groups) {
  59. $this->jid = $jid;
  60. $this->subscription = $subscription;
  61. $this->groups = $groups;
  62. }
  63. }
  64. /**
  65. * Jaxl class extends base XMPPStream class with following functionalities:
  66. * 1) Adds an event based wrapper over xmpp stream lifecycle
  67. * 2) Provides restart strategy and signal handling to ensure connectivity of xmpp stream
  68. * 3) Roster management as specified in XMPP-IM
  69. * 4) Management of XEP's inside xmpp stream lifecycle
  70. * 5) Adds a logging facility
  71. * 6) Adds a cron job facility in sync with connected xmpp stream timeline
  72. *
  73. * @author abhinavsingh
  74. *
  75. */
  76. class JAXL extends XMPPStream {
  77. // lib meta info
  78. const version = '3.0.0-alpha-1';
  79. const name = 'JAXL :: Jabber XMPP Library';
  80. // cached init config array
  81. public $cfg = array();
  82. // event callback engine for xmpp stream lifecycle
  83. protected $ev = null;
  84. // reference to various xep instance objects
  85. public $xeps = array();
  86. // local cache of roster list
  87. public $roster = array();
  88. // whether jaxl must also populate local roster cache with
  89. // received presence information about the contacts
  90. public $manage_roster = true;
  91. // what to do with presence sub requests
  92. // "none" | "accept" | "mutual"
  93. public $manage_subscribe = "none";
  94. // path variables
  95. public $log_level = JAXL_INFO;
  96. public $priv_dir;
  97. public $tmp_dir;
  98. public $log_dir;
  99. public $pid_dir;
  100. public $sock_dir;
  101. // ipc utils
  102. private $sock;
  103. private $cli;
  104. // env
  105. public $local_ip;
  106. public $pid;
  107. public $mode;
  108. // current status message
  109. public $status;
  110. // identity
  111. public $features = array();
  112. public $category = 'client';
  113. public $type = 'bot';
  114. public $lang = 'en';
  115. // after cth failed attempt
  116. // retry connect after k * $retry_interval seconds
  117. // where k is a random number between 0 and 2^c - 1.
  118. public $retry = true;
  119. private $retry_interval = 1;
  120. private $retry_attempt = 0;
  121. private $retry_max_interval = 32; // 2^5 seconds (means 5 max tries)
  122. public function __construct($config) {
  123. // env
  124. $this->cfg = $config;
  125. $strict = isset($this->cfg['strict']) ? $this->cfg['strict'] : TRUE;
  126. if($strict) $this->add_exception_handlers();
  127. $this->mode = PHP_SAPI;
  128. $this->local_ip = gethostbyname(php_uname('n'));
  129. $this->pid = getmypid();
  130. // initialize core modules
  131. $this->ev = new JAXLEvent();
  132. // jid object
  133. $jid = @$this->cfg['jid'] ? new XMPPJid($this->cfg['jid']) : null;
  134. // handle signals
  135. if(extension_loaded('pcntl')) {
  136. pcntl_signal(SIGHUP, array($this, 'signal_handler'));
  137. pcntl_signal(SIGINT, array($this, 'signal_handler'));
  138. pcntl_signal(SIGTERM, array($this, 'signal_handler'));
  139. }
  140. // create .jaxl directory in JAXL_CWD
  141. // for our /tmp, /run and /log folders
  142. // overwrite these using jaxl config array
  143. $this->priv_dir = @$this->cfg['priv_dir'] ? $this->cfg['priv_dir'] : JAXL_CWD."/.jaxl";
  144. $this->tmp_dir = $this->priv_dir."/tmp";
  145. $this->pid_dir = $this->priv_dir."/run";
  146. $this->log_dir = $this->priv_dir."/log";
  147. $this->sock_dir = $this->priv_dir."/sock";
  148. if(!is_dir($this->priv_dir)) mkdir($this->priv_dir);
  149. if(!is_dir($this->tmp_dir)) mkdir($this->tmp_dir);
  150. if(!is_dir($this->pid_dir)) mkdir($this->pid_dir);
  151. if(!is_dir($this->log_dir)) mkdir($this->log_dir);
  152. if(!is_dir($this->sock_dir)) mkdir($this->sock_dir);
  153. // setup logger
  154. if(isset($this->cfg['log_path'])) JAXLLogger::$path = $this->cfg['log_path'];
  155. //else JAXLLogger::$path = $this->log_dir."/jaxl.log";
  156. if(isset($this->cfg['log_level'])) JAXLLogger::$level = $this->log_level = $this->cfg['log_level'];
  157. else JAXLLogger::$level = $this->log_level;
  158. // touch pid file
  159. if($this->mode == "cli") {
  160. touch($this->get_pid_file_path());
  161. _info("created pid file ".$this->get_pid_file_path());
  162. }
  163. // include mandatory xmpp xeps
  164. // service discovery and entity caps
  165. // are recommended for every xmpp entity
  166. $this->require_xep(array('0030', '0115'));
  167. // do dns lookup, update $cfg['host'] and $cfg['port'] if not already specified
  168. $host = @$this->cfg['host']; $port = @$this->cfg['port'];
  169. if(!$host && !$port && $jid) {
  170. // this dns lookup is blocking
  171. _info("dns srv lookup for ".$jid->domain);
  172. list($host, $port) = JAXLUtil::get_dns_srv($jid->domain);
  173. }
  174. $this->cfg['host'] = $host; $this->cfg['port'] = $port;
  175. // choose appropriate transport
  176. // if 'bosh_url' cfg is defined include 0206
  177. if(@$this->cfg['bosh_url']) {
  178. _debug("including bosh xep");
  179. $this->require_xep('0206');
  180. $transport = $this->xeps['0206'];
  181. }
  182. else {
  183. list($host, $port) = JAXLUtil::get_dns_srv($jid->domain);
  184. $stream_context = @$this->cfg['stream_context'];
  185. $transport = new JAXLSocketClient($stream_context);
  186. }
  187. // initialize xmpp stream with configured transport
  188. parent::__construct(
  189. $transport,
  190. $jid,
  191. @$this->cfg['pass'],
  192. @$this->cfg['resource'] ? 'jaxl#'.$this->cfg['resource'] : 'jaxl#'.md5(time()),
  193. @$this->cfg['force_tls']
  194. );
  195. }
  196. public function __destruct() {
  197. // delete pid file
  198. _info("cleaning up pid and unix sock files");
  199. @unlink($this->get_pid_file_path());
  200. @unlink($this->get_sock_file_path());
  201. parent::__destruct();
  202. }
  203. public function add_exception_handlers() {
  204. _info("strict mode enabled, adding exception handlers. Set 'strict'=>TRUE inside JAXL config to disable this");
  205. set_error_handler(array('JAXLException', 'error_handler'));
  206. set_exception_handler(array('JAXLException', 'exception_handler'));
  207. register_shutdown_function(array('JAXLException', 'shutdown_handler'));
  208. }
  209. public function get_pid_file_path() {
  210. return $this->pid_dir."/jaxl_".$this->pid.".pid";
  211. }
  212. public function get_sock_file_path() {
  213. return $this->sock_dir."/jaxl_".$this->pid.".sock";
  214. }
  215. public function require_xep($xeps) {
  216. if(!is_array($xeps))
  217. $xeps = array($xeps);
  218. foreach($xeps as $xep) {
  219. $filename = 'xep_'.$xep.'.php';
  220. $classname = 'XEP_'.$xep;
  221. // include xep
  222. require_once JAXL_CWD.'/xep/'.$filename;
  223. $this->xeps[$xep] = new $classname($this);
  224. // add necessary requested callback on events
  225. foreach($this->xeps[$xep]->init() as $ev=>$cb) {
  226. $this->add_cb($ev, array($this->xeps[$xep], $cb));
  227. }
  228. }
  229. }
  230. public function add_cb($ev, $cb, $pri=1) {
  231. return $this->ev->add($ev, $cb, $pri);
  232. }
  233. public function del_cb($ref) {
  234. $this->ev->del($ref);
  235. }
  236. public function set_status($status, $show='chat', $priority=10) {
  237. $this->send($this->get_pres_pkt(
  238. array(),
  239. $status,
  240. $show,
  241. $priority
  242. ));
  243. }
  244. public function send_chat_msg($to, $body, $thread=null, $subject=null) {
  245. $msg = new XMPPMsg(
  246. array(
  247. 'type'=>'chat',
  248. 'to'=>$to,
  249. 'from'=>$this->full_jid->to_string()
  250. ),
  251. $body,
  252. $thread,
  253. $subject
  254. );
  255. $this->send($msg);
  256. }
  257. public function get_vcard($jid=null, $cb=null) {
  258. $attrs = array(
  259. 'type'=>'get',
  260. 'from'=>$this->full_jid->to_string()
  261. );
  262. if($jid) {
  263. $jid = new XMPPJid($jid);
  264. $attrs['to'] = $jid->node."@".$jid->domain;
  265. }
  266. $pkt = $this->get_iq_pkt(
  267. $attrs,
  268. new JAXLXml('vCard', 'vcard-temp')
  269. );
  270. if($cb) $this->add_cb('on_stanza_id_'.$pkt->id, $cb);
  271. $this->send($pkt);
  272. }
  273. public function get_roster($cb=null) {
  274. $pkt = $this->get_iq_pkt(
  275. array(
  276. 'type'=>'get',
  277. 'from'=>$this->full_jid->to_string()
  278. ),
  279. new JAXLXml('query', 'jabber:iq:roster')
  280. );
  281. if($cb) $this->add_cb('on_stanza_id_'.$pkt->id, $cb);
  282. $this->send($pkt);
  283. }
  284. public function subscribe($to) {
  285. $this->send($this->get_pres_pkt(
  286. array('to'=>$to, 'type'=>'subscribe')
  287. ));
  288. }
  289. public function subscribed($to) {
  290. $this->send($this->get_pres_pkt(
  291. array('to'=>$to, 'type'=>'subscribed')
  292. ));
  293. }
  294. public function unsubscribe($to) {
  295. $this->send($this->get_pres_pkt(
  296. array('to'=>$to, 'type'=>'unsubscribe')
  297. ));
  298. }
  299. public function unsubscribed($to) {
  300. $this->send($this->get_pres_pkt(
  301. array('to'=>$to, 'type'=>'unsubscribed')
  302. ));
  303. }
  304. public function get_socket_path() {
  305. return ($this->cfg['port'] == 5223 ? "ssl" : "tcp")."://".$this->cfg['host'].":".$this->cfg['port'];
  306. }
  307. public function start($opts=array()) {
  308. // is bosh bot?
  309. if(@$this->cfg['bosh_url']) {
  310. $this->trans->session_start();
  311. for(;;) {
  312. // while any of the curl request is pending
  313. // keep receiving response
  314. while(sizeof($this->trans->chs) != 0) {
  315. $this->trans->recv();
  316. }
  317. // if no request in queue, ping bosh end point
  318. // and repeat recv
  319. $this->trans->ping();
  320. }
  321. $this->trans->session_end();
  322. return;
  323. }
  324. // is xmpp client or component?
  325. // if on_connect event have no callbacks
  326. // set default on_connect callback to $this->start_stream()
  327. // i.e. xmpp client mode
  328. if(!$this->ev->exists('on_connect'))
  329. $this->add_cb('on_connect', array($this, 'start_stream'));
  330. // connect to the destination host/port
  331. if($this->connect($this->get_socket_path())) {
  332. // emit
  333. $this->ev->emit('on_connect');
  334. // parse opts
  335. if(@$opts['--with-debug-shell']) $this->enable_debug_shell();
  336. if(@$opts['--with-unix-sock']) $this->enable_unix_sock();
  337. // run main loop
  338. JAXLLoop::run();
  339. // emit
  340. $this->ev->emit('on_disconnect');
  341. }
  342. // if connection to the destination fails
  343. else {
  344. if($this->trans->errno == 61
  345. || $this->trans->errno == 110
  346. || $this->trans->errno == 111
  347. ) {
  348. $retry_after = pow(2, $this->retry_attempt) * $this->retry_interval;
  349. $this->retry_attempt++;
  350. _debug("unable to connect with errno ".$this->trans->errno." (".$this->trans->errstr."), will try again in ".$retry_after." seconds");
  351. // TODO: use sigalrm instead
  352. // they usually doesn't gel well inside select loop
  353. sleep($retry_after);
  354. $this->start();
  355. }
  356. else {
  357. $this->ev->emit('on_connect_error', array(
  358. $this->trans->errno,
  359. $this->trans->errstr
  360. ));
  361. }
  362. }
  363. }
  364. //
  365. // callback methods
  366. //
  367. // signals callback handler
  368. // not for public api consumption
  369. public function signal_handler($sig) {
  370. $this->end_stream();
  371. $this->disconnect();
  372. $this->ev->emit('on_disconnect');
  373. switch($sig) {
  374. // terminal line hangup
  375. case SIGHUP:
  376. _debug("got sighup");
  377. break;
  378. // interrupt program
  379. case SIGINT:
  380. _debug("got sigint");
  381. break;
  382. // software termination signal
  383. case SIGTERM:
  384. _debug("got sigterm");
  385. break;
  386. }
  387. exit;
  388. }
  389. // called internally for ipc
  390. // not for public consumption
  391. public function on_unix_sock_accept($_c, $addr) {
  392. $this->sock->read($_c);
  393. }
  394. // this currently simply evals the incoming raw string
  395. // know what you are doing while in production
  396. public function on_unix_sock_request($_c, $_raw) {
  397. _debug("evaling raw string rcvd over unix sock: ".$_raw);
  398. $this->sock->send($_c, serialize(eval($_raw)));
  399. $this->sock->read($_c);
  400. }
  401. public function enable_unix_sock() {
  402. $this->sock = new JAXLSocketServer(
  403. 'unix://'.$this->get_sock_file_path(),
  404. array(&$this, 'on_unix_sock_accept'),
  405. array(&$this, 'on_unix_sock_request')
  406. );
  407. }
  408. // this simply eval the incoming raw data
  409. // inside current jaxl environment
  410. // security is all upto you, no checks made here
  411. public function handle_debug_shell($_raw) {
  412. print_r(eval($_raw));
  413. echo PHP_EOL;
  414. JAXLCli::prompt();
  415. }
  416. protected function enable_debug_shell() {
  417. $this->cli = new JAXLCli(array(&$this, 'handle_debug_shell'));
  418. JAXLCli::prompt();
  419. }
  420. //
  421. // abstract method implementation
  422. //
  423. protected function send_fb_challenge_response($challenge) {
  424. $this->send($this->get_fb_challenge_response_pkt($challenge));
  425. }
  426. // refer https://developers.facebook.com/docs/chat/#jabber
  427. public function get_fb_challenge_response_pkt($challenge) {
  428. $stanza = new JAXLXml('response', NS_SASL);
  429. $challenge = base64_decode($challenge);
  430. $challenge = urldecode($challenge);
  431. parse_str($challenge, $challenge_arr);
  432. $response = http_build_query(array(
  433. 'method' => $challenge_arr['method'],
  434. 'nonce' => $challenge_arr['nonce'],
  435. 'access_token' => $this->cfg['fb_access_token'],
  436. 'api_key' => $this->cfg['fb_app_key'],
  437. 'call_id' => 0,
  438. 'v' => '1.0'
  439. ));
  440. $stanza->t(base64_encode($response));
  441. return $stanza;
  442. }
  443. public function wait_for_fb_sasl_response($event, $args) {
  444. switch($event) {
  445. case "stanza_cb":
  446. $stanza = $args[0];
  447. if($stanza->name == 'challenge' && $stanza->ns == NS_SASL) {
  448. $challenge = $stanza->text;
  449. $this->send_fb_challenge_response($challenge);
  450. return "wait_for_sasl_response";
  451. }
  452. else {
  453. _debug("got unhandled sasl response, should never happen here");
  454. exit;
  455. }
  456. break;
  457. default:
  458. _debug("not catched $event, should never happen here");
  459. exit;
  460. break;
  461. }
  462. }
  463. // someday this needs to go inside xmpp stream
  464. public function wait_for_cram_md5_response($event, $args) {
  465. switch($event) {
  466. case "stanza_cb":
  467. $stanza = $args[0];
  468. if($stanza->name == 'challenge' && $stanza->ns == NS_SASL) {
  469. $challenge = base64_decode($stanza->text);
  470. $resp = new JAXLXml('response', NS_SASL);
  471. $resp->t(base64_encode($this->jid->to_string().' '.hash_hmac('md5', $challenge, $this->pass)));
  472. $this->send($resp);
  473. return "wait_for_sasl_response";
  474. }
  475. else {
  476. _debug("got unhandled sasl response, should never happen here");
  477. exit;
  478. }
  479. break;
  480. default:
  481. _debug("not catched $event, should never happen here");
  482. exit;
  483. break;
  484. }
  485. }
  486. // http://tools.ietf.org/html/rfc5802#section-5
  487. public function get_scram_sha1_response($pass, $challenge) {
  488. // it contains users iteration count i and the user salt
  489. // also server will append it's own nonce to the one we specified
  490. $decoded = $this->explode_data(base64_decode($challenge));
  491. // r=,s=,i=
  492. $nonce = $decoded['r'];
  493. $salt = base64_decode($decoded['s']);
  494. $iteration = intval($decoded['i']);
  495. // SaltedPassword := Hi(Normalize(password), salt, i)
  496. $salted = JAXLUtil::pbkdf2($this->pass, $salt, $iteration);
  497. // ClientKey := HMAC(SaltedPassword, "Client Key")
  498. $client_key = hash_hmac('sha1', $salted, "Client Key", true);
  499. // StoredKey := H(ClientKey)
  500. $stored_key = hash('sha1', $client_key, true);
  501. // AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof
  502. $auth_message = '';
  503. // ClientSignature := HMAC(StoredKey, AuthMessage)
  504. $signature = hash_hmac('sha1', $stored_key, $auth_message, true);
  505. // ClientProof := ClientKey XOR ClientSignature
  506. $client_proof = $client_key ^ $signature;
  507. $proof = 'c=biws,r='.$nonce.',p='.base64_encode($client_proof);
  508. return base64_encode($proof);
  509. }
  510. public function wait_for_scram_sha1_response($event, $args) {
  511. switch($event) {
  512. case "stanza_cb":
  513. $stanza = $args[0];
  514. if($stanza->name == 'challenge' && $stanza->ns == NS_SASL) {
  515. $challenge = $stanza->text;
  516. $resp = new JAXLXml('response', NS_SASL);
  517. $resp->t($this->get_scram_sha1_response($this->pass, $challenge));
  518. $this->send($resp);
  519. return "wait_for_sasl_response";
  520. }
  521. else {
  522. _debug("got unhandled sasl response, should never happen here");
  523. exit;
  524. }
  525. break;
  526. default:
  527. _debug("not catched $event, should never happen here");
  528. exit;
  529. break;
  530. }
  531. }
  532. public function handle_auth_mechs($stanza, $mechanisms) {
  533. if($this->ev->exists('on_stream_features')) {
  534. return $this->ev->emit('on_stream_features', array($stanza));
  535. }
  536. // extract available mechanisms
  537. $mechs = array();
  538. if($mechanisms) foreach($mechanisms->childrens as $mechanism) $mechs[$mechanism->text] = true;
  539. // check if preferred auth type exists in available mechanisms
  540. $pref_auth = @$this->cfg['auth_type'] ? $this->cfg['auth_type'] : 'PLAIN';
  541. $pref_auth_exists = isset($mechs[$pref_auth]) ? true : false;
  542. _debug("pref_auth ".$pref_auth." ".($pref_auth_exists ? "exists" : "doesn't exists"));
  543. // if pref auth exists, try it
  544. if($pref_auth_exists) {
  545. $mech = $pref_auth;
  546. }
  547. // if pref auth doesn't exists, choose one from available mechanisms
  548. else {
  549. foreach($mechs as $mech=>$any) {
  550. // choose X-FACEBOOK-PLATFORM only if fb_access_token config value is available
  551. if($mech == 'X-FACEBOOK-PLATFORM') {
  552. if(@$this->cfg['fb_access_token']) {
  553. break;
  554. }
  555. }
  556. // else try first of the available methods
  557. else {
  558. break;
  559. }
  560. }
  561. _error("preferred auth type not supported, trying $mech");
  562. }
  563. $this->send_auth_pkt($mech, @$this->jid ? $this->jid->to_string() : null, @$this->pass);
  564. if($pref_auth == 'X-FACEBOOK-PLATFORM') {
  565. return "wait_for_fb_sasl_response";
  566. }
  567. else if($pref_auth == 'CRAM-MD5') {
  568. return "wait_for_cram_md5_response";
  569. }
  570. else if($pref_auth == 'SCRAM-SHA-1') {
  571. return "wait_for_scram_sha1_response";
  572. }
  573. }
  574. public function handle_auth_success() {
  575. // if not a component
  576. /*if(!@$this->xeps['0114']) {
  577. $this->xeps['0030']->get_info($this->full_jid->domain, array(&$this, 'handle_domain_info'));
  578. $this->xeps['0030']->get_items($this->full_jid->domain, array(&$this, 'handle_domain_items'));
  579. }*/
  580. $this->ev->emit('on_auth_success');
  581. }
  582. public function handle_auth_failure($reason) {
  583. $this->ev->emit('on_auth_failure', array(
  584. $reason
  585. ));
  586. }
  587. public function handle_stream_start($stanza) {
  588. $stanza = new XMPPStanza($stanza);
  589. $this->ev->emit('on_stream_start', array($stanza));
  590. return array(@$this->cfg['bosh_url'] ? 'wait_for_stream_features' : 'connected', 1);
  591. }
  592. public function handle_iq($stanza) {
  593. $stanza = new XMPPStanza($stanza);
  594. // emit callback registered on stanza id's
  595. $emited = false;
  596. if($stanza->id && $this->ev->exists('on_stanza_id_'.$stanza->id)) {
  597. //_debug("on stanza id callbackd");
  598. $emited = true;
  599. $this->ev->emit('on_stanza_id_'.$stanza->id, array($stanza));
  600. }
  601. // catch roster list
  602. if($stanza->type == 'result' && ($query = $stanza->exists('query', 'jabber:iq:roster'))) {
  603. foreach($query->childrens as $child) {
  604. if($child->name == 'item') {
  605. $jid = $child->attrs['jid'];
  606. $subscription = $child->attrs['subscription'];
  607. $groups = array();
  608. foreach($child->childrens as $group) {
  609. if($group->name == 'group') {
  610. $groups[] = $group->text;
  611. }
  612. }
  613. $this->roster[$jid] = new RosterItem($jid, $subscription, $groups);
  614. }
  615. }
  616. // emit this event if not emited above
  617. if(!$emited)
  618. $this->ev->emit('on_roster_update');
  619. }
  620. // if managing roster
  621. // catch contact vcard results
  622. if($this->manage_roster && $stanza->type == 'result' && ($query = $stanza->exists('vCard', 'vcard-temp'))) {
  623. if(@$this->roster[$stanza->from])
  624. $this->roster[$stanza->from]->vcard = $query;
  625. }
  626. // on_get_iq, on_result_iq, and other events are only
  627. // emitted if on_stanza_id_{id} wasn't emitted above
  628. // TODO: can we add more checks here before calling back
  629. // e.g. checks on existence of an attribute, check on 1st level child ns and so on
  630. if(!$emited)
  631. $this->ev->emit('on_'.$stanza->type.'_iq', array($stanza));
  632. }
  633. public function handle_presence($stanza) {
  634. $stanza = new XMPPStanza($stanza);
  635. // if managing roster
  636. // catch available/unavailable type stanza
  637. if($this->manage_roster) {
  638. $type = ($stanza->type ? $stanza->type : "available");
  639. $jid = new XMPPJid($stanza->from);
  640. if($type == 'available') {
  641. $this->roster[$jid->bare]->resources[$jid->resource] = $stanza;
  642. }
  643. else if($type == 'unavailable') {
  644. if(@$this->roster[$jid->bare] && @$this->roster[$jid->bare]->resources[$jid->resource])
  645. unset($this->roster[$jid->bare]->resources[$jid->resource]);
  646. }
  647. }
  648. // if managing subscription requests
  649. // we need to automate stuff here
  650. if($stanza->type == "subscribe" && $this->manage_subscribe != "none") {
  651. $this->subscribed($stanza->from);
  652. if($this->manage_subscribe == "mutual")
  653. $this->subscribe($stanza->from);
  654. }
  655. $this->ev->emit('on_presence_stanza', array($stanza));
  656. }
  657. public function handle_message($stanza) {
  658. $stanza = new XMPPStanza($stanza);
  659. $this->ev->emit('on_'.$stanza->type.'_message', array($stanza));
  660. }
  661. // unhandled event and arguments bubbled up
  662. // TODO: in a lot of cases this will be called, need more checks
  663. public function handle_other($event, $args) {
  664. $stanza = $args[0];
  665. $stanza = new XMPPStanza($stanza);
  666. $ev = 'on_'.$stanza->name.'_stanza';
  667. if($this->ev->exists($ev)) {
  668. return $this->ev->emit($ev, array($stanza));
  669. }
  670. else {
  671. _warning("event '".$event."' catched in handle_other with stanza name ".$stanza->name);
  672. }
  673. }
  674. public function handle_domain_info($stanza) {
  675. $query = $stanza->exists('query', NS_DISCO_INFO);
  676. foreach($query->childrens as $k=>$child) {
  677. if($child->name == 'identity') {
  678. //echo 'identity category:'.@$child->attrs['category'].', type:'.@$child->attrs['type'].', name:'.@$child->attrs['name'].PHP_EOL;
  679. }
  680. else if($child->name == 'x') {
  681. //echo 'x ns:'.$child->ns.PHP_EOL;
  682. }
  683. else if($child->name == 'feature') {
  684. //echo 'feature var:'.$child->attrs['var'].PHP_EOL;
  685. }
  686. }
  687. }
  688. public function handle_domain_items($stanza) {
  689. $query = $stanza->exists('query', NS_DISCO_ITEMS);
  690. foreach($query->childrens as $k=>$child) {
  691. if($child->name == 'item') {
  692. //echo 'item jid:'.@$child->attrs['jid'].', name:'.@$child->attrs['name'].', node:'.@$child->attrs['node'].PHP_EOL;
  693. }
  694. }
  695. }
  696. }
  697. ?>