* @revision 05 * @license http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons Attribution-Share Alike 3.0 Germany * @homepage http://oss.tiggerswelt.net/xmpp * @copyright Copyright © 2009 tiggersWelt.net */ require_once ('tiggerXMPP/stream.php'); require_once ('phpEvents/socket/stream/xml/tag.php'); /** * XMPP Component * -------------- * Component-Implementation according to XEP-0114 * At the moment we only implement the component-accept-method * * @class tiggerXMPP_Component * @extends tiggerXMPP_Stream * @author Bernd Holzmueller **/ class tiggerXMPP_Component extends tiggerXMPP_Stream { const STREAM_TYPE = tiggerXMPP_Stream::STREAM_TYPE_COMPONENT; const COMPONENT_TYPE_XEP = 0; const COMPONENT_TYPE_JABBERD2 = 1; /* Jabberd2 Options */ const COMPONENT_OPTIONS_NONE = 0; const COMPONENT_OPTIONS_DEFAULT = 1; /* Use this domain as default transport */ const COMPONENT_OPTIONS_LOG = 2; /* Receive a copy of each packet */ private $componentType = tiggerXMPP_Component::COMPONENT_TYPE_XEP; private $componentEnvelope = false; private $componentDomains = array (); private $componentNeighbours = array (); // {{{ __construct /** * Create a new XMPP Client * * @param string $Domain Domain to connect to * @param string $Server (optional) Manually specify server * @param int $Port (optional) Manually specify port on server * @param enum $Debug (optional) Set initial debug-value * * @access public * @return void * @see tiggerXMPP_Stream::__construct */ public function __construct ($Domain, $Server = '', $Port = null, $Debug = null) { $this->registerNamespace ('http://jabberd.jabberstudio.org/ns/component/1.0', 'handleRouterPacket', array ('bind', 'unbind', 'throttle', 'presence', 'route'), false); parent::__construct ($Domain, $Server, $Port, self::STREAM_TYPE, $Debug); } // }}} // {{{ setComponentType /** * Set type of this component * * @param enum $Type * * @access public * @return bool **/ public function setComponentType ($Type) { if (!in_array ($Type, array (self::COMPONENT_TYPE_XEP, self::COMPONENT_TYPE_JABBERD2))) return false; $this->componentType = $Type; return true; } // }}} // {{{ streamSetNamespace /** * Determine if stream-initiator has to set a namespace on initial packet * * @access protected * @return bool **/ protected function streamSetNamespace () { return ($this->componentType != self::COMPONENT_TYPE_JABBERD2); } // }}} // {{{ streamSetDestination /** * Determine if stream-initiator should present a stream-destination upon connect * * @access protected * @return bool **/ protected function streamSetDestination () { return ($this->componentType != self::COMPONENT_TYPE_JABBERD2); } // }}} // {{{ authenticate /** * Register authentication-data * * @param string $Password Passwort to authenticate with * @param string $Username (optional) * @param int $Options (optional) * * @access public * @return void */ public function authenticate ($Password, $Username = 'jabberd', $Options = self::COMPONENT_OPTIONS_NONE) { $this->setAuthenticator (array ($this, 'startAuthenticator'), $Password, $Username); } // }}} // {{{ startAuthenticator /** * Start the authentication-process for components * * @param string $Password * @param string $Username * * @access protected * @return bool **/ protected function startAuthenticator ($Password, $Username) { $this->__debug (self::DEBUG_DEBUG, 'called.', __FUNCTION__, __LINE__, __CLASS__, __FILE__); // Perform SASL-Authentication on Jabberd2-components if ($this->componentType == self::COMPONENT_TYPE_JABBERD2) { if (!parent::authenticateSASL ($Username, $Password)) return false; $this->componentEnvelope = true; $this->bindDomain ($this->Domain, $Options); return true; } // Submit handshake $this->sendXML (new phpEvents_Socket_Stream_XML_Tag ('handshake', null, strtolower (sha1 ($this->connectionID . $Password)))); // Wait for response on the handshake $Tag = $this->waitBlock (array ('handshake', 'stream:error')); // Fire the callbacks if ($Tag->getName () == 'handshake') { $this->authenticationSuccess (); return $this->__debug (self::DEBUG_NOTICE, 'Authentication succeded', __FUNCTION__, __LINE__, __CLASS__, __FILE__, true); } $this->authenticationFailure (); return $this->__debug (self::DEBUG_ERROR, 'Authentication failed', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); } // }}} // {{{ bindDomain /** * Bind a domain to our component * * @param string $Domainname * @param int $Options (optional) * * @access public * @return bool **/ public function bindDomain ($Domainname, $Options = self::COMPONENT_OPTIONS_NONE) { // This is only supported on jabberd2 if ($this->componentType != self::COMPONENT_TYPE_JABBERD2) return false; // Create the bind-request $Bind = new tiggerXMPP_Packet ('bind'); $Bind->setNamespace ('http://jabberd.jabberstudio.org/ns/component/1.0'); $Bind->setAttribute ('name', $Domainname); if (($Options & self::COMPONENT_OPTIONS_DEFAULT) == self::COMPONENT_OPTIONS_DEFAULT) $Bind->createSubtag ('default'); if (($Options & self::COMPONENT_OPTIONS_LOG) == self::COMPONENT_OPTIONS_LOG) $Bind->createSubtag ('log'); $this->sendXML ($Bind, null, false, false); // Handle the response if (!is_object ($Resp = $this->waitBlock ('bind'))) return $this->__debug (self::DEBUG_ERROR, 'Failed to read next tag', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); if ($Resp->getName () != 'bind') return $this->__debug (self::DEBUG_ERROR, 'Server sent unexpected response', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); if ($Resp->getAttribute ('error', 0) > 0) return false; $this->componentDomains [strtolower ($Domainname)] = $Domainname; return true; } // }}} // {{{ unbindDomain /** * Unbind our component from a given domain * * @param string $Domainname * * @access public * @return bool **/ public function unbindDomain ($Domainname) { // This is only supported on jabberd2 if ($this->componentType != self::COMPONENT_TYPE_JABBERD2) return false; // Don't unbind our default domain if (strtolower ($Domainname) == strtolower ($this->Domain)) return $this->__debug (self::DEBUG_ERROR, 'Please do not unbind our default domain', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); // Create the unbind-request $Unbind = new tiggerXMPP_Packet ('unbind'); $Unbind->setNamespace ('http://jabberd.jabberstudio.org/ns/component/1.0'); $Unbind->setAttribute ('name', $Domainname); $this->sendXML ($Unbind, null, false, false); // Handle the response if (!is_object ($Resp = $this->tagReadNext ())) return $this->__debug (self::DEBUG_ERROR, 'Failed to read next tag', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); if ($Resp->getName () != 'unbind') return $this->__debug (self::DEBUG_ERROR, 'Server sent unexpected response', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); if ($Resp->getAttribute ('error', 0) > 0) return false; unset ($this->componentDomains [strtolower ($Domainname)]); return true; } // }}} // {{{ throttle /** * Throttle/Unthrottle our router-connection * * @access public * @return bool **/ public function throttle () { // This is only supported on jabberd2 if ($this->componentType != self::COMPONENT_TYPE_JABBERD2) return false; // Create the bind-request $this->sendXML (new tiggerXMPP_Packet ('throttle'), null, false, false); // Handle the response if (!is_object ($Resp = $this->tagReadNext ())) return $this->__debug (self::DEBUG_ERROR, 'Failed to read next tag', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); if ($Resp->getName () != 'throttle') return $this->__debug (self::DEBUG_ERROR, 'Server sent unexpected response', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); return true; } // }}} // {{{ sendXML /** * Send an XML-Block over the wire * * @param object $Tag * @param object $Packet (optional) Packet to reply to * @param bool $returnLength (optional) Return length instead of a boolean * @param bool $allowEnvelope (optional) Allow to put an envelope around this packet * * @access public * @return bool **/ public function sendXML ($Tag, $Packet = null, $returnLength = false, $allowEnvelope = true) { // Check if we have a non-objective tag if (!is_object ($Tag)) { trigger_error ('Make sure that you send XML-Tags as an object to jabberd2'); $Tag = $this->toObject ($Tag); } // Append respond-packet if (is_object ($Packet) && ($id = $Packet->getAttribute ('id', false))) $Tag->setAttribute ('id', $id); // Generate a jabberd2-envelope and inherit to our parent if ($this->componentEnvelope && $allowEnvelope) $Tag = $this->envelopeTag ($Tag); return parent::sendXML ($Tag, null, $returnLength); } // }}} // {{{ envelopeTag /** * Wrap a route-tag around a given tag * * @param object $Tag * @param string $fromDomain (optional) * * @access protected * @return object **/ protected function envelopeTag ($Tag, $fromDomain = null) { // Check if an envelope is required if ($this->componentType != self::COMPONENT_TYPE_JABBERD2) return $Tag; // Check if this is already a route-tag if (!is_object ($Tag) || ($Tag->getName () == 'route')) return $Tag; // Retrive the destination of the packet if (!($Destination = $Tag->getAttribute ('to', false))) return $this->__debug (self::DEBUG_ERROR, 'Missing to-Attribute on tag ' . $Tag->getName (), __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); // Try to Auto-Detect our originator if (($fromDomain === null) && !($fromDomain = $this->detectDomain ($Tag))) return $Tag; // Create a new route $Envelope = new tiggerXMPP_Packet ('route'); $Envelope->setNamespace ('http://jabberd.jabberstudio.org/ns/component/1.0'); $Envelope->setAttribute ('to', $this->getJID (false, true, false, $Destination)); $Envelope->setAttribute ('from', $fromDomain); // Attach the tag to the route $Tag->setParent ($Envelope); return $Envelope; } // }}} // {{{ detectDomain /** * Try to auto-detect a source-domain on an XML-Tag * * @param object $Tag * * @access protected * @return string **/ protected function detectDomain ($Tag) { // Try to get the from-Attribute from tag if (!($Source = $Tag->getAttribute ('from', false))) return $this->__debug (self::DEBUG_ERROR, 'Missing from-Attribute on tag ' . $Tag->getName (), __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); // Truncate any username and ressource $Source = strtolower ($this->getJID (false, true, false, $Source)); if (isset ($this->componentDomains [$Source])) return $Source; foreach ($this->componentDomains as $fromDomain) return $fromDomain; return $this->__debug (self::DEBUG_ERROR, 'Did not find any domain', __FUNCTION__, __LINE__, __CLASS__, __FILE__, false); } // }}} // {{{ boundDomain /** * Check if this component is bound to a given domain * * @param string $Domain * * @access public * @return bool **/ public function boundDomain ($Domain) { return isset ($this->componentDomains [$Domain]); } // }}} // {{{ handleRouterPacket /** * Handle an incoming packet from the jabberd2-router * * @param object $Tag * @param object $Response * * @access protected * @return mixed **/ protected function handleRouterPacket ($Tag, $Response) { // Ignore log-packets by default if ($Tag->getAttribute ('type') == 'log') return null; // Handle the packet switch ($Tag->getName ()) { case 'bind': case 'unbind': case 'throtle': // Just ignore these packets, they are *normally* handled by other functions break; case 'route': // Just forward the children of this tag foreach ($Tag->getSubtags () as $Subtags) foreach ($Subtags as $Subtag) $this->receiveTag ($Subtag); break; case 'presence': // Mark a component as offline if ($Tag->getAttribute ('type') == self::PRESENCE_OFFLINE) unset ($this->componentNeighbours [$Tag->getOriginator ()]); // Mark a component as online (if not already online) elseif (!isset ($this->componentNeighbours [$Tag->getOriginator ()])) $this->componentNeighbours [$Tag->getOriginator ()] = true; break; } return null; } // }}} // {{{ waitBlockMatch /** * Check if a given XML-Tag matches the one we are waiting for * * @access protected * @return object **/ public function waitBlockMatch ($Tag, $Types, $IDs) { // Only use this function for jabberd2-components if (($this->componentType != self::COMPONENT_TYPE_JABBERD2) || ($Tag->getName () != 'route')) return parent::waitBlockMatch ($Tag, $Types, $IDs); foreach ($Tag->getSubtags () as $Subtags) foreach ($Subtags as $Subtag) if (is_object (parent::waitBlockMatch ($Subtag, $Types, $IDs))) return $Subtag; return false; } // }}} } ?>