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