andre@908: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik andre@908: * Software engineering by Intevation GmbH andre@908: * andre@908: * This file is Free Software under the GNU GPL (v>=2) andre@908: * and comes with ABSOLUTELY NO WARRANTY! andre@908: * See LICENSE.txt for details. andre@908: */ andre@908: andre@908: #include "sslconnection_curl.h" andre@910: #include andre@908: andre@908: #define CONNECTION_DEBUG andre@908: andre@908: SSLConnectionCurl::SSLConnectionCurl(const QString& url, andre@908: const QByteArray& certificate): andre@908: SSLConnection (url, certificate), andre@908: mCurl (NULL) andre@908: { andre@942: if (certificate.isEmpty()) { andre@956: /* Security: curl does not support leaf certificate pinning. So andre@956: * while the bare connection pins the certificate directly the andre@956: * curl implementation pins the issuer of the peer certificate andre@956: * andre@956: * CURLINFO_TLS_SESSION is also not implmented for polarssl andre@956: * so there is no way to obtain / verify peer certificate manually andre@956: * at this point. andre@956: **/ andre@956: #ifdef RELEASE_BUILD andre@956: #error "Curl release build is not supported at this moment." andre@956: #else andre@956: QFile certResource(":certs/geotrust"); andre@956: #endif andre@942: certResource.open(QFile::ReadOnly); andre@942: mPinnedCert = certResource.readAll(); andre@942: certResource.close(); andre@942: } andre@942: andre@908: curl_global_init(CURL_GLOBAL_DEFAULT); andre@908: mCurl = curl_easy_init(); andre@908: andre@908: if (!mCurl) { andre@908: qDebug() << "Failed to initialize curl"; andre@908: return; andre@908: } andre@908: andre@908: if (curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 1L) != CURLE_OK) { andre@908: /* Should be default anyway */ andre@908: qDebug() << "Setting verifypeer failed"; andre@908: return; andre@908: } andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_ERRORBUFFER, mErrBuf) != CURLE_OK) { andre@910: qDebug() << "Setting errorbuf failed"; andre@910: return; andre@910: } andre@910: andre@908: mCertFile.open(); andre@908: if (mCertFile.write(mPinnedCert) != mPinnedCert.size()) { andre@908: qDebug() << "Failed to write temporary certificate"; andre@908: return; andre@908: } andre@908: mCertFile.close(); andre@908: andre@908: if (curl_easy_setopt(mCurl, CURLOPT_CAINFO, andre@908: mCertFile.fileName().toUtf8().constData()) != CURLE_OK) { andre@908: qDebug() << "Failed to write temporary certificate"; andre@908: return; andre@908: } andre@908: mInitialized = true; andre@908: andre@908: #ifdef CONNECTION_DEBUG andre@908: curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L); andre@908: #endif andre@908: } andre@908: andre@908: SSLConnectionCurl::~SSLConnectionCurl() { andre@908: if (mCurl) { andre@908: curl_easy_cleanup (mCurl); andre@908: } andre@908: if (mInitialized) { andre@908: mCertFile.close(); andre@908: } andre@908: curl_global_cleanup(); andre@908: } andre@908: andre@908: int SSLConnectionCurl::connect() { andre@910: CURLcode retval; andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_URL, mUrl.toEncoded().constData()) != CURLE_OK) { andre@910: qDebug() << "Failed to set URL"; andre@910: return -1; andre@910: } andre@910: andre@908: if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 1L) != CURLE_OK) { andre@908: qDebug() << "Failed to set connect only option"; andre@908: return -1; andre@908: } andre@910: retval = curl_easy_perform(mCurl); andre@910: if (retval != CURLE_OK) { andre@910: qDebug() << "Failed to connect: " << mErrBuf << " retval: " << retval; andre@910: if (retval == CURLE_PEER_FAILED_VERIFICATION) { andre@910: mErrorState = InvalidCertificate; andre@910: return -1; andre@910: } andre@910: andre@908: mErrorState = NoConnection; andre@908: return -1; andre@908: } andre@908: mConnected = true; andre@908: return 0; andre@908: } andre@908: andre@910: /* Globally do this as we can't pass this to the c function */ andre@910: size_t ssl_curl_max_write, ssl_curl_written; andre@908: andre@910: size_t write_data(void *ptr, size_t size, size_t nmemb, andre@910: QSaveFile *fp) andre@910: { andre@910: qDebug() << "Writing size: " << size << " * " << nmemb; andre@910: if (ssl_curl_max_write < ssl_curl_written) { andre@910: qDebug() << "Aborting write. Too much data."; andre@910: return 0; andre@908: } andre@910: size_t written = fp->write((const char *)ptr, size * nmemb); andre@910: if (written != size * nmemb) { andre@910: qDebug() << "Failed to write data. Written: " << written andre@910: << " requested: " << size * nmemb; andre@910: return 0; andre@908: } andre@910: ssl_curl_written += written; andre@910: return written; andre@908: } andre@908: andre@910: bool SSLConnectionCurl::downloadFile(const QString &resource, andre@910: const QString &fileName, andre@910: size_t maxSize) andre@908: { andre@910: QSaveFile outputFile(fileName); andre@910: ssl_curl_written = 0; andre@910: ssl_curl_max_write = maxSize; andre@910: // Open / Create the file to write to. andre@910: if (!outputFile.open(QIODevice::WriteOnly)) { andre@910: qDebug() << "Failed to open file"; andre@910: return false; andre@910: } andre@910: QUrl urlCopy = mUrl; andre@910: urlCopy.setPath(resource); andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_URL, urlCopy.toEncoded().constData()) != CURLE_OK) { andre@910: qDebug() << "Failed to set URL"; andre@910: return false; andre@910: } andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set connect"; andre@910: return false; andre@910: } andre@908: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_HEADER, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set header"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_NOBODY, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set no body"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, write_data) != CURLE_OK) { andre@910: qDebug() << "Failed to set write function"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, &outputFile) != CURLE_OK) { andre@910: qDebug() << "Failed to set write function"; andre@910: return false; andre@910: } andre@910: andre@910: if (curl_easy_perform (mCurl) != CURLE_OK) { andre@910: qDebug() << "Failed to perform download."; andre@910: return false; andre@910: } andre@910: andre@910: if (!outputFile.commit()) { andre@910: qDebug() << "Failed to commit data to filesystem."; andre@910: return false; andre@910: } andre@910: andre@910: return true; andre@908: } andre@910: andre@910: QDateTime SSLConnectionCurl::getLastModifiedHeader(const QString &resource) { andre@910: QUrl urlCopy = mUrl; andre@910: urlCopy.setPath(resource); andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_URL, urlCopy.toEncoded().constData()) != CURLE_OK) { andre@910: qDebug() << "Failed to set URL"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L) != CURLE_OK) { andre@910: qDebug() << "Failed to set connect"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_HEADER, 1L) != CURLE_OK) { andre@910: qDebug() << "Failed to set header"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_NOBODY, 1L) != CURLE_OK) { andre@910: qDebug() << "Failed to set no body"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_setopt(mCurl, CURLOPT_FILETIME, 1L) != CURLE_OK) { andre@910: qDebug() << "Failed to set filetime"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (curl_easy_perform (mCurl) != CURLE_OK) { andre@910: qDebug() << "Failed to perform last modified check."; andre@910: return QDateTime(); andre@910: } andre@910: long filetime = 0; andre@910: andre@910: if (curl_easy_getinfo (mCurl, CURLINFO_FILETIME, &filetime) != CURLE_OK) { andre@910: qDebug() << "Failed to get filetime"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: if (filetime == -1) { andre@910: qDebug() << "Invalid Time"; andre@910: return QDateTime(); andre@910: } andre@910: return QDateTime::fromTime_t(filetime); andre@910: } andre@956: andre@956: void SSLConnectionCurl::setProxy(const QUrl& proxyUrl) { andre@956: if (curl_easy_setopt(mCurl, CURLOPT_PROXY, proxyUrl.toEncoded().constData()) != CURLE_OK) { andre@956: qDebug() << "Failed to set proxy"; andre@956: return; andre@956: } andre@956: qDebug() << "Set proxy to: " << proxyUrl; andre@956: }