diff --git a/Makefile b/Makefile index d644fd8..e6dd21d 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,10 @@ target: $(PYC) $(INSTALL) -d $(PYCDIR)/ctf $(INSTALL) $(PYC) $(PYCDIR)/ctf + $(INSTALL) -d $(DESTDIR)/usr/lib/python2.6/site-packages/ctf + $(INSTALL) ctf/__init__.py $(DESTDIR)/usr/lib/python2.6/site-packages/ctf + $(INSTALL) ctf/config.py $(DESTDIR)/usr/lib/python2.6/site-packages/ctf + $(INSTALL) -d $(DESTDIR)/usr/sbin $(INSTALL) ctfd.py $(DESTDIR)/usr/sbin $(INSTALL) new-contest $(DESTDIR)/usr/sbin diff --git a/ctf.css b/ctf.css index d3c033a..0d6ed28 100644 --- a/ctf.css +++ b/ctf.css @@ -7,7 +7,7 @@ html { body { font-family: sans-serif; color: #fff; - margin: 50px 0 0 100px; + margin: 50px 0 0 110px; padding: 10px; max-width: 700px; } @@ -30,6 +30,54 @@ h1:first-child:before { content: "Capture The Flag: "; } +/*** left side bar ***/ + +#navigation { + position: absolute; + background: #222; + opacity: 0.9; + top: 80px; + left: 0px; + padding: 0; +} + +#navigation h3 { + font-size: 100%; + border-bottom: 2px solid #444; +} + +#navigation ul { + list-style: none; + padding: 0; + margin: 0; +} + +#navigation li a { + display: block; + height: 25px; + width: 90px; + padding: 5px; + margin: 5px; + background: inherit; + border-right: 4px solid #444; + color: #999; + text-transform: lowercase; + font-size: 0.9em; +} + +#navigation li a:hover { + color: #f4f4f4; + background: #333; + border-right: 4px solid #2a2; +} + +#navigation li .active { + color: #999; + background: #333; + border-right: 4px solid #444; +} + + /**** body ****/ a img { @@ -64,6 +112,39 @@ th, td { vertical-align: top; } +p { + line-height: 1.4em; + margin-bottom: 20px; + color: #f4f4f4; +} + +hr { + border: 1px solid #444; +} + +dt { + white-space: pre; + background-color: #333; + padding: 5px; + border: 2px solid green; + border-bottom: none; + font-weight: bold; +} + +dd { + border: 2px solid green; + margin: 0px; + padding: 5px; + background-color: #282828; +} + + +/**** special cases ****/ + +.wide { + max-width: inherit; +} + .scoreboard { background: #222; } @@ -72,24 +153,20 @@ th, td { height: 400px; } -p { - line-height: 1.4em; - margin-bottom: 20px; - color: #f4f4f4; -} - .solved { text-decoration: line-through; } table.pollster { - margin-left: 5em; + margin-left: 5em; } table.pollster td { - padding: 2px 1em 2px 5px; + padding: 2px 1em 2px 5px; } table.pollster thead { - font-weight: bold; + font-weight: bold; } + + diff --git a/ctf/config.py b/ctf/config.py index 65f9ae2..1bdbe96 100755 --- a/ctf/config.py +++ b/ctf/config.py @@ -28,27 +28,33 @@ if 'home' in os.environ.get('SCRIPT_FILENAME', ''): } else: # An actual installation - config = {'global': - {'data_dir': '/var/lib/ctf', - 'base_url': '/', - 'css_url': '/ctf.css', - 'disabled_dir': '/var/lib/ctf/disabled', - 'flags_dir': '/var/lib/ctf/flags', - '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', - 'cgi_url': '/puzzler.cgi', - 'base_url': '/puzzler', - 'keys_file': '/usr/lib/ctf/puzzler.keys', - }, - } + config = { + 'global': + { + 'data_dir': '/var/lib/ctf', + 'base_url': '/', + 'css_url': '/ctf.css', + 'disabled_dir': '/var/lib/ctf/disabled', + 'flags_dir': '/var/lib/ctf/flags', + 'house_team': 'dirtbags', + 'passwd': '/var/lib/ctf/passwd', + 'team_colors': team_colors, + }, + 'pollster': + { + 'poll_interval': 60, + 'poll_timeout': 0.5, + 'heartbeat_dir': '/var/lib/pollster', + 'results': '/var/lib/pollster/status.html', + }, + 'puzzler': + { + 'dir': '/usr/lib/www/puzzler', + 'cgi_url': '/puzzler.cgi', + 'base_url': '/puzzler', + 'keys_file': '/usr/lib/ctf/puzzler.keys', + }, + } def get(section, key): return config[section][key] @@ -71,22 +77,45 @@ def datafile(filename): def url(path): return base_url + path -def start_html(title): +def start_html(title, hdr='', cls='', links=[], links_title=None): + ret = [] if os.environ.get('GATEWAY_INTERFACE'): - print('Content-type: text/html') - print() - print(''' + ret.append('Content-type: text/html') + ret.append('') + ret.append(''' - %s - + %(title)s + + %(hdr)s - -

%s

-''' % (title, css, title)) + +

%(title)s

+ ') + return '\n'.join(ret) def end_html(): - print('') + return '' diff --git a/ctf/histogram.py b/ctf/histogram.py index 542b534..3c7426c 100755 --- a/ctf/histogram.py +++ b/ctf/histogram.py @@ -59,12 +59,13 @@ set xtics nomirror set ytics nomirror set nokey set terminal png transparent size 640,200 x000000 xffffff -set output "%(pngout)s" +set output "%(pngout)s,tmp" plot %(plot)s\n''' % {'plot': ','.join(plotparts), 'pngout': pngout}) instructions.flush() gp = os.system('gnuplot %s 2>/dev/null - - - - Team Registration - - - -

%s

-''' % (config.css, title) - -def foot(): - return '''''' - def main(): - print('Content-type: text/html') - print() - f = cgi.FieldStorage() team = f.getfirst('team', '') diff --git a/ctf/scoreboard.py b/ctf/scoreboard.py index 43a1f96..4d25594 100755 --- a/ctf/scoreboard.py +++ b/ctf/scoreboard.py @@ -15,20 +15,8 @@ def main(): categories = [(cat, s.cat_points(cat)) for cat in s.categories()] - print('Content-type: text/html') print('Refresh: 10') - print() - print(''' - - - - Scoreboard - - - -

Scoreboard

- ''' % config.base_url) + print(config.start_html('Scoreboard', cls='wide')) print('') print('') print('') @@ -71,9 +59,8 @@ def main():

scores over time

- - - ''') +''') + print(config.end_html()) if __name__ == '__main__': main() diff --git a/ctfd.py b/ctfd.py index bd34ea5..3b2a23c 100755 --- a/ctfd.py +++ b/ctfd.py @@ -20,13 +20,12 @@ def chart(s): def reap(): try: while True: - os.waitpid(0, os.WNOHANG) + pid, ret = os.waitpid(0, os.WNOHANG) + if not pid: + break except OSError: pass -def sigchld(signum, frame): - do_reap = True - def main(): p = optparse.OptionParser() p.add_option('-p', '--genpass', dest='cat', default=None, @@ -39,13 +38,11 @@ def main(): pointsrv = pointsd.start() flagsrv = flagd.start() - signal.signal(signal.SIGCHLD, sigchld) s = pointsrv.store slen = 0 while True: - if do_reap: - reap() asyncore.loop(timeout=30, use_poll=True, count=1) + reap() if len(s) > slen: slen = len(s) chart(s) diff --git a/mkpuzzles.py b/mkpuzzles.py index 243bf29..ab51030 100755 --- a/mkpuzzles.py +++ b/mkpuzzles.py @@ -17,6 +17,37 @@ opts, args = p.parse_args() keys = [] +js = ''' + +''' + for cat in os.listdir(opts.puzzles): dirname = os.path.join(opts.puzzles, cat) for points in os.listdir(dirname): @@ -46,47 +77,7 @@ for cat in os.listdir(opts.puzzles): title = '%s for %s points' % (cat, points) f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8') - f.write(''' - - - - %(title)s - - - - -

%(title)s

-''' % {'title': title, - 'css': config.css}) + f.write(config.start_html(title, js)) if readme: f.write('
%s
\n' % readme) if files: @@ -108,11 +99,10 @@ for cat in os.listdir(opts.puzzles): - - ''' % {'cgi': config.get('puzzler', 'cgi_url'), 'cat': cat, 'points': points}) + f.write(config.end_html()) f = open(opts.keyfile, 'w', encoding='utf-8') for key in keys: diff --git a/new-contest b/new-contest index 2325e44..55e8afc 100755 --- a/new-contest +++ b/new-contest @@ -13,6 +13,8 @@ rotate /var/lib/ctf/scores.dat rotate /var/lib/ctf/passwd rm -f /var/lib/ctf/flags/* || true +sv restart /var/service/ctf + echo "Things you may want to tweak:" find /var/lib/ctf/disabled find /var/lib/kevin/tokens diff --git a/pollster/in.heartbeatd b/pollster/in.heartbeatd index e991a6a..6d66bff 100755 --- a/pollster/in.heartbeatd +++ b/pollster/in.heartbeatd @@ -1,8 +1,9 @@ #! /bin/sh -case "$REMOTEADDR" in - 10.0.0.[2-254]) - touch /var/lib/pollster/$REMOTEADDR +ip=$(echo $UDPREMOTEADDR | cut -d: -f1) +case "$ip" in + 10.0.0.*) + touch /var/lib/pollster/$ip ;; esac - +echo 'Hello.' diff --git a/pollster/log.run.pollster b/pollster/log.run.pollster new file mode 100755 index 0000000..4325add --- /dev/null +++ b/pollster/log.run.pollster @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t pollster diff --git a/pollster/pollster.py b/pollster/pollster.py index bde9fea..0016464 100755 --- a/pollster/pollster.py +++ b/pollster/pollster.py @@ -2,45 +2,25 @@ import os import re +import io import sys import time import socket import traceback -import threading -import queue from ctf import config from ctf import pointscli -DEBUG = False -POLL_INTERVAL = config.get('poll_interval') -IP_DIR = config.get('heartbeat_dir') -REPORT_PATH = config.get('poll_dir') -SOCK_TIMEOUT = config.get('poll_timeout') +DEBUG = False +POLL_INTERVAL = config.get('pollster', 'poll_interval') +IP_DIR = config.get('pollster', 'heartbeat_dir') +REPORT_PATH = config.get('pollster', 'results') +SOCK_TIMEOUT = config.get('pollster', '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 - specified , send the specified and return the + specified , send the specified and return the response or None if something went wrong. specifies how many times to read from the socket (default to once). ''' @@ -51,7 +31,7 @@ def socket_poll(ip, port, msg, prot, max_recv=1): print('pollster: create socket failed (%s)' % e) traceback.print_exc() return None - + sock.settimeout(SOCK_TIMEOUT) # connect @@ -70,19 +50,16 @@ def socket_poll(ip, port, msg, prot, max_recv=1): sock.send(msg) # get a response - resp = '' + 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): + # read from the socket until responses or read, + # a timeout occurs, the socket closes, or some other exception + # is raised + for i in range(max_recv): data = sock.recv(1024) - resp += data.decode('utf-8') - max_recv -= 1 - sock.close() + if len(data) == 0: + break + resp.append(data) except socket.timeout as e: print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e)) @@ -90,11 +67,13 @@ def socket_poll(ip, port, msg, prot, max_recv=1): except Exception as e: print('pollster: receive from %s:%d failed (%s)' % (ip, port, e)) traceback.print_exc() - + + sock.close() + if len(resp) == 0: return None - return resp + return b''.join(resp) # PUT POLLS FUNCTIONS HERE # Each function should take an IP address and return a team name or None @@ -105,14 +84,14 @@ def poll_fingerd(ip): resp = socket_poll(ip, 79, b'flag\n', socket.SOCK_STREAM) if resp is None: return None - return resp.strip('\r\n') + return resp.strip(b'\r\n') def poll_noted(ip): ''' 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') + return resp.strip(b'\r\n') def poll_catcgi(ip): ''' Poll the cat.cgi web service. Returns None or a team name. ''' @@ -121,79 +100,58 @@ def poll_catcgi(ip): if resp is None: return None - content = resp.split('\r\n\r\n') + content = resp.split(b'\r\n\r\n') if len(content) < 3: return None - content = content[1].split('\r\n') + content = content[1].split(b'\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') + return content[1].strip(b'\r\n') def poll_tftpd(ip): ''' 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 - + if len(resp) <= 5: return None - - resp = resp.split('\n')[0] - return resp[4:].strip('\r\n') - + + resp = resp.split(b'\n')[0] + + # ack + _ = socket_poll(ip, 69, b'\x00\x04' + resp[2:4], socket.SOCK_DGRAM, 0) + + return resp[4:].strip(b'\r\n') + # PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED POLLS = { 'fingerd' : poll_fingerd, - 'noted' : poll_noted, + 'noted' : poll_noted, 'catcgi' : poll_catcgi, - 'tftpd' : poll_tftpd, + 'tftpd' : poll_tftpd, } 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: 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 (%s)' % (IP_DIR, e)) - traceback.print_exc() - - try: - os.remove(REPORT_PATH) - except Exception as e: - pass - - 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') + ips = os.listdir(IP_DIR) + out = io.StringIO() + out.write(config.start_html('Team Service Availability')) for ip in ips: - # check file name format is ip if ip_re.match(ip) is None: continue @@ -217,7 +175,7 @@ while True: # perform polls for service,func in POLLS.items(): - team = func(ip) + team = func(ip).decode('utf-8') if team is None: team = 'dirtbags' @@ -227,11 +185,11 @@ while True: if out is not None: out.write('
\n' % (service, team)) - point_queue.put((service, team, 1)) + pointscli.submit('svc.' + service, team, 1) if out is not None: out.write('
Overall
%s%s
\n') - + if DEBUG is True: print('+-----------------------------------------+') @@ -240,9 +198,9 @@ while True: sleep_time = POLL_INTERVAL - exec_time if out is not None: - out.write('

Next poll in: %ds

\n' % sleep_time) - out.write('\n\n') - out.close() + out.write(config.end_html()) + + open(REPORT_PATH, 'w').write(out.getvalue()) # sleep until its time to poll again time.sleep(sleep_time) diff --git a/pollster/run.heartbeatd b/pollster/run.heartbeatd index 8241141..43f3202 100755 --- a/pollster/run.heartbeatd +++ b/pollster/run.heartbeatd @@ -1,3 +1,3 @@ #! /bin/sh -exec udpsvd 0 9 /usr/sbin/in.heartbeatd +exec udpsvd 0 9 envuidgid ctf /usr/sbin/in.heartbeatd diff --git a/puzzles/posters/10/img.png b/puzzles/hispaniola/10/img.png similarity index 100% rename from puzzles/posters/10/img.png rename to puzzles/hispaniola/10/img.png diff --git a/puzzles/posters/10/index.html b/puzzles/hispaniola/10/index.html similarity index 100% rename from puzzles/posters/10/index.html rename to puzzles/hispaniola/10/index.html diff --git a/puzzles/hispaniola/10/key b/puzzles/hispaniola/10/key new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/puzzles/hispaniola/10/key @@ -0,0 +1 @@ + diff --git a/puzzles/posters/125/index.html b/puzzles/hispaniola/125/index.html similarity index 100% rename from puzzles/posters/125/index.html rename to puzzles/hispaniola/125/index.html diff --git a/puzzles/hispaniola/125/key b/puzzles/hispaniola/125/key new file mode 100644 index 0000000..6c456ba --- /dev/null +++ b/puzzles/hispaniola/125/key @@ -0,0 +1 @@ +‽ diff --git a/puzzles/posters/15/img.png b/puzzles/hispaniola/15/img.png similarity index 100% rename from puzzles/posters/15/img.png rename to puzzles/hispaniola/15/img.png diff --git a/puzzles/posters/15/index.html b/puzzles/hispaniola/15/index.html similarity index 100% rename from puzzles/posters/15/index.html rename to puzzles/hispaniola/15/index.html diff --git a/puzzles/hispaniola/15/key b/puzzles/hispaniola/15/key new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/puzzles/hispaniola/15/key @@ -0,0 +1 @@ + diff --git a/puzzles/posters/5/img.png b/puzzles/hispaniola/5/img.png similarity index 100% rename from puzzles/posters/5/img.png rename to puzzles/hispaniola/5/img.png diff --git a/puzzles/posters/5/index.html b/puzzles/hispaniola/5/index.html similarity index 100% rename from puzzles/posters/5/index.html rename to puzzles/hispaniola/5/index.html diff --git a/puzzles/hispaniola/5/key b/puzzles/hispaniola/5/key new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/puzzles/hispaniola/5/key @@ -0,0 +1 @@ + diff --git a/puzzles/posters/50/index.html b/puzzles/hispaniola/50/index.html similarity index 100% rename from puzzles/posters/50/index.html rename to puzzles/hispaniola/50/index.html diff --git a/puzzles/posters/50/key b/puzzles/hispaniola/50/key similarity index 100% rename from puzzles/posters/50/key rename to puzzles/hispaniola/50/key diff --git a/puzzles/hispaniola/75/index.html b/puzzles/hispaniola/75/index.html new file mode 100644 index 0000000..dc8e55c --- /dev/null +++ b/puzzles/hispaniola/75/index.html @@ -0,0 +1,3 @@ +Give the entire sequence. The symbols are here, in no particular order, for you to copy and paste.
+Make sure to include spaces between symbols (but no leading or trailing spaces).

+◕ ⚑ ♥ ◢ ★ diff --git a/puzzles/posters/75/key b/puzzles/hispaniola/75/key similarity index 100% rename from puzzles/posters/75/key rename to puzzles/hispaniola/75/key diff --git a/puzzles/hispaniola/summary.txt b/puzzles/hispaniola/summary.txt new file mode 100644 index 0000000..77d99e7 --- /dev/null +++ b/puzzles/hispaniola/summary.txt @@ -0,0 +1 @@ +The "hispaniola" category requires contenstants to treasure-hunt for tangible items to learn the keys to each puzzle. diff --git a/puzzles/posters/10/key b/puzzles/posters/10/key deleted file mode 100644 index 071491e..0000000 --- a/puzzles/posters/10/key +++ /dev/null @@ -1 +0,0 @@ -You're well on your way :) diff --git a/puzzles/posters/125/key b/puzzles/posters/125/key deleted file mode 100644 index 66d5619..0000000 --- a/puzzles/posters/125/key +++ /dev/null @@ -1 +0,0 @@ -‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽‽ diff --git a/puzzles/posters/15/key b/puzzles/posters/15/key deleted file mode 100644 index 1941358..0000000 --- a/puzzles/posters/15/key +++ /dev/null @@ -1 +0,0 @@ --462766 diff --git a/puzzles/posters/5/key b/puzzles/posters/5/key deleted file mode 100644 index 9b8d504..0000000 --- a/puzzles/posters/5/key +++ /dev/null @@ -1 +0,0 @@ -3acd767f2717b84076cdcd18e882f01d diff --git a/puzzles/posters/75/index.html b/puzzles/posters/75/index.html deleted file mode 100644 index 9ff3ecf..0000000 --- a/puzzles/posters/75/index.html +++ /dev/null @@ -1 +0,0 @@ -⚑ _ _ _ _ ◢ _ _ _ _ ♥ _ _ _ _ ★ _ _ _ _ ◕ _ _ _ _ diff --git a/puzzles/posters/summary.txt b/puzzles/posters/summary.txt deleted file mode 100644 index e69de29..0000000 diff --git a/puzzles/webapp/10/,ctf.css b/puzzles/webapp/10/,ctf.css index 4b1b9a0..4f1b798 100644 --- a/puzzles/webapp/10/,ctf.css +++ b/puzzles/webapp/10/,ctf.css @@ -3,7 +3,7 @@ html,body { min-height: 100%; background-color: #000000; background-image: url(",binary.png"); - background-repeat: repeat-x repeat-y; + background-repeat: repeat; margin: 0; padding: 0; } @@ -52,3 +52,11 @@ h1,h2,h3,h4 { margin: 2em auto 2em auto; border-bottom: 1px dotted #222; } + +.error { + padding: 1em; + background: #fff; + color: red; + border: 1px solid red; + font-weight: bold; +} diff --git a/puzzles/webapp/20/,binary.png b/puzzles/webapp/20/,binary.png index 736df8b..36053bd 120000 --- a/puzzles/webapp/20/,binary.png +++ b/puzzles/webapp/20/,binary.png @@ -1 +1 @@ -../1/,binary.png \ No newline at end of file +../10/,binary.png \ No newline at end of file diff --git a/puzzles/webapp/20/,ctf.css b/puzzles/webapp/20/,ctf.css index 32cad37..19b2533 120000 --- a/puzzles/webapp/20/,ctf.css +++ b/puzzles/webapp/20/,ctf.css @@ -1 +1 @@ -../1/,ctf.css \ No newline at end of file +../10/,ctf.css \ No newline at end of file diff --git a/puzzles/webapp/30/,binary.png b/puzzles/webapp/30/,binary.png index 736df8b..36053bd 120000 --- a/puzzles/webapp/30/,binary.png +++ b/puzzles/webapp/30/,binary.png @@ -1 +1 @@ -../1/,binary.png \ No newline at end of file +../10/,binary.png \ No newline at end of file diff --git a/puzzles/webapp/30/,ctf.css b/puzzles/webapp/30/,ctf.css index 32cad37..19b2533 120000 --- a/puzzles/webapp/30/,ctf.css +++ b/puzzles/webapp/30/,ctf.css @@ -1 +1 @@ -../1/,ctf.css \ No newline at end of file +../10/,ctf.css \ No newline at end of file diff --git a/puzzles/webapp/40/,binary.png b/puzzles/webapp/40/,binary.png deleted file mode 100644 index 81cecbf..0000000 Binary files a/puzzles/webapp/40/,binary.png and /dev/null differ diff --git a/puzzles/webapp/40/,binary.png b/puzzles/webapp/40/,binary.png new file mode 120000 index 0000000..36053bd --- /dev/null +++ b/puzzles/webapp/40/,binary.png @@ -0,0 +1 @@ +../10/,binary.png \ No newline at end of file diff --git a/puzzles/webapp/40/,ctf.css b/puzzles/webapp/40/,ctf.css deleted file mode 100644 index 4b1b9a0..0000000 --- a/puzzles/webapp/40/,ctf.css +++ /dev/null @@ -1,54 +0,0 @@ -html,body { - height: 100%; - min-height: 100%; - background-color: #000000; - background-image: url(",binary.png"); - background-repeat: repeat-x repeat-y; - margin: 0; - padding: 0; -} - -#wrapper { - min-height: 100%; - height: 100%; - width: 800px; - margin: 0 auto; - border-left: 2px solid #009900; - border-right: 2px solid #009900; - font: .9em monospace; - color: #009900; - padding: 0; - background: #000; -} - -#content { - padding: 2em 1.5em 2em 1.5em; -} - -#footer { - padding: 0; - margin: 0; - height: 2em; - line-height: 2em; - width: 800px; - text-align: center; -} - -input { - background-color: #222; - color: #fff; - border: 1px solid #009900; - padding: 1px 2px 1px 2px; -} - -h1,h2,h3,h4 { - padding-bottom: 5px; -} - -.vertsep { - width: 100%; - height: 1px; - padding: 0; - margin: 2em auto 2em auto; - border-bottom: 1px dotted #222; -} diff --git a/puzzles/webapp/40/,ctf.css b/puzzles/webapp/40/,ctf.css new file mode 120000 index 0000000..19b2533 --- /dev/null +++ b/puzzles/webapp/40/,ctf.css @@ -0,0 +1 @@ +../10/,ctf.css \ No newline at end of file diff --git a/puzzles/webapp/50/,binary,png b/puzzles/webapp/50/,binary,png new file mode 120000 index 0000000..36053bd --- /dev/null +++ b/puzzles/webapp/50/,binary,png @@ -0,0 +1 @@ +../10/,binary.png \ No newline at end of file diff --git a/puzzles/webapp/50/,ctf.css b/puzzles/webapp/50/,ctf.css new file mode 120000 index 0000000..19b2533 --- /dev/null +++ b/puzzles/webapp/50/,ctf.css @@ -0,0 +1 @@ +../10/,ctf.css \ No newline at end of file diff --git a/puzzles/webapp/50/5.cgi b/puzzles/webapp/50/5.cgi new file mode 100755 index 0000000..23cc4e7 --- /dev/null +++ b/puzzles/webapp/50/5.cgi @@ -0,0 +1,89 @@ +#!/usr/bin/python + +import os +import cgi +import cgitb +cgitb.enable(context=10) + +if os.environ.has_key('QUERY_STRING'): + os.environ['QUERY_STRING'] = '' + +fields = cgi.FieldStorage() + +print 'Content-Type: text/html' +print '' + +print ''' + + + 5 + + + +
+
+

Web Application Challenge 5

+

Through some manipulation or interpretation of this CGI script + and the HTML page(s) that it generates, a 10 character key can be + found.

+

Find the key!

+ +
+''' + +PRODUCT_NAME = "Alex Brugh" +QUANT_LIMIT = 1 + +def purchase_success(quantity): + print ''' +

Congratulations, your order for %d "%s" has been placed.

+ ''' % (quantity, PRODUCT_NAME) + +class InvalidQuantityError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +quantity = None +if fields.has_key('quantity') and fields.has_key('product') and fields['product'].value == PRODUCT_NAME: + product = fields['product'].value + try: + quantity = int(fields['quantity'].value) + if quantity > QUANT_LIMIT: + # key = eVkIwHzOok + raise InvalidQuantityError("%d is not a valid quantity (limit %d)" % (quantity, QUANT_LIMIT)) + except ValueError: + print ''' +

There was an error with your order request. Sorry.

+ ''' + quantity = None + +if quantity is not None: + purchase_success(quantity) +else: + print ''' + +

SALE: %s

+

Use the order form below to place an order.

+ +
+ Orders for "%s" are limited to 1 per customer. +

+ + + +
+ ''' % (PRODUCT_NAME, PRODUCT_NAME, PRODUCT_NAME) + +print ''' + +
+ +
+ + +''' + diff --git a/puzzles/webapp/50/key b/puzzles/webapp/50/key new file mode 100644 index 0000000..042dd52 --- /dev/null +++ b/puzzles/webapp/50/key @@ -0,0 +1 @@ +eVkIwHzOok diff --git a/puzzles/webapp/60/,binary.png b/puzzles/webapp/60/,binary.png new file mode 120000 index 0000000..36053bd --- /dev/null +++ b/puzzles/webapp/60/,binary.png @@ -0,0 +1 @@ +../10/,binary.png \ No newline at end of file diff --git a/puzzles/webapp/60/,ctf.css b/puzzles/webapp/60/,ctf.css new file mode 120000 index 0000000..19b2533 --- /dev/null +++ b/puzzles/webapp/60/,ctf.css @@ -0,0 +1 @@ +../10/,ctf.css \ No newline at end of file diff --git a/puzzles/webapp/60/6.cgi b/puzzles/webapp/60/6.cgi new file mode 100755 index 0000000..75e192b --- /dev/null +++ b/puzzles/webapp/60/6.cgi @@ -0,0 +1,72 @@ +#!/usr/bin/python + +import os +import cgi +import cgitb +cgitb.enable(context=10) + +#if os.environ.has_key('QUERY_STRING'): +# os.environ['QUERY_STRING'] = '' + +fields = cgi.FieldStorage() + +import Cookie +c = Cookie.SimpleCookie() +c['key'] = 'QJebByJaKX' +c['content'] = '

Maybe I should have used sessions...

' + +print 'Content-Type: text/html\n%s\n\n\n' % c +print '' + +print ''' + + + 6 + + + + +
+
+

Web Application Challenge 6

+

Through some manipulation or interpretation of this CGI script + and the HTML page(s) that it generates, a 10 character key can be + found.

+

Find the key!

+ +
+
+''' + +print ''' +
+ +
+ + +''' + diff --git a/puzzles/webapp/60/key b/puzzles/webapp/60/key new file mode 100644 index 0000000..f235990 --- /dev/null +++ b/puzzles/webapp/60/key @@ -0,0 +1 @@ +QJebByJaKX diff --git a/puzzles/webapp/70/,binary.png b/puzzles/webapp/70/,binary.png new file mode 120000 index 0000000..36053bd --- /dev/null +++ b/puzzles/webapp/70/,binary.png @@ -0,0 +1 @@ +../10/,binary.png \ No newline at end of file diff --git a/puzzles/webapp/70/,ctf.css b/puzzles/webapp/70/,ctf.css new file mode 120000 index 0000000..19b2533 --- /dev/null +++ b/puzzles/webapp/70/,ctf.css @@ -0,0 +1 @@ +../10/,ctf.css \ No newline at end of file diff --git a/puzzles/webapp/70/7.cgi b/puzzles/webapp/70/7.cgi new file mode 100755 index 0000000..1bfdf64 --- /dev/null +++ b/puzzles/webapp/70/7.cgi @@ -0,0 +1,86 @@ +#!/usr/bin/python + +import os +import cgi +import cgitb +cgitb.enable(context=10) + +#if os.environ.has_key('QUERY_STRING'): +# os.environ['QUERY_STRING'] = '' + +fields = cgi.FieldStorage() + +import Cookie +c = Cookie.SimpleCookie(os.environ.get('HTTP_COOKIE', '')) + +content = { + 'joke1' : '

An infinite number of mathematicians walk into a bar. The first one orders a beer. The second orders half a beer. The third, a quarter of a beer. The bartender says You are all idiots! and pours two beers.

', + 'joke2' : '

Two atoms are talking. One of them says I think I lost an electron! and the other says Are you sure? The first replies Yeah, I am positive!

', +} + +if c.has_key('content_name') and c.has_key('content'): + k = c['content_name'].value + try: + c['content'] = content[k] + except KeyError: + c['content'] = '

key = s4nNlaMScV

' +else: + c['content_name'] = 'joke1'; + c['content'] = content['joke1'] + + +print 'Content-Type: text/html\n%s\n\n\n' % c +print '' + +print ''' + + + 7 + + + + +
+
+

Web Application Challenge 7

+

Through some manipulation or interpretation of this CGI script + and the HTML page(s) that it generates, a 10 character key can be + found.

+

Find the key!

+ +
+
+''' + +print ''' +
+ +
+ + +''' + diff --git a/puzzles/webapp/70/key b/puzzles/webapp/70/key new file mode 100644 index 0000000..16f48e0 --- /dev/null +++ b/puzzles/webapp/70/key @@ -0,0 +1 @@ +s4nNlaMScV diff --git a/puzzles/webapp/summary.txt b/puzzles/webapp/summary.txt index e69de29..23fb9f8 100644 --- a/puzzles/webapp/summary.txt +++ b/puzzles/webapp/summary.txt @@ -0,0 +1,14 @@ +10: the key is in the generated source. +20: enter a non-integer into form field and submit. the key is in the resulting + traceback. +30: change the value in the GET request to a non-integer. the key is in the + resulting traceback. +40: change the value in the POST request to a non-integer. the key is in the + resulting traceback. +50: change the quantity value (hidden form field) to something greater than the + stated quantity limit. the key is in the resulting traceback. entering non- + integers is caught and handled, so that no longer works. +60: the key is in the cookie. note the javascript that reads a value from the + cookie, hopefully causing the player to take a look at the cookie. +70: modify the cookie's content_name field to something invalid, reload the page + and the key will be printed on the page. diff --git a/tanks/lib/Program.py b/tanks/lib/Program.py index ad30b1d..2063d1f 100644 --- a/tanks/lib/Program.py +++ b/tanks/lib/Program.py @@ -7,7 +7,7 @@ skills at designing those wits to survive.

Programming Your Tank

Your tanks are programmed using the Super Useful Command and Kontrol language, -the very best in laser tank AI languages. It includes amazing feature such +the very best in laser tank AI languages. It includes amazing features such as comments (Started by a #, ended at EOL), logic, versatility, and semi-colons (all lines must end in one). As with all new military systems it utilizes only integers; we must never rest in our diff --git a/tanks/lib/docs.py b/tanks/lib/docs.py index 1724d52..0318250 100644 --- a/tanks/lib/docs.py +++ b/tanks/lib/docs.py @@ -4,9 +4,8 @@ def mkDocTable(objects): objects.sort(lambda o1, o2: cmp(o1.__doc__, o2.__doc__)) for object in objects: - print '' if object.__doc__ is None: - print '
%s
Bad object' % \ + print '
%s
Bad object
' % \ xml.sax.saxutils.escape(str(object)) continue text = object.__doc__ @@ -23,5 +22,5 @@ def mkDocTable(objects): body = '\n'.join(body) print '
%s
%s
' % (head, body) #print '
%sIntentionally blank
%s' % (head, body) - print '
' + diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py index ae83990..f5d7a75 100755 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -14,6 +14,8 @@ MAX_HIST = 30 HIST_STEP = 100 key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca' +running = True + class Flagger(asynchat.async_chat): """Use to connect to flagd and submit the current flag holder.""" @@ -31,6 +33,7 @@ class Flagger(asynchat.async_chat): def handle_error(self): # If we lose the connection to flagd, nobody can score any # points. Terminate everything. + running = False asyncore.close_all() asynchat.async_chat.handle_error(self) @@ -84,6 +87,8 @@ def main(): lastrun = 0 while True: asyncore.loop(60, count=1) + if not running: + break now = time.time() if now - lastrun >= 60: run_tanks(args, turns, flagger) diff --git a/tanks/www/ctf.css b/tanks/www/ctf.css deleted file mode 100644 index cf21d76..0000000 --- a/tanks/www/ctf.css +++ /dev/null @@ -1,104 +0,0 @@ -/**** document ****/ - -html { - background: #222 url(grunge.png) repeat-x; -} - -body { - font-family: sans-serif; - color: #eee; - margin: 50px 0 0 100px; - padding: 10px; - max-width: 700px; -} - -/**** heading ****/ - -h1:first-child { - text-transform: lowercase; - font-size: 1.6em; -/* background-color: #222; */ -/* opacity: 0.9; */ - padding: 3px; - color: #2a2; - margin: 0 0 1em 70px; -} - -h1:first-child:before { - color: #fff; - letter-spacing: -0.1em; - content: "Capture The Flag: "; -} - -/**** body ****/ - -a img { - border: 0px; -} - -a { - text-decoration: none; - color: #2a2; - font-weight: bold; -} - -a:hover { - color: #fff; - background: #2a2; - font-weight: bold; -} - - -h1, h2, h3 { - color: #999; - letter-spacing: -0.05em; -} - -code, pre, .readme, div.errors { - color: #fff; - background-color: #555; - margin: 1em; -} - -th, td { - vertical-align: top; -} - -.scoreboard td { - height: 400px; -} - -p { - line-height: 1.4em; - margin-bottom: 20px; - color: #f4f4f4; -} - -dt { - white-space: pre; -} - -dt div.tab { - background-color: #333; - display: inline-block; - padding: 5px; - border: 3px solid green; - border-bottom: none; - font-weight: bold; -} - -dd { - border: 3px solid green; - margin: 0px; - padding: 5px; - background-color: #282828; -} - -fieldset * { - margin: 3px; -} - -table.results td, th{ - padding : 3px; - font-weight : bold; -} diff --git a/tanks/www/docs.cgi b/tanks/www/docs.cgi index 26306e3..6a6b514 100755 --- a/tanks/www/docs.cgi +++ b/tanks/www/docs.cgi @@ -1,10 +1,9 @@ #!/usr/bin/python -print """Content-Type: text/html\n\n""" -print """\n\n""" import cgitb; cgitb.enable() import os import sys +from ctf import config try: from tanks import Program, setup, conditions, actions, docs @@ -15,10 +14,13 @@ except: sys.path.append(os.path.join('/', *path)) import Program, setup, conditions, actions, docs -print open('head.html').read() % "Documentation" -print '' -print '

Pflanzarr Documentation

' -print open('links.html').read() +print(config.start_html('Tanks Documentation', + links_title='Tanks', + links=[('docs.cgi', 'Docs'), + ('results.cgi', 'Results'), + ('submit.html', 'Submit'), + ('errors.cgi', 'My Errors')])) + print Program.__doc__ print '

Setup Actions:

' @@ -34,4 +36,4 @@ print '

Actions:

' print 'These actions are not for cowards. Remember, if actions contradict, your tank will simply do the last thing it was told in a turn. If ordered to hop on a plane to hell it will gladly do so. If order to make tea shortly afterwards, it will serve it politely and with cookies instead.

' docs.mkDocTable(actions.actions.values()) -print '' +print(config.end_html()) diff --git a/tanks/www/errors.cgi b/tanks/www/errors.cgi index 821082a..5f6adf5 100755 --- a/tanks/www/errors.cgi +++ b/tanks/www/errors.cgi @@ -1,7 +1,5 @@ #!/usr/bin/python3 -print("""Content-Type: text/html\n\n""") -print("""\n\n""") import cgi import cgitb; cgitb.enable() import sys @@ -20,15 +18,18 @@ except: path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams +from ctf import config teams.build_teams() -head = open('head.html').read() % "Error Report" -print(head) -print('

Your Errors

') -print(open('links.html').read()) +print(config.start_html('Tanks Errors', + links_title='Tanks', + links=[('docs.cgi', 'Docs'), + ('results.cgi', 'Results'), + ('submit.html', 'Submit'), + ('errors.cgi', 'My Errors')])) def done(): - print('') + print(config.end_html()) sys.exit(0) fields = cgi.FieldStorage() diff --git a/tanks/www/grunge.png b/tanks/www/grunge.png deleted file mode 100644 index 2b98730..0000000 Binary files a/tanks/www/grunge.png and /dev/null differ diff --git a/tanks/www/head.html b/tanks/www/head.html deleted file mode 100644 index f2b66ea..0000000 --- a/tanks/www/head.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - %s - diff --git a/tanks/www/links.html b/tanks/www/links.html deleted file mode 100644 index 1615bf7..0000000 --- a/tanks/www/links.html +++ /dev/null @@ -1,4 +0,0 @@ -Documentation | -Results | -Submit | -My Errors diff --git a/tanks/www/results.cgi b/tanks/www/results.cgi index 3055cf7..0edbf7c 100755 --- a/tanks/www/results.cgi +++ b/tanks/www/results.cgi @@ -2,16 +2,16 @@ import cgitb; cgitb.enable() import os +from ctf import config import Config -print """Content-Type: text/html\n\n""" -print """\n\n""" -head = open('head.html').read() % "Pflanzarr Results" -print head -print "

Results

" -print open('links.html').read() - +print(config.start_html('Tanks Results', + links_title='Tanks', + links=[('docs.cgi', 'Docs'), + ('results.cgi', 'Results'), + ('submit.html', 'Submit'), + ('errors.cgi', 'My Errors')])) try: winner = open(os.path.join(Config.DATA_PATH, 'winner')).read() except: @@ -52,4 +52,4 @@ for num in gameNums: print 'v' % num, print 'r' % num -print '' +print(config.end_html()) diff --git a/tanks/www/submit.cgi b/tanks/www/submit.cgi index 5dad944..ded20f7 100755 --- a/tanks/www/submit.cgi +++ b/tanks/www/submit.cgi @@ -1,7 +1,5 @@ #!/usr/bin/python3 -print("Content-Type: text/html\n\n") -print("""\n\n""") import cgi import cgitb; cgitb.enable() import os @@ -20,15 +18,18 @@ except: path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams +from ctf import config teams.build_teams() -head = open('head.html').read() % "Submission Results" -print(head) -print("

Results

") -print(open('links.html').read()) +print(config.start_html('Tanks Submission', + links_title='Tanks', + links=[('docs.cgi', 'Docs'), + ('results.cgi', 'Results'), + ('submit.html', 'Submit'), + ('errors.cgi', 'My Errors')])) def done(): - print('') + print(config.end_html()) sys.exit(0) fields = cgi.FieldStorage() @@ -36,23 +37,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', quote(team) ) file = open(path, 'w') file.write(code) file.close() -print("

Submission Successful") +print("

Submission successful.

") done() diff --git a/tanks/www/submit.html b/tanks/www/submit.html index f538d5d..d5528a1 100644 --- a/tanks/www/submit.html +++ b/tanks/www/submit.html @@ -1,16 +1,33 @@ - - - ' - Program Submission" - + + + + + Tanks Submission + + + + +

Tanks Submission

+ + - -

Program Submission

-

- Documentation | - Results | - Submit | - My Errors

Your program: