1 /*
2 * %kadu copyright begin%
3 * Copyright 2011 Piotr Galiszewski (piotr.galiszewski@kadu.im)
4 * Copyright 2011, 2012 Wojciech Treter (juzefwt@gmail.com)
5 * Copyright 2011, 2012, 2013 Bartosz Brachaczek (b.brachaczek@gmail.com)
6 * Copyright 2011, 2012, 2013, 2014 Rafał Przemysław Malinowski (rafal.przemyslaw.malinowski@gmail.com)
7 * %kadu copyright end%
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "jabber-protocol.h"
24
25 #include "actions/jabber-actions.h"
26 #include "actions/jabber-protocol-menu-manager.h"
27 #include "qxmpp/jabber-register-extension.h"
28 #include "qxmpp/jabber-roster-extension.h"
29 #include "qxmpp/jabber-ssl-handler.h"
30 #include "services/jabber-change-password-service.h"
31 #include "services/jabber-chat-service.h"
32 #include "services/jabber-chat-state-service.h"
33 #include "services/jabber-error-service.h"
34 #include "services/jabber-file-transfer-service.h"
35 #include "services/jabber-presence-service.h"
36 #include "services/jabber-register-account-service.h"
37 #include "services/jabber-resource-service.h"
38 #include "services/jabber-room-chat-service.h"
39 #include "services/jabber-roster-service.h"
40 #include "services/jabber-stream-debug-service.h"
41 #include "services/jabber-subscription-service.h"
42 #include "services/jabber-vcard-service.h"
43 #include "gtalk-protocol-factory.h"
44 #include "jabber-id-validator.h"
45 #include "jabber-protocol-factory.h"
46 #include "jabber-url-handler.h"
47 #include "jid.h"
48
49 #include "avatars/avatar-manager.h"
50 #include "buddies/buddy-manager.h"
51 #include "buddies/group-manager.h"
52 #include "chat/chat-manager.h"
53 #include "contacts/contact-manager.h"
54 #include "core/version-service.h"
55 #include "gui/windows/message-dialog.h"
56 #include "misc/memory.h"
57 #include "os/generic/system-info.h"
58 #include "plugin/plugin-injected-factory.h"
59 #include "protocols/protocols-manager.h"
60 #include "status/status-type-manager.h"
61 #include "url-handlers/url-handler-manager.h"
62
63 #include <QtCore/QCoreApplication>
64 #include <QtNetwork/QAbstractSocket>
65 #include <QtNetwork/QNetworkProxy>
66 #include <QtNetwork/QSslSocket>
67 #include <qxmpp/QXmppClient.h>
68 #include <qxmpp/QXmppMucManager.h>
69 #include <qxmpp/QXmppRosterManager.h>
70 #include <qxmpp/QXmppTransferManager.h>
71 #include <qxmpp/QXmppVersionManager.h>
72 #include <qxmpp/QXmppVCardManager.h>
73
JabberProtocol(Account account,ProtocolFactory * factory)74 JabberProtocol::JabberProtocol(Account account, ProtocolFactory *factory) :
75 Protocol{account, factory},
76 m_contactsListReadOnly(false)
77 {
78 }
79
~JabberProtocol()80 JabberProtocol::~JabberProtocol()
81 {
82 logout();
83 }
84
setSystemInfo(SystemInfo * systemInfo)85 void JabberProtocol::setSystemInfo(SystemInfo *systemInfo)
86 {
87 m_systemInfo = systemInfo;
88 }
89
setVersionService(VersionService * versionService)90 void JabberProtocol::setVersionService(VersionService *versionService)
91 {
92 m_versionService = versionService;
93 }
94
init()95 void JabberProtocol::init()
96 {
97 auto details = dynamic_cast<JabberAccountDetails *>(account().details());
98 connect(details, SIGNAL(priorityChanged()), this, SLOT(updatePresence()), Qt::UniqueConnection);
99
100 // TODO: remove after 01.05.2015
101 if (account().id().endsWith(QStringLiteral("@chat.facebook.com")))
102 setContactsListReadOnly(true);
103
104 m_presenceService = pluginInjectedFactory()->makeInjected<JabberPresenceService>(this);
105 m_errorService = new JabberErrorService{this};
106
107 m_client = new QXmppClient{this};
108 connect(m_client, SIGNAL(connected()), this, SLOT(connectedToServer()));
109 connect(m_client, SIGNAL(disconnected()), this, SLOT(disconenctedFromServer()));
110 connect(m_client, SIGNAL(error(QXmppClient::Error)), this, SLOT(error(QXmppClient::Error)));
111 connect(m_client, SIGNAL(presenceReceived(QXmppPresence)), this, SLOT(presenceReceived(QXmppPresence)));
112
113 pluginInjectedFactory()->makeInjected<JabberSslHandler>(m_client,
114 [&](){
115 emit stateMachineSslErrorResolved();
116 },
117 [&](){
118 emit stateMachineSslErrorNotResolved();
119 }
120 );
121
122 m_registerExtension = std::make_unique<JabberRegisterExtension>();
123 m_rosterExtension = std::make_unique<JabberRosterExtension>();
124 m_mucManager = std::make_unique<QXmppMucManager>();
125 m_transferManager = std::make_unique<QXmppTransferManager>();
126
127 m_rosterExtension->setJabberErrorService(m_errorService);
128
129 m_client->addExtension(m_registerExtension.get());
130 m_client->insertExtension(0, m_rosterExtension.get());
131 m_client->addExtension(m_mucManager.get());
132 m_client->addExtension(m_transferManager.get());
133
134 m_changePasswordService = new JabberChangePasswordService{m_registerExtension.get(), this};
135 m_changePasswordService->setErrorService(m_errorService);
136
137 m_resourceService = pluginInjectedFactory()->makeInjected<JabberResourceService>(this);
138
139 m_roomChatService = pluginInjectedFactory()->makeInjected<JabberRoomChatService>(m_client, m_mucManager.get(), account(), this);
140
141 auto chatStateService = pluginInjectedFactory()->makeInjected<JabberChatStateService>(m_client, account(), this);
142 chatStateService->setResourceService(m_resourceService);
143
144 m_avatarService = pluginInjectedFactory()->makeInjected<JabberAvatarService>(m_client, account(), this);
145
146 auto chatService = pluginInjectedFactory()->makeInjected<JabberChatService>(m_client, account(), this);
147 chatService->setChatStateService(chatStateService);
148 chatService->setResourceService(m_resourceService);
149 chatService->setRoomChatService(m_roomChatService);
150
151 m_contactPersonalInfoService = pluginInjectedFactory()->makeInjected<JabberContactPersonalInfoService>(account(), this);
152 m_personalInfoService = pluginInjectedFactory()->makeInjected<JabberPersonalInfoService>(account(), this);
153 m_streamDebugService = new JabberStreamDebugService{m_client, this};
154
155 m_fileTransferService = pluginInjectedFactory()->makeInjected<JabberFileTransferService>(m_transferManager.get(), account(), this);
156 m_fileTransferService->setResourceService(m_resourceService);
157
158 m_vcardService = new JabberVCardService{&m_client->vCardManager(), this};
159
160 m_avatarService->setVCardService(m_vcardService);
161 m_contactPersonalInfoService->setVCardService(m_vcardService);
162 m_personalInfoService->setVCardService(m_vcardService);
163
164 auto contacts = contactManager()->contacts(account(), ContactManager::ExcludeAnonymous);
165 auto rosterService = pluginInjectedFactory()->makeInjected<JabberRosterService>(&m_client->rosterManager(), m_rosterExtension.get(), contacts, this);
166
167 connect(rosterService, SIGNAL(rosterReady()), this, SLOT(rosterReady()));
168
169 setChatService(chatService);
170 setChatStateService(chatStateService);
171 setRosterService(rosterService);
172
173 m_subscriptionService = pluginInjectedFactory()->makeInjected<JabberSubscriptionService>(&m_client->rosterManager(), this);
174 }
175
setContactsListReadOnly(bool contactsListReadOnly)176 void JabberProtocol::setContactsListReadOnly(bool contactsListReadOnly)
177 {
178 m_contactsListReadOnly = contactsListReadOnly;
179 }
180
rosterReady()181 void JabberProtocol::rosterReady()
182 {
183 /* Since we are online now, set initial presence. Don't do this
184 * before the roster request or we will receive presence
185 * information before we have updated our roster with actual
186 * contacts from the server! (Iris won't forward presence
187 * information in that case either). */
188 sendStatusToServer();
189 }
190
191 /*
192 * login procedure
193 *
194 * After calling login method we set up JabberClient that must call connectedToServer in order to inform
195 * us that connection was established. Then we can tell this to state machine in Protocol class
196 */
login()197 void JabberProtocol::login()
198 {
199 auto jabberAccountDetails = dynamic_cast<JabberAccountDetails *>(account().details());
200 if (!jabberAccountDetails)
201 {
202 connectionClosed();
203 return;
204 }
205
206 if (jabberAccountDetails->publishSystemInfo())
207 {
208 m_client->versionManager().setClientName("Kadu");
209 m_client->versionManager().setClientVersion(m_versionService->version());
210 m_client->versionManager().setClientOs(m_systemInfo->osFullName());
211 }
212 else
213 {
214 m_client->versionManager().setClientName(QString{});
215 m_client->versionManager().setClientVersion(QString{});
216 m_client->versionManager().setClientOs(QString{});
217 }
218
219 auto details = dynamic_cast<JabberAccountDetails *>(account().details());
220 if (!details)
221 return;
222
223 auto streamSecurityMode = QXmppConfiguration::StreamSecurityMode{};
224 switch (details->encryptionMode())
225 {
226 case JabberAccountDetails::Encryption_Auto:
227 streamSecurityMode = QXmppConfiguration::StreamSecurityMode::TLSEnabled;
228 break;
229 case JabberAccountDetails::Encryption_Yes:
230 streamSecurityMode = QXmppConfiguration::StreamSecurityMode::TLSRequired;
231 break;
232 case JabberAccountDetails::Encryption_No:
233 streamSecurityMode = QXmppConfiguration::StreamSecurityMode::TLSDisabled;
234 break;
235 case JabberAccountDetails::Encryption_Legacy:
236 streamSecurityMode = QXmppConfiguration::StreamSecurityMode::LegacySSL;
237 break;
238 }
239
240 auto useNonSASLAuthentication = details->plainAuthMode() == JabberAccountDetails::AllowPlain
241 ? true
242 : details->plainAuthMode() == JabberAccountDetails::JabberAccountDetails::AllowPlainOverTLS
243 ? QXmppConfiguration::StreamSecurityMode::TLSDisabled != streamSecurityMode
244 : false;
245
246 auto jid = Jid::parse(account().id()).withResource(details->resource());
247
248 auto configuration = QXmppConfiguration{};
249 configuration.setAutoAcceptSubscriptions(false);
250 configuration.setAutoReconnectionEnabled(false);
251 configuration.setIgnoreSslErrors(false);
252 configuration.setJid(jid.full());
253 configuration.setPassword(account().password());
254 configuration.setStreamSecurityMode(streamSecurityMode);
255 configuration.setUseNonSASLAuthentication(useNonSASLAuthentication);
256
257 if (account().proxy())
258 {
259 auto proxy = QNetworkProxy{};
260 if (account().proxy().type() == "socks")
261 proxy.setType(QNetworkProxy::Socks5Proxy);
262 else
263 proxy.setType(QNetworkProxy::HttpProxy);
264
265 proxy.setHostName(account().proxy().address());
266 proxy.setPort(account().proxy().port());
267 proxy.setUser(account().proxy().user());
268 proxy.setPassword(account().proxy().password());
269 configuration.setNetworkProxy(proxy);
270 }
271
272 if (details->useCustomHostPort())
273 {
274 configuration.setHost(details->customHost());
275 configuration.setPort(details->customPort());
276 }
277
278 auto presence = m_presenceService->statusToPresence(status());
279 if (details)
280 presence.setPriority(details->priority());
281
282 static_cast<JabberRosterService *>(rosterService())->prepareRoster();
283 m_client->connectToServer(configuration, presence);
284 }
285
connectedToServer()286 void JabberProtocol::connectedToServer()
287 {
288 loggedIn();
289 }
290
logout()291 void JabberProtocol::logout()
292 {
293 auto logoutStatus = status();
294 logoutStatus.setType(StatusType::Offline);
295 m_client->setClientPresence(m_presenceService->statusToPresence(logoutStatus));
296 m_client->disconnectFromServer();
297
298 loggedOut();
299 }
300
disconenctedFromServer()301 void JabberProtocol::disconenctedFromServer()
302 {
303 m_resourceService->clear();
304 }
305
error(QXmppClient::Error error)306 void JabberProtocol::error(QXmppClient::Error error)
307 {
308 auto errorMessage = QString{};
309 switch (error)
310 {
311 case QXmppClient::Error::SocketError:
312 {
313 switch (m_client->socketError())
314 {
315 case QAbstractSocket::SslHandshakeFailedError:
316 sslError();
317 return;
318 default:
319 break;
320 }
321 break;
322 }
323
324 case QXmppClient::Error::XmppStreamError:
325 {
326 switch (m_client->xmppStreamError())
327 {
328 case QXmppStanza::Error::NotAuthorized:
329 passwordRequired();
330 return;
331 case QXmppStanza::Error::Conflict:
332 errorMessage = tr("Another client connected on the same resource.");
333 setStatus(Status{}, SourceUser);
334 break;
335 default:
336 break;
337 }
338 break;
339 }
340
341 default:
342 break;
343 }
344
345 if (errorMessage.isEmpty())
346 errorMessage = m_errorService->errorMessage(m_client, error);
347 emit connectionError(account(), m_client->configuration().host(), errorMessage);
348 connectionError();
349
350 m_client->disconnectFromServer();
351 }
352
updatePresence()353 void JabberProtocol::updatePresence()
354 {
355 sendStatusToServer();
356 }
357
sendStatusToServer()358 void JabberProtocol::sendStatusToServer()
359 {
360 if (!isConnected() && !isDisconnecting())
361 return;
362
363 auto presence = m_presenceService->statusToPresence(status());
364 auto details = dynamic_cast<JabberAccountDetails *>(account().details());
365 if (details)
366 presence.setPriority(details->priority());
367
368 m_client->setClientPresence(presence);
369 account().accountContact().setCurrentStatus(status());
370 }
371
changePrivateMode()372 void JabberProtocol::changePrivateMode()
373 {
374 sendStatusToServer();
375 }
376
presenceReceived(const QXmppPresence & presence)377 void JabberProtocol::presenceReceived(const QXmppPresence &presence)
378 {
379 if (presence.isMucSupported())
380 return;
381
382 auto jid = Jid::parse(presence.from());
383 auto id = jid.bare();
384 auto contact = contactManager()->byId(account(), id, ActionReturnNull);
385 if (!contact)
386 return;
387
388 auto status = m_presenceService->presenceToStatus(presence);
389 if (status.type() != StatusType::Offline)
390 {
391 auto jabberResource = JabberResource{jid, presence.priority(), status};
392 m_resourceService->updateResource(jabberResource);
393 }
394 else
395 {
396 if (contact.property("jabber:chat-resource", QString{}).toString() == jid.resource())
397 contact.removeProperty("jabber:chat-resource");
398 m_resourceService->removeResource(jid);
399 }
400
401 auto bestResource = m_resourceService->bestResource(id);
402 auto statusToSet = bestResource.isEmpty()
403 ? status
404 : bestResource.status();
405
406 auto oldStatus = contact.currentStatus();
407 contact.setCurrentStatus(statusToSet);
408
409 // see issue #2159 - we need a way to ignore first status of given contact
410 if (contact.ignoreNextStatusChange())
411 contact.setIgnoreNextStatusChange(false);
412 else
413 emit contactStatusChanged(contact, oldStatus);
414 }
415
statusPixmapPath()416 QString JabberProtocol::statusPixmapPath()
417 {
418 return QStringLiteral("xmpp");
419 }
420
changePasswordService() const421 JabberChangePasswordService * JabberProtocol::changePasswordService() const
422 {
423 return m_changePasswordService;
424 }
425
426 #include "moc_jabber-protocol.cpp"
427