1 //////////////////////////////////////////////////////////////////////
2 //
3 // BeeBEEP Copyright (C) 2010-2021 Marco Mastroddi
4 //
5 // BeeBEEP is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published
7 // by the Free Software Foundation, either version 3 of the License,
8 // or (at your option) any later version.
9 //
10 // BeeBEEP is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with BeeBEEP. If not, see <http://www.gnu.org/licenses/>.
17 //
18 // Author: Marco Mastroddi <marco.mastroddi(AT)gmail.com>
19 //
20 // $Id: CoreConnection.cpp 1459 2020-12-29 10:46:47Z mastroddi $
21 //
22 //////////////////////////////////////////////////////////////////////
23
24 #include "Avatar.h"
25 #include "BeeUtils.h"
26 #include "Broadcaster.h"
27 #include "ChatManager.h"
28 #include "ColorManager.h"
29 #include "Connection.h"
30 #include "Core.h"
31 #include "FileShare.h"
32 #include "FirewallManager.h"
33 #include "Hive.h"
34 #include "IconManager.h"
35 #include "NetworkManager.h"
36 #include "Protocol.h"
37 #include "Settings.h"
38 #include "UserManager.h"
39 #ifdef BEEBEEP_USE_SHAREDESKTOP
40 #include "ShareDesktop.h"
41 #endif
42
43
connection(VNumber user_id) const44 Connection* Core::connection( VNumber user_id ) const
45 {
46 foreach( Connection* c, m_connections )
47 {
48 if( c->userId() == user_id )
49 return c;
50 }
51 return Q_NULLPTR;
52 }
53
hasConnection(const QHostAddress & sender_ip,int sender_port) const54 bool Core::hasConnection( const QHostAddress& sender_ip, int sender_port ) const
55 {
56 foreach( Connection* c, m_connections )
57 {
58 if( (sender_port == -1 || c->peerPort() == sender_port) && c->peerAddress() == sender_ip )
59 {
60 if( c->isConnected() || c->isConnecting() )
61 {
62 #ifdef BEEBEEP_DEBUG
63 qDebug() << "Connection from" << qPrintable( sender_ip.toString() ) << sender_port << "is already opened";
64 #endif
65 return true;
66 }
67 }
68 }
69
70 return false;
71 }
72
checkNetworkAddress(const NetworkAddress & na)73 void Core::checkNetworkAddress( const NetworkAddress& na )
74 {
75 if( na == Settings::instance().localUser().networkAddress() )
76 return;
77
78 newPeerFound( na.hostAddress(), na.hostPort() );
79 }
80
createConnection()81 Connection* Core::createConnection()
82 {
83 Connection *c = new Connection( this );
84 m_connections.append( c );
85 return c;
86 }
87
newPeerFound(const QHostAddress & sender_ip,int sender_port)88 void Core::newPeerFound( const QHostAddress& sender_ip, int sender_port )
89 {
90 if( !isConnected() )
91 return;
92
93 if( NetworkAddress::isLoopback( sender_ip ) && sender_port == mp_listener->serverPort() )
94 return;
95
96 if( NetworkManager::instance().isLocalHostAddress( sender_ip ) && sender_port == mp_listener->serverPort() )
97 {
98 #ifdef BEEBEEP_DEBUG
99 qDebug() << "Skip new peer found" << qPrintable( sender_ip.toString() ) << ":" << sender_port << "because it is local user";
100 #endif
101 return;
102 }
103
104 if( !NetworkManager::instance().isHostAddressAllowed( sender_ip ) )
105 {
106 qWarning() << "New peer found" << qPrintable( sender_ip.toString() ) << "is not allowed by file HOSTS";
107 return;
108 }
109
110 if( Settings::instance().preventMultipleConnectionsFromSingleHostAddress() )
111 {
112 if( hasConnection( sender_ip, -1 ) )
113 {
114 qDebug() << "New peer found" << qPrintable( sender_ip.toString() ) << "is already connected and blocked by prevent multiple connections option";
115 return;
116 }
117 }
118
119 NetworkAddress na( sender_ip, static_cast<quint16>( sender_port ) );
120 if( isUserConnected( na ) )
121 {
122 User u = UserManager::instance().findUserByNetworkAddress( na );
123 if( u.isValid() )
124 qDebug() << "Skip new peer found" << qPrintable( sender_ip.toString() ) << "from connected user" << qPrintable( u.name() );
125 else
126 qWarning() << "New peer found" << qPrintable( sender_ip.toString() ) << "is already connected (but user is not authorized yet)";
127 return;
128 }
129
130 qDebug() << "Connecting to new peer found" << qPrintable( sender_ip.toString() ) << sender_port;
131
132 Connection *c = createConnection();
133 setupNewConnection( c );
134 c->connectToNetworkAddress( na );
135 }
136
checkNewConnection(qintptr socket_descriptor)137 void Core::checkNewConnection( qintptr socket_descriptor )
138 {
139 Connection *c = createConnection();
140 c->initSocket( socket_descriptor, mp_listener->serverPort() );
141 qDebug() << "New connection to port" << mp_listener->serverPort() << "from" << qPrintable( c->networkAddress().toString() );
142 if( NetworkManager::instance().isHostAddressAllowed( c->networkAddress().hostAddress() ) )
143 {
144 qDebug() << "New connection to port" << mp_listener->serverPort() << "from" << qPrintable( c->networkAddress().toString() );
145 setupNewConnection( c );
146 }
147 else
148 {
149 qWarning() << "New connection to port" << mp_listener->serverPort() << "from" << qPrintable( c->networkAddress().toString() ) << "is not allowed by file HOSTS";
150 closeConnection( c );
151 }
152 }
153
setupNewConnection(Connection * c)154 void Core::setupNewConnection( Connection *c )
155 {
156 #ifdef BEEBEEP_DEBUG
157 if( !c->peerAddress().isNull() )
158 qDebug() << "Connecting SIGNAL/SLOT to connection from" << qPrintable( c->networkAddress().toString() );
159 #endif
160 connect( c, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( setConnectionError( QAbstractSocket::SocketError ) ) );
161 connect( c, SIGNAL( disconnected() ), this, SLOT( setConnectionClosed() ) );
162 connect( c, SIGNAL( abortRequest() ), this, SLOT( setConnectionClosed() ) );
163 connect( c, SIGNAL( authenticationRequested( const QByteArray& ) ), this, SLOT( checkUserAuthentication( const QByteArray& ) ) );
164 }
165
addConnectionReadyForUse(Connection * c,const User & u)166 void Core::addConnectionReadyForUse( Connection* c, const User& u )
167 {
168 #ifdef BEEBEEP_DEBUG
169 qDebug() << "Connection from" << qPrintable( c->networkAddress().toString() ) << "is ready for use by" << qPrintable( u.path() );
170 #endif
171 c->setReadyForUse( u.id() );
172 connect( c, SIGNAL( newMessage( VNumber, const Message& ) ), this, SLOT( parseMessage( VNumber, const Message& ) ) );
173 }
174
setConnectionError(QAbstractSocket::SocketError se)175 void Core::setConnectionError( QAbstractSocket::SocketError se )
176 {
177 Connection* c = qobject_cast<Connection*>( sender() );
178 if( c )
179 {
180 qWarning() << "Connection from" << qPrintable( c->networkAddress().toString() ) << "has an error:" << c->errorString() << "-" << static_cast<int>(se);
181 closeConnection( c );
182 }
183 else
184 qWarning() << "Connection error" << se << "occurred but the object caller is invalid";
185 }
186
setConnectionClosed()187 void Core::setConnectionClosed()
188 {
189 Connection* c = qobject_cast<Connection*>( sender() );
190 if( c )
191 closeConnection( c );
192 else
193 qWarning() << "Connection closed but the object caller is invalid";
194 }
195
closeConnection(Connection * c)196 void Core::closeConnection( Connection *c )
197 {
198 if( !m_connections.removeOne( c ) )
199 return;
200
201 if( c->userId() != ID_INVALID )
202 {
203 User u = UserManager::instance().findUser( c->userId() );
204 if( u.isValid() )
205 {
206 qDebug() << "User" << qPrintable( u.path() ) << "goes offline";
207 u.setStatus( User::Offline );
208 u.setLastConnection( QDateTime::currentDateTime() );
209 UserManager::instance().setUser( u );
210 UserManager::instance().removeNewConnectedUserId( u.id() );
211
212 emit userChanged( u );
213 emit userConnectionStatusChanged( u );
214
215 Chat default_chat = ChatManager::instance().defaultChat();
216 if( default_chat.removeUser( u.id() ) )
217 ChatManager::instance().setChat( default_chat );
218
219 FileShare::instance().removeFromNetwork( u.id() );
220 emit fileShareAvailable( u );
221
222 if( isConnected() )
223 mp_fileTransfer->removeFilesToUser( u.id() );
224
225 #ifdef BEEBEEP_USE_SHAREDESKTOP
226 if( mp_shareDesktop->isActive() )
227 stopShareDesktop( u.id() );
228 #endif
229 }
230 else
231 qWarning() << "User" << c->userId() << "not found while closing connection";
232 }
233
234 #ifdef BEEBEEP_DEBUG
235 qDebug() << "Deleting connection" << qPrintable( c->networkAddress().toString() );
236 #endif
237 c->disconnect();
238 c->abortConnection();
239 c->deleteLater();
240 }
241
checkUserAuthentication(const QByteArray & auth_byte_array)242 void Core::checkUserAuthentication( const QByteArray& auth_byte_array )
243 {
244 Connection* c = qobject_cast<Connection*>( sender() );
245 if( !c )
246 {
247 qWarning() << "Unable to check authentication for an invalid connection object";
248 return;
249 }
250
251 Message m = Protocol::instance().toMessage( auth_byte_array, c->protocolVersion() );
252 if( !m.isValid() )
253 {
254 qWarning() << "Core has received an invalid HELLO from" << qPrintable( c->networkAddress().toString() );
255 closeConnection( c );
256 return;
257 }
258
259 if( m.type() != Message::Hello )
260 {
261 qWarning() << "Core is waiting for HELLO, but another message type" << m.type() << "is arrived from" << qPrintable( c->networkAddress().toString() );
262 closeConnection( c );
263 return;
264 }
265
266 User u = Protocol::instance().createUser( m, c->peerAddress() );
267 if( !u.isValid() )
268 {
269 qWarning() << "Unable to create a new user (invalid protocol or password) from the message arrived from:" << qPrintable( c->networkAddress().toString() );
270 closeConnection( c );
271 return;
272 }
273 else
274 qDebug() << qPrintable( u.path() ) << "has completed the authentication from peer" << qPrintable( c->networkAddress().toString() );
275
276 User user_found = Protocol::instance().recognizeUser( u, Settings::instance().userRecognitionMethod() );
277 bool user_path_changed = false;
278
279 if( user_found.isValid() )
280 {
281 QString sAlertMsg;
282 user_path_changed = (u.path() != user_found.path());
283 if( user_found.isLocal() )
284 {
285 if( user_path_changed )
286 {
287 #ifdef BEEBEEP_DEBUG
288 qDebug() << "User path changed:" << u.path() << "vs" << user_found.path();
289 #endif
290 if( Settings::instance().userRecognitionMethod() == Settings::RecognizeByAccountAndDomain )
291 {
292 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses your account name: %3." )
293 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), u.accountPath() );
294 sAlertMsg += QString( " (%1)" ).arg( tr( "domain" ) );
295 }
296 else if( Settings::instance().userRecognitionMethod() == Settings::RecognizeByAccount )
297 {
298 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses your account name: %3." )
299 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), u.accountName() );
300 }
301 else
302 {
303 if( u.name() == user_found.name() )
304 {
305 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses your nickname: %3." )
306 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), Bee::replaceHtmlSpecialCharacters( u.name() ) );
307 }
308 else
309 {
310 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses your hash code." )
311 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ) );
312 }
313 }
314 dispatchSystemMessage( ID_DEFAULT_CHAT, user_found.id(), sAlertMsg, DispatchToDefaultAndPrivateChat, ChatMessage::Connection, false );
315 qWarning() << "User with account" << qPrintable( u.accountPath() ) << "and path" << qPrintable( u.path() ) << "is recognized to be Local";
316 }
317 else
318 qDebug() << "Test connection from localhost to port" << mp_listener->serverPort() << "was successful";
319
320 closeConnection( c );
321 return;
322 }
323
324 if( isUserConnected( user_found.id() ) )
325 {
326 if( user_path_changed )
327 {
328 if( Settings::instance().userRecognitionMethod() == Settings::RecognizeByAccountAndDomain )
329 {
330 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses same account name of the already connected user %3: %4." )
331 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), Bee::replaceHtmlSpecialCharacters( user_found.path() ), u.accountPath() );
332 sAlertMsg += QString( " (%1)" ).arg( tr( "domain" ) );
333 qDebug() << "User" << qPrintable( u.path() ) << "is already connected with account (domain)" << qPrintable( user_found.accountPath() );
334 }
335 else if( Settings::instance().userRecognitionMethod() == Settings::RecognizeByAccount )
336 {
337 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses same account name of the already connected user %3: %4." )
338 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), Bee::replaceHtmlSpecialCharacters( user_found.path() ), u.accountName() );
339 qDebug() << "User" << qPrintable( u.path() ) << "is already connected with account" << qPrintable( user_found.accountPath() );
340 }
341 else
342 {
343 if( u.name() == user_found.name() )
344 {
345 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses same nickname of the already connected user %3: %4." )
346 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), Bee::replaceHtmlSpecialCharacters( user_found.path() ), Bee::userNameToShow( u, true ) );
347 qDebug() << "User" << qPrintable( u.path() ) << "is already connected with the same nickname of" << qPrintable( user_found.path() );
348 }
349 else
350 {
351 sAlertMsg = tr( "%1 Connection closed to user %2 because it uses same hash code of the already connected user %3: %4." )
352 .arg( IconManager::instance().toHtml( "warning.png", "*E*" ), Bee::replaceHtmlSpecialCharacters( u.path() ), Bee::replaceHtmlSpecialCharacters( user_found.path() ), Bee::userNameToShow( u, true ) );
353 qDebug() << "User" << qPrintable( u.path() ) << "is already connected with the same hash code of" << qPrintable( user_found.path() );
354 }
355 }
356 dispatchSystemMessage( ID_DEFAULT_CHAT, user_found.id(), sAlertMsg, DispatchToDefaultAndPrivateChat, ChatMessage::Connection, false );
357 }
358 else
359 {
360 qDebug() << "User" << qPrintable( u.path() ) << "is already online with another connection:" << qPrintable( user_found.path() );
361 int remote_protocol_version = c->protocolVersion();
362 closeConnection( c );
363 if( remote_protocol_version < Settings::instance().protocolVersion() ||
364 ( remote_protocol_version == Settings::instance().protocolVersion() && user_found.path() < Settings::instance().localUser().path() ) ) // just one of them to prevent collision
365 {
366 if( mp_broadcaster->addNetworkAddress( user_found.networkAddress() ) )
367 qDebug() << "User" << qPrintable( user_found.path() ) << "is added to addresses to be contacted later";
368 }
369 }
370 return;
371 }
372
373 if( user_path_changed )
374 qDebug() << "On connection old user found" << qPrintable( user_found.path() ) << "and associated to" << qPrintable( u.path() );
375
376 u.setId( user_found.id() );
377 u.setIsFavorite( user_found.isFavorite() );
378 if( !ColorManager::instance().isValidColor( u.color() ) || u.color() == QString( "#000000" ) )
379 u.setColor( user_found.color() );
380 }
381 else
382 qDebug() << "New user connected:" << qPrintable( u.path() );
383
384 if( !ColorManager::instance().isValidColor( u.color() ) || u.color() == QString( "#000000" ) )
385 u.setColor( ColorManager::instance().unselectedQString() );
386 u.setProtocolVersion( c->protocolVersion() );
387 u.setLastConnection( QDateTime::currentDateTime() );
388 UserManager::instance().setUser( u );
389
390 #ifdef BEEBEEP_DEBUG
391 qDebug() << "User" << qPrintable( u.path() ) << "added with id" << u.id() << "and color" << qPrintable( u.color() );
392 #endif
393
394 Chat default_chat = ChatManager::instance().defaultChat();
395 if( default_chat.addUser( u.id() ) )
396 ChatManager::instance().setChat( default_chat );
397
398 Chat private_chat = ChatManager::instance().privateChatForUser( u.id() );
399 if( private_chat.isValid() )
400 {
401 if( user_found.isValid() && u.name() != user_found.name() )
402 {
403 ChatManager::instance().changePrivateChatNameAfterUserNameChanged( u.id(), u.name() );
404 showUserNameChanged( u, user_found.name() );
405 }
406 }
407 else
408 createPrivateChat( u );
409
410 addConnectionReadyForUse( c, u );
411
412 emit userChanged( u );
413 emit userConnectionStatusChanged( u );
414 showMessage( tr( "%1 users connected" ).arg( connectedUsers() ), 3000 );
415
416 if( c->isEncrypted() )
417 {
418 if( c->protocolVersion() < SECURE_LEVEL_3_PROTO_VERSION )
419 {
420 dispatchSystemMessage( ID_DEFAULT_CHAT, u.id(),
421 tr( "%1 %2 uses old encryption level." ).arg( IconManager::instance().toHtml( "warning.png", "*!*" ), Bee::userNameToShow( u, true ) ),
422 DispatchToAllChatsWithUser, ChatMessage::Connection, false );
423 }
424
425 if( Settings::instance().disableConnectionSocketEncryption() )
426 {
427 dispatchSystemMessage( ID_DEFAULT_CHAT, u.id(),
428 QString( "%1 %2." ).arg( IconManager::instance().toHtml( "encryption-enabled.png", "*!*" ),
429 tr( "%1 has end-to-end encryption enabled" ).arg( Bee::userNameToShow( u, true ) ) ),
430 DispatchToAllChatsWithUser, ChatMessage::Connection, false );
431 }
432 }
433 else
434 {
435 if( !Settings::instance().disableConnectionSocketEncryption() )
436 {
437 dispatchSystemMessage( ID_DEFAULT_CHAT, u.id(),
438 QString( "%1 %2." ).arg( IconManager::instance().toHtml( "encryption-disabled.png", "*!*" ),
439 tr( "%1 has end-to-end encryption disabled" ).arg( Bee::userNameToShow( u, true ) ) ),
440 DispatchToAllChatsWithUser, ChatMessage::Connection, false );
441 }
442 }
443
444 if( !Settings::instance().localUser().vCard().hasOnlyNickName() )
445 {
446 if( c->protocolVersion() > 1 )
447 {
448 #ifdef BEEBEEP_DEBUG
449 qDebug() << "Sending my VCard to" << qPrintable( u.path() );
450 #endif
451 c->sendMessage( Protocol::instance().localVCardMessage() );
452 }
453 }
454
455 UserManager::instance().addNewConnectedUserId( u.id() );
456 }
457
connectedUsers() const458 int Core::connectedUsers() const
459 {
460 int connected_users = 0;
461 if( m_connections.isEmpty() )
462 return connected_users;
463
464 foreach( Connection* c, m_connections )
465 {
466 if( c->isConnected() && c->isReadyForUse() )
467 connected_users++;
468 }
469
470 return connected_users;
471 }
472
checkConnectionPorts()473 void Core::checkConnectionPorts()
474 {
475 if( isConnected() && connectedUsers() == 0 )
476 {
477 // FIXME: it works always... not useful to check if firewall ports are opened
478 NetworkAddress na( NetworkManager::instance().localHostAddress(), mp_listener->serverPort() );
479 qDebug() << "Checking connection to localhost" << qPrintable( na.toString() );
480 Connection *c = createConnection();
481 setupNewConnection( c );
482 c->connectToNetworkAddress( na );
483 }
484 }
485
checkFirewall()486 void Core::checkFirewall()
487 {
488 // FIXME: how to do this?
489 FirewallManager::instance().allowApplication( Settings::instance().programName(), QDir::toNativeSeparators( qApp->applicationFilePath() ) );
490 }
491
updateNetworkConfiguration(const QNetworkConfiguration & net_conf)492 void Core::updateNetworkConfiguration( const QNetworkConfiguration& net_conf )
493 {
494 if( !isConnected() )
495 {
496 qDebug() << "Core is not connected and skips to check network configuration:" << qPrintable( net_conf.name() ) << "-" << qPrintable( net_conf.identifier() ) << "-" << qPrintable( net_conf.bearerTypeName() );;
497 return;
498 }
499
500 #ifdef BEEBEEP_DEBUG
501 qDebug() << "Core is checking network configuration:" << qPrintable( net_conf.name() ) << "-" << qPrintable( net_conf.identifier() )
502 << "- bearer:" << qPrintable( net_conf.bearerTypeName() ) << net_conf.bearerType()
503 #if QT_VERSION > 0x050000
504 << net_conf.bearerTypeFamily()
505 #endif
506 << "- purpose:" << net_conf.purpose()
507 << "- type:" << net_conf.type()
508 << "- state:" << net_conf.state();
509 #endif
510
511 if( net_conf.state() == QNetworkConfiguration::Active && (net_conf.bearerType() == QNetworkConfiguration::BearerEthernet || net_conf.bearerType() == QNetworkConfiguration::BearerWLAN) )
512 {
513 qDebug() << "Network configuration:" << qPrintable( net_conf.name() ) << "-" << qPrintable( net_conf.identifier() ) << "is active ethernet or wlan and new broadcast will be requested";
514 mp_broadcaster->sendBroadcast();
515 }
516 }
517