Main Page   Namespace List   Class Hierarchy   Compound List   File List   Compound Members  

jabberoo-session.cpp

00001 /* jabberoo-session.cc
00002  * Jabber Session management
00003  *
00004  * Original Code Copyright (C) 1999-2001 Dave Smith (dave@jabber.org)
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Lesser General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 2.1 of the License, or (at your option) any later version.
00010  * 
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  * 
00016  * You should have received a copy of the GNU Lesser General Public
00017  * License along with this library; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00019  *
00020  * Contributor(s): Julian Missig (IBM)
00021  *
00022  * This Original Code has been modified by IBM Corporation. Modifications 
00023  * made by IBM described herein are Copyright (c) International Business 
00024  * Machines Corporation, 2002.
00025  *
00026  * Date             Modified by     Description of modification
00027  * 01/20/2002       IBM Corp.       Updated to libjudo 1.1.1
00028  * 2002-03-05       IBM Corp.       Updated to libjudo 1.1.5
00029  * 2002-03-09       IBM Corp.       Catch libjudo's ParserError
00030  */
00031 
00032 
00033 #include "session.hh"
00034 #include "XPath.h"
00035 #include <sigc++/object_slot.h>
00036 #include <sha.h>
00037 using namespace SigC;
00038 namespace jabberoo {
00039 // ---------------------------------------------------------
00040 // Initializers
00041 // ---------------------------------------------------------
00042 Session::Session()
00043      : ElementStream(this),
00044        _ID(0),
00045        _ConnState(csNotConnected),
00046        _StreamStart(false),
00047        _Roster(*this),
00048        _DDB(*this),
00049        _PDB(*this)
00050 {}
00051 
00052 Session::~Session()
00053 {
00054     // If connected, transmit closing sequence of XML stream...
00055     if (_ConnState != csNotConnected)
00056     {
00057         std::cerr << "Disconnecting in Jabberoo-Session" << std::endl;
00058         disconnect();
00059     }
00060 
00061     for (XPCBMap::iterator it = _incoming_XPaths.begin();
00062          it != _incoming_XPaths.end(); ++it)
00063     {
00064         delete it->first;
00065     }
00066     for (XPCBMap::iterator it = _outgoing_XPaths.begin();
00067          it != _outgoing_XPaths.end(); ++it)
00068     {
00069         delete it->first;
00070     }
00071 }
00072 
00073 // ---------------------------------------------------------
00074 // Helper ops
00075 // ---------------------------------------------------------
00076 Session& Session::operator<<(const Packet& p)
00077 {
00078     const judo::Element& elem(p.getBaseElement());
00079     // Fire callbacks for anyone that cares
00080     for (XPQueryList::iterator it = _outgoing_queries.begin(); 
00081          it != _outgoing_queries.end(); ++it)
00082     {
00083         if ( (*it)->check(elem) )
00084         {
00085             _outgoing_XPaths[(*it)](elem);
00086         }
00087     }
00088 
00089      if (elem.getName() == "presence")
00090      {
00091           // Send out evtMyPresence if it seems appropriate
00092           Presence pres = Presence(elem);
00093           if (pres.getFrom().empty() && pres.getTo().empty())
00094                evtMyPresence(pres);
00095      }
00096      evtTransmitPacket(p); 
00097      evtTransmitXML(p.toString().c_str()); 
00098      return *this;
00099 }
00100 
00101 // ---------------------------------------------------------
00102 // Connection setup/teardown ops (inc. authentication)
00103 // ---------------------------------------------------------
00104 void Session::connect(const std::string& server, AuthType atype, 
00105                       const std::string& username, const std::string& resource, const std::string& password,
00106                       bool newuser,
00107                       bool should_auth)
00108 {
00109      // If already connected, return
00110      if (_ConnState == csConnected)
00111           return;
00112 
00113      // Store properties for future use
00114      _AuthType = atype;
00115      _ServerID = server;
00116      _Username = username;
00117      _Resource = resource;
00118      _Password = password;
00119      _Authenticate = should_auth;
00120 
00121       // If we haven't connected at all, send the stream header
00122      if (_ConnState == csNotConnected)
00123      {
00124           this->reset(); // Reset ElementStream for new connection
00125           // Transmit opening sequence to establish the XML stream..    
00126           *this << "<stream:stream to='" << server.c_str()
00127                 << "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";  
00128           // Set state properly..
00129           if (!newuser)
00130                _ConnState = csAuthReq;
00131           else
00132                _ConnState = csCreateUser;
00133      }
00134      // Otherwise, attempt to authenicate again     
00135      else
00136      {
00137           if (newuser)
00138                _ConnState = csCreateUser;
00139           if (should_auth)
00140                authenticate();
00141           else
00142                evtConnected(*_StreamElement);
00143      }
00144 }
00145 
00146 bool Session::disconnect()
00147 {
00148      bool success = false;
00149      // only disconnect if we have received the starting stream tag to prevent the tagstream from throwing
00150      // an exception
00151      if ((_ConnState != csNotConnected) && _StreamStart)
00152      {
00153           *this << "</stream:stream>";
00154           success = true;
00155      }
00156      _ConnState = csNotConnected;
00157      _StreamStart = false;
00158 
00159      return success;
00160 }
00161 
00162 // ---------------------------------------------------------
00163 // Accessors
00164 // ---------------------------------------------------------
00165 const Roster& Session::roster() const
00166 {
00167      return _Roster;
00168 }
00169 
00170 Roster& Session::roster()
00171 {
00172      return _Roster;
00173 }
00174 
00175 const DiscoDB& Session::discoDB() const
00176 {
00177     return _DDB;
00178 }
00179 
00180 DiscoDB& Session::discoDB()
00181 {
00182     return _DDB;
00183 }
00184 
00185 const PresenceDB& Session::presenceDB() const
00186 {
00187      return _PDB;
00188 }
00189 
00190 PresenceDB&  Session::presenceDB()
00191 {
00192      return _PDB;
00193 }
00194 
00195 Session::AuthType Session::getAuthType() const
00196 {
00197      return _AuthType;
00198 }
00199 
00200 const std::string& Session::getUserName() const
00201 {
00202         return _Username;
00203 }
00204 
00205 // ---------------------------------------------------------
00206 // Id/auth ops
00207 // ---------------------------------------------------------
00208 std::string Session::getNextID()
00209 {
00210      char buf[8];
00211      _snprintf(buf, 8, "j%ld", _ID++);
00212      return std::string(buf);
00213 }
00214 
00215 std::string Session::getDigest()
00216 {
00217      std::string basic = _SessionID + _Password;
00218      return shahash(basic.c_str()); 
00219 }
00220 
00221 // ---------------------------------------------------------
00222 // Misc ops
00223 // ---------------------------------------------------------
00224 void Session::push(const char* data, int datasz)
00225 {
00226      evtRecvXML(data);
00227      try {
00228           ElementStream::push(data, datasz);
00229      } catch (const ElementStream::exception::ParserError& error) {
00230           // We had a parser error, so nothing's going to be happy
00231           evtXMLParserError(error.getCode(), error.getMessage());
00232 
00233           // Prevent loops :)
00234           _ConnState = csNotConnected;
00235           _StreamStart = false;
00236 
00237           disconnect();
00238      }
00239 }
00240 
00241 void Session::registerIQ(const std::string& id, ElementCallbackFunc f)
00242 {
00243      _Callbacks.insert(std::make_pair(id, f));
00244 }
00245 
00246 judo::XPath::Query* Session::registerXPath(const std::string& query, 
00247         ElementCallbackFunc f, bool incoming)
00248 {
00249     judo::XPath::Query* xpq = new judo::XPath::Query(query);
00250     if (incoming)
00251     {
00252         _incoming_XPaths.insert(XPCBMap::value_type(xpq, f));
00253         _incoming_queries.push_front(xpq);
00254     }
00255     else
00256     {
00257         _outgoing_XPaths.insert(XPCBMap::value_type(xpq, f));
00258         _outgoing_queries.push_front(xpq);
00259     }
00260 
00261     return xpq;
00262 }
00263 
00264 void Session::unregisterXPath(judo::XPath::Query* id, bool incoming)
00265 {
00266     if (incoming)
00267     {
00268         _incoming_XPaths.erase(id);
00269         _incoming_queries.erase(std::find(_incoming_queries.begin(),
00270             _incoming_queries.end(), id));
00271     }
00272     else
00273     {
00274         _outgoing_XPaths.erase(id);
00275         _outgoing_queries.erase(std::find(_outgoing_queries.begin(),
00276             _outgoing_queries.end(), id));
00277     }
00278 
00279     delete id;
00280 }
00281 
00282 void Session::queryNamespace(const std::string& nspace, ElementCallbackFunc f, const std::string& to)
00283 {
00284      // Get a unique ID
00285      std::string id = getNextID();
00286 
00287      // Construct a query for this namespace
00288      judo::Element iq("iq");
00289      iq.putAttrib("type", "get");
00290      iq.putAttrib("id", id);
00291      if (!to.empty())
00292           iq.putAttrib("to", to);
00293      iq.addElement("query")->putAttrib("xmlns", nspace);
00294 
00295      // Register a callback for this IQ
00296      _Callbacks.insert(std::make_pair(id, f));
00297 
00298      // Transmit the IQ
00299      *this << iq.toString().c_str();
00300 }
00301 
00302 // ---------------------------------------------------------
00303 // INTERNAL
00304 //
00305 // Authentication
00306 // ---------------------------------------------------------
00307 void Session::authenticate()
00308 {
00309      // Request auth types allowed
00310      std::string id = getNextID();
00311      judo::Element iq("iq");
00312      iq.putAttrib("type", "get");
00313      iq.putAttrib("id", id);
00314      judo::Element* query = iq.addElement("query");
00315      query->putAttrib("xmlns", "jabber:iq:auth");
00316      query->addElement("username", _Username, false);
00317      *this << iq.toString().c_str();
00318 
00319      // If they explicitly set auth, that's the type we use
00320      // otherwise detect the auth types allowed
00321      if (_AuthType != Session::atAutoAuth)
00322           sendLogin(_AuthType, NULL);
00323      else
00324                  registerIQ(id, SigC::slot(*this, &Session::OnAuthTypeReceived));
00325 }
00326 
00327 void Session::OnAuthTypeReceived(const judo::Element& t)
00328 {
00329      // Grab the supported auth types and login using appropriate one
00330      Session::AuthType atype = Session::atPlaintextAuth;
00331      const judo::Element* query = NULL;
00332      if (!t.empty())
00333      {
00334           query = t.findElement("query");
00335           if (query != NULL)
00336           {
00337                // Plaintext auth
00338                const judo::Element* password = query->findElement("password");
00339                if (password != NULL)
00340                     atype = Session::atPlaintextAuth;
00341                // Digest auth
00342                const judo::Element* digest = query->findElement("digest");
00343                if (digest != NULL)
00344                     atype = Session::atDigestAuth;
00345                // Zero-Knowledge
00346                const judo::Element* token = query->findElement("token");
00347                const judo::Element* sequence = query->findElement("sequence");
00348                if (token != NULL && sequence != NULL)
00349                     atype = Session::at0kAuth;  // Add 0k support
00350           }
00351      }
00352      sendLogin(atype, query);
00353 }
00354 
00355 void Session::sendLogin(Session::AuthType atype, const judo::Element* squery)
00356 {
00357      // Construct the login
00358      std::string id = getNextID();
00359 
00360      // Create the IQ
00361      judo::Element iq("iq");
00362      iq.putAttrib("type", "set");
00363      iq.putAttrib("id", id);
00364 
00365      // Setup the query
00366      judo::Element* query = iq.addElement("query");
00367 
00368      // Add basic tags
00369      query->addElement("username", _Username);
00370      query->addElement("resource", _Resource);
00371      
00372      // Handle user creation
00373      if (_ConnState == csCreateUser)
00374      {
00375           query->putAttrib("xmlns", "jabber:iq:register");
00376           query->addElement("password", _Password);
00377           // Register the create user callback
00378           registerIQ(id, slot(*this, &Session::IQHandler_CreateUser)); 
00379 
00380      }
00381      else if (_ConnState == csAuthReq)
00382      {
00383           query->putAttrib("xmlns", "jabber:iq:auth");
00384           // Include necessary passphrase/digest
00385           switch (atype)
00386           {
00387           case atDigestAuth: 
00388                query->addElement("digest", getDigest());
00389                break;
00390           case atPlaintextAuth:
00391                query->addElement("password", _Password);
00392                break;
00393           case at0kAuth:
00394                std::string hashA = shahash(_Password.c_str());
00395                std::string token = squery->getChildCData("token");
00396                int seq = atoi(squery->getChildCData("sequence").c_str());
00397                std::string hash = shahash(std::string(hashA + token).c_str());
00398                for (int i = 0; i < seq; i++)
00399                     hash = shahash(hash.c_str());
00400                query->addElement("hash", hash);
00401                break;
00402           }
00403           // Register the auth callback
00404           registerIQ(id, slot(*this, &Session::IQHandler_Auth));
00405      }
00406 
00407      // Transmit the buffer
00408      *this << iq.toString().c_str();
00409 
00410      // Adjust the connection state
00411      _ConnState = csAwaitingAuth;
00412 }
00413 
00414 // ---------------------------------------------------------
00415 // INTERNAL
00416 //
00417 // ElementStreamEventListener handlers
00418 // ---------------------------------------------------------
00419 void Session::onDocumentStart(judo::Element* t)
00420 {
00421      // Retrieve the SID from the stream header
00422      _SessionID = t->getAttrib("id");
00423      // Save stream header
00424      _StreamElement = t;
00425      _StreamStart = true;
00426      // Authenticate
00427      if (_Authenticate)
00428           authenticate();
00429      else
00430           evtConnected(*_StreamElement);
00431 }
00432 
00433 void Session::onElement(judo::Element* t) 
00434 {
00435      judo::Element& tref = *t;
00436      // See if a xpath handles this
00437      for (XPQueryList::iterator it = _incoming_queries.begin(); 
00438           it != _incoming_queries.end(); ++it)
00439      {
00440          if ( (*it)->check(tref) )
00441          {
00442              _incoming_XPaths[(*it)](tref);
00443          }
00444      }
00445 
00446      // Determine what kind of packet we recv'd and call the 
00447      // appropriate handler
00448      if (tref.getName() == "message")
00449           handleMessage(tref);
00450      else if (tref.getName() == "presence")
00451           handlePresence(tref);
00452      else if (tref.getName() == "iq")
00453           handleIQ(tref);
00454      else
00455           evtUnknownPacket(tref);
00456      delete t;
00457 }
00458 
00459 void Session::onCDATA(judo::CDATA* c)
00460 {
00461      // If we ever want to deal with CDATA we receive
00462      // possibly message sizes and things like that
00463      // then we do it here.
00464      delete c;
00465 }
00466 
00467 void Session::onDocumentEnd() 
00468 {
00469      // Reset roster
00470      _Roster.reset();
00471      // Reset the presenceDB
00472      _PDB.clear();
00473 
00474      // Clear connection state
00475      _ConnState = csNotConnected;
00476      _StreamStart = false;
00477 
00478       // Transmit a closing stream tag...
00479      *this << "</stream:stream>";
00480 
00481      // Fire session Disconnect handler
00482      evtDisconnected();
00483 }
00484 
00485 // ---------------------------------------------------------
00486 // INTERNAL
00487 //
00488 // Routing handlers (see also: IQ handlers)
00489 // ---------------------------------------------------------
00490 void Session::handleMessage(judo::Element& t)
00491 {
00492      // Call the signal handler
00493      evtMessage(Message(t));
00494 }
00495 
00496 void Session::handlePresence(judo::Element& t)
00497 {
00498      Presence p(t);
00499 
00500      // If no sender, it's about my presence
00501      if (p.getFrom() == "")
00502      {
00503           evtMyPresence(p);
00504      }
00505 
00506      // If it's a subscription request, fire proper event
00507      else if ((p.getType() == Presence::ptSubRequest) ||
00508          (p.getType() == Presence::ptUnsubRequest))
00509           evtPresenceRequest(p);
00510 
00511      // Otherwise, examine presence more closely
00512      else
00513      {
00514           // Determine the previous status for this jid
00515           Presence::Type prev_type;
00516           try {
00517                prev_type = _PDB.find(p.getFrom())->getType();
00518           } catch (PresenceDB::XCP_InvalidJID) {
00519                prev_type = Presence::ptUnavailable;
00520           }
00521 
00522           // Insert the packet into the presence db
00523           _PDB.insert(p);
00524 
00525           // Check for roster update
00526           if (_Roster.containsJID(p.getFrom()))
00527                _Roster.update(p, prev_type);           
00528           // pass handling to standard event
00529           evtPresence(p, prev_type);
00530      }
00531 }
00532 
00533 void Session::handleIQ(judo::Element& t)
00534 {
00535      // Check for callback w/ ID
00536      typedef std::multimap<std::string, ElementCallbackFunc>::iterator CIT;
00537      std::pair<CIT, CIT> cb = _Callbacks.equal_range(t.getAttrib("id"));
00538      if (cb.first != cb.second)
00539      {
00540           // Iterate across the range and fire callbacks
00541           for (CIT it = cb.first; it != cb.second; )
00542           {
00543                // Adding a callback with an ID one greater than the ID of this tag
00544                // screws up the iterator so we need to make sure the id's are actually
00545                // equal.  Is there a way to prevent the iterator from being broken?
00546                if (it->first == t.getAttrib("id")) {
00547                     // Fire the requested function
00548                     ElementCallbackFunc& f = it->second;
00549                     f(t);
00550                     // erasing it then incrementing it doesn't work right so increment first
00551                     // then delete
00552                     CIT nit = it++;
00553                     _Callbacks.erase(nit);
00554                } else
00555                     it++;
00556           }
00557      }
00558      // Proceed with xmlns examination
00559      else
00560      {
00561           judo::Element* q = t.findElement("query");
00562 
00563           // Catch the odd case of an IQ not having a <query> tag..
00564           if (q == NULL) return;
00565 
00566           // jabber:iq:roster 
00567           if (q->cmpAttrib("xmlns", "jabber:iq:roster"))
00568           {
00569                _Roster.update(*q);
00570                evtOnRoster();
00571           }
00572 
00573           // jabber:iq:version
00574           if (t.cmpAttrib("type", "get") && q->cmpAttrib("xmlns", "jabber:iq:version"))
00575           {
00576                std::string name, ver, os;
00577                evtOnVersion(name, ver, os);
00578                ver += " (powered by jabberoo 1.1.2)";
00579                Packet iq("iq");
00580                iq.getBaseElement().putAttrib("type", "result");
00581                iq.setTo(t.getAttrib("from"));
00582                iq.setID(t.getAttrib("id"));
00583                judo::Element* query = iq.getBaseElement().addElement("query");
00584                query->putAttrib("xmlns", "jabber:iq:version");
00585                query->addElement("name", name);
00586                query->addElement("version", ver);
00587                query->addElement("os", os);
00588                *this << iq;
00589           }
00590           // jabber:iq:last
00591           else if (t.cmpAttrib("type", "get") && q->cmpAttrib("xmlns", "jabber:iq:last"))
00592           {
00593                std::string seconds;
00594                evtOnLast(seconds);
00595                Packet iq("iq");
00596                iq.getBaseElement().putAttrib("type", "result");
00597                iq.setTo(t.getAttrib("from"));
00598                iq.setID(t.getAttrib("id"));
00599                judo::Element* query = iq.getBaseElement().addElement("query");
00600                query->putAttrib("xmlns", "jabber:iq:last");
00601                query->putAttrib("seconds", seconds);
00602                *this << iq;
00603           }
00604           // jabber:iq:time
00605           else if (t.cmpAttrib("type", "get") && q->cmpAttrib("xmlns", "jabber:iq:time"))
00606           {
00607 #ifndef WIN32
00608                Packet iq("iq");
00609                iq.getBaseElement().putAttrib("type", "result");
00610                iq.setTo(t.getAttrib("from"));
00611                iq.setID(t.getAttrib("id"));
00612                judo::Element* query = iq.getBaseElement().addElement("query");
00613                query->putAttrib("xmlns", "jabber:iq:time");
00614                query->addElement("utc", jutil::getTimeStamp());
00615 
00616                struct tm *loctime;
00617                time_t curtime;
00618                char timestr[1024];
00619 
00620                curtime = time(0);
00621                loctime = localtime(&curtime);
00622 
00623                // locale-dependent
00624                //strftime(timestr, 1024, "%d %b %Y %H:%M:%S", loctime);
00625                strftime(timestr, 1024, "%c", loctime);
00626                std::string local= std::string(timestr); // Local time std::string
00627                strftime(timestr, 1024, "%Z", loctime);
00628                std::string tz = std::string(timestr); // Time zone
00629                if (local.find(tz)!=std::string::npos) // Don't duplicate timezone
00630                        local.erase(local.find(tz));
00631                evtOnTime(local, tz);
00632 
00633                query->addElement("display", local);
00634                query->addElement("tz", tz);
00635                *this << iq;
00636 #endif
00637           }
00638           // Otherwise pass handling to standard event
00639           else
00640                evtIQ(t);
00641      }
00642 }
00643 
00644 
00645 // ---------------------------------------------------------
00646 // INTERNAL
00647 //
00648 // Internal IQ handlers
00649 // ---------------------------------------------------------
00650 void Session::IQHandler_Auth(const judo::Element& t)
00651 {
00652      // If successful, update connection state
00653      // and call _Events.OnConnected
00654      if (t.cmpAttrib("type", "result"))
00655      {
00656           _ConnState = Session::csConnected;           // Update connection state
00657           evtConnected(*_StreamElement);               // Notify libuser that connection is available
00658           if (_Authenticate)                           // If they wanted authentication, then
00659                _Roster.fetch();                        // Request roster
00660           delete _StreamElement;                       // Release stream header tag
00661      }
00662      else
00663      {
00664           // Handle errors
00665           const judo::Element* error = t.findElement("error");
00666           if (error != NULL)
00667           {
00668                _ConnState = Session::csAuthReq;
00669                evtAuthError(atoi(error->getAttrib("code").c_str()), error->getCDATA().c_str());
00670           }
00671           else
00672                evtAuthError(-1, t.toString().c_str());
00673      }
00674 }
00675 
00676 void Session::IQHandler_CreateUser(const judo::Element& t)
00677 {
00678      // If successful, update connection state and
00679      // attempt to authenticate
00680      _ConnState = Session::csAuthReq;
00681      if (t.cmpAttrib("type", "result"))
00682           authenticate();
00683      else
00684      {
00685           const judo::Element* error = t.findElement("error");
00686           if (error != NULL)
00687           {
00688                evtAuthError(atoi(error->getAttrib("code").c_str()), error->getCDATA().c_str());
00689           }
00690      }
00691 }
00692 
00693 } // namespace jabberoo 

Generated on Thu Jul 24 13:31:50 2003 for jabberoo by doxygen1.3-rc3