def revoke(flag, user): """Revoke a flag submission [for a given user].""" with r8.db: submissions = [ x[0] for x in r8.db.execute("SELECT uid FROM submissions WHERE fid=?", ( flag, )).fetchall() ] if not submissions: raise click.UsageError(f"No submissions for {flag}.") if user: if user in submissions: r8.db.execute( "DELETE FROM submissions WHERE fid = ? AND uid = ?", (flag, user)) r8.echo("r8", f"Submission revoked.") else: raise click.UsageError(f"Error: {user} did not submit {flag}.") else: click.confirm( f"Deleting all {len(submissions)} submission for {flag}. Continue?", abort=True) r8.db.execute("DELETE FROM submissions WHERE fid = ?", (flag, )) r8.echo( "r8", f"Submissions have been revoked for the following users: {', '.join(submissions)}" )
def load(self): r8.echo("r8", "Loading challenges...") for entry_point in pkg_resources.iter_entry_points('r8.challenges'): entry_point.load() for cid in get_challenges(): self._instances[cid] = self.make_instance(cid)
def submit(flag, user, force): """Submit a flag for a user.""" try: cid = r8.util.submit_flag(flag, user, "127.0.0.1", force) except ValueError as e: raise click.UsageError(str(e)) else: r8.echo("r8", f"Solved {cid} for {user}.")
async def _stop(self, cid: str) -> None: inst = self[cid] try: await inst.stop() except Exception: r8.echo(cid, f"Error on stop.", err=True) traceback.print_exc() else: if type(inst).stop is not Challenge.stop: r8.echo(cid, "Stopped.")
def update(user, password): """Update a user's password.""" password = util.hash_password(password) with r8.db: exists = r8.db.execute("SELECT COUNT(*) FROM users WHERE uid = ?", (user, )).fetchone()[0] if not exists: raise click.UsageError("User does not exist.") r8.db.execute("UPDATE users SET password = ? WHERE uid = ?", (password, user)) r8.echo("r8", f"Password updated.")
async def start(): global runner r8.echo("r8", "Starting server...") app = make_app() runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, r8.settings["host"], r8.settings["port"]) await site.start() address = r8.util.format_address( (r8.settings["host"], r8.settings["port"])) r8.echo("r8", f"Running at {address}.") return runner
async def _start(self, cid: str) -> None: inst = self[cid] if type(inst).start is not Challenge.start: r8.echo(cid, "Starting...") try: await inst.start() except Exception: r8.echo(cid, "Error on start.", err=True) traceback.print_exc() # if start failed, don't bother with stop. inst.stop = lambda: asyncio.sleep(0)
def rename(old_name, new_name): """Change a team name.""" with r8.db: old_exists = r8.db.execute("SELECT COUNT(*) FROM teams WHERE tid = ?", (old_name, )).fetchone()[0] if not old_exists: raise click.UsageError("Old team does not exist.") new_exists = r8.db.execute("SELECT COUNT(*) FROM teams WHERE tid = ?", (new_name, )).fetchone()[0] if new_exists: raise click.UsageError("New team name does already exist.") r8.db.execute("UPDATE teams SET tid = ? WHERE tid = ?", (new_name, old_name)) r8.echo("r8", f"""Renamed "{old_name}" to "{new_name}".""")
def delete(flag): """Delete an unsubmitted flag.""" with r8.db: exists = r8.db.execute("SELECT COUNT(*) FROM flags WHERE fid = ?", (flag, )).fetchone()[0] if not exists: raise click.UsageError("Flag does not exist.") submissions = r8.db.execute( "SELECT COUNT(*) FROM submissions WHERE fid = ?", (flag, )).fetchone()[0] if submissions: raise click.UsageError( "Cannot delete a flag that is in use. Revoke all submissions first." ) r8.db.execute("DELETE FROM flags WHERE fid = ?", (flag, )) r8.echo("r8", f"Successfully deleted {flag}.")
def wrapper(database, **kwds): if echo: r8.echo("r8", f"Loading database ({database})...") r8.db = sqlite3_connect(database) with r8.db: r8.settings = {} for k, v in r8.db.execute( "SELECT key, value FROM settings").fetchall(): try: val = json.loads(v) except ValueError as e: raise ValueError( f"Setting {k} is not JSON-deserializable: {v!r}" ) from e r8.settings[k] = val return f(**kwds)
def update(user, password): """Update a user's password. Note that this change may only be temporary if you reset credentials in `config.sql`. """ password = util.hash_password(password) with r8.db: exists = r8.db.execute("SELECT COUNT(*) FROM users WHERE uid = ?", (user, )).fetchone()[0] if not exists: raise click.UsageError("User does not exist.") r8.db.execute("UPDATE users SET password = ? WHERE uid = ?", (password, user)) r8.echo( "r8", f"Password updated. " f"Note that this change may only be temporary if you reset credentials in `config.sql`." )
async def get_updates(user: str, request: web.Request): ws = web.WebSocketResponse(heartbeat=25) await ws.prepare(request) ws_connections.add(ws) # r8.echo('scoreboard', 'websocket connection opened') try: async for msg in ws: assert isinstance(msg, aiohttp.WSMessage) # this is here for debugging. if msg.type == aiohttp.WSMsgType.TEXT: if msg.data == 'close': await ws.close() else: await ws.send_str(msg.data) elif msg.type == aiohttp.WSMsgType.ERROR: r8.echo( "scoreboard", f'ws connection closed with exception {ws.exception()}') finally: ws_connections.remove(ws) return ws
async def on_startup(app): scoreboards[0].timestamp = r8.settings.get("start", time.time()) with r8.db: submissions = r8.db.execute(""" SELECT tid, cid, CAST(strftime('%s',timestamp) AS INTEGER) AS timestamp FROM submissions NATURAL INNER JOIN flags NATURAL INNER JOIN teams ORDER BY TIMESTAMP """) for team, cid, timestamp in submissions: if team.startswith("_"): continue scoreboards.append(scoreboards[-1].solve(team, r8.challenges[cid], timestamp)) if len(scoreboards) > 1: scoreboards[0].timestamp = min(scoreboards[0].timestamp, scoreboards[1].timestamp) r8.echo( "scoreboard", f"Processed {len(scoreboards) - 1} submission(s): {scoreboards[-1]}") r8.util.on_submit.connect(on_solve)
def cli(debug) -> None: """Run the server.""" print(cars.best_car()) loop = asyncio.get_event_loop() if debug: r8.db.set_trace_callback(lambda msg: _log_sql(msg)) loop.set_debug(True) r8.challenges.load() loop.run_until_complete( asyncio.gather(server.start(), r8.challenges.start())) r8.echo("r8", "Started.") if os.name != "nt": loop.add_signal_handler(signal.SIGTERM, loop.stop) try: loop.run_forever() except KeyboardInterrupt: pass r8.echo("r8", "Shutting down...") loop.run_until_complete( asyncio.gather( r8.challenges.stop(), server.stop(), )) r8.echo("r8", "Shut down.") loop.close()
with r8.db: submissions = r8.db.execute(""" SELECT tid, cid, CAST(strftime('%s',timestamp) AS INTEGER) AS timestamp FROM submissions NATURAL INNER JOIN flags NATURAL INNER JOIN teams ORDER BY TIMESTAMP """) for team, cid, timestamp in submissions: if next := scoreboards[-1].solve(team, r8.challenges[cid], timestamp): if timestamp < scoreboards[0].timestamp: next.timestamp = scoreboards[0].timestamp scoreboards.clear() scoreboards.append(next) r8.echo( "scoreboard", f"Processed {len(scoreboards) - 1} submission(s): {scoreboards[-1]}") r8.util.on_submit.connect(on_solve) def on_solve(sender, user, cid): team = r8.util.get_team(user) if next := scoreboards[-1].solve(team, r8.challenges[cid], time.time()): scoreboards.append(next) else: return data = scoreboards[-1].to_json() for ws in ws_connections: asyncio.create_task(send_task(ws, data))
async def stop(): r8.echo("r8", "Stopping server...") await runner.cleanup() r8.echo("r8", "Stopped.")
def init(origin, static_dir, host, port, database) -> None: """Initialize database.""" if os.path.exists(database): raise click.UsageError("Database already exists.") conn = util.sqlite3_connect(database) conn.executescript(""" CREATE TABLE users ( uid TEXT PRIMARY KEY NOT NULL, password TEXT NOT NULL ); CREATE TABLE challenges ( cid TEXT PRIMARY KEY NOT NULL, team BOOLEAN NOT NULL DEFAULT 0, t_start DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, t_stop DATETIME NOT NULL ); CREATE TABLE flags ( fid TEXT PRIMARY KEY NOT NULL, cid TEXT NOT NULL, max_submissions INTEGER NOT NULL, FOREIGN KEY (cid) REFERENCES challenges(cid) ); CREATE TABLE submissions ( uid TEXT NOT NULL, fid TEXT NOT NULL, timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (uid) REFERENCES users(uid), FOREIGN KEY (fid) REFERENCES flags(fid), PRIMARY KEY (uid, fid) ); CREATE TABLE events ( time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ip TEXT NOT NULL, type TEXT NOT NULL, data TEXT, cid TEXT, uid TEXT ); CREATE TABLE teams ( uid TEXT PRIMARY KEY NOT NULL, tid TEXT NOT NULL, FOREIGN KEY (uid) REFERENCES users(uid) ); CREATE TABLE data ( cid TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, FOREIGN KEY (cid) REFERENCES challenges(cid), PRIMARY KEY (cid, key) ); CREATE TABLE settings ( key TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL ); """) conn.executemany("INSERT INTO settings (key, value) VALUES (?,?)", [(k, json.dumps(v)) for k, v in [ ("secret", secrets.token_hex(32)), ("static_dir", static_dir), ("origin", origin.rstrip("/")), ("host", host), ("port", port), ]]) conn.commit() r8.echo("r8", f"{database} initialized!")
def echo(self, message: str, err: bool = False) -> None: """Print to console with the challenge's namespace added in front.""" r8.echo(self.id, message, err)