OSDN Git Service

Some more work to prevent DLL pre-loading attacks. Full protection is only enabled...
[mutilities/MUtilities.git] / src / UpdateChecker.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2018 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 //
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
21
22 #include <MUtils/Global.h>
23 #include <MUtils/UpdateChecker.h>
24 #include <MUtils/OSSupport.h>
25 #include <MUtils/Exception.h>
26
27 #include <QStringList>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QProcess>
31 #include <QUrl>
32 #include <QEventLoop>
33 #include <QTimer>
34 #include <QElapsedTimer>
35 #include <QSet>
36
37 #include "Mirrors.h"
38
39 ///////////////////////////////////////////////////////////////////////////////
40 // CONSTANTS
41 ///////////////////////////////////////////////////////////////////////////////
42
43 static const char *header_id = "!Update";
44
45 static const char *mirror_url_postfix[] = 
46 {
47         "update.ver",
48         "update_beta.ver",
49         NULL
50 };
51
52 static const int MIN_CONNSCORE = 5;
53 static const int QUICK_MIRRORS = 3;
54 static const int MAX_CONN_TIMEOUT = 16000;
55 static const int DOWNLOAD_TIMEOUT = 30000;
56
57 static const int VERSION_INFO_EXPIRES_MONTHS = 6;
58 static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0"; /*use something innocuous*/
59
60 #define CHECK_CANCELLED() do \
61 { \
62         if(MUTILS_BOOLIFY(m_cancelled)) \
63         { \
64                 m_success.fetchAndStoreOrdered(0); \
65                 log("", "Update check has been cancelled by user!"); \
66                 setProgress(m_maxProgress); \
67                 setStatus(UpdateStatus_CancelledByUser); \
68                 return; \
69         } \
70 } \
71 while(0)
72
73 ////////////////////////////////////////////////////////////
74 // Helper Functions
75 ////////////////////////////////////////////////////////////
76
77 static int getMaxProgress(void)
78 {
79         int counter = 0;
80         while (update_mirrors[counter])
81         {
82                 counter++;
83         }
84         counter += MIN_CONNSCORE + QUICK_MIRRORS + 2;
85         return counter; ;
86 }
87
88 static QStringList buildRandomList(const char *const values[])
89 {
90         QStringList list;
91         for (int index = 0; values[index]; index++)
92         {
93                 const int pos = MUtils::next_rand_u32() % (index + 1);
94                 list.insert(pos, QString::fromLatin1(values[index]));
95         }
96         return list;
97 }
98
99 ////////////////////////////////////////////////////////////
100 // Update Info Class
101 ////////////////////////////////////////////////////////////
102
103 MUtils::UpdateCheckerInfo::UpdateCheckerInfo(void)
104 {
105         resetInfo();
106 }
107         
108 void MUtils::UpdateCheckerInfo::resetInfo(void)
109 {
110         m_buildNo = 0;
111         m_buildDate.setDate(1900, 1, 1);
112         m_downloadSite.clear();
113         m_downloadAddress.clear();
114         m_downloadFilename.clear();
115         m_downloadFilecode.clear();
116         m_downloadChecksum.clear();
117 }
118
119 bool MUtils::UpdateCheckerInfo::isComplete(void)
120 {
121         if(this->m_buildNo < 1)                return false;
122         if(this->m_buildDate.year() < 2010)    return false;
123         if(this->m_downloadSite.isEmpty())     return false;
124         if(this->m_downloadAddress.isEmpty())  return false;
125         if(this->m_downloadFilename.isEmpty()) return false;
126         if(this->m_downloadFilecode.isEmpty()) return false;
127         if(this->m_downloadChecksum.isEmpty()) return false;
128
129         return true;
130 }
131
132 ////////////////////////////////////////////////////////////
133 // Constructor & Destructor
134 ////////////////////////////////////////////////////////////
135
136 MUtils::UpdateChecker::UpdateChecker(const QString &binWGet, const QString &binMCat, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode)
137 :
138         m_updateInfo(new UpdateCheckerInfo()),
139         m_binaryWGet(binWGet),
140         m_binaryMCat(binMCat),
141         m_binaryGnuPG(binGnuPG),
142         m_binaryKeys(binKeys),
143         m_applicationId(applicationId),
144         m_installedBuildNo(installedBuildNo),
145         m_betaUpdates(betaUpdates),
146         m_testMode(testMode),
147         m_maxProgress(getMaxProgress())
148 {
149         m_status = UpdateStatus_NotStartedYet;
150         m_progress = 0;
151
152         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
153         {
154                 MUTILS_THROW("Tools not initialized correctly!");
155         }
156 }
157
158 MUtils::UpdateChecker::~UpdateChecker(void)
159 {
160 }
161
162 ////////////////////////////////////////////////////////////
163 // Public slots
164 ////////////////////////////////////////////////////////////
165
166 void MUtils::UpdateChecker::start(Priority priority)
167 {
168         m_success.fetchAndStoreOrdered(0);
169         m_cancelled.fetchAndStoreOrdered(0);
170         QThread::start(priority);
171 }
172
173 ////////////////////////////////////////////////////////////
174 // Protected functions
175 ////////////////////////////////////////////////////////////
176
177 void MUtils::UpdateChecker::run(void)
178 {
179         qDebug("Update checker thread started!");
180         MUTILS_EXCEPTION_HANDLER(m_testMode ? testMirrorsList() : checkForUpdates());
181         qDebug("Update checker thread completed.");
182 }
183
184 void MUtils::UpdateChecker::checkForUpdates(void)
185 {
186         // ----- Initialization ----- //
187
188         m_updateInfo->resetInfo();
189         setProgress(0);
190
191         // ----- Test Internet Connection ----- //
192
193         log("Checking internet connection...", "");
194         setStatus(UpdateStatus_CheckingConnection);
195
196         const int networkStatus = OS::network_status();
197         if(networkStatus == OS::NETWORK_TYPE_NON)
198         {
199                 log("Operating system reports that the computer is currently offline !!!");
200                 setProgress(m_maxProgress);
201                 setStatus(UpdateStatus_ErrorNoConnection);
202                 return;
203         }
204         
205         setProgress(1);
206
207         // ----- Test Known Hosts Connectivity ----- //
208
209         int connectionScore = 0;
210         QStringList mirrorList = buildRandomList(known_hosts);
211
212         for(int connectionTimout = 500; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2)
213         {
214                 QElapsedTimer elapsedTimer;
215                 elapsedTimer.start();
216                 const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout;
217                 while (!elapsedTimer.hasExpired(globalTimout))
218                 {
219                         const QString hostName = mirrorList.takeFirst();
220                         if (tryContactHost(hostName, connectionTimout))
221                         {
222                                 connectionScore += 1;
223                                 setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
224                                 elapsedTimer.restart();
225                                 if (connectionScore >= MIN_CONNSCORE)
226                                 {
227                                         goto endLoop; /*success*/
228                                 }
229                         }
230                         else
231                         {
232                                 mirrorList.append(hostName); /*re-schedule*/
233                         }
234                         CHECK_CANCELLED();
235                         msleep(1);
236                 }
237         }
238
239 endLoop:
240         if(connectionScore < MIN_CONNSCORE)
241         {
242                 log("", "Connectivity test has failed: Internet connection appears to be broken!");
243                 setProgress(m_maxProgress);
244                 setStatus(UpdateStatus_ErrorConnectionTestFailed);
245                 return;
246         }
247
248         // ----- Fetch Update Info From Server ----- //
249
250         log("----", "", "Checking for updates online...");
251         setStatus(UpdateStatus_FetchingUpdates);
252
253         int mirrorCount = 0;
254         mirrorList = buildRandomList(update_mirrors);
255
256         while(!mirrorList.isEmpty())
257         {
258                 setProgress(m_progress + 1);
259                 const QString currentMirror = mirrorList.takeFirst();
260                 const bool isQuick = (mirrorCount++ < QUICK_MIRRORS);
261                 if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick))
262                 {
263                         m_success.ref(); /*success*/
264                         break;
265                 }
266                 if (isQuick)
267                 {
268                         mirrorList.append(currentMirror); /*re-schedule*/
269                 }
270                 CHECK_CANCELLED();
271                 msleep(1);
272         }
273         
274         while (m_progress < m_maxProgress)
275         {
276                 msleep(16);
277                 setProgress(m_progress + 1);
278                 CHECK_CANCELLED();
279         }
280
281         // ----- Generate final result ----- //
282
283         if(MUTILS_BOOLIFY(m_success))
284         {
285                 if(m_updateInfo->m_buildNo > m_installedBuildNo)
286                 {
287                         setStatus(UpdateStatus_CompletedUpdateAvailable);
288                 }
289                 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
290                 {
291                         setStatus(UpdateStatus_CompletedNoUpdates);
292                 }
293                 else
294                 {
295                         setStatus(UpdateStatus_CompletedNewVersionOlder);
296                 }
297         }
298         else
299         {
300                 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
301         }
302 }
303
304 void MUtils::UpdateChecker::testMirrorsList(void)
305 {
306         // ----- Test update mirrors ----- //
307
308         QStringList mirrorList;
309         for(int i = 0; update_mirrors[i]; i++)
310         {
311                 mirrorList << QString::fromLatin1(update_mirrors[i]);
312         }
313
314         qDebug("\n[Mirror Sites]");
315         log("Testing all known mirror sites...", "", "---");
316
317         UpdateCheckerInfo updateInfo;
318         while (!mirrorList.isEmpty())
319         {
320                 const QString currentMirror = mirrorList.takeFirst();
321                 bool success = false;
322                 qDebug("Testing: %s", MUTILS_L1STR(currentMirror));
323                 log("", "Testing:", currentMirror, "");
324                 for (quint8 attempt = 0; attempt < 3; ++attempt)
325                 {
326                         updateInfo.resetInfo();
327                         if (tryUpdateMirror(&updateInfo, currentMirror, (!attempt)))
328                         {
329                                 success = true;
330                                 break;
331                         }
332                 }
333                 if (!success)
334                 {
335                         qWarning("\nUpdate mirror seems to be unavailable:\n%s\n", MUTILS_L1STR(currentMirror));
336                 }
337                 log("", "---");
338         }
339
340         // ----- Test known hosts ----- //
341
342         QStringList knownHostList;
343         for (int i = 0; known_hosts[i]; i++)
344         {
345                 knownHostList << QString::fromLatin1(known_hosts[i]);
346         }
347
348         qDebug("\n[Known Hosts]");
349         log("Testing all known hosts...", "", "---");
350
351         QSet<quint32> ipAddrSet;
352         quint32 ipAddr;
353         while(!knownHostList.isEmpty())
354         {
355                 const QString currentHost = knownHostList.takeFirst();
356                 qDebug("Testing: %s", MUTILS_L1STR(currentHost));
357                 log(QLatin1String(""), "Testing:", currentHost, "");
358                 if (tryContactHost(currentHost, DOWNLOAD_TIMEOUT, &ipAddr))
359                 {
360                         if (ipAddrSet.contains(ipAddr))
361                         {
362                                 qWarning("Duplicate IP-address 0x%08X was encountered!", ipAddr);
363                         }
364                         else
365                         {
366                                 ipAddrSet << ipAddr; /*not encountered yet*/
367                         }
368                 }
369                 else
370                 {
371                         qWarning("\nConnectivity test FAILED on the following host:\n%s\n", MUTILS_L1STR(currentHost));
372                 }
373                 log("", "---");
374         }
375 }
376
377 ////////////////////////////////////////////////////////////
378 // PRIVATE FUNCTIONS
379 ////////////////////////////////////////////////////////////
380
381 void MUtils::UpdateChecker::setStatus(const int status)
382 {
383         if(m_status != status)
384         {
385                 m_status = status;
386                 emit statusChanged(status);
387         }
388 }
389
390 void MUtils::UpdateChecker::setProgress(const int progress)
391 {
392         if(m_progress != progress)
393         {
394                 m_progress = progress;
395                 emit progressChanged(progress);
396         }
397 }
398
399 void MUtils::UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
400 {
401         if(!str1.isNull()) emit messageLogged(str1);
402         if(!str2.isNull()) emit messageLogged(str2);
403         if(!str3.isNull()) emit messageLogged(str3);
404         if(!str4.isNull()) emit messageLogged(str4);
405 }
406
407 bool MUtils::UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick)
408 {
409         bool success = false;
410         log("", "Trying mirror:", url, "");
411
412         if (quick)
413         {
414                 if (!tryContactHost(QUrl(url).host(), (MAX_CONN_TIMEOUT / 10)))
415                 {
416                         log("", "Mirror is too slow, skipping!");
417                         return false;
418                 }
419         }
420
421         const QString randPart = next_rand_str();
422         const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart);
423         const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart);
424
425         if (getUpdateInfo(url, outFileVers, outFileSign))
426         {
427                 log("", "Download okay, checking signature:");
428                 if (checkSignature(outFileVers, outFileSign))
429                 {
430                         log("", "Signature okay, parsing info:", "");
431                         success = parseVersionInfo(outFileVers, updateInfo);
432                 }
433                 else
434                 {
435                         log("", "Bad signature, take care!");
436                 }
437         }
438         else
439         {
440                 log("", "Download has failed!");
441         }
442
443         QFile::remove(outFileVers);
444         QFile::remove(outFileSign);
445         
446         return success;
447 }
448
449 bool MUtils::UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
450 {
451         log("Downloading update info:", "");
452         if(getFile(QString("%1%2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers))
453         {
454                 if (!m_cancelled)
455                 {
456                         log("", "Downloading signature:", "");
457                         if (getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
458                         {
459                                 return true;
460                         }
461                 }
462         }
463         return false;
464 }
465
466 bool MUtils::UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo)
467 {
468         QRegExp value("^(\\w+)=(.+)$");
469         QRegExp section("^\\[(.+)\\]$");
470
471         QDate updateInfoDate;
472         updateInfo->resetInfo();
473
474         QFile data(file);
475         if(!data.open(QIODevice::ReadOnly))
476         {
477                 qWarning("Cannot open update info file for reading!");
478                 return false;
479         }
480         
481         bool inHdr = false;
482         bool inSec = false;
483         
484         while(!data.atEnd())
485         {
486                 QString line = QString::fromLatin1(data.readLine()).trimmed();
487                 if(section.indexIn(line) >= 0)
488                 {
489                         log(QString("Sec: [%1]").arg(section.cap(1)));
490                         inSec = (section.cap(1).compare(m_applicationId, Qt::CaseInsensitive) == 0);
491                         inHdr = (section.cap(1).compare(QString::fromLatin1(header_id), Qt::CaseInsensitive) == 0);
492                         continue;
493                 }
494                 if(inSec && (value.indexIn(line) >= 0))
495                 {
496                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
497                         if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
498                         {
499                                 bool ok = false;
500                                 const unsigned int temp = value.cap(2).toUInt(&ok);
501                                 if(ok) updateInfo->m_buildNo = temp;
502                         }
503                         else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
504                         {
505                                 const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
506                                 if(temp.isValid()) updateInfo->m_buildDate = temp;
507                         }
508                         else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
509                         {
510                                 updateInfo->m_downloadSite = value.cap(2).trimmed();
511                         }
512                         else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
513                         {
514                                 updateInfo->m_downloadAddress = value.cap(2).trimmed();
515                         }
516                         else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
517                         {
518                                 updateInfo->m_downloadFilename = value.cap(2).trimmed();
519                         }
520                         else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
521                         {
522                                 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
523                         }
524                         else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0)
525                         {
526                                 updateInfo->m_downloadChecksum = value.cap(2).trimmed();
527                         }
528                 }
529                 if(inHdr && (value.indexIn(line) >= 0))
530                 {
531                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
532                         if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
533                         {
534                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
535                                 if(temp.isValid()) updateInfoDate = temp;
536                         }
537                 }
538         }
539
540         if(!updateInfoDate.isValid())
541         {
542                 updateInfo->resetInfo();
543                 log("WARNING: Version info timestamp is missing!");
544                 return false;
545         }
546         
547         const QDate currentDate = OS::current_date();
548         if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate)
549         {
550                 updateInfo->resetInfo();
551                 log(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate)));
552                 return false;
553         }
554         else if(currentDate < updateInfoDate)
555         {
556                 log("Version info is from the future, take care!");
557                 qWarning("Version info is from the future, take care!");
558         }
559         
560         if(!updateInfo->isComplete())
561         {
562                 log("WARNING: Version info is incomplete!");
563                 return false;
564         }
565
566         return true;
567 }
568
569 //----------------------------------------------------------
570 // EXTERNAL TOOLS
571 //----------------------------------------------------------
572
573 bool MUtils::UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir)
574 {
575         for (int i = 0; i < 2; i++)
576         {
577                 if (getFile(url, (i > 0), outFile, maxRedir))
578                 {
579                         return true;
580                 }
581                 if (MUTILS_BOOLIFY(m_cancelled))
582                 {
583                         break; /*cancelled*/
584                 }
585         }
586         return false;
587 }
588
589 bool MUtils::UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir)
590 {
591         QFileInfo output(outFile);
592         output.setCaching(false);
593
594         if (output.exists())
595         {
596                 QFile::remove(output.canonicalFilePath());
597                 if (output.exists())
598                 {
599                         return false;
600                 }
601         }
602
603         QProcess process;
604         init_process(process, output.absolutePath());
605
606         QStringList args;
607         if (forceIp4)
608         {
609                 args << "-4";
610         }
611
612         args << "--no-config" << "--no-cache" << "--no-dns-cache" << "--no-check-certificate" << "--no-hsts";
613         args << QString().sprintf("--max-redirect=%u", maxRedir) << QString().sprintf("--timeout=%u", DOWNLOAD_TIMEOUT / 1000);
614         args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
615         args << "-O" << output.fileName() << url;
616
617         QEventLoop loop;
618         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
619         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
620         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
621
622         QTimer timer;
623         timer.setSingleShot(true);
624         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
625
626         const QRegExp httpResponseOK("200 OK$");
627
628         process.start(m_binaryWGet, args);
629
630         if (!process.waitForStarted())
631         {
632                 return false;
633         }
634
635         timer.start(DOWNLOAD_TIMEOUT);
636
637         while (process.state() != QProcess::NotRunning)
638         {
639                 loop.exec();
640                 const bool bTimeOut = (!timer.isActive());
641                 while (process.canReadLine())
642                 {
643                         const QString line = QString::fromLatin1(process.readLine()).simplified();
644                         log(line);
645                 }
646                 if (bTimeOut || MUTILS_BOOLIFY(m_cancelled))
647                 {
648                         qWarning("WGet process timed out <-- killing!");
649                         process.kill();
650                         process.waitForFinished();
651                         log(bTimeOut ? "!!! TIMEOUT !!!": "!!! CANCELLED !!!");
652                         return false;
653                 }
654         }
655
656         timer.stop();
657         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
658
659         log(QString().sprintf("Exited with code %d", process.exitCode()));
660         return (process.exitCode() == 0) && output.exists() && output.isFile();
661 }
662
663 bool MUtils::UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec, quint32 *const ipAddrOut)
664 {
665         log(QString("Connecting to host: %1").arg(hostname), "");
666
667         QProcess process;
668         init_process(process, temp_folder());
669
670         QStringList args;
671         args << "--retry" << QString::number(3) << hostname << QString::number(80);
672
673         QEventLoop loop;
674         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
675         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
676         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
677
678         QTimer timer;
679         timer.setSingleShot(true);
680         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
681
682         QScopedPointer<QRegExp> ipAddr;
683         if (ipAddrOut)
684         {
685                 *ipAddrOut = 0;
686                 ipAddr.reset(new QRegExp("Connecting\\s+to\\s+(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+):(\\d+)", Qt::CaseInsensitive));
687         }
688         
689         process.start(m_binaryMCat, args);
690
691         if (!process.waitForStarted())
692         {
693                 return false;
694         }
695
696         timer.start(timeoutMsec);
697
698         while (process.state() != QProcess::NotRunning)
699         {
700                 loop.exec();
701                 const bool bTimeOut = (!timer.isActive());
702                 while (process.canReadLine())
703                 {
704                         const QString line = QString::fromLatin1(process.readLine()).simplified();
705                         if (!ipAddr.isNull())
706                         {
707                                 if (ipAddr->indexIn(line) >= 0)
708                                 {
709                                         quint32 values[4];
710                                         if (MUtils::regexp_parse_uint32((*ipAddr), values, 4))
711                                         {
712                                                 *ipAddrOut |= ((values[0] & 0xFF) << 0x18);
713                                                 *ipAddrOut |= ((values[1] & 0xFF) << 0x10);
714                                                 *ipAddrOut |= ((values[2] & 0xFF) << 0x08);
715                                                 *ipAddrOut |= ((values[3] & 0xFF) << 0x00);
716                                         }
717                                 }
718                         }
719                         log(line);
720                 }
721                 if (bTimeOut || MUTILS_BOOLIFY(m_cancelled))
722                 {
723                         qWarning("MCat process timed out <-- killing!");
724                         process.kill();
725                         process.waitForFinished();
726                         log(bTimeOut ? "!!! TIMEOUT !!!" : "!!! CANCELLED !!!");
727                         return false;
728                 }
729         }
730
731         timer.stop();
732         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
733
734         if (process.exitCode() != 0)
735         {
736                 log("Connection has failed!");
737         }
738
739         log(QString().sprintf("Exited with code %d", process.exitCode()), "");
740         return (process.exitCode() == 0);
741 }
742
743 bool MUtils::UpdateChecker::checkSignature(const QString &file, const QString &signature)
744 {
745         if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
746         {
747                 qWarning("CheckSignature: File and signature should be in same folder!");
748                 return false;
749         }
750
751         QString keyRingPath(m_binaryKeys);
752         bool removeKeyring = false;
753         if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
754         {
755                 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
756                 removeKeyring = true;
757                 if (!QFile::copy(m_binaryKeys, keyRingPath))
758                 {
759                         qWarning("CheckSignature: Failed to copy the key-ring file!");
760                         return false;
761                 }
762         }
763
764         QProcess process;
765         init_process(process, QFileInfo(file).absolutePath());
766
767         QEventLoop loop;
768         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
769         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
770         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
771
772         process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
773
774         if (!process.waitForStarted())
775         {
776                 if (removeKeyring)
777                 {
778                         remove_file(keyRingPath);
779                 }
780                 return false;
781         }
782
783         while (process.state() == QProcess::Running)
784         {
785                 loop.exec();
786                 while (process.canReadLine())
787                 {
788                         log(QString::fromLatin1(process.readLine()).simplified());
789                 }
790         }
791
792         if (removeKeyring)
793         {
794                 remove_file(keyRingPath);
795         }
796
797         log(QString().sprintf("Exited with code %d", process.exitCode()));
798         return (process.exitCode() == 0);
799 }
800
801 ////////////////////////////////////////////////////////////
802 // SLOTS
803 ////////////////////////////////////////////////////////////
804
805 /*NONE*/
806
807 ////////////////////////////////////////////////////////////
808 // EVENTS
809 ////////////////////////////////////////////////////////////
810
811 /*NONE*/