mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-02 05:38:06 -06:00
Move network related code to core/net.
This commit is contained in:
299
src/core/net/dnsupdater.cpp
Normal file
299
src/core/net/dnsupdater.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||
#include <QUrlQuery>
|
||||
#endif
|
||||
#include "dnsupdater.h"
|
||||
#include "logger.h"
|
||||
|
||||
using namespace Net;
|
||||
|
||||
DNSUpdater::DNSUpdater(QObject *parent) :
|
||||
QObject(parent), m_state(OK), m_service(DNS::NONE)
|
||||
{
|
||||
updateCredentials();
|
||||
|
||||
// Load saved settings from previous session
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
m_lastIPCheckTime = pref->getDNSLastUpd();
|
||||
m_lastIP = QHostAddress(pref->getDNSLastIP());
|
||||
|
||||
// Start IP checking timer
|
||||
m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS);
|
||||
connect(&m_ipCheckTimer, SIGNAL(timeout()), SLOT(checkPublicIP()));
|
||||
m_ipCheckTimer.start();
|
||||
|
||||
// Check lastUpdate to avoid flooding
|
||||
if (!m_lastIPCheckTime.isValid() ||
|
||||
m_lastIPCheckTime.secsTo(QDateTime::currentDateTime())*1000 > IP_CHECK_INTERVAL_MS) {
|
||||
checkPublicIP();
|
||||
}
|
||||
}
|
||||
|
||||
DNSUpdater::~DNSUpdater() {
|
||||
// Save lastupdate time and last ip
|
||||
Preferences* const pref = Preferences::instance();
|
||||
pref->setDNSLastUpd(m_lastIPCheckTime);
|
||||
pref->setDNSLastIP(m_lastIP.toString());
|
||||
}
|
||||
|
||||
void DNSUpdater::checkPublicIP()
|
||||
{
|
||||
Q_ASSERT(m_state == OK);
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)),
|
||||
SLOT(ipRequestFinished(QNetworkReply*)));
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl("http://checkip.dyndns.org"));
|
||||
request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org");
|
||||
manager->get(request);
|
||||
}
|
||||
|
||||
void DNSUpdater::ipRequestFinished(QNetworkReply *reply)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if (reply->error()) {
|
||||
// Error
|
||||
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
|
||||
} else {
|
||||
// Parse response
|
||||
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
|
||||
QString ret = reply->readAll();
|
||||
if (ipregex.indexIn(ret) >= 0) {
|
||||
QString ip_str = ipregex.cap(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str;
|
||||
QHostAddress new_ip(ip_str);
|
||||
if (!new_ip.isNull()) {
|
||||
if (m_lastIP != new_ip) {
|
||||
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
|
||||
qDebug() << m_lastIP.toString() << "->" << new_ip.toString();
|
||||
m_lastIP = new_ip;
|
||||
updateDNSService();
|
||||
}
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string";
|
||||
}
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Regular expression failed ot capture the IP address";
|
||||
}
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void DNSUpdater::updateDNSService()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
// Prepare request
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)),
|
||||
SLOT(ipUpdateFinished(QNetworkReply*)));
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
QNetworkRequest request;
|
||||
request.setUrl(getUpdateUrl());
|
||||
request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org");
|
||||
manager->get(request);
|
||||
}
|
||||
|
||||
QUrl DNSUpdater::getUpdateUrl() const
|
||||
{
|
||||
QUrl url;
|
||||
#ifdef QT_NO_OPENSSL
|
||||
url.setScheme("http");
|
||||
#else
|
||||
url.setScheme("https");
|
||||
#endif
|
||||
url.setUserName(m_username);
|
||||
url.setPassword(m_password);
|
||||
|
||||
Q_ASSERT(!m_lastIP.isNull());
|
||||
// Service specific
|
||||
switch(m_service) {
|
||||
case DNS::DYNDNS:
|
||||
url.setHost("members.dyndns.org");
|
||||
break;
|
||||
case DNS::NOIP:
|
||||
url.setHost("dynupdate.no-ip.com");
|
||||
break;
|
||||
default:
|
||||
qWarning() << "Unrecognized Dynamic DNS service!";
|
||||
Q_ASSERT(0);
|
||||
}
|
||||
url.setPath("/nic/update");
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
url.addQueryItem("hostname", m_domain);
|
||||
url.addQueryItem("myip", m_lastIP.toString());
|
||||
#else
|
||||
QUrlQuery urlQuery(url);
|
||||
urlQuery.addQueryItem("hostname", m_domain);
|
||||
urlQuery.addQueryItem("myip", m_lastIP.toString());
|
||||
url.setQuery(urlQuery);
|
||||
#endif
|
||||
Q_ASSERT(url.isValid());
|
||||
|
||||
qDebug() << Q_FUNC_INFO << url.toString();
|
||||
return url;
|
||||
}
|
||||
|
||||
void DNSUpdater::ipUpdateFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error()) {
|
||||
// Error
|
||||
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
|
||||
} else {
|
||||
// Pase reply
|
||||
processIPUpdateReply(reply->readAll());
|
||||
}
|
||||
// Clean up
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
{
|
||||
Logger* const logger = Logger::instance();
|
||||
qDebug() << Q_FUNC_INFO << reply;
|
||||
QString code = reply.split(" ").first();
|
||||
qDebug() << Q_FUNC_INFO << "Code:" << code;
|
||||
if (code == "good" || code == "nochg") {
|
||||
logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO);
|
||||
return;
|
||||
}
|
||||
if (code == "911" || code == "dnserr") {
|
||||
logger->addMessage(tr("Dynamic DNS error: The service is temporarily unavailable, it will be retried in 30 minutes."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
// It will retry in 30 minutes because the timer was not stopped
|
||||
return;
|
||||
}
|
||||
// Everything bellow is an error, stop updating until the user updates something
|
||||
m_ipCheckTimer.stop();
|
||||
m_lastIP.clear();
|
||||
if (code == "nohost") {
|
||||
logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL);
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
if (code == "badauth") {
|
||||
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
if (code == "badagent") {
|
||||
logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
if (code == "!donator") {
|
||||
logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
if (code == "abuse") {
|
||||
logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DNSUpdater::updateCredentials()
|
||||
{
|
||||
if (m_state == FATAL) return;
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
bool change = false;
|
||||
// Get DNS service information
|
||||
if (m_service != pref->getDynDNSService()) {
|
||||
m_service = pref->getDynDNSService();
|
||||
change = true;
|
||||
}
|
||||
if (m_domain != pref->getDynDomainName()) {
|
||||
m_domain = pref->getDynDomainName();
|
||||
QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$");
|
||||
if (domain_regex.indexIn(m_domain) < 0) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
change = true;
|
||||
}
|
||||
if (m_username != pref->getDynDNSUsername()) {
|
||||
m_username = pref->getDynDNSUsername();
|
||||
if (m_username.length() < 4) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
change = true;
|
||||
}
|
||||
if (m_password != pref->getDynDNSPassword()) {
|
||||
m_password = pref->getDynDNSPassword();
|
||||
if (m_password.length() < 4) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
change = true;
|
||||
}
|
||||
|
||||
if (m_state == INVALID_CREDS && change) {
|
||||
m_state = OK; // Try again
|
||||
m_ipCheckTimer.start();
|
||||
checkPublicIP();
|
||||
}
|
||||
}
|
||||
|
||||
QUrl DNSUpdater::getRegistrationUrl(int service)
|
||||
{
|
||||
switch(service) {
|
||||
case DNS::DYNDNS:
|
||||
return QUrl("https://www.dyndns.com/account/services/hosts/add.html");
|
||||
case DNS::NOIP:
|
||||
return QUrl("http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html");
|
||||
default:
|
||||
Q_ASSERT(0);
|
||||
}
|
||||
return QUrl();
|
||||
}
|
||||
86
src/core/net/dnsupdater.h
Normal file
86
src/core/net/dnsupdater.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef DNSUPDATER_H
|
||||
#define DNSUPDATER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkReply>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include "preferences.h"
|
||||
|
||||
namespace Net
|
||||
{
|
||||
|
||||
/*!
|
||||
* Based on http://www.dyndns.com/developers/specs/
|
||||
*/
|
||||
class DNSUpdater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DNSUpdater(QObject *parent = 0);
|
||||
~DNSUpdater();
|
||||
static QUrl getRegistrationUrl(int service);
|
||||
|
||||
public slots:
|
||||
void updateCredentials();
|
||||
|
||||
private slots:
|
||||
void checkPublicIP();
|
||||
void ipRequestFinished(QNetworkReply* reply);
|
||||
void updateDNSService();
|
||||
void ipUpdateFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
QUrl getUpdateUrl() const;
|
||||
void processIPUpdateReply(const QString &reply);
|
||||
|
||||
private:
|
||||
QHostAddress m_lastIP;
|
||||
QDateTime m_lastIPCheckTime;
|
||||
QTimer m_ipCheckTimer;
|
||||
int m_state;
|
||||
// Service creds
|
||||
DNS::Service m_service;
|
||||
QString m_domain;
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
|
||||
private:
|
||||
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
|
||||
enum State { OK, INVALID_CREDS, FATAL };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // DNSUPDATER_H
|
||||
94
src/core/net/reverseresolution.cpp
Normal file
94
src/core/net/reverseresolution.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHostInfo>
|
||||
#include <QString>
|
||||
|
||||
#include <boost/version.hpp>
|
||||
#if BOOST_VERSION < 103500
|
||||
#include <libtorrent/asio/ip/tcp.hpp>
|
||||
#else
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#endif
|
||||
|
||||
#include "reverseresolution.h"
|
||||
|
||||
const int CACHE_SIZE = 500;
|
||||
|
||||
using namespace Net;
|
||||
|
||||
static inline bool isUsefulHostName(const QString &hostname, const QString &ip)
|
||||
{
|
||||
return (!hostname.isEmpty() && hostname != ip);
|
||||
}
|
||||
|
||||
ReverseResolution::ReverseResolution(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_cache.setMaxCost(CACHE_SIZE);
|
||||
}
|
||||
|
||||
ReverseResolution::~ReverseResolution()
|
||||
{
|
||||
qDebug("Deleting host name resolver...");
|
||||
}
|
||||
|
||||
void ReverseResolution::resolve(const QString &ip)
|
||||
{
|
||||
if (m_cache.contains(ip)) {
|
||||
const QString &hostname = *m_cache.object(ip);
|
||||
qDebug() << "Resolved host name using cache: " << ip << " -> " << hostname;
|
||||
if (isUsefulHostName(hostname, ip))
|
||||
emit ipResolved(ip, hostname);
|
||||
}
|
||||
else {
|
||||
// Actually resolve the ip
|
||||
m_lookups.insert(QHostInfo::lookupHost(ip, this, SLOT(hostResolved(QHostInfo))), ip);
|
||||
}
|
||||
}
|
||||
|
||||
void ReverseResolution::hostResolved(const QHostInfo &host)
|
||||
{
|
||||
const QString &ip = m_lookups.take(host.lookupId());
|
||||
Q_ASSERT(!ip.isNull());
|
||||
|
||||
if (host.error() != QHostInfo::NoError) {
|
||||
qDebug() << "DNS Reverse resolution error: " << host.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString &hostname = host.hostName();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << ip << QString("->") << hostname;
|
||||
m_cache.insert(ip, new QString(hostname));
|
||||
if (isUsefulHostName(hostname, ip))
|
||||
emit ipResolved(ip, hostname);
|
||||
}
|
||||
67
src/core/net/reverseresolution.h
Normal file
67
src/core/net/reverseresolution.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef NET_REVERSERESOLUTION_H
|
||||
#define NET_REVERSERESOLUTION_H
|
||||
|
||||
#include <QCache>
|
||||
#include <QObject>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QHostInfo;
|
||||
class QString;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Net
|
||||
{
|
||||
class ReverseResolution : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ReverseResolution)
|
||||
|
||||
public:
|
||||
explicit ReverseResolution(QObject *parent = 0);
|
||||
~ReverseResolution();
|
||||
|
||||
void resolve(const QString &ip);
|
||||
|
||||
signals:
|
||||
void ipResolved(const QString &ip, const QString &hostname);
|
||||
|
||||
private slots:
|
||||
void hostResolved(const QHostInfo &host);
|
||||
|
||||
private:
|
||||
QHash<int /* LookupID */, QString /* IP */> m_lookups;
|
||||
QCache<QString /* IP */, QString /* HostName */> m_cache;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // NET_REVERSERESOLUTION_H
|
||||
482
src/core/net/smtp.cpp
Normal file
482
src/core/net/smtp.cpp
Normal file
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on QxtSmtp from libqxt (http://libqxt.org)
|
||||
*/
|
||||
|
||||
#include "smtp.h"
|
||||
#include "preferences.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QTextStream>
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslSocket>
|
||||
#else
|
||||
#include <QTcpSocket>
|
||||
#endif
|
||||
#include <QTextCodec>
|
||||
#include <QDebug>
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QCryptographicHash>
|
||||
#include <QStringList>
|
||||
|
||||
namespace {
|
||||
const short DEFAULT_PORT = 25;
|
||||
const short DEFAULT_PORT_SSL = 465;
|
||||
|
||||
QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
|
||||
{
|
||||
const int blockSize = 64; // HMAC-MD5 block size
|
||||
if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression
|
||||
key = QCryptographicHash::hash(key, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
|
||||
QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
|
||||
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
|
||||
// Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)
|
||||
|
||||
for (int i = 0; i < key.length(); i++) {
|
||||
innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
|
||||
outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
|
||||
}
|
||||
|
||||
// result = hash ( outerPadding CONCAT hash ( innerPadding CONCAT baseString ) ).toBase64
|
||||
QByteArray total = outerPadding;
|
||||
QByteArray part = innerPadding;
|
||||
part.append(msg);
|
||||
total.append(QCryptographicHash::hash(part, QCryptographicHash::Md5));
|
||||
return QCryptographicHash::hash(total, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
QByteArray determineFQDN()
|
||||
{
|
||||
QString hostname = QHostInfo::localHostName();
|
||||
if (hostname.isEmpty())
|
||||
hostname = "localhost";
|
||||
|
||||
return hostname.toLocal8Bit();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
using namespace Net;
|
||||
|
||||
Smtp::Smtp(QObject *parent): QObject(parent),
|
||||
state(Init), use_ssl(false) {
|
||||
#ifndef QT_NO_OPENSSL
|
||||
socket = new QSslSocket(this);
|
||||
#else
|
||||
socket = new QTcpSocket(this);
|
||||
#endif
|
||||
|
||||
connect(socket, SIGNAL(readyRead()), SLOT(readyRead()));
|
||||
connect(socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
||||
|
||||
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
|
||||
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
|
||||
== "750c783e6ab0b503eaa86e310a5db738");
|
||||
Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
"Hi There").toHex()
|
||||
== "9294727a3638bb1c13f48ef8158bfc9d");
|
||||
}
|
||||
|
||||
Smtp::~Smtp() {
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
}
|
||||
|
||||
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body) {
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
|
||||
message = "";
|
||||
message += encode_mime_header("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1);
|
||||
message += encode_mime_header("From", from, latin1);
|
||||
message += encode_mime_header("Subject", subject, latin1);
|
||||
message += encode_mime_header("To", to, latin1);
|
||||
message += "MIME-Version: 1.0\r\n";
|
||||
message += "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||
message += "Content-Transfer-Encoding: base64\r\n";
|
||||
message += "\r\n";
|
||||
// Encode the body in base64
|
||||
QString crlf_body = body;
|
||||
QByteArray b = crlf_body.replace("\n","\r\n").toUtf8().toBase64();
|
||||
int ct = b.length();
|
||||
for (int i = 0; i < ct; i += 78)
|
||||
{
|
||||
message += b.mid(i, 78);
|
||||
}
|
||||
this->from = from;
|
||||
rcpt = to;
|
||||
// Authentication
|
||||
if (pref->getMailNotificationSMTPAuth()) {
|
||||
username = pref->getMailNotificationSMTPUsername();
|
||||
password = pref->getMailNotificationSMTPPassword();
|
||||
}
|
||||
|
||||
// Connect to SMTP server
|
||||
#ifndef QT_NO_OPENSSL
|
||||
if (pref->getMailNotificationSMTPSSL()) {
|
||||
socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
|
||||
use_ssl = true;
|
||||
} else {
|
||||
#endif
|
||||
socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||
use_ssl = false;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Smtp::readyRead()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
// SMTP is line-oriented
|
||||
buffer += socket->readAll();
|
||||
while (true)
|
||||
{
|
||||
int pos = buffer.indexOf("\r\n");
|
||||
if (pos < 0) return; // Loop exit condition
|
||||
QByteArray line = buffer.left(pos);
|
||||
buffer = buffer.mid(pos + 2);
|
||||
qDebug() << "Response line:" << line;
|
||||
// Extract reponse code
|
||||
QByteArray code = line.left(3);
|
||||
|
||||
switch(state) {
|
||||
case Init: {
|
||||
if (code[0] == '2') {
|
||||
// The server may send a multiline greeting/INIT/220 response.
|
||||
// We wait until it finishes.
|
||||
if (line[3] != ' ')
|
||||
break;
|
||||
// Connection was successful
|
||||
ehlo();
|
||||
} else {
|
||||
logError("Connection failed, unrecognized reply: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EhloSent:
|
||||
case HeloSent:
|
||||
case EhloGreetReceived:
|
||||
parseEhloResponse(code, line[3] != ' ', line.mid(4));
|
||||
break;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
case StartTLSSent:
|
||||
if (code == "220") {
|
||||
socket->startClientEncryption();
|
||||
ehlo();
|
||||
} else {
|
||||
authenticate();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case AuthRequestSent:
|
||||
case AuthUsernameSent:
|
||||
if (authType == AuthPlain) authPlain();
|
||||
else if (authType == AuthLogin) authLogin();
|
||||
else authCramMD5(line.mid(4));
|
||||
break;
|
||||
case AuthSent:
|
||||
case Authenticated:
|
||||
if (code[0] == '2') {
|
||||
qDebug() << "Sending <mail from>...";
|
||||
socket->write("mail from:<" + from.toLatin1() + ">\r\n");
|
||||
socket->flush();
|
||||
state = Rcpt;
|
||||
} else {
|
||||
// Authentication failed!
|
||||
logError("Authentication failed, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Rcpt:
|
||||
if (code[0] == '2') {
|
||||
socket->write("rcpt to:<" + rcpt.toLatin1() + ">\r\n");
|
||||
socket->flush();
|
||||
state = Data;
|
||||
} else {
|
||||
logError("<mail from> was rejected by server, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Data:
|
||||
if (code[0] == '2') {
|
||||
socket->write("data\r\n");
|
||||
socket->flush();
|
||||
state = Body;
|
||||
} else {
|
||||
logError("<Rcpt to> was rejected by server, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Body:
|
||||
if (code[0] == '3') {
|
||||
socket->write(message + "\r\n.\r\n");
|
||||
socket->flush();
|
||||
state = Quit;
|
||||
} else {
|
||||
logError("<data> was rejected by server, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
case Quit:
|
||||
if (code[0] == '2') {
|
||||
socket->write("QUIT\r\n");
|
||||
socket->flush();
|
||||
// here, we just close.
|
||||
state = Close;
|
||||
} else {
|
||||
logError("Message was rejected by the server, error: "+line);
|
||||
state = Close;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Disconnecting from host";
|
||||
socket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix)
|
||||
{
|
||||
QByteArray rv = "";
|
||||
QByteArray line = key.toLatin1() + ": ";
|
||||
if (!prefix.isEmpty()) line += prefix;
|
||||
if (!value.contains("=?") && latin1->canEncode(value)) {
|
||||
bool firstWord = true;
|
||||
foreach (const QByteArray& word, value.toLatin1().split(' ')) {
|
||||
if (line.size() > 78) {
|
||||
rv = rv + line + "\r\n";
|
||||
line.clear();
|
||||
}
|
||||
if (firstWord)
|
||||
line += word;
|
||||
else
|
||||
line += " " + word;
|
||||
firstWord = false;
|
||||
}
|
||||
} else {
|
||||
// The text cannot be losslessly encoded as Latin-1. Therefore, we
|
||||
// must use base64 encoding.
|
||||
QByteArray utf8 = value.toUtf8();
|
||||
// Use base64 encoding
|
||||
QByteArray base64 = utf8.toBase64();
|
||||
int ct = base64.length();
|
||||
line += "=?utf-8?b?";
|
||||
for (int i = 0; i < ct; i += 4) {
|
||||
/*if (line.length() > 72) {
|
||||
rv += line + "?\n\r";
|
||||
line = " =?utf-8?b?";
|
||||
}*/
|
||||
line = line + base64.mid(i, 4);
|
||||
}
|
||||
line += "?="; // end encoded-word atom
|
||||
}
|
||||
return rv + line + "\r\n";
|
||||
}
|
||||
|
||||
void Smtp::ehlo()
|
||||
{
|
||||
QByteArray address = determineFQDN();
|
||||
socket->write("ehlo " + address + "\r\n");
|
||||
socket->flush();
|
||||
state = EhloSent;
|
||||
}
|
||||
|
||||
void Smtp::helo()
|
||||
{
|
||||
QByteArray address = determineFQDN();
|
||||
socket->write("helo " + address + "\r\n");
|
||||
socket->flush();
|
||||
state = HeloSent;
|
||||
}
|
||||
|
||||
void Smtp::parseEhloResponse(const QByteArray& code, bool continued, const QString& line)
|
||||
{
|
||||
if (code != "250") {
|
||||
// Error
|
||||
if (state == EhloSent) {
|
||||
// try to send HELO instead of EHLO
|
||||
qDebug() << "EHLO failed, trying HELO instead...";
|
||||
helo();
|
||||
} else {
|
||||
// Both EHLO and HELO failed, chances are this is NOT
|
||||
// a SMTP server
|
||||
logError("Both EHLO and HELO failed, msg: "+line);
|
||||
state = Close;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state != EhloGreetReceived) {
|
||||
if (!continued) {
|
||||
// greeting only, no extensions
|
||||
qDebug() << "No extension";
|
||||
state = EhloDone;
|
||||
} else {
|
||||
// greeting followed by extensions
|
||||
state = EhloGreetReceived;
|
||||
qDebug () << "EHLO greet received";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper()
|
||||
<< line.section(' ', 1);
|
||||
extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1);
|
||||
if (!continued)
|
||||
state = EhloDone;
|
||||
}
|
||||
if (state != EhloDone) return;
|
||||
if (extensions.contains("STARTTLS") && use_ssl) {
|
||||
qDebug() << "STARTTLS";
|
||||
startTLS();
|
||||
} else {
|
||||
authenticate();
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::authenticate()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if (!extensions.contains("AUTH") ||
|
||||
username.isEmpty() || password.isEmpty()) {
|
||||
// Skip authentication
|
||||
qDebug() << "Skipping authentication...";
|
||||
state = Authenticated;
|
||||
// At this point the server will not send any response
|
||||
// So fill the buffer with a fake one to pass the tests
|
||||
// in readyRead()
|
||||
buffer.push_front("250 QBT FAKE RESPONSE\r\n");
|
||||
return;
|
||||
}
|
||||
// AUTH extension is supported, check which
|
||||
// authentication modes are supported by
|
||||
// the server
|
||||
QStringList auth = extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts);
|
||||
if (auth.contains("CRAM-MD5")) {
|
||||
qDebug() << "Using CRAM-MD5 authentication...";
|
||||
authCramMD5();
|
||||
}
|
||||
else if (auth.contains("PLAIN")) {
|
||||
qDebug() << "Using PLAIN authentication...";
|
||||
authPlain();
|
||||
}
|
||||
else if (auth.contains("LOGIN")) {
|
||||
qDebug() << "Using LOGIN authentication...";
|
||||
authLogin();
|
||||
} else {
|
||||
// Skip authentication
|
||||
logError("The SMTP server does not seem to support any of the authentications modes "
|
||||
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
|
||||
"knowing it is likely to fail... Server Auth Modes: "+auth.join("|"));
|
||||
state = Authenticated;
|
||||
// At this point the server will not send any response
|
||||
// So fill the buffer with a fake one to pass the tests
|
||||
// in readyRead()
|
||||
buffer.push_front("250 QBT FAKE RESPONSE\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::startTLS()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
socket->write("starttls\r\n");
|
||||
socket->flush();
|
||||
state = StartTLSSent;
|
||||
#else
|
||||
authenticate();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Smtp::authCramMD5(const QByteArray& challenge)
|
||||
{
|
||||
if (state != AuthRequestSent) {
|
||||
socket->write("auth cram-md5\r\n");
|
||||
socket->flush();
|
||||
authType = AuthCramMD5;
|
||||
state = AuthRequestSent;
|
||||
} else {
|
||||
QByteArray response = username.toLatin1() + ' '
|
||||
+ hmacMD5(password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
|
||||
socket->write(response.toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthSent;
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::authPlain()
|
||||
{
|
||||
if (state != AuthRequestSent) {
|
||||
authType = AuthPlain;
|
||||
// Prepare Auth string
|
||||
QByteArray auth;
|
||||
auth += '\0';
|
||||
auth += username.toLatin1();
|
||||
qDebug() << "username: " << username.toLatin1();
|
||||
auth += '\0';
|
||||
auth += password.toLatin1();
|
||||
qDebug() << "password: " << password.toLatin1();
|
||||
// Send it
|
||||
socket->write("auth plain "+ auth.toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthSent;
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::authLogin()
|
||||
{
|
||||
if (state != AuthRequestSent && state != AuthUsernameSent) {
|
||||
socket->write("auth login\r\n");
|
||||
socket->flush();
|
||||
authType = AuthLogin;
|
||||
state = AuthRequestSent;
|
||||
}
|
||||
else if (state == AuthRequestSent) {
|
||||
socket->write(username.toLatin1().toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthUsernameSent;
|
||||
}
|
||||
else {
|
||||
socket->write(password.toLatin1().toBase64() + "\r\n");
|
||||
socket->flush();
|
||||
state = AuthSent;
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::logError(const QString &msg)
|
||||
{
|
||||
qDebug() << "Email Notification Error:" << msg;
|
||||
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
|
||||
}
|
||||
105
src/core/net/smtp.h
Normal file
105
src/core/net/smtp.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2011 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on QxtSmtp from libqxt (http://libqxt.org)
|
||||
*/
|
||||
|
||||
#ifndef SMTP_H
|
||||
#define SMTP_H
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextStream;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
class QSslSocket;
|
||||
#else
|
||||
class QTcpSocket;
|
||||
#endif
|
||||
class QTextCodec;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Net
|
||||
{
|
||||
|
||||
class Smtp : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Smtp(QObject *parent = 0);
|
||||
~Smtp();
|
||||
void sendMail(const QString &from, const QString &to, const QString &subject, const QString &body);
|
||||
|
||||
private slots:
|
||||
void readyRead();
|
||||
|
||||
private:
|
||||
QByteArray encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix=QByteArray());
|
||||
void ehlo();
|
||||
void helo();
|
||||
void parseEhloResponse(const QByteArray& code, bool continued, const QString& line);
|
||||
void authenticate();
|
||||
void startTLS();
|
||||
void authCramMD5(const QByteArray& challenge = QByteArray());
|
||||
void authPlain();
|
||||
void authLogin();
|
||||
void logError(const QString &msg);
|
||||
|
||||
private:
|
||||
enum states { Rcpt, EhloSent, HeloSent, EhloDone, EhloGreetReceived, AuthRequestSent, AuthSent,
|
||||
AuthUsernameSent, Authenticated, StartTLSSent, Data, Init, Body, Quit, Close };
|
||||
enum AuthType { AuthPlain, AuthLogin, AuthCramMD5 };
|
||||
|
||||
private:
|
||||
QByteArray message;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
QSslSocket *socket;
|
||||
#else
|
||||
QTcpSocket *socket;
|
||||
#endif
|
||||
QString from;
|
||||
QString rcpt;
|
||||
QString response;
|
||||
int state;
|
||||
QHash<QString, QString> extensions;
|
||||
QByteArray buffer;
|
||||
bool use_ssl;
|
||||
AuthType authType;
|
||||
QString username;
|
||||
QString password;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user