1 #include "config.h"
2 #include "meta.h"
3 #include "acfg.h"
4 
5 #include <acbuf.h>
6 #include <aclogger.h>
7 #include <dirwalk.h>
8 #include <fcntl.h>
9 
10 #ifdef HAVE_SSL
11 #include <openssl/evp.h>
12 #endif
13 
14 #include <stddef.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <unistd.h>
18 #include <sys/time.h>
19 #include <signal.h>
20 #include <regex.h>
21 #include <errno.h>
22 
23 #include <cstdbool>
24 #include <cstdint>
25 #include <cstdlib>
26 #include <ctime>
27 #include <cstdio>
28 #include <cstring>
29 #include <functional>
30 
31 #include <iostream>
32 #include <fstream>
33 #include <string>
34 #include <list>
35 #include <queue>
36 
37 #include "debug.h"
38 #include "dlcon.h"
39 #include "fileio.h"
40 #include "fileitem.h"
41 
42 #ifdef HAVE_SSL
43 #include "openssl/bio.h"
44 #include "openssl/ssl.h"
45 #include "openssl/err.h"
46 #include <openssl/rand.h>
47 #include <openssl/sha.h>
48 #include <openssl/crypto.h>
49 #endif
50 
51 #include "filereader.h"
52 #include "csmapping.h"
53 #include "cleaner.h"
54 
55 using namespace std;
56 using namespace acng;
57 
58 bool g_bVerbose = false;
59 
60 // dummies to satisfy references
61 namespace acng
62 {
63 LPCSTR ReTest(LPCSTR);
cleaner()64 cleaner::cleaner() : m_thr(pthread_t()) {}
~cleaner()65 cleaner::~cleaner() {}
ScheduleFor(time_t,cleaner::eType)66 void cleaner::ScheduleFor(time_t, cleaner::eType) {}
67 cleaner g_victor;
68 }
69 
70 struct IFitemFactory
71 {
72 	virtual SHARED_PTR<fileitem> Create() =0;
73 	virtual ~IFitemFactory() =default;
74 };
75 
76 struct CPrintItemFactory : public IFitemFactory
77 {
CreateCPrintItemFactory78 	virtual SHARED_PTR<fileitem> Create()
79 	{
80 		class tPrintItem : public fileitem
81 		{
82 		public:
83 			tPrintItem()
84 			{
85 				m_bAllowStoreData=false;
86 				m_nSizeChecked = m_nSizeSeen = 0;
87 			};
88 			virtual FiStatus Setup(bool) override
89 			{
90 				m_nSizeChecked = m_nSizeSeen = 0;
91 				return m_status = FIST_INITED;
92 			}
93 			virtual int GetFileFd() override
94 			{	return 1;}; // something, don't care for now
95 			virtual bool DownloadStartedStoreHeader(const header &h, size_t, const char *,
96 					bool, bool&) override
97 			{
98 				m_head = h;
99 				auto opt_dbg=getenv("ACNGTOOL_DEBUG_DOWNLOAD");
100 				if(opt_dbg && *opt_dbg)
101 					std::cerr << (std::string) h.ToString() << std::endl;
102 				return true;
103 			}
104 			virtual bool StoreFileData(const char *data, unsigned int size) override
105 			{
106 				if(!size)
107 				m_status = FIST_COMPLETE;
108 
109 				return (size==fwrite(data, sizeof(char), size, stdout));
110 			}
111 			ssize_t SendData(int , int, off_t &, size_t ) override
112 			{
113 				return 0;
114 			}
115 		};
116 		return make_shared<tPrintItem>();
117 	}
118 };
119 
120 struct verbprint
121 {
122 	int cnt = 0;
dotverbprint123 	void dot()
124 	{
125 		if (!g_bVerbose)
126 			return;
127 		cnt++;
128 		cerr << '.';
129 	}
msgverbprint130 	void msg(cmstring& msg)
131 	{
132 		if (!g_bVerbose)
133 			return;
134 		fin();
135 		cerr << msg << endl;
136 
137 	}
finverbprint138 	void fin()
139 	{
140 		if (!g_bVerbose)
141 			return;
142 		if (cnt)
143 			cerr << endl;
144 		cnt = 0;
145 	}
146 } vprint;
147 
148 struct CReportItemFactory : public IFitemFactory
149 {
CreateCReportItemFactory150 	virtual SHARED_PTR<fileitem> Create()
151 	{
152 		class tRepItem : public fileitem
153 		{
154 			acbuf lineBuf;
155 			string m_key = maark;
156 			tStrVec m_errMsg;
157 
158 		public:
159 
160 			tRepItem()
161 			{
162 				m_bAllowStoreData=false;
163 				m_nSizeChecked = m_nSizeSeen = 0;
164 				lineBuf.setsize(1<<16);
165 				memset(lineBuf.wptr(), 0, 1<<16);
166 			};
167 			virtual FiStatus Setup(bool) override
168 			{
169 				m_nSizeChecked = m_nSizeSeen = 0;
170 				return m_status = FIST_INITED;
171 			}
172 			virtual int GetFileFd() override
173 			{	return 1;}; // something, don't care for now
174 			virtual bool DownloadStartedStoreHeader(const header &h, size_t, const char *,
175 					bool, bool&) override
176 			{
177 				m_head = h;
178 				return true;
179 			}
180 			virtual bool StoreFileData(const char *data, unsigned int size) override
181 			{
182 				if(!size)
183 				{
184 					m_status = FIST_COMPLETE;
185 					vprint.fin();
186 				}
187 				auto consumed = std::min(size, lineBuf.freecapa());
188 				memcpy(lineBuf.wptr(), data, consumed);
189 				lineBuf.got(consumed);
190 				for(;;)
191 				{
192 					LPCSTR p = lineBuf.rptr();
193 					auto end = mempbrk(p, "\r\n", lineBuf.size());
194 					if(!end)
195 						break;
196 					string s(p, end-p);
197 					lineBuf.drop(s.length()+1);
198 					vprint.dot();
199 					if(startsWith(s, m_key))
200 					{
201 						// that's for us... "<key><type> content\n"
202 						char *endchar = nullptr;
203 						p = s.c_str();
204 						auto val = strtoul(p + m_key.length(), &endchar, 10);
205 						if(!endchar || !*endchar)
206 							continue; // heh? shall not finish here
207 						switch(ControLineType(val))
208 						{
209 						case ControLineType::BeforeError:
210 							m_errMsg.emplace_back(endchar, s.size() - (endchar - p));
211 							vprint.msg(m_errMsg.back());
212 							break;
213 						case ControLineType::Error:
214 						{
215 							if(!g_bVerbose) // printed before
216 								for(auto l : m_errMsg)
217 									cerr << l << endl;
218 							m_errMsg.clear();
219 							string msg(endchar, s.size() - (endchar - p));
220 							vprint.fin();
221 							cerr << msg << endl;
222 							break;
223 						}
224 						default:
225 							continue;
226 						}
227 					}
228 				}
229 				return true;
230 			}
231 			ssize_t SendData(int , int, off_t &, size_t ) override
232 			{
233 				return 0;
234 			}
235 		};
236 		return make_shared<tRepItem>();
237 	}
238 };
239 
240 int wcat(LPCSTR url, LPCSTR proxy, IFitemFactory*, IDlConFactory *pdlconfa = &g_tcp_con_factory);
241 
usage(int retCode=0,LPCSTR cmd=nullptr)242 static void usage(int retCode = 0, LPCSTR cmd = nullptr)
243 {
244 	if(cmd)
245 	{
246 		if(0 == strcmp(cmd, "shrink"))
247 			cerr << "USAGE: acngtool shrink numberX [-f | -n] [-x] [-v] [variable assignments...]" <<endl <<
248 			"-f: delete files"<< endl <<
249 			"-n: dry run, display results" << endl <<
250 			"-v: more verbosity" << endl <<
251 			"-x: also drop index files (can be dangerous)" <<endl <<
252 			"Suffix X can be k,K,m,M,g,G (for kb,KiB,mb,MiB,gb,GiB)" << endl;
253 	}
254 	else
255 		(retCode ? cout : cerr) <<
256 		"Usage: acngtool command parameter... [options]\n\n"
257 			"command := { printvar, cfgdump, retest, patch, curl, encb64, maint, shrink }\n"
258 			"parameter := (specific to command)\n"
259 			"options := (see apt-cacher-ng options)\n"
260 			"extra options := -h, --verbose\n"
261 #if SUPPWHASH
262 #warning FIXME
263 			"-H: read a password from STDIN and print its hash\n"
264 #endif
265 "\n";
266 			exit(retCode);
267 }
268 
269 struct pkgEntry
270 {
271 	std::string path;
272 	time_t lastDate;
273 	blkcnt_t blocks;
274 	// for prio.queue, oldest shall be on top
operator <pkgEntry275 	bool operator<(const pkgEntry &other) const
276 	{
277 		return lastDate > other.lastDate;
278 	}
279 };
280 
shrink(off_t wantedSize,bool dryrun,bool apply,bool verbose,bool incIfiles)281 int shrink(off_t wantedSize, bool dryrun, bool apply, bool verbose, bool incIfiles)
282 {
283 	if(!dryrun && !apply)
284 	{
285 		cerr << "Error: needs -f or -n options" << endl;
286 		return 97;
287 	}
288 	if(dryrun && apply)
289 	{
290 		cerr << "Error: -f and -n are mutually exclusive" <<endl;
291 		return 107;
292 	}
293 //	cout << "wanted: " << wantedSize << endl;
294 	std::priority_queue<pkgEntry/*, vector<pkgEntry>, cmpLessDate */ > delQ;
295 	std::unordered_map<string, pair<time_t,off_t> > related;
296 
297 	blkcnt_t totalBlocks = 0;
298 
299 	IFileHandler::FindFiles(cfg::cachedir,
300 			[&delQ, &totalBlocks, &related, &incIfiles](cmstring & path, const struct stat& finfo) -> bool
301 			{
302 		// reference date used in the prioqueue heap
303 		auto dateLatest = max(finfo.st_ctim.tv_sec, finfo.st_mtim.tv_sec);
304 		auto isHead = endsWithSzAr(path, ".head");
305 		string pkgPath, otherName;
306 		if(isHead)
307 		{
308 			pkgPath = path.substr(0, path.length()-5);
309 			otherName = pkgPath;
310 		}
311 		else
312 		{
313 			pkgPath = path;
314 			otherName = path + ".head";
315 		}
316 		auto ftype = rex::GetFiletype(pkgPath);
317 		if((ftype==rex::FILE_SPECIAL_VOLATILE || ftype == rex::FILE_VOLATILE) && !incIfiles)
318 			return true;
319 		// anything else is considered junk
320 
321 		auto other = related.find(otherName);
322 		if(other == related.end())
323 		{
324 			// the related file will appear soon
325 			related.insert(make_pair(path, make_pair(dateLatest, finfo.st_blocks)));
326 			return true;
327 		}
328 		// care only about stamps on .head files (track mode)
329 		// or ONLY about data file's timestamp (not-track mode)
330 		if( (cfg::trackfileuse && !isHead) || (!cfg::trackfileuse && isHead))
331 			dateLatest = other->second.first;
332 
333 		auto bothBlocks = (finfo.st_blocks + other->second.second);
334 		related.erase(other);
335 
336 		totalBlocks += bothBlocks;
337 		delQ.push({pkgPath, dateLatest, bothBlocks});
338 
339 		return true;
340 			}
341 	, true, false);
342 
343 	// there might be some unmatched remains...
344 	for(auto kv: related)
345 		delQ.push({kv.first, kv.second.first, kv.second.second});
346 	related.clear();
347 
348 	auto foundSizeString = offttosHdotted(totalBlocks*512);
349 	blkcnt_t wantedBlocks = wantedSize / 512;
350 
351 	if(totalBlocks < wantedBlocks)
352 	{
353 		if(verbose)
354 			cout << "Requested size smaller than current size, nothing to do." << endl;
355 		return 0;
356 	}
357 
358 	if(verbose)
359 	{
360 		cout << "Found " << foundSizeString << " bytes of relevant data, reducing to "
361 		<< offttosHdotted(wantedSize) << " (~"<< (wantedBlocks*100/totalBlocks) << "%)"
362 		<< endl;
363 	}
364 	while(!delQ.empty())
365 	{
366 		bool todel = (totalBlocks > wantedBlocks);
367 		if(todel)
368 			totalBlocks -= delQ.top().blocks;
369 		const char *msg = 0;
370 		if(verbose || dryrun)
371 			msg = (todel ? "Delete: " : "Keep: " );
372 		auto& delpath(delQ.top().path);
373 		if(msg)
374 			cout << msg << delpath << endl << msg << delpath << ".head" << endl;
375 		if(todel && apply)
376 		{
377 			unlink(delpath.c_str());
378 			unlink(mstring(delpath + ".head").c_str());
379 		}
380 		delQ.pop();
381 	}
382 	if(verbose)
383 	{
384 		cout << "New size: " << offttosHdotted(totalBlocks*512) << " (before: "
385 		<< foundSizeString << ")" << endl;
386 	}
387 	return 0;
388 }
389 
390 #if SUPPWHASH
391 
hashpwd()392 int hashpwd()
393 {
394 #ifdef HAVE_SSL
395 	string plain;
396 	uint32_t salt=0;
397 	for(unsigned i=10; i; --i)
398 	{
399 		if(RAND_bytes(reinterpret_cast<unsigned char*>(&salt), 4) >0)
400 			break;
401 		else
402 			salt=0;
403 		sleep(1);
404 	}
405 	if(!salt) // ok, whatever...
406 	{
407 		uintptr_t pval = reinterpret_cast<uintptr_t>(&plain);
408 		srandom(uint(time(0)) + uint(pval) +uint(getpid()));
409 		salt=random();
410 		timespec ts;
411 		clock_gettime(CLOCK_BOOTTIME, &ts);
412 		for(auto c=(ts.tv_nsec+ts.tv_sec)%1024 ; c; c--)
413 			salt=random();
414 	}
415 	string crypass = BytesToHexString(reinterpret_cast<const uint8_t*>(&salt), 4);
416 #ifdef DEBUG
417 	plain="moopa";
418 #else
419 	cin >> plain;
420 #endif
421 	trimString(plain);
422 	if(!AppendPasswordHash(crypass, plain.data(), plain.size()))
423 		return EXIT_FAILURE;
424 	cout << crypass <<endl;
425 	return EXIT_SUCCESS;
426 #else
427 	cerr << "OpenSSL not available, hashing functionality disabled." <<endl;
428 	return EXIT_FAILURE;
429 #endif
430 }
431 
432 
AppendPasswordHash(string & stringWithSalt,LPCSTR plainPass,size_t passLen)433 bool AppendPasswordHash(string &stringWithSalt, LPCSTR plainPass, size_t passLen)
434 {
435 	if(stringWithSalt.length()<8)
436 		return false;
437 
438 	uint8_t sum[20];
439 	if(1!=PKCS5_PBKDF2_HMAC_SHA1(plainPass, passLen,
440 			(unsigned char*) (stringWithSalt.data()+stringWithSalt.size()-8), 8,
441 			NUM_PBKDF2_ITERATIONS,
442 			sizeof(sum), (unsigned char*) sum))
443 		return false;
444 	stringWithSalt+=EncodeBase64((LPCSTR)sum, 20);
445 	stringWithSalt+="00";
446 #warning dbg
447 	// checksum byte
448 	uint8_t pCs=0;
449 	for(char c : stringWithSalt)
450 		pCs+=c;
451 	stringWithSalt+=BytesToHexString(&pCs, 1);
452 	return true;
453 }
454 #endif
455 
456 typedef deque<tPtrLen> tPatchSequence;
457 
458 // might need to access the last line externally
459 unsigned long rangeStart(0), rangeLast(0);
460 
patchChunk(tPatchSequence & idx,LPCSTR pline,size_t len,tPatchSequence chunk)461 inline bool patchChunk(tPatchSequence& idx, LPCSTR pline, size_t len, tPatchSequence chunk)
462 {
463 	char op = 0x0;
464 	auto n = sscanf(pline, "%lu,%lu%c\n", &rangeStart, &rangeLast, &op);
465 	if (n == 1) // good enough
466 		rangeLast = rangeStart, op = pline[len - 2];
467 	else if(n!=3)
468 		return false; // bad instruction
469 	if (rangeStart > idx.size() || rangeLast > idx.size() || rangeStart > rangeLast)
470 		return false;
471 	if (op == 'a')
472 		idx.insert(idx.begin() + (size_t) rangeStart + 1, chunk.begin(), chunk.end());
473 	else
474 	{
475 		size_t i = 0;
476 		for (; i < chunk.size(); ++i, ++rangeStart)
477 		{
478 			if (rangeStart <= rangeLast)
479 				idx[rangeStart] = chunk[i];
480 			else
481 				break; // new stuff bigger than replaced range
482 		}
483 		if (i < chunk.size()) // not enough space :-(
484 			idx.insert(idx.begin() + (size_t) rangeStart, chunk.begin() + i, chunk.end());
485 		else if (rangeStart - 1 != rangeLast) // less data now?
486 			idx.erase(idx.begin() + (size_t) rangeStart, idx.begin() + (size_t) rangeLast + 1);
487 	}
488 	return true;
489 }
490 
maint_job()491 int maint_job()
492 {
493 	cfg::SetOption("proxy=", nullptr);
494 
495 	tStrVec hostips;
496 #if 0 // FIXME, processing on UDS gets stuck somewhere
497 	// prefer UDS if configured in a sane way
498 	if (startsWithSz(cfg::fifopath, "/"))
499 		hostips.emplace_back(cfg::fifopath);
500 #endif
501 	auto nips = Tokenize(cfg::bindaddr, SPACECHARS, hostips, true);
502 	if (!nips)
503 		hostips.emplace_back("localhost");
504 
505 	for (const auto& hostaddr : hostips)
506 	{
507 		// use an own connection factory which does "the right thing" and leaks the
508 		// internal connection result
509 		struct maintfac: public IDlConFactory
510 		{
511 			bool m_bOK = false;
512 			mstring m_hname;
513 			maintfac(cmstring& s) :
514 					m_hname(s)
515 			{
516 			}
517 
518 			void RecycleIdleConnection(tDlStreamHandle & handle) override
519 			{
520 				// keep going, no recycling/restoring
521 			}
522 			virtual tDlStreamHandle CreateConnected(cmstring &, cmstring &, mstring &, bool *,
523 					cfg::tRepoData::IHookHandler *, bool, int, bool) override
524 			{
525 				string serr;
526 
527 				if (m_hname[0] != '/') // not UDS
528 				{
529 					auto mhandle = g_tcp_con_factory.CreateConnected(m_hname, cfg::port, serr, 0, 0,
530 							false, 30, true);
531 					m_bOK = mhandle.get();
532 					return mhandle;
533 				}
534 				// otherwise build a fake connection on unix domain socket
535 				struct udsconnection: public tcpconnect
536 				{
537 					udsconnection(cmstring& udspath, bool *ok) :
538 							tcpconnect(nullptr)
539 					{
540 #ifdef DEBUG
541 						cerr << "Socket path: " << udspath << endl;
542 #endif
543 						auto m_conFd = socket(PF_UNIX, SOCK_STREAM, 0);
544 						if (m_conFd < 0)
545 							return;
546 
547 						struct sockaddr_un addr;
548 						addr.sun_family = PF_UNIX;
549 						strcpy(addr.sun_path, cfg::fifopath.c_str());
550 						socklen_t adlen =
551 								cfg::fifopath.length() + 1 + offsetof(struct sockaddr_un, sun_path);
552 						if (connect(m_conFd, (struct sockaddr*) &addr, adlen))
553 						{
554 #ifdef DEBUG
555 							perror("connect");
556 #endif
557 							return;
558 						}
559 						// basic identification needed
560 						tSS ids;
561 						ids << "GET / HTTP/1.0\r\nX-Original-Source: localhost\r\n\r\n";
562 						if (!ids.send(m_conFd))
563 							return;
564 
565 #ifdef HAVE_SSL
566 						m_ssl = nullptr;
567 						m_bio = nullptr;
568 #endif
569 						// better match the TCP socket parameters
570 						m_sHostName = "localhost";
571 						m_sPort = sDefPortHTTP;
572 						*ok = true;
573 					}
574 				};
575 				return make_shared<udsconnection>(m_hname, &m_bOK);
576 			}
577 		};
578 		maintfac factoryWrapper(hostaddr);
579 		tSS urlPath;
580 		urlPath << "http://";
581 		if (!cfg::adminauth.empty())
582 			urlPath << UserinfoEscape(cfg::adminauth) << "@";
583 		if (hostaddr[0] == '/')
584 			urlPath << "localhost";
585 		else
586 			urlPath << hostaddr << ":" << cfg::port;
587 
588 		if (cfg::reportpage.empty())
589 			return -1;
590 		if(cfg::reportpage[0] != '/')
591 			urlPath << "/";
592 		urlPath << cfg::reportpage;
593 		LPCSTR req = getenv("ACNGREQ");
594 		urlPath << (req ? req : "?doExpire=Start+Expiration&abortOnErrors=aOe");
595 
596 #ifdef DEBUG
597 		cerr << "Constructed URL: " << (string) urlPath << endl;
598 #endif
599 
600 		CReportItemFactory printItemFactory;
601 		auto retcode = wcat(urlPath.c_str(), nullptr, &printItemFactory, &factoryWrapper);
602 		if (retcode)
603 		{
604 			if (!factoryWrapper.m_bOK) // connection failed, try another IP
605 				continue;
606 			// otherwise the stuff has been printed
607 			return 2;
608 		}
609 		else
610 			return 0;
611 	}
612 	// all attempts failed
613 	return 3;
614 }
615 
patch_file(string sBase,string sPatch,string sResult)616 int patch_file(string sBase, string sPatch, string sResult)
617 {
618 	filereader frBase, frPatch;
619 	if(!frBase.OpenFile(sBase, true) || !frPatch.OpenFile(sPatch, true))
620 		return -2;
621 	auto buf = frBase.GetBuffer();
622 	auto size = frBase.GetSize();
623 	tPatchSequence idx;
624 	idx.emplace_back(buf, 0); // dummy entry to avoid -1 calculations because of ed numbering style
625 	for (auto p = buf; p < buf + size;)
626 	{
627 		LPCSTR crNext = strchr(p, '\n');
628 		if (crNext)
629 		{
630 			idx.emplace_back(p, crNext + 1 - p);
631 			p = crNext + 1;
632 		}
633 		else
634 		{
635 			idx.emplace_back(p, buf + size - p);
636 			break;
637 		}
638 	}
639 
640 	auto pbuf = frPatch.GetBuffer();
641 	auto psize = frPatch.GetSize();
642 	tPatchSequence chunk;
643 	LPCSTR cmd =0;
644 	size_t cmdlen = 0;
645 	for (auto p = pbuf; p < pbuf + psize;)
646 	{
647 		LPCSTR crNext = strchr(p, '\n');
648 		size_t len = 0;
649 		LPCSTR line=p;
650 		if (crNext)
651 		{
652 			len = crNext + 1 - p;
653 			p = crNext + 1;
654 		}
655 		else
656 		{
657 			len = pbuf + psize - p;
658 			p = pbuf + psize + 1; // break signal, actually
659 		}
660 		p=crNext+1;
661 
662 		bool gogo = (len == 2 && *line == '.');
663 		if(!gogo)
664 		{
665 			if(!cmdlen)
666 			{
667 				if(!strncmp("s/.//\n", line, 6))
668 				{
669 					// oh, that's the fix-the-last-line command :-(
670 					if(rangeStart)
671 						idx[rangeStart].first = ".\n", idx[rangeStart].second=2;
672 					continue;
673 				}
674 				else if(line[0] == 'w')
675 					continue; // don't care, we know the target
676 
677 				cmdlen = len;
678 				cmd = line;
679 
680 				if(len>2 && line[len-2] == 'd')
681 					gogo = true; // no terminator to expect
682 			}
683 			else
684 				chunk.emplace_back(line, len);
685 		}
686 
687 		if(gogo)
688 		{
689 			if(!patchChunk(idx, cmd, cmdlen, chunk))
690 			{
691 				cerr << "Bad patch line: ";
692 				cerr.write(cmd, cmdlen);
693 				exit(EINVAL);
694 			}
695 			chunk.clear();
696 			cmdlen = 0;
697 		}
698 	}
699 	ofstream res(sResult.c_str());
700 	if(!res.is_open())
701 		return -3;
702 
703 	for(const auto& kv : idx)
704 		res.write(kv.first, kv.second);
705 	res.flush();
706 //	dump_proc_status_always();
707 	return res.good() ? 0 : -4;
708 }
709 
710 
711 struct parm {
712 	unsigned minArg, maxArg; // if maxArg is UINT_MAX, there will be a final call with NULL argument
713 	std::function<void(LPCSTR)> f;
714 };
715 
716 // some globals shared across the functions
717 int g_exitCode(0);
718 LPCSTR g_missingCfgDir = nullptr;
719 
parse_options(int argc,const char ** argv,function<void (LPCSTR)> f)720 void parse_options(int argc, const char **argv, function<void (LPCSTR)> f)
721 {
722 	LPCSTR szCfgDir=CFGDIR;
723 	std::vector<LPCSTR> validargs, nonoptions;
724 	bool ignoreCfgErrors = false;
725 
726 	for (auto p=argv; p<argv+argc; p++)
727 	{
728 		if (!strncmp(*p, "-h", 2))
729 			usage();
730 		else if (!strcmp(*p, "-c"))
731 		{
732 			++p;
733 			if (p < argv + argc)
734 				szCfgDir = *p;
735 			else
736 				usage(2);
737 		}
738 		else if(!strcmp(*p, "--verbose"))
739 			g_bVerbose=true;
740 		else if(!strcmp(*p, "-i"))
741 			ignoreCfgErrors = true;
742 		else if(**p) // not empty
743 			validargs.emplace_back(*p);
744 
745 #if SUPPWHASH
746 #warning FIXME
747 		else if (!strncmp(*p, "-H", 2))
748 			exit(hashpwd());
749 #endif
750 	}
751 
752 	if(szCfgDir)
753 	{
754 		Cstat info(szCfgDir);
755 		if(!info || !S_ISDIR(info.st_mode))
756 			g_missingCfgDir = szCfgDir;
757 		else
758 			cfg::ReadConfigDirectory(szCfgDir, ignoreCfgErrors);
759 	}
760 
761 	tStrVec non_opt_args;
762 
763 	for(auto& keyval : validargs)
764 	{
765 		cfg::g_bQuiet = true;
766 		if(!cfg::SetOption(keyval, 0))
767 			nonoptions.emplace_back(keyval);
768 		cfg::g_bQuiet = false;
769 	}
770 
771 	cfg::PostProcConfig();
772 
773 	for(const auto& x: nonoptions)
774 		f(x);
775 }
776 
777 
778 #if SUPPWHASH
ssl_init()779 void ssl_init()
780 {
781 #ifdef HAVE_SSL
782 	SSL_load_error_strings();
783 	ERR_load_BIO_strings();
784 	ERR_load_crypto_strings();
785 	ERR_load_SSL_strings();
786 	OpenSSL_add_all_algorithms();
787 	SSL_library_init();
788 #endif
789 }
790 #endif
791 
792 /*
793 void assert_cfgdir()
794 {
795 	if(!g_missingCfgDir)
796 		return;
797 	cerr << "Failed to open config directory: " << g_missingCfgDir <<endl;
798 	exit(EXIT_FAILURE);
799 }
800 */
801 
warn_cfgdir()802 void warn_cfgdir()
803 {
804 	if (g_missingCfgDir)
805 		cerr << "Warning: failed to open config directory: " << g_missingCfgDir <<endl;
806 }
807 
808 std::unordered_map<string, parm> parms = {
809 #if 0
810    {
811 		"urltest",
812 		{ 1, 1, [](LPCSTR p)
813 			{
814 				std::cout << EncodeBase64Auth(p);
815 			}
816 		}
817 	}
818 	,
819 #endif
820 #if 0
821    {
822 		"bin2hex",
823 		{ 1, 1, [](LPCSTR p)
824 			{
825          filereader f;
826          if(f.OpenFile(p, true))
827             exit(EIO);
828          std::cout << BytesToHexString(f.GetBuffer(), f.GetSize()) << std::endl;
829          exit(EXIT_SUCCESS);
830 			}
831 		}
832 	}
833 	,
834 #endif
835 #if 0 // def HAVE_DECB64
836 	{
837      "decb64",
838      { 1, 1, [](LPCSTR p)
839         {
840 #ifdef DEBUG
841            cerr << "decoding " << p <<endl;
842 #endif
843            acbuf res;
844            if(DecodeBase64(p, strlen(p), res))
845            {
846               std::cout.write(res.rptr(), res.size());
847               exit(0);
848            }
849            exit(1);
850         }
851      }
852   }
853 	,
854 #endif
855 	{
856 		"encb64",
857 		{ 1, 1, [](LPCSTR p)
__anon3bf6efdb0202() 858 			{
859 #ifdef DEBUG
860 			cerr << "encoding " << p <<endl;
861 #endif
862 				std::cout << EncodeBase64Auth(p);
863 			}
864 		}
865 	}
866 	,
867 		{
868 			"cfgdump",
__anon3bf6efdb0302() 869 			{ 0, 0, [](LPCSTR p) {
870 				warn_cfgdir();
871 						     cfg::dump_config(false);
872 					     }
873 			}
874 		}
875 	,
876 		{
877 			"curl",
878 			{ 1, UINT_MAX, [](LPCSTR p)
__anon3bf6efdb0402() 879 				{
880 					if(!p)
881 						return;
882 
883 					CPrintItemFactory fac;
884 					auto ret=wcat(p, getenv("http_proxy"), &fac);
885 					if(!g_exitCode)
886 						g_exitCode = ret;
887 
888 				}
889 			}
890 		},
891 		{
892 			"retest",
893 			{
894 				1, 1, [](LPCSTR p)
__anon3bf6efdb0502() 895 				{
896 					warn_cfgdir();
897 					std::cout << ReTest(p) << std::endl;
898 				}
899 			}
900 		}
901 	,
902 		{
903 			"printvar",
904 			{
905 				1, 1, [](LPCSTR p)
__anon3bf6efdb0602() 906 				{
907 					warn_cfgdir();
908 					auto ps(cfg::GetStringPtr(p));
909 					if(ps) { cout << *ps << endl; return; }
910 					auto pi(cfg::GetIntPtr(p));
911 					if(pi) {
912 						cout << *pi << endl;
913 						return;
914 					}
915 					g_exitCode=23;
916 				}
917 			}
918 		},
919 		{
920 			"patch",
921 			{
922 				3, 3, [](LPCSTR p)
__anon3bf6efdb0702() 923 				{
924 					static tStrVec iop;
925 					iop.emplace_back(p);
926 					if(iop.size() == 3)
927 						g_exitCode+=patch_file(iop[0], iop[1], iop[2]);
928 				}
929 			}
930 		}
931 
932 	,
933 		{
934 			"maint",
935 			{
936 				0, 0, [](LPCSTR p)
__anon3bf6efdb0802() 937 				{
938 					warn_cfgdir();
939 					g_exitCode+=maint_job();
940 				}
941 			}
942 		}
943    ,
944    {
945 		   "shrink",
946 		   {
947 				   1, UINT_MAX, [](LPCSTR p)
__anon3bf6efdb0902() 948 				   {
949 					   static bool dryrun(false), apply(false), verbose(false), incIfiles(false);
950 					   static off_t wantedSize(4000000000);
951 					   if(!p)
952 						   g_exitCode += shrink(wantedSize, dryrun, apply, verbose, incIfiles);
953 					   else if(*p > '0' && *p<='9')
954 						   wantedSize = strsizeToOfft(p);
955 					   else if(*p == '-')
956 					   {
957 						   for(++p;*p;++p)
958 						   {
959 							   if(*p == 'f') apply = true;
960 							   else if(*p == 'n') dryrun = true;
961 							   else if (*p == 'x') incIfiles = true;
962 							   else if (*p == 'v') verbose = true;
963 						   }
964 					   }
965 				   }
966 		   }
967    }
968 };
969 
main(int argc,const char ** argv)970 int main(int argc, const char **argv)
971 {
972 	using namespace acng;
973 
974 	string exe(argv[0]);
975 	unsigned aOffset=1;
976 	if(endsWithSzAr(exe, "expire-caller.pl"))
977 	{
978 		aOffset=0;
979 		argv[0] = "maint";
980 	}
981 	cfg::g_bQuiet = false;
982 	cfg::g_bNoComplex = true; // no DB for just single variables
983 
984   parm* parm = nullptr;
985   LPCSTR mode = nullptr;
986   unsigned xargCount = 0;
987 
988 	parse_options(argc-aOffset, argv+aOffset, [&](LPCSTR p)
989 			{
990 		bool bFirst = false;
991 		if(!mode)
992 			bFirst = (0 != (mode = p));
993 		else
994 			xargCount++;
995 		if(!parm)
996 			{
997 			auto it = parms.find(mode);
998 			if(it == parms.end())
999 				usage(1);
1000 			parm = & it->second;
1001 			}
1002 		if(xargCount > parm->maxArg)
1003 			usage(2);
1004 		if(!bFirst)
1005 			parm->f(p);
1006 			});
1007 	if(!mode || !parm)
1008 		usage(3);
1009 #ifdef DEBUG
1010 	log::open();
1011 #endif
1012 	if(!xargCount) // should run the code at least once?
1013 	{
1014 		if(parm->minArg) // uh... needs argument(s)
1015 			usage(4, mode);
1016 		parm->f(nullptr);
1017 	}
1018 	else if(parm->maxArg == UINT_MAX) // or needs to terminate it?
1019 		parm->f(nullptr);
1020 	return g_exitCode;
1021 }
1022 
wcat(LPCSTR surl,LPCSTR proxy,IFitemFactory * fac,IDlConFactory * pDlconFac)1023 int wcat(LPCSTR surl, LPCSTR proxy, IFitemFactory* fac, IDlConFactory *pDlconFac)
1024 {
1025 	cfg::dnscachetime=0;
1026 	cfg::persistoutgoing=0;
1027 	cfg::badredmime.clear();
1028 	cfg::redirmax=10;
1029 
1030 	if(proxy)
1031 		if(cfg::SetOption(string("proxy:")+proxy, nullptr))
1032 			return -1;
1033 	tHttpUrl url;
1034 	if(!surl)
1035 		return 2;
1036 	string xurl(surl);
1037 	if(!url.SetHttpUrl(xurl, false))
1038 		return -2;
1039 	dlcon dl(true, nullptr, pDlconFac);
1040 
1041 	auto fi=fac->Create();
1042 	dl.AddJob(fi, &url, nullptr, nullptr, 0, cfg::REDIRMAX_DEFAULT);
1043 	dl.WorkLoop();
1044 	auto fistatus = fi->GetStatus();
1045 	header hh = fi->GetHeader();
1046 	int st=hh.getStatus();
1047 
1048 	if(fistatus == fileitem::FIST_COMPLETE && st == 200)
1049 		return EXIT_SUCCESS;
1050 
1051 	// don't reveal passwords
1052 	auto xpos=xurl.find('@');
1053 	if(xpos!=stmiss)
1054 		xurl.erase(0, xpos+1);
1055 	cerr << "Error: cannot fetch " << xurl <<", "  << hh.frontLine << endl;
1056 	if (st>=500)
1057 		return EIO;
1058 	if (st>=400)
1059 		return EACCES;
1060 
1061 	return EXIT_FAILURE;
1062 }
1063 
1064 #if 0
1065 
1066 void do_stuff_before_config()
1067 {
1068 	LPCSTR envvar(nullptr);
1069 
1070 	cerr << "Pandora: " << sizeof(regex_t) << endl;
1071 	/*
1072 	// PLAYGROUND
1073 	if (argc < 2)
1074 		return -1;
1075 
1076 	acng::cfg:tHostInfo hi;
1077 	cout << "Parsing " << argv[1] << ", result: " << hi.SetUrl(argv[1]) << endl;
1078 	cout << "Host: " << hi.sHost << ", Port: " << hi.sPort << ", Path: "
1079 			<< hi.sPath << endl;
1080 	return 0;
1081 
1082 	bool Bz2compressFile(const char *, const char*);
1083 	return !Bz2compressFile(argv[1], argv[2]);
1084 
1085 	char tbuf[40];
1086 	FormatCurrentTime(tbuf);
1087 	std::cerr << tbuf << std::endl;
1088 	exit(1);
1089 	*/
1090 	envvar = getenv("PARSEIDX");
1091 	if (envvar)
1092 	{
1093 		int parseidx_demo(LPCSTR);
1094 		exit(parseidx_demo(envvar));
1095 	}
1096 
1097 	envvar = getenv("GETSUM");
1098 	if (envvar)
1099 	{
1100 		uint8_t csum[20];
1101 		string s(envvar);
1102 		off_t resSize;
1103 		bool ok = filereader::GetChecksum(s, CSTYPE_SHA1, csum, false, resSize /*, stdout*/);
1104 		if(!ok)
1105 		{
1106 			perror("");
1107 			exit(1);
1108 		}
1109 		for (unsigned i = 0; i < sizeof(csum); i++)
1110 			printf("%02x", csum[i]);
1111 		printf("\n");
1112 		envvar = getenv("REFSUM");
1113 		if (ok && envvar)
1114 		{
1115 			if(CsEqual(envvar, csum, sizeof(csum)))
1116 			{
1117 				printf("IsOK\n");
1118 				exit(0);
1119 			}
1120 			else
1121 			{
1122 				printf("Diff\n");
1123 				exit(1);
1124 			}
1125 		}
1126 		exit(0);
1127 	}
1128 }
1129 
1130 #endif
1131 #if 0
1132 #warning line reader test enabled
1133 	if (cmd == "wcl")
1134 	{
1135 		if (argc < 3)
1136 			usage(2);
1137 		filereader r;
1138 		if (!r.OpenFile(argv[2], true))
1139 		{
1140 			cerr << r.getSErrorString() << endl;
1141 			return EXIT_FAILURE;
1142 		}
1143 		size_t count = 0;
1144 		auto p = r.GetBuffer();
1145 		auto e = p + r.GetSize();
1146 		for (;p < e; ++p)
1147 			count += (*p == '\n');
1148 		cout << count << endl;
1149 
1150 		exit(EXIT_SUCCESS);
1151 	}
1152 #endif
1153 #if 0
1154 #warning header parser enabled
1155 	if (cmd == "htest")
1156 	{
1157 		header h;
1158 		h.LoadFromFile(argv[2]);
1159 		cout << string(h.ToString()) << endl;
1160 
1161 		h.clear();
1162 		filereader r;
1163 		r.OpenFile(argv[2]);
1164 		std::vector<std::pair<std::string, std::string>> oh;
1165 		h.Load(r.GetBuffer(), r.GetSize(), &oh);
1166 		for(auto& r : oh)
1167 			cout << "X:" << r.first << " to " << r.second;
1168 		exit(0);
1169 	}
1170 #endif
1171 #if 0
1172 #warning benchmark enabled
1173 	if (cmd == "benchmark")
1174 	{
1175 		dump_proc_status_always();
1176 		cfg::g_bQuiet = true;
1177 		cfg::g_bNoComplex = false;
1178 		parse_options(argc - 2, argv + 2, true);
1179 		cfg::PostProcConfig();
1180 		string s;
1181 		tHttpUrl u;
1182 		int res=0;
1183 /*
1184 		acng::cfg:tRepoResolvResult hm;
1185 		tHttpUrl wtf;
1186 		wtf.SetHttpUrl(non_opt_args.front());
1187 		acng::cfg:GetRepNameAndPathResidual(wtf, hm);
1188 */
1189 		while(cin)
1190 		{
1191 			std::getline(cin, s);
1192 			s += "/xtest.deb";
1193 			if(u.SetHttpUrl(s))
1194 			{
1195 				cfg::tRepoResolvResult xdata;
1196 				cfg::GetRepNameAndPathResidual(u, xdata);
1197 				cout << s << " -> "
1198 						<< (xdata.psRepoName ? "matched" : "not matched")
1199 						<< endl;
1200 			}
1201 		}
1202 		dump_proc_status_always();
1203 		exit(res);
1204 	}
1205 #endif
1206