Many changes, unit test

* Everything in /srv/ctf now, set $CTF_BASE to override that
* CGI now accepts parms in argv
* Fix bug with uninitialized CGI vars
This commit is contained in:
Neale Pickett 2010-09-13 12:23:39 -06:00
parent 507dd93d88
commit 8d47593986
10 changed files with 295 additions and 100 deletions

View File

@ -1,11 +1,11 @@
TARGETS = in.tokend claim.cgi puzzler.cgi pointscli puzzles
TARGETS = in.tokend pointscli claim.cgi puzzler.cgi puzzles.cgi
all: $(TARGETS)
in.tokend: in.tokend.o xxtea.o
puzzles: puzzles.o common.o
in.tokend: in.tokend.o xxtea.o common.o
pointscli: pointscli.o common.o
puzzles.cgi: puzzles.cgi.o common.o
claim.cgi: claim.cgi.o common.o
puzzler.cgi: puzzler.cgi.o common.o

View File

@ -1,19 +1,16 @@
#include <stdlib.h>
#include "common.h"
char const *tokenlog = "/var/lib/ctf/tokend/tokens.log";
char const *claimlog = "/var/lib/ctf/tokend/claim.log";
int
main(int argc, char *argv[])
{
char team[9];
char token[100];
/* XXX: This code needs to be tested */
return 1;
team[0] = 0;
token[0] = 0;
if (-1 == cgi_init()) {
if (-1 == cgi_init(argv)) {
return 0;
}
@ -53,7 +50,7 @@ main(int argc, char *argv[])
/* Does the token exist? */
if (! fgrepx(token, tokenlog)) {
if (! fgrepx(token, srv_path("tokens.db"))) {
cgi_page("Token does not exist", "");
}
@ -69,7 +66,8 @@ main(int argc, char *argv[])
category[i] = '\0';
award_and_log_uniquely(team, category, 1,
claimlog, "%s %s", team, token);
"tokens.db",
"%s %s", team, token);
}

View File

@ -6,39 +6,56 @@
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <values.h>
#include <time.h>
#include "common.h"
/*
* CGI
*/
static size_t inlen = 0;
static int is_cgi = 0;
static int is_cgi = 0;
static char **argv = NULL;
int
cgi_init()
static int
read_char_argv()
{
char *rm = getenv("REQUEST_METHOD");
static int arg = 0;
static char *p;
if (! (rm && (0 == strcmp(rm, "POST")))) {
printf("405 Method not allowed\r\n"
"Allow: POST\r\n"
"Content-type: text/html\r\n"
"\r\n"
"<h1>Method not allowed</h1>\n"
"<p>I only speak POST. Sorry.</p>\n");
return -1;
if (NULL == argv) {
return EOF;
}
inlen = atoi(getenv("CONTENT_LENGTH"));
is_cgi = 1;
if (0 == arg) {
arg = 1;
p = argv[1];
}
return 0;
if (! p) {
return EOF;
} else if (! *p) {
arg += 1;
p = argv[arg];
return '&';
}
return *(p++);
}
static int
read_char()
read_char_stdin()
{
static int inlen = -1;
if (-1 == inlen) {
char *p = getenv("CONTENT_LENGTH");
if (p) {
inlen = atoi(p);
} else {
inlen = 0;
}
}
if (inlen) {
inlen -= 1;
return getchar();
@ -46,6 +63,53 @@ read_char()
return EOF;
}
static int
read_char_query_string()
{
static char *p = (char *)-1;
if ((char *)-1 == p) {
p = getenv("QUERY_STRING");
}
if (! p) {
return EOF;
} else if (! *p) {
return EOF;
} else {
return *(p++);
}
}
static int (* read_char)() = read_char_argv;
int
cgi_init(char *global_argv[])
{
char *rm = getenv("REQUEST_METHOD");
if (! rm) {
read_char = read_char_argv;
argv = global_argv;
} else if (0 == strcmp(rm, "POST")) {
read_char = read_char_stdin;
is_cgi = 1;
} else if (0 == strcmp(rm, "GET")) {
read_char = read_char_query_string;
is_cgi = 1;
} else {
printf(("405 Method not allowed\r\n"
"Allow: GET, POST\r\n"
"Content-type: text/plain\r\n"
"\r\n"
"%s is not allowed.\n"),
rm);
return -1;
}
return 0;
}
static char
tonum(int c)
{
@ -207,22 +271,48 @@ my_snprintf(char *buf, size_t buflen, char *fmt, ...)
va_start(ap, fmt);
len = vsnprintf(buf, buflen - 1, fmt, ap);
va_end(ap);
if (len >= 0) {
buf[len] = '\0';
return len;
buf[buflen] = '\0';
if (len >= buflen) {
return buflen - 1;
} else {
return -1;
return len;
}
}
char *
srv_path(char const *fmt, ...)
{
char relpath[PATH_MAX];
static char path[PATH_MAX];
char *srv;
int len;
va_list ap;
va_start(ap, fmt);
len = vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap);
va_end(ap);
relpath[sizeof(relpath) - 1] = '\0';
srv = getenv("CTF_BASE");
if (! srv) {
srv = "/srv/ctf";
}
my_snprintf(path, sizeof(path), "%s/%s", srv, relpath);
return path;
}
int
team_exists(char const *teamhash)
{
struct stat buf;
char filename[100];
int ret;
int i;
if ((! teamhash) || (! *teamhash)) {
return 0;
}
/* Check for invalid characters. */
for (i = 0; teamhash[i]; i += 1) {
if (! isalnum(teamhash[i])) {
@ -230,15 +320,8 @@ team_exists(char const *teamhash)
}
}
/* Build filename. */
ret = snprintf(filename, sizeof(filename),
"%s/%s", teamdir, teamhash);
if (sizeof(filename) <= ret) {
return 0;
}
/* lstat seems to be the preferred way to check for existence. */
ret = lstat(filename, &buf);
ret = lstat(srv_path("teams/names/%s", teamhash), &buf);
if (-1 == ret) {
return 0;
}
@ -253,8 +336,7 @@ award_points(char const *teamhash,
{
char line[100];
int linelen;
char filename[100];
int filenamelen;
char *filename;
int fd;
int ret;
time_t now = time(NULL);
@ -296,13 +378,9 @@ award_points(char const *teamhash,
token log.
*/
filenamelen = snprintf(filename, sizeof(filename),
"%s/%d.%d.%s.%s.%ld",
pointsdir, now, getpid(),
teamhash, category, points);
if (sizeof(filename) <= filenamelen) {
return -1;
}
filename = srv_path("points.new/%d.%d.%s.%s.%ld",
now, getpid(),
teamhash, category, points);
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (-1 == fd) {
@ -322,14 +400,15 @@ void
award_and_log_uniquely(char const *team,
char const *category,
long points,
char const *logfile,
char const *dbfile,
char const *fmt, ...)
{
char line[200];
int len;
int ret;
int fd;
va_list ap;
char *dbpath = srv_path(dbfile);
char line[200];
int len;
int ret;
int fd;
va_list ap;
/* Make sure they haven't already claimed these points */
va_start(ap, fmt);
@ -338,13 +417,13 @@ award_and_log_uniquely(char const *team,
if (sizeof(line) <= len) {
cgi_error("Log line too long");
}
if (fgrepx(line, logfile)) {
if (fgrepx(line, dbpath)) {
cgi_page("Already claimed",
"<p>Your team has already claimed these points.</p>");
}
/* Open and lock logfile */
fd = open(logfile, O_WRONLY | O_CREAT, 0666);
fd = open(dbpath, O_WRONLY | O_CREAT, 0666);
if (-1 == fd) {
cgi_error("Unable to open log");
}

View File

@ -6,7 +6,7 @@
#define TEAM_MAX 40
#define CAT_MAX 40
int cgi_init();
int cgi_init(char *global_argv[]);
size_t cgi_item(char *str, size_t maxlen);
void cgi_head(char *title);
void cgi_foot();
@ -14,12 +14,10 @@ void cgi_page(char *title, char *fmt, ...);
void cgi_error(char *fmt, ...);
#define teamdir "/var/lib/ctf/teams/names"
#define pointsdir "/var/lib/ctf/points/new"
int fgrepx(char const *needle, char const *filename);
char *srv_path(char const *fmt, ...);
int team_exists(char const *teamhash);
int award_points(char const *teamhash,
int award_points(char const *teamhacsh,
char const *category,
long point);
void award_and_log_uniquely(char const *team,

View File

@ -8,13 +8,11 @@
#include <stdio.h>
#include <stddef.h>
#include <ctype.h>
#include "common.h"
#include "xxtea.h"
#define itokenlen 3
char const *keydir = "/var/lib/ctf/tokend/keys";
char const *tokenlog = "/var/lib/ctf/tokend/tokens.log";
char const consonants[] = "bcdfghklmnprstvz";
char const vowels[] = "aeiouy";
@ -91,16 +89,10 @@ main(int argc, char *argv[])
/* Read in that service's key. */
{
char path[100];
int fd;
size_t len;
int ret;
ret = snprintf(path, sizeof(path),
"%s/%s.key", keydir, service);
if (ret < sizeof(path)) {
fd = open(path, O_RDONLY);
}
fd = open(srv_path("token.keys/%s", service), O_RDONLY);
if (-1 == fd) {
write(1, "!nosvc", 6);
return 0;
@ -139,7 +131,7 @@ main(int argc, char *argv[])
int ret;
do {
fd = open(tokenlog, O_WRONLY | O_CREAT, 0644);
fd = open(srv_path("tokens.db"), O_WRONLY | O_CREAT, 0644);
if (-1 == fd) break;
ret = lockf(fd, F_LOCK, 0);

View File

@ -1,18 +1,21 @@
#include <stdlib.h>
#include "common.h"
char const *logfile = "/var/lib/ctf/puzzler.log";
int
main(int argc, char *argv)
main(int argc, char *argv[])
{
char team[TEAM_MAX];
char category[CAT_MAX];
char points_str[5];
char answer[500];
long points;
long points = 0;
if (-1 == cgi_init()) {
team[0] = 0;
category[0] = 0;
answer[0] = 0;
if (-1 == cgi_init(argv)) {
return 0;
}
@ -58,26 +61,24 @@ main(int argc, char *argv)
/* Check answer (also assures category exists) */
{
char filename[100];
char needle[400];
my_snprintf(filename, sizeof(filename),
"/srv/%s/answers.txt", category);
my_snprintf(needle, sizeof(needle),
"%ld %s", points, answer);
if (! fgrepx(needle, filename)) {
if (! fgrepx(needle,
srv_path("packages/%s/answers.txt", category))) {
cgi_page("Wrong answer", "");
}
}
award_and_log_uniquely(team, category, points,
logfile, "%s %s %ld", team, category, points);
"puzzler.db",
"%s %s %ld", team, category, points);
cgi_page("Points awarded",
("<p>%d points for %s.</p>\n"
"<p>Patience please: it may be up to 1 minute before "
"the next puzzle opens in this category.</p>"),
points, team);
("<p>%d points for %s.</p>"
"<!-- awarded %d -->"),
points, team, points);
return 0;
}

View File

@ -27,7 +27,7 @@ int ncats = 0;
void
read_points_by_cat()
{
FILE *f = fopen("/var/lib/ctf/puzzler.log", "r");
FILE *f = fopen(srv_path("puzzler.db"), "r");
char cat[CAT_MAX];
long points;
int i;
@ -59,10 +59,13 @@ main(int argc, char *argv[])
int i;
DIR *srv;
if (-1 == cgi_init(argv)) {
return 0;
}
read_points_by_cat();
/* Open /srv/ */
srv = opendir("/srv");
srv = opendir(srv_path("packages"));
if (NULL == srv) {
cgi_error("Cannot opendir(\"/srv\")");
}
@ -75,7 +78,6 @@ main(int argc, char *argv[])
struct dirent *e = readdir(srv);
char *cat = e->d_name;
DIR *puzzles;
char path[PATH_MAX];
long catpoints[PUZZLES_MAX];
size_t ncatpoints = 0;
@ -84,9 +86,8 @@ main(int argc, char *argv[])
/* We have to lstat anyway to see if it's a directory; may as
well just barge ahead and watch for errors. */
/* Open /srv/$cat/puzzles/ */
my_snprintf(path, sizeof(path), "/srv/%s/puzzles", cat);
puzzles = opendir(path);
/* Open /srv/ctf/$cat/puzzles/ */
puzzles = opendir(srv_path("packages/%s/puzzles", cat));
if (NULL == puzzles) {
continue;
}

View File

@ -8,9 +8,11 @@ fi
# Don't overwrite files
set -C
base=${CTF_BASE:-/srv/ctf}
# Assign a color. I spent two days selecting this color pallette for
# people with protanopia. Please don't change these colors.
nteams=$(ls /var/lib/ctf/teams/names/ | wc -l)
nteams=$(ls $base/teams/names/ | wc -l)
case $(expr $nteams % 15) in
0) color=8c7a69 ;;
1) color=7f7062 ;;
@ -39,7 +41,7 @@ esac
# me since all team hashes are in the set /[0-9a-f]{8}/.
hash=$(echo "$1" | md5sum | cut -b 1-8)
echo "$1" > /var/lib/ctf/teams/names/$hash
echo "$color" > /var/lib/ctf/teams/colors/$hash
echo "$1" > $base/teams/names/$hash
echo "$color" > $base/teams/colors/$hash
echo "Registered with hash $hash"
echo "Registered with hash: $hash"

View File

@ -63,6 +63,11 @@ function output( t, c) {
}
BEGIN {
base = ENVIRON["CTF_BASE"]
if (! base) {
base = "/srv/ctf"
}
# Only display two decimal places
CONVFMT = "%.2f"
@ -95,11 +100,11 @@ BEGIN {
# Get team colors and names
for (team in teams) {
fn = "/var/lib/ctf/teams/colors/" team
fn = base "/teams/colors/" team
getline colors_by_team[team] < fn
close(fn)
fn = "/var/lib/ctf/teams/names/" team
fn = base "/teams/names/" team
getline names_by_team[team] < fn
close(fn)
}

119
src/test.sh Executable file
View File

@ -0,0 +1,119 @@
#! /bin/sh -e
die () {
echo $*
exit 1
}
CTF_BASE=/tmp/ctf-test.$$ export CTF_BASE
trap "rm -rf $CTF_BASE" 0
mkdir $CTF_BASE
# Some skeletal structure
mkdir -p $CTF_BASE/points.new
# Set up some packages
for cat in cat1 cat2 cat3; do
mkdir -p $CTF_BASE/packages/$cat
cat >$CTF_BASE/packages/$cat/answers.txt <<EOF
10 ${cat}answer10
20 ${cat}answer20
30 ${cat}answer30
EOF
for i in 10 20 30; do
mkdir -p $CTF_BASE/packages/$cat/puzzles/$i
done
done
# Set up some teams
mkdir -p $CTF_BASE/teams/names
mkdir -p $CTF_BASE/teams/colors
for team in team1 team2 team3; do
hash=$(./register $team | awk '{print $NF;}')
done
##
## Puzzler tests
##
if ./puzzles.cgi | grep 20; then
die "20 points puzzles shouldn't show up here"
fi
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer20 | grep -q 'awarded'; then
die "Awarded points with wrong answer"
fi
if ./puzzler.cgi t=$hash c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with wrong category"
fi
if ./puzzler.cgi t=$hash c=cat1 p=20 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with wrong point value"
fi
if ./puzzler.cgi t=merfmerfmerfmerf c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with bad team"
fi
if ! ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
die "Didn't award points for correct answer"
fi
if ! ./puzzles.cgi | grep -q 20; then
die "20 point answer didn't show up"
fi
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
die "Awarded same points twice"
fi
##
## Scoreboard tests
##
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'total.*team3: 1'; then
die "Scoreboard total incorrect"
fi
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'cat1.*team3: 10'; then
die "Scoreboard cat1 points incorrect"
fi
##
## Token tests
##
mkdir -p $CTF_BASE/token.keys
echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat
# in.tokend uses a random number generator
echo -n 'tokencat' | ./in.tokend > /dev/null
if ! grep -q 'tokencat:x....-....x' $CTF_BASE/tokens.db; then
die "in.tokend didn't write to database"
fi
if ./claim.cgi t=lalalala k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi gave points to a bogus team"
fi
if ./claim.cgi t=$hash k=tokencat:xanax-xanax | grep -q success; then
die "claim.cgi gave points for a bogus token"
fi
if ! ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi didn't give me any points"
fi
if ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi gave me points twice for the same token"
fi
if ! [ -f $CTF_BASE/points.new/*.$hash.tokencat.1 ]; then
die "claim.cgi didn't actually record any points"
fi
echo "$0: All tests passed!"
echo "$0: Aren't you just the best programmer ever?"