From 8d475939866d4f87992504c3354b498d5c19bab4 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 13 Sep 2010 12:23:39 -0600 Subject: [PATCH] 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 --- src/Makefile | 6 +- src/claim.cgi.c | 14 ++- src/common.c | 173 ++++++++++++++++++++++--------- src/common.h | 8 +- src/in.tokend.c | 14 +-- src/puzzler.cgi.c | 27 ++--- src/{puzzles.c => puzzles.cgi.c} | 15 +-- src/register | 10 +- src/scoreboard | 9 +- src/test.sh | 119 +++++++++++++++++++++ 10 files changed, 295 insertions(+), 100 deletions(-) rename src/{puzzles.c => puzzles.cgi.c} (91%) create mode 100755 src/test.sh diff --git a/src/Makefile b/src/Makefile index 35f1ef1..08c2f03 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/claim.cgi.c b/src/claim.cgi.c index 08e18ef..cc2a467 100644 --- a/src/claim.cgi.c +++ b/src/claim.cgi.c @@ -1,19 +1,16 @@ #include #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); } diff --git a/src/common.c b/src/common.c index 3adeb60..b82e71d 100644 --- a/src/common.c +++ b/src/common.c @@ -6,39 +6,56 @@ #include #include #include +#include #include #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" - "

Method not allowed

\n" - "

I only speak POST. Sorry.

\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", "

Your team has already claimed these points.

"); } /* 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"); } diff --git a/src/common.h b/src/common.h index 35d4cc4..3d55abb 100644 --- a/src/common.h +++ b/src/common.h @@ -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, diff --git a/src/in.tokend.c b/src/in.tokend.c index 68511f4..7cd37c7 100644 --- a/src/in.tokend.c +++ b/src/in.tokend.c @@ -8,13 +8,11 @@ #include #include #include +#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); diff --git a/src/puzzler.cgi.c b/src/puzzler.cgi.c index 87fe86d..2fa900a 100644 --- a/src/puzzler.cgi.c +++ b/src/puzzler.cgi.c @@ -1,18 +1,21 @@ #include #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", - ("

%d points for %s.

\n" - "

Patience please: it may be up to 1 minute before " - "the next puzzle opens in this category.

"), - points, team); + ("

%d points for %s.

" + ""), + points, team, points); return 0; } diff --git a/src/puzzles.c b/src/puzzles.cgi.c similarity index 91% rename from src/puzzles.c rename to src/puzzles.cgi.c index e565a14..3c316e9 100644 --- a/src/puzzles.c +++ b/src/puzzles.cgi.c @@ -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; } diff --git a/src/register b/src/register index 91f34d4..6aad0b5 100755 --- a/src/register +++ b/src/register @@ -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" diff --git a/src/scoreboard b/src/scoreboard index 93ff9f4..216ea03 100755 --- a/src/scoreboard +++ b/src/scoreboard @@ -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) } diff --git a/src/test.sh b/src/test.sh new file mode 100755 index 0000000..c19228b --- /dev/null +++ b/src/test.sh @@ -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 < $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?"