Mercurial > trustbridge
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) { |