componentxmpp.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # -*- coding: utf-8 -*-
  2. """
  3. sleekxmpp.clientxmpp
  4. ~~~~~~~~~~~~~~~~~~~~
  5. This module provides XMPP functionality that
  6. is specific to external server component connections.
  7. Part of SleekXMPP: The Sleek XMPP Library
  8. :copyright: (c) 2011 Nathanael C. Fritz
  9. :license: MIT, see LICENSE for more details
  10. """
  11. from __future__ import absolute_import
  12. import logging
  13. import sys
  14. import hashlib
  15. from sleekxmpp.basexmpp import BaseXMPP
  16. from sleekxmpp.xmlstream import XMLStream
  17. from sleekxmpp.xmlstream import ET
  18. from sleekxmpp.xmlstream.matcher import MatchXPath
  19. from sleekxmpp.xmlstream.handler import Callback
  20. log = logging.getLogger(__name__)
  21. class ComponentXMPP(BaseXMPP):
  22. """
  23. SleekXMPP's basic XMPP server component.
  24. Use only for good, not for evil.
  25. :param jid: The JID of the component.
  26. :param secret: The secret or password for the component.
  27. :param host: The server accepting the component.
  28. :param port: The port used to connect to the server.
  29. :param plugin_config: A dictionary of plugin configurations.
  30. :param plugin_whitelist: A list of approved plugins that
  31. will be loaded when calling
  32. :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
  33. :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
  34. should be used instead of the standard
  35. ``'jabber:component:accept'`` namespace.
  36. Defaults to ``False``.
  37. """
  38. def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
  39. if not plugin_whitelist:
  40. plugin_whitelist = []
  41. if not plugin_config:
  42. plugin_config = {}
  43. if use_jc_ns:
  44. default_ns = 'jabber:client'
  45. else:
  46. default_ns = 'jabber:component:accept'
  47. BaseXMPP.__init__(self, jid, default_ns)
  48. self.auto_authorize = None
  49. self.stream_header = "<stream:stream %s %s to='%s'>" % (
  50. 'xmlns="jabber:component:accept"',
  51. 'xmlns:stream="%s"' % self.stream_ns,
  52. jid)
  53. self.stream_footer = "</stream:stream>"
  54. self.server_host = host
  55. self.server_port = port
  56. self.secret = secret
  57. self.plugin_config = plugin_config
  58. self.plugin_whitelist = plugin_whitelist
  59. self.is_component = True
  60. self.register_handler(
  61. Callback('Handshake',
  62. MatchXPath('{jabber:component:accept}handshake'),
  63. self._handle_handshake))
  64. self.add_event_handler('presence_probe',
  65. self._handle_probe)
  66. def connect(self, host=None, port=None, use_ssl=False,
  67. use_tls=False, reattempt=True):
  68. """Connect to the server.
  69. Setting ``reattempt`` to ``True`` will cause connection attempts to
  70. be made every second until a successful connection is established.
  71. :param host: The name of the desired server for the connection.
  72. Defaults to :attr:`server_host`.
  73. :param port: Port to connect to on the server.
  74. Defauts to :attr:`server_port`.
  75. :param use_ssl: Flag indicating if SSL should be used by connecting
  76. directly to a port using SSL.
  77. :param use_tls: Flag indicating if TLS should be used, allowing for
  78. connecting to a port without using SSL immediately and
  79. later upgrading the connection.
  80. :param reattempt: Flag indicating if the socket should reconnect
  81. after disconnections.
  82. """
  83. if host is None:
  84. host = self.server_host
  85. if port is None:
  86. port = self.server_port
  87. self.server_name = self.boundjid.host
  88. if use_tls:
  89. log.info("XEP-0114 components can not use TLS")
  90. log.debug("Connecting to %s:%s", host, port)
  91. return XMLStream.connect(self, host=host, port=port,
  92. use_ssl=use_ssl,
  93. use_tls=False,
  94. reattempt=reattempt)
  95. def incoming_filter(self, xml):
  96. """
  97. Pre-process incoming XML stanzas by converting any
  98. ``'jabber:client'`` namespaced elements to the component's
  99. default namespace.
  100. :param xml: The XML stanza to pre-process.
  101. """
  102. if xml.tag.startswith('{jabber:client}'):
  103. xml.tag = xml.tag.replace('jabber:client', self.default_ns)
  104. return xml
  105. def start_stream_handler(self, xml):
  106. """
  107. Once the streams are established, attempt to handshake
  108. with the server to be accepted as a component.
  109. :param xml: The incoming stream's root element.
  110. """
  111. BaseXMPP.start_stream_handler(self, xml)
  112. # Construct a hash of the stream ID and the component secret.
  113. sid = xml.get('id', '')
  114. pre_hash = '%s%s' % (sid, self.secret)
  115. if sys.version_info >= (3, 0):
  116. # Handle Unicode byte encoding in Python 3.
  117. pre_hash = bytes(pre_hash, 'utf-8')
  118. handshake = ET.Element('{jabber:component:accept}handshake')
  119. handshake.text = hashlib.sha1(pre_hash).hexdigest().lower()
  120. self.send_xml(handshake, now=True)
  121. def _handle_handshake(self, xml):
  122. """The handshake has been accepted.
  123. :param xml: The reply handshake stanza.
  124. """
  125. self.session_bind_event.set()
  126. self.session_started_event.set()
  127. self.event('session_bind', self.boundjid, direct=True)
  128. self.event('session_start')
  129. def _handle_probe(self, pres):
  130. self.roster[pres['to']][pres['from']].handle_probe(pres)