aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2021-09-22 02:11:50 +0200
committerJohn Ankarström <john@ankarstrom.se>2021-09-22 02:11:50 +0200
commit1e0433c9df062ead21d0f46b15d088622b7d5c4f (patch)
treecc894172ca7deae5d835aea8f313da3aab1e2915
parent3ea978c60ee03dba2cb883ba18d3b334cfbf6cd0 (diff)
downloadcforum-master.tar.gz
Log in, log outHEADmaster
-rw-r--r--Makefile3
-rw-r--r--README14
-rw-r--r--cforum.c73
-rw-r--r--cforum.h21
-rw-r--r--ctl.c171
-rw-r--r--db.c193
-rw-r--r--err.c2
-rw-r--r--mktpl/README2
-rw-r--r--mktpl/mktpl.lex12
-rw-r--r--query.c49
-rw-r--r--t/front.t7
11 files changed, 381 insertions, 166 deletions
diff --git a/Makefile b/Makefile
index f6f78ac..ae09e1c 100644
--- a/Makefile
+++ b/Makefile
@@ -37,4 +37,5 @@ db:
sqlite3 db "INSERT INTO posts values(NULL, 1, 1462137896, NULL, 'Hello World!', 'This is the first post.');"
sqlite3 db "INSERT INTO posts values(1, 1, 1462138896, NULL, 'Re: Hello World!', 'This is the second post!');"
sqlite3 db "CREATE TABLE atts(post INT NOT NULL, name NOT NULL, desc, mime NOT NULL, data BLOB, FOREIGN KEY (post) REFERENCES posts(oid));"
- sqlite3 db "$$(printf "INSERT INTO atts values(1, 'example', 'Some example shell code.', 'text/plain', '#!/bin/sh\necho Hello World!');")" \ No newline at end of file
+ sqlite3 db "$$(printf "INSERT INTO atts values(1, 'example', 'Some example shell code.', 'text/plain', '#!/bin/sh\necho Hello World!');")"
+ sqlite3 db "CREATE TABLE sessions(user INT NOT NULL, string NOT NULL, created INT NOT NULL, FOREIGN KEY (user) REFERENCES users(oid));"
diff --git a/README b/README
index e84f8a4..93d8782 100644
--- a/README
+++ b/README
@@ -8,12 +8,12 @@ C89, it can be run on practically any UNIX system.
It is also rather small:
wc -l *.c *.h crypt/*.c crypt/*.h */*.t */*.lex
- 97 cforum.c
- 381 ctl.c
- 306 db.c
+ 138 cforum.c
+ 404 ctl.c
+ 403 db.c
11 err.c
- 144 query.c
- 101 cforum.h
+ 171 query.c
+ 120 cforum.h
907 crypt/crypt_blowfish.c
124 crypt/crypt_gensalt.c
551 crypt/wrapper.c
@@ -23,11 +23,11 @@ It is also rather small:
43 crypt/ow-crypt.h
3 t/err.t
1 t/foot.t
- 30 t/front.t
+ 37 t/front.t
15 t/head.t
18 t/login.t
38 t/newuser.t
28 t/post.t
12 t/user.t
95 mktpl/mktpl.lex
- 2986 total
+ 3200 total
diff --git a/cforum.c b/cforum.c
index b536827..fbd5d85 100644
--- a/cforum.c
+++ b/cforum.c
@@ -10,10 +10,11 @@
int
main(int argc, char *argv[])
{
- char msg[MAXMSG], *new, *p, *v;
- int attid, postid, userid;
+ char *delete, *home, *k, msg[MAXMSG], *new, *ssecret, *v;
+ int attid, postid, suser, userid;
+ struct session *session;
sqlite3_stmt *stmt;
-
+
/*
* The database is opened or a server error is generated.
* In the rest of the program, the database is always
@@ -27,7 +28,7 @@ main(int argc, char *argv[])
sqlite3_close(db);
return 1;
}
-
+
/*
* The site name is retrieved from the database. This early on,
* it is appropriate to die with a server error on failure.
@@ -52,30 +53,60 @@ main(int argc, char *argv[])
return 1;
}
sqlite3_finalize(stmt);
-
+
/*
* The global struct query is set, or the program dies.
* From now on, query is assumed to be set.
*/
setquery();
-
+
+ /* Check session. */
+ curuser = NULL;
+ ssecret = NULL;
+ suser = -1;
+ while(k = nextcookie(MAXCOOKIE+20)){
+ v = split(k);
+ if(!ssecret && strcmp(k, "session") == 0) ssecret = strdup(v);
+ else if(suser == -1 && strcmp(k, "user") == 0) suser = atoi(v);
+ }
+
+ if(sqlite3_prepare(db,
+ "SELECT oid, * FROM sessions WHERE user = ? AND string = ?",
+ -1, &stmt, 0) != SQLITE_OK)
+ goto skip;
+
+ if(sqlite3_bind_int(stmt, 1, suser) != SQLITE_OK)
+ goto skip;
+
+ if(sqlite3_bind_text(stmt, 2, ssecret, -1, SQLITE_STATIC) != SQLITE_OK)
+ goto skip;
+
+ if(!(session = getsession(stmt)))
+ goto skip;
+
+ /* Session is valid. */
+ cursession = session;
+ curuser = getuser(selectbyint("users", "oid", suser));
+
+skip:
/* Handle empty request. */
if(!*query.string){
showfront();
goto end;
}
-
+
/* Parse query string. */
- new = NULL;
+ new = delete = NULL;
attid = postid = userid = 0;
- while(p = nextparam(GET, NULL, 128)){
- v = split(p);
- if(!attid && strcmp(p, "att") == 0) attid = atoi(v);
- else if(!postid && strcmp(p, "post") == 0) postid = atoi(v);
- else if(!userid && strcmp(p, "user") == 0) userid = atoi(v);
- else if(!new && strcmp(p, "new") == 0) new = strdup(v);
+ while(k = nextparam(GET, NULL, 128)){
+ v = split(k);
+ if(!attid && strcmp(k, "att") == 0) attid = atoi(v);
+ else if(!postid && strcmp(k, "post") == 0) postid = atoi(v);
+ else if(!userid && strcmp(k, "user") == 0) userid = atoi(v);
+ else if(!new && strcmp(k, "new") == 0) new = strdup(v);
+ else if(!delete && strcmp(k,"delete") == 0) delete = strdup(v);
}
-
+
/* Handle request. */
if(attid)
showatt(attid);
@@ -89,9 +120,19 @@ main(int argc, char *argv[])
else if(strcmp(v, "user") == 0) newuser();
else if(strcmp(v, "session") == 0) newsession();
else showfront(); /* TODO */
+ }
+ else if(delete){
+ if(strcmp(v, "session") == 0){
+ if(cursession)
+ deletesession(cursession);
+ home = getenv("REQUEST_URI");
+ home[strcspn(home, "?")] = 0;
+ printf("Status: 303 See Other\n");
+ printf("Location: %s\n\n", home);
+ }
}else
showfront();
-
+
end:
sqlite3_close(db);
return 0;
diff --git a/cforum.h b/cforum.h
index 7d7dc60..7285e76 100644
--- a/cforum.h
+++ b/cforum.h
@@ -27,6 +27,13 @@ struct post{
char *text;
};
+struct session{
+ int id;
+ int user;
+ int created;
+ char *string;
+};
+
struct user{
int id;
int created;
@@ -60,14 +67,20 @@ void showuser(int);
/* db.c */
int addatt(struct att *);
+int addpost(struct post *);
+int addsession(struct session *);
int adduser(struct user *);
+int deletesession(struct session *);
int haspass(struct user *, char *);
+void makerandom(char *, int);
void makehash(char *, char **, char **);
struct att *getatt(sqlite3_stmt *);
struct post *getpost(sqlite3_stmt *);
+struct session *getsession(sqlite3_stmt *);
struct user *getuser(sqlite3_stmt *);
struct att *nextatt(sqlite3_stmt *);
struct post *nextpost(sqlite3_stmt *);
+struct session *nextsession(sqlite3_stmt *);
struct user *nextuser(sqlite3_stmt *);
sqlite3_stmt *selectbyint(char *, char *, int);
@@ -75,6 +88,7 @@ sqlite3_stmt *selectbyint(char *, char *, int);
void srverr(char *);
/* query.c */
+char *nextcookie(int);
char *nextparam(enum method, int *, int);
void setquery(void);
char *split(char *);
@@ -95,8 +109,13 @@ int urldecode(char *, int);
#define MAXUSERFULL 128
#define MAXUSERPASS 72
+/* Maximum size of cookie values. */
+#define MAXCOOKIE 40
+
/***** Variables *****/
sqlite3 *db;
struct query query;
-struct site site; \ No newline at end of file
+struct site site;
+struct user *curuser;
+struct session *cursession; \ No newline at end of file
diff --git a/ctl.c b/ctl.c
index 9583c44..39581c2 100644
--- a/ctl.c
+++ b/ctl.c
@@ -1,41 +1,16 @@
#include <err.h>
+#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <unistd.h>
#include "cforum.h"
#define MAXMSG 256
-/* Print UNIX timestamp as written date. */
-void
-printdate(int timestamp)
-{
- char buf[20];
- struct tm *t;
-
- t = localtime((time_t *)&timestamp);
- strftime(buf, 20, "%Y-%m-%d %H:%M", t);
-
- printf(buf);
-}
-
-/* Print HTML-escaped string. */
-void
-printhtml(char *s)
-{
- char *t;
-
- for(t = s; *t; t++)
- switch(*t){
- case '&': printf("&amp;"); break;
- case '\"': printf("&quot;"); break;
- case '\'': printf("&apos;"); break;
- case '<': printf("&lt;"); break;
- case '>': printf("&gt;"); break;
- default: printf("%c", *t);
- }
-}
+static void printdate(int);
+static void printhtml(char *);
/*
* The `new' functions provide a way to add a new attachment/post/user.
@@ -53,21 +28,26 @@ newpost()
void
newsession()
{
- char *hlite, msg[MAXMSG], *p, *v;
+ char *hlite, *home, msg[MAXMSG], *p, rnd[MAXCOOKIE], *v;
char *name, *pass, *remember;
- char title[] = "Log In";
struct user *user;
+ struct session session;
sqlite3_stmt *stmt;
-
+
+ char title[] = "Log In";
+
*msg = 0;
hlite = name = pass = remember = NULL;
-
+
+ home = getenv("REQUEST_URI");
+ home[strcspn(home, "?")] = 0;
+
if(query.method == GET){
printf("Content-Type: text/html\n\n");
#include "t/login.tc"
return;
}
-
+
if(query.length > MAXSESSIONDATA){
snprintf(msg, MAXMSG, "Input exceeded server limitations");
printf("Status: 431 Request Header Fields Too Large\n");
@@ -75,10 +55,10 @@ newsession()
#include "t/login.tc"
return;
}
-
+
while(p = nextparam(POST, NULL, MAXSESSIONDATA)){
if(!(v = split(p))) continue;
-
+
if(!name && strcmp(p, "name") == 0)
name = strdup(v);
else if(!pass && strcmp(p, "pass") == 0)
@@ -88,7 +68,7 @@ newsession()
else
continue;
}
-
+
if(!name || !*name){
snprintf(msg, MAXMSG, "Username may not be empty");
printf("Status: 400 Bad Request\n");
@@ -96,28 +76,42 @@ newsession()
#include "t/login.tc"
return;
}
-
- if(sqlite3_prepare(db, "SELECT oid, * from users WHERE name = ?",
+
+ if(sqlite3_prepare(db, "SELECT oid, * FROM users WHERE name = ?",
-1, &stmt, 0) != SQLITE_OK)
goto fail;
-
+
if(sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC) != SQLITE_OK)
goto dberr;
-
+
if(!(user = getuser(stmt)))
goto fail;
-
+
if(!haspass(user, pass))
goto fail;
+
+ /* Create session cookie. */
+ makerandom(rnd, sizeof(rnd));
+ session.user = user->id;
+ session.string = rnd;
+ if(!addsession(&session)){
+ snprintf(msg, MAXMSG, "Could not create session for user");
+ printf("Status: 500 Internal Server Error");
+ printf("Content-Type: text/html\n\n");
+ #include "t/login.tc"
+ return;
+ }
- printf("Content-Type: text/plain\n\n");
- printf("Success!\n");
+ printf("Status: 303 See Other\n");
+ printf("Set-Cookie: user=%d\n", user->id);
+ printf("Set-Cookie: session=%s\n", rnd);
+ printf("Location: %s\n\n", home);
return;
dberr:
snprintf(msg, MAXMSG, "Could not retrieve user: %s",
sqlite3_errmsg(db));
- printf("Status: 500 Internal Server Errror\n");
+ printf("Status: 500 Internal Server Error\n");
printf("Content-Type: text/html\n\n");
#include "t/login.tc"
return;
@@ -136,16 +130,16 @@ newuser()
char title[] = "New User";
int i;
struct user *user;
-
+
*msg = 0;
captcha = confirm = hlite = name = full = pass = NULL;
-
+
if(query.method == GET){
printf("Content-Type: text/html\n\n");
#include "t/newuser.tc"
return;
}
-
+
if(query.length > MAXUSERDATA){
snprintf(msg, MAXMSG, "Input exceeded server limitations");
printf("Status: 431 Request Header Fields Too Large\n");
@@ -153,10 +147,10 @@ newuser()
#include "t/newuser.tc"
return;
}
-
+
while(p = nextparam(POST, NULL, MAXUSERDATA)){
if(!(v = split(p))) continue;
-
+
if(!captcha && strcmp(p, "captcha") == 0)
captcha = strdup(v);
if(!confirm && strcmp(p, "confirm") == 0)
@@ -170,7 +164,7 @@ newuser()
else
continue;
}
-
+
/* Decode URL-encoded fields. */
if(captcha && *captcha)
captcha[urldecode(captcha, -1)] = 0;
@@ -182,7 +176,7 @@ newuser()
full[urldecode(full, -1)] = 0;
if(pass && *pass)
pass[urldecode(pass, -1)] = 0;
-
+
/* Constrain lengths of decoded fields. */
if(name && *name && strlen(name)-1 > MAXUSERNAME){
hlite = strdup("name");
@@ -202,10 +196,10 @@ newuser()
MAXUSERPASS-1);
goto err;
}
-
+
#define ISVIS(c) ((unsigned char)c >= 32)
#define ISALNUM(c) (c>='A' && c<='Z' || c>='a' && c<='z' || c>='0' && c<='9')
-
+
/* Constrain character sets. */
for(i = 0; name[i]; i++)
if(!name[i] == '_' && !ISALNUM(name[i])){
@@ -217,13 +211,12 @@ newuser()
}
for(i = 0; full[i]; i++)
if(!ISVIS(full[i])){
- fprintf(stderr, "%d\n", full[i]);
hlite = strdup("full");
snprintf(msg, MAXMSG,
"Full name may only contain visible characters");
goto err;
}
-
+
/* Validate captcha. */
if(captcha && strcmp(captcha, "9") != 0){
hlite = strdup("captcha");
@@ -247,14 +240,14 @@ newuser()
snprintf(msg, MAXMSG, "Passwords do not match");
goto err;
}
-
+
if(!(user = malloc(sizeof(struct user))))
err(1, "malloc");
-
+
user->name = name;
user->full = *full? full: NULL;
makehash(pass, &user->hash, &user->salt);
-
+
if(!adduser(user)){
if(strcmp(sqlite3_errmsg(db), "column name is not unique")==0)
snprintf(msg, MAXMSG, "Username already exists.");
@@ -263,7 +256,7 @@ newuser()
sqlite3_errmsg(db));
goto err;
}
-
+
printf("Content-Type: text/html\n\n");
snprintf(msg, MAXMSG, "SUCCESS: User was added to database");
#include "t/newuser.tc"
@@ -275,6 +268,36 @@ err:
return;
}
+/* Print UNIX timestamp as written date. */
+void
+printdate(int timestamp)
+{
+ char buf[20];
+ struct tm *t;
+
+ t = localtime((time_t *)&timestamp);
+ strftime(buf, 20, "%Y-%m-%d %H:%M", t);
+
+ printf(buf);
+}
+
+/* Print HTML-escaped string. */
+void
+printhtml(char *s)
+{
+ char *t;
+
+ for(t = s; *t; t++)
+ switch(*t){
+ case '&': printf("&amp;"); break;
+ case '\"': printf("&quot;"); break;
+ case '\'': printf("&apos;"); break;
+ case '<': printf("&lt;"); break;
+ case '>': printf("&gt;"); break;
+ default: printf("%c", *t);
+ }
+}
+
/*
* The `show' functions show an existing attachment/post/user
* or some other type of page.
@@ -283,12 +306,12 @@ void
showatt(id)
{
struct att *att;
-
+
if(!(att = getatt(selectbyint("atts", "oid", id)))){
srverr("Could not retrieve attachment");
return;
}
-
+
printf("Content-Type: %s\n\n", att->mime);
printf("%.*s", att->bytes, att->data);
free(att);
@@ -301,21 +324,21 @@ showfront()
struct post *post;
struct user *user;
sqlite3_stmt *pstmt, *ustmt;
-
+
if(sqlite3_prepare(db,
"SELECT oid, * from posts ORDER BY created DESC",
-1, &pstmt, 0) != SQLITE_OK){
srverr("Could not retrieve posts");
return;
}
-
+
if(sqlite3_prepare(db,
"SELECT oid, * from users ORDER BY created DESC",
-1, &ustmt, 0) != SQLITE_OK){
srverr("Could not retrieve users");
return;
}
-
+
title = site.name;
printf("Content-Type: text/html\n\n");
#include "t/front.tc"
@@ -329,19 +352,19 @@ showpost(int id)
struct post *post;
struct user *user;
sqlite3_stmt *stmt;
-
+
if(!(post = getpost(selectbyint("posts", "oid", id)))){
srverr("Could not retrieve post");
return;
}
-
+
if(!(user = getuser(selectbyint("users", "oid", post->user)))){
srverr("Could not retrieve post author");
return;
}
-
+
stmt = selectbyint("atts", "post", id);
-
+
title = post->subject;
printf("Content-Type: text/html\n\n");
#include "t/post.tc"
@@ -356,26 +379,26 @@ showuser(int id)
sqlite3_stmt *stmt;
struct post *post;
struct user *user;
-
+
if(!(user = getuser(selectbyint("users", "oid", id)))){
srverr("Could not retrieve user");
return;
}
-
+
if(sqlite3_prepare(db,
"SELECT oid, * from posts WHERE user = ? ORDER BY created DESC",
-1, &stmt, 0) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_int(stmt, 1, id) != SQLITE_OK)
goto err;
-
+
snprintf(title, 120, "User: %s", user->full? user->full: user->name);
printf("Content-Type: text/html\n\n");
#include "t/user.tc"
free(user);
return;
-
+
err:
srverr("Could not retrieve posts");
return;
diff --git a/db.c b/db.c
index 4b1b234..29797d3 100644
--- a/db.c
+++ b/db.c
@@ -9,7 +9,7 @@
#include "cforum.h"
#include "crypt/ow-crypt.h"
-static char * strdupn(const unsigned char *);
+static char *strdupn(const unsigned char *);
/*
* The `add' functions insert an att/post/user struct into the database.
@@ -24,30 +24,30 @@ addatt(struct att *att)
" VALUES (?, ?, ?, ?, ?)",
-1, &stmt, 0) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_int(stmt, 1, att->post)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 2, att->name, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 3, att->desc, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 4, att->mime, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_blob(stmt, 5, att->data, att->bytes, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_step(stmt) != SQLITE_DONE)
goto err;
-
+
sqlite3_finalize(stmt);
return 1;
err:
@@ -65,27 +65,58 @@ addpost(struct post *post)
" VALUES (?, ?, ?, ?, ?)",
-1, &stmt, 0) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_int(stmt, 1, post->parent) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_int(stmt, 2, post->user) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_int(stmt, 3, post->created) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 4, post->subject, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 5, post->text, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_step(stmt) != SQLITE_DONE)
goto err;
-
+
+ sqlite3_finalize(stmt);
+ return 1;
+err:
+ sqlite3_finalize(stmt);
+ return 0;
+}
+
+int
+addsession(struct session *session)
+{
+ sqlite3_stmt *stmt;
+
+ if(sqlite3_prepare(db, "INSERT INTO sessions"
+ " (user, string, created)"
+ " VALUES (?, ?, ?)",
+ -1, &stmt, 0) != SQLITE_OK)
+ goto err;
+
+ if(sqlite3_bind_int(stmt, 1, session->user) != SQLITE_OK)
+ goto err;
+
+ if(sqlite3_bind_text(stmt, 2, session->string, -1, SQLITE_STATIC)
+ != SQLITE_OK)
+ goto err;
+
+ if(sqlite3_bind_int(stmt, 3, time(NULL)) != SQLITE_OK)
+ goto err;
+
+ if(sqlite3_step(stmt) != SQLITE_DONE)
+ goto err;
+
sqlite3_finalize(stmt);
return 1;
err:
@@ -103,15 +134,15 @@ adduser(struct user *user)
" VALUES (?, ?, ?, ?, ?)",
-1, &stmt, 0) != SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 1, user->name, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 2, user->full, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_text(stmt, 3, user->hash, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
@@ -119,13 +150,13 @@ adduser(struct user *user)
if(sqlite3_bind_text(stmt, 4, user->salt, -1, SQLITE_STATIC)
!= SQLITE_OK)
goto err;
-
+
if(sqlite3_bind_int(stmt, 5, time(NULL)) != SQLITE_OK)
goto err;
-
+
if(sqlite3_step(stmt) != SQLITE_DONE)
goto err;
-
+
sqlite3_finalize(stmt);
return 1;
err:
@@ -133,6 +164,21 @@ err:
return 0;
}
+int
+deletesession(struct session *session)
+{
+ sqlite3_stmt *stmt;
+
+ if(sqlite3_prepare(db, "DELETE FROM sessions WHERE oid = ?",
+ -1, &stmt, 0) != SQLITE_OK)
+ return 0;
+
+ if(sqlite3_bind_int(stmt, 1, session->id) != SQLITE_OK)
+ return 0;
+
+ return sqlite3_step(stmt) != SQLITE_ROW;
+}
+
/*
* The `get' functions retrieve an att/post/user struct once,
* after which the statement is automatically finalized.
@@ -155,6 +201,15 @@ getpost(sqlite3_stmt *stmt)
return post;
}
+struct session *
+getsession(sqlite3_stmt *stmt)
+{
+ struct session *session;
+ session = nextsession(stmt);
+ sqlite3_finalize(stmt);
+ return session;
+}
+
struct user *
getuser(sqlite3_stmt *stmt)
{
@@ -169,27 +224,47 @@ int
haspass(struct user *user, char *pass)
{
char *newhash;
-
+
newhash = crypt(pass, user->salt);
return strcmp(user->hash, newhash) == 0;
}
+/* Generate string of size-1 random lowercase letters. */
+void
+makerandom(char *buf, int size)
+{
+ int fd, i;
+
+ if((fd = open("/dev/urandom", O_RDONLY)) == -1)
+ err(1, "open");
+
+ if(read(fd, buf, size-1) == -1)
+ err(1, "read");
+
+ close(fd);
+
+ /* Convert to lowercase letters. */
+ for(i = 0; i < size; i++)
+ buf[i] = (buf[i]+128)%25+97;
+ buf[size] = 0;
+}
+
/* Generate new salt and hash for password. */
void
makehash(char *pass, char **hash, char **salt)
{
char data[50];
- int fd;
-
- if(fd = open("/dev/urandom", O_RDONLY) == -1)
+ int fd, n;
+
+ if((fd = open("/dev/urandom", O_RDONLY)) == -1)
err(1, "open");
- if(read(fd, data, sizeof(data)) == -1)
+ if((n = read(fd, data, sizeof(data))) == -1)
err(1, "read");
-
+
if(!(*salt = crypt_gensalt("$2b$", 10, data, sizeof(data))))
err(1, "crypt_gensalt");
-
+
*hash = crypt(pass, *salt);
close(fd);
}
@@ -203,28 +278,28 @@ struct att *
nextatt(sqlite3_stmt *stmt)
{
struct att *att;
-
+
if(!stmt)
return NULL;
-
+
if(sqlite3_step(stmt) != SQLITE_ROW)
return NULL;
-
+
if(!(att = malloc(sizeof(struct att))))
err(1, "malloc");
-
+
att->id = sqlite3_column_int(stmt, 0);
att->post = sqlite3_column_int(stmt, 1);
att->name = strdupn(sqlite3_column_text(stmt, 2));
att->desc = strdupn(sqlite3_column_text(stmt, 3));
att->mime = strdupn(sqlite3_column_text(stmt, 4));
-
+
att->bytes = sqlite3_column_bytes(stmt, 5);
if(!(att->data = malloc(att->bytes)))
err(1, "malloc");
memcpy(att->data, sqlite3_column_blob(stmt, 5),
att->bytes);
-
+
return att;
}
@@ -232,16 +307,16 @@ struct post *
nextpost(sqlite3_stmt *stmt)
{
struct post *post;
-
+
if(!stmt)
return NULL;
-
+
if(sqlite3_step(stmt) != SQLITE_ROW)
return NULL;
-
+
if(!(post = malloc(sizeof(struct post))))
err(1, "malloc");
-
+
post->id = sqlite3_column_int(stmt, 0);
post->parent = sqlite3_column_int(stmt, 1);
post->user = sqlite3_column_int(stmt, 2);
@@ -249,31 +324,53 @@ nextpost(sqlite3_stmt *stmt)
post->edited = sqlite3_column_int(stmt, 4);
post->subject = strdupn(sqlite3_column_text(stmt, 5));
post->text = strdupn(sqlite3_column_text(stmt, 6));
-
+
return post;
}
+struct session *
+nextsession(sqlite3_stmt *stmt)
+{
+ struct session *session;
+
+ if(!stmt)
+ return NULL;
+
+ if(sqlite3_step(stmt) != SQLITE_ROW)
+ return NULL;
+
+ if(!(session = malloc(sizeof(struct session))))
+ err(1, "malloc");
+
+ session->id = sqlite3_column_int(stmt, 0);
+ session->user = sqlite3_column_int(stmt, 1);
+ session->string = strdupn(sqlite3_column_text(stmt, 2));
+ session->created = sqlite3_column_int(stmt, 3);
+
+ return session;
+}
+
struct user *
nextuser(sqlite3_stmt *stmt)
{
struct user *user;
-
+
if(!stmt)
return NULL;
-
+
if(sqlite3_step(stmt) != SQLITE_ROW)
return NULL;
-
+
if(!(user = malloc(sizeof(struct user))))
err(1, "malloc");
-
+
user->id = sqlite3_column_int(stmt, 0);
user->name = strdupn(sqlite3_column_text(stmt, 1));
user->full = strdupn(sqlite3_column_text(stmt, 2));
user->hash = strdupn(sqlite3_column_text(stmt, 3));
user->salt = strdupn(sqlite3_column_text(stmt, 4));
user->created = sqlite3_column_int(stmt, 5);
-
+
return user;
}
@@ -287,15 +384,15 @@ selectbyint(char *table, char *field, int i)
{
char sql[100];
sqlite3_stmt *stmt;
-
+
snprintf(sql, 100, "SELECT oid, * FROM %s WHERE %s = ?", table, field);
-
+
if(sqlite3_prepare(db, sql, -1, &stmt, 0) != SQLITE_OK)
return NULL;
-
+
if(sqlite3_bind_int(stmt, 1, i) != SQLITE_OK)
return NULL;
-
+
return stmt;
}
diff --git a/err.c b/err.c
index 07a50a2..35a5666 100644
--- a/err.c
+++ b/err.c
@@ -5,7 +5,7 @@ void
srverr(char *err)
{
char title[] = "500 Internal Server Error";
-
+
printf("Status: %s\n", title);
printf("Content-Type: text/html\n\n");
#include "t/err.tc"
diff --git a/mktpl/README b/mktpl/README
index 85a2fe3..cb50a6e 100644
--- a/mktpl/README
+++ b/mktpl/README
@@ -20,7 +20,7 @@ is compiled to the following C code:
/* This C code will be executed. */
for(i = 0; i<10; i++) printf("Hello World!\n");
strcpy(s, "This");
-
+
printf("\n");
printf("%s", s );
printf(" will be evaluated and printed as a string.");
diff --git a/mktpl/mktpl.lex b/mktpl/mktpl.lex
index e184c0e..296cc0b 100644
--- a/mktpl/mktpl.lex
+++ b/mktpl/mktpl.lex
@@ -5,12 +5,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-
+
void apnd();
void eval();
void text();
void prnt();
-
+
char *buf;
int len;
int sz;
@@ -38,7 +38,7 @@ main(int argc, char *argv[])
sz = 512;
if(!(buf = malloc(sz)))
err(1, "malloc");
-
+
yylex();
return 0;
}
@@ -51,7 +51,7 @@ apnd()
if(!(buf = realloc(buf, sz)))
err(1, "realloc");
}
-
+
buf[len++] = *yytext;
}
@@ -66,9 +66,9 @@ void
text()
{
int i, ofs;
-
+
if(!len) return;
-
+
printf("printf(\"");
for(i = ofs = 0; i < len; i++){
/*
diff --git a/query.c b/query.c
index 8f54ead..1f3fe88 100644
--- a/query.c
+++ b/query.c
@@ -4,6 +4,33 @@
#include <string.h>
#include "cforum.h"
+/* Return an allocated string containing the next cookie ("name=value"). */
+char *
+nextcookie(int max)
+{
+ char *buf;
+ int n;
+ static char *cs = NULL;
+
+ if(!(buf = malloc(max)))
+ err(1, "malloc");
+
+ if(!cs)
+ cs = getenv("HTTP_COOKIE");
+
+ if(!cs || !*cs)
+ return NULL;
+
+ if(*cs == ' ')
+ cs++;
+
+ n = strcspn(cs, ";");
+ snprintf(buf, max, "%.*s", n, cs);
+ cs += n + (n != strlen(cs));
+
+ return buf;
+}
+
/*
* Return an allocated string containing the next parameter ("key=value").
* The method argument decides which data (GET or POST) to read from.
@@ -21,17 +48,17 @@ nextparam(enum method method, int *len, int max)
char *buf;
int i, sz;
static int j = 0;
-
+
/* Return NULL if at end of parameter string. */
if(method == GET && !query.string[j-1])
return NULL;
-
+
#define STEP 256
-
+
sz = STEP;
if(!(buf = malloc(1+sz))) /* Leave space for -1th character. */
err(1, "malloc");
-
+
/*
* buf's -1th character is set to 1 if the string
* has been truncated (i.e. if input exceeded max).
@@ -51,14 +78,14 @@ first:
/* Return NULL if at end of parameter string. */
if(!READ(buf))
return NULL;
-
+
/* Skip empty parameters. */
if(*buf == '&')
goto first;
-
+
i = 0;
goto rest;
-
+
for(; READ(buf+i); i++){
rest:
if(buf[i] == '&'){
@@ -80,7 +107,7 @@ rest:
}
if(len) *len = i;
else buf[i] = 0;
-
+
return buf;
}
@@ -125,10 +152,10 @@ int
urldecode(char *s, int len)
{
unsigned int code, i, j;
-
+
if(len < 0)
len = strlen(s);
-
+
for(i = j = 0; i < len; i++, j++){
if(s[i] == '+')
s[j] = ' ';
@@ -140,6 +167,6 @@ urldecode(char *s, int len)
}else
s[j] = s[i];
}
-
+
return j;
} \ No newline at end of file
diff --git a/t/front.t b/t/front.t
index 4f94a9b..f8dc9e3 100644
--- a/t/front.t
+++ b/t/front.t
@@ -1,8 +1,15 @@
<% #include "head.tc" %>
<h1><%= site.name %></h1>
+<% if(curuser) { %>
+Logged in as <%= curuser->name %>.
+<% } %>
<ul>
+ <% if(curuser){ %>
+ <li><a href="?delete=session">Log out</a></li>
+ <% }else{ %>
<li><a href="?new=user">Sign up</a></li>
<li><a href="?new=session">Log in</a></li>
+ <% } %>
</ul>
<h3>Latest posts</h3>
<table border="1">