1 
2 /* Web Polygraph       http://www.web-polygraph.org/
3  * Copyright 2003-2011 The Measurement Factory
4  * Licensed under the Apache License, Version 2.0 */
5 
6 #include "base/polygraph.h"
7 #include "pgl/pgl.h"
8 
9 #include <fstream>
10 
11 #include "xstd/RegEx.h"
12 #include "xstd/Size.h"
13 #include "xstd/gadgets.h"
14 #include "base/opts.h"
15 #include "base/polyOpts.h"
16 #include "base/CmdLine.h"
17 #include "base/RndPermut.h"
18 #include "pgl/PglPp.h"
19 #include "pgl/PglCtx.h"
20 
21 #include "pgl/PglStaticSemx.h"
22 #include "pgl/PglContainerSym.h"
23 #include "pgl/PglStringSym.h"
24 #include "pgl/PglRegExSym.h"
25 #include "pgl/AgentSymIter.h"
26 #include "pgl/AclSym.h"
27 #include "pgl/RobotSym.h"
28 
29 
30 class MyOpts: public OptGrp {
31 	public:
MyOpts()32 		MyOpts():
33 			theHelpOpt(this,    "help",          "list of options"),
34 			theVersOpt(this,    "version",       "package version info"),
35 			theCfgName(this,    "config <filename>",  "PGL configuration"),
36 			theCfgDirs(this,    "cfg_dirs <dirs>",  "directories for PGL #includes"),
37 			theGlbRngSeed(this, "global_rng_seed <int>","per-test r.n.g. seed", 1)
38 			{}
39 
40 		virtual bool validate() const;
41 
42 	public:
43 		HelpOpt theHelpOpt;
44 		VersionOpt theVersOpt;
45 		StrOpt theCfgName;
46 		StrArrOpt theCfgDirs;
47 		IntOpt theGlbRngSeed;
48 };
49 
50 class Rules;
51 
52 class RuleCondnItem {
53 	public:
RuleCondnItem(RegEx * anExpr=0,RegExExpr::Oper anOper=RegExExpr::opNone)54 		RuleCondnItem(RegEx *anExpr = 0, RegExExpr::Oper anOper = RegExExpr::opNone): oper(anOper), expr(anExpr) {}
55 
56 		bool operator ==(const RuleCondnItem &i) const;
operator !=(const RuleCondnItem & i) const57 		bool operator !=(const RuleCondnItem &i) const { return !(*this == i); }
58 		int cmp(const RuleCondnItem &i) const;
59 
60 		RuleCondnItem negate() const;
61 
62 		ostream &print(ostream &os) const;
63 
64 	public:
65 		RegExExpr::Oper oper;
66 		RegEx *expr;
67 };
68 
69 class RuleCondn;
70 class RuleCondnHash {
71 	public:
72 		RuleCondnHash(const RuleCondn &aBase, int expCount = 0);
73 
expCount() const74 		int expCount() const { return theExpCount; }
capacity() const75 		int capacity() const { return theIds.capacity(); }
76 
77 		int idxOf(const RuleCondnItem &i) const; // returns -1 if none
78 
79 		void add(const RuleCondnItem &i, int idx);
80 
81 	protected:
82 		int hash(const RuleCondnItem &i) const;
83 
84 	protected:
85 		const RuleCondn &theBase;
86 		Array<int> theIds;
87 		int theExpCount;
88 		int theCount;
89 };
90 
91 // array of possibly negated RegExes
92 class RuleCondn {
93 	public:
94 		RuleCondn(int aCapacity = 0);
95 
count() const96 		int count() const { return theItems.count(); }
97 		bool has(const RuleCondnItem &h, int offset = 0) const;
98 
item(int idx) const99 		const RuleCondnItem &item(int idx) const { return theItems.item(idx); }
100 
101 		bool operator ==(const RuleCondn &condn) const;
operator !=(const RuleCondn & condn) const102 		bool operator !=(const RuleCondn &condn) const { return !(*this == condn); }
103 		int cmp(const RuleCondn &c) const;
104 
105 		bool possible() const;
106 		bool covers(const RuleCondn &c) const;
107 
108 		void add(const RuleCondnItem &i);
109 
110 		void sort();
111 		void prune();
112 		void merge(const RuleCondn &c);
113 		void simplify(Rules &rules, int offset);
114 
115 		ostream &print(ostream &os) const;
116 
117 	protected:
118 		void createHash(int expCount = 8);
119 		void reHash();
120 
121 	private:
122 		Array<RuleCondnItem> theItems;
123 		RuleCondnHash *theHash;
124 };
125 
126 // condition -> action
127 class AclRule {
128 	public:
129 		enum Action { rlUnknown, rlAllow, rlDeny, rlRewrite, rlAny };
130 
131 	public:
132 		AclRule(Action anAction, RuleCondn *aCondn);
133 
action() const134 		Action action() const { return theAction; }
condn() const135 		const RuleCondn &condn() const { return *theCondn; }
136 
137 		bool operator ==(const AclRule &rule) const;
operator !=(const AclRule & rule) const138 		bool operator !=(const AclRule &rule) const { return !(*this == rule); }
139 		int cmp(const AclRule &r) const;
140 		bool covers(const AclRule &r) const;
141 
142 		void sort();
143 		void prune();
144 		void simplify(Rules &rules, int offset);
145 
146 		ostream &print(ostream &os) const;
147 
148 	protected:
149 		Action theAction;
150 		RuleCondn *theCondn;
151 };
152 
153 // array of rules
154 class Rules: public Array<AclRule*> {
155 	public:
Rules()156 		Rules(): Array<AclRule*>(16) {}
~Rules()157 		~Rules() { while (count()) delete pop(); }
158 
159 		const AclRule *covered(const AclRule &r, int excIdx = -1) const;
160 
161 		//const AclRule *haveOtherAction(const AclRule &rule) const;
162 		//const AclRule *haveAnyAction(const AclRule &rule) const;
163 
164 		const AclRule *commit(const AclRule &rule);
165 
166 		void sort();
167 		void prune();
168 		void simplify();
169 
170 		ostream &print(ostream &os) const;
171 };
172 
173 class Normalizer {
174 	public:
175 		Normalizer(AclRule::Action anAction, const RegExExpr &expr, Rules &rules);
176 
177 		void run();
178 
179 	protected:
180 		void doNoneOp();
181 		void doNotOp();
182 		void doAndOp();
183 		void doOrOp();
184 
185 	protected:
186 		AclRule::Action theAction;
187 		const RegExExpr &theExpr;
188 		Rules &theRules;
189 };
190 
191 
192 static MyOpts TheOpts;
193 
194 
195 /* MyOpt */
196 
validate() const197 bool MyOpts::validate() const {
198 	if (!theCfgName)
199 		cerr << "must specify PGL configuration file (--config)" << endl;
200 	else
201 		return true;
202 	return false;
203 }
204 
205 
206 /* RuleCondnItem */
207 
operator ==(const RuleCondnItem & item) const208 bool RuleCondnItem::operator ==(const RuleCondnItem &item) const {
209 	return oper == item.oper && expr->image() == item.expr->image();
210 }
211 
cmp(const RuleCondnItem & i) const212 int RuleCondnItem::cmp(const RuleCondnItem &i) const {
213 	if (const int operDiff = oper - i.oper)
214 		return operDiff;
215 
216 	return expr->image().cmp(i.expr->image());
217 }
218 
negate() const219 RuleCondnItem RuleCondnItem::negate() const {
220 	const RegExExpr::Oper o = oper == RegExExpr::opNone ?
221 			RegExExpr::opNot : RegExExpr::opNone;
222 	return RuleCondnItem(expr, o);
223 }
224 
print(ostream & os) const225 ostream &RuleCondnItem::print(ostream &os) const {
226 	if (oper == RegExExpr::opNot)
227 		os << "not ";
228 
229 	const char *scope = 0;
230 	if (expr->image().cmp("user_group=", 11) == 0)
231 		scope = "group";
232 	else
233 	if (expr->image().cmp("url=", 4) == 0)
234 		scope = "url";
235 	else
236 	if (expr->image().cmp("url_host=", 9) == 0)
237 		scope = "server-name";
238 	else
239 	if (expr->image().cmp("url_host_ip=", 12) == 0)
240 		scope = "server-ip";
241 	else
242 	if (expr->image().cmp("url_path=", 9) == 0)
243 		scope = "url";
244 
245 	os << scope << ' ';
246 
247 	if ((expr->flags() & RegEx::reExact) == 0)
248 		os << "matches ";
249 
250 	os << '"' << expr->pattern() << '"';
251 
252 	return os;
253 }
254 
255 
256 /* RuleCondnHash */
257 
RuleCondnHash(const RuleCondn & aBase,int expCount)258 RuleCondnHash::RuleCondnHash(const RuleCondn &aBase, int expCount):
259 	theBase(aBase), theExpCount(expCount), theCount(0) {
260 	theIds.resize(expCount*3 + 1);
261 }
262 
idxOf(const RuleCondnItem & i) const263 int RuleCondnHash::idxOf(const RuleCondnItem &i) const {
264 	const int pos = hash(i);
265 	for (int p = pos, c = 0; theIds[p]; ++p, p %= theIds.count(), ++c) {
266 		const int idx = theIds[p] - 1;
267 		if (theBase.item(idx) == i)
268 			return idx;
269 		Assert(c < theIds.count());
270 	}
271 	return -1;
272 }
273 
add(const RuleCondnItem & i,int idx)274 void RuleCondnHash::add(const RuleCondnItem &i, int idx) {
275 	Assert(theCount < theIds.count());
276 	Assert(idx >= 0);
277 
278 	int p = hash(i);
279 	for (; theIds[p]; ++p, p %= theIds.count()) {
280 		const int idx = theIds[p] - 1;
281 		if (theBase.item(idx) == i)
282 			return; // do not hash duplicates?
283 	}
284 
285 	theIds[p] = idx + 1;
286 	theCount++;
287 }
288 
StrHash(const String & s)289 inline int StrHash(const String &s) {
290 	const int step = Max(1, s.len() / 16);
291 	unsigned int res = 0;
292 	for (int i = 0; i < s.len(); i += step)
293 		res = res*33U + s.data()[i];
294 
295 	return Max(1, abs((int)res));
296 }
297 
hash(const RuleCondnItem & i) const298 int RuleCondnHash::hash(const RuleCondnItem &i) const {
299 	// XXX: replace StrHash with String::hash
300 	return abs(i.oper ^ StrHash(i.expr->image())) % theIds.count();
301 }
302 
303 
304 /* RuleCondn */
305 
RuleCondn(int aCapacity)306 RuleCondn::RuleCondn(int aCapacity): theItems(aCapacity), theHash(0) {
307 	if (aCapacity > 1)
308 		createHash(aCapacity);
309 }
310 
operator ==(const RuleCondn & c) const311 bool RuleCondn::operator ==(const RuleCondn &c) const {
312 	if (count() != c.count())
313 		return false;
314 
315 	for (int i = 0; i < c.count(); ++i) {
316 		if (!has(c.item(i)))
317 			return false;
318 	}
319 
320 	return true;
321 }
322 
has(const RuleCondnItem & h,int offset) const323 bool RuleCondn::has(const RuleCondnItem &h, int offset) const {
324 	if (theHash)
325 		return theHash->idxOf(h) >= offset;
326 
327 	for (int i = offset; i < count(); ++i) {
328 		if (item(i) == h)
329 			return true;
330 	}
331 	return false;
332 }
333 
possible() const334 bool RuleCondn::possible() const {
335 	for (int i = 0; i < count(); ++i) {
336 		if (has(item(i).negate(), i+1))
337 			return false;
338 	}
339 	return true;
340 }
341 
covers(const RuleCondn & c) const342 bool RuleCondn::covers(const RuleCondn &c) const {
343 	for (int i = 0; i < count(); ++i) {
344 		if (!c.has(item(i)))
345 			return false;
346 	}
347 	return true;
348 }
349 
cmp(const RuleCondn & c) const350 int RuleCondn::cmp(const RuleCondn &c) const {
351 	if (int cntDiff = count() - c.count())
352 		return cntDiff;
353 
354 	Assert(count() == c.count());
355 	for (int i = 0; i < count(); ++i) {
356 		if (int itemDiff = item(i).cmp(c.item(i)))
357 			return itemDiff;
358 	}
359 
360 	return 0;
361 }
362 
363 static
cmpRuleCondnItems(const void * ip1,const void * ip2)364 int cmpRuleCondnItems(const void *ip1, const void *ip2) {
365 	const RuleCondnItem &i1 = *(const RuleCondnItem*)ip1;
366 	const RuleCondnItem &i2 = *(const RuleCondnItem*)ip2;
367 	return (i1.cmp(i2));
368 }
369 
sort()370 void RuleCondn::sort() {
371 	delete theHash;
372 	theHash = 0;
373 	qsort(theItems.items(), count(), sizeof(*theItems.items()), &cmpRuleCondnItems);
374 	createHash(count());
375 }
376 
prune()377 void RuleCondn::prune() {
378 	Array<RuleCondnItem> prunedItems;
379 
380 	// remove duplicates
381 	for (int i = 0; i < count(); ++i) {
382 		if (!has(item(i), i+1))
383 			prunedItems.append(item(i));
384 	}
385 
386 	if (prunedItems.count() != count()) {
387 		theItems = prunedItems;
388 		reHash();
389 	}
390 }
391 
merge(const RuleCondn & c)392 void RuleCondn::merge(const RuleCondn &c) {
393 	theItems.stretch(count() + c.count());
394 	for (int i = 0; i < c.count(); ++i)
395 		if (!has(c.item(i)))
396 			add(c.item(i));
397 }
398 
simplify(Rules & rules,int offset)399 void RuleCondn::simplify(Rules &rules, int offset) {
400 	Array<RuleCondnItem> prunedItems;
401 
402 	for (int i = 0; i < count(); ++i) {
403 		const RuleCondnItem &ci = item(i);
404 		const RuleCondnItem notCi = ci.negate();
405 		bool ejectCandidate = true;
406 		for (int r = offset; ejectCandidate && r < rules.count(); ++r) {
407 			ejectCandidate = rules[r]->condn().has(ci) ||
408 				!rules[r]->condn().has(notCi);
409 		}
410 		if (!ejectCandidate)
411 			prunedItems.append(ci);
412 	}
413 
414 	if (prunedItems.count() != count()) {
415 		theItems = prunedItems;
416 		reHash();
417 	}
418 }
419 
print(ostream & os) const420 ostream &RuleCondn::print(ostream &os) const {
421 	if (!count())
422 		return os << "any";
423 
424 	for (int i = 0; i < count(); ++i) {
425 		if (i)
426 			os << " and ";
427 		item(i).print(os);
428 	}
429 	return os;
430 }
431 
add(const RuleCondnItem & i)432 void RuleCondn::add(const RuleCondnItem &i) {
433 	if (count() && !theHash)
434 		createHash();
435 
436 	if (theHash && count() > 0.75*theHash->capacity())
437 		reHash();
438 
439 	if (theHash)
440 		theHash->add(i, theItems.count());
441 
442 	theItems.append(i);
443 }
444 
createHash(int expCount)445 void RuleCondn::createHash(int expCount) {
446 	if (theHash && expCount <= theHash->expCount())
447 		return;
448 
449 	if (theHash)
450 		delete theHash;
451 	theHash = new RuleCondnHash(*this, expCount);
452 
453 	for (int i = 0; i < count(); ++i)
454 		theHash->add(item(i), i);
455 }
456 
reHash()457 void RuleCondn::reHash() {
458 	delete theHash;
459 	theHash = 0;
460 	createHash(count());
461 }
462 
463 /* AclRule */
464 
AclRule(Action anAction,RuleCondn * aCondn)465 AclRule::AclRule(Action anAction, RuleCondn *aCondn):
466 	theAction(anAction), theCondn(aCondn) {
467 }
468 
operator ==(const AclRule & rule) const469 bool AclRule::operator ==(const AclRule &rule) const {
470 	return theAction == rule.theAction && (*theCondn) == (*rule.theCondn);
471 }
472 
covers(const AclRule & r) const473 bool AclRule::covers(const AclRule &r) const {
474 	return action() == r.action() && theCondn->covers(*r.theCondn);
475 }
476 
cmp(const AclRule & r) const477 int AclRule::cmp(const AclRule &r) const {
478 	if (const int cndDiff = theCondn->cmp(*r.theCondn))
479 		return cndDiff;
480 
481 	return action() - r.action();
482 }
483 
sort()484 void AclRule::sort() {
485 	theCondn->sort();
486 }
487 
prune()488 void AclRule::prune() {
489 	theCondn->prune();
490 }
491 
simplify(Rules & rules,int offset)492 void AclRule::simplify(Rules &rules, int offset) {
493 	theCondn->simplify(rules, offset);
494 }
495 
print(ostream & os) const496 ostream &AclRule::print(ostream &os) const {
497 	if (theAction == rlAllow)
498 		os << "allow ";
499 	else
500 	if (theAction == rlDeny)
501 		os << "deny ";
502 	else
503 	if (theAction == rlRewrite)
504 		os << "rewrite ";
505 	else
506 		Assert(false);
507 
508 	Assert(theCondn);
509 	theCondn->print(os);
510 	return os << endl;
511 }
512 
513 /* Rules */
514 
515 #if FUTURE_CODE
516 // checks both for (A,a) and (A,any)
cover(const AclRule & rule) const517 const AclRule *Rules::cover(const AclRule &rule) const {
518 	for (int i = 0; i < count(); ++i) {
519 		if (item(i)->action() != rule.action())
520 			continue;
521 		if (item(i).body() == AclRule::AnyCondn)
522 			return &item(i);
523 		if (rule.body() == item(i)::body())
524 			return &item(i);
525 	}
526 	return 0;
527 }
528 
529 // checks for both (!A,a) and (!A,any)
coverOtherAction(const AclRule & rule) const530 const AclRule *Rules::coverOtherAction(const AclRule &rule) const {
531 	for (int i = 0; i < count(); ++i) {
532 		if (item(i).action() == rule.action())
533 			continue;
534 		if (item(i).body() == AclRule::AnyCondn)
535 			return &item(i);
536 		if (rule.body() == item(i)::body())
537 			return &item(i);
538 	}
539 	return 0;
540 }
541 
542 // checks for (*,a)
coverAnyAction(const AclRule & rule) const543 const AclRule *Rules::coverAnyAction(const AclRule &rule) const {
544 	for (int i = 0; i < count(); ++i) {
545 		if (rule.body() == item(i)::body())
546 			return &item(i);
547 	}
548 	return 0;
549 }
550 
commit(const AclRule & rule)551 const AclRule *Rules::commit(const AclRule &rule) {
552 	append(rule.clone());
553 	return last();
554 }
555 #endif
556 
covered(const AclRule & r,int excIdx) const557 const AclRule *Rules::covered(const AclRule &r, int excIdx) const {
558 	for (int i = 0; i < count(); ++i) {
559 		if (i == excIdx)
560 			continue;
561 		if (item(i)->covers(r))
562 			return item(i);
563 	}
564 	return 0;
565 }
566 
prune()567 void Rules::prune() {
568 	// prune individual rules
569 	{for (int i = 0; i < count(); ++i)
570 		item(i)->prune();
571 	}
572 
573 	// remove duplicates and rules with impossible conditions
574 	// also remove rules that are covered by more general rules
575 	{for (int i = 0; i < count();) {
576 		const AclRule &r = *item(i);
577 		bool bad = !r.condn().possible() || covered(*item(i), i);
578 		for (int goodIdx = 0; !bad && goodIdx < i; ++goodIdx) {
579 			bad = *item(goodIdx) == r;
580 		}
581 		if (bad)
582 			eject(i);
583 		else
584 			++i;
585 	}}
586 }
587 
588 static
cmpRules(const void * rp1,const void * rp2)589 int cmpRules(const void *rp1, const void *rp2) {
590 	const AclRule *r1 = *(const AclRule**)rp1;
591 	const AclRule *r2 = *(const AclRule**)rp2;
592 	return (r1->cmp(*r2));
593 }
594 
sort()595 void Rules::sort() {
596 	// sort individual rules
597 	for (int i = 0; i < count(); ++i)
598 		item(i)->sort();
599 
600 	qsort(items(), count(), sizeof(*items()), &cmpRules);
601 }
602 
simplify()603 void Rules::simplify() {
604 	for (int i = 0; i < count(); ++i)
605 		item(i)->simplify(*this, i+1);
606 }
607 
print(ostream & os) const608 ostream &Rules::print(ostream &os) const {
609 	for (int i = 0; i < count(); ++i) {
610 		//cout << setw(4) << setfill('0') << i << setfill(' ') << ' ';
611 		item(i)->print(cout);
612 	}
613 	return os;
614 }
615 
616 /* Normalizer */
617 
Normalizer(AclRule::Action anAction,const RegExExpr & anExpr,Rules & aRules)618 Normalizer::Normalizer(AclRule::Action anAction, const RegExExpr &anExpr, Rules &aRules):
619 	theAction(anAction), theExpr(anExpr), theRules(aRules) {
620 }
621 
doNoneOp()622 void Normalizer::doNoneOp() {
623 	RuleCondn *condn = new RuleCondn(1);
624 	condn->add(RuleCondnItem(theExpr.theVal));
625 	AclRule *rule = new AclRule(theAction, condn);
626 	theRules.append(rule);
627 }
628 
doNotOp()629 void Normalizer::doNotOp() {
630 	RegExExpr *e = theExpr.theLhs;
631 	Assert(e);
632 	switch (e->theOper) {
633 		case RegExExpr::opNone: {
634 			RuleCondn *condn = new RuleCondn(1);
635 			condn->add(RuleCondnItem(e->theVal, RegExExpr::opNot));
636 			AclRule *rule = new AclRule(theAction, condn);
637 			theRules.append(rule);
638 			break;
639 		}
640 		case RegExExpr::opNot: {
641 			Normalizer n(theAction, *e->theLhs, theRules);
642 			n.run();
643 			break;
644 		}
645 		case RegExExpr::opAnd: {
646 			RegExExpr notL(e->theLhs, RegExExpr::opNot, 0);
647 			RegExExpr notR(e->theRhs, RegExExpr::opNot, 0);
648 			RegExExpr x(&notL, RegExExpr::opOr, &notR);
649 			Normalizer n(theAction, x, theRules);
650 			n.run();
651 			break;
652 		}
653 		case RegExExpr::opOr: {
654 			RegExExpr notL(e->theLhs, RegExExpr::opNot, 0);
655 			RegExExpr notR(e->theRhs, RegExExpr::opNot, 0);
656 			RegExExpr x(&notL, RegExExpr::opAnd, &notR);
657 			Normalizer n(theAction, x, theRules);
658 			n.run();
659 			break;
660 		}
661 		default:
662 			Assert(false);
663 	}
664 }
665 
doOrOp()666 void Normalizer::doOrOp() {
667 	Normalizer lhs(theAction, *theExpr.theLhs, theRules);
668 	lhs.run();
669 
670 	Normalizer rhs(theAction, *theExpr.theRhs, theRules);
671 	rhs.run();
672 }
673 
doAndOp()674 void Normalizer::doAndOp() {
675 	Rules lhsRules;
676 	Normalizer lhs(theAction, *theExpr.theLhs, lhsRules);
677 	lhs.run();
678 
679 	Rules rhsRules;
680 	Normalizer rhs(theAction, *theExpr.theRhs, rhsRules);
681 	rhs.run();
682 
683 	//rhsRules.prune();
684 	//lhsRules.prune();
685 
686 	theRules.stretch(theRules.count() + lhsRules.count()*rhsRules.count());
687 	for (int l = 0; l < lhsRules.count(); ++l) {
688 		for (int r = 0; r < rhsRules.count(); ++r) {
689 			RuleCondn *condn = new RuleCondn;
690 			condn->merge(lhsRules[l]->condn());
691 			condn->merge(rhsRules[r]->condn());
692 			AclRule *rule = new AclRule(theAction, condn);
693 			theRules.append(rule);
694 		}
695 	}
696 }
697 
run()698 void Normalizer::run() {
699 	switch (theExpr.theOper) {
700 		case RegExExpr::opNone: {
701 			doNoneOp();
702 			break;
703 		}
704 		case RegExExpr::opNot: {
705 			doNotOp();
706 			break;
707 		}
708 		case RegExExpr::opAnd: {
709 			doAndOp();
710 			break;
711 		}
712 		case RegExExpr::opOr: {
713 			doOrOp();
714 			break;
715 		}
716 		default:
717 			Assert(false);
718 	}
719 }
720 
721 static
normalizeRules(AclRule::Action action,const RegExExpr & expr,Rules & rules)722 void normalizeRules(AclRule::Action action, const RegExExpr &expr, Rules &rules) {
723 	Normalizer n(action, expr, rules);
724 	n.run();
725 }
726 
main(int argc,char ** argv)727 int main(int argc, char **argv) {
728 	CmdLine cmd;
729 	cmd.configure(Array<OptGrp*>() << &TheOpts);
730 	if (!cmd.parse(argc, argv) || !TheOpts.validate())
731 		return -1;
732 
733 	configureStream(cout, 2);
734 	configureStream(clog, 3);
735 
736 	GlbPermut().reseed(TheOpts.theGlbRngSeed);
737 
738 	clog << argv[0] << ": parsing..." << endl;
739 	TheOpts.theCfgDirs.copy(PglPp::TheDirs);
740 	PglStaticSemx::Interpret(TheOpts.theCfgName);
741 
742 	clog << argv[0] << ": collecting rules..." << endl;
743 	// collect all rules
744 	Rules rules;
745 	for (AgentSymIter ai(PglStaticSemx::TheAgentsToUse, RobotSym::TheType, false); ai; ++ai) {
746 		const RobotSym &robot = (const RobotSym&)*ai.agent();
747 		if (AclSym *acl = robot.acl()) {
748 			if (acl->allow())
749 				normalizeRules(AclRule::rlAllow, *acl->allow(), rules);
750 			if (acl->deny())
751 				normalizeRules(AclRule::rlDeny, *acl->deny(), rules);
752 			if (acl->rewrite())
753 				normalizeRules(AclRule::rlRewrite, *acl->rewrite(), rules);
754 		}
755 	}
756 
757 	// collect all groups
758 	//for (int i = 0; i < PglSemx::TheUserGroupsToUse; ++i)
759 	//	TheGroups.append(PglSemx::TheUserGroupsToUse[i]);
760 
761 	clog << argv[0] << ": pruning " << rules.count() << " rules ..." << endl;
762 	rules.prune();
763 	// check for conflicts
764 	// check for coverage (holes)
765 
766 	clog << argv[0] << ": sorting " << rules.count() << " rules ..." << endl;
767 	rules.sort();
768 
769 	clog << argv[0] << ": simplifying..." << endl;
770 	rules.simplify(); // prune based on order
771 
772 	clog << argv[0] << ": printing " << rules.count() << " rules ..." << endl;
773 	rules.print(cout);
774 
775 	return 0;
776 }
777