XMPP.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <?php
  2. /**
  3. * XMPPHP: The PHP XMPP Library
  4. * Copyright (C) 2008 Nathanael C. Fritz
  5. * This file is part of SleekXMPP.
  6. *
  7. * XMPPHP is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * XMPPHP is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with XMPPHP; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category xmpphp
  22. * @package XMPPHP
  23. * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
  24. * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
  25. * @author Michael Garvin <JID: gar@netflint.net>
  26. * @author Alexander Birkner (https://github.com/BirknerAlex)
  27. * @author zorn-v (https://github.com/zorn-v/xmpphp/)
  28. * @author GNU social
  29. * @copyright 2008 Nathanael C. Fritz
  30. */
  31. namespace XMPPHP;
  32. /** XMPPHP_XMLStream */
  33. require_once __DIR__ . DIRECTORY_SEPARATOR . 'XMLStream.php';
  34. require_once __DIR__ . DIRECTORY_SEPARATOR . 'Roster.php';
  35. /**
  36. * XMPPHP XMPP
  37. *
  38. * @package XMPPHP
  39. * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
  40. * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
  41. * @author Michael Garvin <JID: gar@netflint.net>
  42. * @copyright 2008 Nathanael C. Fritz
  43. * @version $Id$
  44. */
  45. class XMPP extends XMLStream
  46. {
  47. /**
  48. * @var string
  49. */
  50. public $server;
  51. /**
  52. * @var string
  53. */
  54. public $user;
  55. /**
  56. * @var boolean
  57. */
  58. public $track_presence = true;
  59. /**
  60. * @var object
  61. */
  62. public $roster;
  63. /**
  64. * @var string
  65. */
  66. protected $password;
  67. /**
  68. * @var string
  69. */
  70. protected $resource;
  71. /**
  72. * @var string
  73. */
  74. protected $fulljid;
  75. /**
  76. * @var string
  77. */
  78. protected $basejid;
  79. /**
  80. * @var boolean
  81. */
  82. protected $authed = false;
  83. protected $session_started = false;
  84. /**
  85. * @var boolean
  86. */
  87. protected $auto_subscribe = false;
  88. /**
  89. * @var boolean
  90. */
  91. protected $use_encryption = true;
  92. /**
  93. * Constructor
  94. *
  95. * @param string $host
  96. * @param integer $port
  97. * @param string $user
  98. * @param string $password
  99. * @param string $resource
  100. * @param string $server
  101. * @param boolean $printlog
  102. * @param string $loglevel
  103. */
  104. public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null)
  105. {
  106. parent::__construct($host, $port, $printlog, $loglevel);
  107. $this->user = $user;
  108. $this->password = $password;
  109. $this->resource = $resource;
  110. if (!$server) {
  111. $server = $host;
  112. }
  113. $this->server = $server;
  114. $this->basejid = $this->user . '@' . $this->host;
  115. $this->roster = new Roster();
  116. $this->track_presence = true;
  117. $this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
  118. $this->stream_end = '</stream:stream>';
  119. $this->default_ns = 'jabber:client';
  120. $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler');
  121. $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler');
  122. $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler');
  123. $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler');
  124. $this->addXPathHandler('{jabber:client}message', 'message_handler');
  125. $this->addXPathHandler('{jabber:client}presence', 'presence_handler');
  126. $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler');
  127. }
  128. /**
  129. * Turn encryption on/ff
  130. *
  131. * @param boolean $useEncryption
  132. */
  133. public function useEncryption($useEncryption = true)
  134. {
  135. $this->use_encryption = $useEncryption;
  136. }
  137. /**
  138. * Turn on auto-authorization of subscription requests.
  139. *
  140. * @param boolean $autoSubscribe
  141. */
  142. public function autoSubscribe($autoSubscribe = true)
  143. {
  144. $this->auto_subscribe = $autoSubscribe;
  145. }
  146. /**
  147. * Send XMPP Message
  148. *
  149. * @param string $to
  150. * @param string $body
  151. * @param string $type
  152. * @param string $subject
  153. * @param null $payload
  154. * @throws Exception
  155. */
  156. public function message($to, $body, $type = 'chat', $subject = null, $payload = null)
  157. {
  158. if ($this->disconnected) {
  159. throw new Exception('You need to connect first');
  160. }
  161. if (empty($type)) {
  162. $type = 'chat';
  163. }
  164. $to = htmlspecialchars($to);
  165. $body = htmlspecialchars($body);
  166. $subject = htmlspecialchars($subject);
  167. $subject = ($subject) ? '<subject>' . $subject . '</subject>' : '';
  168. $payload = ($payload) ? $payload : '';
  169. $sprintf = '<message from="%s" to="%s" type="%s">%s<body>%s</body>%s</message>';
  170. $output = sprintf($sprintf, $this->fulljid, $to, $type, $subject, $body, $payload);
  171. $this->send($output);
  172. }
  173. /**
  174. * Set Presence
  175. *
  176. * @param string $status
  177. * @param string $show
  178. * @param string $to
  179. * @param string $type
  180. * @param null $priority
  181. * @throws Exception
  182. */
  183. public function presence($status = null, $show = 'available', $to = null, $type = 'available', $priority = null)
  184. {
  185. if ($this->disconnected) {
  186. throw new Exception('You need to connect first');
  187. }
  188. if ($type == 'available') {
  189. $type = '';
  190. }
  191. $to = htmlspecialchars($to);
  192. $status = htmlspecialchars($status);
  193. if ($show == 'unavailable') {
  194. $type = 'unavailable';
  195. }
  196. $out = "<presence";
  197. if ($to) {
  198. $out .= " to=\"$to\"";
  199. }
  200. if ($type) {
  201. $out .= " type='$type'";
  202. }
  203. if ($show == 'available' and !$status and $priority !== null) {
  204. $out .= "/>";
  205. } else {
  206. $out .= ">";
  207. if ($show != 'available') {
  208. $out .= "<show>$show</show>";
  209. }
  210. if ($status) {
  211. $out .= "<status>$status</status>";
  212. }
  213. if ($priority !== null) {
  214. $out .= "<priority>$priority</priority>";
  215. }
  216. $out .= "</presence>";
  217. }
  218. $this->send($out);
  219. }
  220. /**
  221. * Send Auth request
  222. *
  223. * @param string $jid
  224. */
  225. public function subscribe($jid)
  226. {
  227. $this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
  228. #$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
  229. }
  230. /**
  231. * Message handler
  232. *
  233. * @param string $xml
  234. */
  235. public function message_handler($xml)
  236. {
  237. if (isset($xml->attrs['type'])) {
  238. $payload['type'] = $xml->attrs['type'];
  239. } else {
  240. $payload['type'] = 'chat';
  241. }
  242. $body = $xml->sub('body');
  243. $payload['from'] = $xml->attrs['from'];
  244. $payload['body'] = is_object($body) ? $body->data : false; // $xml->sub('body')->data;
  245. $payload['xml'] = $xml;
  246. $this->log->log("Message: {$payload['body']}", Log::LEVEL_DEBUG);
  247. $this->event('message', $payload);
  248. }
  249. /**
  250. * Presence handler
  251. *
  252. * @param string $xml
  253. */
  254. public function presence_handler($xml)
  255. {
  256. $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available';
  257. $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type'];
  258. $payload['from'] = $xml->attrs['from'];
  259. $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : '';
  260. $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0;
  261. $payload['xml'] = $xml;
  262. if ($this->track_presence) {
  263. $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']);
  264. }
  265. $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", Log::LEVEL_DEBUG);
  266. if (array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') {
  267. if ($this->auto_subscribe) {
  268. $this->send("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
  269. $this->send("<presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
  270. }
  271. $this->event('subscription_requested', $payload);
  272. } elseif (array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
  273. $this->event('subscription_accepted', $payload);
  274. } else {
  275. $this->event('presence', $payload);
  276. }
  277. }
  278. /**
  279. * Retrieves the roster
  280. *
  281. */
  282. public function getRoster()
  283. {
  284. $id = $this->getID();
  285. $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
  286. }
  287. /**
  288. * Retrieves the vcard
  289. * @param null $jid
  290. */
  291. public function getVCard($jid = null)
  292. {
  293. $id = $this->getID();
  294. $this->addIdHandler($id, 'vcard_get_handler');
  295. if ($jid) {
  296. $this->send("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
  297. } else {
  298. $this->send("<iq type='get' id='$id'><vCard xmlns='vcard-temp' /></iq>");
  299. }
  300. }
  301. /**
  302. * Features handler
  303. *
  304. * @param string $xml
  305. */
  306. protected function features_handler($xml)
  307. {
  308. if ($xml->hasSub('starttls') and $this->use_encryption) {
  309. $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
  310. } elseif ($xml->hasSub('bind') and $this->authed) {
  311. $id = $this->getId();
  312. $this->addIdHandler($id, 'resource_bind_handler');
  313. $this->send("<iq xmlns=\"jabber:client\" type=\"set\" id=\"$id\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>{$this->resource}</resource></bind></iq>");
  314. } else {
  315. $this->log->log("Attempting Auth...");
  316. if ($this->password) {
  317. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
  318. } else {
  319. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
  320. }
  321. }
  322. }
  323. /**
  324. * SASL success handler
  325. *
  326. * @param string $xml
  327. */
  328. protected function sasl_success_handler($xml)
  329. {
  330. $this->log->log("Auth success!");
  331. $this->authed = true;
  332. $this->reset();
  333. }
  334. /**
  335. * SASL feature handler
  336. *
  337. * @param string $xml
  338. * @throws Exception
  339. */
  340. protected function sasl_failure_handler($xml)
  341. {
  342. $this->log->log("Auth failed!", Log::LEVEL_ERROR);
  343. $this->disconnect();
  344. throw new Exception('Auth failed!');
  345. }
  346. /**
  347. * Resource bind handler
  348. *
  349. * @param string $xml
  350. */
  351. protected function resource_bind_handler($xml)
  352. {
  353. if ($xml->attrs['type'] == 'result') {
  354. $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data);
  355. $this->fulljid = $xml->sub('bind')->sub('jid')->data;
  356. $jidarray = explode('/', $this->fulljid);
  357. $this->jid = $jidarray[0];
  358. }
  359. $id = $this->getId();
  360. $this->addIdHandler($id, 'session_start_handler');
  361. $this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
  362. }
  363. /**
  364. * Roster iq handler
  365. * Gets all packets matching XPath "iq/{jabber:iq:roster}query'
  366. *
  367. * @param string $xml
  368. */
  369. protected function roster_iq_handler($xml)
  370. {
  371. $status = "result";
  372. $xmlroster = $xml->sub('query');
  373. foreach ($xmlroster->subs as $item) {
  374. $groups = array();
  375. if ($item->name == 'item') {
  376. $jid = $item->attrs['jid']; //REQUIRED
  377. $name = $item->attrs['name']; //MAY
  378. $subscription = $item->attrs['subscription'];
  379. foreach ($item->subs as $subitem) {
  380. if ($subitem->name == 'group') {
  381. $groups[] = $subitem->data;
  382. }
  383. }
  384. $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen
  385. } else {
  386. $status = "error";
  387. }
  388. }
  389. if ($status == "result") { //No errors, add contacts
  390. foreach ($contacts as $contact) {
  391. $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]);
  392. }
  393. }
  394. if ($xml->attrs['type'] == 'set') {
  395. $this->send("<iq type=\"reply\" id=\"{$xml->attrs['id']}\" to=\"{$xml->attrs['from']}\" />");
  396. }
  397. }
  398. /**
  399. * Session start handler
  400. *
  401. * @param string $xml
  402. */
  403. protected function session_start_handler($xml)
  404. {
  405. $this->log->log("Session started");
  406. $this->session_started = true;
  407. $this->event('session_start');
  408. }
  409. /**
  410. * TLS proceed handler
  411. *
  412. * @param string $xml
  413. */
  414. protected function tls_proceed_handler($xml)
  415. {
  416. $this->log->log("Starting TLS encryption");
  417. stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
  418. $this->reset();
  419. }
  420. /**
  421. * VCard retrieval handler
  422. *
  423. * @param XMLObj $xml
  424. */
  425. protected function vcard_get_handler($xml)
  426. {
  427. $vcard_array = array();
  428. $vcard = $xml->sub('vcard');
  429. // go through all of the sub elements and add them to the vcard array
  430. foreach ($vcard->subs as $sub) {
  431. if ($sub->subs) {
  432. $vcard_array[$sub->name] = array();
  433. foreach ($sub->subs as $sub_child) {
  434. $vcard_array[$sub->name][$sub_child->name] = $sub_child->data;
  435. }
  436. } else {
  437. $vcard_array[$sub->name] = $sub->data;
  438. }
  439. }
  440. $vcard_array['from'] = $xml->attrs['from'];
  441. $this->event('vcard', $vcard_array);
  442. }
  443. }