From 6d0a3ec6a755c788b5b4c90caa532dbe3490032a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Sun, 21 Feb 2016 14:45:52 +0100 Subject: [mod_csrf] module to aid against csrf attacks --- configure.ac | 6 + doc/config/conf.d/Makefile.am | 1 + doc/config/conf.d/csrf.conf | 101 ++++++++ src/CMakeLists.txt | 5 + src/Makefile.am | 5 + src/SConscript | 1 + src/mod_csrf.c | 537 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 656 insertions(+) create mode 100644 doc/config/conf.d/csrf.conf create mode 100644 src/mod_csrf.c 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 +#include +#include + +#if defined (USE_OPENSSL) + +#include +#include + +/* 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 -- cgit v1.2.1