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. 72
      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
namespace Http namespace Http
{ {
class IRequestHandler;
class IRequestHandler; class Connection : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Connection)
class Connection : public QObject public:
{ Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
Q_OBJECT ~Connection();
Q_DISABLE_COPY(Connection)
public:
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
~Connection();
private slots:
void read();
private: private slots:
static bool acceptsGzipEncoding(const QString &encoding); void read();
void sendResponse(const Response &response);
QTcpSocket *m_socket; private:
IRequestHandler *m_requestHandler; static bool acceptsGzipEncoding(const QString &encoding);
QByteArray m_receivedData; void sendResponse(const Response &response);
};
QTcpSocket *m_socket;
IRequestHandler *m_requestHandler;
QByteArray m_receivedData;
};
} }
#endif // HTTP_CONNECTION_H #endif // HTTP_CONNECTION_H

14
src/core/http/irequesthandler.h

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

50
src/core/http/requestparser.h

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

32
src/core/http/responsebuilder.h

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

12
src/core/http/responsegenerator.h

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

2
src/core/http/server.cpp

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

56
src/core/http/server.h

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

88
src/core/http/types.h

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

412
src/core/net/dnsupdater.cpp

@ -35,265 +35,271 @@
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
#include <QUrlQuery> #include <QUrlQuery>
#endif #endif
#include "dnsupdater.h"
#include "core/logger.h" #include "core/logger.h"
#include "dnsupdater.h"
using namespace Net; using namespace Net;
DNSUpdater::DNSUpdater(QObject *parent) : DNSUpdater::DNSUpdater(QObject *parent)
QObject(parent), m_state(OK), m_service(DNS::NONE) : QObject(parent)
, m_state(OK)
, m_service(DNS::NONE)
{ {
updateCredentials(); updateCredentials();
// Load saved settings from previous session // Load saved settings from previous session
const Preferences* const pref = Preferences::instance(); const Preferences *const pref = Preferences::instance();
m_lastIPCheckTime = pref->getDNSLastUpd(); m_lastIPCheckTime = pref->getDNSLastUpd();
m_lastIP = QHostAddress(pref->getDNSLastIP()); m_lastIP = QHostAddress(pref->getDNSLastIP());
// Start IP checking timer // Start IP checking timer
m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS); m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS);
connect(&m_ipCheckTimer, SIGNAL(timeout()), SLOT(checkPublicIP())); connect(&m_ipCheckTimer, SIGNAL(timeout()), SLOT(checkPublicIP()));
m_ipCheckTimer.start(); m_ipCheckTimer.start();
// Check lastUpdate to avoid flooding // Check lastUpdate to avoid flooding
if (!m_lastIPCheckTime.isValid() || if (!m_lastIPCheckTime.isValid()
m_lastIPCheckTime.secsTo(QDateTime::currentDateTime())*1000 > IP_CHECK_INTERVAL_MS) { || (m_lastIPCheckTime.secsTo(QDateTime::currentDateTime()) * 1000 > IP_CHECK_INTERVAL_MS)) {
checkPublicIP(); checkPublicIP();
} }
} }
DNSUpdater::~DNSUpdater() { DNSUpdater::~DNSUpdater()
// Save lastupdate time and last ip {
Preferences* const pref = Preferences::instance(); // Save lastupdate time and last ip
pref->setDNSLastUpd(m_lastIPCheckTime); Preferences *const pref = Preferences::instance();
pref->setDNSLastIP(m_lastIP.toString()); pref->setDNSLastUpd(m_lastIPCheckTime);
pref->setDNSLastIP(m_lastIP.toString());
} }
void DNSUpdater::checkPublicIP() void DNSUpdater::checkPublicIP()
{ {
Q_ASSERT(m_state == OK); Q_ASSERT(m_state == OK);
QNetworkAccessManager *manager = new QNetworkAccessManager(this); QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(ipRequestFinished(QNetworkReply *)));
SLOT(ipRequestFinished(QNetworkReply*))); m_lastIPCheckTime = QDateTime::currentDateTime();
m_lastIPCheckTime = QDateTime::currentDateTime(); QNetworkRequest request;
QNetworkRequest request; request.setUrl(QUrl("http://checkip.dyndns.org"));
request.setUrl(QUrl("http://checkip.dyndns.org")); request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org");
request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org"); manager->get(request);
manager->get(request);
} }
void DNSUpdater::ipRequestFinished(QNetworkReply *reply) void DNSUpdater::ipRequestFinished(QNetworkReply *reply)
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if (reply->error()) { if (reply->error()) {
// Error // Error
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString(); qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
} else { }
// Parse response else {
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>"); // Parse response
QString ret = reply->readAll(); QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
if (ipregex.indexIn(ret) >= 0) { QString ret = reply->readAll();
QString ip_str = ipregex.cap(1); if (ipregex.indexIn(ret) >= 0) {
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str; QString ip_str = ipregex.cap(1);
QHostAddress new_ip(ip_str); qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str;
if (!new_ip.isNull()) { QHostAddress new_ip(ip_str);
if (m_lastIP != new_ip) { if (!new_ip.isNull()) {
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS..."; if (m_lastIP != new_ip) {
qDebug() << m_lastIP.toString() << "->" << new_ip.toString(); qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
m_lastIP = new_ip; qDebug() << m_lastIP.toString() << "->" << new_ip.toString();
updateDNSService(); 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
// Clean up reply->deleteLater();
reply->deleteLater(); sender()->deleteLater();
sender()->deleteLater();
} }
void DNSUpdater::updateDNSService() void DNSUpdater::updateDNSService()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
// Prepare request // Prepare request
QNetworkAccessManager *manager = new QNetworkAccessManager(this); QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), connect(manager, SIGNAL(finished(QNetworkReply *)), SLOT(ipUpdateFinished(QNetworkReply *)));
SLOT(ipUpdateFinished(QNetworkReply*))); m_lastIPCheckTime = QDateTime::currentDateTime();
m_lastIPCheckTime = QDateTime::currentDateTime(); QNetworkRequest request;
QNetworkRequest request; request.setUrl(getUpdateUrl());
request.setUrl(getUpdateUrl()); request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org");
request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org"); manager->get(request);
manager->get(request);
} }
QUrl DNSUpdater::getUpdateUrl() const QUrl DNSUpdater::getUpdateUrl() const
{ {
QUrl url; QUrl url;
#ifdef QT_NO_OPENSSL #ifdef QT_NO_OPENSSL
url.setScheme("http"); url.setScheme("http");
#else #else
url.setScheme("https"); url.setScheme("https");
#endif #endif
url.setUserName(m_username); url.setUserName(m_username);
url.setPassword(m_password); url.setPassword(m_password);
Q_ASSERT(!m_lastIP.isNull()); Q_ASSERT(!m_lastIP.isNull());
// Service specific // Service specific
switch(m_service) { switch(m_service) {
case DNS::DYNDNS: case DNS::DYNDNS:
url.setHost("members.dyndns.org"); url.setHost("members.dyndns.org");
break; break;
case DNS::NOIP: case DNS::NOIP:
url.setHost("dynupdate.no-ip.com"); url.setHost("dynupdate.no-ip.com");
break; break;
default: default:
qWarning() << "Unrecognized Dynamic DNS service!"; qWarning() << "Unrecognized Dynamic DNS service!";
Q_ASSERT(0); Q_ASSERT(0);
} }
url.setPath("/nic/update"); url.setPath("/nic/update");
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
url.addQueryItem("hostname", m_domain); url.addQueryItem("hostname", m_domain);
url.addQueryItem("myip", m_lastIP.toString()); url.addQueryItem("myip", m_lastIP.toString());
#else #else
QUrlQuery urlQuery(url); QUrlQuery urlQuery(url);
urlQuery.addQueryItem("hostname", m_domain); urlQuery.addQueryItem("hostname", m_domain);
urlQuery.addQueryItem("myip", m_lastIP.toString()); urlQuery.addQueryItem("myip", m_lastIP.toString());
url.setQuery(urlQuery); url.setQuery(urlQuery);
#endif #endif
Q_ASSERT(url.isValid()); Q_ASSERT(url.isValid());
qDebug() << Q_FUNC_INFO << url.toString(); qDebug() << Q_FUNC_INFO << url.toString();
return url; return url;
} }
void DNSUpdater::ipUpdateFinished(QNetworkReply *reply) void DNSUpdater::ipUpdateFinished(QNetworkReply *reply)
{ {
if (reply->error()) { if (reply->error()) {
// Error // Error
qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString(); qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString();
} else { }
// Pase reply else {
processIPUpdateReply(reply->readAll()); // Pase reply
} processIPUpdateReply(reply->readAll());
// Clean up }
reply->deleteLater(); // Clean up
sender()->deleteLater(); reply->deleteLater();
sender()->deleteLater();
} }
void DNSUpdater::processIPUpdateReply(const QString &reply) void DNSUpdater::processIPUpdateReply(const QString &reply)
{ {
Logger* const logger = Logger::instance(); Logger *const logger = Logger::instance();
qDebug() << Q_FUNC_INFO << reply; qDebug() << Q_FUNC_INFO << reply;
QString code = reply.split(" ").first(); QString code = reply.split(" ").first();
qDebug() << Q_FUNC_INFO << "Code:" << code; qDebug() << Q_FUNC_INFO << "Code:" << code;
if (code == "good" || code == "nochg") { if (code == "good" || code == "nochg") {
logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO); logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO);
return; return;
} }
if (code == "911" || code == "dnserr") { 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->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(); m_lastIP.clear();
// It will retry in 30 minutes because the timer was not stopped if (code == "nohost") {
return; logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL);
} m_state = INVALID_CREDS;
// Everything bellow is an error, stop updating until the user updates something return;
m_ipCheckTimer.stop(); }
m_lastIP.clear(); if (code == "badauth") {
if (code == "nohost") { logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL); m_state = INVALID_CREDS;
m_state = INVALID_CREDS; return;
return; }
} if (code == "badagent") {
if (code == "badauth") { logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."),
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL); Log::CRITICAL);
m_state = INVALID_CREDS; m_state = FATAL;
return; return;
} }
if (code == "badagent") { if (code == "!donator") {
logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."), 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); Log::CRITICAL);
m_state = FATAL; m_state = FATAL;
return; return;
} }
if (code == "!donator") { if (code == "abuse") {
logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"), logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
Log::CRITICAL); m_state = FATAL;
m_state = FATAL; return;
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() void DNSUpdater::updateCredentials()
{ {
if (m_state == FATAL) return; if (m_state == FATAL) return;
Preferences* const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
Logger* const logger = Logger::instance(); Logger *const logger = Logger::instance();
bool change = false; bool change = false;
// Get DNS service information // Get DNS service information
if (m_service != pref->getDynDNSService()) { if (m_service != pref->getDynDNSService()) {
m_service = pref->getDynDNSService(); m_service = pref->getDynDNSService();
change = true; change = true;
} }
if (m_domain != pref->getDynDomainName()) { if (m_domain != pref->getDynDomainName()) {
m_domain = pref->getDynDomainName(); m_domain = pref->getDynDomainName();
QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$"); QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$");
if (domain_regex.indexIn(m_domain) < 0) { if (domain_regex.indexIn(m_domain) < 0) {
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
m_lastIP.clear(); m_lastIP.clear();
m_ipCheckTimer.stop(); m_ipCheckTimer.stop();
m_state = INVALID_CREDS; m_state = INVALID_CREDS;
return; return;
}
change = true;
} }
change = true; if (m_username != pref->getDynDNSUsername()) {
} m_username = pref->getDynDNSUsername();
if (m_username != pref->getDynDNSUsername()) { if (m_username.length() < 4) {
m_username = pref->getDynDNSUsername(); logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL);
if (m_username.length() < 4) { m_lastIP.clear();
logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL); m_ipCheckTimer.stop();
m_lastIP.clear(); m_state = INVALID_CREDS;
m_ipCheckTimer.stop(); return;
m_state = INVALID_CREDS; }
return; change = true;
} }
change = true; if (m_password != pref->getDynDNSPassword()) {
} m_password = pref->getDynDNSPassword();
if (m_password != pref->getDynDNSPassword()) { if (m_password.length() < 4) {
m_password = pref->getDynDNSPassword(); logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL);
if (m_password.length() < 4) { m_lastIP.clear();
logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL); m_ipCheckTimer.stop();
m_lastIP.clear(); m_state = INVALID_CREDS;
m_ipCheckTimer.stop(); return;
m_state = INVALID_CREDS; }
return; change = true;
} }
change = true;
}
if (m_state == INVALID_CREDS && change) { if ((m_state == INVALID_CREDS) && change) {
m_state = OK; // Try again m_state = OK; // Try again
m_ipCheckTimer.start(); m_ipCheckTimer.start();
checkPublicIP(); checkPublicIP();
} }
} }
QUrl DNSUpdater::getRegistrationUrl(int service) QUrl DNSUpdater::getRegistrationUrl(int service)
{ {
switch(service) { switch(service) {
case DNS::DYNDNS: case DNS::DYNDNS:
return QUrl("https://www.dyndns.com/account/services/hosts/add.html"); return QUrl("https://www.dyndns.com/account/services/hosts/add.html");
case DNS::NOIP: case DNS::NOIP:
return QUrl("http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html"); return QUrl("http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html");
default: default:
Q_ASSERT(0); Q_ASSERT(0);
} }
return QUrl(); return QUrl();
} }

72
src/core/net/dnsupdater.h

@ -40,47 +40,51 @@
namespace Net namespace Net
{ {
// Based on http://www.dyndns.com/developers/specs/
class DNSUpdater : public QObject
{
Q_OBJECT
/*! public:
* Based on http://www.dyndns.com/developers/specs/ explicit DNSUpdater(QObject *parent = 0);
*/ ~DNSUpdater();
class DNSUpdater : public QObject
{ static QUrl getRegistrationUrl(int service);
Q_OBJECT
public:
explicit DNSUpdater(QObject *parent = 0);
~DNSUpdater();
static QUrl getRegistrationUrl(int service);
public slots: public slots:
void updateCredentials(); void updateCredentials();
private slots: private slots:
void checkPublicIP(); void checkPublicIP();
void ipRequestFinished(QNetworkReply* reply); void ipRequestFinished(QNetworkReply *reply);
void updateDNSService(); void updateDNSService();
void ipUpdateFinished(QNetworkReply* reply); void ipUpdateFinished(QNetworkReply *reply);
private: private:
QUrl getUpdateUrl() const; QUrl getUpdateUrl() const;
void processIPUpdateReply(const QString &reply); void processIPUpdateReply(const QString &reply);
private: private:
QHostAddress m_lastIP; QHostAddress m_lastIP;
QDateTime m_lastIPCheckTime; QDateTime m_lastIPCheckTime;
QTimer m_ipCheckTimer; QTimer m_ipCheckTimer;
int m_state; int m_state;
// Service creds // Service creds
DNS::Service m_service; DNS::Service m_service;
QString m_domain; QString m_domain;
QString m_username; QString m_username;
QString m_password; QString m_password;
private: private:
static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min
enum State { OK, INVALID_CREDS, FATAL };
};
enum State
{
OK,
INVALID_CREDS,
FATAL
};
};
} }
#endif // DNSUPDATER_H #endif // DNSUPDATER_H

728
src/core/net/smtp.cpp

@ -50,433 +50,453 @@
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QStringList> #include <QStringList>
namespace { 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 const short DEFAULT_PORT = 25;
if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression const short DEFAULT_PORT_SSL = 465;
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() QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
{ {
QString hostname = QHostInfo::localHostName(); const int blockSize = 64; // HMAC-MD5 block size
if (hostname.isEmpty()) if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression
hostname = "localhost"; 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 } // namespace
using namespace Net; using namespace Net;
Smtp::Smtp(QObject *parent): QObject(parent), Smtp::Smtp(QObject *parent)
state(Init), use_ssl(false) { : QObject(parent)
, m_state(Init)
, m_useSsl(false)
{
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
socket = new QSslSocket(this); m_socket = new QSslSocket(this);
#else #else
socket = new QTcpSocket(this); m_socket = new QTcpSocket(this);
#endif #endif
connect(socket, SIGNAL(readyRead()), SLOT(readyRead())); connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(socket, SIGNAL(disconnected()), SLOT(deleteLater())); connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html) // Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex() Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
== "750c783e6ab0b503eaa86e310a5db738"); == "750c783e6ab0b503eaa86e310a5db738");
Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), "Hi There").toHex()
"Hi There").toHex() == "9294727a3638bb1c13f48ef8158bfc9d");
== "9294727a3638bb1c13f48ef8158bfc9d");
} }
Smtp::~Smtp() { Smtp::~Smtp()
qDebug() << Q_FUNC_INFO; {
qDebug() << Q_FUNC_INFO;
} }
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body) { 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"); const Preferences* const pref = Preferences::instance();
message = ""; QTextCodec* latin1 = QTextCodec::codecForName("latin1");
message += encode_mime_header("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1); m_message = "";
message += encode_mime_header("From", from, latin1); m_message += encodeMimeHeader("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1);
message += encode_mime_header("Subject", subject, latin1); m_message += encodeMimeHeader("From", from, latin1);
message += encode_mime_header("To", to, latin1); m_message += encodeMimeHeader("Subject", subject, latin1);
message += "MIME-Version: 1.0\r\n"; m_message += encodeMimeHeader("To", to, latin1);
message += "Content-Type: text/plain; charset=UTF-8\r\n"; m_message += "MIME-Version: 1.0\r\n";
message += "Content-Transfer-Encoding: base64\r\n"; m_message += "Content-Type: text/plain; charset=UTF-8\r\n";
message += "\r\n"; m_message += "Content-Transfer-Encoding: base64\r\n";
// Encode the body in base64 m_message += "\r\n";
QString crlf_body = body; // Encode the body in base64
QByteArray b = crlf_body.replace("\n","\r\n").toUtf8().toBase64(); QString crlf_body = body;
int ct = b.length(); QByteArray b = crlf_body.replace("\n","\r\n").toUtf8().toBase64();
for (int i = 0; i < ct; i += 78) int ct = b.length();
{ for (int i = 0; i < ct; i += 78)
message += b.mid(i, 78); m_message += b.mid(i, 78);
} m_from = from;
this->from = from; m_rcpt = to;
rcpt = to; // Authentication
// Authentication if (pref->getMailNotificationSMTPAuth()) {
if (pref->getMailNotificationSMTPAuth()) { m_username = pref->getMailNotificationSMTPUsername();
username = pref->getMailNotificationSMTPUsername(); m_password = pref->getMailNotificationSMTPPassword();
password = pref->getMailNotificationSMTPPassword(); }
}
// Connect to SMTP server
// Connect to SMTP server
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
if (pref->getMailNotificationSMTPSSL()) { if (pref->getMailNotificationSMTPSSL()) {
socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL); m_socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
use_ssl = true; m_useSsl = true;
} else { }
else {
#endif #endif
socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT); m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
use_ssl = false; m_useSsl = false;
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
} }
#endif #endif
} }
void Smtp::readyRead() void Smtp::readyRead()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
// SMTP is line-oriented // SMTP is line-oriented
buffer += socket->readAll(); m_buffer += m_socket->readAll();
while (true) while (true) {
{ int pos = m_buffer.indexOf("\r\n");
int pos = buffer.indexOf("\r\n"); if (pos < 0) return; // Loop exit condition
if (pos < 0) return; // Loop exit condition QByteArray line = m_buffer.left(pos);
QByteArray line = buffer.left(pos); m_buffer = m_buffer.mid(pos + 2);
buffer = buffer.mid(pos + 2); qDebug() << "Response line:" << line;
qDebug() << "Response line:" << line; // Extract reponse code
// Extract reponse code QByteArray code = line.left(3);
QByteArray code = line.left(3);
switch (m_state) {
switch(state) { case Init: {
case Init: { if (code[0] == '2') {
if (code[0] == '2') { // The server may send a multiline greeting/INIT/220 response.
// The server may send a multiline greeting/INIT/220 response. // We wait until it finishes.
// We wait until it finishes. if (line[3] != ' ')
if (line[3] != ' ') break;
break; // Connection was successful
// Connection was successful ehlo();
ehlo(); }
} else { else {
logError("Connection failed, unrecognized reply: "+line); logError("Connection failed, unrecognized reply: "+line);
state = Close; m_state = Close;
} }
break; break;
} }
case EhloSent: case EhloSent:
case HeloSent: case HeloSent:
case EhloGreetReceived: case EhloGreetReceived:
parseEhloResponse(code, line[3] != ' ', line.mid(4)); parseEhloResponse(code, line[3] != ' ', line.mid(4));
break; break;
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
case StartTLSSent: case StartTLSSent:
if (code == "220") { if (code == "220") {
socket->startClientEncryption(); m_socket->startClientEncryption();
ehlo(); ehlo();
} else { }
authenticate(); else {
} authenticate();
break; }
break;
#endif #endif
case AuthRequestSent: case AuthRequestSent:
case AuthUsernameSent: case AuthUsernameSent:
if (authType == AuthPlain) authPlain(); if (m_authType == AuthPlain) authPlain();
else if (authType == AuthLogin) authLogin(); else if (m_authType == AuthLogin) authLogin();
else authCramMD5(line.mid(4)); else authCramMD5(line.mid(4));
break; break;
case AuthSent: case AuthSent:
case Authenticated: case Authenticated:
if (code[0] == '2') { if (code[0] == '2') {
qDebug() << "Sending <mail from>..."; qDebug() << "Sending <mail from>...";
socket->write("mail from:<" + from.toLatin1() + ">\r\n"); m_socket->write("mail from:<" + m_from.toLatin1() + ">\r\n");
socket->flush(); m_socket->flush();
state = Rcpt; m_state = Rcpt;
} else { }
// Authentication failed! else {
logError("Authentication failed, msg: "+line); // Authentication failed!
state = Close; logError("Authentication failed, msg: "+line);
} m_state = Close;
break; }
case Rcpt: break;
if (code[0] == '2') { case Rcpt:
socket->write("rcpt to:<" + rcpt.toLatin1() + ">\r\n"); if (code[0] == '2') {
socket->flush(); m_socket->write("rcpt to:<" + m_rcpt.toLatin1() + ">\r\n");
state = Data; m_socket->flush();
} else { m_state = Data;
logError("<mail from> was rejected by server, msg: "+line); }
state = Close; else {
} logError("<mail from> was rejected by server, msg: "+line);
break; m_state = Close;
case Data: }
if (code[0] == '2') { break;
socket->write("data\r\n"); case Data:
socket->flush(); if (code[0] == '2') {
state = Body; m_socket->write("data\r\n");
} else { m_socket->flush();
logError("<Rcpt to> was rejected by server, msg: "+line); m_state = Body;
state = Close; }
} else {
break; logError("<Rcpt to> was rejected by server, msg: "+line);
case Body: m_state = Close;
if (code[0] == '3') { }
socket->write(message + "\r\n.\r\n"); break;
socket->flush(); case Body:
state = Quit; if (code[0] == '3') {
} else { m_socket->write(m_message + "\r\n.\r\n");
logError("<data> was rejected by server, msg: "+line); m_socket->flush();
state = Close; m_state = Quit;
} }
break; else {
case Quit: logError("<data> was rejected by server, msg: "+line);
if (code[0] == '2') { m_state = Close;
socket->write("QUIT\r\n"); }
socket->flush(); break;
// here, we just close. case Quit:
state = Close; if (code[0] == '2') {
} else { m_socket->write("QUIT\r\n");
logError("Message was rejected by the server, error: "+line); m_socket->flush();
state = Close; // here, we just close.
} m_state = Close;
break; }
default: else {
qDebug() << "Disconnecting from host"; logError("Message was rejected by the server, error: "+line);
socket->disconnectFromHost(); m_state = Close;
return; }
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 rv = "";
QByteArray line = key.toLatin1() + ": "; QByteArray line = key.toLatin1() + ": ";
if (!prefix.isEmpty()) line += prefix; if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value)) { if (!value.contains("=?") && latin1->canEncode(value)) {
bool firstWord = true; bool firstWord = true;
foreach (const QByteArray& word, value.toLatin1().split(' ')) { foreach (const QByteArray& word, value.toLatin1().split(' ')) {
if (line.size() > 78) { if (line.size() > 78) {
rv = rv + line + "\r\n"; rv = rv + line + "\r\n";
line.clear(); line.clear();
} }
if (firstWord) if (firstWord)
line += word; line += word;
else else
line += " " + word; line += " " + word;
firstWord = false; firstWord = false;
}
} }
} else { else {
// The text cannot be losslessly encoded as Latin-1. Therefore, we // The text cannot be losslessly encoded as Latin-1. Therefore, we
// must use base64 encoding. // must use base64 encoding.
QByteArray utf8 = value.toUtf8(); QByteArray utf8 = value.toUtf8();
// Use base64 encoding // Use base64 encoding
QByteArray base64 = utf8.toBase64(); QByteArray base64 = utf8.toBase64();
int ct = base64.length(); int ct = base64.length();
line += "=?utf-8?b?"; line += "=?utf-8?b?";
for (int i = 0; i < ct; i += 4) { for (int i = 0; i < ct; i += 4) {
/*if (line.length() > 72) { /*if (line.length() > 72) {
rv += line + "?\n\r"; rv += line + "?\n\r";
line = " =?utf-8?b?"; 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() void Smtp::ehlo()
{ {
QByteArray address = determineFQDN(); QByteArray address = determineFQDN();
socket->write("ehlo " + address + "\r\n"); m_socket->write("ehlo " + address + "\r\n");
socket->flush(); m_socket->flush();
state = EhloSent; m_state = EhloSent;
} }
void Smtp::helo() void Smtp::helo()
{ {
QByteArray address = determineFQDN(); QByteArray address = determineFQDN();
socket->write("helo " + address + "\r\n"); m_socket->write("helo " + address + "\r\n");
socket->flush(); m_socket->flush();
state = HeloSent; 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") { if (code != "250") {
// Error // Error
if (state == EhloSent) { if (m_state == EhloSent) {
// try to send HELO instead of EHLO // try to send HELO instead of EHLO
qDebug() << "EHLO failed, trying HELO instead..."; qDebug() << "EHLO failed, trying HELO instead...";
helo(); helo();
} else { }
// Both EHLO and HELO failed, chances are this is NOT else {
// a SMTP server // Both EHLO and HELO failed, chances are this is NOT
logError("Both EHLO and HELO failed, msg: "+line); // a SMTP server
state = Close; logError("Both EHLO and HELO failed, msg: "+line);
m_state = Close;
}
return;
} }
return;
} if (m_state != EhloGreetReceived) {
if (state != EhloGreetReceived) { if (!continued) {
if (!continued) { // greeting only, no extensions
// greeting only, no extensions qDebug() << "No extension";
qDebug() << "No extension"; m_state = EhloDone;
state = EhloDone; }
} else { else {
// greeting followed by extensions // greeting followed by extensions
state = EhloGreetReceived; m_state = EhloGreetReceived;
qDebug () << "EHLO greet received"; qDebug () << "EHLO greet received";
return; 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() void Smtp::authenticate()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if (!extensions.contains("AUTH") || if (!m_extensions.contains("AUTH") ||
username.isEmpty() || password.isEmpty()) { m_username.isEmpty() || m_password.isEmpty()) {
// Skip authentication // Skip authentication
qDebug() << "Skipping authentication..."; qDebug() << "Skipping authentication...";
state = Authenticated; m_state = Authenticated;
// At this point the server will not send any response // At this point the server will not send any response
// So fill the buffer with a fake one to pass the tests // So fill the buffer with a fake one to pass the tests
// in readyRead() // in readyRead()
buffer.push_front("250 QBT FAKE RESPONSE\r\n"); m_buffer.push_front("250 QBT FAKE RESPONSE\r\n");
return; return;
} }
// AUTH extension is supported, check which // AUTH extension is supported, check which
// authentication modes are supported by // authentication modes are supported by
// the server // the server
QStringList auth = extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts); QStringList auth = m_extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts);
if (auth.contains("CRAM-MD5")) { if (auth.contains("CRAM-MD5")) {
qDebug() << "Using CRAM-MD5 authentication..."; qDebug() << "Using CRAM-MD5 authentication...";
authCramMD5(); authCramMD5();
} }
else if (auth.contains("PLAIN")) { else if (auth.contains("PLAIN")) {
qDebug() << "Using PLAIN authentication..."; qDebug() << "Using PLAIN authentication...";
authPlain(); authPlain();
} }
else if (auth.contains("LOGIN")) { else if (auth.contains("LOGIN")) {
qDebug() << "Using LOGIN authentication..."; qDebug() << "Using LOGIN authentication...";
authLogin(); authLogin();
} else { }
// Skip authentication else {
logError("The SMTP server does not seem to support any of the authentications modes " // Skip authentication
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, " logError("The SMTP server does not seem to support any of the authentications modes "
"knowing it is likely to fail... Server Auth Modes: "+auth.join("|")); "we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
state = Authenticated; "knowing it is likely to fail... Server Auth Modes: "+auth.join("|"));
// At this point the server will not send any response m_state = Authenticated;
// So fill the buffer with a fake one to pass the tests // At this point the server will not send any response
// in readyRead() // So fill the buffer with a fake one to pass the tests
buffer.push_front("250 QBT FAKE RESPONSE\r\n"); // in readyRead()
} m_buffer.push_front("250 QBT FAKE RESPONSE\r\n");
}
} }
void Smtp::startTLS() void Smtp::startTLS()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
socket->write("starttls\r\n"); m_socket->write("starttls\r\n");
socket->flush(); m_socket->flush();
state = StartTLSSent; m_state = StartTLSSent;
#else #else
authenticate(); authenticate();
#endif #endif
} }
void Smtp::authCramMD5(const QByteArray& challenge) void Smtp::authCramMD5(const QByteArray& challenge)
{ {
if (state != AuthRequestSent) { if (m_state != AuthRequestSent) {
socket->write("auth cram-md5\r\n"); m_socket->write("auth cram-md5\r\n");
socket->flush(); m_socket->flush();
authType = AuthCramMD5; m_authType = AuthCramMD5;
state = AuthRequestSent; m_state = AuthRequestSent;
} else { }
QByteArray response = username.toLatin1() + ' ' else {
+ hmacMD5(password.toLatin1(), QByteArray::fromBase64(challenge)).toHex(); QByteArray response = m_username.toLatin1() + ' '
socket->write(response.toBase64() + "\r\n"); + hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
socket->flush(); m_socket->write(response.toBase64() + "\r\n");
state = AuthSent; m_socket->flush();
} m_state = AuthSent;
}
} }
void Smtp::authPlain() void Smtp::authPlain()
{ {
if (state != AuthRequestSent) { if (m_state != AuthRequestSent) {
authType = AuthPlain; m_authType = AuthPlain;
// Prepare Auth string // Prepare Auth string
QByteArray auth; QByteArray auth;
auth += '\0'; auth += '\0';
auth += username.toLatin1(); auth += m_username.toLatin1();
qDebug() << "username: " << username.toLatin1(); qDebug() << "username: " << m_username.toLatin1();
auth += '\0'; auth += '\0';
auth += password.toLatin1(); auth += m_password.toLatin1();
qDebug() << "password: " << password.toLatin1(); qDebug() << "password: " << m_password.toLatin1();
// Send it // Send it
socket->write("auth plain "+ auth.toBase64() + "\r\n"); m_socket->write("auth plain "+ auth.toBase64() + "\r\n");
socket->flush(); m_socket->flush();
state = AuthSent; m_state = AuthSent;
} }
} }
void Smtp::authLogin() void Smtp::authLogin()
{ {
if (state != AuthRequestSent && state != AuthUsernameSent) { if ((m_state != AuthRequestSent) && (m_state != AuthUsernameSent)) {
socket->write("auth login\r\n"); m_socket->write("auth login\r\n");
socket->flush(); m_socket->flush();
authType = AuthLogin; m_authType = AuthLogin;
state = AuthRequestSent; m_state = AuthRequestSent;
} }
else if (state == AuthRequestSent) { else if (m_state == AuthRequestSent) {
socket->write(username.toLatin1().toBase64() + "\r\n"); m_socket->write(m_username.toLatin1().toBase64() + "\r\n");
socket->flush(); m_socket->flush();
state = AuthUsernameSent; m_state = AuthUsernameSent;
} }
else { else {
socket->write(password.toLatin1().toBase64() + "\r\n"); m_socket->write(m_password.toLatin1().toBase64() + "\r\n");
socket->flush(); m_socket->flush();
state = AuthSent; m_state = AuthSent;
} }
} }
void Smtp::logError(const QString &msg) void Smtp::logError(const QString &msg)
{ {
qDebug() << "Email Notification Error:" << msg; qDebug() << "Email Notification Error:" << msg;
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL); Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
} }

98
src/core/net/smtp.h

@ -52,54 +52,74 @@ QT_END_NAMESPACE
namespace Net namespace Net
{ {
class Smtp : public QObject
{
Q_OBJECT
class Smtp : public QObject { public:
Q_OBJECT Smtp(QObject *parent = 0);
~Smtp();
public: void sendMail(const QString &m_from, const QString &to, const QString &subject, const QString &body);
Smtp(QObject *parent = 0);
~Smtp();
void sendMail(const QString &from, const QString &to, const QString &subject, const QString &body);
private slots: private slots:
void readyRead(); void readyRead();
private: private:
QByteArray encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix=QByteArray()); enum States
void ehlo(); {
void helo(); Rcpt,
void parseEhloResponse(const QByteArray& code, bool continued, const QString& line); EhloSent,
void authenticate(); HeloSent,
void startTLS(); EhloDone,
void authCramMD5(const QByteArray& challenge = QByteArray()); EhloGreetReceived,
void authPlain(); AuthRequestSent,
void authLogin(); AuthSent,
void logError(const QString &msg); AuthUsernameSent,
Authenticated,
StartTLSSent,
Data,
Init,
Body,
Quit,
Close
};
private: enum AuthType
enum states { Rcpt, EhloSent, HeloSent, EhloDone, EhloGreetReceived, AuthRequestSent, AuthSent, {
AuthUsernameSent, Authenticated, StartTLSSent, Data, Init, Body, Quit, Close }; AuthPlain,
enum AuthType { AuthPlain, AuthLogin, AuthCramMD5 }; AuthLogin,
AuthCramMD5
};
private: QByteArray encodeMimeHeader(const QString &key, const QString &value, QTextCodec *latin1, const QByteArray &prefix = QByteArray());
QByteArray message; 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 #ifndef QT_NO_OPENSSL
QSslSocket *socket; QSslSocket *m_socket;
#else #else
QTcpSocket *socket; QTcpSocket *m_socket;
#endif #endif
QString from; QString m_from;
QString rcpt; QString m_rcpt;
QString response; QString m_response;
int state; int m_state;
QHash<QString, QString> extensions; QHash<QString, QString> m_extensions;
QByteArray buffer; QByteArray m_buffer;
bool use_ssl; bool m_useSsl;
AuthType authType; AuthType m_authType;
QString username; QString m_username;
QString password; QString m_password;
}; };
} }
#endif #endif

Loading…
Cancel
Save