1 
2 #include "debug.h"
3 
4 #include "acfg.h"
5 #include "meta.h"
6 #include "filereader.h"
7 #include "fileio.h"
8 #include "sockio.h"
9 #include "lockable.h"
10 #include "cleaner.h"
11 
12 #include <regex.h>
13 
14 #include <iostream>
15 #include <fstream>
16 #include <deque>
17 #include <algorithm>
18 #include <list>
19 #include <unordered_map>
20 #include <atomic>
21 
22 using namespace std;
23 
24 namespace acng
25 {
26 // hint to use the main configuration excluding the complex directives
27 //bool g_testMode=false;
28 
29 bool bIsHashedPwd=false;
30 
31 #define BARF(x) {if(!g_bQuiet) { cerr << x << endl;} exit(EXIT_FAILURE); }
32 #define BADSTUFF_PATTERN "\\.\\.($|%|/)"
33 
34 namespace rex
35 {
36 bool CompileExpressions();
37 }
38 
39 
40 namespace cfg {
41 
42 bool g_bQuiet=false, g_bNoComplex=false;
43 
44 extern std::atomic_bool degraded;
45 
46 // internal stuff:
47 string sPopularPath("/debian/");
48 string tmpDontcache, tmpDontcacheReq, tmpDontcacheTgt, optProxyCheckCmd;
49 int optProxyCheckInt = 99;
50 
51 tStrMap localdirs;
52 static class : public base_with_mutex, public NoCaseStringMap {} mimemap;
53 
54 std::bitset<TCP_PORT_MAX> *pUserPorts = nullptr;
55 
56 tHttpUrl proxy_info;
57 
58 struct MapNameToString
59 {
60 	const char *name; mstring *ptr;
61 };
62 
63 struct MapNameToInt
64 {
65 	const char *name; int *ptr;
66 	const char *warn; uint8_t base;
67 	uint8_t hidden;	// just a hint
68 };
69 
70 struct tProperty
71 {
72 	const char *name;
73 	std::function<bool(cmstring& key, cmstring& value)> set;
74 	std::function<mstring(bool superUser)> get; // returns a string value. A string starting with # tells to skip the output
75 };
76 
77 #ifndef MINIBUILD
78 
79 // predeclare some
80 void _ParseLocalDirs(cmstring &value);
81 void AddRemapInfo(bool bAsBackend, const string & token, const string &repname);
82 void AddRemapFlag(const string & token, const string &repname);
83 void _AddHooksFile(cmstring& vname);
84 
85 unsigned ReadBackendsFile(const string & sFile, const string &sRepName);
86 unsigned ReadRewriteFile(const string & sFile, cmstring& sRepName);
87 
88 map<cmstring, tRepoData> repoparms;
89 typedef decltype(repoparms)::iterator tPairRepoNameData;
90 // maps hostname:port -> { <pathprefix,repopointer>, ... }
91 std::unordered_map<string, list<pair<cmstring,tPairRepoNameData>>> mapUrl2pVname;
92 
93 MapNameToString n2sTbl[] = {
94 		{   "Port",                    &port}
95 		,{  "CacheDir",                &cachedir}
96 		,{  "LogDir",                  &logdir}
97 		,{  "SupportDir",              &suppdir}
98 		,{  "SocketPath",              &fifopath}
99 		,{  "PidFile",                 &pidfile}
100 		,{  "ReportPage",              &reportpage}
101 		,{  "VfilePattern",            &vfilepat}
102 		,{  "PfilePattern",            &pfilepat}
103 		,{  "SPfilePattern",           &spfilepat}
104 		,{  "SVfilePattern",           &svfilepat}
105 		,{  "WfilePattern",            &wfilepat}
106 		,{  "VfilePatternEx",          &vfilepatEx}
107 		,{  "PfilePatternEx",          &pfilepatEx}
108 		,{  "WfilePatternEx",          &wfilepatEx}
109 		,{  "SPfilePatternEx",         &spfilepatEx}
110 		,{  "SVfilePatternEx",         &svfilepatEx}
111 //		,{  "AdminAuth",               &adminauth}
112 		,{  "BindAddress",             &bindaddr}
113 		,{  "UserAgent",               &agentname}
114 		,{  "DontCache",               &tmpDontcache}
115 		,{  "DontCacheRequested",      &tmpDontcacheReq}
116 		,{  "DontCacheResolved",       &tmpDontcacheTgt}
117 		,{  "PrecacheFor",             &mirrorsrcs}
118 		,{  "RequestAppendix",         &requestapx}
119 		,{  "PassThroughPattern",      &connectPermPattern}
120 		,{  "CApath",                  &capath}
121 		,{  "CAfile",                  &cafile}
122 		,{  "BadRedirDetectMime",      &badredmime}
123 		,{	"OptProxyCheckCommand",	   &optProxyCheckCmd}
124 		,{  "BusAction",                &sigbuscmd} // "Special debugging helper, see manual!"
125 };
126 
127 MapNameToInt n2iTbl[] = {
128 		{   "Debug",                             &debug,            nullptr,    10, false}
129 		,{  "OfflineMode",                       &offlinemode,      nullptr,    10, false}
130 		,{  "ForeGround",                        &foreground,       nullptr,    10, false}
131 		,{  "ForceManaged",                      &forcemanaged,     nullptr,    10, false}
132 		,{  "StupidFs",                          &stupidfs,         nullptr,    10, false}
133 		,{  "VerboseLog",                        &verboselog,       nullptr,    10, false}
134 		,{  "ExThreshold",                       &extreshhold,      nullptr,    10, false}
135 		,{  "ExTreshold",                        &extreshhold,      nullptr,    10, true} // wrong spelling :-(
136 		,{  "MaxStandbyConThreads",              &tpstandbymax,     nullptr,    10, false}
137 		,{  "MaxConThreads",                     &tpthreadmax,      nullptr,    10, false}
138 		,{  "DnsCacheSeconds",                   &dnscachetime,     nullptr,    10, false}
139 		,{  "UnbufferLogs",                      &debug,            nullptr,    10, false}
140 		,{  "ExAbortOnProblems",                 &exfailabort,      nullptr,    10, false}
141 		,{  "ExposeOrigin",                      &exporigin,        nullptr,    10, false}
142 		,{  "LogSubmittedOrigin",                &logxff,           nullptr,    10, false}
143 		,{  "RecompBz2",                         &recompbz2,        nullptr,    10, false}
144 		,{  "NetworkTimeout",                    &nettimeout,       nullptr,    10, false}
145 		,{  "MinUpdateInterval",                 &updinterval,      nullptr,    10, false}
146 		,{  "ForwardBtsSoap",                    &forwardsoap,      nullptr,    10, false}
147 		,{  "KeepExtraVersions",                 &keepnver,         nullptr,    10, false}
148 		,{  "UseWrap",                           &usewrap,          nullptr,    10, false}
149 		,{  "FreshIndexMaxAge",                  &maxtempdelay,     nullptr,    10, false}
150 		,{  "RedirMax",                          &redirmax,         nullptr,    10, false}
151 		,{  "VfileUseRangeOps",                  &vrangeops,        nullptr,    10, false}
152 		,{  "ResponseFreezeDetectTime",          &stucksecs,        nullptr,    10, false}
153 		,{  "ReuseConnections",                  &persistoutgoing,  nullptr,    10, false}
154 		,{  "PipelineDepth",                     &pipelinelen,      nullptr,    10, false}
155 		,{  "ExSuppressAdminNotification",       &exsupcount,       nullptr,    10, false}
156 		,{  "OptProxyTimeout",                   &optproxytimeout,  nullptr,    10, false}
157 		,{  "MaxDlSpeed",                        &maxdlspeed,       nullptr,    10, false}
158 		,{  "MaxInresponsiveDlSize",             &maxredlsize,      nullptr,    10, false}
159 		,{  "OptProxyCheckInterval",             &optProxyCheckInt, nullptr,    10, false}
160 		,{  "TrackFileUse",		             	 &trackfileuse,		nullptr,    10, false}
161         ,{  "ReserveSpace",                      &allocspace, 		nullptr ,   10, false}
162 
163         // octal base interpretation of UNIX file permissions
164 		,{  "DirPerms",                          &dirperms,         nullptr,    8, false}
165 		,{  "FilePerms",                         &fileperms,        nullptr,    8, false}
166 
167 		,{ "Verbose", 			nullptr,		"Option is deprecated, ignoring the value." , 10, true}
168 		,{ "MaxSpareThreadSets",&tpstandbymax, 	"Deprecated option name, mapped to MaxStandbyConThreads", 10, true}
169 		,{ "OldIndexUpdater",	&oldupdate, 	"Option is deprecated, ignoring the value." , 10, true}
170 		,{ "Patrace",	&patrace, 				"Don't use in config files!" , 10, false}
171 		,{ "NoSSLchecks",	&nsafriendly, 		"Disable SSL security checks" , 10, false}
172 };
173 
174 
175 tProperty n2pTbl[] =
176 {
177 { "Proxy", [](cmstring& key, cmstring& value)
__anon1fac21350202() 178 {
179 	if(value.empty()) proxy_info=tHttpUrl();
180 	else
181 	{
182 		if (!proxy_info.SetHttpUrl(value) || proxy_info.sHost.empty())
183 		BARF("Invalid proxy specification, aborting...");
184 	}
185 	return true;
186 }, [](bool superUser) -> string
__anon1fac21350302() 187 {
188 	if(!superUser && !proxy_info.sUserPass.empty())
189 		return string("#");
190 	return proxy_info.sHost.empty() ? sEmptyString : proxy_info.ToURI(false);
191 } },
192 { "LocalDirs", [](cmstring& key, cmstring& value) -> bool
__anon1fac21350402() 193 {
194 	if(g_bNoComplex)
195 	return true;
196 	_ParseLocalDirs(value);
197 	return !localdirs.empty();
198 }, [](bool) -> string
__anon1fac21350502() 199 {
200 	string ret;
201 	for(auto kv : localdirs)
202 	ret += kv.first + " " + kv.second + "; ";
203 	return ret;
204 } },
205 { "Remap-", [](cmstring& key, cmstring& value) -> bool
__anon1fac21350602() 206 {
207 	if(g_bNoComplex)
208 	return true;
209 
210 	string vname=key.substr(6, key.npos);
211 	if(vname.empty())
212 	{
213 		if(!g_bQuiet)
214 		cerr << "Bad repository name in " << key << endl;
215 		return false;
216 	}
217 	int type(-1); // nothing =-1; prefixes =0 ; backends =1; flags =2
218 		for(tSplitWalk split(&value); split.Next();)
219 		{
220 			cmstring s(split);
221 			if(s.empty())
222 			continue;
223 			if(s.at(0)=='#')
224 			break;
225 			if(type<0)
226 			type=0;
227 			if(s.at(0)==';')
228 			++type;
229 			else if(0 == type)
230 			AddRemapInfo(false, s, vname);
231 			else if(1 == type)
232 			AddRemapInfo(true, s, vname);
233 			else if(2 == type)
234 			AddRemapFlag(s, vname);
235 		}
236 		if(type<0)
237 		{
238 			if(!g_bQuiet)
239 			cerr << "Invalid entry, no configuration: " << key << ": " << value <<endl;
240 			return false;
241 		}
242 		_AddHooksFile(vname);
243 		return true;
244 	}, [](bool) -> string
__anon1fac21350702() 245 	{
246 		return "# mixed options";
247 	} },
248 { "AllowUserPorts", [](cmstring& key, cmstring& value) -> bool
__anon1fac21350802() 249 {
250 	if(!pUserPorts)
251 	pUserPorts=new bitset<TCP_PORT_MAX>;
252 	for(tSplitWalk split(&value); split.Next();)
253 	{
254 		cmstring s(split);
255 		const char *start(s.c_str());
256 		char *p(0);
257 		unsigned long n=strtoul(start, &p, 10);
258 		if(n>=TCP_PORT_MAX || !p || '\0' != *p || p == start)
259 		BARF("Bad port in AllowUserPorts: " << start);
260 		if(n == 0)
261 		{
262 			pUserPorts->set();
263 			break;
264 		}
265 		pUserPorts->set(n, true);
266 	}
267 	return true;
268 }, [](bool) -> string
__anon1fac21350902() 269 {
270 	tSS ret;
271 	if(pUserPorts)
272 		{
273 	for(auto i=0; i<TCP_PORT_MAX; ++i)
274 	ret << (ret.empty() ? "" : ", ") << i;
275 		}
276 	return (string) ret;
277 } },
278 { "ConnectProto", [](cmstring& key, cmstring& value) -> bool
__anon1fac21350a02() 279 {
280 	int *p = conprotos;
281 	for (tSplitWalk split(&value); split.Next(); ++p)
282 	{
283 		cmstring val(split);
284 		if (val.empty())
285 		break;
286 
287 		if (p >= conprotos + _countof(conprotos))
288 		BARF("Too many protocols specified: " << val);
289 
290 		if (val == "v6")
291 		*p = PF_INET6;
292 		else if (val == "v4")
293 		*p = PF_INET;
294 		else
295 		BARF("IP protocol not supported: " << val);
296 	}
297 	return true;
298 }, [](bool) -> string
__anon1fac21350b02() 299 {
300 	string ret(conprotos[0] == PF_INET6 ? "v6" : "v4");
301 	if(conprotos[0] != conprotos[1])
302 		ret += string(" ") + (conprotos[1] == PF_INET6 ? "v6" : "v4");
303 	return ret;
304 } },
305 { "AdminAuth", [](cmstring& key, cmstring& value) -> bool
__anon1fac21350c02() 306 {
307 	adminauth=value;
308 	adminauthB64=EncodeBase64Auth(value);
309 	return true;
310 }, [](bool) -> string
__anon1fac21350d02() 311 {
312 	return "#"; // TOP SECRET";
313 } }
314 ,
315 { "ExStartTradeOff", [](cmstring& key, cmstring& value) -> bool
__anon1fac21350e02() 316 {
317 	exstarttradeoff = strsizeToOfft(value.c_str());
318 	return true;
319 }, [](bool) -> string
__anon1fac21350f02() 320 {
321 	return ltos(exstarttradeoff);
322 } }
323 
324  #if SUPPWHASH
325  bIsHashedPwd=false;
326  }
327 
328  else if(CHECKOPTKEY("AdminAuthHash"))
329  {
330  adminauth=value;
331  bIsHashedPwd=true;
332  #endif
333 
334 };
335 
GetStringPtr(LPCSTR key)336 string * GetStringPtr(LPCSTR key) {
337 	for(auto &ent : n2sTbl)
338 		if(0==strcasecmp(key, ent.name))
339 			return ent.ptr;
340 	return nullptr;
341 }
342 
GetIntPtr(LPCSTR key,int & base)343 int * GetIntPtr(LPCSTR key, int &base) {
344 	for(auto &ent : n2iTbl)
345 	{
346 		if(0==strcasecmp(key, ent.name))
347 		{
348 			if(ent.warn)
349 				cerr << "Warning, " << key << ": " << ent.warn << endl;
350 			base = ent.base;
351 			return ent.ptr;
352 		}
353 	}
354 	return nullptr;
355 }
356 
GetPropPtr(cmstring & key)357 tProperty* GetPropPtr(cmstring& key)
358 {
359 	auto sep = key.find('-');
360 	auto szkey = key.c_str();
361 	for (auto &ent : n2pTbl)
362 	{
363 		if (0 == strcasecmp(szkey, ent.name))
364 			return &ent;
365 		// identified as prefix, with matching length?
366 		if(sep != stmiss && 0==strncasecmp(szkey, ent.name, sep) && 0 == ent.name[sep+1])
367 			return &ent;
368 	}
369 	return nullptr;
370 }
371 
GetIntPtr(LPCSTR key)372 int * GetIntPtr(LPCSTR key) {
373 	for(auto &ent : n2iTbl)
374 		if(0==strcasecmp(key, ent.name))
375 			return ent.ptr;
376 	return nullptr;
377 }
378 
379 // shortcut for frequently needed code, opens the config file, reads step-by-step
380 // and skips comment and empty lines
381 struct tCfgIter
382 {
383 	filereader reader;
384 	string sLine;
385 	string sFilename;
tCfgIteracng::cfg::tCfgIter386 	tCfgIter(cmstring &fn) : sFilename(fn)
387 	{
388 		reader.OpenFile(fn, false, 1);
389 	}
operator boolacng::cfg::tCfgIter390 	inline operator bool() const { return reader.CheckGoodState(false, &sFilename); }
Nextacng::cfg::tCfgIter391 	inline bool Next()
392 	{
393 		while(reader.GetOneLine(sLine))
394 		{
395 			trimFront(sLine);
396 			if(sLine.empty() || sLine[0] == '#')
397 				continue;
398 			return true;
399 		}
400 		return false;
401 	}
402 };
403 
qgrep(cmstring & needle,cmstring & file)404 inline bool qgrep(cmstring &needle, cmstring &file)
405 {
406 	for(cfg::tCfgIter itor(file); itor.Next();)
407 		if(StrHas(itor.sLine, needle))
408 			return true;
409 	return false;
410 }
411 
DegradedMode()412 bool DegradedMode()
413 {
414 	return degraded.load();
415 }
416 
_FixPostPreSlashes(string & val)417 inline void _FixPostPreSlashes(string &val)
418 {
419 	// fix broken entries
420 
421 	if (val.empty() || val.at(val.length()-1) != '/')
422 		val.append("/");
423 	if (val.at(0) != '/')
424 		val.insert(0, "/", 1);
425 }
426 
ReadOneConfFile(const string & szFilename,bool bReadErrorIsFatal=true)427 bool ReadOneConfFile(const string & szFilename, bool bReadErrorIsFatal=true)
428 {
429 	tCfgIter itor(szFilename);
430 	itor.reader.CheckGoodState(bReadErrorIsFatal, &szFilename);
431 
432 	NoCaseStringMap dupeCheck;
433 
434 	while(itor.Next())
435 	{
436 #ifdef DEBUG
437 		cerr << itor.sLine <<endl;
438 #endif
439 		// XXX: To something about escaped/quoted version
440 		tStrPos pos=itor.sLine.find('#');
441 		if(stmiss != pos)
442 			itor.sLine.erase(pos);
443 
444 		if(! SetOption(itor.sLine, &dupeCheck))
445 			BARF("Error reading main options, terminating.");
446 	}
447 	return true;
448 }
449 
GetRepoEntryRef(const string & sRepName)450 inline decltype(repoparms)::iterator GetRepoEntryRef(const string & sRepName)
451 {
452 	auto it = repoparms.find(sRepName);
453 	if(repoparms.end() != it)
454 		return it;
455 	// strange...
456 	auto rv(repoparms.insert(make_pair(sRepName,tRepoData())));
457 	return rv.first;
458 }
459 
ParseOptionLine(const string & sLine,string & key,string & val)460 inline bool ParseOptionLine(const string &sLine, string &key, string &val)
461 {
462 	string::size_type posCol = sLine.find(":");
463 	string::size_type posEq = sLine.find("=");
464 	if (posEq==stmiss && posCol==stmiss)
465 	{
466 		if(!g_bQuiet)
467 			cerr << "Not a valid configuration directive: " << sLine <<endl;
468 		return false;
469 	}
470 	string::size_type pos;
471 	if (posEq!=stmiss && posCol!=stmiss)
472 		pos=min(posEq,posCol);
473 	else if (posEq!=stmiss)
474 		pos=posEq;
475 	else
476 		pos=posCol;
477 
478 	key=sLine.substr(0, pos);
479 	val=sLine.substr(pos+1);
480 	trimString(key);
481 	trimString(val);
482 	if(key.empty())
483 		return false; // weird
484 
485 	if(endsWithSzAr(val, "\\"))
486 		cerr << "Warning: multilines are not supported, consider using \\n." <<endl;
487 
488 	return true;
489 }
490 
491 
AddRemapFlag(const string & token,const string & repname)492 inline void AddRemapFlag(const string & token, const string &repname)
493 {
494 	mstring key, value;
495 	if(!ParseOptionLine(token, key, value))
496 		return;
497 
498 	tRepoData &where = repoparms[repname];
499 
500 	if(key=="keyfile")
501 	{
502 		if(value.empty())
503 			return;
504 		if (cfg::debug & log::LOG_FLUSH)
505 			cerr << "Fatal keyfile for " <<repname<<": "<<value <<endl;
506 
507 		where.m_keyfiles.emplace_back(value);
508 	}
509 	else if(key=="deltasrc")
510 	{
511 		if(value.empty())
512 			return;
513 
514 		if(!endsWithSzAr(value, "/"))
515 			value+="/";
516 
517 		if(!where.m_deltasrc.SetHttpUrl(value))
518 			cerr << "Couldn't parse Debdelta source URL, ignored " <<value <<endl;
519 	}
520 	else if(key=="proxy")
521 	{
522 		static std::list<tHttpUrl> alt_proxies;
523 		tHttpUrl cand;
524 		if(value.empty() || cand.SetHttpUrl(value))
525 		{
526 			alt_proxies.emplace_back(cand);
527 			where.m_pProxy = & alt_proxies.back();
528 		}
529 		else
530 		{
531 			cerr << "Warning, failed to parse proxy setting " << value << " , "
532 					<< endl << "ignoring it" <<endl;
533 		}
534 	}
535 }
536 
ExpandFileTokens(cmstring & token)537 tStrDeq ExpandFileTokens(cmstring &token)
538 {
539 	string sPath = token.substr(5);
540 	if (sPath.empty())
541 		BARF("Bad file spec for repname, file:?");
542 	bool bAbs = IsAbsolute(sPath);
543 	if (suppdir.empty() || bAbs)
544 	{
545 		if (!bAbs)
546 			sPath = confdir + sPathSep + sPath;
547 		return ExpandFilePattern(sPath, true);
548 	}
549 	auto pat = confdir + sPathSep + sPath;
550 	StrSubst(pat, "//", "/");
551 	auto res = ExpandFilePattern(pat, true);
552 	if (res.size() == 1 && !Cstat(res.front()))
553 		res.clear(); // not existing, wildcard returned
554 	pat = suppdir + sPathSep + sPath;
555 	StrSubst(pat, "//", "/");
556 	auto suppres = ExpandFilePattern(pat, true);
557 	if (suppres.size() == 1 && !Cstat(suppres.front()))
558 		return res; // errrr... done here
559 	// merge them
560 	tStrSet dupeFil;
561         for(const auto& s: res)
562 #ifdef COMPATGCC47
563            dupeFil.insert(GetBaseName(s));
564 #else
565         dupeFil.emplace(GetBaseName(s));
566 #endif
567 	for(const auto& s: suppres)
568 		if(!ContHas(dupeFil, GetBaseName(s)))
569 			res.emplace_back(s);
570 	return res;
571 }
572 
AddRemapInfo(bool bAsBackend,const string & token,const string & repname)573 inline void AddRemapInfo(bool bAsBackend, const string & token,
574 		const string &repname)
575 {
576 	if (0!=token.compare(0, 5, "file:"))
577 	{
578 		tHttpUrl url;
579 		if(! url.SetHttpUrl(token))
580 			BARF(token + " <-- bad URL detected");
581 		_FixPostPreSlashes(url.sPath);
582 
583 		if (bAsBackend)
584 			repoparms[repname].m_backends.emplace_back(url);
585 		else
586 			mapUrl2pVname[url.sHost+":"+url.GetPort()].emplace_back(
587 					url.sPath, GetRepoEntryRef(repname));
588 	}
589 	else
590 	{
591 		auto func = bAsBackend ? ReadBackendsFile : ReadRewriteFile;
592 		unsigned count = 0;
593 		for(auto& src : ExpandFileTokens(token))
594 			count += func(src, repname);
595 		if(!count)
596 			for(auto& src : ExpandFileTokens(token + ".default"))
597 				count = func(src, repname);
598 		if(!count && !g_bQuiet)
599 			cerr << "WARNING: No configuration was read from " << token << endl;
600 	}
601 }
602 
603 struct tHookHandler: public tRepoData::IHookHandler, public base_with_mutex
604 {
605 	string cmdRel, cmdCon;
606 	time_t downDuration, downTimeNext;
607 
608 	int m_nRefCnt;
609 
tHookHandleracng::cfg::tHookHandler610 	tHookHandler(cmstring&) :
611 		downDuration(30), downTimeNext(0), m_nRefCnt(0)
612 	{
613 //		cmdRel = "logger JobRelease/" + name;
614 //		cmdCon = "logger JobConnect/" + name;
615 	}
OnReleaseacng::cfg::tHookHandler616 	virtual void OnRelease() override
617 	{
618 		setLockGuard;
619 		if (0 >= --m_nRefCnt)
620 		{
621 			//system(cmdRel.c_str());
622 			downTimeNext = ::time(0) + downDuration;
623 			g_victor.ScheduleFor(downTimeNext, cleaner::TYPE_ACFGHOOKS);
624 		}
625 	}
OnAccessacng::cfg::tHookHandler626 	virtual void OnAccess() override
627 	{
628 		setLockGuard;
629 		if (0 == m_nRefCnt++)
630 		{
631 			if(downTimeNext) // huh, already ticking? reset
632 				downTimeNext=0;
633 			else if(system(cmdCon.c_str()))
634 				log::err(tSS() << "Warning: " << cmdCon << " returned with error code.");
635 		}
636 	}
637 };
638 
_AddHooksFile(cmstring & vname)639 inline void _AddHooksFile(cmstring& vname)
640 {
641 	tCfgIter itor(cfg::confdir+"/"+vname+".hooks");
642 	if(!itor)
643 		return;
644 
645 	struct tHookHandler &hs = *(new tHookHandler(vname));
646 	mstring key,val;
647 	while (itor.Next())
648 	{
649 		if(!ParseOptionLine(itor.sLine, key, val))
650 			continue;
651 
652 		const char *p = key.c_str();
653 		if (strcasecmp("PreUp", p) == 0)
654 		{
655 			hs.cmdCon = val;
656 		}
657 		else if (strcasecmp("Down", p) == 0)
658 		{
659 			hs.cmdRel = val;
660 		}
661 		else if (strcasecmp("DownTimeout", p) == 0)
662 		{
663 			errno = 0;
664 			unsigned n = strtoul(val.c_str(), nullptr, 10);
665 			if (!errno)
666 				hs.downDuration = n;
667 		}
668 	}
669 	repoparms[vname].m_pHooks = &hs;
670 }
671 
_ParseLocalDirs(cmstring & value)672 inline void _ParseLocalDirs(cmstring &value)
673 {
674 	for(tSplitWalk splitter(&value, ";"); splitter.Next(); )
675 	{
676 		mstring token=splitter.str();
677 		trimString(token);
678 		tStrPos pos = token.find_first_of(SPACECHARS);
679 		if(stmiss == pos)
680 		{
681 			cerr << "Cannot map " << token << ", needed format: virtualdir realdir, ignoring it";
682 			continue;
683 		}
684 		string from(token, 0, pos);
685 		trimString(from, "/");
686 		string what(token, pos);
687 		trimString(what, SPACECHARS "'\"");
688 		if(what.empty())
689 		{
690 			cerr << "Unsupported target of " << from << ": " << what << ", ignoring it" << endl;
691 			continue;
692 		}
693 		localdirs[from]=what;
694 	}
695 }
696 
697 
GetMimeType(cmstring & path)698 cmstring & GetMimeType(cmstring &path)
699 {
700 	{
701 		lockguard g(mimemap);
702 		static bool inited = false;
703 		if (!inited)
704 		{
705 			inited = true;
706 			for (tCfgIter itor("/etc/mime.types"); itor.Next();)
707 			{
708 				// # regular types:
709 				// text/plain             asc txt text pot brf  # plain ascii files
710 
711 				tSplitWalk split(&itor.sLine);
712 				if (!split.Next())
713 					continue;
714 
715 				mstring mimetype = split;
716 				if (startsWithSz(mimetype, "#"))
717 					continue;
718 
719 				while (split.Next())
720 				{
721 					mstring suf = split;
722 					if (startsWithSz(suf, "#"))
723 						break;
724 					mimemap[suf] = mimetype;
725 				}
726 			}
727 		}
728 	}
729 
730 	tStrPos dpos = path.find_last_of('.');
731 	if (dpos != stmiss)
732 	{
733 		NoCaseStringMap::const_iterator it = cfg::mimemap.find(path.substr(
734 				dpos + 1));
735 		if (it != cfg::mimemap.end())
736 			return it->second;
737 	}
738 	// try some educated guess... assume binary if we are sure, text if we are almost sure
739 	static cmstring os("application/octet-stream"), tp("text/plain");
740 	filereader f;
741 	if(f.OpenFile(path, true))
742 	{
743 		size_t maxLen = std::min(size_t(255), f.GetSize());
744 		for(unsigned i=0; i< maxLen; ++i)
745 		{
746 			if(!isascii((uint) *(f.GetBuffer()+i)))
747 				return os;
748 		}
749 		return tp;
750 	}
751 	return sEmptyString;
752 }
753 
SetOption(const string & sLine,NoCaseStringMap * pDupeCheck)754 bool SetOption(const string &sLine, NoCaseStringMap *pDupeCheck)
755 {
756 	string key, value;
757 
758 	if(!ParseOptionLine(sLine, key, value))
759 		return false;
760 
761 	string * psTarget;
762 	int * pnTarget;
763 	tProperty * ppTarget;
764 	int nNumBase(10);
765 
766 	if ( nullptr != (psTarget = GetStringPtr(key.c_str())))
767 	{
768 
769 		if(pDupeCheck && !g_bQuiet)
770 		{
771 			mstring &w = (*pDupeCheck)[key];
772 			if(w.empty())
773 				w = value;
774 			else
775 				cerr << "WARNING: " << key << " was previously set to " << w << endl;
776 		}
777 
778 		*psTarget=value;
779 	}
780 	else if ( nullptr != (pnTarget = GetIntPtr(key.c_str(), nNumBase)))
781 	{
782 
783 		if(pDupeCheck && !g_bQuiet)
784 		{
785 			mstring &w = (*pDupeCheck)[key];
786 			if(w.empty())
787 				w = value;
788 			else
789 				cerr << "WARNING: " << key << " was already set to " << w << endl;
790 		}
791 
792 		const char *pStart=value.c_str();
793 		if(! *pStart)
794 		{
795 			cerr << "Missing value for " << key << " option!" <<endl;
796 			return false;
797 		}
798 
799 		errno=0;
800 		char *pEnd(nullptr);
801 		long nVal = strtol(pStart, &pEnd, nNumBase);
802 
803 		if(RESERVED_DEFVAL == nVal)
804 		{
805 			cerr << "Bad value for " << key << " (protected value, use another one)" <<endl;
806 			return false;
807 		}
808 
809 		*pnTarget=nVal;
810 
811 		if (errno)
812 		{
813 			cerr << "Invalid number for " << key << " ";
814 			perror("option");
815 			return false;
816 		}
817 		if(*pEnd)
818 		{
819 			cerr << "Bad value for " << key << " option or found trailing garbage: " << pEnd <<endl;
820 			return false;
821 		}
822 	}
823 	else if ( nullptr != (ppTarget = GetPropPtr(key)))
824 	{
825 		return ppTarget->set(key, value);
826 	}
827 	else
828 	{
829 		if(!g_bQuiet)
830 			cerr << "Warning, unknown configuration directive: " << key <<endl;
831 		return false;
832 	}
833 	return true;
834 }
835 
GetRepNameAndPathResidual(const tHttpUrl & in,tRepoResolvResult & result)836 void GetRepNameAndPathResidual(const tHttpUrl & in, tRepoResolvResult &result)
837 {
838 	// get all the URLs matching THE HOSTNAME
839 	auto rangeIt=mapUrl2pVname.find(in.sHost+":"+in.GetPort());
840 	if(rangeIt == mapUrl2pVname.end())
841 		return;
842 
843 	tStrPos bestMatchLen(0);
844 	auto pBestHit = repoparms.end();
845 
846 	// now find the longest directory part which is the suffix of requested URL's path
847 	for (auto& repo : rangeIt->second)
848 	{
849 		// rewrite rule path must be a real prefix
850 		// it's also surrounded by /, ensured during construction
851 		const string & prefix=repo.first; // path of the rewrite entry
852 		tStrPos len=prefix.length();
853 		if (len>bestMatchLen && in.sPath.size() > len && 0==in.sPath.compare(0, len, prefix))
854 		{
855 			bestMatchLen=len;
856 			pBestHit=repo.second;
857 		}
858 	}
859 
860 	if(pBestHit != repoparms.end())
861 	{
862 		result.psRepoName = & pBestHit->first;
863 		result.sRestPath = in.sPath.substr(bestMatchLen);
864 		result.repodata = & pBestHit->second;
865 	}
866 }
867 
GetRepoData(cmstring & vname)868 const tRepoData * GetRepoData(cmstring &vname)
869 {
870 	auto it=repoparms.find(vname);
871 	if(it==repoparms.end())
872 		return nullptr;
873 	return & it->second;
874 }
875 
ReadBackendsFile(const string & sFile,const string & sRepName)876 unsigned ReadBackendsFile(const string & sFile, const string &sRepName)
877 {
878 	unsigned nAddCount=0;
879 	string key, val;
880 	tHttpUrl entry;
881 
882 	tCfgIter itor(sFile);
883 	if(debug&6)
884 		cerr << "Reading backend file: " << sFile <<endl;
885 
886 	if(!itor)
887 	{
888 		if(debug&6)
889 			cerr << "No backend data found, file ignored."<<endl;
890 		return 0;
891 	}
892 
893 	while(itor.Next())
894 	{
895 		if(debug & log::LOG_DEBUG)
896 			cerr << "Backend URL: " << itor.sLine <<endl;
897 
898 		trimBack(itor.sLine);
899 
900 		if( entry.SetHttpUrl(itor.sLine)
901 				||	( itor.sLine.empty() && ! entry.sHost.empty() && ! entry.sPath.empty()) )
902 		{
903 			_FixPostPreSlashes(entry.sPath);
904 #ifdef DEBUG
905 			cerr << "Backend: " << sRepName << " <-- " << entry.ToURI(false) <<endl;
906 #endif
907 			repoparms[sRepName].m_backends.emplace_back(entry);
908 			nAddCount++;
909 			entry.clear();
910 		}
911 		else if(ParseKeyValLine(itor.sLine, key, val))
912 		{
913 			if(keyEq("Site", key))
914 				entry.sHost=val;
915 			else if(keyEq("Archive-http", key) || keyEq("X-Archive-http", key))
916 				entry.sPath=val;
917 		}
918 		else
919 		{
920 			BARF("Bad backend description, around line " << sFile << ":"
921 					<< itor.reader.GetCurrentLine());
922 		}
923 	}
924 	return nAddCount;
925 }
926 
ShutDown()927 void ShutDown()
928 {
929 	mapUrl2pVname.clear();
930 	repoparms.clear();
931 }
932 
933 /* This parses also legacy files, i.e. raw RFC-822 formated mirror catalogue from the
934  * Debian archive maintenance repository.
935  */
ReadRewriteFile(const string & sFile,cmstring & sRepName)936 unsigned ReadRewriteFile(const string & sFile, cmstring& sRepName)
937 {
938 	unsigned nAddCount=0;
939 	filereader reader;
940 	if(debug>4)
941 		cerr << "Reading rewrite file: " << sFile <<endl;
942 	reader.OpenFile(sFile, false, 1);
943 	reader.CheckGoodState(true, &sFile);
944 
945 	tStrVec hosts, paths;
946 	string sLine, key, val;
947 	tHttpUrl url;
948 
949 	while (reader.GetOneLine(sLine))
950 	{
951 		trimFront(sLine);
952 
953 		if (0 == sLine.compare(0, 1, "#"))
954 			continue;
955 
956 		if (url.SetHttpUrl(sLine))
957 		{
958 			_FixPostPreSlashes(url.sPath);
959 
960 			mapUrl2pVname[url.sHost + ":" + url.GetPort()].emplace_back(url.sPath,
961 					GetRepoEntryRef(sRepName));
962 #ifdef DEBUG
963 			cerr << "Mapping: " << url.ToURI(false) << " -> " << sRepName << endl;
964 #endif
965 
966 			++nAddCount;
967 			continue;
968 		}
969 
970 		// otherwise deal with the complicated RFC-822 format for legacy reasons
971 
972 		if (sLine.empty()) // end of block, eof, ... -> commit it
973 		{
974 			if (hosts.empty() && paths.empty())
975 				continue; // dummy run or whitespace in a URL style list
976 			if ( !hosts.empty() && paths.empty())
977 			{
978 				cerr << "Warning, missing path spec for the site " << hosts[0] <<", ignoring mirror."<< endl;
979 				continue;
980 			}
981 			if ( !paths.empty() && hosts.empty())
982 			{
983 				BARF("Parse error, missing Site: field around line "
984 						<< sFile << ":"<< reader.GetCurrentLine());
985 			}
986 			for (const auto& host : hosts)
987 			{
988 				for (const auto& path : paths)
989 				{
990 					//mapUrl2pVname[*itHost+*itPath]= &itHostiVec->first;
991 					tHttpUrl url;
992 					url.sHost=host;
993 					url.sPath=path;
994 					mapUrl2pVname[url.sHost+":"+url.GetPort()].emplace_back(url.sPath,
995 							GetRepoEntryRef(sRepName));
996 
997 #ifdef DEBUG
998 						cerr << "Mapping: "<< host << path
999 						<< " -> "<< sRepName <<endl;
1000 #endif
1001 
1002 						++nAddCount;
1003 				}
1004 			}
1005 			hosts.clear();
1006 			paths.clear();
1007 			continue;
1008 		}
1009 
1010 		if(!ParseKeyValLine(sLine, key, val))
1011 		{
1012 			BARF("Error parsing rewrite definitions, around line "
1013 					<< sFile << ":"<< reader.GetCurrentLine() << " : " << sLine);
1014 		}
1015 
1016 		// got something, interpret it...
1017 		if( keyEq("Site", key) || keyEq("Alias", key) || keyEq("Aliases", key))
1018 			Tokenize(val, SPACECHARS, hosts, true);
1019 
1020 		if(keyEq("Archive-http", key) || keyEq("X-Archive-http", key))
1021 		{
1022 			// help STL saving some memory
1023 			if(sPopularPath==val)
1024 				paths.emplace_back(sPopularPath);
1025 			else
1026 			{
1027 				_FixPostPreSlashes(val);
1028 				paths.emplace_back(val);
1029 			}
1030 			continue;
1031 		}
1032 	}
1033 
1034 	return nAddCount;
1035 }
1036 
1037 
~tRepoData()1038 tRepoData::~tRepoData()
1039 {
1040 	delete m_pHooks;
1041 }
1042 
ReadConfigDirectory(const char * szPath,bool bReadErrorIsFatal)1043 void ReadConfigDirectory(const char *szPath, bool bReadErrorIsFatal)
1044 {
1045 	dump_proc_status();
1046 	char buf[PATH_MAX];
1047 	if(!realpath(szPath, buf))
1048 		BARF("Failed to open config directory");
1049 
1050 	confdir=buf; // pickup the last config directory
1051 
1052 #if defined(HAVE_WORDEXP) || defined(HAVE_GLOB)
1053 	for(const auto& src: ExpandFilePattern(confdir+SZPATHSEP "*.conf", true))
1054 		ReadOneConfFile(src, bReadErrorIsFatal);
1055 #else
1056 	ReadOneConfFile(confdir+SZPATHSEP"acng.conf", bReadErrorIsFatal);
1057 #endif
1058 	dump_proc_status();
1059 	if(debug & log::LOG_DEBUG)
1060 	{
1061 		unsigned nUrls=0;
1062 		for(const auto& x: mapUrl2pVname)
1063 			nUrls+=x.second.size();
1064 
1065 		if(debug&6)
1066 			cerr << "Loaded " << repoparms.size() << " backend descriptors\nLoaded mappings for "
1067 				<< mapUrl2pVname.size() << " hosts and " << nUrls<<" paths\n";
1068 	}
1069 }
1070 
PostProcConfig()1071 void PostProcConfig()
1072 {
1073 	mapUrl2pVname.rehash(mapUrl2pVname.size());
1074 
1075 	if(port.empty()) // heh?
1076 		port=ACNG_DEF_PORT;
1077 
1078 	if(connectPermPattern == "~~~")
1079 	   connectPermPattern="^(bugs\\.debian\\.org|changelogs\\.ubuntu\\.com):443$";
1080 
1081 	// let's also apply the umask to the directory permissions
1082 	{
1083 		mode_t mask = umask(0);
1084 		umask(mask); // restore it...
1085 		dirperms &= ~mask;
1086 		fileperms &= ~mask;
1087 	}
1088 
1089     // postprocessing
1090 
1091 #ifdef FORCE_CUSTOM_UMASK
1092 	if(!sUmask.empty())
1093 	{
1094 		mode_t nUmask=0;
1095 		if(sUmask.size()>4)
1096 			BARF("Invalid umask length\n");
1097 		for(unsigned int i=0; i<sUmask.size(); i++)
1098 		{
1099 			unsigned int val = sUmask[sUmask.size()-i-1]-'0';
1100 			if(val>7)
1101 				BARF("Invalid umask value\n");
1102 			nUmask |= (val<<(3*i));
1103 
1104 		}
1105 		//cerr << "Got umask: " << nUmask <<endl;
1106 		umask(nUmask);
1107 	}
1108 #endif
1109 
1110    if(cachedir.empty() || cachedir[0] != CPATHSEP)
1111    {
1112 	   cerr << "Warning: Cache directory unknown or not absolute, running in degraded mode!" << endl;
1113 	   degraded=true;
1114    }
1115    if(!rex::CompileExpressions())
1116 	   BARF("An error occurred while compiling file type regular expression!");
1117 
1118    if(cfg::tpthreadmax < 0)
1119 	   cfg::tpthreadmax = MAX_VAL(int);
1120 
1121    // get rid of duplicated and trailing slash(es)
1122 	for(tStrPos pos; stmiss != (pos = cachedir.find(SZPATHSEP SZPATHSEP )); )
1123 		cachedir.erase(pos, 1);
1124 
1125 	cacheDirSlash=cachedir+CPATHSEP;
1126 
1127    if(!pidfile.empty() && pidfile.at(0) != CPATHSEP)
1128 	   BARF("Pid file path must be absolute, terminating...");
1129 
1130    if(!cfg::agentname.empty())
1131 	   cfg::agentheader=string("User-Agent: ")+cfg::agentname + "\r\n";
1132 
1133    stripPrefixChars(cfg::reportpage, '/');
1134 
1135    trimString(cfg::requestapx);
1136    if(!cfg::requestapx.empty())
1137 	   cfg::requestapx = unEscape(cfg::requestapx);
1138 
1139    // create working paths before something else fails somewhere
1140    if(!fifopath.empty())
1141 	   mkbasedir(cfg::fifopath);
1142    if(!cachedir.empty())
1143 	   mkbasedir(cfg::cachedir);
1144    if(! pidfile.empty())
1145 	   mkbasedir(cfg::pidfile);
1146 
1147    if(nettimeout < 5) {
1148 	   cerr << "Warning: NetworkTimeout value too small, using: 5." << endl;
1149 	   nettimeout = 5;
1150    }
1151 
1152    if(RESERVED_DEFVAL == forwardsoap)
1153 	   forwardsoap = !forcemanaged;
1154 
1155    if(RESERVED_DEFVAL == exsupcount)
1156 	   exsupcount = (extreshhold >= 5);
1157 
1158 #ifdef _SC_NPROCESSORS_ONLN
1159 	numcores = (int) sysconf(_SC_NPROCESSORS_ONLN);
1160 #elif defined(_SC_NPROC_ONLN)
1161 	numcores = (int) sysconf(_SC_NPROC_ONLN);
1162 #endif
1163 
1164    if(!rex::CompileUncExpressions(rex::NOCACHE_REQ,
1165 		   tmpDontcacheReq.empty() ? tmpDontcache : tmpDontcacheReq)
1166    || !rex::CompileUncExpressions(rex::NOCACHE_TGT,
1167 		   tmpDontcacheTgt.empty() ? tmpDontcache : tmpDontcacheTgt))
1168    {
1169 	   BARF("An error occurred while compiling regular expression for non-cached paths!");
1170    }
1171    tmpDontcache.clear();
1172    tmpDontcacheTgt.clear();
1173    tmpDontcacheReq.clear();
1174 
1175    if(usewrap == RESERVED_DEFVAL)
1176    {
1177 	   usewrap=(qgrep("apt-cacher-ng", "/etc/hosts.deny")
1178 			   || qgrep("apt-cacher-ng", "/etc/hosts.allow"));
1179 #ifndef HAVE_LIBWRAP
1180 	   cerr << "Warning: configured to use libwrap filters but feature is not built-in." <<endl;
1181 #endif
1182    }
1183 
1184    if(maxtempdelay<0)
1185    {
1186 	   cerr << "Invalid maxtempdelay value, using default" <<endl;
1187 	   maxtempdelay=27;
1188    }
1189 
1190    if(redirmax == RESERVED_DEFVAL)
1191 	   redirmax = forcemanaged ? 0 : REDIRMAX_DEFAULT;
1192 
1193    if(!persistoutgoing)
1194 	   pipelinelen = 1;
1195 
1196    if(!pipelinelen) {
1197 	   cerr << "Warning, remote pipeline depth of 0 makes no sense, assuming 1." << endl;
1198 	   pipelinelen = 1;
1199    }
1200 
1201 } // PostProcConfig
1202 
dump_config(bool includeDelicate)1203 void dump_config(bool includeDelicate)
1204 {
1205 	ostream &cmine(cout);
1206 
1207 	for (auto& n2s : n2sTbl)
1208 		if (n2s.ptr)
1209 			cmine << n2s.name << " = " << *n2s.ptr << endl;
1210 
1211 	if (cfg::debug >= log::LOG_DEBUG)
1212 	{
1213 		cerr << "escaped version:" << endl;
1214 		for (const auto& n2s : n2sTbl)
1215 		{
1216 			if (n2s.ptr)
1217 			{
1218 				cerr << n2s.name << " = ";
1219 				for (const char *p = n2s.ptr->c_str(); *p; p++)
1220 				{
1221 					if ('\\' == *p)
1222 						cmine << "\\\\";
1223 					else
1224 						cmine << *p;
1225 				}
1226 				cmine << endl;
1227 			}
1228 		}
1229 	}
1230 
1231 	for (const auto& n2i : n2iTbl)
1232 		if (n2i.ptr && !n2i.hidden)
1233 			cmine << n2i.name << " = " << *n2i.ptr << endl;
1234 
1235 	for (const auto& x : n2pTbl)
1236 	{
1237 		auto val(x.get(includeDelicate));
1238 		if(startsWithSz(val, "#")) continue;
1239 		cmine << x.name << " = " << val << endl;
1240 	}
1241 
1242 #ifndef DEBUG
1243 	if (cfg::debug >= log::LOG_DEBUG)
1244 		cerr << "\n\nAdditional debugging information not compiled in.\n\n";
1245 #endif
1246 
1247 #if 0 //def DEBUG
1248 #warning adding hook control pins
1249 	for(tMapString2Hostivec::iterator it = repoparms.begin();
1250 			it!=repoparms.end(); ++it)
1251 	{
1252 		tHookHandler *p = new tHookHandler(it->first);
1253 		p->downDuration=10;
1254 		p->cmdCon = "logger wanna/connect";
1255 		p->cmdRel = "logger wanna/disconnect";
1256 		it->second.m_pHooks = p;
1257 	}
1258 
1259 	if(debug == -42)
1260 	{
1261 		/*
1262 		 for(tMapString2Hostivec::const_iterator it=mapRepName2Backends.begin();
1263 		 it!=mapRepName2Backends.end(); it++)
1264 		 {
1265 		 for(tRepoData::const_iterator jit=it->second.begin();
1266 		 jit != it->second.end(); jit++)
1267 		 {
1268 		 cout << jit->ToURI() <<endl;
1269 		 }
1270 		 }
1271 
1272 		 for(tUrl2RepIter it=mapUrl2pVname.begin(); it!=mapUrl2pVname.end(); it++)
1273 		 {
1274 		 cout << it->first.ToURI(false) << " ___" << *(it->second) << endl;
1275 		 }
1276 
1277 		 exit(1);
1278 		 */
1279 	}
1280 #endif
1281 }
1282 
1283 //! @brief Fires hook callbacks in the background thread
BackgroundCleanup()1284 time_t BackgroundCleanup()
1285 {
1286 	time_t ret(END_OF_TIME), now(time(0));
1287 	for (const auto& parm : repoparms)
1288 	{
1289 		if (!parm.second.m_pHooks)
1290 			continue;
1291 		tHookHandler & hooks = *(static_cast<tHookHandler*> (parm.second.m_pHooks));
1292 		lockguard g(hooks);
1293 		if (hooks.downTimeNext)
1294 		{
1295 			if (hooks.downTimeNext <= now) // time to execute
1296 			{
1297 				if(cfg::debug & log::LOG_MORE)
1298 					log::misc(hooks.cmdRel, 'X');
1299 				if(cfg::debug & log::LOG_FLUSH)
1300 					log::flush();
1301 
1302 				if(system(hooks.cmdRel.c_str()))
1303 					log::err(tSS() << "Warning: " << hooks.cmdRel << " returned with error code.");
1304 				hooks.downTimeNext = 0;
1305 			}
1306 			else // in future, use the soonest time
1307 				ret = min(ret, hooks.downTimeNext);
1308 		}
1309 	}
1310 	return ret;
1311 }
1312 
1313 acmutex authLock;
1314 
CheckAdminAuth(LPCSTR auth)1315 int CheckAdminAuth(LPCSTR auth)
1316 {
1317 	if(cfg::adminauthB64.empty())
1318 		return 0;
1319 	if(!auth || !*auth)
1320 		return 1; // request it from user
1321 	if(strncmp(auth, "Basic", 5))
1322 		return -1; // looks like crap
1323 	auto p=auth+5;
1324 	while(*p && isspace((uint) *p)) ++p;
1325 
1326 #ifndef SUPPWHASH
1327 	return adminauthB64.compare(p) == 0 ? 0 : 1;
1328 
1329 #else
1330 
1331 	if(!bIsHashedPwd)
1332 		return adminauth.compare(p) == 0 ? 0 : 1;
1333 
1334 #ifndef HAVE_SSL
1335 #warning You really want to add SSL support in order to support hashed passwords
1336 	return -1;
1337 #endif
1338 	acbuf bufDecoded;
1339 	if(!DecodeBase64(p, bufDecoded))
1340 		return -1;
1341 #if 0
1342 	// there is always a char reserved
1343 	cerr << "huhu, user sent: " << bufDecoded.c_str() <<endl;
1344 #endif
1345 
1346 	string usersauth(bufDecoded.rptr(), bufDecoded.size());
1347 	auto poscol=usersauth.find(':');
1348 	if(poscol==0 || poscol==stmiss || poscol+1==usersauth.size())
1349 		return 1;
1350 
1351 	// ok, try to match against our hash, first copy user and salt from config
1352 	// always calculate the thing and compare the user and maybe hash later
1353 	// attacker should not gain any knowledge from faster abort (side channel...)
1354 	lockguard g(&authLock);
1355 	string testHash=adminauth.substr(0, poscol+9);
1356 	if(!AppendPasswordHash(testHash, usersauth.data()+poscol+1, usersauth.size()-poscol+1))
1357 		return 1;
1358 	if(testHash == adminauth)
1359 	{
1360 		// great! Cache it!
1361 		adminauth = p;
1362 		bIsHashedPwd = false;
1363 		return 0;
1364 	}
1365 	return 1;
1366 #endif
1367 }
1368 
1369 #endif // MINIBUILD
1370 
1371 static bool proxy_failstate = false;
1372 acmutex proxy_fail_lock;
GetProxyInfo()1373 const tHttpUrl* GetProxyInfo()
1374 {
1375 	if(proxy_info.sHost.empty())
1376 		return nullptr;
1377 
1378 	static time_t last_check=0;
1379 
1380 	lockguard g(proxy_fail_lock);
1381 	time_t now = time(nullptr);
1382 	time_t sinceCheck = now - last_check;
1383 	if(sinceCheck > optProxyCheckInt)
1384 	{
1385 		last_check = now;
1386 		if(optProxyCheckCmd.empty())
1387 			proxy_failstate = false;
1388 		else
1389 			proxy_failstate = (bool) system(optProxyCheckCmd.c_str());
1390 	}
1391 
1392 	return proxy_failstate ? nullptr : &proxy_info;
1393 }
1394 
MarkProxyFailure()1395 void MarkProxyFailure()
1396 {
1397 	lockguard g(proxy_fail_lock);
1398 	if(optProxyCheckInt <= 0) // urgs, would never recover
1399 		return;
1400 	proxy_failstate = true;
1401 }
1402 
1403 } // namespace acfg
1404 
1405 namespace rex
1406 {
1407 	// this has the exact order of the "regular" types in the enum
1408 	struct { regex_t *pat=nullptr, *extra=nullptr; } rex[ematchtype_max];
1409 	vector<regex_t> vecReqPatters, vecTgtPatterns;
1410 
CompileExpressions()1411 bool CompileExpressions()
1412 {
1413 	auto compat = [](regex_t* &re, LPCSTR ps)
1414 	{
1415 		if(!ps ||! *ps )
1416 		return true;
1417 		re=new regex_t;
1418 		int nErr=regcomp(re, ps, REG_EXTENDED);
1419 		if(!nErr)
1420 		return true;
1421 
1422 		char buf[1024];
1423 		regerror(nErr, re, buf, sizeof(buf));
1424 		delete re;
1425 		re=nullptr;
1426 		buf[_countof(buf)-1]=0; // better be safe...
1427 			std::cerr << buf << ": " << ps << std::endl;
1428 			return false;
1429 		};
1430 	using namespace cfg;
1431 	return (compat(rex[FILE_SOLID].pat, pfilepat.c_str())
1432 			&& compat(rex[FILE_VOLATILE].pat, vfilepat.c_str())
1433 			&& compat(rex[FILE_WHITELIST].pat, wfilepat.c_str())
1434 			&& compat(rex[FILE_SOLID].extra, pfilepatEx.c_str())
1435 			&& compat(rex[FILE_VOLATILE].extra, vfilepatEx.c_str())
1436 			&& compat(rex[FILE_WHITELIST].extra, wfilepatEx.c_str())
1437 			&& compat(rex[NASTY_PATH].pat, BADSTUFF_PATTERN)
1438 			&& compat(rex[FILE_SPECIAL_SOLID].pat, spfilepat.c_str())
1439 			&& compat(rex[FILE_SPECIAL_SOLID].extra, spfilepatEx.c_str())
1440 			&& compat(rex[FILE_SPECIAL_VOLATILE].pat, svfilepat.c_str())
1441 			&& compat(rex[FILE_SPECIAL_VOLATILE].extra, svfilepatEx.c_str())
1442 			&& (connectPermPattern == "~~~" ?
1443 			true : compat(rex[PASSTHROUGH].pat, connectPermPattern.c_str())));
1444 }
1445 
1446 // match the specified type by internal pattern PLUS the user-added pattern
MatchType(cmstring & in,eMatchType type)1447 inline bool MatchType(cmstring &in, eMatchType type)
1448 {
1449 	if(rex[type].pat && !regexec(rex[type].pat, in.c_str(), 0, nullptr, 0))
1450 		return true;
1451 	if(rex[type].extra && !regexec(rex[type].extra, in.c_str(), 0, nullptr, 0))
1452 		return true;
1453 	return false;
1454 }
1455 
Match(cmstring & in,eMatchType type)1456 bool Match(cmstring &in, eMatchType type)
1457 {
1458 	if(MatchType(in, type))
1459 		return true;
1460 	// very special behavior... for convenience
1461 	return (type == FILE_SOLID && MatchType(in, FILE_SPECIAL_SOLID))
1462 		|| (type == FILE_VOLATILE && MatchType(in, FILE_SPECIAL_VOLATILE));
1463 }
1464 
GetFiletype(const string & in)1465 eMatchType GetFiletype(const string & in)
1466 {
1467 	if (MatchType(in, FILE_SPECIAL_VOLATILE))
1468 		return FILE_VOLATILE;
1469 	if (MatchType(in, FILE_SPECIAL_SOLID))
1470 		return FILE_SOLID;
1471 	if (MatchType(in, FILE_VOLATILE))
1472 		return FILE_VOLATILE;
1473 	if (MatchType(in, FILE_SOLID))
1474 		return FILE_SOLID;
1475 	return FILE_INVALID;
1476 }
1477 
1478 #ifndef MINIBUILD
1479 
CompileUncachedRex(const string & token,NOCACHE_PATTYPE type,bool bRecursiveCall)1480 inline bool CompileUncachedRex(const string & token, NOCACHE_PATTYPE type, bool bRecursiveCall)
1481 {
1482 	auto & patvec = (NOCACHE_TGT == type) ? vecTgtPatterns : vecReqPatters;
1483 
1484 	if (0!=token.compare(0, 5, "file:")) // pure pattern
1485 	{
1486 		unsigned pos = patvec.size();
1487 		patvec.resize(pos+1);
1488 		return 0==regcomp(&patvec[pos], token.c_str(), REG_EXTENDED);
1489 	}
1490 	else if(!bRecursiveCall) // don't go further than one level
1491 	{
1492 		tStrDeq srcs = cfg::ExpandFileTokens(token);
1493 		for(const auto& src: srcs)
1494 		{
1495 			cfg::tCfgIter itor(src);
1496 			if(!itor)
1497 			{
1498 				cerr << "Error opening pattern file: " << src <<endl;
1499 				return false;
1500 			}
1501 			while(itor.Next())
1502 			{
1503 				if(!CompileUncachedRex(itor.sLine, type, true))
1504 					return false;
1505 			}
1506 		}
1507 
1508 		return true;
1509 	}
1510 
1511 	cerr << token << " is not supported here" <<endl;
1512 	return false;
1513 }
1514 
1515 
CompileUncExpressions(NOCACHE_PATTYPE type,cmstring & pat)1516 bool CompileUncExpressions(NOCACHE_PATTYPE type, cmstring& pat)
1517 {
1518 	for(tSplitWalk split(&pat); split.Next(); )
1519 		if (!CompileUncachedRex(split, type, false))
1520 			return false;
1521 	return true;
1522 }
1523 
MatchUncacheable(const string & in,NOCACHE_PATTYPE type)1524 bool MatchUncacheable(const string & in, NOCACHE_PATTYPE type)
1525 {
1526 	for(const auto& patre: (type == NOCACHE_REQ) ? vecReqPatters : vecTgtPatterns)
1527 		if(!regexec(&patre, in.c_str(), 0, nullptr, 0))
1528 			return true;
1529 	return false;
1530 }
1531 
1532 #endif //MINIBUILD
1533 
1534 
1535 } // namespace rechecks
1536 
1537 
1538 #ifndef MINIBUILD
ReTest(LPCSTR s)1539 LPCSTR ReTest(LPCSTR s)
1540 {
1541 	static LPCSTR names[rex::ematchtype_max] =
1542 	{
1543 				"FILE_SOLID", "FILE_VOLATILE",
1544 				"FILE_WHITELIST",
1545 				"NASTY_PATH", "PASSTHROUGH",
1546 				"FILE_SPECIAL_SOLID"
1547 	};
1548 	auto t = rex::GetFiletype(s);
1549 	if(t<0 || t>=rex::ematchtype_max)
1550 		return "NOMATCH";
1551 	return names[t];
1552 }
1553 #endif
1554 
1555 }
1556