/* * This file is part of nzbget. See . * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2019 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 "Util.h" #include "FileSystem.h" #include "Options.h" #include "Log.h" #include "MessageBase.h" #include "DownloadInfo.h" // Program options static const char* OPTION_CONFIGFILE = "ConfigFile"; static const char* OPTION_APPBIN = "AppBin"; static const char* OPTION_APPDIR = "AppDir"; static const char* OPTION_VERSION = "Version"; static const char* OPTION_MAINDIR = "MainDir"; static const char* OPTION_DESTDIR = "DestDir"; static const char* OPTION_INTERDIR = "InterDir"; static const char* OPTION_TEMPDIR = "TempDir"; static const char* OPTION_QUEUEDIR = "QueueDir"; static const char* OPTION_NZBDIR = "NzbDir"; static const char* OPTION_WEBDIR = "WebDir"; static const char* OPTION_CONFIGTEMPLATE = "ConfigTemplate"; static const char* OPTION_SCRIPTDIR = "ScriptDir"; static const char* OPTION_REQUIREDDIR = "RequiredDir"; static const char* OPTION_LOGFILE = "LogFile"; static const char* OPTION_WRITELOG = "WriteLog"; static const char* OPTION_ROTATELOG = "RotateLog"; static const char* OPTION_APPENDCATEGORYDIR = "AppendCategoryDir"; static const char* OPTION_LOCKFILE = "LockFile"; static const char* OPTION_DAEMONUSERNAME = "DaemonUsername"; static const char* OPTION_OUTPUTMODE = "OutputMode"; static const char* OPTION_DUPECHECK = "DupeCheck"; static const char* OPTION_DOWNLOADRATE = "DownloadRate"; static const char* OPTION_CONTROLIP = "ControlIp"; static const char* OPTION_CONTROLPORT = "ControlPort"; static const char* OPTION_CONTROLUSERNAME = "ControlUsername"; static const char* OPTION_CONTROLPASSWORD = "ControlPassword"; static const char* OPTION_RESTRICTEDUSERNAME = "RestrictedUsername"; static const char* OPTION_RESTRICTEDPASSWORD = "RestrictedPassword"; static const char* OPTION_ADDUSERNAME = "AddUsername"; static const char* OPTION_ADDPASSWORD = "AddPassword"; static const char* OPTION_FORMAUTH = "FormAuth"; static const char* OPTION_SECURECONTROL = "SecureControl"; static const char* OPTION_SECUREPORT = "SecurePort"; static const char* OPTION_SECURECERT = "SecureCert"; static const char* OPTION_SECUREKEY = "SecureKey"; static const char* OPTION_CERTSTORE = "CertStore"; static const char* OPTION_CERTCHECK = "CertCheck"; static const char* OPTION_AUTHORIZEDIP = "AuthorizedIP"; static const char* OPTION_ARTICLETIMEOUT = "ArticleTimeout"; static const char* OPTION_URLTIMEOUT = "UrlTimeout"; static const char* OPTION_REMOTETIMEOUT = "RemoteTimeout"; static const char* OPTION_FLUSHQUEUE = "FlushQueue"; static const char* OPTION_NZBLOG = "NzbLog"; static const char* OPTION_RAWARTICLE = "RawArticle"; static const char* OPTION_SKIPWRITE = "SkipWrite"; static const char* OPTION_ARTICLERETRIES = "ArticleRetries"; static const char* OPTION_ARTICLEINTERVAL = "ArticleInterval"; static const char* OPTION_URLRETRIES = "UrlRetries"; static const char* OPTION_URLINTERVAL = "UrlInterval"; static const char* OPTION_CONTINUEPARTIAL = "ContinuePartial"; static const char* OPTION_URLCONNECTIONS = "UrlConnections"; static const char* OPTION_LOGBUFFER = "LogBuffer"; static const char* OPTION_INFOTARGET = "InfoTarget"; static const char* OPTION_WARNINGTARGET = "WarningTarget"; static const char* OPTION_ERRORTARGET = "ErrorTarget"; static const char* OPTION_DEBUGTARGET = "DebugTarget"; static const char* OPTION_DETAILTARGET = "DetailTarget"; static const char* OPTION_PARCHECK = "ParCheck"; static const char* OPTION_PARREPAIR = "ParRepair"; static const char* OPTION_PARSCAN = "ParScan"; static const char* OPTION_PARQUICK = "ParQuick"; static const char* OPTION_POSTSTRATEGY = "PostStrategy"; static const char* OPTION_FILENAMING = "FileNaming"; static const char* OPTION_PARRENAME = "ParRename"; static const char* OPTION_PARBUFFER = "ParBuffer"; static const char* OPTION_PARTHREADS = "ParThreads"; static const char* OPTION_RARRENAME = "RarRename"; static const char* OPTION_HEALTHCHECK = "HealthCheck"; static const char* OPTION_DIRECTRENAME = "DirectRename"; static const char* OPTION_UMASK = "UMask"; static const char* OPTION_UPDATEINTERVAL = "UpdateInterval"; static const char* OPTION_CURSESNZBNAME = "CursesNzbName"; static const char* OPTION_CURSESTIME = "CursesTime"; static const char* OPTION_CURSESGROUP = "CursesGroup"; static const char* OPTION_CRCCHECK = "CrcCheck"; static const char* OPTION_DIRECTWRITE = "DirectWrite"; static const char* OPTION_WRITEBUFFER = "WriteBuffer"; static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval"; static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge"; static const char* OPTION_DISKSPACE = "DiskSpace"; static const char* OPTION_CRASHTRACE = "CrashTrace"; static const char* OPTION_CRASHDUMP = "CrashDump"; static const char* OPTION_PARPAUSEQUEUE = "ParPauseQueue"; static const char* OPTION_SCRIPTPAUSEQUEUE = "ScriptPauseQueue"; static const char* OPTION_NZBCLEANUPDISK = "NzbCleanupDisk"; static const char* OPTION_PARTIMELIMIT = "ParTimeLimit"; static const char* OPTION_KEEPHISTORY = "KeepHistory"; static const char* OPTION_UNPACK = "Unpack"; static const char* OPTION_DIRECTUNPACK = "DirectUnpack"; static const char* OPTION_UNPACKCLEANUPDISK = "UnpackCleanupDisk"; static const char* OPTION_UNRARCMD = "UnrarCmd"; static const char* OPTION_SEVENZIPCMD = "SevenZipCmd"; static const char* OPTION_UNPACKPASSFILE = "UnpackPassFile"; static const char* OPTION_UNPACKPAUSEQUEUE = "UnpackPauseQueue"; static const char* OPTION_SCRIPTORDER = "ScriptOrder"; static const char* OPTION_EXTENSIONS = "Extensions"; static const char* OPTION_EXTCLEANUPDISK = "ExtCleanupDisk"; static const char* OPTION_PARIGNOREEXT = "ParIgnoreExt"; static const char* OPTION_UNPACKIGNOREEXT = "UnpackIgnoreExt"; static const char* OPTION_FEEDHISTORY = "FeedHistory"; static const char* OPTION_URLFORCE = "UrlForce"; static const char* OPTION_TIMECORRECTION = "TimeCorrection"; static const char* OPTION_PROPAGATIONDELAY = "PropagationDelay"; static const char* OPTION_ARTICLECACHE = "ArticleCache"; static const char* OPTION_EVENTINTERVAL = "EventInterval"; static const char* OPTION_SHELLOVERRIDE = "ShellOverride"; static const char* OPTION_MONTHLYQUOTA = "MonthlyQuota"; static const char* OPTION_QUOTASTARTDAY = "QuotaStartDay"; static const char* OPTION_DAILYQUOTA = "DailyQuota"; static const char* OPTION_REORDERFILES = "ReorderFiles"; static const char* OPTION_UPDATECHECK = "UpdateCheck"; // obsolete options static const char* OPTION_POSTLOGKIND = "PostLogKind"; static const char* OPTION_NZBLOGKIND = "NZBLogKind"; static const char* OPTION_RETRYONCRCERROR = "RetryOnCrcError"; static const char* OPTION_ALLOWREPROCESS = "AllowReProcess"; static const char* OPTION_POSTPROCESS = "PostProcess"; static const char* OPTION_LOADPARS = "LoadPars"; static const char* OPTION_THREADLIMIT = "ThreadLimit"; static const char* OPTION_PROCESSLOGKIND = "ProcessLogKind"; static const char* OPTION_APPENDNZBDIR = "AppendNzbDir"; static const char* OPTION_RENAMEBROKEN = "RenameBroken"; static const char* OPTION_MERGENZB = "MergeNzb"; static const char* OPTION_STRICTPARNAME = "StrictParName"; static const char* OPTION_RELOADURLQUEUE = "ReloadUrlQueue"; static const char* OPTION_RELOADPOSTQUEUE = "ReloadPostQueue"; static const char* OPTION_NZBPROCESS = "NZBProcess"; static const char* OPTION_NZBADDEDPROCESS = "NZBAddedProcess"; static const char* OPTION_CREATELOG = "CreateLog"; static const char* OPTION_RESETLOG = "ResetLog"; static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue"; static const char* OPTION_DELETECLEANUPDISK = "DeleteCleanupDisk"; static const char* OPTION_HISTORYCLEANUPDISK = "HistoryCleanupDisk"; static const char* OPTION_SCANSCRIPT = "ScanScript"; static const char* OPTION_QUEUESCRIPT = "QueueScript"; static const char* OPTION_FEEDSCRIPT = "FeedScript"; static const char* OPTION_DECODE = "Decode"; static const char* OPTION_SAVEQUEUE = "SaveQueue"; static const char* OPTION_RELOADQUEUE = "ReloadQueue"; static const char* OPTION_TERMINATETIMEOUT = "TerminateTimeout"; static const char* OPTION_ACCURATERATE = "AccurateRate"; static const char* OPTION_CREATEBROKENLOG = "CreateBrokenLog"; static const char* OPTION_BROKENLOG = "BrokenLog"; const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" }; const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; const int BoolCount = 12; #ifndef WIN32 const char* PossibleConfigLocations[] = { "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf", nullptr }; #endif void Options::OptEntry::SetValue(const char* value) { m_value = value; if (!m_defValue) { m_defValue = value; } } bool Options::OptEntry::Restricted() { BString<1024> loName = *m_name; for (char* p = loName; *p; p++) *p = tolower(*p); // convert string to lowercase bool restricted = !strcasecmp(m_name, OPTION_CONTROLIP) || !strcasecmp(m_name, OPTION_CONTROLPORT) || !strcasecmp(m_name, OPTION_FORMAUTH) || !strcasecmp(m_name, OPTION_SECURECONTROL) || !strcasecmp(m_name, OPTION_SECUREPORT) || !strcasecmp(m_name, OPTION_SECURECERT) || !strcasecmp(m_name, OPTION_SECUREKEY) || !strcasecmp(m_name, OPTION_CERTSTORE) || !strcasecmp(m_name, OPTION_CERTCHECK) || !strcasecmp(m_name, OPTION_AUTHORIZEDIP) || !strcasecmp(m_name, OPTION_DAEMONUSERNAME) || !strcasecmp(m_name, OPTION_UMASK) || strchr(m_name, ':') || // All extension script options strstr(loName, "username") || // ServerX.Username, ControlUsername, etc. strstr(loName, "password"); // ServerX.Password, ControlPassword, etc. return restricted; } Options::OptEntry* Options::OptEntries::FindOption(const char* name) { if (!name) { return nullptr; } for (OptEntry& optEntry : this) { if (!strcasecmp(optEntry.GetName(), name)) { return &optEntry; } } return nullptr; } Options::Category* Options::Categories::FindCategory(const char* name, bool searchAliases) { if (!name) { return nullptr; } for (Category& category : this) { if (!strcasecmp(category.GetName(), name)) { return &category; } } if (searchAliases) { for (Category& category : this) { for (CString& alias : category.GetAliases()) { WildMask mask(alias); if (mask.Match(name)) { return &category; } } } } return nullptr; } Options::Options(const char* exeName, const char* configFilename, bool noConfig, CmdOptList* commandLineOptions, Extender* extender) { Init(exeName, configFilename, noConfig, commandLineOptions, false, extender); } Options::Options(CmdOptList* commandLineOptions, Extender* extender) { Init("nzbget/nzbget", nullptr, true, commandLineOptions, true, extender); } void Options::Init(const char* exeName, const char* configFilename, bool noConfig, CmdOptList* commandLineOptions, bool noDiskAccess, Extender* extender) { g_Options = this; m_extender = extender; m_noDiskAccess = noDiskAccess; m_noConfig = noConfig; m_configFilename = configFilename; SetOption(OPTION_CONFIGFILE, ""); CString filename; if (m_noDiskAccess) { filename = exeName; } else { filename = FileSystem::GetExeFileName(exeName); } FileSystem::NormalizePathSeparators(filename); SetOption(OPTION_APPBIN, filename); char* end = strrchr(filename, PATH_SEPARATOR); if (end) *end = '\0'; SetOption(OPTION_APPDIR, filename); m_appDir = *filename; SetOption(OPTION_VERSION, Util::VersionRevision()); InitDefaults(); InitOptFile(); if (m_fatalError) { return; } if (commandLineOptions) { InitCommandLineOptions(commandLineOptions); } if (!m_configFilename && !noConfig) { printf("No configuration-file found\n"); #ifdef WIN32 printf("Please put configuration-file \"nzbget.conf\" into the directory with exe-file\n"); #else printf("Please use option \"-c\" or put configuration-file in one of the following locations:\n"); int p = 0; while (const char* filename = PossibleConfigLocations[p++]) { printf("%s\n", filename); } #endif m_fatalError = true; return; } ConvertOldOptions(&m_optEntries); InitOptions(); CheckOptions(); InitServers(); InitCategories(); InitScheduler(); InitFeeds(); } Options::~Options() { g_Options = nullptr; } void Options::ConfigError(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", m_configFilename ? FileSystem::BaseFileName(m_configFilename) : "", m_configLine, tmp2); error("%s(%i): %s", m_configFilename ? FileSystem::BaseFileName(m_configFilename) : "", m_configLine, tmp2); m_configErrors = true; } void Options::ConfigWarn(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", FileSystem::BaseFileName(m_configFilename), m_configLine, tmp2); warn("%s(%i): %s", FileSystem::BaseFileName(m_configFilename), m_configLine, tmp2); } void Options::LocateOptionSrcPos(const char *optionName) { OptEntry* optEntry = FindOption(optionName); if (optEntry) { m_configLine = optEntry->GetLineNo(); } else { m_configLine = 0; } } void Options::InitDefaults() { #ifdef WIN32 SetOption(OPTION_MAINDIR, "${AppDir}"); SetOption(OPTION_WEBDIR, "${AppDir}\\webui"); SetOption(OPTION_CONFIGTEMPLATE, "${AppDir}\\nzbget.conf.template"); #else SetOption(OPTION_MAINDIR, "~/downloads"); SetOption(OPTION_WEBDIR, ""); SetOption(OPTION_CONFIGTEMPLATE, ""); #endif SetOption(OPTION_TEMPDIR, "${MainDir}/tmp"); SetOption(OPTION_DESTDIR, "${MainDir}/dst"); SetOption(OPTION_INTERDIR, ""); SetOption(OPTION_QUEUEDIR, "${MainDir}/queue"); SetOption(OPTION_NZBDIR, "${MainDir}/nzb"); SetOption(OPTION_LOCKFILE, "${MainDir}/nzbget.lock"); SetOption(OPTION_LOGFILE, "${MainDir}/nzbget.log"); SetOption(OPTION_SCRIPTDIR, "${MainDir}/scripts"); SetOption(OPTION_REQUIREDDIR, ""); SetOption(OPTION_WRITELOG, "append"); SetOption(OPTION_ROTATELOG, "3"); SetOption(OPTION_APPENDCATEGORYDIR, "yes"); SetOption(OPTION_OUTPUTMODE, "curses"); SetOption(OPTION_DUPECHECK, "yes"); SetOption(OPTION_DOWNLOADRATE, "0"); SetOption(OPTION_CONTROLIP, "0.0.0.0"); SetOption(OPTION_CONTROLUSERNAME, "nzbget"); SetOption(OPTION_CONTROLPASSWORD, "tegbzn6789"); SetOption(OPTION_RESTRICTEDUSERNAME, ""); SetOption(OPTION_RESTRICTEDPASSWORD, ""); SetOption(OPTION_ADDUSERNAME, ""); SetOption(OPTION_ADDPASSWORD, ""); SetOption(OPTION_CONTROLPORT, "6789"); SetOption(OPTION_FORMAUTH, "no"); SetOption(OPTION_SECURECONTROL, "no"); SetOption(OPTION_SECUREPORT, "6791"); SetOption(OPTION_SECURECERT, ""); SetOption(OPTION_SECUREKEY, ""); SetOption(OPTION_CERTSTORE, ""); SetOption(OPTION_CERTCHECK, "no"); SetOption(OPTION_AUTHORIZEDIP, ""); SetOption(OPTION_ARTICLETIMEOUT, "60"); SetOption(OPTION_URLTIMEOUT, "60"); SetOption(OPTION_REMOTETIMEOUT, "90"); SetOption(OPTION_FLUSHQUEUE, "yes"); SetOption(OPTION_NZBLOG, "yes"); SetOption(OPTION_RAWARTICLE, "no"); SetOption(OPTION_SKIPWRITE, "no"); SetOption(OPTION_ARTICLERETRIES, "3"); SetOption(OPTION_ARTICLEINTERVAL, "10"); SetOption(OPTION_URLRETRIES, "3"); SetOption(OPTION_URLINTERVAL, "10"); SetOption(OPTION_CONTINUEPARTIAL, "no"); SetOption(OPTION_URLCONNECTIONS, "4"); SetOption(OPTION_LOGBUFFER, "1000"); SetOption(OPTION_INFOTARGET, "both"); SetOption(OPTION_WARNINGTARGET, "both"); SetOption(OPTION_ERRORTARGET, "both"); SetOption(OPTION_DEBUGTARGET, "none"); SetOption(OPTION_DETAILTARGET, "both"); SetOption(OPTION_PARCHECK, "auto"); SetOption(OPTION_PARREPAIR, "yes"); SetOption(OPTION_PARSCAN, "extended"); SetOption(OPTION_PARQUICK, "yes"); SetOption(OPTION_POSTSTRATEGY, "sequential"); SetOption(OPTION_FILENAMING, "article"); SetOption(OPTION_PARRENAME, "yes"); SetOption(OPTION_PARBUFFER, "16"); SetOption(OPTION_PARTHREADS, "1"); SetOption(OPTION_RARRENAME, "yes"); SetOption(OPTION_HEALTHCHECK, "none"); SetOption(OPTION_DIRECTRENAME, "no"); SetOption(OPTION_SCRIPTORDER, ""); SetOption(OPTION_EXTENSIONS, ""); SetOption(OPTION_DAEMONUSERNAME, "root"); SetOption(OPTION_UMASK, "1000"); SetOption(OPTION_UPDATEINTERVAL, "200"); SetOption(OPTION_CURSESNZBNAME, "yes"); SetOption(OPTION_CURSESTIME, "no"); SetOption(OPTION_CURSESGROUP, "no"); SetOption(OPTION_CRCCHECK, "yes"); SetOption(OPTION_DIRECTWRITE, "yes"); SetOption(OPTION_WRITEBUFFER, "0"); SetOption(OPTION_NZBDIRINTERVAL, "5"); SetOption(OPTION_NZBDIRFILEAGE, "60"); SetOption(OPTION_DISKSPACE, "250"); SetOption(OPTION_CRASHTRACE, "no"); SetOption(OPTION_CRASHDUMP, "no"); SetOption(OPTION_PARPAUSEQUEUE, "no"); SetOption(OPTION_SCRIPTPAUSEQUEUE, "no"); SetOption(OPTION_NZBCLEANUPDISK, "no"); SetOption(OPTION_PARTIMELIMIT, "0"); SetOption(OPTION_KEEPHISTORY, "7"); SetOption(OPTION_UNPACK, "no"); SetOption(OPTION_DIRECTUNPACK, "no"); SetOption(OPTION_UNPACKCLEANUPDISK, "no"); #ifdef WIN32 SetOption(OPTION_UNRARCMD, "unrar.exe"); SetOption(OPTION_SEVENZIPCMD, "7z.exe"); #else SetOption(OPTION_UNRARCMD, "unrar"); SetOption(OPTION_SEVENZIPCMD, "7z"); #endif SetOption(OPTION_UNPACKPASSFILE, ""); SetOption(OPTION_UNPACKPAUSEQUEUE, "no"); SetOption(OPTION_EXTCLEANUPDISK, ""); SetOption(OPTION_PARIGNOREEXT, ""); SetOption(OPTION_UNPACKIGNOREEXT, ""); SetOption(OPTION_FEEDHISTORY, "7"); SetOption(OPTION_URLFORCE, "yes"); SetOption(OPTION_TIMECORRECTION, "0"); SetOption(OPTION_PROPAGATIONDELAY, "0"); SetOption(OPTION_ARTICLECACHE, "0"); SetOption(OPTION_EVENTINTERVAL, "0"); SetOption(OPTION_SHELLOVERRIDE, ""); SetOption(OPTION_MONTHLYQUOTA, "0"); SetOption(OPTION_QUOTASTARTDAY, "1"); SetOption(OPTION_DAILYQUOTA, "0"); SetOption(OPTION_REORDERFILES, "no"); SetOption(OPTION_UPDATECHECK, "none"); } void Options::InitOptFile() { if (!m_configFilename && !m_noConfig) { // search for config file in default locations #ifdef WIN32 BString<1024> filename("%s\\nzbget.conf", *m_appDir); if (!FileSystem::FileExists(filename)) { char appDataPath[MAX_PATH]; SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, appDataPath); filename.Format("%s\\NZBGet\\nzbget.conf", appDataPath); if (m_extender && !FileSystem::FileExists(filename)) { m_extender->SetupFirstStart(); } } if (FileSystem::FileExists(filename)) { m_configFilename = filename; } #else // look in the exe-directory first BString<1024> filename("%s/nzbget.conf", *m_appDir); if (FileSystem::FileExists(filename)) { m_configFilename = filename; } else { int p = 0; while (const char* altfilename = PossibleConfigLocations[p++]) { // substitute HOME-variable filename = FileSystem::ExpandHomePath(altfilename); if (FileSystem::FileExists(filename)) { m_configFilename = *filename; break; } } } #endif } if (m_configFilename) { // normalize path in filename CString filename = FileSystem::ExpandFileName(m_configFilename); #ifndef WIN32 // substitute HOME-variable filename = FileSystem::ExpandHomePath(filename); #endif m_configFilename = *filename; SetOption(OPTION_CONFIGFILE, m_configFilename); LoadConfigFile(); } } void Options::CheckDir(CString& dir, const char* optionName, const char* parentDir, bool allowEmpty, bool create) { const char* tempdir = GetOption(optionName); if (m_noDiskAccess) { dir = tempdir; return; } if (Util::EmptyStr(tempdir)) { if (!allowEmpty) { ConfigError("Invalid value for option \"%s\": ", optionName); } dir = ""; return; } dir = tempdir; FileSystem::NormalizePathSeparators((char*)dir); if (!Util::EmptyStr(dir) && dir[dir.Length() - 1] == PATH_SEPARATOR) { // remove trailing slash dir[dir.Length() - 1] = '\0'; } if (!(dir[0] == PATH_SEPARATOR || dir[0] == ALT_PATH_SEPARATOR || (dir[0] && dir[1] == ':')) && !Util::EmptyStr(parentDir)) { // convert relative path to absolute path int plen = strlen(parentDir); BString<1024> usedir2; if (parentDir[plen-1] == PATH_SEPARATOR || parentDir[plen-1] == ALT_PATH_SEPARATOR) { usedir2.Format("%s%s", parentDir, *dir); } else { usedir2.Format("%s%c%s", parentDir, PATH_SEPARATOR, *dir); } FileSystem::NormalizePathSeparators((char*)usedir2); dir = usedir2; SetOption(optionName, usedir2); } // Ensure the dir is created CString errmsg; if (create && !FileSystem::ForceDirectories(dir, errmsg)) { ConfigError("Invalid value for option \"%s\" (%s): %s", optionName, *dir, *errmsg); } } void Options::InitOptions() { const char* mainDir = GetOption(OPTION_MAINDIR); CheckDir(m_destDir, OPTION_DESTDIR, mainDir, false, false); CheckDir(m_interDir, OPTION_INTERDIR, mainDir, true, false); CheckDir(m_tempDir, OPTION_TEMPDIR, mainDir, false, true); CheckDir(m_queueDir, OPTION_QUEUEDIR, mainDir, false, true); CheckDir(m_webDir, OPTION_WEBDIR, nullptr, true, false); CheckDir(m_scriptDir, OPTION_SCRIPTDIR, mainDir, true, false); CheckDir(m_nzbDir, OPTION_NZBDIR, mainDir, false, true); m_requiredDir = GetOption(OPTION_REQUIREDDIR); m_configTemplate = GetOption(OPTION_CONFIGTEMPLATE); m_scriptOrder = GetOption(OPTION_SCRIPTORDER); m_extensions = GetOption(OPTION_EXTENSIONS); m_controlIp = GetOption(OPTION_CONTROLIP); m_controlUsername = GetOption(OPTION_CONTROLUSERNAME); m_controlPassword = GetOption(OPTION_CONTROLPASSWORD); m_restrictedUsername = GetOption(OPTION_RESTRICTEDUSERNAME); m_restrictedPassword = GetOption(OPTION_RESTRICTEDPASSWORD); m_addUsername = GetOption(OPTION_ADDUSERNAME); m_addPassword = GetOption(OPTION_ADDPASSWORD); m_secureCert = GetOption(OPTION_SECURECERT); m_secureKey = GetOption(OPTION_SECUREKEY); m_certStore = GetOption(OPTION_CERTSTORE); m_authorizedIp = GetOption(OPTION_AUTHORIZEDIP); m_lockFile = GetOption(OPTION_LOCKFILE); m_daemonUsername = GetOption(OPTION_DAEMONUSERNAME); m_logFile = GetOption(OPTION_LOGFILE); m_unrarCmd = GetOption(OPTION_UNRARCMD); m_sevenZipCmd = GetOption(OPTION_SEVENZIPCMD); m_unpackPassFile = GetOption(OPTION_UNPACKPASSFILE); m_extCleanupDisk = GetOption(OPTION_EXTCLEANUPDISK); m_parIgnoreExt = GetOption(OPTION_PARIGNOREEXT); m_unpackIgnoreExt = GetOption(OPTION_UNPACKIGNOREEXT); m_shellOverride = GetOption(OPTION_SHELLOVERRIDE); m_downloadRate = ParseIntValue(OPTION_DOWNLOADRATE, 10) * 1024; m_articleTimeout = ParseIntValue(OPTION_ARTICLETIMEOUT, 10); m_urlTimeout = ParseIntValue(OPTION_URLTIMEOUT, 10); m_remoteTimeout = ParseIntValue(OPTION_REMOTETIMEOUT, 10); m_articleRetries = ParseIntValue(OPTION_ARTICLERETRIES, 10); m_articleInterval = ParseIntValue(OPTION_ARTICLEINTERVAL, 10); m_urlRetries = ParseIntValue(OPTION_URLRETRIES, 10); m_urlInterval = ParseIntValue(OPTION_URLINTERVAL, 10); m_controlPort = ParseIntValue(OPTION_CONTROLPORT, 10); m_securePort = ParseIntValue(OPTION_SECUREPORT, 10); m_urlConnections = ParseIntValue(OPTION_URLCONNECTIONS, 10); m_logBuffer = ParseIntValue(OPTION_LOGBUFFER, 10); m_rotateLog = ParseIntValue(OPTION_ROTATELOG, 10); m_umask = ParseIntValue(OPTION_UMASK, 8); m_updateInterval = ParseIntValue(OPTION_UPDATEINTERVAL, 10); m_writeBuffer = ParseIntValue(OPTION_WRITEBUFFER, 10); m_nzbDirInterval = ParseIntValue(OPTION_NZBDIRINTERVAL, 10); m_nzbDirFileAge = ParseIntValue(OPTION_NZBDIRFILEAGE, 10); m_diskSpace = ParseIntValue(OPTION_DISKSPACE, 10); m_parTimeLimit = ParseIntValue(OPTION_PARTIMELIMIT, 10); m_keepHistory = ParseIntValue(OPTION_KEEPHISTORY, 10); m_feedHistory = ParseIntValue(OPTION_FEEDHISTORY, 10); m_timeCorrection = ParseIntValue(OPTION_TIMECORRECTION, 10); if (-24 <= m_timeCorrection && m_timeCorrection <= 24) { m_timeCorrection *= 60; } m_timeCorrection *= 60; m_propagationDelay = ParseIntValue(OPTION_PROPAGATIONDELAY, 10) * 60; m_articleCache = ParseIntValue(OPTION_ARTICLECACHE, 10); m_eventInterval = ParseIntValue(OPTION_EVENTINTERVAL, 10); m_parBuffer = ParseIntValue(OPTION_PARBUFFER, 10); m_parThreads = ParseIntValue(OPTION_PARTHREADS, 10); m_monthlyQuota = ParseIntValue(OPTION_MONTHLYQUOTA, 10); m_quotaStartDay = ParseIntValue(OPTION_QUOTASTARTDAY, 10); m_dailyQuota = ParseIntValue(OPTION_DAILYQUOTA, 10); m_nzbLog = (bool)ParseEnumValue(OPTION_NZBLOG, BoolCount, BoolNames, BoolValues); m_appendCategoryDir = (bool)ParseEnumValue(OPTION_APPENDCATEGORYDIR, BoolCount, BoolNames, BoolValues); m_continuePartial = (bool)ParseEnumValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, BoolValues); m_flushQueue = (bool)ParseEnumValue(OPTION_FLUSHQUEUE, BoolCount, BoolNames, BoolValues); m_dupeCheck = (bool)ParseEnumValue(OPTION_DUPECHECK, BoolCount, BoolNames, BoolValues); m_parRepair = (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues); m_parQuick = (bool)ParseEnumValue(OPTION_PARQUICK, BoolCount, BoolNames, BoolValues); m_parRename = (bool)ParseEnumValue(OPTION_PARRENAME, BoolCount, BoolNames, BoolValues); m_rarRename = (bool)ParseEnumValue(OPTION_RARRENAME, BoolCount, BoolNames, BoolValues); m_directRename = (bool)ParseEnumValue(OPTION_DIRECTRENAME, BoolCount, BoolNames, BoolValues); m_cursesNzbName = (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues); m_cursesTime = (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues); m_cursesGroup = (bool)ParseEnumValue(OPTION_CURSESGROUP, BoolCount, BoolNames, BoolValues); m_crcCheck = (bool)ParseEnumValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues); m_directWrite = (bool)ParseEnumValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues); m_rawArticle = (bool)ParseEnumValue(OPTION_RAWARTICLE, BoolCount, BoolNames, BoolValues); m_skipWrite = (bool)ParseEnumValue(OPTION_SKIPWRITE, BoolCount, BoolNames, BoolValues); m_crashTrace = (bool)ParseEnumValue(OPTION_CRASHTRACE, BoolCount, BoolNames, BoolValues); m_crashDump = (bool)ParseEnumValue(OPTION_CRASHDUMP, BoolCount, BoolNames, BoolValues); m_parPauseQueue = (bool)ParseEnumValue(OPTION_PARPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_scriptPauseQueue = (bool)ParseEnumValue(OPTION_SCRIPTPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_nzbCleanupDisk = (bool)ParseEnumValue(OPTION_NZBCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_formAuth = (bool)ParseEnumValue(OPTION_FORMAUTH, BoolCount, BoolNames, BoolValues); m_secureControl = (bool)ParseEnumValue(OPTION_SECURECONTROL, BoolCount, BoolNames, BoolValues); m_unpack = (bool)ParseEnumValue(OPTION_UNPACK, BoolCount, BoolNames, BoolValues); m_directUnpack = (bool)ParseEnumValue(OPTION_DIRECTUNPACK, BoolCount, BoolNames, BoolValues); m_unpackCleanupDisk = (bool)ParseEnumValue(OPTION_UNPACKCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_unpackPauseQueue = (bool)ParseEnumValue(OPTION_UNPACKPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_urlForce = (bool)ParseEnumValue(OPTION_URLFORCE, BoolCount, BoolNames, BoolValues); m_certCheck = (bool)ParseEnumValue(OPTION_CERTCHECK, BoolCount, BoolNames, BoolValues); m_reorderFiles = (bool)ParseEnumValue(OPTION_REORDERFILES, BoolCount, BoolNames, BoolValues); const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" }; const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses }; const int OutputModeCount = 7; m_outputMode = (EOutputMode)ParseEnumValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, OutputModeValues); const char* ParCheckNames[] = { "auto", "always", "force", "manual" }; const int ParCheckValues[] = { pcAuto, pcAlways, pcForce, pcManual }; const int ParCheckCount = 6; m_parCheck = (EParCheck)ParseEnumValue(OPTION_PARCHECK, ParCheckCount, ParCheckNames, ParCheckValues); const char* ParScanNames[] = { "limited", "extended", "full", "dupe" }; const int ParScanValues[] = { psLimited, psExtended, psFull, psDupe }; const int ParScanCount = 4; m_parScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues); const char* PostStrategyNames[] = { "sequential", "balanced", "aggressive", "rocket" }; const int PostStrategyValues[] = { ppSequential, ppBalanced, ppAggressive, ppRocket }; const int PostStrategyCount = 4; m_postStrategy = (EPostStrategy)ParseEnumValue(OPTION_POSTSTRATEGY, PostStrategyCount, PostStrategyNames, PostStrategyValues); const char* FileNamingNames[] = { "auto", "article", "nzb" }; const int FileNamingValues[] = { nfAuto, nfArticle, nfNzb }; const int FileNamingCount = 4; m_fileNaming = (EFileNaming)ParseEnumValue(OPTION_FILENAMING, FileNamingCount, FileNamingNames, FileNamingValues); const char* HealthCheckNames[] = { "pause", "delete", "park", "none" }; const int HealthCheckValues[] = { hcPause, hcDelete, hcPark, hcNone }; const int HealthCheckCount = 4; m_healthCheck = (EHealthCheck)ParseEnumValue(OPTION_HEALTHCHECK, HealthCheckCount, HealthCheckNames, HealthCheckValues); const char* TargetNames[] = { "screen", "log", "both", "none" }; const int TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone }; const int TargetCount = 4; m_infoTarget = (EMessageTarget)ParseEnumValue(OPTION_INFOTARGET, TargetCount, TargetNames, TargetValues); m_warningTarget = (EMessageTarget)ParseEnumValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, TargetValues); m_errorTarget = (EMessageTarget)ParseEnumValue(OPTION_ERRORTARGET, TargetCount, TargetNames, TargetValues); m_debugTarget = (EMessageTarget)ParseEnumValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, TargetValues); m_detailTarget = (EMessageTarget)ParseEnumValue(OPTION_DETAILTARGET, TargetCount, TargetNames, TargetValues); const char* WriteLogNames[] = { "none", "append", "reset", "rotate" }; const int WriteLogValues[] = { wlNone, wlAppend, wlReset, wlRotate }; const int WriteLogCount = 4; m_writeLog = (EWriteLog)ParseEnumValue(OPTION_WRITELOG, WriteLogCount, WriteLogNames, WriteLogValues); } int Options::ParseEnumValue(const char* OptName, int argc, const char * argn[], const int argv[]) { OptEntry* optEntry = FindOption(OptName); if (!optEntry) { ConfigError("Undefined value for option \"%s\"", OptName); return argv[0]; } int defNum = 0; for (int i = 0; i < argc; i++) { if (!strcasecmp(optEntry->GetValue(), argn[i])) { // normalizing option value in option list, for example "NO" -> "no" for (int j = 0; j < argc; j++) { if (argv[j] == argv[i]) { if (strcmp(argn[j], optEntry->GetValue())) { optEntry->SetValue(argn[j]); } break; } } return argv[i]; } if (!strcasecmp(optEntry->GetDefValue(), argn[i])) { defNum = i; } } m_configLine = optEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, optEntry->GetValue()); optEntry->SetValue(argn[defNum]); return argv[defNum]; } int Options::ParseIntValue(const char* OptName, int base) { OptEntry* optEntry = FindOption(OptName); if (!optEntry) { ConfigError("Undefined value for option \"%s\"", OptName); return 0; } char *endptr; int val = strtol(optEntry->GetValue(), &endptr, base); if (endptr && *endptr != '\0') { m_configLine = optEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, optEntry->GetValue()); optEntry->SetValue(optEntry->GetDefValue()); val = strtol(optEntry->GetDefValue(), nullptr, base); } return val; } void Options::SetOption(const char* optname, const char* value) { OptEntry* optEntry = FindOption(optname); if (!optEntry) { m_optEntries.emplace_back(optname, nullptr); optEntry = &m_optEntries.back(); } CString curvalue; #ifndef WIN32 if (value && (value[0] == '~') && (value[1] == '/')) { if (m_noDiskAccess) { curvalue = value; } else { curvalue = FileSystem::ExpandHomePath(value); } } else #endif { curvalue = value; } optEntry->SetLineNo(m_configLine); // expand variables while (const char* dollar = strstr(curvalue, "${")) { const char* end = strchr(dollar, '}'); if (end) { int varlen = (int)(end - dollar - 2); BString<100> variable; variable.Set(dollar + 2, varlen); const char* varvalue = GetOption(variable); if (varvalue) { curvalue.Replace((int)(dollar - curvalue), 2 + varlen + 1, varvalue); } else { break; } } else { break; } } optEntry->SetValue(curvalue); } Options::OptEntry* Options::FindOption(const char* optname) { OptEntry* optEntry = m_optEntries.FindOption(optname); // normalize option name in option list; for example "server1.joingroup" -> "Server1.JoinGroup" if (optEntry && strcmp(optEntry->GetName(), optname)) { optEntry->SetName(optname); } return optEntry; } const char* Options::GetOption(const char* optname) { OptEntry* optEntry = FindOption(optname); if (optEntry) { if (optEntry->GetLineNo() > 0) { m_configLine = optEntry->GetLineNo(); } return optEntry->GetValue(); } return nullptr; } void Options::InitServers() { int n = 1; while (true) { const char* nactive = GetOption(BString<100>("Server%i.Active", n)); bool active = true; if (nactive) { active = (bool)ParseEnumValue(BString<100>("Server%i.Active", n), BoolCount, BoolNames, BoolValues); } const char* nname = GetOption(BString<100>("Server%i.Name", n)); const char* nlevel = GetOption(BString<100>("Server%i.Level", n)); const char* ngroup = GetOption(BString<100>("Server%i.Group", n)); const char* nhost = GetOption(BString<100>("Server%i.Host", n)); const char* nport = GetOption(BString<100>("Server%i.Port", n)); const char* nusername = GetOption(BString<100>("Server%i.Username", n)); const char* npassword = GetOption(BString<100>("Server%i.Password", n)); const char* njoingroup = GetOption(BString<100>("Server%i.JoinGroup", n)); bool joinGroup = false; if (njoingroup) { joinGroup = (bool)ParseEnumValue(BString<100>("Server%i.JoinGroup", n), BoolCount, BoolNames, BoolValues); } const char* noptional = GetOption(BString<100>("Server%i.Optional", n)); bool optional = false; if (noptional) { optional = (bool)ParseEnumValue(BString<100>("Server%i.Optional", n), BoolCount, BoolNames, BoolValues); } const char* ntls = GetOption(BString<100>("Server%i.Encryption", n)); bool tls = false; if (ntls) { tls = (bool)ParseEnumValue(BString<100>("Server%i.Encryption", n), BoolCount, BoolNames, BoolValues); #ifdef DISABLE_TLS if (tls) { ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", *BString<100>("Server%i.Encryption", n)); tls = false; } #endif m_tls |= tls; } const char* nipversion = GetOption(BString<100>("Server%i.IpVersion", n)); int ipversion = 0; if (nipversion) { const char* IpVersionNames[] = {"auto", "ipv4", "ipv6"}; const int IpVersionValues[] = {0, 4, 6}; const int IpVersionCount = 3; ipversion = ParseEnumValue(BString<100>("Server%i.IpVersion", n), IpVersionCount, IpVersionNames, IpVersionValues); } const char* ncipher = GetOption(BString<100>("Server%i.Cipher", n)); const char* nconnections = GetOption(BString<100>("Server%i.Connections", n)); const char* nretention = GetOption(BString<100>("Server%i.Retention", n)); bool definition = nactive || nname || nlevel || ngroup || nhost || nport || noptional || nusername || npassword || nconnections || njoingroup || ntls || ncipher || nretention; bool completed = nhost && nport && nconnections; if (!definition) { break; } if (completed) { if (m_extender) { m_extender->AddNewsServer(n, active, nname, nhost, nport ? atoi(nport) : 119, ipversion, nusername, npassword, joinGroup, tls, ncipher, nconnections ? atoi(nconnections) : 1, nretention ? atoi(nretention) : 0, nlevel ? atoi(nlevel) : 0, ngroup ? atoi(ngroup) : 0, optional); } } else { ConfigError("Server definition not complete for \"Server%i\"", n); } n++; } } void Options::InitCategories() { int n = 1; while (true) { const char* nname = GetOption(BString<100>("Category%i.Name", n)); const char* ndestdir = GetOption(BString<100>("Category%i.DestDir", n)); const char* nunpack = GetOption(BString<100>("Category%i.Unpack", n)); bool unpack = true; if (nunpack) { unpack = (bool)ParseEnumValue(BString<100>("Category%i.Unpack", n), BoolCount, BoolNames, BoolValues); } const char* nextensions = GetOption(BString<100>("Category%i.Extensions", n)); const char* naliases = GetOption(BString<100>("Category%i.Aliases", n)); bool definition = nname || ndestdir || nunpack || nextensions || naliases; bool completed = nname && strlen(nname) > 0; if (!definition) { break; } if (completed) { CString destDir; if (ndestdir && ndestdir[0] != '\0') { CheckDir(destDir, BString<100>("Category%i.DestDir", n), m_destDir, false, false); } m_categories.emplace_back(nname, destDir, unpack, nextensions); Category& category = m_categories.back(); // split Aliases into tokens and create items for each token if (naliases) { Tokenizer tok(naliases, ",;"); while (const char* aliasName = tok.Next()) { category.GetAliases()->push_back(aliasName); } } } else { ConfigError("Category definition not complete for \"Category%i\"", n); } n++; } } void Options::InitFeeds() { int n = 1; while (true) { const char* nname = GetOption(BString<100>("Feed%i.Name", n)); const char* nurl = GetOption(BString<100>("Feed%i.URL", n)); const char* nfilter = GetOption(BString<100>("Feed%i.Filter", n)); const char* ncategory = GetOption(BString<100>("Feed%i.Category", n)); const char* nextensions = GetOption(BString<100>("Feed%i.Extensions", n)); const char* nbacklog = GetOption(BString<100>("Feed%i.Backlog", n)); bool backlog = true; if (nbacklog) { backlog = (bool)ParseEnumValue(BString<100>("Feed%i.Backlog", n), BoolCount, BoolNames, BoolValues); } const char* npausenzb = GetOption(BString<100>("Feed%i.PauseNzb", n)); bool pauseNzb = false; if (npausenzb) { pauseNzb = (bool)ParseEnumValue(BString<100>("Feed%i.PauseNzb", n), BoolCount, BoolNames, BoolValues); } const char* ninterval = GetOption(BString<100>("Feed%i.Interval", n)); const char* npriority = GetOption(BString<100>("Feed%i.Priority", n)); bool definition = nname || nurl || nfilter || ncategory || nbacklog || npausenzb || ninterval || npriority || nextensions; bool completed = nurl; if (!definition) { break; } if (completed) { if (m_extender) { m_extender->AddFeed(n, nname, nurl, ninterval ? atoi(ninterval) : 0, nfilter, backlog, pauseNzb, ncategory, npriority ? atoi(npriority) : 0, nextensions); } } else { ConfigError("Feed definition not complete for \"Feed%i\"", n); } n++; } } void Options::InitScheduler() { for (int n = 1; ; n++) { const char* time = GetOption(BString<100>("Task%i.Time", n)); const char* weekDays = GetOption(BString<100>("Task%i.WeekDays", n)); const char* command = GetOption(BString<100>("Task%i.Command", n)); const char* downloadRate = GetOption(BString<100>("Task%i.DownloadRate", n)); const char* process = GetOption(BString<100>("Task%i.Process", n)); const char* param = GetOption(BString<100>("Task%i.Param", n)); if (Util::EmptyStr(param) && !Util::EmptyStr(process)) { param = process; } if (Util::EmptyStr(param) && !Util::EmptyStr(downloadRate)) { param = downloadRate; } bool definition = time || weekDays || command || downloadRate || param; bool completed = time && command; if (!definition) { break; } if (!completed) { ConfigError("Task definition not complete for \"Task%i\"", n); continue; } const char* CommandNames[] = { "pausedownload", "pause", "unpausedownload", "resumedownload", "unpause", "resume", "pausepostprocess", "unpausepostprocess", "resumepostprocess", "pausepost", "unpausepost", "resumepost", "downloadrate", "setdownloadrate", "rate", "speed", "script", "process", "pausescan", "unpausescan", "resumescan", "activateserver", "activateservers", "deactivateserver", "deactivateservers", "fetchfeed", "fetchfeeds" }; const int CommandValues[] = { scPauseDownload, scPauseDownload, scUnpauseDownload, scUnpauseDownload, scUnpauseDownload, scUnpauseDownload, scPausePostProcess, scUnpausePostProcess, scUnpausePostProcess, scPausePostProcess, scUnpausePostProcess, scUnpausePostProcess, scDownloadRate, scDownloadRate, scDownloadRate, scDownloadRate, scScript, scProcess, scPauseScan, scUnpauseScan, scUnpauseScan, scActivateServer, scActivateServer, scDeactivateServer, scDeactivateServer, scFetchFeed, scFetchFeed }; const int CommandCount = 27; ESchedulerCommand taskCommand = (ESchedulerCommand)ParseEnumValue( BString<100>("Task%i.Command", n), CommandCount, CommandNames, CommandValues); if (param && strlen(param) > 0 && taskCommand == scProcess && Util::SplitCommandLine(param).empty()) { ConfigError("Invalid value for option \"Task%i.Param\"", n); continue; } if (taskCommand == scDownloadRate) { if (param) { char* err; int downloadRateVal = strtol(param, &err, 10); if (!err || *err != '\0' || downloadRateVal < 0) { ConfigError("Invalid value for option \"Task%i.Param\": \"%s\"", n, downloadRate); continue; } } else { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n); continue; } } if ((taskCommand == scScript || taskCommand == scProcess || taskCommand == scActivateServer || taskCommand == scDeactivateServer || taskCommand == scFetchFeed) && Util::EmptyStr(param)) { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n); continue; } CreateSchedulerTask(n, time, weekDays, taskCommand, param); } } void Options::CreateSchedulerTask(int id, const char* time, const char* weekDays, ESchedulerCommand command, const char* param) { if (!id) { m_configLine = 0; } int weekDaysVal = 0; if (weekDays && !ParseWeekDays(weekDays, &weekDaysVal)) { ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", id, weekDays); return; } int hours, minutes; Tokenizer tok(time, ";,"); while (const char* oneTime = tok.Next()) { if (!ParseTime(oneTime, &hours, &minutes)) { ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", id, oneTime); return; } if (m_extender) { if (hours == -2) { for (int everyHour = 0; everyHour < 24; everyHour++) { m_extender->AddTask(id, everyHour, minutes, weekDaysVal, command, param); } } else { m_extender->AddTask(id, hours, minutes, weekDaysVal, command, param); } } } } bool Options::ParseTime(const char* time, int* hours, int* minutes) { if (!strcmp(time, "*")) { *hours = -1; return true; } int colons = 0; const char* p = time; while (*p) { if (!strchr("0123456789: *", *p)) { return false; } if (*p == ':') { colons++; } p++; } if (colons != 1) { return false; } const char* colon = strchr(time, ':'); if (!colon) { return false; } if (time[0] == '*') { *hours = -2; } else { *hours = atoi(time); if (*hours < 0 || *hours > 23) { return false; } } if (colon[1] == '*') { return false; } *minutes = atoi(colon + 1); if (*minutes < 0 || *minutes > 59) { return false; } return true; } bool Options::ParseWeekDays(const char* weekDays, int* weekDaysBits) { *weekDaysBits = 0; const char* p = weekDays; int firstDay = 0; bool range = false; while (*p) { if (strchr("1234567", *p)) { int day = *p - '0'; if (range) { if (day <= firstDay || firstDay == 0) { return false; } for (int i = firstDay; i <= day; i++) { *weekDaysBits |= 1 << (i - 1); } firstDay = 0; } else { *weekDaysBits |= 1 << (day - 1); firstDay = day; } range = false; } else if (*p == ',') { range = false; } else if (*p == '-') { range = true; } else if (*p == ' ') { // skip spaces } else { return false; } p++; } return true; } void Options::LoadConfigFile() { SetOption(OPTION_CONFIGFILE, m_configFilename); DiskFile infile; if (!infile.Open(m_configFilename, DiskFile::omRead)) { ConfigError("Could not open file %s", *m_configFilename); m_fatalError = true; return; } m_configLine = 0; int bufLen = (int)FileSystem::FileSize(m_configFilename) + 1; CharBuffer buf(bufLen); int line = 0; while (infile.ReadLine(buf, buf.Size() - 1)) { m_configLine = ++line; if (buf[0] != 0 && buf[strlen(buf)-1] == '\n') { buf[strlen(buf)-1] = 0; // remove traling '\n' } if (buf[0] != 0 && buf[strlen(buf)-1] == '\r') { buf[strlen(buf)-1] = 0; // remove traling '\r' (for windows line endings) } if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } SetOptionString(buf); } infile.Close(); m_configLine = 0; } void Options::InitCommandLineOptions(CmdOptList* commandLineOptions) { for (const char* option : *commandLineOptions) { SetOptionString(option); } } bool Options::SetOptionString(const char* option) { CString optname; CString optvalue; if (!SplitOptionString(option, optname, optvalue)) { ConfigError("Invalid option \"%s\"", option); return false; } bool ok = ValidateOptionName(optname, optvalue); if (ok) { SetOption(optname, optvalue); } else { ConfigError("Invalid option \"%s\"", *optname); } return ok; } /* * Splits option string into name and value; * Converts old names and values if necessary; * Returns true if the option string has name and value; */ bool Options::SplitOptionString(const char* option, CString& optName, CString& optValue) { const char* eq = strchr(option, '='); if (!eq || eq == option) { return false; } optName.Set(option, (int)(eq - option)); optValue.Set(eq + 1); ConvertOldOption(optName, optValue); return true; } bool Options::ValidateOptionName(const char* optname, const char* optvalue) { if (!strcasecmp(optname, OPTION_CONFIGFILE) || !strcasecmp(optname, OPTION_APPBIN) || !strcasecmp(optname, OPTION_APPDIR) || !strcasecmp(optname, OPTION_VERSION)) { // read-only options return false; } const char* v = GetOption(optname); if (v) { // it's predefined option, OK return true; } if (!strncasecmp(optname, "server", 6)) { char* p = (char*)optname + 6; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".active") || !strcasecmp(p, ".name") || !strcasecmp(p, ".level") || !strcasecmp(p, ".host") || !strcasecmp(p, ".port") || !strcasecmp(p, ".username") || !strcasecmp(p, ".password") || !strcasecmp(p, ".joingroup") || !strcasecmp(p, ".encryption") || !strcasecmp(p, ".connections") || !strcasecmp(p, ".cipher") || !strcasecmp(p, ".group") || !strcasecmp(p, ".retention") || !strcasecmp(p, ".optional") || !strcasecmp(p, ".notes") || !strcasecmp(p, ".ipversion"))) { return true; } } if (!strncasecmp(optname, "task", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".time") || !strcasecmp(p, ".weekdays") || !strcasecmp(p, ".command") || !strcasecmp(p, ".param") || !strcasecmp(p, ".downloadrate") || !strcasecmp(p, ".process"))) { return true; } } if (!strncasecmp(optname, "category", 8)) { char* p = (char*)optname + 8; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".extensions") || !strcasecmp(p, ".unpack") || !strcasecmp(p, ".aliases"))) { return true; } } if (!strncasecmp(optname, "feed", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".url") || !strcasecmp(p, ".interval") || !strcasecmp(p, ".filter") || !strcasecmp(p, ".backlog") || !strcasecmp(p, ".pausenzb") || !strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".extensions"))) { return true; } } // scripts options if (strchr(optname, ':')) { return true; } // print warning messages for obsolete options if (!strcasecmp(optname, OPTION_RETRYONCRCERROR) || !strcasecmp(optname, OPTION_ALLOWREPROCESS) || !strcasecmp(optname, OPTION_LOADPARS) || !strcasecmp(optname, OPTION_THREADLIMIT) || !strcasecmp(optname, OPTION_POSTLOGKIND) || !strcasecmp(optname, OPTION_NZBLOGKIND) || !strcasecmp(optname, OPTION_PROCESSLOGKIND) || !strcasecmp(optname, OPTION_APPENDNZBDIR) || !strcasecmp(optname, OPTION_RENAMEBROKEN) || !strcasecmp(optname, OPTION_MERGENZB) || !strcasecmp(optname, OPTION_STRICTPARNAME) || !strcasecmp(optname, OPTION_RELOADURLQUEUE) || !strcasecmp(optname, OPTION_RELOADPOSTQUEUE) || !strcasecmp(optname, OPTION_PARCLEANUPQUEUE) || !strcasecmp(optname, OPTION_DELETECLEANUPDISK) || !strcasecmp(optname, OPTION_HISTORYCLEANUPDISK) || !strcasecmp(optname, OPTION_SAVEQUEUE) || !strcasecmp(optname, OPTION_RELOADQUEUE) || !strcasecmp(optname, OPTION_TERMINATETIMEOUT) || !strcasecmp(optname, OPTION_ACCURATERATE) || !strcasecmp(optname, OPTION_CREATEBROKENLOG) || !strcasecmp(optname, OPTION_BROKENLOG)) { ConfigWarn("Option \"%s\" is obsolete, ignored", optname); return true; } if (!strcasecmp(optname, OPTION_POSTPROCESS) || !strcasecmp(optname, OPTION_NZBPROCESS) || !strcasecmp(optname, OPTION_NZBADDEDPROCESS)) { if (optvalue && strlen(optvalue) > 0) { ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead", optname, OPTION_SCRIPTDIR, OPTION_EXTENSIONS); } return true; } if (!strcasecmp(optname, OPTION_SCANSCRIPT) || !strcasecmp(optname, OPTION_QUEUESCRIPT) || !strcasecmp(optname, OPTION_FEEDSCRIPT)) { // will be automatically converted into "Extensions" return true; } if (!strcasecmp(optname, OPTION_CREATELOG) || !strcasecmp(optname, OPTION_RESETLOG)) { ConfigWarn("Option \"%s\" is obsolete, ignored, use \"%s\" instead", optname, OPTION_WRITELOG); return true; } return false; } void Options::ConvertOldOption(CString& option, CString& value) { // for compatibility with older versions accept old option names if (!strcasecmp(option, "$MAINDIR")) { option = "MainDir"; } if (!strcasecmp(option, "ServerIP")) { option = "ControlIP"; } if (!strcasecmp(option, "ServerPort")) { option = "ControlPort"; } if (!strcasecmp(option, "ServerPassword")) { option = "ControlPassword"; } if (!strcasecmp(option, "PostPauseQueue")) { option = "ScriptPauseQueue"; } if (!strcasecmp(option, "ParCheck") && !strcasecmp(value, "yes")) { value = "always"; } if (!strcasecmp(option, "ParCheck") && !strcasecmp(value, "no")) { value = "auto"; } if (!strcasecmp(option, "ParScan") && !strcasecmp(value, "auto")) { value = "extended"; } if (!strcasecmp(option, "DefScript") || !strcasecmp(option, "PostScript")) { option = "Extensions"; } int nameLen = strlen(option); if (!strncasecmp(option, "Category", 8) && ((nameLen > 10 && !strcasecmp(option + nameLen - 10, ".DefScript")) || (nameLen > 11 && !strcasecmp(option + nameLen - 11, ".PostScript")))) { option.Replace(".DefScript", ".Extensions"); option.Replace(".PostScript", ".Extensions"); } if (!strncasecmp(option, "Feed", 4) && nameLen > 11 && !strcasecmp(option + nameLen - 11, ".FeedScript")) { option.Replace(".FeedScript", ".Extensions"); } if (!strcasecmp(option, "WriteBufferSize")) { option = "WriteBuffer"; int val = strtol(value, nullptr, 10); val = val == -1 ? 1024 : val / 1024; value.Format("%i", val); } if (!strcasecmp(option, "ConnectionTimeout")) { option = "ArticleTimeout"; } if (!strcasecmp(option, "Retries")) { option = "ArticleRetries"; } if (!strcasecmp(option, "RetryInterval")) { option = "ArticleInterval"; } if (!strcasecmp(option, "DumpCore")) { option = OPTION_CRASHDUMP; } if (!strcasecmp(option, OPTION_DECODE)) { option = OPTION_RAWARTICLE; value = !strcasecmp(value, "no") ? "yes" : "no"; } if (!strcasecmp(option, "LogBufferSize")) { option = OPTION_LOGBUFFER; } } void Options::CheckOptions() { #ifdef DISABLE_PARCHECK if (m_parCheck != pcManual) { LocateOptionSrcPos(OPTION_PARCHECK); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARCHECK); } if (m_parRename) { LocateOptionSrcPos(OPTION_PARRENAME); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARRENAME); } if (m_directRename) { LocateOptionSrcPos(OPTION_DIRECTRENAME); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_DIRECTRENAME); } #endif #ifdef DISABLE_CURSES if (m_outputMode == omNCurses) { LocateOptionSrcPos(OPTION_OUTPUTMODE); ConfigError("Invalid value for option \"%s\": program was compiled without curses-support", OPTION_OUTPUTMODE); } #endif #ifdef DISABLE_TLS if (m_secureControl) { LocateOptionSrcPos(OPTION_SECURECONTROL); ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_SECURECONTROL); } if (m_certCheck) { LocateOptionSrcPos(OPTION_CERTCHECK); ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_CERTCHECK); } #endif #ifdef HAVE_OPENSSL #ifndef HAVE_X509_CHECK_HOST if (m_certCheck) { LocateOptionSrcPos(OPTION_CERTCHECK); ConfigWarn("TLS certificate verification (option \"%s\") is limited because the program " "was compiled with older OpenSSL version not supporting hostname validation (at least OpenSSL 1.0.2d is required)", OPTION_CERTCHECK); } #endif #endif #ifdef HAVE_LIBGNUTLS #if GNUTLS_VERSION_NUMBER < 0x030104 if (m_certCheck) { LocateOptionSrcPos(OPTION_CERTCHECK); ConfigWarn("TLS certificate verification (option \"%s\") is disabled because the program " "was compiled with older GnuTLS version not supporting certificate validation (at least GnuTLS 3.1.4 is required)", OPTION_CERTCHECK); } #endif #endif if (m_certCheck && m_certStore.Empty()) { LocateOptionSrcPos(OPTION_CERTCHECK); ConfigError("Option \"%s\" requires proper configuration of option \"%s\"", OPTION_CERTCHECK, OPTION_CERTSTORE); m_certCheck = false; } if (m_rawArticle) { m_directWrite = false; } if (m_skipWrite) { m_directRename = false; } // if option "ConfigTemplate" is not set, use "WebDir" as default location for template // (for compatibility with versions 9 and 10). if (m_configTemplate.Empty() && !m_noDiskAccess) { m_configTemplate.Format("%s%s", *m_webDir, "nzbget.conf"); if (!FileSystem::FileExists(m_configTemplate)) { m_configTemplate = ""; } } if (m_articleCache < 0) { m_articleCache = 0; } else if (sizeof(void*) == 4 && m_articleCache > 1900) { ConfigError("Invalid value for option \"ArticleCache\": %i. Changed to 1900", m_articleCache); m_articleCache = 1900; } else if (sizeof(void*) == 4 && m_parBuffer > 1900) { ConfigError("Invalid value for option \"ParBuffer\": %i. Changed to 1900", m_parBuffer); m_parBuffer = 1900; } if (sizeof(void*) == 4 && m_parBuffer + m_articleCache > 1900) { ConfigError("Options \"ArticleCache\" and \"ParBuffer\" in total cannot use more than 1900MB of memory in 32-Bit mode. Changed to 1500 and 400"); m_articleCache = 1500; m_parBuffer = 400; } if (!m_unpackPassFile.Empty() && !FileSystem::FileExists(m_unpackPassFile)) { ConfigError("Invalid value for option \"UnpackPassFile\": %s. File not found", *m_unpackPassFile); } } void Options::ConvertOldOptions(OptEntries* optEntries) { MergeOldScriptOption(optEntries, OPTION_SCANSCRIPT, true); MergeOldScriptOption(optEntries, OPTION_QUEUESCRIPT, true); MergeOldScriptOption(optEntries, OPTION_FEEDSCRIPT, false); } void Options::MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories) { OptEntry* optEntry = optEntries->FindOption(optname); if (!optEntry || Util::EmptyStr(optEntry->GetValue())) { return; } OptEntry* extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS); if (!extensionsOpt) { optEntries->emplace_back(OPTION_EXTENSIONS, ""); extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS); } const char* scriptList = optEntry->GetValue(); Tokenizer tok(scriptList, ",;"); while (const char* scriptName = tok.Next()) { // merge into global "Extensions" if (!HasScript(extensionsOpt->m_value, scriptName)) { if (!extensionsOpt->m_value.Empty()) { extensionsOpt->m_value.Append(","); } extensionsOpt->m_value.Append(scriptName); } // merge into categories' "Extensions" (if not empty) if (mergeCategories) { for (OptEntry& opt : optEntries) { const char* catoptname = opt.GetName(); if (!strncasecmp(catoptname, "category", 8)) { char* p = (char*)catoptname + 8; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".extensions"))) { if (!opt.m_value.Empty() && !HasScript(opt.m_value, scriptName)) { opt.m_value.Append(","); opt.m_value.Append(scriptName); } } } } } } } bool Options::HasScript(const char* scriptList, const char* scriptName) { Tokenizer tok(scriptList, ",;"); while (const char* scriptName2 = tok.Next()) { if (!strcasecmp(scriptName2, scriptName)) { return true; } } return false; };