diff --git a/src/core/http/connection.h b/src/core/http/connection.h index 19c8acb6a..95498b1e6 100644 --- a/src/core/http/connection.h +++ b/src/core/http/connection.h @@ -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(); -public: - Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0); - ~Connection(); + private slots: + void read(); -private slots: - void read(); - -private: - static bool acceptsGzipEncoding(const QString &encoding); - void sendResponse(const Response &response); - - 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 diff --git a/src/core/http/irequesthandler.h b/src/core/http/irequesthandler.h index 2fab8d5c6..0d2cf9a5c 100644 --- a/src/core/http/irequesthandler.h +++ b/src/core/http/irequesthandler.h @@ -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 diff --git a/src/core/http/requestparser.h b/src/core/http/requestparser.h index ec8265e38..5ea8e05e4 100644 --- a/src/core/http/requestparser.h +++ b/src/core/http/requestparser.h @@ -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 */); + + private: + RequestParser(uint maxContentLength); + + 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 splitMultipartData(const QByteArray &data, const QByteArray &boundary); + + static bool parseHeaderLine(const QString &line, QPair &out); + static bool parseHeaderValue(const QString &value, QStringMap &out); + + const uint m_maxContentLength; + Request m_request; }; - - // 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); - - 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 splitMultipartData(const QByteArray &data, const QByteArray &boundary); - - static bool parseHeaderLine(const QString &line, QPair &out); - static bool parseHeaderValue(const QString &value, QStringMap &out); - - const uint m_maxContentLength; - Request m_request; -}; - } #endif // HTTP_REQUESTPARSER_H diff --git a/src/core/http/responsebuilder.h b/src/core/http/responsebuilder.h index 0a3b0d8f1..53e92c418 100644 --- a/src/core/http/responsebuilder.h +++ b/src/core/http/responsebuilder.h @@ -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(); -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; - Response response() const; - -private: - 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 diff --git a/src/core/http/responsegenerator.h b/src/core/http/responsegenerator.h index 43d73d709..e12b5c344 100644 --- a/src/core/http/responsegenerator.h +++ b/src/core/http/responsegenerator.h @@ -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 diff --git a/src/core/http/server.cpp b/src/core/http/server.cpp index 8bf905fec..f9f58e10c 100644 --- a/src/core/http/server.cpp +++ b/src/core/http/server.cpp @@ -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 diff --git a/src/core/http/server.h b/src/core/http/server.h index c8cb774c3..87d62fe92 100644 --- a/src/core/http/server.h +++ b/src/core/http/server.h @@ -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(); -public: - Server(IRequestHandler *requestHandler, QObject *parent = 0); - ~Server(); + #ifndef QT_NO_OPENSSL + void enableHttps(const QSslCertificate &certificate, const QSslKey &key); + void disableHttps(); + #endif -#ifndef QT_NO_OPENSSL - void enableHttps(const QSslCertificate &certificate, const QSslKey &key); - void disableHttps(); -#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 -}; + 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 diff --git a/src/core/http/types.h b/src/core/http/types.h index 5857031bb..5a2d9cf49 100644 --- a/src/core/http/types.h +++ b/src/core/http/types.h @@ -37,59 +37,57 @@ typedef QMap 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 files; + }; -struct Request -{ - QString method; - QString path; - QStringMap headers; - QStringMap gets; - QStringMap posts; - QMap 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) {} + }; - 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 diff --git a/src/core/net/dnsupdater.cpp b/src/core/net/dnsupdater.cpp index bb47528b8..11f0e392f 100644 --- a/src/core/net/dnsupdater.cpp +++ b/src/core/net/dnsupdater.cpp @@ -35,265 +35,271 @@ #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include #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+([^<]+)"); - 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"; + qDebug() << Q_FUNC_INFO; + if (reply->error()) { + // Error + qWarning() << Q_FUNC_INFO << "Error:" << reply->errorString(); } - } - // Clean up - reply->deleteLater(); - sender()->deleteLater(); + else { + // Parse response + QRegExp ipregex("Current IP Address:\\s+([^<]+)"); + QString ret = reply->readAll(); + if (ipregex.indexIn(ret) >= 0) { + QString ip_str = ipregex.cap(1); + qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ip_str; + QHostAddress new_ip(ip_str); + if (!new_ip.isNull()) { + if (m_lastIP != new_ip) { + qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS..."; + qDebug() << m_lastIP.toString() << "->" << new_ip.toString(); + m_lastIP = new_ip; + updateDNSService(); + } + } + else { + qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string"; + } + } + else { + qWarning() << Q_FUNC_INFO << "Regular expression failed ot capture the IP address"; + } + } + // Clean up + reply->deleteLater(); + sender()->deleteLater(); } void DNSUpdater::updateDNSService() { - qDebug() << Q_FUNC_INFO; - // Prepare request - QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), - SLOT(ipUpdateFinished(QNetworkReply*))); - m_lastIPCheckTime = QDateTime::currentDateTime(); - QNetworkRequest request; - request.setUrl(getUpdateUrl()); - request.setRawHeader("User-Agent", "qBittorrent/" VERSION" chris@qbittorrent.org"); - manager->get(request); + 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; } - 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_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_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_username != pref->getDynDNSUsername()) { + m_username = pref->getDynDNSUsername(); + if (m_username.length() < 4) { + logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL); + m_lastIP.clear(); + m_ipCheckTimer.stop(); + m_state = INVALID_CREDS; + return; + } + change = true; + } + if (m_password != pref->getDynDNSPassword()) { + m_password = pref->getDynDNSPassword(); + if (m_password.length() < 4) { + logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL); + m_lastIP.clear(); + m_ipCheckTimer.stop(); + m_state = INVALID_CREDS; + return; + } + change = true; } - 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(); } diff --git a/src/core/net/dnsupdater.h b/src/core/net/dnsupdater.h index 9a252405a..0cc809360 100644 --- a/src/core/net/dnsupdater.h +++ b/src/core/net/dnsupdater.h @@ -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(); -public slots: - void updateCredentials(); + static QUrl getRegistrationUrl(int service); -private slots: - void checkPublicIP(); - void ipRequestFinished(QNetworkReply* reply); - void updateDNSService(); - void ipUpdateFinished(QNetworkReply* reply); + public slots: + void updateCredentials(); -private: - QUrl getUpdateUrl() const; - void processIPUpdateReply(const QString &reply); + private slots: + void checkPublicIP(); + void ipRequestFinished(QNetworkReply *reply); + void updateDNSService(); + void ipUpdateFinished(QNetworkReply *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: + QUrl getUpdateUrl() const; + void processIPUpdateReply(const QString &reply); -private: - static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min - enum State { OK, INVALID_CREDS, FATAL }; -}; + private: + QHostAddress m_lastIP; + QDateTime m_lastIPCheckTime; + QTimer m_ipCheckTimer; + int m_state; + // Service creds + DNS::Service m_service; + QString m_domain; + QString m_username; + QString m_password; + private: + static const int IP_CHECK_INTERVAL_MS = 1800000; // 30 min + + enum State + { + OK, + INVALID_CREDS, + FATAL + }; + }; } #endif // DNSUPDATER_H diff --git a/src/core/net/smtp.cpp b/src/core/net/smtp.cpp index 1a685b48b..5123ccf81 100644 --- a/src/core/net/smtp.cpp +++ b/src/core/net/smtp.cpp @@ -50,433 +50,453 @@ #include #include -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); - } + const short DEFAULT_PORT = 25; + const short DEFAULT_PORT_SSL = 465; - 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) + 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); + } - 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 - } + 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) - // 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); -} + 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 + } -QByteArray determineFQDN() -{ - QString hostname = QHostInfo::localHostName(); - if (hostname.isEmpty()) - hostname = "localhost"; + // 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); + } - return hostname.toLocal8Bit(); -} + 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(); - } +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 + // 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); + 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(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; + 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 ..."; - 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(" 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(" 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(" 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 ..."; + 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(" 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(" 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(" 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); } diff --git a/src/core/net/smtp.h b/src/core/net/smtp.h index 9b332416d..4ca441e59 100644 --- a/src/core/net/smtp.h +++ b/src/core/net/smtp.h @@ -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 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 m_extensions; + QByteArray m_buffer; + bool m_useSsl; + AuthType m_authType; + QString m_username; + QString m_password; + }; } #endif