Redirect based on php-set cookies

tqvn2004 nginx-forum at nginx.us
Wed Feb 24 17:06:33 MSK 2010


Not sure if anyone interested in this module, but here is the code I made:

/*
 * Author: tqvn2004
 * Date: 24/02/2010
 */

#include 
#include 
#include 
#include 


/*
 * 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){
            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){
            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)){
            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)){
            case 'a':
            case 't':
            case 's':
            case 'u':
                new = ngx_tolower(rule->data);
                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;
                       /*
                        * we need temp 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 & 0xf];
        hexchar[0] = hex[(rawbuf>>4) & 0xf];
        if ((hexchar[0] != hexbuf[2*i]) || (hexchar[1] != hexbuf[2*i+1])){
            return 1;
        }
        i++;
    }
    return 0;
}

Posted at Nginx Forum: http://forum.nginx.org/read.php?2,55378,56822#msg-56822




More information about the nginx mailing list