summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Bühler <stbuehler@web.de>2016-02-21 14:45:52 +0100
committerStefan Bühler <buehler@cert.uni-stuttgart.de>2016-09-23 09:11:48 +0200
commit6d0a3ec6a755c788b5b4c90caa532dbe3490032a (patch)
treee81809cf0c339a4fd0de663c9dccd0f0f6a7aaf3
parented3065cfb2d775368c87869be078bcc48c9486e8 (diff)
downloadlighttpd1.4-personal/stbuehler/mod-csrf-old.tar.gz
lighttpd1.4-personal/stbuehler/mod-csrf-old.zip
[mod_csrf] module to aid against csrf attackspersonal/stbuehler/mod-csrf-old
-rw-r--r--configure.ac6
-rw-r--r--doc/config/conf.d/Makefile.am1
-rw-r--r--doc/config/conf.d/csrf.conf101
-rw-r--r--src/CMakeLists.txt5
-rw-r--r--src/Makefile.am5
-rw-r--r--src/SConscript1
-rw-r--r--src/mod_csrf.c537
7 files changed, 656 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index cee52208..732897b3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1017,6 +1017,12 @@ else
disable_feature="$disable_feature $features"
fi
+plugin="mod_csrf"
+if test "x$SSL_LIB" \!= x; then
+ do_build="$do_build $plugin"
+else
+ no_build="$no_build $plugin"
+fi
dnl output
diff --git a/doc/config/conf.d/Makefile.am b/doc/config/conf.d/Makefile.am
index 165c17e2..06e6080b 100644
--- a/doc/config/conf.d/Makefile.am
+++ b/doc/config/conf.d/Makefile.am
@@ -3,6 +3,7 @@ EXTRA_DIST=access_log.conf \
cgi.conf \
cml.conf \
compress.conf \
+ csrf.conf \
debug.conf \
dirlisting.conf \
evhost.conf \
diff --git a/doc/config/conf.d/csrf.conf b/doc/config/conf.d/csrf.conf
new file mode 100644
index 00000000..bd11ddf5
--- /dev/null
+++ b/doc/config/conf.d/csrf.conf
@@ -0,0 +1,101 @@
+#######################################################################
+##
+## CSRF Protection Module
+## ----------------
+##
+## Make sure to load "mod_auth" before (or whatever is supposed to set
+## REMOTE_USER).
+##
+server.modules += ( "mod_csrf" )
+
+## Activate CSRF module:
+##
+## If module is activated and (REMOTE_USER not empty or
+## csrf.require-user is disabled) the module makes sure the client
+## receives a token unless it has a valid token which is less than
+## csrf.ttl/4 seconds old
+##
+## Default:
+# csrf.activate = "disable"
+
+## Use conditions to activate protection for certain URLs:
+# $HTTP["url"] =~ "^/(someurl|cgi-bin)/(.+)" {
+# csrf.activate = "enable"
+# }
+
+## CSRF-protect all requests
+##
+## As soon as CSRF is activated all requests are by default protected
+## (event GET requests), i.e. require the client to send a valid CSRF
+## token. You can disable protection to just make sure the client
+## receives a valid token for future requests.
+##
+## Default:
+# csrf.protect = "enable"
+
+## Don't require CSRF for GET requests (but still send tokens in
+## response)
+# csrf.activate = "enable"
+# $HTTP["request-method"] == "GET" {
+# csrf.protect = "disable"
+# }
+
+## Require a logged in user
+##
+## To prevent mistakes in the config by default a REMOTE_USER is
+## required. If your users are authenticated in another way (say client
+## ip address) and you don't have REMOTE_USER you still can use this
+## module to prevent CSRF from external sites, but you need to disable
+## this option.
+##
+## Default:
+# csrf.require-user = "enable"
+
+## Activate debug logging
+##
+## Default:
+# csrf.debug = "disable"
+
+## Hash function to use for HMAC
+##
+## Supports whatever your openssl library recognizes
+##
+## Default:
+# csrf.hash = "sha256"
+
+## HTTP Header name for CSRF tokens
+##
+## Header name for both HTTP requests and HTTP responses.
+##
+## A client application needs to read this header from responses and
+## copy it into new requests to gain access to protected resources.
+##
+## Default:
+# csrf.header = "X-Csrf-Token"
+
+## Secret key for HMAC to "sign" token data with
+##
+## Only set this if you need tokens to stay valid across a load-balanced
+## setup. If set needs to be at least 20 characters long. Use some
+## secure "password" generator if you need this (e.g. "pwgen -s 32 1")
+##
+## Default: create a random 20-byte secret on each restart
+# csrf.secret = "..."
+
+## Default Time-To-Live for a token
+##
+## How long (in seconds) a token is valid; after csrf.ttl/4 seconds the
+## module will send the client a new token.
+##
+## Your client applications still need to be able to handle token
+## timeouts (i.e. retry requests with the new token they received).
+##
+## A token will also be valid csrf.ttl seconds *before* its timestamp
+## (to avoid problems with time sync between multiple nodes in a
+## cluster)
+##
+## Default: (10 minutes)
+# csrf.ttl = 600
+
+##
+#######################################################################
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 95e86658..eb019ff4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -563,6 +563,7 @@ if(NOT WIN32)
add_and_install_library(mod_cgi mod_cgi.c)
endif()
add_and_install_library(mod_cml "mod_cml.c;mod_cml_lua.c;mod_cml_funcs.c")
+add_and_install_library(mod_csrf mod_csrf.c)
add_and_install_library(mod_compress mod_compress.c)
add_and_install_library(mod_deflate mod_deflate.c)
add_and_install_library(mod_dirlisting mod_dirlisting.c)
@@ -683,6 +684,10 @@ if(HAVE_LDAP_H)
endif()
target_link_libraries(mod_authn_ldap ${L_MOD_AUTHN_LDAP})
+if(HAVE_LIBCRYPT)
+ target_link_libraries(mod_csrf crypt)
+endif()
+
if(HAVE_ZLIB_H)
if(HAVE_BZLIB_H)
target_link_libraries(mod_compress ${ZLIB_LIBRARY} bz2)
diff --git a/src/Makefile.am b/src/Makefile.am
index 0470c330..6e26ac61 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -202,6 +202,11 @@ mod_usertrack_la_SOURCES = mod_usertrack.c
mod_usertrack_la_LDFLAGS = $(common_module_ldflags)
mod_usertrack_la_LIBADD = $(common_libadd)
+lib_LTLIBRARIES += mod_csrf.la
+mod_csrf_la_SOURCES = mod_csrf.c
+mod_csrf_la_LDFLAGS = -module -export-dynamic -avoid-version
+mod_csrf_la_LIBADD = $(CRYPT_LIB) $(common_libadd)
+
lib_LTLIBRARIES += mod_proxy.la
mod_proxy_la_SOURCES = mod_proxy.c
mod_proxy_la_LDFLAGS = $(common_module_ldflags)
diff --git a/src/SConscript b/src/SConscript
index fca71503..6f405820 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -80,6 +80,7 @@ modules = {
'mod_access' : { 'src' : [ 'mod_access.c' ] },
'mod_alias' : { 'src' : [ 'mod_alias.c' ] },
'mod_cgi' : { 'src' : [ 'mod_cgi.c' ] },
+ 'mod_csrf' : { 'src' : [ 'mod_csrf.c' ], 'lib' : [ env['LIBCRYPT'] ] },
'mod_fastcgi' : { 'src' : [ 'mod_fastcgi.c' ] },
'mod_scgi' : { 'src' : [ 'mod_scgi.c' ] },
'mod_extforward' : { 'src' : [ 'mod_extforward.c' ] },
diff --git a/src/mod_csrf.c b/src/mod_csrf.c
new file mode 100644
index 00000000..37d70f7a
--- /dev/null
+++ b/src/mod_csrf.c
@@ -0,0 +1,537 @@
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "base64.h"
+
+#include "plugin.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined (USE_OPENSSL)
+
+#include <openssl/rand.h>
+#include <openssl/hmac.h>
+
+/* Token:
+ *
+ * the token protects a message + additional data:
+ *
+ * "message":
+ * - 1 byte: version (0x01)
+ * - 8 bytes: uint64_t big endian timestamp in seconds since epoch
+ * "additional data":
+ * - REMOTE_USER as simple string (without null termination); can be empty
+ * REMOTE_USER is usually set by mod_auth; you must load mod_auth before mod_csrf!
+ *
+ * The token consists of the base64-encoding of "message" and a checksum (HMAC)
+ */
+
+#define SECRET_SIZE_BYTES 20 /* secret to automatically generate as fallback */
+#define TOKEN_DEFAULT_TTL (10*60) /* in seconds: 10 minutes */
+#define TOKEN_DEFAULT_HEADER "X-Csrf-Token"
+
+typedef enum {
+ TOKEN_CHECK_OK,
+ TOKEN_CHECK_OK_RENEW,
+ TOKEN_CHECK_FAILED
+} token_check_result;
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ unsigned short activate:1;
+ unsigned short protect:1;
+ unsigned short require_user:1;
+ unsigned short debug:1;
+
+ int ttl;
+
+ const EVP_MD* hash;
+ buffer* header;
+ buffer* secret;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config* config_storage;
+ plugin_config conf;
+} plugin_data;
+
+static buffer* create_message(void) {
+ uint64_t now = (uint64_t) time(NULL);
+ buffer* msg = buffer_init();
+
+ {
+ char* raw = buffer_string_prepare_append(msg, 1 + sizeof(now));
+ size_t i;
+
+ *raw++ = 1; /* CSRF token "version" */
+ /* store "now" as big endian */
+ for (i = sizeof(now); i-- > 0; ) {
+ raw[i] = (char) (now);
+ now >>= 8;
+ }
+ buffer_commit(msg, 1 + sizeof(now));
+ }
+
+ return msg;
+}
+
+/* returns 0 on failure */
+static int parse_and_check_message(server *srv, plugin_data* p, char const* msg, size_t msg_len, uint64_t* ts) {
+ if (0 == msg_len) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "invalid token length: empty message");
+ }
+ return 0;
+ }
+
+ if (msg[0] != 1) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sx",
+ "invalid token version, expected 0x01, got: ",
+ (int)(unsigned char)msg[0]);
+ }
+ return 0; /* CSRF token "version" check */
+ }
+
+ if (msg_len != 9) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "invalid token message length, expected 9, got: ",
+ (int) msg_len);
+ }
+ return 0;
+ }
+
+ /* parse timestamp */
+ {
+ uint64_t tsn = 0;
+ size_t i;
+ for (i = 1; i < 9; ++i) {
+ tsn = (tsn << 8) | (unsigned char)(msg[i]);
+ }
+ *ts = tsn;
+ }
+
+ return 1;
+}
+
+/* returns 0 on failure */
+static int hmac_message(plugin_data* p, char* digest, char const* msg, size_t msg_len, const char* user) {
+ HMAC_CTX ctx;
+ HMAC_CTX_init(&ctx);
+
+ force_assert(buffer_string_length(p->conf.secret) >= SECRET_SIZE_BYTES);
+ force_assert(NULL != p->conf.hash);
+ if (!HMAC_Init_ex(&ctx, CONST_BUF_LEN(p->conf.secret), p->conf.hash, NULL)) goto err;
+
+ if (!HMAC_Update(&ctx, (unsigned char const*) msg, msg_len)) goto err;
+ /* message must be "self-terminating" and length-checked!
+ * -> just append user to message if there is one
+ */
+ if (user && !HMAC_Update(&ctx, (unsigned char const*) user, strlen(user))) goto err;
+ if (!HMAC_Final(&ctx, (unsigned char*) digest, NULL)) goto err;
+
+ HMAC_CTX_cleanup(&ctx);
+ return 1;
+
+err:
+ HMAC_CTX_cleanup(&ctx);
+ return 0;
+}
+
+/* returns 0 on failure */
+static int append_token(server* srv, plugin_data* p, buffer* buf, const char* user) {
+ buffer* msg = create_message();
+
+ {
+ const size_t digest_len = EVP_MD_size(p->conf.hash);
+ char* digest = buffer_string_prepare_append(msg, digest_len);
+ if (!hmac_message(p, digest, CONST_BUF_LEN(msg), user)) {
+ buffer_free(msg);
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "failed to create token digest");
+ return 0;
+ }
+ buffer_commit(msg, digest_len);
+ }
+ buffer_append_base64_encode_no_padding(buf, (unsigned char const*) CONST_BUF_LEN(msg), BASE64_STANDARD);
+ buffer_free(msg);
+
+ return 1;
+}
+
+static token_check_result verify_token(server* srv, plugin_data* p, char const* token, size_t token_len, const char* user) {
+ uint64_t ts;
+
+ {
+ const size_t digest_len = EVP_MD_size(p->conf.hash);
+ size_t msg_len;
+ buffer* decoded_token = buffer_init();
+
+ if (NULL == buffer_append_base64_decode(decoded_token, token, token_len, BASE64_STANDARD)) {
+ buffer_free(decoded_token);
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "failed to decode base64 token");
+ }
+ return TOKEN_CHECK_FAILED;
+ }
+
+ if (buffer_string_length(decoded_token) < digest_len) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "token too short for digest");
+ }
+ return TOKEN_CHECK_FAILED;
+ }
+ msg_len = buffer_string_length(decoded_token) - digest_len;
+
+ if (!parse_and_check_message(srv, p, decoded_token->ptr, msg_len, &ts)) {
+ buffer_free(decoded_token);
+ return TOKEN_CHECK_FAILED;
+ }
+
+ {
+ buffer* digest_buf = buffer_init();
+ char* digest = buffer_string_prepare_append(digest_buf, digest_len);
+ if (!hmac_message(p, digest, decoded_token->ptr, msg_len, user)
+ || 0 != strncmp(decoded_token->ptr + msg_len, (char const*) digest, digest_len))
+ {
+ buffer_free(decoded_token);
+ buffer_free(digest_buf);
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "token digest didn't match");
+ }
+ return TOKEN_CHECK_FAILED;
+ }
+ buffer_free(digest_buf);
+ }
+ buffer_free(decoded_token);
+ }
+
+ {
+ int64_t timediff = (int64_t)(ts - (uint64_t)time(NULL));
+ /* accept "ttl" seconds in BOTH directions - usually you shouldn't sign
+ * too much ahead of time (in case of multiple servers)
+ */
+ if (timediff < -p->conf.ttl || timediff > p->conf.ttl) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "token expired");
+ }
+ return TOKEN_CHECK_FAILED; /* timeout */
+ }
+
+ if (timediff > p->conf.ttl / 4) return TOKEN_CHECK_OK_RENEW;
+ }
+
+ return TOKEN_CHECK_OK;
+}
+
+/* init the plugin data */
+INIT_FUNC(mod_csrf_init) {
+ plugin_data* p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_csrf_free) {
+ plugin_data* p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config* s = &p->config_storage[i];
+
+ buffer_free(s->header);
+ buffer_free(s->secret);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+#define CSRF_CONFIG_ACTIVATE "csrf.activate"
+#define CSRF_CONFIG_PROTECT "csrf.protect"
+#define CSRF_CONFIG_REQUIRE_USER "csrf.require-user"
+#define CSRF_CONFIG_DEBUG "csrf.debug"
+#define CSRF_CONFIG_HASH "csrf.hash"
+#define CSRF_CONFIG_HEADER "csrf.header"
+#define CSRF_CONFIG_SECRET "csrf.secret"
+#define CSRF_CONFIG_TTL "csrf.ttl"
+
+SETDEFAULTS_FUNC(mod_csrf_set_defaults) {
+ plugin_data* p = p_d;
+ size_t i = 0;
+ buffer* hash = buffer_init();
+
+ config_values_t cv[] = {
+ { CSRF_CONFIG_ACTIVATE, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { CSRF_CONFIG_PROTECT, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { CSRF_CONFIG_REQUIRE_USER, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { CSRF_CONFIG_DEBUG, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { CSRF_CONFIG_TTL, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+ { CSRF_CONFIG_HASH, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { CSRF_CONFIG_HEADER, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
+ { CSRF_CONFIG_SECRET, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ data_config const* config = (data_config const*)srv->config_context->data[i];
+ plugin_config* s = &p->config_storage[i];
+
+ unsigned short activate = 0;
+ unsigned short protect = 1; /* protect by default (when activated) */
+ /* empty user not allowed by default to prevent mistakes in mod_auth/mod_csrf ordering */
+ unsigned short require_user = 1;
+ unsigned short debug = 0;
+ unsigned short ttl = TOKEN_DEFAULT_TTL;
+
+ s->hash = NULL;
+ s->header = buffer_init();
+ s->secret = buffer_init();
+
+ buffer_reset(hash);
+
+ cv[0].destination = &(activate);
+ cv[1].destination = &(protect);
+ cv[2].destination = &(require_user);
+ cv[3].destination = &(debug);
+ cv[4].destination = &(ttl);
+ cv[5].destination = hash;
+ cv[6].destination = s->header;
+ cv[7].destination = s->secret;
+
+ if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
+ return HANDLER_ERROR;
+ }
+
+ s->activate = activate;
+ s->protect = protect;
+ s->require_user = require_user;
+ s->debug = debug;
+ s->ttl = ttl;
+
+ if (!buffer_is_empty(s->secret) && buffer_string_length(s->secret) < SECRET_SIZE_BYTES) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ CSRF_CONFIG_SECRET " too short");
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(hash)) {
+ s->hash = EVP_get_digestbyname(hash->ptr);
+ if (NULL == s->hash) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "couldn't find " CSRF_CONFIG_HASH ":",
+ hash);
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ {
+ plugin_config* s = &p->config_storage[0];
+
+ if (NULL == s->hash) s->hash = EVP_sha256();
+ if (buffer_string_is_empty(s->header)) buffer_copy_string_len(s->header, CONST_STR_LEN(TOKEN_DEFAULT_HEADER));
+ if (buffer_string_is_empty(s->secret)) {
+ buffer_string_set_length(s->secret, SECRET_SIZE_BYTES);
+
+ if (RAND_bytes((unsigned char*) s->secret->ptr, SECRET_SIZE_BYTES) == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "failed to generate secret key");
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_csrf_patch_connection(server* srv, connection* con, plugin_data* p) {
+ size_t i, j;
+ plugin_config* s = &p->config_storage[0];
+
+ PATCH(activate);
+ PATCH(protect);
+ PATCH(require_user);
+ PATCH(debug);
+ PATCH(hash);
+ PATCH(header);
+ PATCH(secret);
+ PATCH(ttl);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config* dc = (data_config*) srv->config_context->data[i];
+ s = &p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset* du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_ACTIVATE))) {
+ PATCH(activate);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_PROTECT))) {
+ PATCH(protect);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_REQUIRE_USER))) {
+ PATCH(require_user);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_DEBUG))) {
+ PATCH(debug);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_HASH))) {
+ PATCH(hash);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_HEADER))) {
+ PATCH(header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_SECRET))) {
+ PATCH(secret);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CSRF_CONFIG_TTL))) {
+ PATCH(ttl);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_csrf_uri_handler) {
+ plugin_data* p = p_d;
+ buffer* csrf_req_header = NULL;
+ const char* user = NULL;
+ token_check_result result = TOKEN_CHECK_FAILED;
+
+ mod_csrf_patch_connection(srv, con, p);
+
+ if (!p->conf.activate) return HANDLER_GO_ON;
+
+ {
+ data_string* ds_user = (data_string*) array_get_element(con->environment, "REMOTE_USER");
+ if (NULL != ds_user) user = ds_user->value->ptr;
+ }
+
+ if (p->conf.require_user && (NULL == user || 0 == strlen(user))) {
+ if (p->conf.protect) {
+ con->http_status = 403;
+ con->mode = DIRECT;
+ if (p->conf.debug || con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "require user to protect with csrf: user is missing -> rejecting request");
+ }
+ return HANDLER_FINISHED;
+ } else {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "require user to activate csrf: user is missing -> not generating token");
+ }
+ /* only activate when we actually have a user */
+ return HANDLER_GO_ON;
+ }
+ }
+
+ {
+ data_string* ds_req_header = (data_string*) array_get_element(con->request.headers, p->conf.header->ptr);
+ if (NULL != ds_req_header) csrf_req_header = ds_req_header->value;
+ }
+
+ if (csrf_req_header) {
+ result = verify_token(srv, p, CONST_BUF_LEN(csrf_req_header), user);
+ }
+
+ switch (result) {
+ case TOKEN_CHECK_OK:
+ break;
+ default:
+ {
+ data_string* ds_resp_header = data_response_init();
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "old/invalid csrf token: sending new token");
+ }
+
+ buffer_copy_buffer(ds_resp_header->key, p->conf.header);
+ if (!append_token(srv, p, ds_resp_header->value, user)) {
+ ds_resp_header->free((data_unset*) ds_resp_header);
+ } else {
+ array_insert_unique(con->response.headers, (data_unset*) ds_resp_header);
+ }
+ }
+ break;
+ }
+
+ switch (result) {
+ case TOKEN_CHECK_OK:
+ case TOKEN_CHECK_OK_RENEW:
+ if (p->conf.debug || con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "valid csrf token: accepting request");
+ }
+ break;
+ default:
+ if (p->conf.protect) {
+ con->http_status = 403;
+ con->mode = DIRECT;
+ if (p->conf.debug || con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "missing/invalid csrf token: rejecting request");
+ }
+ return HANDLER_FINISHED;
+ }
+ break;
+ }
+ return HANDLER_GO_ON;
+}
+
+int mod_csrf_plugin_init(plugin* p);
+int mod_csrf_plugin_init(plugin* p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("csrf");
+
+ p->init = mod_csrf_init;
+ p->handle_uri_clean = mod_csrf_uri_handler;
+ p->set_defaults = mod_csrf_set_defaults;
+ p->cleanup = mod_csrf_free;
+
+ p->data = NULL;
+
+ return 0;
+}
+
+#else
+
+/* if we don't have openssl support, this plugin does nothing */
+int mod_csrf_plugin_init(plugin* p);
+int mod_csrf_plugin_init(plugin* p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("csrf");
+ return 0;
+}
+
+#endif