protected function _authenticate($command, $xml) { //response switch switch ($command) { case self::AUTH_TYPE_STREAM: // Connection initialized (or after authentication). Not much to do here... if (isset($xml['stream:stream'][0]['#']['stream:features'])) { // we already got all info we need $features = $xml['stream:stream'][0]['#']; } else { $features = $this->wait(); } $second_time = isset($this->_streamId); $this->_streamId = $xml['stream:stream'][0]['@']['id']; if ($second_time) { // If we are here for the second time after TLS, we need to continue logging in //if there are no features if (!sizeof($features)) { //throw exception Eden_Jabber_Error::i(Eden_Jabber_Error::NO_FEATURES)->trigger(); } return $this->_response($features); } //we are on the first step $this->_negotiation = self::AUTH_STARTED; // go on with authentication? if (isset($features['stream:features'][0]['#']['mechanisms']) || $this->_negotiation == self::AUTH_PROCEED) { return $this->_response($features); } break; case self::AUTH_TYPE_FEATURES: // Resource binding after successful authentication if ($this->_negotiation == self::AUTH_SUCCESS) { // session required? $this->_session = isset($xml['stream:features'][0]['#']['session']); $this->send("<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" . "<resource>" . htmlspecialchars($this->_resource) . '</resource></bind></iq>'); return $this->_response($this->wait()); } // Let's use TLS if SSL is not enabled and we can actually use it if (!$this->_ssl && $this->_tls && $this->_canUseSSL() && isset($xml['stream:features'][0]['#']['starttls'])) { $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n"); return $this->_response($this->wait()); } // Does the server support SASL authentication? // I hope so, because we do (and no other method). if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl') { // Now decide on method $methods = array(); foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value) { $methods[] = $value['#']; } // we prefer DIGEST-MD5 // we don't want to use plain authentication (neither does the server usually) if no encryption is in place // http://www.xmpp.org/extensions/attic/jep-0078-1.7.html // The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS) // and the client has verified that the server certificate is signed by a trusted certificate authority. if (in_array('DIGEST-MD5', $methods)) { $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"); } else { if (in_array('PLAIN', $methods) && ($this->_ssl || $this->_negotiation == self::AUTH_PROCEED)) { $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode(chr(0) . $this->_user . '@' . $this->_domain . chr(0) . $this->_pass) . '</auth>'); } else { if (in_array('ANONYMOUS', $methods)) { $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>"); } else { // not good... //disconnect $this->disconnect(); //throw an exception Eden_Jabber_Error::i(Eden_Jabber_Error::NO_AUTH_METHOD)->trigger(); } } } return $this->_response($this->wait()); } // ok, this is it. bye. //disconnect $this->disconnect(); //throw an exception Eden_Jabber_Error::i(Eden_Jabber_Error::NO_SASL)->trigger(); break; case self::AUTH_TYPE_CHALLENGE: // continue with authentication...a challenge literally -_- $this->_negotiation = self::AUTH_CHALLENGE; $decoded = base64_decode($xml['challenge'][0]['#']); $decoded = $this->_parseData($decoded); if (!isset($decoded['digest-uri'])) { $decoded['digest-uri'] = 'xmpp/' . $this->_host; } // better generate a cnonce, maybe it's needed $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true))); // second challenge? if (isset($decoded['rspauth'])) { $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); } else { // Make sure we only use 'auth' for qop (relevant for $this->_encryptPass()) // If the <response> is choking up on the changed parameter we may need to adjust _encryptPass() directly if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false) { $decoded['qop'] = 'auth'; } $response = array('username' => $this->_user, 'response' => $this->_encryptPass(array_merge($decoded, array('nc' => '00000001'))), 'charset' => 'utf-8', 'nc' => '00000001', 'qop' => 'auth'); // only auth being supported foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key) { if (isset($decoded[$key])) { $response[$key] = $decoded[$key]; } } $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" . base64_encode($this->_implodeData($response)) . '</response>'); } return $this->_response($this->wait()); case self::AUTH_TYPE_FAILURE: $this->_negotiation = self::AUTH_FAILIURE; $this->trigger('failiure'); //disconnect $this->disconnect(); //throw an exception Eden_Jabber_Error::i(Eden_Jabber_Error::SERVER_FAILED)->trigger(); case self::AUTH_TYPE_PROCEED: // continue switching to TLS $meta = stream_get_meta_data($this->_connection); socket_set_blocking($this->_connection, 1); if (!stream_socket_enable_crypto($this->_connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { //'Error: TLS mode change failed.' Eden_Jabber_Error::i(Eden_Jabber_Error::SERVER_FAILED)->trigger(); } socket_set_blocking($this->_connection, $meta['blocked']); $this->_negotiation = self::AUTH_PROCEED; // new stream $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n"); $this->send("<stream:stream to='" . $this->_host . "' xmlns='jabber:client' " . "xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"); return $this->_response($this->wait()); case self::AUTH_TYPE_SUCCESS: // Yay, authentication successful. $this->send("<stream:stream to='" . $this->_host . "' xmlns='jabber:client' " . "xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"); $this->_negotiation = self::AUTH_SUCCESS; // we have to wait for another response return $this->_response($this->wait()); } return $this; }
protected function _authenticate($command, $xml) { switch ($command) { case self::AUTH_TYPE_STREAM: if (isset($xml['stream:stream'][0]['#']['stream:features'])) { $features = $xml['stream:stream'][0]['#']; } else { $features = $this->wait(); } $second_time = isset($this->_streamId); $this->_streamId = $xml['stream:stream'][0]['@']['id']; if ($second_time) { if (!sizeof($features)) { Eden_Jabber_Error::i(Eden_Jabber_Error::NO_FEATURES)->trigger(); } return $this->_response($features); } $this->_negotiation = self::AUTH_STARTED; if (isset($features['stream:features'][0]['#']['mechanisms']) || $this->_negotiation == self::AUTH_PROCEED) { return $this->_response($features); } break; case self::AUTH_TYPE_FEATURES: if ($this->_negotiation == self::AUTH_SUCCESS) { $this->_session = isset($xml['stream:features'][0]['#']['session']); $this->send("<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" . "<resource>" . htmlspecialchars($this->_resource) . '</resource></bind></iq>'); return $this->_response($this->wait()); } if (!$this->_ssl && $this->_tls && $this->_canUseSSL() && isset($xml['stream:features'][0]['#']['starttls'])) { $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n"); return $this->_response($this->wait()); } if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl') { $methods = array(); foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value) { $methods[] = $value['#']; } if (in_array('DIGEST-MD5', $methods)) { $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"); } else { if (in_array('PLAIN', $methods) && ($this->_ssl || $this->_negotiation == self::AUTH_PROCEED)) { $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode(chr(0) . $this->_user . '@' . $this->_domain . chr(0) . $this->_pass) . '</auth>'); } else { if (in_array('ANONYMOUS', $methods)) { $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>"); } else { $this->disconnect(); Eden_Jabber_Error::i(Eden_Jabber_Error::NO_AUTH_METHOD)->trigger(); } } } return $this->_response($this->wait()); } $this->disconnect(); Eden_Jabber_Error::i(Eden_Jabber_Error::NO_SASL)->trigger(); break; case self::AUTH_TYPE_CHALLENGE: $this->_negotiation = self::AUTH_CHALLENGE; $decoded = base64_decode($xml['challenge'][0]['#']); $decoded = $this->_parseData($decoded); if (!isset($decoded['digest-uri'])) { $decoded['digest-uri'] = 'xmpp/' . $this->_host; } $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true))); if (isset($decoded['rspauth'])) { $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); } else { if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false) { $decoded['qop'] = 'auth'; } $response = array('username' => $this->_user, 'response' => $this->_encryptPass(array_merge($decoded, array('nc' => '00000001'))), 'charset' => 'utf-8', 'nc' => '00000001', 'qop' => 'auth'); foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key) { if (isset($decoded[$key])) { $response[$key] = $decoded[$key]; } } $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" . base64_encode($this->_implodeData($response)) . '</response>'); } return $this->_response($this->wait()); case self::AUTH_TYPE_FAILURE: $this->_negotiation = self::AUTH_FAILIURE; $this->trigger('failiure'); $this->disconnect(); Eden_Jabber_Error::i(Eden_Jabber_Error::SERVER_FAILED)->trigger(); case self::AUTH_TYPE_PROCEED: $meta = stream_get_meta_data($this->_connection); socket_set_blocking($this->_connection, 1); if (!stream_socket_enable_crypto($this->_connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { Eden_Jabber_Error::i(Eden_Jabber_Error::SERVER_FAILED)->trigger(); } socket_set_blocking($this->_connection, $meta['blocked']); $this->_negotiation = self::AUTH_PROCEED; $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n"); $this->send("<stream:stream to='" . $this->_host . "' xmlns='jabber:client' " . "xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"); return $this->_response($this->wait()); case self::AUTH_TYPE_SUCCESS: $this->send("<stream:stream to='" . $this->_host . "' xmlns='jabber:client' " . "xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n"); $this->_negotiation = self::AUTH_SUCCESS; return $this->_response($this->wait()); } return $this; }