1 // The MIT License (MIT)
2 //
3 // Copyright (c) Itay Grudev 2015 - 2020
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22
23 //
24 // W A R N I N G !!!
25 // -----------------
26 //
27 // This file is not part of the SingleApplication API. It is used purely as an
28 // implementation detail. This header file may change from version to
29 // version without notice, or may even be removed.
30 //
31
32 #include <cstdlib>
33 #include <cstddef>
34
35 #include <QtCore/QDir>
36 #include <QtCore/QThread>
37 #include <QtCore/QByteArray>
38 #include <QtCore/QDataStream>
39 #include <QtCore/QElapsedTimer>
40 #include <QtCore/QCryptographicHash>
41 #include <QtNetwork/QLocalServer>
42 #include <QtNetwork/QLocalSocket>
43
44 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
45 #include <QtCore/QRandomGenerator>
46 #else
47 #include <QtCore/QDateTime>
48 #endif
49
50 #include "singleapplication.h"
51 #include "singleapplication_p.h"
52
53 #ifdef Q_OS_UNIX
54 #include <unistd.h>
55 #include <sys/types.h>
56 #include <pwd.h>
57 #endif
58
59 #ifdef Q_OS_WIN
60 #ifndef NOMINMAX
61 #define NOMINMAX 1
62 #endif
63 #include <windows.h>
64 #include <lmcons.h>
65 #endif
66
SingleApplicationPrivate(SingleApplication * q_ptr)67 SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
68 : q_ptr( q_ptr )
69 {
70 server = nullptr;
71 socket = nullptr;
72 memory = nullptr;
73 instanceNumber = 0;
74 }
75
~SingleApplicationPrivate()76 SingleApplicationPrivate::~SingleApplicationPrivate()
77 {
78 if( socket != nullptr ){
79 socket->close();
80 delete socket;
81 }
82
83 if( memory != nullptr ){
84 memory->lock();
85 auto *inst = static_cast<InstancesInfo*>(memory->data());
86 if( server != nullptr ){
87 server->close();
88 delete server;
89 inst->primary = false;
90 inst->primaryPid = -1;
91 inst->primaryUser[0] = '\0';
92 inst->checksum = blockChecksum();
93 }
94 memory->unlock();
95
96 delete memory;
97 }
98 }
99
getUsername()100 QString SingleApplicationPrivate::getUsername()
101 {
102 #ifdef Q_OS_WIN
103 wchar_t username[UNLEN + 1];
104 // Specifies size of the buffer on input
105 DWORD usernameLength = UNLEN + 1;
106 if( GetUserNameW( username, &usernameLength ) )
107 return QString::fromWCharArray( username );
108 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
109 return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
110 #else
111 return qEnvironmentVariable( "USERNAME" );
112 #endif
113 #endif
114 #ifdef Q_OS_UNIX
115 QString username;
116 uid_t uid = geteuid();
117 struct passwd *pw = getpwuid( uid );
118 if( pw )
119 username = QString::fromLocal8Bit( pw->pw_name );
120 if ( username.isEmpty() ){
121 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
122 username = QString::fromLocal8Bit( qgetenv( "USER" ) );
123 #else
124 username = qEnvironmentVariable( "USER" );
125 #endif
126 }
127 return username;
128 #endif
129 }
130
genBlockServerName()131 void SingleApplicationPrivate::genBlockServerName()
132 {
133 QCryptographicHash appData( QCryptographicHash::Sha256 );
134 appData.addData( "SingleApplication", 17 );
135 appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
136 appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
137 appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
138
139 if ( ! appDataList.isEmpty() )
140 appData.addData( appDataList.join( "" ).toUtf8() );
141
142 if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
143 appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
144 }
145
146 if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
147 #ifdef Q_OS_WIN
148 appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
149 #else
150 appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
151 #endif
152 }
153
154 // User level block requires a user specific data in the hash
155 if( options & SingleApplication::Mode::User ){
156 appData.addData( getUsername().toUtf8() );
157 }
158
159 // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
160 // server naming requirements.
161 blockServerName = appData.result().toBase64().replace("/", "_");
162 }
163
initializeMemoryBlock() const164 void SingleApplicationPrivate::initializeMemoryBlock() const
165 {
166 auto *inst = static_cast<InstancesInfo*>( memory->data() );
167 inst->primary = false;
168 inst->secondary = 0;
169 inst->primaryPid = -1;
170 inst->primaryUser[0] = '\0';
171 inst->checksum = blockChecksum();
172 }
173
startPrimary()174 void SingleApplicationPrivate::startPrimary()
175 {
176 // Reset the number of connections
177 auto *inst = static_cast <InstancesInfo*>( memory->data() );
178
179 inst->primary = true;
180 inst->primaryPid = QCoreApplication::applicationPid();
181 qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
182 inst->checksum = blockChecksum();
183 instanceNumber = 0;
184 // Successful creation means that no main process exists
185 // So we start a QLocalServer to listen for connections
186 QLocalServer::removeServer( blockServerName );
187 server = new QLocalServer();
188
189 // Restrict access to the socket according to the
190 // SingleApplication::Mode::User flag on User level or no restrictions
191 if( options & SingleApplication::Mode::User ){
192 server->setSocketOptions( QLocalServer::UserAccessOption );
193 } else {
194 server->setSocketOptions( QLocalServer::WorldAccessOption );
195 }
196
197 server->listen( blockServerName );
198 QObject::connect(
199 server,
200 &QLocalServer::newConnection,
201 this,
202 &SingleApplicationPrivate::slotConnectionEstablished
203 );
204 }
205
startSecondary()206 void SingleApplicationPrivate::startSecondary()
207 {
208 auto *inst = static_cast <InstancesInfo*>( memory->data() );
209
210 inst->secondary += 1;
211 inst->checksum = blockChecksum();
212 instanceNumber = inst->secondary;
213 }
214
connectToPrimary(int msecs,ConnectionType connectionType)215 bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
216 {
217 QElapsedTimer time;
218 time.start();
219
220 // Connect to the Local Server of the Primary Instance if not already
221 // connected.
222 if( socket == nullptr ){
223 socket = new QLocalSocket();
224 }
225
226 if( socket->state() == QLocalSocket::ConnectedState ) return true;
227
228 if( socket->state() != QLocalSocket::ConnectedState ){
229
230 while( true ){
231 randomSleep();
232
233 if( socket->state() != QLocalSocket::ConnectingState )
234 socket->connectToServer( blockServerName );
235
236 if( socket->state() == QLocalSocket::ConnectingState ){
237 socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
238 }
239
240 // If connected break out of the loop
241 if( socket->state() == QLocalSocket::ConnectedState ) break;
242
243 // If elapsed time since start is longer than the method timeout return
244 if( time.elapsed() >= msecs ) return false;
245 }
246 }
247
248 // Initialisation message according to the SingleApplication protocol
249 QByteArray initMsg;
250 QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
251
252 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
253 writeStream.setVersion(QDataStream::Qt_5_6);
254 #endif
255
256 writeStream << blockServerName.toLatin1();
257 writeStream << static_cast<quint8>(connectionType);
258 writeStream << instanceNumber;
259 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
260 quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
261 #else
262 quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
263 #endif
264 writeStream << checksum;
265
266 // The header indicates the message length that follows
267 QByteArray header;
268 QDataStream headerStream(&header, QIODevice::WriteOnly);
269
270 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
271 headerStream.setVersion(QDataStream::Qt_5_6);
272 #endif
273 headerStream << static_cast <quint64>( initMsg.length() );
274
275 socket->write( header );
276 socket->write( initMsg );
277 bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
278 socket->flush();
279 return result;
280 }
281
blockChecksum() const282 quint16 SingleApplicationPrivate::blockChecksum() const
283 {
284 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
285 quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
286 #else
287 quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
288 #endif
289 return checksum;
290 }
291
primaryPid() const292 qint64 SingleApplicationPrivate::primaryPid() const
293 {
294 qint64 pid;
295
296 memory->lock();
297 auto *inst = static_cast<InstancesInfo*>( memory->data() );
298 pid = inst->primaryPid;
299 memory->unlock();
300
301 return pid;
302 }
303
primaryUser() const304 QString SingleApplicationPrivate::primaryUser() const
305 {
306 QByteArray username;
307
308 memory->lock();
309 auto *inst = static_cast<InstancesInfo*>( memory->data() );
310 username = inst->primaryUser;
311 memory->unlock();
312
313 return QString::fromUtf8( username );
314 }
315
316 /**
317 * @brief Executed when a connection has been made to the LocalServer
318 */
slotConnectionEstablished()319 void SingleApplicationPrivate::slotConnectionEstablished()
320 {
321 QLocalSocket *nextConnSocket = server->nextPendingConnection();
322 connectionMap.insert(nextConnSocket, ConnectionInfo());
323
324 QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
325 [nextConnSocket, this](){
326 auto &info = connectionMap[nextConnSocket];
327 Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
328 }
329 );
330
331 QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
332
333 QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
334 [nextConnSocket, this](){
335 connectionMap.remove(nextConnSocket);
336 }
337 );
338
339 QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
340 [nextConnSocket, this](){
341 auto &info = connectionMap[nextConnSocket];
342 switch(info.stage){
343 case StageHeader:
344 readInitMessageHeader(nextConnSocket);
345 break;
346 case StageBody:
347 readInitMessageBody(nextConnSocket);
348 break;
349 case StageConnected:
350 Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
351 break;
352 default:
353 break;
354 };
355 }
356 );
357 }
358
readInitMessageHeader(QLocalSocket * sock)359 void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
360 {
361 if (!connectionMap.contains( sock )){
362 return;
363 }
364
365 if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
366 return;
367 }
368
369 QDataStream headerStream( sock );
370
371 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
372 headerStream.setVersion( QDataStream::Qt_5_6 );
373 #endif
374
375 // Read the header to know the message length
376 quint64 msgLen = 0;
377 headerStream >> msgLen;
378 ConnectionInfo &info = connectionMap[sock];
379 info.stage = StageBody;
380 info.msgLen = msgLen;
381
382 if ( sock->bytesAvailable() >= (qint64) msgLen ){
383 readInitMessageBody( sock );
384 }
385 }
386
readInitMessageBody(QLocalSocket * sock)387 void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
388 {
389 Q_Q(SingleApplication);
390
391 if (!connectionMap.contains( sock )){
392 return;
393 }
394
395 ConnectionInfo &info = connectionMap[sock];
396 if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
397 return;
398 }
399
400 // Read the message body
401 QByteArray msgBytes = sock->read(info.msgLen);
402 QDataStream readStream(msgBytes);
403
404 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
405 readStream.setVersion( QDataStream::Qt_5_6 );
406 #endif
407
408 // server name
409 QByteArray latin1Name;
410 readStream >> latin1Name;
411
412 // connection type
413 ConnectionType connectionType = InvalidConnection;
414 quint8 connTypeVal = InvalidConnection;
415 readStream >> connTypeVal;
416 connectionType = static_cast <ConnectionType>( connTypeVal );
417
418 // instance id
419 quint32 instanceId = 0;
420 readStream >> instanceId;
421
422 // checksum
423 quint16 msgChecksum = 0;
424 readStream >> msgChecksum;
425
426 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
427 const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
428 #else
429 const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
430 #endif
431
432 bool isValid = readStream.status() == QDataStream::Ok &&
433 QLatin1String(latin1Name) == blockServerName &&
434 msgChecksum == actualChecksum;
435
436 if( !isValid ){
437 sock->close();
438 return;
439 }
440
441 info.instanceId = instanceId;
442 info.stage = StageConnected;
443
444 if( connectionType == NewInstance ||
445 ( connectionType == SecondaryInstance &&
446 options & SingleApplication::Mode::SecondaryNotification ) )
447 {
448 Q_EMIT q->instanceStarted();
449 }
450
451 if (sock->bytesAvailable() > 0){
452 Q_EMIT this->slotDataAvailable( sock, instanceId );
453 }
454 }
455
slotDataAvailable(QLocalSocket * dataSocket,quint32 instanceId)456 void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
457 {
458 Q_Q(SingleApplication);
459 Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
460 }
461
slotClientConnectionClosed(QLocalSocket * closedSocket,quint32 instanceId)462 void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
463 {
464 if( closedSocket->bytesAvailable() > 0 )
465 Q_EMIT slotDataAvailable( closedSocket, instanceId );
466 }
467
randomSleep()468 void SingleApplicationPrivate::randomSleep()
469 {
470 #if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
471 QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
472 #else
473 qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
474 QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
475 #endif
476 }
477
addAppData(const QString & data)478 void SingleApplicationPrivate::addAppData(const QString &data)
479 {
480 appDataList.push_back(data);
481 }
482
appData() const483 QStringList SingleApplicationPrivate::appData() const
484 {
485 return appDataList;
486 }
487