diff --git a/.gitignore b/.gitignore index 8b89da39..2b4b5cfb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,15 +3,19 @@ macOSPackage/ # C++ objects and libs -*.slo -*.lo -*.o *.a +*.dll +*.dmg +*.dylib +*.exe +*.exp *.la *.lai +*.lib +*.lo +*.o +*.slo *.so -*.dll -*.dylib # Qt-es /.qmake.cache @@ -47,4 +51,5 @@ CMakeLists.txt.user* *.*~ # Certificates -*.p12 \ No newline at end of file +*.p12 + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..6c8fa28b --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,27 @@ +variables: + GIT_STRATEGY: clone + +stages: + - build + +build-windows: + stage: build + tags: + - windows + script: + - cmd.exe /k "deploy\windows-env.bat && cd deploy && windows.bat" + artifacts: + name: artifacts-windows + paths: + - AmneziaVPN.exe + +build-macos: + stage: build + tags: + - macos + script: + - cd deploy && ./macos.sh + artifacts: + name: artifacts-macos + paths: + - AmneziaVPN.dmg diff --git a/3rd/QtSsh b/3rd/QtSsh deleted file mode 160000 index a34ded6e..00000000 --- a/3rd/QtSsh +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a34ded6e69f0a5ab492a17c1edf898104448d4d3 diff --git a/AmneziaVPN.pro b/AmneziaVPN.pro new file mode 100644 index 00000000..20bf4844 --- /dev/null +++ b/AmneziaVPN.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = client service platform diff --git a/client/AmniziaVPN.pro b/client/AmniziaVPN.pro deleted file mode 100644 index b2acfea6..00000000 --- a/client/AmniziaVPN.pro +++ /dev/null @@ -1,76 +0,0 @@ -QT += widgets core gui network xml - -TARGET = AmneziaVPN -TEMPLATE = app -#CONFIG += console - -DEFINES += QT_DEPRECATED_WARNINGS - -HEADERS += \ - core/router.h \ - debug.h \ - defines.h \ - runguard.h \ - ui/Controls/SlidingStackedWidget.h \ - ui/mainwindow.h \ - -SOURCES += \ - core/router.cpp \ - debug.cpp \ - main.cpp \ - runguard.cpp \ - ui/Controls/SlidingStackedWidget.cpp \ - ui/mainwindow.cpp \ - - -FORMS += ui/mainwindow.ui - -RESOURCES += \ - resources.qrc - -TRANSLATIONS = \ - translations/amneziavpn.en.ts \ - translations/amneziavpn.ru.ts - -win32 { - -OTHER_FILES += platform_win/vpnclient.rc -RC_FILE = platform_win/vpnclient.rc - -HEADERS += -SOURCES += - -#CONFIG -= embed_manifest_exe -#DEFINES += _CRT_SECURE_NO_WARNINGS VPNCLIENT_TAPSIGNED -#QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\'\" - -VERSION = 1.1.1.1 -QMAKE_TARGET_COMPANY = "AmneziaVPN" -QMAKE_TARGET_PRODUCT = "AmneziaVPN" - -#CONFIG -= embed_manifest_exe - -LIBS += \ - -luser32 \ - -lrasapi32 \ - -lshlwapi \ - -liphlpapi \ - -lws2_32 \ - -liphlpapi \ - -lgdi32 - -#LIBS += -L$$PWD/../../../../../../../OpenSSL-Win32/lib/ -llibcrypto - -#MT_PATH = \"C:/Program Files (x86)/Microsoft SDKs/Windows/v7.1A/bin/x64/mt.exe\" -#WIN_PWD = $$replace(PWD, /, \\) -#OUT_PWD_WIN = $$replace(OUT_PWD, /, \\) - -#!win32-g++: QMAKE_POST_LINK = "$$MT_PATH -manifest $$quote($$WIN_PWD\\platform_win\\$$basename(TARGET).exe.manifest) -outputresource:$$quote($$OUT_PWD_WIN\\$(DESTDIR_TARGET);1)" -# else: QMAKE_POST_LINK = "$$MT_PATH -manifest $$PWD/platform_win/$$basename(TARGET).exe.manifest -outputresource:$$OUT_PWD/$(DESTDIR_TARGET)" - -} - - -macx { -ICON = $$PWD/images/app.icns -} diff --git a/client/client.pro b/client/client.pro new file mode 100644 index 00000000..76cf33d8 --- /dev/null +++ b/client/client.pro @@ -0,0 +1,67 @@ +QT += widgets core gui network xml + +TARGET = AmneziaVPN +TEMPLATE = app +#CONFIG += console + +DEFINES += QT_DEPRECATED_WARNINGS + +HEADERS += \ + core/router.h \ + debug.h \ + defines.h \ + runguard.h \ + ui/Controls/SlidingStackedWidget.h \ + ui/mainwindow.h \ + +SOURCES += \ + core/router.cpp \ + debug.cpp \ + main.cpp \ + runguard.cpp \ + ui/Controls/SlidingStackedWidget.cpp \ + ui/mainwindow.cpp \ + + +FORMS += ui/mainwindow.ui + +RESOURCES += \ + resources.qrc + +TRANSLATIONS = \ + translations/amneziavpn.en.ts \ + translations/amneziavpn.ru.ts + +CONFIG(release, debug|release) { + DESTDIR = $$PWD/../../AmneziaVPN-build/client/release + MOC_DIR = $$DESTDIR + OBJECTS_DIR = $$DESTDIR + RCC_DIR = $$DESTDIR +} + +win32 { + OTHER_FILES += platform_win/vpnclient.rc + RC_FILE = platform_win/vpnclient.rc + + HEADERS += + SOURCES += + + VERSION = 1.1.1.1 + QMAKE_TARGET_COMPANY = "AmneziaVPN" + QMAKE_TARGET_PRODUCT = "AmneziaVPN" + + LIBS += \ + -luser32 \ + -lrasapi32 \ + -lshlwapi \ + -liphlpapi \ + -lws2_32 \ + -liphlpapi \ + -lgdi32 + + #LIBS += -L$$PWD/../../../../../../../OpenSSL-Win32/lib/ -llibcrypto +} + +macx { + ICON = $$PWD/images/app.icns +} diff --git a/client/debug.cpp b/client/debug.cpp index f2b83178..3103b947 100644 --- a/client/debug.cpp +++ b/client/debug.cpp @@ -1,126 +1,68 @@ -#include "debug.h" - +#include +#include #include #include -#include -#include -#include +#include -#define LOGS_DIR "logs" -#define CLIENT_LOG_SUFFIX "amneziavpn.log" -#define MAX_LOG_FILES 5 -#define FORMAT_STRING "yyyy-MM-dd--hh-mm-ss" +#include "debug.h" +#include "defines.h" -QFile Debug::m_clientLog; -QTextStream Debug::m_clientLogTextStream; -QString Debug::m_clientLogName; +QFile Debug::m_file; +QTextStream Debug::m_textStream; +QString Debug::m_logFileName; -void debugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { - // Skip Qt warnings - if (msg.contains("known incorrect sRGB profile")) return; - if (msg.contains("libpng warning")) return; - if (msg.contains("Unknown property ffont")) return; + if (msg.simplified().isEmpty()) { + return; + } - Debug::m_clientLogTextStream << qFormatLogMessage(type, context, msg) << endl << flush; + // Temporally disabled + if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap")) { + return; + } + + Debug::m_textStream << qFormatLogMessage(type, context, msg) << endl << flush; } bool Debug::init() { - QString path = qApp->applicationDirPath(); + QString path = logsDir(); QDir appDir(path); - - // init function is called before exec application, so data location folder may not exist - if (!appDir.exists()) - { - qWarning() << "Debug: init: log directory doesn't exist or mkpath command error:" << path; + if (!appDir.mkpath(path)) { return false; } - if (!appDir.exists(LOGS_DIR) && !appDir.mkdir(LOGS_DIR)) - { - qWarning() << "Debug: init: log directory doesn't exist or mkdir command error:" << path << LOGS_DIR; - return false; - } + m_logFileName = QString("%1.log").arg(APPLICATION_NAME); - if (!appDir.cd(LOGS_DIR)) - { - qWarning() << "Debug: init: cd command error:" << path << LOGS_DIR; - return false; - } - - //delete older log files - auto clientLogsCount = 0; - QFileInfoList logDirList = appDir.entryInfoList( - QDir::Files | QDir::NoDotAndDotDot, - QDir::Time); - for (auto fileInfo : logDirList) - { - if ((fileInfo.completeSuffix() == CLIENT_LOG_SUFFIX && - ++clientLogsCount > MAX_LOG_FILES)) - { - appDir.remove(fileInfo.filePath()); - } - } - - //prepare log file names - auto currentDateTime = QDateTime::currentDateTime().toString(FORMAT_STRING); - - m_clientLogName = QString("%1.%2").arg(currentDateTime).arg(CLIENT_LOG_SUFFIX); - return init(appDir); -} - -bool Debug::init(QDir& appDir) -{ - Q_UNUSED(appDir) qSetMessagePattern("[%{time}|%{type}] %{message}"); -#ifndef QT_DEBUG - m_clientLog.setFileName(appDir.filePath(m_clientLogName)); - if (!m_clientLog.open(QIODevice::WriteOnly | QIODevice::Append)) { - qWarning() << "Debug::init - failed to open m_clientLog file:" << m_clientLogName; + m_file.setFileName(appDir.filePath(m_logFileName)); + if (!m_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + qWarning() << "Cannot open log file:" << m_logFileName; return false; } - m_clientLog.setTextModeEnabled(true); - m_clientLogTextStream.setDevice(&m_clientLog); + m_file.setTextModeEnabled(true); + m_textStream.setDevice(&m_file); qInstallMessageHandler(debugMessageHandler); -#else -#ifdef DEBUG_OUTPUT_TWO_DIRECTIONAL - m_clientLog.setFileName(appDir.filePath(m_clientLogName)); - if (!m_clientLog.open(QIODevice::WriteOnly | QIODevice::Append)) - return false; - m_clientLog.setTextModeEnabled(true); - m_clientLogTextStream.setDevice(&m_clientLog); - defaultMessageHandler = qInstallMessageHandler(debugMessageHandler); -#endif -#endif return true; } -QString Debug::getPathToClientLog() +QString Debug::logsDir() { - QString path = qApp->applicationDirPath(); - QDir appDir(path); - if (!appDir.exists(LOGS_DIR) || !appDir.cd(LOGS_DIR)) - { - qWarning() << "Debug: log directory doesn't exist or cd command error:" << path; - return ""; - } - - return appDir.filePath(m_clientLogName); + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/logs"; } -QString Debug::getPathToLogsDir() +bool Debug::openLogsFolder() { - QString path = qApp->applicationDirPath(); - QDir appDir(path); - if (!appDir.exists(LOGS_DIR) || !appDir.cd(LOGS_DIR)) - { - qWarning() << "Debug: log directory doesn't exist or cd command error" << path; - return ""; + QString path = logsDir(); +#ifdef Q_OS_WIN + path = "file:///" + path; +#endif + if (!QDesktopServices::openUrl(QUrl::fromLocalFile(path))) { + qWarning() << "Can't open url:" << path; + return false; } - return appDir.absolutePath(); + return true; } - - diff --git a/client/debug.h b/client/debug.h index b51aabc2..aabbea4d 100644 --- a/client/debug.h +++ b/client/debug.h @@ -9,19 +9,16 @@ class Debug { public: + static QString logsDir(); static bool init(); - static QString getPathToClientLog(); - static QString getPathToLogsDir(); + static bool openLogsFolder(); private: - static bool init(QDir& appDir); + static QFile m_file; + static QTextStream m_textStream; + static QString m_logFileName; -private: - static QFile m_clientLog; - static QTextStream m_clientLogTextStream; - static QString m_clientLogName; - - friend void debugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); + friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); }; #endif // DEBUG_H diff --git a/client/defines.h b/client/defines.h index aa1d863b..edf60b10 100644 --- a/client/defines.h +++ b/client/defines.h @@ -2,6 +2,7 @@ #define DEFINES_H #define APPLICATION_NAME "AmneziaVPN" +#define SERVICE_NAME "AmneziaVPN-service" #define ORGANIZATION_NAME "AmneziaVPN.ORG" #endif // DEFINES_H diff --git a/client/main.cpp b/client/main.cpp index 68676ec3..840ada2e 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -51,7 +51,6 @@ int main(int argc, char *argv[]) app.setApplicationName(APPLICATION_NAME); app.setOrganizationName(ORGANIZATION_NAME); app.setApplicationDisplayName(APPLICATION_NAME); - app.setApplicationVersion("1.0.0.0"); //app.setQuitOnLastWindowClosed(false); @@ -60,17 +59,8 @@ int main(int argc, char *argv[]) parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption debugToConsoleOption("d", QCoreApplication::translate("main", "Output to console instead log file")); - parser.addOption(debugToConsoleOption); - parser.process(app); // Process the actual command line arguments given by the user - - bool debugToConsole = parser.isSet(debugToConsoleOption); - - qDebug() << "Set output to console: " << debugToConsole; - if (!debugToConsole) { - if (!Debug::init()) { - qCritical() << "Initialization of debug subsystem failed"; - } + if (!Debug::init()) { + qCritical() << "Initialization of debug subsystem failed"; } QFont f("Lato Regular", 10); diff --git a/client/platform_win/amnezia-client.exe.manifest b/client/platform_win/amnezia-client.exe.manifest deleted file mode 100644 index 418f78d7..00000000 --- a/client/platform_win/amnezia-client.exe.manifest +++ /dev/null @@ -1,15 +0,0 @@ - - - - amneziavpn - - - - - - - - - diff --git a/client/ui/mainwindow.cpp b/client/ui/mainwindow.cpp index e1a7a28d..4ce6bdf5 100644 --- a/client/ui/mainwindow.cpp +++ b/client/ui/mainwindow.cpp @@ -1,3 +1,8 @@ +#include +#include + +#include "debug.h" +#include "defines.h" #include "mainwindow.h" #include "ui_mainwindow.h" @@ -5,13 +10,71 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi { ui->setupUi(this); - // Post initialization + // Post initialization ui->widget_tittlebar->hide(); ui->stackedWidget_main->setCurrentIndex(2); + + connect(ui->pushButton_blocked_list, SIGNAL(clicked(bool)), this, SLOT(onPushButtonBlockedListClicked(bool))); + connect(ui->pushButton_connect, SIGNAL(clicked(bool)), this, SLOT(onPushButtonConnectClicked(bool))); + connect(ui->pushButton_settings, SIGNAL(clicked(bool)), this, SLOT(onPushButtonSettingsClicked(bool))); + + connect(ui->pushButton_back_from_sites, SIGNAL(clicked(bool)), this, SLOT(onPushButtonBackFromSitesClicked(bool))); + connect(ui->pushButton_back_from_settings, SIGNAL(clicked(bool)), this, SLOT(onPushButtonBackFromSettingsClicked(bool))); + + setFixedSize(width(),height()); + + qDebug() << APPLICATION_NAME; + qDebug() << "Started"; } MainWindow::~MainWindow() { delete ui; + + qDebug() << "Closed"; } +void MainWindow::goToIndex(int index) +{ + ui->stackedWidget_main->setCurrentIndex(index); +} + +void MainWindow::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_L: + if (!Debug::openLogsFolder()) { + QMessageBox::warning(this, APPLICATION_NAME, tr("Cannot open logs folder!")); + } + break; + default: + ; + } +} + +void MainWindow::onPushButtonBackFromSettingsClicked(bool) +{ + goToIndex(2); +} + +void MainWindow::onPushButtonBackFromSitesClicked(bool) +{ + goToIndex(2); +} + +void MainWindow::onPushButtonBlockedListClicked(bool) +{ + goToIndex(3); +} + +void MainWindow::onPushButtonSettingsClicked(bool) +{ + goToIndex(4); +} + +void MainWindow::onPushButtonConnectClicked(bool) +{ + qDebug() << "onPushButtonConnectClicked"; +} + + diff --git a/client/ui/mainwindow.h b/client/ui/mainwindow.h index d593abf6..7d247d42 100644 --- a/client/ui/mainwindow.h +++ b/client/ui/mainwindow.h @@ -22,8 +22,19 @@ public slots: private slots: + void onPushButtonBlockedListClicked(bool clicked); + void onPushButtonConnectClicked(bool clicked); + void onPushButtonSettingsClicked(bool clicked); + + void onPushButtonBackFromSettingsClicked(bool clicked); + void onPushButtonBackFromSitesClicked(bool clicked); + +protected: + void keyPressEvent(QKeyEvent* event); private: + void goToIndex(int index); + Ui::MainWindow *ui; }; diff --git a/client/ui/mainwindow.ui b/client/ui/mainwindow.ui index 6a26945c..2990f1e1 100644 --- a/client/ui/mainwindow.ui +++ b/client/ui/mainwindow.ui @@ -268,6 +268,9 @@ QStackedWidget QWidget { } + + 2 + diff --git a/deploy/data/macos/AmneziaVPN.plist b/deploy/data/macos/AmneziaVPN.plist new file mode 100644 index 00000000..8dabbc9e --- /dev/null +++ b/deploy/data/macos/AmneziaVPN.plist @@ -0,0 +1,26 @@ + + + + + Label + AmneziaVPN-service + ProgramArguments + + /Applications/AmneziaVPN.app/Contents/MacOS/AmneziaVPN-service + + KeepAlive + + Sockets + + Listeners + + SockServiceName + 5959 + SockType + stream + SockFamily + IPv4 + + + + diff --git a/deploy/data/macos/openvpn b/deploy/data/macos/openvpn new file mode 100644 index 00000000..f1c08e93 Binary files /dev/null and b/deploy/data/macos/openvpn differ diff --git a/deploy/data/macos/post_install.sh b/deploy/data/macos/post_install.sh new file mode 100644 index 00000000..95c1a570 --- /dev/null +++ b/deploy/data/macos/post_install.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +APP_NAME=AmneziaVPN +PLIST_NAME=$APP_NAME.plist +LAUNCH_DAEMONS_PLIST_NAME=/Library/LaunchDaemons/$PLIST_NAME +LOG_FOLDER=/var/log/$APP_NAME +LOG_FILE="$LOG_FOLDER/post-install.log" +APP_PATH=/Applications/$APP_NAME.app + +if launchctl list "$APP_NAME-service" &> /dev/null; then + launchctl unload $LAUNCH_DAEMONS_PLIST_NAME + rm -f $LAUNCH_DAEMONS_PLIST_NAME +fi + +tar xzf $APP_PATH/$APP_NAME.tar.gz -C $APP_PATH +rm -f $APP_PATH/$APP_NAME.tar.gz + +rm -rf $LOG_FOLDER +mkdir -p $LOG_FOLDER + +echo "`date` Script started" > $LOG_FILE + +killall -9 $APP_NAME-service 2>> $LOG_FILE + +mv -f $APP_PATH/$PLIST_NAME $LAUNCH_DAEMONS_PLIST_NAME 2>> $LOG_FILE +chown root:wheel $LAUNCH_DAEMONS_PLIST_NAME +launchctl load $LAUNCH_DAEMONS_PLIST_NAME + +echo "`date` Service status: $?" >> $LOG_FILE +echo "`date` Script finished" >> $LOG_FILE + +rm -- "$0" diff --git a/deploy/data/macos/post_uninstall.sh b/deploy/data/macos/post_uninstall.sh new file mode 100644 index 00000000..6e8e9fa8 --- /dev/null +++ b/deploy/data/macos/post_uninstall.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +APP_NAME=AmneziaVPN +PLIST_NAME=$APP_NAME.plist +LAUNCH_DAEMONS_PLIST_NAME=/Library/LaunchDaemons/$PLIST_NAME + +if launchctl list "$APP_NAME-service" &> /dev/null; then + launchctl unload $LAUNCH_DAEMONS_PLIST_NAME + rm -f $LAUNCH_DAEMONS_PLIST_NAME +fi + +rm -rf "$HOME/Library/Application Support/$APP_NAME" +rm -rf /var/log/$APP_NAME +rm -rf /Applications/$APP_NAME.app/Contents diff --git a/deploy/windows/openvpn/license.txt b/deploy/data/windows/openvpn/license.txt similarity index 100% rename from deploy/windows/openvpn/license.txt rename to deploy/data/windows/openvpn/license.txt diff --git a/deploy/windows/tap/license.txt b/deploy/data/windows/tap/license.txt similarity index 100% rename from deploy/windows/tap/license.txt rename to deploy/data/windows/tap/license.txt diff --git a/deploy/windows/tap/x32/OemVista.inf b/deploy/data/windows/tap/x32/OemVista.inf similarity index 100% rename from deploy/windows/tap/x32/OemVista.inf rename to deploy/data/windows/tap/x32/OemVista.inf diff --git a/deploy/windows/tap/x32/tap0901.cat b/deploy/data/windows/tap/x32/tap0901.cat similarity index 100% rename from deploy/windows/tap/x32/tap0901.cat rename to deploy/data/windows/tap/x32/tap0901.cat diff --git a/deploy/windows/tap/x32/tap0901.sys b/deploy/data/windows/tap/x32/tap0901.sys similarity index 100% rename from deploy/windows/tap/x32/tap0901.sys rename to deploy/data/windows/tap/x32/tap0901.sys diff --git a/deploy/windows/tap/x64/OemVista.inf b/deploy/data/windows/tap/x64/OemVista.inf similarity index 100% rename from deploy/windows/tap/x64/OemVista.inf rename to deploy/data/windows/tap/x64/OemVista.inf diff --git a/deploy/windows/tap/x64/tap0901.cat b/deploy/data/windows/tap/x64/tap0901.cat similarity index 100% rename from deploy/windows/tap/x64/tap0901.cat rename to deploy/data/windows/tap/x64/tap0901.cat diff --git a/deploy/windows/tap/x64/tap0901.sys b/deploy/data/windows/tap/x64/tap0901.sys similarity index 100% rename from deploy/windows/tap/x64/tap0901.sys rename to deploy/data/windows/tap/x64/tap0901.sys diff --git a/deploy/installer/config/controlscript.js b/deploy/installer/config/controlscript.js new file mode 100644 index 00000000..d5ea5c54 --- /dev/null +++ b/deploy/installer/config/controlscript.js @@ -0,0 +1,329 @@ +var requestToQuitFromApp = false; +var updaterCompleted = 0; +var desktopAppProcessRunning = false; +var appInstalledUninstallerPath; + +function appName() +{ + return installer.value("Name"); +} + +function appAlreadyInstalled() +{ + return installer.fileExists(appInstalledUninstallerPath); +} + +function appExecutableFileName() +{ + if (runningOnWindows()) { + return appName() + ".exe"; + } else { + return appName(); + } +} + +function appInstalled() +{ + if (runningOnWindows()) { + appInstalledUninstallerPath = installer.value("TargetDir") + "\\maintenancetool.exe"; + } else if (runningOnMacOS()){ + appInstalledUninstallerPath = "/Applications/" + appName() + ".app/maintenancetool.app/Contents/MacOS/maintenancetool"; + } + + return appAlreadyInstalled(); +} + +function endsWith(str, suffix) +{ + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} + +function runningOnWindows() +{ + return (installer.value("os") === "win"); +} + +function runningOnMacOS() +{ + return (installer.value("os") === "mac"); +} + +function sleep(miliseconds) { + var currentTime = new Date().getTime(); + while (currentTime + miliseconds >= new Date().getTime()) {} +} + +function raiseInstallerWindow() +{ + if (!runningOnMacOS()) { + return; + } + + var result = installer.execute("/bin/bash", ["-c", "ps -A | grep -m1 '" + appName() + "' | awk '{print $1}'"]); + if (Number(result[0]) > 0) { + var arg = 'tell application \"System Events\" ' + + '\n set frontmost of the first process whose unix id is ' + Number(result[0]) + ' to true ' + + '\n end tell' + + '\n '; + installer.execute("osascript", ["-e", arg]); + } +} + +function appProcessIsRunning() +{ + if (runningOnWindows()) { + var cmdArgs = ["/FI", "WINDOWTITLE eq " + appName()]; + var result = installer.execute("tasklist", cmdArgs); + + if ( Number(result[1]) === 0 ) { + if (result[0].indexOf(appExecutableFileName()) !== -1) { + return true; + } + } + } else { + return checkProccesIsRunning("pgrep -x '" + appName() + "'") + } + + return false; +} + +function checkProccesIsRunning(arg) +{ + var cmdArgs = ["-c", arg]; + var result = installer.execute("/bin/bash", cmdArgs); + var resultArg1 = Number(result[0]) + if (resultArg1 >= 3) { + return true; + } + return false; +} + +function requestToQuit(installer,gui) +{ + requestToQuitFromApp = true; + + installer.setDefaultPageVisible(QInstaller.IntroductionPage, false); + installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); + installer.setDefaultPageVisible(QInstaller.ComponentSelection, false); + installer.setDefaultPageVisible(QInstaller.LicenseCheck, false); + installer.setDefaultPageVisible(QInstaller.StartMenuSelection, false); + installer.setDefaultPageVisible(QInstaller.ReadyForInstallation, false); + installer.setDefaultPageVisible(QInstaller.PerformInstallation, false); + installer.setDefaultPageVisible(QInstaller.FinishedPage, false); + + gui.clickButton(buttons.NextButton); + gui.clickButton(buttons.FinishButton); + gui.clickButton(buttons.CancelButton); + + if (runningOnWindows()) { + installer.setCancelled(); + } +} + + +Controller.prototype.PerformInstallationPageCallback = function() +{ + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.LicenseAgreementPageCallback = function() +{ + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.FinishedPageCallback = function () +{ + if (desktopAppProcessRunning) { + gui.clickButton(buttons.FinishButton); + } else if (installer.isUpdater()) { + installer.autoAcceptMessageBoxes(); + gui.clickButton(buttons.FinishButton); + } +} + +Controller.prototype.RestartPageCallback = function () +{ + updaterCompleted = 1; + gui.clickButton(buttons.FinishButton); +} + +Controller.prototype.StartMenuDirectoryPageCallback = function() +{ + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.ComponentSelectionPageCallback = function() +{ + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.ReadyForInstallationPageCallback = function() +{ + if (installer.isUpdater()) { + gui.clickButton(buttons.CommitButton); + } +} + +Controller.prototype.TargetDirectoryPageCallback = function () +{ + var widget = gui.pageById(QInstaller.TargetDirectory); + + if (widget !== null) { + widget.BrowseDirectoryButton.clicked.disconnect(onBrowseButtonClicked); + widget.BrowseDirectoryButton.clicked.connect(onBrowseButtonClicked); + + gui.clickButton(buttons.NextButton); + } +} + +Controller.prototype.IntroductionPageCallback = function () +{ + var widget = gui.currentPageWidget(); + if (installer.isUpdater() && updaterCompleted === 1) { + gui.clickButton(buttons.FinishButton); + gui.clickButton(buttons.CancelButton); + return; + } + + if (installer.isUninstaller()) { + if (widget !== null) { + widget.findChild("PackageManagerRadioButton").visible = false; + widget.findChild("UpdaterRadioButton").visible = false; + } + } + + if (installer.isUpdater()) { + gui.clickButton(buttons.NextButton); + } +} + +onBrowseButtonClicked = function() +{ + var widget = gui.pageById(QInstaller.TargetDirectory); + if (widget !== null) { + if (runningOnWindows()) { + // On Windows we are appending \ if selected path don't ends with + var targetDir = widget.TargetDirectoryLineEdit.text; + if (! endsWith(targetDir, appName())) { + targetDir = targetDir + "\\" + appName(); + } + installer.setValue("TargetDir", targetDir); + widget.TargetDirectoryLineEdit.setText(installer.value("TargetDir")); + } + } +} + +onNextButtonClicked = function() +{ + var widget = gui.pageById(QInstaller.TargetDirectory); + if (widget !== null) { + installer.setValue("APP_BUNDLE_TARGET_DIR", widget.TargetDirectoryLineEdit.text); + } +} + +function Controller () { + console.log("OS: %1, architecture: %2".arg(systemInfo.prettyProductName).arg(systemInfo.currentCpuArchitecture)); + + if (installer.isInstaller() || installer.isUpdater()) { + console.log("Check if app already installed: " + appInstalled()); + } + + if (runningOnWindows()) { + installer.setValue("AllUsers", "true"); + } + + if (installer.isInstaller()) { + installer.setDefaultPageVisible(QInstaller.ComponentSelection, false); + installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); + installer.setDefaultPageVisible(QInstaller.StartMenuDirectoryPage, false); + installer.setDefaultPageVisible(QInstaller.LicenseCheck, false); + + isDesktopAppProcessRunningMessageLoop(); + + if (requestToQuitFromApp === true) { + requestToQuit(installer, gui); + return; + } + + if (runningOnMacOS()) { + installer.setMessageBoxAutomaticAnswer("OverwriteTargetDirectory", QMessageBox.Yes); + } + + if (appAlreadyInstalled()) { + if (QMessageBox.Ok === QMessageBox.information("os.information", appName(), + qsTr("The application is already installed.") + " " + + qsTr("We need to remove the old installation first. Do you wish to proceed?"), + QMessageBox.Ok | QMessageBox.Cancel)) { + + if (appAlreadyInstalled()) { + var resultArray = installer.execute(appInstalledUninstallerPath); + + console.log("Uninstaller finished with code: " + resultArray[1]) + + if (Number(resultArray[1]) !== 0) { + console.log("Uninstallation aborted by user"); + installer.setCancelled(); + return; + } else { + for (var i = 0; i < 100; i++) { + sleep(100); + if (!installer.fileExists(appInstalledUninstallerPath)) { + break; + } + } + } + } + + raiseInstallerWindow(); + + } else { + console.log("Request to quit from user"); + installer.setCancelled(); + return; + } + } + + } else if (installer.isUninstaller()) { + isDesktopAppProcessRunningMessageLoop(); + + if (requestToQuitFromApp === true) { + requestToQuit(installer, gui); + return; + } + + } else if (installer.isUpdater()) { + installer.setMessageBoxAutomaticAnswer("cancelInstallation", QMessageBox.No); + installer.installationFinished.connect(function() { + gui.clickButton(buttons.NextButton); + }); + } +} + +isDesktopAppProcessRunningMessageLoop = function () +{ + if (requestToQuitFromApp === true) { + return; + } + + if (installer.isUpdater()) { + for (var i = 0; i < 400; i++) { + desktopAppProcessRunning = appProcessIsRunning(); + if (!desktopAppProcessRunning) { + break; + } + } + } + desktopAppProcessRunning = appProcessIsRunning(); + + if (desktopAppProcessRunning) { + var result = QMessageBox.warning("QMessageBox", appName() + " installer", + appName() + " is active. Close the app and press \"Retry\" button to continue installation. Press \"Abort\" button to abort the installer and exit.", + QMessageBox.Retry | QMessageBox.Abort); + if (result === QMessageBox.Retry) { + isDesktopAppProcessRunningMessageLoop(); + } else { + requestToQuitFromApp = true; + return; + } + } +} diff --git a/deploy/installer/config/macos.xml b/deploy/installer/config/macos.xml new file mode 100644 index 00000000..82edef16 --- /dev/null +++ b/deploy/installer/config/macos.xml @@ -0,0 +1,27 @@ + + + AmneziaVPN + 1.0.0 + AmneziaVPN + AmneziaVPN + AmneziaVPN + /Applications/AmneziaVPN.app + 600 + 380 + Modern + true + true + false + controlscript.js + false + true + false + true + + + https://amneziavpn.org/updates/macos + true + AmneziaVPN - repository for macOS + + + diff --git a/deploy/installer/config/windows.xml b/deploy/installer/config/windows.xml new file mode 100644 index 00000000..18728f64 --- /dev/null +++ b/deploy/installer/config/windows.xml @@ -0,0 +1,27 @@ + + + AmneziaVPN + 1.0.0 + AmneziaVPN + AmneziaVPN + AmneziaVPN + @ApplicationsDir@/AmneziaVPN + 600 + 380 + Modern + true + true + false + controlscript.js + false + true + false + true + + + https://amneziavpn.org/updates/windows + true + AmneziaVPN - repository for Windows + + + diff --git a/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js b/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js new file mode 100644 index 00000000..94128aef --- /dev/null +++ b/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js @@ -0,0 +1,115 @@ + +function appName() +{ + return installer.value("Name") +} + +function serviceName() +{ + return (appName() + "-service") +} + +function appExecutableFileName() +{ + if (runningOnWindows()) { + return appName() + ".exe"; + } else { + return appName(); + } +} + +function runningOnWindows() +{ + return (systemInfo.kernelType === "winnt"); +} + +function runningOnMacOS() +{ + return (systemInfo.kernelType === "darwin"); +} + +function vcRuntimeIsInstalled() +{ + return (installer.findPath("msvcp140.dll", [installer.value("RootDir")+ "\\Windows\\System32\\"]).length !== 0) +} + +function Component() +{ + component.loaded.connect(this, Component.prototype.componentLoaded); + installer.installationFinished.connect(this, Component.prototype.installationFinishedPageIsShown); + installer.finishButtonClicked.connect(this, Component.prototype.installationFinished); +} + +Component.prototype.componentLoaded = function () +{ + +} + +Component.prototype.installationFinishedPageIsShown = function() +{ + if (installer.isInstaller() && installer.status === QInstaller.Success) { + gui.clickButton(buttons.FinishButton); + } +} + +Component.prototype.createOperations = function() +{ + component.createOperations(); + + if (runningOnWindows()) { + + component.addOperation("CreateShortcut", "@TargetDir@/" + appExecutableFileName(), + QDesktopServices.storageLocation(QDesktopServices.DesktopLocation) + "/" + appName() + ".lnk", + "workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\" + appExecutableFileName(), "iconId=0"); + + + component.addElevatedOperation("CreateShortcut", "@TargetDir@/" + appExecutableFileName(), + installer.value("AllUsersStartMenuProgramsPath") + "/" + appName() + ".lnk", + "workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\" + appExecutableFileName(), "iconId=0"); + + if (!vcRuntimeIsInstalled()) { + component.addElevatedOperation("Execute", "@TargetDir@\\" + "vc_redist.x86.exe", "/install", "/quiet", "/norestart", "/log", "vc_redist_2017_x86.log"); + } else { + console.log("Microsoft Visual C++ 2017 Redistributable already installed"); + } + + component.addElevatedOperation("Execute", + ["sc", "create", serviceName(), "binpath=", installer.value("TargetDir").replace(/\//g, '\\') + "\\" + serviceName() + ".exe", + "start=", "auto", "depend=", "BFE/nsi"], + "UNDOEXECUTE", ["post-uninstall.exe"]); + + } else if (runningOnMacOS()) { + component.addElevatedOperation("Execute", "@TargetDir@/post_install.sh", "UNDOEXECUTE", "@TargetDir@/post_uninstall.sh"); + } +} + +Component.prototype.installationFinished = function() +{ + var command = ""; + var args = []; + + if ((installer.status === QInstaller.Success) && (installer.isInstaller() || installer.isUpdater())) { + + if (!installer.gainAdminRights()) { + console.log("Fatal error! Cannot get admin rights!") + return + } + + if (runningOnWindows()) { + command = "@TargetDir@/" + appExecutableFileName() + + var status1 = installer.execute("net", ["start", serviceName()]) + console.log(("%1 started with status: %2 ").arg(serviceName()).arg(status1)) + + var status2 = installer.execute("sc", ["failure", serviceName(), "reset=", "100", "actions=", "restart/2000/restart/2000/restart/2000"]) + console.log(("Changed settings for %1 with status: %2 ").arg(serviceName()).arg(status2)) + + } else if (runningOnMacOS()) { + command = "/Applications/" + appName() + ".app/Contents/MacOS/" + appName(); + } + + installer.dropAdminRights() + + processStatus = installer.executeDetached(command, args, installer.value("TargetDir")); + } +} diff --git a/deploy/installer/packages/org.amneziavpn.package/meta/package.xml b/deploy/installer/packages/org.amneziavpn.package/meta/package.xml new file mode 100644 index 00000000..a5db6b61 --- /dev/null +++ b/deploy/installer/packages/org.amneziavpn.package/meta/package.xml @@ -0,0 +1,12 @@ + + + AmneziaVPN + Installation package for AmneziaVPN + 1.0.0 + 1970-01-01 + true + true + true + + + diff --git a/deploy/macos.sh b/deploy/macos.sh new file mode 100644 index 00000000..32659b7e --- /dev/null +++ b/deploy/macos.sh @@ -0,0 +1,58 @@ +#!/bin/bash -ex + + +QT_BIN_DIR='/Users/admin/Qt/5.14.2/clang_64/bin' +QIF_BIN_DIR='/Users/admin/Qt/Tools/QtInstallerFramework/4.0/bin' + +APP_NAME=AmneziaVPN +APP_FILENAME=$APP_NAME.app +APP_DOMAIN=org.amneziavpn.package +PLIST_NAME=$APP_NAME.plist + +LAUNCH_DIR=$(pwd) +TOP_DIR=$LAUNCH_DIR/.. +RELEASE_DIR=$TOP_DIR/../$APP_NAME-build +OUT_APP_DIR=$RELEASE_DIR/client/release +BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME +DEPLOY_DATA_DIR=$LAUNCH_DIR/data/macos +INSTALLER_DATA_DIR=$RELEASE_DIR/installer/packages/$APP_DOMAIN/data + +PRO_FILE_PATH=$TOP_DIR/$APP_NAME.pro +QMAKE_STASH_FILE=$TOP_DIR/.qmake_stash +TARGET_FILENAME=$TOP_DIR/$APP_NAME.dmg + +cleanBuild() +{ + rm -rf $RELEASE_DIR + rm -rf $QMAKE_STASH_FILE +} + + +cleanBuild + +cd $TOP_DIR +$QT_BIN_DIR/qmake $PRO_FILE_PATH 'CONFIG+=release CONFIG+=x86_64' +make -j `sysctl -n hw.ncpu` + +$QT_BIN_DIR/macdeployqt $OUT_APP_DIR/$APP_FILENAME -always-overwrite +cp -av $RELEASE_DIR/server/release/$APP_NAME-service.app/Contents/macOS/$APP_NAME-service $BUNDLE_DIR/Contents/macOS +cp -av $LAUNCH_DIR/data/macos/openvpn $BUNDLE_DIR/Contents/macOS + +mkdir -p $INSTALLER_DATA_DIR +cp -av $LAUNCH_DIR/installer $RELEASE_DIR +cp -av $DEPLOY_DATA_DIR/post_install.sh $INSTALLER_DATA_DIR/post_install.sh +cp -av $DEPLOY_DATA_DIR/post_uninstall.sh $INSTALLER_DATA_DIR/post_uninstall.sh +cp -av $DEPLOY_DATA_DIR/$PLIST_NAME $INSTALLER_DATA_DIR/$PLIST_NAME + +cd $BUNDLE_DIR +tar czf $INSTALLER_DATA_DIR/$APP_NAME.tar.gz ./ + +cd $RELEASE_DIR/installer +$QIF_BIN_DIR/binarycreator --offline-only -v -c config/macos.xml -p packages -f $APP_NAME +hdiutil create -volname $APP_NAME -srcfolder $APP_NAME.app -ov -format UDZO $TARGET_FILENAME + +cleanBuild + +cd $LAUNCH_DIR + +echo "Finished, see $APP_NAME.dmg in '$TOP_DIR'" diff --git a/deploy/macos/openvpn/openvpn b/deploy/macos/openvpn/openvpn deleted file mode 100644 index 783232ba..00000000 Binary files a/deploy/macos/openvpn/openvpn and /dev/null differ diff --git a/deploy/windows-env.bat b/deploy/windows-env.bat new file mode 100644 index 00000000..349bd868 --- /dev/null +++ b/deploy/windows-env.bat @@ -0,0 +1 @@ +"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" diff --git a/deploy/windows.bat b/deploy/windows.bat new file mode 100644 index 00000000..1a1bd1d9 --- /dev/null +++ b/deploy/windows.bat @@ -0,0 +1,71 @@ + @ECHO OFF + +CHCP 1252 + +SET QT_BIN_DIR="c:\Devel\Qt\5.14.2\msvc2017\bin" +SET QIF_BIN_DIR="c:\Devel\Qt\Tools\QtInstallerFramework\4.0\bin" + +set APP_NAME=AmneziaVPN +set APP_FILENAME=%APP_NAME:"=%.exe +set APP_DOMAIN=org.amneziavpn.package +set LAUNCH_DIR=%cd% +set TOP_DIR=%LAUNCH_DIR:"=%\.. +set RELEASE_DIR=%TOP_DIR:"=%\..\%APP_NAME:"=%-build +set OUT_APP_DIR=%RELEASE_DIR:"=%\client\release +set DEPLOY_DATA_DIR=%LAUNCH_DIR:"=%\data\windows +set INSTALLER_DATA_DIR=%RELEASE_DIR:"=%\installer\packages\%APP_DOMAIN:"=%\data +set PRO_FILE_PATH=%TOP_DIR:"=%\%APP_NAME:"=%.pro +set QMAKE_STASH_FILE=%TOP_DIR:"=%\.qmake_stash +set TARGET_FILENAME=%TOP_DIR:"=%\%APP_NAME:"=%.exe + +echo "Environment:" +echo "APP_FILENAME: %APP_FILENAME%" +echo "LAUNCH_DIR: %LAUNCH_DIR%" +echo "TOP_DIR: %TOP_DIR%" +echo "RELEASE_DIR: %RELEASE_DIR%" +echo "OUT_APP_DIR: %OUT_APP_DIR%" +echo "DEPLOY_DATA_DIR: %DEPLOY_DATA_DIR%" +echo "INSTALLER_DATA_DIR: %INSTALLER_DATA_DIR%" +echo "PRO_FILE_PATH: %PRO_FILE_PATH%" +echo "QMAKE_STASH_FILE: %QMAKE_STASH_FILE%" +echo "TARGET_FILENAME: %TARGET_FILENAME%" + +echo "Cleanup..." +Rmdir /Q /S %RELEASE_DIR% +Del %QMAKE_STASH_FILE% +Del %TARGET_FILENAME% + + +cd %TOP_DIR% +"%QT_BIN_DIR:"=%\qmake" %PRO_FILE_PATH% -spec win32-msvc +set CL=/MP +nmake /A /NOLOGO +del "%OUT_APP_DIR:"=%\*.obj" +del "%OUT_APP_DIR:"=%\*.cpp" +del "%OUT_APP_DIR:"=%\*.h" +del "%OUT_APP_DIR:"=%\*.res" +del "%OUT_APP_DIR:"=%\*.o" +del "%OUT_APP_DIR:"=%\*.lib" +del "%OUT_APP_DIR:"=%\*.exp" +echo "Deploying..." +"%QT_BIN_DIR:"=%\windeployqt" --release --force --no-translations "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%" +echo "Copying deploy data..." +xcopy %DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f +copy "%RELEASE_DIR:"=%\server\release\%APP_NAME:"=%-service.exe" %OUT_APP_DIR% +copy "%RELEASE_DIR:"=%\post-uninstall\release\post-uninstall.exe" %OUT_APP_DIR% + +cd %LAUNCH_DIR% +xcopy %LAUNCH_DIR:"=%\installer %RELEASE_DIR:"=%\installer /s /e /y /i /f +mkdir %INSTALLER_DATA_DIR% + +cd %OUT_APP_DIR% +echo "Compressing data..." +"%QIF_BIN_DIR:"=%\archivegen" -c 9 %INSTALLER_DATA_DIR:"=%\%APP_NAME:"=%.7z ./ + +cd "%RELEASE_DIR:"=%\installer" +echo "Creating installer..." +"%QIF_BIN_DIR:"=%\binarycreator" --offline-only -v -c config\windows.xml -p packages -f %TARGET_FILENAME% + + +cd %LAUNCH_DIR% +echo "Finished, see %TARGET_FILENAME%" diff --git a/deploy/windows/openvpn/x32/openssl.exe b/deploy/windows/openvpn/x32/openssl.exe deleted file mode 100644 index db9967f4..00000000 Binary files a/deploy/windows/openvpn/x32/openssl.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x32/openvpn.exe b/deploy/windows/openvpn/x32/openvpn.exe deleted file mode 100644 index f9f8314c..00000000 Binary files a/deploy/windows/openvpn/x32/openvpn.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x32/openvpnserv.exe b/deploy/windows/openvpn/x32/openvpnserv.exe deleted file mode 100644 index c4bb8396..00000000 Binary files a/deploy/windows/openvpn/x32/openvpnserv.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x32/tapctl.exe b/deploy/windows/openvpn/x32/tapctl.exe deleted file mode 100644 index 9bfa320d..00000000 Binary files a/deploy/windows/openvpn/x32/tapctl.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x64/openssl.exe b/deploy/windows/openvpn/x64/openssl.exe deleted file mode 100644 index eb88c459..00000000 Binary files a/deploy/windows/openvpn/x64/openssl.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x64/openvpn.exe b/deploy/windows/openvpn/x64/openvpn.exe deleted file mode 100644 index 26576366..00000000 Binary files a/deploy/windows/openvpn/x64/openvpn.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x64/openvpnserv.exe b/deploy/windows/openvpn/x64/openvpnserv.exe deleted file mode 100644 index f4c44543..00000000 Binary files a/deploy/windows/openvpn/x64/openvpnserv.exe and /dev/null differ diff --git a/deploy/windows/openvpn/x64/tapctl.exe b/deploy/windows/openvpn/x64/tapctl.exe deleted file mode 100644 index 4aff283b..00000000 Binary files a/deploy/windows/openvpn/x64/tapctl.exe and /dev/null differ diff --git a/deploy/windows/tap/arm64/OemVista.inf b/deploy/windows/tap/arm64/OemVista.inf deleted file mode 100644 index fe3744f6..00000000 --- a/deploy/windows/tap/arm64/OemVista.inf +++ /dev/null @@ -1,191 +0,0 @@ -; **************************************************************************** -; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * -; * This program is free software; you can redistribute it and/or modify * -; * it under the terms of the GNU General Public License version 2 * -; * as published by the Free Software Foundation. * -; **************************************************************************** - -; SYNTAX CHECKER -; cd \WINDDK\3790\tools\chkinf -; chkinf c:\src\openvpn\tap-win32\i386\oemvista.inf -; OUTPUT -> file:///c:/WINDDK/3790/tools/chkinf/htm/c%23+src+openvpn+tap-win32+i386+__OemWin2k.htm - -; INSTALL/REMOVE DRIVER -; tapinstall install OemVista.inf tapoas -; tapinstall update OemVista.inf tapoas -; tapinstall remove tapoas - -;********************************************************* -; Note to Developers: -; -; If you are bundling the TAP-Windows driver with your app, -; you should try to rename it in such a way that it will -; not collide with other instances of TAP-Windows defined -; by other apps. Multiple versions of the TAP-Windows -; driver, each installed by different apps, can coexist -; on the same machine if you follow these guidelines. -; NOTE: these instructions assume you are editing the -; generated OemWin2k.inf file, not the source -; OemWin2k.inf.in file which is preprocessed by winconfig -; and uses macro definitions from settings.in. -; -; (1) Rename all tapXXXX instances in this file to -; something different (use at least 5 characters -; for this name!) -; (2) Change the "!define TAP" definition in openvpn.nsi -; to match what you changed tapXXXX to. -; (3) Change TARGETNAME in SOURCES to match what you -; changed tapXXXX to. -; (4) Change TAP_COMPONENT_ID in common.h to match what -; you changed tapXXXX to. -; (5) Change SZDEPENDENCIES in service.h to match what -; you changed tapXXXX to. -; (6) Change DeviceDescription and Provider strings. -; (7) Change PRODUCT_TAP_WIN_DEVICE_DESCRIPTION in constants.h to what you -; set DeviceDescription to. -; -;********************************************************* - -[Version] - Signature = "$Windows NT$" - CatalogFile = tap0901.cat - ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} - Provider = %Provider% - Class = Net - -; This version number should match the version -; number given in ..\version.m4. - DriverVer = 09/27/2019,9.24.2.601 - -[Strings] - DeviceDescription = "TAP-Windows Adapter V9" - Provider = "TAP-Windows Provider V9" - -;---------------------------------------------------------------- -; Manufacturer + Product Section (Done) -;---------------------------------------------------------------- -[Manufacturer] - %Provider% = tap0901, NTARM64 - -[tap0901.NTARM64] - %DeviceDescription% = tap0901.ndi, root\tap0901 ; Root enumerated - %DeviceDescription% = tap0901.ndi, tap0901 ; Legacy - -;--------------------------------------------------------------- -; Driver Section (Done) -;--------------------------------------------------------------- - -;----------------- Characteristics ------------ -; NCF_PHYSICAL = 0x04 -; NCF_VIRTUAL = 0x01 -; NCF_SOFTWARE_ENUMERATED = 0x02 -; NCF_HIDDEN = 0x08 -; NCF_NO_SERVICE = 0x10 -; NCF_HAS_UI = 0x80 -;----------------- Characteristics ------------ - -[tap0901.ndi] - CopyFiles = tap0901.driver, tap0901.files - AddReg = tap0901.reg - AddReg = tap0901.params.reg - Characteristics = 0x1 - *IfType = 53 ; IF_TYPE_PROP_VIRTUAL - *MediaType = 0x0 ; NdisMedium802_3 - *PhysicalMediaType = 0 ; NdisPhysicalMediumUnspecified - -[tap0901.ndi.Services] - AddService = tap0901, 2, tap0901.service - -[tap0901.reg] - HKR, Ndi, Service, 0, "tap0901" - HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" - HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" - HKR, , Manufacturer, 0, "%Provider%" - HKR, , ProductName, 0, "%DeviceDescription%" - -[tap0901.params.reg] - HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" - HKR, Ndi\params\MTU, Type, 0, "int" - HKR, Ndi\params\MTU, Default, 0, "1500" - HKR, Ndi\params\MTU, Optional, 0, "0" - HKR, Ndi\params\MTU, Min, 0, "100" - HKR, Ndi\params\MTU, Max, 0, "1500" - HKR, Ndi\params\MTU, Step, 0, "1" - HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" - HKR, Ndi\params\MediaStatus, Type, 0, "enum" - HKR, Ndi\params\MediaStatus, Default, 0, "0" - HKR, Ndi\params\MediaStatus, Optional, 0, "0" - HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" - HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" - HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" - HKR, Ndi\params\MAC, Type, 0, "edit" - HKR, Ndi\params\MAC, Optional, 0, "1" - HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" - HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" - HKR, Ndi\params\AllowNonAdmin, Default, 0, "1" - HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" - HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" - HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" - -;---------------------------------------------------------------- -; Service Section -;---------------------------------------------------------------- - -;---------- Service Type ------------- -; SERVICE_KERNEL_DRIVER = 0x01 -; SERVICE_WIN32_OWN_PROCESS = 0x10 -;---------- Service Type ------------- - -;---------- Start Mode --------------- -; SERVICE_BOOT_START = 0x0 -; SERVICE_SYSTEM_START = 0x1 -; SERVICE_AUTO_START = 0x2 -; SERVICE_DEMAND_START = 0x3 -; SERVICE_DISABLED = 0x4 -;---------- Start Mode --------------- - -[tap0901.service] - DisplayName = %DeviceDescription% - ServiceType = 1 - StartType = 3 - ErrorControl = 1 - LoadOrderGroup = NDIS - ServiceBinary = %12%\tap0901.sys - -;----------------------------------------------------------------- -; File Installation -;----------------------------------------------------------------- - -;----------------- Copy Flags ------------ -; COPYFLG_NOSKIP = 0x02 -; COPYFLG_NOVERSIONCHECK = 0x04 -;----------------- Copy Flags ------------ - -; SourceDisksNames -; diskid = description[, [tagfile] [, , subdir]] -; 1 = "Intel Driver Disk 1",e100bex.sys,, - -[SourceDisksNames] - 1 = %DeviceDescription%, tap0901.sys - -; SourceDisksFiles -; filename_on_source = diskID[, [subdir][, size]] -; e100bex.sys = 1,, ; on distribution disk 1 - -[SourceDisksFiles] -tap0901.sys = 1 - -[DestinationDirs] - tap0901.files = 11 - tap0901.driver = 12 - -[tap0901.files] -; TapPanel.cpl,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK -; cipsrvr.exe,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK - -[tap0901.driver] - tap0901.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK - -;--------------------------------------------------------------- -; End -;--------------------------------------------------------------- diff --git a/deploy/windows/tap/arm64/tap0901.cat b/deploy/windows/tap/arm64/tap0901.cat deleted file mode 100644 index f181c2aa..00000000 Binary files a/deploy/windows/tap/arm64/tap0901.cat and /dev/null differ diff --git a/deploy/windows/tap/arm64/tap0901.sys b/deploy/windows/tap/arm64/tap0901.sys deleted file mode 100644 index 23bda8d1..00000000 Binary files a/deploy/windows/tap/arm64/tap0901.sys and /dev/null differ diff --git a/deploy/windows/tap/arm64/tapinstall.exe b/deploy/windows/tap/arm64/tapinstall.exe deleted file mode 100644 index 5d23422a..00000000 Binary files a/deploy/windows/tap/arm64/tapinstall.exe and /dev/null differ diff --git a/deploy/windows/tap/include/tap-windows.h b/deploy/windows/tap/include/tap-windows.h deleted file mode 100644 index 42231533..00000000 --- a/deploy/windows/tap/include/tap-windows.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * TAP-Windows -- A kernel driver to provide virtual tap - * device functionality on Windows. - * - * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. - * - * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., - * and is released under the GPL version 2 (see below). This particular file - * (tap-windows.h) is also licensed using the MIT license (see COPYRIGHT.MIT). - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program (see the file COPYING included with this - * distribution); if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __TAP_WIN_H -#define __TAP_WIN_H - -/* - * ============= - * TAP IOCTLs - * ============= - */ - -#define TAP_WIN_CONTROL_CODE(request,method) \ - CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) - -/* Present in 8.1 */ - -#define TAP_WIN_IOCTL_GET_MAC TAP_WIN_CONTROL_CODE (1, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_VERSION TAP_WIN_CONTROL_CODE (2, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_MTU TAP_WIN_CONTROL_CODE (3, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_INFO TAP_WIN_CONTROL_CODE (4, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT TAP_WIN_CONTROL_CODE (5, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_SET_MEDIA_STATUS TAP_WIN_CONTROL_CODE (6, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_CONFIG_DHCP_MASQ TAP_WIN_CONTROL_CODE (7, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE (8, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT TAP_WIN_CONTROL_CODE (9, METHOD_BUFFERED) - -/* Added in 8.2 */ - -/* obsoletes TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT */ -#define TAP_WIN_IOCTL_CONFIG_TUN TAP_WIN_CONTROL_CODE (10, METHOD_BUFFERED) - -/* Control whether 802.1Q headers are added for priority */ -#define TAP_WIN_IOCTL_PRIORITY_BEHAVIOR TAP_WIN_CONTROL_CODE (11, METHOD_BUFFERED) -#define TAP_PRIORITY_BEHAVIOR_NOPRIORITY 0 -#define TAP_PRIORITY_BEHAVIOR_ENABLED 1 -#define TAP_PRIORITY_BEHAVIOR_ADDALWAYS 2 -#define TAP_PRIORITY_BEHAVIOR_MAX 2 - -/* - * ================= - * Registry keys - * ================= - */ - -#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" - -#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" - -/* - * ====================== - * Filesystem prefixes - * ====================== - */ - -#define USERMODEDEVICEDIR "\\\\.\\Global\\" -#define SYSDEVICEDIR "\\Device\\" -#define USERDEVICEDIR "\\DosDevices\\Global\\" -#define TAP_WIN_SUFFIX ".tap" - -#endif // __TAP_WIN_H diff --git a/deploy/windows/tap/x32/tapinstall.exe b/deploy/windows/tap/x32/tapinstall.exe deleted file mode 100644 index 6ffd2eb4..00000000 Binary files a/deploy/windows/tap/x32/tapinstall.exe and /dev/null differ diff --git a/deploy/windows/tap/x64/tapinstall.exe b/deploy/windows/tap/x64/tapinstall.exe deleted file mode 100644 index 6e26987f..00000000 Binary files a/deploy/windows/tap/x64/tapinstall.exe and /dev/null differ diff --git a/platform/platform.pro b/platform/platform.pro new file mode 100644 index 00000000..4ed46764 --- /dev/null +++ b/platform/platform.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +win32 { + SUBDIRS += post-uninstall +} diff --git a/platform/post-uninstall/main.cpp b/platform/post-uninstall/main.cpp new file mode 100644 index 00000000..3be66a13 --- /dev/null +++ b/platform/post-uninstall/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include "defines.h" + +bool executeProcess(const QString& cmd, const QStringList& args) +{ + QProcess process; + process.start(cmd, args); + return process.waitForFinished(); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + executeProcess("sc", QStringList() << "stop" << SERVICE_NAME); + executeProcess("sc", QStringList() << "delete" << SERVICE_NAME); + + return 0; +} diff --git a/platform/post-uninstall/post-uninstall.pro b/platform/post-uninstall/post-uninstall.pro new file mode 100644 index 00000000..a59aa9f8 --- /dev/null +++ b/platform/post-uninstall/post-uninstall.pro @@ -0,0 +1,16 @@ +TARGET = post-uninstall +TEMPLATE = app +CONFIG += console qt +QT = core + +SOURCES = \ + main.cpp + +CONFIG(release, debug|release) { + DESTDIR = $$PWD/../../../AmneziaVPN-build/post-uninstall/release + MOC_DIR = $$DESTDIR + OBJECTS_DIR = $$DESTDIR + RCC_DIR = $$DESTDIR +} + +INCLUDEPATH += "$$PWD/../../client" diff --git a/service/buildlib/buildlib.pro b/service/buildlib/buildlib.pro new file mode 100644 index 00000000..1e51cc1d --- /dev/null +++ b/service/buildlib/buildlib.pro @@ -0,0 +1,13 @@ +TEMPLATE=lib +CONFIG += qt dll qtservice-buildlib +mac:CONFIG += absolute_library_soname +win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release build_all +include(../src/qtservice.pri) +TARGET = $$QTSERVICE_LIBNAME +DESTDIR = $$QTSERVICE_LIBDIR +win32 { + DLLDESTDIR = $$[QT_INSTALL_BINS] + QMAKE_DISTCLEAN += $$[QT_INSTALL_BINS]\\$${QTSERVICE_LIBNAME}.dll +} +target.path = $$DESTDIR +INSTALLS += target diff --git a/service/common.pri b/service/common.pri new file mode 100644 index 00000000..d8487537 --- /dev/null +++ b/service/common.pri @@ -0,0 +1,10 @@ +#exists(config.pri):infile(config.pri, SOLUTIONS_LIBRARY, yes): CONFIG += qtservice-uselib +TEMPLATE += fakelib +QTSERVICE_LIBNAME = QtSolutions_Service-head +CONFIG(debug, debug|release) { + mac:QTSERVICE_LIBNAME = $$member(QTSERVICE_LIBNAME, 0)_debug + else:win32:QTSERVICE_LIBNAME = $$member(QTSERVICE_LIBNAME, 0)d +} +TEMPLATE -= fakelib +QTSERVICE_LIBDIR = $$PWD/lib +unix:qtservice-uselib:!qtservice-buildlib:QMAKE_RPATHDIR += $$QTSERVICE_LIBDIR diff --git a/service/server/main.cpp b/service/server/main.cpp new file mode 100644 index 00000000..480c80a9 --- /dev/null +++ b/service/server/main.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "server.h" + +int main(int argc, char **argv) +{ +#if !defined(Q_OS_WIN) + // QtService stores service settings in SystemScope, which normally require root privileges. + // To allow testing this example as non-root, we change the directory of the SystemScope settings file. + QSettings::setPath(QSettings::NativeFormat, QSettings::SystemScope, QDir::tempPath()); + qWarning("(Example uses dummy settings file: %s/QtSoftware.conf)", QDir::tempPath().toLatin1().constData()); +#endif + HttpService service(argc, argv); + return service.exec(); +} diff --git a/service/server/server.cpp b/service/server/server.cpp new file mode 100644 index 00000000..6b1ff170 --- /dev/null +++ b/service/server/server.cpp @@ -0,0 +1,112 @@ +#include +#include +#include + +#include "server.h" + +HttpDaemon::HttpDaemon(quint16 port, QObject* parent) + : QTcpServer(parent), disabled(false) +{ + listen(QHostAddress::Any, port); + qDebug() << "Listen on port: " << port; + + connect(this, &QTcpServer::newConnection, this, &HttpDaemon::sendFortune); +} + +void HttpDaemon::sendFortune() +{ + qDebug() << "New connection: "; + + QTcpSocket *clientConnection = this->nextPendingConnection(); + connect(clientConnection, &QAbstractSocket::disconnected, + clientConnection, &QObject::deleteLater); + + + connect(clientConnection, SIGNAL(readyRead()), this, SLOT(readClient())); + connect(clientConnection, SIGNAL(disconnected()), this, SLOT(discardClient())); + //->setSocketDescriptor(socket); +} + +void HttpDaemon::pause() +{ + disabled = true; +} + +void HttpDaemon::resume() +{ + disabled = false; +} + +void HttpDaemon::readClient() +{ + qDebug() << "readClient"; + + // if (disabled) + // return; + // + // This slot is called when the client sent data to the server. The + // server looks if it was a get request and sends a very simple HTML + // document back. + QTcpSocket* socket = (QTcpSocket*)sender(); + if (socket->canReadLine()) { + QStringList tokens = QString(socket->readLine()).split(QRegExp("[ \r\n][ \r\n]*")); + if (tokens[0] == "GET") { + QTextStream os(socket); + os.setAutoDetectUnicode(true); + os << "HTTP/1.0 200 Ok\r\n" + "Content-Type: text/html; charset=\"utf-8\"\r\n" + "\r\n" + "

Nothing to see here

\n" + << QDateTime::currentDateTime().toString() << "\n"; + socket->close(); + + QtServiceBase::instance()->logMessage("Wrote to client"); + + if (socket->state() == QTcpSocket::UnconnectedState) { + delete socket; + QtServiceBase::instance()->logMessage("Connection closed"); + } + } + } +} + +void HttpDaemon::discardClient() +{ + QTcpSocket* socket = (QTcpSocket*)sender(); + socket->deleteLater(); + + QtServiceBase::instance()->logMessage("Connection closed"); +} + + + + + + +HttpService::HttpService(int argc, char **argv) + : QtService(argc, argv, "Qt HTTP Daemon") +{ + setServiceDescription("A dummy HTTP service implemented with Qt"); + setServiceFlags(QtServiceBase::CanBeSuspended); +} + +void HttpService::start() +{ + QCoreApplication *app = application(); + daemon = new HttpDaemon(8989, app); + + if (!daemon->isListening()) { + logMessage(QString("Failed to bind to port %1").arg(daemon->serverPort()), QtServiceBase::Error); + app->quit(); + } +} + +void HttpService::pause() +{ + daemon->pause(); +} + +void HttpService::resume() +{ + daemon->resume(); +} diff --git a/service/server/server.h b/service/server/server.h new file mode 100644 index 00000000..c741afb2 --- /dev/null +++ b/service/server/server.h @@ -0,0 +1,43 @@ +#ifndef SERVER_H +#define SERVER_H + +#include +#include + +#include "qtservice.h" + + +class HttpDaemon : public QTcpServer +{ + Q_OBJECT +public: + HttpDaemon(quint16 port, QObject* parent = 0); + void sendFortune(); + + void pause(); + void resume(); + +private slots: + void readClient(); + void discardClient(); +private: + bool disabled; +}; + + +class HttpService : public QtService +{ +public: + HttpService(int argc, char **argv); + +protected: + void pause(); + void resume(); + void start(); + +private: + HttpDaemon *daemon; +}; + + +#endif // SERVER_H diff --git a/service/server/server.pro b/service/server/server.pro new file mode 100644 index 00000000..1e31d93d --- /dev/null +++ b/service/server/server.pro @@ -0,0 +1,19 @@ +TARGET = AmneziaVPN-service +TEMPLATE = app +CONFIG += console qt +QT = core network + +HEADERS = \ + server.h +SOURCES = \ + server.cpp \ + main.cpp + +include(../src/qtservice.pri) + +CONFIG(release, debug|release) { + DESTDIR = $$PWD/../../../AmneziaVPN-build/server/release + MOC_DIR = $$DESTDIR + OBJECTS_DIR = $$DESTDIR + RCC_DIR = $$DESTDIR +} diff --git a/service/service.pro b/service/service.pro new file mode 100644 index 00000000..8b08654f --- /dev/null +++ b/service/service.pro @@ -0,0 +1,5 @@ +TEMPLATE=subdirs +CONFIG += ordered +include(common.pri) +qtservice-uselib:SUBDIRS=buildlib +SUBDIRS+=server diff --git a/service/src/QtService b/service/src/QtService new file mode 100644 index 00000000..57e17a52 --- /dev/null +++ b/service/src/QtService @@ -0,0 +1 @@ +#include "qtservice.h" diff --git a/service/src/QtServiceBase b/service/src/QtServiceBase new file mode 100644 index 00000000..57e17a52 --- /dev/null +++ b/service/src/QtServiceBase @@ -0,0 +1 @@ +#include "qtservice.h" diff --git a/service/src/QtServiceController b/service/src/QtServiceController new file mode 100644 index 00000000..57e17a52 --- /dev/null +++ b/service/src/QtServiceController @@ -0,0 +1 @@ +#include "qtservice.h" diff --git a/service/src/qtservice.cpp b/service/src/qtservice.cpp new file mode 100644 index 00000000..5eae058e --- /dev/null +++ b/service/src/qtservice.cpp @@ -0,0 +1,1129 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtservice.h" +#include "qtservice_p.h" +#include +#include +#include +#include +#include + +#if defined(QTSERVICE_DEBUG) +#include +#include +#include +#include +#include +#if defined(Q_OS_WIN32) +#include +#else +#include +#include +#endif + +static QFile* f = 0; + +static void qtServiceCloseDebugLog() +{ + if (!f) + return; + f->write(QTime::currentTime().toString("HH:mm:ss.zzz").toLatin1()); + f->write(" --- DEBUG LOG CLOSED ---\n\n"); + f->flush(); + f->close(); + delete f; + f = 0; +} + +#if QT_VERSION >= 0x050000 +void qtServiceLogDebug(QtMsgType type, const QMessageLogContext &context, const QString &msg) +#else +void qtServiceLogDebug(QtMsgType type, const char* msg) +#endif +{ + static QMutex mutex; + QMutexLocker locker(&mutex); +#if defined(Q_OS_WIN32) + const qulonglong processId = GetCurrentProcessId(); +#else + const qulonglong processId = getpid(); +#endif + QByteArray s(QTime::currentTime().toString("HH:mm:ss.zzz").toLatin1()); + s += " ["; + s += QByteArray::number(processId); + s += "] "; + + if (!f) { +#if defined(Q_OS_WIN32) + f = new QFile("c:/service-debuglog.txt"); +#else + f = new QFile("/tmp/service-debuglog.txt"); +#endif + if (!f->open(QIODevice::WriteOnly | QIODevice::Append)) { + delete f; + f = 0; + return; + } + QByteArray ps('\n' + s + "--- DEBUG LOG OPENED ---\n"); + f->write(ps); + } + + switch (type) { + case QtWarningMsg: + s += "WARNING: "; + break; + case QtCriticalMsg: + s += "CRITICAL: "; + break; + case QtFatalMsg: + s+= "FATAL: "; + break; + case QtDebugMsg: + s += "DEBUG: "; + break; + default: + // Nothing + break; + } + +#if QT_VERSION >= 0x050400 + s += qFormatLogMessage(type, context, msg).toLocal8Bit(); +#elif QT_VERSION >= 0x050000 + s += msg.toLocal8Bit(); + Q_UNUSED(context) +#else + s += msg; +#endif + s += '\n'; + + f->write(s); + f->flush(); + + if (type == QtFatalMsg) { + qtServiceCloseDebugLog(); + exit(1); + } +} + +#endif + +/*! + \class QtServiceController + + \brief The QtServiceController class allows you to control + services from separate applications. + + QtServiceController provides a collection of functions that lets + you install and run a service controlling its execution, as well + as query its status. + + In order to run a service, the service must be installed in the + system's service database using the install() function. The system + will start the service depending on the specified StartupType; it + can either be started during system startup, or when a process + starts it manually. + + Once a service is installed, the service can be run and controlled + manually using the start(), stop(), pause(), resume() or + sendCommand() functions. You can at any time query for the + service's status using the isInstalled() and isRunning() + functions, or you can query its properties using the + serviceDescription(), serviceFilePath(), serviceName() and + startupType() functions. For example: + + \code + MyService service; \\ which inherits QtService + QString serviceFilePath; + + QtServiceController controller(service.serviceName()); + + if (controller.install(serviceFilePath)) + controller.start() + + if (controller.isRunning()) + QMessageBox::information(this, tr("Service Status"), + tr("The %1 service is started").arg(controller.serviceName())); + + ... + + controller.stop(); + controller.uninstall(); + } + \endcode + + An instance of the service controller can only control one single + service. To control several services within one application, you + must create en equal number of service controllers. + + The QtServiceController destructor neither stops nor uninstalls + the associated service. To stop a service the stop() function must + be called explicitly. To uninstall a service, you can use the + uninstall() function. + + \sa QtServiceBase, QtService +*/ + +/*! + \enum QtServiceController::StartupType + This enum describes when a service should be started. + + \value AutoStartup The service is started during system startup. + \value ManualStartup The service must be started manually by a process. + + \warning The \a StartupType enum is ignored under UNIX-like + systems. A service, or daemon, can only be started manually on such + systems with current implementation. + + \sa startupType() +*/ + + +/*! + Creates a controller object for the service with the given + \a name. +*/ +QtServiceController::QtServiceController(const QString &name) + : d_ptr(new QtServiceControllerPrivate()) +{ + Q_D(QtServiceController); + d->q_ptr = this; + d->serviceName = name; +} +/*! + Destroys the service controller. This neither stops nor uninstalls + the controlled service. + + To stop a service the stop() function must be called + explicitly. To uninstall a service, you can use the uninstall() + function. + + \sa stop(), QtServiceController::uninstall() +*/ +QtServiceController::~QtServiceController() +{ + delete d_ptr; +} +/*! + \fn bool QtServiceController::isInstalled() const + + Returns true if the service is installed; otherwise returns false. + + On Windows it uses the system's service control manager. + + On Unix it checks configuration written to QSettings::SystemScope + using "QtSoftware" as organization name. + + \sa install() +*/ + +/*! + \fn bool QtServiceController::isRunning() const + + Returns true if the service is running; otherwise returns false. A + service must be installed before it can be run using a controller. + + \sa start(), isInstalled() +*/ + +/*! + Returns the name of the controlled service. + + \sa QtServiceController(), serviceDescription() +*/ +QString QtServiceController::serviceName() const +{ + Q_D(const QtServiceController); + return d->serviceName; +} +/*! + \fn QString QtServiceController::serviceDescription() const + + Returns the description of the controlled service. + + \sa install(), serviceName() +*/ + +/*! + \fn QtServiceController::StartupType QtServiceController::startupType() const + + Returns the startup type of the controlled service. + + \sa install(), serviceName() +*/ + +/*! + \fn QString QtServiceController::serviceFilePath() const + + Returns the file path to the controlled service. + + \sa install(), serviceName() +*/ + +/*! + Installs the service with the given \a serviceFilePath + and returns true if the service is installed + successfully; otherwise returns false. + + On Windows service is installed in the system's service control manager with the given + \a account and \a password. + + On Unix service configuration is written to QSettings::SystemScope + using "QtSoftware" as organization name. \a account and \a password + arguments are ignored. + + \warning Due to the different implementations of how services (daemons) + are installed on various UNIX-like systems, this method doesn't + integrate the service into the system's startup scripts. + + \sa uninstall(), start() +*/ +bool QtServiceController::install(const QString &serviceFilePath, const QString &account, + const QString &password) +{ + QStringList arguments; + arguments << QLatin1String("-i"); + arguments << account; + arguments << password; + return (QProcess::execute(serviceFilePath, arguments) == 0); +} + + +/*! + \fn bool QtServiceController::uninstall() + + Uninstalls the service and returns true if successful; otherwise returns false. + + On Windows service is uninstalled using the system's service control manager. + + On Unix service configuration is cleared using QSettings::SystemScope + with "QtSoftware" as organization name. + + + \sa install() +*/ + +/*! + \fn bool QtServiceController::start(const QStringList &arguments) + + Starts the installed service passing the given \a arguments to the + service. A service must be installed before a controller can run it. + + Returns true if the service could be started; otherwise returns + false. + + \sa install(), stop() +*/ + +/*! + \overload + + Starts the installed service without passing any arguments to the service. +*/ +bool QtServiceController::start() +{ + return start(QStringList()); +} + +/*! + \fn bool QtServiceController::stop() + + Requests the running service to stop. The service will call the + QtServiceBase::stop() implementation unless the service's state + is QtServiceBase::CannotBeStopped. This function does nothing if + the service is not running. + + Returns true if a running service was successfully stopped; + otherwise false. + + \sa start(), QtServiceBase::stop(), QtServiceBase::ServiceFlags +*/ + +/*! + \fn bool QtServiceController::pause() + + Requests the running service to pause. If the service's state is + QtServiceBase::CanBeSuspended, the service will call the + QtServiceBase::pause() implementation. The function does nothing + if the service is not running. + + Returns true if a running service was successfully paused; + otherwise returns false. + + \sa resume(), QtServiceBase::pause(), QtServiceBase::ServiceFlags +*/ + +/*! + \fn bool QtServiceController::resume() + + Requests the running service to continue. If the service's state + is QtServiceBase::CanBeSuspended, the service will call the + QtServiceBase::resume() implementation. This function does nothing + if the service is not running. + + Returns true if a running service was successfully resumed; + otherwise returns false. + + \sa pause(), QtServiceBase::resume(), QtServiceBase::ServiceFlags +*/ + +/*! + \fn bool QtServiceController::sendCommand(int code) + + Sends the user command \a code to the service. The service will + call the QtServiceBase::processCommand() implementation. This + function does nothing if the service is not running. + + Returns true if the request was sent to a running service; + otherwise returns false. + + \sa QtServiceBase::processCommand() +*/ + +class QtServiceStarter : public QObject +{ + Q_OBJECT +public: + QtServiceStarter(QtServiceBasePrivate *service) + : QObject(), d_ptr(service) {} +public slots: + void slotStart() + { + d_ptr->startService(); + } +private: + QtServiceBasePrivate *d_ptr; +}; +#include "qtservice.moc" + +QtServiceBase *QtServiceBasePrivate::instance = 0; + +QtServiceBasePrivate::QtServiceBasePrivate(const QString &name) + : startupType(QtServiceController::ManualStartup), serviceFlags(0), controller(name) +{ + +} + +QtServiceBasePrivate::~QtServiceBasePrivate() +{ + +} + +void QtServiceBasePrivate::startService() +{ + q_ptr->start(); +} + +int QtServiceBasePrivate::run(bool asService, const QStringList &argList) +{ + int argc = argList.size(); + QVector argv(argc); + QList argvData; + for (int i = 0; i < argc; ++i) + argvData.append(argList.at(i).toLocal8Bit()); + for (int i = 0; i < argc; ++i) + argv[i] = argvData[i].data(); + + if (asService && !sysInit()) + return -1; + + q_ptr->createApplication(argc, argv.data()); + QCoreApplication *app = QCoreApplication::instance(); + if (!app) + return -1; + + if (asService) + sysSetPath(); + + QtServiceStarter starter(this); + QTimer::singleShot(0, &starter, SLOT(slotStart())); + int res = q_ptr->executeApplication(); + delete app; + + if (asService) + sysCleanup(); + return res; +} + + +/*! + \class QtServiceBase + + \brief The QtServiceBase class provides an API for implementing + Windows services and Unix daemons. + + A Windows service or Unix daemon (a "service"), is a program that + runs "in the background" independently of whether a user is logged + in or not. A service is often set up to start when the machine + boots up, and will typically run continuously as long as the + machine is on. + + Services are usually non-interactive console applications. User + interaction, if required, is usually implemented in a separate, + normal GUI application that communicates with the service through + an IPC channel. For simple communication, + QtServiceController::sendCommand() and QtService::processCommand() + may be used, possibly in combination with a shared settings + file. For more complex, interactive communication, a custom IPC + channel should be used, e.g. based on Qt's networking classes. (In + certain circumstances, a service may provide a GUI itself, + ref. the "interactive" example documentation). + + Typically, you will create a service by subclassing the QtService + template class which inherits QtServiceBase and allows you to + create a service for a particular application type. + + The Windows implementation uses the NT Service Control Manager, + and the application can be controlled through the system + administration tools. Services are usually launched using the + system account, which requires that all DLLs that the service + executable depends on (i.e. Qt), are located in the same directory + as the service, or in a system path. + + On Unix a service is implemented as a daemon. + + You can retrieve the service's description, state, and startup + type using the serviceDescription(), serviceFlags() and + startupType() functions respectively. The service's state is + decribed by the ServiceFlag enum. The mentioned properites can + also be set using the corresponding set functions. In addition you + can retrieve the service's name using the serviceName() function. + + Several of QtServiceBase's protected functions are called on + requests from the QtServiceController class: + + \list + \o start() + \o pause() + \o processCommand() + \o resume() + \o stop() + \endlist + + You can control any given service using an instance of the + QtServiceController class which also allows you to control + services from separate applications. The mentioned functions are + all virtual and won't do anything unless they are + reimplemented. You can reimplement these functions to pause and + resume the service's execution, as well as process user commands + and perform additional clean-ups before shutting down. + + QtServiceBase also provides the static instance() function which + returns a pointer to an application's QtServiceBase instance. In + addition, a service can report events to the system's event log + using the logMessage() function. The MessageType enum describes + the different types of messages a service reports. + + The implementation of a service application's main function + typically creates an service object derived by subclassing the + QtService template class. Then the main function will call this + service's exec() function, and return the result of that call. For + example: + + \code + int main(int argc, char **argv) + { + MyService service(argc, argv); + return service.exec(); + } + \endcode + + When the exec() function is called, it will parse the service + specific arguments passed in \c argv, perform the required + actions, and return. + + \target serviceSpecificArguments + + The following arguments are recognized as service specific: + + \table + \header \i Short \i Long \i Explanation + \row \i -i \i -install \i Install the service. + \row \i -u \i -uninstall \i Uninstall the service. + \row \i -e \i -exec + \i Execute the service as a standalone application (useful for debug purposes). + This is a blocking call, the service will be executed like a normal application. + In this mode you will not be able to communicate with the service from the contoller. + \row \i -t \i -terminate \i Stop the service. + \row \i -p \i -pause \i Pause the service. + \row \i -r \i -resume \i Resume a paused service. + \row \i -c \e{cmd} \i -command \e{cmd} + \i Send the user defined command code \e{cmd} to the service application. + \row \i -v \i -version \i Display version and status information. + \endtable + + If \e none of the arguments is recognized as service specific, + exec() will first call the createApplication() function, then + executeApplication() and finally the start() function. In the end, + exec() returns while the service continues in its own process + waiting for commands from the service controller. + + \sa QtService, QtServiceController +*/ + +/*! + \enum QtServiceBase::MessageType + + This enum describes the different types of messages a service + reports to the system log. + + \value Success An operation has succeeded, e.g. the service + is started. + \value Error An operation failed, e.g. the service failed to start. + \value Warning An operation caused a warning that might require user + interaction. + \value Information Any type of usually non-critical information. +*/ + +/*! + \enum QtServiceBase::ServiceFlag + + This enum describes the different capabilities of a service. + + \value Default The service can be stopped, but not suspended. + \value CanBeSuspended The service can be suspended. + \value CannotBeStopped The service cannot be stopped. + \value NeedsStopOnShutdown (Windows only) The service will be stopped before the system shuts down. Note that Microsoft recommends this only for services that must absolutely clean up during shutdown, because there is a limited time available for shutdown of services. +*/ + +/*! + Creates a service instance called \a name. The \a argc and \a argv + parameters are parsed after the exec() function has been + called. Then they are passed to the application's constructor. + The application type is determined by the QtService subclass. + + The service is neither installed nor started. The name must not + contain any backslashes or be longer than 255 characters. In + addition, the name must be unique in the system's service + database. + + \sa exec(), start(), QtServiceController::install() +*/ +QtServiceBase::QtServiceBase(int argc, char **argv, const QString &name) +{ +#if defined(QTSERVICE_DEBUG) +# if QT_VERSION >= 0x050000 + qInstallMessageHandler(qtServiceLogDebug); +# else + qInstallMsgHandler(qtServiceLogDebug); +# endif + qAddPostRoutine(qtServiceCloseDebugLog); +#endif + + Q_ASSERT(!QtServiceBasePrivate::instance); + QtServiceBasePrivate::instance = this; + + QString nm(name); + if (nm.length() > 255) { + qWarning("QtService: 'name' is longer than 255 characters."); + nm.truncate(255); + } + if (nm.contains('\\')) { + qWarning("QtService: 'name' contains backslashes '\\'."); + nm.replace((QChar)'\\', (QChar)'\0'); + } + + d_ptr = new QtServiceBasePrivate(nm); + d_ptr->q_ptr = this; + + d_ptr->serviceFlags = 0; + d_ptr->sysd = 0; + for (int i = 0; i < argc; ++i) + d_ptr->args.append(QString::fromLocal8Bit(argv[i])); +} + +/*! + Destroys the service object. This neither stops nor uninstalls the + service. + + To stop a service the stop() function must be called + explicitly. To uninstall a service, you can use the + QtServiceController::uninstall() function. + + \sa stop(), QtServiceController::uninstall() +*/ +QtServiceBase::~QtServiceBase() +{ + delete d_ptr; + QtServiceBasePrivate::instance = 0; +} + +/*! + Returns the name of the service. + + \sa QtServiceBase(), serviceDescription() +*/ +QString QtServiceBase::serviceName() const +{ + return d_ptr->controller.serviceName(); +} + +/*! + Returns the description of the service. + + \sa setServiceDescription(), serviceName() +*/ +QString QtServiceBase::serviceDescription() const +{ + return d_ptr->serviceDescription; +} + +/*! + Sets the description of the service to the given \a description. + + \sa serviceDescription() +*/ +void QtServiceBase::setServiceDescription(const QString &description) +{ + d_ptr->serviceDescription = description; +} + +/*! + Returns the service's startup type. + + \sa QtServiceController::StartupType, setStartupType() +*/ +QtServiceController::StartupType QtServiceBase::startupType() const +{ + return d_ptr->startupType; +} + +/*! + Sets the service's startup type to the given \a type. + + \sa QtServiceController::StartupType, startupType() +*/ +void QtServiceBase::setStartupType(QtServiceController::StartupType type) +{ + d_ptr->startupType = type; +} + +/*! + Returns the service's state which is decribed using the + ServiceFlag enum. + + \sa ServiceFlags, setServiceFlags() +*/ +QtServiceBase::ServiceFlags QtServiceBase::serviceFlags() const +{ + return d_ptr->serviceFlags; +} + +/*! + \fn void QtServiceBase::setServiceFlags(ServiceFlags flags) + + Sets the service's state to the state described by the given \a + flags. + + \sa ServiceFlags, serviceFlags() +*/ + +/*! + Executes the service. + + When the exec() function is called, it will parse the \l + {serviceSpecificArguments} {service specific arguments} passed in + \c argv, perform the required actions, and exit. + + If none of the arguments is recognized as service specific, exec() + will first call the createApplication() function, then executeApplication() and + finally the start() function. In the end, exec() + returns while the service continues in its own process waiting for + commands from the service controller. + + \sa QtServiceController +*/ +int QtServiceBase::exec() +{ + if (d_ptr->args.size() > 1) { + QString a = d_ptr->args.at(1); + if (a == QLatin1String("-i") || a == QLatin1String("-install")) { + if (!d_ptr->controller.isInstalled()) { + QString account; + QString password; + if (d_ptr->args.size() > 2) + account = d_ptr->args.at(2); + if (d_ptr->args.size() > 3) + password = d_ptr->args.at(3); + if (!d_ptr->install(account, password)) { + fprintf(stderr, "The service %s could not be installed\n", serviceName().toLatin1().constData()); + return -1; + } else { + printf("The service %s has been installed under: %s\n", + serviceName().toLatin1().constData(), d_ptr->filePath().toLatin1().constData()); + } + } else { + fprintf(stderr, "The service %s is already installed\n", serviceName().toLatin1().constData()); + } + return 0; + } else if (a == QLatin1String("-u") || a == QLatin1String("-uninstall")) { + if (d_ptr->controller.isInstalled()) { + if (!d_ptr->controller.uninstall()) { + fprintf(stderr, "The service %s could not be uninstalled\n", serviceName().toLatin1().constData()); + return -1; + } else { + printf("The service %s has been uninstalled.\n", + serviceName().toLatin1().constData()); + } + } else { + fprintf(stderr, "The service %s is not installed\n", serviceName().toLatin1().constData()); + } + return 0; + } else if (a == QLatin1String("-v") || a == QLatin1String("-version")) { + printf("The service\n" + "\t%s\n\t%s\n\n", serviceName().toLatin1().constData(), d_ptr->args.at(0).toLatin1().constData()); + printf("is %s", (d_ptr->controller.isInstalled() ? "installed" : "not installed")); + printf(" and %s\n\n", (d_ptr->controller.isRunning() ? "running" : "not running")); + return 0; + } else if (a == QLatin1String("-e") || a == QLatin1String("-exec")) { + d_ptr->args.removeAt(1); + int ec = d_ptr->run(false, d_ptr->args); + if (ec == -1) + qErrnoWarning("The service could not be executed."); + return ec; + } else if (a == QLatin1String("-t") || a == QLatin1String("-terminate")) { + if (!d_ptr->controller.stop()) + qErrnoWarning("The service could not be stopped."); + return 0; + } else if (a == QLatin1String("-p") || a == QLatin1String("-pause")) { + d_ptr->controller.pause(); + return 0; + } else if (a == QLatin1String("-r") || a == QLatin1String("-resume")) { + d_ptr->controller.resume(); + return 0; + } else if (a == QLatin1String("-c") || a == QLatin1String("-command")) { + int code = 0; + if (d_ptr->args.size() > 2) + code = d_ptr->args.at(2).toInt(); + d_ptr->controller.sendCommand(code); + return 0; + } else if (a == QLatin1String("-h") || a == QLatin1String("-help")) { + printf("\n%s -[i|u|e|t|p|r|c|v|h]\n" + "\t-i(nstall) [account] [password]\t: Install the service, optionally using given account and password\n" + "\t-u(ninstall)\t: Uninstall the service.\n" + "\t-e(xec)\t\t: Run as a regular application. Useful for debugging.\n" + "\t-t(erminate)\t: Stop the service.\n" + "\t-p(ause)\t: Pause the service.\n" + "\t-r(esume)\t: Resume a paused service.\n" + "\t-c(ommand) num\t: Send command code num to the service.\n" + "\t-v(ersion)\t: Print version and status information.\n" + "\t-h(elp) \t: Show this help\n" + "\tNo arguments\t: Start the service.\n", + d_ptr->args.at(0).toLatin1().constData()); + return 0; + } + } +#if defined(Q_OS_UNIX) + if (::getenv("QTSERVICE_RUN")) { + // Means we're the detached, real service process. + int ec = d_ptr->run(true, d_ptr->args); + if (ec == -1) + qErrnoWarning("The service failed to run."); + return ec; + } +#endif + if (!d_ptr->start()) { + fprintf(stderr, "The service %s could not start\n", serviceName().toLatin1().constData()); + return -4; + } + return 0; +} + +/*! + \fn void QtServiceBase::logMessage(const QString &message, MessageType type, + int id, uint category, const QByteArray &data) + + Reports a message of the given \a type with the given \a message + to the local system event log. The message identifier \a id and + the message \a category are user defined values. The \a data + parameter can contain arbitrary binary data. + + Message strings for \a id and \a category must be provided by a + message file, which must be registered in the system registry. + Refer to the MSDN for more information about how to do this on + Windows. + + \sa MessageType +*/ + +/*! + Returns a pointer to the current application's QtServiceBase + instance. +*/ +QtServiceBase *QtServiceBase::instance() +{ + return QtServiceBasePrivate::instance; +} + +/*! + \fn void QtServiceBase::start() + + This function must be implemented in QtServiceBase subclasses in + order to perform the service's work. Usually you create some main + object on the heap which is the heart of your service. + + The function is only called when no service specific arguments + were passed to the service constructor, and is called by exec() + after it has called the executeApplication() function. + + Note that you \e don't need to create an application object or + call its exec() function explicitly. + + \sa exec(), stop(), QtServiceController::start() +*/ + +/*! + Reimplement this function to perform additional cleanups before + shutting down (for example deleting a main object if it was + created in the start() function). + + This function is called in reply to controller requests. The + default implementation does nothing. + + \sa start(), QtServiceController::stop() +*/ +void QtServiceBase::stop() +{ +} + +/*! + Reimplement this function to pause the service's execution (for + example to stop a polling timer, or to ignore socket notifiers). + + This function is called in reply to controller requests. The + default implementation does nothing. + + \sa resume(), QtServiceController::pause() +*/ +void QtServiceBase::pause() +{ +} + +/*! + Reimplement this function to continue the service after a call to + pause(). + + This function is called in reply to controller requests. The + default implementation does nothing. + + \sa pause(), QtServiceController::resume() +*/ +void QtServiceBase::resume() +{ +} + +/*! + Reimplement this function to process the user command \a code. + + + This function is called in reply to controller requests. The + default implementation does nothing. + + \sa QtServiceController::sendCommand() +*/ +void QtServiceBase::processCommand(int /*code*/) +{ +} + +/*! + \fn void QtServiceBase::createApplication(int &argc, char **argv) + + Creates the application object using the \a argc and \a argv + parameters. + + This function is only called when no \l + {serviceSpecificArguments}{service specific arguments} were + passed to the service constructor, and is called by exec() before + it calls the executeApplication() and start() functions. + + The createApplication() function is implemented in QtService, but + you might want to reimplement it, for example, if the chosen + application type's constructor needs additional arguments. + + \sa exec(), QtService +*/ + +/*! + \fn int QtServiceBase::executeApplication() + + Executes the application previously created with the + createApplication() function. + + This function is only called when no \l + {serviceSpecificArguments}{service specific arguments} were + passed to the service constructor, and is called by exec() after + it has called the createApplication() function and before start() function. + + This function is implemented in QtService. + + \sa exec(), createApplication() +*/ + +/*! + \class QtService + + \brief The QtService is a convenient template class that allows + you to create a service for a particular application type. + + A Windows service or Unix daemon (a "service"), is a program that + runs "in the background" independently of whether a user is logged + in or not. A service is often set up to start when the machine + boots up, and will typically run continuously as long as the + machine is on. + + Services are usually non-interactive console applications. User + interaction, if required, is usually implemented in a separate, + normal GUI application that communicates with the service through + an IPC channel. For simple communication, + QtServiceController::sendCommand() and QtService::processCommand() + may be used, possibly in combination with a shared settings file. For + more complex, interactive communication, a custom IPC channel + should be used, e.g. based on Qt's networking classes. (In certain + circumstances, a service may provide a GUI itself, ref. the + "interactive" example documentation). + + \bold{Note:} On Unix systems, this class relies on facilities + provided by the QtNetwork module, provided as part of the + \l{Qt Open Source Edition} and certain \l{Qt Commercial Editions}. + + The QtService class functionality is inherited from QtServiceBase, + but in addition the QtService class binds an instance of + QtServiceBase with an application type. + + Typically, you will create a service by subclassing the QtService + template class. For example: + + \code + class MyService : public QtService + { + public: + MyService(int argc, char **argv); + ~MyService(); + + protected: + void start(); + void stop(); + void pause(); + void resume(); + void processCommand(int code); + }; + \endcode + + The application type can be QCoreApplication for services without + GUI, QApplication for services with GUI or you can use your own + custom application type. + + You must reimplement the QtServiceBase::start() function to + perform the service's work. Usually you create some main object on + the heap which is the heart of your service. + + In addition, you might want to reimplement the + QtServiceBase::pause(), QtServiceBase::processCommand(), + QtServiceBase::resume() and QtServiceBase::stop() to intervene the + service's process on controller requests. You can control any + given service using an instance of the QtServiceController class + which also allows you to control services from separate + applications. The mentioned functions are all virtual and won't do + anything unless they are reimplemented. + + Your custom service is typically instantiated in the application's + main function. Then the main function will call your service's + exec() function, and return the result of that call. For example: + + \code + int main(int argc, char **argv) + { + MyService service(argc, argv); + return service.exec(); + } + \endcode + + When the exec() function is called, it will parse the \l + {serviceSpecificArguments} {service specific arguments} passed in + \c argv, perform the required actions, and exit. + + If none of the arguments is recognized as service specific, exec() + will first call the createApplication() function, then executeApplication() and + finally the start() function. In the end, exec() + returns while the service continues in its own process waiting for + commands from the service controller. + + \sa QtServiceBase, QtServiceController +*/ + +/*! + \fn QtService::QtService(int argc, char **argv, const QString &name) + + Constructs a QtService object called \a name. The \a argc and \a + argv parameters are parsed after the exec() function has been + called. Then they are passed to the application's constructor. + + There can only be one QtService object in a process. + + \sa QtServiceBase() +*/ + +/*! + \fn QtService::~QtService() + + Destroys the service object. +*/ + +/*! + \fn Application *QtService::application() const + + Returns a pointer to the application object. +*/ + +/*! + \fn void QtService::createApplication(int &argc, char **argv) + + Creates application object of type Application passing \a argc and + \a argv to its constructor. + + \reimp + +*/ + +/*! + \fn int QtService::executeApplication() + + \reimp +*/ diff --git a/service/src/qtservice.h b/service/src/qtservice.h new file mode 100644 index 00000000..01d5b07f --- /dev/null +++ b/service/src/qtservice.h @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTSERVICE_H +#define QTSERVICE_H + +#include + +#if defined(Q_OS_WIN) +# if !defined(QT_QTSERVICE_EXPORT) && !defined(QT_QTSERVICE_IMPORT) +# define QT_QTSERVICE_EXPORT +# elif defined(QT_QTSERVICE_IMPORT) +# if defined(QT_QTSERVICE_EXPORT) +# undef QT_QTSERVICE_EXPORT +# endif +# define QT_QTSERVICE_EXPORT __declspec(dllimport) +# elif defined(QT_QTSERVICE_EXPORT) +# undef QT_QTSERVICE_EXPORT +# define QT_QTSERVICE_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTSERVICE_EXPORT +#endif + +class QStringList; +class QtServiceControllerPrivate; + +class QT_QTSERVICE_EXPORT QtServiceController +{ + Q_DECLARE_PRIVATE(QtServiceController) +public: + enum StartupType + { + AutoStartup = 0, ManualStartup + }; + + QtServiceController(const QString &name); + virtual ~QtServiceController(); + + bool isInstalled() const; + bool isRunning() const; + + QString serviceName() const; + QString serviceDescription() const; + StartupType startupType() const; + QString serviceFilePath() const; + + static bool install(const QString &serviceFilePath, const QString &account = QString(), + const QString &password = QString()); + bool uninstall(); + + bool start(const QStringList &arguments); + bool start(); + bool stop(); + bool pause(); + bool resume(); + bool sendCommand(int code); + +private: + QtServiceControllerPrivate *d_ptr; +}; + +class QtServiceBasePrivate; + +class QT_QTSERVICE_EXPORT QtServiceBase +{ + Q_DECLARE_PRIVATE(QtServiceBase) +public: + + enum MessageType + { + Success = 0, Error, Warning, Information + }; + + enum ServiceFlag + { + Default = 0x00, + CanBeSuspended = 0x01, + CannotBeStopped = 0x02, + NeedsStopOnShutdown = 0x04 + }; + + Q_DECLARE_FLAGS(ServiceFlags, ServiceFlag) + + QtServiceBase(int argc, char **argv, const QString &name); + virtual ~QtServiceBase(); + + QString serviceName() const; + + QString serviceDescription() const; + void setServiceDescription(const QString &description); + + QtServiceController::StartupType startupType() const; + void setStartupType(QtServiceController::StartupType startupType); + + ServiceFlags serviceFlags() const; + void setServiceFlags(ServiceFlags flags); + + int exec(); + + void logMessage(const QString &message, MessageType type = Success, + int id = 0, uint category = 0, const QByteArray &data = QByteArray()); + + static QtServiceBase *instance(); + +protected: + + virtual void start() = 0; + virtual void stop(); + virtual void pause(); + virtual void resume(); + virtual void processCommand(int code); + + virtual void createApplication(int &argc, char **argv) = 0; + + virtual int executeApplication() = 0; + +private: + + friend class QtServiceSysPrivate; + QtServiceBasePrivate *d_ptr; +}; + +template +class QtService : public QtServiceBase +{ +public: + QtService(int argc, char **argv, const QString &name) + : QtServiceBase(argc, argv, name), app(0) + { } + ~QtService() + { + } + +protected: + Application *application() const + { return app; } + + virtual void createApplication(int &argc, char **argv) + { + app = new Application(argc, argv); + QCoreApplication *a = app; + Q_UNUSED(a); + } + + virtual int executeApplication() + { return Application::exec(); } + +private: + Application *app; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QtServiceBase::ServiceFlags) + +#endif // QTSERVICE_H diff --git a/service/src/qtservice.pri b/service/src/qtservice.pri new file mode 100644 index 00000000..09452981 --- /dev/null +++ b/service/src/qtservice.pri @@ -0,0 +1,21 @@ +include(../common.pri) +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD +!win32:QT += network +win32:LIBS += -luser32 + +qtservice-uselib:!qtservice-buildlib { + LIBS += -L$$QTSERVICE_LIBDIR -l$$QTSERVICE_LIBNAME +} else { + HEADERS += $$PWD/qtservice.h \ + $$PWD/qtservice_p.h + SOURCES += $$PWD/qtservice.cpp + win32:SOURCES += $$PWD/qtservice_win.cpp + unix:HEADERS += $$PWD/qtunixsocket.h $$PWD/qtunixserversocket.h + unix:SOURCES += $$PWD/qtservice_unix.cpp $$PWD/qtunixsocket.cpp $$PWD/qtunixserversocket.cpp +} + +win32 { + qtservice-buildlib:shared:DEFINES += QT_QTSERVICE_EXPORT + else:qtservice-uselib:DEFINES += QT_QTSERVICE_IMPORT +} diff --git a/service/src/qtservice_p.h b/service/src/qtservice_p.h new file mode 100644 index 00000000..a88992cf --- /dev/null +++ b/service/src/qtservice_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTSERVICE_P_H +#define QTSERVICE_P_H + +#include +#include "qtservice.h" + +class QtServiceControllerPrivate +{ + Q_DECLARE_PUBLIC(QtServiceController) +public: + QString serviceName; + QtServiceController *q_ptr; +}; + +class QtServiceBasePrivate +{ + Q_DECLARE_PUBLIC(QtServiceBase) +public: + + QtServiceBasePrivate(const QString &name); + ~QtServiceBasePrivate(); + + QtServiceBase *q_ptr; + + QString serviceDescription; + QtServiceController::StartupType startupType; + QtServiceBase::ServiceFlags serviceFlags; + QStringList args; + + static class QtServiceBase *instance; + + QtServiceController controller; + + void startService(); + int run(bool asService, const QStringList &argList); + bool install(const QString &account, const QString &password); + + bool start(); + + QString filePath() const; + bool sysInit(); + void sysSetPath(); + void sysCleanup(); + class QtServiceSysPrivate *sysd; +}; + +#endif diff --git a/service/src/qtservice_unix.cpp b/service/src/qtservice_unix.cpp new file mode 100644 index 00000000..345acc6a --- /dev/null +++ b/service/src/qtservice_unix.cpp @@ -0,0 +1,482 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtservice.h" +#include "qtservice_p.h" +#include "qtunixsocket.h" +#include "qtunixserversocket.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QString encodeName(const QString &name, bool allowUpper = false) +{ + QString n = name.toLower(); + QString legal = QLatin1String("abcdefghijklmnopqrstuvwxyz1234567890"); + if (allowUpper) + legal += QLatin1String("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + int pos = 0; + while (pos < n.size()) { + if (legal.indexOf(n[pos]) == -1) + n.remove(pos, 1); + else + ++pos; + } + return n; +} + +static QString login() +{ + QString l; + uid_t uid = getuid(); + passwd *pw = getpwuid(uid); + if (pw) + l = QString(pw->pw_name); + return l; +} + +static QString socketPath(const QString &serviceName) +{ + QString sn = encodeName(serviceName); + return QString(QLatin1String("/var/tmp/") + sn + QLatin1String(".") + login()); +} + +static bool sendCmd(const QString &serviceName, const QString &cmd) +{ + bool retValue = false; + QtUnixSocket sock; + if (sock.connectTo(socketPath(serviceName))) { + sock.write(QString(cmd+"\r\n").toLatin1().constData()); + sock.flush(); + sock.waitForReadyRead(-1); + QString reply = sock.readAll(); + if (reply == QLatin1String("true")) + retValue = true; + sock.close(); + } + return retValue; +} + +static QString absPath(const QString &path) +{ + QString ret; + if (path[0] != QChar('/')) { // Not an absolute path + int slashpos; + if ((slashpos = path.lastIndexOf('/')) != -1) { // Relative path + QDir dir = QDir::current(); + dir.cd(path.left(slashpos)); + ret = dir.absolutePath(); + } else { // Need to search $PATH + char *envPath = ::getenv("PATH"); + if (envPath) { + QStringList envPaths = QString::fromLocal8Bit(envPath).split(':'); + for (int i = 0; i < envPaths.size(); ++i) { + if (QFile::exists(envPaths.at(i) + QLatin1String("/") + QString(path))) { + QDir dir(envPaths.at(i)); + ret = dir.absolutePath(); + break; + } + } + } + } + } else { + QFileInfo fi(path); + ret = fi.absolutePath(); + } + return ret; +} + +QString QtServiceBasePrivate::filePath() const +{ + QString ret; + if (args.isEmpty()) + return ret; + QFileInfo fi(args[0]); + QDir dir(absPath(args[0])); + return dir.absoluteFilePath(fi.fileName()); +} + + +QString QtServiceController::serviceDescription() const +{ + QSettings settings(QSettings::SystemScope, "QtSoftware"); + settings.beginGroup("services"); + settings.beginGroup(serviceName()); + + QString desc = settings.value("description").toString(); + + settings.endGroup(); + settings.endGroup(); + + return desc; +} + +QtServiceController::StartupType QtServiceController::startupType() const +{ + QSettings settings(QSettings::SystemScope, "QtSoftware"); + settings.beginGroup("services"); + settings.beginGroup(serviceName()); + + StartupType startupType = (StartupType)settings.value("startupType").toInt(); + + settings.endGroup(); + settings.endGroup(); + + return startupType; +} + +QString QtServiceController::serviceFilePath() const +{ + QSettings settings(QSettings::SystemScope, "QtSoftware"); + settings.beginGroup("services"); + settings.beginGroup(serviceName()); + + QString path = settings.value("path").toString(); + + settings.endGroup(); + settings.endGroup(); + + return path; +} + +bool QtServiceController::uninstall() +{ + QSettings settings(QSettings::SystemScope, "QtSoftware"); + settings.beginGroup("services"); + + settings.remove(serviceName()); + + settings.endGroup(); + settings.sync(); + + QSettings::Status ret = settings.status(); + if (ret == QSettings::AccessError) { + fprintf(stderr, "Cannot uninstall \"%s\". Cannot write to: %s. Check permissions.\n", + serviceName().toLatin1().constData(), + settings.fileName().toLatin1().constData()); + } + return (ret == QSettings::NoError); +} + + +bool QtServiceController::start(const QStringList &arguments) +{ + if (!isInstalled()) + return false; + if (isRunning()) + return false; + return QProcess::startDetached(serviceFilePath(), arguments); +} + +bool QtServiceController::stop() +{ + return sendCmd(serviceName(), QLatin1String("terminate")); +} + +bool QtServiceController::pause() +{ + return sendCmd(serviceName(), QLatin1String("pause")); +} + +bool QtServiceController::resume() +{ + return sendCmd(serviceName(), QLatin1String("resume")); +} + +bool QtServiceController::sendCommand(int code) +{ + return sendCmd(serviceName(), QString(QLatin1String("num:") + QString::number(code))); +} + +bool QtServiceController::isInstalled() const +{ + QSettings settings(QSettings::SystemScope, "QtSoftware"); + settings.beginGroup("services"); + + QStringList list = settings.childGroups(); + + settings.endGroup(); + + QStringListIterator it(list); + while (it.hasNext()) { + if (it.next() == serviceName()) + return true; + } + + return false; +} + +bool QtServiceController::isRunning() const +{ + QtUnixSocket sock; + if (sock.connectTo(socketPath(serviceName()))) + return true; + return false; +} + + + + +/////////////////////////////////// + +class QtServiceSysPrivate : public QtUnixServerSocket +{ + Q_OBJECT +public: + QtServiceSysPrivate(); + ~QtServiceSysPrivate(); + + char *ident; + + QtServiceBase::ServiceFlags serviceFlags; + +protected: +#if QT_VERSION >= 0x050000 + void incomingConnection(qintptr socketDescriptor); +#else + void incomingConnection(int socketDescriptor); +#endif + +private slots: + void slotReady(); + void slotClosed(); + +private: + QString getCommand(const QTcpSocket *socket); + QMap cache; +}; + +QtServiceSysPrivate::QtServiceSysPrivate() + : QtUnixServerSocket(), ident(0), serviceFlags(0) +{ +} + +QtServiceSysPrivate::~QtServiceSysPrivate() +{ + if (ident) + delete[] ident; +} + +#if QT_VERSION >= 0x050000 +void QtServiceSysPrivate::incomingConnection(qintptr socketDescriptor) +#else +void QtServiceSysPrivate::incomingConnection(int socketDescriptor) +#endif +{ + QTcpSocket *s = new QTcpSocket(this); + s->setSocketDescriptor(socketDescriptor); + connect(s, SIGNAL(readyRead()), this, SLOT(slotReady())); + connect(s, SIGNAL(disconnected()), this, SLOT(slotClosed())); +} + +void QtServiceSysPrivate::slotReady() +{ + QTcpSocket *s = (QTcpSocket *)sender(); + cache[s] += QString(s->readAll()); + QString cmd = getCommand(s); + while (!cmd.isEmpty()) { + bool retValue = false; + if (cmd == QLatin1String("terminate")) { + if (!(serviceFlags & QtServiceBase::CannotBeStopped)) { + QtServiceBase::instance()->stop(); + QCoreApplication::instance()->quit(); + retValue = true; + } + } else if (cmd == QLatin1String("pause")) { + if (serviceFlags & QtServiceBase::CanBeSuspended) { + QtServiceBase::instance()->pause(); + retValue = true; + } + } else if (cmd == QLatin1String("resume")) { + if (serviceFlags & QtServiceBase::CanBeSuspended) { + QtServiceBase::instance()->resume(); + retValue = true; + } + } else if (cmd == QLatin1String("alive")) { + retValue = true; + } else if (cmd.length() > 4 && cmd.left(4) == QLatin1String("num:")) { + cmd = cmd.mid(4); + QtServiceBase::instance()->processCommand(cmd.toInt()); + retValue = true; + } + QString retString; + if (retValue) + retString = QLatin1String("true"); + else + retString = QLatin1String("false"); + s->write(retString.toLatin1().constData()); + s->flush(); + cmd = getCommand(s); + } +} + +void QtServiceSysPrivate::slotClosed() +{ + QTcpSocket *s = (QTcpSocket *)sender(); + s->deleteLater(); +} + +QString QtServiceSysPrivate::getCommand(const QTcpSocket *socket) +{ + int pos = cache[socket].indexOf("\r\n"); + if (pos >= 0) { + QString ret = cache[socket].left(pos); + cache[socket].remove(0, pos+2); + return ret; + } + return ""; +} + +#include "qtservice_unix.moc" + +bool QtServiceBasePrivate::sysInit() +{ + sysd = new QtServiceSysPrivate; + sysd->serviceFlags = serviceFlags; + // Restrict permissions on files that are created by the service + ::umask(027); + + return true; +} + +void QtServiceBasePrivate::sysSetPath() +{ + if (sysd) + sysd->setPath(socketPath(controller.serviceName())); +} + +void QtServiceBasePrivate::sysCleanup() +{ + if (sysd) { + sysd->close(); + delete sysd; + sysd = 0; + } +} + +bool QtServiceBasePrivate::start() +{ + if (sendCmd(controller.serviceName(), "alive")) { + // Already running + return false; + } + // Could just call controller.start() here, but that would fail if + // we're not installed. We do not want to strictly require installation. + ::setenv("QTSERVICE_RUN", "1", 1); // Tell the detached process it's it + return QProcess::startDetached(filePath(), args.mid(1), "/"); +} + +bool QtServiceBasePrivate::install(const QString &account, const QString &password) +{ + Q_UNUSED(account) + Q_UNUSED(password) + QSettings settings(QSettings::SystemScope, "QtSoftware"); + + settings.beginGroup("services"); + settings.beginGroup(controller.serviceName()); + + settings.setValue("path", filePath()); + settings.setValue("description", serviceDescription); + settings.setValue("automaticStartup", startupType); + + settings.endGroup(); + settings.endGroup(); + settings.sync(); + + QSettings::Status ret = settings.status(); + if (ret == QSettings::AccessError) { + fprintf(stderr, "Cannot install \"%s\". Cannot write to: %s. Check permissions.\n", + controller.serviceName().toLatin1().constData(), + settings.fileName().toLatin1().constData()); + } + return (ret == QSettings::NoError); +} + +void QtServiceBase::logMessage(const QString &message, QtServiceBase::MessageType type, + int, uint, const QByteArray &) +{ + if (!d_ptr->sysd) + return; + int st; + switch(type) { + case QtServiceBase::Error: + st = LOG_ERR; + break; + case QtServiceBase::Warning: + st = LOG_WARNING; + break; + default: + st = LOG_INFO; + } + if (!d_ptr->sysd->ident) { + QString tmp = encodeName(serviceName(), true); + int len = tmp.toLocal8Bit().size(); + d_ptr->sysd->ident = new char[len+1]; + d_ptr->sysd->ident[len] = '\0'; + ::memcpy(d_ptr->sysd->ident, tmp.toLocal8Bit().constData(), len); + } + openlog(d_ptr->sysd->ident, LOG_PID, LOG_DAEMON); + foreach(QString line, message.split('\n')) + syslog(st, "%s", line.toLocal8Bit().constData()); + closelog(); +} + +void QtServiceBase::setServiceFlags(QtServiceBase::ServiceFlags flags) +{ + if (d_ptr->serviceFlags == flags) + return; + d_ptr->serviceFlags = flags; + if (d_ptr->sysd) + d_ptr->sysd->serviceFlags = flags; +} + diff --git a/service/src/qtservice_win.cpp b/service/src/qtservice_win.cpp new file mode 100644 index 00000000..e5b7ecc5 --- /dev/null +++ b/service/src/qtservice_win.cpp @@ -0,0 +1,952 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtservice.h" +#include "qtservice_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +# include +#endif +#include +#if defined(QTSERVICE_DEBUG) +#include +#endif + +typedef SERVICE_STATUS_HANDLE(WINAPI*PRegisterServiceCtrlHandler)(const wchar_t*,LPHANDLER_FUNCTION); +static PRegisterServiceCtrlHandler pRegisterServiceCtrlHandler = 0; +typedef BOOL(WINAPI*PSetServiceStatus)(SERVICE_STATUS_HANDLE,LPSERVICE_STATUS); +static PSetServiceStatus pSetServiceStatus = 0; +typedef BOOL(WINAPI*PChangeServiceConfig2)(SC_HANDLE,DWORD,LPVOID); +static PChangeServiceConfig2 pChangeServiceConfig2 = 0; +typedef BOOL(WINAPI*PCloseServiceHandle)(SC_HANDLE); +static PCloseServiceHandle pCloseServiceHandle = 0; +typedef SC_HANDLE(WINAPI*PCreateService)(SC_HANDLE,LPCTSTR,LPCTSTR,DWORD,DWORD,DWORD,DWORD,LPCTSTR,LPCTSTR,LPDWORD,LPCTSTR,LPCTSTR,LPCTSTR); +static PCreateService pCreateService = 0; +typedef SC_HANDLE(WINAPI*POpenSCManager)(LPCTSTR,LPCTSTR,DWORD); +static POpenSCManager pOpenSCManager = 0; +typedef BOOL(WINAPI*PDeleteService)(SC_HANDLE); +static PDeleteService pDeleteService = 0; +typedef SC_HANDLE(WINAPI*POpenService)(SC_HANDLE,LPCTSTR,DWORD); +static POpenService pOpenService = 0; +typedef BOOL(WINAPI*PQueryServiceStatus)(SC_HANDLE,LPSERVICE_STATUS); +static PQueryServiceStatus pQueryServiceStatus = 0; +typedef BOOL(WINAPI*PStartServiceCtrlDispatcher)(CONST SERVICE_TABLE_ENTRY*); +static PStartServiceCtrlDispatcher pStartServiceCtrlDispatcher = 0; +typedef BOOL(WINAPI*PStartService)(SC_HANDLE,DWORD,const wchar_t**); +static PStartService pStartService = 0; +typedef BOOL(WINAPI*PControlService)(SC_HANDLE,DWORD,LPSERVICE_STATUS); +static PControlService pControlService = 0; +typedef HANDLE(WINAPI*PDeregisterEventSource)(HANDLE); +static PDeregisterEventSource pDeregisterEventSource = 0; +typedef BOOL(WINAPI*PReportEvent)(HANDLE,WORD,WORD,DWORD,PSID,WORD,DWORD,LPCTSTR*,LPVOID); +static PReportEvent pReportEvent = 0; +typedef HANDLE(WINAPI*PRegisterEventSource)(LPCTSTR,LPCTSTR); +static PRegisterEventSource pRegisterEventSource = 0; +typedef DWORD(WINAPI*PRegisterServiceProcess)(DWORD,DWORD); +static PRegisterServiceProcess pRegisterServiceProcess = 0; +typedef BOOL(WINAPI*PQueryServiceConfig)(SC_HANDLE,LPQUERY_SERVICE_CONFIG,DWORD,LPDWORD); +static PQueryServiceConfig pQueryServiceConfig = 0; +typedef BOOL(WINAPI*PQueryServiceConfig2)(SC_HANDLE,DWORD,LPBYTE,DWORD,LPDWORD); +static PQueryServiceConfig2 pQueryServiceConfig2 = 0; + + +#define RESOLVE(name) p##name = (P##name)lib.resolve(#name); +#define RESOLVEA(name) p##name = (P##name)lib.resolve(#name"A"); +#define RESOLVEW(name) p##name = (P##name)lib.resolve(#name"W"); + +static bool winServiceInit() +{ + if (!pOpenSCManager) { + QLibrary lib("advapi32"); + + // only resolve unicode versions + RESOLVEW(RegisterServiceCtrlHandler); + RESOLVE(SetServiceStatus); + RESOLVEW(ChangeServiceConfig2); + RESOLVE(CloseServiceHandle); + RESOLVEW(CreateService); + RESOLVEW(OpenSCManager); + RESOLVE(DeleteService); + RESOLVEW(OpenService); + RESOLVE(QueryServiceStatus); + RESOLVEW(StartServiceCtrlDispatcher); + RESOLVEW(StartService); // need only Ansi version + RESOLVE(ControlService); + RESOLVE(DeregisterEventSource); + RESOLVEW(ReportEvent); + RESOLVEW(RegisterEventSource); + RESOLVEW(QueryServiceConfig); + RESOLVEW(QueryServiceConfig2); + } + return pOpenSCManager != 0; +} + +bool QtServiceController::isInstalled() const +{ + Q_D(const QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, 0); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t*)d->serviceName.utf16(), + SERVICE_QUERY_CONFIG); + + if (hService) { + result = true; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::isRunning() const +{ + Q_D(const QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, 0); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_QUERY_STATUS); + if (hService) { + SERVICE_STATUS info; + int res = pQueryServiceStatus(hService, &info); + if (res) + result = info.dwCurrentState != SERVICE_STOPPED; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + + +QString QtServiceController::serviceFilePath() const +{ + Q_D(const QtServiceController); + QString result; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, 0); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_QUERY_CONFIG); + if (hService) { + DWORD sizeNeeded = 0; + char data[8 * 1024]; + if (pQueryServiceConfig(hService, (LPQUERY_SERVICE_CONFIG)data, 8 * 1024, &sizeNeeded)) { + LPQUERY_SERVICE_CONFIG config = (LPQUERY_SERVICE_CONFIG)data; + result = QString::fromUtf16((const ushort*)config->lpBinaryPathName); + } + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +QString QtServiceController::serviceDescription() const +{ + Q_D(const QtServiceController); + QString result; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, 0); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_QUERY_CONFIG); + if (hService) { + DWORD dwBytesNeeded; + char data[8 * 1024]; + if (pQueryServiceConfig2( + hService, + SERVICE_CONFIG_DESCRIPTION, + (unsigned char *)data, + 8096, + &dwBytesNeeded)) { + LPSERVICE_DESCRIPTION desc = (LPSERVICE_DESCRIPTION)data; + if (desc->lpDescription) + result = QString::fromUtf16((const ushort*)desc->lpDescription); + } + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +QtServiceController::StartupType QtServiceController::startupType() const +{ + Q_D(const QtServiceController); + StartupType result = ManualStartup; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, 0); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_QUERY_CONFIG); + if (hService) { + DWORD sizeNeeded = 0; + char data[8 * 1024]; + if (pQueryServiceConfig(hService, (QUERY_SERVICE_CONFIG *)data, 8 * 1024, &sizeNeeded)) { + QUERY_SERVICE_CONFIG *config = (QUERY_SERVICE_CONFIG *)data; + result = config->dwStartType == SERVICE_DEMAND_START ? ManualStartup : AutoStartup; + } + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::uninstall() +{ + Q_D(QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), DELETE); + if (hService) { + if (pDeleteService(hService)) + result = true; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::start(const QStringList &args) +{ + Q_D(QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT); + if (hSCM) { + // Try to open the service + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), SERVICE_START); + if (hService) { + QVector argv(args.size()); + for (int i = 0; i < args.size(); ++i) + argv[i] = (const wchar_t*)args.at(i).utf16(); + + if (pStartService(hService, args.size(), argv.data())) + result = true; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::stop() +{ + Q_D(QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT); + if (hSCM) { + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), SERVICE_STOP|SERVICE_QUERY_STATUS); + if (hService) { + SERVICE_STATUS status; + if (pControlService(hService, SERVICE_CONTROL_STOP, &status)) { + bool stopped = status.dwCurrentState == SERVICE_STOPPED; + int i = 0; + while(!stopped && i < 10) { + Sleep(200); + if (!pQueryServiceStatus(hService, &status)) + break; + stopped = status.dwCurrentState == SERVICE_STOPPED; + ++i; + } + result = stopped; + } else { + qErrnoWarning(GetLastError(), "stopping"); + } + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::pause() +{ + Q_D(QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT); + if (hSCM) { + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_PAUSE_CONTINUE); + if (hService) { + SERVICE_STATUS status; + if (pControlService(hService, SERVICE_CONTROL_PAUSE, &status)) + result = true; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::resume() +{ + Q_D(QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT); + if (hSCM) { + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_PAUSE_CONTINUE); + if (hService) { + SERVICE_STATUS status; + if (pControlService(hService, SERVICE_CONTROL_CONTINUE, &status)) + result = true; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +bool QtServiceController::sendCommand(int code) +{ + Q_D(QtServiceController); + bool result = false; + if (!winServiceInit()) + return result; + + if (code < 0 || code > 127 || !isRunning()) + return result; + + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT); + if (hSCM) { + SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), + SERVICE_USER_DEFINED_CONTROL); + if (hService) { + SERVICE_STATUS status; + if (pControlService(hService, 128 + code, &status)) + result = true; + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +#if defined(QTSERVICE_DEBUG) +# if QT_VERSION >= 0x050000 +extern void qtServiceLogDebug(QtMsgType type, const QMessageLogContext &context, const QString &msg); +# else +extern void qtServiceLogDebug(QtMsgType type, const char* msg); +# endif +#endif + +void QtServiceBase::logMessage(const QString &message, MessageType type, + int id, uint category, const QByteArray &data) +{ +#if defined(QTSERVICE_DEBUG) + QByteArray dbgMsg("[LOGGED "); + switch (type) { + case Error: dbgMsg += "Error] " ; break; + case Warning: dbgMsg += "Warning] "; break; + case Success: dbgMsg += "Success] "; break; + case Information: //fall through + default: dbgMsg += "Information] "; break; + } +# if QT_VERSION >= 0x050000 + qtServiceLogDebug((QtMsgType)-1, QMessageLogContext(), QLatin1String(dbgMsg) + message); +# else + qtServiceLogDebug((QtMsgType)-1, (dbgMsg + message.toAscii()).constData()); +# endif +#endif + + Q_D(QtServiceBase); + if (!winServiceInit()) + return; + WORD wType; + switch (type) { + case Error: wType = EVENTLOG_ERROR_TYPE; break; + case Warning: wType = EVENTLOG_WARNING_TYPE; break; + case Information: wType = EVENTLOG_INFORMATION_TYPE; break; + default: wType = EVENTLOG_SUCCESS; break; + } + HANDLE h = pRegisterEventSource(0, (wchar_t *)d->controller.serviceName().utf16()); + if (h) { + const wchar_t *msg = (wchar_t*)message.utf16(); + const char *bindata = data.size() ? data.constData() : 0; + pReportEvent(h, wType, category, id, 0, 1, data.size(),(const wchar_t **)&msg, + const_cast(bindata)); + pDeregisterEventSource(h); + } +} + +class QtServiceControllerHandler : public QObject +{ + Q_OBJECT +public: + QtServiceControllerHandler(QtServiceSysPrivate *sys); + +protected: + void customEvent(QEvent *e); + +private: + QtServiceSysPrivate *d_sys; +}; + +class QtServiceSysPrivate +{ +public: + enum { + QTSERVICE_STARTUP = 256 + }; + QtServiceSysPrivate(); + + void setStatus( DWORD dwState ); + void setServiceFlags(QtServiceBase::ServiceFlags flags); + DWORD serviceFlags(QtServiceBase::ServiceFlags flags) const; + inline bool available() const; + static void WINAPI serviceMain( DWORD dwArgc, wchar_t** lpszArgv ); + static void WINAPI handler( DWORD dwOpcode ); + + SERVICE_STATUS status; + SERVICE_STATUS_HANDLE serviceStatus; + QStringList serviceArgs; + + static QtServiceSysPrivate *instance; +#if QT_VERSION < 0x050000 + static QCoreApplication::EventFilter nextFilter; +#endif + + QWaitCondition condition; + QMutex mutex; + QSemaphore startSemaphore; + QSemaphore startSemaphore2; + + QtServiceControllerHandler *controllerHandler; + + void handleCustomEvent(QEvent *e); +}; + +QtServiceControllerHandler::QtServiceControllerHandler(QtServiceSysPrivate *sys) + : QObject(), d_sys(sys) +{ + +} + +void QtServiceControllerHandler::customEvent(QEvent *e) +{ + d_sys->handleCustomEvent(e); +} + + +QtServiceSysPrivate *QtServiceSysPrivate::instance = 0; +#if QT_VERSION < 0x050000 +QCoreApplication::EventFilter QtServiceSysPrivate::nextFilter = 0; +#endif + +QtServiceSysPrivate::QtServiceSysPrivate() +{ + instance = this; +} + +inline bool QtServiceSysPrivate::available() const +{ + return 0 != pOpenSCManager; +} + +void WINAPI QtServiceSysPrivate::serviceMain(DWORD dwArgc, wchar_t** lpszArgv) +{ + if (!instance || !QtServiceBase::instance()) + return; + + // Windows spins off a random thread to call this function on + // startup, so here we just signal to the QApplication event loop + // in the main thread to go ahead with start()'ing the service. + + for (DWORD i = 0; i < dwArgc; i++) + instance->serviceArgs.append(QString::fromUtf16((unsigned short*)lpszArgv[i])); + + instance->startSemaphore.release(); // let the qapp creation start + instance->startSemaphore2.acquire(); // wait until its done + // Register the control request handler + instance->serviceStatus = pRegisterServiceCtrlHandler((TCHAR*)QtServiceBase::instance()->serviceName().utf16(), handler); + + if (!instance->serviceStatus) // cannot happen - something is utterly wrong + return; + + handler(QTSERVICE_STARTUP); // Signal startup to the application - + // causes QtServiceBase::start() to be called in the main thread + + // The MSDN doc says that this thread should just exit - the service is + // running in the main thread (here, via callbacks in the handler thread). +} + + +// The handler() is called from the thread that called +// StartServiceCtrlDispatcher, i.e. our HandlerThread, and +// not from the main thread that runs the event loop, so we +// have to post an event to ourselves, and use a QWaitCondition +// and a QMutex to synchronize. +void QtServiceSysPrivate::handleCustomEvent(QEvent *e) +{ + int code = e->type() - QEvent::User; + + switch(code) { + case QTSERVICE_STARTUP: // Startup + QtServiceBase::instance()->start(); + break; + case SERVICE_CONTROL_STOP: + QtServiceBase::instance()->stop(); + QCoreApplication::instance()->quit(); + break; + case SERVICE_CONTROL_PAUSE: + QtServiceBase::instance()->pause(); + break; + case SERVICE_CONTROL_CONTINUE: + QtServiceBase::instance()->resume(); + break; + default: + if (code >= 128 && code <= 255) + QtServiceBase::instance()->processCommand(code - 128); + break; + } + + mutex.lock(); + condition.wakeAll(); + mutex.unlock(); +} + +void WINAPI QtServiceSysPrivate::handler( DWORD code ) +{ + if (!instance) + return; + + instance->mutex.lock(); + switch (code) { + case QTSERVICE_STARTUP: // QtService startup (called from WinMain when started) + instance->setStatus(SERVICE_START_PENDING); + QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code))); + instance->condition.wait(&instance->mutex); + instance->setStatus(SERVICE_RUNNING); + break; + case SERVICE_CONTROL_STOP: // 1 + instance->setStatus(SERVICE_STOP_PENDING); + QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code))); + instance->condition.wait(&instance->mutex); + // status will be reported as stopped by start() when qapp::exec returns + break; + + case SERVICE_CONTROL_PAUSE: // 2 + instance->setStatus(SERVICE_PAUSE_PENDING); + QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code))); + instance->condition.wait(&instance->mutex); + instance->setStatus(SERVICE_PAUSED); + break; + + case SERVICE_CONTROL_CONTINUE: // 3 + instance->setStatus(SERVICE_CONTINUE_PENDING); + QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code))); + instance->condition.wait(&instance->mutex); + instance->setStatus(SERVICE_RUNNING); + break; + + case SERVICE_CONTROL_INTERROGATE: // 4 + break; + + case SERVICE_CONTROL_SHUTDOWN: // 5 + // Don't waste time with reporting stop pending, just do it + QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + SERVICE_CONTROL_STOP))); + instance->condition.wait(&instance->mutex); + // status will be reported as stopped by start() when qapp::exec returns + break; + + default: + if ( code >= 128 && code <= 255 ) { + QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code))); + instance->condition.wait(&instance->mutex); + } + break; + } + + instance->mutex.unlock(); + + // Report current status + if (instance->available() && instance->status.dwCurrentState != SERVICE_STOPPED) + pSetServiceStatus(instance->serviceStatus, &instance->status); +} + +void QtServiceSysPrivate::setStatus(DWORD state) +{ + if (!available()) + return; + status.dwCurrentState = state; + pSetServiceStatus(serviceStatus, &status); +} + +void QtServiceSysPrivate::setServiceFlags(QtServiceBase::ServiceFlags flags) +{ + if (!available()) + return; + status.dwControlsAccepted = serviceFlags(flags); + pSetServiceStatus(serviceStatus, &status); +} + +DWORD QtServiceSysPrivate::serviceFlags(QtServiceBase::ServiceFlags flags) const +{ + DWORD control = 0; + if (flags & QtServiceBase::CanBeSuspended) + control |= SERVICE_ACCEPT_PAUSE_CONTINUE; + if (!(flags & QtServiceBase::CannotBeStopped)) + control |= SERVICE_ACCEPT_STOP; + if (flags & QtServiceBase::NeedsStopOnShutdown) + control |= SERVICE_ACCEPT_SHUTDOWN; + + return control; +} + +#include "qtservice_win.moc" + + +class HandlerThread : public QThread +{ +public: + HandlerThread() + : success(true), console(false), QThread() + {} + + bool calledOk() { return success; } + bool runningAsConsole() { return console; } + +protected: + bool success, console; + void run() + { + SERVICE_TABLE_ENTRYW st [2]; + st[0].lpServiceName = (wchar_t*)QtServiceBase::instance()->serviceName().utf16(); + st[0].lpServiceProc = QtServiceSysPrivate::serviceMain; + st[1].lpServiceName = 0; + st[1].lpServiceProc = 0; + + success = (pStartServiceCtrlDispatcher(st) != 0); // should block + + if (!success) { + if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { + // Means we're started from console, not from service mgr + // start() will ask the mgr to start another instance of us as a service instead + console = true; + } + else { + QtServiceBase::instance()->logMessage(QString("The Service failed to start [%1]").arg(qt_error_string(GetLastError())), QtServiceBase::Error); + } + QtServiceSysPrivate::instance->startSemaphore.release(); // let start() continue, since serviceMain won't be doing it + } + } +}; + +/* + Ignore WM_ENDSESSION system events, since they make the Qt kernel quit +*/ + +#if QT_VERSION >= 0x050000 + +class QtServiceAppEventFilter : public QAbstractNativeEventFilter +{ +public: + QtServiceAppEventFilter() {} + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); +}; + +bool QtServiceAppEventFilter::nativeEventFilter(const QByteArray &, void *message, long *result) +{ + MSG *winMessage = (MSG*)message; + if (winMessage->message == WM_ENDSESSION && (winMessage->lParam & ENDSESSION_LOGOFF)) { + *result = TRUE; + return true; + } + return false; +} + +Q_GLOBAL_STATIC(QtServiceAppEventFilter, qtServiceAppEventFilter) + +#else + +bool myEventFilter(void* message, long* result) +{ + MSG* msg = reinterpret_cast(message); + if (!msg || (msg->message != WM_ENDSESSION) || !(msg->lParam & ENDSESSION_LOGOFF)) + return QtServiceSysPrivate::nextFilter ? QtServiceSysPrivate::nextFilter(message, result) : false; + + if (QtServiceSysPrivate::nextFilter) + QtServiceSysPrivate::nextFilter(message, result); + if (result) + *result = TRUE; + return true; +} + +#endif + +/* There are three ways we can be started: + + - By a service controller (e.g. the Services control panel), with + no (service-specific) arguments. ServiceBase::exec() will then call + start() below, and the service will start. + + - From the console, but with no (service-specific) arguments. This + means we should ask a controller to start the service (i.e. another + instance of this executable), and then just terminate. We discover + this case (as different from the above) by the fact that + StartServiceCtrlDispatcher will return an error, instead of blocking. + + - From the console, with -e(xec) argument. ServiceBase::exec() will + then call ServiceBasePrivate::exec(), which calls + ServiceBasePrivate::run(), which runs the application as a normal + program. +*/ + +bool QtServiceBasePrivate::start() +{ + sysInit(); + if (!winServiceInit()) + return false; + + // Since StartServiceCtrlDispatcher() blocks waiting for service + // control events, we need to call it in another thread, so that + // the main thread can run the QApplication event loop. + HandlerThread* ht = new HandlerThread(); + ht->start(); + + QtServiceSysPrivate* sys = QtServiceSysPrivate::instance; + + // Wait until service args have been received by serviceMain. + // If Windows doesn't call serviceMain (or + // StartServiceControlDispatcher doesn't return an error) within + // a timeout of 20 secs, something is very wrong; give up + if (!sys->startSemaphore.tryAcquire(1, 20000)) + return false; + + if (!ht->calledOk()) { + if (ht->runningAsConsole()) + return controller.start(args.mid(1)); + else + return false; + } + + int argc = sys->serviceArgs.size(); + QVector argv(argc); + QList argvData; + for (int i = 0; i < argc; ++i) + argvData.append(sys->serviceArgs.at(i).toLocal8Bit()); + for (int i = 0; i < argc; ++i) + argv[i] = argvData[i].data(); + + q_ptr->createApplication(argc, argv.data()); + QCoreApplication *app = QCoreApplication::instance(); + if (!app) + return false; + +#if QT_VERSION >= 0x050000 + QAbstractEventDispatcher::instance()->installNativeEventFilter(qtServiceAppEventFilter()); +#else + QtServiceSysPrivate::nextFilter = app->setEventFilter(myEventFilter); +#endif + + sys->controllerHandler = new QtServiceControllerHandler(sys); + + sys->startSemaphore2.release(); // let serviceMain continue (and end) + + sys->status.dwWin32ExitCode = q_ptr->executeApplication(); + sys->setStatus(SERVICE_STOPPED); + + if (ht->isRunning()) + ht->wait(1000); // let the handler thread finish + delete sys->controllerHandler; + sys->controllerHandler = 0; + if (ht->isFinished()) + delete ht; + delete app; + sysCleanup(); + return true; +} + +bool QtServiceBasePrivate::install(const QString &account, const QString &password) +{ + bool result = false; + if (!winServiceInit()) + return result; + + // Open the Service Control Manager + SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if (hSCM) { + QString acc = account; + DWORD dwStartType = startupType == QtServiceController::AutoStartup ? SERVICE_AUTO_START : SERVICE_DEMAND_START; + DWORD dwServiceType = SERVICE_WIN32_OWN_PROCESS; + wchar_t *act = 0; + wchar_t *pwd = 0; + if (!acc.isEmpty()) { + // The act string must contain a string of the format "Domain\UserName", + // so if only a username was specified without a domain, default to the local machine domain. + if (!acc.contains(QChar('\\'))) { + acc.prepend(QLatin1String(".\\")); + } + if (!acc.endsWith(QLatin1String("\\LocalSystem"))) + act = (wchar_t*)acc.utf16(); + } + if (!password.isEmpty() && act) { + pwd = (wchar_t*)password.utf16(); + } + + // Only set INTERACTIVE if act is LocalSystem. (and act should be 0 if it is LocalSystem). + if (!act) dwServiceType |= SERVICE_INTERACTIVE_PROCESS; + + // Create the service + SC_HANDLE hService = pCreateService(hSCM, (wchar_t *)controller.serviceName().utf16(), + (wchar_t *)controller.serviceName().utf16(), + SERVICE_ALL_ACCESS, + dwServiceType, // QObject::inherits ( const char * className ) for no inter active ???? + dwStartType, SERVICE_ERROR_NORMAL, (wchar_t *)filePath().utf16(), + 0, 0, 0, + act, pwd); + if (hService) { + result = true; + if (!serviceDescription.isEmpty()) { + SERVICE_DESCRIPTION sdesc; + sdesc.lpDescription = (wchar_t *)serviceDescription.utf16(); + pChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &sdesc); + } + pCloseServiceHandle(hService); + } + pCloseServiceHandle(hSCM); + } + return result; +} + +QString QtServiceBasePrivate::filePath() const +{ + wchar_t path[_MAX_PATH]; + ::GetModuleFileNameW( 0, path, sizeof(path) ); + return QString::fromUtf16((unsigned short*)path); +} + +bool QtServiceBasePrivate::sysInit() +{ + sysd = new QtServiceSysPrivate(); + + sysd->serviceStatus = 0; + sysd->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS; + sysd->status.dwCurrentState = SERVICE_STOPPED; + sysd->status.dwControlsAccepted = sysd->serviceFlags(serviceFlags); + sysd->status.dwWin32ExitCode = NO_ERROR; + sysd->status.dwServiceSpecificExitCode = 0; + sysd->status.dwCheckPoint = 0; + sysd->status.dwWaitHint = 0; + + return true; +} + +void QtServiceBasePrivate::sysSetPath() +{ + +} + +void QtServiceBasePrivate::sysCleanup() +{ + if (sysd) { + delete sysd; + sysd = 0; + } +} + +void QtServiceBase::setServiceFlags(QtServiceBase::ServiceFlags flags) +{ + if (d_ptr->serviceFlags == flags) + return; + d_ptr->serviceFlags = flags; + if (d_ptr->sysd) + d_ptr->sysd->setServiceFlags(flags); +} + + diff --git a/service/src/qtunixserversocket.cpp b/service/src/qtunixserversocket.cpp new file mode 100644 index 00000000..0ad9134c --- /dev/null +++ b/service/src/qtunixserversocket.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtunixserversocket.h" +#include +#include +#include +#include +#include + +#ifndef SUN_LEN +#define SUN_LEN(ptr) ((size_t)(((struct sockaddr_un *) 0)->sun_path) \ + +strlen ((ptr)->sun_path)) +#endif + +QtUnixServerSocket::QtUnixServerSocket(const QString &path, QObject *parent) + : QTcpServer(parent) +{ + setPath(path); +} + +QtUnixServerSocket::QtUnixServerSocket(QObject *parent) + : QTcpServer(parent) +{ +} + +void QtUnixServerSocket::setPath(const QString &path) +{ + path_.clear(); + + int sock = ::socket(PF_UNIX, SOCK_STREAM, 0); + if (sock != -1) { + struct sockaddr_un addr; + ::memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + ::unlink(path.toLatin1().constData()); // ### This might need to be changed + unsigned int pathlen = strlen(path.toLatin1().constData()); + if (pathlen > sizeof(addr.sun_path)) pathlen = sizeof(addr.sun_path); + ::memcpy(addr.sun_path, path.toLatin1().constData(), pathlen); + if ((::bind(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)) != -1) && + (::listen(sock, 5) != -1)) { + setSocketDescriptor(sock); + path_ = path; + } + } +} + +void QtUnixServerSocket::close() +{ + QTcpServer::close(); + if (!path_.isEmpty()) { + ::unlink(path_.toLatin1().constData()); + path_.clear(); + } +} diff --git a/service/src/qtunixserversocket.h b/service/src/qtunixserversocket.h new file mode 100644 index 00000000..1fc8b702 --- /dev/null +++ b/service/src/qtunixserversocket.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTUNIXSERVERSOCKET_H +#define QTUNIXSERVERSOCKET_H + +#include + +class QtUnixServerSocket : public QTcpServer +{ + Q_OBJECT +public: + QtUnixServerSocket(const QString &path, QObject *parent = 0); + QtUnixServerSocket(QObject *parent = 0); + + void setPath(const QString &path); + void close(); + +private: + QString path_; +}; + + +#endif diff --git a/service/src/qtunixsocket.cpp b/service/src/qtunixsocket.cpp new file mode 100644 index 00000000..f6d4c970 --- /dev/null +++ b/service/src/qtunixsocket.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtunixsocket.h" +#include +#include +#include +#include +#include + +#ifndef SUN_LEN +#define SUN_LEN(ptr) ((size_t)(((struct sockaddr_un *) 0)->sun_path) \ + +strlen ((ptr)->sun_path)) +#endif + +QtUnixSocket::QtUnixSocket(QObject *parent) + : QTcpSocket(parent) +{ +} + +bool QtUnixSocket::connectTo(const QString &path) +{ + bool ret = false; + int sock = ::socket(PF_UNIX, SOCK_STREAM, 0); + if (sock != -1) { + struct sockaddr_un addr; + ::memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + size_t pathlen = strlen(path.toLatin1().constData()); + pathlen = qMin(pathlen, sizeof(addr.sun_path)); + ::memcpy(addr.sun_path, path.toLatin1().constData(), pathlen); + int err = ::connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)); + if (err != -1) { + setSocketDescriptor(sock); + ret = true; + } else { + ::close(sock); + } + } + return ret; +} diff --git a/service/src/qtunixsocket.h b/service/src/qtunixsocket.h new file mode 100644 index 00000000..1d34fba4 --- /dev/null +++ b/service/src/qtunixsocket.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTUNIXSOCKET_H +#define QTUNIXSOCKET_H + +#include + +class QtUnixSocket : public QTcpSocket +{ + Q_OBJECT +public: + QtUnixSocket(QObject *parent = 0); + + bool connectTo(const QString &path); +}; + +#endif