├── .gitignore ├── Changelog ├── Icons ├── Icon1024.png └── create_icns.src ├── Known_Issues ├── LICENSE ├── README.md ├── Stubby.icns ├── StubbyManager.pro ├── configfilemanager.cpp ├── configfilemanager.h ├── daemoncontroller.cpp ├── daemoncontroller.h ├── editconfig.cpp ├── editconfig.h ├── editconfig.ui ├── gitversion.pri ├── logging.cpp ├── logging.h ├── logging.ui ├── main.cpp ├── msgdefs.cpp ├── msgdefs.h ├── nativenotification.h ├── nativenotification.mm ├── nativestatusbutton.h ├── nativestatusbutton.mm ├── resources.qrc ├── runtask.cpp ├── runtask.h ├── stubby@245x145.png ├── stubbymanager.cpp ├── stubbymanager.h ├── stubbymanager.ui ├── stubbysetdns.cpp └── stubbysetdns.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Qt user/platform specific settings. # 2 | *.pro.user 3 | build/* 4 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | Version 0.2.6 - 2019-4-16 2 | 3 | * Bumping version to match Stubby 4 | 5 | Version 0.1.0-alpha - 2017-12 6 | 7 | * First alpha version of GUI 8 | -------------------------------------------------------------------------------- /Icons/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sinodun/stubby_manager_gui/958d5a4307c8c16deb1aa225edf2a373a12c2887/Icons/Icon1024.png -------------------------------------------------------------------------------- /Icons/create_icns.src: -------------------------------------------------------------------------------- 1 | mkdir MyIcon.iconset 2 | sips -z 16 16 Icon1024.png --out MyIcon.iconset/icon_16x16.png 3 | sips -z 32 32 Icon1024.png --out MyIcon.iconset/icon_16x16@2x.png 4 | sips -z 32 32 Icon1024.png --out MyIcon.iconset/icon_32x32.png 5 | sips -z 64 64 Icon1024.png --out MyIcon.iconset/icon_32x32@2x.png 6 | sips -z 128 128 Icon1024.png --out MyIcon.iconset/icon_128x128.png 7 | sips -z 256 256 Icon1024.png --out MyIcon.iconset/icon_128x128@2x.png 8 | sips -z 256 256 Icon1024.png --out MyIcon.iconset/icon_256x256.png 9 | sips -z 512 512 Icon1024.png --out MyIcon.iconset/icon_256x256@2x.png 10 | sips -z 512 512 Icon1024.png --out MyIcon.iconset/icon_512x512.png 11 | cp Icon1024.png MyIcon.iconset/icon_512x512@2x.png 12 | iconutil -c icns MyIcon.iconset 13 | rm -R MyIcon.iconset 14 | mv MyIcon.icns ../Stubby.icns 15 | -------------------------------------------------------------------------------- /Known_Issues: -------------------------------------------------------------------------------- 1 | Known Issues 2 | ---------------- 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *** This project is no longer maintained, work on a newer GUI is active at https://github.com/Sinodun/Stubby_Manager *** 2 | 3 | # Stubby Manager GUI 4 | 5 | A Prototype GUI to aid using Stubby on macOS. 6 | 7 | Developed by Sinodun IT. 8 | 9 | ## Installation 10 | 11 | * This code assumes that getdns and stubby have already been built and installed. 12 | * Also that the file stubby-ui-helper from the stubby build output is manually 13 | installed in /usr/local/sbin and has setUID privileges. 14 | * Additionally, a plist called org.getdns.stubby.plist for the stubby service 15 | must be installed in /Library/LaunchDaemons (for example plist content, see 16 | https://github.com/getdnsapi/stubby/macos/org.getdns.stubbydev.plist) 17 | 18 | As part of installation, authorization rights for Stubby actions must also be 19 | installed. Example plist files are also available in 20 | https://github.com/getdnsapi/stubby/macos/ 21 | 22 | Run the following from the command line: 23 | 24 | ``` 25 | $ security authorizationdb write net.getdnsapi.stubby.daemon.run < rights.daemon.run.plist 26 | $ security authorizationdb write net.getdnsapi.stubby.dns.local < rights.dns.local.plist 27 | ``` 28 | 29 | ## Icons 30 | 31 | To generate a new set of icons (Stubby.icns) from a single image 32 | 33 | * cd into the Icons directory 34 | * update the Icon1024.png file with the new image 35 | * run `source create_icns.src` 36 | 37 | 38 | ## Contributors 39 | 40 | * Georgina Hawes 41 | * Sara Dickinson 42 | * John Dickinson 43 | * Jim Hague 44 | * Molly Carton 45 | -------------------------------------------------------------------------------- /Stubby.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sinodun/stubby_manager_gui/958d5a4307c8c16deb1aa225edf2a373a12c2887/Stubby.icns -------------------------------------------------------------------------------- /StubbyManager.pro: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, 2019 Sinodun Internet Technologies Ltd. 3 | # 4 | # This Source Code Form is subject to the terms of the Mozilla Public 5 | # License, v. 2.0. If a copy of the MPL was not distributed with this 6 | # file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | # 8 | 9 | #------------------------------------------------- 10 | # 11 | # Project created by QtCreator 2017-06-05T16:45:10 12 | # 13 | #------------------------------------------------- 14 | 15 | QT += core gui 16 | 17 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 18 | 19 | TARGET = StubbyManager 20 | TEMPLATE = app 21 | 22 | message($$QMAKESPEC) 23 | 24 | macx { 25 | message(using macx to set scope) 26 | QT += macextras 27 | ICON = Stubby.icns 28 | LIBS += -framework Foundation 29 | LIBS += -framework AppKit 30 | LIBS += -framework Security 31 | } 32 | 33 | include(gitversion.pri) 34 | 35 | # The following define makes your compiler emit warnings if you use 36 | # any feature of Qt which as been marked as deprecated (the exact warnings 37 | # depend on your compiler). Please consult the documentation of the 38 | # deprecated API in order to know how to port your code away from it. 39 | DEFINES += QT_DEPRECATED_WARNINGS 40 | 41 | # You can also make your code fail to compile if you use deprecated APIs. 42 | # In order to do so, uncomment the following line. 43 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 44 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 45 | 46 | SOURCES += \ 47 | main.cpp \ 48 | stubbymanager.cpp \ 49 | daemoncontroller.cpp \ 50 | runtask.cpp \ 51 | logging.cpp \ 52 | editconfig.cpp \ 53 | stubbysetdns.cpp \ 54 | nativenotification.mm \ 55 | nativestatusbutton.mm \ 56 | configfilemanager.cpp \ 57 | msgdefs.cpp 58 | 59 | HEADERS += \ 60 | stubbymanager.h \ 61 | daemoncontroller.h \ 62 | runtask.h \ 63 | logging.h \ 64 | editconfig.h \ 65 | stubbysetdns.h \ 66 | nativenotification.h \ 67 | nativestatusbutton.h \ 68 | configfilemanager.h \ 69 | msgdefs.h 70 | 71 | FORMS += \ 72 | stubbymanager.ui \ 73 | logging.ui \ 74 | editconfig.ui 75 | 76 | RESOURCES += \ 77 | resources.qrc 78 | 79 | DISTFILES += \ 80 | README.md \ 81 | Changelog \ 82 | Known_Issues \ 83 | LICENSE 84 | -------------------------------------------------------------------------------- /configfilemanager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "configfilemanager.h" 10 | #include 11 | #include "QTextStream" 12 | #include "runtask.h" 13 | #include "msgdefs.h" 14 | #include 15 | 16 | 17 | const QString CONFIG_RUNTIME_FILE_NAME = "/usr/local/etc/stubby/stubby.yml"; // Runtime file used by stubby when it runs 18 | const QString CONFIG_DEFAULT_FILE_NAME = "/usr/local/etc/stubby/stubby.yml.example"; // Example file used to revert to defaults 19 | const QString CONFIG_TEMP_FILE_NAME = "/tmp/stubby.temp"; // File used to hold changes made in the edit window. 20 | // Only applied when user hits 'Apply' in the main window. 21 | const QString CONFIG_VAL_FILE_NAME = "/tmp/stubby.yml.validation"; // File used purely to check if the config if valid using check_config 22 | 23 | /* 24 | * public member functions 25 | */ 26 | ConfigFileManager::ConfigFileManager(QObject *parent) : 27 | m_parent(parent) 28 | { 29 | } 30 | 31 | /* 32 | * check the current config file for GETDNS_AUTHENTICATION_REQUIRED. 33 | */ 34 | bool ConfigFileManager::strictAuthentication(bool *ok) 35 | { 36 | QString fileText = getRuntimeConfig(ok); 37 | if (!*ok) return false; 38 | 39 | //TODO: Add function to ui helper to determine this 40 | if (fileText.contains(QRegExp("\\ntls_authentication:\\s*GETDNS_AUTHENTICATION_REQUIRED"))) { 41 | return true; 42 | } else { 43 | return false; 44 | } 45 | } 46 | 47 | /* 48 | * set strict authentication 49 | */ 50 | void ConfigFileManager::setStrictAuthentication(bool *ok) 51 | { 52 | QString fileText = getRuntimeConfig(ok); 53 | if (!*ok) return; 54 | 55 | if (fileText.contains(QRegExp("\\ntls_authentication:\\s*GETDNS_AUTHENTICATION_REQUIRED"))) { 56 | *ok = true; 57 | return; /* nothing to do */ 58 | } else { 59 | fileText.insert(fileText.lastIndexOf("}"), ", tls_authentication: GETDNS_AUTHENTICATION_NONE\n"); 60 | saveTempConfig(fileText, ok); 61 | } 62 | *ok = false; 63 | } 64 | 65 | /* 66 | * set opportunistic authentication 67 | */ 68 | void ConfigFileManager::setOpportunisticAuthentication(bool *ok) 69 | { 70 | QString fileText = getRuntimeConfig(ok); 71 | if (!*ok) return; 72 | *ok = false; 73 | 74 | } 75 | 76 | /* 77 | * get the contents of the config (from the current file) 78 | */ 79 | QString ConfigFileManager::getRuntimeConfig(bool *ok) 80 | { 81 | return getConfigFromFile(CONFIG_RUNTIME_FILE_NAME, ok); 82 | } 83 | 84 | /* 85 | * save config text (in a temporary file) 86 | */ 87 | void ConfigFileManager::saveTempConfig(const QString text, bool *ok) 88 | { 89 | /*if a temp config file already exists, delete it*/ 90 | deleteFile(CONFIG_TEMP_FILE_NAME, ok); 91 | if (!*ok) 92 | SMDebug(eConfiguration, "Error deleting %s", CONFIG_TEMP_FILE_NAME.toLatin1().data()); 93 | 94 | saveConfigTextToFile(CONFIG_TEMP_FILE_NAME, text, ok); 95 | } 96 | 97 | /* 98 | * Most recent saved config - ie, 99 | * if temporary file exists, read from that, otherwise read from the current file. 100 | */ 101 | QString ConfigFileManager::getLastSavedConfig(bool *ok) 102 | { 103 | QFile file(CONFIG_TEMP_FILE_NAME); 104 | 105 | if (file.exists()) { 106 | return getConfigFromFile(CONFIG_TEMP_FILE_NAME, ok); 107 | } else { 108 | return getRuntimeConfig(ok); 109 | } 110 | } 111 | 112 | /* 113 | * revert Config to default 114 | */ 115 | void ConfigFileManager::saveDefaultToTempConfig(bool *ok) 116 | { 117 | QString text = getConfigFromFile(CONFIG_DEFAULT_FILE_NAME, ok); 118 | if (*ok) { 119 | saveConfigTextToFile(CONFIG_TEMP_FILE_NAME, text, ok); 120 | } 121 | } 122 | 123 | /* 124 | * discard latest changes, i.e. delete the temporary file. 125 | * (After doing this, will also need to restore edit text to the current config) 126 | */ 127 | void ConfigFileManager::discardTempConfigFile(bool *ok) 128 | { 129 | deleteFile(CONFIG_TEMP_FILE_NAME, ok); 130 | } 131 | 132 | /* 133 | * changes exist? ie temporary file exists? 134 | */ 135 | bool ConfigFileManager::tempConfigFileExists() 136 | { 137 | return QFile(CONFIG_TEMP_FILE_NAME).exists(); 138 | } 139 | 140 | /* 141 | * validate the configuration. Write the config to a temp file and parse with getdns_query. Delete the file afterwards. 142 | * Return code is 0 if file validated without problem, ok is true if validation occured (no error in process). 143 | */ 144 | bool ConfigFileManager::isValidConfigText(QString text, bool *ok) 145 | { 146 | saveConfigTextToFile(CONFIG_VAL_FILE_NAME, text, ok); 147 | if (!*ok) { 148 | SMDebug(eConfiguration, "Error saving configuration to %s before validation", CONFIG_VAL_FILE_NAME.toLatin1().data()); 149 | return false; 150 | } 151 | 152 | RunHelperTask validationProcess("check_config", QString(), CONFIG_VAL_FILE_NAME, m_parent); 153 | int exit_code = validationProcess.execute(); 154 | 155 | if (!QFile(CONFIG_VAL_FILE_NAME).remove()) 156 | qDebug() << __FILE__ << ":" << __FUNCTION__ << ": Error clearing up after parse"; 157 | 158 | *ok = ((exit_code == 0) || exit_code == 1); 159 | return (exit_code == 0); 160 | } 161 | 162 | /* 163 | * apply the configuration - i.e. overwrite the current file with the temporary file. 164 | */ 165 | void ConfigFileManager::applyConfig(bool *ok) 166 | { 167 | if (QFile(CONFIG_TEMP_FILE_NAME).exists()) { 168 | QString text = getConfigFromFile(CONFIG_TEMP_FILE_NAME, ok); 169 | if (*ok) { 170 | //saveConfigTextToFile(CONFIG_RUNTIME_FILE_NAME, text, ok); 171 | RunHelperTask applyconfigProcess("write_config", RunHelperTask::RIGHT_DAEMON_RUN, CONFIG_TEMP_FILE_NAME, m_parent); 172 | int exit_code = applyconfigProcess.execute(); 173 | if (*ok && exit_code) deleteFile(CONFIG_TEMP_FILE_NAME, ok); 174 | } 175 | } else { 176 | /*nothing to do*/ 177 | *ok = true; 178 | } 179 | } 180 | 181 | /* 182 | * Private member functions 183 | */ 184 | QString ConfigFileManager::getConfigFromFile(const QString filename, bool *ok) 185 | { 186 | QFile file(filename); 187 | 188 | if (!file.exists()) { 189 | SMDebug(eConfiguration, "%s does not exist", filename.toLatin1().data()); 190 | *ok = false; 191 | return QString(); 192 | } 193 | 194 | if (!file.open(QFile::ReadOnly|QFile::Text)) { 195 | SMDebug(eConfiguration, "Error opening %s", filename.toLatin1().data()); 196 | *ok = false; 197 | return QString(); 198 | } 199 | 200 | QTextStream in(&file); 201 | *ok = true; 202 | return in.readAll(); 203 | } 204 | 205 | void ConfigFileManager::saveConfigTextToFile(const QString filename, const QString text, bool *ok) 206 | { 207 | QFile file(filename); 208 | if (file.open(QFile::WriteOnly|QFile::Text)) { 209 | QTextStream out(&file); 210 | out << text; 211 | out.flush(); 212 | *ok = true; 213 | } else { 214 | *ok = false; 215 | } 216 | } 217 | 218 | void ConfigFileManager::deleteFile(const QString filename, bool *ok) 219 | { 220 | QFile file(filename); 221 | 222 | if (!file.exists()) { 223 | *ok = true; //it's ok if there's no file - nothing to discard 224 | return; 225 | } 226 | 227 | *ok = file.remove(); 228 | } 229 | -------------------------------------------------------------------------------- /configfilemanager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef CONFIGFILE_H 10 | #define CONFIGFILE_H 11 | 12 | #include 13 | #include 14 | 15 | 16 | class ConfigFileManager 17 | { 18 | public: 19 | ConfigFileManager(QObject *parent = 0); 20 | 21 | /* 22 | * check the current config file for GETDNS_AUTHENTICATION_REQUIRED. 23 | */ 24 | bool strictAuthentication(bool *ok); 25 | 26 | /* 27 | * set strict authentication 28 | */ 29 | void setStrictAuthentication(bool *ok); 30 | 31 | /* 32 | * set opportunistic authentication 33 | */ 34 | void setOpportunisticAuthentication(bool *ok); 35 | 36 | /* 37 | * get the contents of the config (from the current file) 38 | */ 39 | QString getRuntimeConfig(bool *ok); 40 | 41 | /* 42 | * save config text (in a temporary file) 43 | */ 44 | void saveTempConfig(const QString text, bool *ok); 45 | 46 | /* 47 | * Most recent saved config - ie, 48 | * if temporary file exists, read from that, otherwise read from the current file. 49 | */ 50 | QString getLastSavedConfig(bool *ok); 51 | 52 | /* 53 | * revert Config to default 54 | */ 55 | void saveDefaultToTempConfig(bool *ok); 56 | 57 | /* 58 | * discard config changes, i.e. delete the temporary file. 59 | */ 60 | void discardTempConfigFile(bool *ok); 61 | 62 | /* 63 | * changes exist? ie temporary file exists? 64 | */ 65 | bool tempConfigFileExists(); 66 | 67 | /* 68 | * validate the configuration. Write the config to a temp file and parse with getdns_query. Delete the file afterwards. 69 | */ 70 | bool isValidConfigText(QString text, bool *ok); 71 | 72 | /* 73 | * apply the configuration. ie copy stubby.temp to stubby.conf 74 | */ 75 | void applyConfig(bool *ok); 76 | 77 | private: 78 | QString getConfigFromFile(const QString filename, bool *ok); 79 | void saveConfigTextToFile(const QString filename, const QString text, bool *ok); 80 | void deleteFile(const QString filename, bool *ok); 81 | 82 | QObject *m_parent; 83 | }; 84 | 85 | #endif // CONFIGFILE_H 86 | -------------------------------------------------------------------------------- /daemoncontroller.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "msgdefs.h" 10 | #include 11 | #include 12 | 13 | #include "daemoncontroller.h" 14 | #include "stubbymanager.h" 15 | 16 | DaemonController::DaemonController(StubbyManager *parent) : 17 | QObject(parent), 18 | m_daemonState(DaemonController::Unknown), 19 | m_manager(parent), 20 | m_starterProcess(0), 21 | m_stopperProcess(0), 22 | m_checkerProcess(0), 23 | m_checkerProcess_output("") 24 | { 25 | m_starterProcess = new RunHelperTask("start", RunHelperTask::RIGHT_DAEMON_RUN, QString(), this, parent); 26 | m_stopperProcess = new RunHelperTask("stop", RunHelperTask::RIGHT_DAEMON_RUN, QString(), this, parent); 27 | m_checkerProcess = new RunHelperTask("list", QString(), QString(), this, parent); 28 | 29 | connect(m_starterProcess, SIGNAL(readyReadStandardError()), this, SLOT(on_starterProcess_readyReadStderr())); 30 | connect(m_starterProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(on_starterProcess_readyReadStdout())); 31 | connect(m_starterProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_starterProcess_errorOccurred(QProcess::ProcessError))); 32 | 33 | connect(m_stopperProcess, SIGNAL(readyReadStandardError()), this, SLOT(on_stopperProcess_readyReadStderr())); 34 | connect(m_stopperProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(on_stopperProcess_readyReadStdout())); 35 | connect(m_stopperProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_stopperProcess_errorOccurred(QProcess::ProcessError))); 36 | 37 | connect(m_checkerProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_checkerProcess_finished(int, QProcess::ExitStatus))); 38 | connect(m_checkerProcess, SIGNAL(readyReadStandardError()), this, SLOT(on_checkerProcess_readyReadStderr())); 39 | connect(m_checkerProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(on_checkerProcess_readyReadStdout())); 40 | connect(m_checkerProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_checkerProcess_errorOccurred(QProcess::ProcessError))); 41 | 42 | } 43 | 44 | DaemonController::~DaemonController() 45 | { 46 | SMDebug(eDestructors,""); 47 | 48 | } 49 | 50 | int DaemonController::startDaemon() 51 | { 52 | if (daemonState() == Unknown || daemonState() == NotRunning) { 53 | connect(m_starterProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_starterProcess_finished(int, QProcess::ExitStatus))); 54 | setDaemonState(Starting); 55 | m_starterProcess->start(); 56 | } 57 | return 0; 58 | } 59 | 60 | int DaemonController::stopDaemon() 61 | { 62 | if (daemonState() == Unknown || daemonState() == Running) { 63 | connect(m_stopperProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_stopperProcess_finished(int, QProcess::ExitStatus))); 64 | setDaemonState(Stopping); 65 | m_stopperProcess->start(); 66 | } 67 | return 0; 68 | } 69 | 70 | int DaemonController::restartDaemon() 71 | { 72 | if (daemonState() == Unknown || daemonState() == Running) { 73 | setDaemonState(Stopping); 74 | connect(m_stopperProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_restartStopperProcess_finished(int, QProcess::ExitStatus))); 75 | m_stopperProcess->start(); 76 | } 77 | return 0; 78 | } 79 | 80 | int DaemonController::checkDaemon() 81 | { 82 | m_checkerProcess_output = ""; 83 | m_checkerProcess->start(); 84 | return 0; 85 | } 86 | 87 | /* private slots to handle QProcess signals */ 88 | 89 | void DaemonController::on_starterProcess_finished(int exitCode, QProcess::ExitStatus exitStatus) 90 | { 91 | SMDebug(eDaemon, "Exit status %d, launchstl exit code %d", exitStatus, exitCode); 92 | disconnect(m_starterProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_starterProcess_finished(int, QProcess::ExitStatus))); 93 | 94 | checkDaemon(); 95 | } 96 | 97 | void DaemonController::on_starterProcess_readyReadStderr() 98 | { 99 | 100 | } 101 | 102 | void DaemonController::on_starterProcess_readyReadStdout() 103 | { 104 | 105 | } 106 | 107 | void DaemonController::on_starterProcess_errorOccurred(QProcess::ProcessError) 108 | { 109 | 110 | } 111 | 112 | void DaemonController::on_restartStarterProcess_finished(int exitCode, QProcess::ExitStatus exitStatus) 113 | { 114 | SMDebug(eDaemon, "Exit status %d, launchstl exit code %d", exitStatus, exitCode); 115 | disconnect(m_starterProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_restartStarterProcess_finished(int, QProcess::ExitStatus))); 116 | 117 | checkDaemon(); 118 | } 119 | 120 | void DaemonController::on_stopperProcess_finished(int exitCode, QProcess::ExitStatus exitStatus) 121 | { 122 | SMDebug(eDaemon, "Exit status %d, launchstl exit code %d", exitStatus, exitCode); 123 | disconnect(m_stopperProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_stopperProcess_finished(int, QProcess::ExitStatus))); 124 | 125 | checkDaemon(); 126 | } 127 | 128 | void DaemonController::on_stopperProcess_readyReadStderr() 129 | { 130 | 131 | } 132 | 133 | void DaemonController::on_stopperProcess_readyReadStdout() 134 | { 135 | 136 | } 137 | 138 | void DaemonController::on_stopperProcess_errorOccurred(QProcess::ProcessError) 139 | { 140 | 141 | } 142 | 143 | void DaemonController::on_restartStopperProcess_finished(int exitCode, QProcess::ExitStatus exitStatus) 144 | { 145 | SMDebug(eDaemon, "Exit status %d, launchstl exit code %d", exitStatus, exitCode); 146 | 147 | disconnect(m_stopperProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_restartStopperProcess_finished(int, QProcess::ExitStatus))); 148 | 149 | connect(m_starterProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_restartStarterProcess_finished(int, QProcess::ExitStatus))); 150 | m_starterProcess->start(); 151 | 152 | } 153 | 154 | void DaemonController::on_checkerProcess_finished(int exitCode, QProcess::ExitStatus exitStatus) 155 | { 156 | SMDebug(eDaemon, "Exit status %d, launchstl exit code %d", exitStatus, exitCode); 157 | 158 | if (exitStatus == QProcess::NormalExit) { 159 | /* search for "PID" = integer in the text */ 160 | QRegularExpression pidRegEx("\"PID\" = \\d+;"); 161 | QRegularExpressionMatch match; 162 | match = pidRegEx.match(m_checkerProcess_output); 163 | if (match.hasMatch()) { 164 | SMDebug(eDaemon, "matched string is %s", match.captured().toLatin1().data()); 165 | m_daemonState = Running; 166 | emit daemonStateChanged(m_daemonState); 167 | } else { 168 | m_daemonState = NotRunning; 169 | emit daemonStateChanged(m_daemonState); 170 | } 171 | } else { 172 | m_daemonState = Unknown; 173 | emit daemonStateChanged(m_daemonState); 174 | } 175 | } 176 | 177 | void DaemonController::on_checkerProcess_readyReadStderr() 178 | { 179 | 180 | } 181 | 182 | void DaemonController::on_checkerProcess_readyReadStdout() 183 | { 184 | m_checkerProcess_output = m_checkerProcess_output + QString::fromLatin1(m_checkerProcess->readAllStandardOutput().data()); 185 | //SMDebug(eDaemon, "Output from launchctl list - %s", m_checkerProcess_output.toLatin1().data()); 186 | } 187 | 188 | void DaemonController::on_checkerProcess_errorOccurred(QProcess::ProcessError) 189 | { 190 | 191 | } 192 | 193 | -------------------------------------------------------------------------------- /daemoncontroller.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef DAEMONCONTROLLER_H 10 | #define DAEMONCONTROLLER_H 11 | 12 | #include 13 | 14 | #include "runtask.h" 15 | 16 | class DaemonController : public QObject 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | DaemonController(StubbyManager *parent); 22 | virtual ~DaemonController(); 23 | 24 | typedef enum { 25 | NotRunning, 26 | Starting, 27 | Stopping, 28 | Running, 29 | Unknown 30 | } DaemonState; 31 | 32 | int startDaemon(); 33 | int stopDaemon(); 34 | int restartDaemon(); 35 | int checkDaemon(); 36 | DaemonState daemonState() { return m_daemonState; } 37 | 38 | private: 39 | void setDaemonState(const DaemonState state) { m_daemonState = state; } 40 | 41 | public: 42 | signals: 43 | void daemonStateChanged(DaemonController::DaemonState); 44 | 45 | private slots: 46 | void on_starterProcess_finished(int, QProcess::ExitStatus); 47 | void on_starterProcess_readyReadStderr(); 48 | void on_starterProcess_readyReadStdout(); 49 | void on_starterProcess_errorOccurred(QProcess::ProcessError); 50 | void on_restartStarterProcess_finished(int exitCode, QProcess::ExitStatus exitStatus); 51 | 52 | void on_stopperProcess_finished(int, QProcess::ExitStatus); 53 | void on_stopperProcess_readyReadStderr(); 54 | void on_stopperProcess_readyReadStdout(); 55 | void on_stopperProcess_errorOccurred(QProcess::ProcessError); 56 | void on_restartStopperProcess_finished(int exitCode, QProcess::ExitStatus exitStatus); 57 | 58 | void on_checkerProcess_finished(int, QProcess::ExitStatus); 59 | void on_checkerProcess_readyReadStderr(); 60 | void on_checkerProcess_readyReadStdout(); 61 | void on_checkerProcess_errorOccurred(QProcess::ProcessError); 62 | 63 | private: 64 | DaemonState m_daemonState; 65 | StubbyManager *m_manager; 66 | RunHelperTask *m_starterProcess; 67 | RunHelperTask *m_stopperProcess; 68 | RunHelperTask *m_checkerProcess; 69 | QString m_checkerProcess_output; 70 | }; 71 | 72 | #endif // DAEMONCONTROLLER_H 73 | -------------------------------------------------------------------------------- /editconfig.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "editconfig.h" 10 | #include "ui_editconfig.h" 11 | #include "configfilemanager.h" 12 | #include "msgdefs.h" 13 | #include 14 | 15 | EditConfig::EditConfig(QWidget *parent) : 16 | QDialog(parent, Qt::Sheet), 17 | ui(new Ui::EditConfig), 18 | m_fileName(""), 19 | m_defaultFileName("") 20 | { 21 | ui->setupUi(this); 22 | setSizeGripEnabled((true)); 23 | SMDebug(eConfiguration, "sizeGripEnabled is %s", (isSizeGripEnabled() ? "true" : "false")); 24 | 25 | connect(this, SIGNAL(rejected()), this, SLOT(on_rejected())); 26 | 27 | bool ok; 28 | 29 | /* Open the most recently edited configuration*/ 30 | QString configText = ConfigFileManager().getLastSavedConfig(&ok); 31 | if (ok) { 32 | ui->plainTextEdit->setPlainText(configText); 33 | } else { 34 | ui->plainTextEdit->setPlainText("Error opening config file"); 35 | } 36 | } 37 | 38 | EditConfig::~EditConfig() 39 | { 40 | SMDebug(eDestructors,""); 41 | delete ui; 42 | } 43 | 44 | void EditConfig:: on_rejected() 45 | { 46 | SMDebug(eConfiguration, ""); 47 | 48 | /*there may be changes from earlier*/ 49 | emit doneEditing(ConfigFileManager().tempConfigFileExists()); 50 | } 51 | 52 | void EditConfig::on_saveButton_clicked() 53 | { 54 | SMDebug(eConfiguration, ""); 55 | 56 | bool ok; 57 | 58 | QString text = ui->plainTextEdit->toPlainText(); 59 | if (ConfigFileManager().isValidConfigText(text, &ok) && ok) { 60 | ConfigFileManager().saveTempConfig(text, &ok); //saves to temporary file, overwriting if one already exists. 61 | qDebug() << __FILE__ << ":" << __FUNCTION__ << ": config saved to temp file"; 62 | if (ConfigFileManager().getRuntimeConfig(&ok) == text) { 63 | ConfigFileManager().discardTempConfigFile(&ok); 64 | emit doneEditing(false); 65 | } else { 66 | emit doneEditing(true); 67 | } 68 | } else { 69 | SMWarning("Edited configuration could not be validated - it was not saved"); 70 | emit newStatusMessage("Edited configuration could not be validated - it was not saved"); 71 | 72 | ui->validateLabel->setText("Not valid - config cannot be saved."); 73 | } 74 | } 75 | 76 | void EditConfig::on_validateButton_clicked() 77 | { 78 | bool ok; 79 | if (ConfigFileManager().isValidConfigText(ui->plainTextEdit->toPlainText(), &ok) && ok) { 80 | ui->validateLabel->setText("Valid!"); 81 | } else { 82 | if (ok) { 83 | ui->validateLabel->setText("Not valid"); 84 | } else { 85 | ui->validateLabel->setText("Error"); 86 | SMDebug(eConfiguration, "Error during validation"); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /editconfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef EDITCONFIG_H 10 | #define EDITCONFIG_H 11 | 12 | #include 13 | #include 14 | 15 | namespace Ui { 16 | class EditConfig; 17 | } 18 | 19 | class EditConfig : public QDialog 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit EditConfig(QWidget *parent = 0); 25 | ~EditConfig(); 26 | 27 | signals: 28 | void doneEditing(const bool changesExist); 29 | void newStatusMessage(const QString msg); 30 | 31 | private slots: 32 | void on_rejected(); 33 | 34 | void on_validateButton_clicked(); 35 | void on_saveButton_clicked(); 36 | 37 | private: 38 | Ui::EditConfig *ui; 39 | QString m_fileName; 40 | QString m_defaultFileName; 41 | }; 42 | 43 | #endif // EDITCONFIG_H 44 | -------------------------------------------------------------------------------- /editconfig.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | EditConfig 4 | 5 | 6 | 7 | 0 8 | 0 9 | 689 10 | 500 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Edit Stubby Config 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 500 31 | 300 32 | 33 | 34 | 35 | 36 | Courier 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Click to find out if conifguration is valid 47 | 48 | 49 | Validate Config 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 40 58 | 0 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Click OK to save, cancel to discard changes. 70 | 71 | 72 | Qt::Horizontal 73 | 74 | 75 | QDialogButtonBox::Cancel 76 | 77 | 78 | 79 | 80 | 81 | 82 | Click to Save Config 83 | 84 | 85 | true 86 | 87 | 88 | Save 89 | 90 | 91 | false 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | buttonBox 106 | accepted() 107 | EditConfig 108 | accept() 109 | 110 | 111 | 248 112 | 254 113 | 114 | 115 | 157 116 | 274 117 | 118 | 119 | 120 | 121 | buttonBox 122 | rejected() 123 | EditConfig 124 | reject() 125 | 126 | 127 | 316 128 | 260 129 | 130 | 131 | 286 132 | 274 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /gitversion.pri: -------------------------------------------------------------------------------- 1 | # If there is no version tag in git this one will be used 2 | VERSION = 0.1.0 3 | 4 | # Need to discard STDERR so get path to NULL device 5 | win32 { 6 | NULL_DEVICE = NUL # Windows doesn't have /dev/null but has NUL 7 | } else { 8 | NULL_DEVICE = /dev/null 9 | } 10 | 11 | # Trying to get version from git tag / revision 12 | GIT_VERSION = $$system(git describe --long 2> $$NULL_DEVICE) 13 | 14 | # Version is initial dd.dd.dd from tag. 15 | VERSION = $$GIT_VERSION 16 | VERSION ~= s/-\d+-.*// 17 | 18 | # Bundle version is VERSION plus 'd' and number of commits if not 0. 19 | BUNDLE_VERSION = $$GIT_VERSION 20 | BUNDLE_VERSION ~= s/-(\d+)-.*/d\1/ 21 | BUNDLE_VERSION ~= s/d0$// 22 | 23 | DEFINES += GIT_VERSION=\\\"$$GIT_VERSION\\\" APP_VERSION=\\\"$$VERSION\\\" 24 | 25 | # With Qt 5.12.1, Qt Creator 4.8.1, no version info gets into Info.plist. 26 | # Add it. CFBundleShortVersionString and CFBundleVersion aren't set when 27 | # Info.plist is created. We could just Add them, but that will error 28 | # if either has already been added (i.e. we're not doing a clean build). 29 | # So merge it in if missing; this is the only way I can find which doesn't 30 | # error if either already exists. It doesn't update them either, which 31 | # is not what I'd prefer, but I'l settle for. 32 | macx { 33 | INFO_PLIST_PATH = $$shell_quote($${OUT_PWD}/$${TARGET}.app/Contents/Info.plist) 34 | QMAKE_POST_LINK += /usr/libexec/PlistBuddy -c \"Clear dict\" -c \"Add :CFBundleShortVersionString string $${VERSION}\" -c \"Add :CFBundleVersion string $${BUNDLE_VERSION}\" version.plist; /usr/libexec/PlistBuddy -c \"Merge version.plist\" -c \"Set :CFBundleGetInfoString $${GIT_VERSION}\" $${INFO_PLIST_PATH} 35 | } 36 | -------------------------------------------------------------------------------- /logging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "logging.h" 10 | #include "ui_logging.h" 11 | #include "msgdefs.h" 12 | #include 13 | 14 | Logging::Logging(QWidget *parent) : 15 | QWidget(parent, Qt::Window), //Without Qt::Window this widget opens over the top of contents of the window that owns the object. 16 | ui(new Ui::Logging), 17 | m_logger(0), 18 | m_isRunning(false) 19 | { 20 | ui->setupUi(this); 21 | m_logger = new RunTask("tail -f /var/log/stubby.log", this); 22 | connect(m_logger, SIGNAL(started()), this, SLOT(on_started())); 23 | connect(m_logger, SIGNAL(finished(int)), this, SLOT(on_finished(int))); 24 | connect(m_logger, SIGNAL(readyReadStandardOutput()), this, SLOT(on_readyRead())); 25 | } 26 | 27 | Logging::~Logging() 28 | { 29 | SMDebug(eDestructors,""); 30 | delete ui; 31 | } 32 | 33 | void Logging::start() 34 | { 35 | if (m_logger) m_logger->start(); 36 | } 37 | 38 | void Logging::on_started() 39 | { 40 | m_isRunning = true; 41 | } 42 | 43 | void Logging::on_finished(int exitCode) 44 | { 45 | SMDebug(eLogging, "Exit code is %d", exitCode); 46 | m_isRunning = false; 47 | } 48 | 49 | void Logging::on_readyRead() 50 | { 51 | QByteArray stdoutData; 52 | stdoutData = m_logger->readAllStandardOutput(); 53 | 54 | static bool isAlert = false; 55 | static QByteArray incompleteLine; 56 | incompleteLine.append(stdoutData); 57 | QList lines = incompleteLine.split('\n'); 58 | int n = lines.count(); 59 | for (int i = 0; i < n-1; i++) { 60 | //SMDebug(eLogging, "Line %d: %s", i, lines[i].data()); 61 | /*now parse this line of data - for now just look for indication of failure or success*/ 62 | if (lines[i].contains("*FAILURE* no valid transports or upstreams available!")) { 63 | if (!isAlert) emit alert(true); 64 | isAlert = true; 65 | } 66 | // TODO: This won't mean a successfull connection, just a retry. 67 | // There's no text that works for Strict and Oppo at the moment..... 68 | if (lines[i].contains("Conn opened: ")) { 69 | if (isAlert) emit alert(false); 70 | isAlert = false; 71 | } 72 | } 73 | incompleteLine = lines[n-1]; //save the remnant so it can be processed next time 74 | //SMDebug(eLogging, "Last line is %s", incompleteLine.data()); 75 | 76 | ui->textEdit->moveCursor (QTextCursor::End); 77 | ui->textEdit->insertPlainText (QString::fromLatin1(stdoutData.data())); 78 | ui->textEdit->moveCursor (QTextCursor::End); 79 | 80 | } 81 | -------------------------------------------------------------------------------- /logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef LOGGING_H 10 | #define LOGGING_H 11 | 12 | #include 13 | #include "runtask.h" 14 | 15 | namespace Ui { 16 | class Logging; 17 | } 18 | 19 | class Logging : public QWidget 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit Logging(QWidget *parent = 0); 25 | ~Logging(); 26 | 27 | void start(); 28 | 29 | signals: 30 | void alert(bool on); 31 | 32 | private slots: 33 | void on_started(); 34 | void on_finished(int exitCode); 35 | void on_readyRead(); 36 | 37 | private: 38 | Ui::Logging *ui; 39 | RunTask *m_logger; 40 | bool m_isRunning; 41 | }; 42 | 43 | #endif // LOGGING_H 44 | -------------------------------------------------------------------------------- /logging.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Logging 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1386 10 | 300 11 | 12 | 13 | 14 | Stubby Log 15 | 16 | 17 | 18 | 19 | 20 | 21 | 1270 22 | 0 23 | 24 | 25 | 26 | 27 | Courier 28 | 13 29 | 30 | 31 | 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "stubbymanager.h" 10 | #include 11 | #include "msgdefs.h" 12 | 13 | bool debugFlags[eNoOfDebugMsgTypes]; 14 | 15 | static void InitMsgFlags(int argc, char * argv[]); 16 | static void SetAllMsgFlags(const bool on); 17 | static void SetMsgFlag(const DebugMsgType type, const bool on); 18 | static bool EnableIfNumber(QString sArg); 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | QApplication a(argc, argv); 23 | 24 | InitMsgFlags(argc, argv); 25 | 26 | StubbyManager w; 27 | w.show(); 28 | 29 | return a.exec(); 30 | } 31 | 32 | static void InitMsgFlags(int argc, char *argv[]) 33 | { 34 | SetAllMsgFlags(false); 35 | 36 | bool bFlagWithoutVals = false; 37 | int i = 0; 38 | while (i < argc) { 39 | if (strcmp(argv[i++], "-debug") == 0) { 40 | bFlagWithoutVals = true; 41 | while ((i < argc) && (EnableIfNumber(argv[i]))) { 42 | bFlagWithoutVals = false; 43 | i++; 44 | } 45 | } 46 | } 47 | if (bFlagWithoutVals) SetAllMsgFlags(true); 48 | } 49 | 50 | static void SetAllMsgFlags(const bool on) 51 | { 52 | for (int i = 0; i < eNoOfDebugMsgTypes; i++) { 53 | SetMsgFlag((DebugMsgType)i, on); 54 | } 55 | } 56 | 57 | static void SetMsgFlag(const DebugMsgType type, const bool on) 58 | { 59 | if ((type >= 0) && (type < eNoOfDebugMsgTypes)) { 60 | debugFlags[type] = on; 61 | if (on) { 62 | SMInfo("%s %s", "Debug messages enabled: ", DebugTypeText().text(type)); 63 | } 64 | } 65 | } 66 | 67 | static bool EnableIfNumber(QString sArg) 68 | { 69 | bool isNumber; 70 | int iFlag = sArg.toInt(&isNumber); 71 | if (isNumber && (iFlag < eNoOfDebugMsgTypes)) { 72 | SetMsgFlag((DebugMsgType)iFlag, true); 73 | } 74 | return isNumber; 75 | } 76 | -------------------------------------------------------------------------------- /msgdefs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "msgdefs.h" 10 | 11 | const char *DebugTypeText::s_typeUnknown = "UNKNOWN"; 12 | const char *DebugTypeText::s_textArray[eNoOfDebugMsgTypes] = {"GENERIC", "RUNTASK", "LOGGING", "CONFIGURATION", "DESTRUCTORS", "DAEMON", "DNSSERVER"}; 13 | 14 | const char* DebugTypeText::text(DebugMsgType type) 15 | { 16 | const char *returnText = s_typeUnknown; 17 | if ((type < eNoOfDebugMsgTypes) && (s_textArray[type])) { 18 | returnText = s_textArray[type]; 19 | } 20 | return returnText; 21 | } 22 | -------------------------------------------------------------------------------- /msgdefs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef MSGDEFS_H 10 | #define MSGDEFS_H 11 | #include 12 | 13 | typedef enum 14 | { 15 | eGeneric = 0, 16 | eRunTask, 17 | eLogging, 18 | eConfiguration, 19 | eDestructors, 20 | eDaemon, 21 | eDnsServer, 22 | eNoOfDebugMsgTypes 23 | } DebugMsgType; 24 | 25 | extern bool debugFlags[eNoOfDebugMsgTypes]; 26 | 27 | #define DEBUG_MESSAGES_ON 28 | 29 | #ifdef DEBUG_MESSAGES_ON 30 | #define SMDebug(type, fmt, ...) qDebug("%s:%s(%d) " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) 31 | #else 32 | #define SMDebug(type, fmt, ...) 33 | #endif 34 | 35 | #define SMInfo(fmt, ...) qDebug(fmt, ##__VA_ARGS__) 36 | #define SMWarning(fmt, ...) qWarning("%s:%s: " fmt, __FILE__, __FUNCTION__, ##__VA_ARGS__) 37 | #define SMCritical(fmt, ...) qCritical("%s:%s: " fmt, __FILE__, __FUNCTION__, ##__VA_ARGS__) 38 | #define SMFatal(fmt, ...) qFatal(fmt, __VA_ARGS__) 39 | 40 | class DebugTypeText 41 | { 42 | public: 43 | DebugTypeText() {} 44 | const char *text(DebugMsgType type); 45 | 46 | private: 47 | static const char *s_textArray[eNoOfDebugMsgTypes]; 48 | static const char *s_typeUnknown; 49 | }; 50 | 51 | #endif // MSGDEFS_H 52 | -------------------------------------------------------------------------------- /nativenotification.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef NATIVENOTIFICATION_H 10 | #define NATIVENOTIFICATION_H 11 | #include 12 | 13 | class NativeNotification 14 | { 15 | public: 16 | NativeNotification(); 17 | 18 | void postNotification(QString title, QString message); 19 | }; 20 | 21 | #endif // NATIVENOTIFICATION_H 22 | -------------------------------------------------------------------------------- /nativenotification.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "nativenotification.h" 10 | #import 11 | 12 | NativeNotification::NativeNotification() 13 | { 14 | 15 | } 16 | 17 | void NativeNotification::postNotification(QString title, QString message) 18 | { 19 | NSUserNotification* notification = [[NSUserNotification alloc] init]; 20 | notification.title = title.toNSString(); 21 | notification.informativeText = message.toNSString(); 22 | notification.soundName = NSUserNotificationDefaultSoundName; 23 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notification]; 24 | [notification autorelease]; 25 | } 26 | -------------------------------------------------------------------------------- /nativestatusbutton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef NATIVESTATUSBUTTON_H 10 | #define NATIVESTATUSBUTTON_H 11 | 12 | struct StatusItemWrapper; 13 | 14 | class NativeStatusButton 15 | { 16 | public: 17 | NativeStatusButton(); 18 | ~NativeStatusButton(); 19 | 20 | void showDisabled(const bool disabled); 21 | 22 | private: 23 | StatusItemWrapper *m_statusItemWrapper; 24 | }; 25 | 26 | #endif // NATIVESTATUSBUTTON_H 27 | -------------------------------------------------------------------------------- /nativestatusbutton.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "nativestatusbutton.h" 10 | #include "msgdefs.h" 11 | 12 | #import 13 | #import 14 | #include 15 | 16 | struct StatusItemWrapper 17 | { 18 | NSStatusItem *statusItem; 19 | }; 20 | 21 | NativeStatusButton::NativeStatusButton() : 22 | m_statusItemWrapper(new StatusItemWrapper) 23 | { 24 | m_statusItemWrapper->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; 25 | m_statusItemWrapper->statusItem.button.image = [NSApp applicationIconImage]; 26 | m_statusItemWrapper->statusItem.button.imageScaling = NSImageScaleProportionallyUpOrDown; //scales the icon so that it fits in menu bar 27 | m_statusItemWrapper->statusItem.button.alternateImage = [NSApp applicationIconImage]; //TODO - find another image and use this to indicate "strict"? 28 | m_statusItemWrapper->statusItem.button.accessibilityTitle = @"My Status Item"; 29 | m_statusItemWrapper->statusItem.menu = 0; //"single click behaviour" if no menu. ?? What is single click behaviour? 30 | m_statusItemWrapper->statusItem.button.appearsDisabled = true; 31 | m_statusItemWrapper->statusItem.toolTip = @"Stubby v0.1.0-alpha"; 32 | } 33 | 34 | NativeStatusButton::~NativeStatusButton() 35 | { 36 | SMDebug(eDestructors,""); 37 | delete m_statusItemWrapper; 38 | } 39 | 40 | void NativeStatusButton::showDisabled(const bool disabled) 41 | { 42 | m_statusItemWrapper->statusItem.button.appearsDisabled = disabled; 43 | } 44 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | stubby@245x145.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /runtask.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "runtask.h" 10 | #include "msgdefs.h" 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | RunTask::RunTask(const QString command, QObject *parent) : 20 | QProcess(parent), 21 | m_command(command) 22 | { 23 | setProgram(m_command); 24 | } 25 | 26 | RunTask::~RunTask() 27 | { 28 | //SMDebug(eDestructors,""); 29 | if (state() == Running) { 30 | terminate(); 31 | waitForFinished(); 32 | SMDebug(eRunTask,"waited for finish"); 33 | } 34 | } 35 | 36 | void RunTask::start() 37 | { 38 | QProcess::start(m_command, ReadWrite); 39 | } 40 | 41 | static const char STUBBY_UI_HELPER[] = "/usr/local/sbin/stubby-ui-helper"; 42 | 43 | const char *RunHelperTask::RIGHT_DAEMON_RUN = "net.getdnsapi.stubby.daemon.run"; 44 | const char *RunHelperTask::RIGHT_DNS_LOCAL = "net.getdnsapi.stubby.dns.local"; 45 | 46 | RunHelperTask::RunHelperTask(const QString command, QString need_right, const QString config, QObject *parent, StubbyManager *manager) : 47 | QProcess(parent), 48 | m_command(command), 49 | m_need_right(need_right), 50 | m_config(config), 51 | m_manager(manager) 52 | { 53 | setProgram(STUBBY_UI_HELPER); 54 | 55 | OSStatus oss = AuthorizationCreate(NULL, NULL, 0, &m_auth_ref); 56 | assert(oss == errAuthorizationSuccess); 57 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "Auth create " << (oss == errAuthorizationSuccess); 58 | 59 | // Ensure rights have been created. 60 | oss = AuthorizationRightGet(RIGHT_DAEMON_RUN, NULL); 61 | assert(oss == errAuthorizationSuccess); 62 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "Auth daemon " << (oss == errAuthorizationSuccess); 63 | oss = AuthorizationRightGet(RIGHT_DNS_LOCAL, NULL); 64 | assert(oss == errAuthorizationSuccess); 65 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "Auth dns " << (oss == errAuthorizationSuccess); 66 | } 67 | 68 | RunHelperTask::~RunHelperTask() 69 | { 70 | AuthorizationFree(m_auth_ref, kAuthorizationFlagDefaults); 71 | 72 | qDebug() << __FILE__ << ":" << __FUNCTION__; 73 | if (state() == Running) { 74 | terminate(); 75 | waitForFinished(); 76 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "waited for finish"; 77 | } 78 | } 79 | 80 | int RunHelperTask::execute() 81 | { 82 | QString cmd = makeCommandLine(); 83 | if (cmd.isNull()) { 84 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "auth failed"; 85 | return 1; 86 | } 87 | else { 88 | //qDebug() << __FILE__ << ":" << __FUNCTION__ << "executing " << cmd; 89 | QProcess::start(cmd); 90 | if (!waitForStarted()) { 91 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "Command could not be executed, process not found " << cmd; 92 | return -2; 93 | } 94 | QString ext_auth = makeExternalAuth(); 95 | if (!ext_auth.isEmpty()) 96 | write(ext_auth.toUtf8()); 97 | closeWriteChannel(); 98 | if (!waitForFinished()) 99 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "Waiting for finish failed " << cmd; 100 | return exitStatus() == NormalExit ? exitCode() : -1; 101 | } 102 | } 103 | 104 | void RunHelperTask::start() 105 | { 106 | QString cmd = makeCommandLine(); 107 | if (cmd.isNull()) 108 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "auth failed"; 109 | else { 110 | //qDebug() << __FILE__ << ":" << __FUNCTION__ << "starting " << cmd; 111 | 112 | QProcess::start(cmd); 113 | QString ext_auth = makeExternalAuth(); 114 | if (!ext_auth.isEmpty()) 115 | write(ext_auth.toUtf8()); 116 | closeWriteChannel(); 117 | } 118 | } 119 | 120 | QString RunHelperTask::makeCommandLine() 121 | { 122 | QString cmd(STUBBY_UI_HELPER); 123 | 124 | if ( !m_need_right.isEmpty() ) { 125 | AuthorizationItem right_detail[] = { 126 | { NULL, 0, NULL, 0 }, 127 | }; 128 | AuthorizationRights the_right = { 1, &right_detail[0] }; 129 | OSStatus oss; 130 | QByteArray needed_right = m_need_right.toUtf8(); 131 | right_detail[0].name = needed_right.constData(); 132 | 133 | oss = AuthorizationCopyRights( 134 | m_auth_ref, 135 | &the_right, 136 | kAuthorizationEmptyEnvironment, 137 | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize, 138 | NULL); 139 | if (oss != errAuthorizationSuccess) 140 | return NULL; 141 | 142 | cmd += " -auth - "; 143 | } 144 | 145 | if ( !m_config.isEmpty() ) { 146 | cmd += " -config "; 147 | cmd += m_config; 148 | } 149 | 150 | cmd += " " + m_command; 151 | return cmd; 152 | } 153 | 154 | QString RunHelperTask::makeExternalAuth() 155 | { 156 | QString res; 157 | AuthorizationExternalForm auth_ext_form; 158 | 159 | OSStatus oss = AuthorizationMakeExternalForm(m_auth_ref, &auth_ext_form); 160 | if ( oss == errAuthorizationSuccess) 161 | for (size_t i = 0; i < kAuthorizationExternalFormLength; ++i) { 162 | // Turn into a printable string of hex digits. 163 | char b = auth_ext_form.bytes[i]; 164 | char c; 165 | 166 | c = (b >> 4) & 0xf; 167 | res.append(c >= 10 ? 'a' + c - 10 : '0' + c); 168 | c = b & 0xf; 169 | res.append(c >= 10 ? 'a' + c - 10 : '0' + c); 170 | } 171 | return res; 172 | } 173 | -------------------------------------------------------------------------------- /runtask.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef RUNTASK_H 10 | #define RUNTASK_H 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | class StubbyManager; 18 | 19 | class RunTask : public QProcess 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | RunTask(const QString command, QObject *parent = 0); 25 | virtual ~RunTask(); 26 | void start(); 27 | 28 | private: 29 | QString m_command; 30 | }; 31 | 32 | class RunHelperTask : public QProcess 33 | { 34 | Q_OBJECT 35 | 36 | public: 37 | RunHelperTask(const QString command, QString need_right = QString(), const QString config = QString(), QObject *parent = 0, StubbyManager *manager = 0); 38 | virtual ~RunHelperTask(); 39 | int execute(); 40 | void start(); 41 | 42 | static const char *RIGHT_DAEMON_RUN; 43 | static const char *RIGHT_DNS_LOCAL; 44 | 45 | private: 46 | QString makeCommandLine(); 47 | QString makeExternalAuth(); 48 | 49 | QString m_command; 50 | QString m_need_right; 51 | QString m_config; 52 | StubbyManager *m_manager; 53 | AuthorizationRef m_auth_ref; 54 | }; 55 | 56 | #endif // RUNTASK_H 57 | -------------------------------------------------------------------------------- /stubby@245x145.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sinodun/stubby_manager_gui/958d5a4307c8c16deb1aa225edf2a373a12c2887/stubby@245x145.png -------------------------------------------------------------------------------- /stubbymanager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "stubbymanager.h" 10 | #include "ui_stubbymanager.h" 11 | #include "nativenotification.h" 12 | #include "nativestatusbutton.h" 13 | #include "configfilemanager.h" 14 | #include "msgdefs.h" 15 | #include 16 | 17 | StubbyManager::StubbyManager(QWidget *parent) : 18 | QMainWindow(parent), 19 | ui(new Ui::StubbyManager), 20 | m_stubbyController(0), 21 | m_stubbyState(DaemonController::Unknown), 22 | m_dnsServerController(0), 23 | m_dnsServerState(StubbySetdns::Unknown), 24 | m_dnsServerIsStubby(false), 25 | m_dnsServerDoesNotMatchCheckbox(false), 26 | m_editConfig(0), 27 | m_configChanged(false), 28 | m_statusButton(0), 29 | m_logging(0), 30 | m_testerProcess(NULL) 31 | { 32 | ui->setupUi(this); 33 | m_stubbyController = new DaemonController(this); 34 | if (m_stubbyController) { 35 | connect(m_stubbyController, SIGNAL(daemonStateChanged(DaemonController::DaemonState)), this, SLOT(on_stubbyStateChanged(DaemonController::DaemonState))); 36 | 37 | /* Check whether or not Stubby is running. 38 | * on_StubbyStateChanged() will be called on completion, 39 | * the "Running"/"Not running" widget will be updated, 40 | * and the stubby dns checkbox may be disabled.*/ 41 | m_stubbyController->checkDaemon(); 42 | } 43 | 44 | m_dnsServerController = new StubbySetdns(this); 45 | if (m_dnsServerController) { 46 | connect(m_dnsServerController, SIGNAL(dnsServerStateChanged(int)), this, SLOT(on_dnsServerStateChanged(int))); 47 | 48 | /* Find out if stubby dns is in use. 49 | * on_dnsServerStateChanged() will be called on completion. 50 | * the checkbox will be checked/unchecked depending on which servers are in use. 51 | * Apply and Revert buttons will be disabled (greyed out) */ 52 | m_dnsServerController->getDnsServerState(); 53 | } 54 | 55 | /* Button in the status bar 56 | * For now, the button is greyed out and updated in the Server check function */ 57 | m_statusButton = new NativeStatusButton(); 58 | m_statusButton->showDisabled(true); 59 | 60 | m_logging = new Logging(this); 61 | if (m_logging) { 62 | connect(m_logging, SIGNAL(alert(bool)), this, SLOT(on_alert(bool))); 63 | m_logging->start(); 64 | } 65 | 66 | } 67 | 68 | StubbyManager::~StubbyManager() 69 | { 70 | SMDebug(eDestructors,""); 71 | 72 | /* Discard configuration changes made, but not applied. */ 73 | bool ok; 74 | ConfigFileManager(this).discardTempConfigFile(&ok); 75 | 76 | delete m_statusButton; 77 | delete ui; 78 | } 79 | 80 | void StubbyManager::on_startButton_clicked() 81 | { 82 | ui->startButton->setEnabled(false); 83 | 84 | /* Run the launchctl command to load the stubby daemon.*/ 85 | m_stubbyController->startDaemon(); 86 | 87 | } 88 | 89 | void StubbyManager::on_stopButton_clicked() 90 | { 91 | ui->stopButton->setEnabled(false); 92 | ui->privacyCheckBox->setEnabled(false); 93 | 94 | /* run the launchctl command to unload the stubby daemon,*/ 95 | if (m_dnsServerState == StubbySetdns::NotStubby) { 96 | /* in this case, leave service settings as they are. There are No connections with private dns.*/ 97 | } else { 98 | /* Does this need to be more sophisticated? 99 | * a) Could there be a mix of private and non-private dns settings? We switch all to default anyway. 100 | * b) Instead of assuming default (ie from DHCP) DNS setting is the alternative to 101 | * Private, maybe we should save the setting before switching to Private, 102 | * so that at this point we can return to it. 103 | */ 104 | m_dnsServerController->defaultDns(); //TODO arrange for this to finish BEFORE stopping the daemon. 105 | } 106 | 107 | /* and also the script to restore default DNS servers */ 108 | m_stubbyController->stopDaemon(); 109 | 110 | } 111 | 112 | void StubbyManager::on_applyButton_clicked() 113 | { 114 | /* changed configuration doesn't take effect until Stubby has been restarted... */ 115 | if (m_configChanged) { 116 | bool ok; 117 | ConfigFileManager(this).applyConfig(&ok); 118 | if (ok) { 119 | m_configChanged = false; 120 | } else { 121 | SMDebug(eConfiguration, "Error applying configuration"); 122 | } 123 | 124 | m_stubbyController->restartDaemon(); 125 | 126 | } //TODO: make sure this finishes before changing the servers to private...or does it matter? 127 | 128 | /* if the privacy checkbox is true then run the script to switch to the private DNS servers 129 | * if the privacy checkbox is false then run the script to switch to the default DNS server 130 | */ 131 | if (m_dnsServerDoesNotMatchCheckbox) { 132 | m_dnsServerDoesNotMatchCheckbox = false; 133 | if (ui->privacyCheckBox->isChecked()) { 134 | m_dnsServerController->stubbyDns(); 135 | } else { 136 | m_dnsServerController->defaultDns(); 137 | } 138 | } 139 | //TODO Check that the change has happened 140 | 141 | bool ok; 142 | bool strict = ConfigFileManager().strictAuthentication(&ok); 143 | m_statusButton->showDisabled(!strict || !m_dnsServerIsStubby); 144 | 145 | updateWidgetVisibility(); 146 | 147 | } 148 | 149 | void StubbyManager::on_advancedButton_clicked() 150 | { 151 | /* open the stubby.conf edit window */ 152 | if (!m_editConfig) { 153 | m_editConfig = new EditConfig(this); 154 | connect(m_editConfig, SIGNAL(doneEditing(bool)), this, SLOT(on_doneEditing(bool))); 155 | connect(m_editConfig, SIGNAL(newStatusMessage(QString)), this, SLOT(on_newStatusMessage(QString))); 156 | } 157 | m_editConfig->show(); 158 | } 159 | 160 | void StubbyManager::on_privacyCheckBox_clicked() 161 | { 162 | m_dnsServerDoesNotMatchCheckbox = (m_dnsServerIsStubby != ui->privacyCheckBox->checkState()); 163 | updateWidgetVisibility(); 164 | } 165 | 166 | void StubbyManager::on_logButton_clicked() 167 | { 168 | m_logging->show(); 169 | } 170 | 171 | void StubbyManager::on_stubbyStateChanged(DaemonController::DaemonState state) 172 | { 173 | m_stubbyState = state; 174 | 175 | ui->status->setText(stubbyState(state)); 176 | 177 | updateWidgetVisibility(); 178 | } 179 | 180 | void StubbyManager::on_doneEditing(const bool changesExist) 181 | { 182 | SMDebug(eConfiguration, ""); 183 | m_configChanged = changesExist; 184 | updateWidgetVisibility(); 185 | delete m_editConfig; 186 | m_editConfig = 0; 187 | } 188 | 189 | void StubbyManager::on_newStatusMessage(const QString msg) 190 | { 191 | statusBar()->showMessage(msg, 5000); //Message will displayed for 5s 192 | } 193 | 194 | void StubbyManager::on_dnsServerStateChanged(int state) //StubbySetdns::DnsServerState 195 | { 196 | SMDebug(eDnsServer, "%s", ((state == 1) ? "Using stubby dns" : "Not using stubby dns")); 197 | 198 | m_dnsServerState = (StubbySetdns::DnsServerState)state; 199 | m_dnsServerIsStubby = m_dnsServerState == StubbySetdns::Stubby; 200 | 201 | updateWidgetVisibility(); 202 | 203 | /*..ok to do this the first time we check status, but afterwards?.... 204 | * 205 | * After changing to/from stubby dns? It's ok: 206 | * the action was trigger by clicking apply button, so state should match checkbox. 207 | * After stop button clicked? It's ok: 208 | * Automatically switches to default servers before stopping stubby and disabling the checkbox. 209 | * As the checkbox will be disabled, it makes sense for its state to match the actual state. 210 | * During restart, which happens as a result of Apply if the configuration has changed? 211 | * ?????? 212 | */ 213 | ui->privacyCheckBox->setChecked(state == (int)StubbySetdns::Stubby); 214 | bool ok; 215 | bool strict = ConfigFileManager().strictAuthentication(&ok); 216 | m_statusButton->showDisabled(!strict || !m_dnsServerIsStubby); 217 | 218 | } 219 | 220 | /* alerts come from logging: 221 | * the logging object checks for errors indicating that stubby dns queries are failing. 222 | * if they are, it emits an alert - which is picked up here. 223 | * an alert is also emitted when the problem goes away. 224 | */ 225 | void StubbyManager::on_alert(bool on) 226 | { 227 | if (on) { 228 | /* display alert */ 229 | SMDebug(eGeneric, "!!!Stubby failing!!!"); 230 | statusBar()->showMessage(tr("Stubby is failing - look at the log.")); 231 | #ifdef Q_OS_MACOS 232 | QtMac::setBadgeLabelText("alert!"); 233 | 234 | NativeNotification notification; 235 | notification.postNotification("Stubby", "Alert!"); 236 | 237 | #endif 238 | } else { 239 | /* clear alert */ 240 | SMDebug(eGeneric, "!!!Stubby has recovered!!!"); 241 | statusBar()->clearMessage(); 242 | #ifdef Q_OS_MACOS 243 | QtMac::setBadgeLabelText(QString()); 244 | 245 | #endif 246 | } 247 | } 248 | 249 | void StubbyManager::on_revertToDefaultButton_clicked() 250 | { 251 | bool ok; 252 | ConfigFileManager(this).saveDefaultToTempConfig(&ok); 253 | if (!ok) 254 | SMDebug(eConfiguration, "Error occurred reverting to default configuration"); 255 | m_configChanged = true; 256 | updateWidgetVisibility(); 257 | } 258 | 259 | void StubbyManager::on_revertButton_clicked() 260 | { 261 | bool ok; 262 | ConfigFileManager(this).discardTempConfigFile(&ok); 263 | if (!ok) 264 | SMDebug(eConfiguration, "Error discarding changes"); 265 | m_configChanged = false; 266 | 267 | if (m_dnsServerDoesNotMatchCheckbox) { 268 | m_dnsServerDoesNotMatchCheckbox = false; 269 | ui->privacyCheckBox->setChecked(m_dnsServerIsStubby); 270 | } 271 | 272 | updateWidgetVisibility(); 273 | 274 | } 275 | 276 | /* 277 | * Helper functions 278 | */ 279 | QString StubbyManager::stubbyState(const DaemonController::DaemonState state) 280 | { 281 | switch (state) { 282 | case DaemonController::NotRunning : return "Not running"; 283 | case DaemonController::Starting : return "Starting"; 284 | case DaemonController::Stopping : return "Stopping"; 285 | case DaemonController::Running : return "Running"; 286 | case DaemonController::Unknown : 287 | default : return "unknown"; 288 | } 289 | } 290 | 291 | void StubbyManager::updateWidgetVisibility() 292 | { 293 | ui->privacyCheckBox->setEnabled(m_stubbyState == DaemonController::Running); 294 | ui->startButton->setEnabled(m_stubbyState == DaemonController::NotRunning); 295 | ui->stopButton->setEnabled(m_stubbyState == DaemonController::Running); 296 | ui->testButton->setEnabled(m_stubbyState == DaemonController::Running); 297 | ui->restartButton->setEnabled(m_stubbyState == DaemonController::Running); 298 | ui->testOutput->clear(); 299 | 300 | ui->applyButton->setEnabled(m_configChanged || m_dnsServerDoesNotMatchCheckbox); 301 | ui->revertButton->setEnabled(m_configChanged || m_dnsServerDoesNotMatchCheckbox); 302 | 303 | if (m_configChanged || m_dnsServerDoesNotMatchCheckbox) { 304 | //ui->changesLabel->setText("There are changes that need Applying"); 305 | statusBar()->showMessage("There are changes that need Applying"); 306 | } else { 307 | ui->changesLabel->setText(""); 308 | statusBar()->clearMessage(); 309 | } 310 | 311 | } 312 | 313 | void StubbyManager::on_testButton_clicked() 314 | { 315 | ui->testOutput->setText("Testing...."); 316 | if (m_testerProcess == NULL) 317 | m_testerProcess = new RunTask("dig @127.0.0.1 getdnsapi.net", this); 318 | connect(m_testerProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_testerProcess_finished(int, QProcess::ExitStatus))); 319 | m_testerProcess->start(); 320 | } 321 | 322 | 323 | void StubbyManager::on_testerProcess_finished(int, QProcess::ExitStatus) 324 | { 325 | QByteArray stdoutData; 326 | stdoutData = m_testerProcess->readAllStandardOutput(); 327 | if (stdoutData.isEmpty()) 328 | return; 329 | if (stdoutData.contains("NOERROR")) { 330 | qDebug() << __FILE__ << ":" << __FUNCTION__ << "OK"; 331 | ui->testOutput->setText("Test was OK!"); 332 | } 333 | else { 334 | ui->testOutput->setText("Test failed."); 335 | } 336 | } 337 | 338 | void StubbyManager::on_restartButton_clicked() 339 | { 340 | qDebug() << __FILE__ << ":" << __FUNCTION__ << ""; 341 | m_stubbyController->restartDaemon(); 342 | } 343 | 344 | -------------------------------------------------------------------------------- /stubbymanager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef STUBBYMANAGER_H 10 | #define STUBBYMANAGER_H 11 | 12 | #include 13 | 14 | #ifdef Q_OS_MACOS 15 | #include 16 | #endif 17 | #include "nativestatusbutton.h" 18 | 19 | class StubbyManager; 20 | 21 | #include "daemoncontroller.h" 22 | #include "stubbysetdns.h" 23 | #include "logging.h" 24 | #include "editconfig.h" 25 | 26 | namespace Ui { 27 | class StubbyManager; 28 | } 29 | 30 | class StubbyManager : public QMainWindow 31 | { 32 | Q_OBJECT 33 | 34 | public: 35 | explicit StubbyManager(QWidget *parent = 0); 36 | ~StubbyManager(); 37 | 38 | private slots: 39 | /* automatic slots for ui controls */ 40 | void on_startButton_clicked(); 41 | 42 | void on_stopButton_clicked(); 43 | 44 | void on_applyButton_clicked(); 45 | 46 | void on_advancedButton_clicked(); 47 | 48 | void on_privacyCheckBox_clicked(); 49 | 50 | void on_logButton_clicked(); 51 | 52 | void on_revertToDefaultButton_clicked(); 53 | 54 | void on_revertButton_clicked(); 55 | 56 | void on_testerProcess_finished(int, QProcess::ExitStatus); 57 | 58 | /* slots for handling signals from daemon controller and config editor */ 59 | void on_stubbyStateChanged(DaemonController::DaemonState state); 60 | 61 | void on_doneEditing(const bool changesExist); 62 | 63 | void on_dnsServerStateChanged(int state); //StubbySetdns::DnsServerState 64 | 65 | void on_alert(bool on); 66 | 67 | void on_testButton_clicked(); 68 | 69 | void on_restartButton_clicked(); 70 | 71 | void on_newStatusMessage(const QString msg); 72 | 73 | private: 74 | QString stubbyState(const DaemonController::DaemonState state); 75 | void updateWidgetVisibility(); 76 | 77 | private: 78 | Ui::StubbyManager *ui; 79 | 80 | DaemonController *m_stubbyController; 81 | DaemonController::DaemonState m_stubbyState; 82 | 83 | StubbySetdns * m_dnsServerController; 84 | StubbySetdns::DnsServerState m_dnsServerState; 85 | bool m_dnsServerIsStubby; 86 | bool m_dnsServerDoesNotMatchCheckbox; 87 | 88 | EditConfig *m_editConfig; 89 | bool m_configChanged; 90 | NativeStatusButton *m_statusButton; 91 | 92 | Logging *m_logging; 93 | RunTask *m_testerProcess; 94 | 95 | }; 96 | 97 | #endif // STUBBYMANAGER_H 98 | -------------------------------------------------------------------------------- /stubbymanager.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | StubbyManager 4 | 5 | 6 | 7 | 0 8 | 0 9 | 463 10 | 401 11 | 12 | 13 | 14 | 15 | 460 16 | 0 17 | 18 | 19 | 20 | StubbyManager 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 88 38 | 0 39 | 40 | 41 | 42 | DNS Servers: 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 100 53 | 0 54 | 55 | 56 | 57 | 58 | 59 | 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 110 69 | 0 70 | 71 | 72 | 73 | Test 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 112 82 | 0 83 | 84 | 85 | 86 | Restart 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 99 | 0 100 | 101 | 102 | 103 | 104 | 75 105 | true 106 | 107 | 108 | 109 | <html><head/><body><p>Not running</p></body></html> 110 | 111 | 112 | Qt::AutoText 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 110 121 | 0 122 | 123 | 124 | 125 | Start Stubby 126 | 127 | 128 | Start 129 | 130 | 131 | false 132 | 133 | 134 | 135 | 136 | 137 | 138 | true 139 | 140 | 141 | 142 | 110 143 | 0 144 | 145 | 146 | 147 | Revert to default DNS settings and stop Stubby 148 | 149 | 150 | Stop 151 | 152 | 153 | false 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Use Stubby to manage DNS 165 | 166 | 167 | Use Stubby DNS 168 | 169 | 170 | 171 | 172 | 173 | 174 | <html><head/><body><p><span style=" font-size:11pt;">Start the service then check this box and Apply settings to start using Stubby DNS.</span></p><p><span style=" font-size:11pt;">Hit the Stop button to return to default DNS settings.</span></p></body></html> 175 | 176 | 177 | true 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | Qt::Horizontal 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 50 195 | 0 196 | 197 | 198 | 199 | Service Status: 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 45 208 | 68 209 | 210 | 211 | 212 | 213 | 214 | 215 | :/new/prefix1/stubby@245x145.png 216 | 217 | 218 | true 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | Qt::Horizontal 232 | 233 | 234 | 235 | 40 236 | 20 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | Make changes to Stubby DNS configuration 245 | 246 | 247 | Advanced... 248 | 249 | 250 | 251 | 252 | 253 | 254 | View the log of Stubby DNS server interactions 255 | 256 | 257 | View the log... 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | Qt::Horizontal 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | Qt::Horizontal 276 | 277 | 278 | 279 | 40 280 | 20 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 75 290 | true 291 | 292 | 293 | 294 | 295 | 296 | 297 | true 298 | 299 | 300 | 301 | 302 | 303 | 304 | Select Stubby default configuration. Click on Apply to confirm. 305 | 306 | 307 | Revert to default 308 | 309 | 310 | 311 | 312 | 313 | 314 | Ignore changes 315 | 316 | 317 | Revert 318 | 319 | 320 | 321 | 322 | 323 | 324 | Apply use of Stubby DNS and/or Stubby configuration changes 325 | 326 | 327 | Apply 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | Qt::Horizontal 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 0 348 | 0 349 | 463 350 | 22 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | TopToolBarArea 360 | 361 | 362 | false 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /stubbysetdns.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #include "msgdefs.h" 10 | 11 | #include 12 | #include 13 | 14 | #include "stubbysetdns.h" 15 | 16 | #include "stubbymanager.h" 17 | 18 | StubbySetdns::StubbySetdns(StubbyManager *parent) : 19 | QObject(parent), 20 | m_dnsState(StubbySetdns::Unknown), 21 | //m_dnsState(false), 22 | m_manager(parent), 23 | m_stubbyDns(0), 24 | m_defaultDns(0), 25 | m_getDnsServerState(0), 26 | m_getDnsServerState_output("") 27 | { 28 | m_stubbyDns = new RunHelperTask("dns_stubby", RunHelperTask::RIGHT_DNS_LOCAL, QString(), this, parent); 29 | m_defaultDns = new RunHelperTask("dns_default", RunHelperTask::RIGHT_DNS_LOCAL, QString(), this, parent); 30 | m_getDnsServerState = new RunHelperTask("dns_list", QString(), QString(), this, parent); 31 | 32 | connect(m_stubbyDns, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_stubbyDns_finished(int,QProcess::ExitStatus))); 33 | connect(m_stubbyDns, SIGNAL(readyReadStandardError()), this, SLOT(on_stubbyDns_readyReadStderr())); 34 | connect(m_stubbyDns, SIGNAL(readyReadStandardOutput()), this, SLOT(on_stubbyDns_readyReadStdout())); 35 | connect(m_stubbyDns, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_stubbyDns_errorOccurred(QProcess::ProcessError))); 36 | 37 | connect(m_defaultDns, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_defaultDns_finished(int, QProcess::ExitStatus))); 38 | connect(m_defaultDns, SIGNAL(readyReadStandardError()), this, SLOT(on_defaultDns_readyReadStderr())); 39 | connect(m_defaultDns, SIGNAL(readyReadStandardOutput()), this, SLOT(on_defaultDns_readyReadStdout())); 40 | connect(m_defaultDns, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_defaultDns_errorOccurred(QProcess::ProcessError))); 41 | 42 | connect(m_getDnsServerState, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(on_getDnsServerState_finished(int, QProcess::ExitStatus))); 43 | connect(m_getDnsServerState, SIGNAL(readyReadStandardError()), this, SLOT(on_getDnsServerState_readyReadStderr())); 44 | connect(m_getDnsServerState, SIGNAL(readyReadStandardOutput()), this, SLOT(on_getDnsServerState_readyReadStdout())); 45 | connect(m_getDnsServerState, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_getDnsServerState_errorOccurred(QProcess::ProcessError))); 46 | } 47 | 48 | StubbySetdns::~StubbySetdns() 49 | { 50 | SMDebug(eDestructors,""); 51 | } 52 | 53 | int StubbySetdns::stubbyDns() 54 | { 55 | m_stubbyDns->start(); 56 | return 0; 57 | } 58 | 59 | int StubbySetdns::defaultDns() 60 | { 61 | m_defaultDns->start(); 62 | return 0; 63 | } 64 | 65 | int StubbySetdns::getDnsServerState() 66 | { 67 | m_getDnsServerState_output = ""; 68 | m_getDnsServerState->start(); 69 | return 0; 70 | } 71 | 72 | void StubbySetdns::on_stubbyDns_finished(int exitCode, QProcess::ExitStatus) 73 | { 74 | SMDebug(eDnsServer, "Exit code is %d", exitCode); 75 | getDnsServerState(); 76 | } 77 | 78 | void StubbySetdns::on_stubbyDns_readyReadStderr() 79 | { 80 | 81 | } 82 | 83 | void StubbySetdns::on_stubbyDns_readyReadStdout() 84 | { 85 | 86 | } 87 | 88 | void StubbySetdns::on_stubbyDns_errorOccurred(QProcess::ProcessError) 89 | { 90 | 91 | } 92 | 93 | void StubbySetdns::on_defaultDns_finished(int exitCode, QProcess::ExitStatus) 94 | { 95 | SMDebug(eDnsServer, "Exit code is %d", exitCode); 96 | getDnsServerState(); 97 | } 98 | 99 | void StubbySetdns::on_defaultDns_readyReadStderr() 100 | 101 | { 102 | 103 | } 104 | 105 | void StubbySetdns::on_defaultDns_readyReadStdout() 106 | { 107 | 108 | } 109 | 110 | void StubbySetdns::on_defaultDns_errorOccurred(QProcess::ProcessError) 111 | { 112 | 113 | } 114 | 115 | void StubbySetdns::on_getDnsServerState_finished(int exitCode, QProcess::ExitStatus exitStatus) 116 | { 117 | SMDebug(eDnsServer, "Exit code is %d", exitCode); 118 | /* 119 | * $ sudo ./stubby-setdns.sh -l 120 | * ** Current DNS settings ** 121 | * Apple USB Ethernet Adapter: 127.0.0.1 ::1 122 | * Wi-Fi: 127.0.0.1 ::1 123 | * Bluetooth PAN: 127.0.0.1 ::1 124 | * $ sudo ./stubby-setdns.sh 125 | * ** Current DNS settings ** -l 126 | * Apple USB Ethernet Adapter: There aren't any DNS Servers set on Apple USB Ethernet Adapter. 127 | * Wi-Fi: There aren't any DNS Servers set on Wi-Fi. 128 | * Bluetooth PAN: There aren't any DNS Servers set on Bluetooth PAN. 129 | * $ 130 | */ 131 | 132 | if (exitStatus == QProcess::NormalExit) { 133 | QStringList slist = m_getDnsServerState_output.split("\n"); 134 | SMDebug(eDnsServer, "Output from stdout is\n%s", m_getDnsServerState_output.toLatin1().data()); 135 | //SMDebug(eDnsServer, "Number of lines in dns server state output is %d", slist.length()); 136 | int numberOfConnectors = slist.length() - 2; 137 | bool isStubby[numberOfConnectors]; 138 | bool isNotStubby[numberOfConnectors]; 139 | 140 | QString localPID("127.0.0.1 ::1"); 141 | int i = 1; 142 | while (i <= numberOfConnectors) { 143 | if (slist[i].contains(&localPID)) { 144 | isStubby[i-1] = true; 145 | isNotStubby[i-1] = false; 146 | } else { 147 | isStubby[i-1] = false; 148 | isNotStubby[i-1] = true; 149 | } 150 | i++; 151 | } 152 | 153 | /* if all isStubby are true then stubbyDNS is place */ 154 | i = 0; 155 | while ((i < numberOfConnectors) && isStubby[i]) { i++; } 156 | if (i==numberOfConnectors) { 157 | m_dnsState = Stubby; 158 | emit dnsServerStateChanged((int)Stubby); 159 | SMDebug(eDnsServer, "All connections are using Stubby"); 160 | return; 161 | } 162 | 163 | /* if all isNotStubby are true then local IP is not being used */ 164 | i = 0; 165 | while ((i < numberOfConnectors) && isNotStubby[i]) { i++; } 166 | if (i==numberOfConnectors) { 167 | m_dnsState = NotStubby; 168 | emit dnsServerStateChanged((int)NotStubby); 169 | SMDebug(eDnsServer, "No connections are using Stubby"); 170 | return; 171 | } 172 | 173 | /* otherwhise, the true state is unknown.*/ 174 | } 175 | m_dnsState = Unknown; 176 | emit dnsServerStateChanged((int)Unknown); 177 | SMDebug(eDnsServer, "Error - dns server use of Stubby is unknown"); 178 | } 179 | 180 | void StubbySetdns::on_getDnsServerState_readyReadStderr() 181 | { 182 | 183 | } 184 | 185 | void StubbySetdns::on_getDnsServerState_readyReadStdout() 186 | 187 | { 188 | m_getDnsServerState_output = m_getDnsServerState_output + QString::fromLatin1(m_getDnsServerState->readAllStandardOutput().data()); 189 | //SMDebug(eDnsServer, "Output from stdout is\n%s", m_getDnsServerState_output.toLatin1().data()); 190 | } 191 | 192 | void StubbySetdns::on_getDnsServerState_errorOccurred(QProcess::ProcessError) 193 | { 194 | 195 | } 196 | -------------------------------------------------------------------------------- /stubbysetdns.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Sinodun Internet Technologies Ltd. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, you can obtain one at https://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | #ifndef STUBBYSETDNS_H 10 | #define STUBBYSETDNS_H 11 | 12 | #include 13 | 14 | #include "runtask.h" 15 | 16 | class StubbySetdns : public QObject 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | StubbySetdns(StubbyManager *parent = 0); 22 | virtual ~StubbySetdns(); 23 | 24 | typedef enum { 25 | NotStubby = 0, 26 | Stubby, 27 | Unknown 28 | } DnsServerState; 29 | 30 | int stubbyDns(); 31 | int defaultDns(); 32 | int getDnsServerState(); 33 | DnsServerState dnsState() { return m_dnsState; } 34 | 35 | public: 36 | signals: 37 | void dnsServerStateChanged(int); //DnsServerState 38 | 39 | private slots: 40 | void on_stubbyDns_finished(int exitCode, QProcess::ExitStatus); 41 | void on_stubbyDns_readyReadStderr(); 42 | void on_stubbyDns_readyReadStdout(); 43 | void on_stubbyDns_errorOccurred(QProcess::ProcessError); 44 | 45 | void on_defaultDns_finished(int exitCode, QProcess::ExitStatus); 46 | void on_defaultDns_readyReadStderr(); 47 | void on_defaultDns_readyReadStdout(); 48 | void on_defaultDns_errorOccurred(QProcess::ProcessError); 49 | 50 | void on_getDnsServerState_finished(int exitCode, QProcess::ExitStatus exitStatus); 51 | void on_getDnsServerState_readyReadStderr(); 52 | void on_getDnsServerState_readyReadStdout(); 53 | void on_getDnsServerState_errorOccurred(QProcess::ProcessError); 54 | 55 | private: 56 | DnsServerState m_dnsState; 57 | StubbyManager *m_manager; 58 | RunHelperTask *m_stubbyDns; 59 | RunHelperTask *m_defaultDns; 60 | RunHelperTask *m_getDnsServerState; 61 | QString m_getDnsServerState_output; 62 | }; 63 | 64 | #endif // STUBBYSETDNS_H 65 | --------------------------------------------------------------------------------