Browse Source

Fix coding style (Issue #2192).

adaptive-webui-19844
Vladimir Golovnev (Glassez) 10 years ago
parent
commit
5f288d228d
  1. 36
      src/core/http/connection.h
  2. 14
      src/core/http/irequesthandler.h
  3. 50
      src/core/http/requestparser.h
  4. 32
      src/core/http/responsebuilder.h
  5. 12
      src/core/http/responsegenerator.h
  6. 2
      src/core/http/server.cpp
  7. 56
      src/core/http/server.h
  8. 88
      src/core/http/types.h
  9. 412
      src/core/net/dnsupdater.cpp
  10. 74
      src/core/net/dnsupdater.h
  11. 728
      src/core/net/smtp.cpp
  12. 98
      src/core/net/smtp.h

36
src/core/http/connection.h

@ -42,30 +42,28 @@ QT_END_NAMESPACE @@ -42,30 +42,28 @@ QT_END_NAMESPACE
namespace Http
{
class IRequestHandler;
class IRequestHandler;
class Connection : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Connection)
class Connection : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Connection)
public:
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
~Connection();
private slots:
void read();
public:
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
~Connection();
private:
static bool acceptsGzipEncoding(const QString &encoding);
void sendResponse(const Response &response);
private slots:
void read();
QTcpSocket *m_socket;
IRequestHandler *m_requestHandler;
QByteArray m_receivedData;
};
private:
static bool acceptsGzipEncoding(const QString &encoding);
void sendResponse(const Response &response);
QTcpSocket *m_socket;
IRequestHandler *m_requestHandler;
QByteArray m_receivedData;
};
}
#endif // HTTP_CONNECTION_H

14
src/core/http/irequesthandler.h

@ -33,14 +33,12 @@ @@ -33,14 +33,12 @@
namespace Http
{
class IRequestHandler
{
public:
virtual ~IRequestHandler() {}
virtual Response processRequest(const Request &request, const Environment &env) = 0;
};
class IRequestHandler
{
public:
virtual ~IRequestHandler() {}
virtual Response processRequest(const Request &request, const Environment &env) = 0;
};
}
#endif // HTTP_IREQUESTHANDLER_H

50
src/core/http/requestparser.h

@ -36,39 +36,37 @@ @@ -36,39 +36,37 @@
namespace Http
{
class RequestParser
{
public:
enum ErrorCode
class RequestParser
{
NoError = 0,
IncompleteRequest,
BadRequest
};
public:
enum ErrorCode
{
NoError = 0,
IncompleteRequest,
BadRequest
};
// when result != NoError parsed request is undefined
// Warning! Header names are converted to lower-case.
static ErrorCode parse(const QByteArray &data, Request &request, uint maxContentLength = 10000000 /* ~10MB */);
// when result != NoError parsed request is undefined
// Warning! Header names are converted to lower-case.
static ErrorCode parse(const QByteArray &data, Request &request, uint maxContentLength = 10000000 /* ~10MB */);
private:
RequestParser(uint maxContentLength);
private:
RequestParser(uint maxContentLength);
ErrorCode parseHttpRequest(const QByteArray &data, Request &request);
ErrorCode parseHttpRequest(const QByteArray &data, Request &request);
bool parseHttpHeader(const QByteArray &data);
bool parseStartingLine(const QString &line);
bool parseContent(const QByteArray &data);
bool parseFormData(const QByteArray &data);
QList<QByteArray> splitMultipartData(const QByteArray &data, const QByteArray &boundary);
bool parseHttpHeader(const QByteArray &data);
bool parseStartingLine(const QString &line);
bool parseContent(const QByteArray &data);
bool parseFormData(const QByteArray &data);
QList<QByteArray> splitMultipartData(const QByteArray &data, const QByteArray &boundary);
static bool parseHeaderLine(const QString &line, QPair<QString, QString> &out);
static bool parseHeaderValue(const QString &value, QStringMap &out);
const uint m_maxContentLength;
Request m_request;
};
static bool parseHeaderLine(const QString &line, QPair<QString, QString> &out);
static bool parseHeaderValue(const QString &value, QStringMap &out);
const uint m_maxContentLength;
Request m_request;
};
}
#endif // HTTP_REQUESTPARSER_H

32
src/core/http/responsebuilder.h

@ -34,27 +34,25 @@ @@ -34,27 +34,25 @@
namespace Http
{
class ResponseBuilder : public QObject
{
public:
explicit ResponseBuilder(QObject *parent = 0);
class ResponseBuilder : public QObject
{
public:
explicit ResponseBuilder(QObject *parent = 0);
protected:
void status(uint code = 200, const QString &text = QLatin1String("OK"));
void header(const QString &name, const QString &value);
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
void print(const QByteArray &data, const QString &type = CONTENT_TYPE_HTML);
void clear();
Response response() const;
protected:
void status(uint code = 200, const QString &text = QLatin1String("OK"));
void header(const QString &name, const QString &value);
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
void print(const QByteArray &data, const QString &type = CONTENT_TYPE_HTML);
void clear();
private:
void print_impl(const QByteArray &data, const QString &type);
Response response() const;
Response m_response;
};
private:
void print_impl(const QByteArray &data, const QString &type);
Response m_response;
};
}
#endif // HTTP_RESPONSEBUILDER_H

12
src/core/http/responsegenerator.h

@ -37,13 +37,11 @@ @@ -37,13 +37,11 @@
namespace Http
{
class ResponseGenerator
{
public:
static QByteArray generate(Response response);
};
class ResponseGenerator
{
public:
static QByteArray generate(Response response);
};
}
#endif // HTTP_RESPONSEGENERATOR_H

2
src/core/http/server.cpp

@ -38,7 +38,7 @@ @@ -38,7 +38,7 @@
using namespace Http;
Server::Server(IRequestHandler *requestHandler, QObject* parent)
Server::Server(IRequestHandler *requestHandler, QObject *parent)
: QTcpServer(parent)
, m_requestHandler(requestHandler)
#ifndef QT_NO_OPENSSL

56
src/core/http/server.h

@ -41,40 +41,38 @@ @@ -41,40 +41,38 @@
namespace Http
{
class IRequestHandler;
class Connection;
class IRequestHandler;
class Connection;
class Server : public QTcpServer
{
Q_OBJECT
Q_DISABLE_COPY(Server)
class Server : public QTcpServer
{
Q_OBJECT
Q_DISABLE_COPY(Server)
public:
Server(IRequestHandler *requestHandler, QObject *parent = 0);
~Server();
#ifndef QT_NO_OPENSSL
void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
void disableHttps();
#endif
public:
Server(IRequestHandler *requestHandler, QObject *parent = 0);
~Server();
private:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
void incomingConnection(qintptr socketDescriptor);
#else
void incomingConnection(int socketDescriptor);
#endif
#ifndef QT_NO_OPENSSL
void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
void disableHttps();
#endif
private:
IRequestHandler *m_requestHandler;
#ifndef QT_NO_OPENSSL
bool m_https;
QSslCertificate m_certificate;
QSslKey m_key;
#endif
};
private:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
void incomingConnection(qintptr socketDescriptor);
#else
void incomingConnection(int socketDescriptor);
#endif
private:
IRequestHandler *m_requestHandler;
#ifndef QT_NO_OPENSSL
bool m_https;
QSslCertificate m_certificate;
QSslKey m_key;
#endif
};
}
#endif // HTTP_SERVER_H

88
src/core/http/types.h

@ -37,59 +37,57 @@ typedef QMap<QString, QString> QStringMap; @@ -37,59 +37,57 @@ typedef QMap<QString, QString> QStringMap;
namespace Http
{
const QString HEADER_SET_COOKIE = "Set-Cookie";
const QString HEADER_CONTENT_TYPE = "Content-Type";
const QString HEADER_CONTENT_ENCODING = "Content-Encoding";
const QString HEADER_CONTENT_LENGTH = "Content-Length";
const QString HEADER_CACHE_CONTROL = "Cache-Control";
const QString HEADER_SET_COOKIE = "Set-Cookie";
const QString HEADER_CONTENT_TYPE = "Content-Type";
const QString HEADER_CONTENT_ENCODING = "Content-Encoding";
const QString HEADER_CONTENT_LENGTH = "Content-Length";
const QString HEADER_CACHE_CONTROL = "Cache-Control";
const QString CONTENT_TYPE_CSS = "text/css; charset=UTF-8";
const QString CONTENT_TYPE_GIF = "image/gif";
const QString CONTENT_TYPE_HTML = "text/html; charset=UTF-8";
const QString CONTENT_TYPE_JS = "text/javascript; charset=UTF-8";
const QString CONTENT_TYPE_PNG = "image/png";
const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8";
const QString CONTENT_TYPE_CSS = "text/css; charset=UTF-8";
const QString CONTENT_TYPE_GIF = "image/gif";
const QString CONTENT_TYPE_HTML = "text/html; charset=UTF-8";
const QString CONTENT_TYPE_JS = "text/javascript; charset=UTF-8";
const QString CONTENT_TYPE_PNG = "image/png";
const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8";
struct Environment
{
QHostAddress clientAddress;
};
struct Environment
{
QHostAddress clientAddress;
};
struct UploadedFile
{
QString filename; // original filename
QString type; // MIME type
QByteArray data; // File data
};
struct UploadedFile
{
QString filename; // original filename
QString type; // MIME type
QByteArray data; // File data
};
struct Request
{
QString method;
QString path;
QStringMap headers;
QStringMap gets;
QStringMap posts;
QMap<QString, UploadedFile> files;
};
struct Request
{
QString method;
QString path;
QStringMap headers;
QStringMap gets;
QStringMap posts;
QMap<QString, UploadedFile> files;
};
struct ResponseStatus
{
uint code;
QString text;
struct ResponseStatus
{
uint code;
QString text;
ResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {}
};
struct Response
{
ResponseStatus status;
QStringMap headers;
QByteArray content;
ResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {}
};
Response(uint code = 200, const QString& text = "OK"): status(code, text) {}
};
struct Response
{
ResponseStatus status;
QStringMap headers;
QByteArray content;
Response(uint code = 200, const QString& text = "OK"): status(code, text) {}
};
}
#endif // HTTP_TYPES_H

412
src/core/net/dnsupdater.cpp

@ -35,265 +35,271 @@ @@ -35,265 +35,271 @@
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
#include <QUrlQuery>
#endif
#include "dnsupdater.h"
#include "core/logger.h"
#include "dnsupdater.h"
using namespace Net;
DNSUpdater::DNSUpdater(QObject *parent) :
QObject(parent), m_state(OK), m_service(DNS::NONE)
DNSUpdater::DNSUpdater(QObject *parent)
: QObject(parent)
, m_state(OK)
, m_service(DNS::NONE)
{
updateCredentials();
updateCredentials();
// Load saved settings from previous session
const Preferences* const pref = Preferences::instance();
m_lastIPCheckTime = pref->getDNSLastUpd();
m_lastIP = QHostAddress(pref->getDNSLastIP());
// 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();
// 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();
}
// 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());
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);
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();
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";
}
} 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();
// 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);
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;
QUrl url;
#ifdef QT_NO_OPENSSL
url.setScheme("http");
url.setScheme("http");
#else
url.setScheme("https");
url.setScheme("https");
#endif
url.setUserName(m_username);
url.setPassword(m_password);
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");
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());
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);
QUrlQuery urlQuery(url);
urlQuery.addQueryItem("hostname", m_domain);
urlQuery.addQueryItem("myip", m_lastIP.toString());
url.setQuery(urlQuery);
#endif
Q_ASSERT(url.isValid());
Q_ASSERT(url.isValid());
qDebug() << Q_FUNC_INFO << url.toString();
return url;
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();
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);
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();
// 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;
}
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;
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;
}
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;
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;
}
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;
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;
}
change = true;
}
if (m_state == INVALID_CREDS && change) {
m_state = OK; // Try again
m_ipCheckTimer.start();
checkPublicIP();
}
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();
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();
}

74
src/core/net/dnsupdater.h

@ -39,48 +39,52 @@ @@ -39,48 +39,52 @@
#include "core/preferences.h"
namespace Net
{
{
// Based on http://www.dyndns.com/developers/specs/
class DNSUpdater : public QObject
{
Q_OBJECT
/*!
* 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:
explicit DNSUpdater(QObject *parent = 0);
~DNSUpdater();
static QUrl getRegistrationUrl(int service);
public slots:
void updateCredentials();
public slots:
void updateCredentials();
private slots:
void checkPublicIP();
void ipRequestFinished(QNetworkReply* reply);
void updateDNSService();
void ipUpdateFinished(QNetworkReply* reply);
private slots:
void checkPublicIP();
void ipRequestFinished(QNetworkReply *reply);
void updateDNSService();
void ipUpdateFinished(QNetworkReply *reply);
private:
QUrl getUpdateUrl() const;
void processIPUpdateReply(const QString &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:
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 };
};
private:
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
enum State
{
OK,
INVALID_CREDS,
FATAL
};
};
}
#endif // DNSUPDATER_H

728
src/core/net/smtp.cpp

@ -50,433 +50,453 @@ @@ -50,433 +50,453 @@
#include <QCryptographicHash>
#include <QStringList>
namespace {
const short DEFAULT_PORT = 25;
const short DEFAULT_PORT_SSL = 465;
QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
namespace
{
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);
}
const short DEFAULT_PORT = 25;
const short DEFAULT_PORT_SSL = 465;
QByteArray determineFQDN()
{
QString hostname = QHostInfo::localHostName();
if (hostname.isEmpty())
hostname = "localhost";
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);
}
return hostname.toLocal8Bit();
}
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) {
Smtp::Smtp(QObject *parent)
: QObject(parent)
, m_state(Init)
, m_useSsl(false)
{
#ifndef QT_NO_OPENSSL
socket = new QSslSocket(this);
m_socket = new QSslSocket(this);
#else
socket = new QTcpSocket(this);
m_socket = new QTcpSocket(this);
#endif
connect(socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(socket, SIGNAL(disconnected()), SLOT(deleteLater()));
connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(m_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");
// 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;
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
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");
m_message = "";
m_message += encodeMimeHeader("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1);
m_message += encodeMimeHeader("From", from, latin1);
m_message += encodeMimeHeader("Subject", subject, latin1);
m_message += encodeMimeHeader("To", to, latin1);
m_message += "MIME-Version: 1.0\r\n";
m_message += "Content-Type: text/plain; charset=UTF-8\r\n";
m_message += "Content-Transfer-Encoding: base64\r\n";
m_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)
m_message += b.mid(i, 78);
m_from = from;
m_rcpt = to;
// Authentication
if (pref->getMailNotificationSMTPAuth()) {
m_username = pref->getMailNotificationSMTPUsername();
m_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 {
if (pref->getMailNotificationSMTPSSL()) {
m_socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
m_useSsl = true;
}
else {
#endif
socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
use_ssl = false;
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
m_useSsl = 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;
qDebug() << Q_FUNC_INFO;
// SMTP is line-oriented
m_buffer += m_socket->readAll();
while (true) {
int pos = m_buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition
QByteArray line = m_buffer.left(pos);
m_buffer = m_buffer.mid(pos + 2);
qDebug() << "Response line:" << line;
// Extract reponse code
QByteArray code = line.left(3);
switch (m_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);
m_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;
case StartTLSSent:
if (code == "220") {
m_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;
case AuthRequestSent:
case AuthUsernameSent:
if (m_authType == AuthPlain) authPlain();
else if (m_authType == AuthLogin) authLogin();
else authCramMD5(line.mid(4));
break;
case AuthSent:
case Authenticated:
if (code[0] == '2') {
qDebug() << "Sending <mail from>...";
m_socket->write("mail from:<" + m_from.toLatin1() + ">\r\n");
m_socket->flush();
m_state = Rcpt;
}
else {
// Authentication failed!
logError("Authentication failed, msg: "+line);
m_state = Close;
}
break;
case Rcpt:
if (code[0] == '2') {
m_socket->write("rcpt to:<" + m_rcpt.toLatin1() + ">\r\n");
m_socket->flush();
m_state = Data;
}
else {
logError("<mail from> was rejected by server, msg: "+line);
m_state = Close;
}
break;
case Data:
if (code[0] == '2') {
m_socket->write("data\r\n");
m_socket->flush();
m_state = Body;
}
else {
logError("<Rcpt to> was rejected by server, msg: "+line);
m_state = Close;
}
break;
case Body:
if (code[0] == '3') {
m_socket->write(m_message + "\r\n.\r\n");
m_socket->flush();
m_state = Quit;
}
else {
logError("<data> was rejected by server, msg: "+line);
m_state = Close;
}
break;
case Quit:
if (code[0] == '2') {
m_socket->write("QUIT\r\n");
m_socket->flush();
// here, we just close.
m_state = Close;
}
else {
logError("Message was rejected by the server, error: "+line);
m_state = Close;
}
break;
default:
qDebug() << "Disconnecting from host";
m_socket->disconnectFromHost();
return;
}
}
}
}
QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix)
QByteArray Smtp::encodeMimeHeader(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;
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) {
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 = line + base64.mid(i, 4);
}
line += "?="; // end encoded-word atom
}
line += "?="; // end encoded-word atom
}
return rv + line + "\r\n";
return rv + line + "\r\n";
}
void Smtp::ehlo()
{
QByteArray address = determineFQDN();
socket->write("ehlo " + address + "\r\n");
socket->flush();
state = EhloSent;
QByteArray address = determineFQDN();
m_socket->write("ehlo " + address + "\r\n");
m_socket->flush();
m_state = EhloSent;
}
void Smtp::helo()
{
QByteArray address = determineFQDN();
socket->write("helo " + address + "\r\n");
socket->flush();
state = HeloSent;
QByteArray address = determineFQDN();
m_socket->write("helo " + address + "\r\n");
m_socket->flush();
m_state = HeloSent;
}
void Smtp::parseEhloResponse(const QByteArray& code, bool continued, const QString& line)
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;
if (code != "250") {
// Error
if (m_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);
m_state = Close;
}
return;
}
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;
if (m_state != EhloGreetReceived) {
if (!continued) {
// greeting only, no extensions
qDebug() << "No extension";
m_state = EhloDone;
}
else {
// greeting followed by extensions
m_state = EhloGreetReceived;
qDebug () << "EHLO greet received";
return;
}
}
else {
qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper()
<< line.section(' ', 1);
m_extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1);
if (!continued)
m_state = EhloDone;
}
if (m_state != EhloDone) return;
if (m_extensions.contains("STARTTLS") && m_useSsl) {
qDebug() << "STARTTLS";
startTLS();
}
else {
authenticate();
}
} 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");
}
qDebug() << Q_FUNC_INFO;
if (!m_extensions.contains("AUTH") ||
m_username.isEmpty() || m_password.isEmpty()) {
// Skip authentication
qDebug() << "Skipping authentication...";
m_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()
m_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 = m_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("|"));
m_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()
m_buffer.push_front("250 QBT FAKE RESPONSE\r\n");
}
}
void Smtp::startTLS()
{
qDebug() << Q_FUNC_INFO;
qDebug() << Q_FUNC_INFO;
#ifndef QT_NO_OPENSSL
socket->write("starttls\r\n");
socket->flush();
state = StartTLSSent;
m_socket->write("starttls\r\n");
m_socket->flush();
m_state = StartTLSSent;
#else
authenticate();
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;
}
if (m_state != AuthRequestSent) {
m_socket->write("auth cram-md5\r\n");
m_socket->flush();
m_authType = AuthCramMD5;
m_state = AuthRequestSent;
}
else {
QByteArray response = m_username.toLatin1() + ' '
+ hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
m_socket->write(response.toBase64() + "\r\n");
m_socket->flush();
m_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;
}
if (m_state != AuthRequestSent) {
m_authType = AuthPlain;
// Prepare Auth string
QByteArray auth;
auth += '\0';
auth += m_username.toLatin1();
qDebug() << "username: " << m_username.toLatin1();
auth += '\0';
auth += m_password.toLatin1();
qDebug() << "password: " << m_password.toLatin1();
// Send it
m_socket->write("auth plain "+ auth.toBase64() + "\r\n");
m_socket->flush();
m_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;
}
if ((m_state != AuthRequestSent) && (m_state != AuthUsernameSent)) {
m_socket->write("auth login\r\n");
m_socket->flush();
m_authType = AuthLogin;
m_state = AuthRequestSent;
}
else if (m_state == AuthRequestSent) {
m_socket->write(m_username.toLatin1().toBase64() + "\r\n");
m_socket->flush();
m_state = AuthUsernameSent;
}
else {
m_socket->write(m_password.toLatin1().toBase64() + "\r\n");
m_socket->flush();
m_state = AuthSent;
}
}
void Smtp::logError(const QString &msg)
{
qDebug() << "Email Notification Error:" << msg;
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
qDebug() << "Email Notification Error:" << msg;
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
}

98
src/core/net/smtp.h

@ -52,54 +52,74 @@ QT_END_NAMESPACE @@ -52,54 +52,74 @@ QT_END_NAMESPACE
namespace Net
{
class Smtp : public QObject
{
Q_OBJECT
class Smtp : public QObject {
Q_OBJECT
public:
Smtp(QObject *parent = 0);
~Smtp();
public:
Smtp(QObject *parent = 0);
~Smtp();
void sendMail(const QString &from, const QString &to, const QString &subject, const QString &body);
void sendMail(const QString &m_from, const QString &to, const QString &subject, const QString &body);
private slots:
void readyRead();
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
};
private:
enum states { Rcpt, EhloSent, HeloSent, EhloDone, EhloGreetReceived, AuthRequestSent, AuthSent,
AuthUsernameSent, Authenticated, StartTLSSent, Data, Init, Body, Quit, Close };
enum AuthType { AuthPlain, AuthLogin, AuthCramMD5 };
enum AuthType
{
AuthPlain,
AuthLogin,
AuthCramMD5
};
private:
QByteArray message;
QByteArray encodeMimeHeader(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);
QByteArray m_message;
#ifndef QT_NO_OPENSSL
QSslSocket *socket;
QSslSocket *m_socket;
#else
QTcpSocket *socket;
QTcpSocket *m_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;
};
QString m_from;
QString m_rcpt;
QString m_response;
int m_state;
QHash<QString, QString> m_extensions;
QByteArray m_buffer;
bool m_useSsl;
AuthType m_authType;
QString m_username;
QString m_password;
};
}
#endif

Loading…
Cancel
Save