diff --git a/Changelog b/Changelog index 56f4b48c1..d5f8dbda6 100644 --- a/Changelog +++ b/Changelog @@ -1,6 +1,8 @@ * Unreleased - Christophe Dumez - v2.8.0 - - FEATURE: Added monochrome icon for light themes + - FEATURE: Added support for secure SMTP connection (SSL) + - FEATURE: Added support for SMTP authentication - BUGFIX: Change systray icon on the fly (no restart needed) + - COSMETIC: Added monochrome icon for light themes * Sun Mar 20 2011 - Christophe Dumez - v2.7.0 - FEATURE: Added search field for torrent content diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d4b46540a..cbe1b7cc6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -82,6 +82,7 @@ void qt_mac_set_dock_menu(QMenu *menu); #include "programupdater.h" #endif #include "powermanagement.h" +#include "smtp.h" using namespace libtorrent; @@ -97,6 +98,10 @@ using namespace libtorrent; // Constructor MainWindow::MainWindow(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), m_posInitialized(false), force_exit(false) { setupUi(this); + // TODO: Remove this + Smtp *sender = new Smtp(this); + sender->sendMail("notification@qbittorrent.org", Preferences().getMailNotificationEmail(), "title", "content"); + Preferences pref; ui_locked = pref.isUILocked(); setWindowTitle(tr("qBittorrent %1", "e.g: qBittorrent v0.x").arg(QString::fromUtf8(VERSION))); diff --git a/src/preferences/options.ui b/src/preferences/options.ui index ca1420b58..c37baaf46 100644 --- a/src/preferences/options.ui +++ b/src/preferences/options.ui @@ -506,9 +506,9 @@ 0 - -344 + -387 499 - 728 + 849 @@ -853,26 +853,76 @@ QGroupBox { false - - - - - Destination email: - - - - - - - - - - SMTP server: - - - - - + + + + + + + Destination email: + + + + + + + + + + SMTP server: + + + + + + + + + + Authentication + + + true + + + false + + + + + + Username: + + + + + + + + + + Password: + + + + + + + QLineEdit::Password + + + + + + + + + + This server requires a secure connection (SSL) + + + + @@ -927,7 +977,7 @@ QGroupBox { 0 0 - 499 + 392 426 @@ -1407,8 +1457,8 @@ QGroupBox { 0 0 - 514 - 384 + 328 + 306 @@ -1786,7 +1836,7 @@ QGroupBox { 0 0 - 499 + 486 408 @@ -2149,8 +2199,8 @@ QGroupBox { 0 0 - 514 - 384 + 316 + 236 @@ -2298,8 +2348,8 @@ QGroupBox { 0 0 - 514 - 384 + 86 + 16 @@ -2365,5 +2415,38 @@ QGroupBox { - + + + checkUploadLimit + toggled(bool) + spinUploadLimit + setEnabled(bool) + + + 367 + 61 + + + 448 + 62 + + + + + checkDownloadLimit + toggled(bool) + spinDownloadLimit + setEnabled(bool) + + + 377 + 81 + + + 430 + 87 + + + + diff --git a/src/preferences/options_imp.cpp b/src/preferences/options_imp.cpp index e0dd8c563..3e7602132 100644 --- a/src/preferences/options_imp.cpp +++ b/src/preferences/options_imp.cpp @@ -113,9 +113,6 @@ options_imp::options_imp(QWidget *parent): // Connect signals / slots // General tab connect(checkShowSystray, SIGNAL(toggled(bool)), this, SLOT(setSystrayOptionsState(bool))); - // Connection tab - connect(checkUploadLimit, SIGNAL(toggled(bool)), this, SLOT(enableUploadLimit(bool))); - connect(checkDownloadLimit, SIGNAL(toggled(bool)), this, SLOT(enableDownloadLimit(bool))); // Bittorrent tab connect(checkMaxConnecs, SIGNAL(toggled(bool)), this, SLOT(enableMaxConnecsLimit(bool))); connect(checkMaxConnecsPerTorrent, SIGNAL(toggled(bool)), this, SLOT(enableMaxConnecsLimitPerTorrent(bool))); @@ -158,6 +155,10 @@ options_imp::options_imp(QWidget *parent): connect(groupMailNotification, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(dest_email_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); connect(smtp_server_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); + connect(checkSmtpSSL, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); + connect(groupMailNotifAuth, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); + connect(mailNotifUsername, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); + connect(mailNotifPassword, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); connect(autoRunBox, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(autoRun_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); // Connection tab @@ -366,6 +367,10 @@ void options_imp::saveOptions(){ pref.setMailNotificationEnabled(groupMailNotification->isChecked()); pref.setMailNotificationEmail(dest_email_txt->text()); pref.setMailNotificationSMTP(smtp_server_txt->text()); + pref.setMailNotificationSMTPSSL(checkSmtpSSL->isChecked()); + pref.setMailNotificationSMTPAuth(groupMailNotifAuth->isChecked()); + pref.setMailNotificationSMTPUsername(mailNotifUsername->text()); + pref.setMailNotificationSMTPPassword(mailNotifPassword->text()); pref.setAutoRunEnabled(autoRunBox->isChecked()); pref.setAutoRunProgram(autoRun_txt->text()); pref.setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl()); @@ -522,6 +527,10 @@ void options_imp::loadOptions(){ groupMailNotification->setChecked(pref.isMailNotificationEnabled()); dest_email_txt->setText(pref.getMailNotificationEmail()); smtp_server_txt->setText(pref.getMailNotificationSMTP()); + checkSmtpSSL->setChecked(pref.getMailNotificationSMTPSSL()); + groupMailNotifAuth->setChecked(pref.getMailNotificationSMTPAuth()); + mailNotifUsername->setText(pref.getMailNotificationSMTPUsername()); + mailNotifPassword->setText(pref.getMailNotificationSMTPPassword()); autoRunBox->setChecked(pref.isAutoRunEnabled()); autoRun_txt->setText(pref.getAutoRunProgram()); intValue = pref.getActionOnDblClOnTorrentDl(); @@ -832,14 +841,6 @@ void options_imp::on_buttonBox_rejected(){ reject(); } -void options_imp::enableDownloadLimit(bool checked){ - if(checked){ - spinDownloadLimit->setEnabled(true); - }else{ - spinDownloadLimit->setEnabled(false); - } -} - bool options_imp::useAdditionDialog() const{ return checkAdditionDialog->isChecked(); } @@ -880,10 +881,6 @@ void options_imp::enableMaxUploadsLimitPerTorrent(bool checked){ } } -void options_imp::enableUploadLimit(bool checked){ - spinUploadLimit->setEnabled(checked); -} - void options_imp::enableApplyButton(){ applyButton->setEnabled(true); } diff --git a/src/preferences/options_imp.h b/src/preferences/options_imp.h index f255242c5..d28c258e3 100644 --- a/src/preferences/options_imp.h +++ b/src/preferences/options_imp.h @@ -55,8 +55,6 @@ public: QSize sizeFittingScreen(); protected slots: - void enableUploadLimit(bool checked); - void enableDownloadLimit(bool checked); void enableProxy(int comboIndex); void enableProxyAuth(bool checked); void enableMaxConnecsLimit(bool checked); diff --git a/src/preferences/preferences.h b/src/preferences/preferences.h index fad81255d..a7c634d0a 100644 --- a/src/preferences/preferences.h +++ b/src/preferences/preferences.h @@ -306,6 +306,38 @@ public: setValue(QString::fromUtf8("Preferences/MailNotification/smtp_server"), smtp_server); } + bool getMailNotificationSMTPSSL() const { + return value(QString::fromUtf8("Preferences/MailNotification/req_ssl"), false).toBool(); + } + + void setMailNotificationSMTPSSL(bool use) { + setValue(QString::fromUtf8("Preferences/MailNotification/req_ssl"), use); + } + + bool getMailNotificationSMTPAuth() const { + return value(QString::fromUtf8("Preferences/MailNotification/req_auth"), false).toBool(); + } + + void setMailNotificationSMTPAuth(bool use) { + setValue(QString::fromUtf8("Preferences/MailNotification/req_auth"), use); + } + + QString getMailNotificationSMTPUsername() const { + return value(QString::fromUtf8("Preferences/MailNotification/username")).toString(); + } + + void setMailNotificationSMTPUsername(const QString &username) { + setValue(QString::fromUtf8("Preferences/MailNotification/username"), username); + } + + QString getMailNotificationSMTPPassword() const { + return value(QString::fromUtf8("Preferences/MailNotification/password")).toString(); + } + + void setMailNotificationSMTPPassword(const QString &password) { + setValue(QString::fromUtf8("Preferences/MailNotification/password"), password); + } + int getActionOnDblClOnTorrentDl() const { return value(QString::fromUtf8("Preferences/Downloads/DblClOnTorDl"), 0).toInt(); } diff --git a/src/qtlibtorrent/qbtsession.cpp b/src/qtlibtorrent/qbtsession.cpp index 230fb7db3..9e465a056 100644 --- a/src/qtlibtorrent/qbtsession.cpp +++ b/src/qtlibtorrent/qbtsession.cpp @@ -2004,7 +2004,8 @@ void QBtSession::sendNotificationEmail(const QTorrentHandle &h) { content += tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds").arg(misc::userFriendlyDuration(h.active_time())) + "\n\n\n"; content += tr("Thank you for using qBittorrent.") + "\n"; // Send the notification email - new Smtp("notification@qbittorrent.org", Preferences().getMailNotificationEmail(), tr("[qBittorrent] %1 has finished downloading").arg(h.name()), content); + Smtp *sender = new Smtp(this); + sender->sendMail("notification@qbittorrent.org", Preferences().getMailNotificationEmail(), tr("[qBittorrent] %1 has finished downloading").arg(h.name()), content); } // Read alerts sent by the Bittorrent session diff --git a/src/smtp.cpp b/src/smtp.cpp index 57579e05f..fcc20574b 100644 --- a/src/smtp.cpp +++ b/src/smtp.cpp @@ -1,26 +1,105 @@ -/**************************************************************************** -** $Id: qt/smtp.h 3.3.6 edited Aug 31 2005 $ -** -** Copyright (C) 1992-2005 Trolltech AS. All rights reserved. -** -** This file is part of an example program for Qt. This example -** program may be used, distributed and modified without limitation. -** -*****************************************************************************/ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2011 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +/* + * This code is based on QxtSmtp from libqxt (http://libqxt.org) + */ + #include "smtp.h" #include "preferences.h" #include +#ifndef QT_NO_OPENSSL +#include +#else #include +#endif #include #include #include #include +#include + +const short DEFAULT_PORT = 25; +const short DEFAULT_PORT_SSL = 465; + +QByteArray hmacMD5(QByteArray key, const QByteArray &msg) +{ + const int blockSize = 64; // HMAC-MD5 block size + if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with MD5 compression + key = QCryptographicHash::hash(key, QCryptographicHash::Md5); + } -Smtp::Smtp(const QString &from, const QString &to, const QString &subject, const QString &body) { + 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); +} + +Smtp::Smtp(QObject *parent): QObject(parent), + state(Init), use_ssl(false) { +#ifndef QT_NO_OPENSSL + socket = new QSslSocket(this); +#else socket = new QTcpSocket(this); +#endif + + connect(socket, SIGNAL(readyRead()), SLOT(readyRead())); + connect(socket, SIGNAL(disconnected()), SLOT(deleteLater())); - connect( socket, SIGNAL( readyRead() ), this, SLOT( readyRead() ) ); + // Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html) + Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex() + == "750c783e6ab0b503eaa86e310a5db738"); + Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), + "Hi There").toHex() + == "9294727a3638bb1c13f48ef8158bfc9d"); +} + +Smtp::~Smtp() { + qDebug() << Q_FUNC_INFO; +} + +void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body) { + Preferences pref; QTextCodec* latin1 = QTextCodec::codecForName("latin1"); message = ""; message += encode_mime_header("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1); @@ -41,15 +120,135 @@ Smtp::Smtp(const QString &from, const QString &to, const QString &subject, const } this->from = from; rcpt = to; - state = Init; - socket->connectToHost(Preferences().getMailNotificationSMTP(), 25); - if(socket->waitForConnected ( 30000 )) { - qDebug("connected"); + // Authentication + if(pref.getMailNotificationSMTPAuth()) { + username = pref.getMailNotificationSMTPUsername(); + password = pref.getMailNotificationSMTPPassword(); + } + + // Connect to SMTP server +#ifndef QT_NO_OPENSSL + if(pref.getMailNotificationSMTPSSL()) { + socket->connectToHostEncrypted(pref.getMailNotificationSMTP(), DEFAULT_PORT_SSL); + use_ssl = true; } else { - t = 0; - deleteLater(); +#endif + socket->connectToHost(pref.getMailNotificationSMTP(), DEFAULT_PORT); + use_ssl = false; +#ifndef QT_NO_OPENSSL + } +#endif +} + +void Smtp::readyRead() +{ + qDebug() << Q_FUNC_INFO; + // SMTP is line-oriented + buffer += socket->readAll(); + while (true) + { + int pos = buffer.indexOf("\r\n"); + if (pos < 0) return; // Loop exit condition + QByteArray line = buffer.left(pos); + buffer = buffer.mid(pos + 2); + qDebug() << "Response line:" << line; + // Extract reponse code + QByteArray code = line.left(3); + + switch(state) { + case Init: { + if(code[0] == '2') { + // Connection was successful + ehlo(); + } else { + // TODO: Log something + qDebug() << "Connection failed, unrecognized reply:" << line; + state = Close; + } + break; + } + case EhloSent: + case HeloSent: + case EhloGreetReceived: + parseEhloResponse(code, line[3] != ' ', line.mid(4)); + break; +#ifndef QT_NO_OPENSSL + case StartTLSSent: + if (code == "220") { + socket->startClientEncryption(); + ehlo(); + } else { + authenticate(); + } + break; +#endif + case AuthRequestSent: + case AuthUsernameSent: + if (authType == AuthPlain) authPlain(); + else if (authType == AuthLogin) authLogin(); + else authCramMD5(line.mid(4)); + break; + case AuthSent: + case Authenticated: + if (code[0] == '2') { + qDebug() << "Login was OK, send ..."; + socket->write("mail from:<" + from.toAscii() + ">\r\n"); + socket->flush(); + state = Rcpt; + } else { + // Authentication failed! + // TODO: Log something + qDebug() << "Authentication not sent properly, aborting"; + state = Close; + } + break; + case Rcpt: + if (code[0] == '2') { + socket->write("rcpt to:<" + rcpt.toAscii() + ">\r\n"); + socket->flush(); + state = Data; + } else { + qDebug() << " not sent properly, aborting"; + state = Close; + } + break; + case Data: + if (code[0] == '2') { + socket->write("data\r\n"); + socket->flush(); + state = Body; + } else { + qDebug() << " not sent properly, aborting"; + state = Close; + } + break; + case Body: + if (code[0] == '3') { + socket->write(message + "\r\n.\r\n"); + socket->flush(); + state = Quit; + } else { + qDebug() << "data not sent properly, aborting"; + state = Close; + } + break; + case Quit: + if (code[0] == '2') { + socket->write("QUIT\r\n"); + socket->flush(); + // here, we just close. + state = Close; + } else { + qDebug() << "Message not sent properly, aborting"; + state = Close; + } + break; + default: + qDebug() << "Disconnecting from host"; + socket->disconnectFromHost(); + return; + } } - t = new QTextStream(socket); } QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix) @@ -91,102 +290,163 @@ QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QT return rv + line + "\r\n"; } -Smtp::~Smtp() -{ - if(t) - delete t; - delete socket; -} - -void Smtp::readyRead() +void Smtp::ehlo() { - - qDebug() << "readyRead"; - // SMTP is line-oriented - - QString responseLine; - do + QByteArray address = "127.0.0.1"; + foreach(const QHostAddress& addr, QNetworkInterface::allAddresses()) { - responseLine = socket->readLine(); - response += responseLine; + if (addr == QHostAddress::LocalHost || addr == QHostAddress::LocalHostIPv6) + continue; + address = addr.toString().toAscii(); + break; } - while ( socket->canReadLine() && responseLine[3] != ' ' ); - - qDebug("Response line: %s", qPrintable(response)); - - responseLine.truncate( 3 ); - + // Send EHLO + socket->write("ehlo "+ address + "\r\n"); + socket->flush(); + state = EhloSent; +} - if ( state == Init && responseLine[0] == '2' ) - { - // banner was okay, let's go on - QByteArray address = "127.0.0.1"; - foreach(const QHostAddress& addr, QNetworkInterface::allAddresses()) - { - if (addr == QHostAddress::LocalHost || addr == QHostAddress::LocalHostIPv6) - continue; - address = addr.toString().toAscii(); - break; +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..."; + socket->write("helo\r\n"); + socket->flush(); + state = HeloSent; + } else { + // Both EHLO and HELO failed, chances are this is NOT + // a SMTP server + // TODO: log something + qDebug() << "Both EHLO and HELO failed, aborting."; + state = Close; } - *t << "ehlo "+ address + "\r\n"; - t->flush(); - - state = Mail; + return; } - else if ( state == Mail || state == Mail2 ) - { - if(responseLine[0] == '2') { - // EHLO response was okay (well, it has to be) - *t << "mail from:<" << from << ">\r\n"; - t->flush(); - state = Rcpt; + if (state != EhloGreetReceived) { + if (!continued) { + // greeting only, no extensions + qDebug() << "No extension"; + state = EhloDone; } else { - if(state == Mail) { - // ehlo did not work, try helo instead - *t << "helo\r\n"; - t->flush(); - state = Mail2; - } + // greeting followed by extensions + state = EhloGreetReceived; + qDebug () << "EHLO greet received"; + return; } + } else { + qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper() + << line.section(' ', 1); + extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1); + if (!continued) + state = EhloDone; } - else if ( state == Rcpt && responseLine[0] == '2' ) - { + if (state != EhloDone) return; + if (extensions.contains("STARTTLS") && use_ssl) { + qDebug() << "STARTTLS"; + startTLS(); + } else { + authenticate(); + } +} - *t << "rcpt to:<" << rcpt << ">\r\n"; //r - t->flush(); - state = Data; +void Smtp::authenticate() +{ + qDebug() << Q_FUNC_INFO; + if (!extensions.contains("AUTH") || + username.isEmpty() || password.isEmpty()) { + // Skip authentication + qDebug() << "Skipping authentication..."; + state = Authenticated; + return; } - else if ( state == Data && responseLine[0] == '2' ) - { + // 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 + qDebug() << "Server does not support any of our AUTH modes, skip authentication..."; + state = Authenticated; + } +} + +void Smtp::startTLS() +{ + qDebug() << Q_FUNC_INFO; +#ifndef QT_NO_OPENSSL + socket->write("starttls\r\n"); + socket->flush(); + state = StartTLSSent; +#else + authenticate(); +#endif +} - *t << "data\r\n"; - t->flush(); - state = Body; +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.toAscii() + ' ' + + hmacMD5(password.toAscii(), QByteArray::fromBase64(challenge)).toHex(); + socket->write(response.toBase64() + "\r\n"); + socket->flush(); + state = AuthSent; } - else if ( state == Body && responseLine[0] == '3' ) - { +} - *t << message << "\r\n.\r\n"; - t->flush(); - state = Quit; +void Smtp::authPlain() +{ + if (state != AuthRequestSent) { + authType = AuthPlain; + // Prepare Auth string + QByteArray auth; + auth += '\0'; + auth += username.toAscii(); + qDebug() << "username: " << username.toAscii(); + auth += '\0'; + auth += password.toAscii(); + qDebug() << "password: " << password.toAscii(); + // Send it + socket->write("auth plain "+ auth.toBase64() + "\r\n"); + socket->flush(); + state = AuthSent; } - else if(state == Quit && responseLine[0] == '2') - { +} - *t << "QUIT\r\n"; - t->flush(); - // here, we just close. - state = Close; +void Smtp::authLogin() +{ + if (state != AuthRequestSent && state != AuthUsernameSent) { + socket->write("auth login\r\n"); + socket->flush(); + authType = AuthLogin; + state = AuthRequestSent; } - else if ( state == Close ) - { - deleteLater(); - return; + else if (state == AuthRequestSent) { + socket->write(username.toAscii().toBase64() + "\r\n"); + socket->flush(); + state = AuthUsernameSent; } - else - { - // something broke. - state = Close; + else { + socket->write(password.toAscii().toBase64() + "\r\n"); + socket->flush(); + state = AuthSent; } - response = ""; } diff --git a/src/smtp.h b/src/smtp.h index 8e3cbb456..51898def8 100644 --- a/src/smtp.h +++ b/src/smtp.h @@ -1,49 +1,97 @@ -/**************************************************************************** -** $Id: qt/smtp.h 3.3.6 edited Aug 31 2005 $ -** -** Copyright (C) 1992-2005 Trolltech AS. All rights reserved. -** -** This file is part of an example program for Qt. This example -** program may be used, distributed and modified without limitation. -** -*****************************************************************************/ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2011 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +/* + * This code is based on QxtSmtp from libqxt (http://libqxt.org) + */ #ifndef SMTP_H #define SMTP_H - #include #include #include +#include QT_BEGIN_NAMESPACE struct QTextStream; +#ifndef QT_NO_OPENSSL +struct QSslSocket; +#else struct QTcpSocket; -class QTextCodec; +#endif +struct QTextCodec; QT_END_NAMESPACE class Smtp : public QObject { Q_OBJECT public: - Smtp(const QString &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: void readyRead(); private: QByteArray encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix=QByteArray()); + void ehlo(); + void parseEhloResponse(const QByteArray& code, bool continued, const QString& line); + void authenticate(); + void startTLS(); + void authCramMD5(const QByteArray& challenge = QByteArray()); + void authPlain(); + void authLogin(); + +private: + enum states { Rcpt, EhloSent, HeloSent, EhloDone, EhloGreetReceived, AuthRequestSent, AuthSent, + AuthUsernameSent, Authenticated, StartTLSSent, Data, Init, Body, Quit, Close }; + enum AuthType { AuthPlain, AuthLogin, AuthCramMD5 }; private: QByteArray message; - QTextStream *t; +#ifndef QT_NO_OPENSSL + QSslSocket *socket; +#else QTcpSocket *socket; +#endif QString from; QString rcpt; QString response; - enum states{Rcpt,Mail,Mail2,Data,Init,Body,Quit,Close}; int state; - + QHash extensions; + QByteArray buffer; + bool use_ssl; + AuthType authType; + QString username; + QString password; }; #endif