From 5ca2d5e27bbe68c09b1488f579999b4ee6371c21 Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 21 Dec 2017 10:43:28 +0100 Subject: [PATCH] added retrotor option in global .pri, and TorControl code --- .../src/TorControl/AddOnionCommand.cpp | 105 +++ .../src/TorControl/AddOnionCommand.h | 77 ++ .../src/TorControl/AuthenticateCommand.cpp | 64 ++ .../src/TorControl/AuthenticateCommand.h | 63 ++ retroshare-gui/src/TorControl/CryptoKey.cpp | 477 +++++++++++ retroshare-gui/src/TorControl/CryptoKey.h | 95 +++ .../src/TorControl/GetConfCommand.cpp | 124 +++ .../src/TorControl/GetConfCommand.h | 77 ++ .../src/TorControl/HiddenService.cpp | 137 ++++ retroshare-gui/src/TorControl/HiddenService.h | 104 +++ .../src/TorControl/PendingOperation.cpp | 84 ++ .../src/TorControl/PendingOperation.h | 87 ++ .../src/TorControl/ProtocolInfoCommand.cpp | 85 ++ .../src/TorControl/ProtocolInfoCommand.h | 78 ++ retroshare-gui/src/TorControl/SecureRNG.cpp | 146 ++++ retroshare-gui/src/TorControl/SecureRNG.h | 51 ++ .../src/TorControl/SetConfCommand.cpp | 106 +++ .../src/TorControl/SetConfCommand.h | 78 ++ retroshare-gui/src/TorControl/Settings.cpp | 553 +++++++++++++ retroshare-gui/src/TorControl/Settings.h | 257 ++++++ retroshare-gui/src/TorControl/StringUtil.cpp | 118 +++ retroshare-gui/src/TorControl/StringUtil.h | 46 ++ retroshare-gui/src/TorControl/TorControl.cpp | 763 ++++++++++++++++++ retroshare-gui/src/TorControl/TorControl.h | 141 ++++ .../src/TorControl/TorControlCommand.cpp | 63 ++ .../src/TorControl/TorControlCommand.h | 70 ++ .../src/TorControl/TorControlSocket.cpp | 176 ++++ .../src/TorControl/TorControlSocket.h | 77 ++ retroshare-gui/src/TorControl/TorManager.cpp | 335 ++++++++ retroshare-gui/src/TorControl/TorManager.h | 94 +++ retroshare-gui/src/TorControl/TorProcess.cpp | 311 +++++++ retroshare-gui/src/TorControl/TorProcess.h | 100 +++ retroshare-gui/src/TorControl/TorProcess_p.h | 79 ++ retroshare-gui/src/TorControl/TorSocket.cpp | 155 ++++ retroshare-gui/src/TorControl/TorSocket.h | 97 +++ retroshare-gui/src/TorControl/Useful.h | 71 ++ retroshare-gui/src/main.cpp | 26 +- retroshare-gui/src/retroshare-gui.pro | 46 +- retroshare.pri | 4 + 39 files changed, 5617 insertions(+), 3 deletions(-) create mode 100644 retroshare-gui/src/TorControl/AddOnionCommand.cpp create mode 100644 retroshare-gui/src/TorControl/AddOnionCommand.h create mode 100644 retroshare-gui/src/TorControl/AuthenticateCommand.cpp create mode 100644 retroshare-gui/src/TorControl/AuthenticateCommand.h create mode 100644 retroshare-gui/src/TorControl/CryptoKey.cpp create mode 100644 retroshare-gui/src/TorControl/CryptoKey.h create mode 100644 retroshare-gui/src/TorControl/GetConfCommand.cpp create mode 100644 retroshare-gui/src/TorControl/GetConfCommand.h create mode 100644 retroshare-gui/src/TorControl/HiddenService.cpp create mode 100644 retroshare-gui/src/TorControl/HiddenService.h create mode 100644 retroshare-gui/src/TorControl/PendingOperation.cpp create mode 100644 retroshare-gui/src/TorControl/PendingOperation.h create mode 100644 retroshare-gui/src/TorControl/ProtocolInfoCommand.cpp create mode 100644 retroshare-gui/src/TorControl/ProtocolInfoCommand.h create mode 100644 retroshare-gui/src/TorControl/SecureRNG.cpp create mode 100644 retroshare-gui/src/TorControl/SecureRNG.h create mode 100644 retroshare-gui/src/TorControl/SetConfCommand.cpp create mode 100644 retroshare-gui/src/TorControl/SetConfCommand.h create mode 100644 retroshare-gui/src/TorControl/Settings.cpp create mode 100644 retroshare-gui/src/TorControl/Settings.h create mode 100644 retroshare-gui/src/TorControl/StringUtil.cpp create mode 100644 retroshare-gui/src/TorControl/StringUtil.h create mode 100644 retroshare-gui/src/TorControl/TorControl.cpp create mode 100644 retroshare-gui/src/TorControl/TorControl.h create mode 100644 retroshare-gui/src/TorControl/TorControlCommand.cpp create mode 100644 retroshare-gui/src/TorControl/TorControlCommand.h create mode 100644 retroshare-gui/src/TorControl/TorControlSocket.cpp create mode 100644 retroshare-gui/src/TorControl/TorControlSocket.h create mode 100644 retroshare-gui/src/TorControl/TorManager.cpp create mode 100644 retroshare-gui/src/TorControl/TorManager.h create mode 100644 retroshare-gui/src/TorControl/TorProcess.cpp create mode 100644 retroshare-gui/src/TorControl/TorProcess.h create mode 100644 retroshare-gui/src/TorControl/TorProcess_p.h create mode 100644 retroshare-gui/src/TorControl/TorSocket.cpp create mode 100644 retroshare-gui/src/TorControl/TorSocket.h create mode 100644 retroshare-gui/src/TorControl/Useful.h diff --git a/retroshare-gui/src/TorControl/AddOnionCommand.cpp b/retroshare-gui/src/TorControl/AddOnionCommand.cpp new file mode 100644 index 000000000..88ae232fd --- /dev/null +++ b/retroshare-gui/src/TorControl/AddOnionCommand.cpp @@ -0,0 +1,105 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2016, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "AddOnionCommand.h" +#include "HiddenService.h" +#include "CryptoKey.h" +#include "StringUtil.h" + +using namespace Tor; + +AddOnionCommand::AddOnionCommand(HiddenService *service) + : m_service(service) +{ + Q_ASSERT(m_service); +} + +bool AddOnionCommand::isSuccessful() const +{ + return statusCode() == 250 && m_errorMessage.isEmpty(); +} + +QByteArray AddOnionCommand::build() +{ + QByteArray out("ADD_ONION"); + + if (m_service->privateKey().isLoaded()) { + out += " RSA1024:"; + out += m_service->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64(); + } else { + out += " NEW:RSA1024"; + } + + foreach (const HiddenService::Target &target, m_service->targets()) { + out += " Port="; + out += QByteArray::number(target.servicePort); + out += ","; + out += target.targetAddress.toString().toLatin1(); + out += ":"; + out += QByteArray::number(target.targetPort); + } + + out.append("\r\n"); + return out; +} + +void AddOnionCommand::onReply(int statusCode, const QByteArray &data) +{ + TorControlCommand::onReply(statusCode, data); + if (statusCode != 250) { + m_errorMessage = QString::fromLatin1(data); + return; + } + + const QByteArray keyPrefix("PrivateKey=RSA1024:"); + if (data.startsWith(keyPrefix)) { + QByteArray keyData(QByteArray::fromBase64(data.mid(keyPrefix.size()))); + CryptoKey key; + if (!key.loadFromData(keyData, CryptoKey::PrivateKey, CryptoKey::DER)) { + m_errorMessage = QStringLiteral("Key decoding failed"); + return; + } + + m_service->setPrivateKey(key); + } +} + +void AddOnionCommand::onFinished(int statusCode) +{ + TorControlCommand::onFinished(statusCode); + if (isSuccessful()) + emit succeeded(); + else + emit failed(statusCode); +} + + diff --git a/retroshare-gui/src/TorControl/AddOnionCommand.h b/retroshare-gui/src/TorControl/AddOnionCommand.h new file mode 100644 index 000000000..7c0afaf5e --- /dev/null +++ b/retroshare-gui/src/TorControl/AddOnionCommand.h @@ -0,0 +1,77 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2016, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ADDONIONCOMMAND_H +#define ADDONIONCOMMAND_H + +#include "TorControlCommand.h" +#include +#include +#include + +namespace Tor +{ + +class HiddenService; + +class AddOnionCommand : public TorControlCommand +{ + Q_OBJECT + Q_DISABLE_COPY(AddOnionCommand) + + Q_PROPERTY(QString errorMessage READ errorMessage CONSTANT) + Q_PROPERTY(bool successful READ isSuccessful CONSTANT) + +public: + AddOnionCommand(HiddenService *service); + + QByteArray build(); + + QString errorMessage() const { return m_errorMessage; } + bool isSuccessful() const; + +signals: + void succeeded(); + void failed(int code); + +protected: + HiddenService *m_service; + QString m_errorMessage; + + virtual void onReply(int statusCode, const QByteArray &data); + virtual void onFinished(int statusCode); +}; + +} + +#endif // ADDONIONCOMMAND_H + diff --git a/retroshare-gui/src/TorControl/AuthenticateCommand.cpp b/retroshare-gui/src/TorControl/AuthenticateCommand.cpp new file mode 100644 index 000000000..497c28f89 --- /dev/null +++ b/retroshare-gui/src/TorControl/AuthenticateCommand.cpp @@ -0,0 +1,64 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "AuthenticateCommand.h" + +using namespace Tor; + +AuthenticateCommand::AuthenticateCommand() +{ +} + +QByteArray AuthenticateCommand::build(const QByteArray &data) +{ + if (data.isNull()) + return QByteArray("AUTHENTICATE\r\n"); + + return QByteArray("AUTHENTICATE ") + data.toHex() + "\r\n"; +} + +void AuthenticateCommand::onReply(int statusCode, const QByteArray &data) +{ + TorControlCommand::onReply(statusCode, data); + m_statusMessage = QString::fromLatin1(data); +} + +void AuthenticateCommand::onFinished(int statusCode) +{ + if (statusCode == 515) { + m_statusMessage = QStringLiteral("Authentication failed - incorrect password"); + } else if (statusCode != 250) { + if (m_statusMessage.isEmpty()) + m_statusMessage = QStringLiteral("Authentication failed (error %1").arg(statusCode); + } + TorControlCommand::onFinished(statusCode); +} diff --git a/retroshare-gui/src/TorControl/AuthenticateCommand.h b/retroshare-gui/src/TorControl/AuthenticateCommand.h new file mode 100644 index 000000000..79c901d98 --- /dev/null +++ b/retroshare-gui/src/TorControl/AuthenticateCommand.h @@ -0,0 +1,63 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AUTHENTICATECOMMAND_H +#define AUTHENTICATECOMMAND_H + +#include "TorControlCommand.h" + +namespace Tor +{ + +class AuthenticateCommand : public TorControlCommand +{ + Q_OBJECT + +public: + AuthenticateCommand(); + + QByteArray build(const QByteArray &data = QByteArray()); + + bool isSuccessful() const { return statusCode() == 250; } + QString errorMessage() const { return m_statusMessage; } + +protected: + virtual void onReply(int statusCode, const QByteArray &data); + virtual void onFinished(int statusCode); + +private: + QString m_statusMessage; +}; + +} + +#endif // AUTHENTICATECOMMAND_H diff --git a/retroshare-gui/src/TorControl/CryptoKey.cpp b/retroshare-gui/src/TorControl/CryptoKey.cpp new file mode 100644 index 000000000..8aa035754 --- /dev/null +++ b/retroshare-gui/src/TorControl/CryptoKey.cpp @@ -0,0 +1,477 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CryptoKey.h" +#include "SecureRNG.h" +#include "Useful.h" +#include +#include +#include +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) +{ + *p = r->p; + *q = r->q; +} +#define RSA_bits(o) (BN_num_bits((o)->n)) +#endif + +void base32_encode(char *dest, unsigned destlen, const char *src, unsigned srclen); +bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srclen); + +CryptoKey::CryptoKey() +{ +} + +CryptoKey::~CryptoKey() +{ + clear(); +} + +CryptoKey::Data::~Data() +{ + if (key) + { + RSA_free(key); + key = 0; + } +} + +void CryptoKey::clear() +{ + d = 0; +} + +bool CryptoKey::loadFromData(const QByteArray &data, KeyType type, KeyFormat format) +{ + RSA *key = NULL; + clear(); + + if (data.isEmpty()) + return false; + + if (format == PEM) { + BIO *b = BIO_new_mem_buf((void*)data.constData(), -1); + + if (type == PrivateKey) + key = PEM_read_bio_RSAPrivateKey(b, NULL, NULL, NULL); + else + key = PEM_read_bio_RSAPublicKey(b, NULL, NULL, NULL); + + BIO_free(b); + } else if (format == DER) { + const uchar *dp = reinterpret_cast(data.constData()); + + if (type == PrivateKey) + key = d2i_RSAPrivateKey(NULL, &dp, data.size()); + else + key = d2i_RSAPublicKey(NULL, &dp, data.size()); + } else { + Q_UNREACHABLE(); + } + + if (!key) { + qWarning() << "Failed to parse" << (type == PrivateKey ? "private" : "public") << "key from data"; + return false; + } + + d = new Data(key); + return true; +} + +bool CryptoKey::loadFromFile(const QString &path, KeyType type, KeyFormat format) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Failed to open" << (type == PrivateKey ? "private" : "public") << "key from" + << path << "-" << file.errorString(); + return false; + } + + QByteArray data = file.readAll(); + file.close(); + + return loadFromData(data, type, format); +} + +bool CryptoKey::isPrivate() const +{ + if (!isLoaded()) { + return false; + } else { + const BIGNUM *p, *q; + RSA_get0_factors(d->key, &p, &q); + return (p != 0); + } +} + +int CryptoKey::bits() const +{ + return isLoaded() ? RSA_bits(d->key) : 0; +} + +QByteArray CryptoKey::publicKeyDigest() const +{ + if (!isLoaded()) + return QByteArray(); + + QByteArray buf = encodedPublicKey(DER); + + QByteArray re(20, 0); + bool ok = SHA1(reinterpret_cast(buf.constData()), buf.size(), + reinterpret_cast(re.data())) != NULL; + + if (!ok) + { + qWarning() << "Failed to hash public key data for digest"; + return QByteArray(); + } + + return re; +} + +QByteArray CryptoKey::encodedPublicKey(KeyFormat format) const +{ + if (!isLoaded()) + return QByteArray(); + + if (format == PEM) { + BIO *b = BIO_new(BIO_s_mem()); + + if (!PEM_write_bio_RSAPublicKey(b, d->key)) { + BUG() << "Failed to encode public key in PEM format"; + BIO_free(b); + return QByteArray(); + } + + BUF_MEM *buf; + BIO_get_mem_ptr(b, &buf); + + /* Close BIO, but don't free buf. */ + (void)BIO_set_close(b, BIO_NOCLOSE); + BIO_free(b); + + QByteArray re((const char *)buf->data, (int)buf->length); + BUF_MEM_free(buf); + return re; + } else if (format == DER) { + uchar *buf = NULL; + int len = i2d_RSAPublicKey(d->key, &buf); + if (len <= 0 || !buf) { + BUG() << "Failed to encode public key in DER format"; + return QByteArray(); + } + + QByteArray re((const char*)buf, len); + OPENSSL_free(buf); + return re; + } else { + Q_UNREACHABLE(); + } + + return QByteArray(); +} + +QByteArray CryptoKey::encodedPrivateKey(KeyFormat format) const +{ + if (!isLoaded() || !isPrivate()) + return QByteArray(); + + if (format == PEM) { + BIO *b = BIO_new(BIO_s_mem()); + + if (!PEM_write_bio_RSAPrivateKey(b, d->key, NULL, NULL, 0, NULL, NULL)) { + BUG() << "Failed to encode private key in PEM format"; + BIO_free(b); + return QByteArray(); + } + + BUF_MEM *buf; + BIO_get_mem_ptr(b, &buf); + + /* Close BIO, but don't free buf. */ + (void)BIO_set_close(b, BIO_NOCLOSE); + BIO_free(b); + + QByteArray re((const char *)buf->data, (int)buf->length); + BUF_MEM_free(buf); + return re; + } else if (format == DER) { + uchar *buf = NULL; + int len = i2d_RSAPrivateKey(d->key, &buf); + if (len <= 0 || !buf) { + BUG() << "Failed to encode private key in DER format"; + return QByteArray(); + } + + QByteArray re((const char*)buf, len); + OPENSSL_free(buf); + return re; + } else { + Q_UNREACHABLE(); + } + + return QByteArray(); +} + +QString CryptoKey::torServiceID() const +{ + if (!isLoaded()) + return QString(); + + QByteArray digest = publicKeyDigest(); + if (digest.isNull()) + return QString(); + + static const int hostnameDigestSize = 10; + static const int hostnameEncodedSize = 16; + + QByteArray re(hostnameEncodedSize+1, 0); + base32_encode(re.data(), re.size(), digest.constData(), hostnameDigestSize); + + // Chop extra null byte + re.chop(1); + + return QString::fromLatin1(re); +} + +QByteArray CryptoKey::signData(const QByteArray &data) const +{ + QByteArray digest(32, 0); + bool ok = SHA256(reinterpret_cast(data.constData()), data.size(), + reinterpret_cast(digest.data())) != NULL; + if (!ok) { + qWarning() << "Digest for RSA signature failed"; + return QByteArray(); + } + + return signSHA256(digest); +} + +QByteArray CryptoKey::signSHA256(const QByteArray &digest) const +{ + if (!isPrivate()) + return QByteArray(); + + QByteArray re(RSA_size(d->key), 0); + unsigned sigsize = 0; + int r = RSA_sign(NID_sha256, reinterpret_cast(digest.constData()), digest.size(), + reinterpret_cast(re.data()), &sigsize, d->key); + + if (r != 1) { + qWarning() << "RSA encryption failed when generating signature"; + return QByteArray(); + } + + re.truncate(sigsize); + return re; +} + +bool CryptoKey::verifyData(const QByteArray &data, QByteArray signature) const +{ + QByteArray digest(32, 0); + bool ok = SHA256(reinterpret_cast(data.constData()), data.size(), + reinterpret_cast(digest.data())) != NULL; + + if (!ok) { + qWarning() << "Digest for RSA verify failed"; + return false; + } + + return verifySHA256(digest, signature); +} + +bool CryptoKey::verifySHA256(const QByteArray &digest, QByteArray signature) const +{ + if (!isLoaded()) + return false; + + int r = RSA_verify(NID_sha256, reinterpret_cast(digest.constData()), digest.size(), + reinterpret_cast(signature.data()), signature.size(), d->key); + if (r != 1) + return false; + return true; +} + +/* Cryptographic hash of a password as expected by Tor's HashedControlPassword */ +QByteArray torControlHashedPassword(const QByteArray &password) +{ + QByteArray salt = SecureRNG::random(8); + if (salt.isNull()) + return QByteArray(); + + int count = ((quint32)16 + (96 & 15)) << ((96 >> 4) + 6); + + SHA_CTX hash; + SHA1_Init(&hash); + + QByteArray tmp = salt + password; + while (count) + { + int c = qMin(count, tmp.size()); + SHA1_Update(&hash, reinterpret_cast(tmp.constData()), c); + count -= c; + } + + unsigned char md[20]; + SHA1_Final(md, &hash); + + /* 60 is the hex-encoded value of 96, which is a constant used by Tor's algorithm. */ + return QByteArray("16:") + salt.toHex().toUpper() + QByteArray("60") + + QByteArray::fromRawData(reinterpret_cast(md), 20).toHex().toUpper(); +} + +/* Copyright (c) 2001-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson + * Copyright (c) 2007-2010, The Tor Project, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567" + +/* Implements base32 encoding as in rfc3548. Requires that srclen*8 is a multiple of 5. */ +void base32_encode(char *dest, unsigned destlen, const char *src, unsigned srclen) +{ + unsigned i, bit, v, u; + unsigned nbits = srclen * 8; + + /* We need an even multiple of 5 bits, and enough space */ + if ((nbits%5) != 0 || destlen > (nbits/5)+1) { + Q_ASSERT(false); + memset(dest, 0, destlen); + return; + } + + for (i = 0, bit = 0; bit < nbits; ++i, bit += 5) + { + /* set v to the 16-bit value starting at src[bits/8], 0-padded. */ + v = ((quint8) src[bit / 8]) << 8; + if (bit + 5 < nbits) + v += (quint8) src[(bit/8)+1]; + + /* set u to the 5-bit value at the bit'th bit of src. */ + u = (v >> (11 - (bit % 8))) & 0x1F; + dest[i] = BASE32_CHARS[u]; + } + + dest[i] = '\0'; +} + +/* Implements base32 decoding as in rfc3548. Requires that srclen*5 is a multiple of 8. */ +bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srclen) +{ + unsigned int i, j, bit; + unsigned nbits = srclen * 5; + + /* We need an even multiple of 8 bits, and enough space */ + if ((nbits%8) != 0 || (nbits/8)+1 > destlen) { + Q_ASSERT(false); + return false; + } + + char *tmp = new char[srclen]; + + /* Convert base32 encoded chars to the 5-bit values that they represent. */ + for (j = 0; j < srclen; ++j) + { + if (src[j] > 0x60 && src[j] < 0x7B) + tmp[j] = src[j] - 0x61; + else if (src[j] > 0x31 && src[j] < 0x38) + tmp[j] = src[j] - 0x18; + else if (src[j] > 0x40 && src[j] < 0x5B) + tmp[j] = src[j] - 0x41; + else + { + delete[] tmp; + return false; + } + } + + /* Assemble result byte-wise by applying five possible cases. */ + for (i = 0, bit = 0; bit < nbits; ++i, bit += 8) + { + switch (bit % 40) + { + case 0: + dest[i] = (((quint8)tmp[(bit/5)]) << 3) + (((quint8)tmp[(bit/5)+1]) >> 2); + break; + case 8: + dest[i] = (((quint8)tmp[(bit/5)]) << 6) + (((quint8)tmp[(bit/5)+1]) << 1) + + (((quint8)tmp[(bit/5)+2]) >> 4); + break; + case 16: + dest[i] = (((quint8)tmp[(bit/5)]) << 4) + (((quint8)tmp[(bit/5)+1]) >> 1); + break; + case 24: + dest[i] = (((quint8)tmp[(bit/5)]) << 7) + (((quint8)tmp[(bit/5)+1]) << 2) + + (((quint8)tmp[(bit/5)+2]) >> 3); + break; + case 32: + dest[i] = (((quint8)tmp[(bit/5)]) << 5) + ((quint8)tmp[(bit/5)+1]); + break; + } + } + + delete[] tmp; + return true; +} diff --git a/retroshare-gui/src/TorControl/CryptoKey.h b/retroshare-gui/src/TorControl/CryptoKey.h new file mode 100644 index 000000000..70ada1977 --- /dev/null +++ b/retroshare-gui/src/TorControl/CryptoKey.h @@ -0,0 +1,95 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTOKEY_H +#define CRYPTOKEY_H + +#include +#include +#include + +class CryptoKey +{ +public: + enum KeyType { + PrivateKey, + PublicKey + }; + + enum KeyFormat { + PEM, + DER + }; + + CryptoKey(); + CryptoKey(const CryptoKey &other) : d(other.d) { } + ~CryptoKey(); + + bool loadFromData(const QByteArray &data, KeyType type, KeyFormat format = PEM); + bool loadFromFile(const QString &path, KeyType type, KeyFormat format = PEM); + void clear(); + + bool isLoaded() const { return d.data() && d->key != 0; } + bool isPrivate() const; + + QByteArray publicKeyDigest() const; + QByteArray encodedPublicKey(KeyFormat format) const; + QByteArray encodedPrivateKey(KeyFormat format) const; + QString torServiceID() const; + int bits() const; + + // Calculate and sign SHA-256 digest of data using this key and PKCS #1 v2.0 padding + QByteArray signData(const QByteArray &data) const; + // Verify a signature as per signData + bool verifyData(const QByteArray &data, QByteArray signature) const; + + // Sign the input SHA-256 digest using this key and PKCS #1 v2.0 padding + QByteArray signSHA256(const QByteArray &digest) const; + // Verify a signature as per signSHA256 + bool verifySHA256(const QByteArray &digest, QByteArray signature) const; + +private: + struct Data : public QSharedData + { + typedef struct rsa_st RSA; + RSA *key; + + Data(RSA *k = 0) : key(k) { } + ~Data(); + }; + + QExplicitlySharedDataPointer d; +}; + +QByteArray torControlHashedPassword(const QByteArray &password); + +#endif // CRYPTOKEY_H diff --git a/retroshare-gui/src/TorControl/GetConfCommand.cpp b/retroshare-gui/src/TorControl/GetConfCommand.cpp new file mode 100644 index 000000000..f7574f7fd --- /dev/null +++ b/retroshare-gui/src/TorControl/GetConfCommand.cpp @@ -0,0 +1,124 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "GetConfCommand.h" +#include "StringUtil.h" +#include + +using namespace Tor; + +GetConfCommand::GetConfCommand(Type t) + : type(t) +{ +} + +QByteArray GetConfCommand::build(const QByteArray &key) +{ + return build(QList() << key); +} + +QByteArray GetConfCommand::build(const QList &keys) +{ + QByteArray out; + if (type == GetConf) { + out = "GETCONF"; + } else if (type == GetInfo) { + out = "GETINFO"; + } else { + Q_ASSERT(false); + return out; + } + + foreach (const QByteArray &key, keys) { + out.append(' '); + out.append(key); + } + + out.append("\r\n"); + return out; +} + +void GetConfCommand::onReply(int statusCode, const QByteArray &data) +{ + TorControlCommand::onReply(statusCode, data); + if (statusCode != 250) + return; + + int kep = data.indexOf('='); + QString key = QString::fromLatin1(data.mid(0, kep)); + QVariant value; + if (kep >= 0) + value = QString::fromLatin1(unquotedString(data.mid(kep + 1))); + + m_lastKey = key; + QVariantMap::iterator it = m_results.find(key); + if (it != m_results.end()) { + // Make a list of values + QVariantList results = it->toList(); + if (results.isEmpty()) + results.append(*it); + results.append(value); + *it = QVariant(results); + } else { + m_results.insert(key, value); + } +} + +void GetConfCommand::onDataLine(const QByteArray &data) +{ + if (m_lastKey.isEmpty()) { + qWarning() << "torctrl: Unexpected data line in GetConf command"; + return; + } + + QVariantMap::iterator it = m_results.find(m_lastKey); + if (it != m_results.end()) { + QVariantList results = it->toList(); + if (results.isEmpty() && !it->toByteArray().isEmpty()) + results.append(*it); + results.append(data); + *it = QVariant(results); + } else { + m_results.insert(m_lastKey, QVariantList() << data); + } +} + +void GetConfCommand::onDataFinished() +{ + m_lastKey.clear(); +} + +QVariant GetConfCommand::get(const QByteArray &key) const +{ + return m_results.value(QString::fromLatin1(key)); +} + diff --git a/retroshare-gui/src/TorControl/GetConfCommand.h b/retroshare-gui/src/TorControl/GetConfCommand.h new file mode 100644 index 000000000..0de97d1b7 --- /dev/null +++ b/retroshare-gui/src/TorControl/GetConfCommand.h @@ -0,0 +1,77 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GETCONFCOMMAND_H +#define GETCONFCOMMAND_H + +#include "TorControlCommand.h" +#include +#include + +namespace Tor +{ + +class GetConfCommand : public TorControlCommand +{ + Q_OBJECT + Q_DISABLE_COPY(GetConfCommand) + + Q_PROPERTY(QVariantMap results READ results CONSTANT) + +public: + enum Type { + GetConf, + GetInfo + }; + const Type type; + + GetConfCommand(Type type); + + QByteArray build(const QByteArray &key); + QByteArray build(const QList &keys); + + const QVariantMap &results() const { return m_results; } + QVariant get(const QByteArray &key) const; + +protected: + virtual void onReply(int statusCode, const QByteArray &data); + virtual void onDataLine(const QByteArray &data); + virtual void onDataFinished(); + +private: + QVariantMap m_results; + QString m_lastKey; +}; + +} + +#endif // GETCONFCOMMAND_H diff --git a/retroshare-gui/src/TorControl/HiddenService.cpp b/retroshare-gui/src/TorControl/HiddenService.cpp new file mode 100644 index 000000000..e22576b5d --- /dev/null +++ b/retroshare-gui/src/TorControl/HiddenService.cpp @@ -0,0 +1,137 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "HiddenService.h" +#include "TorControl.h" +#include "TorSocket.h" +#include "CryptoKey.h" +#include "Useful.h" +#include +#include +#include +#include + +using namespace Tor; + +HiddenService::HiddenService(QObject *parent) + : QObject(parent), m_status(NotCreated) +{ +} + +HiddenService::HiddenService(const QString &path, QObject *parent) + : QObject(parent), m_dataPath(path), m_status(NotCreated) +{ + /* Set the initial status and, if possible, load the hostname */ + if (QDir(m_dataPath).exists(QLatin1String("private_key"))) { + loadPrivateKey(); + if (!m_hostname.isEmpty()) + m_status = Offline; + } +} + +HiddenService::HiddenService(const CryptoKey &privateKey, const QString &path, QObject *parent) + : QObject(parent), m_dataPath(path), m_status(NotCreated) +{ + setPrivateKey(privateKey); + m_status = Offline; +} + +void HiddenService::setStatus(Status newStatus) +{ + if (m_status == newStatus) + return; + + Status old = m_status; + m_status = newStatus; + + emit statusChanged(m_status, old); + + if (m_status == Online) + emit serviceOnline(); +} + +void HiddenService::addTarget(const Target &target) +{ + m_targets.append(target); +} + +void HiddenService::addTarget(quint16 servicePort, QHostAddress targetAddress, quint16 targetPort) +{ + Target t = { targetAddress, servicePort, targetPort }; + m_targets.append(t); +} + +void HiddenService::setPrivateKey(const CryptoKey &key) +{ + if (m_privateKey.isLoaded()) { + BUG() << "Cannot change the private key on an existing HiddenService"; + return; + } + + if (!key.isPrivate()) { + BUG() << "Cannot create a hidden service with a public key"; + return; + } + + m_privateKey = key; + m_hostname = m_privateKey.torServiceID() + QStringLiteral(".onion"); + emit privateKeyChanged(); +} + +void HiddenService::loadPrivateKey() +{ + if (m_privateKey.isLoaded() || m_dataPath.isEmpty()) + return; + + bool ok = m_privateKey.loadFromFile(m_dataPath + QLatin1String("/private_key"), CryptoKey::PrivateKey); + if (!ok) { + qWarning() << "Failed to load hidden service key"; + return; + } + + m_hostname = m_privateKey.torServiceID(); + emit privateKeyChanged(); +} + +void HiddenService::servicePublished() +{ + loadPrivateKey(); + + if (m_hostname.isEmpty()) { + qDebug() << "Failed to read hidden service hostname"; + return; + } + + qDebug() << "Hidden service published successfully"; + setStatus(Online); +} + diff --git a/retroshare-gui/src/TorControl/HiddenService.h b/retroshare-gui/src/TorControl/HiddenService.h new file mode 100644 index 000000000..71c20a972 --- /dev/null +++ b/retroshare-gui/src/TorControl/HiddenService.h @@ -0,0 +1,104 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HIDDENSERVICE_H +#define HIDDENSERVICE_H + +#include +#include +#include +#include "CryptoKey.h" + +namespace Tor +{ + +class TorSocket; + +class HiddenService : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(HiddenService) + + friend class TorControlPrivate; + +public: + struct Target + { + QHostAddress targetAddress; + quint16 servicePort, targetPort; + }; + + enum Status + { + NotCreated = -1, /* Service has not been created yet */ + Offline = 0, /* Data exists, but service is not published */ + Online /* Published */ + }; + + HiddenService(QObject *parent = 0); + HiddenService(const QString &dataPath, QObject *parent = 0); + HiddenService(const CryptoKey &privateKey, const QString &dataPath = QString(), QObject *parent = 0); + + Status status() const { return m_status; } + + const QString &hostname() const { return m_hostname; } + const QString &dataPath() const { return m_dataPath; } + + CryptoKey privateKey() { return m_privateKey; } + void setPrivateKey(const CryptoKey &privateKey); + + const QList &targets() const { return m_targets; } + void addTarget(const Target &target); + void addTarget(quint16 servicePort, QHostAddress targetAddress, quint16 targetPort); + +signals: + void statusChanged(int newStatus, int oldStatus); + void serviceOnline(); + void privateKeyChanged(); + +private slots: + void servicePublished(); + +private: + QString m_dataPath; + QList m_targets; + QString m_hostname; + Status m_status; + CryptoKey m_privateKey; + + void loadPrivateKey(); + void setStatus(Status newStatus); +}; + +} + +#endif // HIDDENSERVICE_H diff --git a/retroshare-gui/src/TorControl/PendingOperation.cpp b/retroshare-gui/src/TorControl/PendingOperation.cpp new file mode 100644 index 000000000..4a11f3e75 --- /dev/null +++ b/retroshare-gui/src/TorControl/PendingOperation.cpp @@ -0,0 +1,84 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "PendingOperation.h" + +PendingOperation::PendingOperation(QObject *parent) + : QObject(parent), m_finished(false) +{ +} + +bool PendingOperation::isFinished() const +{ + return m_finished; +} + +bool PendingOperation::isSuccess() const +{ + return m_finished && m_errorMessage.isNull(); +} + +bool PendingOperation::isError() const +{ + return m_finished && !m_errorMessage.isNull(); +} + +QString PendingOperation::errorMessage() const +{ + return m_errorMessage; +} + +void PendingOperation::finishWithError(const QString &message) +{ + if (message.isEmpty()) + m_errorMessage = QStringLiteral("Unknown Error"); + m_errorMessage = message; + + if (!m_finished) { + m_finished = true; + emit finished(); + emit error(m_errorMessage); + } +} + +void PendingOperation::finishWithSuccess() +{ + Q_ASSERT(m_errorMessage.isNull()); + + if (!m_finished) { + m_finished = true; + emit finished(); + if (isSuccess()) + emit success(); + } +} + diff --git a/retroshare-gui/src/TorControl/PendingOperation.h b/retroshare-gui/src/TorControl/PendingOperation.h new file mode 100644 index 000000000..8f776a8d7 --- /dev/null +++ b/retroshare-gui/src/TorControl/PendingOperation.h @@ -0,0 +1,87 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PENDINGOPERATION_H +#define PENDINGOPERATION_H + +#include + +/* Represents an asynchronous operation for reporting status + * + * This class is used for asynchronous operations that report a + * status and errors when finished, particularly for exposing them + * to QML. + * + * Subclass PendingOperation to implement your operation's logic. + * You also need to handle the object's lifetime, for example by + * calling deleteLater() when finished() is emitted. + * + * PendingOperation will emit finished() and one of success() or + * error() when completed. + */ +class PendingOperation : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool isFinished READ isFinished NOTIFY finished FINAL) + Q_PROPERTY(bool isSuccess READ isSuccess NOTIFY success FINAL) + Q_PROPERTY(bool isError READ isError NOTIFY error FINAL) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY finished FINAL) + +public: + PendingOperation(QObject *parent = 0); + + bool isFinished() const; + bool isSuccess() const; + bool isError() const; + QString errorMessage() const; + +signals: + // Always emitted once when finished, regardless of status + void finished(); + + // One of error() or success() is emitted once + void error(const QString &errorMessage); + void success(); + +protected slots: + void finishWithError(const QString &errorMessage); + void finishWithSuccess(); + +private: + bool m_finished; + QString m_errorMessage; +}; + +Q_DECLARE_METATYPE(PendingOperation*) + +#endif diff --git a/retroshare-gui/src/TorControl/ProtocolInfoCommand.cpp b/retroshare-gui/src/TorControl/ProtocolInfoCommand.cpp new file mode 100644 index 000000000..ad8c9e3e6 --- /dev/null +++ b/retroshare-gui/src/TorControl/ProtocolInfoCommand.cpp @@ -0,0 +1,85 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ProtocolInfoCommand.h" +#include "TorControl.h" +#include "StringUtil.h" +#include + +using namespace Tor; + +ProtocolInfoCommand::ProtocolInfoCommand(TorControl *m) + : manager(m) +{ +} + +QByteArray ProtocolInfoCommand::build() +{ + return QByteArray("PROTOCOLINFO 1\r\n"); +} + +void ProtocolInfoCommand::onReply(int statusCode, const QByteArray &data) +{ + TorControlCommand::onReply(statusCode, data); + if (statusCode != 250) + return; + + if (data.startsWith("AUTH ")) + { + QList tokens = splitQuotedStrings(data.mid(5), ' '); + + foreach (QByteArray token, tokens) + { + if (token.startsWith("METHODS=")) + { + QList textMethods = unquotedString(token.mid(8)).split(','); + for (QList::Iterator it = textMethods.begin(); it != textMethods.end(); ++it) + { + if (*it == "NULL") + m_authMethods |= AuthNull; + else if (*it == "HASHEDPASSWORD") + m_authMethods |= AuthHashedPassword; + else if (*it == "COOKIE") + m_authMethods |= AuthCookie; + } + } + else if (token.startsWith("COOKIEFILE=")) + { + m_cookieFile = QString::fromLatin1(unquotedString(token.mid(11))); + } + } + } + else if (data.startsWith("VERSION Tor=")) + { + m_torVersion = QString::fromLatin1(unquotedString(data.mid(12, data.indexOf(' ', 12)))); + } +} diff --git a/retroshare-gui/src/TorControl/ProtocolInfoCommand.h b/retroshare-gui/src/TorControl/ProtocolInfoCommand.h new file mode 100644 index 000000000..7789cfefd --- /dev/null +++ b/retroshare-gui/src/TorControl/ProtocolInfoCommand.h @@ -0,0 +1,78 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PROTOCOLINFOCOMMAND_H +#define PROTOCOLINFOCOMMAND_H + +#include "TorControlCommand.h" +#include + +namespace Tor +{ + +class TorControl; + +class ProtocolInfoCommand : public TorControlCommand +{ + Q_OBJECT + Q_DISABLE_COPY(ProtocolInfoCommand) + +public: + enum AuthMethod + { + AuthUnknown = 0, + AuthNull = 0x1, + AuthHashedPassword = 0x2, + AuthCookie = 0x4 + }; + Q_DECLARE_FLAGS(AuthMethods, AuthMethod) + + ProtocolInfoCommand(TorControl *manager); + QByteArray build(); + + AuthMethods authMethods() const { return m_authMethods; } + QString torVersion() const { return m_torVersion; } + QString cookieFile() const { return m_cookieFile; } + +protected: + virtual void onReply(int statusCode, const QByteArray &data); + +private: + TorControl *manager; + AuthMethods m_authMethods; + QString m_torVersion; + QString m_cookieFile; +}; + +} + +#endif // PROTOCOLINFOCOMMAND_H diff --git a/retroshare-gui/src/TorControl/SecureRNG.cpp b/retroshare-gui/src/TorControl/SecureRNG.cpp new file mode 100644 index 000000000..3a6eacd83 --- /dev/null +++ b/retroshare-gui/src/TorControl/SecureRNG.cpp @@ -0,0 +1,146 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "SecureRNG.h" +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +#if QT_VERSION >= 0x040700 +#include +#endif + +bool SecureRNG::seed() +{ +#if QT_VERSION >= 0x040700 + QElapsedTimer timer; + timer.start(); +#endif + +#ifdef Q_OS_WIN + /* RAND_poll is very unreliable on windows; with older versions of OpenSSL, + * it can take up to several minutes to run and has been known to crash. + * Even newer versions seem to take around 400ms, which is far too long for + * interactive startup. Random data from the windows CSP is used as a seed + * instead, as it should be very high quality random and fast. */ + HCRYPTPROV provider = 0; + if (!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + qWarning() << "Failed to acquire CSP context for RNG seed:" << hex << GetLastError(); + return false; + } + + /* Same amount of entropy OpenSSL uses, apparently. */ + char buf[32]; + + if (!CryptGenRandom(provider, sizeof(buf), reinterpret_cast(buf))) + { + qWarning() << "Failed to get entropy from CSP for RNG seed: " << hex << GetLastError(); + CryptReleaseContext(provider, 0); + return false; + } + + CryptReleaseContext(provider, 0); + + RAND_seed(buf, sizeof(buf)); + memset(buf, 0, sizeof(buf)); +#else + if (!RAND_poll()) + { + qWarning() << "OpenSSL RNG seed failed:" << ERR_get_error(); + return false; + } +#endif + +#if QT_VERSION >= 0x040700 + qDebug() << "RNG seed took" << timer.elapsed() << "ms"; +#endif + + return true; +} + +void SecureRNG::random(char *buf, int size) +{ + int r = RAND_bytes(reinterpret_cast(buf), size); + if (r <= 0) + qFatal("RNG failed: %lu", ERR_get_error()); +} + +QByteArray SecureRNG::random(int size) +{ + QByteArray re(size, 0); + random(re.data(), size); + return re; +} + +QByteArray SecureRNG::randomPrintable(int length) +{ + QByteArray re(length, 0); + for (int i = 0; i < re.size(); i++) + re[i] = randomInt(95) + 32; + return re; +} + +unsigned SecureRNG::randomInt(unsigned max) +{ + unsigned cutoff = UINT_MAX - (UINT_MAX % max); + unsigned value = 0; + + for (;;) + { + random(reinterpret_cast(&value), sizeof(value)); + if (value < cutoff) + return value % max; + } +} + +#ifndef UINT64_MAX +#define UINT64_MAX ((quint64)-1) +#endif + +quint64 SecureRNG::randomInt64(quint64 max) +{ + quint64 cutoff = UINT64_MAX - (UINT64_MAX % max); + quint64 value = 0; + + for (;;) + { + random(reinterpret_cast(value), sizeof(value)); + if (value < cutoff) + return value % max; + } +} diff --git a/retroshare-gui/src/TorControl/SecureRNG.h b/retroshare-gui/src/TorControl/SecureRNG.h new file mode 100644 index 000000000..f3b2a4b64 --- /dev/null +++ b/retroshare-gui/src/TorControl/SecureRNG.h @@ -0,0 +1,51 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SECURERNG_H +#define SECURERNG_H + +#include + +class SecureRNG +{ +public: + static bool seed(); + + static void random(char *buf, int size); + static QByteArray random(int size); + + static QByteArray randomPrintable(int length); + static unsigned randomInt(unsigned max); + static quint64 randomInt64(quint64 max); +}; + +#endif // SECURERNG_H diff --git a/retroshare-gui/src/TorControl/SetConfCommand.cpp b/retroshare-gui/src/TorControl/SetConfCommand.cpp new file mode 100644 index 000000000..fa4fd5b27 --- /dev/null +++ b/retroshare-gui/src/TorControl/SetConfCommand.cpp @@ -0,0 +1,106 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "SetConfCommand.h" +#include "StringUtil.h" + +using namespace Tor; + +SetConfCommand::SetConfCommand() + : m_resetMode(false) +{ +} + +void SetConfCommand::setResetMode(bool enabled) +{ + m_resetMode = enabled; +} + +bool SetConfCommand::isSuccessful() const +{ + return statusCode() == 250; +} + +QByteArray SetConfCommand::build(const QByteArray &key, const QByteArray &value) +{ + return build(QList >() << qMakePair(key, value)); +} + +QByteArray SetConfCommand::build(const QVariantMap &data) +{ + QList > out; + + for (QVariantMap::ConstIterator it = data.begin(); it != data.end(); it++) { + QByteArray key = it.key().toLatin1(); + + if (static_cast(it.value().type()) == QMetaType::QVariantList) { + QVariantList values = it.value().value(); + foreach (const QVariant &value, values) + out.append(qMakePair(key, value.toString().toLatin1())); + } else { + out.append(qMakePair(key, it.value().toString().toLatin1())); + } + } + + return build(out); +} + +QByteArray SetConfCommand::build(const QList > &data) +{ + QByteArray out(m_resetMode ? "RESETCONF" : "SETCONF"); + + for (int i = 0; i < data.size(); i++) { + out += " " + data[i].first; + if (!data[i].second.isEmpty()) + out += "=" + quotedString(data[i].second); + } + + out.append("\r\n"); + return out; +} + +void SetConfCommand::onReply(int statusCode, const QByteArray &data) +{ + TorControlCommand::onReply(statusCode, data); + if (statusCode != 250) + m_errorMessage = QString::fromLatin1(data); +} + +void SetConfCommand::onFinished(int statusCode) +{ + TorControlCommand::onFinished(statusCode); + if (isSuccessful()) + emit setConfSucceeded(); + else + emit setConfFailed(statusCode); +} + diff --git a/retroshare-gui/src/TorControl/SetConfCommand.h b/retroshare-gui/src/TorControl/SetConfCommand.h new file mode 100644 index 000000000..5bdcb9329 --- /dev/null +++ b/retroshare-gui/src/TorControl/SetConfCommand.h @@ -0,0 +1,78 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SETCONFCOMMAND_H +#define SETCONFCOMMAND_H + +#include "TorControlCommand.h" +#include +#include +#include + +namespace Tor +{ + +class SetConfCommand : public TorControlCommand +{ + Q_OBJECT + Q_DISABLE_COPY(SetConfCommand) + + Q_PROPERTY(QString errorMessage READ errorMessage CONSTANT) + Q_PROPERTY(bool successful READ isSuccessful CONSTANT) + +public: + SetConfCommand(); + + void setResetMode(bool resetMode); + + QByteArray build(const QByteArray &key, const QByteArray &value); + QByteArray build(const QVariantMap &data); + QByteArray build(const QList > &data); + + QString errorMessage() const { return m_errorMessage; } + bool isSuccessful() const; + +signals: + void setConfSucceeded(); + void setConfFailed(int code); + +protected: + QString m_errorMessage; + bool m_resetMode; + + virtual void onReply(int statusCode, const QByteArray &data); + virtual void onFinished(int statusCode); +}; + +} + +#endif // SETCONFCOMMAND_H diff --git a/retroshare-gui/src/TorControl/Settings.cpp b/retroshare-gui/src/TorControl/Settings.cpp new file mode 100644 index 000000000..b20d330b7 --- /dev/null +++ b/retroshare-gui/src/TorControl/Settings.cpp @@ -0,0 +1,553 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SettingsFilePrivate : public QObject +{ + Q_OBJECT + +public: + SettingsFile *q; + QString filePath; + QString errorMessage; + QTimer syncTimer; + QJsonObject jsonRoot; + SettingsObject *rootObject; + + SettingsFilePrivate(SettingsFile *qp); + virtual ~SettingsFilePrivate(); + + void reset(); + void setError(const QString &message); + bool checkDirPermissions(const QString &path); + bool readFile(); + bool writeFile(); + + static QStringList splitPath(const QString &input, bool &ok); + QJsonValue read(const QJsonObject &base, const QStringList &path); + bool write(const QStringList &path, const QJsonValue &value); + +signals: + void modified(const QStringList &path, const QJsonValue &value); + +private slots: + void sync(); +}; + +SettingsFile::SettingsFile(QObject *parent) + : QObject(parent), d(new SettingsFilePrivate(this)) +{ + d->rootObject = new SettingsObject(this, QString()); +} + +SettingsFile::~SettingsFile() +{ +} + +SettingsFilePrivate::SettingsFilePrivate(SettingsFile *qp) + : QObject(qp) + , q(qp) + , rootObject(0) +{ + syncTimer.setInterval(0); + syncTimer.setSingleShot(true); + connect(&syncTimer, &QTimer::timeout, this, &SettingsFilePrivate::sync); +} + +SettingsFilePrivate::~SettingsFilePrivate() +{ + if (syncTimer.isActive()) + sync(); + delete rootObject; +} + +void SettingsFilePrivate::reset() +{ + filePath.clear(); + errorMessage.clear(); + + jsonRoot = QJsonObject(); + emit modified(QStringList(), jsonRoot); +} + +QString SettingsFile::filePath() const +{ + return d->filePath; +} + +bool SettingsFile::setFilePath(const QString &filePath) +{ + if (d->filePath == filePath) + return hasError(); + + d->reset(); + d->filePath = filePath; + + QFileInfo fileInfo(filePath); + QDir dir(fileInfo.path()); + if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { + d->setError(QStringLiteral("Cannot create directory: %1").arg(dir.path())); + return false; + } + d->checkDirPermissions(fileInfo.path()); + + if (!d->readFile()) + return false; + + return true; +} + +QString SettingsFile::errorMessage() const +{ + return d->errorMessage; +} + +bool SettingsFile::hasError() const +{ + return !d->errorMessage.isEmpty(); +} + +void SettingsFilePrivate::setError(const QString &message) +{ + errorMessage = message; + emit q->error(); +} + +bool SettingsFilePrivate::checkDirPermissions(const QString &path) +{ + static QFile::Permissions desired = QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ExeUser; + static QFile::Permissions ignored = QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner; + + QFile file(path); + if ((file.permissions() & ~ignored) != desired) { + qDebug() << "Correcting permissions on configuration directory"; + if (!file.setPermissions(desired)) { + qWarning() << "Correcting permissions on configuration directory failed"; + return false; + } + } + + return true; +} + +SettingsObject *SettingsFile::root() +{ + return d->rootObject; +} + +const SettingsObject *SettingsFile::root() const +{ + return d->rootObject; +} + +void SettingsFilePrivate::sync() +{ + if (filePath.isEmpty()) + return; + + syncTimer.stop(); + writeFile(); +} + +bool SettingsFilePrivate::readFile() +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadWrite)) { + setError(file.errorString()); + return false; + } + + QByteArray data = file.readAll(); + if (data.isEmpty() && (file.error() != QFileDevice::NoError || file.size() > 0)) { + setError(file.errorString()); + return false; + } + + if (data.isEmpty()) { + jsonRoot = QJsonObject(); + return true; + } + + QJsonParseError parseError; + QJsonDocument document = QJsonDocument::fromJson(data, &parseError); + if (document.isNull()) { + setError(parseError.errorString()); + return false; + } + + if (!document.isObject()) { + setError(QStringLiteral("Invalid configuration file (expected object)")); + return false; + } + + jsonRoot = document.object(); + + emit modified(QStringList(), jsonRoot); + return true; +} + +bool SettingsFilePrivate::writeFile() +{ + QSaveFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + setError(file.errorString()); + return false; + } + + QJsonDocument document(jsonRoot); + QByteArray data = document.toJson(); + if (data.isEmpty() && !document.isEmpty()) { + setError(QStringLiteral("Encoding failure")); + return false; + } + + if (file.write(data) < data.size() || !file.commit()) { + setError(file.errorString()); + return false; + } + + return true; +} + +QStringList SettingsFilePrivate::splitPath(const QString &input, bool &ok) +{ + QStringList components = input.split(QLatin1Char('.')); + + // Allow a leading '.' to simplify concatenation + if (!components.isEmpty() && components.first().isEmpty()) + components.takeFirst(); + + // No other empty components, including a trailing . + foreach (const QString &word, components) { + if (word.isEmpty()) { + ok = false; + return QStringList(); + } + } + + ok = true; + return components; +} + +QJsonValue SettingsFilePrivate::read(const QJsonObject &base, const QStringList &path) +{ + QJsonValue current = base; + + foreach (const QString &key, path) { + QJsonObject object = current.toObject(); + if (object.isEmpty() || (current = object.value(key)).isUndefined()) + return QJsonValue::Undefined; + } + + return current; +} + +// Compare two QJsonValue to find keys that have changed, +// recursing into objects and building paths as necessary. +typedef QList > ModifiedList; +static void findModifiedRecursive(ModifiedList &modified, const QStringList &path, const QJsonValue &oldValue, const QJsonValue &newValue) +{ + if (oldValue.isObject() || newValue.isObject()) { + // If either is a non-object type, this returns an empty object + QJsonObject oldObject = oldValue.toObject(); + QJsonObject newObject = newValue.toObject(); + + // Iterate keys of the original object and compare to new + for (QJsonObject::iterator it = oldObject.begin(); it != oldObject.end(); it++) { + QJsonValue newSubValue = newObject.value(it.key()); + if (*it == newSubValue) + continue; + + if ((*it).isObject() || newSubValue.isObject()) + findModifiedRecursive(modified, QStringList() << path << it.key(), *it, newSubValue); + else + modified.append(qMakePair(QStringList() << path << it.key(), newSubValue)); + } + + // Iterate keys of the new object that may not be in original + for (QJsonObject::iterator it = newObject.begin(); it != newObject.end(); it++) { + if (oldObject.contains(it.key())) + continue; + + if ((*it).isObject()) + findModifiedRecursive(modified, QStringList() << path << it.key(), QJsonValue::Undefined, it.value()); + else + modified.append(qMakePair(QStringList() << path << it.key(), it.value())); + } + } else + modified.append(qMakePair(path, newValue)); +} + +bool SettingsFilePrivate::write(const QStringList &path, const QJsonValue &value) +{ + typedef QVarLengthArray > ObjectStack; + ObjectStack stack; + QJsonValue current = jsonRoot; + QJsonValue originalValue; + QString currentKey; + + foreach (const QString &key, path) { + const QJsonObject &parent = current.toObject(); + stack.append(qMakePair(currentKey, parent)); + current = parent.value(key); + currentKey = key; + } + + // Stack now contains parent objects starting with the root, and current + // is the old value. Write back changes in reverse. + if (current == value) + return false; + originalValue = current; + current = value; + + ObjectStack::const_iterator it = stack.end(), begin = stack.begin(); + while (it != begin) { + --it; + QJsonObject update = it->second; + update.insert(currentKey, current); + current = update; + currentKey = it->first; + } + + // current is now the updated jsonRoot + jsonRoot = current.toObject(); + syncTimer.start(); + + ModifiedList modified; + findModifiedRecursive(modified, path, originalValue, value); + + for (ModifiedList::iterator it = modified.begin(); it != modified.end(); it++) + emit this->modified(it->first, it->second); + + return true; +} + +class SettingsObjectPrivate : public QObject +{ + Q_OBJECT + +public: + explicit SettingsObjectPrivate(SettingsObject *q); + + SettingsObject *q; + SettingsFile *file; + QStringList path; + QJsonObject object; + bool invalid; + + void setFile(SettingsFile *file); + +public slots: + void modified(const QStringList &absolutePath, const QJsonValue &value); +}; + +SettingsObject::SettingsObject(QObject *parent) + : QObject(parent) + , d(new SettingsObjectPrivate(this)) +{ + d->setFile(defaultFile()); + if (d->file) + setPath(QString()); +} + +SettingsObject::SettingsObject(const QString &path, QObject *parent) + : QObject(parent) + , d(new SettingsObjectPrivate(this)) +{ + d->setFile(defaultFile()); + setPath(path); +} + +SettingsObject::SettingsObject(SettingsFile *file, const QString &path, QObject *parent) + : QObject(parent) + , d(new SettingsObjectPrivate(this)) +{ + d->setFile(file); + setPath(path); +} + +SettingsObject::SettingsObject(SettingsObject *base, const QString &path, QObject *parent) + : QObject(parent) + , d(new SettingsObjectPrivate(this)) +{ + d->setFile(base->d->file); + setPath(base->path() + QLatin1Char('.') + path); +} + +SettingsObjectPrivate::SettingsObjectPrivate(SettingsObject *qp) + : QObject(qp) + , q(qp) + , file(0) + , invalid(true) +{ +} + +void SettingsObjectPrivate::setFile(SettingsFile *value) +{ + if (file == value) + return; + + if (file) + disconnect(file, 0, this, 0); + file = value; + if (file) + connect(file->d, &SettingsFilePrivate::modified, this, &SettingsObjectPrivate::modified); +} + +// Emit SettingsObject::modified with a relative path if path is matched +void SettingsObjectPrivate::modified(const QStringList &key, const QJsonValue &value) +{ + if (key.size() < path.size()) + return; + + for (int i = 0; i < path.size(); i++) { + if (path[i] != key[i]) + return; + } + + object = file->d->read(file->d->jsonRoot, path).toObject(); + emit q->modified(QStringList(key.mid(path.size())).join(QLatin1Char('.')), value); + emit q->dataChanged(); +} + +static QPointer defaultObjectFile; + +SettingsFile *SettingsObject::defaultFile() +{ + return defaultObjectFile; +} + +void SettingsObject::setDefaultFile(SettingsFile *file) +{ + defaultObjectFile = file; +} + +QString SettingsObject::path() const +{ + return d->path.join(QLatin1Char('.')); +} + +void SettingsObject::setPath(const QString &input) +{ + bool ok = false; + QStringList newPath = SettingsFilePrivate::splitPath(input, ok); + if (!ok) { + d->invalid = true; + d->path.clear(); + d->object = QJsonObject(); + + emit pathChanged(); + emit dataChanged(); + return; + } + + if (!d->invalid && d->path == newPath) + return; + + d->path = newPath; + if (d->file) { + d->invalid = false; + d->object = d->file->d->read(d->file->d->jsonRoot, d->path).toObject(); + emit dataChanged(); + } + + emit pathChanged(); +} + +QJsonObject SettingsObject::data() const +{ + return d->object; +} + +void SettingsObject::setData(const QJsonObject &input) +{ + if (d->invalid || d->object == input) + return; + + d->object = input; + d->file->d->write(d->path, d->object); +} + +QJsonValue SettingsObject::read(const QString &key, const QJsonValue &defaultValue) const +{ + bool ok = false; + QStringList splitKey = SettingsFilePrivate::splitPath(key, ok); + if (d->invalid || !ok || splitKey.isEmpty()) { + qDebug() << "Invalid settings read of path" << key; + return defaultValue; + } + + QJsonValue ret = d->file->d->read(d->object, splitKey); + if (ret.isUndefined()) + ret = defaultValue; + return ret; +} + +void SettingsObject::write(const QString &key, const QJsonValue &value) +{ + bool ok = false; + QStringList splitKey = SettingsFilePrivate::splitPath(key, ok); + if (d->invalid || !ok || splitKey.isEmpty()) { + qDebug() << "Invalid settings write of path" << key; + return; + } + + splitKey = d->path + splitKey; + d->file->d->write(splitKey, value); +} + +void SettingsObject::unset(const QString &key) +{ + write(key, QJsonValue()); +} + +void SettingsObject::undefine() +{ + if (d->invalid) + return; + + d->object = QJsonObject(); + d->file->d->write(d->path, QJsonValue::Undefined); +} + +#include "Settings.moc" diff --git a/retroshare-gui/src/TorControl/Settings.h b/retroshare-gui/src/TorControl/Settings.h new file mode 100644 index 000000000..79ad032d1 --- /dev/null +++ b/retroshare-gui/src/TorControl/Settings.h @@ -0,0 +1,257 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include +#include +#include +#include + +class SettingsObject; +class SettingsFilePrivate; +class SettingsObjectPrivate; + +/* SettingsFile represents a JSON-encoded configuration file. + * + * SettingsFile is an API for reading, writing, and change notification + * on JSON-encoded settings files. + * + * Data is accessed via SettingsObject, either using the root property + * or by creating a SettingsObject, optionally using a base path. + */ +class SettingsFile : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(SettingsFile) + + Q_PROPERTY(SettingsObject *root READ root CONSTANT) + Q_PROPERTY(QString filePath READ filePath WRITE setFilePath NOTIFY filePathChanged) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY error) + Q_PROPERTY(bool hasError READ hasError NOTIFY error) + +public: + explicit SettingsFile(QObject *parent = 0); + virtual ~SettingsFile(); + + QString filePath() const; + bool setFilePath(const QString &filePath); + + QString errorMessage() const; + bool hasError() const; + + SettingsObject *root(); + const SettingsObject *root() const; + +signals: + void filePathChanged(); + void error(); + +private: + SettingsFilePrivate *d; + + friend class SettingsObject; + friend class SettingsObjectPrivate; +}; + +/* SettingsObject reads and writes data within a SettingsFile + * + * A SettingsObject is associated with a SettingsFile and represents an object + * tree within that file. It refers to the JSON object tree using a path + * notation with keys separated by '.'. For example: + * + * { + * "one": { + * "two": { + * "three": "value" + * } + * } + * } + * + * With this data, a SettingsObject with an empty path can read with the path + * "one.two.three", and a SettingsObject with a path of "one.two" can simply + * read or write on "three". + * + * Multiple SettingsObjects may be created for the same path, and will be kept + * synchronized with changes. The modified signal is emitted for all changes + * affecting keys within a path, including writes of object trees and from other + * instances. + */ +class SettingsObject : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(SettingsObject) + + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QJsonObject data READ data WRITE setData NOTIFY dataChanged) + +public: + explicit SettingsObject(QObject *parent = 0); + explicit SettingsObject(const QString &path, QObject *parent = 0); + explicit SettingsObject(SettingsFile *file, const QString &path, QObject *parent = 0); + explicit SettingsObject(SettingsObject *base, const QString &path, QObject *parent = 0); + + /* Specify a SettingsFile to use by default on SettingsObject instances. + * + * After calling setDefaultFile, a SettingsObject created without any file, e.g.: + * + * SettingsObject settings; + * SettingsObject animals(QStringLiteral("animals")); + * + * Will use the specified SettingsFile instance by default. This is a convenience + * over passing around instances of SettingsFile in application use cases, and is + * particularly useful for QML. + */ + static SettingsFile *defaultFile(); + static void setDefaultFile(SettingsFile *file); + + QString path() const; + void setPath(const QString &path); + + QJsonObject data() const; + void setData(const QJsonObject &data); + + Q_INVOKABLE QJsonValue read(const QString &key, const QJsonValue &defaultValue = QJsonValue::Undefined) const; + template T read(const QString &key) const; + Q_INVOKABLE void write(const QString &key, const QJsonValue &value); + template void write(const QString &key, const T &value); + Q_INVOKABLE void unset(const QString &key); + + // const char* key overloads + QJsonValue read(const char *key, const QJsonValue &defaultValue = QJsonValue::Undefined) const + { + return read(QString::fromLatin1(key), defaultValue); + } + template T read(const char *key) const + { + return read(QString::fromLatin1(key)); + } + void write(const char *key, const QJsonValue &value) + { + write(QString::fromLatin1(key), value); + } + template void write(const char *key, const T &value) + { + write(QString::fromLatin1(key), value); + } + void unset(const char *key) + { + unset(QString::fromLatin1(key)); + } + + Q_INVOKABLE void undefine(); + +signals: + void pathChanged(); + void dataChanged(); + + void modified(const QString &path, const QJsonValue &value); + +private: + SettingsObjectPrivate *d; +}; + +template inline void SettingsObject::write(const QString &key, const T &value) +{ + write(key, QJsonValue(value)); +} + +template<> inline QString SettingsObject::read(const QString &key) const +{ + return read(key).toString(); +} + +template<> inline QJsonArray SettingsObject::read(const QString &key) const +{ + return read(key).toArray(); +} + +template<> inline QJsonObject SettingsObject::read(const QString &key) const +{ + return read(key).toObject(); +} + +template<> inline double SettingsObject::read(const QString &key) const +{ + return read(key).toDouble(); +} + +template<> inline int SettingsObject::read(const QString &key) const +{ + return read(key).toInt(); +} + +template<> inline bool SettingsObject::read(const QString &key) const +{ + return read(key).toBool(); +} + +template<> inline QDateTime SettingsObject::read(const QString &key) const +{ + QString value = read(key).toString(); + if (value.isEmpty()) + return QDateTime(); + return QDateTime::fromString(value, Qt::ISODate).toLocalTime(); +} + +template<> inline void SettingsObject::write(const QString &key, const QDateTime &value) +{ + write(key, QJsonValue(value.toUTC().toString(Qt::ISODate))); +} + +// Explicitly store value encoded as base64. Decodes and casts implicitly to QByteArray for reads. +class Base64Encode +{ +public: + explicit Base64Encode(const QByteArray &value) : d(value) { } + operator QByteArray() { return d; } + QByteArray encoded() const { return d.toBase64(); } + +private: + QByteArray d; +}; + +template<> inline Base64Encode SettingsObject::read(const QString &key) const +{ + return Base64Encode(QByteArray::fromBase64(read(key).toString().toLatin1())); +} + +template<> inline void SettingsObject::write(const QString &key, const Base64Encode &value) +{ + write(key, QJsonValue(QString::fromLatin1(value.encoded()))); +} + +#endif + diff --git a/retroshare-gui/src/TorControl/StringUtil.cpp b/retroshare-gui/src/TorControl/StringUtil.cpp new file mode 100644 index 000000000..8215d1351 --- /dev/null +++ b/retroshare-gui/src/TorControl/StringUtil.cpp @@ -0,0 +1,118 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "StringUtil.h" + +QByteArray quotedString(const QByteArray &string) +{ + QByteArray out; + out.reserve(string.size() * 2); + + out.append('"'); + + for (int i = 0; i < string.size(); ++i) + { + switch (string[i]) + { + case '"': + out.append("\\\""); + break; + case '\\': + out.append("\\\\"); + break; + default: + out.append(string[i]); + break; + } + } + + out.append('"'); + return out; +} + +QByteArray unquotedString(const QByteArray &string) +{ + if (string.size() < 2 || string[0] != '"') + return string; + + QByteArray out; + out.reserve(string.size() - 2); + + for (int i = 1; i < string.size(); ++i) + { + switch (string[i]) + { + case '\\': + if (++i < string.size()) + out.append(string[i]); + break; + case '"': + return out; + default: + out.append(string[i]); + } + } + + return out; +} + +QList splitQuotedStrings(const QByteArray &input, char separator) +{ + QList out; + bool inquote = false; + int start = 0; + + for (int i = 0; i < input.size(); ++i) + { + switch (input[i]) + { + case '"': + inquote = !inquote; + break; + case '\\': + if (inquote) + ++i; + break; + } + + if (!inquote && input[i] == separator) + { + out.append(input.mid(start, i - start)); + start = i+1; + } + } + + if (start < input.size()) + out.append(input.mid(start)); + + return out; +} diff --git a/retroshare-gui/src/TorControl/StringUtil.h b/retroshare-gui/src/TorControl/StringUtil.h new file mode 100644 index 000000000..c86d2c6ec --- /dev/null +++ b/retroshare-gui/src/TorControl/StringUtil.h @@ -0,0 +1,46 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STRINGUTIL_H +#define STRINGUTIL_H + +#include +#include + +QByteArray quotedString(const QByteArray &string); + +/* Return the unquoted contents of a string, either until an end quote or an unescaped separator character. */ +QByteArray unquotedString(const QByteArray &string); + +QList splitQuotedStrings(const QByteArray &input, char separator); + +#endif // STRINGUTIL_H diff --git a/retroshare-gui/src/TorControl/TorControl.cpp b/retroshare-gui/src/TorControl/TorControl.cpp new file mode 100644 index 000000000..8c5a1adee --- /dev/null +++ b/retroshare-gui/src/TorControl/TorControl.cpp @@ -0,0 +1,763 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TorControl.h" +#include "TorControlSocket.h" +#include "HiddenService.h" +#include "ProtocolInfoCommand.h" +#include "AuthenticateCommand.h" +#include "SetConfCommand.h" +#include "GetConfCommand.h" +#include "AddOnionCommand.h" +#include "StringUtil.h" +#include "Settings.h" +#include "PendingOperation.h" +#include +#include +#include +//#include +#include +#include +#include +#include + +Tor::TorControl *torControl = 0; + +using namespace Tor; + +namespace Tor { + +class TorControlPrivate : public QObject +{ + Q_OBJECT + +public: + TorControl *q; + + TorControlSocket *socket; + QHostAddress torAddress; + QString errorMessage; + QString torVersion; + QByteArray authPassword; + QHostAddress socksAddress; + QList services; + quint16 controlPort, socksPort; + TorControl::Status status; + TorControl::TorStatus torStatus; + QVariantMap bootstrapStatus; + bool hasOwnership; + + TorControlPrivate(TorControl *parent); + + void setStatus(TorControl::Status status); + void setTorStatus(TorControl::TorStatus status); + + void getTorInfo(); + void publishServices(); + +public slots: + void socketConnected(); + void socketDisconnected(); + void socketError(); + + void authenticateReply(); + void protocolInfoReply(); + void getTorInfoReply(); + void setError(const QString &message); + + void statusEvent(int code, const QByteArray &data); + void updateBootstrap(const QList &data); +}; + +} + +TorControl::TorControl(QObject *parent) + : QObject(parent), d(new TorControlPrivate(this)) +{ +} + +TorControlPrivate::TorControlPrivate(TorControl *parent) + : QObject(parent), q(parent), controlPort(0), socksPort(0), + status(TorControl::NotConnected), torStatus(TorControl::TorUnknown), + hasOwnership(false) +{ + socket = new TorControlSocket(this); + QObject::connect(socket, SIGNAL(connected()), this, SLOT(socketConnected())); + QObject::connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError())); + QObject::connect(socket, SIGNAL(error(QString)), this, SLOT(setError(QString))); +} + +QNetworkProxy TorControl::connectionProxy() +{ + return QNetworkProxy(QNetworkProxy::Socks5Proxy, d->socksAddress.toString(), d->socksPort); +} + +void TorControlPrivate::setStatus(TorControl::Status n) +{ + if (n == status) + return; + + TorControl::Status old = status; + status = n; + + if (old == TorControl::Error) + errorMessage.clear(); + + emit q->statusChanged(status, old); + + if (status == TorControl::Connected && old < TorControl::Connected) + emit q->connected(); + else if (status < TorControl::Connected && old >= TorControl::Connected) + emit q->disconnected(); +} + +void TorControlPrivate::setTorStatus(TorControl::TorStatus n) +{ + if (n == torStatus) + return; + + TorControl::TorStatus old = torStatus; + torStatus = n; + emit q->torStatusChanged(torStatus, old); + emit q->connectivityChanged(); + + if (torStatus == TorControl::TorReady && socksAddress.isNull()) { + // Request info again to read the SOCKS port + getTorInfo(); + } +} + +void TorControlPrivate::setError(const QString &message) +{ + errorMessage = message; + setStatus(TorControl::Error); + + qWarning() << "torctrl: Error:" << errorMessage; + + socket->abort(); + + QTimer::singleShot(15000, q, SLOT(reconnect())); +} + +TorControl::Status TorControl::status() const +{ + return d->status; +} + +TorControl::TorStatus TorControl::torStatus() const +{ + return d->torStatus; +} + +QString TorControl::torVersion() const +{ + return d->torVersion; +} + +QString TorControl::errorMessage() const +{ + return d->errorMessage; +} + +bool TorControl::hasConnectivity() const +{ + return torStatus() == TorReady && !d->socksAddress.isNull(); +} + +QHostAddress TorControl::socksAddress() const +{ + return d->socksAddress; +} + +quint16 TorControl::socksPort() const +{ + return d->socksPort; +} + +QList TorControl::hiddenServices() const +{ + return d->services; +} + +QVariantMap TorControl::bootstrapStatus() const +{ + return d->bootstrapStatus; +} + +void TorControl::setAuthPassword(const QByteArray &password) +{ + d->authPassword = password; +} + +void TorControl::connect(const QHostAddress &address, quint16 port) +{ + if (status() > Connecting) + { + qDebug() << "Ignoring TorControl::connect due to existing connection"; + return; + } + + d->torAddress = address; + d->controlPort = port; + d->setTorStatus(TorUnknown); + + bool b = d->socket->blockSignals(true); + d->socket->abort(); + d->socket->blockSignals(b); + + d->setStatus(Connecting); + d->socket->connectToHost(address, port); +} + +void TorControl::reconnect() +{ + Q_ASSERT(!d->torAddress.isNull() && d->controlPort); + if (d->torAddress.isNull() || !d->controlPort || status() >= Connecting) + return; + + d->setStatus(Connecting); + d->socket->connectToHost(d->torAddress, d->controlPort); +} + +void TorControlPrivate::authenticateReply() +{ + AuthenticateCommand *command = qobject_cast(sender()); + Q_ASSERT(command); + Q_ASSERT(status == TorControl::Authenticating); + if (!command) + return; + + if (!command->isSuccessful()) { + setError(command->errorMessage()); + return; + } + + qDebug() << "torctrl: Authentication successful"; + setStatus(TorControl::Connected); + + setTorStatus(TorControl::TorUnknown); + + TorControlCommand *clientEvents = new TorControlCommand; + connect(clientEvents, &TorControlCommand::replyLine, this, &TorControlPrivate::statusEvent); + socket->registerEvent("STATUS_CLIENT", clientEvents); + + getTorInfo(); + publishServices(); + + // XXX Fix old configurations that would store unwanted options in torrc. + // This can be removed some suitable amount of time after 1.0.4. + if (hasOwnership) + q->saveConfiguration(); +} + +void TorControlPrivate::socketConnected() +{ + Q_ASSERT(status == TorControl::Connecting); + + qDebug() << "torctrl: Connected socket; querying information"; + setStatus(TorControl::Authenticating); + + ProtocolInfoCommand *command = new ProtocolInfoCommand(q); + connect(command, &TorControlCommand::finished, this, &TorControlPrivate::protocolInfoReply); + socket->sendCommand(command, command->build()); +} + +void TorControlPrivate::socketDisconnected() +{ + /* Clear some internal state */ + torVersion.clear(); + socksAddress.clear(); + socksPort = 0; + setTorStatus(TorControl::TorUnknown); + + /* This emits the disconnected() signal as well */ + setStatus(TorControl::NotConnected); +} + +void TorControlPrivate::socketError() +{ + setError(QStringLiteral("Connection failed: %1").arg(socket->errorString())); +} + +void TorControlPrivate::protocolInfoReply() +{ + ProtocolInfoCommand *info = qobject_cast(sender()); + if (!info) + return; + + torVersion = info->torVersion(); + + if (status == TorControl::Authenticating) + { + AuthenticateCommand *auth = new AuthenticateCommand; + connect(auth, &TorControlCommand::finished, this, &TorControlPrivate::authenticateReply); + + QByteArray data; + ProtocolInfoCommand::AuthMethods methods = info->authMethods(); + + if (methods.testFlag(ProtocolInfoCommand::AuthNull)) + { + qDebug() << "torctrl: Using null authentication"; + data = auth->build(); + } + else if (methods.testFlag(ProtocolInfoCommand::AuthCookie) && !info->cookieFile().isEmpty()) + { + QString cookieFile = info->cookieFile(); + QString cookieError; + qDebug() << "torctrl: Using cookie authentication with file" << cookieFile; + + QFile file(cookieFile); + if (file.open(QIODevice::ReadOnly)) + { + QByteArray cookie = file.readAll(); + file.close(); + + /* Simple test to avoid a vulnerability where any process listening on what we think is + * the control port could trick us into sending the contents of an arbitrary file */ + if (cookie.size() == 32) + data = auth->build(cookie); + else + cookieError = QStringLiteral("Unexpected file size"); + } + else + cookieError = file.errorString(); + + if (!cookieError.isNull() || data.isNull()) + { + /* If we know a password and password authentication is allowed, try using that instead. + * This is a strange corner case that will likely never happen in a normal configuration, + * but it has happened. */ + if (methods.testFlag(ProtocolInfoCommand::AuthHashedPassword) && !authPassword.isEmpty()) + { + qDebug() << "torctrl: Unable to read authentication cookie file:" << cookieError; + goto usePasswordAuth; + } + + setError(QStringLiteral("Unable to read authentication cookie file: %1").arg(cookieError)); + delete auth; + return; + } + } + else if (methods.testFlag(ProtocolInfoCommand::AuthHashedPassword) && !authPassword.isEmpty()) + { + usePasswordAuth: + qDebug() << "torctrl: Using hashed password authentication"; + data = auth->build(authPassword); + } + else + { + if (methods.testFlag(ProtocolInfoCommand::AuthHashedPassword)) + setError(QStringLiteral("Tor requires a control password to connect, but no password is configured.")); + else + setError(QStringLiteral("Tor is not configured to accept any supported authentication methods.")); + delete auth; + return; + } + + socket->sendCommand(auth, data); + } +} + +void TorControlPrivate::getTorInfo() +{ + Q_ASSERT(q->isConnected()); + + GetConfCommand *command = new GetConfCommand(GetConfCommand::GetInfo); + connect(command, &TorControlCommand::finished, this, &TorControlPrivate::getTorInfoReply); + + QList keys; + keys << QByteArray("status/circuit-established") << QByteArray("status/bootstrap-phase"); + + /* If these are set in the config, they override the automatic behavior. */ + SettingsObject settings(QStringLiteral("tor")); + QHostAddress forceAddress(settings.read("socksAddress").toString()); + quint16 port = (quint16)settings.read("socksPort").toInt(); + + if (!forceAddress.isNull() && port) { + qDebug() << "torctrl: Using manually specified SOCKS connection settings"; + socksAddress = forceAddress; + socksPort = port; + emit q->connectivityChanged(); + } else + keys << QByteArray("net/listeners/socks"); + + socket->sendCommand(command, command->build(keys)); +} + +void TorControlPrivate::getTorInfoReply() +{ + GetConfCommand *command = qobject_cast(sender()); + if (!command || !q->isConnected()) + return; + + QList listenAddresses = splitQuotedStrings(command->get(QByteArray("net/listeners/socks")).toString().toLatin1(), ' '); + for (QList::Iterator it = listenAddresses.begin(); it != listenAddresses.end(); ++it) { + QByteArray value = unquotedString(*it); + int sepp = value.indexOf(':'); + QHostAddress address(QString::fromLatin1(value.mid(0, sepp))); + quint16 port = (quint16)value.mid(sepp+1).toUInt(); + + /* Use the first address that matches the one used for this control connection. If none do, + * just use the first address and rely on the user to reconfigure if necessary (not a problem; + * their setup is already very customized) */ + if (socksAddress.isNull() || address == socket->peerAddress()) { + socksAddress = address; + socksPort = port; + if (address == socket->peerAddress()) + break; + } + } + + /* It is not immediately an error to have no SOCKS address; when DisableNetwork is set there won't be a + * listener yet. To handle that situation, we'll try to read the socks address again when TorReady state + * is reached. */ + if (!socksAddress.isNull()) { + qDebug().nospace() << "torctrl: SOCKS address is " << socksAddress.toString() << ":" << socksPort; + emit q->connectivityChanged(); + } + + if (command->get(QByteArray("status/circuit-established")).toInt() == 1) { + qDebug() << "torctrl: Tor indicates that circuits have been established; state is TorReady"; + setTorStatus(TorControl::TorReady); + } else { + setTorStatus(TorControl::TorOffline); + } + + QByteArray bootstrap = command->get(QByteArray("status/bootstrap-phase")).toString().toLatin1(); + if (!bootstrap.isEmpty()) + updateBootstrap(splitQuotedStrings(bootstrap, ' ')); +} + +void TorControl::addHiddenService(HiddenService *service) +{ + if (d->services.contains(service)) + return; + + d->services.append(service); +} + +void TorControlPrivate::publishServices() +{ + Q_ASSERT(q->isConnected()); + if (services.isEmpty()) + return; + + SettingsObject settings(QStringLiteral("tor")); + if (settings.read("neverPublishServices").toBool()) + { + qDebug() << "torctrl: Skipping service publication because neverPublishService is enabled"; + + /* Call servicePublished under the assumption that they're published externally. */ + for (QList::Iterator it = services.begin(); it != services.end(); ++it) + (*it)->servicePublished(); + + return; + } + + if (q->torVersionAsNewAs(QStringLiteral("0.2.7"))) { + foreach (HiddenService *service, services) { + if (service->hostname().isEmpty()) + qDebug() << "torctrl: Creating a new hidden service"; + else + qDebug() << "torctrl: Publishing hidden service" << service->hostname(); + AddOnionCommand *onionCommand = new AddOnionCommand(service); + QObject::connect(onionCommand, &AddOnionCommand::succeeded, service, &HiddenService::servicePublished); + socket->sendCommand(onionCommand, onionCommand->build()); + } + } else { + qDebug() << "torctrl: Using legacy SETCONF hidden service configuration for tor" << torVersion; + SetConfCommand *command = new SetConfCommand; + QList > torConfig; + + foreach (HiddenService *service, services) + { + if (service->dataPath().isEmpty()) + continue; + + if (service->privateKey().isLoaded() && !QFile::exists(service->dataPath() + QStringLiteral("/private_key"))) { + // This case can happen if tor is downgraded after the profile is created + qWarning() << "Cannot publish ephemeral hidden services with this version of tor; skipping"; + continue; + } + + qDebug() << "torctrl: Configuring hidden service at" << service->dataPath(); + + QDir dir(service->dataPath()); + torConfig.append(qMakePair(QByteArray("HiddenServiceDir"), dir.absolutePath().toLocal8Bit())); + + const QList &targets = service->targets(); + for (QList::ConstIterator tit = targets.begin(); tit != targets.end(); ++tit) + { + QString target = QString::fromLatin1("%1 %2:%3").arg(tit->servicePort) + .arg(tit->targetAddress.toString()) + .arg(tit->targetPort); + torConfig.append(qMakePair(QByteArray("HiddenServicePort"), target.toLatin1())); + } + + QObject::connect(command, &SetConfCommand::setConfSucceeded, service, &HiddenService::servicePublished); + } + + if (!torConfig.isEmpty()) + socket->sendCommand(command, command->build(torConfig)); + } +} + +void TorControl::shutdown() +{ + if (!hasOwnership()) { + qWarning() << "torctrl: Ignoring shutdown command for a tor instance I don't own"; + return; + } + + d->socket->sendCommand("SIGNAL SHUTDOWN\r\n"); +} + +void TorControl::shutdownSync() +{ + if (!hasOwnership()) { + qWarning() << "torctrl: Ignoring shutdown command for a tor instance I don't own"; + return; + } + + shutdown(); + while (d->socket->bytesToWrite()) + { + if (!d->socket->waitForBytesWritten(5000)) + return; + } +} + +void TorControlPrivate::statusEvent(int code, const QByteArray &data) +{ + Q_UNUSED(code); + + QList tokens = splitQuotedStrings(data.trimmed(), ' '); + if (tokens.size() < 3) + return; + + qDebug() << "torctrl: status event:" << data.trimmed(); + + if (tokens[2] == "CIRCUIT_ESTABLISHED") { + setTorStatus(TorControl::TorReady); + } else if (tokens[2] == "CIRCUIT_NOT_ESTABLISHED") { + setTorStatus(TorControl::TorOffline); + } else if (tokens[2] == "BOOTSTRAP") { + tokens.takeFirst(); + updateBootstrap(tokens); + } +} + +void TorControlPrivate::updateBootstrap(const QList &data) +{ + bootstrapStatus.clear(); + // WARN or NOTICE + bootstrapStatus[QStringLiteral("severity")] = data.value(0); + for (int i = 1; i < data.size(); i++) { + int equals = data[i].indexOf('='); + QString key = QString::fromLatin1(data[i].mid(0, equals)); + QString value; + if (equals >= 0) + value = QString::fromLatin1(unquotedString(data[i].mid(equals + 1))); + bootstrapStatus[key.toLower()] = value; + } + + qDebug() << bootstrapStatus; + emit q->bootstrapStatusChanged(); +} + +QObject *TorControl::getConfiguration(const QString &options) +{ + GetConfCommand *command = new GetConfCommand(GetConfCommand::GetConf); + d->socket->sendCommand(command, command->build(options.toLatin1())); + + //QQmlEngine::setObjectOwnership(command, QQmlEngine::CppOwnership); + return command; +} + +QObject *TorControl::setConfiguration(const QVariantMap &options) +{ + SetConfCommand *command = new SetConfCommand; + command->setResetMode(true); + d->socket->sendCommand(command, command->build(options)); + + //QQmlEngine::setObjectOwnership(command, QQmlEngine::CppOwnership); + return command; +} + +namespace Tor { + +class SaveConfigOperation : public PendingOperation +{ + Q_OBJECT + +public: + SaveConfigOperation(QObject *parent) + : PendingOperation(parent), command(0) + { + } + + void start(TorControlSocket *socket) + { + Q_ASSERT(!command); + command = new GetConfCommand(GetConfCommand::GetInfo); + QObject::connect(command, &TorControlCommand::finished, this, &SaveConfigOperation::configTextReply); + socket->sendCommand(command, command->build(QList() << "config-text" << "config-file")); + } + +private slots: + void configTextReply() + { + Q_ASSERT(command); + if (!command) + return; + + QString path = QFile::decodeName(command->get("config-file").toByteArray()); + if (path.isEmpty()) { + finishWithError(QStringLiteral("Cannot write torrc without knowing its path")); + return; + } + + // Out of paranoia, refuse to write any file not named 'torrc', or if the + // file doesn't exist + QFileInfo fileInfo(path); + if (fileInfo.fileName() != QStringLiteral("torrc") || !fileInfo.exists()) { + finishWithError(QStringLiteral("Refusing to write torrc to unacceptable path %1").arg(path)); + return; + } + + QSaveFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + finishWithError(QStringLiteral("Failed opening torrc file for writing: %1").arg(file.errorString())); + return; + } + + // Remove these keys when writing torrc; they are set at runtime and contain + // absolute paths or port numbers + static const char *bannedKeys[] = { + "ControlPortWriteToFile", + "DataDirectory", + "HiddenServiceDir", + "HiddenServicePort", + 0 + }; + + QVariantList configText = command->get("config-text").toList(); + foreach (const QVariant &value, configText) { + QByteArray line = value.toByteArray(); + + bool skip = false; + for (const char **key = bannedKeys; *key; key++) { + if (line.startsWith(*key)) { + skip = true; + break; + } + } + if (skip) + continue; + + file.write(line); + file.write("\n"); + } + + if (!file.commit()) { + finishWithError(QStringLiteral("Failed writing torrc: %1").arg(file.errorString())); + return; + } + + qDebug() << "torctrl: Wrote torrc file"; + finishWithSuccess(); + } + +private: + GetConfCommand *command; +}; + +} + +PendingOperation *TorControl::saveConfiguration() +{ + if (!hasOwnership()) { + qWarning() << "torctrl: Ignoring save configuration command for a tor instance I don't own"; + return 0; + } + + SaveConfigOperation *operation = new SaveConfigOperation(this); + QObject::connect(operation, &PendingOperation::finished, operation, &QObject::deleteLater); + operation->start(d->socket); + + //QQmlEngine::setObjectOwnership(operation, QQmlEngine::CppOwnership); + return operation; +} + +bool TorControl::hasOwnership() const +{ + return d->hasOwnership; +} + +void TorControl::takeOwnership() +{ + d->hasOwnership = true; + d->socket->sendCommand("TAKEOWNERSHIP\r\n"); + + // Reset PID-based polling + QVariantMap options; + options[QStringLiteral("__OwningControllerProcess")] = QVariant(); + setConfiguration(options); +} + +bool TorControl::torVersionAsNewAs(const QString &match) const +{ + QRegularExpression r(QStringLiteral("[.-]")); + QStringList split = torVersion().split(r); + QStringList matchSplit = match.split(r); + + for (int i = 0; i < matchSplit.size(); i++) { + if (i >= split.size()) + return false; + bool ok1 = false, ok2 = false; + int currentVal = split[i].toInt(&ok1); + int matchVal = matchSplit[i].toInt(&ok2); + if (!ok1 || !ok2) + return false; + if (currentVal > matchVal) + return true; + if (currentVal < matchVal) + return false; + } + + // Versions are equal, up to the length of match + return true; +} + +#include "TorControl.moc" + diff --git a/retroshare-gui/src/TorControl/TorControl.h b/retroshare-gui/src/TorControl/TorControl.h new file mode 100644 index 000000000..93be0099c --- /dev/null +++ b/retroshare-gui/src/TorControl/TorControl.h @@ -0,0 +1,141 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TORCONTROL_H +#define TORCONTROL_H + +#include +#include +#include "PendingOperation.h" + +class QNetworkProxy; + +namespace Tor +{ + +class HiddenService; +class TorControlPrivate; + +class TorControl : public QObject +{ + Q_OBJECT + Q_ENUMS(Status TorStatus) + + // Status of the control connection + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + // Status of Tor (and whether it believes it can connect) + Q_PROPERTY(TorStatus torStatus READ torStatus NOTIFY torStatusChanged) + // Whether it's possible to make a SOCKS connection and connect + Q_PROPERTY(bool hasConnectivity READ hasConnectivity NOTIFY connectivityChanged) + Q_PROPERTY(QString torVersion READ torVersion NOTIFY connected) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY statusChanged) + Q_PROPERTY(QVariantMap bootstrapStatus READ bootstrapStatus NOTIFY bootstrapStatusChanged) + Q_PROPERTY(bool hasOwnership READ hasOwnership NOTIFY hasOwnershipChanged) + +public: + enum Status + { + Error = -1, + NotConnected, + Connecting, + Authenticating, + Connected + }; + + enum TorStatus + { + TorUnknown, + TorOffline, + TorReady + }; + + explicit TorControl(QObject *parent = 0); + + /* Information */ + Status status() const; + TorStatus torStatus() const; + QString torVersion() const; + bool torVersionAsNewAs(const QString &version) const; + QString errorMessage() const; + + bool hasConnectivity() const; + QHostAddress socksAddress() const; + quint16 socksPort() const; + QNetworkProxy connectionProxy(); + + /* Authentication */ + void setAuthPassword(const QByteArray &password); + + /* Connection */ + bool isConnected() const { return status() == Connected; } + void connect(const QHostAddress &address, quint16 port); + + /* Ownership means that tor is managed by this socket, and we + * can shut it down, own its configuration, etc. */ + bool hasOwnership() const; + void takeOwnership(); + + /* Hidden Services */ + QList hiddenServices() const; + void addHiddenService(HiddenService *service); + + QVariantMap bootstrapStatus() const; + Q_INVOKABLE QObject *getConfiguration(const QString &options); + Q_INVOKABLE QObject *setConfiguration(const QVariantMap &options); + Q_INVOKABLE PendingOperation *saveConfiguration(); + +signals: + void statusChanged(int newStatus, int oldStatus); + void torStatusChanged(int newStatus, int oldStatus); + void connected(); + void disconnected(); + void connectivityChanged(); + void bootstrapStatusChanged(); + void hasOwnershipChanged(); + +public slots: + /* Instruct Tor to shutdown */ + void shutdown(); + /* Call shutdown(), and wait synchronously for the command to be written */ + void shutdownSync(); + + void reconnect(); + +private: + TorControlPrivate *d; +}; + +} + +extern Tor::TorControl *torControl; + +#endif // TORCONTROLMANAGER_H diff --git a/retroshare-gui/src/TorControl/TorControlCommand.cpp b/retroshare-gui/src/TorControl/TorControlCommand.cpp new file mode 100644 index 000000000..48d4aab8b --- /dev/null +++ b/retroshare-gui/src/TorControl/TorControlCommand.cpp @@ -0,0 +1,63 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TorControlCommand.h" +#include + +using namespace Tor; + +TorControlCommand::TorControlCommand() + : m_finalStatus(0) +{ +} + +void TorControlCommand::onReply(int statusCode, const QByteArray &data) +{ + emit replyLine(statusCode, data); +} + +void TorControlCommand::onFinished(int statusCode) +{ + m_finalStatus = statusCode; + emit finished(); +} + +void TorControlCommand::onDataLine(const QByteArray &data) +{ + Q_UNUSED(data); +} + +void TorControlCommand::onDataFinished() +{ + qWarning() << "torctrl: Unexpected data response for command"; +} + diff --git a/retroshare-gui/src/TorControl/TorControlCommand.h b/retroshare-gui/src/TorControl/TorControlCommand.h new file mode 100644 index 000000000..894381054 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorControlCommand.h @@ -0,0 +1,70 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TORCONTROLCOMMAND_H +#define TORCONTROLCOMMAND_H + +#include +#include + +namespace Tor +{ + +class TorControlCommand : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(TorControlCommand) + + friend class TorControlSocket; + +public: + TorControlCommand(); + + int statusCode() const { return m_finalStatus; } + +signals: + void replyLine(int statusCode, const QByteArray &data); + void finished(); + +protected: + virtual void onReply(int statusCode, const QByteArray &data); + virtual void onFinished(int statusCode); + virtual void onDataLine(const QByteArray &data); + virtual void onDataFinished(); + +private: + int m_finalStatus; +}; + +} + +#endif // TORCONTROLCOMMAND_H diff --git a/retroshare-gui/src/TorControl/TorControlSocket.cpp b/retroshare-gui/src/TorControl/TorControlSocket.cpp new file mode 100644 index 000000000..33b411c54 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorControlSocket.cpp @@ -0,0 +1,176 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TorControlSocket.h" +#include "TorControlCommand.h" +#include + +using namespace Tor; + +TorControlSocket::TorControlSocket(QObject *parent) + : QTcpSocket(parent), currentCommand(0), inDataReply(false) +{ + connect(this, SIGNAL(readyRead()), this, SLOT(process())); + connect(this, SIGNAL(disconnected()), this, SLOT(clear())); +} + +TorControlSocket::~TorControlSocket() +{ + clear(); +} + +void TorControlSocket::sendCommand(TorControlCommand *command, const QByteArray &data) +{ + Q_ASSERT(data.endsWith("\r\n")); + + commandQueue.append(command); + write(data); + + qDebug() << "torctrl: Sent" << data.trimmed(); +} + +void TorControlSocket::registerEvent(const QByteArray &event, TorControlCommand *command) +{ + eventCommands.insert(event, command); + + QByteArray data("SETEVENTS"); + foreach (const QByteArray &key, eventCommands.keys()) { + data += ' '; + data += key; + } + data += "\r\n"; + + sendCommand(data); +} + +void TorControlSocket::clear() +{ + qDeleteAll(commandQueue); + commandQueue.clear(); + qDeleteAll(eventCommands); + eventCommands.clear(); + inDataReply = false; + currentCommand = 0; +} + +void TorControlSocket::setError(const QString &message) +{ + m_errorMessage = message; + emit error(message); + abort(); +} + +void TorControlSocket::process() +{ + for (;;) { + if (!canReadLine()) + return; + + QByteArray line = readLine(5120); + if (!line.endsWith("\r\n")) { + setError(QStringLiteral("Invalid control message syntax")); + return; + } + line.chop(2); + + if (inDataReply) { + if (line == ".") { + inDataReply = false; + if (currentCommand) + currentCommand->onDataFinished(); + currentCommand = 0; + } else { + if (currentCommand) + currentCommand->onDataLine(line); + } + continue; + } + + if (line.size() < 4) { + setError(QStringLiteral("Invalid control message syntax")); + return; + } + + int statusCode = line.left(3).toInt(); + char type = line[3]; + bool isFinalReply = (type == ' '); + inDataReply = (type == '+'); + + // Trim down to just data + line = line.mid(4); + + if (!isFinalReply && !inDataReply && type != '-') { + setError(QStringLiteral("Invalid control message syntax")); + return; + } + + // 6xx replies are asynchronous responses + if (statusCode >= 600 && statusCode < 700) { + if (!currentCommand) { + int space = line.indexOf(' '); + if (space > 0) + currentCommand = eventCommands.value(line.mid(0, space)); + + if (!currentCommand) { + qWarning() << "torctrl: Ignoring unknown event"; + continue; + } + } + + currentCommand->onReply(statusCode, line); + if (isFinalReply) { + currentCommand->onFinished(statusCode); + currentCommand = 0; + } + continue; + } + + if (commandQueue.isEmpty()) { + qWarning() << "torctrl: Received unexpected data"; + continue; + } + + TorControlCommand *command = commandQueue.first(); + if (command) + command->onReply(statusCode, line); + + if (inDataReply) { + currentCommand = command; + } else if (isFinalReply) { + commandQueue.takeFirst(); + if (command) { + command->onFinished(statusCode); + command->deleteLater(); + } + } + } +} diff --git a/retroshare-gui/src/TorControl/TorControlSocket.h b/retroshare-gui/src/TorControl/TorControlSocket.h new file mode 100644 index 000000000..2db911503 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorControlSocket.h @@ -0,0 +1,77 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TORCONTROLSOCKET_H +#define TORCONTROLSOCKET_H + +#include +#include + +namespace Tor +{ + +class TorControlCommand; + +class TorControlSocket : public QTcpSocket +{ +Q_OBJECT +public: + explicit TorControlSocket(QObject *parent = 0); + virtual ~TorControlSocket(); + + QString errorMessage() const { return m_errorMessage; } + + void registerEvent(const QByteArray &event, TorControlCommand *handler); + + void sendCommand(const QByteArray &data) { sendCommand(0, data); } + void sendCommand(TorControlCommand *command, const QByteArray &data); + +signals: + void error(const QString &message); + +private slots: + void process(); + void clear(); + +private: + QQueue commandQueue; + QHash eventCommands; + QString m_errorMessage; + TorControlCommand *currentCommand; + bool inDataReply; + + void setError(const QString &message); +}; + +} + +#endif // TORCONTROLSOCKET_H diff --git a/retroshare-gui/src/TorControl/TorManager.cpp b/retroshare-gui/src/TorControl/TorManager.cpp new file mode 100644 index 000000000..3b0c2d77f --- /dev/null +++ b/retroshare-gui/src/TorControl/TorManager.cpp @@ -0,0 +1,335 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "TorManager.h" +#include "TorProcess.h" +#include "TorControl.h" +#include "GetConfCommand.h" +#include "Settings.h" +#include +#include +#include + +using namespace Tor; + +namespace Tor +{ + +class TorManagerPrivate : public QObject +{ + Q_OBJECT + +public: + TorManager *q; + TorProcess *process; + TorControl *control; + QString dataDir; + QStringList logMessages; + QString errorMessage; + bool configNeeded; + + explicit TorManagerPrivate(TorManager *parent = 0); + + QString torExecutablePath() const; + bool createDataDir(const QString &path); + bool createDefaultTorrc(const QString &path); + + void setError(const QString &errorMessage); + +public slots: + void processStateChanged(int state); + void processErrorChanged(const QString &errorMessage); + void processLogMessage(const QString &message); + void controlStatusChanged(int status); + void getConfFinished(); +}; + +} + +TorManager::TorManager(QObject *parent) + : QObject(parent), d(new TorManagerPrivate(this)) +{ +} + +TorManagerPrivate::TorManagerPrivate(TorManager *parent) + : QObject(parent) + , q(parent) + , process(0) + , control(new TorControl(this)) + , configNeeded(false) +{ + connect(control, SIGNAL(statusChanged(int,int)), SLOT(controlStatusChanged(int))); +} + +TorManager *TorManager::instance() +{ + static TorManager *p = 0; + if (!p) + p = new TorManager(qApp); + return p; +} + +TorControl *TorManager::control() +{ + return d->control; +} + +TorProcess *TorManager::process() +{ + return d->process; +} + +QString TorManager::dataDirectory() const +{ + return d->dataDir; +} + +void TorManager::setDataDirectory(const QString &path) +{ + d->dataDir = QDir::fromNativeSeparators(path); + if (!d->dataDir.isEmpty() && !d->dataDir.endsWith(QLatin1Char('/'))) + d->dataDir.append(QLatin1Char('/')); +} + +bool TorManager::configurationNeeded() const +{ + return d->configNeeded; +} + +QStringList TorManager::logMessages() const +{ + return d->logMessages; +} + +bool TorManager::hasError() const +{ + return !d->errorMessage.isEmpty(); +} + +QString TorManager::errorMessage() const +{ + return d->errorMessage; +} + +void TorManager::start() +{ + if (!d->errorMessage.isEmpty()) { + d->errorMessage.clear(); + emit errorChanged(); + } + + SettingsObject settings(QStringLiteral("tor")); + + // If a control port is defined by config or environment, skip launching tor + if (!settings.read("controlPort").isUndefined() || + !qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) + { + QHostAddress address(settings.read("controlAddress").toString()); + quint16 port = (quint16)settings.read("controlPort").toInt(); + QByteArray password = settings.read("controlPassword").toString().toLatin1(); + + if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_HOST")) + address = QHostAddress(QString::fromLatin1(qgetenv("TOR_CONTROL_HOST"))); + + if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) { + bool ok = false; + port = qgetenv("TOR_CONTROL_PORT").toUShort(&ok); + if (!ok) + port = 0; + } + + if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PASSWD")) + password = qgetenv("TOR_CONTROL_PASSWD"); + + if (!port) { + d->setError(QStringLiteral("Invalid control port settings from environment or configuration")); + return; + } + + if (address.isNull()) + address = QHostAddress::LocalHost; + + d->control->setAuthPassword(password); + d->control->connect(address, port); + } else { + // Launch a bundled Tor instance + QString executable = d->torExecutablePath(); + + std::cerr << "Executable path: " << executable.toStdString() << std::endl; + + if (executable.isEmpty()) { + d->setError(QStringLiteral("Cannot find tor executable")); + return; + } + + if (!d->process) { + d->process = new TorProcess(this); + connect(d->process, SIGNAL(stateChanged(int)), d, SLOT(processStateChanged(int))); + connect(d->process, SIGNAL(errorMessageChanged(QString)), d, + SLOT(processErrorChanged(QString))); + connect(d->process, SIGNAL(logMessage(QString)), d, SLOT(processLogMessage(QString))); + } + + if (!QFile::exists(d->dataDir) && !d->createDataDir(d->dataDir)) { + d->setError(QStringLiteral("Cannot write data location: %1").arg(d->dataDir)); + return; + } + + QString defaultTorrc = d->dataDir + QStringLiteral("default_torrc"); + if (!QFile::exists(defaultTorrc) && !d->createDefaultTorrc(defaultTorrc)) { + d->setError(QStringLiteral("Cannot write data files: %1").arg(defaultTorrc)); + return; + } + + QFile torrc(d->dataDir + QStringLiteral("torrc")); + if (!torrc.exists() || torrc.size() == 0) { + d->configNeeded = true; + emit configurationNeededChanged(); + } + + d->process->setExecutable(executable); + d->process->setDataDir(d->dataDir); + d->process->setDefaultTorrc(defaultTorrc); + d->process->start(); + } +} + +void TorManagerPrivate::processStateChanged(int state) +{ + qDebug() << Q_FUNC_INFO << state << TorProcess::Ready << process->controlPassword() << process->controlHost() << process->controlPort(); + if (state == TorProcess::Ready) { + control->setAuthPassword(process->controlPassword()); + control->connect(process->controlHost(), process->controlPort()); + } +} + +void TorManagerPrivate::processErrorChanged(const QString &errorMessage) +{ + qDebug() << "tor error:" << errorMessage; + setError(errorMessage); +} + +void TorManagerPrivate::processLogMessage(const QString &message) +{ + qDebug() << "tor:" << message; + if (logMessages.size() >= 50) + logMessages.takeFirst(); + logMessages.append(message); +} + +void TorManagerPrivate::controlStatusChanged(int status) +{ + if (status == TorControl::Connected) { + if (!configNeeded) { + // If DisableNetwork is 1, trigger configurationNeeded + connect(control->getConfiguration(QStringLiteral("DisableNetwork")), + SIGNAL(finished()), SLOT(getConfFinished())); + } + + if (process) { + // Take ownership via this control socket + control->takeOwnership(); + } + } +} + +void TorManagerPrivate::getConfFinished() +{ + GetConfCommand *command = qobject_cast(sender()); + if (!command) + return; + + if (command->get("DisableNetwork").toInt() == 1 && !configNeeded) { + configNeeded = true; + emit q->configurationNeededChanged(); + } +} + +QString TorManagerPrivate::torExecutablePath() const +{ + SettingsObject settings(QStringLiteral("tor")); + QString path = settings.read("executablePath").toString(); + if (!path.isEmpty()) + return path; + +#ifdef Q_OS_WIN + QString filename(QStringLiteral("/tor.exe")); +#else + QString filename(QStringLiteral("/tor")); +#endif + + path = qApp->applicationDirPath(); + if (QFile::exists(path + filename)) + return path + filename; + +#ifdef BUNDLED_TOR_PATH + path = QStringLiteral(BUNDLED_TOR_PATH); + if (QFile::exists(path + filename)) + return path + filename; +#endif + + // Try $PATH + return filename.mid(1); +} + +bool TorManagerPrivate::createDataDir(const QString &path) +{ + QDir dir(path); + return dir.mkpath(QStringLiteral(".")); +} + +bool TorManagerPrivate::createDefaultTorrc(const QString &path) +{ + static const char defaultTorrcContent[] = + "SocksPort auto\n" + "AvoidDiskWrites 1\n" + "DisableNetwork 1\n" + "__ReloadTorrcOnSIGHUP 0\n"; + + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) + return false; + if (file.write(defaultTorrcContent) < 0) + return false; + return true; +} + +void TorManagerPrivate::setError(const QString &message) +{ + errorMessage = message; + emit q->errorChanged(); +} + +#include "TorManager.moc" + diff --git a/retroshare-gui/src/TorControl/TorManager.h b/retroshare-gui/src/TorControl/TorManager.h new file mode 100644 index 000000000..86b4327a0 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorManager.h @@ -0,0 +1,94 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This code has been further modified to fit Retroshare context. + +#ifndef TORMANAGER_H +#define TORMANAGER_H + +#include +#include + +namespace Tor +{ + +class TorProcess; +class TorControl; +class TorManagerPrivate; + +/* Run/connect to an instance of Tor according to configuration, and manage + * UI interaction, first time configuration, etc. */ +class TorManager : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool configurationNeeded READ configurationNeeded NOTIFY configurationNeededChanged) + Q_PROPERTY(QStringList logMessages READ logMessages CONSTANT) + Q_PROPERTY(Tor::TorProcess* process READ process CONSTANT) + Q_PROPERTY(Tor::TorControl* control READ control CONSTANT) + Q_PROPERTY(bool hasError READ hasError NOTIFY errorChanged) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorChanged) + Q_PROPERTY(QString dataDirectory READ dataDirectory WRITE setDataDirectory) + +public: + explicit TorManager(QObject *parent = 0); + static TorManager *instance(); + + TorProcess *process(); + TorControl *control(); + + QString dataDirectory() const; + void setDataDirectory(const QString &path); + + // True on first run or when the Tor configuration wizard needs to be shown + bool configurationNeeded() const; + + QStringList logMessages() const; + + bool hasError() const; + QString errorMessage() const; + +public slots: + void start(); + +signals: + void configurationNeededChanged(); + void errorChanged(); + +private: + TorManagerPrivate *d; +}; + +} + +#endif +# diff --git a/retroshare-gui/src/TorControl/TorProcess.cpp b/retroshare-gui/src/TorControl/TorProcess.cpp new file mode 100644 index 000000000..ca1aecfe5 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorProcess.cpp @@ -0,0 +1,311 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TorProcess_p.h" +#include "CryptoKey.h" +#include "SecureRNG.h" +#include +#include +#include + +using namespace Tor; + +TorProcess::TorProcess(QObject *parent) + : QObject(parent), d(new TorProcessPrivate(this)) +{ +} + +TorProcess::~TorProcess() +{ + if (state() > NotStarted) + stop(); +} + +TorProcessPrivate::TorProcessPrivate(TorProcess *q) + : QObject(q), q(q), state(TorProcess::NotStarted), controlPort(0), controlPortAttempts(0) +{ + connect(&process, &QProcess::started, this, &TorProcessPrivate::processStarted); + connect(&process, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, + this, &TorProcessPrivate::processFinished); + connect(&process, (void (QProcess::*)(QProcess::ProcessError))&QProcess::error, + this, &TorProcessPrivate::processError); + connect(&process, &QProcess::readyRead, this, &TorProcessPrivate::processReadable); + + controlPortTimer.setInterval(500); + connect(&controlPortTimer, &QTimer::timeout, this, &TorProcessPrivate::tryReadControlPort); +} + +QString TorProcess::executable() const +{ + return d->executable; +} + +void TorProcess::setExecutable(const QString &path) +{ + d->executable = path; +} + +QString TorProcess::dataDir() const +{ + return d->dataDir; +} + +void TorProcess::setDataDir(const QString &path) +{ + d->dataDir = path; +} + +QString TorProcess::defaultTorrc() const +{ + return d->defaultTorrc; +} + +void TorProcess::setDefaultTorrc(const QString &path) +{ + d->defaultTorrc = path; +} + +QStringList TorProcess::extraSettings() const +{ + return d->extraSettings; +} + +void TorProcess::setExtraSettings(const QStringList &settings) +{ + d->extraSettings = settings; +} + +TorProcess::State TorProcess::state() const +{ + return d->state; +} + +QString TorProcess::errorMessage() const +{ + return d->errorMessage; +} + +void TorProcess::start() +{ + if (state() > NotStarted) + return; + + d->errorMessage.clear(); + + if (d->executable.isEmpty() || d->dataDir.isEmpty()) { + d->errorMessage = QStringLiteral("Tor executable and data directory not specified"); + d->state = Failed; + emit errorMessageChanged(d->errorMessage); + emit stateChanged(d->state); + return; + } + + if (!d->ensureFilesExist()) { + d->state = Failed; + emit errorMessageChanged(d->errorMessage); + emit stateChanged(d->state); + return; + } + + QByteArray password = controlPassword(); + QByteArray hashedPassword = torControlHashedPassword(password); + if (password.isEmpty() || hashedPassword.isEmpty()) { + d->errorMessage = QStringLiteral("Random password generation failed"); + d->state = Failed; + emit errorMessageChanged(d->errorMessage); + emit stateChanged(d->state); + } + + QStringList args; + if (!d->defaultTorrc.isEmpty()) + args << QStringLiteral("--defaults-torrc") << d->defaultTorrc; + args << QStringLiteral("-f") << d->torrcPath(); + args << QStringLiteral("DataDirectory") << d->dataDir; + args << QStringLiteral("HashedControlPassword") << QString::fromLatin1(hashedPassword); + args << QStringLiteral("ControlPort") << QStringLiteral("auto"); + args << QStringLiteral("ControlPortWriteToFile") << d->controlPortFilePath(); + args << QStringLiteral("__OwningControllerProcess") << QString::number(qApp->applicationPid()); + args << d->extraSettings; + + d->state = Starting; + emit stateChanged(d->state); + + if (QFile::exists(d->controlPortFilePath())) + QFile::remove(d->controlPortFilePath()); + d->controlPort = 0; + d->controlHost.clear(); + + d->process.setProcessChannelMode(QProcess::MergedChannels); + d->process.start(d->executable, args, QIODevice::ReadOnly); +} + +void TorProcess::stop() +{ + if (state() < Starting) + return; + + d->controlPortTimer.stop(); + + if (d->process.state() == QProcess::Starting) + d->process.waitForStarted(2000); + + d->state = NotStarted; + + // Windows can't terminate the process well, but Tor will clean itself up +#ifndef Q_OS_WIN + if (d->process.state() == QProcess::Running) { + d->process.terminate(); + if (!d->process.waitForFinished(5000)) { + qWarning() << "Tor process" << d->process.pid() << "did not respond to terminate, killing..."; + d->process.kill(); + if (!d->process.waitForFinished(2000)) { + qCritical() << "Tor process" << d->process.pid() << "did not respond to kill!"; + } + } + } +#endif + + emit stateChanged(d->state); +} + +QByteArray TorProcess::controlPassword() +{ + if (d->controlPassword.isEmpty()) + d->controlPassword = SecureRNG::randomPrintable(16); + return d->controlPassword; +} + +QHostAddress TorProcess::controlHost() +{ + return d->controlHost; +} + +quint16 TorProcess::controlPort() +{ + return d->controlPort; +} + +bool TorProcessPrivate::ensureFilesExist() +{ + QFile torrc(torrcPath()); + if (!torrc.exists()) { + QDir dir(dataDir); + if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { + errorMessage = QStringLiteral("Cannot create Tor data directory: %1").arg(dataDir); + return false; + } + + if (!torrc.open(QIODevice::ReadWrite)) { + errorMessage = QStringLiteral("Cannot create Tor configuration file: %1").arg(torrcPath()); + return false; + } + } + + return true; +} + +QString TorProcessPrivate::torrcPath() const +{ + return QDir::toNativeSeparators(dataDir) + QDir::separator() + QStringLiteral("torrc"); +} + +QString TorProcessPrivate::controlPortFilePath() const +{ + return QDir::toNativeSeparators(dataDir) + QDir::separator() + QStringLiteral("control-port"); +} + +void TorProcessPrivate::processStarted() +{ + state = TorProcess::Connecting; + emit q->stateChanged(state); + + controlPortAttempts = 0; + controlPortTimer.start(); +} + +void TorProcessPrivate::processFinished() +{ + if (state < TorProcess::Starting) + return; + + controlPortTimer.stop(); + errorMessage = process.errorString(); + if (errorMessage.isEmpty()) + errorMessage = QStringLiteral("Process exited unexpectedly (code %1)").arg(process.exitCode()); + state = TorProcess::Failed; + emit q->errorMessageChanged(errorMessage); + emit q->stateChanged(state); +} + +void TorProcessPrivate::processError(QProcess::ProcessError error) +{ + if (error == QProcess::FailedToStart || error == QProcess::Crashed) + processFinished(); +} + +void TorProcessPrivate::processReadable() +{ + while (process.bytesAvailable() > 0) { + QByteArray line = process.readLine(2048).trimmed(); + if (!line.isEmpty()) + emit q->logMessage(QString::fromLatin1(line)); + } +} + +void TorProcessPrivate::tryReadControlPort() +{ + QFile file(controlPortFilePath()); + if (file.open(QIODevice::ReadOnly)) { + QByteArray data = file.readLine().trimmed(); + + int p; + if (data.startsWith("PORT=") && (p = data.lastIndexOf(':')) > 0) { + controlHost = QHostAddress(QString::fromLatin1(data.mid(5, p - 5))); + controlPort = data.mid(p+1).toUShort(); + + if (!controlHost.isNull() && controlPort > 0) { + controlPortTimer.stop(); + state = TorProcess::Ready; + emit q->stateChanged(state); + return; + } + } + } + + if (++controlPortAttempts * controlPortTimer.interval() > 10000) { + errorMessage = QStringLiteral("No control port available after launching process"); + state = TorProcess::Failed; + emit q->errorMessageChanged(errorMessage); + emit q->stateChanged(state); + } +} + diff --git a/retroshare-gui/src/TorControl/TorProcess.h b/retroshare-gui/src/TorControl/TorProcess.h new file mode 100644 index 000000000..ad489dc43 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorProcess.h @@ -0,0 +1,100 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TORPROCESS_H +#define TORPROCESS_H + +#include +#include + +namespace Tor +{ + +class TorProcessPrivate; + +/* Launches and controls a Tor instance with behavior suitable for bundling + * an instance with the application. */ +class TorProcess : public QObject +{ + Q_OBJECT + Q_ENUMS(State) + + Q_PROPERTY(State state READ state NOTIFY stateChanged) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) + +public: + enum State { + Failed = -1, + NotStarted, + Starting, + Connecting, + Ready + }; + + explicit TorProcess(QObject *parent = 0); + virtual ~TorProcess(); + + QString executable() const; + void setExecutable(const QString &path); + + QString dataDir() const; + void setDataDir(const QString &path); + + QString defaultTorrc() const; + void setDefaultTorrc(const QString &path); + + QStringList extraSettings() const; + void setExtraSettings(const QStringList &settings); + + State state() const; + QString errorMessage() const; + QHostAddress controlHost(); + quint16 controlPort(); + QByteArray controlPassword(); + +public slots: + void start(); + void stop(); + +signals: + void stateChanged(int newState); + void errorMessageChanged(const QString &errorMessage); + void logMessage(const QString &message); + +private: + TorProcessPrivate *d; +}; + +} + +#endif + diff --git a/retroshare-gui/src/TorControl/TorProcess_p.h b/retroshare-gui/src/TorControl/TorProcess_p.h new file mode 100644 index 000000000..9aa2585e3 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorProcess_p.h @@ -0,0 +1,79 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TORPROCESS_P_H +#define TORPROCESS_P_H + +#include "TorProcess.h" +#include +#include + +namespace Tor { + +class TorProcessPrivate : public QObject +{ + Q_OBJECT + +public: + TorProcess *q; + QProcess process; + QString executable; + QString dataDir; + QString defaultTorrc; + QStringList extraSettings; + TorProcess::State state; + QString errorMessage; + QHostAddress controlHost; + quint16 controlPort; + QByteArray controlPassword; + + QTimer controlPortTimer; + int controlPortAttempts; + + TorProcessPrivate(TorProcess *q); + + QString torrcPath() const; + QString controlPortFilePath() const; + bool ensureFilesExist(); + +public slots: + void processStarted(); + void processFinished(); + void processError(QProcess::ProcessError error); + void processReadable(); + void tryReadControlPort(); +}; + +} + +#endif + diff --git a/retroshare-gui/src/TorControl/TorSocket.cpp b/retroshare-gui/src/TorControl/TorSocket.cpp new file mode 100644 index 000000000..615369b3a --- /dev/null +++ b/retroshare-gui/src/TorControl/TorSocket.cpp @@ -0,0 +1,155 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TorSocket.h" +#include "TorControl.h" +#include + +using namespace Tor; + +TorSocket::TorSocket(QObject *parent) + : QTcpSocket(parent) + , m_port(0) + , m_reconnectEnabled(true) + , m_maxInterval(900) + , m_connectAttempts(0) +{ + connect(torControl, SIGNAL(connectivityChanged()), SLOT(connectivityChanged())); + connect(&m_connectTimer, SIGNAL(timeout()), SLOT(reconnect())); + connect(this, SIGNAL(disconnected()), SLOT(onFailed())); + connect(this, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(onFailed())); + + m_connectTimer.setSingleShot(true); + connectivityChanged(); +} + +TorSocket::~TorSocket() +{ +} + +void TorSocket::setReconnectEnabled(bool enabled) +{ + if (enabled == m_reconnectEnabled) + return; + + m_reconnectEnabled = enabled; + if (m_reconnectEnabled) { + m_connectAttempts = 0; + reconnect(); + } else { + m_connectTimer.stop(); + } +} + +void TorSocket::setMaxAttemptInterval(int interval) +{ + m_maxInterval = interval; +} + +void TorSocket::resetAttempts() +{ + m_connectAttempts = 0; + if (m_connectTimer.isActive()) { + m_connectTimer.stop(); + m_connectTimer.start(reconnectInterval() * 1000); + } +} + +int TorSocket::reconnectInterval() +{ + int delay = 0; + if (m_connectAttempts <= 4) + delay = 30; + else if (m_connectAttempts <= 6) + delay = 120; + else + delay = m_maxInterval; + + return qMin(delay, m_maxInterval); +} + +void TorSocket::reconnect() +{ + if (!torControl->hasConnectivity() || !reconnectEnabled()) + return; + + m_connectTimer.stop(); + if (!m_host.isEmpty() && m_port) { + qDebug() << "Attempting reconnection of socket to" << m_host << m_port; + connectToHost(m_host, m_port); + } +} + +void TorSocket::connectivityChanged() +{ + if (torControl->hasConnectivity()) { + setProxy(torControl->connectionProxy()); + if (state() == QAbstractSocket::UnconnectedState) + reconnect(); + } else { + m_connectTimer.stop(); + m_connectAttempts = 0; + } +} + +void TorSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode, + NetworkLayerProtocol protocol) +{ + m_host = hostName; + m_port = port; + + if (!torControl->hasConnectivity()) + return; + + if (proxy() != torControl->connectionProxy()) + setProxy(torControl->connectionProxy()); + + QAbstractSocket::connectToHost(hostName, port, openMode, protocol); +} + +void TorSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode) +{ + TorSocket::connectToHost(address.toString(), port, openMode); +} + +void TorSocket::onFailed() +{ + // Make sure the internal connection to the SOCKS proxy is closed + // Otherwise reconnect attempts will fail (#295) + close(); + + if (reconnectEnabled() && !m_connectTimer.isActive()) { + m_connectAttempts++; + m_connectTimer.start(reconnectInterval() * 1000); + qDebug() << "Reconnecting socket to" << m_host << m_port << "in" << m_connectTimer.interval() / 1000 << "seconds"; + } +} diff --git a/retroshare-gui/src/TorControl/TorSocket.h b/retroshare-gui/src/TorControl/TorSocket.h new file mode 100644 index 000000000..0c68f7854 --- /dev/null +++ b/retroshare-gui/src/TorControl/TorSocket.h @@ -0,0 +1,97 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TORSOCKET_H +#define TORSOCKET_H + +#include +#include + +namespace Tor { + +/* Specialized QTcpSocket which makes connections over the SOCKS proxy + * from a TorControl instance, automatically attempts reconnections, and + * reacts to Tor's connectivity state. + * + * Use normal QTcpSocket/QAbstractSocket API. When a connection fails, it + * will be retried automatically after the correct interval and when + * connectivity is available. + * + * To fully disconnect, destroy the object, or call + * setReconnectEnabled(false) and disconnect the socket with + * disconnectFromHost or abort. + * + * The caller is responsible for resetting the attempt counter if a + * connection was successful and reconnection will be used again. + */ +class TorSocket : public QTcpSocket +{ + Q_OBJECT + +public: + explicit TorSocket(QObject *parent = 0); + virtual ~TorSocket(); + + bool reconnectEnabled() const { return m_reconnectEnabled; } + void setReconnectEnabled(bool enabled); + int maxAttemptInterval() { return m_maxInterval; } + void setMaxAttemptInterval(int interval); + void resetAttempts(); + + virtual void connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol); + virtual void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite); + + QString hostName() const { return m_host; } + quint16 port() const { return m_port; } + +protected: + virtual int reconnectInterval(); + +private slots: + void reconnect(); + void connectivityChanged(); + void onFailed(); + +private: + QString m_host; + quint16 m_port; + QTimer m_connectTimer; + bool m_reconnectEnabled; + int m_maxInterval; + int m_connectAttempts; + + using QAbstractSocket::connectToHost; +}; + +} + +#endif diff --git a/retroshare-gui/src/TorControl/Useful.h b/retroshare-gui/src/TorControl/Useful.h new file mode 100644 index 000000000..6bb6e44c1 --- /dev/null +++ b/retroshare-gui/src/TorControl/Useful.h @@ -0,0 +1,71 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UTILS_USEFUL_H +#define UTILS_USEFUL_H + +#include +#include + +/* Print a warning for bug conditions, and assert on a debug build. + * + * This should be used in place of Q_ASSERT for bug conditions, along + * with a proper error case for release-mode builds. For example: + * + * if (!connection || !user) { + * BUG() << "Request" << request << "should have a connection and user"; + * return false; + * } + * + * Do not confuse bugs with actual error cases; BUG() should never be + * triggered unless the code or logic is wrong. + */ +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) +# define BUG() Explode(__FILE__,__LINE__), qWarning() << "BUG:" +namespace { +class Explode +{ +public: + const char *file; + int line; + Explode(const char *file, int line) : file(file), line(line) { } + ~Explode() { + qt_assert("something broke!", file, line); + } +}; +} +#else +# define BUG() qWarning() << "BUG:" +#endif + +#endif + diff --git a/retroshare-gui/src/main.cpp b/retroshare-gui/src/main.cpp index 2430a1984..59213ac5b 100644 --- a/retroshare-gui/src/main.cpp +++ b/retroshare-gui/src/main.cpp @@ -46,6 +46,10 @@ #include "lang/languagesupport.h" #include "util/RsGxsUpdateBroadcast.h" +#ifdef RETROTOR +#include "TorControl/TorManager.h" +#endif + #include "retroshare/rsidentity.h" #ifdef SIGFPE_DEBUG @@ -274,8 +278,26 @@ feenableexcept(FE_INVALID | FE_DIVBYZERO); RshareSettings::Create (); /* Setup The GUI Stuff */ - Rshare rshare(args, argc, argv, - QString::fromUtf8(RsAccounts::ConfigDirectory().c_str())); + Rshare rshare(args, argc, argv, QString::fromUtf8(RsAccounts::ConfigDirectory().c_str())); + +#ifdef RETROTOR + // Start the Tor engine, and make sure it provides a viable hidden service + + /* Tor control manager */ + Tor::TorManager *torManager = Tor::TorManager::instance(); + torManager->setDataDirectory(Rshare::dataDirectory() + QString("/tor/")); + //torManager->setDataDirectory(QString("./tor"));//settings->filePath()).path() + QString("/tor/")); + + Tor::TorControl *torControl = torManager->control(); + torManager->start(); + + while(torManager->configurationNeeded()) + { + usleep(1000*1000) ; + + // we should display some configuration window here! + } +#endif /* Start RetroShare */ QSplashScreen splashScreen(QPixmap(":/images/logo/logo_splash.png")/* , Qt::WindowStaysOnTopHint*/); diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 6b3211392..f7e23bc8c 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -1,7 +1,7 @@ !include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") TEMPLATE = app -QT += network xml +QT += network xml CONFIG += qt gui uic qrc resources idle bitdht CONFIG += link_prl TARGET = retroshare @@ -20,6 +20,9 @@ profiling { QMAKE_LFLAGS *= -pg } +retrotor { + DEFINES *= RETROTOR +} #QMAKE_CFLAGS += -fmudflap #LIBS *= /usr/lib/gcc/x86_64-linux-gnu/4.4/libmudflap.a /usr/lib/gcc/x86_64-linux-gnu/4.4/libmudflapth.a @@ -325,6 +328,47 @@ INCLUDEPATH += $$PWD/../../libresapi/src PRE_TARGETDEPS *= $$OUT_PWD/../../libresapi/src/lib/libresapi.a LIBS += $$OUT_PWD/../../libresapi/src/lib/libresapi.a +retrotor { +HEADERS += TorControl/AddOnionCommand.h \ + TorControl/AuthenticateCommand.h \ + TorControl/GetConfCommand.h \ + TorControl/HiddenService.h \ + TorControl/ProtocolInfoCommand.h \ + TorControl/SetConfCommand.h \ + TorControl/TorControlCommand.h \ + TorControl/TorControl.h \ + TorControl/TorControlSocket.h \ + TorControl/TorManager.h \ + TorControl/TorProcess.h \ + TorControl/TorProcess_p.h \ + TorControl/TorSocket.h \ + TorControl/Useful.h \ + TorControl/CryptoKey.h \ + TorControl/PendingOperation.h \ + TorControl/SecureRNG.h \ + TorControl/Settings.h \ + TorControl/StringUtil.h \ + TorControl/TorProcess_p.h + +SOURCES += TorControl/AddOnionCommand.cpp \ + TorControl/AuthenticateCommand.cpp \ + TorControl/GetConfCommand.cpp \ + TorControl/HiddenService.cpp \ + TorControl/ProtocolInfoCommand.cpp \ + TorControl/SetConfCommand.cpp \ + TorControl/TorControlCommand.cpp \ + TorControl/TorControl.cpp \ + TorControl/TorControlSocket.cpp \ + TorControl/TorManager.cpp \ + TorControl/TorProcess.cpp \ + TorControl/TorSocket.cpp \ + TorControl/CryptoKey.cpp \ + TorControl/PendingOperation.cpp \ + TorControl/SecureRNG.cpp \ + TorControl/Settings.cpp \ + TorControl/StringUtil.cpp +} + # Input HEADERS += rshare.h \ retroshare-gui/configpage.h \ diff --git a/retroshare.pri b/retroshare.pri index 0b61671f6..5ae0e46b0 100644 --- a/retroshare.pri +++ b/retroshare.pri @@ -3,6 +3,10 @@ CONFIG *= retroshare_gui no_retroshare_gui:CONFIG -= retroshare_gui +# To build the RetroTor executable, just include the following option + +CONFIG *= retrotor + # To disable RetroShare-nogui append the following # assignation to qmake command line "CONFIG+=no_retroshare_nogui" CONFIG *= retroshare_nogui