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