#include "fbgui.h" #include "sysinfo.h" #include "downloadmanager.h" #include "javascriptinterface.h" #include #include "qlog4cxx.h" using namespace log4cxx; using namespace log4cxx::helpers; LoggerPtr coreLogger(Logger::getLogger("fbgui.core")); #include #include #include QThread dmThread; QString ipConfigFilePath(""); QString binPath(""); QUrl baseURL(""); QString downloadPath(""); int updateInterval = -1; QString fileToTriggerURL(""); QString serialLocation(""); QString sessionID(""); bool sslSupport; //int debugMode=-1; //QString logFilePath(""); //------------------------------------------------------------------------------------------- /** * A constructor. * * The constructor of the fbgui class. It initializes the main objects * which are needed while the program is running. * The appearance of the webView is here also defined. * * @see JavascriptInterface * @see DownloadManager */ fbgui::fbgui() : agui(){ } fbgui::~fbgui(){ dmThread.quit(); } /** * init function. */ void fbgui::init() { // start fbgui LOG4CXX_DEBUG(coreLogger, "Initializing fbgui..."); if(sslSupport) LOG4CXX_DEBUG(coreLogger, "SSL enabled."); // initialize javascript interface JavascriptInterface* jsi = new JavascriptInterface( _webView->page()->mainFrame()); QObject::connect(jsi, SIGNAL(quitFbgui()), this, SLOT(close())); QObject::connect(jsi, SIGNAL(shutDownClient()), this, SLOT(shutdownSystem())); QObject::connect(_webView->page()->mainFrame(), SIGNAL( javaScriptWindowObjectCleared()), jsi, SLOT(attachToDOM())); // initialize download manager DownloadManager* dm = new DownloadManager(); QObject::connect(dm, SIGNAL(downloadInfo(const QString&, const double&)), jsi, SLOT(downloadInfo(const QString&, const double&))); QObject::connect(dm, SIGNAL(notify(const QString&)), jsi, SLOT(notify(const QString&))); QObject::connect(jsi, SIGNAL(requestFile(const QString&)), dm, SLOT(downloadFile(const QString&))); QObject::connect( dm, SIGNAL(updateProgress(const int&, const double&, const QString&)), jsi, SLOT(updateProgressBar(const int&, const double&, const QString&))); QObject::connect(dm, SIGNAL(downloadQueueEmpty()), jsi, SLOT( callbackOnFinished())); QObject::connect(dm, SIGNAL(downloadQueueEmpty()), this, SLOT(loadSystem())); // move download manager to its own thread dm->moveToThread(&dmThread); dmThread.start(); loadURL(); // set properties setWindowTitle("fbgui"); showFullScreen(); } //------------------------------------------------------------------------------------------- // Preparations for URL load //------------------------------------------------------------------------------------------- /** * This method checks the existance of the host. * * This method checks if the host exists / can be found. * The host is from the URL given through the configuration. */ bool fbgui::checkHost() const { QHostInfo hostInfo = QHostInfo::fromName(baseURL.host()); if (hostInfo.error() != QHostInfo::NoError) { LOG4CXX_DEBUG(coreLogger, "Lookup of " << baseURL.host() << "failed."); LOG4CXX_DEBUG(coreLogger, "Host can not be reached."); return false; } else { LOG4CXX_DEBUG(coreLogger, "Lookup of " << baseURL.host() << " succeeded."); return true; } } //------------------------------------------------------------------------------------------- /** * This method tries loads the URL. * * This method loads the main screen via an POST request. If also disconnects the watcher * of the file, (Watcher is set in the fbgui::watchForTrigger() method). * and generates the POST data body. * * @see fbgui::watchForTrigger() * @see fbgui::generatePOSTData() */ void fbgui::loadURL() { if (checkHost()) { LOG4CXX_DEBUG(coreLogger, "Loading URL: " << baseURL.toString() << " ..."); // Generate POST identification data needed by PBS. QByteArray postData = generatePOSTData(); // Generate a Network Request Object _qnr = new QNetworkRequest(baseURL); //Add OpenSLX Certificate to SSLConfiguration QList certList = QSslCertificate::fromPath(QLatin1String("/usr/lib/ssl/openslx/CA/certs/openslx-cert.pem")); certList.append(QSslCertificate::fromPath(QLatin1String("/usr/lib/ssl/openslx/CA/certs/pbsfr-cert.pem"))); setCACertificates(certList); //Ignore the SelfSignedCertificateInChain-error for the OpenSLX-Certificate QSslError error(QSslError::SelfSignedCertificateInChain, certList.at(0)); _expectedSslErrors.append(error); //Add User Certificate to SSLConfiguration QList userCertList = QSslCertificate::fromPath(QLatin1String("/usr/lib/ssl/openslx/CA/certs/guest-cert.pem")); setLocalCertificate(userCertList.at(0)); //Add User PrivateKey to SSLConfiguration QFile keyFile("/usr/lib/ssl/openslx/CA/private/guest.pem"); Q_ASSERT(keyFile.open(QIODevice::ReadOnly)); QByteArray keyContent = keyFile.readAll(); setPrivateKey(QSslKey(keyContent, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray("guest"))); //Connect webViews NetworkAccessManager to SSLErrorHandler SLOT QObject::connect(_webView->page()->networkAccessManager(), SIGNAL(finished(QNetworkReply*)), this, SLOT(httpErrorHandler(QNetworkReply*))); //Connect webViews NetworkAccessManager to ErrorHandler SLOT QObject::connect(_webView->page()->networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*, const QList & )), this, SLOT(sslErrorHandler(QNetworkReply*, const QList & ))); // show cursor again since user is about to interact. //QWSServer::instance()->setCursorVisible(true); //TODO: ?enabled in original _qnr->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(loadURLDone(bool))); _webView->load(*_qnr, QNetworkAccessManager::PostOperation, postData); // ////////////////////TEST/////////////////////// // QSslSocket qssReg = new QSslSocket(this); // qssReg.setSslConfiguration(_qnr->sslConfiguration()); // // QHttp http = new QHttp(QLatin1String("pbs2.mp.openslx.org"), QHttp::ConnectionModeHttps, 443, this); // http.setSocket(&qssReg); // // //QObject::connect(http, SIGNAL(done(bool)), this, SLOT(done(bool))); // QObject::connect(&http, SIGNAL(sslErrors(const QList &)), this, // SLOT(sslErrorHandler(const QList & ))); } // TODO: error page if no host. } void fbgui::loadURLDone(bool success) { // done contains the success of the loading: false / true if (!success) { LOG4CXX_DEBUG(coreLogger, "Loading failed. URL: " << _webView->url().toString()); LOG4CXX_DEBUG(coreLogger, "You can quit with CTRL + X ..."); // TODO handle failure properly... } else { LOG4CXX_DEBUG(coreLogger, "Loaded URL: " << _webView->url().toString()); } } //Handles QNetworkReply SSL Errors void fbgui::sslErrorHandler(QNetworkReply* reply, const QList & errlist) { foreach (QSslError err, errlist) LOG4CXX_DEBUG(coreLogger, "SSL Error: " << err.error()); // QSslConfiguration replySslConf = reply->sslConfiguration(); // foreach (QSslCertificate cert, replySslConf.peerCertificateChain()) // LOG4CXX_DEBUG(coreLogger,"Cert info: \n" << cert.toPem()); reply->ignoreSslErrors(_expectedSslErrors); } //Handles QNetworkReply Errors void fbgui::httpErrorHandler(QNetworkReply* reply) { if(reply->error() != QNetworkReply::NoError ) LOG4CXX_DEBUG(coreLogger, "HTTP Error: " << reply->errorString()); } //Dump all Certificates in SSLConfiguration void fbgui::dumpSSLConfiguration(){ QSslConfiguration sslConfig = _qnr->sslConfiguration(); LOG4CXX_DEBUG(coreLogger,"****************SSLConfiguration************************** \n"); LOG4CXX_DEBUG(coreLogger,"CACertificates: \n"); foreach (QSslCertificate cert, sslConfig.caCertificates()){ LOG4CXX_DEBUG(coreLogger,cert.toPem() << "\n"); } LOG4CXX_DEBUG(coreLogger,"----------------------------------------------------- \n"); LOG4CXX_DEBUG(coreLogger,"LocalCertificate: \n"); LOG4CXX_DEBUG(coreLogger,sslConfig.localCertificate().toPem() << "\n"); LOG4CXX_DEBUG(coreLogger,"----------------------------------------------------- \n"); LOG4CXX_DEBUG(coreLogger,"Private Key: \n"); LOG4CXX_DEBUG(coreLogger,sslConfig.privateKey().toPem() << "\n"); LOG4CXX_DEBUG(coreLogger,"********************************************************** \n"); } //Saves CACertificates to SslConfiguration void fbgui::setCACertificates(const QList & certificates){ foreach (QSslCertificate cert, certificates){ LOG4CXX_DEBUG(coreLogger,"Is cert valid: " << cert.isValid()); LOG4CXX_DEBUG(coreLogger,"Cert Issuer: " << cert.issuerInfo(QSslCertificate::CommonName)); LOG4CXX_DEBUG(coreLogger,"Cert Subject: " << cert.subjectInfo(QSslCertificate::CommonName)); } QSslConfiguration sslConfig = _qnr->sslConfiguration(); QList caCerts = sslConfig.caCertificates(); caCerts.append(certificates); sslConfig.setCaCertificates(caCerts); _qnr->setSslConfiguration(sslConfig); } //Saves User Certificate to SslConfiguration void fbgui::setLocalCertificate(const QSslCertificate& cert){ LOG4CXX_DEBUG(coreLogger,"Is cert valid: " << cert.isValid()); LOG4CXX_DEBUG(coreLogger,"Cert Issuer: " << cert.issuerInfo(QSslCertificate::CommonName)); LOG4CXX_DEBUG(coreLogger,"Cert Subject: " << cert.subjectInfo(QSslCertificate::CommonName)); QSslConfiguration sslConfig = _qnr->sslConfiguration(); sslConfig.setLocalCertificate(cert); _qnr->setSslConfiguration(sslConfig); } //Saves PrivateKey to SslConfiguration void fbgui::setPrivateKey(const QSslKey & key){ LOG4CXX_DEBUG(coreLogger,"Key length: " << key.length()); QSslConfiguration sslConfig = _qnr->sslConfiguration(); sslConfig.setPrivateKey(key); _qnr->setSslConfiguration(sslConfig); } //------------------------------------------------------------------------------------------- /** * This method generates the POST data body. * * This method generates the POST data body. The body contains the * MAC address, an hardwarehash and a specific serial number. * The hardwarehash is a MD5 hash over the MAC address and the * mainboard serial number. * The specific serial number is set at the creation of the usb boot stick. * This file has to be present on the directory specified in * the configuration for this to work. * * @see SysInfo::getMACAddress() * @see SysInfo::getMainboardSerial() */ QByteArray fbgui::generatePOSTData() { LOG4CXX_DEBUG(coreLogger, "Generating POST data..."); // use MAC address as base data SysInfo si; QByteArray data(si.getInfo("mac").toUtf8()); // append mainboard serial to the mac address for more unique hardwarehash QByteArray mbserial(si.getInfo("mbserial").toUtf8()); if (!mbserial.isEmpty()) data.append(mbserial); else { LOG4CXX_DEBUG(coreLogger, "Mainboard serial was empty. Not appending to base hash data."); } LOG4CXX_DEBUG(coreLogger, "[post] Hashing: " << data); // generate MD5 hash of data QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Md5); LOG4CXX_DEBUG(coreLogger, "[post] MD5 Hash: " << hash.toHex()); // fetch serial number from usb QByteArray serial; QFile file(serialLocation); if (!file.open(QIODevice::ReadOnly)) { LOG4CXX_DEBUG(coreLogger, "[post] No such file: " << file.fileName()); } // everything ok, read data serial = file.readAll(); file.close(); serial.chop(1); // chop EOF LOG4CXX_DEBUG(coreLogger, "[post] Serial number is: " << serial); // construct final byte array QByteArray postData("mac="); postData.append(si.getInfo("mac")); postData.append("&hardwarehash=" + hash.toHex()); postData.append("&serialnumber=" + serial); LOG4CXX_DEBUG(coreLogger, "[post] POST data: " << postData); return postData; } //------------------------------------------------------------------------------------------- // Preparing Kernel Switch per kexec (initiating Stage 3) //------------------------------------------------------------------------------------------- void fbgui::loadSystem() { //show loading system page. //_webView->disconnect(this); //QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(prepareKexec())); _webView->load(QUrl("qrc:/html/loadsystem.html")); QTimer::singleShot(1000, this, SLOT(prepareKexec())); } //------------------------------------------------------------------------------------------- /** * This method prepares kexec. * * The kernel command line file that should have been downloaded from the Preboot-Server * and the ip config file (created by udhcpc) are merged into the final completed KCL. * * A process is then started to load the kernel, initramfs and kcl into kexec. * The process tries to execute kexec -l with these parameters. * * If this succeeds, runKexec() is called * * @see fbgui::runKexec() * */ void fbgui::prepareKexec() { LOG4CXX_DEBUG(coreLogger, "Preparing kexec ..."); // try to read KCL file that was downloaded. QFile file(downloadPath + "/kcl"); if (!file.open(QIODevice::ReadOnly)) { LOG4CXX_DEBUG(coreLogger, "No such file: " << file.fileName()); } // everything ok, read data. QString kcl = file.readAll(); file.close(); LOG4CXX_DEBUG(coreLogger, "KCL from PBS: " << kcl); // try to read ipconfig file generated by udhcpc. file.setFileName("/tmp/ip_config_fbgui"); if (!file.open(QIODevice::ReadOnly)) { LOG4CXX_DEBUG(coreLogger, "No such file: " << file.fileName()); } // everything ok, read data. QString ipConfig = file.readAll(); file.close(); LOG4CXX_DEBUG(coreLogger, "IP config: " << ipConfig); // append ipConfig kcl.append(" ip="); kcl.append(ipConfig); LOG4CXX_DEBUG(coreLogger, "Complete KCL: " << kcl); // load the kernel + initramfs + append of kcl into kexec. QProcess *process = new QProcess(this); QString cmdline = "kexec -l " + downloadPath.toUtf8() + "/kernel --initrd=" + downloadPath.toUtf8() + "/initramfs --append=\"" + kcl.toUtf8() + "\""; LOG4CXX_DEBUG(coreLogger, "kexec cmdline: " << cmdline); process->start(cmdline); bool ret = process->waitForFinished(); if (!ret) { LOG4CXX_DEBUG(coreLogger, "Failed to execute: " << cmdline); LOG4CXX_DEBUG(coreLogger, "Exiting in 5 seconds..."); QTimer::singleShot(5000, this, SLOT(close())); } else { LOG4CXX_DEBUG(coreLogger, "Kexec load was successfull."); if (debugMode < 0) { // if process successfully finished, try to run kexec -e runKexec(); } else { LOG4CXX_DEBUG(coreLogger, "Skipping execution of kexec - open debug shell."); LOG4CXX_DEBUG(coreLogger, "To start the system execute \"kexec -e\" in your shell."); close(); } } } //------------------------------------------------------------------------------------------- /** * This method tries to execute: kexec -e * * This method tries to execute: kexec -e * */ void fbgui::runKexec() { QProcess *process = new QProcess(this); process->start("kexec -e"); if (!process->waitForStarted()) { LOG4CXX_DEBUG(coreLogger, "Failed to execute: kexec -e"); LOG4CXX_DEBUG(coreLogger, "Exiting in 5 seconds..."); QTimer::singleShot(5000, this, SLOT(close())); //TODO: Handle failure properly... } }