/* This file is part of vmod-tbf Copyright (C) 2013-2015 Sergey Poznyakoff Vmod-tbf is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Vmod-tbf is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with vmod-tbf. If not, see . */ #include #include #include #include #include #include #include "vrt.h" #include "vcc_if.h" #include "pthread.h" #if VARNISHVERSION == 3 # include "bin/varnishd/cache.h" # define VCL_VOID void # define VCL_STRING const char * # define VCL_REAL double # define VCL_DURATION double # define WS_Copy(w,s,l) WS_Dup(w,s) # define VARIABLE_CTX struct sess * # define WSPTR(s) ((s)->wrk->ws) #else # include "bin/varnishd/cache/cache.h" # define VARIABLE_CTX const struct vrt_ctx * # define WSPTR(s) ((s)->ws) #endif /* |hash_size| defines a sequence of symbol table sizes. These are prime numbers, each of which is approximately twice its predecessor. */ static unsigned int hash_size[] = { 7, 17, 37, 101, 229, 487, 1009, 2039, 4091, 8191, 16411 }; /* max_rehash keeps the number of entries in |hash_size| table. */ static unsigned int max_rehash = sizeof(hash_size) / sizeof(hash_size[0]); enum variable_type { variable_unset, variable_string, variable_int, variable_real, variable_duration }; static char *typestr[] = { "UNSET", "STRING", "INT", "REAL", "DURATION" }; union value { char *s; int i; double r; double d; }; struct variable { char *name; enum variable_type type; union value v; }; struct symtab { uint32_t vxid; unsigned int hash_num; /* Index to hash_size table */ struct variable **tab; }; static struct variable * var_alloc(const char *name) { struct variable *ent; ent = malloc(sizeof(struct variable)); AN(ent); ent->name = strdup(name); ent->type = variable_unset; AN(ent->name); return ent; } static unsigned hash_string(const char *name, unsigned long hashsize) { unsigned i; for (i = 0; *name; name++) { i <<= 1; i ^= *(unsigned char*) name; } return i % hashsize; } static unsigned var_hash(struct variable *var, unsigned long hashsize) { return hash_string(var->name, hashsize); } static void var_free(struct variable *var) { free(var->name); if (var->type == variable_string) free(var->v.s); free(var); } static unsigned symtab_insert_pos(struct symtab *st, struct variable *elt) { unsigned i; unsigned pos = var_hash(elt, hash_size[st->hash_num]); for (i = pos; st->tab[i];) { if (++i >= hash_size[st->hash_num]) i = 0; if (i == pos) abort(); } return i; } static int symtab_rehash(struct symtab *st) { struct variable **old_tab = st->tab; struct variable **new_tab; unsigned int i; unsigned int hash_num = st->hash_num + 1; if (hash_num >= max_rehash) return E2BIG; new_tab = calloc(hash_size[hash_num], sizeof(*new_tab)); AN(new_tab); st->tab = new_tab; if (old_tab) { st->hash_num = hash_num; for (i = 0; i < hash_size[hash_num-1]; i++) { struct variable *elt = old_tab[i]; if (elt->name) { unsigned n = symtab_insert_pos(st, elt); new_tab[n] = elt; } } free(old_tab); } return 0; } static int symtab_remove(struct symtab *st, const char *name) { unsigned int pos, i, j, r; struct variable *entry; pos = hash_string(name, hash_size[st->hash_num]); for (i = pos; (entry = st->tab[i]);) { if (strcmp(entry->name, name) == 0) break; if (++i >= hash_size[st->hash_num]) i = 0; if (i == pos) return ENOENT; } var_free(entry); for (;;) { st->tab[i] = NULL; j = i; do { if (++i >= hash_size[st->hash_num]) i = 0; if (!st->tab[i]) return 0; r = hash_string(st->tab[i]->name, hash_size[st->hash_num]); } while ((j < r && r <= i) || (i < j && j < r) || (r <= i && i < j)); st->tab[j] = st->tab[i]; } return 0; } static int symtab_get_index(unsigned *idx, struct symtab *st, const char *name, int *install) { int rc; unsigned i, pos; struct variable *elem; if (!st->tab) { if (install) { rc = symtab_rehash(st); if (rc) return rc; } else return ENOENT; } pos = hash_string(name, hash_size[st->hash_num]); for (i = pos; (elem = st->tab[i]);) { if (strcmp(elem->name, name) == 0) { if (install) *install = 0; *idx = i; return 0; } if (++i >= hash_size[st->hash_num]) i = 0; if (i == pos) break; } if (!install) return ENOENT; if (!elem) { *install = 1; *idx = i; return 0; } if ((rc = symtab_rehash(st)) != 0) return rc; return symtab_get_index(idx, st, name, install); } static struct variable * symtab_lookup_or_install(struct symtab *st, const char *name, int *install) { unsigned i; int rc = symtab_get_index(&i, st, name, install); if (rc == 0) { if (install && *install == 1) { struct variable *ent = var_alloc(name); if (!ent) { errno = ENOMEM; return NULL; } st->tab[i] = ent; return ent; } else return st->tab[i]; } errno = rc; return NULL; } static void symtab_clear(struct symtab *st) { unsigned i, hs; if (!st || !st->tab) return; hs = hash_size[st->hash_num]; for (i = 0; i < hs; i++) { struct variable *elem = st->tab[i]; if (elem) { var_free(elem); st->tab[i] = NULL; } } } static struct symtab * symtab_create() { struct symtab *st = calloc(1, sizeof(*st)); AN(st); st->tab = calloc(hash_size[st->hash_num], sizeof(*st->tab)); AN(st->tab); return st; } static void symtab_free(struct symtab *st) { if (st) { symtab_clear(st); free(st->tab); free(st); } } static struct symtab **symtabv; static size_t symtabc; static pthread_mutex_t symtab_mtx = PTHREAD_MUTEX_INITIALIZER; static struct symtab *global_symtab; static pthread_mutex_t global_mtx = PTHREAD_MUTEX_INITIALIZER; VCL_VOID vmod_global_set(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value) { struct variable *var; int inst; AZ(pthread_mutex_lock(&global_mtx)); if (!global_symtab) global_symtab = symtab_create(); if (!value) symtab_remove(global_symtab, name); else { inst = 1; var = symtab_lookup_or_install(global_symtab, name, &inst); if (!inst && var->type == variable_string) free(var->v.s); var->type = variable_string; var->v.s = strdup(value); AN(var->v.s); } AZ(pthread_mutex_unlock(&global_mtx)); } VCL_STRING vmod_global_get(VARIABLE_CTX ctx, VCL_STRING name) { char *s; struct variable *var; AZ(pthread_mutex_lock(&global_mtx)); if (!global_symtab) global_symtab = symtab_create(); var = symtab_lookup_or_install(global_symtab, name, NULL); if (var && var->type == variable_string) { s = WS_Copy(ctx->ws, var->v.s, -1); AN(s); } else s = NULL; AZ(pthread_mutex_unlock(&global_mtx)); return s; } VCL_BOOL vmod_global_defined(VARIABLE_CTX ctx, VCL_STRING name) { return !!vmod_global_get(ctx, name); } VCL_VOID vmod_global_clear(VARIABLE_CTX ctx) { AZ(pthread_mutex_lock(&global_mtx)); symtab_clear(global_symtab); AZ(pthread_mutex_unlock(&global_mtx)); } VCL_VOID vmod_global_unset(VARIABLE_CTX ctx, VCL_STRING name) { AZ(pthread_mutex_lock(&global_mtx)); symtab_remove(global_symtab, name); AZ(pthread_mutex_unlock(&global_mtx)); } static struct symtab * get_symtab(VARIABLE_CTX ctx) { struct symtab *st; int fd = ctx->req->sp->fd; assert(fd >= 0); AZ(pthread_mutex_lock(&symtab_mtx)); if (symtabc <= fd) { size_t n = fd + 1; symtabv = realloc(symtabv, n * sizeof(symtabv[0])); while (symtabc < n) symtabv[symtabc++] = NULL; } if (!symtabv[fd]) symtabv[fd] = symtab_create(); st = symtabv[fd]; if (st->vxid != ctx->req->sp->vxid) symtab_clear(st); st->vxid = ctx->req->sp->vxid; AZ(pthread_mutex_unlock(&symtab_mtx)); return st; } #define getvar(vt, name) symtab_lookup_or_install(vt, name, NULL) static struct variable * defvar(struct symtab *vt, const char *name, enum variable_type t, union value *val) { int inst = 1; struct variable *var = symtab_lookup_or_install(vt, name, &inst); if (!inst && var->type == variable_string) free(var->v.s); if (t != variable_unset && val) { var->type = t; var->v = *val; } else var->type = variable_unset; return var; } VCL_VOID vmod_clear(VARIABLE_CTX ctx) { symtab_clear(get_symtab(ctx)); } VCL_STRING vmod_get_string(VARIABLE_CTX ctx, VCL_STRING name) { struct variable *var = getvar(get_symtab(ctx), name); if (var && var->type == variable_string) return var->v.s; return NULL; } VCL_VOID vmod_set_string(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value) { struct symtab *vt = get_symtab(ctx); struct variable *var = defvar(vt, name, variable_unset, NULL); var->type = variable_string; var->v.s = strdup(value ? value : ""); AN(var->v.s); } VCL_STRING vmod_get(VARIABLE_CTX ctx, VCL_STRING name) { return vmod_get_string(ctx, name); } VCL_VOID vmod_set(VARIABLE_CTX ctx, VCL_STRING name, VCL_STRING value) { vmod_set_string(ctx, name, value); } #define __cat__(a,b) a ## b #define DEFGET(r_type, vcl_type, memb) \ vcl_type \ __cat__(vmod_get_,r_type)(VARIABLE_CTX ctx, VCL_STRING name) \ { \ struct variable *var = getvar(get_symtab(ctx), name); \ if (var && var->type == __cat__(variable_,r_type)) \ return var->v.memb; \ return 0; \ } #define DEFSET(r_type, vcl_type, memb) \ VCL_VOID \ __cat__(vmod_set_,r_type)(VARIABLE_CTX ctx, VCL_STRING name, \ vcl_type value) \ { \ struct symtab *vt = get_symtab(ctx); \ struct variable *var = defvar(vt, name, variable_unset, NULL); \ var->type = __cat__(variable_,r_type); \ var->v.memb = value; \ } #define DEF(name, vcl_type, memb) \ DEFGET(name, vcl_type, memb) \ DEFSET(name, vcl_type, memb) DEF(int, VCL_INT, i) DEF(real, VCL_REAL, r) DEF(duration, VCL_DURATION, d) VCL_BOOL vmod_defined(VARIABLE_CTX ctx, VCL_STRING name) { return !!getvar(get_symtab(ctx), name); } VCL_STRING vmod_type_of(VARIABLE_CTX ctx, VCL_STRING name) { struct variable *var = getvar(get_symtab(ctx), name); return typestr[var ? var->type : variable_unset]; } VCL_VOID vmod_unset(VARIABLE_CTX ctx, VCL_STRING name) { symtab_remove(get_symtab(ctx), name); } static void log_error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_DAEMON|LOG_NOTICE, fmt, ap); va_end(ap); } #define S(s) ((s) ? (s) : "NULL") struct vardef { struct vardef *next; enum variable_type type; char *name; char *repl; }; static struct vardef * vardef_new(enum variable_type type, char const *nameptr, size_t namelen, char const *replptr, size_t repllen) { struct vardef *def = malloc(sizeof(def[0]) + namelen + repllen + 2); def->next = NULL; def->type = type; def->name = (char*)(def + 1); memcpy(def->name, nameptr, namelen); def->name[namelen] = 0; if (replptr) { def->repl = def->name + namelen + 1; memcpy(def->repl, replptr, repllen); def->repl[repllen] = 0; } else def->repl = NULL; return def; } static void vardef_free(struct vardef *v) { while (v) { struct vardef *n = v->next; free(v); v = n; } } static struct vardef * vardef_find(struct vardef *v, const char *name, size_t len) { for (; v; v = v->next) if (strlen(v->name) == len && memcmp(v->name, name, len) == 0) break; return v; } static enum variable_type str2type(const char *s, size_t len) { int i; for (i = 0; i < sizeof(typestr)/sizeof(typestr[0]); i++) { if (strlen(typestr[i]) < len) continue; if (strncasecmp(typestr[i], s, len) == 0) return i; } return variable_unset; } static double str2duration(const char *str) { char *p; double r; errno = 0; r = strtod(str, &p); if (errno) return 0.0; while (*p && isspace(*p)) p++; switch (*p++) { case 's': break; case 'm': if (*p == 's') { r *= 1e-3; p++; } else r *= 60.; break; case 'h': r *= 60.*60.; break; case 'd': r *= 60.*60.*24.; break; case 'w': r *= 60.*60.*24.*7.; break; case 'y': r *= 60.*60.*24.*365.; break; default: return 0.0; } while (*p && isspace(*p)) p++; return *p ? 0 : r; } static char * bref_expand(const char *str, const char *input, pcre *re, int ovsize, int *ovector) { size_t rlen = 0; char *rbase = NULL; char *rptr; int nm = 2*ovsize/3; const char *p; for (p = str; *p; ) { if (*p == '\\' && strchr("123456789", p[1])) { int n = 2*(p[1] - '0'); if (n < nm) { rlen += ovector[n+1] - ovector[n]; p += 2; continue; } } ++p; ++rlen; } rbase = malloc(rlen + 1); AN(rbase); p = str; rptr = rbase; while (*p) { if (*p == '\\' && strchr("123456789", p[1])) { int n = 2*(p[1] - '0'); if (n < nm) { memcpy(rptr, input + ovector[n], ovector[n+1] - ovector[n]); rptr += ovector[n+1] - ovector[n]; p += 2; continue; } } *rptr++ = *p++; } *rptr = 0; return rbase; } static void setval(union value *val, const char *s, enum variable_type type, char **err) { char *p; long lval; *err = NULL; switch (type) { case variable_string: val->s = strdup(s); AN(val->s); break; case variable_int: errno = 0; lval = strtol(s, &p, 10); if (*p) { *err = "not an integer"; val->i = 0; } else if (errno) { *err = strerror(errno); val->i = 0; } else if (lval < INT_MIN || lval > INT_MAX) { *err = "value out of range"; val->i = 0; } else val->i = lval; break; case variable_real: errno = 0; val->r = strtod(s, &p); if (*p) { *err = "not a valid number"; val->r = 0; } else if (errno) { *err = strerror(errno); val->r = 0; } break; case variable_duration: val->d = str2duration(s); break; default: abort(); } } VCL_VOID vmod_regset(VARIABLE_CTX ctx, VCL_STRING vars, VCL_STRING rxs, VCL_STRING input) { struct symtab *vt = get_symtab(ctx); struct vardef *head = NULL, *tail = NULL, *def; size_t count = 0; size_t n; const char *v = vars; const char *error_ptr; int error_offset; int cflags = 0; union value value; int ovsize; int *ovector; int i; int rc; pcre *re; if (!vars || !rxs || !input) { log_error("variable.regset: bad arguments: vars=%s, rxs=%s, input=%s", S(vars), S(rxs), S(input)); return; } while (*v) { char const *nameptr = v; size_t n = strcspn(v, ":=,"); size_t namelen = n; char const *replptr; size_t repllen; char rbuf[3]; enum variable_type type; int delim; v += n; delim = *v ? *v++ : 0; if (delim == ':') { n = strcspn(v, "=,"); type = str2type(v, n); v += n; delim = *v ? *v++ : 0; } else type = variable_string; if (delim == '=') { n = strcspn(v, ","); replptr = v; repllen = n; v += n; delim = *v ? *v++ : 0; } else { rbuf[0] = '\\'; rbuf[1] = count + '1'; rbuf[2] = 0; replptr = rbuf; repllen = 2; } def = vardef_new(type, nameptr, namelen, replptr, repllen); if (tail) tail->next = def; else head = def; tail = def; ++count; } re = pcre_compile(rxs, cflags, &error_ptr, &error_offset, NULL); if (!re) { log_error("variable.regset: %s: compilation failed near %s: %s", rxs, rxs + error_offset, error_ptr); vardef_free(head); return; } rc = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &n); if (rc) { log_error("pcre_fullinfo() failed: %d", rc); vardef_free(head); return; } if (n < count) { log_error("variable.regset: %s: too few subexpressions to satisfy %s", rxs, vars); vardef_free(head); return; } ovsize = (n + 1) * 3; ovector = calloc(ovsize, sizeof(*ovector)); rc = pcre_exec(re, 0, input, strlen(input), 0, 0, ovector, ovsize); if (rc <= 0) { if (rc != PCRE_ERROR_NOMATCH) log_error("variable.regset: pcre_exec failed: %d", rc); vardef_free(head); return; } for (def = head; def; def = def->next, i++) { char *s = bref_expand(def->repl, input, re, ovsize, ovector); char *err; setval(&value, s, def->type, &err); if (err) log_error("variable.regset: %s(%s)#%d: %s", rxs, input, i, err); defvar(vt, def->name, def->type, &value); free(s); } pcre_free(re); free(ovector); vardef_free(head); } static unsigned long hex2ul(char hex) { if (hex >= '0' && hex <= '9') return hex - '0'; if (hex >= 'a' && hex <= 'z') return hex - 'a' + 10; if (hex >= 'A' && hex <= 'Z') return hex - 'A' + 10; return -1; } /* From RFC 1738, section 2.2 */ static void xdecode(char *s) { char *d; d = strchr(s, '%'); if (!d) return; for (s = d; *s; ) { if (*s == '%' && hex2ul(s[1]) != -1 && hex2ul(s[2]) != -1) { *d++ = (hex2ul(s[1]) << 4) + hex2ul(s[2]); s += 3; } else *d++ = *s++; } *d = 0; } static void define_param(struct symtab *vt, struct vardef *def, char const *valptr, size_t vallen) { char *s; char *err; union value value; s = malloc(vallen+1); AN(s); memcpy(s, valptr, vallen); s[vallen] = 0; xdecode(s); setval(&value, s, def->type, &err); if (err) log_error("variable.queryset: %s", err); defvar(vt, def->name, def->type, &value); free(s); } VCL_VOID vmod_queryset(VARIABLE_CTX ctx, VCL_STRING vars, VCL_STRING query) { struct symtab *vt = get_symtab(ctx); struct vardef *head = NULL, *tail = NULL, *def; size_t count = 0; const char *v = vars; while (*v) { char const *nameptr = v; size_t n = strcspn(v, ":,"); size_t namelen = n; enum variable_type type; int delim; v += n; delim = *v ? *v++ : 0; if (delim == ':') { n = strcspn(v, ","); type = str2type(v, n); v += n; delim = *v ? *v++ : 0; } else type = variable_string; def = vardef_new(type, nameptr, namelen, NULL, 0); if (tail) tail->next = def; else head = def; tail = def; ++count; } if (!query || !*query) { for (def = head; def; def = def->next) symtab_remove(vt, def->name); vardef_free(head); return; } v = query; while (*v) { char const *paramptr = v; size_t n = strcspn(v, "=&"); size_t paramlen = n; char const *valptr = NULL; size_t vallen = 0; int delim; v += n; delim = *v ? *v++ : 0; if (delim == '=') { n = strcspn(v, "&"); valptr = v; vallen = n; v += n; delim = *v ? *v++ : 0; } if (head) { def = vardef_find(head, paramptr, paramlen); if (def) define_param(vt, def, valptr, vallen); } else { def = vardef_new(variable_string, paramptr, paramlen, NULL, 0); define_param(vt, def, valptr, vallen); vardef_free(def); } } vardef_free(head); }