3 * Copyright (C) Igor Sysoev
7 #include <ngx_config.h>
12 #define NGX_HTTP_USERID_OFF 0
13 #define NGX_HTTP_USERID_LOG 1
14 #define NGX_HTTP_USERID_V1 2
15 #define NGX_HTTP_USERID_ON 3
17 /* 31 Dec 2037 23:55:55 GMT */
18 #define NGX_HTTP_USERID_MAX_EXPIRES 2145916555
34 } ngx_http_userid_conf_t;
41 } ngx_http_userid_ctx_t;
44 static ngx_http_userid_ctx_t *ngx_http_userid_get_uid(ngx_http_request_t *r,
45 ngx_http_userid_conf_t *conf);
46 static ngx_int_t ngx_http_userid_variable(ngx_http_request_t *r,
47 ngx_http_variable_value_t *v, ngx_str_t *name, uint32_t *uid);
48 static ngx_int_t ngx_http_userid_set_uid(ngx_http_request_t *r,
49 ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
51 static ngx_int_t ngx_http_userid_add_variables(ngx_conf_t *cf);
52 static ngx_int_t ngx_http_userid_init(ngx_conf_t *cf);
53 static void *ngx_http_userid_create_conf(ngx_conf_t *cf);
54 static char *ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent,
56 static char *ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data);
57 static char *ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data);
58 static char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd,
60 static char *ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data);
61 static char *ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd,
63 static ngx_int_t ngx_http_userid_init_worker(ngx_cycle_t *cycle);
67 static uint32_t start_value;
68 static uint32_t sequencer_v1 = 1;
69 static uint32_t sequencer_v2 = 0x03030302;
72 static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT";
75 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
78 static ngx_conf_enum_t ngx_http_userid_state[] = {
79 { ngx_string("off"), NGX_HTTP_USERID_OFF },
80 { ngx_string("log"), NGX_HTTP_USERID_LOG },
81 { ngx_string("v1"), NGX_HTTP_USERID_V1 },
82 { ngx_string("on"), NGX_HTTP_USERID_ON },
83 { ngx_null_string, 0 }
87 static ngx_conf_post_handler_pt ngx_http_userid_domain_p =
88 ngx_http_userid_domain;
89 static ngx_conf_post_handler_pt ngx_http_userid_path_p = ngx_http_userid_path;
90 static ngx_conf_post_handler_pt ngx_http_userid_p3p_p = ngx_http_userid_p3p;
93 static ngx_command_t ngx_http_userid_commands[] = {
95 { ngx_string("userid"),
96 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
97 ngx_conf_set_enum_slot,
98 NGX_HTTP_LOC_CONF_OFFSET,
99 offsetof(ngx_http_userid_conf_t, enable),
100 ngx_http_userid_state },
102 { ngx_string("userid_service"),
103 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
104 ngx_conf_set_num_slot,
105 NGX_HTTP_LOC_CONF_OFFSET,
106 offsetof(ngx_http_userid_conf_t, service),
109 { ngx_string("userid_name"),
110 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
111 ngx_conf_set_str_slot,
112 NGX_HTTP_LOC_CONF_OFFSET,
113 offsetof(ngx_http_userid_conf_t, name),
116 { ngx_string("userid_domain"),
117 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
118 ngx_conf_set_str_slot,
119 NGX_HTTP_LOC_CONF_OFFSET,
120 offsetof(ngx_http_userid_conf_t, domain),
121 &ngx_http_userid_domain_p },
123 { ngx_string("userid_path"),
124 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
125 ngx_conf_set_str_slot,
126 NGX_HTTP_LOC_CONF_OFFSET,
127 offsetof(ngx_http_userid_conf_t, path),
128 &ngx_http_userid_path_p },
130 { ngx_string("userid_expires"),
131 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
132 ngx_http_userid_expires,
133 NGX_HTTP_LOC_CONF_OFFSET,
137 { ngx_string("userid_p3p"),
138 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
139 ngx_conf_set_str_slot,
140 NGX_HTTP_LOC_CONF_OFFSET,
141 offsetof(ngx_http_userid_conf_t, p3p),
142 &ngx_http_userid_p3p_p },
144 { ngx_string("userid_mark"),
145 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
146 ngx_http_userid_mark,
147 NGX_HTTP_LOC_CONF_OFFSET,
155 static ngx_http_module_t ngx_http_userid_filter_module_ctx = {
156 ngx_http_userid_add_variables, /* preconfiguration */
157 ngx_http_userid_init, /* postconfiguration */
159 NULL, /* create main configuration */
160 NULL, /* init main configuration */
162 NULL, /* create server configuration */
163 NULL, /* merge server configuration */
165 ngx_http_userid_create_conf, /* create location configration */
166 ngx_http_userid_merge_conf /* merge location configration */
170 ngx_module_t ngx_http_userid_filter_module = {
172 &ngx_http_userid_filter_module_ctx, /* module context */
173 ngx_http_userid_commands, /* module directives */
174 NGX_HTTP_MODULE, /* module type */
175 NULL, /* init master */
176 NULL, /* init module */
177 ngx_http_userid_init_worker, /* init process */
178 NULL, /* init thread */
179 NULL, /* exit thread */
180 NULL, /* exit process */
181 NULL, /* exit master */
182 NGX_MODULE_V1_PADDING
186 static ngx_str_t ngx_http_userid_got = ngx_string("uid_got");
187 static ngx_str_t ngx_http_userid_set = ngx_string("uid_set");
191 ngx_http_userid_filter(ngx_http_request_t *r)
193 ngx_http_userid_ctx_t *ctx;
194 ngx_http_userid_conf_t *conf;
197 return ngx_http_next_header_filter(r);
200 conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
202 if (conf->enable <= NGX_HTTP_USERID_LOG) {
203 return ngx_http_next_header_filter(r);
206 ctx = ngx_http_userid_get_uid(r, conf);
212 if (ctx->uid_got[3] != 0) {
214 if (conf->mark == '\0') {
215 return ngx_http_next_header_filter(r);
218 if (ctx->cookie.len > 23
219 && ctx->cookie.data[22] == conf->mark
220 && ctx->cookie.data[23] == '=')
222 return ngx_http_next_header_filter(r);
227 /* ctx->status == NGX_DECLINED */
229 if (ngx_http_userid_set_uid(r, ctx, conf) == NGX_OK) {
230 return ngx_http_next_header_filter(r);
238 ngx_http_userid_got_variable(ngx_http_request_t *r,
239 ngx_http_variable_value_t *v, uintptr_t data)
241 ngx_http_userid_ctx_t *ctx;
242 ngx_http_userid_conf_t *conf;
244 conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
246 if (conf->enable == NGX_HTTP_USERID_OFF) {
251 ctx = ngx_http_userid_get_uid(r, conf);
257 if (ctx->uid_got[3] != 0) {
258 return ngx_http_userid_variable(r, v, &conf->name, ctx->uid_got);
261 /* ctx->status == NGX_DECLINED */
270 ngx_http_userid_set_variable(ngx_http_request_t *r,
271 ngx_http_variable_value_t *v, uintptr_t data)
273 ngx_http_userid_ctx_t *ctx;
274 ngx_http_userid_conf_t *conf;
276 ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
278 if (ctx == NULL || ctx->uid_set[3] == 0) {
283 conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
285 return ngx_http_userid_variable(r, v, &conf->name, ctx->uid_set);
289 static ngx_http_userid_ctx_t *
290 ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
294 ngx_table_elt_t **cookies;
295 ngx_http_userid_ctx_t *ctx;
297 ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
304 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_userid_ctx_t));
309 ngx_http_set_ctx(r, ctx, ngx_http_userid_filter_module);
312 n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &conf->name,
314 if (n == NGX_DECLINED) {
318 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
319 "uid cookie: \"%V\"", &ctx->cookie);
321 if (ctx->cookie.len < 22) {
322 cookies = r->headers_in.cookies.elts;
323 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
324 "client sent too short userid cookie \"%V\"",
332 * we have to limit the encoded string to 22 characters because
333 * 1) cookie may be marked by "userid_mark",
334 * 2) and there are already the millions cookies with a garbage
335 * instead of the correct base64 trail "=="
340 dst.data = (u_char *) ctx->uid_got;
342 if (ngx_decode_base64(&dst, &src) == NGX_ERROR) {
343 cookies = r->headers_in.cookies.elts;
344 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
345 "client sent invalid userid cookie \"%V\"",
350 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
351 "uid: %08XD%08XD%08XD%08XD",
352 ctx->uid_got[0], ctx->uid_got[1],
353 ctx->uid_got[2], ctx->uid_got[3]);
360 ngx_http_userid_set_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
361 ngx_http_userid_conf_t *conf)
366 ngx_table_elt_t *set_cookie, *p3p;
368 * TODO: in the threaded mode the sequencers should be in TLS and their
369 * ranges should be divided between threads
372 if (ctx->uid_got[3] == 0) {
374 if (conf->enable == NGX_HTTP_USERID_V1) {
375 if (conf->service == NGX_CONF_UNSET) {
378 ctx->uid_set[0] = conf->service;
380 ctx->uid_set[1] = (uint32_t) ngx_time();
381 ctx->uid_set[2] = start_value;
382 ctx->uid_set[3] = sequencer_v1;
383 sequencer_v1 += 0x100;
386 if (conf->service == NGX_CONF_UNSET) {
387 if (ngx_http_server_addr(r, NULL) != NGX_OK) {
391 ctx->uid_set[0] = htonl(r->in_addr);
394 ctx->uid_set[0] = htonl(conf->service);
397 ctx->uid_set[1] = htonl((uint32_t) ngx_time());
398 ctx->uid_set[2] = htonl(start_value);
399 ctx->uid_set[3] = htonl(sequencer_v2);
400 sequencer_v2 += 0x100;
401 if (sequencer_v2 < 0x03030302) {
402 sequencer_v2 = 0x03030302;
407 ctx->uid_set[0] = ctx->uid_got[0];
408 ctx->uid_set[1] = ctx->uid_got[1];
409 ctx->uid_set[2] = ctx->uid_got[2];
410 ctx->uid_set[3] = ctx->uid_got[3];
413 len = conf->name.len + 1 + ngx_base64_encoded_length(16) + conf->path.len;
416 len += sizeof(expires) - 1 + 2;
419 if (conf->domain.len) {
420 len += conf->domain.len;
423 cookie = ngx_pnalloc(r->pool, len);
424 if (cookie == NULL) {
428 p = ngx_copy(cookie, conf->name.data, conf->name.len);
431 if (ctx->uid_got[3] == 0) {
433 src.data = (u_char *) ctx->uid_set;
436 ngx_encode_base64(&dst, &src);
441 *(p - 2) = conf->mark;
445 p = ngx_cpymem(p, ctx->cookie.data, 22);
450 if (conf->expires == NGX_HTTP_USERID_MAX_EXPIRES) {
451 p = ngx_cpymem(p, expires, sizeof(expires) - 1);
453 } else if (conf->expires) {
454 p = ngx_cpymem(p, expires, sizeof("; expires=") - 1);
455 p = ngx_http_cookie_time(p, ngx_time() + conf->expires);
458 p = ngx_copy(p, conf->domain.data, conf->domain.len);
460 p = ngx_copy(p, conf->path.data, conf->path.len);
462 set_cookie = ngx_list_push(&r->headers_out.headers);
463 if (set_cookie == NULL) {
467 set_cookie->hash = 1;
468 set_cookie->key.len = sizeof("Set-Cookie") - 1;
469 set_cookie->key.data = (u_char *) "Set-Cookie";
470 set_cookie->value.len = p - cookie;
471 set_cookie->value.data = cookie;
473 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
474 "uid cookie: \"%V\"", &set_cookie->value);
476 if (conf->p3p.len == 0) {
480 p3p = ngx_list_push(&r->headers_out.headers);
486 p3p->key.len = sizeof("P3P") - 1;
487 p3p->key.data = (u_char *) "P3P";
488 p3p->value = conf->p3p;
495 ngx_http_userid_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
496 ngx_str_t *name, uint32_t *uid)
498 v->len = name->len + sizeof("=00001111222233334444555566667777") - 1;
499 v->data = ngx_pnalloc(r->pool, v->len);
500 if (v->data == NULL) {
508 ngx_sprintf(v->data, "%V=%08XD%08XD%08XD%08XD",
509 name, uid[0], uid[1], uid[2], uid[3]);
516 ngx_http_userid_add_variables(ngx_conf_t *cf)
518 ngx_http_variable_t *var;
520 var = ngx_http_add_variable(cf, &ngx_http_userid_got, NGX_HTTP_VAR_NOHASH);
525 var->get_handler = ngx_http_userid_got_variable;
527 var = ngx_http_add_variable(cf, &ngx_http_userid_set, NGX_HTTP_VAR_NOHASH);
532 var->get_handler = ngx_http_userid_set_variable;
539 ngx_http_userid_create_conf(ngx_conf_t *cf)
541 ngx_http_userid_conf_t *conf;
543 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_userid_conf_t));
545 return NGX_CONF_ERROR;
549 * set by ngx_pcalloc():
551 * conf->name.len = 0;
552 * conf->name.date = NULL;
553 * conf->domain.len = 0;
554 * conf->domain.date = NULL;
555 * conf->path.len = 0;
556 * conf->path.date = NULL;
558 * conf->p3p.date = NULL;
561 conf->enable = NGX_CONF_UNSET_UINT;
562 conf->service = NGX_CONF_UNSET;
563 conf->expires = NGX_CONF_UNSET;
564 conf->mark = (u_char) '\xFF';
571 ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent, void *child)
573 ngx_http_userid_conf_t *prev = parent;
574 ngx_http_userid_conf_t *conf = child;
576 ngx_conf_merge_uint_value(conf->enable, prev->enable,
577 NGX_HTTP_USERID_OFF);
579 ngx_conf_merge_str_value(conf->name, prev->name, "uid");
580 ngx_conf_merge_str_value(conf->domain, prev->domain, "");
581 ngx_conf_merge_str_value(conf->path, prev->path, "; path=/");
582 ngx_conf_merge_str_value(conf->p3p, prev->p3p, "");
584 ngx_conf_merge_value(conf->service, prev->service, NGX_CONF_UNSET);
585 ngx_conf_merge_sec_value(conf->expires, prev->expires, 0);
587 if (conf->mark == (u_char) '\xFF') {
588 if (prev->mark == (u_char) '\xFF') {
591 conf->mark = prev->mark;
600 ngx_http_userid_init(ngx_conf_t *cf)
602 ngx_http_next_header_filter = ngx_http_top_header_filter;
603 ngx_http_top_header_filter = ngx_http_userid_filter;
610 ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data)
612 ngx_str_t *domain = data;
616 if (ngx_strcmp(domain->data, "none") == 0) {
618 domain->data = (u_char *) "";
623 new = ngx_pnalloc(cf->pool, sizeof("; domain=") - 1 + domain->len);
625 return NGX_CONF_ERROR;
628 p = ngx_cpymem(new, "; domain=", sizeof("; domain=") - 1);
629 ngx_memcpy(p, domain->data, domain->len);
631 domain->len += sizeof("; domain=") - 1;
639 ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data)
641 ngx_str_t *path = data;
645 new = ngx_pnalloc(cf->pool, sizeof("; path=") - 1 + path->len);
647 return NGX_CONF_ERROR;
650 p = ngx_cpymem(new, "; path=", sizeof("; path=") - 1);
651 ngx_memcpy(p, path->data, path->len);
653 path->len += sizeof("; path=") - 1;
661 ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
663 ngx_http_userid_conf_t *ucf = conf;
667 if (ucf->expires != NGX_CONF_UNSET) {
668 return "is duplicate";
671 value = cf->args->elts;
673 if (ngx_strcmp(value[1].data, "max") == 0) {
674 ucf->expires = NGX_HTTP_USERID_MAX_EXPIRES;
678 if (ngx_strcmp(value[1].data, "off") == 0) {
683 ucf->expires = ngx_parse_time(&value[1], 1);
684 if (ucf->expires == NGX_ERROR) {
685 return "invalid value";
688 if (ucf->expires == NGX_PARSE_LARGE_TIME) {
689 return "value must be less than 68 years";
697 ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data)
699 ngx_str_t *p3p = data;
701 if (ngx_strcmp(p3p->data, "none") == 0) {
703 p3p->data = (u_char *) "";
711 ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
713 ngx_http_userid_conf_t *ucf = conf;
717 if (ucf->mark != (u_char) '\xFF') {
718 return "is duplicate";
721 value = cf->args->elts;
723 if (ngx_strcmp(value[1].data, "off") == 0) {
728 if (value[1].len != 1
729 || !((value[1].data[0] >= '0' && value[1].data[0] <= '9')
730 || (value[1].data[0] >= 'A' && value[1].data[0] <= 'Z')
731 || (value[1].data[0] >= 'a' && value[1].data[0] <= 'z')
732 || value[1].data[0] == '='))
734 return "value must be \"off\" or a single letter, digit or \"=\"";
737 ucf->mark = value[1].data[0];
744 ngx_http_userid_init_worker(ngx_cycle_t *cycle)
748 ngx_gettimeofday(&tp);
750 /* use the most significant usec part that fits to 16 bits */
751 start_value = ((tp.tv_usec / 20) << 16) | ngx_pid;