Beispiel #1
0
 /**
  * This handles all the different XML elements
  * @param array $xml
  * @access public
  * @return bool
  */
 public function response($xml)
 {
     if (!is_array($xml) || !count($xml)) {
         return false;
     }
     // did we get multiple elements? do one after another
     // array('message' => ..., 'presence' => ...)
     if (count($xml) > 1) {
         foreach ($xml as $key => $value) {
             $this->response(array($key => $value));
         }
         return;
     } else {
         // or even multiple elements of the same type?
         // array('message' => array(0 => ..., 1 => ...))
         if (count(reset($xml)) > 1) {
             foreach (reset($xml) as $value) {
                 $this->response(array(key($xml) => array(0 => $value)));
             }
             return;
         }
     }
     switch (key($xml)) {
         case 'stream:stream':
             // Connection initialised (or after authentication). Not much to do here...
             if (isset($xml['stream:stream'][0]['#']['stream:features'])) {
                 // we already got all info we need
                 $this->features = $xml['stream:stream'][0]['#'];
             } else {
                 $this->features = $this->listen();
             }
             $second_time = isset($this->session['id']);
             $this->session['id'] = $xml['stream:stream'][0]['@']['id'];
             if ($second_time) {
                 // If we are here for the second time after TLS, we need to continue logging in
                 $this->login();
                 return;
             }
             // go on with authentication?
             if (isset($this->features['stream:features'][0]['#']['bind'])) {
                 return $this->response($this->features);
             }
             break;
         case 'stream:features':
             // Resource binding after successful authentication
             if (isset($this->session['authenticated'])) {
                 // session required?
                 $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']);
                 $this->send("<iq type='set' id='bind_1'>\n\t\t\t\t\t\t\t\t   <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>\n\t\t\t\t\t\t\t    \t <resource>" . Jabber::jspecialchars($this->resource) . "</resource>\n                                   </bind>\n                                 </iq>");
                 return $this->response($this->listen());
             }
             // Let's use TLS if SSL is not enabled and we can actually use it
             if ($this->session['security'] == SECURITY_TLS && isset($xml['stream:features'][0]['#']['starttls'])) {
                 $this->log('Switching to TLS.');
                 $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n");
                 return $this->response($this->listen());
             }
             // 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 this one
                 if (in_array('DIGEST-MD5', $methods)) {
                     $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
                     // we don't want to use this (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.
                 } else {
                     if (in_array('PLAIN', $methods) && (isset($this->session['ssl']) || isset($this->session['tls']))) {
                         $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode(chr(0) . $this->user . '@' . $this->server . chr(0) . $this->password) . "</auth>");
                     } else {
                         if (in_array('ANONYMOUS', $methods)) {
                             $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
                             // not good...
                         } else {
                             $this->log('Error: No authentication method supported.');
                             $this->disconnect();
                             return false;
                         }
                     }
                 }
                 return $this->response($this->listen());
             } else {
                 // ok, this is it. bye.
                 $this->log('Error: Server does not offer SASL authentication.');
                 $this->disconnect();
                 return false;
             }
             break;
         case 'challenge':
             // continue with authentication...a challenge literally -_-
             $decoded = base64_decode($xml['challenge'][0]['#']);
             $decoded = Jabber::parse_data($decoded);
             if (!isset($decoded['digest-uri'])) {
                 $decoded['digest-uri'] = 'xmpp/' . $this->server;
             }
             // 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 {
                 $response = array('username' => $this->user, 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))), 'charset' => 'utf-8', 'nc' => '00000001', 'qop' => 'auth');
                 // the only option we support anyway
                 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(Jabber::implode_data($response)) . "</response>");
             }
             return $this->response($this->listen());
         case 'failure':
             $this->log('Error: Server sent "failure".');
             $this->disconnect();
             return false;
         case '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)) {
                 $this->log('Error: TLS mode change failed.');
                 return false;
             }
             socket_set_blocking($this->connection, $meta['blocked']);
             $this->session['tls'] = true;
             // new stream
             $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
             $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
             return $this->response($this->listen());
         case 'success':
             // Yay, authentication successful.
             $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
             $this->session['authenticated'] = true;
             return $this->response($this->listen());
             // we have to wait for another response
         // we have to wait for another response
         case 'iq':
             // we are not interested in IQs we did not expect
             if (!isset($xml['iq'][0]['@']['id'])) {
                 return false;
             }
             // multiple possibilities here
             switch ($xml['iq'][0]['@']['id']) {
                 case 'bind_1':
                     $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#'];
                     // and (maybe) yet another request to be able to send messages *finally*
                     if ($this->session['sess_required']) {
                         $this->send("<iq to='{$this->server}'\n                                             type='set'\n                                             id='sess_1'>\n                                          <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>\n                                        </iq>");
                         return $this->response($this->listen());
                     }
                     return true;
                 case 'sess_1':
                     return true;
                 case 'reg_1':
                     $this->send("<iq type='set' id='reg_2'>\n                                       <query xmlns='jabber:iq:register'>\n                                         <username>" . Jabber::jspecialchars($this->user) . "</username>\n                                         <password>" . Jabber::jspecialchars($this->password) . "</password>\n                                       </query>\n                                     </iq>");
                     return $this->response($this->listen());
                 case 'reg_2':
                     // registration end
                     if (isset($xml['iq'][0]['#']['error'])) {
                         $this->log('Warning: Registration failed.');
                         return false;
                     }
                     return true;
                 case 'unreg_1':
                     return true;
                 default:
                     $this->log('Notice: Received unexpected IQ.');
                     return false;
             }
             break;
         case 'message':
             // we are only interested in content...
             if (!isset($xml['message'][0]['#']['body'])) {
                 return false;
             }
             $message['body'] = $xml['message'][0]['#']['body'][0]['#'];
             $message['from'] = $xml['message'][0]['@']['from'];
             if (isset($xml['message'][0]['#']['subject'])) {
                 $message['subject'] = $xml['message'][0]['#']['subject'][0]['#'];
             }
             $this->session['messages'][] = $message;
             break;
         default:
             // hm...don't know this response
             $this->log('Notice: Unknown server response (' . key($xml) . ')');
             return false;
     }
 }