mirror of
https://github.com/nzbget/nzbget.git
synced 2026-04-21 13:26:53 -04:00
485 lines
11 KiB
C++
485 lines
11 KiB
C++
/*
|
|
* This file is part of nzbget
|
|
*
|
|
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* $Revision$
|
|
* $Date$
|
|
*
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
#include "Log.h"
|
|
#include "Util.h"
|
|
#include "Maintenance.h"
|
|
#include "Options.h"
|
|
#include "CommandLineParser.h"
|
|
|
|
extern void ExitProc();
|
|
extern int g_ArgumentCount;
|
|
extern char* (*g_Arguments)[];
|
|
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
class Signature
|
|
{
|
|
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();
|
|
|
|
public:
|
|
Signature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename);
|
|
~Signature();
|
|
bool Verify();
|
|
};
|
|
#endif
|
|
|
|
|
|
Maintenance::Maintenance()
|
|
{
|
|
m_idMessageGen = 0;
|
|
m_updateScriptController = NULL;
|
|
m_updateScript = NULL;
|
|
}
|
|
|
|
Maintenance::~Maintenance()
|
|
{
|
|
m_controllerMutex.Lock();
|
|
if (m_updateScriptController)
|
|
{
|
|
m_updateScriptController->Detach();
|
|
m_controllerMutex.Unlock();
|
|
while (m_updateScriptController)
|
|
{
|
|
usleep(20*1000);
|
|
}
|
|
}
|
|
|
|
m_messages.Clear();
|
|
|
|
free(m_updateScript);
|
|
}
|
|
|
|
void Maintenance::ResetUpdateController()
|
|
{
|
|
m_controllerMutex.Lock();
|
|
m_updateScriptController = NULL;
|
|
m_controllerMutex.Unlock();
|
|
}
|
|
|
|
MessageList* Maintenance::LockMessages()
|
|
{
|
|
m_logMutex.Lock();
|
|
return &m_messages;
|
|
}
|
|
|
|
void Maintenance::UnlockMessages()
|
|
{
|
|
m_logMutex.Unlock();
|
|
}
|
|
|
|
void Maintenance::AddMessage(Message::EKind kind, time_t time, const char * text)
|
|
{
|
|
if (time == 0)
|
|
{
|
|
time = ::time(NULL);
|
|
}
|
|
|
|
m_logMutex.Lock();
|
|
Message* message = new Message(++m_idMessageGen, kind, time, text);
|
|
m_messages.push_back(message);
|
|
m_logMutex.Unlock();
|
|
}
|
|
|
|
bool Maintenance::StartUpdate(EBranch branch)
|
|
{
|
|
m_controllerMutex.Lock();
|
|
bool alreadyUpdating = m_updateScriptController != NULL;
|
|
m_controllerMutex.Unlock();
|
|
|
|
if (alreadyUpdating)
|
|
{
|
|
error("Could not start update-script: update-script is already running");
|
|
return false;
|
|
}
|
|
|
|
if (m_updateScript)
|
|
{
|
|
free(m_updateScript);
|
|
m_updateScript = NULL;
|
|
}
|
|
|
|
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
|
|
)
|
|
{
|
|
char filename[MAX_PATH + 100];
|
|
snprintf(filename, sizeof(filename), "%s%c%s", g_Options->GetAppDir(), PATH_SEPARATOR, m_updateScript);
|
|
free(m_updateScript);
|
|
m_updateScript = strdup(filename);
|
|
}
|
|
|
|
m_messages.Clear();
|
|
|
|
m_updateScriptController = new UpdateScriptController();
|
|
m_updateScriptController->SetScript(m_updateScript);
|
|
m_updateScriptController->SetBranch(branch);
|
|
m_updateScriptController->SetAutoDestroy(true);
|
|
|
|
m_updateScriptController->Start();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Maintenance::CheckUpdates(char** updateInfo)
|
|
{
|
|
char* updateInfoScript;
|
|
if (!ReadPackageInfoStr("update-info-script", &updateInfoScript))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*updateInfo = NULL;
|
|
UpdateInfoScriptController::ExecuteScript(updateInfoScript, updateInfo);
|
|
|
|
free(updateInfoScript);
|
|
|
|
return *updateInfo;
|
|
}
|
|
|
|
bool Maintenance::ReadPackageInfoStr(const char* key, char** value)
|
|
{
|
|
char fileName[1024];
|
|
snprintf(fileName, 1024, "%s%cpackage-info.json", g_Options->GetWebDir(), PATH_SEPARATOR);
|
|
fileName[1024-1] = '\0';
|
|
|
|
char* packageInfo;
|
|
int packageInfoLen;
|
|
if (!Util::LoadFileIntoBuffer(fileName, &packageInfo, &packageInfoLen))
|
|
{
|
|
error("Could not load file %s", fileName);
|
|
return false;
|
|
}
|
|
|
|
char keyStr[100];
|
|
snprintf(keyStr, 100, "\"%s\"", key);
|
|
keyStr[100-1] = '\0';
|
|
|
|
char* p = strstr(packageInfo, keyStr);
|
|
if (!p)
|
|
{
|
|
error("Could not parse file %s", fileName);
|
|
free(packageInfo);
|
|
return false;
|
|
}
|
|
|
|
p = strchr(p + strlen(keyStr), '"');
|
|
if (!p)
|
|
{
|
|
error("Could not parse file %s", fileName);
|
|
free(packageInfo);
|
|
return false;
|
|
}
|
|
|
|
p++;
|
|
char* pend = strchr(p, '"');
|
|
if (!pend)
|
|
{
|
|
error("Could not parse file %s", fileName);
|
|
free(packageInfo);
|
|
return false;
|
|
}
|
|
|
|
int len = pend - p;
|
|
if (len >= sizeof(fileName))
|
|
{
|
|
error("Could not parse file %s", fileName);
|
|
free(packageInfo);
|
|
return false;
|
|
}
|
|
|
|
*value = (char*)malloc(len+1);
|
|
strncpy(*value, p, len);
|
|
(*value)[len] = '\0';
|
|
|
|
WebUtil::JsonDecode(*value);
|
|
|
|
free(packageInfo);
|
|
|
|
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());
|
|
|
|
char infoName[1024];
|
|
snprintf(infoName, 1024, "update-script %s", Util::BaseFileName(GetScript()));
|
|
infoName[1024-1] = '\0';
|
|
SetInfoName(infoName);
|
|
|
|
const char* branchName[] = { "STABLE", "TESTING", "DEVEL" };
|
|
SetEnvVar("NZBUP_BRANCH", branchName[m_branch]);
|
|
|
|
SetEnvVar("NZBUP_RUNMODE", g_CommandLineParser->GetDaemonMode() ? "DAEMON" : "SERVER");
|
|
|
|
for (int i = 0; i < g_ArgumentCount; i++)
|
|
{
|
|
char envName[40];
|
|
snprintf(envName, 40, "NZBUP_CMDLINE%i", i);
|
|
infoName[40-1] = '\0';
|
|
SetEnvVar(envName, (*g_Arguments)[i]);
|
|
}
|
|
|
|
char processId[20];
|
|
#ifdef WIN32
|
|
int pid = (int)GetCurrentProcessId();
|
|
#else
|
|
int pid = (int)getpid();
|
|
#endif
|
|
snprintf(processId, 20, "%i", pid);
|
|
processId[20-1] = '\0';
|
|
SetEnvVar("NZBUP_PROCESSID", processId);
|
|
|
|
char logPrefix[100];
|
|
strncpy(logPrefix, Util::BaseFileName(GetScript()), 100);
|
|
logPrefix[100-1] = '\0';
|
|
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, time(NULL), text);
|
|
ScriptController::AddMessage(kind, text);
|
|
}
|
|
}
|
|
|
|
void UpdateInfoScriptController::ExecuteScript(const char* script, char** updateInfo)
|
|
{
|
|
detail("Executing update-info-script %s", Util::BaseFileName(script));
|
|
|
|
UpdateInfoScriptController* scriptController = new UpdateInfoScriptController();
|
|
scriptController->SetScript(script);
|
|
|
|
char infoName[1024];
|
|
snprintf(infoName, 1024, "update-info-script %s", Util::BaseFileName(script));
|
|
infoName[1024-1] = '\0';
|
|
scriptController->SetInfoName(infoName);
|
|
|
|
char logPrefix[1024];
|
|
strncpy(logPrefix, Util::BaseFileName(script), 1024);
|
|
logPrefix[1024-1] = '\0';
|
|
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.GetBuffer())
|
|
{
|
|
int len = strlen(scriptController->m_updateInfo.GetBuffer());
|
|
*updateInfo = (char*)malloc(len + 1);
|
|
strncpy(*updateInfo, scriptController->m_updateInfo.GetBuffer(), len);
|
|
(*updateInfo)[len] = '\0';
|
|
}
|
|
|
|
delete scriptController;
|
|
}
|
|
|
|
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 = NULL;
|
|
}
|
|
|
|
Signature::~Signature()
|
|
{
|
|
RSA_free(m_pubKey);
|
|
}
|
|
|
|
// Calculate SHA-256 for input file (m_szInFilename)
|
|
bool Signature::ComputeInHash()
|
|
{
|
|
FILE* infile = fopen(m_inFilename, FOPEN_RB);
|
|
if (!infile)
|
|
{
|
|
return false;
|
|
}
|
|
SHA256_CTX sha256;
|
|
SHA256_Init(&sha256);
|
|
const int bufSize = 32*1024;
|
|
char* buffer = (char*)malloc(bufSize);
|
|
while(int bytesRead = fread(buffer, 1, bufSize, infile))
|
|
{
|
|
SHA256_Update(&sha256, buffer, bytesRead);
|
|
}
|
|
SHA256_Final(m_inHash, &sha256);
|
|
free(buffer);
|
|
fclose(infile);
|
|
return true;
|
|
}
|
|
|
|
// Read signature from file (m_szSigFilename) into memory
|
|
bool Signature::ReadSignature()
|
|
{
|
|
char sigTitle[256];
|
|
snprintf(sigTitle, sizeof(sigTitle), "\"RSA-SHA256(%s)\" : \"", Util::BaseFileName(m_inFilename));
|
|
sigTitle[256-1] = '\0';
|
|
|
|
FILE* infile = fopen(m_sigFilename, FOPEN_RB);
|
|
if (!infile)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ok = false;
|
|
int titLen = strlen(sigTitle);
|
|
char buf[1024];
|
|
uchar* output = m_signature;
|
|
while (fgets(buf, sizeof(buf) - 1, infile))
|
|
{
|
|
if (!strncmp(buf, sigTitle, titLen))
|
|
{
|
|
char* hexSig = buf + titLen;
|
|
int sigLen = strlen(hexSig);
|
|
if (sigLen > 2)
|
|
{
|
|
hexSig[sigLen - 2] = '\0'; // trim trailing ",
|
|
}
|
|
for (; *hexSig && *(hexSig+1);)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
fclose(infile);
|
|
return ok;
|
|
}
|
|
|
|
// Read public key from file (m_szPubKeyFilename) into memory
|
|
bool Signature::ReadPubKey()
|
|
{
|
|
char* keybuf;
|
|
int keybuflen;
|
|
if (!Util::LoadFileIntoBuffer(m_pubKeyFilename, &keybuf, &keybuflen))
|
|
{
|
|
return false;
|
|
}
|
|
BIO* mem = BIO_new_mem_buf(keybuf, keybuflen);
|
|
m_pubKey = PEM_read_bio_RSA_PUBKEY(mem, NULL, NULL, NULL);
|
|
BIO_free(mem);
|
|
free(keybuf);
|
|
return m_pubKey != NULL;
|
|
}
|
|
|
|
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 */
|