# HG changeset patch # User Andre Heinecke # Date 1394655967 -3600 # Node ID d8e93fa1fc93944d06b224992667249a9822060d # Parent 37fc669675172a9a94a33de983567b93528ea0af Downloader logic diff -r 37fc66967517 -r d8e93fa1fc93 ui/downloader.cpp --- a/ui/downloader.cpp Thu Mar 13 18:12:16 2014 +0000 +++ b/ui/downloader.cpp Wed Mar 12 21:26:07 2014 +0100 @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include @@ -20,6 +23,19 @@ #define MAX_SW_SIZE 10485760 #define MAX_LIST_SIZE 1048576 +#define MAX_IO_TRIES 10 + +#define LIST_RESOURCE "/incoming/aheinecke/test" +#define SW_RESOURCE "/incoming/aheinecke/test" + + +#ifdef CONNECTION_DEBUG +static void my_debug(void *ctx, int level, const char *str) +{ + fprintf((FILE *) ctx, "%s", str); + fflush((FILE *) ctx); +} +#endif QString getErrorMsg(int ret) { @@ -108,6 +124,13 @@ ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); +#ifdef RELEASE_BUILD + ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3); +#endif + +#ifdef CONNECTION_DEBUG + ssl_set_dbg(&mSSL, my_debug, stdout); +#endif return 0; } @@ -145,9 +168,8 @@ int ret = -1; const x509_crt *peerCert; - mErrorState = ErrUnknown; - - if (mServerFD == -1) { + if (mServerFD == -1 || !mInitialized) { + mErrorState = ErrUnknown; return -1; } @@ -159,27 +181,29 @@ ret != POLARSSL_ERR_NET_WANT_WRITE) { qDebug() << "SSL Handshake failed: " << getErrorMsg(ret); + mErrorState = SSLHandshakeFailed; return ret; } } + /* we might want to set the verify function + * with ssl_set_verify before to archive the + * certificate pinning. */ + ret = ssl_get_verify_result(&mSSL); if (ret != 0 ) { - - if( ( ret & BADCERT_EXPIRED ) != 0 ) - qDebug() << "server certificate has expired"; - - if( ( ret & BADCERT_REVOKED ) != 0 ) - qDebug() << "server certificate has been revoked"; - - if( ( ret & BADCERT_CN_MISMATCH ) != 0 ) - qDebug() << "CN mismatch"; - - if( ( ret & BADCERT_NOT_TRUSTED ) != 0 ) + if((ret & BADCERT_EXPIRED) != 0) + qDebug() << "server certificate has expired"; + if((ret & BADCERT_REVOKED) != 0) + qDebug() << "server certificate has been revoked"; + if((ret & BADCERT_CN_MISMATCH) != 0) + qDebug() << "CN mismatch"; + if((ret & BADCERT_NOT_TRUSTED) != 0) qDebug() << "self-signed or not signed by a trusted CA"; - + ret = -1; #ifdef RELEASE_BUILD + mErrorState = InvalidCertificate; return -1; #endif } @@ -214,13 +238,169 @@ return -1; } } - mErrorState = NoError; return 0; } +/* Helper around polarssl bare bone api */ +int polarSSLWrite (ssl_context *ssl, const QByteArray& request) +{ + unsigned int tries = 0; + int ret = -1; + + const unsigned char *buf = (const unsigned char *) request.constData(); + size_t len = (size_t) request.size(); + + qDebug() << "Seinding request: " << request; + /* According to doc for ssl_write: + * + * When this function returns POLARSSL_ERR_NET_WANT_WRITE, + * it must be called later with the same arguments, + * until it returns a positive value. + */ + do { + ret = ssl_write(ssl, buf, len); + if (ret >= 0) { + if ((unsigned int) ret == len) { + return 0; + } else { + qDebug() << "Write failed to write everything"; + return -1; + } + } + + if (ret != POLARSSL_ERR_NET_WANT_WRITE) { + return ret; + } + tries++; + net_usleep(100000); /* sleep 100ms to give the socket a chance + to clean up. */ + } while (tries < MAX_IO_TRIES); + + return ret; +} + +/* Helper around polarssl bare bone api read at most len bytes + * and return them as a byte array returns a NULL byte array on error*/ +QByteArray polarSSLRead (ssl_context *ssl, size_t len) +{ + unsigned char buf[len]; + QByteArray retval(""); + int ret = -1; + + do { + memset (buf, 0, sizeof(buf)); + ret = ssl_read(ssl, buf, len); + if (ret == 0 || + ret == POLARSSL_ERR_SSL_CONN_EOF) { + /* EOF */ + return retval; + } + if (ret <= 0) { + qDebug() << "Read failed: " << getErrorMsg(ret); + return QByteArray(); + } + if (len < (len - (unsigned int) ret)) { + /* Should never happen if ssl_read behaves */ + qDebug() << "integer overflow in polarSSLRead"; + return QByteArray(); + } + len -= (unsigned int) ret; + retval.append((const char *)buf, len); + } while (len > 0); + + return retval; +} + + +QDateTime Downloader::getLastModifiedHeader(const QString &resource) { + int ret = -1; + QByteArray response; + QTextStream responseStream(&response); + QLocale cLocale = QLocale::c(); + QString headRequest = + QString::fromLatin1("HEAD %1 HTTP/1.1\r\n" + "Connection: Keep-Alive\r\n" + "\r\n\r\n").arg(resource); + + ret = polarSSLWrite (&mSSL, headRequest.toUtf8()); + if (ret != 0) { + mErrorState = ConnectionLost; + return QDateTime(); + } + + response = polarSSLRead(&mSSL, 1024); + + if (response.isNull()) { + qDebug() << "No response"; + mErrorState = ConnectionLost; + return QDateTime(); + } + + while (1) { + QString line = responseStream.readLine(); + if (line.isNull()) { + break; + } + if (line.startsWith("Last-Modified:")) { + QDateTime candidate = cLocale.toDateTime(line, "'Last-Modified: 'ddd, dd MMM yyyy HH:mm:ss' GMT'"); + qDebug() << "Parsed line : " << line << " to " << candidate; + if (candidate.isValid()) { + return candidate; + } + } + } + + mErrorState = InvalidResponse; + return QDateTime(); +} + +bool Downloader::downloadFile(const QString &resource, + const QString &fileName, + size_t maxSize) +{ + int ret = -1; + size_t bytesRead = 0; + QString getRequest = + QString::fromLatin1("GET %1 HTTP/1.1\r\n\r\n").arg(resource); + + QSaveFile outputFile(fileName); + + ret = polarSSLWrite (&mSSL, getRequest.toUtf8()); + + // Open / Create the file to write to. + if (!outputFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to open file"; + return false; + } + + if (ret != 0) { + mErrorState = ConnectionLost; + return false; + } + + do { + QByteArray response = polarSSLRead(&mSSL, 8192); + if (response.isNull()) { + qDebug() << "Error reading response"; + mErrorState = ConnectionLost; + return false; + } + if (response.isEmpty()) { + /* We have read everything there is to read */ + break; + } + + outputFile.write(response); + bytesRead += response.size(); + } while (bytesRead < maxSize); + + return outputFile.commit(); +} void Downloader::run() { int ret; + QDateTime remoteModList; + QDateTime remoteModSW; if (!mInitialized) { emit error(tr("Failed to initialize SSL Module."), ErrUnknown); @@ -228,7 +408,8 @@ } ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), - mUrl.port(443)); + mUrl.port(443)); + if (ret != 0) { mErrorState = NoConnection; emit error(tr("Failed to connect to %1.").arg(mUrl.host()), @@ -246,8 +427,55 @@ return; } - qDebug() << "Connected to: " << mUrl.host(); - // TODO + remoteModSW = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test")); + remoteModList = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test")); + + if (!remoteModSW.isValid() || !remoteModList.isValid()) { + qDebug() << "Could not read headers"; + return; + } + + if (!mLastModSW.isValid() || remoteModSW > mLastModSW) { + QString dataDirectory = getDataDirectory(); + + if (dataDirectory.isEmpty()) { + qDebug() << "Failed to get data directory"; + return; + } + + QString fileName = dataDirectory.append("/SW-") + .append(remoteModSW.toString("yyyymmddHHmmss")) + .append(".exe"); + + qDebug() << "fileName: " << fileName; + + if (!downloadFile(QString::fromLatin1(SW_RESOURCE), + fileName, MAX_SW_SIZE)) { + return; + } + + emit newSoftwareAvailable(fileName, remoteModSW); + } else if (!mLastModList.isValid() || remoteModList > mLastModList) { + QString dataDirectory = getDataDirectory(); + + if (dataDirectory.isEmpty()) { + qDebug() << "Failed to get data directory"; + return; + } + + QString fileName = dataDirectory.append("/list-") + .append(remoteModSW.toString("yyyymmddHHmmss")) + .append(".txt"); + + qDebug() << "fileName: " << fileName; + + if (!downloadFile(QString::fromLatin1(LIST_RESOURCE), + fileName, MAX_LIST_SIZE)) { + return; + } + + emit newListAvailable(fileName, remoteModList); + } emit progress(tr("Closing"), 1, -1); ssl_close_notify (&mSSL); diff -r 37fc66967517 -r d8e93fa1fc93 ui/downloader.h --- a/ui/downloader.h Thu Mar 13 18:12:16 2014 +0000 +++ b/ui/downloader.h Wed Mar 12 21:26:07 2014 +0100 @@ -49,8 +49,10 @@ enum ErrorCode { NoError, NoConnection, + SSLHandshakeFailed, InvalidCertificate, InvalidPinnedCertificate, + InvalidResponse, ConnectionLost, Timeout, ErrUnknown @@ -92,6 +94,7 @@ ErrorCode mErrorState; bool mInitialized; + bool mConnectionEstablished; int mServerFD; @@ -106,13 +109,39 @@ */ int init(); - /** @brief: Establish the connection + /** @brief: Establish the ssl connection + * + * Modifies the error state. Before calling this the mServerFD should + * be set to a valid socket. * * @returns 0 on success otherwise a polarssl error or -1 is returned */ int establishSSLConnection(); -#ifdef Q_OS_WIN -#endif + + /** @brief get the last modified header of a resource. + * + * Connection should be established beforehand. + * Modifies the error state. + * + * @param[in] resource The resource to check + * + * @returns the last modified date or a null datetime in case of errors + */ + QDateTime getLastModifiedHeader(const QString &resource); + + /** @brief Download resource + * + * Download a resource with the established connection. + * Modifies the error state. + * + * @param[in] resource the resource to download + * @param[in] filename where the file should be saved. + * @param[in] maxSize maximum amount of bytes to download + * + * @returns True if the download was successful. + */ + bool downloadFile(const QString &resource, const QString &filename, + size_t maxSize); Q_SIGNALS: /**