state machine implementation, connectToHost and login working

This commit is contained in:
Attila Tőkés 2012-09-07 00:28:07 +03:00 committed by Attila
parent b0e7a31b64
commit 618d583551
2 changed files with 213 additions and 167 deletions

View File

@ -21,6 +21,8 @@
#include <QFileInfo> #include <QFileInfo>
#include <QByteArray> #include <QByteArray>
#include <iostream>
using namespace std;
/* [1] Constructors and destructors */ /* [1] Constructors and destructors */
@ -88,6 +90,10 @@ void SmtpClient::setConnectionType(ConnectionType ct)
case SslConnection: case SslConnection:
case TlsConnection: case TlsConnection:
socket = new QSslSocket(this); socket = new QSslSocket(this);
//QSslSocket &s = *((QSslSocket*) socket);
connect(socket, SIGNAL(encrypted()),
this, SLOT(socketEncrypted()));
break;
} }
} }
@ -152,155 +158,22 @@ QTcpSocket* SmtpClient::getSocket() {
bool SmtpClient::connectToHost() bool SmtpClient::connectToHost()
{ {
switch (connectionType) changeState(ConnectingState);
{
case TlsConnection:
case TcpConnection:
socket->connectToHost(host, port);
break;
case SslConnection:
((QSslSocket*) socket)->connectToHostEncrypted(host, port);
break;
}
// Tries to connect to server
if (!socket->waitForConnected(connectionTimeout))
{
emit smtpError(ConnectionTimeoutError);
return false;
}
try
{
// Wait for the server's response
waitForResponse();
// If the response code is not 220 (Service ready)
// means that is something wrong with the server
if (responseCode != 220)
{
emit smtpError(ServerError);
return false;
}
// Send a EHLO/HELO message to the server
// The client's first command must be EHLO/HELO
sendMessage("EHLO " + name);
// Wait for the server's response
waitForResponse();
// The response code needs to be 250.
if (responseCode != 250) {
emit smtpError(ServerError);
return false;
}
if (connectionType == TlsConnection) {
// send a request to start TLS handshake
sendMessage("STARTTLS");
// Wait for the server's response
waitForResponse();
// The response code needs to be 220.
if (responseCode != 220) {
emit smtpError(ServerError);
return false;
};
((QSslSocket*) socket)->startClientEncryption();
if (!((QSslSocket*) socket)->waitForEncrypted(connectionTimeout)) {
qDebug() << ((QSslSocket*) socket)->errorString();
emit SmtpError(ConnectionTimeoutError);
return false;
}
// Send ELHO one more time
sendMessage("EHLO " + name);
// Wait for the server's response
waitForResponse();
// The response code needs to be 250.
if (responseCode != 250) {
emit smtpError(ServerError);
return false;
}
}
}
catch (ResponseTimeoutException)
{
return false;
}
// If no errors occured the function returns true.
return true; return true;
} }
bool SmtpClient::login() bool SmtpClient::login()
{ {
return login(user, password, authMethod); changeState(AuthenticatingState);
return true;
} }
bool SmtpClient::login(const QString &user, const QString &password, AuthMethod method) bool SmtpClient::login(const QString &user, const QString &password, AuthMethod method)
{ {
try { this->user = user;
if (method == AuthPlain) this->password = password;
{ this->authMethod = method;
// Sending command: AUTH PLAIN base64('\0' + username + '\0' + password) return login();
sendMessage("AUTH PLAIN " + QByteArray().append((char) 0).append(user).append((char) 0).append(password).toBase64());
// Wait for the server's response
waitForResponse();
// If the response is not 235 then the authentication was faild
if (responseCode != 235)
{
emit smtpError(AuthenticationFailedError);
return false;
}
}
else if (method == AuthLogin)
{
// Sending command: AUTH LOGIN
sendMessage("AUTH LOGIN");
// Wait for 334 response code
waitForResponse();
if (responseCode != 334) { emit smtpError(AuthenticationFailedError); return false; }
// Send the username in base64
sendMessage(QByteArray().append(user).toBase64());
// Wait for 334
waitForResponse();
if (responseCode != 334) { emit smtpError(AuthenticationFailedError); return false; }
// Send the password in base64
sendMessage(QByteArray().append(password).toBase64());
// Wait for the server's responce
waitForResponse();
// If the response is not 235 then the authentication was faild
if (responseCode != 235)
{
emit smtpError(AuthenticationFailedError);
return false;
}
}
}
catch (ResponseTimeoutException e)
{
// Responce Timeout exceeded
emit smtpError(AuthenticationFailedError);
return false;
}
return true;
} }
bool SmtpClient::sendMail(MimeMessage& email) bool SmtpClient::sendMail(MimeMessage& email)
@ -392,41 +265,103 @@ void SmtpClient::waitForResponse() throw (ResponseTimeoutException)
void SmtpClient::changeState(ClientState state) { void SmtpClient::changeState(ClientState state) {
this->state = state; this->state = state;
#ifdef QT_NO_DEBUG
// Emit stateChanged signal only for non-internal states
if (state <= DisconnectingState) {
emit stateChanged(state);
}
#else
// emit all in debug mode
emit stateChanged(state);
#endif
switch (state) switch (state)
{ {
case _ELHO_State: case ConnectingState:
// Service ready. Send ELHO message and chage the state switch (connectionType)
sendMessage("ELHO " + name); {
case TlsConnection:
case TcpConnection:
socket->connectToHost(host, port);
break;
case SslConnection:
((QSslSocket*) socket)->connectToHostEncrypted(host, port);
break;
}
break; break;
case _EHLO_State:
// Service ready. Send EHLO message and chage the state
sendMessage("EHLO " + name);
break;
case _READY_Connected:
changeState(ReadyState);
emit readyConnected();
break;
/* --- TLS --- */
case _TLS_State: case _TLS_State:
changeState(_TLS_0_STARTTLS);
break;
case _TLS_0_STARTTLS:
// send a request to start TLS handshake // send a request to start TLS handshake
sendMessage("STARTTLS"); sendMessage("STARTTLS");
break; break;
case _TLS_1_ENCRYPT:
((QSslSocket*) socket)->startClientEncryption();
break;
case _TLS_2_EHLO:
// Send EHLO one more time
sendMessage("EHLO " + name);
break;
case _READY_Encrypted:
changeState(_READY_Connected);
break;
/* --- AUTH --- */
case AuthenticatingState:
changeState(authMethod == AuthPlain ? _AUTH_PLAIN_0 : _AUTH_LOGIN_0);
break;
case _AUTH_PLAIN_0:
// Sending command: AUTH PLAIN base64('\0' + username + '\0' + password)
sendMessage("AUTH PLAIN " + QByteArray().append((char) 0).append(user)
.append((char) 0).append(password).toBase64());
break;
case _AUTH_LOGIN_0:
sendMessage("AUTH LOGIN");
break;
case _AUTH_LOGIN_1_USER:
// Send the username in base64
sendMessage(QByteArray().append(user).toBase64());
break;
case _AUTH_LOGIN_2_PASS:
// Send the password in base64
sendMessage(QByteArray().append(password).toBase64());
break;
case _READY_Authenticated:
changeState(ReadyState);
emit authenticated();
break;
default: default:
; ;
} }
if (state <= DisconnectingState) { // don't emit for internal signals
emit stateChanged(state);
}
} }
void SmtpClient::processResponse() { void SmtpClient::processResponse() {
// Save the server's response
responseText = socket->readAll();
// Extract the respose code from the server's responce (first 3 digits)
responseCode = responseText.left(3).toInt();
if (responseCode / 100 == 4) {
emit smtpError(ServerError); return;
}
if (responseCode / 100 == 5) {
emit smtpError(ClientError); return;
}
switch (state) switch (state)
{ {
@ -435,16 +370,69 @@ void SmtpClient::processResponse() {
if (responseCode != 220) { if (responseCode != 220) {
emit smtpError(ServerError); return; emit smtpError(ServerError); return;
} }
changeState(_ELHO_State); changeState(_EHLO_State);
break; break;
case _ELHO_State: case _EHLO_State:
// The response code needs to be 250. // The response code needs to be 250.
if (responseCode != 250) { if (responseCode != 250) {
emit smtpError(ServerError); return; emit smtpError(ServerError); return;
} }
changeState((connectionType != TlsConnection) ? ReadyState : _TLS_State); changeState((connectionType != TlsConnection) ? _READY_Connected : _TLS_State);
break;
/* --- TLS --- */
case _TLS_0_STARTTLS:
// The response code needs to be 220.
if (responseCode != 220) {
emit smtpError(ServerError);
return;
}
changeState(_TLS_1_ENCRYPT);
break;
case _TLS_2_EHLO:
// The response code needs to be 250.
if (responseCode != 250) {
emit smtpError(ServerError);
return;
}
changeState(_READY_Encrypted);
break;
/* --- AUTH --- */
case _AUTH_PLAIN_0:
// If the response is not 235 then the authentication was failed
if (responseCode != 235) {
emit smtpError(AuthenticationFailedError);
return;
}
changeState(_READY_Authenticated);
break;
case _AUTH_LOGIN_0:
if (responseCode != 334) {
emit smtpError(AuthenticationFailedError);
return;
}
changeState(_AUTH_LOGIN_1_USER);
break;
case _AUTH_LOGIN_1_USER:
if (responseCode != 334) {
emit smtpError(AuthenticationFailedError);
return;
}
changeState(_AUTH_LOGIN_2_PASS);
break;
case _AUTH_LOGIN_2_PASS:
if (responseCode != 235) {
emit smtpError(AuthenticationFailedError);
return;
}
changeState(_READY_Authenticated);
break; break;
default: default:
@ -454,6 +442,7 @@ void SmtpClient::processResponse() {
void SmtpClient::sendMessage(const QString &text) void SmtpClient::sendMessage(const QString &text)
{ {
socket->flush();
socket->write(text.toUtf8() + "\r\n"); socket->write(text.toUtf8() + "\r\n");
} }
@ -478,12 +467,47 @@ void SmtpClient::socketStateChanged(QAbstractSocket::SocketState state) {
} }
void SmtpClient::socketError(QAbstractSocket::SocketError socketError) { void SmtpClient::socketError(QAbstractSocket::SocketError socketError) {
qDebug() << "this is sparta" << endl;
emit smtpError(SocketError); emit smtpError(SocketError);
} }
void SmtpClient::socketReadyRead() void SmtpClient::socketReadyRead()
{ {
QString responseLine;
while (socket->canReadLine()) {
// Save the server's response
responseLine = socket->readLine();
tempResponse += responseLine;
// Extract the respose code from the server's responce (first 3 digits)
responseCode = responseLine.left(3).toInt();
// Check for server error
if (responseCode / 100 == 4) {
emit smtpError(ServerError);
return;
}
// Check for client error
if (responseCode / 100 == 5) {
emit smtpError(ClientError);
return;
}
}
// Is this the last line of the response
if (responseLine[3] == ' ') {
responseText = tempResponse;
processResponse();
}
}
void SmtpClient::socketEncrypted() {
if (state == _TLS_1_ENCRYPT) {
changeState(_TLS_2_EHLO);
}
} }
/* [5] --- */ /* [5] --- */

View File

@ -65,13 +65,27 @@ public:
DisconnectingState = 6, DisconnectingState = 6,
/* Internal States */ /* Internal States */
_ELHO_State = 50, _EHLO_State = 50,
_TLS_State = 51, _TLS_State = 51,
_READY_Connected = 52,
_READY_Authenticated = 53,
_READY_MailSended = 54,
_READY_Encrypted = 55,
/* Internal Substates */ /* Internal Substates */
_TLS_0_START = 60,
// TLS
_TLS_0_STARTTLS = 60,
_TLS_1_ENCRYPT = 61, _TLS_1_ENCRYPT = 61,
_TLS_2_ELHO = 62 _TLS_2_EHLO = 62,
// AUTH
_AUTH_PLAIN_0 = 63,
_AUTH_LOGIN_0 = 64,
_AUTH_LOGIN_1_USER = 65,
_AUTH_LOGIN_2_PASS = 66
}; };
@ -124,7 +138,8 @@ public:
bool connectToHost(); bool connectToHost();
bool login(); bool login();
bool login(const QString &user, const QString &password, AuthMethod method = AuthLogin); bool login(const QString &user, const QString &password,
AuthMethod method = AuthLogin);
bool sendMail(MimeMessage& email); bool sendMail(MimeMessage& email);
@ -154,6 +169,7 @@ protected:
int responseTimeout; int responseTimeout;
QString responseText; QString responseText;
QString tempResponse;
int responseCode; int responseCode;
@ -180,6 +196,7 @@ protected slots:
void socketStateChanged(QAbstractSocket::SocketState state); void socketStateChanged(QAbstractSocket::SocketState state);
void socketError(QAbstractSocket::SocketError error); void socketError(QAbstractSocket::SocketError error);
void socketReadyRead(); void socketReadyRead();
void socketEncrypted();
/* [6] --- */ /* [6] --- */
@ -189,7 +206,12 @@ signals:
/* [7] Signals */ /* [7] Signals */
void smtpError(SmtpError e); void smtpError(SmtpError e);
void stateChanged(ClientState s); void stateChanged(SmtpClient::ClientState s);
void connected();
void readyConnected();
void authenticated();
void mailSended();
void disconnected();
/* [7] --- */ /* [7] --- */