1 /*
2  * This file is part of Licq, an instant messaging client for UNIX.
3  * Copyright (C) 2010-2014 Licq developers <licq-dev@googlegroups.com>
4  *
5  * Please refer to the COPYRIGHT file distributed with this source
6  * distribution for the names of the individual contributors.
7  *
8  * Licq is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Licq is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Licq; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "client.h"
24 
25 #include "sessionmanager.h"
26 #include "user.h"
27 #include "vcard.h"
28 
29 #include <gloox/connectionhttpproxy.h>
30 #include <gloox/connectiontcpclient.h>
31 #include <gloox/disco.h>
32 #include <gloox/glooxversion.h>
33 #include <gloox/message.h>
34 #include <gloox/rostermanager.h>
35 #include <gloox/vcardupdate.h>
36 
37 #include <licq/daemon.h>
38 #include <licq/logging/log.h>
39 #include <licq/licqversion.h>
40 #include <licq/thread/mutexlocker.h>
41 
42 #define TRACE_FORMAT "Client::%s: "
43 #define TRACE_ARGS __func__
44 #include "debug.h"
45 
46 using namespace LicqJabber;
47 
48 using Licq::gDaemon;
49 using Licq::gLog;
50 using std::string;
51 
52 static const time_t PING_TIMEOUT = 60;
53 Licq::Mutex Client::myGlooxMutex;
54 
GlooxClient(const gloox::JID & jid,const string & password)55 GlooxClient::GlooxClient(const gloox::JID& jid, const string& password) :
56   gloox::Client(jid, password)
57 {
58   // Empty
59 }
60 
checkStreamVersion(const string & version)61 bool GlooxClient::checkStreamVersion(const string& version)
62 {
63   // do not consider absence of the version as an error
64   if (version.empty())
65     return true;
66 
67   return gloox::Client::checkStreamVersion(version);
68 }
69 
Client(Licq::MainLoop & mainLoop,const Licq::UserId & ownerId,const string & username,const string & password,const string & host,int port,const string & resource,gloox::TLSPolicy tlsPolicy)70 Client::Client(Licq::MainLoop& mainLoop, const Licq::UserId& ownerId,
71     const string& username, const string& password, const string& host,
72     int port, const string& resource, gloox::TLSPolicy tlsPolicy) :
73   myMainLoop(mainLoop),
74   myHandler(ownerId),
75   mySessionManager(NULL),
76   myJid(username + "/" + resource),
77   myClient(myJid, password),
78   myTcpClient(NULL),
79   myRosterManager(myClient.rosterManager()),
80   myVCardManager(&myClient)
81 {
82   myClient.registerStanzaExtension(new gloox::VCardUpdate);
83   myClient.addPresenceExtension(new gloox::VCardUpdate);
84 
85   myClient.registerConnectionListener(this);
86   myRosterManager->registerRosterListener(this, false);
87   myClient.logInstance().registerLogHandler(
88       gloox::LogLevelDebug, gloox::LogAreaAll, this);
89 
90   mySessionManager = new SessionManager(myClient, myHandler);
91   myClient.registerMessageSessionHandler(mySessionManager);
92 
93   myClient.disco()->setIdentity("client", "pc");
94   myClient.disco()->setVersion("Licq", LICQ_VERSION_STRING);
95 
96   myClient.setTls(tlsPolicy);
97 
98   if (!gDaemon.proxyEnabled())
99   {
100     if (!host.empty())
101       myClient.setServer(host);
102     if (port > 0)
103       myClient.setPort(port);
104   }
105   else if (gDaemon.proxyType() == Licq::Daemon::ProxyTypeHttp)
106   {
107     myTcpClient = new gloox::ConnectionTCPClient(
108       myClient.logInstance(), gDaemon.proxyHost(), gDaemon.proxyPort());
109 
110     std::string server = myClient.server();
111     if (!host.empty())
112       server = host;
113 
114     gloox::ConnectionHTTPProxy* httpProxy =
115       new gloox::ConnectionHTTPProxy(
116         &myClient, myTcpClient, myClient.logInstance(),
117         server, (port > 0 ? port : -1));
118 
119     httpProxy->setProxyAuth(gDaemon.proxyLogin(), gDaemon.proxyPasswd());
120 
121     myClient.setConnectionImpl(httpProxy);
122   }
123 }
124 
~Client()125 Client::~Client()
126 {
127   myVCardManager.cancelVCardOperations(this);
128 
129   {
130     // Lock is needed here to protect the calls to gnutls_global_init
131     Licq::MutexLocker locker(myGlooxMutex);
132     myClient.disconnect();
133   }
134 
135   // Avoid memory leak in gloox
136   myClient.removePresenceExtension(gloox::ExtVCardUpdate);
137 
138   delete mySessionManager;
139 }
140 
getSocket()141 int Client::getSocket()
142 {
143   if (myTcpClient != NULL)
144     return myTcpClient->socket();
145   else
146     return static_cast<gloox::ConnectionTCPClient*>(
147       myClient.connectionImpl())->socket();
148 }
149 
rawFileEvent(int,int,int)150 void Client::rawFileEvent(int /*id*/, int /*fd*/, int /*revents*/)
151 {
152   myClient.recv();
153 }
154 
timeoutEvent(int)155 void Client::timeoutEvent(int /*id*/)
156 {
157   myClient.whitespacePing();
158 }
159 
setPassword(const string & password)160 void Client::setPassword(const string& password)
161 {
162   myClient.setPassword(password);
163 }
164 
connect(unsigned status)165 bool Client::connect(unsigned status)
166 {
167   // When having multiple accounts connecting at the same time, gloox can
168   // SIGSEGV: TLSDefault::init returns false (probably thread race), making
169   // ClientBase::getDefaultEncryption delete the TLSDefault instance which
170   // deletes GnuTLSBase. The destructor for GnuTLSBase calls cleanup() which
171   // does gnutls_bye(*m_session, ...). But m_session == NULL...
172   Licq::MutexLocker locker(myGlooxMutex);
173 
174   changeStatus(status, false);
175   myMainLoop.removeCallback(this);
176   if (!myClient.connect(false))
177     return false;
178 
179   // Register with mainloop for socket monitoring and whitespace ping
180   myMainLoop.addRawFile(getSocket(), this);
181   myMainLoop.addTimeout(PING_TIMEOUT*1000, this, 0, false);
182 
183   return true;
184 }
185 
isConnected()186 bool Client::isConnected()
187 {
188   return myClient.authed();
189 }
190 
changeStatus(unsigned status,bool notifyHandler)191 void Client::changeStatus(unsigned status, bool notifyHandler)
192 {
193   // Must reset status to avoid sending the old status message
194   myClient.presence().resetStatus();
195 
196   string msg = myHandler.getStatusMessage(status);
197   myClient.setPresence(statusToPresence(status), 0, msg);
198   if (notifyHandler)
199     myHandler.onChangeStatus(status);
200 }
201 
getVCard(const string & user)202 void Client::getVCard(const string& user)
203 {
204   myVCardManager.fetchVCard(gloox::JID(user), this);
205 }
206 
setOwnerVCard(const UserToVCard & wrapper)207 void Client::setOwnerVCard(const UserToVCard& wrapper)
208 {
209   myPendingPhotoHash = wrapper.pictureSha1();
210 
211   gloox::VCard* card = wrapper.createVCard();
212   myVCardManager.storeVCard(card, this);
213 }
214 
addUser(const string & user,const gloox::StringList & groupNames,bool notify)215 void Client::addUser(const string& user, const gloox::StringList& groupNames,
216                      bool notify)
217 {
218   if (notify)
219     myRosterManager->subscribe(gloox::JID(user), user, groupNames);
220   else
221     myRosterManager->add(gloox::JID(user), user, groupNames);
222 }
223 
changeUserGroups(const string & user,const gloox::StringList & groups)224 void Client::changeUserGroups(
225     const string& user, const gloox::StringList& groups)
226 {
227   gloox::RosterItem* item = myRosterManager->getRosterItem(gloox::JID(user));
228   if (item != NULL)
229   {
230     item->setGroups(groups);
231     myRosterManager->synchronize();
232   }
233 }
234 
removeUser(const string & user)235 void Client::removeUser(const string& user)
236 {
237   myRosterManager->remove(gloox::JID(user));
238 }
239 
renameUser(const string & user,const string & newName)240 void Client::renameUser(const string& user, const string& newName)
241 {
242   gloox::RosterItem* item = myRosterManager->getRosterItem(gloox::JID(user));
243   if (item != NULL)
244   {
245     item->setName(newName);
246     myRosterManager->synchronize();
247   }
248 }
249 
grantAuthorization(const string & user)250 void Client::grantAuthorization(const string& user)
251 {
252   myRosterManager->ackSubscriptionRequest(gloox::JID(user), true);
253 }
254 
refuseAuthorization(const string & user)255 void Client::refuseAuthorization(const string& user)
256 {
257   myRosterManager->ackSubscriptionRequest(gloox::JID(user), false);
258 }
259 
requestAuthorization(const string & user,const string & msg)260 void Client::requestAuthorization(const string& user, const string& msg)
261 {
262   gloox::Subscription subscription(
263       gloox::Subscription::Subscribe, gloox::JID(user), msg);
264   myClient.send(subscription);
265 }
266 
onConnect()267 void Client::onConnect()
268 {
269   gloox::ConnectionBase* conn = myClient.connectionImpl();
270   myHandler.onConnect(conn->localInterface(), conn->localPort(),
271                       presenceToStatus(myClient.presence().subtype()));
272 
273   // Fetch the current vCard from the server
274   myVCardManager.fetchVCard(myClient.jid().bareJID(), this);
275 }
276 
onTLSConnect(const gloox::CertInfo &)277 bool Client::onTLSConnect(const gloox::CertInfo& /*info*/)
278 {
279   return true;
280 }
281 
onDisconnect(gloox::ConnectionError error)282 void Client::onDisconnect(gloox::ConnectionError error)
283 {
284   // Socket no longer open, stop monitoring it
285   myMainLoop.removeCallback(this);
286 
287   bool authError = false;
288 
289   switch (error)
290   {
291     case gloox::ConnNoError:
292       break;
293     case gloox::ConnStreamError:
294       gLog.error("stream error (%d): %s", myClient.streamError(),
295           myClient.streamErrorText().c_str());
296       break;
297     case gloox::ConnStreamVersionError:
298       gLog.error("incoming stream version not supported");
299       break;
300     case gloox::ConnStreamClosed:
301       gLog.error("connection closed by the server");
302       break;
303     case gloox::ConnProxyAuthRequired:
304     case gloox::ConnProxyAuthFailed:
305     case gloox::ConnProxyNoSupportedAuth:
306       gLog.error("proxy authentication failed");
307       authError = true;
308       break;
309     case gloox::ConnIoError:
310       gLog.error("connection I/O error");
311       break;
312     case gloox::ConnParseError:
313       gLog.error("XML parse error");
314       break;
315     case gloox::ConnConnectionRefused:
316       gLog.error("server refused connection");
317       break;
318     case gloox::ConnDnsError:
319       gLog.error("could not resolve server hostname");
320       break;
321     case gloox::ConnOutOfMemory:
322       gLog.error("out of memory");
323       break;
324     case gloox::ConnNoSupportedAuth:
325       gLog.error("no supported authentication mechanism");
326       break;
327     case gloox::ConnTlsFailed:
328       gLog.error("TLS veification failed");
329       break;
330     case gloox::ConnTlsNotAvailable:
331       gLog.error("TLS not available");
332       break;
333     case gloox::ConnCompressionFailed:
334       gLog.error("compression error");
335       break;
336     case gloox::ConnAuthenticationFailed:
337       gLog.error("authentication failed (error %d)", myClient.authError());
338       authError = true;
339       break;
340     case gloox::ConnUserDisconnected:
341       break;
342     case gloox::ConnNotConnected:
343       break;
344   }
345   myHandler.onDisconnect(authError);
346 }
347 
handleItemAdded(const gloox::JID & jid)348 void Client::handleItemAdded(const gloox::JID& jid)
349 {
350   TRACE("%s", jid.full().c_str());
351 
352   gloox::RosterItem* item = myRosterManager->getRosterItem(jid);
353   addRosterItem(*item);
354 }
355 
handleItemSubscribed(const gloox::JID & jid)356 void Client::handleItemSubscribed(const gloox::JID& jid)
357 {
358   TRACE("%s", jid.full().c_str());
359 
360   gLog.info("Now authorized for %s", jid.bare().c_str());
361 }
362 
handleItemRemoved(const gloox::JID & jid)363 void Client::handleItemRemoved(const gloox::JID& jid)
364 {
365   TRACE("%s", jid.full().c_str());
366 
367   myHandler.onUserRemoved(jid.bare());
368 }
369 
handleItemUpdated(const gloox::JID & jid)370 void Client::handleItemUpdated(const gloox::JID& jid)
371 {
372   TRACE("%s", jid.full().c_str());
373 
374   gloox::RosterItem* item = myRosterManager->getRosterItem(jid);
375   addRosterItem(*item);
376 }
377 
handleItemUnsubscribed(const gloox::JID & jid)378 void Client::handleItemUnsubscribed(const gloox::JID& jid)
379 {
380   TRACE("%s", jid.full().c_str());
381 
382   gLog.info("No longer authorized for %s", jid.bare().c_str());
383 }
384 
handleRoster(const gloox::Roster & roster)385 void Client::handleRoster(const gloox::Roster& roster)
386 {
387   TRACE();
388 
389   std::set<string> jidlist;
390   gloox::Roster::const_iterator it;
391 
392   for (it = roster.begin(); it != roster.end(); ++it)
393   {
394     if (addRosterItem(*it->second))
395       jidlist.insert(it->first);
396   }
397 
398   myHandler.onRosterReceived(jidlist);
399 }
400 
handleRosterPresence(const gloox::RosterItem & item,const string &,gloox::Presence::PresenceType presence,const string & msg)401 void Client::handleRosterPresence(const gloox::RosterItem& item,
402                                   const string& /*resource*/,
403                                   gloox::Presence::PresenceType presence,
404                                   const string& msg)
405 {
406   using namespace gloox;
407 
408   TRACE("%s %d",
409 #if GLOOXVERSION < 0x010001
410       item.jid().c_str(),
411 #else
412       item.jidJID().full().c_str(),
413 #endif
414       presence);
415 
416   std::string photoHash;
417 
418   const RosterItem::ResourceMap& resources = item.resources();
419   for (RosterItem::ResourceMap::const_iterator resource = resources.begin();
420        photoHash.empty() && resource != resources.end(); ++resource)
421   {
422     const StanzaExtensionList& extensions = resource->second->extensions();
423     for (StanzaExtensionList::const_iterator it = extensions.begin();
424          photoHash.empty() && it != extensions.end(); ++it)
425     {
426       if ((*it)->extensionType() == gloox::ExtVCardUpdate)
427       {
428         const VCardUpdate* vCardUpdate = dynamic_cast<const VCardUpdate*>(*it);
429         if (vCardUpdate != NULL)
430           photoHash = vCardUpdate->hash();
431       }
432     }
433   }
434 
435   myHandler.onUserStatusChange(
436 #if GLOOXVERSION < 0x010001
437       JID(item.jid()).bare(),
438 #else
439       item.jidJID().bare(),
440 #endif
441       presenceToStatus(presence), msg, photoHash);
442 }
443 
handleSelfPresence(const gloox::RosterItem &,const string &,gloox::Presence::PresenceType,const string &)444 void Client::handleSelfPresence(const gloox::RosterItem& /*item*/,
445                                 const string& /*resource*/,
446                                 gloox::Presence::PresenceType /*presence*/,
447                                 const string& /*msg*/)
448 {
449   TRACE();
450 }
451 
handleSubscriptionRequest(const gloox::JID & jid,const string & msg)452 bool Client::handleSubscriptionRequest(
453     const gloox::JID& jid, const string& msg)
454 {
455   TRACE();
456 
457   myHandler.onUserAuthorizationRequest(jid.bare(), msg);
458   return false; // Ignored by gloox
459 }
460 
handleUnsubscriptionRequest(const gloox::JID &,const string &)461 bool Client::handleUnsubscriptionRequest(
462     const gloox::JID& /*jid*/, const string& /*msg*/)
463 {
464   TRACE();
465 
466   return false; // Ignored by gloox
467 }
468 
handleNonrosterPresence(const gloox::Presence &)469 void Client::handleNonrosterPresence(const gloox::Presence& /*presence*/)
470 {
471   TRACE();
472 }
473 
handleRosterError(const gloox::IQ &)474 void Client::handleRosterError(const gloox::IQ& /*iq*/)
475 {
476   TRACE();
477 }
478 
handleLog(gloox::LogLevel level,gloox::LogArea area,const string & message)479 void Client::handleLog(gloox::LogLevel level, gloox::LogArea area,
480                        const string& message)
481 {
482   const char* areaStr = "Area ???";
483   switch (area)
484   {
485     case gloox::LogAreaClassParser:
486       areaStr = "Parser";
487       break;
488     case gloox::LogAreaClassConnectionTCPBase:
489       areaStr = "TCP base";
490       break;
491     case gloox::LogAreaClassClient:
492       areaStr = "Client";
493       break;
494     case gloox::LogAreaClassClientbase:
495       areaStr = "Client base";
496       break;
497     case gloox::LogAreaClassComponent:
498       areaStr = "Component";
499       break;
500     case gloox::LogAreaClassDns:
501       areaStr = "DNS";
502       break;
503     case gloox::LogAreaClassConnectionHTTPProxy:
504       areaStr = "HTTP proxy";
505       break;
506     case gloox::LogAreaClassConnectionSOCKS5Proxy:
507       areaStr = "SOCKS5 proxy";
508       break;
509     case gloox::LogAreaClassConnectionTCPClient:
510       areaStr = "TCP client";
511       break;
512     case gloox::LogAreaClassConnectionTCPServer:
513       areaStr = "TCP server";
514       break;
515     case gloox::LogAreaClassS5BManager:
516       areaStr = "SOCKS5";
517       break;
518     case gloox::LogAreaClassSOCKS5Bytestream:
519       areaStr = "SOCKS5 bytestream";
520       break;
521     case gloox::LogAreaClassConnectionBOSH:
522       areaStr = "BOSH";
523       break;
524     case gloox::LogAreaClassConnectionTLS:
525       areaStr = "TLS";
526       break;
527 #if GLOOXVERSION >= 0x010001
528     case gloox::LogAreaLinkLocalManager:
529       areaStr = "LinkLocalManager";
530       break;
531 #endif
532     case gloox::LogAreaXmlIncoming:
533       areaStr = "XML in";
534       break;
535     case gloox::LogAreaXmlOutgoing:
536       areaStr = "XML out";
537       break;
538     case gloox::LogAreaUser:
539       areaStr = "User";
540       break;
541     case gloox::LogAreaAllClasses:
542     case gloox::LogAreaAll:
543       areaStr = "All";
544       break;
545   }
546 
547   switch (level)
548   {
549     case gloox::LogLevelDebug:
550       gLog.debug("[%s] %s", areaStr, message.c_str());
551       break;
552     default:
553     case gloox::LogLevelWarning:
554       gLog.warning("[%s] %s", areaStr, message.c_str());
555       break;
556     case gloox::LogLevelError:
557       gLog.error("[%s] %s", areaStr, message.c_str());
558       break;
559   }
560 }
561 
handleVCard(const gloox::JID & jid,const gloox::VCard * vcard)562 void Client::handleVCard(const gloox::JID& jid, const gloox::VCard* vcard)
563 {
564   TRACE();
565 
566   if (vcard != NULL)
567   {
568     VCardToUser user(vcard);
569     myHandler.onUserInfo(jid.bare(), user);
570 
571     if (jid.bare() == myClient.jid().bare())
572       broadcastPhotoHash(user.pictureSha1());
573   }
574 }
575 
handleVCardResult(gloox::VCardHandler::VCardContext context,const gloox::JID & jid,gloox::StanzaError error)576 void Client::handleVCardResult(gloox::VCardHandler::VCardContext context,
577                                const gloox::JID& jid, gloox::StanzaError error)
578 {
579   TRACE();
580 
581   if (error != gloox::StanzaErrorUndefined)
582   {
583     gLog.warning("%s vCard for user %s failed with error %u",
584         context == gloox::VCardHandler::StoreVCard ? "Storing" : "Fetching",
585         jid ? jid.bare().c_str() : myClient.jid().bare().c_str(), error);
586   }
587 
588   if (!jid && context == gloox::VCardHandler::StoreVCard)
589   {
590     if (error == gloox::StanzaErrorUndefined)
591       broadcastPhotoHash(myPendingPhotoHash);
592     else
593       broadcastPhotoHash(boost::none);
594 
595     myPendingPhotoHash = boost::none;
596   }
597 }
598 
broadcastPhotoHash(const boost::optional<std::string> & hash)599 void Client::broadcastPhotoHash(const boost::optional<std::string>& hash)
600 {
601   TRACE();
602 
603   if (hash)
604   {
605     if (hash->empty())
606     {
607       // Bug in gloox: if the hash is empty then VCardUpdate will not generate
608       // a empty photo tag as it should.
609       gloox::VCardUpdate card("dummy");
610 
611       gloox::Tag* tag = card.tag();
612       tag->removeChild("photo");
613       new gloox::Tag(tag, "photo");
614 
615       myClient.addPresenceExtension(new gloox::VCardUpdate(tag));
616       delete tag;
617     }
618     else
619       myClient.addPresenceExtension(new gloox::VCardUpdate(*hash));
620   }
621   else
622   {
623     myClient.addPresenceExtension(new gloox::VCardUpdate);
624   }
625 
626   myClient.setPresence();
627 }
628 
addRosterItem(const gloox::RosterItem & item)629 bool Client::addRosterItem(const gloox::RosterItem& item)
630 {
631   // Filter out the states where the contact should not be on our list
632   if (item.subscription() == gloox::S10nNoneIn
633       || item.subscription() == gloox::S10nFrom)
634     return false;
635 
636   // States where we have sent a subscription request that hasn't be answered
637   bool awaitAuth = item.subscription() == gloox::S10nNoneOut
638       || item.subscription() == gloox::S10nNoneOutIn
639       || item.subscription() == gloox::S10nFromOut;
640 
641   myHandler.onUserAdded(
642 #if GLOOXVERSION < 0x010001
643       item.jid(),
644 #else
645       item.jidJID().bare(),
646 #endif
647       item.name(), item.groups(), awaitAuth);
648   return true;
649 }
650 
presenceToStatus(gloox::Presence::PresenceType presence)651 unsigned Client::presenceToStatus(gloox::Presence::PresenceType presence)
652 {
653   switch (presence)
654   {
655     case gloox::Presence::Invalid:
656     case gloox::Presence::Probe:
657     case gloox::Presence::Error:
658     case gloox::Presence::Unavailable:
659       return User::OfflineStatus;
660 
661     case gloox::Presence::Chat:
662       return User::OnlineStatus | User::FreeForChatStatus;
663 
664     case gloox::Presence::Away:
665       return User::OnlineStatus | User::AwayStatus;
666 
667     case gloox::Presence::DND:
668       return User::OnlineStatus | User::DoNotDisturbStatus;
669 
670     case gloox::Presence::XA:
671       return User::OnlineStatus | User::NotAvailableStatus;
672 
673     case gloox::Presence::Available:
674     default:
675       return User::OnlineStatus;
676   }
677 }
678 
statusToPresence(unsigned status)679 gloox::Presence::PresenceType Client::statusToPresence(unsigned status)
680 {
681   if (status == User::OfflineStatus)
682     return gloox::Presence::Unavailable;
683 
684   if (status & User::AwayStatus)
685     return gloox::Presence::Away;
686 
687   if (status & User::NotAvailableStatus)
688     return gloox::Presence::XA;
689 
690   if (status & (User::OccupiedStatus | User::DoNotDisturbStatus))
691     return gloox::Presence::DND;
692 
693   if (status & User::FreeForChatStatus)
694     return gloox::Presence::Chat;
695 
696   return gloox::Presence::Available;
697 }
698