aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
aheinecke@404:  * Software engineering by Intevation GmbH
aheinecke@404:  *
aheinecke@404:  * This file is Free Software under the GNU GPL (v>=2)
aheinecke@404:  * and comes with ABSOLUTELY NO WARRANTY!
aheinecke@404:  * See LICENSE.txt for details.
aheinecke@404:  */
aheinecke@10: #include "downloader.h"
aheinecke@10: 
aheinecke@10: #ifndef DOWNLOAD_SERVER
andre@1088: #define DOWNLOAD_SERVER "https://tb-devel.intevation.de"
aheinecke@10: #endif
aheinecke@10: 
aheinecke@10: #include <QFile>
aheinecke@15: #include <QDir>
aheinecke@10: #include <QDebug>
aheinecke@15: #include <QStandardPaths>
andre@32: #include <QLocale>
andre@32: #include <QSaveFile>
andre@956: #include <QSettings>
andre@27: 
andre@27: #include <polarssl/net.h>
andre@27: #include <polarssl/ssl.h>
andre@27: #include <polarssl/entropy.h>
andre@27: #include <polarssl/ctr_drbg.h>
andre@27: #include <polarssl/error.h>
andre@27: #include <polarssl/certs.h>
andre@990: #include <polarssl/ssl_ciphersuites.h>
andre@27: 
andre@808: #define MAX_SW_SIZE 15728640
andre@27: #define MAX_LIST_SIZE 1048576
andre@32: 
andre@924: #ifdef USE_CURL
andre@908: #include "sslconnection_curl.h"
andre@924: #else
andre@908: #include "sslconnection_bare.h"
andre@924: #endif
aheinecke@10: 
andre@990: static int accept_ciphers[] = {
andre@990:     TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
andre@990:     TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
andre@990:     TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
andre@990:     0
andre@990: };
andre@990: 
aheinecke@15: Downloader::Downloader(QObject* parent, const QString& url,
aheinecke@15:                        const QByteArray& certificate,
aheinecke@15:                        const QDateTime& newestSW,
aheinecke@45:                        const QDateTime& newestList,
aheinecke@45:                        const QString& resourceSW,
rrenkert@460:                        const QString& resourceList,
rrenkert@460:                        bool downloadSW):
aheinecke@12:     QThread(parent),
aheinecke@15:     mLastModSW(newestSW),
andre@27:     mLastModList(newestList),
aheinecke@45:     mResourceSW(resourceSW),
aheinecke@45:     mResourceList(resourceList),
andre@908:     mDownloadSW(downloadSW)
aheinecke@10: {
andre@908: #ifdef USE_CURL
andre@908:     mSSLConnection = new SSLConnectionCurl(url, certificate);
andre@956: /* Set up Proxy support. */
andre@956:     QSettings settings;
andre@956:     QString settingsProxy = settings.value("ProxyURL").toString();
andre@1062:     bool useProxy = settings.value("UseProxy", false).toBool();
andre@1062:     if (useProxy && settingsProxy.isEmpty()) {
andre@956:         QByteArray envProxy = qgetenv("http_proxy");
andre@956:         if (envProxy.size()) {
andre@956:             settingsProxy = QString::fromLocal8Bit(envProxy);
andre@956:         }
andre@956:     }
andre@1062:     if (useProxy && !settingsProxy.isEmpty()) {
andre@956:         mSSLConnection->setProxy(QUrl(settingsProxy));
andre@956:     }
andre@908: #else
andre@908:     mSSLConnection = new SSLConnectionBare(url, certificate);
andre@908: #endif
andre@990:     setCiphersuites(accept_ciphers);
andre@27: }
andre@27: 
andre@27: Downloader::~Downloader() {
andre@908:     delete mSSLConnection;
aheinecke@10: }
aheinecke@10: 
andre@990: void Downloader::setCiphersuites(int suites[]) {
andre@990:     mSSLConnection->setCiphersuites(suites);
andre@990: }
andre@990: 
aheinecke@15: QString Downloader::getDataDirectory()
aheinecke@10: {
aheinecke@15:     QString candidate =
aheinecke@15:         QStandardPaths::writableLocation(QStandardPaths::DataLocation);
aheinecke@12: 
aheinecke@15:     if (candidate.isEmpty()) {
aheinecke@15:         qDebug() << "Could not find writeable locaction for me";
aheinecke@15:         return QString();
aheinecke@15:     }
aheinecke@15: 
aheinecke@15:     QDir cDir(candidate);
aheinecke@15: 
aheinecke@15:     if (!cDir.exists()) {
aheinecke@15:         if (!cDir.mkpath(candidate)) {
aheinecke@15:             qDebug() << "Could not create path to: " << candidate;
aheinecke@15:             return QString();
aheinecke@15:         }
aheinecke@15:     }
aheinecke@15:     return cDir.absolutePath();
aheinecke@10: }
andre@27: 
andre@27: 
andre@27: void Downloader::run() {
andre@27:     int ret;
andre@32:     QDateTime remoteModList;
andre@32:     QDateTime remoteModSW;
andre@27: 
andre@908:     if (!mSSLConnection->initialized()) {
aheinecke@45:         emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown);
andre@27:         return;
andre@27:     }
andre@27: 
andre@908:     ret = mSSLConnection->connect();
andre@32: 
andre@27:     if (ret != 0) {
aheinecke@45:         emit error(tr("Failed to connect."),
andre@908:                    mSSLConnection->getLastError());
andre@27:         return;
andre@27:     }
andre@27: 
andre@27:     emit progress(tr("Connected"), 1, -1);
andre@27: 
andre@910:     remoteModSW = mSSLConnection->getLastModifiedHeader(mResourceSW);
rrenkert@485:     emit lastModifiedDate(remoteModSW);
andre@32: 
aheinecke@459:     if (!remoteModSW.isValid()) {
andre@910:         emit error (tr("Connection failed."), SSLConnection::InvalidResponse);
aheinecke@459:         qDebug() << "Could not parse headers for Software";
andre@32:         return;
andre@32:     }
andre@32: 
andre@32:     if (!mLastModSW.isValid() || remoteModSW > mLastModSW) {
andre@32:         QString dataDirectory = getDataDirectory();
andre@32: 
andre@32:         if (dataDirectory.isEmpty()) {
andre@32:             qDebug() << "Failed to get data directory";
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         QString fileName = dataDirectory.append("/SW-")
aheinecke@442:             .append(remoteModSW.toString("yyyyMMddHHmmss"))
andre@809: #ifdef WIN32
andre@32:             .append(".exe");
andre@809: #else
andre@809:             .append(".sh");
andre@809: #endif
andre@32: 
andre@32:         qDebug() << "fileName: " << fileName;
andre@32: 
rrenkert@460:         if (mDownloadSW) {
andre@910:             if (!mSSLConnection->downloadFile(mResourceSW, fileName, MAX_SW_SIZE)) {
andre@1227:                 emit error(tr("Failed to download File.") + "\n"
andre@1227:                         + tr("The connection to the update server was lost or"
andre@1227:                             " the disk is full."), SSLConnection::ConnectionLost);
andre@809:                 qDebug() << "Failed to download software update.";
rrenkert@460:                 return;
rrenkert@460:             }
andre@809:             QFile::setPermissions(fileName, QFileDevice::ReadOwner |
andre@809:                                             QFileDevice::WriteOwner |
andre@809:                                             QFileDevice::ExeOwner);
andre@32:         }
andre@32: 
andre@32:         emit newSoftwareAvailable(fileName, remoteModSW);
aheinecke@458:         return;
aheinecke@458:     }
aheinecke@458: 
andre@910:     remoteModList = mSSLConnection->getLastModifiedHeader(mResourceList);
aheinecke@459:     if (!remoteModList.isValid()) {
andre@910:         emit error (tr("Connection failed."), SSLConnection::InvalidResponse);
aheinecke@459:         qDebug() << "Could not parse headers for List";
aheinecke@459:         return;
aheinecke@459:     }
aheinecke@458: 
aheinecke@458:     if (!mLastModList.isValid() || remoteModList > mLastModList) {
andre@32:         QString dataDirectory = getDataDirectory();
andre@32: 
andre@32:         if (dataDirectory.isEmpty()) {
andre@32:             qDebug() << "Failed to get data directory";
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         QString fileName = dataDirectory.append("/list-")
rrenkert@443:             .append(remoteModList.toString("yyyyMMddHHmmss"))
andre@32:             .append(".txt");
andre@32: 
andre@32:         qDebug() << "fileName: " << fileName;
andre@32: 
andre@910:         if (!mSSLConnection->downloadFile(mResourceList, fileName, MAX_LIST_SIZE)) {
andre@32:             return;
andre@32:         }
andre@32: 
andre@32:         emit newListAvailable(fileName, remoteModList);
andre@32:     }
andre@27: 
andre@27:     emit progress(tr("Closing"), 1, -1);
andre@27: }