1 /*
2  *  This file is part of nzbget. See <http://nzbget.net>.
3  *
4  *  Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
5  *  Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 #include "nzbget.h"
23 #include "Util.h"
24 #include "FileSystem.h"
25 #include "Options.h"
26 #include "Log.h"
27 #include "MessageBase.h"
28 #include "DownloadInfo.h"
29 
30 // Program options
31 static const char* OPTION_CONFIGFILE			= "ConfigFile";
32 static const char* OPTION_APPBIN				= "AppBin";
33 static const char* OPTION_APPDIR				= "AppDir";
34 static const char* OPTION_VERSION				= "Version";
35 static const char* OPTION_MAINDIR				= "MainDir";
36 static const char* OPTION_DESTDIR				= "DestDir";
37 static const char* OPTION_INTERDIR				= "InterDir";
38 static const char* OPTION_TEMPDIR				= "TempDir";
39 static const char* OPTION_QUEUEDIR				= "QueueDir";
40 static const char* OPTION_NZBDIR				= "NzbDir";
41 static const char* OPTION_WEBDIR				= "WebDir";
42 static const char* OPTION_CONFIGTEMPLATE		= "ConfigTemplate";
43 static const char* OPTION_SCRIPTDIR				= "ScriptDir";
44 static const char* OPTION_REQUIREDDIR			= "RequiredDir";
45 static const char* OPTION_LOGFILE				= "LogFile";
46 static const char* OPTION_WRITELOG				= "WriteLog";
47 static const char* OPTION_ROTATELOG				= "RotateLog";
48 static const char* OPTION_APPENDCATEGORYDIR		= "AppendCategoryDir";
49 static const char* OPTION_LOCKFILE				= "LockFile";
50 static const char* OPTION_DAEMONUSERNAME		= "DaemonUsername";
51 static const char* OPTION_OUTPUTMODE			= "OutputMode";
52 static const char* OPTION_DUPECHECK				= "DupeCheck";
53 static const char* OPTION_DOWNLOADRATE			= "DownloadRate";
54 static const char* OPTION_CONTROLIP				= "ControlIp";
55 static const char* OPTION_CONTROLPORT			= "ControlPort";
56 static const char* OPTION_CONTROLUSERNAME		= "ControlUsername";
57 static const char* OPTION_CONTROLPASSWORD		= "ControlPassword";
58 static const char* OPTION_RESTRICTEDUSERNAME	= "RestrictedUsername";
59 static const char* OPTION_RESTRICTEDPASSWORD	= "RestrictedPassword";
60 static const char* OPTION_ADDUSERNAME			= "AddUsername";
61 static const char* OPTION_ADDPASSWORD			= "AddPassword";
62 static const char* OPTION_FORMAUTH				= "FormAuth";
63 static const char* OPTION_SECURECONTROL			= "SecureControl";
64 static const char* OPTION_SECUREPORT			= "SecurePort";
65 static const char* OPTION_SECURECERT			= "SecureCert";
66 static const char* OPTION_SECUREKEY				= "SecureKey";
67 static const char* OPTION_CERTSTORE				= "CertStore";
68 static const char* OPTION_CERTCHECK				= "CertCheck";
69 static const char* OPTION_AUTHORIZEDIP			= "AuthorizedIP";
70 static const char* OPTION_ARTICLETIMEOUT		= "ArticleTimeout";
71 static const char* OPTION_URLTIMEOUT			= "UrlTimeout";
72 static const char* OPTION_REMOTETIMEOUT			= "RemoteTimeout";
73 static const char* OPTION_FLUSHQUEUE			= "FlushQueue";
74 static const char* OPTION_NZBLOG				= "NzbLog";
75 static const char* OPTION_RAWARTICLE			= "RawArticle";
76 static const char* OPTION_SKIPWRITE				= "SkipWrite";
77 static const char* OPTION_ARTICLERETRIES		= "ArticleRetries";
78 static const char* OPTION_ARTICLEINTERVAL		= "ArticleInterval";
79 static const char* OPTION_URLRETRIES			= "UrlRetries";
80 static const char* OPTION_URLINTERVAL			= "UrlInterval";
81 static const char* OPTION_CONTINUEPARTIAL		= "ContinuePartial";
82 static const char* OPTION_URLCONNECTIONS		= "UrlConnections";
83 static const char* OPTION_LOGBUFFER				= "LogBuffer";
84 static const char* OPTION_INFOTARGET			= "InfoTarget";
85 static const char* OPTION_WARNINGTARGET			= "WarningTarget";
86 static const char* OPTION_ERRORTARGET			= "ErrorTarget";
87 static const char* OPTION_DEBUGTARGET			= "DebugTarget";
88 static const char* OPTION_DETAILTARGET			= "DetailTarget";
89 static const char* OPTION_PARCHECK				= "ParCheck";
90 static const char* OPTION_PARREPAIR				= "ParRepair";
91 static const char* OPTION_PARSCAN				= "ParScan";
92 static const char* OPTION_PARQUICK				= "ParQuick";
93 static const char* OPTION_POSTSTRATEGY			= "PostStrategy";
94 static const char* OPTION_FILENAMING			= "FileNaming";
95 static const char* OPTION_PARRENAME				= "ParRename";
96 static const char* OPTION_PARBUFFER				= "ParBuffer";
97 static const char* OPTION_PARTHREADS			= "ParThreads";
98 static const char* OPTION_RARRENAME				= "RarRename";
99 static const char* OPTION_HEALTHCHECK			= "HealthCheck";
100 static const char* OPTION_DIRECTRENAME			= "DirectRename";
101 static const char* OPTION_UMASK					= "UMask";
102 static const char* OPTION_UPDATEINTERVAL		= "UpdateInterval";
103 static const char* OPTION_CURSESNZBNAME			= "CursesNzbName";
104 static const char* OPTION_CURSESTIME			= "CursesTime";
105 static const char* OPTION_CURSESGROUP			= "CursesGroup";
106 static const char* OPTION_CRCCHECK				= "CrcCheck";
107 static const char* OPTION_DIRECTWRITE			= "DirectWrite";
108 static const char* OPTION_WRITEBUFFER			= "WriteBuffer";
109 static const char* OPTION_NZBDIRINTERVAL		= "NzbDirInterval";
110 static const char* OPTION_NZBDIRFILEAGE			= "NzbDirFileAge";
111 static const char* OPTION_DISKSPACE				= "DiskSpace";
112 static const char* OPTION_CRASHTRACE			= "CrashTrace";
113 static const char* OPTION_CRASHDUMP				= "CrashDump";
114 static const char* OPTION_PARPAUSEQUEUE			= "ParPauseQueue";
115 static const char* OPTION_SCRIPTPAUSEQUEUE		= "ScriptPauseQueue";
116 static const char* OPTION_NZBCLEANUPDISK		= "NzbCleanupDisk";
117 static const char* OPTION_PARTIMELIMIT			= "ParTimeLimit";
118 static const char* OPTION_KEEPHISTORY			= "KeepHistory";
119 static const char* OPTION_UNPACK				= "Unpack";
120 static const char* OPTION_DIRECTUNPACK			= "DirectUnpack";
121 static const char* OPTION_UNPACKCLEANUPDISK		= "UnpackCleanupDisk";
122 static const char* OPTION_UNRARCMD				= "UnrarCmd";
123 static const char* OPTION_SEVENZIPCMD			= "SevenZipCmd";
124 static const char* OPTION_UNPACKPASSFILE		= "UnpackPassFile";
125 static const char* OPTION_UNPACKPAUSEQUEUE		= "UnpackPauseQueue";
126 static const char* OPTION_SCRIPTORDER			= "ScriptOrder";
127 static const char* OPTION_EXTENSIONS			= "Extensions";
128 static const char* OPTION_EXTCLEANUPDISK		= "ExtCleanupDisk";
129 static const char* OPTION_PARIGNOREEXT			= "ParIgnoreExt";
130 static const char* OPTION_UNPACKIGNOREEXT		= "UnpackIgnoreExt";
131 static const char* OPTION_FEEDHISTORY			= "FeedHistory";
132 static const char* OPTION_URLFORCE				= "UrlForce";
133 static const char* OPTION_TIMECORRECTION		= "TimeCorrection";
134 static const char* OPTION_PROPAGATIONDELAY		= "PropagationDelay";
135 static const char* OPTION_ARTICLECACHE			= "ArticleCache";
136 static const char* OPTION_EVENTINTERVAL			= "EventInterval";
137 static const char* OPTION_SHELLOVERRIDE			= "ShellOverride";
138 static const char* OPTION_MONTHLYQUOTA			= "MonthlyQuota";
139 static const char* OPTION_QUOTASTARTDAY			= "QuotaStartDay";
140 static const char* OPTION_DAILYQUOTA			= "DailyQuota";
141 static const char* OPTION_REORDERFILES			= "ReorderFiles";
142 static const char* OPTION_UPDATECHECK			= "UpdateCheck";
143 
144 // obsolete options
145 static const char* OPTION_POSTLOGKIND			= "PostLogKind";
146 static const char* OPTION_NZBLOGKIND			= "NZBLogKind";
147 static const char* OPTION_RETRYONCRCERROR		= "RetryOnCrcError";
148 static const char* OPTION_ALLOWREPROCESS		= "AllowReProcess";
149 static const char* OPTION_POSTPROCESS			= "PostProcess";
150 static const char* OPTION_LOADPARS				= "LoadPars";
151 static const char* OPTION_THREADLIMIT			= "ThreadLimit";
152 static const char* OPTION_PROCESSLOGKIND		= "ProcessLogKind";
153 static const char* OPTION_APPENDNZBDIR			= "AppendNzbDir";
154 static const char* OPTION_RENAMEBROKEN			= "RenameBroken";
155 static const char* OPTION_MERGENZB				= "MergeNzb";
156 static const char* OPTION_STRICTPARNAME			= "StrictParName";
157 static const char* OPTION_RELOADURLQUEUE		= "ReloadUrlQueue";
158 static const char* OPTION_RELOADPOSTQUEUE		= "ReloadPostQueue";
159 static const char* OPTION_NZBPROCESS			= "NZBProcess";
160 static const char* OPTION_NZBADDEDPROCESS		= "NZBAddedProcess";
161 static const char* OPTION_CREATELOG				= "CreateLog";
162 static const char* OPTION_RESETLOG				= "ResetLog";
163 static const char* OPTION_PARCLEANUPQUEUE		= "ParCleanupQueue";
164 static const char* OPTION_DELETECLEANUPDISK		= "DeleteCleanupDisk";
165 static const char* OPTION_HISTORYCLEANUPDISK	= "HistoryCleanupDisk";
166 static const char* OPTION_SCANSCRIPT			= "ScanScript";
167 static const char* OPTION_QUEUESCRIPT			= "QueueScript";
168 static const char* OPTION_FEEDSCRIPT			= "FeedScript";
169 static const char* OPTION_DECODE				= "Decode";
170 static const char* OPTION_SAVEQUEUE				= "SaveQueue";
171 static const char* OPTION_RELOADQUEUE			= "ReloadQueue";
172 static const char* OPTION_TERMINATETIMEOUT		= "TerminateTimeout";
173 static const char* OPTION_ACCURATERATE			= "AccurateRate";
174 static const char* OPTION_CREATEBROKENLOG		= "CreateBrokenLog";
175 static const char* OPTION_BROKENLOG				= "BrokenLog";
176 
177 const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" };
178 const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
179 const int BoolCount = 12;
180 
181 #ifndef WIN32
182 const char* PossibleConfigLocations[] =
183 	{
184 		"~/.nzbget",
185 		"/etc/nzbget.conf",
186 		"/usr/etc/nzbget.conf",
187 		"/usr/local/etc/nzbget.conf",
188 		"/opt/etc/nzbget.conf",
189 		nullptr
190 	};
191 #endif
192 
SetValue(const char * value)193 void Options::OptEntry::SetValue(const char* value)
194 {
195 	m_value = value;
196 	if (!m_defValue)
197 	{
198 		m_defValue = value;
199 	}
200 }
201 
Restricted()202 bool Options::OptEntry::Restricted()
203 {
204 	BString<1024> loName = *m_name;
205 	for (char* p = loName; *p; p++) *p = tolower(*p); // convert string to lowercase
206 
207 	bool restricted = !strcasecmp(m_name, OPTION_CONTROLIP) ||
208 		!strcasecmp(m_name, OPTION_CONTROLPORT) ||
209 		!strcasecmp(m_name, OPTION_FORMAUTH) ||
210 		!strcasecmp(m_name, OPTION_SECURECONTROL) ||
211 		!strcasecmp(m_name, OPTION_SECUREPORT) ||
212 		!strcasecmp(m_name, OPTION_SECURECERT) ||
213 		!strcasecmp(m_name, OPTION_SECUREKEY) ||
214 		!strcasecmp(m_name, OPTION_CERTSTORE) ||
215 		!strcasecmp(m_name, OPTION_CERTCHECK) ||
216 		!strcasecmp(m_name, OPTION_AUTHORIZEDIP) ||
217 		!strcasecmp(m_name, OPTION_DAEMONUSERNAME) ||
218 		!strcasecmp(m_name, OPTION_UMASK) ||
219 		strchr(m_name, ':') ||			// All extension script options
220 		strstr(loName, "username") ||	// ServerX.Username, ControlUsername, etc.
221 		strstr(loName, "password");		// ServerX.Password, ControlPassword, etc.
222 
223 	return restricted;
224 }
225 
FindOption(const char * name)226 Options::OptEntry* Options::OptEntries::FindOption(const char* name)
227 {
228 	if (!name)
229 	{
230 		return nullptr;
231 	}
232 
233 	for (OptEntry& optEntry : this)
234 	{
235 		if (!strcasecmp(optEntry.GetName(), name))
236 		{
237 			return &optEntry;
238 		}
239 	}
240 
241 	return nullptr;
242 }
243 
244 
FindCategory(const char * name,bool searchAliases)245 Options::Category* Options::Categories::FindCategory(const char* name, bool searchAliases)
246 {
247 	if (!name)
248 	{
249 		return nullptr;
250 	}
251 
252 	for (Category& category : this)
253 	{
254 		if (!strcasecmp(category.GetName(), name))
255 		{
256 			return &category;
257 		}
258 	}
259 
260 	if (searchAliases)
261 	{
262 		for (Category& category : this)
263 		{
264 			for (CString& alias : category.GetAliases())
265 			{
266 				WildMask mask(alias);
267 				if (mask.Match(name))
268 				{
269 					return &category;
270 				}
271 			}
272 		}
273 	}
274 
275 	return nullptr;
276 }
277 
278 
Options(const char * exeName,const char * configFilename,bool noConfig,CmdOptList * commandLineOptions,Extender * extender)279 Options::Options(const char* exeName, const char* configFilename, bool noConfig,
280 	CmdOptList* commandLineOptions, Extender* extender)
281 {
282 	Init(exeName, configFilename, noConfig, commandLineOptions, false, extender);
283 }
284 
Options(CmdOptList * commandLineOptions,Extender * extender)285 Options::Options(CmdOptList* commandLineOptions, Extender* extender)
286 {
287 	Init("nzbget/nzbget", nullptr, true, commandLineOptions, true, extender);
288 }
289 
Init(const char * exeName,const char * configFilename,bool noConfig,CmdOptList * commandLineOptions,bool noDiskAccess,Extender * extender)290 void Options::Init(const char* exeName, const char* configFilename, bool noConfig,
291 	CmdOptList* commandLineOptions, bool noDiskAccess, Extender* extender)
292 {
293 	g_Options = this;
294 	m_extender = extender;
295 	m_noDiskAccess = noDiskAccess;
296 	m_noConfig = noConfig;
297 	m_configFilename = configFilename;
298 
299 	SetOption(OPTION_CONFIGFILE, "");
300 
301 	CString filename;
302 	if (m_noDiskAccess)
303 	{
304 		filename = exeName;
305 	}
306 	else
307 	{
308 		filename = FileSystem::GetExeFileName(exeName);
309 	}
310 	FileSystem::NormalizePathSeparators(filename);
311 	SetOption(OPTION_APPBIN, filename);
312 	char* end = strrchr(filename, PATH_SEPARATOR);
313 	if (end) *end = '\0';
314 	SetOption(OPTION_APPDIR, filename);
315 	m_appDir = *filename;
316 
317 	SetOption(OPTION_VERSION, Util::VersionRevision());
318 
319 	InitDefaults();
320 
321 	InitOptFile();
322 	if (m_fatalError)
323 	{
324 		return;
325 	}
326 
327 	if (commandLineOptions)
328 	{
329 		InitCommandLineOptions(commandLineOptions);
330 	}
331 
332 	if (!m_configFilename && !noConfig)
333 	{
334 		printf("No configuration-file found\n");
335 #ifdef WIN32
336 		printf("Please put configuration-file \"nzbget.conf\" into the directory with exe-file\n");
337 #else
338 		printf("Please use option \"-c\" or put configuration-file in one of the following locations:\n");
339 		int p = 0;
340 		while (const char* filename = PossibleConfigLocations[p++])
341 		{
342 			printf("%s\n", filename);
343 		}
344 #endif
345 		m_fatalError = true;
346 		return;
347 	}
348 
349 	ConvertOldOptions(&m_optEntries);
350 	InitOptions();
351 	CheckOptions();
352 
353 	InitServers();
354 	InitCategories();
355 	InitScheduler();
356 	InitFeeds();
357 }
358 
~Options()359 Options::~Options()
360 {
361 	g_Options = nullptr;
362 }
363 
ConfigError(const char * msg,...)364 void Options::ConfigError(const char* msg, ...)
365 {
366 	char tmp2[1024];
367 
368 	va_list ap;
369 	va_start(ap, msg);
370 	vsnprintf(tmp2, 1024, msg, ap);
371 	tmp2[1024-1] = '\0';
372 	va_end(ap);
373 
374 	printf("%s(%i): %s\n", m_configFilename ? FileSystem::BaseFileName(m_configFilename) : "<noconfig>", m_configLine, tmp2);
375 	error("%s(%i): %s", m_configFilename ? FileSystem::BaseFileName(m_configFilename) : "<noconfig>", m_configLine, tmp2);
376 
377 	m_configErrors = true;
378 }
379 
ConfigWarn(const char * msg,...)380 void Options::ConfigWarn(const char* msg, ...)
381 {
382 	char tmp2[1024];
383 
384 	va_list ap;
385 	va_start(ap, msg);
386 	vsnprintf(tmp2, 1024, msg, ap);
387 	tmp2[1024-1] = '\0';
388 	va_end(ap);
389 
390 	printf("%s(%i): %s\n", FileSystem::BaseFileName(m_configFilename), m_configLine, tmp2);
391 	warn("%s(%i): %s", FileSystem::BaseFileName(m_configFilename), m_configLine, tmp2);
392 }
393 
LocateOptionSrcPos(const char * optionName)394 void Options::LocateOptionSrcPos(const char *optionName)
395 {
396 	OptEntry* optEntry = FindOption(optionName);
397 	if (optEntry)
398 	{
399 		m_configLine = optEntry->GetLineNo();
400 	}
401 	else
402 	{
403 		m_configLine = 0;
404 	}
405 }
406 
InitDefaults()407 void Options::InitDefaults()
408 {
409 #ifdef WIN32
410 	SetOption(OPTION_MAINDIR, "${AppDir}");
411 	SetOption(OPTION_WEBDIR, "${AppDir}\\webui");
412 	SetOption(OPTION_CONFIGTEMPLATE, "${AppDir}\\nzbget.conf.template");
413 #else
414 	SetOption(OPTION_MAINDIR, "~/downloads");
415 	SetOption(OPTION_WEBDIR, "");
416 	SetOption(OPTION_CONFIGTEMPLATE, "");
417 #endif
418 	SetOption(OPTION_TEMPDIR, "${MainDir}/tmp");
419 	SetOption(OPTION_DESTDIR, "${MainDir}/dst");
420 	SetOption(OPTION_INTERDIR, "");
421 	SetOption(OPTION_QUEUEDIR, "${MainDir}/queue");
422 	SetOption(OPTION_NZBDIR, "${MainDir}/nzb");
423 	SetOption(OPTION_LOCKFILE, "${MainDir}/nzbget.lock");
424 	SetOption(OPTION_LOGFILE, "${MainDir}/nzbget.log");
425 	SetOption(OPTION_SCRIPTDIR, "${MainDir}/scripts");
426 	SetOption(OPTION_REQUIREDDIR, "");
427 	SetOption(OPTION_WRITELOG, "append");
428 	SetOption(OPTION_ROTATELOG, "3");
429 	SetOption(OPTION_APPENDCATEGORYDIR, "yes");
430 	SetOption(OPTION_OUTPUTMODE, "curses");
431 	SetOption(OPTION_DUPECHECK, "yes");
432 	SetOption(OPTION_DOWNLOADRATE, "0");
433 	SetOption(OPTION_CONTROLIP, "0.0.0.0");
434 	SetOption(OPTION_CONTROLUSERNAME, "nzbget");
435 	SetOption(OPTION_CONTROLPASSWORD, "tegbzn6789");
436 	SetOption(OPTION_RESTRICTEDUSERNAME, "");
437 	SetOption(OPTION_RESTRICTEDPASSWORD, "");
438 	SetOption(OPTION_ADDUSERNAME, "");
439 	SetOption(OPTION_ADDPASSWORD, "");
440 	SetOption(OPTION_CONTROLPORT, "6789");
441 	SetOption(OPTION_FORMAUTH, "no");
442 	SetOption(OPTION_SECURECONTROL, "no");
443 	SetOption(OPTION_SECUREPORT, "6791");
444 	SetOption(OPTION_SECURECERT, "");
445 	SetOption(OPTION_SECUREKEY, "");
446 	SetOption(OPTION_CERTSTORE, "");
447 	SetOption(OPTION_CERTCHECK, "no");
448 	SetOption(OPTION_AUTHORIZEDIP, "");
449 	SetOption(OPTION_ARTICLETIMEOUT, "60");
450 	SetOption(OPTION_URLTIMEOUT, "60");
451 	SetOption(OPTION_REMOTETIMEOUT, "90");
452 	SetOption(OPTION_FLUSHQUEUE, "yes");
453 	SetOption(OPTION_NZBLOG, "yes");
454 	SetOption(OPTION_RAWARTICLE, "no");
455 	SetOption(OPTION_SKIPWRITE, "no");
456 	SetOption(OPTION_ARTICLERETRIES, "3");
457 	SetOption(OPTION_ARTICLEINTERVAL, "10");
458 	SetOption(OPTION_URLRETRIES, "3");
459 	SetOption(OPTION_URLINTERVAL, "10");
460 	SetOption(OPTION_CONTINUEPARTIAL, "no");
461 	SetOption(OPTION_URLCONNECTIONS, "4");
462 	SetOption(OPTION_LOGBUFFER, "1000");
463 	SetOption(OPTION_INFOTARGET, "both");
464 	SetOption(OPTION_WARNINGTARGET, "both");
465 	SetOption(OPTION_ERRORTARGET, "both");
466 	SetOption(OPTION_DEBUGTARGET, "none");
467 	SetOption(OPTION_DETAILTARGET, "both");
468 	SetOption(OPTION_PARCHECK, "auto");
469 	SetOption(OPTION_PARREPAIR, "yes");
470 	SetOption(OPTION_PARSCAN, "extended");
471 	SetOption(OPTION_PARQUICK, "yes");
472 	SetOption(OPTION_POSTSTRATEGY, "sequential");
473 	SetOption(OPTION_FILENAMING, "article");
474 	SetOption(OPTION_PARRENAME, "yes");
475 	SetOption(OPTION_PARBUFFER, "16");
476 	SetOption(OPTION_PARTHREADS, "1");
477 	SetOption(OPTION_RARRENAME, "yes");
478 	SetOption(OPTION_HEALTHCHECK, "none");
479 	SetOption(OPTION_DIRECTRENAME, "no");
480 	SetOption(OPTION_SCRIPTORDER, "");
481 	SetOption(OPTION_EXTENSIONS, "");
482 	SetOption(OPTION_DAEMONUSERNAME, "root");
483 	SetOption(OPTION_UMASK, "1000");
484 	SetOption(OPTION_UPDATEINTERVAL, "200");
485 	SetOption(OPTION_CURSESNZBNAME, "yes");
486 	SetOption(OPTION_CURSESTIME, "no");
487 	SetOption(OPTION_CURSESGROUP, "no");
488 	SetOption(OPTION_CRCCHECK, "yes");
489 	SetOption(OPTION_DIRECTWRITE, "yes");
490 	SetOption(OPTION_WRITEBUFFER, "0");
491 	SetOption(OPTION_NZBDIRINTERVAL, "5");
492 	SetOption(OPTION_NZBDIRFILEAGE, "60");
493 	SetOption(OPTION_DISKSPACE, "250");
494 	SetOption(OPTION_CRASHTRACE, "no");
495 	SetOption(OPTION_CRASHDUMP, "no");
496 	SetOption(OPTION_PARPAUSEQUEUE, "no");
497 	SetOption(OPTION_SCRIPTPAUSEQUEUE, "no");
498 	SetOption(OPTION_NZBCLEANUPDISK, "no");
499 	SetOption(OPTION_PARTIMELIMIT, "0");
500 	SetOption(OPTION_KEEPHISTORY, "7");
501 	SetOption(OPTION_UNPACK, "no");
502 	SetOption(OPTION_DIRECTUNPACK, "no");
503 	SetOption(OPTION_UNPACKCLEANUPDISK, "no");
504 #ifdef WIN32
505 	SetOption(OPTION_UNRARCMD, "unrar.exe");
506 	SetOption(OPTION_SEVENZIPCMD, "7z.exe");
507 #else
508 	SetOption(OPTION_UNRARCMD, "unrar");
509 	SetOption(OPTION_SEVENZIPCMD, "7z");
510 #endif
511 	SetOption(OPTION_UNPACKPASSFILE, "");
512 	SetOption(OPTION_UNPACKPAUSEQUEUE, "no");
513 	SetOption(OPTION_EXTCLEANUPDISK, "");
514 	SetOption(OPTION_PARIGNOREEXT, "");
515 	SetOption(OPTION_UNPACKIGNOREEXT, "");
516 	SetOption(OPTION_FEEDHISTORY, "7");
517 	SetOption(OPTION_URLFORCE, "yes");
518 	SetOption(OPTION_TIMECORRECTION, "0");
519 	SetOption(OPTION_PROPAGATIONDELAY, "0");
520 	SetOption(OPTION_ARTICLECACHE, "0");
521 	SetOption(OPTION_EVENTINTERVAL, "0");
522 	SetOption(OPTION_SHELLOVERRIDE, "");
523 	SetOption(OPTION_MONTHLYQUOTA, "0");
524 	SetOption(OPTION_QUOTASTARTDAY, "1");
525 	SetOption(OPTION_DAILYQUOTA, "0");
526 	SetOption(OPTION_REORDERFILES, "no");
527 	SetOption(OPTION_UPDATECHECK, "none");
528 }
529 
InitOptFile()530 void Options::InitOptFile()
531 {
532 	if (!m_configFilename && !m_noConfig)
533 	{
534 		// search for config file in default locations
535 #ifdef WIN32
536 		BString<1024> filename("%s\\nzbget.conf", *m_appDir);
537 
538 		if (!FileSystem::FileExists(filename))
539 		{
540 			char appDataPath[MAX_PATH];
541 			SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, appDataPath);
542 			filename.Format("%s\\NZBGet\\nzbget.conf", appDataPath);
543 
544 			if (m_extender && !FileSystem::FileExists(filename))
545 			{
546 				m_extender->SetupFirstStart();
547 			}
548 		}
549 
550 		if (FileSystem::FileExists(filename))
551 		{
552 			m_configFilename = filename;
553 		}
554 #else
555 		// look in the exe-directory first
556 		BString<1024> filename("%s/nzbget.conf", *m_appDir);
557 
558 		if (FileSystem::FileExists(filename))
559 		{
560 			m_configFilename = filename;
561 		}
562 		else
563 		{
564 			int p = 0;
565 			while (const char* altfilename = PossibleConfigLocations[p++])
566 			{
567 				// substitute HOME-variable
568 				filename = FileSystem::ExpandHomePath(altfilename);
569 
570 				if (FileSystem::FileExists(filename))
571 				{
572 					m_configFilename = *filename;
573 					break;
574 				}
575 			}
576 		}
577 #endif
578 	}
579 
580 	if (m_configFilename)
581 	{
582 		// normalize path in filename
583 		CString filename = FileSystem::ExpandFileName(m_configFilename);
584 
585 #ifndef WIN32
586 		// substitute HOME-variable
587 		filename = FileSystem::ExpandHomePath(filename);
588 #endif
589 
590 		m_configFilename = *filename;
591 
592 		SetOption(OPTION_CONFIGFILE, m_configFilename);
593 		LoadConfigFile();
594 	}
595 }
596 
CheckDir(CString & dir,const char * optionName,const char * parentDir,bool allowEmpty,bool create)597 void Options::CheckDir(CString& dir, const char* optionName,
598 	const char* parentDir, bool allowEmpty, bool create)
599 {
600 	const char* tempdir = GetOption(optionName);
601 
602 	if (m_noDiskAccess)
603 	{
604 		dir = tempdir;
605 		return;
606 	}
607 
608 	if (Util::EmptyStr(tempdir))
609 	{
610 		if (!allowEmpty)
611 		{
612 			ConfigError("Invalid value for option \"%s\": <empty>", optionName);
613 		}
614 		dir = "";
615 		return;
616 	}
617 
618 	dir = tempdir;
619 	FileSystem::NormalizePathSeparators((char*)dir);
620 	if (!Util::EmptyStr(dir) && dir[dir.Length() - 1] == PATH_SEPARATOR)
621 	{
622 		// remove trailing slash
623 		dir[dir.Length() - 1] = '\0';
624 	}
625 
626 	if (!(dir[0] == PATH_SEPARATOR || dir[0] == ALT_PATH_SEPARATOR ||
627 		(dir[0] && dir[1] == ':')) &&
628 		!Util::EmptyStr(parentDir))
629 	{
630 		// convert relative path to absolute path
631 		int plen = strlen(parentDir);
632 
633 		BString<1024> usedir2;
634 		if (parentDir[plen-1] == PATH_SEPARATOR || parentDir[plen-1] == ALT_PATH_SEPARATOR)
635 		{
636 			usedir2.Format("%s%s", parentDir, *dir);
637 		}
638 		else
639 		{
640 			usedir2.Format("%s%c%s", parentDir, PATH_SEPARATOR, *dir);
641 		}
642 
643 		FileSystem::NormalizePathSeparators((char*)usedir2);
644 		dir = usedir2;
645 		SetOption(optionName, usedir2);
646 	}
647 
648 	// Ensure the dir is created
649 	CString errmsg;
650 	if (create && !FileSystem::ForceDirectories(dir, errmsg))
651 	{
652 		ConfigError("Invalid value for option \"%s\" (%s): %s", optionName, *dir, *errmsg);
653 	}
654 }
655 
InitOptions()656 void Options::InitOptions()
657 {
658 	const char* mainDir = GetOption(OPTION_MAINDIR);
659 
660 	CheckDir(m_destDir, OPTION_DESTDIR, mainDir, false, false);
661 	CheckDir(m_interDir, OPTION_INTERDIR, mainDir, true, false);
662 	CheckDir(m_tempDir, OPTION_TEMPDIR, mainDir, false, true);
663 	CheckDir(m_queueDir, OPTION_QUEUEDIR, mainDir, false, true);
664 	CheckDir(m_webDir, OPTION_WEBDIR, nullptr, true, false);
665 	CheckDir(m_scriptDir, OPTION_SCRIPTDIR, mainDir, true, false);
666 	CheckDir(m_nzbDir, OPTION_NZBDIR, mainDir, false, true);
667 
668 	m_requiredDir = GetOption(OPTION_REQUIREDDIR);
669 
670 	m_configTemplate		= GetOption(OPTION_CONFIGTEMPLATE);
671 	m_scriptOrder			= GetOption(OPTION_SCRIPTORDER);
672 	m_extensions			= GetOption(OPTION_EXTENSIONS);
673 	m_controlIp				= GetOption(OPTION_CONTROLIP);
674 	m_controlUsername		= GetOption(OPTION_CONTROLUSERNAME);
675 	m_controlPassword		= GetOption(OPTION_CONTROLPASSWORD);
676 	m_restrictedUsername	= GetOption(OPTION_RESTRICTEDUSERNAME);
677 	m_restrictedPassword	= GetOption(OPTION_RESTRICTEDPASSWORD);
678 	m_addUsername			= GetOption(OPTION_ADDUSERNAME);
679 	m_addPassword			= GetOption(OPTION_ADDPASSWORD);
680 	m_secureCert			= GetOption(OPTION_SECURECERT);
681 	m_secureKey				= GetOption(OPTION_SECUREKEY);
682 	m_certStore				= GetOption(OPTION_CERTSTORE);
683 	m_authorizedIp			= GetOption(OPTION_AUTHORIZEDIP);
684 	m_lockFile				= GetOption(OPTION_LOCKFILE);
685 	m_daemonUsername		= GetOption(OPTION_DAEMONUSERNAME);
686 	m_logFile				= GetOption(OPTION_LOGFILE);
687 	m_unrarCmd				= GetOption(OPTION_UNRARCMD);
688 	m_sevenZipCmd			= GetOption(OPTION_SEVENZIPCMD);
689 	m_unpackPassFile		= GetOption(OPTION_UNPACKPASSFILE);
690 	m_extCleanupDisk		= GetOption(OPTION_EXTCLEANUPDISK);
691 	m_parIgnoreExt			= GetOption(OPTION_PARIGNOREEXT);
692 	m_unpackIgnoreExt		= GetOption(OPTION_UNPACKIGNOREEXT);
693 	m_shellOverride			= GetOption(OPTION_SHELLOVERRIDE);
694 
695 	m_downloadRate			= ParseIntValue(OPTION_DOWNLOADRATE, 10) * 1024;
696 	m_articleTimeout		= ParseIntValue(OPTION_ARTICLETIMEOUT, 10);
697 	m_urlTimeout			= ParseIntValue(OPTION_URLTIMEOUT, 10);
698 	m_remoteTimeout			= ParseIntValue(OPTION_REMOTETIMEOUT, 10);
699 	m_articleRetries		= ParseIntValue(OPTION_ARTICLERETRIES, 10);
700 	m_articleInterval		= ParseIntValue(OPTION_ARTICLEINTERVAL, 10);
701 	m_urlRetries			= ParseIntValue(OPTION_URLRETRIES, 10);
702 	m_urlInterval			= ParseIntValue(OPTION_URLINTERVAL, 10);
703 	m_controlPort			= ParseIntValue(OPTION_CONTROLPORT, 10);
704 	m_securePort			= ParseIntValue(OPTION_SECUREPORT, 10);
705 	m_urlConnections		= ParseIntValue(OPTION_URLCONNECTIONS, 10);
706 	m_logBuffer				= ParseIntValue(OPTION_LOGBUFFER, 10);
707 	m_rotateLog				= ParseIntValue(OPTION_ROTATELOG, 10);
708 	m_umask					= ParseIntValue(OPTION_UMASK, 8);
709 	m_updateInterval		= ParseIntValue(OPTION_UPDATEINTERVAL, 10);
710 	m_writeBuffer			= ParseIntValue(OPTION_WRITEBUFFER, 10);
711 	m_nzbDirInterval		= ParseIntValue(OPTION_NZBDIRINTERVAL, 10);
712 	m_nzbDirFileAge			= ParseIntValue(OPTION_NZBDIRFILEAGE, 10);
713 	m_diskSpace				= ParseIntValue(OPTION_DISKSPACE, 10);
714 	m_parTimeLimit			= ParseIntValue(OPTION_PARTIMELIMIT, 10);
715 	m_keepHistory			= ParseIntValue(OPTION_KEEPHISTORY, 10);
716 	m_feedHistory			= ParseIntValue(OPTION_FEEDHISTORY, 10);
717 	m_timeCorrection		= ParseIntValue(OPTION_TIMECORRECTION, 10);
718 	if (-24 <= m_timeCorrection && m_timeCorrection <= 24)
719 	{
720 		m_timeCorrection *= 60;
721 	}
722 	m_timeCorrection *= 60;
723 	m_propagationDelay		= ParseIntValue(OPTION_PROPAGATIONDELAY, 10) * 60;
724 	m_articleCache			= ParseIntValue(OPTION_ARTICLECACHE, 10);
725 	m_eventInterval			= ParseIntValue(OPTION_EVENTINTERVAL, 10);
726 	m_parBuffer				= ParseIntValue(OPTION_PARBUFFER, 10);
727 	m_parThreads			= ParseIntValue(OPTION_PARTHREADS, 10);
728 	m_monthlyQuota			= ParseIntValue(OPTION_MONTHLYQUOTA, 10);
729 	m_quotaStartDay			= ParseIntValue(OPTION_QUOTASTARTDAY, 10);
730 	m_dailyQuota			= ParseIntValue(OPTION_DAILYQUOTA, 10);
731 
732 	m_nzbLog				= (bool)ParseEnumValue(OPTION_NZBLOG, BoolCount, BoolNames, BoolValues);
733 	m_appendCategoryDir		= (bool)ParseEnumValue(OPTION_APPENDCATEGORYDIR, BoolCount, BoolNames, BoolValues);
734 	m_continuePartial		= (bool)ParseEnumValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, BoolValues);
735 	m_flushQueue			= (bool)ParseEnumValue(OPTION_FLUSHQUEUE, BoolCount, BoolNames, BoolValues);
736 	m_dupeCheck				= (bool)ParseEnumValue(OPTION_DUPECHECK, BoolCount, BoolNames, BoolValues);
737 	m_parRepair				= (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues);
738 	m_parQuick				= (bool)ParseEnumValue(OPTION_PARQUICK, BoolCount, BoolNames, BoolValues);
739 	m_parRename				= (bool)ParseEnumValue(OPTION_PARRENAME, BoolCount, BoolNames, BoolValues);
740 	m_rarRename				= (bool)ParseEnumValue(OPTION_RARRENAME, BoolCount, BoolNames, BoolValues);
741 	m_directRename			= (bool)ParseEnumValue(OPTION_DIRECTRENAME, BoolCount, BoolNames, BoolValues);
742 	m_cursesNzbName			= (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues);
743 	m_cursesTime			= (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues);
744 	m_cursesGroup			= (bool)ParseEnumValue(OPTION_CURSESGROUP, BoolCount, BoolNames, BoolValues);
745 	m_crcCheck				= (bool)ParseEnumValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues);
746 	m_directWrite			= (bool)ParseEnumValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues);
747 	m_rawArticle			= (bool)ParseEnumValue(OPTION_RAWARTICLE, BoolCount, BoolNames, BoolValues);
748 	m_skipWrite				= (bool)ParseEnumValue(OPTION_SKIPWRITE, BoolCount, BoolNames, BoolValues);
749 	m_crashTrace			= (bool)ParseEnumValue(OPTION_CRASHTRACE, BoolCount, BoolNames, BoolValues);
750 	m_crashDump				= (bool)ParseEnumValue(OPTION_CRASHDUMP, BoolCount, BoolNames, BoolValues);
751 	m_parPauseQueue			= (bool)ParseEnumValue(OPTION_PARPAUSEQUEUE, BoolCount, BoolNames, BoolValues);
752 	m_scriptPauseQueue		= (bool)ParseEnumValue(OPTION_SCRIPTPAUSEQUEUE, BoolCount, BoolNames, BoolValues);
753 	m_nzbCleanupDisk		= (bool)ParseEnumValue(OPTION_NZBCLEANUPDISK, BoolCount, BoolNames, BoolValues);
754 	m_formAuth				= (bool)ParseEnumValue(OPTION_FORMAUTH, BoolCount, BoolNames, BoolValues);
755 	m_secureControl			= (bool)ParseEnumValue(OPTION_SECURECONTROL, BoolCount, BoolNames, BoolValues);
756 	m_unpack				= (bool)ParseEnumValue(OPTION_UNPACK, BoolCount, BoolNames, BoolValues);
757 	m_directUnpack			= (bool)ParseEnumValue(OPTION_DIRECTUNPACK, BoolCount, BoolNames, BoolValues);
758 	m_unpackCleanupDisk		= (bool)ParseEnumValue(OPTION_UNPACKCLEANUPDISK, BoolCount, BoolNames, BoolValues);
759 	m_unpackPauseQueue		= (bool)ParseEnumValue(OPTION_UNPACKPAUSEQUEUE, BoolCount, BoolNames, BoolValues);
760 	m_urlForce				= (bool)ParseEnumValue(OPTION_URLFORCE, BoolCount, BoolNames, BoolValues);
761 	m_certCheck				= (bool)ParseEnumValue(OPTION_CERTCHECK, BoolCount, BoolNames, BoolValues);
762 	m_reorderFiles			= (bool)ParseEnumValue(OPTION_REORDERFILES, BoolCount, BoolNames, BoolValues);
763 
764 	const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" };
765 	const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses };
766 	const int OutputModeCount = 7;
767 	m_outputMode = (EOutputMode)ParseEnumValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, OutputModeValues);
768 
769 	const char* ParCheckNames[] = { "auto", "always", "force", "manual" };
770 	const int ParCheckValues[] = { pcAuto, pcAlways, pcForce, pcManual };
771 	const int ParCheckCount = 6;
772 	m_parCheck = (EParCheck)ParseEnumValue(OPTION_PARCHECK, ParCheckCount, ParCheckNames, ParCheckValues);
773 
774 	const char* ParScanNames[] = { "limited", "extended", "full", "dupe" };
775 	const int ParScanValues[] = { psLimited, psExtended, psFull, psDupe };
776 	const int ParScanCount = 4;
777 	m_parScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues);
778 
779 	const char* PostStrategyNames[] = { "sequential", "balanced", "aggressive", "rocket" };
780 	const int PostStrategyValues[] = { ppSequential, ppBalanced, ppAggressive, ppRocket };
781 	const int PostStrategyCount = 4;
782 	m_postStrategy = (EPostStrategy)ParseEnumValue(OPTION_POSTSTRATEGY, PostStrategyCount, PostStrategyNames, PostStrategyValues);
783 
784 	const char* FileNamingNames[] = { "auto", "article", "nzb" };
785 	const int FileNamingValues[] = { nfAuto, nfArticle, nfNzb };
786 	const int FileNamingCount = 4;
787 	m_fileNaming = (EFileNaming)ParseEnumValue(OPTION_FILENAMING, FileNamingCount, FileNamingNames, FileNamingValues);
788 
789 	const char* HealthCheckNames[] = { "pause", "delete", "park", "none" };
790 	const int HealthCheckValues[] = { hcPause, hcDelete, hcPark, hcNone };
791 	const int HealthCheckCount = 4;
792 	m_healthCheck = (EHealthCheck)ParseEnumValue(OPTION_HEALTHCHECK, HealthCheckCount, HealthCheckNames, HealthCheckValues);
793 
794 	const char* TargetNames[] = { "screen", "log", "both", "none" };
795 	const int TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone };
796 	const int TargetCount = 4;
797 	m_infoTarget = (EMessageTarget)ParseEnumValue(OPTION_INFOTARGET, TargetCount, TargetNames, TargetValues);
798 	m_warningTarget = (EMessageTarget)ParseEnumValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, TargetValues);
799 	m_errorTarget = (EMessageTarget)ParseEnumValue(OPTION_ERRORTARGET, TargetCount, TargetNames, TargetValues);
800 	m_debugTarget = (EMessageTarget)ParseEnumValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, TargetValues);
801 	m_detailTarget = (EMessageTarget)ParseEnumValue(OPTION_DETAILTARGET, TargetCount, TargetNames, TargetValues);
802 
803 	const char* WriteLogNames[] = { "none", "append", "reset", "rotate" };
804 	const int WriteLogValues[] = { wlNone, wlAppend, wlReset, wlRotate };
805 	const int WriteLogCount = 4;
806 	m_writeLog = (EWriteLog)ParseEnumValue(OPTION_WRITELOG, WriteLogCount, WriteLogNames, WriteLogValues);
807 }
808 
ParseEnumValue(const char * OptName,int argc,const char * argn[],const int argv[])809 int Options::ParseEnumValue(const char* OptName, int argc, const char * argn[], const int argv[])
810 {
811 	OptEntry* optEntry = FindOption(OptName);
812 	if (!optEntry)
813 	{
814 		ConfigError("Undefined value for option \"%s\"", OptName);
815 		return argv[0];
816 	}
817 
818 	int defNum = 0;
819 
820 	for (int i = 0; i < argc; i++)
821 	{
822 		if (!strcasecmp(optEntry->GetValue(), argn[i]))
823 		{
824 			// normalizing option value in option list, for example "NO" -> "no"
825 			for (int j = 0; j < argc; j++)
826 			{
827 				if (argv[j] == argv[i])
828 				{
829 					if (strcmp(argn[j], optEntry->GetValue()))
830 					{
831 						optEntry->SetValue(argn[j]);
832 					}
833 					break;
834 				}
835 			}
836 
837 			return argv[i];
838 		}
839 
840 		if (!strcasecmp(optEntry->GetDefValue(), argn[i]))
841 		{
842 			defNum = i;
843 		}
844 	}
845 
846 	m_configLine = optEntry->GetLineNo();
847 	ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, optEntry->GetValue());
848 	optEntry->SetValue(argn[defNum]);
849 	return argv[defNum];
850 }
851 
ParseIntValue(const char * OptName,int base)852 int Options::ParseIntValue(const char* OptName, int base)
853 {
854 	OptEntry* optEntry = FindOption(OptName);
855 	if (!optEntry)
856 	{
857 		ConfigError("Undefined value for option \"%s\"", OptName);
858 		return 0;
859 	}
860 
861 	char *endptr;
862 	int val = strtol(optEntry->GetValue(), &endptr, base);
863 
864 	if (endptr && *endptr != '\0')
865 	{
866 		m_configLine = optEntry->GetLineNo();
867 		ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, optEntry->GetValue());
868 		optEntry->SetValue(optEntry->GetDefValue());
869 		val = strtol(optEntry->GetDefValue(), nullptr, base);
870 	}
871 
872 	return val;
873 }
874 
SetOption(const char * optname,const char * value)875 void Options::SetOption(const char* optname, const char* value)
876 {
877 	OptEntry* optEntry = FindOption(optname);
878 	if (!optEntry)
879 	{
880 		m_optEntries.emplace_back(optname, nullptr);
881 		optEntry = &m_optEntries.back();
882 	}
883 
884 	CString curvalue;
885 
886 #ifndef WIN32
887 	if (value && (value[0] == '~') && (value[1] == '/'))
888 	{
889 		if (m_noDiskAccess)
890 		{
891 			curvalue = value;
892 		}
893 		else
894 		{
895 			curvalue = FileSystem::ExpandHomePath(value);
896 		}
897 	}
898 	else
899 #endif
900 	{
901 		curvalue = value;
902 	}
903 
904 	optEntry->SetLineNo(m_configLine);
905 
906 	// expand variables
907 	while (const char* dollar = strstr(curvalue, "${"))
908 	{
909 		const char* end = strchr(dollar, '}');
910 		if (end)
911 		{
912 			int varlen = (int)(end - dollar - 2);
913 			BString<100> variable;
914 			variable.Set(dollar + 2, varlen);
915 			const char* varvalue = GetOption(variable);
916 			if (varvalue)
917 			{
918 				curvalue.Replace((int)(dollar - curvalue), 2 + varlen + 1, varvalue);
919 			}
920 			else
921 			{
922 				break;
923 			}
924 		}
925 		else
926 		{
927 			break;
928 		}
929 	}
930 
931 	optEntry->SetValue(curvalue);
932 }
933 
FindOption(const char * optname)934 Options::OptEntry* Options::FindOption(const char* optname)
935 {
936 	OptEntry* optEntry = m_optEntries.FindOption(optname);
937 
938 	// normalize option name in option list; for example "server1.joingroup" -> "Server1.JoinGroup"
939 	if (optEntry && strcmp(optEntry->GetName(), optname))
940 	{
941 		optEntry->SetName(optname);
942 	}
943 
944 	return optEntry;
945 }
946 
GetOption(const char * optname)947 const char* Options::GetOption(const char* optname)
948 {
949 	OptEntry* optEntry = FindOption(optname);
950 	if (optEntry)
951 	{
952 		if (optEntry->GetLineNo() > 0)
953 		{
954 			m_configLine = optEntry->GetLineNo();
955 		}
956 		return optEntry->GetValue();
957 	}
958 	return nullptr;
959 }
960 
InitServers()961 void Options::InitServers()
962 {
963 	int n = 1;
964 	while (true)
965 	{
966 		const char* nactive = GetOption(BString<100>("Server%i.Active", n));
967 		bool active = true;
968 		if (nactive)
969 		{
970 			active = (bool)ParseEnumValue(BString<100>("Server%i.Active", n), BoolCount, BoolNames, BoolValues);
971 		}
972 
973 		const char* nname = GetOption(BString<100>("Server%i.Name", n));
974 		const char* nlevel = GetOption(BString<100>("Server%i.Level", n));
975 		const char* ngroup = GetOption(BString<100>("Server%i.Group", n));
976 		const char* nhost = GetOption(BString<100>("Server%i.Host", n));
977 		const char* nport = GetOption(BString<100>("Server%i.Port", n));
978 		const char* nusername = GetOption(BString<100>("Server%i.Username", n));
979 		const char* npassword = GetOption(BString<100>("Server%i.Password", n));
980 
981 		const char* njoingroup = GetOption(BString<100>("Server%i.JoinGroup", n));
982 		bool joinGroup = false;
983 		if (njoingroup)
984 		{
985 			joinGroup = (bool)ParseEnumValue(BString<100>("Server%i.JoinGroup", n), BoolCount, BoolNames, BoolValues);
986 		}
987 
988 		const char* noptional = GetOption(BString<100>("Server%i.Optional", n));
989 		bool optional = false;
990 		if (noptional)
991 		{
992 			optional = (bool)ParseEnumValue(BString<100>("Server%i.Optional", n), BoolCount, BoolNames, BoolValues);
993 		}
994 
995 		const char* ntls = GetOption(BString<100>("Server%i.Encryption", n));
996 		bool tls = false;
997 		if (ntls)
998 		{
999 			tls = (bool)ParseEnumValue(BString<100>("Server%i.Encryption", n), BoolCount, BoolNames, BoolValues);
1000 #ifdef DISABLE_TLS
1001 			if (tls)
1002 			{
1003 				ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support",
1004 					*BString<100>("Server%i.Encryption", n));
1005 				tls = false;
1006 			}
1007 #endif
1008 			m_tls |= tls;
1009 		}
1010 
1011 		const char* nipversion = GetOption(BString<100>("Server%i.IpVersion", n));
1012 		int ipversion = 0;
1013 		if (nipversion)
1014 		{
1015 			const char* IpVersionNames[] = {"auto", "ipv4", "ipv6"};
1016 			const int IpVersionValues[] = {0, 4, 6};
1017 			const int IpVersionCount = 3;
1018 			ipversion = ParseEnumValue(BString<100>("Server%i.IpVersion", n), IpVersionCount, IpVersionNames, IpVersionValues);
1019 		}
1020 
1021 		const char* ncipher = GetOption(BString<100>("Server%i.Cipher", n));
1022 		const char* nconnections = GetOption(BString<100>("Server%i.Connections", n));
1023 		const char* nretention = GetOption(BString<100>("Server%i.Retention", n));
1024 
1025 		bool definition = nactive || nname || nlevel || ngroup || nhost || nport || noptional ||
1026 			nusername || npassword || nconnections || njoingroup || ntls || ncipher || nretention;
1027 		bool completed = nhost && nport && nconnections;
1028 
1029 		if (!definition)
1030 		{
1031 			break;
1032 		}
1033 
1034 		if (completed)
1035 		{
1036 			if (m_extender)
1037 			{
1038 				m_extender->AddNewsServer(n, active, nname,
1039 					nhost,
1040 					nport ? atoi(nport) : 119,
1041 					ipversion,
1042 					nusername, npassword,
1043 					joinGroup, tls, ncipher,
1044 					nconnections ? atoi(nconnections) : 1,
1045 					nretention ? atoi(nretention) : 0,
1046 					nlevel ? atoi(nlevel) : 0,
1047 					ngroup ? atoi(ngroup) : 0,
1048 					optional);
1049 			}
1050 		}
1051 		else
1052 		{
1053 			ConfigError("Server definition not complete for \"Server%i\"", n);
1054 		}
1055 
1056 		n++;
1057 	}
1058 }
1059 
InitCategories()1060 void Options::InitCategories()
1061 {
1062 	int n = 1;
1063 	while (true)
1064 	{
1065 		const char* nname = GetOption(BString<100>("Category%i.Name", n));
1066 		const char* ndestdir = GetOption(BString<100>("Category%i.DestDir", n));
1067 
1068 		const char* nunpack = GetOption(BString<100>("Category%i.Unpack", n));
1069 		bool unpack = true;
1070 		if (nunpack)
1071 		{
1072 			unpack = (bool)ParseEnumValue(BString<100>("Category%i.Unpack", n), BoolCount, BoolNames, BoolValues);
1073 		}
1074 
1075 		const char* nextensions = GetOption(BString<100>("Category%i.Extensions", n));
1076 		const char* naliases = GetOption(BString<100>("Category%i.Aliases", n));
1077 
1078 		bool definition = nname || ndestdir || nunpack || nextensions || naliases;
1079 		bool completed = nname && strlen(nname) > 0;
1080 
1081 		if (!definition)
1082 		{
1083 			break;
1084 		}
1085 
1086 		if (completed)
1087 		{
1088 			CString destDir;
1089 			if (ndestdir && ndestdir[0] != '\0')
1090 			{
1091 				CheckDir(destDir, BString<100>("Category%i.DestDir", n), m_destDir, false, false);
1092 			}
1093 
1094 			m_categories.emplace_back(nname, destDir, unpack, nextensions);
1095 			Category& category = m_categories.back();
1096 
1097 			// split Aliases into tokens and create items for each token
1098 			if (naliases)
1099 			{
1100 				Tokenizer tok(naliases, ",;");
1101 				while (const char* aliasName = tok.Next())
1102 				{
1103 					category.GetAliases()->push_back(aliasName);
1104 				}
1105 			}
1106 		}
1107 		else
1108 		{
1109 			ConfigError("Category definition not complete for \"Category%i\"", n);
1110 		}
1111 
1112 		n++;
1113 	}
1114 }
1115 
InitFeeds()1116 void Options::InitFeeds()
1117 {
1118 	int n = 1;
1119 	while (true)
1120 	{
1121 		const char* nname = GetOption(BString<100>("Feed%i.Name", n));
1122 		const char* nurl = GetOption(BString<100>("Feed%i.URL", n));
1123 		const char* nfilter = GetOption(BString<100>("Feed%i.Filter", n));
1124 		const char* ncategory = GetOption(BString<100>("Feed%i.Category", n));
1125 		const char* nextensions = GetOption(BString<100>("Feed%i.Extensions", n));
1126 
1127 		const char* nbacklog = GetOption(BString<100>("Feed%i.Backlog", n));
1128 		bool backlog = true;
1129 		if (nbacklog)
1130 		{
1131 			backlog = (bool)ParseEnumValue(BString<100>("Feed%i.Backlog", n), BoolCount, BoolNames, BoolValues);
1132 		}
1133 
1134 		const char* npausenzb = GetOption(BString<100>("Feed%i.PauseNzb", n));
1135 		bool pauseNzb = false;
1136 		if (npausenzb)
1137 		{
1138 			pauseNzb = (bool)ParseEnumValue(BString<100>("Feed%i.PauseNzb", n), BoolCount, BoolNames, BoolValues);
1139 		}
1140 
1141 		const char* ninterval = GetOption(BString<100>("Feed%i.Interval", n));
1142 		const char* npriority = GetOption(BString<100>("Feed%i.Priority", n));
1143 
1144 		bool definition = nname || nurl || nfilter || ncategory || nbacklog || npausenzb ||
1145 			ninterval || npriority || nextensions;
1146 		bool completed = nurl;
1147 
1148 		if (!definition)
1149 		{
1150 			break;
1151 		}
1152 
1153 		if (completed)
1154 		{
1155 			if (m_extender)
1156 			{
1157 				m_extender->AddFeed(n, nname, nurl, ninterval ? atoi(ninterval) : 0, nfilter,
1158 					backlog, pauseNzb, ncategory, npriority ? atoi(npriority) : 0, nextensions);
1159 			}
1160 		}
1161 		else
1162 		{
1163 			ConfigError("Feed definition not complete for \"Feed%i\"", n);
1164 		}
1165 
1166 		n++;
1167 	}
1168 }
1169 
InitScheduler()1170 void Options::InitScheduler()
1171 {
1172 	for (int n = 1; ; n++)
1173 	{
1174 		const char* time = GetOption(BString<100>("Task%i.Time", n));
1175 		const char* weekDays = GetOption(BString<100>("Task%i.WeekDays", n));
1176 		const char* command = GetOption(BString<100>("Task%i.Command", n));
1177 		const char* downloadRate = GetOption(BString<100>("Task%i.DownloadRate", n));
1178 		const char* process = GetOption(BString<100>("Task%i.Process", n));
1179 		const char* param = GetOption(BString<100>("Task%i.Param", n));
1180 
1181 		if (Util::EmptyStr(param) && !Util::EmptyStr(process))
1182 		{
1183 			param = process;
1184 		}
1185 		if (Util::EmptyStr(param) && !Util::EmptyStr(downloadRate))
1186 		{
1187 			param = downloadRate;
1188 		}
1189 
1190 		bool definition = time || weekDays || command || downloadRate || param;
1191 		bool completed = time && command;
1192 
1193 		if (!definition)
1194 		{
1195 			break;
1196 		}
1197 
1198 		if (!completed)
1199 		{
1200 			ConfigError("Task definition not complete for \"Task%i\"", n);
1201 			continue;
1202 		}
1203 
1204 		const char* CommandNames[] = { "pausedownload", "pause", "unpausedownload", "resumedownload", "unpause", "resume",
1205 			"pausepostprocess", "unpausepostprocess", "resumepostprocess", "pausepost", "unpausepost", "resumepost",
1206 			"downloadrate", "setdownloadrate", "rate", "speed", "script", "process", "pausescan", "unpausescan", "resumescan",
1207 			"activateserver", "activateservers", "deactivateserver", "deactivateservers", "fetchfeed", "fetchfeeds" };
1208 		const int CommandValues[] = { scPauseDownload, scPauseDownload, scUnpauseDownload,
1209 			scUnpauseDownload, scUnpauseDownload, scUnpauseDownload,
1210 			scPausePostProcess, scUnpausePostProcess, scUnpausePostProcess,
1211 			scPausePostProcess, scUnpausePostProcess, scUnpausePostProcess,
1212 			scDownloadRate, scDownloadRate, scDownloadRate, scDownloadRate,
1213 			scScript, scProcess, scPauseScan, scUnpauseScan, scUnpauseScan,
1214 			scActivateServer, scActivateServer, scDeactivateServer,
1215 			scDeactivateServer, scFetchFeed, scFetchFeed };
1216 		const int CommandCount = 27;
1217 		ESchedulerCommand taskCommand = (ESchedulerCommand)ParseEnumValue(
1218 			BString<100>("Task%i.Command", n), CommandCount, CommandNames, CommandValues);
1219 
1220 		if (param && strlen(param) > 0 && taskCommand == scProcess &&
1221 			Util::SplitCommandLine(param).empty())
1222 		{
1223 			ConfigError("Invalid value for option \"Task%i.Param\"", n);
1224 			continue;
1225 		}
1226 
1227 		if (taskCommand == scDownloadRate)
1228 		{
1229 			if (param)
1230 			{
1231 				char* err;
1232 				int downloadRateVal = strtol(param, &err, 10);
1233 				if (!err || *err != '\0' || downloadRateVal < 0)
1234 				{
1235 					ConfigError("Invalid value for option \"Task%i.Param\": \"%s\"", n, downloadRate);
1236 					continue;
1237 				}
1238 			}
1239 			else
1240 			{
1241 				ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n);
1242 				continue;
1243 			}
1244 		}
1245 
1246 		if ((taskCommand == scScript ||
1247 			 taskCommand == scProcess ||
1248 			 taskCommand == scActivateServer ||
1249 			 taskCommand == scDeactivateServer ||
1250 			 taskCommand == scFetchFeed) &&
1251 			Util::EmptyStr(param))
1252 		{
1253 			ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n);
1254 			continue;
1255 		}
1256 
1257 		CreateSchedulerTask(n, time, weekDays, taskCommand, param);
1258 	}
1259 }
1260 
CreateSchedulerTask(int id,const char * time,const char * weekDays,ESchedulerCommand command,const char * param)1261 void Options::CreateSchedulerTask(int id, const char* time, const char* weekDays,
1262 	ESchedulerCommand command, const char* param)
1263 {
1264 	if (!id)
1265 	{
1266 		m_configLine = 0;
1267 	}
1268 
1269 	int weekDaysVal = 0;
1270 	if (weekDays && !ParseWeekDays(weekDays, &weekDaysVal))
1271 	{
1272 		ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", id, weekDays);
1273 		return;
1274 	}
1275 
1276 	int hours, minutes;
1277 	Tokenizer tok(time, ";,");
1278 	while (const char* oneTime = tok.Next())
1279 	{
1280 		if (!ParseTime(oneTime, &hours, &minutes))
1281 		{
1282 			ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", id, oneTime);
1283 			return;
1284 		}
1285 
1286 		if (m_extender)
1287 		{
1288 			if (hours == -2)
1289 			{
1290 				for (int everyHour = 0; everyHour < 24; everyHour++)
1291 				{
1292 					m_extender->AddTask(id, everyHour, minutes, weekDaysVal, command, param);
1293 				}
1294 			}
1295 			else
1296 			{
1297 				m_extender->AddTask(id, hours, minutes, weekDaysVal, command, param);
1298 			}
1299 		}
1300 	}
1301 }
1302 
ParseTime(const char * time,int * hours,int * minutes)1303 bool Options::ParseTime(const char* time, int* hours, int* minutes)
1304 {
1305 	if (!strcmp(time, "*"))
1306 	{
1307 		*hours = -1;
1308 		return true;
1309 	}
1310 
1311 	int colons = 0;
1312 	const char* p = time;
1313 	while (*p)
1314 	{
1315 		if (!strchr("0123456789: *", *p))
1316 		{
1317 			return false;
1318 		}
1319 		if (*p == ':')
1320 		{
1321 			colons++;
1322 		}
1323 		p++;
1324 	}
1325 
1326 	if (colons != 1)
1327 	{
1328 		return false;
1329 	}
1330 
1331 	const char* colon = strchr(time, ':');
1332 	if (!colon)
1333 	{
1334 		return false;
1335 	}
1336 
1337 	if (time[0] == '*')
1338 	{
1339 		*hours = -2;
1340 	}
1341 	else
1342 	{
1343 		*hours = atoi(time);
1344 		if (*hours < 0 || *hours > 23)
1345 		{
1346 			return false;
1347 		}
1348 	}
1349 
1350 	if (colon[1] == '*')
1351 	{
1352 		return false;
1353 	}
1354 	*minutes = atoi(colon + 1);
1355 	if (*minutes < 0 || *minutes > 59)
1356 	{
1357 		return false;
1358 	}
1359 
1360 	return true;
1361 }
1362 
ParseWeekDays(const char * weekDays,int * weekDaysBits)1363 bool Options::ParseWeekDays(const char* weekDays, int* weekDaysBits)
1364 {
1365 	*weekDaysBits = 0;
1366 	const char* p = weekDays;
1367 	int firstDay = 0;
1368 	bool range = false;
1369 	while (*p)
1370 	{
1371 		if (strchr("1234567", *p))
1372 		{
1373 			int day = *p - '0';
1374 			if (range)
1375 			{
1376 				if (day <= firstDay || firstDay == 0)
1377 				{
1378 					return false;
1379 				}
1380 				for (int i = firstDay; i <= day; i++)
1381 				{
1382 					*weekDaysBits |= 1 << (i - 1);
1383 				}
1384 				firstDay = 0;
1385 			}
1386 			else
1387 			{
1388 				*weekDaysBits |= 1 << (day - 1);
1389 				firstDay = day;
1390 			}
1391 			range = false;
1392 		}
1393 		else if (*p == ',')
1394 		{
1395 			range = false;
1396 		}
1397 		else if (*p == '-')
1398 		{
1399 			range = true;
1400 		}
1401 		else if (*p == ' ')
1402 		{
1403 			// skip spaces
1404 		}
1405 		else
1406 		{
1407 			return false;
1408 		}
1409 		p++;
1410 	}
1411 	return true;
1412 }
1413 
LoadConfigFile()1414 void Options::LoadConfigFile()
1415 {
1416 	SetOption(OPTION_CONFIGFILE, m_configFilename);
1417 
1418 	DiskFile infile;
1419 
1420 	if (!infile.Open(m_configFilename, DiskFile::omRead))
1421 	{
1422 		ConfigError("Could not open file %s", *m_configFilename);
1423 		m_fatalError = true;
1424 		return;
1425 	}
1426 
1427 	m_configLine = 0;
1428 	int bufLen = (int)FileSystem::FileSize(m_configFilename) + 1;
1429 	CharBuffer buf(bufLen);
1430 
1431 	int line = 0;
1432 	while (infile.ReadLine(buf, buf.Size() - 1))
1433 	{
1434 		m_configLine = ++line;
1435 
1436 		if (buf[0] != 0 && buf[strlen(buf)-1] == '\n')
1437 		{
1438 			buf[strlen(buf)-1] = 0; // remove traling '\n'
1439 		}
1440 		if (buf[0] != 0 && buf[strlen(buf)-1] == '\r')
1441 		{
1442 			buf[strlen(buf)-1] = 0; // remove traling '\r' (for windows line endings)
1443 		}
1444 
1445 		if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf))
1446 		{
1447 			continue;
1448 		}
1449 
1450 		SetOptionString(buf);
1451 	}
1452 
1453 	infile.Close();
1454 
1455 	m_configLine = 0;
1456 }
1457 
InitCommandLineOptions(CmdOptList * commandLineOptions)1458 void Options::InitCommandLineOptions(CmdOptList* commandLineOptions)
1459 {
1460 	for (const char* option : *commandLineOptions)
1461 	{
1462 		SetOptionString(option);
1463 	}
1464 }
1465 
SetOptionString(const char * option)1466 bool Options::SetOptionString(const char* option)
1467 {
1468 	CString optname;
1469 	CString optvalue;
1470 
1471 	if (!SplitOptionString(option, optname, optvalue))
1472 	{
1473 		ConfigError("Invalid option \"%s\"", option);
1474 		return false;
1475 	}
1476 
1477 	bool ok = ValidateOptionName(optname, optvalue);
1478 	if (ok)
1479 	{
1480 		SetOption(optname, optvalue);
1481 	}
1482 	else
1483 	{
1484 		ConfigError("Invalid option \"%s\"", *optname);
1485 	}
1486 
1487 	return ok;
1488 }
1489 
1490 /*
1491  * Splits option string into name and value;
1492  * Converts old names and values if necessary;
1493  * Returns true if the option string has name and value;
1494  */
SplitOptionString(const char * option,CString & optName,CString & optValue)1495 bool Options::SplitOptionString(const char* option, CString& optName, CString& optValue)
1496 {
1497 	const char* eq = strchr(option, '=');
1498 	if (!eq || eq == option)
1499 	{
1500 		return false;
1501 	}
1502 
1503 	optName.Set(option, (int)(eq - option));
1504 	optValue.Set(eq + 1);
1505 
1506 	ConvertOldOption(optName, optValue);
1507 
1508 	return true;
1509 }
1510 
ValidateOptionName(const char * optname,const char * optvalue)1511 bool Options::ValidateOptionName(const char* optname, const char* optvalue)
1512 {
1513 	if (!strcasecmp(optname, OPTION_CONFIGFILE) || !strcasecmp(optname, OPTION_APPBIN) ||
1514 		!strcasecmp(optname, OPTION_APPDIR) || !strcasecmp(optname, OPTION_VERSION))
1515 	{
1516 		// read-only options
1517 		return false;
1518 	}
1519 
1520 	const char* v = GetOption(optname);
1521 	if (v)
1522 	{
1523 		// it's predefined option, OK
1524 		return true;
1525 	}
1526 
1527 	if (!strncasecmp(optname, "server", 6))
1528 	{
1529 		char* p = (char*)optname + 6;
1530 		while (*p >= '0' && *p <= '9') p++;
1531 		if (p &&
1532 			(!strcasecmp(p, ".active") || !strcasecmp(p, ".name") ||
1533 			!strcasecmp(p, ".level") || !strcasecmp(p, ".host") ||
1534 			!strcasecmp(p, ".port") || !strcasecmp(p, ".username") ||
1535 			!strcasecmp(p, ".password") || !strcasecmp(p, ".joingroup") ||
1536 			!strcasecmp(p, ".encryption") || !strcasecmp(p, ".connections") ||
1537 			!strcasecmp(p, ".cipher") || !strcasecmp(p, ".group") ||
1538 			!strcasecmp(p, ".retention") || !strcasecmp(p, ".optional") ||
1539 			!strcasecmp(p, ".notes") || !strcasecmp(p, ".ipversion")))
1540 		{
1541 			return true;
1542 		}
1543 	}
1544 
1545 	if (!strncasecmp(optname, "task", 4))
1546 	{
1547 		char* p = (char*)optname + 4;
1548 		while (*p >= '0' && *p <= '9') p++;
1549 		if (p && (!strcasecmp(p, ".time") || !strcasecmp(p, ".weekdays") ||
1550 			!strcasecmp(p, ".command") || !strcasecmp(p, ".param") ||
1551 			!strcasecmp(p, ".downloadrate") || !strcasecmp(p, ".process")))
1552 		{
1553 			return true;
1554 		}
1555 	}
1556 
1557 	if (!strncasecmp(optname, "category", 8))
1558 	{
1559 		char* p = (char*)optname + 8;
1560 		while (*p >= '0' && *p <= '9') p++;
1561 		if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".extensions") ||
1562 			!strcasecmp(p, ".unpack") || !strcasecmp(p, ".aliases")))
1563 		{
1564 			return true;
1565 		}
1566 	}
1567 
1568 	if (!strncasecmp(optname, "feed", 4))
1569 	{
1570 		char* p = (char*)optname + 4;
1571 		while (*p >= '0' && *p <= '9') p++;
1572 		if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".url") || !strcasecmp(p, ".interval") ||
1573 			 !strcasecmp(p, ".filter") || !strcasecmp(p, ".backlog") || !strcasecmp(p, ".pausenzb") ||
1574 			 !strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".extensions")))
1575 		{
1576 			return true;
1577 		}
1578 	}
1579 
1580 	// scripts options
1581 	if (strchr(optname, ':'))
1582 	{
1583 		return true;
1584 	}
1585 
1586 	// print warning messages for obsolete options
1587 	if (!strcasecmp(optname, OPTION_RETRYONCRCERROR) ||
1588 		!strcasecmp(optname, OPTION_ALLOWREPROCESS) ||
1589 		!strcasecmp(optname, OPTION_LOADPARS) ||
1590 		!strcasecmp(optname, OPTION_THREADLIMIT) ||
1591 		!strcasecmp(optname, OPTION_POSTLOGKIND) ||
1592 		!strcasecmp(optname, OPTION_NZBLOGKIND) ||
1593 		!strcasecmp(optname, OPTION_PROCESSLOGKIND) ||
1594 		!strcasecmp(optname, OPTION_APPENDNZBDIR) ||
1595 		!strcasecmp(optname, OPTION_RENAMEBROKEN) ||
1596 		!strcasecmp(optname, OPTION_MERGENZB) ||
1597 		!strcasecmp(optname, OPTION_STRICTPARNAME) ||
1598 		!strcasecmp(optname, OPTION_RELOADURLQUEUE) ||
1599 		!strcasecmp(optname, OPTION_RELOADPOSTQUEUE) ||
1600 		!strcasecmp(optname, OPTION_PARCLEANUPQUEUE) ||
1601 		!strcasecmp(optname, OPTION_DELETECLEANUPDISK) ||
1602 		!strcasecmp(optname, OPTION_HISTORYCLEANUPDISK) ||
1603 		!strcasecmp(optname, OPTION_SAVEQUEUE) ||
1604 		!strcasecmp(optname, OPTION_RELOADQUEUE) ||
1605 		!strcasecmp(optname, OPTION_TERMINATETIMEOUT) ||
1606 		!strcasecmp(optname, OPTION_ACCURATERATE) ||
1607 		!strcasecmp(optname, OPTION_CREATEBROKENLOG) ||
1608 		!strcasecmp(optname, OPTION_BROKENLOG))
1609 	{
1610 		ConfigWarn("Option \"%s\" is obsolete, ignored", optname);
1611 		return true;
1612 	}
1613 
1614 	if (!strcasecmp(optname, OPTION_POSTPROCESS) ||
1615 		!strcasecmp(optname, OPTION_NZBPROCESS) ||
1616 		!strcasecmp(optname, OPTION_NZBADDEDPROCESS))
1617 	{
1618 		if (optvalue && strlen(optvalue) > 0)
1619 		{
1620 			ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead",
1621 				optname, OPTION_SCRIPTDIR, OPTION_EXTENSIONS);
1622 		}
1623 		return true;
1624 	}
1625 
1626 	if (!strcasecmp(optname, OPTION_SCANSCRIPT) ||
1627 		!strcasecmp(optname, OPTION_QUEUESCRIPT) ||
1628 		!strcasecmp(optname, OPTION_FEEDSCRIPT))
1629 	{
1630 		// will be automatically converted into "Extensions"
1631 		return true;
1632 	}
1633 
1634 	if (!strcasecmp(optname, OPTION_CREATELOG) || !strcasecmp(optname, OPTION_RESETLOG))
1635 	{
1636 		ConfigWarn("Option \"%s\" is obsolete, ignored, use \"%s\" instead", optname, OPTION_WRITELOG);
1637 		return true;
1638 	}
1639 
1640 	return false;
1641 }
1642 
ConvertOldOption(CString & option,CString & value)1643 void Options::ConvertOldOption(CString& option, CString& value)
1644 {
1645 	// for compatibility with older versions accept old option names
1646 
1647 	if (!strcasecmp(option, "$MAINDIR"))
1648 	{
1649 		option = "MainDir";
1650 	}
1651 
1652 	if (!strcasecmp(option, "ServerIP"))
1653 	{
1654 		option = "ControlIP";
1655 	}
1656 
1657 	if (!strcasecmp(option, "ServerPort"))
1658 	{
1659 		option = "ControlPort";
1660 	}
1661 
1662 	if (!strcasecmp(option, "ServerPassword"))
1663 	{
1664 		option = "ControlPassword";
1665 	}
1666 
1667 	if (!strcasecmp(option, "PostPauseQueue"))
1668 	{
1669 		option = "ScriptPauseQueue";
1670 	}
1671 
1672 	if (!strcasecmp(option, "ParCheck") && !strcasecmp(value, "yes"))
1673 	{
1674 		value = "always";
1675 	}
1676 
1677 	if (!strcasecmp(option, "ParCheck") && !strcasecmp(value, "no"))
1678 	{
1679 		value = "auto";
1680 	}
1681 
1682 	if (!strcasecmp(option, "ParScan") && !strcasecmp(value, "auto"))
1683 	{
1684 		value = "extended";
1685 	}
1686 
1687 	if (!strcasecmp(option, "DefScript") || !strcasecmp(option, "PostScript"))
1688 	{
1689 		option = "Extensions";
1690 	}
1691 
1692 	int nameLen = strlen(option);
1693 	if (!strncasecmp(option, "Category", 8) &&
1694 		((nameLen > 10 && !strcasecmp(option + nameLen - 10, ".DefScript")) ||
1695 		 (nameLen > 11 && !strcasecmp(option + nameLen - 11, ".PostScript"))))
1696 	{
1697 		option.Replace(".DefScript", ".Extensions");
1698 		option.Replace(".PostScript", ".Extensions");
1699 	}
1700 	if (!strncasecmp(option, "Feed", 4) && nameLen > 11 && !strcasecmp(option + nameLen - 11, ".FeedScript"))
1701 	{
1702 		option.Replace(".FeedScript", ".Extensions");
1703 	}
1704 
1705 	if (!strcasecmp(option, "WriteBufferSize"))
1706 	{
1707 		option = "WriteBuffer";
1708 		int val = strtol(value, nullptr, 10);
1709 		val = val == -1 ? 1024 : val / 1024;
1710 		value.Format("%i", val);
1711 	}
1712 
1713 	if (!strcasecmp(option, "ConnectionTimeout"))
1714 	{
1715 		option = "ArticleTimeout";
1716 	}
1717 
1718 	if (!strcasecmp(option, "Retries"))
1719 	{
1720 		option = "ArticleRetries";
1721 	}
1722 
1723 	if (!strcasecmp(option, "RetryInterval"))
1724 	{
1725 		option = "ArticleInterval";
1726 	}
1727 
1728 	if (!strcasecmp(option, "DumpCore"))
1729 	{
1730 		option = OPTION_CRASHDUMP;
1731 	}
1732 
1733 	if (!strcasecmp(option, OPTION_DECODE))
1734 	{
1735 		option = OPTION_RAWARTICLE;
1736 		value = !strcasecmp(value, "no") ? "yes" : "no";
1737 	}
1738 
1739 	if (!strcasecmp(option, "LogBufferSize"))
1740 	{
1741 		option = OPTION_LOGBUFFER;
1742 	}
1743 }
1744 
CheckOptions()1745 void Options::CheckOptions()
1746 {
1747 #ifdef DISABLE_PARCHECK
1748 	if (m_parCheck != pcManual)
1749 	{
1750 		LocateOptionSrcPos(OPTION_PARCHECK);
1751 		ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARCHECK);
1752 	}
1753 	if (m_parRename)
1754 	{
1755 		LocateOptionSrcPos(OPTION_PARRENAME);
1756 		ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARRENAME);
1757 	}
1758 	if (m_directRename)
1759 	{
1760 		LocateOptionSrcPos(OPTION_DIRECTRENAME);
1761 		ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_DIRECTRENAME);
1762 	}
1763 #endif
1764 
1765 #ifdef DISABLE_CURSES
1766 	if (m_outputMode == omNCurses)
1767 	{
1768 		LocateOptionSrcPos(OPTION_OUTPUTMODE);
1769 		ConfigError("Invalid value for option \"%s\": program was compiled without curses-support", OPTION_OUTPUTMODE);
1770 	}
1771 #endif
1772 
1773 #ifdef DISABLE_TLS
1774 	if (m_secureControl)
1775 	{
1776 		LocateOptionSrcPos(OPTION_SECURECONTROL);
1777 		ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_SECURECONTROL);
1778 	}
1779 
1780 	if (m_certCheck)
1781 	{
1782 		LocateOptionSrcPos(OPTION_CERTCHECK);
1783 		ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_CERTCHECK);
1784 	}
1785 #endif
1786 
1787 #ifdef HAVE_OPENSSL
1788 #ifndef HAVE_X509_CHECK_HOST
1789 	if (m_certCheck)
1790 	{
1791 		LocateOptionSrcPos(OPTION_CERTCHECK);
1792 		ConfigWarn("TLS certificate verification (option \"%s\") is limited because the program "
1793 			"was compiled with older OpenSSL version not supporting hostname validation (at least OpenSSL 1.0.2d is required)", OPTION_CERTCHECK);
1794 	}
1795 #endif
1796 #endif
1797 
1798 #ifdef HAVE_LIBGNUTLS
1799 #if	GNUTLS_VERSION_NUMBER < 0x030104
1800 	if (m_certCheck)
1801 	{
1802 		LocateOptionSrcPos(OPTION_CERTCHECK);
1803 		ConfigWarn("TLS certificate verification (option \"%s\") is disabled because the program "
1804 			"was compiled with older GnuTLS version not supporting certificate validation (at least GnuTLS 3.1.4 is required)", OPTION_CERTCHECK);
1805 	}
1806 #endif
1807 #endif
1808 
1809 	if (m_certCheck && m_certStore.Empty())
1810 	{
1811 		LocateOptionSrcPos(OPTION_CERTCHECK);
1812 		ConfigError("Option \"%s\" requires proper configuration of option \"%s\"", OPTION_CERTCHECK, OPTION_CERTSTORE);
1813 		m_certCheck = false;
1814 	}
1815 
1816 	if (m_rawArticle)
1817 	{
1818 		m_directWrite = false;
1819 	}
1820 
1821 	if (m_skipWrite)
1822 	{
1823 		m_directRename = false;
1824 	}
1825 
1826 	// if option "ConfigTemplate" is not set, use "WebDir" as default location for template
1827 	// (for compatibility with versions 9 and 10).
1828 	if (m_configTemplate.Empty() && !m_noDiskAccess)
1829 	{
1830 		m_configTemplate.Format("%s%s", *m_webDir, "nzbget.conf");
1831 		if (!FileSystem::FileExists(m_configTemplate))
1832 		{
1833 			m_configTemplate = "";
1834 		}
1835 	}
1836 
1837 	if (m_articleCache < 0)
1838 	{
1839 		m_articleCache = 0;
1840 	}
1841 	else if (sizeof(void*) == 4 && m_articleCache > 1900)
1842 	{
1843 		ConfigError("Invalid value for option \"ArticleCache\": %i. Changed to 1900", m_articleCache);
1844 		m_articleCache = 1900;
1845 	}
1846 	else if (sizeof(void*) == 4 && m_parBuffer > 1900)
1847 	{
1848 		ConfigError("Invalid value for option \"ParBuffer\": %i. Changed to 1900", m_parBuffer);
1849 		m_parBuffer = 1900;
1850 	}
1851 
1852 	if (sizeof(void*) == 4 && m_parBuffer + m_articleCache > 1900)
1853 	{
1854 		ConfigError("Options \"ArticleCache\" and \"ParBuffer\" in total cannot use more than 1900MB of memory in 32-Bit mode. Changed to 1500 and 400");
1855 		m_articleCache = 1500;
1856 		m_parBuffer = 400;
1857 	}
1858 
1859 	if (!m_unpackPassFile.Empty() && !FileSystem::FileExists(m_unpackPassFile))
1860 	{
1861 		ConfigError("Invalid value for option \"UnpackPassFile\": %s. File not found", *m_unpackPassFile);
1862 	}
1863 }
1864 
ConvertOldOptions(OptEntries * optEntries)1865 void Options::ConvertOldOptions(OptEntries* optEntries)
1866 {
1867 	MergeOldScriptOption(optEntries, OPTION_SCANSCRIPT, true);
1868 	MergeOldScriptOption(optEntries, OPTION_QUEUESCRIPT, true);
1869 	MergeOldScriptOption(optEntries, OPTION_FEEDSCRIPT, false);
1870 }
1871 
MergeOldScriptOption(OptEntries * optEntries,const char * optname,bool mergeCategories)1872 void Options::MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories)
1873 {
1874 	OptEntry* optEntry = optEntries->FindOption(optname);
1875 	if (!optEntry || Util::EmptyStr(optEntry->GetValue()))
1876 	{
1877 		return;
1878 	}
1879 
1880 	OptEntry* extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS);
1881 	if (!extensionsOpt)
1882 	{
1883 		optEntries->emplace_back(OPTION_EXTENSIONS, "");
1884 		extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS);
1885 	}
1886 
1887 	const char* scriptList = optEntry->GetValue();
1888 
1889 	Tokenizer tok(scriptList, ",;");
1890 	while (const char* scriptName = tok.Next())
1891 	{
1892 		// merge into global "Extensions"
1893 		if (!HasScript(extensionsOpt->m_value, scriptName))
1894 		{
1895 			if (!extensionsOpt->m_value.Empty())
1896 			{
1897 				extensionsOpt->m_value.Append(",");
1898 			}
1899 			extensionsOpt->m_value.Append(scriptName);
1900 		}
1901 
1902 		// merge into categories' "Extensions" (if not empty)
1903 		if (mergeCategories)
1904 		{
1905 			for (OptEntry& opt : optEntries)
1906 			{
1907 				const char* catoptname = opt.GetName();
1908 				if (!strncasecmp(catoptname, "category", 8))
1909 				{
1910 					char* p = (char*)catoptname + 8;
1911 					while (*p >= '0' && *p <= '9') p++;
1912 					if (p && (!strcasecmp(p, ".extensions")))
1913 					{
1914 						if (!opt.m_value.Empty() && !HasScript(opt.m_value, scriptName))
1915 						{
1916 							opt.m_value.Append(",");
1917 							opt.m_value.Append(scriptName);
1918 						}
1919 					}
1920 				}
1921 			}
1922 		}
1923 	}
1924 }
1925 
HasScript(const char * scriptList,const char * scriptName)1926 bool Options::HasScript(const char* scriptList, const char* scriptName)
1927 {
1928 	Tokenizer tok(scriptList, ",;");
1929 	while (const char* scriptName2 = tok.Next())
1930 	{
1931 		if (!strcasecmp(scriptName2, scriptName))
1932 		{
1933 			return true;
1934 		}
1935 	}
1936 	return false;
1937 };
1938