diff --git a/src/Makefile b/src/Makefile index 81105b6..4416543 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,10 +1,11 @@ -TARGETS = in.tokend claim.cgi pointscli +TARGETS = in.tokend claim.cgi puzzler.cgi pointscli all: $(TARGETS) in.tokend: in.tokend.o xxtea.o claim.cgi: claim.cgi.o cgi.o common.o +puzzler.cgi: puzzler.cgi.o cgi.o common.o pointscli: common.o pointscli.o clean: diff --git a/src/cgi.c b/src/cgi.c index 8169e3d..6d2383d 100644 --- a/src/cgi.c +++ b/src/cgi.c @@ -11,10 +11,10 @@ cgi_init() char *rm = getenv("REQUEST_METHOD"); if (! (rm && (0 == strcmp(rm, "POST")))) { - printf("405 Method not allowed\n" - "Allow: POST\n" - "Content-type: text/html\n" - "\n" + 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; @@ -93,23 +93,25 @@ cgi_item(char *str, size_t maxlen) void cgi_page(char *title, char *fmt, ...) { - FILE *p; va_list ap; - printf("Content-type: text/html\r\n" - "\r\n"); - fflush(stdout); - p = popen("./template", "w"); - if (NULL == p) { - printf("

%s

\n", title); - p = stdout; - } else { - fprintf(p, "Title: %s\n", title); - } + printf(("Content-type: text/html\r\n" + "\r\n" + "\n" + "\n" + " \n" + " %s\n" + " \n" + " \n" + " \n" + "

%s

\n"), + title, title); va_start(ap, fmt); - vfprintf(p, fmt, ap); + vprintf(fmt, ap); va_end(ap); - fclose(p); + printf("\n" + " \n" + "\n"); exit(0); } diff --git a/src/claim.cgi.c b/src/claim.cgi.c index f079836..f087166 100644 --- a/src/claim.cgi.c +++ b/src/claim.cgi.c @@ -11,12 +11,10 @@ int main(int argc, char *argv[]) { char team[9]; - size_t teamlen; char token[100]; - size_t tokenlen; /* XXX: This code needs to be tested */ - abort(); + return 1; if (-1 == cgi_init()) { return 0; @@ -26,34 +24,16 @@ main(int argc, char *argv[]) while (1) { size_t len; char key[20]; - int i; len = cgi_item(key, sizeof(key)); if (0 == len) break; - if (1 == len) { - if ('t' == key[0]) { - teamlen = cgi_item(team, sizeof(team) - 1); - - /* End string at # read or first bad char */ - for (i = 0; i < teamlen; i += 1) { - if (! isalnum(team[i])) { - break; - } - } - team[i] = '\0'; - } else if ('k' == key[0]) { - tokenlen = cgi_item(token, sizeof(token) - 1); - - /* End string at # read or first bad char */ - for (i = 0; i < tokenlen; i += 1) { - if ((! isalnum(token[i])) && - (token[i] != '-') && - (token[i] != ':')) { - break; - } - } - token[i] = '\0'; - } + switch (key[0]) { + case 't': + cgi_item(team, sizeof(team)); + break; + case 'k': + cgi_item(token, sizeof(token)); + break; } } @@ -61,87 +41,42 @@ main(int argc, char *argv[]) cgi_page("No such team", ""); } - if (! fgrepx(token, claimlog)) { - cgi_page("Invalid token", - "

Sorry, that token's no good.

"); - } - - /* If the token's unclaimed, award points and log the claim */ + /* Any weird characters in token name? */ { - FILE *f; - int claimed = 0; - char needle[100]; + char *p; - f = fopen(claimlog, "rw"); - if (! f) { - cgi_error("Couldn't fopen(\"%s\", \"rw\")", claimlog); - } - sprintf(needle, "%s %s", team, token); - while (1) { - char line[100]; - char *p; - int pos; - - if (NULL == fgets(line, sizeof(line), f)) { - break; - } - - /* Skip to past first space */ - for (p = line; (*p && (*p != ' ')); p += 1); - - if (0 == mystrcmp(p, needle)) { - claimed = 1; - break; + for (p = token; *p; p += 1) { + if ((! isalnum(*p)) && + (*p != '-') && + (*p != ':')) { + cgi_page("Invalid token", ""); } } - if (claimed) { - cgi_page("Already claimed", - "

Apparently you've already claimed that token.

"); - } - - /* Now register the points */ - { - char category[20]; - int i; - - /* Pull category name out of the token */ - for (i = 0; token[i] != ':'; i += 1) { - category[i] = token[i]; - } - category[i] = '\0'; - - award_points(team, category, 1); - } - - /* Finally, append an entry to the log file. I figure it's better - to give points first and fail here, than it is to lock them out - of making points and then fail to award them. */ - { - char timestamp[40]; - time_t t; - struct tm *tmp; - int ret; - - t = time(NULL); - tmp = localtime(&t); - if (NULL == tmp) { - cgi_error("I... uh... couldn't figure out what time it is."); - } - if (0 == strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S", tmp)) { - cgi_error("I forgot how to format time."); - } - fseek(f, 0, SEEK_SET); - if (-1 == lockf(fileno(f), F_LOCK, 0)) { - cgi_error("Unable to lock the log file."); - } - fseek(f, 0, SEEK_END); - fprintf(f, "%s %s %s\n", timestamp, team, token); - } - - fclose(f); } - cgi_page("Points awarded", ""); + + /* Does the token exist? */ + if (! fgrepx(token, tokenlog)) { + cgi_page("Token does not exist", ""); + } + + /* Award points */ + { + char category[40]; + int i; + + /* Pull category name out of the token */ + for (i = 0; token[i] != ':'; i += 1) { + category[i] = token[i]; + } + category[i] = '\0'; + + award_and_log_uniquely(team, category, 1, + claimlog, "%s %s", team, token); + } + + + cgi_page("Point awarded", ""); return 0; } diff --git a/src/common.c b/src/common.c index d5ec3cf..ab5e98b 100644 --- a/src/common.c +++ b/src/common.c @@ -2,9 +2,11 @@ #include #include #include +#include #include #include #include +#include "cgi.h" #include "common.h" #define EOL(c) ((EOF == (c)) || (0 == (c)) || ('\n' == (c))) @@ -46,7 +48,24 @@ fgrepx(char const *needle, char const *filename) } int -team_exists(char *teamhash) +my_snprintf(char *buf, size_t buflen, char *fmt, ...) +{ + int len; + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(buf, buflen - 1, fmt, ap); + va_end(ap); + if (len >= 0) { + buf[len] = '\0'; + return len; + } else { + return -1; + } +} + +int +team_exists(char const *teamhash) { struct stat buf; char filename[100]; @@ -63,7 +82,7 @@ team_exists(char *teamhash) /* Build filename. */ ret = snprintf(filename, sizeof(filename), "%s/%s", teamdir, teamhash); - if (sizeof(filename) == ret) { + if (sizeof(filename) <= ret) { return 0; } @@ -77,7 +96,9 @@ team_exists(char *teamhash) } int -award_points(char *teamhash, char *category, int points) +award_points(char const *teamhash, + char const *category, + int points) { char line[100]; int linelen; @@ -94,7 +115,7 @@ award_points(char *teamhash, char *category, int points) linelen = snprintf(line, sizeof(line), "%u %s %s %d\n", now, teamhash, category, points); - if (sizeof(line) == linelen) { + if (sizeof(line) <= linelen) { return -1; } @@ -102,8 +123,8 @@ award_points(char *teamhash, char *category, int points) This works, as long as nobody ever tries to edit the log file. Editing the log file would require locking it, which would block everything trying to score, effectively taking down the entire - contest. If you can't lock it first (nothing in busybox lets you - do this), you have to bring down pretty much everything manually + contest. If you can't lock it first--and nothing in busybox lets + you do this--you have to bring down pretty much everything manually anyway. By putting new scores into new files and periodically appending @@ -115,13 +136,20 @@ award_points(char *teamhash, char *category, int points) 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. */ filenamelen = snprintf(filename, sizeof(filename), "%s/%d.%d.%s.%s.%d", pointsdir, now, getpid(), teamhash, category, points); - if (sizeof(filename) == filenamelen) { + if (sizeof(filename) <= filenamelen) { return -1; } @@ -138,3 +166,54 @@ award_points(char *teamhash, char *category, int points) close(fd); return 0; } + +void +award_and_log_uniquely(char const *team, + char const *category, + int points, + char const *logfile, + char const *fmt, ...) +{ + char line[100]; + int len; + int ret; + int fd; + va_list ap; + + /* Make sure they haven't already claimed these points */ + /* Leave room for a newline later on */ + va_start(ap, fmt); + len = vsnprintf(line, sizeof(line)-1, fmt, ap); + va_end(ap); + if (sizeof(line) <= len) { + cgi_error("Log line too long"); + } + if (fgrepx(line, logfile)) { + cgi_page("Already claimed", + "

Your team has already claimed these points.

"); + } + + /* Open and lock logfile */ + fd = open(logfile, 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 */ + /* We can turn that trailing NUL into a newline now since write + doesn't use C strings */ + line[len] = '\n'; + lseek(fd, 0, SEEK_END); + if (-1 == write(fd, line, len+1)) { + cgi_error("Unable to log your award"); + } + close(fd); +} diff --git a/src/common.h b/src/common.h index 51399db..a119289 100644 --- a/src/common.h +++ b/src/common.h @@ -4,8 +4,15 @@ #define teamdir "/var/lib/ctf/teams/names" #define pointsdir "/var/lib/ctf/points/new" -int team_exists(char *teamhash); -int award_points(char *teamhash, char *category, int point); int fgrepx(char const *needle, char const *filename); +int team_exists(char const *teamhash); +int award_points(char const *teamhash, + char const *category, + int point); +void award_and_log_uniquely(char const *team, + char const *category, + int points, + char const *logfile, + char const *fmt, ...); #endif diff --git a/src/puzzler.cgi.c b/src/puzzler.cgi.c new file mode 100644 index 0000000..844cf5b --- /dev/null +++ b/src/puzzler.cgi.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include "common.h" +#include "cgi.h" + +char const *logfile = "/var/lib/ctf/puzzler.log"; + +int +main(int argc, char *argv) +{ + char team[9]; + char category[30]; + char points_str[5]; + char answer[500]; + int points; + + if (-1 == cgi_init()) { + return 0; + } + + /* Read in team and answer */ + while (1) { + size_t len; + char key[20]; + + len = cgi_item(key, sizeof(key)); + if (0 == len) break; + switch (key[0]) { + case 't': + cgi_item(team, sizeof(team)); + break; + case 'c': + cgi_item(category, sizeof(category)); + break; + case 'p': + cgi_item(points_str, sizeof(points_str)); + points = atoi(points_str); + break; + case 'a': + cgi_item(answer, sizeof(answer)); + break; + } + } + + /* Check to see if team exists */ + if (! team_exists(team)) { + cgi_page("No such team", ""); + } + + /* Validate category name (prevent directory traversal) */ + { + char *p; + + for (p = category; *p; p += 1) { + if (! isalnum(*p)) { + cgi_page("Invalid category", ""); + } + } + } + + /* Check answer (also assures category exists) */ + { + char filename[100]; + char needle[100]; + + my_snprintf(filename, sizeof(filename), + "/srv/%s/answers.txt", category); + my_snprintf(needle, sizeof(needle), + "%d %s", points, answer); + if (! fgrepx(needle, filename)) { + cgi_page("Wrong answer", ""); + } + } + + award_and_log_uniquely(team, category, points, + logfile, "%s %s %d", team, category, points); + + cgi_page("Points awarded", + "

%d points for %s.

", points, team); + + return 0; +}