From 0e8721f30215b3f4ecd72d3bd74f6efd271bbb02 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 8 Jul 2015 11:04:22 +0300 Subject: [PATCH] FTPES simple FTP over TLS support --- src/qftp/qftp.cpp | 125 +++++++++++++++++++++++++++++++++++++++++----- src/qftp/qftp.h | 7 ++- 2 files changed, 119 insertions(+), 13 deletions(-) diff --git a/src/qftp/qftp.cpp b/src/qftp/qftp.cpp index 7686327..866c93c 100644 --- a/src/qftp/qftp.cpp +++ b/src/qftp/qftp.cpp @@ -60,6 +60,8 @@ #include "qtcpserver.h" #include "qlocale.h" #include +#include +#include QT_BEGIN_NAMESPACE @@ -115,6 +117,7 @@ signals: private slots: void socketConnected(); + void socketEncrypted(); void socketReadyRead(); void socketError(QAbstractSocket::SocketError); void socketConnectionClosed(); @@ -167,6 +170,12 @@ public: void clearPendingCommands(); void abort(); + bool isTls() {return tls;} + void setTls(bool _tls) {tls=_tls;} + void addCaCertificates(QList certs) + { + commandSocket.addCaCertificates(certs); + } QString currentCommand() const { return currentCmd; } @@ -181,6 +190,7 @@ signals: void finished(const QString&); void error(int, const QString&); void rawFtpReply(int, const QString&); + void encrypted(); private slots: void hostFound(); @@ -190,6 +200,7 @@ private slots: void readyRead(); void error(QAbstractSocket::SocketError); void check_connectToHost_timeout(); + void sslErrors ( const QList & errors ) ; void dtpConnectState(int); @@ -213,7 +224,7 @@ private: bool processReply(); bool startNextCmd(); - QTcpSocket commandSocket; + QSslSocket commandSocket; QString replyText; char replyCode[3]; State state; @@ -223,9 +234,11 @@ private: bool waitForDtpToConnect; bool waitForDtpToClose; + bool tls; QByteArray bytesFromSocket; QTimer timer; + QSslConfiguration ssl_config; friend class QFtpDTP; }; @@ -323,7 +336,12 @@ void QFtpDTP::connectToHost(const QString & host, quint16 port) delete socket; socket = 0; } - socket = new QTcpSocket(this); + if (pi->isTls()) { + socket = new QSslSocket(this); + } else { + socket = new QTcpSocket(this); + } + #ifndef QT_NO_BEARERMANAGEMENT //copy network session down to the socket 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(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); - socket->connectToHost(host, port); + QSslSocket* ssl_socket = qobject_cast(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) @@ -650,12 +678,21 @@ bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlIn void QFtpDTP::socketConnected() { bytesDone = 0; + QSslSocket* ssl_socket = qobject_cast(socket); + if (ssl_socket){ // 0 if socket is QTcpSocket + return; // wait for encrypted + } #if defined(QFTPDTP_DEBUG) qDebug("QFtpDTP::connectState(CsConnected)"); #endif emit connectState(QFtpDTP::CsConnected); } +void QFtpDTP::socketEncrypted() +{ + emit connectState(QFtpDTP::CsConnected); +} + void QFtpDTP::socketReadyRead() { if (!socket) @@ -795,12 +832,14 @@ QFtpPI::QFtpPI(QObject *parent) : QObject(parent), rawCommand(false), transferConnectionExtended(true), - dtp(this), + dtp(this, this), commandSocket(0), state(Begin), abortState(None), currentCmd(QString()), waitForDtpToConnect(false), - waitForDtpToClose(false) + waitForDtpToClose(false), + tls(false), + ssl_config(QSslConfiguration::defaultConfiguration()) { commandSocket.setObjectName(QLatin1String("QFtpPI_socket")); connect(&commandSocket, SIGNAL(hostFound()), @@ -816,6 +855,27 @@ QFtpPI::QFtpPI(QObject *parent) : connect(&dtp, SIGNAL(connectState(int)), SLOT(dtpConnectState(int))); + + connect(&commandSocket, SIGNAL(encrypted()), + SIGNAL(encrypted())); + connect(&commandSocket, SIGNAL(sslErrors ( const QList & ) ), + SLOT(sslErrors ( const QList & ) )); + + // 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 & 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) @@ -1088,6 +1148,13 @@ bool QFtpPI::processReply() QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4]; quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt(); 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); } } else if (replyCodeInt == 229) { @@ -1121,8 +1188,16 @@ bool QFtpPI::processReply() if (currentCmd.startsWith(QLatin1String("SIZE "))) dtp.setBytesTotal(replyText.simplified().toLongLong()); } else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) { - dtp.waitForConnection(); + if (!tls) dtp.waitForConnection(); 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 @@ -1195,7 +1270,10 @@ bool QFtpPI::startNextCmd() // the address/port arguments are edited in. QHostAddress address = commandSocket.localAddress(); 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); currentCmd = QLatin1String("EPRT |"); currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2'); @@ -1222,7 +1300,7 @@ bool QFtpPI::startNextCmd() currentCmd += QLatin1String("\r\n"); } else if (currentCmd.startsWith(QLatin1String("PASV"))) { - if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) + if ((address.protocol() == QTcpSocket::IPv6Protocol) || transferConnectionExtended) currentCmd = QLatin1String("EPSV\r\n"); } @@ -1669,6 +1747,16 @@ int QFtp::connectToHost(const QString &host, quint16 port) return id; } +void QFtp::addCaCertificates(QList 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 password \a password. @@ -1690,8 +1778,21 @@ int QFtp::connectToHost(const QString &host, quint16 port) int QFtp::login(const QString &user, const QString &password) { 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("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)); } @@ -1766,7 +1867,7 @@ int QFtp::list(const QString &dir) { QStringList cmds; 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()) cmds << QLatin1String("LIST\r\n"); else @@ -1841,7 +1942,7 @@ int QFtp::get(const QString &file, QIODevice *dev, TransferType type) else cmds << QLatin1String("TYPE A\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"); 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"); else 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("STOR ") + file + QLatin1String("\r\n"); 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"); else 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()) cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n"); cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); diff --git a/src/qftp/qftp.h b/src/qftp/qftp.h index 3281b28..e9843dd 100644 --- a/src/qftp/qftp.h +++ b/src/qftp/qftp.h @@ -45,6 +45,8 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE @@ -71,7 +73,8 @@ public: UnknownError, HostNotFound, ConnectionRefused, - NotConnected + NotConnected, + SslError }; enum Command { None, @@ -101,6 +104,8 @@ public: int setProxy(const QString &host, quint16 port); int connectToHost(const QString &host, quint16 port=21); + void addCaCertificates(QList certs); + void setTls(bool tls); int login(const QString &user = QString(), const QString &password = QString()); int close(); int setTransferMode(TransferMode mode);