xmppmanager.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <?php
  2. /*
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2008, 2009, StatusNet, Inc.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
  20. /**
  21. * XMPP background connection manager for XMPP-using queue handlers,
  22. * allowing them to send outgoing messages on the right connection.
  23. *
  24. * Input is handled during socket select loop, keepalive pings during idle.
  25. * Any incoming messages will be handled.
  26. *
  27. * In a multi-site queuedaemon.php run, one connection will be instantiated
  28. * for each site being handled by the current process that has XMPP enabled.
  29. */
  30. class XmppManager extends ImManager
  31. {
  32. protected $lastping = null;
  33. protected $pingid = null;
  34. public $conn = null;
  35. const PING_INTERVAL = 120;
  36. /**
  37. * Initialize connection to server.
  38. * @return boolean true on success
  39. */
  40. public function start($master)
  41. {
  42. if(parent::start($master))
  43. {
  44. $this->connect();
  45. return true;
  46. }else{
  47. return false;
  48. }
  49. }
  50. function send_raw_message($data)
  51. {
  52. $this->connect();
  53. if (!$this->conn || $this->conn->isDisconnected()) {
  54. return false;
  55. }
  56. $this->conn->send($data);
  57. return true;
  58. }
  59. /**
  60. * Message pump is triggered on socket input, so we only need an idle()
  61. * call often enough to trigger our outgoing pings.
  62. */
  63. function timeout()
  64. {
  65. return self::PING_INTERVAL;
  66. }
  67. /**
  68. * Process XMPP events that have come in over the wire.
  69. * @fixme may kill process on XMPP error
  70. * @param resource $socket
  71. */
  72. public function handleInput($socket)
  73. {
  74. // Process the queue for as long as needed
  75. try {
  76. common_log(LOG_DEBUG, "Servicing the XMPP queue.");
  77. $this->stats('xmpp_process');
  78. $this->conn->processTime(0);
  79. } catch (XMPPHP_Exception $e) {
  80. common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
  81. die($e->getMessage());
  82. }
  83. }
  84. /**
  85. * Lists the IM connection socket to allow i/o master to wake
  86. * when input comes in here as well as from the queue source.
  87. *
  88. * @return array of resources
  89. */
  90. public function getSockets()
  91. {
  92. $this->connect();
  93. if($this->conn){
  94. return array($this->conn->getSocket());
  95. }else{
  96. return array();
  97. }
  98. }
  99. /**
  100. * Idle processing for io manager's execution loop.
  101. * Send keepalive pings to server.
  102. *
  103. * Side effect: kills process on exception from XMPP library.
  104. *
  105. * @todo FIXME: non-dying error handling
  106. */
  107. public function idle($timeout=0)
  108. {
  109. $now = time();
  110. if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) {
  111. try {
  112. $this->send_ping();
  113. } catch (XMPPHP_Exception $e) {
  114. common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
  115. die($e->getMessage());
  116. }
  117. }
  118. }
  119. function connect()
  120. {
  121. if (!$this->conn || $this->conn->isDisconnected()) {
  122. $resource = 'queue' . posix_getpid();
  123. $this->conn = new SharingXMPP($this->plugin->host ?
  124. $this->plugin->host :
  125. $this->plugin->server,
  126. $this->plugin->port,
  127. $this->plugin->user,
  128. $this->plugin->password,
  129. $this->plugin->resource,
  130. $this->plugin->server,
  131. $this->plugin->debug ?
  132. true : false,
  133. $this->plugin->debug ?
  134. XMPPHP_Log::LEVEL_VERBOSE : null
  135. );
  136. if (!$this->conn) {
  137. return false;
  138. }
  139. $this->conn->addEventHandler('message', 'handle_xmpp_message', $this);
  140. $this->conn->addEventHandler('reconnect', 'handle_xmpp_reconnect', $this);
  141. $this->conn->setReconnectTimeout(600);
  142. $this->conn->autoSubscribe();
  143. $this->conn->useEncryption($this->plugin->encryption);
  144. try {
  145. $this->conn->connect(true); // true = persistent connection
  146. } catch (XMPPHP_Exception $e) {
  147. common_log(LOG_ERR, $e->getMessage());
  148. return false;
  149. }
  150. $this->conn->processUntil('session_start');
  151. // TRANS: Presence announcement for XMPP.
  152. $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100);
  153. }
  154. return $this->conn;
  155. }
  156. function send_ping()
  157. {
  158. $this->connect();
  159. if (!$this->conn || $this->conn->isDisconnected()) {
  160. return false;
  161. }
  162. $now = time();
  163. if (!isset($this->pingid)) {
  164. $this->pingid = 0;
  165. } else {
  166. $this->pingid++;
  167. }
  168. common_log(LOG_DEBUG, "Sending ping #{$this->pingid}");
  169. $this->conn->send("<iq from='{" . $this->plugin->daemonScreenname() . "}' to='{$this->plugin->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
  170. $this->lastping = $now;
  171. return true;
  172. }
  173. function handle_xmpp_message(&$pl)
  174. {
  175. $this->plugin->enqueueIncomingRaw($pl);
  176. return true;
  177. }
  178. /**
  179. * Callback for Jabber reconnect event
  180. * @param $pl
  181. */
  182. function handle_xmpp_reconnect(&$pl)
  183. {
  184. common_log(LOG_NOTICE, 'XMPP reconnected');
  185. $this->conn->processUntil('session_start');
  186. // TRANS: Message for XMPP reconnect.
  187. $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100);
  188. }
  189. /**
  190. * sends a presence stanza on the XMPP network
  191. *
  192. * @param string $status current status, free-form string
  193. * @param string $show structured status value
  194. * @param string $to recipient of presence, null for general
  195. * @param string $type type of status message, related to $show
  196. * @param int $priority priority of the presence
  197. *
  198. * @return boolean success value
  199. */
  200. function send_presence($status, $show='available', $to=null,
  201. $type = 'available', $priority=null)
  202. {
  203. $this->connect();
  204. if (!$this->conn || $this->conn->isDisconnected()) {
  205. return false;
  206. }
  207. $this->conn->presence($status, $show, $to, $type, $priority);
  208. return true;
  209. }
  210. /**
  211. * sends a "special" presence stanza on the XMPP network
  212. *
  213. * @param string $type Type of presence
  214. * @param string $to JID to send presence to
  215. * @param string $show show value for presence
  216. * @param string $status status value for presence
  217. *
  218. * @return boolean success flag
  219. *
  220. * @see send_presence()
  221. */
  222. function special_presence($type, $to=null, $show=null, $status=null)
  223. {
  224. // @todo FIXME: why use this instead of send_presence()?
  225. $this->connect();
  226. if (!$this->conn || $this->conn->isDisconnected()) {
  227. return false;
  228. }
  229. $to = htmlspecialchars($to);
  230. $status = htmlspecialchars($status);
  231. $out = "<presence";
  232. if ($to) {
  233. $out .= " to='$to'";
  234. }
  235. if ($type) {
  236. $out .= " type='$type'";
  237. }
  238. if ($show == 'available' and !$status) {
  239. $out .= "/>";
  240. } else {
  241. $out .= ">";
  242. if ($show && ($show != 'available')) {
  243. $out .= "<show>$show</show>";
  244. }
  245. if ($status) {
  246. $out .= "<status>$status</status>";
  247. }
  248. $out .= "</presence>";
  249. }
  250. $this->conn->send($out);
  251. return true;
  252. }
  253. }