diff --git a/.gitignore b/.gitignore index 6fa84a3b..1f0c1a36 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ src/gui/gui.pro.user src/gui/.qmake.stash src/gui/.rnd src/setup/win32/synergy.suo +*.smbdelete* diff --git a/CMakeLists.txt b/CMakeLists.txt index af61440e..349cd9e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ # Version number for Synergy set(VERSION_MAJOR 1) set(VERSION_MINOR 7) -set(VERSION_REV 1) +set(VERSION_REV 2) set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") cmake_minimum_required(VERSION 2.6) diff --git a/ChangeLog b/ChangeLog index 1eb0d7f2..77622141 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +v1.7.1 +====== +Bug #3784 - Double click & drag doesn't select words on client +Bug #3052 - Triple-click (select line) does not work +Bug #4367 - Duplicate Alt-S Keyboard Shortcuts on Gui +Bug #4554 - Server unable to accept new SSL connection +Bug #4553 - SSL handshake failure error causes GUI to crash +Bug #4551 - Plugin wizard doesn't create SSL directory +Bug #4548 - Severe code duplication in fingerprint logic +Bug #4547 - Windows server crashes when client fingerprint dialog open +Bug #4539 - Mac client dies when server has SSL_ERROR_SSL +Bug #4537 - Plugin wizard doesn't complete but finish button enabled +Bug #4535 - Server crashes on shut down after multiple connections failed +Bug #4528 - Error SSL_ERROR_SSL is logged on unknown error +Bug #4527 - Server fingerprint dialog on client GUI keeps showing +Bug #4469 - GUI crashes on Windows when generating certificate +Bug #4410 - SSL_ERROR_SSL (unknown protocol) on Mac client +Bug #4409 - SSL_ERROR_SSL (unknown alert type) on Windows 8.1 client +Bug #4557 - GUI doesn't show local fingerprint on fresh install +Enhancement #4522 - SSL server fingerprint verification from client +Enhancement #4526 - Display local fingerprint on server GUI +Enhancement #4549 - Extract SSL certificate and fingerprint generate function +Enhancement #4546 - Redistribute OpenSSL on Windows with installer +Enhancement #4540 - Enable Network Security checkbox only when ns plugin exists +Enhancement #4525 - Reorganize app data directory +Enhancement #4390 - Disable GUI auto-hide by default + 1.7.0 ===== Enhancement #4313 - SSL encrypted secure connection diff --git a/res/openssl/synergy.conf b/res/openssl/synergy.conf new file mode 100644 index 00000000..c7a3e439 --- /dev/null +++ b/res/openssl/synergy.conf @@ -0,0 +1,65 @@ +# +# Synergy OpenSSL configuration file. +# Used for generation of certificate requests. +# + +dir = . + +[ca] +default_ca = CA_default + +[CA_default] +serial = $dir/serial +database = $dir/certindex.txt +new_certs_dir = $dir/certs +certificate = $dir/cacert.pem +private_key = $dir/private/cakey.pem +default_days = 365 +default_md = md5 +preserve = no +email_in_dn = no +nameopt = default_ca +certopt = default_ca +policy = policy_match + +[policy_match] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[req] +default_bits = 1024 # Size of keys +default_keyfile = key.pem # name of generated keys +default_md = md5 # message digest algorithm +string_mask = nombstr # permitted characters +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +0.organizationName = Organization Name (company) +organizationalUnitName = Organizational Unit Name (department, division) +emailAddress = Email Address +emailAddress_max = 40 +localityName = Locality Name (city, district) +stateOrProvinceName = State or Province Name (full name) +countryName = Country Name (2 letter code) +countryName_min = 2 +countryName_max = 2 +commonName = Common Name (hostname, IP, or your name) +commonName_max = 64 +0.organizationName_default = My Company +localityName_default = My Town +stateOrProvinceName_default = State or Providence +countryName_default = US + +[v3_ca] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +[v3_req] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash diff --git a/src/gui/gui.pro b/src/gui/gui.pro index 4f8fcabb..f519885e 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -55,7 +55,9 @@ SOURCES += src/main.cpp \ src/WebClient.cpp \ src/PluginWizardPage.cpp \ src/PluginManager.cpp \ - src/CoreInterface.cpp + src/CoreInterface.cpp \ + src/Fingerprint.cpp \ + src/SslCertificate.cpp HEADERS += src/MainWindow.h \ src/AboutDialog.h \ src/ServerConfig.h \ @@ -97,10 +99,12 @@ HEADERS += src/MainWindow.h \ src/PluginWizardPage.h \ src/ProcessorArch.h \ src/PluginManager.h \ - src/CoreInterface.h + src/CoreInterface.h \ + src/Fingerprint.h \ + src/SslCertificate.h RESOURCES += res/Synergy.qrc RC_FILE = res/win/Synergy.rc -macx { +macx { QMAKE_INFO_PLIST = res/mac/Info.plist TARGET = Synergy QSYNERGY_ICON.files = res/mac/Synergy.icns diff --git a/src/gui/res/MainWindowBase.ui b/src/gui/res/MainWindowBase.ui index 88f07b27..0a47e0d3 100644 --- a/src/gui/res/MainWindowBase.ui +++ b/src/gui/res/MainWindowBase.ui @@ -1,4 +1,4 @@ - + MainWindowBase @@ -87,7 +87,7 @@ - &Server (share this computer's mouse and keyboard): + Ser&ver (share this computer's mouse and keyboard): true @@ -117,6 +117,30 @@ + + + + + + + 0 + 0 + + + + Fingerprint: + + + + + + + + + + + + @@ -128,7 +152,7 @@ - + diff --git a/src/gui/src/Fingerprint.cpp b/src/gui/src/Fingerprint.cpp new file mode 100644 index 00000000..b2816684 --- /dev/null +++ b/src/gui/src/Fingerprint.cpp @@ -0,0 +1,149 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015 Synergy Si Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package 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. If not, see . + */ + +#include "Fingerprint.h" + +#include "CoreInterface.h" + +#include +#include + +static const char kDirName[] = "SSL/Fingerprints"; +static const char kLocalFilename[] = "Local.txt"; +static const char kTrustedServersFilename[] = "TrustedServers.txt"; +static const char kTrustedClientsFilename[] = "TrustedClients.txt"; + +Fingerprint::Fingerprint(const QString& filename) +{ + m_Filename = filename; +} + +void Fingerprint::trust(const QString& fingerprintText, bool append) +{ + Fingerprint::persistDirectory(); + + QIODevice::OpenMode openMode; + if (append) { + openMode = QIODevice::Append; + } + else { + openMode = QIODevice::WriteOnly; + } + + QFile file(filePath()); + if (file.open(openMode)) + { + QTextStream out(&file); + out << fingerprintText << "\n"; + file.close(); + } +} + +bool Fingerprint::fileExists() const +{ + QString dirName = Fingerprint::directoryPath(); + if (!QDir(dirName).exists()) { + return false; + } + + QFile file(filePath()); + return file.exists(); +} + +bool Fingerprint::isTrusted(const QString& fingerprintText) +{ + QStringList list = readList(); + foreach (QString trusted, list) + { + if (trusted == fingerprintText) { + return true; + } + } + return false; +} + +QStringList Fingerprint::readList(const int readTo) +{ + QStringList list; + + QString dirName = Fingerprint::directoryPath(); + if (!QDir(dirName).exists()) { + return list; + } + + QFile file(filePath()); + + if (file.open(QIODevice::ReadOnly)) + { + QTextStream in(&file); + while (!in.atEnd()) + { + list.append(in.readLine()); + if (list.size() == readTo) { + break; + } + } + file.close(); + } + + return list; +} + +QString Fingerprint::readFirst() +{ + QStringList list = readList(1); + return list.at(0); +} + +QString Fingerprint::filePath() const +{ + QString dir = Fingerprint::directoryPath(); + return QString("%1/%2").arg(dir).arg(m_Filename); +} + +void Fingerprint::persistDirectory() +{ + QDir dir(Fingerprint::directoryPath()); + if (!dir.exists()) { + dir.mkpath("."); + } +} + +QString Fingerprint::directoryPath() +{ + CoreInterface coreInterface; + QString profileDir = coreInterface.getProfileDir(); + + return QString("%1/%2") + .arg(profileDir) + .arg(kDirName); +} + +Fingerprint Fingerprint::local() +{ + return Fingerprint(kLocalFilename); +} + +Fingerprint Fingerprint::trustedServers() +{ + return Fingerprint(kTrustedServersFilename); +} + +Fingerprint Fingerprint::trustedClients() +{ + return Fingerprint(kTrustedClientsFilename); +} diff --git a/src/gui/src/Fingerprint.h b/src/gui/src/Fingerprint.h new file mode 100644 index 00000000..29ef5f7e --- /dev/null +++ b/src/gui/src/Fingerprint.h @@ -0,0 +1,46 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015 Synergy Si Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package 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. If not, see . + */ + +#pragma once + +#include + +class Fingerprint +{ +private: + Fingerprint(const QString& filename); + +public: + void trust(const QString& fingerprintText, bool append = true); + bool isTrusted(const QString& fingerprintText); + QStringList readList(const int readTo = -1); + QString readFirst(); + QString filePath() const; + bool fileExists() const; + +public: + static Fingerprint local(); + static Fingerprint trustedServers(); + static Fingerprint trustedClients(); + static QString directoryPath(); + static QString localFingerprint(); + static bool localFingerprintExists(); + static void persistDirectory(); + +private: + QString m_Filename; +}; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index bd7caa28..e54c9c37 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -21,6 +21,9 @@ #include #include "MainWindow.h" + +#include "Fingerprint.h" +#include "PluginManager.h" #include "AboutDialog.h" #include "ServerConfigDialog.h" #include "SettingsDialog.h" @@ -131,6 +134,8 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig) : updateEdition(); m_pLabelPadlock->hide(); + + updateLocalFingerprint(); } MainWindow::~MainWindow() @@ -395,6 +400,50 @@ void MainWindow::updateStateFromLogLine(const QString &line) { setSynergyState(synergyConnected); } + + checkFingerprint(line); +} + +void MainWindow::checkFingerprint(const QString& line) +{ + QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); + if (!fingerprintRegex.exactMatch(line)) { + return; + } + + QString fingerprint = fingerprintRegex.cap(1); + if (Fingerprint::trustedServers().isTrusted(fingerprint)) { + return; + } + + QMessageBox::StandardButton fingerprintReply = + QMessageBox::information( + this, tr("Security question"), + tr("Do you trust this fingerprint?\n\n" + "%1\n\n" + "This is a server fingerprint. You should compare this " + "fingerprint to the one on your server's screen. If the " + "two don't match exactly, then it's probably not the server " + "you're expecting (it could be a malicious user).\n\n" + "To automatically trust this fingerprint for future " + "connections, click Yes. To reject this fingerprint and " + "disconnect from the server, click No.") + .arg(fingerprint), + QMessageBox::Yes | QMessageBox::No); + + if (fingerprintReply == QMessageBox::Yes) { + // restart core process after trusting fingerprint. + Fingerprint::trustedServers().trust(fingerprint); + startSynergy(); + } + else { + // on all platforms, the core process will stop if the + // fingerprint is not trusted, so technically the stop + // isn't really needed. however on windows, the core + // process will keep trying (and failing) unless we + // tell it to stop. + stopSynergy(); + } } void MainWindow::clearLog() @@ -451,7 +500,11 @@ void MainWindow::startSynergy() } #if defined(Q_OS_WIN) - args << "--profile-dir" << getProfileDirectoryForArg(); + // on windows, the profile directory changes depending on the user that + // launched the process (e.g. when launched with elevation). setting the + // profile dir on launch ensures it uses the same profile dir is used + // no matter how its relaunched. + args << "--profile-dir" << getProfileRootForArg(); #endif if ((synergyType() == synergyClient && !clientArgs(args, app)) @@ -492,14 +545,6 @@ void MainWindow::startSynergy() if (desktopMode) { - if (!appConfig().startedBefore()) { - QMessageBox::information( - this, "Synergy", - tr("Synergy will be minimized to the notification " - "area. This will happen automatically when Synergy " - "starts.")); - } - synergyProcess()->start(app, args); if (!synergyProcess()->waitForStarted()) { @@ -757,12 +802,6 @@ void MainWindow::setSynergyState(qSynergyState state) setIcon(state); m_SynergyState = state; - - // if in desktop mode, hide synergy. in service mode the gui can - // just be closed. - if ((appConfig().processMode() == Desktop) && (state == synergyConnected)) { - hide(); - } } void MainWindow::setVisible(bool visible) @@ -893,6 +932,19 @@ void MainWindow::setEdition(int type) setWindowTitle(title); } +void MainWindow::updateLocalFingerprint() +{ + if (Fingerprint::local().fileExists()) { + m_pLabelFingerprint->setVisible(true); + m_pLabelLocalFingerprint->setVisible(true); + m_pLabelLocalFingerprint->setText(Fingerprint::local().readFirst()); + } + else { + m_pLabelFingerprint->setVisible(false); + m_pLabelLocalFingerprint->setVisible(false); + } +} + void MainWindow::on_m_pGroupClient_toggled(bool on) { m_pGroupServer->setChecked(!on); @@ -1172,6 +1224,7 @@ void MainWindow::updateEdition() QString mac = getFirstMacAddress(); QString hashSrc = m_AppConfig.activateEmail() + mac; QString hashResult = hash(hashSrc); + if (hashResult == m_AppConfig.userToken()) { setEdition(m_AppConfig.edition()); } @@ -1223,25 +1276,17 @@ void MainWindow::bonjourInstallFinished() m_pCheckBoxAutoConfig->setChecked(true); } -QString MainWindow::getProfileDirectory() +QString MainWindow::getProfileRootForArg() { + CoreInterface coreInterface; + QString dir = coreInterface.getProfileDir(); + + // HACK: strip our app name since we're returning the root dir. #if defined(Q_OS_WIN) - - QString qtDataDir = QDesktopServices::storageLocation( - QDesktopServices::DataLocation); - - // HACK: core wants the base app data dir, this seems like a very hacky - // way to get it (maybe consider using %LOCALAPPDATA% instead?) - return qtDataDir.replace("\\Synergy\\Synergy", ""); - + dir.replace("\\Synergy", ""); #else - - return ""; - + dir.replace("/.synergy", ""); #endif -} -QString MainWindow::getProfileDirectoryForArg() -{ - return QString("\"%1\"").arg(getProfileDirectory()); + return QString("\"%1\"").arg(dir); } diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index b82984cf..40bb7975 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -106,6 +106,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase void updateZeroconfService(); void serverDetected(const QString name); void setEdition(int type); + void updateLocalFingerprint(); public slots: void appendLogRaw(const QString& text); @@ -165,8 +166,8 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase void downloadBonjour(); void promptAutoConfig(); void updateEdition(); - QString getProfileDirectory(); - QString getProfileDirectoryForArg(); + QString getProfileRootForArg(); + void checkFingerprint(const QString& line); private: QSettings& m_Settings; diff --git a/src/gui/src/PluginManager.cpp b/src/gui/src/PluginManager.cpp index c3ce8d03..8590a328 100644 --- a/src/gui/src/PluginManager.cpp +++ b/src/gui/src/PluginManager.cpp @@ -22,10 +22,12 @@ #include "DataDownloader.h" #include "QUtility.h" #include "ProcessorArch.h" +#include "Fingerprint.h" #include #include #include +#include static QString kBaseUrl = "http://synergy-project.org/files"; static const char kWinProcessorArch32[] = "Windows-x86"; @@ -35,15 +37,9 @@ static const char kLinuxProcessorArchDeb32[] = "Linux-i686-deb"; static const char kLinuxProcessorArchDeb64[] = "Linux-x86_64-deb"; static const char kLinuxProcessorArchRpm32[] = "Linux-i686-rpm"; static const char kLinuxProcessorArchRpm64[] = "Linux-x86_64-rpm"; -static QString kCertificateLifetime = "365"; -static QString kCertificateSubjectInfo = "/CN=Synergy"; -static QString kCertificateFilename = "Synergy.pem"; -static QString kUnixOpenSslCommand = "openssl"; #if defined(Q_OS_WIN) static const char kWinPluginExt[] = ".dll"; -static const char kWinOpenSslSetup[] = "openssl-1.0.2-Windows-x86.exe"; -static const char kWinOpenSslBinary[] = "OpenSSL\\openssl.exe"; #elif defined(Q_OS_MAC) static const char kMacPluginPrefix[] = "lib"; @@ -72,10 +68,30 @@ PluginManager::~PluginManager() { } +bool PluginManager::exist(QString name) +{ + CoreInterface coreInterface; + QString PluginDir = coreInterface.getPluginDir(); + QString pluginName = getPluginOsSpecificName(name); + QString filename; + filename.append(PluginDir); + filename.append(QDir::separator()).append(pluginName); + QFile file(filename); + bool exist = false; + if (file.exists()) { + exist = true; + } + + return exist; +} + void PluginManager::downloadPlugins() { if (m_DataDownloader.isFinished()) { - savePlugin(); + if (!savePlugin()) { + return; + } + if (m_DownloadIndex != m_PluginList.size() - 1) { emit downloadNext(); } @@ -101,59 +117,7 @@ void PluginManager::downloadPlugins() } } -void PluginManager::saveOpenSslSetup() -{ - QDir dir(m_ProfileDir); - if (!dir.exists()) { - dir.mkpath("."); - } - -#if defined(Q_OS_WIN) - - QString filename = - QString("%1\\%2") - .arg(m_ProfileDir) - .arg(kWinOpenSslSetup); - - QFile file(filename); - if (!file.open(QIODevice::WriteOnly)) { - emit error( - tr("Failed to save certificate tool to: %1") - .arg(m_ProfileDir)); - return; - } - - file.write(m_DataDownloader.data()); - file.close(); - - QStringList installArgs; - installArgs.append("-s"); - installArgs.append("-y"); - - if (!runProgram(filename, installArgs, QStringList())) { - return; - } - - // openssl installer no longer needed - QFile::remove(filename); - -#endif - - emit openSslBinaryReady(); -} - -void PluginManager::generateCertificate() -{ - connect( - this, - SIGNAL(openSslBinaryReady()), - this, - SLOT(doGenerateCertificate())); - - downloadOpenSslSetup(); -} - -void PluginManager::savePlugin() +bool PluginManager::savePlugin() { // create the path if not exist QDir dir(m_PluginDir); @@ -169,15 +133,19 @@ void PluginManager::savePlugin() QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { emit error( - tr("Failed to download '%1' plugin to: %2") + tr("Failed to download plugin '%1' to: %2\n%3") .arg(m_PluginList.at(m_DownloadIndex)) - .arg(m_PluginDir)); + .arg(m_PluginDir) + .arg(file.errorString())); - return; + file.close(); + return false; } file.write(m_DataDownloader.data()); file.close(); + + return true; } QString PluginManager::getPluginUrl(const QString& pluginName) @@ -236,19 +204,6 @@ QString PluginManager::getPluginUrl(const QString& pluginName) return result; } -QString PluginManager::getOpenSslSetupUrl() -{ - QString result; - -#if defined(Q_OS_WIN) - result = kBaseUrl; - result.append("/tools/"); - result.append(kWinOpenSslSetup); -#endif - - return result; -} - QString PluginManager::getPluginOsSpecificName(const QString& pluginName) { QString result = pluginName; @@ -261,127 +216,3 @@ QString PluginManager::getPluginOsSpecificName(const QString& pluginName) #endif return result; } - -bool PluginManager::checkOpenSslBinary() -{ - // assume OpenSsl is unavailable on Windows, - // but always available on both Mac and Linux -#if defined(Q_OS_WIN) - return false; -#else - return true; -#endif -} - -void PluginManager::downloadOpenSslSetup() -{ - if (checkOpenSslBinary()) { - emit openSslBinaryReady(); - return; - } - - QString urlString = getOpenSslSetupUrl(); - - QUrl url; - url.setUrl(urlString); - - disconnect( - &m_DataDownloader, - SIGNAL(isComplete()), - this, - SLOT(downloadPlugins())); - - connect( - &m_DataDownloader, - SIGNAL(isComplete()), - this, - SLOT(saveOpenSslSetup())); - - m_DataDownloader.download(url); -} - -void PluginManager::doGenerateCertificate() -{ - QString openSslProgramFile; - -#if defined(Q_OS_WIN) - openSslProgramFile = m_ProfileDir; - openSslProgramFile.append("\\").append(kWinOpenSslBinary); -#else - openSslProgramFile = kUnixOpenSslCommand; -#endif - - QStringList arguments; - - // self signed certificate - arguments.append("req"); - arguments.append("-x509"); - arguments.append("-nodes"); - - // valide duration - arguments.append("-days"); - arguments.append(kCertificateLifetime); - - // subject information - arguments.append("-subj"); - - QString info(kCertificateSubjectInfo); - arguments.append(info); - - // private key - arguments.append("-newkey"); - arguments.append("rsa:1024"); - - // key output filename - arguments.append("-keyout"); - QString filename = m_ProfileDir; - filename.append(QDir::separator()).append(kCertificateFilename); - arguments.append(filename); - - // certificate output filename - arguments.append("-out"); - arguments.append(filename); - - QStringList environment; - -#if defined(Q_OS_WIN) - environment << QString("OPENSSL_CONF=%1\\OpenSSL\\synergy.conf") - .arg(m_ProfileDir); -#endif - - if (!runProgram(openSslProgramFile, arguments, environment)) { - return; - } - - emit generateCertificateFinished(); -} - -bool PluginManager::runProgram( - const QString& program, const QStringList& args, const QStringList& env) -{ - QProcess process; - process.setEnvironment(env); - process.start(program, args); - - bool success = process.waitForStarted(); - - QString standardOutput, standardError; - if (success && process.waitForFinished()) - { - standardOutput = process.readAllStandardOutput().trimmed(); - standardError = process.readAllStandardError().trimmed(); - } - - int code = process.exitCode(); - if (!success || code != 0) - { - emit error( - QString("Program failed: %1\n\nCode: %2\nError: %3") - .arg(program) - .arg(process.exitCode()) - .arg(standardError.isEmpty() ? "Unknown" : standardError)); - return false; - } - - return true; -} diff --git a/src/gui/src/PluginManager.h b/src/gui/src/PluginManager.h index 31b987a9..8426643e 100644 --- a/src/gui/src/PluginManager.h +++ b/src/gui/src/PluginManager.h @@ -22,6 +22,7 @@ #include #include +#include "SslCertificate.h" #include "CoreInterface.h" #include "DataDownloader.h" @@ -35,30 +36,26 @@ public: int downloadIndex() { return m_DownloadIndex; } + static bool exist(QString name); + public slots: void downloadPlugins(); - void saveOpenSslSetup(); - void generateCertificate(); - void doGenerateCertificate(); private: - void savePlugin(); + bool savePlugin(); QString getPluginUrl(const QString& pluginName); - QString getOpenSslSetupUrl(); - QString getPluginOsSpecificName(const QString& pluginName); - bool checkOpenSslBinary(); - void downloadOpenSslSetup(); bool runProgram( const QString& program, const QStringList& args, const QStringList& env); + static QString getPluginOsSpecificName(const QString& pluginName); + signals: void error(QString e); + void info(QString i); void downloadNext(); void downloadFinished(); - void openSslBinaryReady(); - void generateCertificateFinished(); private: QStringList m_PluginList; @@ -67,6 +64,7 @@ private: int m_DownloadIndex; DataDownloader m_DataDownloader; CoreInterface m_CoreInterface; + SslCertificate m_SslCertificate; }; #endif // PLUGINMANAGER_H diff --git a/src/gui/src/PluginWizardPage.cpp b/src/gui/src/PluginWizardPage.cpp index 0cc5d782..1e560b0d 100644 --- a/src/gui/src/PluginWizardPage.cpp +++ b/src/gui/src/PluginWizardPage.cpp @@ -18,6 +18,7 @@ #include "PluginWizardPage.h" #include "ui_PluginWizardPageBase.h" +#include "SslCertificate.h" #include "WebClient.h" #include "PluginManager.h" @@ -29,6 +30,7 @@ PluginWizardPage::PluginWizardPage(AppConfig& appConfig, QWidget *parent) : m_Finished(false), m_pWebClient(NULL), m_pPluginManager(NULL), + m_pSslCertificate(NULL), m_AppConfig(appConfig) { setupUi(this); @@ -36,6 +38,8 @@ PluginWizardPage::PluginWizardPage(AppConfig& appConfig, QWidget *parent) : QMovie *movie = new QMovie(":/res/image/spinning-wheel.gif"); m_pLabelSpinning->setMovie(movie); movie->start(); + + m_pSslCertificate = new SslCertificate(this); } PluginWizardPage::~PluginWizardPage() @@ -47,6 +51,8 @@ PluginWizardPage::~PluginWizardPage() if (m_pPluginManager != NULL) { delete m_pPluginManager; } + + delete m_pSslCertificate; } void PluginWizardPage::changeEvent(QEvent *e) @@ -101,20 +107,20 @@ void PluginWizardPage::finished() void PluginWizardPage::generateCertificate() { - connect(m_pPluginManager, - SIGNAL(generateCertificateFinished()), + connect(m_pSslCertificate, + SIGNAL(generateFinished()), this, SLOT(finished())); - connect(m_pPluginManager, - SIGNAL(generateCertificateFinished()), - m_pPluginManagerThread, + connect(m_pSslCertificate, + SIGNAL(generateFinished()), + m_pThread, SLOT(quit())); updateStatus(tr("Generating SSL certificate...")); QMetaObject::invokeMethod( - m_pPluginManager, + m_pSslCertificate, "generateCertificate", Qt::QueuedConnection); } @@ -128,13 +134,18 @@ void PluginWizardPage::downloadPlugins() { QStringList pluginList = m_pWebClient->getPluginList(); m_pPluginManager = new PluginManager(pluginList); - m_pPluginManagerThread = new QThread; + m_pThread = new QThread; connect(m_pPluginManager, SIGNAL(error(QString)), this, SLOT(showError(QString))); + connect(m_pPluginManager, + SIGNAL(info(QString)), + this, + SLOT(updateStatus(QString))); + connect(m_pPluginManager, SIGNAL(downloadNext()), this, @@ -147,12 +158,12 @@ void PluginWizardPage::downloadPlugins() connect(m_pPluginManager, SIGNAL(error(QString)), - m_pPluginManagerThread, + m_pThread, SLOT(quit())); - connect(m_pPluginManagerThread, + connect(m_pThread, SIGNAL(finished()), - m_pPluginManagerThread, + m_pThread, SLOT(deleteLater())); updateStatus( @@ -160,8 +171,8 @@ void PluginWizardPage::downloadPlugins() .arg(pluginList.at(0)) .arg(pluginList.size())); - m_pPluginManager->moveToThread(m_pPluginManagerThread); - m_pPluginManagerThread->start(); + m_pPluginManager->moveToThread(m_pThread); + m_pThread->start(); QMetaObject::invokeMethod( m_pPluginManager, diff --git a/src/gui/src/PluginWizardPage.h b/src/gui/src/PluginWizardPage.h index ed224201..b2de1e3b 100644 --- a/src/gui/src/PluginWizardPage.h +++ b/src/gui/src/PluginWizardPage.h @@ -25,6 +25,7 @@ class WebClient; class PluginManager; +class SslCertificate; class PluginWizardPage : public QWizardPage, public Ui::PluginWizardPage { @@ -46,13 +47,13 @@ protected: protected slots: void showError(QString error); + void updateStatus(QString info); void queryPluginDone(); void updateDownloadStatus(); void finished(); void generateCertificate(); private: - void updateStatus(QString info); void downloadPlugins(); void showFinished(); @@ -62,7 +63,8 @@ private: QString m_Password; WebClient* m_pWebClient; PluginManager* m_pPluginManager; - QThread* m_pPluginManagerThread; + SslCertificate* m_pSslCertificate; + QThread* m_pThread; AppConfig& m_AppConfig; }; #endif // PLUGINWIZARDPAGE_H diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index 7f6318a5..04e83c79 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -18,6 +18,7 @@ #include "SettingsDialog.h" +#include "PluginManager.h" #include "CoreInterface.h" #include "SynergyLocale.h" #include "QSynergyApplication.h" @@ -30,6 +31,8 @@ #include #include +static const char networkSecurity[] = "ns"; + SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), Ui::SettingsDialogBase(), @@ -57,10 +60,7 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : m_pCheckBoxElevateMode->hide(); #endif - QString pluginDir = m_CoreInterface.getPluginDir(); - QDir dir(pluginDir); - int fileNum = dir.entryInfoList(QDir::NoDotAndDotDot|QDir::AllEntries).count(); - if (fileNum == 0) { + if (!PluginManager::exist(networkSecurity)) { m_pGroupNetworkSecurity->setEnabled(false); m_pCheckBoxEnableCrypto->setChecked(false); } diff --git a/src/gui/src/SetupWizard.cpp b/src/gui/src/SetupWizard.cpp index bc9d1ca3..803725be 100644 --- a/src/gui/src/SetupWizard.cpp +++ b/src/gui/src/SetupWizard.cpp @@ -169,6 +169,7 @@ void SetupWizard::accept() appConfig.setEdition(m_Edition); } m_MainWindow.setEdition(m_Edition); + m_MainWindow.updateLocalFingerprint(); settings.sync(); diff --git a/src/gui/src/SslCertificate.cpp b/src/gui/src/SslCertificate.cpp new file mode 100644 index 00000000..eee38fc6 --- /dev/null +++ b/src/gui/src/SslCertificate.cpp @@ -0,0 +1,175 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015 Synergy Si Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package 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. If not, see . + */ + +#include "SslCertificate.h" + +#include "Fingerprint.h" + +#include +#include +#include + +static const char kCertificateLifetime[] = "365"; +static const char kCertificateSubjectInfo[] = "/CN=Synergy"; +static const char kCertificateFilename[] = "Synergy.pem"; +static const char kSslDir[] = "SSL"; +static const char kUnixOpenSslCommand[] = "openssl"; + +#if defined(Q_OS_WIN) +static const char kWinOpenSslBinary[] = "OpenSSL\\openssl.exe"; +static const char kConfigFile[] = "OpenSSL\\synergy.conf"; +#endif + +SslCertificate::SslCertificate(QObject *parent) : + QObject(parent) +{ + m_ProfileDir = m_CoreInterface.getProfileDir(); + if (m_ProfileDir.isEmpty()) { + emit error(tr("Failed to get profile directory.")); + } +} + +bool SslCertificate::runTool(const QStringList& args) +{ + QString program; +#if defined(Q_OS_WIN) + program = QCoreApplication::applicationDirPath(); + program.append("\\").append(kWinOpenSslBinary); +#else + program = kUnixOpenSslCommand; +#endif + + + QStringList environment; +#if defined(Q_OS_WIN) + environment << QString("OPENSSL_CONF=%1\\%2") + .arg(QCoreApplication::applicationDirPath()) + .arg(kConfigFile); +#endif + + QProcess process; + process.setEnvironment(environment); + process.start(program, args); + + bool success = process.waitForStarted(); + + QString standardError; + if (success && process.waitForFinished()) + { + m_ToolOutput = process.readAllStandardOutput().trimmed(); + standardError = process.readAllStandardError().trimmed(); + } + + int code = process.exitCode(); + if (!success || code != 0) + { + emit error( + QString("SSL tool failed: %1\n\nCode: %2\nError: %3") + .arg(program) + .arg(process.exitCode()) + .arg(standardError.isEmpty() ? "Unknown" : standardError)); + return false; + } + + return true; +} + +void SslCertificate::generateCertificate() +{ + QStringList arguments; + + // self signed certificate + arguments.append("req"); + arguments.append("-x509"); + arguments.append("-nodes"); + + // valide duration + arguments.append("-days"); + arguments.append(kCertificateLifetime); + + // subject information + arguments.append("-subj"); + + QString subInfo(kCertificateSubjectInfo); + arguments.append(subInfo); + + // private key + arguments.append("-newkey"); + arguments.append("rsa:1024"); + + QString sslDirPath = QString("%1%2%3") + .arg(m_ProfileDir) + .arg(QDir::separator()) + .arg(kSslDir); + + QDir sslDir(sslDirPath); + if (!sslDir.exists()) { + sslDir.mkpath("."); + } + + QString filename = QString("%1%2%3") + .arg(sslDirPath) + .arg(QDir::separator()) + .arg(kCertificateFilename); + + // key output filename + arguments.append("-keyout"); + arguments.append(filename); + + // certificate output filename + arguments.append("-out"); + arguments.append(filename); + + if (!runTool(arguments)) { + return; + } + + emit info(tr("SSL certificate generated.")); + + generateFingerprint(filename); + + emit generateFinished(); +} + +void SslCertificate::generateFingerprint(const QString& certificateFilename) +{ + QStringList arguments; + arguments.append("x509"); + arguments.append("-fingerprint"); + arguments.append("-sha1"); + arguments.append("-noout"); + arguments.append("-in"); + arguments.append(certificateFilename); + + if (!runTool(arguments)) { + return; + } + + // find the fingerprint from the tool output + int i = m_ToolOutput.indexOf("="); + if (i != -1) { + i++; + QString fingerprint = m_ToolOutput.mid( + i, m_ToolOutput.size() - i); + + Fingerprint::local().trust(fingerprint, false); + emit info(tr("SSL fingerprint generated.")); + } + else { + emit error(tr("Failed to find SSL fingerprint.")); + } +} diff --git a/src/test/mock/client/MockClient.h b/src/gui/src/SslCertificate.h similarity index 57% rename from src/test/mock/client/MockClient.h rename to src/gui/src/SslCertificate.h index 427c9fe6..e85bc3d4 100644 --- a/src/test/mock/client/MockClient.h +++ b/src/gui/src/SslCertificate.h @@ -1,7 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012 Synergy Si Ltd. - * Copyright (C) 2011 Nick Bolton + * Copyright (C) 2015 Synergy Si Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,20 +17,31 @@ #pragma once -#define TEST_ENV +#include "CoreInterface.h" -#include "client/Client.h" +#include -#include "test/global/gmock.h" - -class IEventQueue; - -class MockClient : public Client +class SslCertificate : public QObject { +Q_OBJECT + public: - MockClient() : Client() { } - MOCK_METHOD2(mouseMove, void(SInt32, SInt32)); - MOCK_METHOD1(setOptions, void(const OptionsList&)); - MOCK_METHOD0(handshakeComplete, void()); - MOCK_METHOD1(setDecryptIv, void(const UInt8*)); + explicit SslCertificate(QObject *parent = 0); + +public slots: + void generateCertificate(); + +signals: + void error(QString e); + void info(QString i); + void generateFinished(); + +private: + bool runTool(const QStringList& args); + void generateFingerprint(const QString& certificateFilename); + +private: + QString m_ProfileDir; + QString m_ToolOutput; + CoreInterface m_CoreInterface; }; diff --git a/src/lib/base/EventTypes.cpp b/src/lib/base/EventTypes.cpp index 5ef90e8d..d317e1d7 100644 --- a/src/lib/base/EventTypes.cpp +++ b/src/lib/base/EventTypes.cpp @@ -95,6 +95,7 @@ REGISTER_EVENT(IListenSocket, connecting) // REGISTER_EVENT(ISocket, disconnected) +REGISTER_EVENT(ISocket, stopRetry) // // OSXScreen diff --git a/src/lib/base/EventTypes.h b/src/lib/base/EventTypes.h index dc605f21..74bbed63 100644 --- a/src/lib/base/EventTypes.h +++ b/src/lib/base/EventTypes.h @@ -281,7 +281,8 @@ private: class ISocketEvents : public EventTypes { public: ISocketEvents() : - m_disconnected(Event::kUnknown) { } + m_disconnected(Event::kUnknown), + m_stopRetry(Event::kUnknown) { } //! @name accessors //@{ @@ -294,10 +295,18 @@ public: */ Event::Type disconnected(); + //! Get stop retry event type + /*! + Returns the stop retry event type. This is sent when the client + doesn't want to reconnect after it disconnects from the server. + */ + Event::Type stopRetry(); + //@} private: Event::Type m_disconnected; + Event::Type m_stopRetry; }; class OSXScreenEvents : public EventTypes { diff --git a/src/lib/base/String.cpp b/src/lib/base/String.cpp index 4ce38899..4674deff 100644 --- a/src/lib/base/String.cpp +++ b/src/lib/base/String.cpp @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include namespace synergy { namespace string { @@ -180,6 +183,30 @@ removeFileExt(String filename) return filename.substr(0, dot); } +void +toHex(String& subject, int width, const char fill) +{ + std::stringstream ss; + ss << std::hex; + for (unsigned int i = 0; i < subject.length(); i++) { + ss << std::setw(width) << std::setfill(fill) << (int)(unsigned char)subject[i]; + } + + subject = ss.str(); +} + +void +uppercase(String& subject) +{ + std::transform(subject.begin(), subject.end(), subject.begin(), ::toupper); +} + +void +removeChar(String& subject, const char c) +{ + subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end()); +} + // // CaselessCmp // diff --git a/src/lib/base/String.h b/src/lib/base/String.h index 9d137143..dbe5c21b 100644 --- a/src/lib/base/String.h +++ b/src/lib/base/String.h @@ -70,6 +70,25 @@ Finds the last dot and remove all characters from the dot to the end */ String removeFileExt(String filename); +//! Convert into hexdecimal +/*! +Convert each character in \c subject into hexdecimal form with \c width +*/ +void toHex(String& subject, int width, const char fill = '0'); + +//! Convert to all uppercase +/*! +Convert each character in \c subject to uppercase +*/ +void uppercase(String& subject); + +//! Remove all specific char in suject +/*! +Remove all specific \c char in \c suject +*/ +void removeChar(String& subject, const char c); + + //! Case-insensitive comparisons /*! This class provides case-insensitve comparison functions. diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 456260f0..3e59c942 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -60,8 +60,7 @@ Client::Client( const String& name, const NetworkAddress& address, ISocketFactory* socketFactory, synergy::Screen* screen, - bool enableDragDrop, - bool enableCrypto) : + ClientArgs& args) : m_mock(false), m_name(name), m_serverAddress(address), @@ -77,9 +76,9 @@ Client::Client( m_events(events), m_sendFileThread(NULL), m_writeToDropDirThread(NULL), - m_enableDragDrop(enableDragDrop), m_socket(NULL), - m_useSecureNetwork(false) + m_useSecureNetwork(false), + m_args(args) { assert(m_socketFactory != NULL); assert(m_screen != NULL); @@ -94,7 +93,7 @@ Client::Client( new TMethodEventJob(this, &Client::handleResume)); - if (m_enableDragDrop) { + if (m_args.m_enableDragDrop) { m_events->adoptHandler(m_events->forIScreen().fileChunkSending(), this, new TMethodEventJob(this, @@ -105,7 +104,7 @@ Client::Client( &Client::handleFileRecieveCompleted)); } - if (enableCrypto) { + if (m_args.m_enableCrypto) { m_useSecureNetwork = ARCH->plugin().exists(s_networkSecurity); if (m_useSecureNetwork == false) { LOG((CLOG_NOTE "crypto disabled because of ns plugin not available")); @@ -471,6 +470,10 @@ Client::setupConnection() m_stream->getEventTarget(), new TMethodEventJob(this, &Client::handleDisconnected)); + + m_events->adoptHandler(m_events->forISocket().stopRetry(), + m_stream->getEventTarget(), + new TMethodEventJob(this, &Client::handleStopRetry)); } void @@ -526,6 +529,8 @@ Client::cleanupConnection() m_stream->getEventTarget()); m_events->removeHandler(m_events->forISocket().disconnected(), m_stream->getEventTarget()); + m_events->removeHandler(m_events->forISocket().stopRetry(), + m_stream->getEventTarget()); cleanupStream(); } } @@ -744,6 +749,11 @@ Client::onFileRecieveCompleted() } } +void +Client::handleStopRetry(const Event&, void*) +{ + m_args.m_restartable = false; +} void Client::writeToDropDirThread(void*) @@ -780,7 +790,8 @@ Client::fileChunkReceived(String data) void Client::dragInfoReceived(UInt32 fileNum, String data) { - if (!m_enableDragDrop) { + // TODO: fix duplicate function from CServer + if (!m_args.m_enableDragDrop) { LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info.")); return; } diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index f869ef64..9245a825 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -23,6 +23,7 @@ #include "synergy/IClipboard.h" #include "synergy/DragInformation.h" #include "synergy/INode.h" +#include "synergy/ClientArgs.h" #include "net/NetworkAddress.h" #include "base/EventTypes.h" @@ -59,13 +60,8 @@ public: const String& name, const NetworkAddress& address, ISocketFactory* socketFactory, synergy::Screen* screen, - bool enableDragDrop, - bool enableCrypto); + ClientArgs& args); ~Client(); - -#ifdef TEST_ENV - Client() : m_mock(true) { } -#endif //! @name manipulators //@{ @@ -196,6 +192,7 @@ private: void handleResume(const Event& event, void*); void handleFileChunkSending(const Event&, void*); void handleFileRecieveCompleted(const Event&, void*); + void handleStopRetry(const Event&, void*); void onFileRecieveCompleted(); public: @@ -224,7 +221,7 @@ private: String m_dragFileExt; Thread* m_sendFileThread; Thread* m_writeToDropDirThread; - bool m_enableDragDrop; TCPSocket* m_socket; bool m_useSecureNetwork; + ClientArgs& m_args; }; diff --git a/src/lib/mt/Thread.cpp b/src/lib/mt/Thread.cpp index dd45d872..f28bb6b9 100644 --- a/src/lib/mt/Thread.cpp +++ b/src/lib/mt/Thread.cpp @@ -18,7 +18,6 @@ #include "mt/Thread.h" -#include "net/XSocket.h" #include "mt/XMT.h" #include "mt/XThread.h" #include "arch/Arch.h" @@ -158,11 +157,6 @@ Thread::threadFunc(void* vjob) job->run(); LOG((CLOG_DEBUG1 "thread 0x%08x exit", id)); } - - catch (XSocket& e) { - // client called cancel() - LOG((CLOG_DEBUG "%s", e.what())); - } catch (XThreadCancel&) { // client called cancel() LOG((CLOG_DEBUG1 "caught cancel on thread 0x%08x", id)); diff --git a/src/lib/net/TCPListenSocket.cpp b/src/lib/net/TCPListenSocket.cpp index cdbd73c4..f0f06ee3 100644 --- a/src/lib/net/TCPListenSocket.cpp +++ b/src/lib/net/TCPListenSocket.cpp @@ -112,27 +112,35 @@ TCPListenSocket::accept() try { socket = new TCPSocket(m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL)); if (socket != NULL) { - m_socketMultiplexer->addSocket(this, - new TSocketMultiplexerMethodJob( - this, &TCPListenSocket::serviceListening, - m_socket, true, false)); + setListeningJob(); } return socket; } catch (XArchNetwork&) { if (socket != NULL) { delete socket; + setListeningJob(); } return NULL; } catch (std::exception &ex) { if (socket != NULL) { delete socket; + setListeningJob(); } throw ex; } } +void +TCPListenSocket::setListeningJob() +{ + m_socketMultiplexer->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &TCPListenSocket::serviceListening, + m_socket, true, false)); +} + ISocketMultiplexerJob* TCPListenSocket::serviceListening(ISocketMultiplexerJob* job, bool read, bool, bool error) diff --git a/src/lib/net/TCPListenSocket.h b/src/lib/net/TCPListenSocket.h index ef2687db..c769e170 100644 --- a/src/lib/net/TCPListenSocket.h +++ b/src/lib/net/TCPListenSocket.h @@ -45,6 +45,9 @@ public: accept(); virtual void deleteSocket(void*) { } +protected: + void setListeningJob(); + public: ISocketMultiplexerJob* serviceListening(ISocketMultiplexerJob*, diff --git a/src/lib/net/TCPSocket.cpp b/src/lib/net/TCPSocket.cpp index c54df40b..62175664 100644 --- a/src/lib/net/TCPSocket.cpp +++ b/src/lib/net/TCPSocket.cpp @@ -461,15 +461,26 @@ TCPSocket::serviceConnected(ISocketMultiplexerJob* job, } bool needNewJob = false; + static UInt32 s_retryOutputBufferSize = 0; if (write) { try { // write data UInt32 n = m_outputBuffer.getSize(); + + if (s_retryOutputBufferSize > 0) { + n = s_retryOutputBufferSize; + } + const void* buffer = m_outputBuffer.peek(n); if (isSecure()) { if (isSecureReady()) { + s_retryOutputBufferSize = n; n = secureWrite(buffer, n); + + if (n > 0) { + s_retryOutputBufferSize = 0; + } } else { return job; @@ -519,7 +530,8 @@ TCPSocket::serviceConnected(ISocketMultiplexerJob* job, if (read && m_readable) { try { - UInt8 buffer[4096]; + static UInt8 buffer[4096]; + memset(buffer, 0, sizeof(buffer)); size_t n = 0; if (isSecure()) { diff --git a/src/lib/net/TCPSocket.h b/src/lib/net/TCPSocket.h index 832cd128..2e75f357 100644 --- a/src/lib/net/TCPSocket.h +++ b/src/lib/net/TCPSocket.h @@ -59,6 +59,7 @@ public: virtual void secureConnect() {} virtual void secureAccept() {} + virtual void setFingerprintFilename(String& f) {} protected: ArchSocket getSocket() { return m_socket; } diff --git a/src/lib/plugin/ns/SecureListenSocket.cpp b/src/lib/plugin/ns/SecureListenSocket.cpp index a0df2519..bf8cd7cc 100644 --- a/src/lib/plugin/ns/SecureListenSocket.cpp +++ b/src/lib/plugin/ns/SecureListenSocket.cpp @@ -23,6 +23,7 @@ #include "net/TSocketMultiplexerMethodJob.h" #include "arch/XArch.h" +static const char s_certificateDir[] = { "SSL" }; static const char s_certificateFilename[] = { "Synergy.pem" }; // @@ -54,39 +55,41 @@ SecureListenSocket::accept() m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL)); + socket->initSsl(true); + + if (socket != NULL) { + setListeningJob(); + } + + String certificateFilename = synergy::string::sprintf( + "%s/%s/%s", + ARCH->getProfileDirectory().c_str(), + s_certificateDir, + s_certificateFilename); + + bool loaded = socket->loadCertificates(certificateFilename); + if (!loaded) { + delete socket; + return NULL; + } + + socket->secureAccept(); m_secureSocketSet.insert(socket); - socket->initSsl(true); - // TODO: customized certificate path - String certificateFilename = ARCH->getProfileDirectory(); -#if SYSAPI_WIN32 - certificateFilename.append("\\"); -#elif SYSAPI_UNIX - certificateFilename.append("/"); -#endif - certificateFilename.append(s_certificateFilename); - - socket->loadCertificates(certificateFilename.c_str()); - socket->secureAccept(); - - if (socket != NULL) { - m_socketMultiplexer->addSocket(this, - new TSocketMultiplexerMethodJob( - this, &TCPListenSocket::serviceListening, - m_socket, true, false)); - } return dynamic_cast(socket); } catch (XArchNetwork&) { if (socket != NULL) { delete socket; + setListeningJob(); } return NULL; } catch (std::exception &ex) { if (socket != NULL) { delete socket; + setListeningJob(); } throw ex; } diff --git a/src/lib/plugin/ns/SecureSocket.cpp b/src/lib/plugin/ns/SecureSocket.cpp index cb6db76f..0a36daab 100644 --- a/src/lib/plugin/ns/SecureSocket.cpp +++ b/src/lib/plugin/ns/SecureSocket.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // // SecureSocket @@ -35,6 +36,11 @@ #define MAX_ERROR_SIZE 65535 +static const char kFingerprintDirName[] = "SSL/Fingerprints"; +//static const char kFingerprintLocalFilename[] = "Local.txt"; +static const char kFingerprintTrustedServersFilename[] = "TrustedServers.txt"; +//static const char kFingerprintTrustedClientsFilename[] = "TrustedClients.txt"; + struct Ssl { SSL_CTX* m_context; SSL* m_ssl; @@ -149,24 +155,46 @@ SecureSocket::initSsl(bool server) initContext(server); } -void -SecureSocket::loadCertificates(const char* filename) +bool +SecureSocket::loadCertificates(String& filename) { - int r = 0; - r = SSL_CTX_use_certificate_file(m_ssl->m_context, filename, SSL_FILETYPE_PEM); - if (r <= 0) { - throwError("could not use ssl certificate"); + if (filename.empty()) { + showError("ssl certificate is not specified"); + return false; + } + else { + std::ifstream file(filename.c_str()); + bool exist = file.good(); + file.close(); + + if (!exist) { + String errorMsg("ssl certificate doesn't exist: "); + errorMsg.append(filename); + showError(errorMsg.c_str()); + return false; + } } - r = SSL_CTX_use_PrivateKey_file(m_ssl->m_context, filename, SSL_FILETYPE_PEM); + int r = 0; + r = SSL_CTX_use_certificate_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM); if (r <= 0) { - throwError("could not use ssl private key"); + showError("could not use ssl certificate"); + return false; + } + + r = SSL_CTX_use_PrivateKey_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM); + if (r <= 0) { + showError("could not use ssl private key"); + return false; } r = SSL_CTX_check_private_key(m_ssl->m_context); if (!r) { - throwError("could not verify ssl private key"); + showError("could not verify ssl private key"); + return false; } + + return true; } void @@ -253,23 +281,30 @@ SecureSocket::secureConnect(int socket) checkResult(r, fatal, retry); if (fatal) { - // tell user and sleep so the socket isn't hammered. LOG((CLOG_ERR "failed to connect secure socket")); LOG((CLOG_INFO "server connection may not be secure")); - ARCH->sleep(1); + return false; } m_secureReady = !retry; if (m_secureReady) { - LOG((CLOG_INFO "connected to secure socket")); - showCertificate(); + if (verifyCertFingerprint()) { + LOG((CLOG_INFO "connected to secure socket")); + if (!showCertificate()) { + disconnect(); + } + } + else { + LOG((CLOG_ERR "failed to verify server certificate fingerprint")); + disconnect(); + } } return retry; } -void +bool SecureSocket::showCertificate() { X509* cert; @@ -284,8 +319,11 @@ SecureSocket::showCertificate() X509_free(cert); } else { - throwError("server has no ssl certificate"); + showError("server has no ssl certificate"); + return false; } + + return true; } void @@ -330,6 +368,21 @@ SecureSocket::checkResult(int n, bool& fatal, bool& retry) case SSL_ERROR_SYSCALL: LOG((CLOG_ERR "secure socket error: SSL_ERROR_SYSCALL")); + if (ERR_peek_error() == 0) { + if (n == 0) { + LOG((CLOG_ERR "an EOF violates the protocol")); + } + else if (n == -1) { + // underlying socket I/O reproted an error + try { + ARCH->throwErrorOnSocket(getSocket()); + } + catch (XArchNetwork& e) { + LOG((CLOG_ERR "%s", e.what())); + } + } + } + fatal = true; break; @@ -339,37 +392,27 @@ SecureSocket::checkResult(int n, bool& fatal, bool& retry) break; default: - LOG((CLOG_ERR "secure socket error: SSL_ERROR_SSL")); + LOG((CLOG_ERR "secure socket error: unknown")); fatal = true; break; } if (fatal) { showError(); - sendEvent(getEvents()->forISocket().disconnected()); - sendEvent(getEvents()->forIStream().inputShutdown()); + disconnect(); } } void -SecureSocket::showError() +SecureSocket::showError(const char* reason) { - String error = getError(); - if (!error.empty()) { - LOG((CLOG_ERR "secure socket error: %s", error.c_str())); + if (reason != NULL) { + LOG((CLOG_ERR "%s", reason)); } -} -void -SecureSocket::throwError(const char* reason) -{ String error = getError(); if (!error.empty()) { - throw XSocket(synergy::string::sprintf( - "%s: %s", reason, error.c_str())); - } - else { - throw XSocket(reason); + LOG((CLOG_ERR "%s", error.c_str())); } } @@ -388,6 +431,82 @@ SecureSocket::getError() } } +void +SecureSocket::disconnect() +{ + sendEvent(getEvents()->forISocket().stopRetry()); + sendEvent(getEvents()->forISocket().disconnected()); + sendEvent(getEvents()->forIStream().inputShutdown()); +} + +void +SecureSocket::formatFingerprint(String& fingerprint, bool hex, bool separator) +{ + if (hex) { + // to hexidecimal + synergy::string::toHex(fingerprint, 2); + } + + // all uppercase + synergy::string::uppercase(fingerprint); + + if (separator) { + // add colon to separate each 2 charactors + size_t separators = fingerprint.size() / 2; + for (size_t i = 1; i < separators; i++) { + fingerprint.insert(i * 3 - 1, ":"); + } + } +} + +bool +SecureSocket::verifyCertFingerprint() +{ + // calculate received certificate fingerprint + X509 *cert = cert = SSL_get_peer_certificate(m_ssl->m_ssl); + EVP_MD* tempDigest; + unsigned char tempFingerprint[EVP_MAX_MD_SIZE]; + unsigned int tempFingerprintLen; + tempDigest = (EVP_MD*)EVP_sha1(); + int digestResult = X509_digest(cert, tempDigest, tempFingerprint, &tempFingerprintLen); + + if (digestResult <= 0) { + LOG((CLOG_ERR "failed to calculate fingerprint, digest result: %d", digestResult)); + return false; + } + + // format fingerprint into hexdecimal format with colon separator + String fingerprint(reinterpret_cast(tempFingerprint), tempFingerprintLen); + formatFingerprint(fingerprint); + LOG((CLOG_NOTE "server fingerprint: %s", fingerprint.c_str())); + + String trustedServersFilename; + trustedServersFilename = synergy::string::sprintf( + "%s/%s/%s", + ARCH->getProfileDirectory().c_str(), + kFingerprintDirName, + kFingerprintTrustedServersFilename); + + // check if this fingerprint exist + String fileLine; + std::ifstream file; + file.open(trustedServersFilename.c_str()); + + bool isValid = false; + while (!file.eof() && file.is_open()) { + getline(file,fileLine); + if (!fileLine.empty()) { + if (fileLine.compare(fingerprint) == 0) { + isValid = true; + break; + } + } + } + + file.close(); + return isValid; +} + ISocketMultiplexerJob* SecureSocket::serviceConnect(ISocketMultiplexerJob* job, bool, bool write, bool error) diff --git a/src/lib/plugin/ns/SecureSocket.h b/src/lib/plugin/ns/SecureSocket.h index d703d2ca..1fa03c47 100644 --- a/src/lib/plugin/ns/SecureSocket.h +++ b/src/lib/plugin/ns/SecureSocket.h @@ -49,7 +49,7 @@ public: UInt32 secureRead(void* buffer, UInt32 n); UInt32 secureWrite(const void* buffer, UInt32 n); void initSsl(bool server); - void loadCertificates(const char* CertFile); + bool loadCertificates(String& CertFile); private: // SSL @@ -57,11 +57,15 @@ private: void createSSL(); bool secureAccept(int s); bool secureConnect(int s); - void showCertificate(); + bool showCertificate(); void checkResult(int n, bool& fatal, bool& retry); - void showError(); - void throwError(const char* reason); + void showError(const char* reason = NULL); String getError(); + void disconnect(); + void formatFingerprint(String& fingerprint, + bool hex = true, + bool separator = true); + bool verifyCertFingerprint(); ISocketMultiplexerJob* serviceConnect(ISocketMultiplexerJob*, diff --git a/src/lib/synergy/ArgsBase.h b/src/lib/synergy/ArgsBase.h index 125f9b51..743202e8 100644 --- a/src/lib/synergy/ArgsBase.h +++ b/src/lib/synergy/ArgsBase.h @@ -24,25 +24,27 @@ class ArgsBase { public: ArgsBase(); virtual ~ArgsBase(); - bool m_daemon; - bool m_backend; - bool m_restartable; - bool m_noHooks; - const char* m_pname; - const char* m_logFilter; - const char* m_logFile; - const char* m_display; - String m_name; - bool m_disableTray; - bool m_enableIpc; - bool m_enableDragDrop; + +public: + bool m_daemon; + bool m_backend; + bool m_restartable; + bool m_noHooks; + const char* m_pname; + const char* m_logFilter; + const char* m_logFile; + const char* m_display; + String m_name; + bool m_disableTray; + bool m_enableIpc; + bool m_enableDragDrop; #if SYSAPI_WIN32 - bool m_debugServiceWait; - bool m_pauseOnExit; - bool m_stopOnDeskSwitch; + bool m_debugServiceWait; + bool m_pauseOnExit; + bool m_stopOnDeskSwitch; #endif #if WINAPI_XWINDOWS - bool m_disableXInitThreads; + bool m_disableXInitThreads; #endif bool m_shouldExit; String m_synergyAddress; diff --git a/src/lib/synergy/ClientApp.cpp b/src/lib/synergy/ClientApp.cpp index b9c00414..dd262a93 100644 --- a/src/lib/synergy/ClientApp.cpp +++ b/src/lib/synergy/ClientApp.cpp @@ -331,7 +331,6 @@ ClientApp::handleClientDisconnected(const Event&, void*) updateStatus(); } - Client* ClientApp::openClient(const String& name, const NetworkAddress& address, synergy::Screen* screen) @@ -342,8 +341,7 @@ ClientApp::openClient(const String& name, const NetworkAddress& address, address, new TCPSocketFactory(m_events, getSocketMultiplexer()), screen, - args().m_enableDragDrop, - args().m_enableCrypto); + args()); try { m_events->adoptHandler( diff --git a/src/lib/synergy/ClientArgs.h b/src/lib/synergy/ClientArgs.h index db749b3e..45b73e3c 100644 --- a/src/lib/synergy/ClientArgs.h +++ b/src/lib/synergy/ClientArgs.h @@ -26,5 +26,5 @@ public: ClientArgs(); public: - int m_yscroll; + int m_yscroll; }; diff --git a/src/setup/win32/Product.wxs b/src/setup/win32/Product.wxs index df61f862..884e329b 100644 --- a/src/setup/win32/Product.wxs +++ b/src/setup/win32/Product.wxs @@ -26,6 +26,7 @@ + @@ -61,6 +62,7 @@ + @@ -68,6 +70,7 @@ + @@ -112,5 +115,14 @@ + + + + + + + + + diff --git a/src/test/integtests/net/NetworkTests.cpp b/src/test/integtests/net/NetworkTests.cpp index 7e3d3bd5..b307ff51 100644 --- a/src/test/integtests/net/NetworkTests.cpp +++ b/src/test/integtests/net/NetworkTests.cpp @@ -140,7 +140,11 @@ TEST_F(NetworkTests, sendToClient_mockData) ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, true, false); + + ClientArgs args; + args.m_enableDragDrop = true; + args.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); m_events.adoptHandler( m_events.forIScreen().fileRecieveCompleted(), &client, @@ -192,7 +196,11 @@ TEST_F(NetworkTests, sendToClient_mockFile) ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, true, false); + + ClientArgs args; + args.m_enableDragDrop = true; + args.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); m_events.adoptHandler( m_events.forIScreen().fileRecieveCompleted(), &client, @@ -238,7 +246,10 @@ TEST_F(NetworkTests, sendToServer_mockData) ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, true, false); + ClientArgs args; + args.m_enableDragDrop = true; + args.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); m_events.adoptHandler( m_events.forClientListener().connected(), &listener, @@ -290,8 +301,11 @@ TEST_F(NetworkTests, sendToServer_mockFile) ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, true, false); - + ClientArgs args; + args.m_enableDragDrop = true; + args.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); + m_events.adoptHandler( m_events.forClientListener().connected(), &listener, new TMethodEventJob( diff --git a/src/test/unittests/base/StringTests.cpp b/src/test/unittests/base/StringTests.cpp index e0108413..2a461f05 100644 --- a/src/test/unittests/base/StringTests.cpp +++ b/src/test/unittests/base/StringTests.cpp @@ -53,3 +53,32 @@ TEST(StringTests, sprintf) EXPECT_EQ("answer=42", result); } + +TEST(StringTests, toHex) +{ + String subject = "foobar"; + int width = 2; + + string::toHex(subject, width); + + EXPECT_EQ("666f6f626172", subject); +} + +TEST(StringTests, uppercase) +{ + String subject = "12foo3BaR"; + + string::uppercase(subject); + + EXPECT_EQ("12FOO3BAR", subject); +} + +TEST(StringTests, removeChar) +{ + String subject = "foobar"; + const char c = 'o'; + + string::removeChar(subject, c); + + EXPECT_EQ("fbar", subject); +}