comparison ui/downloader.cpp @ 32:d8e93fa1fc93

Downloader logic
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 12 Mar 2014 21:26:07 +0100
parents 62cd56cea09b
children 56ba6376426e
comparison
equal deleted inserted replaced
31:37fc66967517 32:d8e93fa1fc93
8 #include <QDir> 8 #include <QDir>
9 #include <QDebug> 9 #include <QDebug>
10 #include <QStandardPaths> 10 #include <QStandardPaths>
11 #include <QUuid> 11 #include <QUuid>
12 #include <QApplication> 12 #include <QApplication>
13 #include <QTextStream>
14 #include <QLocale>
15 #include <QSaveFile>
13 16
14 #include <polarssl/net.h> 17 #include <polarssl/net.h>
15 #include <polarssl/ssl.h> 18 #include <polarssl/ssl.h>
16 #include <polarssl/entropy.h> 19 #include <polarssl/entropy.h>
17 #include <polarssl/ctr_drbg.h> 20 #include <polarssl/ctr_drbg.h>
18 #include <polarssl/error.h> 21 #include <polarssl/error.h>
19 #include <polarssl/certs.h> 22 #include <polarssl/certs.h>
20 23
21 #define MAX_SW_SIZE 10485760 24 #define MAX_SW_SIZE 10485760
22 #define MAX_LIST_SIZE 1048576 25 #define MAX_LIST_SIZE 1048576
26 #define MAX_IO_TRIES 10
27
28 #define LIST_RESOURCE "/incoming/aheinecke/test"
29 #define SW_RESOURCE "/incoming/aheinecke/test"
30
31
32 #ifdef CONNECTION_DEBUG
33 static void my_debug(void *ctx, int level, const char *str)
34 {
35 fprintf((FILE *) ctx, "%s", str);
36 fflush((FILE *) ctx);
37 }
38 #endif
23 39
24 QString getErrorMsg(int ret) 40 QString getErrorMsg(int ret)
25 { 41 {
26 char errbuf[255]; 42 char errbuf[255];
27 polarssl_strerror(ret, errbuf, 255); 43 polarssl_strerror(ret, errbuf, 255);
106 ssl_set_endpoint(&mSSL, SSL_IS_CLIENT); 122 ssl_set_endpoint(&mSSL, SSL_IS_CLIENT);
107 ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL); 123 ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL);
108 ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); 124 ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL);
109 ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); 125 ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED);
110 ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); 126 ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg);
127 #ifdef RELEASE_BUILD
128 ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3);
129 #endif
130
131 #ifdef CONNECTION_DEBUG
132 ssl_set_dbg(&mSSL, my_debug, stdout);
133 #endif
111 134
112 return 0; 135 return 0;
113 } 136 }
114 137
115 Downloader::~Downloader() { 138 Downloader::~Downloader() {
143 166
144 int Downloader::establishSSLConnection() { 167 int Downloader::establishSSLConnection() {
145 int ret = -1; 168 int ret = -1;
146 const x509_crt *peerCert; 169 const x509_crt *peerCert;
147 170
148 mErrorState = ErrUnknown; 171 if (mServerFD == -1 || !mInitialized) {
149 172 mErrorState = ErrUnknown;
150 if (mServerFD == -1) {
151 return -1; 173 return -1;
152 } 174 }
153 175
154 ssl_set_bio(&mSSL, net_recv, &mServerFD, 176 ssl_set_bio(&mSSL, net_recv, &mServerFD,
155 net_send, &mServerFD); 177 net_send, &mServerFD);
157 while ((ret = ssl_handshake(&mSSL)) != 0) { 179 while ((ret = ssl_handshake(&mSSL)) != 0) {
158 if (ret != POLARSSL_ERR_NET_WANT_READ && 180 if (ret != POLARSSL_ERR_NET_WANT_READ &&
159 ret != POLARSSL_ERR_NET_WANT_WRITE) { 181 ret != POLARSSL_ERR_NET_WANT_WRITE) {
160 qDebug() << "SSL Handshake failed: " 182 qDebug() << "SSL Handshake failed: "
161 << getErrorMsg(ret); 183 << getErrorMsg(ret);
184 mErrorState = SSLHandshakeFailed;
162 return ret; 185 return ret;
163 } 186 }
164 } 187 }
165 188
189 /* we might want to set the verify function
190 * with ssl_set_verify before to archive the
191 * certificate pinning. */
192
166 ret = ssl_get_verify_result(&mSSL); 193 ret = ssl_get_verify_result(&mSSL);
167 194
168 if (ret != 0 ) { 195 if (ret != 0 ) {
169 196 if((ret & BADCERT_EXPIRED) != 0)
170 if( ( ret & BADCERT_EXPIRED ) != 0 ) 197 qDebug() << "server certificate has expired";
171 qDebug() << "server certificate has expired"; 198 if((ret & BADCERT_REVOKED) != 0)
172 199 qDebug() << "server certificate has been revoked";
173 if( ( ret & BADCERT_REVOKED ) != 0 ) 200 if((ret & BADCERT_CN_MISMATCH) != 0)
174 qDebug() << "server certificate has been revoked"; 201 qDebug() << "CN mismatch";
175 202 if((ret & BADCERT_NOT_TRUSTED) != 0)
176 if( ( ret & BADCERT_CN_MISMATCH ) != 0 )
177 qDebug() << "CN mismatch";
178
179 if( ( ret & BADCERT_NOT_TRUSTED ) != 0 )
180 qDebug() << "self-signed or not signed by a trusted CA"; 203 qDebug() << "self-signed or not signed by a trusted CA";
181 204 ret = -1;
182 #ifdef RELEASE_BUILD 205 #ifdef RELEASE_BUILD
206 mErrorState = InvalidCertificate;
183 return -1; 207 return -1;
184 #endif 208 #endif
185 } 209 }
186 210
187 peerCert = ssl_get_peer_cert(&mSSL); 211 peerCert = ssl_get_peer_cert(&mSSL);
212 qDebug() << "Certificate content mismatch"; 236 qDebug() << "Certificate content mismatch";
213 mErrorState = InvalidCertificate; 237 mErrorState = InvalidCertificate;
214 return -1; 238 return -1;
215 } 239 }
216 } 240 }
217 mErrorState = NoError;
218 return 0; 241 return 0;
219 } 242 }
220 243
244 /* Helper around polarssl bare bone api */
245 int polarSSLWrite (ssl_context *ssl, const QByteArray& request)
246 {
247 unsigned int tries = 0;
248 int ret = -1;
249
250 const unsigned char *buf = (const unsigned char *) request.constData();
251 size_t len = (size_t) request.size();
252
253 qDebug() << "Seinding request: " << request;
254 /* According to doc for ssl_write:
255 *
256 * When this function returns POLARSSL_ERR_NET_WANT_WRITE,
257 * it must be called later with the same arguments,
258 * until it returns a positive value.
259 */
260 do {
261 ret = ssl_write(ssl, buf, len);
262 if (ret >= 0) {
263 if ((unsigned int) ret == len) {
264 return 0;
265 } else {
266 qDebug() << "Write failed to write everything";
267 return -1;
268 }
269 }
270
271 if (ret != POLARSSL_ERR_NET_WANT_WRITE) {
272 return ret;
273 }
274 tries++;
275 net_usleep(100000); /* sleep 100ms to give the socket a chance
276 to clean up. */
277 } while (tries < MAX_IO_TRIES);
278
279 return ret;
280 }
281
282 /* Helper around polarssl bare bone api read at most len bytes
283 * and return them as a byte array returns a NULL byte array on error*/
284 QByteArray polarSSLRead (ssl_context *ssl, size_t len)
285 {
286 unsigned char buf[len];
287 QByteArray retval("");
288 int ret = -1;
289
290 do {
291 memset (buf, 0, sizeof(buf));
292 ret = ssl_read(ssl, buf, len);
293 if (ret == 0 ||
294 ret == POLARSSL_ERR_SSL_CONN_EOF) {
295 /* EOF */
296 return retval;
297 }
298 if (ret <= 0) {
299 qDebug() << "Read failed: " << getErrorMsg(ret);
300 return QByteArray();
301 }
302 if (len < (len - (unsigned int) ret)) {
303 /* Should never happen if ssl_read behaves */
304 qDebug() << "integer overflow in polarSSLRead";
305 return QByteArray();
306 }
307 len -= (unsigned int) ret;
308 retval.append((const char *)buf, len);
309 } while (len > 0);
310
311 return retval;
312 }
313
314
315 QDateTime Downloader::getLastModifiedHeader(const QString &resource) {
316 int ret = -1;
317 QByteArray response;
318 QTextStream responseStream(&response);
319 QLocale cLocale = QLocale::c();
320 QString headRequest =
321 QString::fromLatin1("HEAD %1 HTTP/1.1\r\n"
322 "Connection: Keep-Alive\r\n"
323 "\r\n\r\n").arg(resource);
324
325 ret = polarSSLWrite (&mSSL, headRequest.toUtf8());
326 if (ret != 0) {
327 mErrorState = ConnectionLost;
328 return QDateTime();
329 }
330
331 response = polarSSLRead(&mSSL, 1024);
332
333 if (response.isNull()) {
334 qDebug() << "No response";
335 mErrorState = ConnectionLost;
336 return QDateTime();
337 }
338
339 while (1) {
340 QString line = responseStream.readLine();
341 if (line.isNull()) {
342 break;
343 }
344 if (line.startsWith("Last-Modified:")) {
345 QDateTime candidate = cLocale.toDateTime(line, "'Last-Modified: 'ddd, dd MMM yyyy HH:mm:ss' GMT'");
346 qDebug() << "Parsed line : " << line << " to " << candidate;
347 if (candidate.isValid()) {
348 return candidate;
349 }
350 }
351 }
352
353 mErrorState = InvalidResponse;
354 return QDateTime();
355 }
356
357 bool Downloader::downloadFile(const QString &resource,
358 const QString &fileName,
359 size_t maxSize)
360 {
361 int ret = -1;
362 size_t bytesRead = 0;
363 QString getRequest =
364 QString::fromLatin1("GET %1 HTTP/1.1\r\n\r\n").arg(resource);
365
366 QSaveFile outputFile(fileName);
367
368 ret = polarSSLWrite (&mSSL, getRequest.toUtf8());
369
370 // Open / Create the file to write to.
371 if (!outputFile.open(QIODevice::WriteOnly)) {
372 qDebug() << "Failed to open file";
373 return false;
374 }
375
376 if (ret != 0) {
377 mErrorState = ConnectionLost;
378 return false;
379 }
380
381 do {
382 QByteArray response = polarSSLRead(&mSSL, 8192);
383 if (response.isNull()) {
384 qDebug() << "Error reading response";
385 mErrorState = ConnectionLost;
386 return false;
387 }
388 if (response.isEmpty()) {
389 /* We have read everything there is to read */
390 break;
391 }
392
393 outputFile.write(response);
394 bytesRead += response.size();
395 } while (bytesRead < maxSize);
396
397 return outputFile.commit();
398 }
221 399
222 void Downloader::run() { 400 void Downloader::run() {
223 int ret; 401 int ret;
402 QDateTime remoteModList;
403 QDateTime remoteModSW;
224 404
225 if (!mInitialized) { 405 if (!mInitialized) {
226 emit error(tr("Failed to initialize SSL Module."), ErrUnknown); 406 emit error(tr("Failed to initialize SSL Module."), ErrUnknown);
227 return; 407 return;
228 } 408 }
229 409
230 ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), 410 ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
231 mUrl.port(443)); 411 mUrl.port(443));
412
232 if (ret != 0) { 413 if (ret != 0) {
233 mErrorState = NoConnection; 414 mErrorState = NoConnection;
234 emit error(tr("Failed to connect to %1.").arg(mUrl.host()), 415 emit error(tr("Failed to connect to %1.").arg(mUrl.host()),
235 mErrorState); 416 mErrorState);
236 return; 417 return;
244 emit error(tr("Failed to connect to %1.").arg(mUrl.host()), 425 emit error(tr("Failed to connect to %1.").arg(mUrl.host()),
245 mErrorState); 426 mErrorState);
246 return; 427 return;
247 } 428 }
248 429
249 qDebug() << "Connected to: " << mUrl.host(); 430 remoteModSW = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test"));
250 // TODO 431 remoteModList = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test"));
432
433 if (!remoteModSW.isValid() || !remoteModList.isValid()) {
434 qDebug() << "Could not read headers";
435 return;
436 }
437
438 if (!mLastModSW.isValid() || remoteModSW > mLastModSW) {
439 QString dataDirectory = getDataDirectory();
440
441 if (dataDirectory.isEmpty()) {
442 qDebug() << "Failed to get data directory";
443 return;
444 }
445
446 QString fileName = dataDirectory.append("/SW-")
447 .append(remoteModSW.toString("yyyymmddHHmmss"))
448 .append(".exe");
449
450 qDebug() << "fileName: " << fileName;
451
452 if (!downloadFile(QString::fromLatin1(SW_RESOURCE),
453 fileName, MAX_SW_SIZE)) {
454 return;
455 }
456
457 emit newSoftwareAvailable(fileName, remoteModSW);
458 } else if (!mLastModList.isValid() || remoteModList > mLastModList) {
459 QString dataDirectory = getDataDirectory();
460
461 if (dataDirectory.isEmpty()) {
462 qDebug() << "Failed to get data directory";
463 return;
464 }
465
466 QString fileName = dataDirectory.append("/list-")
467 .append(remoteModSW.toString("yyyymmddHHmmss"))
468 .append(".txt");
469
470 qDebug() << "fileName: " << fileName;
471
472 if (!downloadFile(QString::fromLatin1(LIST_RESOURCE),
473 fileName, MAX_LIST_SIZE)) {
474 return;
475 }
476
477 emit newListAvailable(fileName, remoteModList);
478 }
251 479
252 emit progress(tr("Closing"), 1, -1); 480 emit progress(tr("Closing"), 1, -1);
253 ssl_close_notify (&mSSL); 481 ssl_close_notify (&mSSL);
254 482
255 if (mServerFD != -1) { 483 if (mServerFD != -1) {

http://wald.intevation.org/projects/trustbridge/