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