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)
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:

View File

@ -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"
"<h1>Method not allowed</h1>\n"
"<p>I only speak POST. Sorry.</p>\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("<h1>%s</h1>\n", title);
p = stdout;
} else {
fprintf(p, "Title: %s\n", title);
}
printf(("Content-type: text/html\r\n"
"\r\n"
"<!DOCTYPE html>\n"
"<html>\n"
" <head>\n"
" <title>%s</title>\n"
" <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">\n"
" </head>\n"
" <body>\n"
" <h1>%s</h1>\n"),
title, title);
va_start(ap, fmt);
vfprintf(p, fmt, ap);
vprintf(fmt, ap);
va_end(ap);
fclose(p);
printf("\n"
" </body>\n"
"</html>\n");
exit(0);
}

View File

@ -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",
"<p>Sorry, that token's no good.</p>");
}
/* 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",
"<p>Apparently you've already claimed that token.</p>");
}
/* 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", "<!-- success -->");
/* 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", "<!-- success -->");
return 0;
}

View File

@ -2,9 +2,11 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#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",
"<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 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

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;
}