Merge remote-tracking branch 'synergy/master'

This commit is contained in:
jpmcmu 2015-04-22 19:14:22 -04:00
commit abfe8f5b78
37 changed files with 1039 additions and 387 deletions

View File

@ -17,7 +17,7 @@
# Version number for Synergy # Version number for Synergy
set(VERSION_MAJOR 1) set(VERSION_MAJOR 1)
set(VERSION_MINOR 7) set(VERSION_MINOR 7)
set(VERSION_REV 1) set(VERSION_REV 2)
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}")
cmake_minimum_required(VERSION 2.6) cmake_minimum_required(VERSION 2.6)

View File

@ -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 1.7.0
===== =====
Enhancement #4313 - SSL encrypted secure connection Enhancement #4313 - SSL encrypted secure connection

65
res/openssl/synergy.conf Normal file
View File

@ -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

View File

@ -55,7 +55,9 @@ SOURCES += src/main.cpp \
src/WebClient.cpp \ src/WebClient.cpp \
src/PluginWizardPage.cpp \ src/PluginWizardPage.cpp \
src/PluginManager.cpp \ src/PluginManager.cpp \
src/CoreInterface.cpp src/CoreInterface.cpp \
src/Fingerprint.cpp \
src/SslCertificate.cpp
HEADERS += src/MainWindow.h \ HEADERS += src/MainWindow.h \
src/AboutDialog.h \ src/AboutDialog.h \
src/ServerConfig.h \ src/ServerConfig.h \
@ -97,7 +99,9 @@ HEADERS += src/MainWindow.h \
src/PluginWizardPage.h \ src/PluginWizardPage.h \
src/ProcessorArch.h \ src/ProcessorArch.h \
src/PluginManager.h \ src/PluginManager.h \
src/CoreInterface.h src/CoreInterface.h \
src/Fingerprint.h \
src/SslCertificate.h
RESOURCES += res/Synergy.qrc RESOURCES += res/Synergy.qrc
RC_FILE = res/win/Synergy.rc RC_FILE = res/win/Synergy.rc
macx { macx {

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="utf-8"?>
<ui version="4.0"> <ui version="4.0">
<class>MainWindowBase</class> <class>MainWindowBase</class>
<widget class="QMainWindow" name="MainWindowBase"> <widget class="QMainWindow" name="MainWindowBase">
@ -87,7 +87,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="title"> <property name="title">
<string>&amp;Server (share this computer's mouse and keyboard):</string> <string>Ser&amp;ver (share this computer's mouse and keyboard):</string>
</property> </property>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
@ -117,6 +117,30 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="m_pLabelFingerprint">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Fingerprint:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_pLabelLocalFingerprint">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QRadioButton" name="m_pRadioInternalConfig"> <widget class="QRadioButton" name="m_pRadioInternalConfig">
<property name="text"> <property name="text">
@ -128,7 +152,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<widget class="QPushButton" name="m_pButtonConfigureServer"> <widget class="QPushButton" name="m_pButtonConfigureServer">
<property name="text"> <property name="text">

149
src/gui/src/Fingerprint.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "Fingerprint.h"
#include "CoreInterface.h"
#include <QDir>
#include <QTextStream>
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);
}

46
src/gui/src/Fingerprint.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
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;
};

View File

@ -21,6 +21,9 @@
#include <iostream> #include <iostream>
#include "MainWindow.h" #include "MainWindow.h"
#include "Fingerprint.h"
#include "PluginManager.h"
#include "AboutDialog.h" #include "AboutDialog.h"
#include "ServerConfigDialog.h" #include "ServerConfigDialog.h"
#include "SettingsDialog.h" #include "SettingsDialog.h"
@ -131,6 +134,8 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig) :
updateEdition(); updateEdition();
m_pLabelPadlock->hide(); m_pLabelPadlock->hide();
updateLocalFingerprint();
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
@ -395,6 +400,50 @@ void MainWindow::updateStateFromLogLine(const QString &line)
{ {
setSynergyState(synergyConnected); 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() void MainWindow::clearLog()
@ -451,7 +500,11 @@ void MainWindow::startSynergy()
} }
#if defined(Q_OS_WIN) #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 #endif
if ((synergyType() == synergyClient && !clientArgs(args, app)) if ((synergyType() == synergyClient && !clientArgs(args, app))
@ -492,14 +545,6 @@ void MainWindow::startSynergy()
if (desktopMode) 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); synergyProcess()->start(app, args);
if (!synergyProcess()->waitForStarted()) if (!synergyProcess()->waitForStarted())
{ {
@ -757,12 +802,6 @@ void MainWindow::setSynergyState(qSynergyState state)
setIcon(state); setIcon(state);
m_SynergyState = 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) void MainWindow::setVisible(bool visible)
@ -893,6 +932,19 @@ void MainWindow::setEdition(int type)
setWindowTitle(title); 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) void MainWindow::on_m_pGroupClient_toggled(bool on)
{ {
m_pGroupServer->setChecked(!on); m_pGroupServer->setChecked(!on);
@ -1172,6 +1224,7 @@ void MainWindow::updateEdition()
QString mac = getFirstMacAddress(); QString mac = getFirstMacAddress();
QString hashSrc = m_AppConfig.activateEmail() + mac; QString hashSrc = m_AppConfig.activateEmail() + mac;
QString hashResult = hash(hashSrc); QString hashResult = hash(hashSrc);
if (hashResult == m_AppConfig.userToken()) { if (hashResult == m_AppConfig.userToken()) {
setEdition(m_AppConfig.edition()); setEdition(m_AppConfig.edition());
} }
@ -1223,25 +1276,17 @@ void MainWindow::bonjourInstallFinished()
m_pCheckBoxAutoConfig->setChecked(true); 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) #if defined(Q_OS_WIN)
dir.replace("\\Synergy", "");
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", "");
#else #else
dir.replace("/.synergy", "");
return "";
#endif #endif
}
QString MainWindow::getProfileDirectoryForArg() return QString("\"%1\"").arg(dir);
{
return QString("\"%1\"").arg(getProfileDirectory());
} }

View File

@ -106,6 +106,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase
void updateZeroconfService(); void updateZeroconfService();
void serverDetected(const QString name); void serverDetected(const QString name);
void setEdition(int type); void setEdition(int type);
void updateLocalFingerprint();
public slots: public slots:
void appendLogRaw(const QString& text); void appendLogRaw(const QString& text);
@ -165,8 +166,8 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase
void downloadBonjour(); void downloadBonjour();
void promptAutoConfig(); void promptAutoConfig();
void updateEdition(); void updateEdition();
QString getProfileDirectory(); QString getProfileRootForArg();
QString getProfileDirectoryForArg(); void checkFingerprint(const QString& line);
private: private:
QSettings& m_Settings; QSettings& m_Settings;

View File

@ -22,10 +22,12 @@
#include "DataDownloader.h" #include "DataDownloader.h"
#include "QUtility.h" #include "QUtility.h"
#include "ProcessorArch.h" #include "ProcessorArch.h"
#include "Fingerprint.h"
#include <QFile> #include <QFile>
#include <QDir> #include <QDir>
#include <QProcess> #include <QProcess>
#include <QCoreApplication>
static QString kBaseUrl = "http://synergy-project.org/files"; static QString kBaseUrl = "http://synergy-project.org/files";
static const char kWinProcessorArch32[] = "Windows-x86"; 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 kLinuxProcessorArchDeb64[] = "Linux-x86_64-deb";
static const char kLinuxProcessorArchRpm32[] = "Linux-i686-rpm"; static const char kLinuxProcessorArchRpm32[] = "Linux-i686-rpm";
static const char kLinuxProcessorArchRpm64[] = "Linux-x86_64-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) #if defined(Q_OS_WIN)
static const char kWinPluginExt[] = ".dll"; 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) #elif defined(Q_OS_MAC)
static const char kMacPluginPrefix[] = "lib"; 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() void PluginManager::downloadPlugins()
{ {
if (m_DataDownloader.isFinished()) { if (m_DataDownloader.isFinished()) {
savePlugin(); if (!savePlugin()) {
return;
}
if (m_DownloadIndex != m_PluginList.size() - 1) { if (m_DownloadIndex != m_PluginList.size() - 1) {
emit downloadNext(); emit downloadNext();
} }
@ -101,59 +117,7 @@ void PluginManager::downloadPlugins()
} }
} }
void PluginManager::saveOpenSslSetup() bool PluginManager::savePlugin()
{
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()
{ {
// create the path if not exist // create the path if not exist
QDir dir(m_PluginDir); QDir dir(m_PluginDir);
@ -169,15 +133,19 @@ void PluginManager::savePlugin()
QFile file(filename); QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
emit error( 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_PluginList.at(m_DownloadIndex))
.arg(m_PluginDir)); .arg(m_PluginDir)
.arg(file.errorString()));
return; file.close();
return false;
} }
file.write(m_DataDownloader.data()); file.write(m_DataDownloader.data());
file.close(); file.close();
return true;
} }
QString PluginManager::getPluginUrl(const QString& pluginName) QString PluginManager::getPluginUrl(const QString& pluginName)
@ -236,19 +204,6 @@ QString PluginManager::getPluginUrl(const QString& pluginName)
return result; 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 PluginManager::getPluginOsSpecificName(const QString& pluginName)
{ {
QString result = pluginName; QString result = pluginName;
@ -261,127 +216,3 @@ QString PluginManager::getPluginOsSpecificName(const QString& pluginName)
#endif #endif
return result; 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;
}

View File

@ -22,6 +22,7 @@
#include <QStringList> #include <QStringList>
#include <QObject> #include <QObject>
#include "SslCertificate.h"
#include "CoreInterface.h" #include "CoreInterface.h"
#include "DataDownloader.h" #include "DataDownloader.h"
@ -35,30 +36,26 @@ public:
int downloadIndex() { return m_DownloadIndex; } int downloadIndex() { return m_DownloadIndex; }
static bool exist(QString name);
public slots: public slots:
void downloadPlugins(); void downloadPlugins();
void saveOpenSslSetup();
void generateCertificate();
void doGenerateCertificate();
private: private:
void savePlugin(); bool savePlugin();
QString getPluginUrl(const QString& pluginName); QString getPluginUrl(const QString& pluginName);
QString getOpenSslSetupUrl();
QString getPluginOsSpecificName(const QString& pluginName);
bool checkOpenSslBinary();
void downloadOpenSslSetup();
bool runProgram( bool runProgram(
const QString& program, const QString& program,
const QStringList& args, const QStringList& args,
const QStringList& env); const QStringList& env);
static QString getPluginOsSpecificName(const QString& pluginName);
signals: signals:
void error(QString e); void error(QString e);
void info(QString i);
void downloadNext(); void downloadNext();
void downloadFinished(); void downloadFinished();
void openSslBinaryReady();
void generateCertificateFinished();
private: private:
QStringList m_PluginList; QStringList m_PluginList;
@ -67,6 +64,7 @@ private:
int m_DownloadIndex; int m_DownloadIndex;
DataDownloader m_DataDownloader; DataDownloader m_DataDownloader;
CoreInterface m_CoreInterface; CoreInterface m_CoreInterface;
SslCertificate m_SslCertificate;
}; };
#endif // PLUGINMANAGER_H #endif // PLUGINMANAGER_H

View File

@ -18,6 +18,7 @@
#include "PluginWizardPage.h" #include "PluginWizardPage.h"
#include "ui_PluginWizardPageBase.h" #include "ui_PluginWizardPageBase.h"
#include "SslCertificate.h"
#include "WebClient.h" #include "WebClient.h"
#include "PluginManager.h" #include "PluginManager.h"
@ -29,6 +30,7 @@ PluginWizardPage::PluginWizardPage(AppConfig& appConfig, QWidget *parent) :
m_Finished(false), m_Finished(false),
m_pWebClient(NULL), m_pWebClient(NULL),
m_pPluginManager(NULL), m_pPluginManager(NULL),
m_pSslCertificate(NULL),
m_AppConfig(appConfig) m_AppConfig(appConfig)
{ {
setupUi(this); setupUi(this);
@ -36,6 +38,8 @@ PluginWizardPage::PluginWizardPage(AppConfig& appConfig, QWidget *parent) :
QMovie *movie = new QMovie(":/res/image/spinning-wheel.gif"); QMovie *movie = new QMovie(":/res/image/spinning-wheel.gif");
m_pLabelSpinning->setMovie(movie); m_pLabelSpinning->setMovie(movie);
movie->start(); movie->start();
m_pSslCertificate = new SslCertificate(this);
} }
PluginWizardPage::~PluginWizardPage() PluginWizardPage::~PluginWizardPage()
@ -47,6 +51,8 @@ PluginWizardPage::~PluginWizardPage()
if (m_pPluginManager != NULL) { if (m_pPluginManager != NULL) {
delete m_pPluginManager; delete m_pPluginManager;
} }
delete m_pSslCertificate;
} }
void PluginWizardPage::changeEvent(QEvent *e) void PluginWizardPage::changeEvent(QEvent *e)
@ -101,20 +107,20 @@ void PluginWizardPage::finished()
void PluginWizardPage::generateCertificate() void PluginWizardPage::generateCertificate()
{ {
connect(m_pPluginManager, connect(m_pSslCertificate,
SIGNAL(generateCertificateFinished()), SIGNAL(generateFinished()),
this, this,
SLOT(finished())); SLOT(finished()));
connect(m_pPluginManager, connect(m_pSslCertificate,
SIGNAL(generateCertificateFinished()), SIGNAL(generateFinished()),
m_pPluginManagerThread, m_pThread,
SLOT(quit())); SLOT(quit()));
updateStatus(tr("Generating SSL certificate...")); updateStatus(tr("Generating SSL certificate..."));
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
m_pPluginManager, m_pSslCertificate,
"generateCertificate", "generateCertificate",
Qt::QueuedConnection); Qt::QueuedConnection);
} }
@ -128,13 +134,18 @@ void PluginWizardPage::downloadPlugins()
{ {
QStringList pluginList = m_pWebClient->getPluginList(); QStringList pluginList = m_pWebClient->getPluginList();
m_pPluginManager = new PluginManager(pluginList); m_pPluginManager = new PluginManager(pluginList);
m_pPluginManagerThread = new QThread; m_pThread = new QThread;
connect(m_pPluginManager, connect(m_pPluginManager,
SIGNAL(error(QString)), SIGNAL(error(QString)),
this, this,
SLOT(showError(QString))); SLOT(showError(QString)));
connect(m_pPluginManager,
SIGNAL(info(QString)),
this,
SLOT(updateStatus(QString)));
connect(m_pPluginManager, connect(m_pPluginManager,
SIGNAL(downloadNext()), SIGNAL(downloadNext()),
this, this,
@ -147,12 +158,12 @@ void PluginWizardPage::downloadPlugins()
connect(m_pPluginManager, connect(m_pPluginManager,
SIGNAL(error(QString)), SIGNAL(error(QString)),
m_pPluginManagerThread, m_pThread,
SLOT(quit())); SLOT(quit()));
connect(m_pPluginManagerThread, connect(m_pThread,
SIGNAL(finished()), SIGNAL(finished()),
m_pPluginManagerThread, m_pThread,
SLOT(deleteLater())); SLOT(deleteLater()));
updateStatus( updateStatus(
@ -160,8 +171,8 @@ void PluginWizardPage::downloadPlugins()
.arg(pluginList.at(0)) .arg(pluginList.at(0))
.arg(pluginList.size())); .arg(pluginList.size()));
m_pPluginManager->moveToThread(m_pPluginManagerThread); m_pPluginManager->moveToThread(m_pThread);
m_pPluginManagerThread->start(); m_pThread->start();
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
m_pPluginManager, m_pPluginManager,

View File

@ -25,6 +25,7 @@
class WebClient; class WebClient;
class PluginManager; class PluginManager;
class SslCertificate;
class PluginWizardPage : public QWizardPage, public Ui::PluginWizardPage { class PluginWizardPage : public QWizardPage, public Ui::PluginWizardPage {
@ -46,13 +47,13 @@ protected:
protected slots: protected slots:
void showError(QString error); void showError(QString error);
void updateStatus(QString info);
void queryPluginDone(); void queryPluginDone();
void updateDownloadStatus(); void updateDownloadStatus();
void finished(); void finished();
void generateCertificate(); void generateCertificate();
private: private:
void updateStatus(QString info);
void downloadPlugins(); void downloadPlugins();
void showFinished(); void showFinished();
@ -62,7 +63,8 @@ private:
QString m_Password; QString m_Password;
WebClient* m_pWebClient; WebClient* m_pWebClient;
PluginManager* m_pPluginManager; PluginManager* m_pPluginManager;
QThread* m_pPluginManagerThread; SslCertificate* m_pSslCertificate;
QThread* m_pThread;
AppConfig& m_AppConfig; AppConfig& m_AppConfig;
}; };
#endif // PLUGINWIZARDPAGE_H #endif // PLUGINWIZARDPAGE_H

View File

@ -18,6 +18,7 @@
#include "SettingsDialog.h" #include "SettingsDialog.h"
#include "PluginManager.h"
#include "CoreInterface.h" #include "CoreInterface.h"
#include "SynergyLocale.h" #include "SynergyLocale.h"
#include "QSynergyApplication.h" #include "QSynergyApplication.h"
@ -30,6 +31,8 @@
#include <QFileDialog> #include <QFileDialog>
#include <QDir> #include <QDir>
static const char networkSecurity[] = "ns";
SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) :
QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),
Ui::SettingsDialogBase(), Ui::SettingsDialogBase(),
@ -57,10 +60,7 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) :
m_pCheckBoxElevateMode->hide(); m_pCheckBoxElevateMode->hide();
#endif #endif
QString pluginDir = m_CoreInterface.getPluginDir(); if (!PluginManager::exist(networkSecurity)) {
QDir dir(pluginDir);
int fileNum = dir.entryInfoList(QDir::NoDotAndDotDot|QDir::AllEntries).count();
if (fileNum == 0) {
m_pGroupNetworkSecurity->setEnabled(false); m_pGroupNetworkSecurity->setEnabled(false);
m_pCheckBoxEnableCrypto->setChecked(false); m_pCheckBoxEnableCrypto->setChecked(false);
} }

View File

@ -169,6 +169,7 @@ void SetupWizard::accept()
appConfig.setEdition(m_Edition); appConfig.setEdition(m_Edition);
} }
m_MainWindow.setEdition(m_Edition); m_MainWindow.setEdition(m_Edition);
m_MainWindow.updateLocalFingerprint();
settings.sync(); settings.sync();

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "SslCertificate.h"
#include "Fingerprint.h"
#include <QProcess>
#include <QDir>
#include <QCoreApplication>
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."));
}
}

View File

@ -1,7 +1,6 @@
/* /*
* synergy -- mouse and keyboard sharing utility * synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012 Synergy Si Ltd. * Copyright (C) 2015 Synergy Si Ltd.
* Copyright (C) 2011 Nick Bolton
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -18,20 +17,31 @@
#pragma once #pragma once
#define TEST_ENV #include "CoreInterface.h"
#include "client/Client.h" #include <QObject>
#include "test/global/gmock.h" class SslCertificate : public QObject
class IEventQueue;
class MockClient : public Client
{ {
Q_OBJECT
public: public:
MockClient() : Client() { } explicit SslCertificate(QObject *parent = 0);
MOCK_METHOD2(mouseMove, void(SInt32, SInt32));
MOCK_METHOD1(setOptions, void(const OptionsList&)); public slots:
MOCK_METHOD0(handshakeComplete, void()); void generateCertificate();
MOCK_METHOD1(setDecryptIv, void(const UInt8*));
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;
}; };

View File

@ -95,6 +95,7 @@ REGISTER_EVENT(IListenSocket, connecting)
// //
REGISTER_EVENT(ISocket, disconnected) REGISTER_EVENT(ISocket, disconnected)
REGISTER_EVENT(ISocket, stopRetry)
// //
// OSXScreen // OSXScreen

View File

@ -281,7 +281,8 @@ private:
class ISocketEvents : public EventTypes { class ISocketEvents : public EventTypes {
public: public:
ISocketEvents() : ISocketEvents() :
m_disconnected(Event::kUnknown) { } m_disconnected(Event::kUnknown),
m_stopRetry(Event::kUnknown) { }
//! @name accessors //! @name accessors
//@{ //@{
@ -294,10 +295,18 @@ public:
*/ */
Event::Type disconnected(); 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: private:
Event::Type m_disconnected; Event::Type m_disconnected;
Event::Type m_stopRetry;
}; };
class OSXScreenEvents : public EventTypes { class OSXScreenEvents : public EventTypes {

View File

@ -27,6 +27,9 @@
#include <algorithm> #include <algorithm>
#include <stdio.h> #include <stdio.h>
#include <cstdarg> #include <cstdarg>
#include <sstream>
#include <iomanip>
#include <algorithm>
namespace synergy { namespace synergy {
namespace string { namespace string {
@ -180,6 +183,30 @@ removeFileExt(String filename)
return filename.substr(0, dot); 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 // CaselessCmp
// //

View File

@ -70,6 +70,25 @@ Finds the last dot and remove all characters from the dot to the end
*/ */
String removeFileExt(String filename); 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 //! Case-insensitive comparisons
/*! /*!
This class provides case-insensitve comparison functions. This class provides case-insensitve comparison functions.

View File

@ -60,8 +60,7 @@ Client::Client(
const String& name, const NetworkAddress& address, const String& name, const NetworkAddress& address,
ISocketFactory* socketFactory, ISocketFactory* socketFactory,
synergy::Screen* screen, synergy::Screen* screen,
bool enableDragDrop, ClientArgs& args) :
bool enableCrypto) :
m_mock(false), m_mock(false),
m_name(name), m_name(name),
m_serverAddress(address), m_serverAddress(address),
@ -77,9 +76,9 @@ Client::Client(
m_events(events), m_events(events),
m_sendFileThread(NULL), m_sendFileThread(NULL),
m_writeToDropDirThread(NULL), m_writeToDropDirThread(NULL),
m_enableDragDrop(enableDragDrop),
m_socket(NULL), m_socket(NULL),
m_useSecureNetwork(false) m_useSecureNetwork(false),
m_args(args)
{ {
assert(m_socketFactory != NULL); assert(m_socketFactory != NULL);
assert(m_screen != NULL); assert(m_screen != NULL);
@ -94,7 +93,7 @@ Client::Client(
new TMethodEventJob<Client>(this, new TMethodEventJob<Client>(this,
&Client::handleResume)); &Client::handleResume));
if (m_enableDragDrop) { if (m_args.m_enableDragDrop) {
m_events->adoptHandler(m_events->forIScreen().fileChunkSending(), m_events->adoptHandler(m_events->forIScreen().fileChunkSending(),
this, this,
new TMethodEventJob<Client>(this, new TMethodEventJob<Client>(this,
@ -105,7 +104,7 @@ Client::Client(
&Client::handleFileRecieveCompleted)); &Client::handleFileRecieveCompleted));
} }
if (enableCrypto) { if (m_args.m_enableCrypto) {
m_useSecureNetwork = ARCH->plugin().exists(s_networkSecurity); m_useSecureNetwork = ARCH->plugin().exists(s_networkSecurity);
if (m_useSecureNetwork == false) { if (m_useSecureNetwork == false) {
LOG((CLOG_NOTE "crypto disabled because of ns plugin not available")); LOG((CLOG_NOTE "crypto disabled because of ns plugin not available"));
@ -471,6 +470,10 @@ Client::setupConnection()
m_stream->getEventTarget(), m_stream->getEventTarget(),
new TMethodEventJob<Client>(this, new TMethodEventJob<Client>(this,
&Client::handleDisconnected)); &Client::handleDisconnected));
m_events->adoptHandler(m_events->forISocket().stopRetry(),
m_stream->getEventTarget(),
new TMethodEventJob<Client>(this, &Client::handleStopRetry));
} }
void void
@ -526,6 +529,8 @@ Client::cleanupConnection()
m_stream->getEventTarget()); m_stream->getEventTarget());
m_events->removeHandler(m_events->forISocket().disconnected(), m_events->removeHandler(m_events->forISocket().disconnected(),
m_stream->getEventTarget()); m_stream->getEventTarget());
m_events->removeHandler(m_events->forISocket().stopRetry(),
m_stream->getEventTarget());
cleanupStream(); cleanupStream();
} }
} }
@ -744,6 +749,11 @@ Client::onFileRecieveCompleted()
} }
} }
void
Client::handleStopRetry(const Event&, void*)
{
m_args.m_restartable = false;
}
void void
Client::writeToDropDirThread(void*) Client::writeToDropDirThread(void*)
@ -780,7 +790,8 @@ Client::fileChunkReceived(String data)
void void
Client::dragInfoReceived(UInt32 fileNum, String data) 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.")); LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info."));
return; return;
} }

View File

@ -23,6 +23,7 @@
#include "synergy/IClipboard.h" #include "synergy/IClipboard.h"
#include "synergy/DragInformation.h" #include "synergy/DragInformation.h"
#include "synergy/INode.h" #include "synergy/INode.h"
#include "synergy/ClientArgs.h"
#include "net/NetworkAddress.h" #include "net/NetworkAddress.h"
#include "base/EventTypes.h" #include "base/EventTypes.h"
@ -59,14 +60,9 @@ public:
const String& name, const NetworkAddress& address, const String& name, const NetworkAddress& address,
ISocketFactory* socketFactory, ISocketFactory* socketFactory,
synergy::Screen* screen, synergy::Screen* screen,
bool enableDragDrop, ClientArgs& args);
bool enableCrypto);
~Client(); ~Client();
#ifdef TEST_ENV
Client() : m_mock(true) { }
#endif
//! @name manipulators //! @name manipulators
//@{ //@{
@ -196,6 +192,7 @@ private:
void handleResume(const Event& event, void*); void handleResume(const Event& event, void*);
void handleFileChunkSending(const Event&, void*); void handleFileChunkSending(const Event&, void*);
void handleFileRecieveCompleted(const Event&, void*); void handleFileRecieveCompleted(const Event&, void*);
void handleStopRetry(const Event&, void*);
void onFileRecieveCompleted(); void onFileRecieveCompleted();
public: public:
@ -224,7 +221,7 @@ private:
String m_dragFileExt; String m_dragFileExt;
Thread* m_sendFileThread; Thread* m_sendFileThread;
Thread* m_writeToDropDirThread; Thread* m_writeToDropDirThread;
bool m_enableDragDrop;
TCPSocket* m_socket; TCPSocket* m_socket;
bool m_useSecureNetwork; bool m_useSecureNetwork;
ClientArgs& m_args;
}; };

View File

@ -18,7 +18,6 @@
#include "mt/Thread.h" #include "mt/Thread.h"
#include "net/XSocket.h"
#include "mt/XMT.h" #include "mt/XMT.h"
#include "mt/XThread.h" #include "mt/XThread.h"
#include "arch/Arch.h" #include "arch/Arch.h"
@ -158,11 +157,6 @@ Thread::threadFunc(void* vjob)
job->run(); job->run();
LOG((CLOG_DEBUG1 "thread 0x%08x exit", id)); LOG((CLOG_DEBUG1 "thread 0x%08x exit", id));
} }
catch (XSocket& e) {
// client called cancel()
LOG((CLOG_DEBUG "%s", e.what()));
}
catch (XThreadCancel&) { catch (XThreadCancel&) {
// client called cancel() // client called cancel()
LOG((CLOG_DEBUG1 "caught cancel on thread 0x%08x", id)); LOG((CLOG_DEBUG1 "caught cancel on thread 0x%08x", id));

View File

@ -112,27 +112,35 @@ TCPListenSocket::accept()
try { try {
socket = new TCPSocket(m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL)); socket = new TCPSocket(m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL));
if (socket != NULL) { if (socket != NULL) {
m_socketMultiplexer->addSocket(this, setListeningJob();
new TSocketMultiplexerMethodJob<TCPListenSocket>(
this, &TCPListenSocket::serviceListening,
m_socket, true, false));
} }
return socket; return socket;
} }
catch (XArchNetwork&) { catch (XArchNetwork&) {
if (socket != NULL) { if (socket != NULL) {
delete socket; delete socket;
setListeningJob();
} }
return NULL; return NULL;
} }
catch (std::exception &ex) { catch (std::exception &ex) {
if (socket != NULL) { if (socket != NULL) {
delete socket; delete socket;
setListeningJob();
} }
throw ex; throw ex;
} }
} }
void
TCPListenSocket::setListeningJob()
{
m_socketMultiplexer->addSocket(this,
new TSocketMultiplexerMethodJob<TCPListenSocket>(
this, &TCPListenSocket::serviceListening,
m_socket, true, false));
}
ISocketMultiplexerJob* ISocketMultiplexerJob*
TCPListenSocket::serviceListening(ISocketMultiplexerJob* job, TCPListenSocket::serviceListening(ISocketMultiplexerJob* job,
bool read, bool, bool error) bool read, bool, bool error)

View File

@ -45,6 +45,9 @@ public:
accept(); accept();
virtual void deleteSocket(void*) { } virtual void deleteSocket(void*) { }
protected:
void setListeningJob();
public: public:
ISocketMultiplexerJob* ISocketMultiplexerJob*
serviceListening(ISocketMultiplexerJob*, serviceListening(ISocketMultiplexerJob*,

View File

@ -461,15 +461,26 @@ TCPSocket::serviceConnected(ISocketMultiplexerJob* job,
} }
bool needNewJob = false; bool needNewJob = false;
static UInt32 s_retryOutputBufferSize = 0;
if (write) { if (write) {
try { try {
// write data // write data
UInt32 n = m_outputBuffer.getSize(); UInt32 n = m_outputBuffer.getSize();
if (s_retryOutputBufferSize > 0) {
n = s_retryOutputBufferSize;
}
const void* buffer = m_outputBuffer.peek(n); const void* buffer = m_outputBuffer.peek(n);
if (isSecure()) { if (isSecure()) {
if (isSecureReady()) { if (isSecureReady()) {
s_retryOutputBufferSize = n;
n = secureWrite(buffer, n); n = secureWrite(buffer, n);
if (n > 0) {
s_retryOutputBufferSize = 0;
}
} }
else { else {
return job; return job;
@ -519,7 +530,8 @@ TCPSocket::serviceConnected(ISocketMultiplexerJob* job,
if (read && m_readable) { if (read && m_readable) {
try { try {
UInt8 buffer[4096]; static UInt8 buffer[4096];
memset(buffer, 0, sizeof(buffer));
size_t n = 0; size_t n = 0;
if (isSecure()) { if (isSecure()) {

View File

@ -59,6 +59,7 @@ public:
virtual void secureConnect() {} virtual void secureConnect() {}
virtual void secureAccept() {} virtual void secureAccept() {}
virtual void setFingerprintFilename(String& f) {}
protected: protected:
ArchSocket getSocket() { return m_socket; } ArchSocket getSocket() { return m_socket; }

View File

@ -23,6 +23,7 @@
#include "net/TSocketMultiplexerMethodJob.h" #include "net/TSocketMultiplexerMethodJob.h"
#include "arch/XArch.h" #include "arch/XArch.h"
static const char s_certificateDir[] = { "SSL" };
static const char s_certificateFilename[] = { "Synergy.pem" }; static const char s_certificateFilename[] = { "Synergy.pem" };
// //
@ -54,39 +55,41 @@ SecureListenSocket::accept()
m_events, m_events,
m_socketMultiplexer, m_socketMultiplexer,
ARCH->acceptSocket(m_socket, NULL)); 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); 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<TCPListenSocket>(
this, &TCPListenSocket::serviceListening,
m_socket, true, false));
}
return dynamic_cast<IDataSocket*>(socket); return dynamic_cast<IDataSocket*>(socket);
} }
catch (XArchNetwork&) { catch (XArchNetwork&) {
if (socket != NULL) { if (socket != NULL) {
delete socket; delete socket;
setListeningJob();
} }
return NULL; return NULL;
} }
catch (std::exception &ex) { catch (std::exception &ex) {
if (socket != NULL) { if (socket != NULL) {
delete socket; delete socket;
setListeningJob();
} }
throw ex; throw ex;
} }

View File

@ -28,6 +28,7 @@
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
#include <fstream>
// //
// SecureSocket // SecureSocket
@ -35,6 +36,11 @@
#define MAX_ERROR_SIZE 65535 #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 { struct Ssl {
SSL_CTX* m_context; SSL_CTX* m_context;
SSL* m_ssl; SSL* m_ssl;
@ -149,24 +155,46 @@ SecureSocket::initSsl(bool server)
initContext(server); initContext(server);
} }
void bool
SecureSocket::loadCertificates(const char* filename) SecureSocket::loadCertificates(String& filename)
{ {
int r = 0; if (filename.empty()) {
r = SSL_CTX_use_certificate_file(m_ssl->m_context, filename, SSL_FILETYPE_PEM); showError("ssl certificate is not specified");
if (r <= 0) { return false;
throwError("could not use ssl certificate"); }
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) { 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); r = SSL_CTX_check_private_key(m_ssl->m_context);
if (!r) { if (!r) {
throwError("could not verify ssl private key"); showError("could not verify ssl private key");
return false;
} }
return true;
} }
void void
@ -253,23 +281,30 @@ SecureSocket::secureConnect(int socket)
checkResult(r, fatal, retry); checkResult(r, fatal, retry);
if (fatal) { if (fatal) {
// tell user and sleep so the socket isn't hammered.
LOG((CLOG_ERR "failed to connect secure socket")); LOG((CLOG_ERR "failed to connect secure socket"));
LOG((CLOG_INFO "server connection may not be secure")); LOG((CLOG_INFO "server connection may not be secure"));
ARCH->sleep(1); return false;
} }
m_secureReady = !retry; m_secureReady = !retry;
if (m_secureReady) { if (m_secureReady) {
if (verifyCertFingerprint()) {
LOG((CLOG_INFO "connected to secure socket")); LOG((CLOG_INFO "connected to secure socket"));
showCertificate(); if (!showCertificate()) {
disconnect();
}
}
else {
LOG((CLOG_ERR "failed to verify server certificate fingerprint"));
disconnect();
}
} }
return retry; return retry;
} }
void bool
SecureSocket::showCertificate() SecureSocket::showCertificate()
{ {
X509* cert; X509* cert;
@ -284,8 +319,11 @@ SecureSocket::showCertificate()
X509_free(cert); X509_free(cert);
} }
else { else {
throwError("server has no ssl certificate"); showError("server has no ssl certificate");
return false;
} }
return true;
} }
void void
@ -330,6 +368,21 @@ SecureSocket::checkResult(int n, bool& fatal, bool& retry)
case SSL_ERROR_SYSCALL: case SSL_ERROR_SYSCALL:
LOG((CLOG_ERR "secure socket error: 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; fatal = true;
break; break;
@ -339,37 +392,27 @@ SecureSocket::checkResult(int n, bool& fatal, bool& retry)
break; break;
default: default:
LOG((CLOG_ERR "secure socket error: SSL_ERROR_SSL")); LOG((CLOG_ERR "secure socket error: unknown"));
fatal = true; fatal = true;
break; break;
} }
if (fatal) { if (fatal) {
showError(); showError();
sendEvent(getEvents()->forISocket().disconnected()); disconnect();
sendEvent(getEvents()->forIStream().inputShutdown());
} }
} }
void void
SecureSocket::showError() SecureSocket::showError(const char* reason)
{ {
String error = getError(); if (reason != NULL) {
if (!error.empty()) { LOG((CLOG_ERR "%s", reason));
LOG((CLOG_ERR "secure socket error: %s", error.c_str()));
} }
}
void
SecureSocket::throwError(const char* reason)
{
String error = getError(); String error = getError();
if (!error.empty()) { if (!error.empty()) {
throw XSocket(synergy::string::sprintf( LOG((CLOG_ERR "%s", error.c_str()));
"%s: %s", reason, error.c_str()));
}
else {
throw XSocket(reason);
} }
} }
@ -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<char*>(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* ISocketMultiplexerJob*
SecureSocket::serviceConnect(ISocketMultiplexerJob* job, SecureSocket::serviceConnect(ISocketMultiplexerJob* job,
bool, bool write, bool error) bool, bool write, bool error)

View File

@ -49,7 +49,7 @@ public:
UInt32 secureRead(void* buffer, UInt32 n); UInt32 secureRead(void* buffer, UInt32 n);
UInt32 secureWrite(const void* buffer, UInt32 n); UInt32 secureWrite(const void* buffer, UInt32 n);
void initSsl(bool server); void initSsl(bool server);
void loadCertificates(const char* CertFile); bool loadCertificates(String& CertFile);
private: private:
// SSL // SSL
@ -57,11 +57,15 @@ private:
void createSSL(); void createSSL();
bool secureAccept(int s); bool secureAccept(int s);
bool secureConnect(int s); bool secureConnect(int s);
void showCertificate(); bool showCertificate();
void checkResult(int n, bool& fatal, bool& retry); void checkResult(int n, bool& fatal, bool& retry);
void showError(); void showError(const char* reason = NULL);
void throwError(const char* reason);
String getError(); String getError();
void disconnect();
void formatFingerprint(String& fingerprint,
bool hex = true,
bool separator = true);
bool verifyCertFingerprint();
ISocketMultiplexerJob* ISocketMultiplexerJob*
serviceConnect(ISocketMultiplexerJob*, serviceConnect(ISocketMultiplexerJob*,

View File

@ -24,6 +24,8 @@ class ArgsBase {
public: public:
ArgsBase(); ArgsBase();
virtual ~ArgsBase(); virtual ~ArgsBase();
public:
bool m_daemon; bool m_daemon;
bool m_backend; bool m_backend;
bool m_restartable; bool m_restartable;

View File

@ -331,7 +331,6 @@ ClientApp::handleClientDisconnected(const Event&, void*)
updateStatus(); updateStatus();
} }
Client* Client*
ClientApp::openClient(const String& name, const NetworkAddress& address, ClientApp::openClient(const String& name, const NetworkAddress& address,
synergy::Screen* screen) synergy::Screen* screen)
@ -342,8 +341,7 @@ ClientApp::openClient(const String& name, const NetworkAddress& address,
address, address,
new TCPSocketFactory(m_events, getSocketMultiplexer()), new TCPSocketFactory(m_events, getSocketMultiplexer()),
screen, screen,
args().m_enableDragDrop, args());
args().m_enableCrypto);
try { try {
m_events->adoptHandler( m_events->adoptHandler(

View File

@ -26,6 +26,7 @@
<Feature Id="ProductFeature" Title="$(var.Name)"> <Feature Id="ProductFeature" Title="$(var.Name)">
<ComponentGroupRef Id="ProductComponents" /> <ComponentGroupRef Id="ProductComponents" />
<ComponentGroupRef Id="OpenSslComponents" />
<MergeRef Id="CRT" /> <MergeRef Id="CRT" />
</Feature> </Feature>
@ -61,6 +62,7 @@
<Directory Id="$(var.ProgramFilesFolder)"> <Directory Id="$(var.ProgramFilesFolder)">
<Directory Id="INSTALLFOLDER" Name="$(var.Name)"> <Directory Id="INSTALLFOLDER" Name="$(var.Name)">
<Merge Id="CRT" Language="0" SourceFile="$(var.CRT)" DiskId="1" /> <Merge Id="CRT" Language="0" SourceFile="$(var.CRT)" DiskId="1" />
<Directory Id="OpenSslDir" Name="OpenSSL" />
</Directory> </Directory>
</Directory> </Directory>
<Directory Id="ProgramMenuFolder" /> <Directory Id="ProgramMenuFolder" />
@ -68,6 +70,7 @@
</Fragment> </Fragment>
<Fragment> <Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="Core" Guid="EC9AD3B0-277C-4157-B5C8-5FD5B6A5F4AD"> <Component Id="Core" Guid="EC9AD3B0-277C-4157-B5C8-5FD5B6A5F4AD">
@ -112,5 +115,14 @@
</Component> </Component>
</ComponentGroup> </ComponentGroup>
<ComponentGroup Id="OpenSslComponents" Directory="OpenSslDir">
<Component Id="OpenSsl" Guid="92648F77-65A6-4B16-AC59-A1F37BD341B1">
<File Source="$(var.ExtPath)/openssl/out32dll/libeay32.dll" Id="OpenSslDll1" />
<File Source="$(var.ExtPath)/openssl/out32dll/ssleay32.dll" Id="OpenSslDll2" />
<File Source="$(var.ExtPath)/openssl/out32dll/openssl.exe" />
<File Source="$(var.ResPath)/openssl/synergy.conf" />
</Component>
</ComponentGroup>
</Fragment> </Fragment>
</Wix> </Wix>

View File

@ -140,7 +140,11 @@ TEST_F(NetworkTests, sendToClient_mockData)
ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape));
ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); 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.adoptHandler(
m_events.forIScreen().fileRecieveCompleted(), &client, m_events.forIScreen().fileRecieveCompleted(), &client,
@ -192,7 +196,11 @@ TEST_F(NetworkTests, sendToClient_mockFile)
ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape));
ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); 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.adoptHandler(
m_events.forIScreen().fileRecieveCompleted(), &client, m_events.forIScreen().fileRecieveCompleted(), &client,
@ -238,7 +246,10 @@ TEST_F(NetworkTests, sendToServer_mockData)
ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape));
ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); 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.adoptHandler(
m_events.forClientListener().connected(), &listener, m_events.forClientListener().connected(), &listener,
@ -290,7 +301,10 @@ TEST_F(NetworkTests, sendToServer_mockFile)
ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape));
ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); 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.adoptHandler(
m_events.forClientListener().connected(), &listener, m_events.forClientListener().connected(), &listener,

View File

@ -53,3 +53,32 @@ TEST(StringTests, sprintf)
EXPECT_EQ("answer=42", result); 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);
}