@ -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 = " " ;
}
}