From bb3cf22005ae3b5d29ef6abb5f3f469d7a8d560b Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 10:38:05 -0600 Subject: [PATCH 01/32] Add beard guy challenge --- puzzles/bletchley/1000/index.html | 108 ++++++++++++++++++++++++++++++ puzzles/bletchley/1000/key | 1 + 2 files changed, 109 insertions(+) create mode 100644 puzzles/bletchley/1000/index.html create mode 100644 puzzles/bletchley/1000/key diff --git a/puzzles/bletchley/1000/index.html b/puzzles/bletchley/1000/index.html new file mode 100644 index 0000000..7dd37e9 --- /dev/null +++ b/puzzles/bletchley/1000/index.html @@ -0,0 +1,108 @@ +Safe to execute. + +Santa's helpers binary diff --git a/puzzles/bletchley/1000/key b/puzzles/bletchley/1000/key new file mode 100644 index 0000000..3be61b9 --- /dev/null +++ b/puzzles/bletchley/1000/key @@ -0,0 +1 @@ +It is a lovely day outside From 4f3c7190d4945872ccdb45a5b579ec65d41070da Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 10:38:30 -0600 Subject: [PATCH 02/32] Gussy up register.py --- ctf/register.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/ctf/register.py b/ctf/register.py index 422b798..b41a7f2 100755 --- a/ctf/register.py +++ b/ctf/register.py @@ -7,6 +7,22 @@ import string from . import teams from . import config +def head(title): + return ''' + + + + Team Registration + + + +

%s

+''' % (config.css, title) + +def foot(): + return '''''' + def main(): print('Content-type: text/html') print() @@ -17,17 +33,8 @@ def main(): pw = f.getfirst('pw') confirm_pw = f.getfirst('confirm_pw') - html = string.Template(''' - - - - Team Registration - - - -

Team Registration

- + html = string.Template(head('Team Registration') + + ('''

Pick a short team name: you'll be typing it a lot.

@@ -50,10 +57,8 @@ def main(): - - - - ''' % (config.css, config.url('register.cgi'))) + ''' % config.url('register.cgi')) + + foot()) if not (team and pw and confirm_pw): # If we're starting from the beginning? html = html.substitute(team_error='', @@ -66,7 +71,9 @@ def main(): pw_match_error='Passwords do not match') else: teams.add(team, pw) - html = 'Team registered.' + html = (head('Team registered') + + ('

Congratulations, %s is now registered. Go back to the front page and start playing!

' % (team, config.url(''))) + + foot()) print(html) From ee26b2876d16cc9e64ccdf7610737cc130a1e4dc Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Wed, 7 Oct 2009 10:50:18 -0600 Subject: [PATCH 03/32] Assorted changes to get my badmath and tanks working on the ctf server. --- badmath/Gyopi.py | 16 +++++++++++----- badmath/badmath.tce | Bin 4492 -> 4574 bytes badmath/fake | 3 ++- badmath/run | 2 +- tanks/Makefile | 5 ++--- tanks/run | 6 ++++-- tanks/t.py | 3 +++ tanks/www/docs.cgi | 2 +- tanks/www/errors.cgi | 34 +++++++++++++++++----------------- tanks/www/submit.cgi | 30 ++++++++++++++++-------------- 10 files changed, 57 insertions(+), 44 deletions(-) create mode 100644 tanks/t.py diff --git a/badmath/Gyopi.py b/badmath/Gyopi.py index 47cef85..c36bdce 100644 --- a/badmath/Gyopi.py +++ b/badmath/Gyopi.py @@ -40,6 +40,7 @@ class Gyopi(irc.Bot): self._lvl = 0 self._flag.set_flag( self.FLAG_DEFAULT ) + self._tokens = [] self._lastAttempt = {} self._affiliations = {} self._newPuzzle() @@ -62,7 +63,7 @@ class Gyopi(irc.Bot): self.last_tb = '%s %s %s' % (t, v, infostr) print(self.last_tb) - def cmd_join(self, sender, forum, addl): + def cmd_JOIN(self, sender, forum, addl): """On join, announce who has the flag.""" if sender.name() in self.nicks: self._tellFlag(forum) @@ -150,11 +151,11 @@ class Gyopi(irc.Bot): self._tokens[user].remove(token) - def cmd_privmsg(self, sender, forum, addl): + def cmd_PRIVMSG(self, sender, forum, addl): text = addl[0] who = sender.name() if text.startswith('!'): - parts = text[1:].lower().split(' ', 1) + parts = text[1:].split(' ', 1) cmd = parts[0] if len(parts) > 1: args = parts[1] @@ -179,6 +180,7 @@ class Gyopi(irc.Bot): elif cmd.startswith('h'): # Help forum.msg('Goal: Help me with my math homework, FROM ANOTHER DIMENSION!') + forum.msg('Order of operations is always left to right.') #forum.msg('Goal: The current winner gets to control the contest music.') forum.msg('Commands: !help, !flag, !register [TEAM], !solve SOLUTION,!? EQUATION, !ops, !problem, !who') elif cmd.startswith('prob'): @@ -208,11 +210,15 @@ class Gyopi(irc.Bot): # self._giveToken(who, sender) self._saveState() else: - forum.msg('%s: %s != %s' % (who, attempt, answer)) forum.msg('%s: That is not correct.' % who) # Test a simple one op command. elif cmd.startswith('?'): + if not args: + forum.msg('%s: Give me an easier problem, and I\'ll ' + 'give you the answer.' % who) + return + try: tokens = badmath.parse(''.join(args)) except (ValueError) as msg: @@ -253,7 +259,7 @@ if __name__ == '__main__': help='Flag server password') p.add_option('-d', '--path', dest='path', default='/var/lib/badmath', help='Path to where we can store state info.') - p.add_option('-c', '--channel', dest='channel', default='+badmath', + p.add_option('-c', '--channel', dest='channel', default='#badmath', help='Which channel to join') opts, args = p.parse_args() diff --git a/badmath/badmath.tce b/badmath/badmath.tce index ec0516ab8ef924ae1e2181836425ae9ac0c6e1e6..fab58178fe612edb15f4cd3e0cf70ed3620983b7 100644 GIT binary patch literal 4574 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 I4FGrm03GM{Y5)KL literal 4492 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 diff --git a/badmath/fake b/badmath/fake index 5e3c978..85d3f2c 100644 --- a/badmath/fake +++ b/badmath/fake @@ -10,5 +10,6 @@ dev=16,ino=87557238,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87557239,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87557240,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87573208,mode=100755,uid=0,gid=0,nlink=1,rdev=0 +dev=16,ino=87573213,mode=100755,uid=0,gid=0,nlink=1,rdev=0 +dev=16,ino=87573285,mode=100755,uid=0,gid=0,nlink=1,rdev=0 dev=16,ino=87573433,mode=100755,uid=0,gid=0,nlink=1,rdev=0 -dev=16,ino=87573456,mode=100755,uid=0,gid=0,nlink=1,rdev=0 diff --git a/badmath/run b/badmath/run index 4477536..d5c3b2b 100755 --- a/badmath/run +++ b/badmath/run @@ -5,4 +5,4 @@ DATA_PATH=/var/lib/badmath mkdir -p $DATA_PATH -exec envuidgid ctf python3.0 usr/lib/ctf/badmath/Gyopi.py --data=$DATA_PATH +exec envuidgid ctf python3 /usr/lib/ctf/badmath/Gyopi.py --path=$DATA_PATH diff --git a/tanks/Makefile b/tanks/Makefile index cb27c38..b769a30 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -17,9 +17,8 @@ target: $(INSTALL) AI/medium/* target/var/lib/tanks/ai/medium/ $(INSTALL) AI/hard/* target/var/lib/tanks/ai/hard/ - $(INSTALL) -d target/var/lib/www/tanks/ - $(INSTALL) www/* target/var/lib/www/tanks/ - $(FAKE) ln -s target/var/lib/tanks/ target/var/lib/www/data + $(INSTALL) -d target/usr/lib/www/tanks/ + $(INSTALL) www/* target/usr/lib/www/tanks/ $(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/ diff --git a/tanks/run b/tanks/run index a8157be..8457d0b 100755 --- a/tanks/run +++ b/tanks/run @@ -2,5 +2,7 @@ [ -f /var/lib/ctf/disabled/tanks ] && exit 0 -envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 & -envuidgid ctf report_score.py +ln -s /var/lib/tanks /usr/lib/www/tanks/results + +envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1 & +envuidgid ctf report_score.py 2>&1 diff --git a/tanks/t.py b/tanks/t.py new file mode 100644 index 0000000..7a2285d --- /dev/null +++ b/tanks/t.py @@ -0,0 +1,3 @@ +import sys + +print >> sys.stderr, 'hello' diff --git a/tanks/www/docs.cgi b/tanks/www/docs.cgi index 8ceb4d0..26306e3 100755 --- a/tanks/www/docs.cgi +++ b/tanks/www/docs.cgi @@ -7,7 +7,7 @@ import os import sys try: - from Tanks import Program, setup, conditions, actions, docs + from tanks import Program, setup, conditions, actions, docs except: path = os.getcwd().split('/') path.pop() diff --git a/tanks/www/errors.cgi b/tanks/www/errors.cgi index 1359cc9..f61d2bc 100755 --- a/tanks/www/errors.cgi +++ b/tanks/www/errors.cgi @@ -1,9 +1,10 @@ -#!/usr/bin/python +#!/usr/bin/python3 -print """Content-Type: text/html\n\n""" -print """\n\n""" +print("""Content-Type: text/html\n\n""") +print("""\n\n""") import cgi import cgitb; cgitb.enable() +import sys import os import Config @@ -16,18 +17,17 @@ except: try: from ctf import teams except: - import sys path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams teams.build_teams() head = open('head.html').read() % "Error Report" -print head -print open('links.html').read() +print(head) +print(open('links.html').read()) def done(): - print '' + print('') sys.exit(0) fields = cgi.FieldStorage() @@ -38,25 +38,25 @@ if team and passwd and \ path = os.path.join(Config.DATA_PATH, 'errors', quote(team)) if os.path.isfile(path): errors = open(path).readlines() - print '

Your latest errors:' - print '

' + print('

Your latest errors:') + print('

') if errors: - print '
\n'.join(errors) + print('
\n'.join(errors)) else: - print 'There were no errors.' - print '
' + print('There were no errors.') + print('
') else: - print '

No error file found.' + print('

No error file found.') done() if team and team not in teams.teams: - print '

Invalid team.' + print('

Invalid team.') if team and team in teams.teams and passwd != teams.teams[team][0]: - print '

Invalid password.' + print('

Invalid password.') -print ''' +print('''

Error report request: @@ -64,6 +64,6 @@ print ''' Password:
-
''' +''') done() diff --git a/tanks/www/submit.cgi b/tanks/www/submit.cgi index 9d57907..5dad944 100755 --- a/tanks/www/submit.cgi +++ b/tanks/www/submit.cgi @@ -1,8 +1,11 @@ -#!/usr/bin/python +#!/usr/bin/python3 +print("Content-Type: text/html\n\n") +print("""\n\n""") import cgi import cgitb; cgitb.enable() import os +import sys import Config @@ -14,21 +17,18 @@ except: try: from ctf import teams except: - import sys path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams teams.build_teams() -print """Content-Type: text/html\n\n""" -print """\n\n""" head = open('head.html').read() % "Submission Results" -print head -print "

Results

" -print open('links.html').read() +print(head) +print("

Results

") +print(open('links.html').read()) def done(): - print '' + print('') sys.exit(0) fields = cgi.FieldStorage() @@ -36,21 +36,23 @@ team = fields.getfirst('team', '').strip() passwd = fields.getfirst('passwd', '').strip() code = fields.getfirst('code', '') if not team: - print '

No team specified'; done() + print('

No team specified'); done() elif not passwd: - print '

No password given'; done() + print('

No password given'); done() elif not code: - print '

No program given.'; done() + print('

No program given.'); done() if team not in teams.teams: - print '

Team is not registered.'; done() + print('

Team is not registered.'); done() if passwd != teams.teams[team][0]: - print '

Invalid password.'; done() + print('

Invalid password.'); done() -path = os.path.join(Config.DATA_PATH, 'ai/players', encode(team) ) +path = os.path.join(Config.DATA_PATH, 'ai/players', quote(team) ) file = open(path, 'w') file.write(code) file.close() +print("

Submission Successful") + done() From 0f2b521279d4e8f96c452095cbf02415c700f5db Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 11:10:18 -0600 Subject: [PATCH 04/32] Add new-contest script --- Makefile | 1 + new-contest | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100755 new-contest diff --git a/Makefile b/Makefile index 96254d0..7b71e7a 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ target: $(PYC) $(INSTALL) -d $(DESTDIR)/usr/sbin $(INSTALL) ctfd.py $(DESTDIR)/usr/sbin + $(INSTALL) new-contest $(DESTDIR)/usr/sbin $(INSTALL) -d $(WWWDIR) $(INSTALL) index.html intro.html ctf.css grunge.png $(WWWDIR) diff --git a/new-contest b/new-contest new file mode 100755 index 0000000..2325e44 --- /dev/null +++ b/new-contest @@ -0,0 +1,18 @@ +#! /bin/sh -e + +ctime () { + stat -c %z $1 | awk '{ print $1; }' +} + +rotate () { + mv $1 $1.$(ctime $1) +} + +rotate /var/lib/ctf/puzzler.dat +rotate /var/lib/ctf/scores.dat +rotate /var/lib/ctf/passwd +rm -f /var/lib/ctf/flags/* || true + +echo "Things you may want to tweak:" +find /var/lib/ctf/disabled +find /var/lib/kevin/tokens From 8023ae12a97cc7ceb2359b3b931f2e9ca26cee16 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 12:03:44 -0600 Subject: [PATCH 05/32] Mention rules in index --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 50fce64..285121e 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@

Welcome

    -
  1. Read the introduction to this event
  2. +
  3. Read the introduction and rules
  4. Register your team
  5. View the score board
From baeb1602b547483573310b846648bba575188e54 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 12:05:43 -0600 Subject: [PATCH 06/32] Add hint to compaq 100 --- puzzles/compaq/100/index.html | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 puzzles/compaq/100/index.html diff --git a/puzzles/compaq/100/index.html b/puzzles/compaq/100/index.html new file mode 100755 index 0000000..aa12b2c --- /dev/null +++ b/puzzles/compaq/100/index.html @@ -0,0 +1,3 @@ +What tools do you have for dealing with this file? You will need one +that you may not have used before. + From ced55fa9a1b4b19170b6111435f95e57f72fd5d3 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 12:08:00 -0600 Subject: [PATCH 07/32] Make the hint more subtle --- puzzles/compaq/100/index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) mode change 100755 => 100644 puzzles/compaq/100/index.html diff --git a/puzzles/compaq/100/index.html b/puzzles/compaq/100/index.html old mode 100755 new mode 100644 index aa12b2c..672516c --- a/puzzles/compaq/100/index.html +++ b/puzzles/compaq/100/index.html @@ -1,3 +1,2 @@ -What tools do you have for dealing with this file? You will need one -that you may not have used before. +Recovery, while not strictly necessary, may be tremendously helpful. From 00de19c0784940269236670e58ff7d54bef666ee Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Wed, 7 Oct 2009 12:28:33 -0600 Subject: [PATCH 08/32] teams.py now writes the team color to file. --- ctf/teams.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ctf/teams.py b/ctf/teams.py index 29ed25b..90b6216 100755 --- a/ctf/teams.py +++ b/ctf/teams.py @@ -28,9 +28,7 @@ def build_teams(): f = open(passwdfn) for line in f: line = line.strip() - team, passwd = [unquote(v) for v in line.strip().split('\t')] - color = team_colors.pop(0) - team_colors.append(color) + team, passwd, color = map(unquote, line.strip().split('\t')) teams[team] = (passwd, color) except IOError: pass @@ -53,10 +51,15 @@ def exists(team): return team in teams def add(team, passwd): + build_teams() + color = team_colors[len(teams)%len(team_colors)] + + assert team not in teams, "Team already exists." + f = open(passwdfn, 'a') fcntl.lockf(f, fcntl.LOCK_EX) f.seek(0, 2) - f.write('%s\t%s\n' % (quote(team), quote(passwd))) + f.write('%s\t%s\t%s\n' % (quote(team), quote(passwd), quote(color))) def color(team): t = teams.get(team) @@ -66,6 +69,3 @@ def color(team): if not t: return '888888' return t[1] - - - From 424af0bbcdda0f553859d2e609b726f9c6746883 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 12:53:50 -0600 Subject: [PATCH 09/32] Add run.log.ctfd --- run.log.ctfd | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 run.log.ctfd diff --git a/run.log.ctfd b/run.log.ctfd new file mode 100755 index 0000000..820d402 --- /dev/null +++ b/run.log.ctfd @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t ctfd From a6a4657a2ee6d04de847d62d0baf7fef04996712 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 13:00:59 -0600 Subject: [PATCH 10/32] Add 50 point compaq puzzle --- .../compaq/50/adddbafb502355634d9ef10e1848cf52 | Bin 0 -> 2011 bytes puzzles/compaq/50/key | 1 + 2 files changed, 1 insertion(+) create mode 100644 puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 create mode 100644 puzzles/compaq/50/key diff --git a/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 b/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 new file mode 100644 index 0000000000000000000000000000000000000000..97e2900b1cdd05f68101657586c76a35a02151c4 GIT binary patch literal 2011 zcmb`E_g52!7RPNXL$D%4hQI@Xycm?7fYBNXB4MXN5W*%%kl_F{s2J#p6a*O(Mv4$4 z7(iqXK~_SE%z$h$kfE_sh9N)*N&=zv^qtp#pg-O3J>Sp0=X~$gdmt}=^5clhzVAYz zXf*heIRt7MhKAaNgKexVPk~Qi!o#5$h;^t9dLN$^?KSmTcVOK}Qx4zTi1(6J3-3N| zlUAV*nh0x~F(@;TQL?cx@c71T@QSh&x~cCy*Bf=Bx-63UEN(9Df~r&g6E9?qqv8kj zeRJivDMn8Ec6rH7nG9QKC88CcIGzKOkav=~0zpBWQdUG`Ex)_cmWpUTfCk9%51|_g zvt7Df#7>){Bd-(=9dvV12pCmxI;3#uNtSV!dSt6~=?k42yAZsD%KWxJY0G$Cm3#*G za)%CRJg{Oud{cmu;hc#C2Pqox5A`#|4Q5O^6gi+ zgkokIM+Gs^3=P`VB7Eqsjv&|8`z&sF^lzjMbgE|G7==;T&i7f{fhUs%#5h_^8Z)B2 zN`qGGUdeUIk4v>JAA{|A-7`TKn{VF615J0cVnR^@zbH|i zuBoAEFkeT7^MWxPc78VVB4Q4E zLNj`G3xi_EhTRRV!2P@q(25Dw9*wAkLHmdxB5L&Y@>F+$hix*E(W#24eXWS7bFCyh zdk}ru>nqFJ=!z3|M;RtDC>^CYLibTRO0gE>IxPkmJc?;potuh^5=C>Ti)pMx+L1ng zc5L(w0)Bmcthr>vv2dz67trKTI7mo6nfq}h=2H5}zxz0G_Mi&N!1a>zxx&FX%*}s{ zJJw%$71ZQIKvT)%tiO5iUw)%DEu8HWwfS2^62LPFKdl74X|Q!_Qu>lzGv~lw3B*0VNWdf?4>IPReM-@bjJsO= z$6DNn$EfZgzE#rY*q(x;0^isTe6V1?>aw(bkJ)OSAOs1!v0J}%BKOR=tyA%=Ou=DwHWI$h z&&b>uC-JJ~UUMnba(sMB2qnEp*j>Z6c`%6(UFL>SeIl?D69y3Y;4NmSkX&K~2T@mb zgIfrkU2&>fwFMnV=iS>(Q68N@yCid%Zy;yw{g@P9(+tqrtVoNtwJ2bj6gQX2D#d@Q zu=hSw+ut<~d5+!=G-GD4^QEHFnfD8Ac|wjy^Igl-mpITUtOuCnUmY%7d&c6(J6}CJ0d;b49!+V5D{bB2E=-RE-^OH06ViN-z2iYwK-GiDKP45)kuuLPkwsR>tA5FQ52v=( z$Zd5Q<$UE8^_<&YX?qWjn&>I0?M}d+GOz3C$m~frKmH zZjjHHXc+Wfe=S7mL3xpPD39N10%c?k)9#K~1eqP%grtx|v zT|gdE0|F9#f*hq(9P^N-R1g|Sx`C(5uGCBN8J|bQ@UiyK<@TNJ1x7GeI?*C$==XqX z_RySzuo{{j$kic9N(qzTQQ`(;gvNSjJp8<>IJgr4(_-X|4Rx%kiVQ)$o$&ui-_6(j zes)X#X;Nl6+}ok Date: Wed, 7 Oct 2009 13:18:30 -0600 Subject: [PATCH 11/32] Added 'fake' to .gitignore. Removed ctf.teams dependency from Pflanzarr --- .gitignore | 1 + tanks/lib/Pflanzarr.py | 54 ++++++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index dd4952d..5439071 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.dat *.swp passwd +fake target/ puzzler/ ctf.tce diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 4f31ca1..c908815 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -9,19 +9,12 @@ from urllib import unquote, quote from PIL import Image, ImageColor, ImageDraw -try: - from ctf import teams -except: - import sys - path = '/home/pflarr/repos/gctf/' - sys.path.append(path) - from ctf import teams -teams.build_teams() - import Tank class Pflanzarr: + TEAMS_FILE = '/var/lib/ctf/passwd' + FRAME_DELAY = 15 SPACING = 150 @@ -42,12 +35,14 @@ class Pflanzarr: if not os.path.exists(self._gameDir): os.mkdir(self._gameDir) + colors = self._getColors() + tmpPlayers = os.listdir(self._playerDir) players = [] for p in tmpPlayers: p = unquote(p) if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))\ - and p in teams.teams: + and p in colors: players.append(p) AIs = {} @@ -73,7 +68,7 @@ class Pflanzarr: self._board = (cols*self.SPACING, rows*self.SPACING) while len(players) < cols*rows: - players.append('#default') + players.append(None) self._tanks = [] for i in range(cols): @@ -82,13 +77,13 @@ class Pflanzarr: startY = j*self.SPACING + self.SPACING/2 player = random.choice(players) players.remove(player) - if player == '#default': + if player == None: color = '#a0a0a0' else: - color = '#%s' % teams.teams[player][1] + color = tank = Tank.Tank( player, (startX, startY), color, self._board, testMode=True) - if player == '#default': + if player == None: tank.program(random.choice(defaultAIs)) else: tank.program(AIs[player]) @@ -156,10 +151,12 @@ class Pflanzarr: if tank in kills[tank]: kills[tank].remove(tank) - self._saveResults(kills) for tank in self._tanks: self._outputErrors(tank) self._makeMovie() + # This needs to go after _makeMovie; the web scripts look for these + # files to see if the game has completed. + self._saveResults(kills) def _killTanks(self, tanks, reason): for tank in tanks: @@ -219,7 +216,8 @@ class Pflanzarr: html.append('') - if winner.name != '#default': + # Write a blank file if the winner is a default tank.. + if winner.name != None: winnerFile.write(tanks[0].name) winnerFile.close() @@ -250,7 +248,7 @@ class Pflanzarr: def _outputErrors(self, tank): """Output errors for each team.""" - if tank.name == '#default': + if tank.name == None: return if tank._program.errors: @@ -378,7 +376,27 @@ class Pflanzarr: defaultAIs.append( file.read() ) return defaultAIs - + + def _getColors(self): + """Get the team colors from the passwd file. The passwd file location + is set by self.TEAMS_FILE. Returns a dictionary of players->color""" + errorColor = '#ffffff' + + try: + file = open(self.TEAMS_FILE) + except: + return {}.fromkeys(players, errorColor) + + colors = {} + for line in file: + try: + team, passwd, color = map(unquote, line.split('\t')) + colors[team] = '#%s' % color + except: + colors[team] = errorColor + + return teams + def _getGameNum(self): """Figure out what game number this is from the past games played.""" From ef78f6da9b4b4828d1693ad004885ba966bf1cc9 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 13:29:00 -0600 Subject: [PATCH 12/32] Make ctfd reap zombies --- ctfd.py | 2 ++ puzzles/survey/1000000/submit.cgi | 1 + 2 files changed, 3 insertions(+) diff --git a/ctfd.py b/ctfd.py index 99071b5..bd34ea5 100755 --- a/ctfd.py +++ b/ctfd.py @@ -4,6 +4,7 @@ import asyncore import os import sys import optparse +import signal from ctf import pointsd from ctf import flagd from ctf import histogram @@ -38,6 +39,7 @@ def main(): pointsrv = pointsd.start() flagsrv = flagd.start() + signal.signal(signal.SIGCHLD, sigchld) s = pointsrv.store slen = 0 while True: diff --git a/puzzles/survey/1000000/submit.cgi b/puzzles/survey/1000000/submit.cgi index bf1bbc4..58549a7 100755 --- a/puzzles/survey/1000000/submit.cgi +++ b/puzzles/survey/1000000/submit.cgi @@ -8,6 +8,7 @@ if f.getfirst('submit'): print() print('Thanks for filling in the survey.') print() + print(dir(f)) print('The key is:') print(' quux blorb frotz') else: From 597323eb365d7bd0dc8a870bdfbee62047a6cf9d Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Wed, 7 Oct 2009 13:53:51 -0600 Subject: [PATCH 13/32] Tanks fixes. --- .gitignore | 1 + tanks/Makefile | 2 +- tanks/lib/Pflanzarr.py | 2 +- tanks/run | 4 ++-- tanks/run_tanks.py | 2 ++ 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 5439071..d18b70c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.pyc *.dat *.swp +*.tce passwd fake target/ diff --git a/tanks/Makefile b/tanks/Makefile index b769a30..dd24893 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -24,7 +24,7 @@ target: $(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) -d target/var/service/tanks - $(INSTALL) run target/var/service/tanks/run + $(INSTALL) run run_tanks.py target/var/service/tanks/ $(INSTALL) -d target/var/service/tanks/log/ $(INSTALL) log.run target/var/service/tanks/log/run diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index c908815..1e05626 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -80,7 +80,7 @@ class Pflanzarr: if player == None: color = '#a0a0a0' else: - color = + color = colors[player] tank = Tank.Tank( player, (startX, startY), color, self._board, testMode=True) if player == None: diff --git a/tanks/run b/tanks/run index 8457d0b..25540a2 100755 --- a/tanks/run +++ b/tanks/run @@ -4,5 +4,5 @@ ln -s /var/lib/tanks /usr/lib/www/tanks/results -envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1 & -envuidgid ctf report_score.py 2>&1 +exec envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1 & +#envuidgid ctf report_score.py 2>&1 diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index 4263e15..e82be10 100644 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -15,6 +15,8 @@ try: time.sleep( diff - T ) except: + import traceback + traceback.print_exc() print 'Usage: python2.6 run_tanks.py data_dir easy|medium|hard max_turns' From ca86e03fd3c6f244dc4d13bbdfc62d47842ebb9c Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Wed, 7 Oct 2009 13:58:23 -0600 Subject: [PATCH 14/32] More tanks fixes. --- tanks/Makefile | 4 ++++ tanks/lib/Pflanzarr.py | 2 +- tanks/run_tanks.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tanks/Makefile b/tanks/Makefile index dd24893..26715cd 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -3,12 +3,16 @@ INSTALL = $(FAKE) install -o 100 all: tanks.tce +push: tanks.tce + netcat -l -q 0 -p 3333 < tanks.tce + tanks.tce: target $(FAKE) sh -c 'cd target && tar -czf - .' > $@ target: $(INSTALL) -d target/var/lib/tanks/ $(INSTALL) -d target/var/lib/tanks/results/ + $(INSTALL) -d target/var/lib/tanks/errors/ $(INSTALL) -d target/var/lib/tanks/ai/easy $(INSTALL) -d target/var/lib/tanks/ai/medium $(INSTALL) -d target/var/lib/tanks/ai/hard diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 1e05626..0922d49 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -395,7 +395,7 @@ class Pflanzarr: except: colors[team] = errorColor - return teams + return colors def _getGameNum(self): """Figure out what game number this is from the past games played.""" diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index e82be10..47b31ed 100644 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -7,7 +7,7 @@ T = 60*5 try: while 1: start = time.time() - p = Pflanzarr(sys.argv[1], sys.argv[2]) + p = Pflanzarr.Pflanzarr(sys.argv[1], sys.argv[2]) p.run(int(sys.argv[3])) diff = time.time() - start From 742d1a0c35897a0eeed36d327db12daee303f7d2 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 15:16:15 -0600 Subject: [PATCH 15/32] Display what points your team has already made --- ctf.css | 4 ++++ ctf/config.py | 20 ++++++++++++++++++++ ctf/puzzler.py | 48 +++++++++++++++++++++--------------------------- ctf/register.py | 8 ++++---- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/ctf.css b/ctf.css index b9aa22b..4131ef8 100644 --- a/ctf.css +++ b/ctf.css @@ -77,3 +77,7 @@ p { margin-bottom: 20px; color: #f4f4f4; } + +.solved { + text-decoration: line-through; +} \ No newline at end of file diff --git a/ctf/config.py b/ctf/config.py index 034eb71..56ab872 100755 --- a/ctf/config.py +++ b/ctf/config.py @@ -66,3 +66,23 @@ def datafile(filename): def url(path): return base_url + path + +def start_html(title): + if os.environ.get('GATEWAY_INTERFACE'): + print('Content-type: text/html') + print() + print(''' + + + + %s + + + +

%s

+''' % (title, css, title)) + +def end_html(): + print('') diff --git a/ctf/puzzler.py b/ctf/puzzler.py index 48e487e..e6d44a6 100755 --- a/ctf/puzzler.py +++ b/ctf/puzzler.py @@ -27,7 +27,7 @@ cat_re = re.compile(r'^[a-z]+$') points_re = re.compile(r'^[0-9]+$') def dbg(*vals): - print('<--: \nContent-type: text/html\n\n-->
')
+    print('
')
     print(*vals)
     print('
') @@ -59,32 +59,16 @@ passwd = f.getfirst('w', passwd) key = f.getfirst('k') def start_html(title): - if os.environ.get('GATEWAY_INTERFACE'): - print('Content-type: text/html') - if team or passwd: - c = http.cookies.SimpleCookie() - if team: - c['team'] = team - if passwd: - c['passwd'] = passwd - print(c) - print() - print(''' - - - - %s - - - -

%s

-''' % (title, config.css, title)) - -def end_html(): - print('') + if team or passwd: + c = http.cookies.SimpleCookie() + if team: + c['team'] = team + if passwd: + c['passwd'] = passwd + print(c) + config.start_html(title) +end_html = config.end_html def safe_join(*args): safe = list(args[:1]) @@ -125,7 +109,17 @@ def show_puzzles(cat, cat_dir): if puzzles: print('
    ') for p in puzzles: - print('
  • %d
  • ' % (base_url, cat, p, p)) + cls = '' + try: + if p in points_by_team[(team, cat)]: + cls = 'solved' + except KeyError: + pass + print('
  • %(points)d
  • ' % + {'base': base_url, + 'cat': cat, + 'points': p, + 'class': cls}) if p > opened: break print('
') diff --git a/ctf/register.py b/ctf/register.py index b41a7f2..95e8e92 100755 --- a/ctf/register.py +++ b/ctf/register.py @@ -33,7 +33,7 @@ def main(): pw = f.getfirst('pw') confirm_pw = f.getfirst('confirm_pw') - html = string.Template(head('Team Registration') + + html = string.Template(config.start_html('Team Registration') + ('''

Pick a short team name: you'll be typing it a lot. @@ -58,7 +58,7 @@ def main(): ''' % config.url('register.cgi')) + - foot()) + config.end_html()) if not (team and pw and confirm_pw): # If we're starting from the beginning? html = html.substitute(team_error='', @@ -71,9 +71,9 @@ def main(): pw_match_error='Passwords do not match') else: teams.add(team, pw) - html = (head('Team registered') + + html = (config.start_html('Team registered') + ('

Congratulations, %s is now registered. Go back to the front page and start playing!

' % (team, config.url(''))) + - foot()) + config.end_html()) print(html) From 31e2d517c363bf4d0347ad6e0d997ecccf798d8d Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 15:45:02 -0600 Subject: [PATCH 16/32] Make survey record answers --- Makefile | 3 ++ .../1000000/{submit.cgi => ,submit.cgi} | 10 ++++- puzzles/survey/1000000/,survey.html | 44 +++++++++++++++++++ puzzles/survey/1000000/index.html | 39 ++-------------- 4 files changed, 60 insertions(+), 36 deletions(-) rename puzzles/survey/1000000/{submit.cgi => ,submit.cgi} (57%) create mode 100644 puzzles/survey/1000000/,survey.html diff --git a/Makefile b/Makefile index 7b71e7a..d644fd8 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ all: ctf.tce target: $(PYC) $(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf + + $(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf/survey + $(INSTALL) -d $(DESTDIR)/var/lib/ctf/disabled touch $(DESTDIR)/var/lib/ctf/disabled/survey diff --git a/puzzles/survey/1000000/submit.cgi b/puzzles/survey/1000000/,submit.cgi similarity index 57% rename from puzzles/survey/1000000/submit.cgi rename to puzzles/survey/1000000/,submit.cgi index 58549a7..39a9a08 100755 --- a/puzzles/survey/1000000/submit.cgi +++ b/puzzles/survey/1000000/,submit.cgi @@ -1,6 +1,8 @@ #! /usr/bin/env python3 import cgi +import time +import os f = cgi.FieldStorage() if f.getfirst('submit'): @@ -8,7 +10,13 @@ if f.getfirst('submit'): print() print('Thanks for filling in the survey.') print() - print(dir(f)) + try: + fn = '/var/lib/ctf/survey/%s.%d.%d.txt' % (time.strftime('%Y-%m-%d'), time.time(), os.getpid()) + o = open(fn, 'w') + for k in f.keys(): + o.write('%s: %r\n' % (k, f.getlist(k))) + except IOError: + pass print('The key is:') print(' quux blorb frotz') else: diff --git a/puzzles/survey/1000000/,survey.html b/puzzles/survey/1000000/,survey.html new file mode 100644 index 0000000..0ed037b --- /dev/null +++ b/puzzles/survey/1000000/,survey.html @@ -0,0 +1,44 @@ + + + + + Survey + + + +
+
    +
  • + Did you have any trouble figuring out how to play? + +
  • + +
  • + How difficult were the puzzles? + +
  • +
+ +

+ Please use the provided space for any additional comments. +

+ +

+ Thanks for your feedback! We hope you had fun and learned + something! +

+ +
+ + + diff --git a/puzzles/survey/1000000/index.html b/puzzles/survey/1000000/index.html index 2b39091..aa2727d 100644 --- a/puzzles/survey/1000000/index.html +++ b/puzzles/survey/1000000/index.html @@ -4,38 +4,7 @@ recieve a key redeemable for ONE MILLION POINTS.

-
- Survey - -
-
    -
  • - Did you have any trouble figuring out how to play? - -
  • - -
  • - How difficult were the puzzles? - -
  • -
- -

- Please use the provided space for any additional comments. -

- -

- Thanks for your feedback! We hope you had fun and learned - something! -

- -
-
+ + Survey + From b5c517e2c08f6d298701f0cee3f04b63766822e7 Mon Sep 17 00:00:00 2001 From: Curt Hash Date: Wed, 7 Oct 2009 16:54:47 -0600 Subject: [PATCH 17/32] pollster thing --- pollster/pollster.py | 145 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100755 pollster/pollster.py diff --git a/pollster/pollster.py b/pollster/pollster.py new file mode 100755 index 0000000..8ac8160 --- /dev/null +++ b/pollster/pollster.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import time +import socket +import urllib.request + +DEBUG = True +POLL_INTERVAL = 2 +IP_DIR = 'iptest/' +REPORT_PATH = 'iptest/pollster.html' + +def socket_poll(ip, port, msg): + ''' Connect via socket to the specified (ip, port), send + the specified msg and return the response or None if something + went wrong. ''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: + sock.connect((ip, port)) + except Exception as e: + return None + + sock.send(msg) + resp = sock.recv(1024) + if len(resp) == 0: + return None + + sock.close() + + resp = resp.decode('utf-8') + return resp + +# PUT POLLS FUNCTIONS HERE +# Each function should take an IP address and return a team name or None +# if (a) the service is not up, (b) it doesn't return a valid team name. + +def poll_fingerd(ip): + ''' Poll the fingerd service. ''' + resp = socket_poll(ip, 79, b'flag\n') + if resp is None: + return None + return resp.strip('\r\n') + +def poll_noted(ip): + ''' Poll the noted service. ''' + resp = socket_poll(ip, 4000, b'rflag\n') + if resp is None: + return None + return resp.strip('\r\n') + +def poll_catcgi(ip): + ''' Poll the cat.cgi web service. ''' + url = urllib.request.urlopen('http://%s/cat.cgi/flag' % ip) + data = url.read() + if len(data) == 0: + return None + return data.strip('\r\n') + +def poll_tftpd(ip): + ''' Poll the ftp service. ''' + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.connect((ip, 69)) + + sock.send(b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00') + resp = sock.recv(1024) + if len(resp) <= 5: + return None + + sock.close() + + return resp[4:].decode('utf-8').strip('\r\n') + +# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED +POLLS = { + 'fingerd' : poll_fingerd, + 'noted' : poll_noted, + 'catcgi' : poll_catcgi, + 'tftpd' : poll_tftpd, +} + +ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}') + +# loop forever +while(True): + + # check that IP_DIR is there, exit if it isn't + if not os.path.isdir(IP_DIR): + sys.stderr.write('directory %s does not exist or is not readable\n' % IP_DIR) + sys.exit(1) + + # gather the list of IPs to poll + ips = os.listdir(IP_DIR) + results = {} + for ip in ips: + + # check file name format is ip + if ip_re.match(ip) is None: + continue + + #os.remove(os.path.join(IP_DIR, ip)) + + results[ip] = {} + + if DEBUG is True: + print('ip: %s' % ip) + + # perform polls + for service,func in POLLS.items(): + team = func(ip) + if team is None: + team = 'dirtbags' + + results[ip][service] = team + + if DEBUG is True: + for k,v in results[ip].items(): + print('\t%s - %s' % (k,v)) + + if DEBUG is True: + print('+-----------------------------------------+') + + # allocate points + + # generate html report + out = open(REPORT_PATH, 'w') + out.write('\n<head>Polling Results</head>\n') + out.write('\n

Polling Results

\n') + + for ip in results.keys(): + out.write('

%s

\n' % ip) + out.write('\n') + out.write('\n') + for service,flag_holder in results[ip].items(): + out.write('\n' % (service, flag_holder)) + out.write('
Service NameFlag Holder
%s%s
\n') + + out.write('\n\n') + out.close() + + # sleep until its time to poll again + time.sleep(POLL_INTERVAL) + From f66693068889485b3a705abcac6acd0bc96a3a07 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 17:32:36 -0600 Subject: [PATCH 18/32] Add heartbeatd --- heartbeatd/in.heartbeatd | 8 ++++++++ heartbeatd/run.heartbeat | 9 +++++++++ heartbeatd/run.heartbeatd | 3 +++ 3 files changed, 20 insertions(+) create mode 100755 heartbeatd/in.heartbeatd create mode 100755 heartbeatd/run.heartbeat create mode 100755 heartbeatd/run.heartbeatd diff --git a/heartbeatd/in.heartbeatd b/heartbeatd/in.heartbeatd new file mode 100755 index 0000000..e991a6a --- /dev/null +++ b/heartbeatd/in.heartbeatd @@ -0,0 +1,8 @@ +#! /bin/sh + +case "$REMOTEADDR" in + 10.0.0.[2-254]) + touch /var/lib/pollster/$REMOTEADDR + ;; +esac + diff --git a/heartbeatd/run.heartbeat b/heartbeatd/run.heartbeat new file mode 100755 index 0000000..3e4f37e --- /dev/null +++ b/heartbeatd/run.heartbeat @@ -0,0 +1,9 @@ +#! /bin/sh + +# Busybox netcat doesn't support UDP unless you compile in desktop mode. +# No problem, traceroute can send a UDP packet too. +while true; do + # Apparently traceroute adds 1 to the base port (-p) + traceroute -m 2 -q 1 -p 8 10.0.0.1 + sleep 10 +done diff --git a/heartbeatd/run.heartbeatd b/heartbeatd/run.heartbeatd new file mode 100755 index 0000000..8241141 --- /dev/null +++ b/heartbeatd/run.heartbeatd @@ -0,0 +1,3 @@ +#! /bin/sh + +exec udpsvd 0 9 /usr/sbin/in.heartbeatd From 6f989bc018af377346f9b411ee81d73e227f7111 Mon Sep 17 00:00:00 2001 From: Danny Quist Date: Wed, 7 Oct 2009 18:30:36 -0600 Subject: [PATCH 19/32] Added new puzzles --- .../compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 | Bin 0 -> 51200 bytes puzzles/compaq/150/key | 1 + .../compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 | Bin 0 -> 31232 bytes puzzles/compaq/350/key | 1 + .../compaq/600/daa36d50d4c807634dfd13a8239046de | Bin 0 -> 26257 bytes puzzles/compaq/600/key | 1 + 6 files changed, 3 insertions(+) create mode 100755 puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 create mode 100644 puzzles/compaq/150/key create mode 100755 puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 create mode 100644 puzzles/compaq/350/key create mode 100755 puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de create mode 100644 puzzles/compaq/600/key diff --git a/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 b/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 new file mode 100755 index 0000000000000000000000000000000000000000..c20b65bbf8c52f1a751bc2f60809f02acf5f94bc GIT binary patch literal 51200 zcmeFYcTiK^*FTz`kPZY0gc5r1-B3aYX+b)n3TmW_382&%P(p}eulNYq6$PaUQUXX( zK(HWE1e8aSfZ`)k1j&!@^Ss{qpx1zM0Av3_a%x)2zOY$kZS*Jx;N(h;}XF7^#7E5-AATR{41|bPi^C= zU5V3I{>8)7i~nNI^!M(P15rRR z0AOfagWoPjK)}!zaC^|(1rOR*Q2$H6U4?E-|Dpdu0C`*apL4be44|_A75Fs{T|Eh+z`G3aW zv5o(E82%5U{#QSfe^vh(+<)%T_5tYrSM$Fa6#!`e-@^Z2`TzP3m>L)v1GfFEj1dEg z-8Jx$zF?tRtnv;B5VUSz+5;d!0}1fiSQLo@c?;|X^tRKx3)eQbCjo{CjEV>F3)e9X zlYiQ1b4UQpi_?YfDO|>u9&k`vfE=IHr$|HIK;U2{nhBp@meR0c=O+$)XT(2?-6fh# znX-p)T{d|jTx$t*RhqMoWTAM0o82pqYNiULumD~J%@Od_bdPvhe@Ark0@NTpFEzw!M*)Ck!vpOAvJ8-4xAHdHnHoD{5bDTTrh;G&zrz8< zk|l_XRu=QXEIda5;dDUcSg&{@m(EgB`|vb}HGX-q9oXpvWD<)(ia^&d2kq$%T0pZ2 z8PN5;JYrN%LhH9dsqUy}E$Sd)Q9mp1+YtZ{vMOMz6$7aS>4Kbk9UnaFI~1yYT@A(@ z7lC=eCehYbE_jHu#Ot_tr#@-b&=|;O&hZ>JtxXD|)WiX+Ne5+AnUXL#Gk$>YAll<) z(zl{3=oH)fvnhRii4#v`GCN{o{js=NULb(x}E3RlN zD;ApA{)=pwiq*XGz#Qet*rky6jatqf7WjOhN4YRy^0 zC&3>e`$Ib$0ac&I8?GJgEN}0J2E`L1&%-7D$`vVy3t^P*95}DLQZ^oi>72zFxfv@0M#H-f$(rYK8kD(tOj(O1M&R)!pJP?s;jmyqae zA2eWZgQy|jIaG+`bd!TbQh-$T$O!5)gBI(MB0`R|J)WjFdFgTs9?f<)R6!HTw9ym# zmrab zGsi=DYkW2hp|AoT*(IYu;=0V@g4GOT#}V>60Yo}9HPkw*M*aTo8J~BVsi^Ek}muEwe+Kej_3de?-rhDj_tAd933OiJtIZ(xICH;yvGc`lmNfe}ApO}ICM*rh zv1K$LkI`|NI?T3wzs~vn%6Rp?GMI_h)vs>9F?^QOWBN6CWh%Kf13QfX3t;%?oR842 zASSGVvTyR|y%414d~{SNv}dk96w(vDbOH2Tf0W|H+4n^cUcMkksH2a6K*o1BdWT{5 zc0j1@DXXB<7UecDb|aeR@@;GL!*%s1D4=2yQHImVgk#gO*vQVs<)S&H%d&-}MF}0r zXph-tr7W4IrYTFR#rER4-e=D2k+ih!o&`u)4~D)$#LMSV`bNKXheEAv@px0b{)+y1 z%4W9MQ`sEShZx!P6-`@Y<+Z;}9#<|r6oaaHcl($i414fm5igUEGo>yt;WUYIf@S5v z=j28r#N8YLg97G~;F#C_s|PK-I5|}$e%llaW+3AMVF?Xp+!2dCx5jy5{S&k^25#^7 z%t<@^uC1Oh+$&Q+s{5ptq%bYLBa;ZXILui-eJR*fL|J_l4B#{?dpA7N6RYE8B|iP0 zdz*tRSdh6*#TCxN0jM(7k>p!suB`GS!aubme_ZPffrCqLG*@%1Wc4p1R)yR zlSH|fsY8>l5K7N3?#@|k|K2@_{EtUizVBguFauPp3aZFu`ax?D-{v-sBf#x=89D?H z?Jv&Eir%0?)Q;G=!FQ1Z*tx_>i9CA`mQ)vliTwn@z}p>U#(P|)@g(eE;mAk9bIH}w zo4&v3{Be+?m@YS0??Du&?=ZL^K7cX(MfCc}{^18N?Y$Cq4Z} zI8a$xRs`i2B`3kvRr-y(FE1iQZ^F(@!JtTPCgLi1vOZbY9;|`2f|<#;Q!Cl42^yAc zfpJ^^>lSEddd?sX8S-q1&UWP5Gk8|~kPZi&(pc{OR;{gU5O0e=9U-LCQz2O=_$hyT zw=JY-r=$7DUqMUB{L+*9$?!NGPPBM_w3sNSA?6g$3UFE+JVNP6dsU;J+aQXzwzbbL zyT}tWT9WWonNAhuCLMC5GltGvJr080FXSUde#PM1K&EF(fuHuyiH?i-V-Bq8=euLL zxV+C%pC6f-)em?lTb+j)6^ScBok+g8x-50acUHA6>QBG*opw7sgrWnhbRd}N?9-oO z7n);uKOM}eFddYt!H3o*Sbl%LFs<8zT{sRos`|8RCB&E4>He<$*W(5hsS(W$LD(Zu8R+!xln~-$Q}q1v3V(L&snDB$uP|xn9E#l7U0qb7+11S zOtj=PuKzd~ArOj#sei#yZbn*u8=@fFj>*yB))u=WWB#$@a-6*JuaVbaS$sG2J>qyz z&6+s!(#%@gN0CMFyOD}6j>f9`$1RS;Y3EPbtrg2b1=roxxVf1x+CTd0<$`%sd`Mm3 zsm#jdhwIn1KD;$Ha$=i4^WoeW-DHo?KiJ_P?{#-VY~|s=-8c6!GTcI~KhUYw&<2#z zpQ=K=KZI#0e?8mouZgpxWpC<+TKbR6C)DjNX@v0?%6bP*s=5_+-Dt0RdZp_Fays`P{@VBFj?$zwA;_WpasY|j~)f%cyJp{e8A2(4K$4?TQB{pF{;Q86uk zW9pfrp=h<=9XgL&l%#KhCJI9t4szt7?@BwA<&Su8nhwwv_ytX_sMz|mLK1p} z0vYICTxw{mee95;pZ0_Az6cl3z{X=jem7kl7$Ef{pw3sY5A-9(PW1v*1t5e(pwYhh zNekdFSfkPPG)akNC`Hx`{e^v{$UHLx=f_1LVUql&fm>M`fsBZVYaz*clqlYE~PhN{#k%oH(7`5WLiQw3h zR|9)(ZF@^)L@NSm1@DQ#Ij;XEkG>-*Yup0i zy?cWi#rN=p)LS~~Zh9Rh@STr>-jSyY`!LB3KTFaO_EnOWNY!lX*n>&aS+)f{hTxk5nE^L)E(zR;x;Vd# zgNg5onI8{Zk|{Dpm$W7@JHDq|hoG4f4Dr`jq*2u=pGTF~xbEmv0^G~wPwt4x8%;yj z{%EH}qXxHKh@ck%=U;==V)3;B2aOgty)9GO-E&}y`C3Ifm@_EibtgX67t?=NE{$2c zON|vXbx%vW0rLg9N`$;bY4!@b**NNb0 zsC1*?;YU04tR0x%g30RB-gw$hsY`x%u!bAC#qftd;sD^do=@Ql zlLe0Ks3FGz_Bq8v-s`Wi=0gu;c=G(lZn++x)%Ke8Z0@#n)dYT#44bn&J&_+GR$zU( zGpMXx($dlVX??cUd+8c%;=Y(AyxGgnh5c3sA-h@!+T? zitXs`N>pf$;c5aXT<4FWBe>g|$aHpKp*gj&%L$z{y2fp==zcVNlpaXP*3!s>P1i`C z)a(!!nZe$lfYwNGvn!kd6Ga9t`Wr-BeqlV%dGQH;`-A{)bR$oU9y=kUxt@lwV2 zs>A5Cg_zvv!fe6d7fd;M)X*8Ies+utG;OC?fO`Gp3$tyFXKQZCGO{lsVZVELKO{`T zrx~eNrbKh2AqP|ln+TR8V}}zJ7fwpN_pPsyCfk!8K%NTXgE#i&hcZcw{9yEL#iz$5 zmhMYQy;nxZ>bg*i>GW@;TZnT`)(U#nEzc%ye3Kbe&_WewRH(uG?IzBF&@zQ(qvp&= zg`_j#w5yGyewkHL=MxF3OCB;C-zOVpq0^ji5ENxKEm&^O8pd2i@2+}wduOP&t<-&J zBJlCM04cA43Kqo4+T+@N;9|?xG!yHeoEf9MFqXta3jdnlYGi_4Po$Ev@iID(0d3bs$y?*B?M_#R88k z;@19zHN*E#8FWW;LzTz1RvdH-6ia!(}N|L>~g zl%aj~`7>Za{<>F#SK>$N9mEx;b)&nTZluJf#350xUN8-lxOnYN#`h9R4-02m(0&NV zargt%Kj$IzA0cyIh@xHvMNC+6_Cnr9_R+mgK%gKy2JvL*%AFHaLPLbiOgMB_Y1Lsa z@9{SPHJ!lNm(&-!IvSd)yn6yJ1g(WdANRNxBm1Uz0;qI5wme6AMUN7O%B$qi#=0a&S+{jUeq1*T}egddqlU(Gs0?l@C!Nn$dFw?YW>X7zmomijPh)LIj;j* zic-_|(HXy}Vq7aNkrAhdIFz4fqK$UtY^i0xg7pDwP`5a0+Qh(Y`w)gmq(Ba8l?;Z4 zZNd?foLmTS>c@d{_4e1n^S)crDUj~;#_S30sNG`Pg$4;IOPaRQXYR7}QNXCxGrfs- z`iOgBYLGN{%pMy(?YnxnVVYr>9yfUXK^OO8(oBGA| zviEYorA8<4)Ath7NHC4ip(o>~28@1m8y<@N5SfPYIDDuik&A^exIQHFX_-4%3MRA4 z^bRH|qqFPG{jX>iT=NkA>dH6uusy}l3-{!ud9Dbhb63X05eLi~rIu4s!$}P<2^=X< z9556SKVO#?nkTQ;QQy!yS($$*iwWmBR7a=&IU^X+QRlU~nCdLOFj&}(5Z^1oUdRNw zKoB8WBxnZns|hiOaD>mlUh?P2Pq*5r!ba#)$u{-R4Yk9XdP{n>BMl27FDlH>$Dtw- ztNAU}T3Nip`$O@;uq*BEDq5TeM99_5$eO0z>^tWIax(4T`nOO8ThN@k01)r_9ne1k zhfikQP4cK)haOh%fE?F~$>=aw{-9s;^8IXtSg|CRZ<%z^bcH_+eOh$(d%-JZgBJBM zCFB#C7P5L-*N3g|-(a{Blw@&-9@!=&Lmb!I0m`SQRV5`ZR!-w?3p-Zk#FNXHzZD3g zCF0?NH7|)X7U|;|e<%Xnu`01DwZijORDJ#j#mbKeap}m?xE*!!#QjUfw2UYlqdsvB~0fLMoLwuxO1Eg^VChqHE*{K42xI(QLl7L2$RuX`jJ2y$oW1xGfXJsL=CvP5t#%^d z0af$EZm>i=id+n~v6!ufwzlSH0nG?bJMwp^#%kJ_l1*+jABetrjU1=;7H(?i_A=dO zKYSpC0%wI@{X4WJvo54+^Yw`%iWq)jI1N|dL5l^GG-kz0+;PQ)hxXaG3=33v{7in` zsC?N}Sbx3jeW&!t9@jTTq6rPvJ<3{Zr%%ki*gJ+c&^+`B>;KP=4^~$Io2Cv-yiLg3 zqT^RE!t)fAkl#V>DYS_>x={>N{-Z^;UI9#~4)V*Ep~?kAe4|&hU#|7Ii}&1j+*fiB z7NUkEA1k;VV|1Q=0dPx2rKNshk0J{x*YDI5QwbZKdlGA27uRU2g_pNSJg^*I%<8Fu zuvw_`xK@MF*ePxyfd}m=?Cna}|3z+0qd>?Ss}LU2WdlCjWfW-Ae_o~TUe9FBQvt1-&+GbUp7jzD9{OuDH-s*d8a8EH)_8>e2i&a8v;ZusU1V&V!qUTUhq{Up#U%K%jqDm|0B0sI8* zYNKDq?z#RHjM#@eZuXphZ=escpA1Q6Y9-#+HT$cHw33@&Pbz>;6mVnspm*4v^=z`` z<0B*Y7b~=ghQHAkMafGw0`$(p%)Na=M7Cb}08)%ttlif6IND)15-^Kj6->5I`o#L& z0g68jhD63M-`n4)Uer9kkF|%jfH3um47En(!iX8MWmvXNrEIP}?|jWO@G84ILQ4l& zEQci))h7#X;U(;ml3I3mc%yc|nt!y}>j;HNf6y(`oVwhyJA@S0rYCnI2#Dzt3#_Hx>p*&?v zt$fK5%b@6spSCS~1D$os^mm@gt~hYvdA;=+p<1W0LE`+uUGTaoDTJ^ zFh!<5Z|&l)gp!V`tuH5RID#GP*1=?vYGTsbAuq6Z5!Of8!gu+pdpo$=G5x|SCI6)B z{<_;SfxN-3Sk*uI)<(=AQ8N$P4K$((?L77pK9Lq>VAPI5*0OFvfO~h-8=+Qp^y!Dt zr)eJfRutV|cV^z~Wr#cvgloK==@>1%?PPLCf4vp_{(F7{uXmV?o z+s7DlUY%}P0CWg;j5+kD6a7l`BT$t;!Efdt@>E-U`)mb#ac0FJhwjpTou)WK%7tp&FJf~f0 zu6tNo)&$S`(HHgn3&*^d3!<)xp}yyZvg(BL9KNi(BpX()|DMLx2yM)M=YF;G9+PaC z|32vUJH}J#e0TYA=nctGUTRZ^J3;$w!Oyre!#9=0NpFgWKERNYS7!!Pj@5Oy@ zzxZ=9XS}xeXaWE4vb=JN(VBENgu6-Ef6M#N7M>_kQC633}S%gFw?Y`sxlW z9b0{={o=ujqB1pPpB$M_Hg5zYoJr`0u84up^*g*wIGGow!+wmtc zk>le`439`+Z~Q_ztQ}3ZWJG7mG!p{-+lp!&LX6%VOMLXc9*tQ%4&|OGWk3vC%L7(r zo~zn)LvK8C0KBAKem1FUtHE%0V{Cwi9eC`&sR#Dl~~-UIm6lZ;VntFxeYjO4-*k*sj`_%>MZH*Rz&4nrR4sZLgai)O*A8 zI44SnCxn82b`44WREFrW+YVY4M|76=<_{qZ%akzJt!!A*j0;Xndul7h`g1X6Hxsjg zqj}bN!jFU%G#imHRRLk2OIu9nL+7By=5iw`f`bqR5Lnt^DZzfp0(hcu<%jE!Ti-QH zgTl*Qf?J;ZTU)Qx3#w`t6UMy+4r7!Tk9Xv3j3@YkJ1D&e5ppaDPk===wi`_ILXJdi zin==d(|IT-<7|&hW3%8nEU_Nf zxZT1?+fp36^e?AeAKQ}!cDAS2t!KX!8rbrWDB7MFXkcQGJ~@Sh7grIcXqxG9@d(kS zOgfk`C4|rVp?~6BF^=V&===0v%ZV(noluPg9G^W3w7E|pS5IqM?KDp?3tFj?XlY3J(F1oH zxmWLSP)c3vqj7pAqk6+ig(XQg?Tf0i7-KNwRT%J4q-d<8Vd}{W6S0>E;$mH0u?QK@ zqy`y$AJayi^}=ku8uq4&l>rrFwW-4>YCc<$BvQ^lukPayboSF1K_e!bcEUw3;pBx! zHDgMZ$3z_>x5Ep`&9*|YrsL^w=ITYOl@5n{QXw4j*-#yZ@16t&NoxQmt!)PRq)WVJ zWOI04h)X$mvTf&s`0&uDHy7Fa?$@v$sqNoH@MQ1PD;(4BpbVh8%A*l7myTfJrhRy9 zb8XBqX=5!1&HGV_{Jq&IjCkUQ?bxAJylfYI-}n1T_m`D`b> zVeHb#+7IFp-qxc{r+X_w+Wd&MJ$6mb?4$dQYF)@{295X$NBe_2mDuZd?dpp}2krTg zP;fiA&&Etu{PPyea&{^7swS0>z8d7@Aon1ZB<>#Sxt{@&Th?O#0FRTS>j6Qf;Z~i_ zbe^==53IF;Y#5z~rT2}o)>k^J24Vl4qx%0d8tk8t+jJ}%g4q$$6Gi{S#=SFJn$|o1 zS)?AFN#`vR!yBjItq=emIvTZhbPEnYaBts^9>|rnAMe^#4%Mq?W@Z}Y6K}p`!6L>G z5=l7(<1dRg8SfyQ9P>Em5nJVQD}31HByyPyOA9yn!6eJUBeOA8o@TS_KMk6{|hH;Kp^H>cRC6zr?jR+|i z%^sEO10>tKvPk09^@bK=FXPCG-LdGPsaa2pTJ0djYw3mW`R0xC9E_}}+(v08@c{|@ zG}TYW=0==Ddaay<3*y8^?G2;}{2DJ~*zYLnS$O{88@X7FMz)I@myV?ubVB>MN1Ifx z&yM^W$i{8zq;?7hNyB7|cc3r&}Nt1JZR&oQfnPugAOk_^r8Jd-dhJv3I ztLFMJjQ763b&8|);NgxSZA6Yf-~s9q`@tn@~*3z<9{^m|h?Ye&j82?b=0ZWiYs0Q;%9l~F>s}Z=6J<#gGaIVpHafe9qYL7W z**?efWh20JX-1@6M?Tai^w$!OEvjK+EFTDSC-FBVflaxI6jiN>#mcQr$xw>zBEkOJ zeKNfOLO|DS%I!(?Kl8jaQUyu zc);@=W!Gb)EY2v3#ZR&qhnQ&t>hGZidR5|%JA&n0m%hDrL)Axu&EbpyjkdO3D9T7P zyz+j10mDW`RWlY{ZC3kr`N_LMWeT&hsq@iUE4pO0A)?kNHpLQ>qE20KBy;oMjAC&xL0h|EKy~vMs`{l9VEB&S_W&=ly!LsDB~h^ zWqL^x{&lCz>;+ZLGarh=He8lX(c*6KR$qxX((|E0g`Y@a`@=t5(|FVp*F0-=1Rfn3 zwI5Yark|CM4B8zCYF*w6QD5K&&c-d5tjQd`!XX2K#Q@As?CvW%)B#({&uq$yn9i=< zX`0$xqSlL|_auLSe!lB6G{?L@E!dQ9i#p7MXLlEOt^9>atQ3$4+X17~6ms_Hkz;&0R zibvG}F**{V@ZxU|@1vs7T@5FBIIc5R>%2ZUMI>4b!0O?^gDVaYcP6vg1Dd~%WF~gw znft(~+VLU;kt#~TEZMEM{u%E!g(9sYwwONB#a>u-EvLVj_Oo{RIa@$IkUEkD%(_~m zrmZ1M*wxW8{1~jRfvoeM^XngREf2W|N0{F3CK5-iPe$M#89 z1vJ*|^Nw_eU6W8Z%Md*Id?_QhKsrP3&PZ#(miKbLbyn|MlV^+HgM9J^VwsoR37-li zJea18;y6QSX!>}APchI&&f)>u1|xE45G)meogBjT?8t`hZQ~?;oJ=q<-)bk)LAD#5 z?$A7g6tXgeNg{~q9Bo*-UMc`8YS4pP%xUz9xj^TXK(*8L_KGX_!wHsu+Ug47Ph894 z#onSKFBnv)PD$bro3F57#A~@C;N*oEMCB!Eh@n#$yoGhb`XKIz8%4@4_1)IsAL=KN z5Zcqs>(rewpD2Hs|EYM!iSS+YY}~ULRnV6Sb9%q{4b|2`#ntx-!E2B_$?P<&Cyq3Z z+eA_GpSa)bpDUKcKCj~%6B`a=Cs>#Oof5;qwY9?zl3&21LaI?h8=*|iNutOPnk{BU z=nPAaE%H>y_hCjs%ZnOLy805UqZbEg@8fH4Al~R^_5W}@R<3e(*_-k8&+s%_A^f^^ zyN#KvXZWsj4}W_{hQ0WB{2|2!tsxmT9E7l$=WQ%C%@-4A&&eF`Y)c5N7@BqGdPy4z zyA4b%6r`|wInpAQO?&FzP}z62ze$5QF%oVs7v%fGdEQtbTtlp@ zTf&S7?F@uEHgLT{+bdtzk$%tW`HRtg!-Fd+3gun67( z!fMpzA|;2Ws=s;QG%Hqnem%rgx|@PE4_a}GniWc~6uCSt3A0gf=ya~0E5tT?b7h^? z6vcjTPD)u;+;%{m=6Il!SZ(ww2cn!%91Yn+kShWY%1Kx$#Tc@~w1$9%?e*qyGuuI$ zOce#-?h7eW_f2))sEt(_23XYGbqxpE%8V<%eJP|7S&Zjq`dBMLXz&A_hhd=g5DP3)isO4lWvicdoZYQU9 zho#613r~Ombo7I`6VRY*b=Ls>hX%P5=ai?iVB%x9Q71Q;ro6+iO?%H&wJbH8GKeB3 z2p@8XVe`k4xv1K_$Z788f*LbB)xGfAqMN7m#}CMZh+0}7C6kI80)P1P<`0>^D!jcU z!xRnE|C#6g@JUR*|4hF^p-)NSb4>7$fU#gN#ygpirf*e#WMKF|cO&*B?t;8PnoVeO ztzCE3iuZq4csP0?P{4!i4Ez)L$H4|HU|Y3(H+}V9U;1jBJMmQqG@{eqHNn)&1!&yc z>`70ot>ZxtoPYJkxscyDj35rDlTvv5L?cs6l=U>Rv*7oXjWnd=LoLck+MH?oouKEt zZ=LY4JSMK?mY`ama}g3>PAf$a*JN0T04TS8&MoPw*muC3(X3^^sJdJ#&{cW`cB3id^HPxEvt8|49GmItFZ1OqY=46Mab)2=UF=}C zQ@SK(ahWER&y%ip@Flo0g%Cim??por@|=01(+}RD324IhWshvEwoEI{M{t zR-d~aMw1Cs<#qaW#JKERQ;0w;D&CXJ!b-SGgt~cr86ABnaJh{xlY~h8y=>+1KtJa+ zp)BR%z3%Pg(SMB9R&8mT(`8o=id6(@DtZL@16n=f^Sce9zUJaTTv5zQ&8+Q>avJFU ztsB>DzOtlh^?aRS$DjDvdNv;?_B)!+Yo3N3-Upsh;{*lU_>+l=o@B(d7on!+nddnV zyQb-BeU{c_+HospM^+n-P3!uMV++R9l@}UU?r-x zHWaL`!Guv1&OFa{Mn;XvxFRYYKCF7*_Vy`6)onOW{E=4NXvNE04$L<0{x9xg}8VI3Z#v$k{(IsJepqc;WL4PWl> zgZwp*k!O|wol~lLSdS*YwAn6t!fT@cV&OEu$CxL1h8MI6ZLK^vw#GvoN1tU1enWQt zaTFCA*{6f!?ZbGU7M|Pj{N|yl>5-yenu6o>A*?CCxFK;|58m=zKJ zskwQ)9(ZE;?g=+t)|$9r10qLn3iLnjDf1z{|ASkG{zT%M`F4i(2xaWO9K!`%ecuro%*3T&o5{Y zdm}g#R{I79GZG(MvZ@1$e#$eWY=PcIqv-G&^TXh`gjPnrU7mEf-$ObFLS40fMEr+?|jMC`g_=KL3zYI42siC4MuK#NuQE_B=wB z>iE};NJv0>PI69u?yBcWeOC@S{7JXhMts3nlcnvl)+MRVQfpPWS?PWb)`Iv01<8TIt4p`Yc%TCuBBaqn6|``scRr%&F- z2DD7b?o;!tuax)F`w(FtM+VS$rz)$Cn`s9o7<|06O$wOpm)S>FngTSj6 zl0)gA&)q1>=~|H(zLg<@_&OU`xz->9zIDxYCwGBLHn2RpeMD14(s)m(y(hxY69J{v zy-u9OvVvXUl8DlWRaHCg%9ABsUUnr{ncF-T7m`UkEaLS0eQgSA`MwFT@J=V2q6&-8 zV_^vC=4I97jB1&CVZmOzF;UjQ<`S-_Y#^y*X)8JJd(n(<0n-2rURf zeVN{Z_}Jpvuf(;?<|9|u^0&XnbberMDiY->zBI<{VU4Z9f6819I8G^8nhy+w)5Ir6 zpi?pu8y52^8Hm6XE;xzH+J4hk<5M-S{NNpeeMTtlBf$p5Az>*42*`?sO=>rFVWb z>0nzN{o~JjxROR|xiz1U59wFr+t?kgoF+8s8z=w>c!Y=L;h%K~n?Mpf5OB3u>-ApH zsXt5!5B7S3)ZY%ZqjOH8+d?Ijf?;9~L~YGJ*EnRL@K-`}PO@mnI-b$%Ex@H;;~7m1 zt|P2P%#C-CHz3B=nasjU$tB7^X;-zZmXc8CRXa2T@JO7`WZR@F9K#7>^s;9FlOe!dagPr3_ZJi;o_IV%}I+ zqsqs~9e)7e;5R1o$cc#JLY64vymFMu5(C|sS6DG8<}&&%0KovcpG5~A2;XQfrYd z=3&aw8|;bC!+WOBFBf+X4AT_g!9~Z zWiQNN=x+9i&wH`kP2b*vTv$-pLogA25F`u>MpoHEgwBFYJCg)jd!x~$4806 zG~t&4CNF(YsFz!p>L6wks{0}&Af6m`z7Jr?{OnfPrxM`9L+biGh^`L)kvzP6+2Kaj z)z!>^oyJB7?$JhB;rA8|4(#UTxW6ni&$dctM_^z7tr`yGg1IR-R<>{~QyJfNWt32_ zj2p6WQKO{jsXm@FxEQ8p%tEQ@D7Qo(JTIqk(tNeOM;y|wZ8pd z0lL1-4~jS+xn8ey;W=ttA!MkC z{UX_Q2C$Rf7@Z(~fxpmS)L5eKuUN!qKng+ffFe25iKY|=PcPU`@o;(N*`7LF9#e-NnLDWG?9g_@}^$Yf0O+bTM zFo0W3wAbIy#8i|#jtOTxDxVsVUfAH*rG9dvK3IoJkD-2m}=z&(v=IGBS(+>YQVz(3Lw zQqTt}v{U;}jef=U;ON3ARvLe*2pG2Gd|f{3ROAVa)Fs1TH+ToI_Ncy)>QXGe&lGJ+-sF&Zl69 zl#EA1-HF=#2H7T$A@^IfBR!J~oYAI6k=CuK#E^rrlM>s%l)y5Vr4J7+N;q~2;hM_b z1gY}1Q|WryzqXUi$L~b|Ju@qM>*+C`N4$M4N~{oh{S0Ul72wiaKRdrJ*IL)ICFXp}CT z@z=R)?8r#NM68C~Y*=uA;&+aOF1lk9>2!NG^i}0G8EG>c8e~~R_L1!ZRMAnQUdHb9 z8@-V0->Z}K;tK)R!W2 zKsQ3#4^gBS+pxFbIs$ijtUl;kI)ozzK~3&)kZf)%!nW0nXNY60bHiDhEsQl4q-& zAVNn{>Q7jtbHrYT@Mb)s&y82ay@>~yXg~*-!1{G8yRHe6c|0(<`b;IxxV?R;>$_u2 zpMz%v7b7o}#Y$>PC%)~Gv6I(2Y}~2)DUBl$^`U-lHwGfcbcZoj-Cr;zF$GyhtIR|c zvFF0wG-0HnQs`9&nAecqXvw}(WxY{F-cvFMs)UxwtT^wo6kV1rSph+P!_Dbfd7ytl1%JRAr|??d z_{gG66l(Uo9&YsjC(vc{4RXTt-mc|cWD?I;anGmoNf~a@GAt@4Zm7pCu4D^VM6hKU zXdIc?x>|wJmcoLcTjO(N2~&im`U&IUaIUj>tN#e;%Y<-Tes_?IQpNC28El;Y`i#rOT}Y~OIWX#S7G{t9U8go5XWE{$F}x6%$Mqy-aG}gJ-g#NBl@*m zdE}AlEta97EuxNkAo$TcW23~<$2MT1G=d=6D7G_rsIVATygC=eXr1r9%$1(W7>z}D z4da=RIf&Q0qC#i0Jc7IOVZW*ZawP~Rz_Az30Qe*TP8NfeM{M3dK=Zr=SBzHx(K7S_cIk+w0>b-PxAP8 z-r=u}>hDmtKJUeM4En|Cm9YYO1uTgCg3}(j2vl~&V6MBsm9vtl4_hzHbf6?y_5M+9 zvsouszfTxa2P%rGmx>gNm2mZ)lB=S;kA2EF`-)*T$d#|4PCX+xH@0ZOvLVhSw+NO! zCagKTfQ<#E_ALd0?woQqj$10^XhFwGl~VYlx(WriyHD?NRWhz7LxEl5iYjpYAHUEe z)PdV(|CGcoT$7lU(|MIhkRc<{(HezjLzilf{as=OHL~8kf9;c4VB)k$^Dj?w0OX7g z|07q<|7p*?`_tEp`9#>7<#?N)Lve^2XnbfRQmd#;^T&uB3!1N62?4TJp9!shg1>npuWSkYTvh#nMUv2lN#AE&! zMb{q9^#8_p-`KD*GYoUTORkekbD2xX+{vYpv=$|8C}M2WE>=nxl?tg;DoU=YxsxuG zN>NnmE0a`0x$Nh6KIc5|_j&*EocB4Ob3UKvyk5`i^}OKm_wydF3+RjDZ1Hh{#641& z-^ScW9Wgl06$ZdubNL|)N%4g`n;viyozjwRQpsJ=5h}a0|H-q^mwEsSB=B<;X6D(-|G<6!Vc&%0^EwT7+T@#rcWbXFTrc0^i*f)@{fr+N8oX?{~H{MQHW~ z$YA*5anaLXBSU9i1cJT*)SbLsyhr3!614^n}!Ze@S1hCOPfhaY+x%LuqLCxt~uWMk}(Om^9jtD?IjDx0E; z8(7k0b_AZh?-{Tnj!1ql^c>_|E!psZ}&m&`ESI-GCMuTAan$63U zPtqOYO~H@#WY!VRNEIX109CT{^eLRf_7s&XpQ2)3q#*Q}mct?5`>UKPSlExT>=VQL z+Onh@OSJQ8UO&i8CI6&a_yn7zanE2g0lysX${|{yM0|5x5V_ZDdyHYcM@^+U;#V<2 z6}cag+C0vx&zkc_99394Yi-0?el9?u9H4W1KKmk(PTPa>RlwvbXE#r5qq9K|13 zDN&z3`Eu{MYcDm7xF$5>+~D!A9?zb*C@E%>zZCbxIV)G6``7yCfq%so9oKaoY%_n7cba_Zm_icq|lLJ^n~fJIZFM;*3vPPT0N`rWPkJ1>hpeq`xA1+an7^ zK~0oFklsCKd)Doi0J-)qz1qLx%8+=jj@i4 z>O^gxL5E&c7zVCHatQGPt$FmC{piQG&DyvnZ#J}2K2^TbR`Y=%?Q({TD5c!;ilLsz#DaZW%IzbskI-&JSn8rsgq!b` zlR7c`Tu(90-lc8Eb~-hcf9^Y8`F{0I=_E&D?K&r){$KuobqbPX7(NXv`-Yr)XAK}a z^zNVWg3*euS$S&4k{-UHjeFH9JL5?dG)!1cL0xGQn6a{1tgZ!a{PK%%e2V9zfi^o~@kYydtf3 zvj6NJwT@66E&OR6LSq2pNA&2Gud;S37wqp0_Ia<*#8a=HE_!2u(cj}yY-K%(rAIAK z$h?h(Ar$GGTV{oyMce6?7FYRh7WK||1d&}t5Rp#q9Ja{mSMA-=%pVUBE&smaCSpKC z8R=osl+cf6?87VWn}9j8vB2%hfQM8m-8JpnH_M!NOM-ZMm20T%jdNO199nB=S`PXK z*k)L@5A}Zihx6dXWk_R(OGYFIaTx(eT}JVE`$`Lxm!OByl>xVV_R^7;QNO*&(kXS< zvK-lFx5Em;06c-uFTRfd2DmhOQ~CpcNwf6ER~c^4UEd!KD)1(YJ=vaGDu3P}!^>)r z_oUVcsUcoqp@O?a@g5ldRkJU}ii0XkqOV+TM5+IfrL$Q)p6CaZFsi*dlAN zm;b0HUs%7+Feebl5q}$Ms}2CqqtyCH5>RTxS1+xD#ZodG>ys8W#c<#M@72%k8%IdD*VX$Xr( zY~<$@(KqG`-3fR^+rdN9_D9xy6lC?K<;17xXy3H&5!>u3ZK^B}(Z4hQaE#3Q7n?r5 zW;)Aj82hr#h9M~b1^<(qaFyL)PCrQC&wnT`2v(5DHmE+>D6J?q7ARQ6%|)-{E(mLW z^;re)i#s0WXz_=UX}2F#kFI)q_tomNYmk>~a8+ytwtMOE+d4-fMq(@P2?8}L3JzjJ zdQEAa{)9twWb{RZ%&>v$>>J>aPT)o?HuMV+u>CV)i44f_%%HV)c0=U5Tgq0%lV9)MLSYT~pRap|Umr(}Ixpv1z^93Yz|kJ6 zLp*~xWL;`E@$&A6%(wB|3;xiHM`B&P2SUXkHw(Krb7mt@T#55xa~CU?ofWk}Q-_3q zrY=4bq&ozUVW3O-myYcPjKY)KqYfzx;xf^zYrTJSoLdhQEyXU@VGQt;P>0n-=FGqB zi5q7xTdlu?2nKm?a3|G&;XMnN%zyZucp#IootaL(uQ~AEvV2Xtt{G#|(7d-=^9N$M zquCa$5FKEj8GdAnefpC7MzdJ6>GN^bqevYYfU+YKc&*CUlZUhW4Uowp zfz-?2xYQVauh@W$n=Yk(pSzI40By+bB@{a9pn;Qn!gNn6G+j4%(d9`GKX(kKJ0Xah z%bI!H_&rngTckJ((gWAP6PCnC`$L@gc28f~Q6c?MBcQ1~Wb|MZm>vw)E$>E*MrA)) zCCuW9$pm_tu}%L{2Qqq^EeI|q#PJ?wOO6N(GFn{uYGEH0koY3= zXX?wN+IeZH!>UI<@m2)9r1ydEf{_!kJv`d|P#JNrEwt=_i9j57c!!Rv%-2F3MTFkr zMVZol5+vlu#$B!3BlmIIyt8hi4x{BNfJ+hURuFEUtjG>IRxrKFHaT$rmi(lIG)hqs ze<3+@w7NE4VcjbR#rqT9@T7ll8g7x9lqql>6|y$9;$N^aS1 zr++#EKz`dQRgHqDI(1m48JM323Ink9`R;*I8F_T{T}XJ!dt4pS+i9VB1NA{>K(cNT#q>;G`uAj+3H{;T z;u?1VnT6KU!~xYLsH@~}gHY?M1-l>y%~-8>;Tr{oA_4t%x^MZ4l|NKi2sJM%Mg(#5 z2k)H{^>tWuew5vhrml8*Trahbrf;}%eX&H>_RsnJHfy$<228`!$28KVouRzV@wDnE z@;a%{P~+?fNO8A=_>jNRbI282O;G||vJMF*CSG7}X#ANk9{6$t?G%8vMpS?4TQ~^xlrsWtCtTOU z?=AmJuRfLxv|?#rzOj2mbF|pJSMWBst~*HKXSB03Vrs@1*4oQUNqPv8!bFDqtKuTX z4jz_OODr@Xk>ge1q;(4?RK4X8g>o_0`w8#P?e}#1=`q2_S-h6ZQ+m{~c0ZYy-_^DO znis5#-TfjmwCb0cc7dFEjI2e}vFL{}-q*u*g#j$`Gmq}IhE1Wt%k0gjhk+@#eV`0L ziPO}`ZV`UGz|KR;V$qtX$}*1c4(2Iq7VhA06T6xoPO?Pf9HW`)q53Hb=h;~gbs^G9 zx`f8|?6=>oJ_^-W)bxhbM#gAms6h1JS~eSauqJM9s>y$ht9key zhvEhfsfWV8Q8zkgINP23q>d5Yg7iE-U-{5J zI)@ufYUMutUtJKYuZPaFu#@oRJBJR2blbZ9;j1_ z&fJRoYw1B^Je{L_#(s$M4NDmzsZfe$j=wCGw;oNlf)OuiS2Zd{AWDc1pmw#KKl zWxc2~O?H3nz_QVXt@zonOhwL3D-68Cwk~ky!n{N+e+1iz{qkz=)wWB}C;Uq3>3)lZ z@U-_jQe*(X)K#iTH!WtzgZ%j~&$JWwV)1D|92gj4p@pL2%HY0*wK6 zA){7Y?&P4Lp=U`h{deax%MRxi)f6^Zjn+T1b={Q(X+NWx^7Npkz#)tv%7o9%^gYVslqrW`pohJj|J(i!QWTb?{OnYiDc;vF_H&RJ^ebVl z38&w24-}`y4+bJSWv729r4-40Xs;k zC1C@2@gwD~2)ggTwsu3znX}U7eO^{=5kJ5Dro#IQw@=3;>ur8bTP%dwnftc1Qp3E$MgOWDLYHR8!D-P_Nj3}z(GyOx=Ce=}F?-IQsPt*B3uJVht z!~P7#DFv!?nq^YqbvFyQLC>@Sq-Cp8gKwNr1FkLT`}lIy5~rg6wC_FaG{u758R?Ay(IG z@vCLJqOGy$3EoHmJY?9f zuLePlqF%C59$A&GI#OJfid5ttv%eA9f1@%L@-8`2kPYnOLn1L{k3??QIB=gb7f~C6 zXrF?_$2wC|5e+@m$y4xX{l{~LYA``VfJ zFb#ZtXJ&6!FRw&I$laeaIZ=YOS34i)Qe3$WxxOC9zB++aGi6j&tMb%}E8l^v)dDg` z#F1B~zDF8i?fZCB6(M1@9Q+AMF#ZH^5_&!^(fA;ja6az;k0Is00TNFM3DeZWmz0!z zJs&sxq|36Yq~t+;4O~Z3RekCEPFSCqS4-?Wg;mukpFHjoSyX;LPI!o~F>RXZ7d-B+ z@f)C+uR-LX(xZ=^q&#KIS1R@&Fw`ugoN)rPU3^cu-8xZeg_iJSn#N~bj+`xfWppl* zsA5=s_Fu5%#7M0#1YpXz9%HW=Tt|Cw_Jt)9a}n-KIQ!zyFPM5&^+!UZP4VRJt*Hj* zv47ZRe;>UKOFxNMufE^pD?D`G9e*vj^-+HRn#gBK6uGjY^irJ1urEGYZX1uFOTm+c zW@)sc!tSF0`HbSDy0$EQNd5&6p$1Q8kd3IS);P5$ zINxtTaTQnLT8C9O_K6y|R2e+ShT!&8nV)cvKYc4u{%SRB5qBybaiS7;R-GN24lJuW zWKcp#jNI&JdSghB2vQ3fRwk?cRx2j>Zso~%{p9rUzUp$64Y{wptjrkP$1W}|js>P0 zmer{A8E|T96pkM93&tD|yD?E$`A|}&-3@1|CR44suc|6RzTb_D&jT*|D-=83dT?1| z_vxn@trg(!ewC*xdI68G{-;*eCX9=l*iL>g^hUV+=E9s2u`0gH2sxJn*!w08Ol&A( zbXx_zR$om?4gx7~+>Y+=cjvu$)xEF5doR5IZqlX>W?*9z<$9}DBh&OA)9@ee7Vb{l zO{UhScMbP%o!ah%$3oPVAR(i#8#Lr0novko-MOo~TkP;yGzxL?nr8A&XDz%+EcWX` zMY+o<09TU;w7=q;?Z6@1Ky3V*^JWwo%h{HKttPUL-3vrnmT#b*lG-~D$-k2c8n+sS zW1BAP_btBF?AITJ>d_VDKY(Kv(c#903gL1GH{FapChTnbXC%Yb?LnR892_)|`9ZRf zqL%x&rZ9ZawFZ;uhmz;=6US!xBhiMiPWuxSi42zF?+7vhewq$MX-*ym1i8ule&;Cz zD+>PG)F`=SDNJ-H1+SE%r*-Lr2q)}I5b8Q9+14jZ8K4w|;>{dCUbiu(2s&4->CQgR zFOQuVyTXoi#+=`9B!ydSn>_f0Kba5MRjUTb1WlIgmYrJ$)EPD>R7YM&>!ED?hWT2E zpo!|kom`D5p+&J@&K*wLbIEA58CG`j$xLZ7T0rq`@STEkYe_(dL!p<3?@abu;Z63` z!GsuZ+q8<8Z)Hxo_tSw}(L_lCuv4i~R|2QW#AU6~$U$Oet7jF%C$2J*_cq} zXv4am<-$e3QblhIG$BV{nA-r8YdQ8mci6#|jwmQ4j7Ooe0>_ezwHFn4Ur-4%NB|Ci zz+8W=+p(zVJ8t5MT38YAGrP6+>6{Zd(QiYKU%CgP+a-Z+Ax`heS~)xd zC=wMxrA|)&AUhxcSqZ}|!x5l!=h{0xF4Rs(1d!W{c^4`8>2q2kzIdY3s`itJSBpCH z)VD!H_FX8^?l)AHZknF#dnm5O$Yiq0iSN2aR_~WwJHiz%9NUtZ z#5_32O12Mtxj%LHPPR)C0O!@!(^lT$aV7w?yb<&(eg!_j`f=+*okI#=M>N(BYASdEK^~Qsu3Hj~-SQ*wUDs6Xr9K zYtqo87H#BPNsi0wHt#(+Mv_Xq0i;qBOPA^EjO4E!a z--~(*2)LU3$k4S!wgVO+$|;f9N2(?Am%wc~hM2Zf|H}Kwjg^AMPnm8@u7H zm$x3}F4b+!eImL)kvOcoNs&_QdYRVew~jAM&-0@H45hgvVh-#vtbN)PC}vhWWko-O z={Edk@1Ibr*VUfuCYZ~d(zxplCilM@s`1a%NWM_*uFmY*!W`ChUNoii^xQozJY7g~ zzi!!ZRmPhPetkmI_ajR8N<-WqzZZG=Qo zQ?s9m;lXN1QX4zs#r*Ga-kt`@&R57s5XZL(`aMVn`=y5H0~GC*Qj$Uu%=2E6#{L%}@%=eWuh6s7tSv@oJ3M1aC(~$iEPJtf zG_FSFbtroAXLc4GXuH++MOc0;8yobdmoXt(XP_o@9s7vgjOvLBKy&dY@dTqjNxpfq zw5Zf55n!z!tLMQIBKN*mNkpGWKNzUeQ3#{Pj?R5u^RwfsjT_Edv|x5W6+8EOXQ&2Jt{3BG6d{KH$ppy4IGl|L z-SNn3d)avf4jgXOO^oxH?TCIHj9I=z*NDvV5uKXpylXLfEZ29%kEspP#49d3jt(G| z{QNvk1!ztOLJEbAWFn^^-oKphlLc-;m$*y0-7=mZ=rG)NDRe;3G3%dT$m z>misWtzU%PbX#&KjF>*u>l1P5e8a>{+;&9b zH;+dPYAm_@z>I|m6*=OPQw1r~Iw;IDJTqF)aL-EniF;b>5_*D)>R6)x&lR16c8w{B zF2kSbuf&=t5GB+VfZgU*#-O92TMM%WX;>w}R)MvToX{|FtII>E47$dG@bS{RL6Qa& z;t|BkUY02u_)aArM&DlV#`r?O_q{So{u2&OHx}rMB3{eXNaIT z4^x4E0v%^vK;>6ok0atmwznxL29+PHSS^>`8}<0EJ72rCfPDdt8Z#NV6!=sZWo@E9 z)C>w3F;pOQs?*xt0f|Jy97PewXZ-%N^Y624|pfveWx?%O}F5K43 zY=a7Psp0|*8;*!ReYidK*qh!==kZMK-t(^3h(b}%smCi3N2?b-(m~Q;(c{3?K;Rz% z(B0kJJ2?aVq|J-xAEYY(~uMe=-^?? zcH?pxGNQ+MEd$}aq*PEvaOeYLzv8$Xm_+HRs&1cRS&J6u8zkLf9p<2c!FYq>u4~!QmK1|OI&jiN zg|wwlWgd)2>DR#b?9J9?@_m5oz5V#{mbP@RZwy26DXu^|XpSIPF_(|1D;HoC&BdsK{3+(f z6-%zc(oDk{h1Jh1ndIKZw=Fhw^zTNI(I^ypeTzUqAm;<$T&UHjLf7es9K>xXxA%PG zeNjq6zoIv|TmS7k*j3!B#HiM0P!5X)rT0vc`l1qq$7r$drdm&svD@_H_|P5 zz)5+B(<|3lN>By;xdTfkD&ZBx;BQ}QOmcD0rO!n@q1Ym7@rXEpX}DL+CjLtaU+gAc z0$n7Yc!rHdRC--uO~m*?jk5_GLyF}c(nPO$%{n$ajZX;rSVm;Z5JE;k#BEJa)#2I^6Ct{`gWZS;Q!f_ol8 zn>4F7D)n((6i=RGgaG&TvCznH1+5hHzI|cVcBKLHQUv6W6pT@_v{<+@kAp*@LJ)hl z*UUE>IJ!MPqhK3s?>U~N6Stfb2 zetukNc^3y2rle}J5e>MChbkk`93{LvB%xUTq@KagY(v#7aC;fcK#qM%mA?UdKOAWa zsNp9R4U$iv#QRbSqQ(0$fPiY!v-ODvpic;v!zU#TXs9xFBTLTEB=(HmKaA8YtJUkO zR;+&=F*t3TAt~INBSby(BR3tnXghl3=cg2N&1q*KZt!{eB|M#WB9QNB20AsyKpq7c zAk{M-$jtNr-72Gt*E<|V92r^ZgPmPlL^`JV23ben9g}?yf+?QswBjqL>nwQ3;5$P0 z$8vI4o!|*i1_3_uDg-1-oC<&v#^e;j>Vt$Q9{6xdZ+)0SBE<2i(50~4jzQ&nq5Zn# zT~W&0t_QL~)*tVQXTSCsaaPT%+t*-$8RllS6Hu2v&f&QMe&QnQpwD<~wW8P0AjnN- zf*92%?9HVqiZJ;d!yO0I%qd8HN@?k3qaZ^#?~?hnxg z)oYFQd;sZ%TO5xCLB4%m8i$I*Kn83*wn%O#f|v(BQ^|%-cTXz)b3e=G?mXKTTP{~^ zS}S{_dHyW=nZh%Y*k=t9jH-#4=l=eCc>XfA>1hB|)mO_aedg(Z_GDL=T`*B0*73V;#8}#$1f)-@g@v=q2*8SH@fBlD+s0v zD#L{)Exu5>P-uXOc7gf@2LZZ!-CuoZ2t@cm2k7Uie<$=Va>}Zb*aJUvYNfo)O_XHpgpjY;W;h(b+V$ zci(oZNeI{=1pVkF0&9KuhBoW^^lPq{VmK8+eSW{AI9y6Sx8ufiV%V~wiDtWON}5Jt zZsOz^SR^|)ZVPW@!ZIwsWZx46WQM06Lw(J;2kDAJ zPo`Fqcnv@5L`wMwAskjiFy@fRaxMhr6zmCocQv@Rcv>MtFeF7HM0?h@(}6Tk%RQ*@ zU}{u!Ty#u_0n2MFQ#N?EI;>g*8yY6CPiEx~yc%dK z-piVtldLN0GE{b>LwQR?^aGRMfqMb=14B_^YeZt@_qb64*C(c#a6mAAbQKD$k0y)Xg^SVNffI*8CRO+#}-C^z#U$ zf+K-gcZCgmQ42Dw`8ET(AX>7GULkCb0@|U=S6&BiyPyC`8;|-HA;|1XT#5nF};;N9vKUzzF8-km_)y`C1`; zAu_ImafkLPS`QJP!};(3t)2Vn!6xoqTGfdj!*?vN93p#WJ|-5K=?yce)(9atn1VYI z+vZc4h>qqFM*cHVS@%yzN1L*_s%)B-E?n6n*F7C#^8$3R-@OmC zZZAGbbOJ#jTVRt!$Y7c8#X1lhzy{2bw{qEl{-E??LOuk6Ac8Z{h}>6Pf`Jbjjo_Xk zpdW{E^d`N2!GUm~el0v54t)HQXXRmaA~G^E9g^-b5Utin>7%Hr8Tim*$I-+yT3iB= znEPZRjB#RT{*YOFkOesL<3wH@B%e+3@VbR!dvZKZTl2&^mD(to1YIl9!|1Wat0e=b zM7C$bp!?m1bNj=&#KJU$OQ$ut6l$d7fX=}vGiL<|JDM>&QB}E;@)NWvpY;B}+W0I#o7K9r!?|(@*B0u@5lZHMpxyD-ihus5%vZheCr^Y3sm5b)rMu+eoD)BiE@%AS zZZD<@uV?~TiWJ>qb<*328GL*&eUTdjlfr@-vJnZ z67QT@h}X5KlE+jpDLmOo7;~w3hT^K=4R;OApmQicDfbUnH3qj16$X9lx(zRa{!w+V z%(PdlKxM$2lJwFY|47p!_RfB6F@d1(qRZs`+g``*vUBl#S7up>%YH7P&gmxTnL(T{ z)b0gWAKQYmq5c_#xu+<^g;qv{?}!QiF&9ELIfDpi!mG+V#mNn!y-@nJX;#Re&zq+l zf;Sp`tG1O8NmT{e1_r7pPbd})Dqmm*k?U_vsz40+tl#Fg;o=5G*_`8H)l?*x!GVGt z5sG;a`O(cFG;35e!S#|1epVKiV3io)_lT0p0_f#u@s%U7cM}4(NzTpaBE2i~-N1{~ShQ73~i2EzVdOc3#o+I+a7jI>kjRt#f7_ArFF|%k85gGTBvd z%UZIIIkmrDf(5K(M&%#+nzo?@6Di%W$9jN^7rkU%I@gJ^CaY zV$gZRp@nZr<@tRJ@RnFVVMWZ{&cr1Abkbsmvq+(#0e~^eZ8|EQ0wXH|*uznq-xO7z z)RU^7#0%dbs+2(=+*_gExq7ShXg?A@S1>VdL3SQsEpYmuV9ncoohNFo#-WFSWI+Y9 zwMG&~1H|Ybz40B01x6;PXYO_HULIna`V)q}`(kFD0};orX@L)j zD+rBF3d1j{Apib>6Z2>rqJA2q7j~kjb1DD>pSxz_uH_-oI1#a#+y_>Tx2Ek{A&M>-9J&znv6n$>L3&_l+x4^y$JRHlLijiYyZFZjS7t-^C%!zMCG z97*+Zsv)?K`M%Rc&nT>^ekK=**g*@qkLB-2Sp{$OkQ&lVUN^^e@j^E=W0!43XQIsf)nV`0MFS(Fn6-v=vk|J^e;5y2k=n#ik* z5BHhqrMx)9=`ylVMm%O_Q3u7$?|;8;;!pC^AErpVe;046A%3;bveSOiU!0wl_2@YO zKS>Pg1xx|(NBlLuVd3~k#4)Ajq$>0y6#gba^+lYcOTMf*FVn7dr9q$Gz}y|RnaTi! z&f0y{{TLal+qN6yM!>$Ng|WlP}tV1o`Qn;ta(

sY~Vy6eEy;ANkcy&-A?P__L@je9^kOK4=vnrU9_Z%usQwpu5ut2iLyOiymJfMuJy`E z%WaXtCsW~e zY8xyFSnGg(8$*(p%g#|?10{m2=528|px?m@>*MR2!L|$y@Qa|Th29N(yTu|5>mNlM zIB5|O8yK;kQH!Rw)glQSw4U-Los_bkaKtGhO>L9KdcIL6);Y_i9MBMu;~Ft@JXHy; z;kKcWtH$uYnzWfu4yEarxZ0@|xhIaC6l)c0cx*YTcCsj--?P+Y2YjTj%rvoFL+3=D zT91q+E38r@COuv|TTKVA57yMEy~7657<9f}dbv*JE>8d6m1d zV5-{BB3@-k2Oq}CBLZ4OYikRZ$n++_-&|@Dx`1+hqy}?%!fb^#MqzS@`adUn1yh?R zw30xg-}${&Pl$&wm-T|oa>FD7#}b?c$V|25wqKSy=Od?pW6M z_b$$|g?-S2?cy|`P@A9MLqfBDODExCMLd~lZ!Q3Bi=f^v(5 zeqrK?0=&1AJRS4>mU5~9dBW*HjT)p%K;I)#ehgYdRY;D;$68G^AC&`#ffbj&sdOKM zb|XH0R2JQP@X2dcg%!!c@y`?k7!B5x^OKd#ztYVMR?r-o<%X?WCtC}B)*RXSrvaVC_|gVF{mY_35Rz*G0=IZ+;COMcJw4| zg{ZgG8a1id*`!)~*?By??s8PmWU{`FT&S+&7{_};Knn0~25u-4C+-P#m%h+!c||uP z{SbR@QjI{Pi@>K8ze+UHcR68Iki}noPh)X-wP>|n)-Imqc(X5k8X6?3ia|<}(}`dc z4L}H7dECepryjuo=z3a=OB&2n!pv06!xd-)$1Ng4ERU!7G~go5tW-j>b|(yM2zA@g z%QVoV>E=*Bszu}+3isN-<7Bs9bY z@$Q-bT!*UGG4Yb8N&qm-6R7YBnhb>Ir-H6thKHEEGa`4R_3uNf{aW#PeI=&>ry>fQ z^sjqakS>K5ok44M2fVZ$9okk|{*N~)vHhSVRxat_&K6!e1bGeD2l;`F`GTpyO{%1^@e`V1!&gX_Gh1jU$9 zvu(LrPKEn(U}G7|24oR}zRGWCb*n5DY=xQ~PZ49(q(9F!}N zyr~{*bqvq3d}JP(-<}T(j?BM7sUqO=PY&Yli~7w?;bf;GR$B z>lGE>f>v6=cC;5*#?gJ=W)3Q9bFImaa-81DV$9mfXx350+>GvevXyU74F>Q^zs783 zW1?Ej;keh@FDyRW+6uPpw*JN5-;0?M=XiHSB~&=2_nQ2SJ2h&A>^R_J&(Vfh>q_>i zxdqPKv)(Nv&zq{(-7fVDOh&rz1Bkyka9 z0*}r-ZJXmbxBz9=BsBMXvBzdRh-a7DtL^Se1=)pkM5228yx;tc`P;JP3268|v#)(-N?B}YLF>g%Z<)Qe7vj-txi;LE-$ZYQwOP6 zc@3ENf%|gn6#j6kouI)~rxyoXu(T;a+~jV>5CMPJ_UYF+Mi~D~Vbd=hbUPa$#BpC_ zx?io9Iq}tGKyMl1^pLFamXVIzKBdqMb%y-en8jswha4@YvZYUEgnJsnG-p6NGXI+f z#MCb-`3dm|b-y%yK5Y8MuNZ25g4eg`?=ZI2QuvCGJ;%upob6oe=!;916JdN)rz8G2 zZWW>*Y-&}!kQ=oJl zbDk%Uu{<%;`9#|$WtvvnEOa?>E~8a26}VRHjOnT(A6v|xHtXo z%wDa#blqR$coNN~-v+jUunKzxxO5FA1IrMsBLNHO|Mo#w9-LigV&ia&e?=W*F@XAR zZMmrYD}ix7-8=3uV9{^5^1j{l(bP6Ou(Ge|yQIpun^hiIo)>xJw_VR_yef3II#vni z8MTtVj!4zCirOz1xN~nPwtgoEV>3~%sR50?_OE3+hnXA(T&3m4>e=|kHRmCHFf+P@Fn1bwgN%Gx z?V}-Wt?pzc2&r8(XzzV*Aeq)0W+NI2IQKEAGr=R_RaS+<@Ydsf~{1}c{s=_KlI83X?VrVYa+wN zi+EgKSW_dv^lWhVmH>s#M^JzeIu)P>esySb9k$hdWVp^WZ6 zD4F+l(Y2fK2-k(uGjQ)Mxvqk^e9vpdyf39yEfqkAP2!!y%P|$=4MI4# z=?mP8B?D_(IGenyTM|8Y_aFi5=b&TEf1Q4PoWDuu3NZ3+;=@~+e~oiHN@jKQ z?;?I7eNacW4m8*w#U#$_?Tk_bEgaJv(3G#KsdZR1s!d#PCi-Ghzg(NDaE!39Uh zF{D*)c!0UJEj;Jdl7JFREN}-ev&_*51eJ1nBmamSDQ@P#6sjX0m`Ppw8QExqCf!h+ zyD)~ZurU2i{11drf8{~t*`nC~(6hpSPsf&HfJ(@OOKz}F2l1-6+g%J#SHIaUfa#`a z?{}J~SKWxv{a<_U8P!zq^$VZW6Oupz1QL2^0#XH}BMBV|7&?fQ2ti6HLIPOOKtLdo zV(*IB5K#dYP%HtIA{G=x1PdrC7!*)?F*p9#UC(;%yWaQ1yVkwm?yNQ0=girA&YnHz z>`W#zzx^}KZ%i>TcD4xZ?iOC*_s)9Cjy`hF`&z?zRytS-Eh@|B}hNZtf(*10Bz{2xQgZhkf=*VmTq? z&srFpu69_ zOH4Emj`#C%R&JN;yWD*t<5%xvaskVdj6Ru2X$DMW5AJq9$=;A=uodiwwe1y5U0&P! zDdN@N5{D_VwXlUQAc-KJ;dh}9 z@b?mOG2$%MO{ia1nYyMWrorc&t;0aRF1*2Tskw|CZSvu*Qv5|l$T4;}{qD_QYP_^& zrMSy+_C@8oQ^u%`Z#OLv(kG&-b?I(ZB5i)l_`O!K5yRO5k%i#4ypxfpVqsd5{OHB{ zmT&MI+`q-vn;d=+fI1|l;8>A9!y zwsnedXX$+f?+o2=8d@VTs@zGhI{_Vl6p>wzQH}-H79fu05Tz+DJ+N|VM$9>U18zFX zq$DW+x^076Rf-;4y)n+}b&==vm6T=6V1aXfbT%eDiv22}q1m~qJ|h6D#)fgJt#Ytv z%I(7$aS)`e_Q+pVg72u3tq}zeg>>ldKf``xY2}F>n#z;nhB}J;=VF`vA2>ldn|>M? z(bu}Dz<#)#F;bFFno4P*L1*+inl}0S^ivdqQ8t7ljW-_LzWfwYtY9cGZvqr^`B@C@ zPWw)5<#EjhS1ppk<#iQ{G8RKsrhtFdd*>0`Nc`+|$epH%#)1hC87$jgY<0-wKu&XT z_dy+wrLyU7m2t+1dO-dQ_V<&xQ1g$F$Gaj)9GELqTj{o%TU~un`-g8I)ZZ0+2(OT` zwcwc9ZQj-PICT#mkV0_Qmdnj`>MGIOk$DQQ8KmtX>1*A|EE(#1g8cT@}!1PUR zFKq5qFgpT;bKr4p+uTO-{LS+ym#U85DL&?S_wFZmx`_P46(ckVA@dh1xqXSD&!qRbbrcVRl>0M*KP%d>$838O?x2J9A%8oFbG8o`OSJ;hG-K5sslY!x^KbDbNfk)dR``=lPbe(=MA6chf=9^$$CkSU)RR2kWCMz$ z8($la57?#oglVPaE0WddiTrdm9!Po1z{@HP36J$d;g)-$iLt%;Q#<#0NK>Sk<1uSjPcQ&*5{Z#!A2z9)e@It0f z9R`!uN{7Sswsx`Viq>4$g)17qS(~I&j2ibv*3t?Q>WK1hu|PZ$Nlx-bdVwH+qMRA4 zg+ifd~?8&Y5!Ot!BIx2uCl5g`Dyk8QoxfE(`Ezg*DPRS87so_EkmF&W1@0f)Vmro117 zy~WxWK3dS>eDI!sP_>3yJ37kVNgoXztx>V%W*mZj)%vRbuP*ktZ~y8F`u@Ex6E)Ok z0&^G>j5&-+Owt9Q#?{Ax1b3Nn=#L+uM5Cu75&r^|t5)wYF)=}dd1jgC{8axLY0kCO z`}NC78y1!^ImstY$wU_#O-<2$Lt=q(_i6R%UP+t2{r-LXPj?16=?}jIGBcwG8_=Ag zZJW(7=d#sjJ?GX-j5cR5Ki_F?Ue1RuEc`Rb0_{Dp_#cBTK3H0+l^Epg@-qLr8*qiQ zOStb$?XS@lcG8Lg&XW_?yT$9R>#FkVhQHgJBfS1anVrtT3MwP`ufs~g}j}@gc|1e@7CWx=HK4Mm8=eL+dkGk zfl9D)Q14b#zl0~f?iSnZPli3EiUcBzsJvYUFaArL=vLEwzOPQb`yc`8a#Fopoa=J5 zjz2j$Db~LD4xn|%d3fGhquvdwTNen+yTz+~Nvpg6{P{z>cT4l)fz_GrQ|jdsp6=c1 z-A39T1N@-*`7_w)!g-qMg7Zmf&2GGUx3&@4X<8B{lrORA%cm>L%geNS@)_*Km6z(B z`z&n>deu=XuSetF^3Rj}C!E%N*^qgpd<*V75#1wsa^KZ_{`@(mGtV-8e)-E^b#0aY z(E+HsHY@bdU1ZJr*Y$S~uHk?CHu+6X@`S4UB#nn2(pFKY$;cip_b^@-Y(;f55;ng+Y1u8BbsHkYh*GPKHSqb;%{0LC-@sTv?lA?e&u{8>0!lO*%2;B zI~hL`G~zZAZJWYrUO6yRX*Kp?2QjtKh4F|1H?XaXxK)RBK!|XR?bUC`XsAjIK zJK8%VoG$Xd(~1-T@Fvh(LH8VVWZ@eDR;L#(Ya4bz=hd$e{D!u_fZA*LgS|P z<&&4kj}rZH&N|9(q4*o)F^$JF&Hx|QMt>A^yaTrzwp{tDVD}ukZ>+29xYu0*58r%> zixnr5H@B+v=MBvvi`KDskD5W13{yrvNt;*-=OF_b;GzcsVLYDW; zF!zK)_2gb#>cL>LRm&%!EFCEPWLnL59CR`=$)u*WvCBb@O~qdkCzl#O2R_cN24Vya1etjONrg#`xz>6xkhYly;`i# zU8po!T5{9e<1#V@>-FTbaJaI2FU`&U@%S*j8NSB;gwV%8R!5dBab{8|IXOcA{?p$f z^73W(WRHGtg%AiD1S7)5e_SUN$O`|MvK18#YHUx05Q$|X_oD~QNhFdH$w%Vfp;D6B zqg+dEy)i&pIUqE{yhBB$tn%V`=)s+yW-uPQlOT^-+V3A$)# z@V4hVi#b}bR@hU+_x-$m71}Q&hB{y`2lX9qiz8$l4{LM?=%e<2qwMWIoh~0}B$VOr zt8-j-g!OOQo8+&mUscII8BO25opSN?>iz!RP%rfqKhu;xmN&-E8PlyU=NKw@j!9iy zMoguRlgCd;8e#n72ll0;6(UjT_rDXq66YXs$dCTULdV^Il7F7{T~q$;*RPyOW-K~s zvNUw+kkmxE#3P$opP<~ECQsw2ClAOE{3ice{qv{y_vzNsuGN{DcuAMsL72_>RGg+o zf0)&oCGVY+^vUbF$ocu&{WM0I?ZUztT6CfM0!?-?ZWR8Syg0S8lq9*;(#z%L(ys0d zboI*03Okj%mi%GG5O??Bjqux6?h>!8;^%zw^54Jmm7;re*H+P5O1>BC6taFUyw{kV z|3#vF?HBnkuU410gq6FG&XC`yGhI$Dkek(8p$RMUN0*`YvAe#Pe*aEOM~h-p#sHt? zT-{N+(Q;{Kr+GRk5{YQ&q*3^%r3pY%oc?g>%u@N+9l531OOul|l7>&x=u<$sOP7J^ zlFZW4?}($NMJ>)6#FfvOR8Kf-8`|r-+X>PWgh2aAQ4@+034MF z;uj|u%J34&rwhx=>i?=-rd6&0wQE8Y$+^h3lUv@Aqp^J@5z(dgI$ki!uC2N_UEtH_ zS_w>ati=6J&+FS3tp8%vGhZi^OTRf^EAh)%&C+k*zR@J@fo}b7^iOT-JbC^{h7tL&5i!&`GGn&XH!{Xpdu|Rh=TNR7c?EQhq$W64aPoo~`AY5E z&Mz~IW@Qg&jP}(!EG?NoROij>#z z&Dk8+|An|+C3KUSJY4>sY{O6OM#(^SByhxZq_*9pT>OXknd&T~d*$Yz(mxG_#Ly=R ze*(XjNqXc7K5z&1PZ-QoO8ke4kXTmyyHw&MzYqQ%AK#(&^arB;_s#>qL!D)q)U@BF z`;4@S-amgHeuFXEq5XSjzUosu=x^%Cblt!9-k+YHj)m?jahUe=Idpzhq2D=udKBv2 z={H?w6umB=IXkPqv-5mSBz$`3mnp?VhTi1Xlg*`y!PaG(CU`#H;3>@%tjn7y0o4aM;&Gm(f&U%DA`PD`C zg>Z~=hWwrdHW~ZljR6})#V*r^20kLs!q$4yKKbP3As7v3WE1wlB>iqOrP~}-8W1O^--C1K@^nljdb(-lzcN*8juGvx}aFb@RWy^-~h7YPZt`RI6$11Ub(Rz~$ zs$j2TKCBYn+hPs98EDE-vg~kLhimGl!d3aiky-z-yN3`xl8^1D{@%f zeCyP=RgwA1iby~C-9}*V+f+A#oh%1Wp+@Nk1Xdi{GQbNWF;9qoKLa*HYxF07z5*Bd zW7z2uckvTgeAxJ(T1-)}k|_t-@)EvE5HXuJ<1>^A(nZcOl)wdD)euEEHBQ^@#by3; zps$Wnj^65zC-D-4Sh8t^$7k}aCqP6o;KL)G-)fn2k&&60C zJazIT9J;#;p|iJ55FEf)Kals48oUR>yL&CL+^|T3_Su=!v@Q{4XfdLF6VK6BJJ9*I zZPIzg9-Sz6m9OC;>fkOLvr4M0F8Pp*(1w?x<%U6o#C*~&XwQCcdcHGC=gxXp=+zx- z_06Oaw}ZQNr9@F-++j;TNRyUB5uWrqYNGJy0`rFES&qC3M@ARUZ|$CX=&`k~^xOm( z=xsVgINpu*og=bS^_NoV-Mn!#k;;6FBYPdc`}R*Qx{#O!Qz9o5?mcqoviBimDBVUq zd!bi`9((9-|1?um22O-vh}GKkO;vx$$AMu#r2nc4JSIPU6YgdN0rsxAFGzS3wIq00 zTcudFmdax|7FFIUsun&Pa_w7rpC$Fx(3Z23xhBSM2vsphhxD<}8?MK$B?QSr#gH_q z=+FRI`!3AB9~aDfZHVci9R=IxBTfiDlC)u!#b-w3z(E_E5 z{Moe8d%!T!tD{2I=v|9vdm=>J18Qq-eajOvi&)qG0M)T@5+%fMe~)*_767FpWC{Mm zFF8nkDiY?4hX1R*T2Gg4PS+Kgv0T$0Yl{} z+X<-pK=SS>@}G;8fGylS#Pjv=7JQRSyh5MUsLH3>43#Dr-Z5x&(9ms^?yn`a>7gIv zuSnU-kh(>GdK@}+GXp`XfdxMK9NNuam=SzEB<418`p5JmEL(n=1 zm7#Geig)ODT7tKasRXZwXyN6=sq{3t{zRxG*kMt`V!PW?a4P~qypJ)-{m{Lz2vEHe zA)$W3k9k11@%`sq)Wv)ox0!}22H_!tPzr^CA(!99zuTgqOk{-TejpcrHjZdhuZVJm zSVXh|k4DO(P==iiuojgrOpA1Ez1oAZo%jNf? zo$p-yp^lpo-D$(YgqXK)>@vSH&FwX`-cC3 zp9X?#X{pNguQCydT8W{Q)Khq|mA$=#pI#u)q=`U9#>Vz^dgK0w;fq=m5HEC_&rVe!GBKkVH$>g??E zX3cpzkQFygI=y8}pyi%SrQ>Fy1X^%Zu&is|J@6e0aZ4Q=teW2l^o32VC&u|mEn92A zC6TYLT(@sSkJYM0Nbkr+9nHos-m8=4#lL-M-x1XN=`FoVy63z~lF*?yi^?$|swHVcW* z*HUo^ymCF!vEL4t3b#yg?&0)&1&Tsd;1d&0B2bECeI)Udh2n%be!$iOk1?PSYFZw` zEm3IFTqZxXXmDw~0>lIfgdGs@2zD!jXQA+S>5jWOHpXS-L{T{OwxO4!U;5-9I`V7uGi zj$>n~3E7?1j5J0VfbKSCIkOrhkD<bm4;bGRI)q-Nrux>VY2g0*|<+F(* zKV;@sM`A1@^$6s^yhcgk3q*^w`HAN8P+1h$8LtuQ0*N{a71UN4W3an2mMz(`ZdfBj zNJ*6Vktw+%O-1Yxp}}C0)Iz_y#oF5IR`mAwGfrm_qmy)w1CDHsQ7FGV$ai zSY}&EK2-O#SY;Sv49avkfQGFw?<3i{DnX>ZrP+(YB}MKJ7zp>GhMhD#5t4%DdG zJW^UtA#-c0rO?^-Q+r%_zob+K+&i2WJX0o)9#nC(yfY1=1}>@W{t4?$>LUA)5grPs zmUR-k#%L&g`T7m$1w!%qSpRr~$XI)}C@GbgMg{y8ZlxB_%_rT~Px6Hz^mfSQf*lVZ zIRRVNAjHy6BBds*t>SKMP|ZM{0VqanJ}vz|7q}jry-iW!R?1G!%F3&Ah#T%-Z#X>` z6wG5PLb~h~Zpm{Mg-M|A6biU$lC$3QLhX%Zd&C-Sqcj;wm(c;=a2mI1#Tk;CofPn@ zbKf%vOeJ;y01*~rPs>ut^$*4XI#xaRLSNi_e2-}O zpd}OEc`x*-l4jG#@9LQ%8R_6)+?IA7y7#9iTYN=^QhK62+>d>B=IXRwyfBw>)w5m62X3IfwG)N1I zA7AWO(Z;gh~ztFAA49lJGON)~bS&=pzV0{kVH;}?Y{4v*R0)wa`_`Wx1v)TCRZ zuc?h?fGuloY+^lJ;|k5E7J1m;!>8bdhuNq{NcWy?&qa3&Vu4t>Mq9mnWa!phDeggvO^;)f=T5o69*V|E9+E55<;gv<#(IRyyo^?IX z+E}Wp@^RQ^(cvJM-9wjKCKk1_9>%>;D5?BQ#fEm`RAxh4g0 zTSG)Jt7x7U7=0^K8>+bWH)>MWh@F-`*kbRI{!@jZ=6KGLPvk?^0CZH5S08U!Dm?DD zO)Iu~y}8l47igZMGX>-J1$hmAwc-i-MPGhN0AT=FM&77fRkIKpa*(#hIa8`E57{RA zn(G|=8Y?5D^sI|go(C7?O;|5;)T7%_+6-;?^?dsZ<)9A{2rcGWonKues$TDKf&kxGCwS(qamZT9(8L2n|J3v@P$jB%du)mKKI3G`I z<1Ko$IPZcTZIL=`(HCK?XW%yM&yBFx27w%`*1 zb;K;JgZH2{q7Co-Ccm1DOM!(&Uhg4pr*o~1DE74XpdP|Sq7&XmhDhZHW2OCF@?DMLH z8O%F+vtz{%6Tnk|^y*y*h8(&mWdjk-5EAhE{!=^g5t;yY+h!_)8+|6r*L)rV2Y8q`xi#$^4?ZJ!Q> z0Ebw6Us+_?Gi_{h65dcpx$>8;Any1qtYZg}-n%1a!3{qkeoDnK`;MP$m7|AUgXstZ zZtf}T2cedvTGY#n=%P&utyPCwE0rljs-aNg0RH`kM;OWQpt%~0^Q#G~2iea)-ztQE z;jE~-PlbhTNpB7JS__^g!oAZwDU_mYfVy(A#s}^fYQRxNa)=Pknyg}t(ZLF#*I_AX zw-jFWY91Q3p1}7+KKsy$I1t~WI-+DDt1~tXkR9upMF^}Z#l)}(CyODB%4i4gilsW6 zBo#-zt%i0&HbT4q99DSncCMHc@lkd?PiosyiWxzKY7pk@30fFy_9@cUbdOA;O#cnA|m$MrV(Y2D}4%@dEWQ2C*=GYW- zzK&JB!UIdFgJVw)s*b1u!-1~~+-+*1WuBsK_~Mol-|^|$+rMP*R-WYJxb-e7476uO45D6VG1 zOM(`Lw#dHE_4h;dq}4v*QV0)IH(PRD=)c+#c}?qgAbb|Ld9lf_FfG}UJ7`K!beDq{ zboh$BEKj)ER0<$Azq1J_F5Dkf*qMX9<6KqTc0kTJ$kk|PQ=eK7Cg?)-d9jA?06O4h zbWvyPW8De5*Q8s{|=y$U&&QxGP1Mr>%sUT-)No&{f>M{!ZxDjasgR_k}^1ZW$<_%|ge> zzqsq+?dkjYg#TdiDROj3U~$m3&)J?Y+`HR^dl> zNo7uPjg^Np3PFC zw83AcBj2u_o!1TMT$xF?&BBW_*Ck(<4jV(CRD{ipUE3^M(DezWV4YHPO}t}(F;C>Y00i`;Ed8i#&r7SVFsTt(Z}>LENS+BT0x1*{6+FFWp6Jk_(jSf9T*y~Aq*>HR z>x-Q}!LoIch2m|4wnv0o!}`i-_C2_= ze=eXg^%2!YUZrLn->hOuY6~prUV7Q5aNIAk^fLfwBT&-0nmUO6=DHpnWwc7E^0q+AdG8`D%x^t$Tlav6C(8| zM^zP(3`5=Jjh(ty?FeLwybKT9| zz#;9i3!2>}l9>+s;kh-#zWTTTOg@5KgYvZe`U;_)KgkVk6&do#%9+tEhV%i$ARKl-6)g^7zcQRp1C_wd8Q#q+K9DuUGtO$2(-?SgFVp~3}5 z^j95QHJoZL3fjuvc(61v0D$Kg9G~DB^Q@fNAB#y&Ut{arvy};LT)SbZy~Uh?+uRb* z!z+3be-PXz&23@3{aa3)44m~Xv3z*+)UY4m-K6rZC?3|dD?H3`P%(L1g3OeM-cfbW zfuFddFsF<}R)}~@okw0_Mm;)6o^1(WO>%4z#lk{toQ@_@!#SvXq9p3)lhHv|&l z_J40ACZXi61+A1|n~G)YuJeEmKDkPxp?-I8+S!7pSNN*LqMa1EeEc!TEy2W)oWG}l zXr&Irc9`2l5s9KI+ob(V$V$FQpXPqq}a%`E1!z}=i6=3|jwe)GI zqry1B)rc>f-mRG@n>9@h4tajHI=VKh%3ZEei!@F7wyU;71T}Xx%14{P$~#{<4mo=# zUx2cU*q;m|j|kMSa_uxA;YJJLe&W4UT*Iq5S93KuTbaVgn{EiqG5mpR>@sN?L1DZl zQ3lU`#CR$-dOvn9e$~#Co@)5?{oXy4MjWdH)R2iI#xr=X0vR@0Cb-o}eY2Co^r0v2 zK#8+Byp3OEg{qH5Ho?80^a=;81%Yk~`}tY3mm1cJp0(cSX1~nOc6T#+>$hKo`TR57 z^4hDGFZf`T0R2csfwcBJeLnnwYl}rUW05z#H048>M%KNwXrCPk71CvA-|4=8Sjb=L zMMBDQzqx5kKp{AfYv0`2?xNtlV)xkUz@)p|?L;{prMQg1$KT({DeUS`Ir$#hCpv0; z%lWT$g`?gXz@zJGM! z{qX2NIyNyi<8EVazOVvp-@E-^h1(_R|BF`epSdA`wei5TfxKb;u^~i3Gz+G%@0~qG zW%cj_ww>FlL3xTrV0=Obd7x4J37X1DCugOmvtr39saYlfnH|r>$A~rTQ{%8q7BhyO zo|-~tq{QMg>?g`rvl7@b@kW@fOa_O&W9?Qf_k#*M)+C6Lk>MO`$4?n5s&fI;olGpM zw@!X`XC$yt@};Xn_yHjt&~wX9ZOj}Bp~uHC9dXG3JO)U;f7f~*NC%jZz*v9DmARL{d)2Gu>Cl@P=5do=X0M>0P|PG-bzW)RtI zW^x+4?d%lBGnLFrWhApg1VT9y~P^j!F^l&lQNq!u&c#r0O!L`ZxYJ4)|6i;;3DY9Kz@IQfztAoFmFpR4l9bLF9Tsp+b&UOxQg>&Yd5=FR`cG2v&O^9u^% z*Qfa~O^RTenoXHzrg}8RgX@-=_x&5b4dw@?VsG08n0#ay;&G?UVpJ#U=e^EF$7-0{ z^gGY}Tw-ixG4paa8IsdyfKA&_dYAsXNR3AyqtbhCdbJRN9`P7DeDkEvk6j3T}Uq(#-V64#4#$6}( zVJ=~W8LMCld zf3+sG@mXNiWLvt7RY=;PHd!yJp}|pgE@cA%n7dupG^VdXP|dRK$df!v-ulfLj2ir? zxu*rW1XT)?z!;X5A0SKU!9)70>NajEigeh!tfW${%d*!1UqmTKZ8r_D0L0TdA zTxroUrjPd0T%rQKeB40aAb&3xd7sQ$@!w6pZh=()FpNuJ7~Rd#Ws{Y0yww~v&<#VS zLej$>uI>Yieoh=&sSzH&|bP_{$gE~ zkeI|u)=t}=uEEL7%En(G5%1W!Yj@6`y+D+S(Jlbo_DS&KBFvEo+J8qbWI~p>+0z$O=g{x1%Ksvq1s)XbwYQ1BN3XTWz z-J0*XgY>j4=%#(Vw$C2S?;+?%ORtIJ0qo<%prQ0lTN5wc^8Uf|s-s%a7FZCEHa7KR zrdC**{N%=4VK{ehKgKqu^Hx;BP-^zNpny+{0CZxlu%hw8A=*1Q8KO^??}Cv%0epTf z21C}&g@B4sC5Tg=5!^f)0<}Lu7TS;@@zLF-S`Y|ea>~pd0)as5nfVk5@O*mJZA?F9X@jO*zpr5 zC0E6#PMDp+a5n@ z@96A$+WqYLi3=)$Ztx$^(EAS`KMjw3{xbUY+xM~YAEJq$zb2=qfB%`8 zots}+TtZ$wxUjsk`d17L9|ejP0CBZ5DQK@D>#!SF1bW+p`H#DzLIS)!ToNZA zytUrp;$&~3-7z>o)a9hdGF;pvr2^yc1oM*RGUP*8yF<_4l3)yQq5IPBT{YSu(cHI%u4k zkfMPhr|g7<^D=iavy69ouo$m(xmcj4b?{6~?6A4x<`y2W)4i6r4hVA8D>(=S`M}e6irP z?hux-1NV|Fp8{#tmE!;w$aF?HH~)#H{0swU7JbV!B_T#vJGo!27DG)>!7;OSQ*g-4 zBu`>&86zWPNUnc`+Q_)oLZ03A&Y8j5)0ZwE!@0%FmS4J#eHvT_X>9&$w`iVWfp8tEQG& z)^fzS5JT+JF;+>!?A=j&z>=jBMeNSpGMBOwlgLPj*|u65iy<;G$q8E}gQb)vMj6*W z98+uFR_~v!0>?B+;;DMycdSJuAT=F(E(*Qj(h{JFF+580Dw6YJ}lnVY1RTWmwnfMpIKOdnJn+KR!Ez%n>EzlAY6Ia2_J0mH!| z;F+n3e&n{!g13(gOoca;SOU=WK6pDQB)lcLxd!xb-gFUc%sP!!d6oliK1a+4cPBSr zk8bEl13kSr#Y!wShub_E4F)=Ss{d=R1Kt0TWtlgi6Y3&q&1UK`66n1Ng(EiG+pgq~ zfq{XWZl>~bzD*3|gPm7|H?gLF=fqfL337=Q9E&oB!w?eCF*TG~_nO=rPh99xFkdI% zj>zNi0GCEDUmb8$5Yc4I?Ld@@mcdAC0mjUF1BO_a?bdPfe46CE>SlxFa?^vy-LDru z7hv8E3pR%ykZeTm%DZy!s(y3{)di1epob2cT{e7aNSoHmi)n-{GFm_!NuC097O2+u zX|NTZbryV(@gb=&NE-VEECZQUmG7!iqvcx$BDFPojC2eq4j4aLuXoNdY&|m7VA|Qkps>l{O0eN8!wEMBTc=sO3AYAFr+w5`#r#*)GlJKD#;52_% z!HjwMD&P8=K*j4yt!blxQr368uvUWbb`W#Hx;`?t%%H2orCY;rf@v!CJjv}@horjJ z<>zIKJsPI<#IC+K--kPlM_OId85*}Qc~fs56im2=(KQapzw=Ps`q2|3flSb3LvF zsT50fp<5tTShfDd05Bvf>|{!9%Ptl%8AeV+s?;Q?re8yva+O%MEw^&PJPlsby%uD? z#{rnZS)8N{dA{so>J?&n3sR-=+zXAjBQD3&NFc@{Mvv0Q)XX=43=#bXP%0Xx;E|GA z4Iq@C<_(eYglVr#mG;&tJ#q~g%c8p6Z};hCgHq+_g-mLQ1c%r987O;NF55?PC{H61 zlqM`AU|h}>@V3f7klx*@f$9>Tb{~x%Hr3@gs9C{@m_M4KbsEe@PMzD)BZ}+=hj*HS zD}|mPEZ~J#Kp3$JR$R_vj!P(HIjV6 zpdK3)Y|&V$;p+JNkz)dxFmyK_jv=x$YbSTYQ5PA}c{}nniI+ct+D=Y8&R@zsH@rgG z!>xL!ou6gea$)eov)sR+72K+ma>tYkE=zxuZZLgWIa-u){u!K{mhyGh} zyu4_4no{27g!nC?h5sxFqtiTHDgVstU}I@!q+=c0Ye~YROo4xb%mv=m==+|3l8=S= zYfqPtJ=A4M!Bv(`KsGA#NV3ZAd$7HLqjPJPw~NU(Lw!f5Wf(V&I5jO6)1MQWdQUaz zu1*8d>z%W1l5SG)z#Ipfl5I^=Ck`yYhB@XMJ9w^h}(OY zRJ!F_h8HV>6^* z#;4iDAM)E(;CMjQD}%lKanS$eHjEcDgIO1#cOX|klbIItU_HNqcv{+XZ_TQ`>;Tax zHI}2(8#oa)5hA;f8XKF=pZp1vBS734?5EVUG{Pt0N6zPv_e}Q$mZyv_Cfb`&MU{`# zlyWGs~dHVhUh%*Y}x{W96VJ309*Eg^*(sI{|Oi;GPSbM4551YW=%=gVySa$}P z_;J3-`!&;c&|&nH7@zI|8+*yZXSJk=%8qansyO6He0=*UqabkEdXQrFsRVZOrheqUPG!tW8-7Dcv+hqtIRGjo7Es z%WLhh0cGD@#ua>QVpvw`Z_}{(WMjt*9<>dk>)Xm{sV6n+g4nGO!ay;dE&? zHUH(T^X`Kg@$a2EadA<$po+aTJq1m*+~n5qP5*(96c)>LjwSdk9Zh6#18(e<8jt=6 z33&E9AYtcb$~}&h%iRp-mnP@A6BWF;5FBVVP!7FJy|rojL+f7-JB`D3p=(iTE3acWu=7ZmR0I>||}0n05Av!LX7-*FQzO25RfG6J<}kivT6Nr3I_TH0Djgab%PEX za5PMhQae$Xt1rPW1IfIf`2+C7ruyo8kF@;k(GbeGkxC!{C1fBa8h?cjF*SLyC!B82 zz7YW_j~r}abTRms9l z%wrQWLvyDN*6E#fR@r?7Cc$X^y5|j7GO8C#4*z?Keh5&gK*>X(=dU<~_gpN(m;Wd6 zk?uvH2V{r?3eO>$&;kFgL{kJ&6^Ul z*~L%(JAc>za>5}~|CjUsM`gSgXznWTM=a*Of|L+Ud8;d#I6l-_qLdeBemq91{aNpk2**S1UJkipOV_9BtLBdNiWj_6X5|N;x{FQVu|&* zEMJP)3QX4mT07eauoBzNq@zj|Gjhme;&tQq5_hk9PqEZ|8M#~ zZvj_46wsIC|F=Y!lt)T(iX`umPDp2S7%WK{TQaHsU*}X7D>df7&fL?POi8_N*$Mv<{*T~) N?(}~@{&!p8e*nfWbou}Q literal 0 HcmV?d00001 diff --git a/puzzles/compaq/150/key b/puzzles/compaq/150/key new file mode 100644 index 0000000..164f87f --- /dev/null +++ b/puzzles/compaq/150/key @@ -0,0 +1 @@ +This is our world now... the world of the electron and the switch, the beauty of the baud. diff --git a/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 b/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 new file mode 100755 index 0000000000000000000000000000000000000000..10b82c9d8685eb3da316ad2aa63dafce8c5aaaa9 GIT binary patch literal 31232 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 z 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≶`!B@tb13%i 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 literal 0 HcmV?d00001 diff --git a/puzzles/compaq/350/key b/puzzles/compaq/350/key new file mode 100644 index 0000000..c4fa843 --- /dev/null +++ b/puzzles/compaq/350/key @@ -0,0 +1 @@ +Actually, Werner, we're all tickled to here you say that. Frankly, watchin' Donny beat Nazis to death is is the closest we ever get to goin' to the movies. diff --git a/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de b/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de new file mode 100755 index 0000000000000000000000000000000000000000..41ba5326b6d6b0bb4d6fabbd5ea0783bb92e272d GIT binary patch literal 26257 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 Date: Wed, 7 Oct 2009 19:02:57 -0600 Subject: [PATCH 20/32] updates per neale's suggestions (more to come) --- pollster/pollster.py | 122 +++++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 33 deletions(-) diff --git a/pollster/pollster.py b/pollster/pollster.py index 8ac8160..ca31dc9 100755 --- a/pollster/pollster.py +++ b/pollster/pollster.py @@ -7,30 +7,71 @@ import time import socket import urllib.request +# TODO: +# special stops for http and tftp? +# get start time and end time of poll, sleep(60-exectime) +# what to do about exceptions +# no nested dicts +# scoring interface +# config interface +# html that uses the proper css + DEBUG = True POLL_INTERVAL = 2 IP_DIR = 'iptest/' REPORT_PATH = 'iptest/pollster.html' +SOCK_TIMEOUT = 0.5 -def socket_poll(ip, port, msg): - ''' Connect via socket to the specified (ip, port), send - the specified msg and return the response or None if something - went wrong. ''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +def socket_poll(ip, port, msg, prot, max_recv=1): + ''' Connect via socket to the specified : using the + specified , send the specified and return the + response or None if something went wrong. specifies + how many times to read from the socket. ''' + + # create a socket + try: + sock = socket.socket(socket.AF_INET, prot) + except Exception as e: + print('pollster: create socket failed') + return None + sock.settimeout(SOCK_TIMEOUT) + + # connect try: sock.connect((ip, port)) - except Exception as e: + except socket.timeout as e: + print('pollster: attempt to connect to %s:%d timed out' % (ip, port)) return None - + except Exception as e: + print('pollster: attempt to connect to %s:%d failed' % (ip, port)) + return None + + # send something sock.send(msg) - resp = sock.recv(1024) + + # get a response + resp = '' + try: + # first read + data = sock.recv(1024) + resp += data.decode('utf-8') + max_recv -= 1 + + # remaining reads as necessary until timeout or socket closes + while(len(data) > 0 and max_recv > 0): + data = sock.recv(1024) + resp += data.decode('utf-8') + max_recv -= 1 + sock.close() + except socket.timeout as e: + print('pollster: timed out waiting for a response from %s:%d' % (ip, port)) + except Exception as e: + print('pollster: receive from %s:%d failed' % (ip, port)) + if len(resp) == 0: return None - - sock.close() - resp = resp.decode('utf-8') return resp # PUT POLLS FUNCTIONS HERE @@ -39,39 +80,51 @@ def socket_poll(ip, port, msg): def poll_fingerd(ip): ''' Poll the fingerd service. ''' - resp = socket_poll(ip, 79, b'flag\n') + resp = socket_poll(ip, 79, b'flag\n', socket.SOCK_STREAM) if resp is None: return None return resp.strip('\r\n') def poll_noted(ip): ''' Poll the noted service. ''' - resp = socket_poll(ip, 4000, b'rflag\n') + resp = socket_poll(ip, 4000, b'rflag\n', socket.SOCK_STREAM) if resp is None: return None return resp.strip('\r\n') def poll_catcgi(ip): ''' Poll the cat.cgi web service. ''' - url = urllib.request.urlopen('http://%s/cat.cgi/flag' % ip) - data = url.read() - if len(data) == 0: + request = bytes('GET /cat.cgi/flag HTTP/1.1\r\nHost: %s\r\n\r\n' % ip, 'ascii') + resp = socket_poll(ip, 80, request, socket.SOCK_STREAM, 3) + if resp is None: return None - return data.strip('\r\n') + + content = resp.split('\r\n\r\n') + if len(content) < 3: + return None + + content = content[1].split('\r\n') + + try: + content_len = int(content[0]) + except Exception as e: + return None + + if content_len <= 0: + return None + return content[1].strip('\r\n') def poll_tftpd(ip): - ''' Poll the ftp service. ''' - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.connect((ip, 69)) - - sock.send(b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00') - resp = sock.recv(1024) + ''' Poll the tftp service. ''' + resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM) + if resp is None: + return None + if len(resp) <= 5: return None - sock.close() - - return resp[4:].decode('utf-8').strip('\r\n') + resp = resp.split('\n')[0] + return resp[4:].strip('\r\n') # PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED POLLS = { @@ -86,13 +139,12 @@ ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}') # loop forever while(True): - # check that IP_DIR is there, exit if it isn't - if not os.path.isdir(IP_DIR): - sys.stderr.write('directory %s does not exist or is not readable\n' % IP_DIR) - sys.exit(1) - # gather the list of IPs to poll - ips = os.listdir(IP_DIR) + try: + ips = os.listdir(IP_DIR) + except Exception as e: + print('pollster: could not list dir %s' % IP_DIR) + results = {} for ip in ips: @@ -100,7 +152,11 @@ while(True): if ip_re.match(ip) is None: continue - #os.remove(os.path.join(IP_DIR, ip)) + # remove the file + #try: + # os.remove(os.path.join(IP_DIR, ip)) + #except Exception as e: + # print('pollster: could not remove %s' % os.path.join(IP_DIR, ip)) results[ip] = {} From 62006499e3a6cf53e70c9259ec88d203cdaf6407 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 21:22:16 -0600 Subject: [PATCH 21/32] Twiddled run_tanks.py (don't hurt me, Paul) --- heartbeatd/run.heartbeat | 2 +- tanks/Makefile | 4 +++- tanks/run | 4 +--- tanks/run_tanks.py | 31 +++++++++++++++++-------------- 4 files changed, 22 insertions(+), 19 deletions(-) mode change 100644 => 100755 tanks/run_tanks.py diff --git a/heartbeatd/run.heartbeat b/heartbeatd/run.heartbeat index 3e4f37e..f7704d8 100755 --- a/heartbeatd/run.heartbeat +++ b/heartbeatd/run.heartbeat @@ -4,6 +4,6 @@ # No problem, traceroute can send a UDP packet too. while true; do # Apparently traceroute adds 1 to the base port (-p) - traceroute -m 2 -q 1 -p 8 10.0.0.1 + traceroute -m 2 -q 1 -p 8 10.0.0.1 2>/dev/null >/dev/null sleep 10 done diff --git a/tanks/Makefile b/tanks/Makefile index 26715cd..1310b71 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -24,12 +24,14 @@ target: $(INSTALL) -d target/usr/lib/www/tanks/ $(INSTALL) www/* target/usr/lib/www/tanks/ + ln -s /var/lib/tanks target/usr/lib/www/tanks/results + $(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) -d target/var/service/tanks $(INSTALL) run run_tanks.py target/var/service/tanks/ - + $(INSTALL) -d target/var/service/tanks/log/ $(INSTALL) log.run target/var/service/tanks/log/run diff --git a/tanks/run b/tanks/run index 25540a2..5a5b932 100755 --- a/tanks/run +++ b/tanks/run @@ -2,7 +2,5 @@ [ -f /var/lib/ctf/disabled/tanks ] && exit 0 -ln -s /var/lib/tanks /usr/lib/www/tanks/results - -exec envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1 & +exec envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1 #envuidgid ctf report_score.py 2>&1 diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py old mode 100644 new mode 100755 index 47b31ed..11c792a --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -1,22 +1,25 @@ +#! /usr/bin/python + import time +import optparse from tanks import Pflanzarr -import sys T = 60*5 +parser = optparse.OptionParser('DATA_DIR easy|medium|hard MAX_TURNS') +opts, args = parser.parse_args() +if (len(args) != 3) or (args[1] not in ('easy', 'medium', 'hard')): + parser.error('Wrong number of arguments') try: - while 1: - start = time.time() - p = Pflanzarr.Pflanzarr(sys.argv[1], sys.argv[2]) - p.run(int(sys.argv[3])) - - diff = time.time() - start - if diff - T > 0: - time.sleep( diff - T ) - + turns = int(args[2]) except: - import traceback - traceback.print_exc() - print 'Usage: python2.6 run_tanks.py data_dir easy|medium|hard max_turns' + parser.error('Invalid number of turns') - +while True: + start = time.time() + p = Pflanzarr.Pflanzarr(args[0], args[1]) + p.run(turns) + + diff = time.time() - start + if diff - T > 0: + time.sleep( diff - T ) From 06fd1a2c877168ec34c31a16233861ed17109576 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 7 Oct 2009 22:57:32 -0600 Subject: [PATCH 22/32] Potential new pwnables image --- {heartbeatd => pollster}/in.heartbeatd | 0 {heartbeatd => pollster}/run.heartbeatd | 0 pwnables/Makefile | 12 +----- pwnables/fingerd/Makefile | 1 - pwnables/fingerd/in.fingerd.c | 38 ------------------- pwnables/fingerd/run | 3 -- pwnables/skel/home/flag/.plan | 1 + pwnables/{ => skel/usr/lib/www}/cat.cgi | 0 pwnables/skel/var/lib/cat/flag | 1 + pwnables/skel/var/lib/notes/flag | 1 + pwnables/skel/var/lib/tftp/flag | 1 + .../skel/var/service/heartbeat/run | 0 pwnables/tftpd/run | 3 -- 13 files changed, 6 insertions(+), 55 deletions(-) rename {heartbeatd => pollster}/in.heartbeatd (100%) rename {heartbeatd => pollster}/run.heartbeatd (100%) delete mode 100644 pwnables/fingerd/Makefile delete mode 100644 pwnables/fingerd/in.fingerd.c delete mode 100755 pwnables/fingerd/run create mode 100644 pwnables/skel/home/flag/.plan rename pwnables/{ => skel/usr/lib/www}/cat.cgi (100%) create mode 100644 pwnables/skel/var/lib/cat/flag create mode 100644 pwnables/skel/var/lib/notes/flag create mode 100644 pwnables/skel/var/lib/tftp/flag rename heartbeatd/run.heartbeat => pwnables/skel/var/service/heartbeat/run (100%) delete mode 100755 pwnables/tftpd/run diff --git a/heartbeatd/in.heartbeatd b/pollster/in.heartbeatd similarity index 100% rename from heartbeatd/in.heartbeatd rename to pollster/in.heartbeatd diff --git a/heartbeatd/run.heartbeatd b/pollster/run.heartbeatd similarity index 100% rename from heartbeatd/run.heartbeatd rename to pollster/run.heartbeatd diff --git a/pwnables/Makefile b/pwnables/Makefile index 154b3a8..7eca8ef 100644 --- a/pwnables/Makefile +++ b/pwnables/Makefile @@ -5,9 +5,9 @@ TARGET = $(CURDIR)/target FAKE = fakeroot -s $(CURDIR)/fake -i $(CURDIR)/fake INSTALL = $(FAKE) install -all: 99-pwnables.tce +all: pwnables.tce -99-pwnables.tce: target +pwnables.tce: target $(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@ target: @@ -17,13 +17,5 @@ target: $(MAKE) -C daemons TARGET=$(TARGET) install - $(INSTALL) -d $(TARGET)/usr/lib/www - $(INSTALL) $(CGI) $(TARGET)/usr/lib/www - - $(INSTALL) -D flag $(TARGET)/var/lib/tftp/flag - $(INSTALL) -D flag $(TARGET)/var/lib/notes/flag - $(INSTALL) -D flag $(TARGET)/home/flag/.plan - - clean: rm -rf target diff --git a/pwnables/fingerd/Makefile b/pwnables/fingerd/Makefile deleted file mode 100644 index 74a0f1b..0000000 --- a/pwnables/fingerd/Makefile +++ /dev/null @@ -1 +0,0 @@ -all: in.fingerd diff --git a/pwnables/fingerd/in.fingerd.c b/pwnables/fingerd/in.fingerd.c deleted file mode 100644 index cb5d9e7..0000000 --- a/pwnables/fingerd/in.fingerd.c +++ /dev/null @@ -1,38 +0,0 @@ -#include - -int -main(int argc, char *argv) -{ - char user[256]; - char path[512]; - char *data; - FILE *f; - size_t count; - int i; - - if (NULL == gets(user)) { - return 0; - } - for (data = user; *data; data += 1) { - if ('\r' == *data) { - *data = 0; - } - } - if (0 == user[0]) { - printf("Nobody's home.\n"); - return 0; - } - - sprintf(path, "/home/%s/.plan", user); - f = fopen(path, "r"); - if (NULL == f) { - printf("No such user.\n"); - return 0; - } - - data = path; - while (count = fread(data, sizeof(*data), 1, f)) { - fwrite(data, count, 1, stdout); - } - return 0; -} diff --git a/pwnables/fingerd/run b/pwnables/fingerd/run deleted file mode 100755 index 4088271..0000000 --- a/pwnables/fingerd/run +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/sh - -exec tcpsvd 0 79 /usr/sbin/in.fingerd diff --git a/pwnables/skel/home/flag/.plan b/pwnables/skel/home/flag/.plan new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/home/flag/.plan @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/cat.cgi b/pwnables/skel/usr/lib/www/cat.cgi similarity index 100% rename from pwnables/cat.cgi rename to pwnables/skel/usr/lib/www/cat.cgi diff --git a/pwnables/skel/var/lib/cat/flag b/pwnables/skel/var/lib/cat/flag new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/var/lib/cat/flag @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/skel/var/lib/notes/flag b/pwnables/skel/var/lib/notes/flag new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/var/lib/notes/flag @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/skel/var/lib/tftp/flag b/pwnables/skel/var/lib/tftp/flag new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/var/lib/tftp/flag @@ -0,0 +1 @@ +dirtbags diff --git a/heartbeatd/run.heartbeat b/pwnables/skel/var/service/heartbeat/run similarity index 100% rename from heartbeatd/run.heartbeat rename to pwnables/skel/var/service/heartbeat/run diff --git a/pwnables/tftpd/run b/pwnables/tftpd/run deleted file mode 100755 index bcba031..0000000 --- a/pwnables/tftpd/run +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/sh - -exec udpsvd 0 69 tftpd /var/lib/tftp From 04893b33bd8481c0349e6ca6c9ca47df67306af7 Mon Sep 17 00:00:00 2001 From: Curt Hash Date: Thu, 8 Oct 2009 08:49:50 -0600 Subject: [PATCH 23/32] css additions for pollster page, more robust pollster --- ctf.css | 14 +++++- pollster/pollster.py | 108 +++++++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/ctf.css b/ctf.css index 4131ef8..d3c033a 100644 --- a/ctf.css +++ b/ctf.css @@ -80,4 +80,16 @@ p { .solved { text-decoration: line-through; -} \ No newline at end of file +} + +table.pollster { + margin-left: 5em; +} + +table.pollster td { + padding: 2px 1em 2px 5px; +} + +table.pollster thead { + font-weight: bold; +} diff --git a/pollster/pollster.py b/pollster/pollster.py index ca31dc9..e0bb62b 100755 --- a/pollster/pollster.py +++ b/pollster/pollster.py @@ -5,19 +5,14 @@ import re import sys import time import socket -import urllib.request +import traceback # TODO: -# special stops for http and tftp? -# get start time and end time of poll, sleep(60-exectime) -# what to do about exceptions -# no nested dicts # scoring interface # config interface -# html that uses the proper css -DEBUG = True -POLL_INTERVAL = 2 +DEBUG = False +POLL_INTERVAL = 60 IP_DIR = 'iptest/' REPORT_PATH = 'iptest/pollster.html' SOCK_TIMEOUT = 0.5 @@ -26,13 +21,14 @@ def socket_poll(ip, port, msg, prot, max_recv=1): ''' Connect via socket to the specified : using the specified , send the specified and return the response or None if something went wrong. specifies - how many times to read from the socket. ''' + how many times to read from the socket (default to once). ''' # create a socket try: sock = socket.socket(socket.AF_INET, prot) except Exception as e: - print('pollster: create socket failed') + print('pollster: create socket failed (%s)' % e) + traceback.print_exc() return None sock.settimeout(SOCK_TIMEOUT) @@ -41,10 +37,12 @@ def socket_poll(ip, port, msg, prot, max_recv=1): try: sock.connect((ip, port)) except socket.timeout as e: - print('pollster: attempt to connect to %s:%d timed out' % (ip, port)) + print('pollster: attempt to connect to %s:%d timed out (%s)' % (ip, port, e)) + traceback.print_exc() return None except Exception as e: - print('pollster: attempt to connect to %s:%d failed' % (ip, port)) + print('pollster: attempt to connect to %s:%d failed (%s)' % (ip, port, e)) + traceback.print_exc() return None # send something @@ -64,10 +62,13 @@ def socket_poll(ip, port, msg, prot, max_recv=1): resp += data.decode('utf-8') max_recv -= 1 sock.close() + except socket.timeout as e: - print('pollster: timed out waiting for a response from %s:%d' % (ip, port)) + print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e)) + traceback.print_exc() except Exception as e: - print('pollster: receive from %s:%d failed' % (ip, port)) + print('pollster: receive from %s:%d failed (%s)' % (ip, port, e)) + traceback.print_exc() if len(resp) == 0: return None @@ -79,21 +80,21 @@ def socket_poll(ip, port, msg, prot, max_recv=1): # if (a) the service is not up, (b) it doesn't return a valid team name. def poll_fingerd(ip): - ''' Poll the fingerd service. ''' + ''' Poll the fingerd service. Returns None or a team name. ''' resp = socket_poll(ip, 79, b'flag\n', socket.SOCK_STREAM) if resp is None: return None return resp.strip('\r\n') def poll_noted(ip): - ''' Poll the noted service. ''' + ''' Poll the noted service. Returns None or a team name. ''' resp = socket_poll(ip, 4000, b'rflag\n', socket.SOCK_STREAM) if resp is None: return None return resp.strip('\r\n') def poll_catcgi(ip): - ''' Poll the cat.cgi web service. ''' + ''' Poll the cat.cgi web service. Returns None or a team name. ''' request = bytes('GET /cat.cgi/flag HTTP/1.1\r\nHost: %s\r\n\r\n' % ip, 'ascii') resp = socket_poll(ip, 80, request, socket.SOCK_STREAM, 3) if resp is None: @@ -115,7 +116,7 @@ def poll_catcgi(ip): return content[1].strip('\r\n') def poll_tftpd(ip): - ''' Poll the tftp service. ''' + ''' Poll the tftp service. Returns None or a team name. ''' resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM) if resp is None: return None @@ -137,15 +138,28 @@ POLLS = { ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}') # loop forever -while(True): +while True: + + t_start = time.time() # gather the list of IPs to poll try: ips = os.listdir(IP_DIR) except Exception as e: - print('pollster: could not list dir %s' % IP_DIR) + print('pollster: could not list dir %s (%s)' % (IP_DIR, e)) + traceback.print_exc() + + try: + os.remove(REPORT_PATH) + except Exception as e: + pass + + out = open(REPORT_PATH, 'w') + out.write('\n\n') + out.write('Pollster Results\n') + out.write('\n') + out.write('\n

Polling Results

\n') - results = {} for ip in ips: # check file name format is ip @@ -153,49 +167,45 @@ while(True): continue # remove the file - #try: - # os.remove(os.path.join(IP_DIR, ip)) - #except Exception as e: - # print('pollster: could not remove %s' % os.path.join(IP_DIR, ip)) + try: + os.remove(os.path.join(IP_DIR, ip)) + except Exception as e: + print('pollster: could not remove %s' % os.path.join(IP_DIR, ip)) + traceback.print_exc() - results[ip] = {} + results = {} if DEBUG is True: print('ip: %s' % ip) + out.write('

%s

\n' % ip) + out.write('\n') + out.write('\n') + # perform polls for service,func in POLLS.items(): team = func(ip) if team is None: team = 'dirtbags' - results[ip][service] = team + if DEBUG is True: + print('\t%s - %s' % (service, team)) - if DEBUG is True: - for k,v in results[ip].items(): - print('\t%s - %s' % (k,v)) + out.write('\n' % (service, team)) - if DEBUG is True: - print('+-----------------------------------------+') - - # allocate points - - # generate html report - out = open(REPORT_PATH, 'w') - out.write('\n<head>Polling Results</head>\n') - out.write('\n

Polling Results

\n') - - for ip in results.keys(): - out.write('

%s

\n' % ip) - out.write('
Service NameFlag Holder
%s%s
\n') - out.write('\n') - for service,flag_holder in results[ip].items(): - out.write('\n' % (service, flag_holder)) out.write('
Service NameFlag Holder
%s%s
\n') + if DEBUG is True: + print('+-----------------------------------------+') + + t_end = time.time() + exec_time = int(t_end - t_start) + sleep_time = POLL_INTERVAL - exec_time + + out.write('

Next poll in: %ds

\n' % sleep_time) out.write('\n\n') out.close() - - # sleep until its time to poll again - time.sleep(POLL_INTERVAL) + + # sleep until its time to poll again + time.sleep(sleep_time) From adca40f63e83ed8970b116de2e91a0b54d08da02 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Thu, 8 Oct 2009 09:04:07 -0600 Subject: [PATCH 24/32] Various changes to tanks run scripts. --- tanks/Makefile | 2 +- tanks/run_tanks.py | 20 +++++++++++++++++++- tanks/www/results.cgi | 20 +++++++++++++------- tanksFlagger/Makefile | 20 ++++++++++++++++++++ tanksFlagger/log.run | 3 +++ {tanks => tanksFlagger}/report_score.py | 0 tanksFlagger/run | 5 +++++ 7 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 tanksFlagger/Makefile create mode 100755 tanksFlagger/log.run rename {tanks => tanksFlagger}/report_score.py (100%) create mode 100755 tanksFlagger/run diff --git a/tanks/Makefile b/tanks/Makefile index 1310b71..4c83034 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -24,7 +24,7 @@ target: $(INSTALL) -d target/usr/lib/www/tanks/ $(INSTALL) www/* target/usr/lib/www/tanks/ - ln -s /var/lib/tanks target/usr/lib/www/tanks/results + ln -s /var/lib/tanks/results target/usr/lib/www/tanks/results $(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/ diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index 11c792a..ab6a8d6 100755 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -1,10 +1,13 @@ #! /usr/bin/python -import time import optparse +import shutil +import time from tanks import Pflanzarr T = 60*5 +MAX_HIST = 30 +HIST_STEP = 100 parser = optparse.OptionParser('DATA_DIR easy|medium|hard MAX_TURNS') opts, args = parser.parse_args() @@ -20,6 +23,21 @@ while True: p = Pflanzarr.Pflanzarr(args[0], args[1]) p.run(turns) + path = os.path.join(args[0], 'results') + files = os.listdir(path) + gameNums = [] + for file in files: + try: + gameNums.append( int(file) ) + except: + continue + + gameNums.sort(reverse=True) + highest = gameNums[0] + for num in gameNums: + if highest - MAX_HIST > num and not (num % HIST_STEP == 0): + shutil.rmtree(os.path.join(path, num)) + diff = time.time() - start if diff - T > 0: time.sleep( diff - T ) diff --git a/tanks/www/results.cgi b/tanks/www/results.cgi index a17ee77..ceb4ae2 100755 --- a/tanks/www/results.cgi +++ b/tanks/www/results.cgi @@ -28,22 +28,28 @@ except: if not games: print "

No games have occurred yet." + gameNums = [] for game in games: try: - num = int(game) - path = os.path.join( 'results', game, 'results.html') - if os.path.exists( path ): - gameNums.append( int(num) ) - else: - continue - + gameNums.append( int(game) ) except: continue gameNums.sort(reverse=True) +# Don't include games that haven't completed +i = 0 +num = str(gameNums[i]) +for i in range(len(gameNums)): + path = os.path.join( 'results', str(gameNums[i]), 'results.html') ) + if os.path.exists( path ): + break +gameNums = gameNums[i:] + for num in gameNums: print '

%d - ' % num, print 'v' % num, print 'r' % num + +print '' diff --git a/tanksFlagger/Makefile b/tanksFlagger/Makefile new file mode 100644 index 0000000..7bd61de --- /dev/null +++ b/tanksFlagger/Makefile @@ -0,0 +1,20 @@ +FAKE = fakeroot -s fake -i fake +INSTALL = $(FAKE) install -o 100 + +all: tanksFlagger.tce + +push: tanksFlagger.tce + netcat -l -q 0 -p 3333 < tanksFlagger.tce + +tanksFlagger.tce: target + $(FAKE) sh -c 'cd target && tar -czf - .' > $@ + +target: + $(INSTALL) -d target/var/service/tanksFlagger + $(INSTALL) run report_score.py target/var/service/tanksFlagger/ + + $(INSTALL) -d target/var/service/tanksFlagger/log/ + $(INSTALL) log.run target/var/service/tanksFlagger/log/run + +clean: + rm -rf target tanksFlagger.tce fake diff --git a/tanksFlagger/log.run b/tanksFlagger/log.run new file mode 100755 index 0000000..f7a77bf --- /dev/null +++ b/tanksFlagger/log.run @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t tanksFlagger diff --git a/tanks/report_score.py b/tanksFlagger/report_score.py similarity index 100% rename from tanks/report_score.py rename to tanksFlagger/report_score.py diff --git a/tanksFlagger/run b/tanksFlagger/run new file mode 100755 index 0000000..e2eb214 --- /dev/null +++ b/tanksFlagger/run @@ -0,0 +1,5 @@ +#! /bin/sh + +[ -f /var/lib/ctf/disabled/tanks ] && exit 0 + +exec envuidgid ctf python3 report_score.py 2>&1 From 30e18ea544db889931a957e87e3e32c318180060 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Thu, 8 Oct 2009 09:12:41 -0600 Subject: [PATCH 25/32] Fixed type error, made results rows keyed by color. --- tanks/lib/Pflanzarr.py | 11 ++++++++--- tanks/lib/Tank.py | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 0922d49..1eaa501 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -205,12 +205,17 @@ class Pflanzarr: '
TeamKillsCause of Death'] for tank in tanks: if tank is winner: - rowStyle = 'style="color:red;"' + rowStyle = 'style="font-weight:bold; '\ + 'background-color:%s"' % tank._color else: - rowStyle = '' + rowStyle = 'style="background-color:%s"' % tank._color + if name: + name = xml.sax.saxutils.escape(tank.name) + else: + name = '#default' html.append('
%s%d%s' % (rowStyle, - xml.sax.saxutils.escape(tank.name), + name, len(kills[tank]), xml.sax.saxutils.escape(tank.deathReason))) diff --git a/tanks/lib/Tank.py b/tanks/lib/Tank.py index dffa85e..7042a59 100644 --- a/tanks/lib/Tank.py +++ b/tanks/lib/Tank.py @@ -75,7 +75,7 @@ class Tank(object): else: self._tAngle = tAngle - self._color = color + self.color = color # You can't fire until fireReady is 0. self._fireReady = self.FIRE_RATE @@ -466,7 +466,7 @@ class Tank(object): # The base body rectangle. for poly in gm.displacePoly(hood, self.pos, self._limits): - d.polygon( poly, fill=self._color ) + d.polygon( poly, fill=self.color ) # The treads for poly in gm.displacePoly(tread1, self.pos, self._limits) + \ @@ -475,7 +475,7 @@ class Tank(object): # The turret circle for poly in gm.displacePoly(self.body, self.pos, self._limits): - d.ellipse( poly, fill=self._color, outline='black') + d.ellipse( poly, fill=self.color, outline='black') self._drawLaser(d) @@ -491,7 +491,7 @@ class Tank(object): if self._fired: laser = gm.rotatePoly( self.laser, self._angle + self._tAngle ) for poly in gm.displacePoly(laser, self.pos, self._limits): - drawing.polygon(poly, fill=self._color) + drawing.polygon(poly, fill=self.color) self._fired = False @@ -522,7 +522,7 @@ class Tank(object): if self._sensorState[i]: color = '#000000' else: - color = self._color + color = self.color r, angle, width, tAttached = self._sensors[i] r = int(r) From dd281b57062339b3157991545677c36b2056348a Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Thu, 8 Oct 2009 09:16:29 -0600 Subject: [PATCH 26/32] Changed results page to have the correct stylesheet. --- tanks/lib/Pflanzarr.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 1eaa501..d7d50e0 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -201,7 +201,11 @@ class Pflanzarr: break winner = random.choice(winners) - html = ['', + html = ['', + 'Game %d results', + '', + '', + '', '\n' % (service, team)) + + point_queue.put((service, team, 1)) + + if out is not None: + out.write('
TeamKillsCause of Death'] for tank in tanks: if tank is winner: From 1ba4f341f6af7058bef2cbb4446c3ced1bbf6ecf Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 8 Oct 2009 10:31:28 -0600 Subject: [PATCH 27/32] Fix exception in flagd.py:FlagServer.handle_close --- ctf/flagd.py | 2 ++ pwnables/Makefile | 2 +- pwnables/skel/usr/lib/www/flag | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 120000 pwnables/skel/usr/lib/www/flag diff --git a/ctf/flagd.py b/ctf/flagd.py index 6ae444b..f827972 100755 --- a/ctf/flagd.py +++ b/ctf/flagd.py @@ -142,6 +142,8 @@ class FlagServer(asynchat.async_chat): self.inbuf.append(data) def set_flag(self, team): + if not self.cat: + return self.flag = team self.submitter.set_flag(self.cat, team) f = open(os.path.join(flags_dir, self.cat), 'w') diff --git a/pwnables/Makefile b/pwnables/Makefile index 7eca8ef..5327560 100644 --- a/pwnables/Makefile +++ b/pwnables/Makefile @@ -18,4 +18,4 @@ target: $(MAKE) -C daemons TARGET=$(TARGET) install clean: - rm -rf target + rm -rf target pwnables.tce diff --git a/pwnables/skel/usr/lib/www/flag b/pwnables/skel/usr/lib/www/flag new file mode 120000 index 0000000..f4bdb9f --- /dev/null +++ b/pwnables/skel/usr/lib/www/flag @@ -0,0 +1 @@ +/var/lib/cat/flag \ No newline at end of file From a1faaed72201dbee07a5d9a9a4cafb3fb414f06d Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 8 Oct 2009 11:03:50 -0600 Subject: [PATCH 28/32] Make run_tanks use asyncore --- tanks/run_tanks.py | 76 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index ab6a8d6..f680bbe 100755 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -3,23 +3,45 @@ import optparse import shutil import time +import asyncore +import asynchat from tanks import Pflanzarr T = 60*5 MAX_HIST = 30 -HIST_STEP = 100 +HIST_STEP = 100 +key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca' -parser = optparse.OptionParser('DATA_DIR easy|medium|hard MAX_TURNS') -opts, args = parser.parse_args() -if (len(args) != 3) or (args[1] not in ('easy', 'medium', 'hard')): - parser.error('Wrong number of arguments') -try: - turns = int(args[2]) -except: - parser.error('Invalid number of turns') +class Flagger(asynchat.async_chat): + """Use to connect to flagd and submit the current flag holder.""" -while True: - start = time.time() + def __init__(self, addr, auth): + asynchat.async_chat.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.connect((addr, 6668)) + self.push(auth + '\n') + self.flag = None + + def handle_read(self): + msg = self.recv(4096) + raise ValueError("Flagger died: %r" % msg) + + def handle_error(self): + # If we lose the connection to flagd, nobody can score any + # points. Terminate everything. + asyncore.close_all() + asynchat.async_chat.handle_error(self) + + def set_flag(self, team): + if team: + eteam = team + else: + eteam = '' + self.push(eteam + '\n') + self.flag = team + + +def run_tanks(): p = Pflanzarr.Pflanzarr(args[0], args[1]) p.run(turns) @@ -38,6 +60,32 @@ while True: if highest - MAX_HIST > num and not (num % HIST_STEP == 0): shutil.rmtree(os.path.join(path, num)) - diff = time.time() - start - if diff - T > 0: - time.sleep( diff - T ) + try: + winner = open('/var/lib/tanks/winner').read().strip() + except: + winner = None + flagger.set_flag(winner) + + +def main(): + parser = optparse.OptionParser('DATA_DIR easy|medium|hard MAX_TURNS') + opts, args = parser.parse_args() + if (len(args) != 3) or (args[1] not in ('easy', 'medium', 'hard')): + parser.error('Wrong number of arguments') + try: + turns = int(args[2]) + except: + parser.error('Invalid number of turns') + + + flagger = Flagger('localhost', key) + lastrun = 0 + while True: + asyncore.loop(60, count=1) + now = time.time() + if now - lastrun >= 60: + run_tanks() + lastrun = now + +if __name__ == '__main__': + main() From 7c07f13117c25a2155d915e473e259eebaaa8fc9 Mon Sep 17 00:00:00 2001 From: Curt Hash Date: Thu, 8 Oct 2009 11:11:41 -0600 Subject: [PATCH 29/32] config and points submission stuff added to polster --- ctf/config.py | 4 +++ pollster/pollster.py | 80 ++++++++++++++++++++++++++++++++------------ 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/ctf/config.py b/ctf/config.py index 56ab872..65f9ae2 100755 --- a/ctf/config.py +++ b/ctf/config.py @@ -37,6 +37,10 @@ else: 'house_team': 'dirtbags', 'passwd': '/var/lib/ctf/passwd', 'team_colors': team_colors, + 'poll_interval': 60, + 'poll_timeout': 0.5, + 'heartbeat_dir': '/var/lib/pollster', + 'poll_dir': '/var/lib/www', }, 'puzzler': {'dir': '/usr/lib/www/puzzler', diff --git a/pollster/pollster.py b/pollster/pollster.py index e0bb62b..bde9fea 100755 --- a/pollster/pollster.py +++ b/pollster/pollster.py @@ -6,16 +6,37 @@ import sys import time import socket import traceback +import threading +import queue -# TODO: -# scoring interface -# config interface +from ctf import config +from ctf import pointscli DEBUG = False -POLL_INTERVAL = 60 -IP_DIR = 'iptest/' -REPORT_PATH = 'iptest/pollster.html' -SOCK_TIMEOUT = 0.5 +POLL_INTERVAL = config.get('poll_interval') +IP_DIR = config.get('heartbeat_dir') +REPORT_PATH = config.get('poll_dir') +SOCK_TIMEOUT = config.get('poll_timeout') + +class PointSubmitter(threading.Thread): + ''' Pulls point allocations from the queue and submits them. ''' + def __init__(self, point_queue): + threading.Thread.__init__(self) + self.point_queue = point_queue + self.sock = pointscli.makesock('localhost') + + def run(self): + # loop forever + while(True): + cat, team, score = self.point_queue.get() + if None in [cat, team, score]: + continue + + try: + pointscli.submit(cat, team, score, sock=self.sock) + except ValueError: + print('pollster: error submitting score (%s, %s, %d)' % (cat, team, score)) + traceback.print_exc() def socket_poll(ip, port, msg, prot, max_recv=1): ''' Connect via socket to the specified : using the @@ -137,6 +158,11 @@ POLLS = { ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}') +# start point submitter thread +point_queue = queue.Queue() +t = PointSubmitter(point_queue) +t.start() + # loop forever while True: @@ -154,11 +180,17 @@ while True: except Exception as e: pass - out = open(REPORT_PATH, 'w') - out.write('\n\n') - out.write('Pollster Results\n') - out.write('\n') - out.write('\n

Polling Results

\n') + try: + out = open(REPORT_PATH, 'w') + except Exception as e: + out = None + pass + + if out is not None: + out.write('\n\n') + out.write('Pollster Results\n') + out.write('\n') + out.write('\n

Polling Results

\n') for ip in ips: @@ -178,9 +210,10 @@ while True: if DEBUG is True: print('ip: %s' % ip) - out.write('

%s

\n' % ip) - out.write('\n') - out.write('\n') + if out is not None: + out.write('

%s

\n' % ip) + out.write('
Service NameFlag Holder
\n') + out.write('\n') # perform polls for service,func in POLLS.items(): @@ -191,9 +224,13 @@ while True: if DEBUG is True: print('\t%s - %s' % (service, team)) - out.write('\n' % (service, team)) - - out.write('
Service NameFlag Holder
%s%s
\n') + if out is not None: + out.write('
%s%s
\n') if DEBUG is True: print('+-----------------------------------------+') @@ -202,9 +239,10 @@ while True: exec_time = int(t_end - t_start) sleep_time = POLL_INTERVAL - exec_time - out.write('

Next poll in: %ds

\n' % sleep_time) - out.write('\n\n') - out.close() + if out is not None: + out.write('

Next poll in: %ds

\n' % sleep_time) + out.write('\n\n') + out.close() # sleep until its time to poll again time.sleep(sleep_time) From 4f44d0bbb692b90d4caa13ce271c0960f7443a22 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Thu, 8 Oct 2009 11:15:06 -0600 Subject: [PATCH 30/32] I hate neale. --- tanks/lib/GameMath.py | 2 +- tanks/lib/Pflanzarr.py | 6 +++--- tanks/run_tanks.py | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tanks/lib/GameMath.py b/tanks/lib/GameMath.py index ff47880..481bf81 100644 --- a/tanks/lib/GameMath.py +++ b/tanks/lib/GameMath.py @@ -48,7 +48,7 @@ def displacePoly(points, disp, limits, coordSequence=False): maxX, maxY = limits basePoints = [] for point in points: - x,y = point[0] + disp[0], point[1] + disp[1] + x,y = int(point[0] + disp[0]), int(point[1] + disp[1]) # Check if duplication is needed on each axis if x > maxX: diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index d7d50e0..8b19777 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -203,16 +203,16 @@ class Pflanzarr: html = ['', 'Game %d results', - '', + '', '', '', '
TeamKillsCause of Death'] for tank in tanks: if tank is winner: rowStyle = 'style="font-weight:bold; '\ - 'background-color:%s"' % tank._color + 'background-color:%s"' % tank.color else: - rowStyle = 'style="background-color:%s"' % tank._color + rowStyle = 'style="background-color:%s"' % tank.color if name: name = xml.sax.saxutils.escape(tank.name) else: diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index f680bbe..f8950d1 100755 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -1,10 +1,11 @@ #! /usr/bin/python +import asynchat +import asyncore import optparse import shutil +import socket import time -import asyncore -import asynchat from tanks import Pflanzarr T = 60*5 @@ -41,7 +42,7 @@ class Flagger(asynchat.async_chat): self.flag = team -def run_tanks(): +def run_tanks(args, turns): p = Pflanzarr.Pflanzarr(args[0], args[1]) p.run(turns) @@ -84,7 +85,7 @@ def main(): asyncore.loop(60, count=1) now = time.time() if now - lastrun >= 60: - run_tanks() + run_tanks(args, turns) lastrun = now if __name__ == '__main__': From 82351791296f7eb90730e831f79cf2b47f851b06 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Thu, 8 Oct 2009 11:18:40 -0600 Subject: [PATCH 31/32] More bug fixes. --- tanks/lib/Pflanzarr.py | 8 ++++---- tanks/run_tanks.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 8b19777..ac600a2 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -213,7 +213,7 @@ class Pflanzarr: 'background-color:%s"' % tank.color else: rowStyle = 'style="background-color:%s"' % tank.color - if name: + if tank.name: name = xml.sax.saxutils.escape(tank.name) else: name = '#default' @@ -250,9 +250,9 @@ class Pflanzarr: clearFrames = ['rm', '-rf', '%s' % self._imageDir] print 'Making Movie' - subprocess.call(movieCmd) -# subprocess.call(movieCmd, stderr=open('/dev/null', 'w'), -# stdout=open('/dev/null', 'w')) +# subprocess.call(movieCmd) + subprocess.call(movieCmd, stderr=open('/dev/null', 'w'), + stdout=open('/dev/null', 'w')) subprocess.call(clearFrames) def _outputErrors(self, tank): diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index f8950d1..ba52a28 100755 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -3,6 +3,7 @@ import asynchat import asyncore import optparse +import os import shutil import socket import time @@ -59,7 +60,7 @@ def run_tanks(args, turns): highest = gameNums[0] for num in gameNums: if highest - MAX_HIST > num and not (num % HIST_STEP == 0): - shutil.rmtree(os.path.join(path, num)) + shutil.rmtree(os.path.join(path, str(num))) try: winner = open('/var/lib/tanks/winner').read().strip() From a4ebc71b58122bb8b2cfb3b53f428e66df9deb3f Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Thu, 8 Oct 2009 11:19:46 -0600 Subject: [PATCH 32/32] This was neale's fault. --- tanks/run_tanks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index ba52a28..ae83990 100755 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -43,7 +43,7 @@ class Flagger(asynchat.async_chat): self.flag = team -def run_tanks(args, turns): +def run_tanks(args, turns, flagger): p = Pflanzarr.Pflanzarr(args[0], args[1]) p.run(turns) @@ -86,7 +86,7 @@ def main(): asyncore.loop(60, count=1) now = time.time() if now - lastrun >= 60: - run_tanks(args, turns) + run_tanks(args, turns, flagger) lastrun = now if __name__ == '__main__':