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