Add puzzler.cgi, put more into common.c

This commit is contained in:
Neale Pickett 2010-09-11 22:57:46 -06:00
parent c9c24d9a8b
commit c3bf43b132
6 changed files with 239 additions and 130 deletions

View File

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

View File

@ -11,10 +11,10 @@ cgi_init()
char *rm = getenv("REQUEST_METHOD"); char *rm = getenv("REQUEST_METHOD");
if (! (rm && (0 == strcmp(rm, "POST")))) { if (! (rm && (0 == strcmp(rm, "POST")))) {
printf("405 Method not allowed\n" printf("405 Method not allowed\r\n"
"Allow: POST\n" "Allow: POST\r\n"
"Content-type: text/html\n" "Content-type: text/html\r\n"
"\n" "\r\n"
"<h1>Method not allowed</h1>\n" "<h1>Method not allowed</h1>\n"
"<p>I only speak POST. Sorry.</p>\n"); "<p>I only speak POST. Sorry.</p>\n");
return -1; return -1;
@ -93,23 +93,25 @@ cgi_item(char *str, size_t maxlen)
void void
cgi_page(char *title, char *fmt, ...) cgi_page(char *title, char *fmt, ...)
{ {
FILE *p;
va_list ap; va_list ap;
printf("Content-type: text/html\r\n" printf(("Content-type: text/html\r\n"
"\r\n"); "\r\n"
fflush(stdout); "<!DOCTYPE html>\n"
p = popen("./template", "w"); "<html>\n"
if (NULL == p) { " <head>\n"
printf("<h1>%s</h1>\n", title); " <title>%s</title>\n"
p = stdout; " <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">\n"
} else { " </head>\n"
fprintf(p, "Title: %s\n", title); " <body>\n"
} " <h1>%s</h1>\n"),
title, title);
va_start(ap, fmt); va_start(ap, fmt);
vfprintf(p, fmt, ap); vprintf(fmt, ap);
va_end(ap); va_end(ap);
fclose(p); printf("\n"
" </body>\n"
"</html>\n");
exit(0); exit(0);
} }

View File

@ -11,12 +11,10 @@ int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
char team[9]; char team[9];
size_t teamlen;
char token[100]; char token[100];
size_t tokenlen;
/* XXX: This code needs to be tested */ /* XXX: This code needs to be tested */
abort(); return 1;
if (-1 == cgi_init()) { if (-1 == cgi_init()) {
return 0; return 0;
@ -26,82 +24,45 @@ main(int argc, char *argv[])
while (1) { while (1) {
size_t len; size_t len;
char key[20]; char key[20];
int i;
len = cgi_item(key, sizeof(key)); len = cgi_item(key, sizeof(key));
if (0 == len) break; if (0 == len) break;
if (1 == len) { switch (key[0]) {
if ('t' == key[0]) { case 't':
teamlen = cgi_item(team, sizeof(team) - 1); cgi_item(team, sizeof(team));
/* End string at # read or first bad char */
for (i = 0; i < teamlen; i += 1) {
if (! isalnum(team[i])) {
break; break;
} case 'k':
} cgi_item(token, sizeof(token));
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; break;
} }
} }
token[i] = '\0';
}
}
}
if (! team_exists(team)) { if (! team_exists(team)) {
cgi_page("No such team", ""); cgi_page("No such team", "");
} }
if (! fgrepx(token, claimlog)) { /* Any weird characters in token name? */
cgi_page("Invalid token",
"<p>Sorry, that token's no good.</p>");
}
/* If the token's unclaimed, award points and log the claim */
{ {
FILE *f;
int claimed = 0;
char needle[100];
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; char *p;
int pos;
if (NULL == fgets(line, sizeof(line), f)) { for (p = token; *p; p += 1) {
break; if ((! isalnum(*p)) &&
} (*p != '-') &&
(*p != ':')) {
/* Skip to past first space */ cgi_page("Invalid token", "");
for (p = line; (*p && (*p != ' ')); p += 1);
if (0 == mystrcmp(p, needle)) {
claimed = 1;
break;
} }
} }
if (claimed) {
cgi_page("Already claimed",
"<p>Apparently you've already claimed that token.</p>");
} }
/* Now register the points */
/* Does the token exist? */
if (! fgrepx(token, tokenlog)) {
cgi_page("Token does not exist", "");
}
/* Award points */
{ {
char category[20]; char category[40];
int i; int i;
/* Pull category name out of the token */ /* Pull category name out of the token */
@ -110,38 +71,12 @@ main(int argc, char *argv[])
} }
category[i] = '\0'; category[i] = '\0';
award_points(team, category, 1); award_and_log_uniquely(team, category, 1,
claimlog, "%s %s", team, token);
} }
/* 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); cgi_page("Point awarded", "<!-- success -->");
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", "<!-- success -->");
return 0; return 0;
} }

View File

@ -2,9 +2,11 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include <time.h> #include <time.h>
#include "cgi.h"
#include "common.h" #include "common.h"
#define EOL(c) ((EOF == (c)) || (0 == (c)) || ('\n' == (c))) #define EOL(c) ((EOF == (c)) || (0 == (c)) || ('\n' == (c)))
@ -46,7 +48,24 @@ fgrepx(char const *needle, char const *filename)
} }
int 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; struct stat buf;
char filename[100]; char filename[100];
@ -63,7 +82,7 @@ team_exists(char *teamhash)
/* Build filename. */ /* Build filename. */
ret = snprintf(filename, sizeof(filename), ret = snprintf(filename, sizeof(filename),
"%s/%s", teamdir, teamhash); "%s/%s", teamdir, teamhash);
if (sizeof(filename) == ret) { if (sizeof(filename) <= ret) {
return 0; return 0;
} }
@ -77,7 +96,9 @@ team_exists(char *teamhash)
} }
int int
award_points(char *teamhash, char *category, int points) award_points(char const *teamhash,
char const *category,
int points)
{ {
char line[100]; char line[100];
int linelen; int linelen;
@ -94,7 +115,7 @@ award_points(char *teamhash, char *category, int points)
linelen = snprintf(line, sizeof(line), linelen = snprintf(line, sizeof(line),
"%u %s %s %d\n", "%u %s %s %d\n",
now, teamhash, category, points); now, teamhash, category, points);
if (sizeof(line) == linelen) { if (sizeof(line) <= linelen) {
return -1; 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. This works, as long as nobody ever tries to edit the log file.
Editing the log file would require locking it, which would block Editing the log file would require locking it, which would block
everything trying to score, effectively taking down the entire everything trying to score, effectively taking down the entire
contest. If you can't lock it first (nothing in busybox lets you contest. If you can't lock it first--and nothing in busybox lets
do this), you have to bring down pretty much everything manually you do this--you have to bring down pretty much everything manually
anyway. anyway.
By putting new scores into new files and periodically appending 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. nicer on the fs.
The fact that this makes the code simpler is just gravy. 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), filenamelen = snprintf(filename, sizeof(filename),
"%s/%d.%d.%s.%s.%d", "%s/%d.%d.%s.%s.%d",
pointsdir, now, getpid(), pointsdir, now, getpid(),
teamhash, category, points); teamhash, category, points);
if (sizeof(filename) == filenamelen) { if (sizeof(filename) <= filenamelen) {
return -1; return -1;
} }
@ -138,3 +166,54 @@ award_points(char *teamhash, char *category, int points)
close(fd); close(fd);
return 0; 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",
"<p>Your team has already claimed these points.</p>");
}
/* 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);
}

View File

@ -4,8 +4,15 @@
#define teamdir "/var/lib/ctf/teams/names" #define teamdir "/var/lib/ctf/teams/names"
#define pointsdir "/var/lib/ctf/points/new" #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 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 #endif

85
src/puzzler.cgi.c Normal file
View File

@ -0,0 +1,85 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#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",
"<p>%d points for %s.</p>", points, team);
return 0;
}