From d95aecd25d4529c0f6a10f2b0549653965870c5e Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 30 May 2012 18:04:24 -0600 Subject: [PATCH] move mcp to new points architecture --- packages/mcp/bin/addteam | 8 +- packages/mcp/service/pointsd/run | 8 -- packages/mcp/src/claim.cgi.c | 23 ++-- packages/mcp/src/common.c | 196 ++++++++++++++----------------- packages/mcp/src/common.h | 17 +-- packages/mcp/src/pointscli.c | 27 +++-- packages/mcp/src/puzzler.cgi.c | 18 +-- 7 files changed, 129 insertions(+), 168 deletions(-) diff --git a/packages/mcp/bin/addteam b/packages/mcp/bin/addteam index 580dd55..c2b7204 100755 --- a/packages/mcp/bin/addteam +++ b/packages/mcp/bin/addteam @@ -1,7 +1,5 @@ #! /bin/sh -e -PATH=/bin:/opt/ctfbase/bin - if [ $# -ne 1 ]; then echo "Usage: $0 TEAM" 1>&2 exit 64 @@ -20,8 +18,8 @@ www=${CTF_BASE:-/var/www} mkdir -p $base/teams/names mkdir -p $base/teams/colors -[ -f $base/teams/nonce ] || dd if=/dev/urandom count=1 | md5sum | cut -b 1-8 > $base/teams/nonce -nonce=$(cat $base/teams/nonce) +[ -f $base/teams/salt ] || dd if=/dev/urandom count=1 2>/dev/null | md5sum | cut -b 1-8 > $base/teams/salt +salt=$(cat $base/teams/salt) # Assign a color. I spent weeks selecting a color pallette that # wouldn't be hell on people with protanopia. Please don't change these @@ -48,7 +46,7 @@ esac # Compute hash of team name; they'll use this for everything in the # contest instead of their team name, which makes stuff much easier on # me since all team hashes are in the set /[0-9a-f]{8}/. -hash=$(printf "%s:%s" $nonce "$1" | md5sum | cut -b 1-8) +hash=$(printf "%s:%s" $salt "$1" | md5sum | cut -b 1-8) echo "$1" > $base/teams/names/$hash echo "$color" > $base/teams/colors/$hash diff --git a/packages/mcp/service/pointsd/run b/packages/mcp/service/pointsd/run index d4f92f4..f393539 100755 --- a/packages/mcp/service/pointsd/run +++ b/packages/mcp/service/pointsd/run @@ -17,14 +17,6 @@ install -d /var/lib/ctf install -o ctf -m 0755 -d $NEWDIR install -o ctf -m 0755 -d /var/lib/ctf/points.tmp -# Make tokens database now, this is as good a time as any -TOKENS=/var/lib/ctf/tokens.db -for fn in $TOKENS /opt/*/tokens.txt; do - [ -f "$fn" ] || continue - cat $fn -done | sort | uniq > $TOKENS.new -mv $TOKENS.new $TOKENS - # Create some files CLAIM=/var/lib/ctf/claim.db touch $CLAIM diff --git a/packages/mcp/src/claim.cgi.c b/packages/mcp/src/claim.cgi.c index cab9d82..4e3885d 100644 --- a/packages/mcp/src/claim.cgi.c +++ b/packages/mcp/src/claim.cgi.c @@ -29,9 +29,6 @@ main(int argc, char *argv[]) } } - if (! team_exists(team)) { - cgi_result(409, "No such team", "

There is no team with that hash.

"); - } /* Any weird characters in token name? */ { @@ -49,12 +46,6 @@ main(int argc, char *argv[]) } } - - /* Does the token exist? */ - if (! fgrepx(token, state_path("tokens.db"))) { - cgi_result(409, "No such token", "

This token has not been issued.

"); - } - /* Award points */ { char *p = token; @@ -62,6 +53,7 @@ main(int argc, char *argv[]) char category[40]; char points_s[40]; int points; + int ret; /* Pull category name out of the token */ for (q = category; *p && (*p != ':'); p += 1) { @@ -70,6 +62,11 @@ main(int argc, char *argv[]) *q = '\0'; if (p) p += 1; + /* Now we can check to see if it's valid */ + if (! anchored_search(package_path("%s/tokens.txt", category), token, 0)) { + cgi_result(409, "No such token", "

This token has not been issued.

"); + } + /* Pull point value out of the token (if it has one) */ for (q = points_s; *p && (*p != ':'); p += 1) { *(q++) = *p; @@ -78,11 +75,9 @@ main(int argc, char *argv[]) points = atoi(points_s); if (0 == points) points = 1; - { - char line[200]; - - my_snprintf(line, sizeof(line), "%s %s", team, token); - award_and_log_uniquely(team, category, points, state_path("claim.db"), line); + ret = award_points(team, category, points, token); + if (ret < 0) { + cgi_fail(ret); } } diff --git a/packages/mcp/src/common.c b/packages/mcp/src/common.c index c78006d..1761a53 100644 --- a/packages/mcp/src/common.c +++ b/packages/mcp/src/common.c @@ -228,6 +228,21 @@ cgi_result(int code, char *desc, char *fmt, ...) exit(0); } +void +cgi_fail(int err) +{ + switch (err) { + case ERR_GENERAL: + cgi_result(500, "Points not awarded", "

The server is unable to award your points at this time.

"); + case ERR_NOTEAM: + cgi_result(409, "No such team", "

There is no team with that hash.

"); + case ERR_CLAIMED: + cgi_result(409, "Already claimed", "

Your team has already claimed these points.

"); + default: + cgi_result(409, "Failure", "

Failure code: %d

", err); + } +} + void cgi_page(char *title, char *fmt, ...) { @@ -252,44 +267,55 @@ cgi_error(char *text) * Common routines */ - -#define EOL(c) ((EOF == (c)) || ('\n' == (c))) - +/* cut -d$ANCHOR -f2- | grep -Fx "$NEEDLE" */ int -fgrepx(char const *needle, char const *filename) +anchored_search(char const *filename, char const *needle, char const anchor) { - FILE *f; - int found = 0; - char const *p = needle; + FILE *f = fopen(filename, "r"); + size_t nlen = strlen(needle); + char line[1024]; + int ret = 0; - f = fopen(filename, "r"); - if (f) { - while (1) { - int c = fgetc(f); + while (f) { + char *p; - /* This list of cases would have looked so much nicer in OCaml. I - apologize. */ - if (EOL(c) && ('\0' == *p)) { - found = 1; - break; - } else if (EOF == c) { /* End of file */ - break; - } else if (('\0' == p) || (*p != (char)c)) { - p = needle; - /* Discard the rest of the line */ - do { - c = fgetc(f); - } while (! EOL(c)); - } else if (EOL(c)) { - p = needle; - } else { /* It matched */ - p += 1; - } + if (NULL == fgets(line, sizeof line, f)) { + break; + } + + /* Find anchor */ + if (anchor) { + p = strchr(line, anchor); + if (! p) { + continue; + } + p += 1; + } else { + p = line; + } + + /* Don't bother with strcmp if string lengths differ. + If this string is shorter than the previous, it's okay. This is + just a performance hack. + */ + if ((p[nlen] != '\n') && + (p[nlen] != '\0')) { + continue; + } + p[nlen] = 0; + + /* Okay, now we can compare! */ + if (0 == strcmp(p, needle)) { + ret = 1; + break; + } } - fclose(f); - } - return found; + if (f) { + fclose(f); + } + + return ret; } void @@ -343,9 +369,8 @@ mkpath(char const *base, char const *fmt, va_list ap) char relpath[PATH_MAX]; static char path[PATH_MAX]; char const *var; - int len; - len = vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap); + vsnprintf(relpath, sizeof(relpath) - 1, fmt, ap); relpath[sizeof(relpath) - 1] = '\0'; var = getenv("CTF_BASE"); @@ -409,26 +434,36 @@ team_exists(char const *teamhash) return 1; } +/* Return values: + -1: general error + -2: no such team + -3: points already awarded + */ int award_points(char const *teamhash, char const *category, - long points) + const long points, + char const *uid) { char line[100]; int linelen; char *filename; - int fd; + FILE *f; time_t now = time(NULL); if (! team_exists(teamhash)) { - return -2; + return ERR_NOTEAM; } linelen = snprintf(line, sizeof(line), - "%lu %s %s %ld\n", - (unsigned long)now, teamhash, category, points); + "%s %s %ld %s", + teamhash, category, points, uid); if (sizeof(line) <= linelen) { - return -1; + return ERR_GENERAL; + } + + if (anchored_search(state_path("points.log"), line, ' ')) { + return ERR_CLAIMED; } /* At one time I had this writing to a single log file, using lockf. @@ -443,43 +478,30 @@ award_points(char const *teamhash, those files to the main log file, it is possible to stop the thing that appends, edit the file at leisure, and then start the appender back up, all without affecting things trying to score: they're - still able to record their score and move on. You don't even - really need an appender, but it does make things look a little - nicer on the fs. - - The fact that this makes the code simpler is just gravy. - - Note that doing this means there's a little time between when a - score's written and when the scoreboard (which only reads the log - file) picks it up. It's not a big deal for the points log, but - this situation makes this technique unsuitable for writing log - files that prevent people from double-scoring, like the puzzler or - token log. + still able to record their score and move on. */ - filename = state_path("points.tmp/%d.%d.%s.%s.%ld", - now, getpid(), + filename = state_path("points.tmp/%lu.%d.%s.%s.%ld", + (unsigned long)now, getpid(), teamhash, category, points); - - fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (-1 == fd) { - return -1; + f = fopen(filename, "w"); + if (! f) { + return ERR_GENERAL; } - if (-1 == write(fd, line, linelen)) { - close(fd); - return -1; + if (EOF == fprintf(f, "%lu %s\n", (unsigned long)now, line)) { + return ERR_GENERAL; } - close(fd); + fclose(f); /* Rename into points.new */ { char ofn[PATH_MAX]; strncpy(ofn, filename, sizeof(ofn)); - filename = state_path("points.new/%d.%d.%s.%s.%ld", - now, getpid(), + filename = state_path("points.new/%lu.%d.%s.%s.%ld", + (unsigned long)now, getpid(), teamhash, category, points); rename(ofn, filename); } @@ -487,49 +509,3 @@ award_points(char const *teamhash, return 0; } -/** Award points iff they haven't been logged. - - If [line] is not in [dbfile], append it and give [points] to [team] - in [category]. -*/ -void -award_and_log_uniquely(char const *team, - char const *category, - long points, - char const *dbpath, - char const *line) -{ - int fd; - - /* Make sure they haven't already claimed these points */ - if (fgrepx(line, dbpath)) { - cgi_result(409, "Already claimed", - "

Your team has already claimed these points.

"); - } - - /* Open and lock logfile */ - fd = open(dbpath, O_WRONLY | O_CREAT, 0666); - if (-1 == fd) { - cgi_error("Unable to open log"); - } - if (-1 == lockf(fd, F_LOCK, 0)) { - cgi_error("Unable to lock log"); - } - - /* Award points */ - if (0 != award_points(team, category, points)) { - cgi_error("Unable to award points"); - } - - /* Log that we did so */ - lseek(fd, 0, SEEK_END); - if (-1 == write(fd, line, strlen(line))) { - cgi_error("Unable to append log"); - } - if (-1 == write(fd, "\n", 1)) { - cgi_error("Unable to append log"); - } - close(fd); -} - - diff --git a/packages/mcp/src/common.h b/packages/mcp/src/common.h index 98432d0..f5cb77f 100644 --- a/packages/mcp/src/common.h +++ b/packages/mcp/src/common.h @@ -9,28 +9,29 @@ #define TOKEN_MAX 80 #define itokenlen 5 +#define ERR_GENERAL -1 +#define ERR_NOTEAM -2 +#define ERR_CLAIMED -3 + int cgi_init(char *global_argv[]); size_t cgi_item(char *str, size_t maxlen); void cgi_head(char *title); void cgi_foot(); void cgi_result(int code, char *desc, char *fmt, ...); +void cgi_fail(int err); void cgi_page(char *title, char *fmt, ...); void cgi_error(char *text); -int fgrepx(char const *needle, char const *filename); +int anchored_search(char const *filename, char const *needle, const char anchor); void urandom(char *buf, size_t buflen); int my_snprintf(char *buf, size_t buflen, char *fmt, ...); char *state_path(char const *fmt, ...); char *package_path(char const *fmt, ...); int team_exists(char const *teamhash); -int award_points(char const *teamhacsh, +int award_points(char const *teamhash, char const *category, - long point); -void award_and_log_uniquely(char const *team, - char const *category, - long points, - char const *logfile, - char const *line); + long point, + char const *uid); #endif diff --git a/packages/mcp/src/pointscli.c b/packages/mcp/src/pointscli.c index f74d512..c2841ff 100644 --- a/packages/mcp/src/pointscli.c +++ b/packages/mcp/src/pointscli.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "common.h" int @@ -8,9 +9,10 @@ main(int argc, char *argv[]) { int points; int ret; + char comment[512]; - if (argc != 4) { - fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS\n"); + if (argc != 5) { + fprintf(stderr, "Usage: pointscli TEAM CATEGORY POINTS 'COMMENT'\n"); return EX_USAGE; } @@ -20,17 +22,22 @@ main(int argc, char *argv[]) return EX_USAGE; } - ret = award_points(argv[1], argv[2], points); + snprintf(comment, sizeof comment, "--%s", argv[4]); + + ret = award_points(argv[1], argv[2], points, comment); switch (ret) { - case -3: - fprintf(stderr, "Runtime error\n"); - return EX_SOFTWARE; - case -2: + case ERR_GENERAL: + perror("General error"); + return EX_UNAVAILABLE; + case ERR_NOTEAM: fprintf(stderr, "No such team\n"); return EX_NOUSER; - case -1: - perror("Couldn't award points"); - return EX_UNAVAILABLE; + case ERR_CLAIMED: + fprintf(stderr, "Duplicate entry\n"); + return EX_DATAERR; + default: + fprintf(stderr, "Error %d\n", ret); + return EX_SOFTWARE; } return 0; diff --git a/packages/mcp/src/puzzler.cgi.c b/packages/mcp/src/puzzler.cgi.c index 6a41910..e2c4697 100644 --- a/packages/mcp/src/puzzler.cgi.c +++ b/packages/mcp/src/puzzler.cgi.c @@ -40,11 +40,6 @@ main(int argc, char *argv[]) } } - /* Check to see if team exists */ - if (! team_exists(team)) { - cgi_page("No such team", ""); - } - /* Validate category name (prevent directory traversal) */ { char *p; @@ -62,19 +57,16 @@ main(int argc, char *argv[]) my_snprintf(needle, sizeof(needle), "%ld %s", points, answer); - if (! fgrepx(needle, - package_path("%s/answers.txt", category))) { + if (! anchored_search(package_path("%s/answers.txt", category), needle, 0)) { cgi_page("Wrong answer", ""); } } { - char line[200]; - - my_snprintf(line, sizeof(line), - "%s %s %ld", team, category, points); - award_and_log_uniquely(team, category, points, - state_path("puzzles.db"), line); + int ret = award_points(team, category, points, "P"); + if (ret < 0) { + cgi_fail(ret); + } } cgi_page("Points awarded",