Browse Source

FEATURE: Added support for secure SMTP connection (SSL)

FEATURE: Added support for SMTP authentication
adaptive-webui-19844
Christophe Dumez 14 years ago
parent
commit
58bfa6f1bb
  1. 4
      Changelog
  2. 5
      src/mainwindow.cpp
  3. 107
      src/preferences/options.ui
  4. 27
      src/preferences/options_imp.cpp
  5. 2
      src/preferences/options_imp.h
  6. 32
      src/preferences/preferences.h
  7. 3
      src/qtlibtorrent/qbtsession.cpp
  8. 442
      src/smtp.cpp
  9. 78
      src/smtp.h

4
Changelog

@ -1,6 +1,8 @@
* Unreleased - Christophe Dumez <chris@qbittorrent.org> - v2.8.0 * Unreleased - Christophe Dumez <chris@qbittorrent.org> - 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) - BUGFIX: Change systray icon on the fly (no restart needed)
- COSMETIC: Added monochrome icon for light themes
* Sun Mar 20 2011 - Christophe Dumez <chris@qbittorrent.org> - v2.7.0 * Sun Mar 20 2011 - Christophe Dumez <chris@qbittorrent.org> - v2.7.0
- FEATURE: Added search field for torrent content - FEATURE: Added search field for torrent content

5
src/mainwindow.cpp

@ -82,6 +82,7 @@ void qt_mac_set_dock_menu(QMenu *menu);
#include "programupdater.h" #include "programupdater.h"
#endif #endif
#include "powermanagement.h" #include "powermanagement.h"
#include "smtp.h"
using namespace libtorrent; using namespace libtorrent;
@ -97,6 +98,10 @@ using namespace libtorrent;
// Constructor // Constructor
MainWindow::MainWindow(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), m_posInitialized(false), force_exit(false) { MainWindow::MainWindow(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), m_posInitialized(false), force_exit(false) {
setupUi(this); setupUi(this);
// TODO: Remove this
Smtp *sender = new Smtp(this);
sender->sendMail("notification@qbittorrent.org", Preferences().getMailNotificationEmail(), "title", "content");
Preferences pref; Preferences pref;
ui_locked = pref.isUILocked(); ui_locked = pref.isUILocked();
setWindowTitle(tr("qBittorrent %1", "e.g: qBittorrent v0.x").arg(QString::fromUtf8(VERSION))); setWindowTitle(tr("qBittorrent %1", "e.g: qBittorrent v0.x").arg(QString::fromUtf8(VERSION)));

107
src/preferences/options.ui

@ -506,9 +506,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>-344</y> <y>-387</y>
<width>499</width> <width>499</width>
<height>728</height> <height>849</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -853,7 +853,9 @@ QGroupBox {
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QGridLayout" name="gridLayout_15"> <layout class="QVBoxLayout" name="verticalLayout_21">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
@ -874,6 +876,54 @@ QGroupBox {
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="smtp_server_txt"/> <widget class="QLineEdit" name="smtp_server_txt"/>
</item> </item>
<item row="3" column="1">
<widget class="QGroupBox" name="groupMailNotifAuth">
<property name="title">
<string>Authentication</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Username:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="mailNotifUsername"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="mailNotifPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="checkSmtpSSL">
<property name="text">
<string>This server requires a secure connection (SSL)</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -927,7 +977,7 @@ QGroupBox {
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>499</width> <width>392</width>
<height>426</height> <height>426</height>
</rect> </rect>
</property> </property>
@ -1407,8 +1457,8 @@ QGroupBox {
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>514</width> <width>328</width>
<height>384</height> <height>306</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_33"> <layout class="QVBoxLayout" name="verticalLayout_33">
@ -1786,7 +1836,7 @@ QGroupBox {
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>499</width> <width>486</width>
<height>408</height> <height>408</height>
</rect> </rect>
</property> </property>
@ -2149,8 +2199,8 @@ QGroupBox {
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>514</width> <width>316</width>
<height>384</height> <height>236</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_23"> <layout class="QVBoxLayout" name="verticalLayout_23">
@ -2298,8 +2348,8 @@ QGroupBox {
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>514</width> <width>86</width>
<height>384</height> <height>16</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_36"/> <layout class="QVBoxLayout" name="verticalLayout_36"/>
@ -2365,5 +2415,38 @@ QGroupBox {
<resources> <resources>
<include location="../icons.qrc"/> <include location="../icons.qrc"/>
</resources> </resources>
<connections/> <connections>
<connection>
<sender>checkUploadLimit</sender>
<signal>toggled(bool)</signal>
<receiver>spinUploadLimit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>367</x>
<y>61</y>
</hint>
<hint type="destinationlabel">
<x>448</x>
<y>62</y>
</hint>
</hints>
</connection>
<connection>
<sender>checkDownloadLimit</sender>
<signal>toggled(bool)</signal>
<receiver>spinDownloadLimit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>377</x>
<y>81</y>
</hint>
<hint type="destinationlabel">
<x>430</x>
<y>87</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

27
src/preferences/options_imp.cpp

@ -113,9 +113,6 @@ options_imp::options_imp(QWidget *parent):
// Connect signals / slots // Connect signals / slots
// General tab // General tab
connect(checkShowSystray, SIGNAL(toggled(bool)), this, SLOT(setSystrayOptionsState(bool))); 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 // Bittorrent tab
connect(checkMaxConnecs, SIGNAL(toggled(bool)), this, SLOT(enableMaxConnecsLimit(bool))); connect(checkMaxConnecs, SIGNAL(toggled(bool)), this, SLOT(enableMaxConnecsLimit(bool)));
connect(checkMaxConnecsPerTorrent, SIGNAL(toggled(bool)), this, SLOT(enableMaxConnecsLimitPerTorrent(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(groupMailNotification, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(dest_email_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); connect(dest_email_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
connect(smtp_server_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(autoRunBox, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton()));
connect(autoRun_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); connect(autoRun_txt, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
// Connection tab // Connection tab
@ -366,6 +367,10 @@ void options_imp::saveOptions(){
pref.setMailNotificationEnabled(groupMailNotification->isChecked()); pref.setMailNotificationEnabled(groupMailNotification->isChecked());
pref.setMailNotificationEmail(dest_email_txt->text()); pref.setMailNotificationEmail(dest_email_txt->text());
pref.setMailNotificationSMTP(smtp_server_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.setAutoRunEnabled(autoRunBox->isChecked());
pref.setAutoRunProgram(autoRun_txt->text()); pref.setAutoRunProgram(autoRun_txt->text());
pref.setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl()); pref.setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl());
@ -522,6 +527,10 @@ void options_imp::loadOptions(){
groupMailNotification->setChecked(pref.isMailNotificationEnabled()); groupMailNotification->setChecked(pref.isMailNotificationEnabled());
dest_email_txt->setText(pref.getMailNotificationEmail()); dest_email_txt->setText(pref.getMailNotificationEmail());
smtp_server_txt->setText(pref.getMailNotificationSMTP()); 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()); autoRunBox->setChecked(pref.isAutoRunEnabled());
autoRun_txt->setText(pref.getAutoRunProgram()); autoRun_txt->setText(pref.getAutoRunProgram());
intValue = pref.getActionOnDblClOnTorrentDl(); intValue = pref.getActionOnDblClOnTorrentDl();
@ -832,14 +841,6 @@ void options_imp::on_buttonBox_rejected(){
reject(); reject();
} }
void options_imp::enableDownloadLimit(bool checked){
if(checked){
spinDownloadLimit->setEnabled(true);
}else{
spinDownloadLimit->setEnabled(false);
}
}
bool options_imp::useAdditionDialog() const{ bool options_imp::useAdditionDialog() const{
return checkAdditionDialog->isChecked(); 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(){ void options_imp::enableApplyButton(){
applyButton->setEnabled(true); applyButton->setEnabled(true);
} }

2
src/preferences/options_imp.h

@ -55,8 +55,6 @@ public:
QSize sizeFittingScreen(); QSize sizeFittingScreen();
protected slots: protected slots:
void enableUploadLimit(bool checked);
void enableDownloadLimit(bool checked);
void enableProxy(int comboIndex); void enableProxy(int comboIndex);
void enableProxyAuth(bool checked); void enableProxyAuth(bool checked);
void enableMaxConnecsLimit(bool checked); void enableMaxConnecsLimit(bool checked);

32
src/preferences/preferences.h

@ -306,6 +306,38 @@ public:
setValue(QString::fromUtf8("Preferences/MailNotification/smtp_server"), smtp_server); 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 { int getActionOnDblClOnTorrentDl() const {
return value(QString::fromUtf8("Preferences/Downloads/DblClOnTorDl"), 0).toInt(); return value(QString::fromUtf8("Preferences/Downloads/DblClOnTorDl"), 0).toInt();
} }

3
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("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"; content += tr("Thank you for using qBittorrent.") + "\n";
// Send the notification email // 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 // Read alerts sent by the Bittorrent session

442
src/smtp.cpp

@ -1,26 +1,105 @@
/**************************************************************************** /*
** $Id: qt/smtp.h 3.3.6 edited Aug 31 2005 $ * Bittorrent Client using Qt4 and libtorrent.
** * Copyright (C) 2011 Christophe Dumez
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved. *
** * This program is free software; you can redistribute it and/or
** This file is part of an example program for Qt. This example * modify it under the terms of the GNU General Public License
** program may be used, distributed and modified without limitation. * 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 "smtp.h"
#include "preferences.h" #include "preferences.h"
#include <QTextStream> #include <QTextStream>
#ifndef QT_NO_OPENSSL
#include <QSslSocket>
#else
#include <QTcpSocket> #include <QTcpSocket>
#endif
#include <QTextCodec> #include <QTextCodec>
#include <QDebug> #include <QDebug>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QCryptographicHash>
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); socket = new QTcpSocket(this);
#endif
connect(socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(socket, SIGNAL(disconnected()), SLOT(deleteLater()));
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
== "750c783e6ab0b503eaa86e310a5db738");
Q_ASSERT(hmacMD5(QByteArray::fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
"Hi There").toHex()
== "9294727a3638bb1c13f48ef8158bfc9d");
}
Smtp::~Smtp() {
qDebug() << Q_FUNC_INFO;
}
connect( socket, SIGNAL( readyRead() ), this, SLOT( readyRead() ) ); void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body) {
Preferences pref;
QTextCodec* latin1 = QTextCodec::codecForName("latin1"); QTextCodec* latin1 = QTextCodec::codecForName("latin1");
message = ""; message = "";
message += encode_mime_header("Date", QDateTime::currentDateTime().toUTC().toString("ddd, d MMM yyyy hh:mm:ss UT"), latin1); 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; this->from = from;
rcpt = to; rcpt = to;
state = Init; // Authentication
socket->connectToHost(Preferences().getMailNotificationSMTP(), 25); if(pref.getMailNotificationSMTPAuth()) {
if(socket->waitForConnected ( 30000 )) { username = pref.getMailNotificationSMTPUsername();
qDebug("connected"); password = pref.getMailNotificationSMTPPassword();
}
// Connect to SMTP server
#ifndef QT_NO_OPENSSL
if(pref.getMailNotificationSMTPSSL()) {
socket->connectToHostEncrypted(pref.getMailNotificationSMTP(), DEFAULT_PORT_SSL);
use_ssl = true;
} else {
#endif
socket->connectToHost(pref.getMailNotificationSMTP(), DEFAULT_PORT);
use_ssl = false;
#ifndef QT_NO_OPENSSL
}
#endif
}
void Smtp::readyRead()
{
qDebug() << Q_FUNC_INFO;
// SMTP is line-oriented
buffer += socket->readAll();
while (true)
{
int pos = buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition
QByteArray line = buffer.left(pos);
buffer = buffer.mid(pos + 2);
qDebug() << "Response line:" << line;
// Extract reponse code
QByteArray code = line.left(3);
switch(state) {
case Init: {
if(code[0] == '2') {
// 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 <mail from>...";
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() << "<Mail from> not sent properly, aborting";
state = Close;
}
break;
case Data:
if (code[0] == '2') {
socket->write("data\r\n");
socket->flush();
state = Body;
} else {
qDebug() << "<Rcpt to> 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 { } else {
t = 0; qDebug() << "data not sent properly, aborting";
deleteLater(); 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) QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix)
@ -91,35 +290,8 @@ QByteArray Smtp::encode_mime_header(const QString& key, const QString& value, QT
return rv + line + "\r\n"; return rv + line + "\r\n";
} }
Smtp::~Smtp() void Smtp::ehlo()
{
if(t)
delete t;
delete socket;
}
void Smtp::readyRead()
{ {
qDebug() << "readyRead";
// SMTP is line-oriented
QString responseLine;
do
{
responseLine = socket->readLine();
response += responseLine;
}
while ( socket->canReadLine() && responseLine[3] != ' ' );
qDebug("Response line: %s", qPrintable(response));
responseLine.truncate( 3 );
if ( state == Init && responseLine[0] == '2' )
{
// banner was okay, let's go on
QByteArray address = "127.0.0.1"; QByteArray address = "127.0.0.1";
foreach(const QHostAddress& addr, QNetworkInterface::allAddresses()) foreach(const QHostAddress& addr, QNetworkInterface::allAddresses())
{ {
@ -128,65 +300,153 @@ void Smtp::readyRead()
address = addr.toString().toAscii(); address = addr.toString().toAscii();
break; break;
} }
*t << "ehlo "+ address + "\r\n"; // Send EHLO
t->flush(); socket->write("ehlo "+ address + "\r\n");
socket->flush();
state = EhloSent;
}
state = Mail; 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;
} }
else if ( state == Mail || state == Mail2 ) return;
{ }
if(responseLine[0] == '2') { if (state != EhloGreetReceived) {
// EHLO response was okay (well, it has to be) if (!continued) {
*t << "mail from:<" << from << ">\r\n"; // greeting only, no extensions
t->flush(); qDebug() << "No extension";
state = Rcpt; state = EhloDone;
} else { } else {
if(state == Mail) { // greeting followed by extensions
// ehlo did not work, try helo instead state = EhloGreetReceived;
*t << "helo\r\n"; qDebug () << "EHLO greet received";
t->flush(); return;
state = Mail2;
} }
} 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();
} }
else if ( state == Rcpt && responseLine[0] == '2' ) }
{
*t << "rcpt to:<" << rcpt << ">\r\n"; //r void Smtp::authenticate()
t->flush(); {
state = Data; 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;
}
}
*t << "data\r\n"; void Smtp::startTLS()
t->flush(); {
state = Body; qDebug() << Q_FUNC_INFO;
#ifndef QT_NO_OPENSSL
socket->write("starttls\r\n");
socket->flush();
state = StartTLSSent;
#else
authenticate();
#endif
}
void Smtp::authCramMD5(const QByteArray& challenge)
{
if (state != AuthRequestSent) {
socket->write("auth cram-md5\r\n");
socket->flush();
authType = AuthCramMD5;
state = AuthRequestSent;
} else {
QByteArray response = username.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"; void Smtp::authPlain()
t->flush(); {
state = Quit; 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"; void Smtp::authLogin()
t->flush(); {
// here, we just close. if (state != AuthRequestSent && state != AuthUsernameSent) {
state = Close; socket->write("auth login\r\n");
socket->flush();
authType = AuthLogin;
state = AuthRequestSent;
} }
else if ( state == Close ) else if (state == AuthRequestSent) {
{ socket->write(username.toAscii().toBase64() + "\r\n");
deleteLater(); socket->flush();
return; state = AuthUsernameSent;
} }
else else {
{ socket->write(password.toAscii().toBase64() + "\r\n");
// something broke. socket->flush();
state = Close; state = AuthSent;
} }
response = "";
} }

78
src/smtp.h

@ -1,49 +1,97 @@
/**************************************************************************** /*
** $Id: qt/smtp.h 3.3.6 edited Aug 31 2005 $ * Bittorrent Client using Qt4 and libtorrent.
** * Copyright (C) 2011 Christophe Dumez
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved. *
** * This program is free software; you can redistribute it and/or
** This file is part of an example program for Qt. This example * modify it under the terms of the GNU General Public License
** program may be used, distributed and modified without limitation. * 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 #ifndef SMTP_H
#define SMTP_H #define SMTP_H
#include <QString> #include <QString>
#include <QObject> #include <QObject>
#include <QByteArray> #include <QByteArray>
#include <QHash>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
struct QTextStream; struct QTextStream;
#ifndef QT_NO_OPENSSL
struct QSslSocket;
#else
struct QTcpSocket; struct QTcpSocket;
class QTextCodec; #endif
struct QTextCodec;
QT_END_NAMESPACE QT_END_NAMESPACE
class Smtp : public QObject { class Smtp : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Smtp(const QString &from, const QString &to, const QString &subject, const QString &body); Smtp(QObject *parent = 0);
~Smtp(); ~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()); 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: private:
QByteArray message; QByteArray message;
QTextStream *t; #ifndef QT_NO_OPENSSL
QSslSocket *socket;
#else
QTcpSocket *socket; QTcpSocket *socket;
#endif
QString from; QString from;
QString rcpt; QString rcpt;
QString response; QString response;
enum states{Rcpt,Mail,Mail2,Data,Init,Body,Quit,Close};
int state; int state;
QHash<QString, QString> extensions;
QByteArray buffer;
bool use_ssl;
AuthType authType;
QString username;
QString password;
}; };
#endif #endif

Loading…
Cancel
Save