XMPP.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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. * @copyright 2008 Nathanael C. Fritz
  27. */
  28. /** XMPPHP_XMLStream */
  29. require_once dirname(__FILE__) . "/XMLStream.php";
  30. require_once dirname(__FILE__) . "/Roster.php";
  31. /**
  32. * XMPPHP Main Class
  33. *
  34. * @category xmpphp
  35. * @package XMPPHP
  36. * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
  37. * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
  38. * @author Michael Garvin <JID: gar@netflint.net>
  39. * @copyright 2008 Nathanael C. Fritz
  40. * @version $Id$
  41. */
  42. class XMPPHP_XMPP extends XMPPHP_XMLStream {
  43. /**
  44. * @var string
  45. */
  46. public $server;
  47. /**
  48. * @var string
  49. */
  50. public $user;
  51. /**
  52. * @var string
  53. */
  54. protected $password;
  55. /**
  56. * @var string
  57. */
  58. protected $resource;
  59. /**
  60. * @var string
  61. */
  62. protected $fulljid;
  63. /**
  64. * @var string
  65. */
  66. protected $basejid;
  67. /**
  68. * @var boolean
  69. */
  70. protected $authed = false;
  71. protected $session_started = false;
  72. /**
  73. * @var boolean
  74. */
  75. protected $auto_subscribe = false;
  76. /**
  77. * @var boolean
  78. */
  79. protected $use_encryption = true;
  80. /**
  81. * @var boolean
  82. */
  83. public $track_presence = true;
  84. /**
  85. * @var object
  86. */
  87. public $roster;
  88. /**
  89. * Constructor
  90. *
  91. * @param string $host
  92. * @param integer $port
  93. * @param string $user
  94. * @param string $password
  95. * @param string $resource
  96. * @param string $server
  97. * @param boolean $printlog
  98. * @param string $loglevel
  99. */
  100. public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) {
  101. parent::__construct($host, $port, $printlog, $loglevel);
  102. $this->user = $user;
  103. $this->password = $password;
  104. $this->resource = $resource;
  105. if(!$server) $server = $host;
  106. $this->basejid = $this->user . '@' . $this->host;
  107. $this->roster = new Roster();
  108. $this->track_presence = true;
  109. $this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
  110. $this->stream_end = '</stream:stream>';
  111. $this->default_ns = 'jabber:client';
  112. $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler');
  113. $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler');
  114. $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler');
  115. $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler');
  116. $this->addXPathHandler('{jabber:client}message', 'message_handler');
  117. $this->addXPathHandler('{jabber:client}presence', 'presence_handler');
  118. $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler');
  119. }
  120. /**
  121. * Turn encryption on/ff
  122. *
  123. * @param boolean $useEncryption
  124. */
  125. public function useEncryption($useEncryption = true) {
  126. $this->use_encryption = $useEncryption;
  127. }
  128. /**
  129. * Turn on auto-authorization of subscription requests.
  130. *
  131. * @param boolean $autoSubscribe
  132. */
  133. public function autoSubscribe($autoSubscribe = true) {
  134. $this->auto_subscribe = $autoSubscribe;
  135. }
  136. /**
  137. * Send XMPP Message
  138. *
  139. * @param string $to
  140. * @param string $body
  141. * @param string $type
  142. * @param string $subject
  143. */
  144. public function message($to, $body, $type = 'chat', $subject = null, $payload = null) {
  145. if(is_null($type))
  146. {
  147. $type = 'chat';
  148. }
  149. $to = htmlspecialchars($to);
  150. $body = htmlspecialchars($body);
  151. $subject = htmlspecialchars($subject);
  152. $out = "<message from=\"{$this->fulljid}\" to=\"$to\" type='$type'>";
  153. if($subject) $out .= "<subject>$subject</subject>";
  154. $out .= "<body>$body</body>";
  155. if($payload) $out .= $payload;
  156. $out .= "</message>";
  157. $this->send($out);
  158. }
  159. /**
  160. * Set Presence
  161. *
  162. * @param string $status
  163. * @param string $show
  164. * @param string $to
  165. */
  166. public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) {
  167. if($type == 'available') $type = '';
  168. $to = htmlspecialchars($to);
  169. $status = htmlspecialchars($status);
  170. if($show == 'unavailable') $type = 'unavailable';
  171. $out = "<presence";
  172. if($to) $out .= " to=\"$to\"";
  173. if($type) $out .= " type='$type'";
  174. if($show == 'available' and !$status) {
  175. $out .= "/>";
  176. } else {
  177. $out .= ">";
  178. if($show != 'available') $out .= "<show>$show</show>";
  179. if($status) $out .= "<status>$status</status>";
  180. if($priority) $out .= "<priority>$priority</priority>";
  181. $out .= "</presence>";
  182. }
  183. $this->send($out);
  184. }
  185. /**
  186. * Send Auth request
  187. *
  188. * @param string $jid
  189. */
  190. public function subscribe($jid) {
  191. $this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
  192. #$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
  193. }
  194. /**
  195. * Message handler
  196. *
  197. * @param string $xml
  198. */
  199. public function message_handler($xml) {
  200. if(isset($xml->attrs['type'])) {
  201. $payload['type'] = $xml->attrs['type'];
  202. } else {
  203. $payload['type'] = 'chat';
  204. }
  205. $payload['from'] = $xml->attrs['from'];
  206. $payload['body'] = $xml->sub('body')->data;
  207. $payload['xml'] = $xml;
  208. $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG);
  209. $this->event('message', $payload);
  210. }
  211. /**
  212. * Presence handler
  213. *
  214. * @param string $xml
  215. */
  216. public function presence_handler($xml) {
  217. $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available';
  218. $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type'];
  219. $payload['from'] = $xml->attrs['from'];
  220. $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : '';
  221. $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0;
  222. $payload['xml'] = $xml;
  223. if($this->track_presence) {
  224. $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']);
  225. }
  226. $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG);
  227. if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') {
  228. if($this->auto_subscribe) {
  229. $this->send("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
  230. $this->send("<presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
  231. }
  232. $this->event('subscription_requested', $payload);
  233. } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
  234. $this->event('subscription_accepted', $payload);
  235. } else {
  236. $this->event('presence', $payload);
  237. }
  238. }
  239. /**
  240. * Features handler
  241. *
  242. * @param string $xml
  243. */
  244. protected function features_handler($xml) {
  245. if($xml->hasSub('starttls') and $this->use_encryption) {
  246. $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
  247. } elseif($xml->hasSub('bind') and $this->authed) {
  248. $id = $this->getId();
  249. $this->addIdHandler($id, 'resource_bind_handler');
  250. $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>");
  251. } else {
  252. $this->log->log("Attempting Auth...");
  253. if ($this->password) {
  254. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
  255. } else {
  256. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
  257. }
  258. }
  259. }
  260. /**
  261. * SASL success handler
  262. *
  263. * @param string $xml
  264. */
  265. protected function sasl_success_handler($xml) {
  266. $this->log->log("Auth success!");
  267. $this->authed = true;
  268. $this->reset();
  269. }
  270. /**
  271. * SASL feature handler
  272. *
  273. * @param string $xml
  274. */
  275. protected function sasl_failure_handler($xml) {
  276. $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR);
  277. $this->disconnect();
  278. throw new XMPPHP_Exception('Auth failed!');
  279. }
  280. /**
  281. * Resource bind handler
  282. *
  283. * @param string $xml
  284. */
  285. protected function resource_bind_handler($xml) {
  286. if($xml->attrs['type'] == 'result') {
  287. $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data);
  288. $this->fulljid = $xml->sub('bind')->sub('jid')->data;
  289. $jidarray = explode('/',$this->fulljid);
  290. $this->jid = $jidarray[0];
  291. }
  292. $id = $this->getId();
  293. $this->addIdHandler($id, 'session_start_handler');
  294. $this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
  295. }
  296. /**
  297. * Retrieves the roster
  298. *
  299. */
  300. public function getRoster() {
  301. $id = $this->getID();
  302. $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
  303. }
  304. /**
  305. * Roster iq handler
  306. * Gets all packets matching XPath "iq/{jabber:iq:roster}query'
  307. *
  308. * @param string $xml
  309. */
  310. protected function roster_iq_handler($xml) {
  311. $status = "result";
  312. $xmlroster = $xml->sub('query');
  313. foreach($xmlroster->subs as $item) {
  314. $groups = array();
  315. if ($item->name == 'item') {
  316. $jid = $item->attrs['jid']; //REQUIRED
  317. $name = $item->attrs['name']; //MAY
  318. $subscription = $item->attrs['subscription'];
  319. foreach($item->subs as $subitem) {
  320. if ($subitem->name == 'group') {
  321. $groups[] = $subitem->data;
  322. }
  323. }
  324. $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen
  325. } else {
  326. $status = "error";
  327. }
  328. }
  329. if ($status == "result") { //No errors, add contacts
  330. foreach($contacts as $contact) {
  331. $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]);
  332. }
  333. }
  334. if ($xml->attrs['type'] == 'set') {
  335. $this->send("<iq type=\"reply\" id=\"{$xml->attrs['id']}\" to=\"{$xml->attrs['from']}\" />");
  336. }
  337. }
  338. /**
  339. * Session start handler
  340. *
  341. * @param string $xml
  342. */
  343. protected function session_start_handler($xml) {
  344. $this->log->log("Session started");
  345. $this->session_started = true;
  346. $this->event('session_start');
  347. }
  348. /**
  349. * TLS proceed handler
  350. *
  351. * @param string $xml
  352. */
  353. protected function tls_proceed_handler($xml) {
  354. $this->log->log("Starting TLS encryption");
  355. stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
  356. $this->reset();
  357. }
  358. /**
  359. * Retrieves the vcard
  360. *
  361. */
  362. public function getVCard($jid = Null) {
  363. $id = $this->getID();
  364. $this->addIdHandler($id, 'vcard_get_handler');
  365. if($jid) {
  366. $this->send("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
  367. } else {
  368. $this->send("<iq type='get' id='$id'><vCard xmlns='vcard-temp' /></iq>");
  369. }
  370. }
  371. /**
  372. * VCard retrieval handler
  373. *
  374. * @param XML Object $xml
  375. */
  376. protected function vcard_get_handler($xml) {
  377. $vcard_array = array();
  378. $vcard = $xml->sub('vcard');
  379. // go through all of the sub elements and add them to the vcard array
  380. foreach ($vcard->subs as $sub) {
  381. if ($sub->subs) {
  382. $vcard_array[$sub->name] = array();
  383. foreach ($sub->subs as $sub_child) {
  384. $vcard_array[$sub->name][$sub_child->name] = $sub_child->data;
  385. }
  386. } else {
  387. $vcard_array[$sub->name] = $sub->data;
  388. }
  389. }
  390. $vcard_array['from'] = $xml->attrs['from'];
  391. $this->event('vcard', $vcard_array);
  392. }
  393. }