Add CSRF tokens
This commit is contained in:
@@ -6,7 +6,7 @@ else
|
|||||||
LFLAGS =
|
LFLAGS =
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CFLAGS = -Wall -Wextra -O0 -g3 -I3p
|
CFLAGS = -Wall -Wextra -O0 -g3 -I3p -fsanitize=address,undefined
|
||||||
|
|
||||||
HFILES = $(shell find src 3p -name "*.h")
|
HFILES = $(shell find src 3p -name "*.h")
|
||||||
CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c"))
|
CFILES = $(filter-out 3p/sqlite3.c, $(shell find src 3p -name "*.c"))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
bcrypt passwords
|
|
||||||
session management using proper random tokens
|
|
||||||
CSRF protection
|
|
||||||
input validation to avoid XSS and injection
|
input validation to avoid XSS and injection
|
||||||
only allow signup, login ecc over HTTPS
|
only allow signup, login ecc over HTTPS
|
||||||
|
session expiration
|
||||||
|
mark cookies as secure
|
||||||
@@ -59,6 +59,8 @@ let style =
|
|||||||
let main =
|
let main =
|
||||||
<main>
|
<main>
|
||||||
<form action="/api/post" method="POST">
|
<form action="/api/post" method="POST">
|
||||||
|
|
||||||
|
<input type="hidden" name="csrf" value=\'"'\$csrf\'"' />
|
||||||
<input type="text" id="title" name="title" placeholder="Title" required />
|
<input type="text" id="title" name="title" placeholder="Title" required />
|
||||||
|
|
||||||
<div class="checkbox-row">
|
<div class="checkbox-row">
|
||||||
|
|||||||
+52
-160
@@ -9,6 +9,7 @@
|
|||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "sqlite3utils.h"
|
#include "sqlite3utils.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
#include "session.h"
|
||||||
|
|
||||||
#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1})
|
#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1})
|
||||||
|
|
||||||
@@ -313,134 +314,8 @@ int http_getparami(HTTP_String body, HTTP_String str, WL_Arena *arena)
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SESSION_LIMIT 1024
|
|
||||||
#define USERNAME_LIMIT 64
|
#define USERNAME_LIMIT 64
|
||||||
|
|
||||||
#define RAW_SESSION_TOKEN_SIZE 32
|
|
||||||
#define RAW_CSRF_TOKEN_SIZE 32
|
|
||||||
|
|
||||||
#define SESSION_TOKEN_SIZE (2 * RAW_SESSION_TOKEN_SIZE)
|
|
||||||
#define CSRF_TOKEN_SIZE (2 * RAW_CSRF_TOKEN_SIZE)
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int user_id;
|
|
||||||
char raw_sess_token[RAW_SESSION_TOKEN_SIZE];
|
|
||||||
char raw_csrf_token[RAW_CSRF_TOKEN_SIZE];
|
|
||||||
} Session;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Session sessions[SESSION_LIMIT];
|
|
||||||
int count;
|
|
||||||
} SessionSet;
|
|
||||||
|
|
||||||
#include "random.h"
|
|
||||||
|
|
||||||
void session_set_init(SessionSet *set)
|
|
||||||
{
|
|
||||||
set->count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char data[SESSION_TOKEN_SIZE];
|
|
||||||
} SessionToken;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char data[CSRF_TOKEN_SIZE];
|
|
||||||
} CSRFToken;
|
|
||||||
|
|
||||||
static void unpack_token(char *src, int srclen, char *dst, int dstlen)
|
|
||||||
{
|
|
||||||
assert(srclen == 2 * dstlen);
|
|
||||||
|
|
||||||
for (int i = 0; i < srclen; i++) {
|
|
||||||
static const char table[] = "0123456789abcdef";
|
|
||||||
int low = (src[i] & 0x0F) >> 0;
|
|
||||||
int high = (src[i] & 0xF0) >> 4;
|
|
||||||
dst[(i << 1) | 0] = table[high];
|
|
||||||
dst[(i << 1) | 1] = table[low];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pack_token(char *src, int srclen, char *dst, int dstlen)
|
|
||||||
{
|
|
||||||
if (srclen & 1)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
assert(2 * srclen == dstlen);
|
|
||||||
|
|
||||||
for (int i = 0; i < srclen; i += 2) {
|
|
||||||
int high = src[i+0];
|
|
||||||
int low = src[i+1];
|
|
||||||
if (!is_hex_digit(high) || !is_hex_digit(low))
|
|
||||||
return -1;
|
|
||||||
dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int session_set_add(SessionSet *set, int user_id, SessionToken *sess_token, CSRFToken *csrf_token)
|
|
||||||
{
|
|
||||||
if (set->count == SESSION_LIMIT)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
Session *session = &set->sessions[set->count];
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
ret = generate_random_bytes(session->raw_sess_token, (int) sizeof(session->raw_sess_token));
|
|
||||||
if (ret) return -1;
|
|
||||||
|
|
||||||
ret = generate_random_bytes(session->raw_csrf_token, (int) sizeof(session->raw_csrf_token));
|
|
||||||
if (ret) return -1;
|
|
||||||
|
|
||||||
session->user_id = user_id;
|
|
||||||
|
|
||||||
unpack_token(
|
|
||||||
session->raw_csrf_token, (int) sizeof(session->raw_csrf_token),
|
|
||||||
csrf_token->data, (int) sizeof(csrf_token->data)
|
|
||||||
);
|
|
||||||
|
|
||||||
unpack_token(
|
|
||||||
session->raw_sess_token, (int) sizeof(session->raw_sess_token),
|
|
||||||
sess_token->data, (int) sizeof(sess_token->data)
|
|
||||||
);
|
|
||||||
|
|
||||||
set->count++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void session_set_remove(SessionSet *set, SessionToken sess_token)
|
|
||||||
{
|
|
||||||
char raw_sess_token[RAW_SESSION_TOKEN_SIZE];
|
|
||||||
pack_token(
|
|
||||||
sess_token.data, (int) sizeof(sess_token.data),
|
|
||||||
raw_sess_token, (int) sizeof(raw_sess_token)
|
|
||||||
);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < set->count && memcmp(set->sessions[i].raw_sess_token, raw_sess_token, RAW_SESSION_TOKEN_SIZE))
|
|
||||||
i++;
|
|
||||||
if (i == set->count)
|
|
||||||
return;
|
|
||||||
set->sessions[i] = set->sessions[--set->count];
|
|
||||||
}
|
|
||||||
|
|
||||||
int session_set_find(SessionSet *set, SessionToken sess_token)
|
|
||||||
{
|
|
||||||
char raw_sess_token[RAW_SESSION_TOKEN_SIZE];
|
|
||||||
pack_token(
|
|
||||||
sess_token.data, (int) sizeof(sess_token.data),
|
|
||||||
raw_sess_token, (int) sizeof(raw_sess_token)
|
|
||||||
);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < set->count && memcmp(set->sessions[i].raw_sess_token, raw_sess_token, RAW_SESSION_TOKEN_SIZE))
|
|
||||||
i++;
|
|
||||||
if (i == set->count)
|
|
||||||
return -1;
|
|
||||||
return set->sessions[i].user_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool valid_name(HTTP_String str)
|
bool valid_name(HTTP_String str)
|
||||||
{
|
{
|
||||||
(void) str; // TODO
|
(void) str; // TODO
|
||||||
@@ -529,8 +404,10 @@ int main(void)
|
|||||||
int pool_cap = 1<<20;
|
int pool_cap = 1<<20;
|
||||||
char *pool = malloc(pool_cap);
|
char *pool = malloc(pool_cap);
|
||||||
|
|
||||||
SessionSet sessions;
|
SessionStorage *session_storage = session_storage_init(1024);
|
||||||
session_set_init(&sessions);
|
if (session_storage == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
@@ -542,22 +419,17 @@ int main(void)
|
|||||||
|
|
||||||
WL_Arena arena = { pool, pool_cap, 0 };
|
WL_Arena arena = { pool, pool_cap, 0 };
|
||||||
|
|
||||||
//printf("%.*s\n", req->raw.len, req->raw.ptr); // TODO
|
printf("%.*s\n", req->raw.len, req->raw.ptr); // TODO
|
||||||
|
|
||||||
// If logged in, these are set to non-negative values
|
int user_id;
|
||||||
SessionToken sess_token;
|
HTTP_String sess;
|
||||||
int user_id = -1;
|
HTTP_String csrf;
|
||||||
|
|
||||||
{
|
sess = http_getcookie(req, HTTP_STR("sess_token"));
|
||||||
HTTP_String str = http_getcookie(req, HTTP_STR("sess_id"));
|
if (find_session(session_storage, sess, &csrf, &user_id) < 0) {
|
||||||
if (str.len == SESSION_TOKEN_SIZE) {
|
user_id = -1;
|
||||||
char tmp[RAW_SESSION_TOKEN_SIZE];
|
sess = (HTTP_String) { NULL, 0 };
|
||||||
int ret = pack_token(str.ptr, str.len, tmp, (int) sizeof(tmp));
|
csrf = (HTTP_String) { NULL, 0 };
|
||||||
if (ret) {
|
|
||||||
memcpy(sess_token.data, str.ptr, str.len);
|
|
||||||
user_id = session_set_find(&sessions, sess_token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTP_String path = req->url.path;
|
HTTP_String path = req->url.path;
|
||||||
@@ -617,9 +489,9 @@ int main(void)
|
|||||||
}
|
}
|
||||||
int user_id = ret;
|
int user_id = ret;
|
||||||
|
|
||||||
CSRFToken csrf_token;
|
HTTP_String sess;
|
||||||
SessionToken sess_token;
|
HTTP_String csrf;
|
||||||
if (session_set_add(&sessions, user_id, &sess_token, &csrf_token) < 0) {
|
if (create_session(session_storage, user_id, &sess, &csrf) < 0) {
|
||||||
http_response_builder_status(builder, 200);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_body(builder, HTML_STR((
|
http_response_builder_body(builder, HTML_STR((
|
||||||
<div class="error">
|
<div class="error">
|
||||||
@@ -630,8 +502,9 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: set cookie as secure
|
||||||
char cookie[1<<9];
|
char cookie[1<<9];
|
||||||
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%.*s; Path=/; HttpOnly", (int) sizeof(sess_token.data)-1, sess_token.data);
|
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_token=%.*s; Path=/; HttpOnly", sess.len, sess.ptr);
|
||||||
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
||||||
http_response_builder_status(builder, 200);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_body(builder, HTML_STR((
|
http_response_builder_body(builder, HTML_STR((
|
||||||
@@ -719,9 +592,9 @@ int main(void)
|
|||||||
}
|
}
|
||||||
user_id = ret;
|
user_id = ret;
|
||||||
|
|
||||||
CSRFToken csrf_token;
|
HTTP_String sess;
|
||||||
SessionToken sess_token;
|
HTTP_String csrf;
|
||||||
if (session_set_add(&sessions, user_id, &sess_token, &csrf_token) < 0) {
|
if (create_session(session_storage, user_id, &sess, &csrf) < 0) {
|
||||||
http_response_builder_status(builder, 200);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_body(builder, HTML_STR((
|
http_response_builder_body(builder, HTML_STR((
|
||||||
<div class="error">
|
<div class="error">
|
||||||
@@ -732,8 +605,9 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: set cookie as secure
|
||||||
char cookie[1<<9];
|
char cookie[1<<9];
|
||||||
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%.*s; Path=/; HttpOnly", (int) sizeof(sess_token.data)-1, sess_token.data);
|
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_token=%.*s; Path=/; HttpOnly", sess.len, sess.ptr);
|
||||||
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
|
||||||
http_response_builder_status(builder, 200);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_body(builder, HTML_STR((
|
http_response_builder_body(builder, HTML_STR((
|
||||||
@@ -758,10 +632,11 @@ int main(void)
|
|||||||
} else if (http_streq(path, HTTP_STR("/api/logout"))) {
|
} else if (http_streq(path, HTTP_STR("/api/logout"))) {
|
||||||
|
|
||||||
if (user_id != -1)
|
if (user_id != -1)
|
||||||
session_set_remove(&sessions, sess_token);
|
delete_session(session_storage, sess);
|
||||||
|
|
||||||
|
// TODO: set cookie as secure
|
||||||
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
|
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
|
||||||
http_response_builder_header(builder, HTTP_STR("Set-Cookie: sess_id=; Path=/; HttpOnly"));
|
http_response_builder_header(builder, HTTP_STR("Set-Cookie: sess_token=; Path=/; HttpOnly"));
|
||||||
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
http_response_builder_header(builder, HTTP_STR("Location: /index"));
|
||||||
http_response_builder_done(builder);
|
http_response_builder_done(builder);
|
||||||
|
|
||||||
@@ -783,11 +658,19 @@ int main(void)
|
|||||||
HTTP_String title = http_getparam(req->body, HTTP_STR("title"), &arena);
|
HTTP_String title = http_getparam(req->body, HTTP_STR("title"), &arena);
|
||||||
HTTP_String link = http_getparam(req->body, HTTP_STR("link"), &arena);
|
HTTP_String link = http_getparam(req->body, HTTP_STR("link"), &arena);
|
||||||
HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena);
|
HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena);
|
||||||
|
HTTP_String csrf2 = http_getparam(req->body, HTTP_STR("csrf"), &arena);
|
||||||
|
|
||||||
title = http_trim(title);
|
title = http_trim(title);
|
||||||
link = http_trim(link);
|
link = http_trim(link);
|
||||||
content = http_trim(content);
|
content = http_trim(content);
|
||||||
|
|
||||||
|
if (!http_streq(csrf, csrf2)) {
|
||||||
|
http_response_builder_status(builder, 400);
|
||||||
|
http_response_builder_body(builder, HTTP_STR("Invalid request"));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!valid_post_title(title)) {
|
if (!valid_post_title(title)) {
|
||||||
http_response_builder_status(builder, 200);
|
http_response_builder_status(builder, 200);
|
||||||
http_response_builder_body(builder, HTML_STR((
|
http_response_builder_body(builder, HTML_STR((
|
||||||
@@ -923,6 +806,14 @@ int main(void)
|
|||||||
int parent_post = http_getparami(req->body, HTTP_STR("parent_post"), &arena);
|
int parent_post = http_getparami(req->body, HTTP_STR("parent_post"), &arena);
|
||||||
int parent_comment = http_getparami(req->body, HTTP_STR("parent_comment"), &arena);
|
int parent_comment = http_getparami(req->body, HTTP_STR("parent_comment"), &arena);
|
||||||
HTTP_String content = http_getparam (req->body, HTTP_STR("content"), &arena);
|
HTTP_String content = http_getparam (req->body, HTTP_STR("content"), &arena);
|
||||||
|
HTTP_String csrf2 = http_getparam(req->body, HTTP_STR("csrf"), &arena);
|
||||||
|
|
||||||
|
if (!http_streq(csrf, csrf2)) {
|
||||||
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_body(builder, HTTP_STR("Invalid request"));
|
||||||
|
http_response_builder_done(builder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
content = http_trim(content);
|
content = http_trim(content);
|
||||||
if (!valid_comment_content(content)) {
|
if (!valid_comment_content(content)) {
|
||||||
@@ -997,7 +888,7 @@ int main(void)
|
|||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/index"))) {
|
} else if (http_streq(path, HTTP_STR("/index"))) {
|
||||||
|
|
||||||
template_eval(builder, 200, WL_STR("pages/index.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/index.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/write"))) {
|
} else if (http_streq(path, HTTP_STR("/write"))) {
|
||||||
|
|
||||||
@@ -1009,7 +900,7 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
template_eval(builder, 200, WL_STR("pages/write.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/write.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/login"))) {
|
} else if (http_streq(path, HTTP_STR("/login"))) {
|
||||||
|
|
||||||
@@ -1021,7 +912,7 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
template_eval(builder, 200, WL_STR("pages/login.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/login.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/signup"))) {
|
} else if (http_streq(path, HTTP_STR("/signup"))) {
|
||||||
|
|
||||||
@@ -1033,13 +924,13 @@ int main(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
template_eval(builder, 200, WL_STR("pages/signup.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 200, WL_STR("pages/signup.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
|
|
||||||
} else if (http_streq(path, HTTP_STR("/post"))) {
|
} else if (http_streq(path, HTTP_STR("/post"))) {
|
||||||
|
|
||||||
int post_id = http_getparami(req->url.query, HTTP_STR("id"), &arena);
|
int post_id = http_getparami(req->url.query, HTTP_STR("id"), &arena);
|
||||||
if (post_id < 0) {
|
if (post_id < 0) {
|
||||||
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,18 +965,19 @@ int main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
template_eval(builder, 200, WL_STR("pages/post.wl"), tpcache, &arena, dbcache, user_id, post_id);
|
template_eval(builder, 200, WL_STR("pages/post.wl"), tpcache, &arena, dbcache, csrf, user_id, post_id);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1);
|
template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, csrf, user_id, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session_storage_free(session_storage);
|
||||||
sqlite_cache_free(dbcache);
|
sqlite_cache_free(dbcache);
|
||||||
template_cache_free(tpcache);
|
template_cache_free(tpcache);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
|
|||||||
+198
@@ -0,0 +1,198 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "random.h"
|
||||||
|
#include "session.h"
|
||||||
|
|
||||||
|
#define CSRF_RAW_TOKEN_SIZE 32
|
||||||
|
#define SESS_RAW_TOKEN_SIZE 32
|
||||||
|
|
||||||
|
#define CSRF_TOKEN_SIZE (2 * CSRF_RAW_TOKEN_SIZE)
|
||||||
|
#define SESS_TOKEN_SIZE (2 * SESS_RAW_TOKEN_SIZE)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int user;
|
||||||
|
char csrf[CSRF_TOKEN_SIZE];
|
||||||
|
char sess[SESS_TOKEN_SIZE];
|
||||||
|
} Session;
|
||||||
|
|
||||||
|
struct SessionStorage {
|
||||||
|
int count;
|
||||||
|
int capacity;
|
||||||
|
Session items[];
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool is_hex_digit(char c)
|
||||||
|
{
|
||||||
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hex_digit_to_int(char c)
|
||||||
|
{
|
||||||
|
if (c >= 'A' && c <= 'F')
|
||||||
|
return c - 'A' + 10;
|
||||||
|
if (c >= 'a' && c <= 'f')
|
||||||
|
return c - 'a' + 10;
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unpack_token(char *src, int srclen, char *dst, int dstlen)
|
||||||
|
{
|
||||||
|
assert(2 * srclen == dstlen);
|
||||||
|
|
||||||
|
for (int i = 0; i < srclen; i++) {
|
||||||
|
static const char table[] = "0123456789abcdef";
|
||||||
|
int low = (src[i] & 0x0F) >> 0;
|
||||||
|
int high = (src[i] & 0xF0) >> 4;
|
||||||
|
dst[(i << 1) | 0] = table[high];
|
||||||
|
dst[(i << 1) | 1] = table[low];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pack_token(char *src, int srclen, char *dst, int dstlen)
|
||||||
|
{
|
||||||
|
if (srclen & 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
assert(srclen == 2 * dstlen);
|
||||||
|
|
||||||
|
for (int i = 0; i < srclen; i += 2) {
|
||||||
|
int high = src[i+0];
|
||||||
|
int low = src[i+1];
|
||||||
|
if (!is_hex_digit(high) || !is_hex_digit(low))
|
||||||
|
return -1;
|
||||||
|
dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionStorage *session_storage_init(int max_sessions)
|
||||||
|
{
|
||||||
|
int capacity = 2 * max_sessions;
|
||||||
|
SessionStorage *storage = malloc(sizeof(SessionStorage) + capacity * sizeof(Session));
|
||||||
|
if (storage == NULL)
|
||||||
|
return NULL;
|
||||||
|
storage->count = 0;
|
||||||
|
storage->capacity = capacity;
|
||||||
|
for (int i = 0; i < capacity; i++)
|
||||||
|
storage->items[i].user = -1;
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_storage_free(SessionStorage *storage)
|
||||||
|
{
|
||||||
|
free(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <stdio.h> // TODO
|
||||||
|
|
||||||
|
static Session *lookup_session_slot(SessionStorage *storage, HTTP_String sess, bool find_unused)
|
||||||
|
{
|
||||||
|
if (find_unused && 2 * storage->count + 2 > storage->capacity)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (sess.len != SESS_TOKEN_SIZE)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
uint64_t key;
|
||||||
|
if (sess.len < (int) (2 * sizeof(key)))
|
||||||
|
return NULL;
|
||||||
|
for (int i = 0; i < (int) sizeof(key); i++) {
|
||||||
|
|
||||||
|
int high = sess.ptr[(i << 1) | 0];
|
||||||
|
int low = sess.ptr[(i << 1) | 1];
|
||||||
|
|
||||||
|
if (!is_hex_digit(sess.ptr[i+0]) ||
|
||||||
|
!is_hex_digit(sess.ptr[i+1]))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
key <<= 4;
|
||||||
|
key |= hex_digit_to_int(high);
|
||||||
|
|
||||||
|
key <<= 4;
|
||||||
|
key |= hex_digit_to_int(low);
|
||||||
|
}
|
||||||
|
int i = key % storage->capacity;
|
||||||
|
|
||||||
|
for (int j = 0; j < storage->capacity; j++) {
|
||||||
|
|
||||||
|
if (find_unused) {
|
||||||
|
|
||||||
|
if (storage->items[i].user < 0)
|
||||||
|
return &storage->items[i]; // Unused slot
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (storage->items[i].user == -1)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (storage->items[i].user != -2)
|
||||||
|
if (!memcmp(storage->items[i].sess, sess.ptr, SESS_TOKEN_SIZE))
|
||||||
|
return &storage->items[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i == storage->capacity)
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
char raw_sess[SESS_RAW_TOKEN_SIZE];
|
||||||
|
char raw_csrf[CSRF_RAW_TOKEN_SIZE];
|
||||||
|
|
||||||
|
ret = generate_random_bytes(raw_sess, SESS_RAW_TOKEN_SIZE);
|
||||||
|
if (ret) return -1;
|
||||||
|
|
||||||
|
ret = generate_random_bytes(raw_csrf, CSRF_RAW_TOKEN_SIZE);
|
||||||
|
if (ret) return -1;
|
||||||
|
|
||||||
|
char sess[SESS_TOKEN_SIZE];
|
||||||
|
char csrf[CSRF_TOKEN_SIZE];
|
||||||
|
unpack_token(raw_sess, SESS_RAW_TOKEN_SIZE, sess, SESS_TOKEN_SIZE);
|
||||||
|
unpack_token(raw_csrf, CSRF_RAW_TOKEN_SIZE, csrf, CSRF_TOKEN_SIZE);
|
||||||
|
|
||||||
|
Session *found = lookup_session_slot(storage, (HTTP_String) { sess, SESS_TOKEN_SIZE }, true);
|
||||||
|
if (found == NULL) return -1;
|
||||||
|
|
||||||
|
found->user = user;
|
||||||
|
memcpy(found->sess, sess, SESS_TOKEN_SIZE);
|
||||||
|
memcpy(found->csrf, csrf, CSRF_TOKEN_SIZE);
|
||||||
|
|
||||||
|
*psess = (HTTP_String) { found->sess, SESS_TOKEN_SIZE };
|
||||||
|
*pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE };
|
||||||
|
|
||||||
|
storage->count++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delete_session(SessionStorage *storage, HTTP_String sess)
|
||||||
|
{
|
||||||
|
char raw_sess[SESS_RAW_TOKEN_SIZE];
|
||||||
|
if (sess.len != SESS_TOKEN_SIZE || pack_token(sess.ptr, sess.len, raw_sess, (int) sizeof(raw_sess)) < 0)
|
||||||
|
return -1;
|
||||||
|
Session *found = lookup_session_slot(storage, sess, false);
|
||||||
|
if (found == NULL)
|
||||||
|
return false;
|
||||||
|
assert(found->user >= 0);
|
||||||
|
found->user = -2;
|
||||||
|
storage->count--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser)
|
||||||
|
{
|
||||||
|
Session *found = lookup_session_slot(storage, sess, false);
|
||||||
|
if (found == NULL)
|
||||||
|
return -1;
|
||||||
|
assert(found->user >= 0);
|
||||||
|
*pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE };
|
||||||
|
*puser = found->user;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef SESSION_INCLUDED
|
||||||
|
#define SESSION_INCLUDED
|
||||||
|
|
||||||
|
#include <chttp.h> // Only for HTTP_String
|
||||||
|
|
||||||
|
typedef struct SessionStorage SessionStorage;
|
||||||
|
|
||||||
|
SessionStorage *session_storage_init(int max_sessions);
|
||||||
|
void session_storage_free(SessionStorage *storage);
|
||||||
|
|
||||||
|
int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf);
|
||||||
|
int delete_session(SessionStorage *storage, HTTP_String sess);
|
||||||
|
int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser);
|
||||||
|
|
||||||
|
#endif // SESSION_INCLUDED
|
||||||
+10
-3
@@ -293,7 +293,7 @@ static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, int user_id, int post_id)
|
static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
|
||||||
{
|
{
|
||||||
(void) dbcache;
|
(void) dbcache;
|
||||||
|
|
||||||
@@ -310,6 +310,13 @@ static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, in
|
|||||||
wl_push_none(rt);
|
wl_push_none(rt);
|
||||||
else
|
else
|
||||||
wl_push_s64(rt, post_id);
|
wl_push_s64(rt, post_id);
|
||||||
|
|
||||||
|
} else if (wl_streq(name, "csrf", -1)) {
|
||||||
|
|
||||||
|
if (csrf.len == 0)
|
||||||
|
wl_push_none(rt);
|
||||||
|
else
|
||||||
|
wl_push_str(rt, (WL_String) { csrf.ptr, csrf.len });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +360,7 @@ static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena
|
|||||||
|
|
||||||
void template_eval(HTTP_ResponseBuilder builder, int status,
|
void template_eval(HTTP_ResponseBuilder builder, int status,
|
||||||
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
||||||
SQLiteCache *dbcache, int user_id, int post_id)
|
SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id)
|
||||||
{
|
{
|
||||||
http_response_builder_status(builder, status);
|
http_response_builder_status(builder, status);
|
||||||
|
|
||||||
@@ -394,7 +401,7 @@ void template_eval(HTTP_ResponseBuilder builder, int status,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case WL_EVAL_SYSVAR:
|
case WL_EVAL_SYSVAR:
|
||||||
push_sysvar(rt, result.str, dbcache, user_id, post_id);
|
push_sysvar(rt, result.str, dbcache, csrf, user_id, post_id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WL_EVAL_SYSCALL:
|
case WL_EVAL_SYSCALL:
|
||||||
|
|||||||
+1
-1
@@ -11,6 +11,6 @@ void template_cache_free(TemplateCache *cache);
|
|||||||
|
|
||||||
void template_eval(HTTP_ResponseBuilder builder, int status,
|
void template_eval(HTTP_ResponseBuilder builder, int status,
|
||||||
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
WL_String path, TemplateCache *cache, WL_Arena *arena,
|
||||||
SQLiteCache *dbcache, int user_id, int post_id);
|
SQLiteCache *dbcache, HTTP_String csrf, int user_id, int post_id);
|
||||||
|
|
||||||
#endif // TEMPLATE_INCLUDED
|
#endif // TEMPLATE_INCLUDED
|
||||||
Reference in New Issue
Block a user