simple FTP over TLS support
This commit is contained in:
Dmitry Belyaev 2015-07-08 11:04:22 +03:00
parent 13132f5567
commit 0e8721f302
2 changed files with 119 additions and 13 deletions

View File

@ -60,6 +60,8 @@
#include "qtcpserver.h" #include "qtcpserver.h"
#include "qlocale.h" #include "qlocale.h"
#include <QTimer> #include <QTimer>
#include <QSslSocket>
#include <QSslConfiguration>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -115,6 +117,7 @@ signals:
private slots: private slots:
void socketConnected(); void socketConnected();
void socketEncrypted();
void socketReadyRead(); void socketReadyRead();
void socketError(QAbstractSocket::SocketError); void socketError(QAbstractSocket::SocketError);
void socketConnectionClosed(); void socketConnectionClosed();
@ -167,6 +170,12 @@ public:
void clearPendingCommands(); void clearPendingCommands();
void abort(); void abort();
bool isTls() {return tls;}
void setTls(bool _tls) {tls=_tls;}
void addCaCertificates(QList<QSslCertificate> certs)
{
commandSocket.addCaCertificates(certs);
}
QString currentCommand() const QString currentCommand() const
{ return currentCmd; } { return currentCmd; }
@ -181,6 +190,7 @@ signals:
void finished(const QString&); void finished(const QString&);
void error(int, const QString&); void error(int, const QString&);
void rawFtpReply(int, const QString&); void rawFtpReply(int, const QString&);
void encrypted();
private slots: private slots:
void hostFound(); void hostFound();
@ -190,6 +200,7 @@ private slots:
void readyRead(); void readyRead();
void error(QAbstractSocket::SocketError); void error(QAbstractSocket::SocketError);
void check_connectToHost_timeout(); void check_connectToHost_timeout();
void sslErrors ( const QList<QSslError> & errors ) ;
void dtpConnectState(int); void dtpConnectState(int);
@ -213,7 +224,7 @@ private:
bool processReply(); bool processReply();
bool startNextCmd(); bool startNextCmd();
QTcpSocket commandSocket; QSslSocket commandSocket;
QString replyText; QString replyText;
char replyCode[3]; char replyCode[3];
State state; State state;
@ -223,9 +234,11 @@ private:
bool waitForDtpToConnect; bool waitForDtpToConnect;
bool waitForDtpToClose; bool waitForDtpToClose;
bool tls;
QByteArray bytesFromSocket; QByteArray bytesFromSocket;
QTimer timer; QTimer timer;
QSslConfiguration ssl_config;
friend class QFtpDTP; friend class QFtpDTP;
}; };
@ -323,7 +336,12 @@ void QFtpDTP::connectToHost(const QString & host, quint16 port)
delete socket; delete socket;
socket = 0; socket = 0;
} }
socket = new QTcpSocket(this); if (pi->isTls()) {
socket = new QSslSocket(this);
} else {
socket = new QTcpSocket(this);
}
#ifndef QT_NO_BEARERMANAGEMENT #ifndef QT_NO_BEARERMANAGEMENT
//copy network session down to the socket //copy network session down to the socket
socket->setProperty("_q_networksession", property("_q_networksession")); socket->setProperty("_q_networksession", property("_q_networksession"));
@ -335,7 +353,17 @@ void QFtpDTP::connectToHost(const QString & host, quint16 port)
connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed())); connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
socket->connectToHost(host, port); QSslSocket* ssl_socket = qobject_cast<QSslSocket*>(socket);
if (ssl_socket) // here we need to setup QSslSocket (0 if QTcpSocket)
{
connect(ssl_socket, SIGNAL(encrypted()), SLOT(socketEncrypted()));
//TODO: implement TLS session resumption (err 450)
ssl_socket->setSslConfiguration(pi->ssl_config);
ssl_socket->connectToHostEncrypted(host, port);
} else {
socket->connectToHost(host, port);
}
} }
int QFtpDTP::setupListener(const QHostAddress &address) int QFtpDTP::setupListener(const QHostAddress &address)
@ -650,12 +678,21 @@ bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlIn
void QFtpDTP::socketConnected() void QFtpDTP::socketConnected()
{ {
bytesDone = 0; bytesDone = 0;
QSslSocket* ssl_socket = qobject_cast<QSslSocket*>(socket);
if (ssl_socket){ // 0 if socket is QTcpSocket
return; // wait for encrypted
}
#if defined(QFTPDTP_DEBUG) #if defined(QFTPDTP_DEBUG)
qDebug("QFtpDTP::connectState(CsConnected)"); qDebug("QFtpDTP::connectState(CsConnected)");
#endif #endif
emit connectState(QFtpDTP::CsConnected); emit connectState(QFtpDTP::CsConnected);
} }
void QFtpDTP::socketEncrypted()
{
emit connectState(QFtpDTP::CsConnected);
}
void QFtpDTP::socketReadyRead() void QFtpDTP::socketReadyRead()
{ {
if (!socket) if (!socket)
@ -795,12 +832,14 @@ QFtpPI::QFtpPI(QObject *parent) :
QObject(parent), QObject(parent),
rawCommand(false), rawCommand(false),
transferConnectionExtended(true), transferConnectionExtended(true),
dtp(this), dtp(this, this),
commandSocket(0), commandSocket(0),
state(Begin), abortState(None), state(Begin), abortState(None),
currentCmd(QString()), currentCmd(QString()),
waitForDtpToConnect(false), waitForDtpToConnect(false),
waitForDtpToClose(false) waitForDtpToClose(false),
tls(false),
ssl_config(QSslConfiguration::defaultConfiguration())
{ {
commandSocket.setObjectName(QLatin1String("QFtpPI_socket")); commandSocket.setObjectName(QLatin1String("QFtpPI_socket"));
connect(&commandSocket, SIGNAL(hostFound()), connect(&commandSocket, SIGNAL(hostFound()),
@ -816,6 +855,27 @@ QFtpPI::QFtpPI(QObject *parent) :
connect(&dtp, SIGNAL(connectState(int)), connect(&dtp, SIGNAL(connectState(int)),
SLOT(dtpConnectState(int))); SLOT(dtpConnectState(int)));
connect(&commandSocket, SIGNAL(encrypted()),
SIGNAL(encrypted()));
connect(&commandSocket, SIGNAL(sslErrors ( const QList<QSslError> & ) ),
SLOT(sslErrors ( const QList<QSslError> & ) ));
// additional ssl settings
ssl_config.setProtocol(QSsl::TlsV1_2);
ssl_config.setPeerVerifyMode(QSslSocket::VerifyPeer); //TODO: option to disable verification
commandSocket.setSslConfiguration(ssl_config);
}
void QFtpPI::sslErrors ( const QList<QSslError> & errors )
{
QString e;
for(int i=0; i< errors.size(); ++i)
{
e.append((errors[i].errorString())+"\n");
}
emit error((int)QFtp::SslError, e);
} }
void QFtpPI::connectToHost(const QString &host, quint16 port) void QFtpPI::connectToHost(const QString &host, quint16 port)
@ -1088,6 +1148,13 @@ bool QFtpPI::processReply()
QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4]; QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4];
quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt(); quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt();
waitForDtpToConnect = true; waitForDtpToConnect = true;
//ssl_conf = commandSocket.sslConfiguration();
//dtp.setSsl_config(ssl_conf);
//dtp.setSessionTicket(ssl_conf.sessionTicket());
#ifndef QT_NO_BEARERMANAGEMENT
//copy network session down to the socket
dtp.setProperty("_q_networksession", commandSocket.property("_q_networksession"));
#endif
dtp.connectToHost(host, port); dtp.connectToHost(host, port);
} }
} else if (replyCodeInt == 229) { } else if (replyCodeInt == 229) {
@ -1121,8 +1188,16 @@ bool QFtpPI::processReply()
if (currentCmd.startsWith(QLatin1String("SIZE "))) if (currentCmd.startsWith(QLatin1String("SIZE ")))
dtp.setBytesTotal(replyText.simplified().toLongLong()); dtp.setBytesTotal(replyText.simplified().toLongLong());
} else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) { } else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) {
dtp.waitForConnection(); if (!tls) dtp.waitForConnection();
dtp.writeData(); dtp.writeData();
} else if (replyCodeInt == 234 && tls) //TLS OK
{
commandSocket.startClientEncryption();
commandSocket.waitForEncrypted(); //TODO: check for encrypted or remove wait
}
else if (replyCodeInt == 235 && tls) //TLS security data needed
{
state = Failure;
} }
// react on new state // react on new state
@ -1195,7 +1270,10 @@ bool QFtpPI::startNextCmd()
// the address/port arguments are edited in. // the address/port arguments are edited in.
QHostAddress address = commandSocket.localAddress(); QHostAddress address = commandSocket.localAddress();
if (currentCmd.startsWith(QLatin1String("PORT"))) { if (currentCmd.startsWith(QLatin1String("PORT"))) {
if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) { if (tls) {
qDebug() << "PORT command ignored in tls mode"; //TODO
return false;
} else if ((address.protocol() == QTcpSocket::IPv6Protocol) || transferConnectionExtended) {
int port = dtp.setupListener(address); int port = dtp.setupListener(address);
currentCmd = QLatin1String("EPRT |"); currentCmd = QLatin1String("EPRT |");
currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2'); currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2');
@ -1222,7 +1300,7 @@ bool QFtpPI::startNextCmd()
currentCmd += QLatin1String("\r\n"); currentCmd += QLatin1String("\r\n");
} else if (currentCmd.startsWith(QLatin1String("PASV"))) { } else if (currentCmd.startsWith(QLatin1String("PASV"))) {
if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) if ((address.protocol() == QTcpSocket::IPv6Protocol) || transferConnectionExtended)
currentCmd = QLatin1String("EPSV\r\n"); currentCmd = QLatin1String("EPSV\r\n");
} }
@ -1669,6 +1747,16 @@ int QFtp::connectToHost(const QString &host, quint16 port)
return id; return id;
} }
void QFtp::addCaCertificates(QList<QSslCertificate> certs)
{
d->pi.addCaCertificates(certs);
}
void QFtp::setTls(bool tls)
{
return d->pi.setTls(tls);
}
/*! /*!
Logs in to the FTP server with the username \a user and the Logs in to the FTP server with the username \a user and the
password \a password. password \a password.
@ -1690,8 +1778,21 @@ int QFtp::connectToHost(const QString &host, quint16 port)
int QFtp::login(const QString &user, const QString &password) int QFtp::login(const QString &user, const QString &password)
{ {
QStringList cmds; QStringList cmds;
if(d->pi.isTls()){
cmds << (QLatin1String("AUTH TLS\r\n"));
}
cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n")); cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n"));
cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n")); cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n"));
//Switch to TLS on the data-channel
if(d->pi.isTls())
{
cmds << (QLatin1String("PBSZ 0\r\n"));
cmds << (QLatin1String("PROT P\r\n"));
}
return d->addCommand(new QFtpCommand(Login, cmds)); return d->addCommand(new QFtpCommand(Login, cmds));
} }
@ -1766,7 +1867,7 @@ int QFtp::list(const QString &dir)
{ {
QStringList cmds; QStringList cmds;
cmds << QLatin1String("TYPE A\r\n"); cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
if (dir.isEmpty()) if (dir.isEmpty())
cmds << QLatin1String("LIST\r\n"); cmds << QLatin1String("LIST\r\n");
else else
@ -1841,7 +1942,7 @@ int QFtp::get(const QString &file, QIODevice *dev, TransferType type)
else else
cmds << QLatin1String("TYPE A\r\n"); cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n"); cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n"); cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n");
return d->addCommand(new QFtpCommand(Get, cmds, dev)); return d->addCommand(new QFtpCommand(Get, cmds, dev));
} }
@ -1877,7 +1978,7 @@ int QFtp::put(const QByteArray &data, const QString &file, TransferType type)
cmds << QLatin1String("TYPE I\r\n"); cmds << QLatin1String("TYPE I\r\n");
else else
cmds << QLatin1String("TYPE A\r\n"); cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n"); cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n");
cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
return d->addCommand(new QFtpCommand(Put, cmds, data)); return d->addCommand(new QFtpCommand(Put, cmds, data));
@ -1904,7 +2005,7 @@ int QFtp::put(QIODevice *dev, const QString &file, TransferType type)
cmds << QLatin1String("TYPE I\r\n"); cmds << QLatin1String("TYPE I\r\n");
else else
cmds << QLatin1String("TYPE A\r\n"); cmds << QLatin1String("TYPE A\r\n");
cmds << QLatin1String(d->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); cmds << QLatin1String(d->transferMode == Passive ? "EPSV\r\n" : "PORT\r\n");
if (!dev->isSequential()) if (!dev->isSequential())
cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n"); cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n");
cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");

View File

@ -45,6 +45,8 @@
#include <QtCore/qstring.h> #include <QtCore/qstring.h>
#include <QtCore/qobject.h> #include <QtCore/qobject.h>
#include <QtFtp/qurlinfo.h> #include <QtFtp/qurlinfo.h>
#include <QList>
#include <QSslCertificate>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -71,7 +73,8 @@ public:
UnknownError, UnknownError,
HostNotFound, HostNotFound,
ConnectionRefused, ConnectionRefused,
NotConnected NotConnected,
SslError
}; };
enum Command { enum Command {
None, None,
@ -101,6 +104,8 @@ public:
int setProxy(const QString &host, quint16 port); int setProxy(const QString &host, quint16 port);
int connectToHost(const QString &host, quint16 port=21); int connectToHost(const QString &host, quint16 port=21);
void addCaCertificates(QList<QSslCertificate> certs);
void setTls(bool tls);
int login(const QString &user = QString(), const QString &password = QString()); int login(const QString &user = QString(), const QString &password = QString());
int close(); int close();
int setTransferMode(TransferMode mode); int setTransferMode(TransferMode mode);