#include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "qmlhandler.h" #include #include #include #include #include using boost::lexical_cast; using boost::asio::buffer; using namespace std; int inpipefd[2]; int outpipefd[2]; char buf[1025]; QUrl sendFileUrl = QUrl(""); QString _IP = ""; bool _CLI_RUNNING = false; bool _RESTART = false; pid_t childpid; bool programActive = true; QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {} void QMLHandler::closeCLI() { _CLI_RUNNING = false; } void QMLHandler::loadSettingsToGUI() { int covertMethod = lexical_cast(Config::getValue("Covert-Channel-Method")); bool saveIP = lexical_cast(Config::getValue("Autofill-IP")); bool saveUsername = lexical_cast(Config::getValue("Autofill-Username")); QString cliPath = QString::fromStdString(Config::getValue("CLI-Path")); emit loadSettings(covertMethod, saveIP, saveUsername, cliPath); } void QMLHandler::reopenCLI(QString ip) { if (_CLI_RUNNING) { waitpid(childpid, NULL, 0); closeCLI(); } _IP = ip; pipe(inpipefd); pipe(outpipefd); childpid = fork(); if (childpid == 0) { // Child dup2(outpipefd[0], STDIN_FILENO); dup2(inpipefd[1], STDOUT_FILENO); // dup2(inpipefd[1], STDERR_FILENO); // ask kernel to deliver SIGTERM in case the parent dies prctl(PR_SET_PDEATHSIG, SIGTERM); // Set the path to the CLI - pass argument h (help) for now // TODO: Change hardcoded path // execl("../../cli/build/ccats-cli", "ccats-cli", ip.toUtf8().constData(), "--machine", (char *)NULL); execl(Config::getValue("CLI-Path").c_str(), "ccats-cli", ip.toUtf8().constData(), "--machine", (char *)NULL); exit(1); } _CLI_RUNNING = true; close(outpipefd[0]); close(inpipefd[1]); std::thread(&QMLHandler::readPipeLoop, this).detach(); } std::vector tokenizeByNewlines(std::string in) { vector res; size_t index; for (index = in.find("\n"); index != std::string::npos; index = in.find("\n")) { if (index != 0) res.push_back(in.substr(0, index)); in = in.substr(index + 1); } if (in.length() > 0) res.push_back(in); return res; } void QMLHandler::onExit() { write(outpipefd[1], "disconnect\n", strlen("disconnect\n")); } // This method gets a string and tries to read it as Json // If it fails to do so, return and do nothing, else handle the content void QMLHandler::handleJSON(string buffer) { Json::Value root; Json::CharReaderBuilder builder; Json::CharReader *reader = builder.newCharReader(); string jsonError; // Try to parse the string as Json and store the result of the pasring in a // boolean bool parsingSuccessful = reader->parse(buffer.c_str(), buffer.c_str() + buffer.size(), &root, &jsonError); // If the string is not correct Json, return if (!parsingSuccessful) { return; } const Json::Value command = root["command"]; string cmd = command.asString(); qInfo() << QString::fromStdString("Received command " + cmd); if (!cmd.compare("status")) { emit footerSetStatus(root["response"].asString().c_str()); } else if (!cmd.compare("close")) { programActive = false; } else if (!cmd.compare("list")) { if (root["accept"] == true) { emit receivingClearFileList(); // Get the array of file Names auto fileNames = root["names"]; for (int i = 0; i < fileNames.size(); i++) { emit receivingListFile(QString::fromStdString(fileNames[i].asString().c_str()), boost::filesystem::exists(fileNames[i].asString())); } } else { emit log(root["error"].asString().c_str()); } } else if (!cmd.compare("connect")) { if (root["accept"].asBool()) { } else { emit ipPopupSetStatus(root["error"].asString().c_str()); closeCLI(); emit ipPopupEnableConnectButton(); } } else if (!cmd.compare("version")) { if (root["accept"] == true) { emit ipPopupClose(); emit loginSignupPopupOpen(); } else { QString errorMessage = QString::fromStdString( string("Version mismatch: \nClient: " + root["clientversion"].asString() + "\nServer: " + root["serverversion"].asString())); emit ipPopupSetStatus(errorMessage); closeCLI(); emit ipPopupEnableConnectButton(); } } else if (!cmd.compare("login")) { if (root["accept"] == true) { emit loginSignupPopupClose(); write(outpipefd[1], "list\n", strlen("list\n")); loadSettingsToGUI(); } else { emit loginSetStatus(root["error"].asString().c_str()); reopenCLI(_IP); emit loginEnableLoginButton(); } } else if (!cmd.compare("signup")) { if (root["accept"] == true) { emit loginSignupPopupClose(); } else { emit signupSetStatus(root["error"].asString().c_str()); reopenCLI(_IP); emit signupEnableRegisterButton(); } } else if (!cmd.compare("put")) { if (root["accept"] == false) { QString errorMessage = QString::fromStdString(string("Error when uploading file " + root["file"].asString() + ":\n" + root["error"].asString())); emit log(errorMessage); } } else if (!cmd.compare("putdata")) { // TODO: Show speed and handle Error } else if (!cmd.compare("get")) { if (root["accept"] == false) { QString errorMessage = QString::fromStdString(string("Error when downloading file " + root["file"].asString() + ":\n" + root["error"].asString())); emit log(errorMessage); } else { string fileName = root["file"].asString(); // TODO: Only do this in getdata when remaining is 0 (when the file is fully downloaded) - maybe set text to "downloading.." in between emit receivingDisableDownloadButton(QString::fromStdString(fileName)); } } else if (!cmd.compare("getdata")) { // TODO: Show speed and handle Error } else if (!cmd.compare("deleteme")) { if (root["accept"] == true) { _RESTART = true; emit closeWindow(); } else { QString errorMessage = QString::fromStdString(root["error"].asString()); emit deleteMePopupSetStatus(errorMessage); } } else if (!cmd.compare("deletefile")) { emit receivingCloseConfirmDeletePopup(); if (root["accept"] == false) { QString errorMessage = QString::fromStdString(string("Error when deleting file " + root["file"].asString() + ":\n" + root["error"].asString())); emit log(errorMessage); } else { QString message = QString::fromStdString(string("Deleted file " + root["file"].asString() + " from the server!")); emit log(message); } } } // This method is a loop which runs in a seperate thread. // If will read the Input Pipe, which containts the string that get printed // on stdout by the CLI/Server, and calls handleJSON with the read content // one it reads a newline. void QMLHandler::readPipeLoop() { unsigned int readOffset = 0; unsigned int pollCount = 0; struct pollfd inPipeStatus; inPipeStatus.fd = inpipefd[0]; inPipeStatus.events = POLLIN; vector inputs; string pipeInput; while (programActive && _CLI_RUNNING) { inputs = vector(); poll(&inPipeStatus, 1, 100); if (inPipeStatus.revents & POLLIN) { readOffset += read(inpipefd[0], buf + readOffset, 1); pollCount = 0; } else { pollCount++; } if (pollCount > 4 && (readOffset || pipeInput.size())) { pipeInput.append(buf); inputs = tokenizeByNewlines(pipeInput); for (string s : inputs) { emit log(QString::fromStdString(s)); qInfo() << QString::fromStdString(s); handleJSON(s); } pipeInput = string(); memset(buf, 0, 1025); pollCount = 0; readOffset = 0; if (waitpid(childpid, NULL, WNOHANG)) { // nonzero means error or childid has changed state // for us that means child has exited -> CLI is dead break; } } if (readOffset >= 1024) { pipeInput.append(buf); readOffset = 0; pollCount = 0; memset(buf, 0, 1025); } } } // ### QML Handlers ### void QMLHandler::onStart() { bool configExists = Config::loadFile(); if (configExists == true) { // Config exists if (Config::checkConfig() == true) { // Config is valid if (!boost::filesystem::is_regular_file(Config::getValue("CLI-Path")) || boost::filesystem::path(Config::getValue("CLI-Path")).filename().compare("ccats-cli")) { // Invalid CLI Path emit invalidCliPathPopupOpen(); } if (Config::getValue("Autofill-IP") == "1") { emit ipPopupSetIP(QString::fromStdString(Config::getValue("Default-IP"))); emit ipPopupCheckSaveCheckbox(); } if (Config::getValue("Autofill-Username") == "1") { emit loginSetUsername(QString::fromStdString(Config::getValue("Default-Username"))); emit loginSignupCheckSaveCheckbox(); } } else { // Config is invalid emit invalidConfigPopupOpen(); } } else { // Config doesn't exist Config::setupDefaultConfig(); emit noConfigFoundPopupOpen(); } } // No Config Found Popup void QMLHandler::onNoConfigFoundPopupContinueButton(QString cli_path) { Config::setValue("CLI-Path", cli_path.toUtf8().constData()); Config::saveFile(); } // Invalid Cli Path Popup void QMLHandler::onInvalidCliPathPopupContinueButton(QString cli_path) { Config::setValue("CLI-Path", cli_path.toUtf8().constData()); Config::saveFile(); } void QMLHandler::onInvalidCliPathPopupQuitButton() { emit closeWindow(); } // Invalid Config Popup void QMLHandler::onInvalidConfigPopupQuitButton() { emit closeWindow(); } void QMLHandler::onInvalidConfigPopupCreateDefaultButton() { Config::setupDefaultConfig(); emit noConfigFoundPopupOpen(); } // Sending void QMLHandler::onSendingSelectFileButton(QUrl url) { sendFileUrl = url.toLocalFile(); emit log("File Selected: " + sendFileUrl.toString()); emit sendingSetFileUrlText("Selected File: " + sendFileUrl.toString()); emit sendingEnableSendButton(); } void QMLHandler::onSendingSendFileButton() { QString command = "put " + sendFileUrl.toString() + "\n"; write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); } void QMLHandler::onSendingClearSelectionButton() { sendFileUrl = QUrl(""); emit log("Cleared Selection"); emit sendingSetFileUrlText("Selected File: None"); emit sendingDisableSendButton(); } // Receiving void QMLHandler::onReceivingListFilesButton() { write(outpipefd[1], "list\n", strlen("list\n")); } void QMLHandler::onReceivingDownloadFileButton(QString fileName) { QString command = "get " + fileName + "\n"; write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); } void QMLHandler::onReceivingConfirmDeleteFileButton(QString fileName) { QString command = "deletefile " + fileName + "\n"; write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); } // Messages void QMLHandler::onMessagesSendButton(QString msg) { emit message(msg); } // Settings void QMLHandler::onSettingsDeleteMeButton(QString password) { QString command = "deleteme " + password + "\n"; write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); } void QMLHandler::onSettingsRevertChangesButton() { loadSettingsToGUI(); emit log("Settings changes reverted."); } void QMLHandler::onSettingsResetButton() { string cli_path = Config::getValue("CLI-Path"); Config::setupDefaultConfig(); Config::setValue("CLI-Path", cli_path); loadSettingsToGUI(); emit log("Settings resetted to default."); } void QMLHandler::onSettingsSaveButton(int covertMethod, bool saveIP, bool saveUsername, QString cliPath) { Config::setValue("Covert-Channel-Method", lexical_cast(covertMethod)); Config::setValue("Autofill-IP", lexical_cast(saveIP)); Config::setValue("Autofill-Username", lexical_cast(saveUsername)); Config::setValue("CLI-Path", cliPath.toUtf8().constData()); Config::saveFile(); emit log("Settings saved."); } // Ip Popup void QMLHandler::onIpPopupConnectButton(QString ip, bool saveAsDefault) { reopenCLI(ip); emit ipPopupDisableConnectButton(); if (saveAsDefault) { Config::setValue("Default-IP", ip.toUtf8().constData()); Config::setValue("Autofill-IP", "1"); Config::saveFile(); } } // Login void QMLHandler::onLoginLoginButton(QString username, QString password, bool saveAsDefault) { QString command = "login " + username + " " + password + "\n"; write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); emit loginDisableLoginButton(); if (saveAsDefault) { Config::setValue("Default-Username", username.toUtf8().constData()); Config::setValue("Autofill-Username", "1"); Config::saveFile(); } } // Signup void QMLHandler::onSignupRegisterButton(QString username, QString passwordOne, QString passwordTwo, bool saveAsDefault) { if (QString::compare(passwordOne, passwordTwo, Qt::CaseSensitive)) { emit signupSetStatus("Passwords don't match"); return; } QString command = "signup " + username + " " + passwordOne + "\n"; write(outpipefd[1], command.toUtf8().constData(), strlen(command.toUtf8().constData())); emit signupDisableRegisterButton(); if (saveAsDefault) { Config::setValue("Default-Username", username.toUtf8().constData()); Config::setValue("Autofill-Username", "1"); Config::saveFile(); } } // Footer void QMLHandler::onFooterGetStatusButton() { write(outpipefd[1], "status\n", strlen("status\n")); }