/* * This file is part of nzbget. See . * * Copyright (C) 2013-2016 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "nzbget.h" #include "Log.h" #include "FileSystem.h" #include "Maintenance.h" #include "Options.h" extern void ExitProc(); extern int g_ArgumentCount; extern char* (*g_Arguments)[]; #ifdef HAVE_OPENSSL class Signature { public: Signature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename); ~Signature(); bool Verify(); private: const char* m_inFilename; const char* m_sigFilename; const char* m_pubKeyFilename; uchar m_inHash[SHA256_DIGEST_LENGTH]; uchar m_signature[256]; RSA* m_pubKey; bool ReadSignature(); bool ComputeInHash(); bool ReadPubKey(); }; #endif Maintenance::~Maintenance() { bool waitScript = false; { Guard guard(m_controllerMutex); if (m_updateScriptController) { m_updateScriptController->Detach(); waitScript = true; } } if (waitScript) { while (m_updateScriptController) { Util::Sleep(20); } } } void Maintenance::ResetUpdateController() { Guard guard(m_controllerMutex); m_updateScriptController = nullptr; } void Maintenance::AddMessage(Message::EKind kind, time_t time, const char * text) { if (time == 0) { time = Util::CurrentTime(); } Guard guard(m_logMutex); m_messages.emplace_back(++m_idMessageGen, kind, time, text); } bool Maintenance::StartUpdate(EBranch branch) { bool alreadyUpdating; { Guard guard(m_controllerMutex); alreadyUpdating = m_updateScriptController != nullptr; } if (alreadyUpdating) { error("Could not start update-script: update-script is already running"); return false; } if (!ReadPackageInfoStr("install-script", m_updateScript)) { return false; } // make absolute path if (m_updateScript[0] != PATH_SEPARATOR #ifdef WIN32 && !(strlen(m_updateScript) > 2 && m_updateScript[1] == ':') #endif ) { m_updateScript = CString::FormatStr("%s%c%s", g_Options->GetAppDir(), PATH_SEPARATOR, *m_updateScript); } m_messages.clear(); m_updateScriptController = new UpdateScriptController(); m_updateScriptController->SetArgs({*m_updateScript}); m_updateScriptController->SetBranch(branch); m_updateScriptController->SetAutoDestroy(true); m_updateScriptController->Start(); return true; } bool Maintenance::CheckUpdates(CString& updateInfo) { CString updateInfoScript; if (!ReadPackageInfoStr("update-info-script", updateInfoScript)) { return false; } UpdateInfoScriptController::ExecuteScript(updateInfoScript, updateInfo); return updateInfo.Length() > 0; } bool Maintenance::ReadPackageInfoStr(const char* key, CString& value) { BString<1024> fileName("%s%cpackage-info.json", g_Options->GetWebDir(), PATH_SEPARATOR); CharBuffer packageInfo; if (!FileSystem::LoadFileIntoBuffer(fileName, packageInfo, true)) { error("Could not load file %s", *fileName); return false; } BString<100> keyStr("\"%s\"", key); char* p = strstr(packageInfo, keyStr); if (!p) { error("Could not parse file %s", *fileName); return false; } p = strchr(p + strlen(keyStr), '"'); if (!p) { error("Could not parse file %s", *fileName); return false; } p++; char* pend = strchr(p, '"'); if (!pend) { error("Could not parse file %s", *fileName); return false; } size_t len = pend - p; if (len >= sizeof(fileName)) { error("Could not parse file %s", *fileName); return false; } value.Reserve(len); strncpy(value, p, len); value[len] = '\0'; WebUtil::JsonDecode(value); return true; } bool Maintenance::VerifySignature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename) { #ifdef HAVE_OPENSSL Signature signature(inFilename, sigFilename, pubKeyFilename); return signature.Verify(); #else return false; #endif } void UpdateScriptController::Run() { // the update-script should not be automatically terminated when the program quits UnregisterRunningScript(); m_prefixLen = 0; PrintMessage(Message::mkInfo, "Executing update-script %s", GetScript()); BString<1024> infoName("update-script %s", FileSystem::BaseFileName(GetScript())); SetInfoName(infoName); const char* branchName[] = { "STABLE", "TESTING", "DEVEL" }; SetEnvVar("NZBUP_BRANCH", branchName[m_branch]); SetEnvVar("NZBUP_RUNMODE", g_Options->GetDaemonMode() ? "DAEMON" : "SERVER"); for (int i = 0; i < g_ArgumentCount; i++) { BString<100> envName("NZBUP_CMDLINE%i", i); SetEnvVar(envName, (*g_Arguments)[i]); } #ifdef WIN32 int pid = (int)GetCurrentProcessId(); #else int pid = (int)getpid(); #endif SetEnvVar("NZBUP_PROCESSID", BString<100>("%i", pid)); BString<100> logPrefix = FileSystem::BaseFileName(GetScript()); if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension SetLogPrefix(logPrefix); m_prefixLen = strlen(logPrefix) + 2; // 2 = strlen(": "); Execute(); g_Maintenance->ResetUpdateController(); } void UpdateScriptController::AddMessage(Message::EKind kind, const char* text) { text = text + m_prefixLen; if (!strncmp(text, "[NZB] ", 6)) { debug("Command %s detected", text + 6); if (!strcmp(text + 6, "QUIT")) { Detach(); ExitProc(); } else { error("Invalid command \"%s\" received", text); } } else { g_Maintenance->AddMessage(kind, Util::CurrentTime(), text); ScriptController::AddMessage(kind, text); } } void UpdateInfoScriptController::ExecuteScript(const char* script, CString& updateInfo) { detail("Executing update-info-script %s", FileSystem::BaseFileName(script)); UpdateInfoScriptController scriptController; scriptController.SetArgs({script}); BString<1024> infoName("update-info-script %s", FileSystem::BaseFileName(script)); scriptController.SetInfoName(infoName); BString<1024> logPrefix = FileSystem::BaseFileName(script); if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension scriptController.SetLogPrefix(logPrefix); scriptController.m_prefixLen = strlen(logPrefix) + 2; // 2 = strlen(": "); scriptController.Execute(); if (!scriptController.m_updateInfo.Empty()) { updateInfo = scriptController.m_updateInfo; } } void UpdateInfoScriptController::AddMessage(Message::EKind kind, const char* text) { text = text + m_prefixLen; if (!strncmp(text, "[NZB] ", 6)) { debug("Command %s detected", text + 6); if (!strncmp(text + 6, "[UPDATEINFO]", 12)) { m_updateInfo.Append(text + 6 + 12); } else { error("Invalid command \"%s\" received from %s", text, GetInfoName()); } } else { ScriptController::AddMessage(kind, text); } } #ifdef HAVE_OPENSSL Signature::Signature(const char *inFilename, const char *sigFilename, const char *pubKeyFilename) { m_inFilename = inFilename; m_sigFilename = sigFilename; m_pubKeyFilename = pubKeyFilename; m_pubKey = nullptr; } Signature::~Signature() { RSA_free(m_pubKey); } // Calculate SHA-256 for input file (m_inFilename) bool Signature::ComputeInHash() { DiskFile infile; if (!infile.Open(m_inFilename, DiskFile::omRead)) { return false; } SHA256_CTX sha256; SHA256_Init(&sha256); CharBuffer buffer(32*1024); while(int bytesRead = (int)infile.Read(buffer, buffer.Size())) { SHA256_Update(&sha256, buffer, bytesRead); } SHA256_Final(m_inHash, &sha256); infile.Close(); return true; } // Read signature from file (m_sigFilename) into memory bool Signature::ReadSignature() { BString<1024> sigTitle("\"RSA-SHA256(%s)\" : \"", FileSystem::BaseFileName(m_inFilename)); DiskFile infile; if (!infile.Open(m_sigFilename, DiskFile::omRead)) { return false; } bool ok = false; int titLen = strlen(sigTitle); char buf[1024]; uchar* output = m_signature; while (infile.ReadLine(buf, sizeof(buf) - 1)) { if (!strncmp(buf, sigTitle, titLen)) { char* hexSig = buf + titLen; int sigLen = strlen(hexSig); if (sigLen > 2) { hexSig[sigLen - 2] = '\0'; // trim trailing ", } while (*hexSig && *(hexSig+1) && output != m_signature + sizeof(m_signature)) { uchar c1 = *hexSig++; uchar c2 = *hexSig++; c1 = '0' <= c1 && c1 <= '9' ? c1 - '0' : 'A' <= c1 && c1 <= 'F' ? c1 - 'A' + 10 : 'a' <= c1 && c1 <= 'f' ? c1 - 'a' + 10 : 0; c2 = '0' <= c2 && c2 <= '9' ? c2 - '0' : 'A' <= c2 && c2 <= 'F' ? c2 - 'A' + 10 : 'a' <= c2 && c2 <= 'f' ? c2 - 'a' + 10 : 0; uchar ch = (c1 << 4) + c2; *output++ = (char)ch; } ok = output == m_signature + sizeof(m_signature); break; } } infile.Close(); return ok; } // Read public key from file (m_szPubKeyFilename) into memory bool Signature::ReadPubKey() { CharBuffer keybuf; if (!FileSystem::LoadFileIntoBuffer(m_pubKeyFilename, keybuf, false)) { return false; } BIO* mem = BIO_new_mem_buf(keybuf, keybuf.Size()); m_pubKey = PEM_read_bio_RSA_PUBKEY(mem, nullptr, nullptr, nullptr); BIO_free(mem); return m_pubKey != nullptr; } bool Signature::Verify() { return ComputeInHash() && ReadSignature() && ReadPubKey() && RSA_verify(NID_sha256, m_inHash, sizeof(m_inHash), m_signature, sizeof(m_signature), m_pubKey) == 1; } #endif /* HAVE_OPENSSL */