xmppmanager.php 8.7 KB

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