1
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_stream.h>
11
12
13 typedef struct {
14 ngx_uint_t hash_max_size;
15 ngx_uint_t hash_bucket_size;
16 } ngx_stream_map_conf_t;
17
18
19 typedef struct {
20 ngx_hash_keys_arrays_t keys;
21
22 ngx_array_t *values_hash;
23 #if (NGX_PCRE)
24 ngx_array_t regexes;
25 #endif
26
27 ngx_stream_variable_value_t *default_value;
28 ngx_conf_t *cf;
29 unsigned hostnames:1;
30 unsigned no_cacheable:1;
31 } ngx_stream_map_conf_ctx_t;
32
33
34 typedef struct {
35 ngx_stream_map_t map;
36 ngx_stream_complex_value_t value;
37 ngx_stream_variable_value_t *default_value;
38 ngx_uint_t hostnames; /* unsigned hostnames:1 */
39 } ngx_stream_map_ctx_t;
40
41
42 static int ngx_libc_cdecl ngx_stream_map_cmp_dns_wildcards(const void *one,
43 const void *two);
44 static void *ngx_stream_map_create_conf(ngx_conf_t *cf);
45 static char *ngx_stream_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
46 void *conf);
47 static char *ngx_stream_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
48
49
50 static ngx_command_t ngx_stream_map_commands[] = {
51
52 { ngx_string("map"),
53 NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
54 ngx_stream_map_block,
55 NGX_STREAM_MAIN_CONF_OFFSET,
56 0,
57 NULL },
58
59 { ngx_string("map_hash_max_size"),
60 NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
61 ngx_conf_set_num_slot,
62 NGX_STREAM_MAIN_CONF_OFFSET,
63 offsetof(ngx_stream_map_conf_t, hash_max_size),
64 NULL },
65
66 { ngx_string("map_hash_bucket_size"),
67 NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
68 ngx_conf_set_num_slot,
69 NGX_STREAM_MAIN_CONF_OFFSET,
70 offsetof(ngx_stream_map_conf_t, hash_bucket_size),
71 NULL },
72
73 ngx_null_command
74 };
75
76
77 static ngx_stream_module_t ngx_stream_map_module_ctx = {
78 NULL, /* preconfiguration */
79 NULL, /* postconfiguration */
80
81 ngx_stream_map_create_conf, /* create main configuration */
82 NULL, /* init main configuration */
83
84 NULL, /* create server configuration */
85 NULL /* merge server configuration */
86 };
87
88
89 ngx_module_t ngx_stream_map_module = {
90 NGX_MODULE_V1,
91 &ngx_stream_map_module_ctx, /* module context */
92 ngx_stream_map_commands, /* module directives */
93 NGX_STREAM_MODULE, /* module type */
94 NULL, /* init master */
95 NULL, /* init module */
96 NULL, /* init process */
97 NULL, /* init thread */
98 NULL, /* exit thread */
99 NULL, /* exit process */
100 NULL, /* exit master */
101 NGX_MODULE_V1_PADDING
102 };
103
104
105 static ngx_int_t
ngx_stream_map_variable(ngx_stream_session_t * s,ngx_stream_variable_value_t * v,uintptr_t data)106 ngx_stream_map_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
107 uintptr_t data)
108 {
109 ngx_stream_map_ctx_t *map = (ngx_stream_map_ctx_t *) data;
110
111 ngx_str_t val, str;
112 ngx_stream_complex_value_t *cv;
113 ngx_stream_variable_value_t *value;
114
115 ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
116 "stream map started");
117
118 if (ngx_stream_complex_value(s, &map->value, &val) != NGX_OK) {
119 return NGX_ERROR;
120 }
121
122 if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') {
123 val.len--;
124 }
125
126 value = ngx_stream_map_find(s, &map->map, &val);
127
128 if (value == NULL) {
129 value = map->default_value;
130 }
131
132 if (!value->valid) {
133 cv = (ngx_stream_complex_value_t *) value->data;
134
135 if (ngx_stream_complex_value(s, cv, &str) != NGX_OK) {
136 return NGX_ERROR;
137 }
138
139 v->valid = 1;
140 v->no_cacheable = 0;
141 v->not_found = 0;
142 v->len = str.len;
143 v->data = str.data;
144
145 } else {
146 *v = *value;
147 }
148
149 ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
150 "stream map: \"%V\" \"%v\"", &val, v);
151
152 return NGX_OK;
153 }
154
155
156 static void *
ngx_stream_map_create_conf(ngx_conf_t * cf)157 ngx_stream_map_create_conf(ngx_conf_t *cf)
158 {
159 ngx_stream_map_conf_t *mcf;
160
161 mcf = ngx_palloc(cf->pool, sizeof(ngx_stream_map_conf_t));
162 if (mcf == NULL) {
163 return NULL;
164 }
165
166 mcf->hash_max_size = NGX_CONF_UNSET_UINT;
167 mcf->hash_bucket_size = NGX_CONF_UNSET_UINT;
168
169 return mcf;
170 }
171
172
173 static char *
ngx_stream_map_block(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)174 ngx_stream_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
175 {
176 ngx_stream_map_conf_t *mcf = conf;
177
178 char *rv;
179 ngx_str_t *value, name;
180 ngx_conf_t save;
181 ngx_pool_t *pool;
182 ngx_hash_init_t hash;
183 ngx_stream_map_ctx_t *map;
184 ngx_stream_variable_t *var;
185 ngx_stream_map_conf_ctx_t ctx;
186 ngx_stream_compile_complex_value_t ccv;
187
188 if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) {
189 mcf->hash_max_size = 2048;
190 }
191
192 if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) {
193 mcf->hash_bucket_size = ngx_cacheline_size;
194
195 } else {
196 mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size,
197 ngx_cacheline_size);
198 }
199
200 map = ngx_pcalloc(cf->pool, sizeof(ngx_stream_map_ctx_t));
201 if (map == NULL) {
202 return NGX_CONF_ERROR;
203 }
204
205 value = cf->args->elts;
206
207 ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
208
209 ccv.cf = cf;
210 ccv.value = &value[1];
211 ccv.complex_value = &map->value;
212
213 if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
214 return NGX_CONF_ERROR;
215 }
216
217 name = value[2];
218
219 if (name.data[0] != '$') {
220 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
221 "invalid variable name \"%V\"", &name);
222 return NGX_CONF_ERROR;
223 }
224
225 name.len--;
226 name.data++;
227
228 var = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
229 if (var == NULL) {
230 return NGX_CONF_ERROR;
231 }
232
233 var->get_handler = ngx_stream_map_variable;
234 var->data = (uintptr_t) map;
235
236 pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
237 if (pool == NULL) {
238 return NGX_CONF_ERROR;
239 }
240
241 ctx.keys.pool = cf->pool;
242 ctx.keys.temp_pool = pool;
243
244 if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) {
245 ngx_destroy_pool(pool);
246 return NGX_CONF_ERROR;
247 }
248
249 ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize);
250 if (ctx.values_hash == NULL) {
251 ngx_destroy_pool(pool);
252 return NGX_CONF_ERROR;
253 }
254
255 #if (NGX_PCRE)
256 if (ngx_array_init(&ctx.regexes, cf->pool, 2,
257 sizeof(ngx_stream_map_regex_t))
258 != NGX_OK)
259 {
260 ngx_destroy_pool(pool);
261 return NGX_CONF_ERROR;
262 }
263 #endif
264
265 ctx.default_value = NULL;
266 ctx.cf = &save;
267 ctx.hostnames = 0;
268 ctx.no_cacheable = 0;
269
270 save = *cf;
271 cf->pool = pool;
272 cf->ctx = &ctx;
273 cf->handler = ngx_stream_map;
274 cf->handler_conf = conf;
275
276 rv = ngx_conf_parse(cf, NULL);
277
278 *cf = save;
279
280 if (rv != NGX_CONF_OK) {
281 ngx_destroy_pool(pool);
282 return rv;
283 }
284
285 if (ctx.no_cacheable) {
286 var->flags |= NGX_STREAM_VAR_NOCACHEABLE;
287 }
288
289 map->default_value = ctx.default_value ? ctx.default_value:
290 &ngx_stream_variable_null_value;
291
292 map->hostnames = ctx.hostnames;
293
294 hash.key = ngx_hash_key_lc;
295 hash.max_size = mcf->hash_max_size;
296 hash.bucket_size = mcf->hash_bucket_size;
297 hash.name = "map_hash";
298 hash.pool = cf->pool;
299
300 if (ctx.keys.keys.nelts) {
301 hash.hash = &map->map.hash.hash;
302 hash.temp_pool = NULL;
303
304 if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts)
305 != NGX_OK)
306 {
307 ngx_destroy_pool(pool);
308 return NGX_CONF_ERROR;
309 }
310 }
311
312 if (ctx.keys.dns_wc_head.nelts) {
313
314 ngx_qsort(ctx.keys.dns_wc_head.elts,
315 (size_t) ctx.keys.dns_wc_head.nelts,
316 sizeof(ngx_hash_key_t), ngx_stream_map_cmp_dns_wildcards);
317
318 hash.hash = NULL;
319 hash.temp_pool = pool;
320
321 if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts,
322 ctx.keys.dns_wc_head.nelts)
323 != NGX_OK)
324 {
325 ngx_destroy_pool(pool);
326 return NGX_CONF_ERROR;
327 }
328
329 map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
330 }
331
332 if (ctx.keys.dns_wc_tail.nelts) {
333
334 ngx_qsort(ctx.keys.dns_wc_tail.elts,
335 (size_t) ctx.keys.dns_wc_tail.nelts,
336 sizeof(ngx_hash_key_t), ngx_stream_map_cmp_dns_wildcards);
337
338 hash.hash = NULL;
339 hash.temp_pool = pool;
340
341 if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts,
342 ctx.keys.dns_wc_tail.nelts)
343 != NGX_OK)
344 {
345 ngx_destroy_pool(pool);
346 return NGX_CONF_ERROR;
347 }
348
349 map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
350 }
351
352 #if (NGX_PCRE)
353
354 if (ctx.regexes.nelts) {
355 map->map.regex = ctx.regexes.elts;
356 map->map.nregex = ctx.regexes.nelts;
357 }
358
359 #endif
360
361 ngx_destroy_pool(pool);
362
363 return rv;
364 }
365
366
367 static int ngx_libc_cdecl
ngx_stream_map_cmp_dns_wildcards(const void * one,const void * two)368 ngx_stream_map_cmp_dns_wildcards(const void *one, const void *two)
369 {
370 ngx_hash_key_t *first, *second;
371
372 first = (ngx_hash_key_t *) one;
373 second = (ngx_hash_key_t *) two;
374
375 return ngx_dns_strcmp(first->key.data, second->key.data);
376 }
377
378
379 static char *
ngx_stream_map(ngx_conf_t * cf,ngx_command_t * dummy,void * conf)380 ngx_stream_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
381 {
382 u_char *data;
383 size_t len;
384 ngx_int_t rv;
385 ngx_str_t *value, v;
386 ngx_uint_t i, key;
387 ngx_stream_map_conf_ctx_t *ctx;
388 ngx_stream_complex_value_t cv, *cvp;
389 ngx_stream_variable_value_t *var, **vp;
390 ngx_stream_compile_complex_value_t ccv;
391
392 ctx = cf->ctx;
393
394 value = cf->args->elts;
395
396 if (cf->args->nelts == 1
397 && ngx_strcmp(value[0].data, "hostnames") == 0)
398 {
399 ctx->hostnames = 1;
400 return NGX_CONF_OK;
401 }
402
403 if (cf->args->nelts == 1
404 && ngx_strcmp(value[0].data, "volatile") == 0)
405 {
406 ctx->no_cacheable = 1;
407 return NGX_CONF_OK;
408 }
409
410 if (cf->args->nelts != 2) {
411 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
412 "invalid number of the map parameters");
413 return NGX_CONF_ERROR;
414 }
415
416 if (ngx_strcmp(value[0].data, "include") == 0) {
417 return ngx_conf_include(cf, dummy, conf);
418 }
419
420 key = 0;
421
422 for (i = 0; i < value[1].len; i++) {
423 key = ngx_hash(key, value[1].data[i]);
424 }
425
426 key %= ctx->keys.hsize;
427
428 vp = ctx->values_hash[key].elts;
429
430 if (vp) {
431 for (i = 0; i < ctx->values_hash[key].nelts; i++) {
432
433 if (vp[i]->valid) {
434 data = vp[i]->data;
435 len = vp[i]->len;
436
437 } else {
438 cvp = (ngx_stream_complex_value_t *) vp[i]->data;
439 data = cvp->value.data;
440 len = cvp->value.len;
441 }
442
443 if (value[1].len != len) {
444 continue;
445 }
446
447 if (ngx_strncmp(value[1].data, data, len) == 0) {
448 var = vp[i];
449 goto found;
450 }
451 }
452
453 } else {
454 if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4,
455 sizeof(ngx_stream_variable_value_t *))
456 != NGX_OK)
457 {
458 return NGX_CONF_ERROR;
459 }
460 }
461
462 var = ngx_palloc(ctx->keys.pool, sizeof(ngx_stream_variable_value_t));
463 if (var == NULL) {
464 return NGX_CONF_ERROR;
465 }
466
467 v.len = value[1].len;
468 v.data = ngx_pstrdup(ctx->keys.pool, &value[1]);
469 if (v.data == NULL) {
470 return NGX_CONF_ERROR;
471 }
472
473 ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
474
475 ccv.cf = ctx->cf;
476 ccv.value = &v;
477 ccv.complex_value = &cv;
478
479 if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
480 return NGX_CONF_ERROR;
481 }
482
483 if (cv.lengths != NULL) {
484 cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_stream_complex_value_t));
485 if (cvp == NULL) {
486 return NGX_CONF_ERROR;
487 }
488
489 *cvp = cv;
490
491 var->len = 0;
492 var->data = (u_char *) cvp;
493 var->valid = 0;
494
495 } else {
496 var->len = v.len;
497 var->data = v.data;
498 var->valid = 1;
499 }
500
501 var->no_cacheable = 0;
502 var->not_found = 0;
503
504 vp = ngx_array_push(&ctx->values_hash[key]);
505 if (vp == NULL) {
506 return NGX_CONF_ERROR;
507 }
508
509 *vp = var;
510
511 found:
512
513 if (ngx_strcmp(value[0].data, "default") == 0) {
514
515 if (ctx->default_value) {
516 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
517 "duplicate default map parameter");
518 return NGX_CONF_ERROR;
519 }
520
521 ctx->default_value = var;
522
523 return NGX_CONF_OK;
524 }
525
526 #if (NGX_PCRE)
527
528 if (value[0].len && value[0].data[0] == '~') {
529 ngx_regex_compile_t rc;
530 ngx_stream_map_regex_t *regex;
531 u_char errstr[NGX_MAX_CONF_ERRSTR];
532
533 regex = ngx_array_push(&ctx->regexes);
534 if (regex == NULL) {
535 return NGX_CONF_ERROR;
536 }
537
538 value[0].len--;
539 value[0].data++;
540
541 ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
542
543 if (value[0].data[0] == '*') {
544 value[0].len--;
545 value[0].data++;
546 rc.options = NGX_REGEX_CASELESS;
547 }
548
549 rc.pattern = value[0];
550 rc.err.len = NGX_MAX_CONF_ERRSTR;
551 rc.err.data = errstr;
552
553 regex->regex = ngx_stream_regex_compile(ctx->cf, &rc);
554 if (regex->regex == NULL) {
555 return NGX_CONF_ERROR;
556 }
557
558 regex->value = var;
559
560 return NGX_CONF_OK;
561 }
562
563 #endif
564
565 if (value[0].len && value[0].data[0] == '\\') {
566 value[0].len--;
567 value[0].data++;
568 }
569
570 rv = ngx_hash_add_key(&ctx->keys, &value[0], var,
571 (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0);
572
573 if (rv == NGX_OK) {
574 return NGX_CONF_OK;
575 }
576
577 if (rv == NGX_DECLINED) {
578 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
579 "invalid hostname or wildcard \"%V\"", &value[0]);
580 }
581
582 if (rv == NGX_BUSY) {
583 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
584 "conflicting parameter \"%V\"", &value[0]);
585 }
586
587 return NGX_CONF_ERROR;
588 }
589