Not sure if anyone interested in this module, but here is the code I made:
/*
* Author: tqvn2004
* Date: 24/02/2010
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_sha1.h>
/*
* The module set the $secret_cookie variable
* to "true" if a secret cookie is set at client.
* Based on the variable, user can be classified
* into verified and non-verified one.
*/
#define NGX_HTTP_SECRET_COOKIE_OFF 0 // Default state
#define NGX_HTTP_SECRET_COOKIE_ON 1
#define NGX_HTTP_SECRET_COOKIE_NOT_SET 1
#define NGX_HTTP_SECRET_COOKIE_SET 0
#define NGX_HTTP_SECRET_COOKIE_DEFAULT_VALID_PERIOD 604800 // 7 days
#define NGX_HTTP_SECRET_COOKIE_MIN_VALID_PERIOD 3600 // 1 hour
// Default rule: Salt + Remote-address + User-agent + Time + Salt
#define NGX_HTTP_SECRET_COOKIE_DEFAULT_RULE "sauts"
#define NGX_HTTP_SECRET_COOKIE_DEFAULT_SALT "x83n7h32"
#define NGX_HTTP_SECRET_COOKIE_DEFAULT_NAME "secret_cookie"
// Record for Location configuration
typedef struct {
ngx_flag_t enable;
ngx_str_t name;
ngx_str_t rule;
ngx_str_t salt;
ngx_uint_t validperiod;
} ngx_http_secret_cookie_conf_t;
// Return variable structure
typedef struct {
ngx_str_t name;
ngx_http_get_variable_pt handler;
uintptr_t data;
} ngx_http_secret_cookie_variable_t;
// Context variable: This alive for request's duration
typedef struct {
ngx_uint_t secret_cookie_status;
ngx_str_t secret_cookie;
} ngx_http_secret_cookie_ctx_t;
static void *ngx_http_secret_cookie_create_conf(ngx_conf_t *cf);
static char *ngx_http_secret_cookie_merge_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_int_t ngx_http_secret_cookie_add_variable(ngx_conf_t *cf);
static ngx_int_t ngx_http_secret_cookie_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_http_secret_cookie_ctx_t * ngx_http_secret_cookie(ngx_http_request_t *r,
ngx_http_secret_cookie_conf_t *cf);
static char * ngx_http_secret_cookie_valid_period(ngx_conf_t *cf, void *post,
void *data);
static char * ngx_http_secret_cookie_name(ngx_conf_t *cf, void *post,
void *data);
static char * ngx_http_secret_cookie_rule(ngx_conf_t *cf, void *post,
void *data);
static u_char * ngx_num2str(u_char *buf, u_char *last, uint64_t ui64);
static ngx_int_t ngx_raw_vs_hex(u_char *rawbuf, size_t rlen, u_char *hexbuf, size_t hlen);
// A pointer to post-handler function to validate the "validperiod" param
static ngx_conf_post_handler_pt ngx_http_secret_cookie_valid_period_p = ngx_http_secret_cookie_valid_period;
static ngx_conf_post_handler_pt ngx_http_secret_cookie_name_p = ngx_http_secret_cookie_name;
static ngx_conf_post_handler_pt ngx_http_secret_cookie_rule_p = ngx_http_secret_cookie_rule;
static ngx_command_t ngx_http_secret_cookie_commands[] = {
/* Define secret_cookie directive (on or off).
* Location: Main, HTTP, Location
* Take: 1 parameter, a Flag
*/
{ ngx_string("secret_cookie"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot, // Convert on/off to value
NGX_HTTP_LOC_CONF_OFFSET, // Instruct value to be written to Location Config,
offsetof(ngx_http_secret_cookie_conf_t, enable), // with this offset
NULL }, // No post-handler is necessary.
/* Define secret_cookie_var1 directive
* Location: Main, HTTP, Location
* Taking: 1 parameter, a string
*/
{ ngx_string("secret_cookie_rule"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_secret_cookie_conf_t, rule),
&ngx_http_secret_cookie_rule_p },
{ ngx_string("secret_cookie_salt"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_secret_cookie_conf_t, salt),
NULL },
{ ngx_string("secret_cookie_name"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_secret_cookie_conf_t, name),
&ngx_http_secret_cookie_name_p },
{ ngx_string("secret_cookie_valid_period"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_secret_cookie_conf_t, validperiod),
&ngx_http_secret_cookie_valid_period_p }, // Post-handler function
ngx_null_command
};
static ngx_http_module_t ngx_http_secret_cookie_module_ctx = {
ngx_http_secret_cookie_add_variable, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_secret_cookie_create_conf, /* create location configuration */
ngx_http_secret_cookie_merge_conf /* merge location configuration */
};
ngx_module_t ngx_http_secret_cookie_module = {
NGX_MODULE_V1,
&ngx_http_secret_cookie_module_ctx, /* module context */
ngx_http_secret_cookie_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_secret_cookie_variable_t ngx_http_secret_cookie_vars[] = {
{ ngx_string("secret_cookie_not_set"), ngx_http_secret_cookie_variable,
NGX_HTTP_SECRET_COOKIE_NOT_SET },
{ ngx_null_string, NULL, 0 }
};
/*
* This function set the $secret_cookie to 1, if the
* cookie is set and valid.
*/
static ngx_int_t
ngx_http_secret_cookie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
uintptr_t data)
{
ngx_http_secret_cookie_ctx_t *rctx;
ngx_http_secret_cookie_conf_t *cf;
cf = ngx_http_get_module_loc_conf(r, ngx_http_secret_cookie_module);
// Only perform cookie search if the module is enabled
if (cf->enable){
rctx = ngx_http_secret_cookie(r, cf);
if ((rctx != NULL) &&
(rctx->secret_cookie_status == NGX_HTTP_SECRET_COOKIE_SET)) {
*v = ngx_http_variable_null_value;
return NGX_OK;
}
}
// Always return true (i.e. secret_cookie not set),
// unless module is enalbed and secret_cookie is checked...
*v = ngx_http_variable_true_value;
return NGX_OK;
}
/*
* This is the main function: It looks for a secret cookie
* in the HTTP request header, and compared with constructed value
* Return: Secret Cookie SET or NOT SET!
*/
static ngx_http_secret_cookie_ctx_t *
ngx_http_secret_cookie(ngx_http_request_t *r, ngx_http_secret_cookie_conf_t *cf)
{
ngx_int_t n, alen, tlen, slen, ulen, total_len;
ngx_http_secret_cookie_ctx_t *ctx;
time_t timestamp; // Current UNIX timestamp
u_char *user_agent = NULL, *salt = NULL, *remote_addr = NULL, *last_change = NULL;
u_char *secret_cookie;
u_char *p;
ngx_sha1_t sha_ctx;
#if (NGX_DEBUG)
// A string for print debug info
ngx_str_t mypen;
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Start checking the secret_cookie:");
#endif
ctx = ngx_http_get_module_ctx(r, ngx_http_secret_cookie_module);
// If existed, secret_cookie was checked before, so stop!
if (ctx){
#if (NGX_DEBUG)
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"The secret_cookie was checked successfully before, aborting!");
#endif
return ctx;
}
// Otherwise, start the checking process
if (ctx == NULL) {
#if (NGX_DEBUG)
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Context variable for secret_cookie not available, creating one!");
#endif
// This r->pool temporary memory will be destroyed later,
// after this request is served!
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secret_cookie_ctx_t));
if (ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: Out of memory");
return NULL;
}
ngx_http_set_ctx(r, ctx, ngx_http_secret_cookie_module);
}
ctx->secret_cookie_status = NGX_HTTP_SECRET_COOKIE_NOT_SET;
// This is copied from USERID Module:
n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &cf->name,
&ctx->secret_cookie);
if (n == NGX_DECLINED) {
#if (NGX_DEBUG)
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"The secret_cookie not found in the header!");
#endif
return ctx;
}
#if (NGX_DEBUG)
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Got a secret_cookie: \"%V\" - \"%V\"", &cf->name, &ctx->secret_cookie);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Rule to compute the secret_cookie: \"%V\"", &cf->rule);
#endif
// First, prepare the variables and count the required length
n = 0;
total_len = 0;
alen = -1;
ulen = -1;
tlen = -1;
slen = -1;
while (n < (ngx_int_t) cf->rule.len){
switch (cf->rule.data[n]){
case 'a':
if (alen == -1){
// Prepare "remote-addr" buffer and its length
alen = r->connection->addr_text.len;
remote_addr = r->connection->addr_text.data;
#if (NGX_DEBUG)
mypen.len = alen;
mypen.data = remote_addr;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Remote address to compute the secret_cookie: \"%i\" - \"%V\"", alen, &mypen);
#endif
}
total_len += alen;
break;
case 't':
if (tlen == -1){
// Prepare "last_change" buffer and its length
// Obtain current time in second
timestamp = ngx_time();
// Calculate the last_change and convert it to string
last_change = ngx_pcalloc(r->pool, NGX_INT64_LEN);
if (last_change == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: Out of memory");
return ctx;
}
p = ngx_num2str(last_change,
last_change + NGX_INT64_LEN,
(uint64_t) timestamp / cf->validperiod);
tlen = p - last_change;
#if (NGX_DEBUG)
mypen.len = tlen;
mypen.data = last_change;
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Last change (time) to compute the secret_cookie: \"%i\" / \"%i\" = \"%V\" (\"%i\")",
timestamp, cf->validperiod, &mypen, tlen);
#endif
}
total_len += tlen;
break;
case 's':
if (slen == -1){
// Prepare "salt" buffer and its length
slen = cf->salt.len;
salt = cf->salt.data;
#if (NGX_DEBUG)
mypen.len = slen;
mypen.data = salt;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"Salt to compute the secret_cookie: \"%i\" - \"%V\"", slen, &mypen);
#endif
}
total_len += slen;
break;
case 'u':
if (ulen == -1){
// Prepare "user-agent" buffer and its length
ulen = r->headers_in.user_agent->value.len;
user_agent = r->headers_in.user_agent->value.data;
#if (NGX_DEBUG)
mypen.len = ulen;
mypen.data = user_agent;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"User agent to compute the secret_cookie: \"%i\" - \"%V\"", ulen, &mypen);
#endif
}
total_len += ulen;
default:
// Do not copy char
break;
}
n++;
}
// Second, create the content of secret_cookie
secret_cookie = ngx_pcalloc(r->pool, total_len);
if (secret_cookie == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: Out of memory");
return ctx;
}
n = 0;
p = secret_cookie;
while (n < (ngx_int_t) cf->rule.len){
switch (cf->rule.data[n]){
case 'a':
// This ngx_copy faster than ngx_cpymem with buffer < 16 bytes
p = ngx_copy(p, remote_addr, alen);
break;
case 't':
p = ngx_copy(p, last_change, tlen);
break;
case 's':
p = ngx_copy(p, salt, slen);
break;
case 'u':
p = ngx_cpymem(p, user_agent, ulen);
break;
}
n++;
}
#if (NGX_DEBUG)
mypen.len = total_len;
mypen.data = secret_cookie;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"The content of the secret_cookie: \"%i\" \"%V\"", total_len, &mypen);
#endif
// Third, perform sha1 on the content
p = ngx_pcalloc(r->pool, SHA_DIGEST_LENGTH);
if (p == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: Out of memory");
return ctx;
}
n = ngx_sha1_init(&sha_ctx);
if (n == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: SHA1 init fails");
return ctx;
}
n = ngx_sha1_update(&sha_ctx, secret_cookie, total_len);
if (n == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: SHA1 update fails");
return ctx;
}
n = ngx_sha1_final(p, &sha_ctx);
if (n == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: SHA1 final fails");
return ctx;
}
#if (NGX_DEBUG)
mypen.len = SHA_DIGEST_LENGTH;
mypen.data = p;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"The content of the secret_cookie after sha1: \"%i\" \"%V\"", SHA_DIGEST_LENGTH, &mypen);
#endif
if (ngx_raw_vs_hex(p, SHA_DIGEST_LENGTH, ctx->secret_cookie.data, 2*SHA_DIGEST_LENGTH) == 0){
ctx->secret_cookie_status = NGX_HTTP_SECRET_COOKIE_SET;
} else {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"secret_cookie: Saved cookie does not match");
}
// Return the result
return ctx;
}
/* Add $secret_cookie_set variable to the http process.
* Called by "preconfiguration" hook!
*/
static ngx_int_t
ngx_http_secret_cookie_add_variable(ngx_conf_t *cf)
{
ngx_http_secret_cookie_variable_t *var;
ngx_http_variable_t *v;
for (var = ngx_http_secret_cookie_vars; var->name.len; var++) {
v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGEABLE);
if (v == NULL) {
return NGX_ERROR;
}
v->get_handler = var->handler;
v->data = var->data;
}
return NGX_OK;
}
/*
* This function is called when location configuration is created.
* It will create a default configuration for secret_cookie module.
*/
static void *
ngx_http_secret_cookie_create_conf(ngx_conf_t *cf)
{
ngx_http_secret_cookie_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secret_cookie_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* Set default value
*/
conf->enable = NGX_CONF_UNSET;
/* This should be set by ngx_pcalloc():
conf->rule = ngx_null_string;
conf->salt = ngx_null_string;
conf->name = ngx_null_string; */
conf->validperiod = NGX_CONF_UNSET;
return conf;
}
/*
* This function is called when location configuration is merged to main config.
* It will create a default configuration for secret_cookie module.
*/
static char *
ngx_http_secret_cookie_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_secret_cookie_conf_t *prev = parent;
ngx_http_secret_cookie_conf_t *conf = child;
// Merge previous to current configration, with a default value:
ngx_conf_merge_value(conf->enable, prev->enable,
NGX_HTTP_SECRET_COOKIE_OFF);
ngx_conf_merge_str_value(conf->rule, prev->rule, NGX_HTTP_SECRET_COOKIE_DEFAULT_RULE);
ngx_conf_merge_str_value(conf->salt, prev->salt, NGX_HTTP_SECRET_COOKIE_DEFAULT_SALT);
ngx_conf_merge_str_value(conf->name, prev->name, NGX_HTTP_SECRET_COOKIE_DEFAULT_NAME);
ngx_conf_merge_uint_value(conf->validperiod, prev->validperiod,
NGX_HTTP_SECRET_COOKIE_DEFAULT_VALID_PERIOD);
if (conf->validperiod < NGX_HTTP_SECRET_COOKIE_MIN_VALID_PERIOD) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"Valid period is too short!");
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
/*
* This function is called after secret_cookie_valid_period is passed
* from config to module. Here you can validate the param, or change the
* param to meaningful value.
*/
static char *
ngx_http_secret_cookie_valid_period(ngx_conf_t *cf, void *post, void *data)
{
ngx_uint_t *validperiod = data;
if (*validperiod < NGX_HTTP_SECRET_COOKIE_MIN_VALID_PERIOD) {
return "Valid period is too short (minimum 3600 second or 1 hour)";
}
return NGX_CONF_OK;
}
/*
* This function is called after secret_cookie_name is passed from config
* to module. Here you can validate the param, or change the
* param to meaningful value.
*/
static char *
ngx_http_secret_cookie_name(ngx_conf_t *cf, void *post, void *data)
{
ngx_str_t *name = data;
if (ngx_strcmp(name->data, "") == 0) {
name->len = sizeof(NGX_HTTP_SECRET_COOKIE_DEFAULT_NAME) - 1;
name->data = (u_char *) NGX_HTTP_SECRET_COOKIE_DEFAULT_NAME;
}
return NGX_CONF_OK;
}
/*
* This function is called after secret_cookie_rule is passed from config
* to module. Here you can validate the param, or change the
* param to meaningful value.
*/
static char *
ngx_http_secret_cookie_rule(ngx_conf_t *cf, void *post, void *data)
{
ngx_str_t *rule = data;
u_char *new;
size_t i, len;
// First count the required length,
i = 0;
len = 0;
while (i < rule->len){
switch (ngx_tolower(rule->data[i])){
case 'a':
case 't':
case 's':
case 'u':
len++;
break;
default:
// Do not copy char
break;
}
i++;
}
if (len == 0) {
return "must contain combination of \"s\" (Salt) and \"a\" (Remote-address) and \"u\" (User-agent) and \"t\" (Time)";
}
// Then do the copying
new = ngx_pnalloc(cf->pool, len);
i = 0;
len = 0;
while (i < rule->len){
switch (ngx_tolower(rule->data[i])){
case 'a':
case 't':
case 's':
case 'u':
new[len] = ngx_tolower(rule->data[i]);
len++;
break;
default:
// Do not copy char
break;
}
i++;
}
rule->len = len;
rule->data = new;
return NGX_CONF_OK;
}
/*
* Convert an int64 to string presentation
*/
static u_char * ngx_num2str(u_char *buf, u_char *last, uint64_t ui64)
{
u_char *p, temp[NGX_INT64_LEN + 1];
/*
* we need temp[NGX_INT64_LEN] only,
* but icc issues the warning
*/
size_t len;
uint32_t ui32;
p = temp + NGX_INT64_LEN;
if (ui64 <= NGX_MAX_UINT32_VALUE) {
ui32 = (uint32_t) ui64;
do {
*--p = (u_char) (ui32 % 10 + '0');
} while (ui32 /= 10);
} else {
do {
*--p = (u_char) (ui64 % 10 + '0');
} while (ui64 /= 10);
}
/* number safe copy */
len = (temp + NGX_INT64_LEN) - p;
if (buf + len > last) {
len = last - buf;
}
return ngx_cpymem(buf, p, len);
}
/*
* Comparison of a raw string and a hex-style string
*/
static ngx_int_t ngx_raw_vs_hex(u_char *rawbuf, size_t rlen, u_char *hexbuf, size_t hlen)
{
static u_char hex[] = "0123456789abcdef";
u_char hexchar[] = "aa";
ngx_int_t len, i;
// Compare by the shortest length
len = ((rlen < hlen / 2) ? rlen : hlen / 2);
i = 0;
while (i < len){
// Calculate a hexchar from rawbuff
hexchar[1] = hex[rawbuf[i] & 0xf];
hexchar[0] = hex[(rawbuf[i]>>4) & 0xf];
if ((hexchar[0] != hexbuf[2*i]) || (hexchar[1] != hexbuf[2*i+1])){
return 1;
}
i++;
}
return 0;
}