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