1 /*
2     Copyright © 2014-2019 by The qTox Project Contributors
3 
4     This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6     qTox is libre software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     qTox is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "src/audio/audio.h"
21 #include "src/ipc.h"
22 #include "src/net/toxuri.h"
23 #include "src/nexus.h"
24 #include "src/persistence/profile.h"
25 #include "src/persistence/settings.h"
26 #include "src/persistence/toxsave.h"
27 #include "src/video/camerasource.h"
28 #include "src/widget/loginscreen.h"
29 #include "src/widget/translator.h"
30 #include "widget/widget.h"
31 #include <QApplication>
32 #include <QCommandLineParser>
33 #include <QDateTime>
34 #include <QDebug>
35 #include <QDir>
36 #include <QFile>
37 #include <QFontDatabase>
38 #include <QMutex>
39 #include <QMutexLocker>
40 
41 #include <QtWidgets/QMessageBox>
42 #include <ctime>
43 #include <sodium.h>
44 #include <stdio.h>
45 
46 #if defined(Q_OS_OSX)
47 #include "platform/install_osx.h"
48 #endif
49 
50 #if defined(Q_OS_UNIX)
51 #include "platform/posixsignalnotifier.h"
52 #endif
53 
54 #ifdef LOG_TO_FILE
55 static QAtomicPointer<FILE> logFileFile = nullptr;
56 static QList<QByteArray>* logBuffer =
57     new QList<QByteArray>(); // Store log messages until log file opened
58 QMutex* logBufferMutex = new QMutex();
59 #endif
60 
cleanup()61 void cleanup()
62 {
63     // force save early even though destruction saves, because Windows OS will
64     // close qTox before cleanup() is finished if logging out or shutting down,
65     // once the top level window has exited, which occurs in ~Widget within
66     // ~Nexus. Re-ordering Nexus destruction is not trivial.
67     auto& s = Settings::getInstance();
68     s.saveGlobal();
69     s.savePersonal();
70     s.sync();
71 
72     Nexus::destroyInstance();
73     CameraSource::destroyInstance();
74     Settings::destroyInstance();
75     qDebug() << "Cleanup success";
76 
77 #ifdef LOG_TO_FILE
78 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
79     FILE* f = logFileFile.loadRelaxed();
80 #else
81     FILE* f = logFileFile.load();
82 #endif
83     if (f != nullptr) {
84         fclose(f);
85 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
86         logFileFile.storeRelaxed(nullptr); // atomically disable logging to file
87 #else
88         logFileFile.store(nullptr); // atomically disable logging to file
89 #endif
90     }
91 #endif
92 }
93 
logMessageHandler(QtMsgType type,const QMessageLogContext & ctxt,const QString & msg)94 void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QString& msg)
95 {
96     // Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images)
97     if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)")
98         && msg == QString("QFSFileEngine::open: No file name specified"))
99         return;
100 
101     QString file = ctxt.file;
102     // We're not using QT_MESSAGELOG_FILE here, because that can be 0, NULL, or
103     // nullptr in release builds.
104     QString path = QString(__FILE__);
105     path = path.left(path.lastIndexOf('/') + 1);
106     if (file.startsWith(path)) {
107         file = file.mid(path.length());
108     }
109 
110     // Time should be in UTC to save user privacy on log sharing
111     QTime time = QDateTime::currentDateTime().toUTC().time();
112     QString LogMsg =
113         QString("[%1 UTC] %2:%3 : ").arg(time.toString("HH:mm:ss.zzz")).arg(file).arg(ctxt.line);
114     switch (type) {
115     case QtDebugMsg:
116         LogMsg += "Debug";
117         break;
118     case QtInfoMsg:
119         LogMsg += "Info";
120         break;
121     case QtWarningMsg:
122         LogMsg += "Warning";
123         break;
124     case QtCriticalMsg:
125         LogMsg += "Critical";
126         break;
127     case QtFatalMsg:
128         LogMsg += "Fatal";
129         break;
130     default:
131         break;
132     }
133 
134     LogMsg += ": " + msg + "\n";
135     QByteArray LogMsgBytes = LogMsg.toUtf8();
136     fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr);
137 
138 #ifdef LOG_TO_FILE
139 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
140     FILE* logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
141 #else
142     FILE* logFilePtr = logFileFile.load(); // atomically load the file pointer
143 #endif
144     if (!logFilePtr) {
145         logBufferMutex->lock();
146         if (logBuffer)
147             logBuffer->append(LogMsgBytes);
148 
149         logBufferMutex->unlock();
150     } else {
151         logBufferMutex->lock();
152         if (logBuffer) {
153             // empty logBuffer to file
154             foreach (QByteArray msg, *logBuffer)
155                 fwrite(msg.constData(), 1, msg.size(), logFilePtr);
156 
157             delete logBuffer; // no longer needed
158             logBuffer = nullptr;
159         }
160         logBufferMutex->unlock();
161 
162         fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), logFilePtr);
163         fflush(logFilePtr);
164     }
165 #endif
166 }
167 
main(int argc,char * argv[])168 int main(int argc, char* argv[])
169 {
170 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
171     QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
172     QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
173 #endif
174 
175     qInstallMessageHandler(logMessageHandler);
176 
177     // initialize random number generator
178 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
179     qsrand(time(nullptr));
180 #endif
181 
182     std::unique_ptr<QApplication> a(new QApplication(argc, argv));
183 
184 #if defined(Q_OS_UNIX)
185     // PosixSignalNotifier is used only for terminating signals,
186     // so it's connected directly to quit() without any filtering.
187     QObject::connect(&PosixSignalNotifier::globalInstance(), &PosixSignalNotifier::activated,
188                      a.get(), &QApplication::quit);
189     PosixSignalNotifier::watchCommonTerminatingSignals();
190 #endif
191 
192     a->setApplicationName("qTox");
193 #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
194     a->setDesktopFileName("io.github.qtox.qTox");
195 #endif
196     a->setApplicationVersion("\nGit commit: " + QString(GIT_VERSION));
197 
198     // Install Unicode 6.1 supporting font
199     // Keep this as close to the beginning of `main()` as possible, otherwise
200     // on systems that have poor support for Unicode qTox will look bad.
201     if (QFontDatabase::addApplicationFont("://font/DejaVuSans.ttf") == -1) {
202         qWarning() << "Couldn't load font";
203     }
204 
205 
206 #if defined(Q_OS_OSX)
207     // TODO: Add setting to enable this feature.
208     // osx::moveToAppFolder();
209     osx::migrateProfiles();
210 #endif
211 
212 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
213     qsrand(time(nullptr));
214 #endif
215     Settings& settings = Settings::getInstance();
216     QString locale = settings.getTranslation();
217     Translator::translate(locale);
218 
219     // Process arguments
220     QCommandLineParser parser;
221     parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION));
222     parser.addHelpOption();
223     parser.addVersionOption();
224     parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse"));
225     parser.addOption(
226         QCommandLineOption(QStringList() << "p"
227                                          << "profile",
228                            QObject::tr("Starts new instance and loads specified profile."),
229                            QObject::tr("profile")));
230     parser.addOption(
231         QCommandLineOption(QStringList() << "l"
232                                          << "login",
233                            QObject::tr("Starts new instance and opens the login screen.")));
234     parser.addOption(QCommandLineOption(QStringList() << "I"
235                                                       << "IPv6",
236                                         QObject::tr("Sets IPv6 <on>/<off>. Default is ON."),
237                                         QObject::tr("on/off")));
238     parser.addOption(QCommandLineOption(QStringList() << "U"
239                                                       << "UDP",
240                                         QObject::tr("Sets UDP <on>/<off>. Default is ON."),
241                                         QObject::tr("on/off")));
242     parser.addOption(
243         QCommandLineOption(QStringList() << "L"
244                                          << "LAN",
245                            QObject::tr(
246                                "Sets LAN discovery <on>/<off>. UDP off overrides. Default is ON."),
247                            QObject::tr("on/off")));
248     parser.addOption(QCommandLineOption(QStringList() << "P"
249                                                       << "proxy",
250                                         QObject::tr("Sets proxy settings. Default is NONE."),
251                                         QObject::tr("(SOCKS5/HTTP/NONE):(ADDRESS):(PORT)")));
252     parser.process(*a);
253 
254     uint32_t profileId = settings.getCurrentProfileId();
255     IPC ipc(profileId);
256     if (ipc.isAttached()) {
257         QObject::connect(&settings, &Settings::currentProfileIdChanged, &ipc, &IPC::setProfileId);
258     } else {
259         qWarning() << "Can't init IPC, maybe we're in a jail? Continuing with reduced multi-client functionality.";
260     }
261 
262     // For the auto-updater
263     if (sodium_init() < 0) {
264         qCritical() << "Can't init libsodium";
265         return EXIT_FAILURE;
266     }
267 
268 #ifdef LOG_TO_FILE
269     QString logFileDir = settings.getAppCacheDirPath();
270     QDir(logFileDir).mkpath(".");
271 
272     QString logfile = logFileDir + "qtox.log";
273     FILE* mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
274 
275     // Trim log file if over 1MB
276     if (QFileInfo(logfile).size() > 1000000) {
277         qDebug() << "Log file over 1MB, rotating...";
278 
279         // close old logfile (need for windows)
280         if (mainLogFilePtr)
281             fclose(mainLogFilePtr);
282 
283         QDir dir(logFileDir);
284 
285         // Check if log.1 already exists, and if so, delete it
286         if (dir.remove(logFileDir + "qtox.log.1"))
287             qDebug() << "Removed old log successfully";
288         else
289             qWarning() << "Unable to remove old log file";
290 
291         if (!dir.rename(logFileDir + "qtox.log", logFileDir + "qtox.log.1"))
292             qCritical() << "Unable to move logs";
293 
294         // open a new logfile
295         mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
296     }
297 
298     if (!mainLogFilePtr)
299         qCritical() << "Couldn't open logfile" << logfile;
300 
301 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
302     logFileFile.storeRelaxed(mainLogFilePtr); // atomically set the logFile
303 #else
304     logFileFile.store(mainLogFilePtr); // atomically set the logFile
305 #endif
306 #endif
307 
308     // Windows platform plugins DLL hell fix
309     QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
310     a->addLibraryPath("platforms");
311 
312     qDebug() << "commit: " << GIT_VERSION;
313 
314     QString profileName;
315     bool autoLogin = settings.getAutoLogin();
316 
317     uint32_t ipcDest = 0;
318     bool doIpc = ipc.isAttached();
319     QString eventType, firstParam;
320     if (parser.isSet("p")) {
321         profileName = parser.value("p");
322         if (!Profile::exists(profileName)) {
323             qWarning() << "-p profile" << profileName + ".tox"
324                        << "doesn't exist, opening login screen";
325             doIpc = false;
326             autoLogin = false;
327         } else {
328             ipcDest = Settings::makeProfileId(profileName);
329             autoLogin = true;
330         }
331     } else if (parser.isSet("l")) {
332         doIpc = false;
333         autoLogin = false;
334     } else {
335         profileName = settings.getCurrentProfile();
336     }
337 
338     if (parser.positionalArguments().empty()) {
339         eventType = "activate";
340     } else {
341         firstParam = parser.positionalArguments()[0];
342         // Tox URIs. If there's already another qTox instance running, we ask it to handle the URI
343         // and we exit
344         // Otherwise we start a new qTox instance and process it ourselves
345         if (firstParam.startsWith("tox:")) {
346             eventType = "uri";
347         } else if (firstParam.endsWith(".tox")) {
348             eventType = "save";
349         } else {
350             qCritical() << "Invalid argument";
351             return EXIT_FAILURE;
352         }
353     }
354 
355     if (doIpc && !ipc.isCurrentOwner()) {
356         time_t event = ipc.postEvent(eventType, firstParam.toUtf8(), ipcDest);
357         // If someone else processed it, we're done here, no need to actually start qTox
358         if (ipc.waitUntilAccepted(event, 2)) {
359             if (eventType == "activate") {
360                 qDebug()
361                     << "Another qTox instance is already running. If you want to start a second "
362                        "instance, please open login screen (qtox -l) or start with a profile (qtox "
363                        "-p <profile name>).";
364             } else {
365                 qDebug() << "Event" << eventType << "was handled by other client.";
366             }
367             return EXIT_SUCCESS;
368         }
369     }
370 
371     if (!Settings::verifyProxySettings(parser)) {
372         return -1;
373     }
374 
375     // TODO(sudden6): remove once we get rid of Nexus
376     Nexus& nexus = Nexus::getInstance();
377     // TODO(kriby): Consider moving application initializing variables into a globalSettings object
378     //  note: Because Settings is shouldering global settings as well as model specific ones it
379     //  cannot be integrated into a central model object yet
380     nexus.setSettings(&settings);
381 
382     // Autologin
383     // TODO (kriby): Shift responsibility of linking views to model objects from nexus
384     // Further: generate view instances separately (loginScreen, mainGUI, audio)
385     Profile* profile = nullptr;
386     if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) {
387         profile = Profile::loadProfile(profileName, &parser);
388         if (!profile) {
389             QMessageBox::information(nullptr, QObject::tr("Error"),
390                                      QObject::tr("Failed to load profile automatically."));
391         }
392     }
393     if (profile) {
394         nexus.bootstrapWithProfile(profile);
395     } else {
396         nexus.setParser(&parser);
397         int returnval = nexus.showLogin(profileName);
398         if (returnval == QDialog::Rejected) {
399             return -1;
400         }
401     }
402 
403     if (ipc.isAttached()) {
404         // Start to accept Inter-process communication
405         ipc.registerEventHandler("uri", &toxURIEventHandler);
406         ipc.registerEventHandler("save", &toxSaveEventHandler);
407         ipc.registerEventHandler("activate", &toxActivateEventHandler);
408     }
409 
410     // Event was not handled by already running instance therefore we handle it ourselves
411     if (eventType == "uri")
412         handleToxURI(firstParam.toUtf8());
413     else if (eventType == "save")
414         handleToxSave(firstParam.toUtf8());
415 
416     QObject::connect(a.get(), &QApplication::aboutToQuit, cleanup);
417 
418     // Run
419     int errorcode = a->exec();
420 
421     qDebug() << "Exit with status" << errorcode;
422     return errorcode;
423 }
424 
425 // Missing in libxccrt.so function __cxa_deleted_virtual, see FreeBSD Bug#200863
__cxa_deleted_virtual()426 extern "C" void __cxa_deleted_virtual()
427 {
428   abort();
429 }
430 
431