diff --git a/ctf.css b/ctf.css index d3c033a..433b9c6 100644 --- a/ctf.css +++ b/ctf.css @@ -30,6 +30,49 @@ h1:first-child:before { content: "Capture The Flag: "; } +/*** left side bar ***/ + +#navigation { + position: fixed; + background: #222; + opacity: 0.9; + top: 80px; + left: 0px; + padding: 0; +} + +#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 +107,18 @@ th, td { vertical-align: top; } +p { + line-height: 1.4em; + margin-bottom: 20px; + color: #f4f4f4; +} + +/**** special cases ****/ + +.wide { + max-width: inherit; +} + .scoreboard { background: #222; } @@ -72,24 +127,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..600aa7a 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,37 @@ def datafile(filename): def url(path): return base_url + path -def start_html(title): +def start_html(title, hdr='', cls=''): + 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

+ +''' % {'title': title, + 'css': css, + 'hdr': hdr, + 'base': base_url, + 'class': cls}) + return '\n'.join(ret) def end_html(): - print('') + return '' diff --git a/ctf/puzzler.py b/ctf/puzzler.py index e6d44a6..8700ef5 100755 --- a/ctf/puzzler.py +++ b/ctf/puzzler.py @@ -66,9 +66,10 @@ def start_html(title): if passwd: c['passwd'] = passwd print(c) - config.start_html(title) + print(config.start_html(title)) -end_html = config.end_html +def end_html(): + print(config.end_html()) def safe_join(*args): safe = list(args[:1]) diff --git a/ctf/register.py b/ctf/register.py index 95e8e92..0755f62 100755 --- a/ctf/register.py +++ b/ctf/register.py @@ -7,26 +7,7 @@ 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() - 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..74a0870 100755 --- a/ctfd.py +++ b/ctfd.py @@ -24,9 +24,6 @@ def reap(): 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 +36,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 3d3e0da..ec17d92 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): @@ -43,47 +74,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: @@ -105,11 +96,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/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/pollster.py b/pollster/pollster.py index bde9fea..1d899b1 100755 --- a/pollster/pollster.py +++ b/pollster/pollster.py @@ -13,10 +13,10 @@ 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') +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. ''' @@ -24,7 +24,7 @@ class PointSubmitter(threading.Thread): threading.Thread.__init__(self) self.point_queue = point_queue self.sock = pointscli.makesock('localhost') - + def run(self): # loop forever while(True): @@ -40,7 +40,7 @@ class PointSubmitter(threading.Thread): 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 +51,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 @@ -90,7 +90,7 @@ 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() - + if len(resp) == 0: return None @@ -131,7 +131,7 @@ def poll_catcgi(ip): content_len = int(content[0]) except Exception as e: return None - + if content_len <= 0: return None return content[1].strip('\r\n') @@ -141,13 +141,13 @@ def poll_tftpd(ip): 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') - + # PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED POLLS = { 'fingerd' : poll_fingerd, @@ -174,7 +174,7 @@ while True: 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: @@ -184,16 +184,9 @@ while True: 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') + 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 @@ -231,7 +224,7 @@ while True: if out is not None: out.write('
Overall
\n') - + if DEBUG is True: print('+-----------------------------------------+') @@ -240,8 +233,7 @@ 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.write(config.end_html()) out.close() # sleep until its time to poll again 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/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)