lighttpd 1.4.x https://www.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

651 lines
23 KiB

#include "first.h"
#include "base.h"
#include "buffer.h"
#include "array.h"
#include "log.h"
#include "http_header.h"
#include "sock_addr.h"
#include "configfile.h"
#include "plugin.h"
#include <string.h>
#include <stdlib.h> /* strtol */
/**
* like all glue code this file contains functions which
* are the external interface of lighttpd. The functions
* are used by the server itself and the plugins.
*
* The main-goal is to have a small library in the end
* which is linked against both and which will define
* the interface itself in the end.
*
*/
/* internal reference to srv->config_context array of (data_config *) */
static struct {
const data_config * const *data; /* (srv->config_context->data) */
uint32_t used; /* (srv->config_context->used) */
} config_reference;
void config_get_config_cond_info(config_cond_info * const cfginfo, uint32_t idx) {
const data_config * const dc = (data_config *)config_reference.data[idx];
cfginfo->comp = dc->comp;
cfginfo->cond = dc->cond;
cfginfo->string = &dc->string;
cfginfo->comp_key = dc->comp_key;
}
int config_feature_bool (const server *srv, const char *feature, int default_value) {
return srv->srvconf.feature_flags
? config_plugin_value_tobool(
array_get_element_klen(srv->srvconf.feature_flags,
feature, strlen(feature)), default_value)
: default_value;
}
int32_t config_feature_int (const server *srv, const char *feature, int32_t default_value) {
return srv->srvconf.feature_flags
? config_plugin_value_to_int32(
array_get_element_klen(srv->srvconf.feature_flags,
feature, strlen(feature)), default_value)
: default_value;
}
int config_plugin_value_tobool (const data_unset *du, int default_value)
{
if (NULL == du) return default_value;
if (du->type == TYPE_STRING) {
const buffer *b = &((const data_string *)du)->value;
if (buffer_eq_icase_slen(b, CONST_STR_LEN("enable"))
|| buffer_eq_icase_slen(b, CONST_STR_LEN("enabled"))
|| buffer_eq_icase_slen(b, CONST_STR_LEN("true"))
|| buffer_eq_icase_slen(b, CONST_STR_LEN("1")))
return 1;
else if (buffer_eq_icase_slen(b, CONST_STR_LEN("disable"))
|| buffer_eq_icase_slen(b, CONST_STR_LEN("disabled"))
|| buffer_eq_icase_slen(b, CONST_STR_LEN("false"))
|| buffer_eq_icase_slen(b, CONST_STR_LEN("0")))
return 0;
else
return default_value;
}
else if (du->type == TYPE_INTEGER)
return (0 != ((const data_integer *)du)->value);
else
return default_value;
}
int32_t config_plugin_value_to_int32 (const data_unset *du, int32_t default_value)
{
if (NULL == du) return default_value;
if (du->type == TYPE_STRING) {
const buffer * const b = &((const data_string *)du)->value;
char *err;
long v = strtol(b->ptr, &err, 10);
return (*err=='\0' && err != b->ptr && INT32_MIN <= v && v <= INT32_MAX)
? (int32_t)v
: default_value;
}
else if (du->type == TYPE_INTEGER)
return ((const data_integer *)du)->value;
else
return default_value;
}
int config_plugin_values_init_block(server * const srv, const array * const ca, const config_plugin_keys_t * const cpk, const char * const mname, config_plugin_value_t *cpv) {
/*(cpv must be list with sufficient elements to store all matches + 1)*/
int rc = 1; /* default is success */
for (int i = 0; cpk[i].ktype != T_CONFIG_UNSET; ++i) {
const data_unset * const du =
array_get_element_klen(ca, cpk[i].k, cpk[i].klen);
if (NULL == du) continue; /* not found */
cpv->k_id = i;
cpv->vtype = cpk[i].ktype;
switch (cpk[i].ktype) {
case T_CONFIG_ARRAY:
case T_CONFIG_ARRAY_KVANY:
case T_CONFIG_ARRAY_KVARRAY:
case T_CONFIG_ARRAY_KVSTRING:
case T_CONFIG_ARRAY_VLIST:
if (du->type == TYPE_ARRAY) {
cpv->v.a = &((const data_array *)du)->value;
}
else {
log_error(srv->errh, __FILE__, __LINE__,
"%s should have been a list like "
"%s = ( \"...\" )", cpk[i].k, cpk[i].k);
rc = 0;
continue;
}
switch (cpk[i].ktype) {
case T_CONFIG_ARRAY_KVANY:
if (!array_is_kvany(cpv->v.a)) {
log_error(srv->errh, __FILE__, __LINE__,
"%s should have been a list of key => values like "
"%s = ( \"...\" => \"...\", \"...\" => \"...\" )",
cpk[i].k, cpk[i].k);
rc = 0;
continue;
}
break;
case T_CONFIG_ARRAY_KVARRAY:
if (!array_is_kvarray(cpv->v.a)) {
log_error(srv->errh, __FILE__, __LINE__,
"%s should have been a list of key => list like "
"%s = ( \"...\" => ( \"...\" => \"...\" ) )",
cpk[i].k, cpk[i].k);
rc = 0;
continue;
}
break;
case T_CONFIG_ARRAY_KVSTRING:
if (!array_is_kvstring(cpv->v.a)) {
log_error(srv->errh, __FILE__, __LINE__,
"%s should have been a list of key => string values like "
"%s = ( \"...\" => \"...\", \"...\" => \"...\" )",
cpk[i].k, cpk[i].k);
rc = 0;
continue;
}
break;
case T_CONFIG_ARRAY_VLIST:
if (!array_is_vlist(cpv->v.a)) {
log_error(srv->errh, __FILE__, __LINE__,
"%s should have been a list of string values like "
"%s = ( \"...\", \"...\" )",
cpk[i].k, cpk[i].k);
rc = 0;
continue;
}
break;
/*case T_CONFIG_ARRAY:*/
default:
break;
}
break;
case T_CONFIG_STRING:
if (du->type == TYPE_STRING) {
cpv->v.b = &((const data_string *)du)->value;
}
else {
log_error(srv->errh, __FILE__, __LINE__,
"%s should have been a string like ... = \"...\"", cpk[i].k);
rc = 0;
continue;
}
break;
case T_CONFIG_SHORT:
switch(du->type) {
case TYPE_INTEGER:
cpv->v.shrt =
(unsigned short)((const data_integer *)du)->value;
break;
case TYPE_STRING: {
/* If the value came from an environment variable, then it is
* a data_string, although it may contain a number in ASCII
* decimal format. We try to interpret the string as a decimal
* short before giving up, in order to support setting numeric
* values with environment variables (e.g. port number).
*/
const char * const v = ((const data_string *)du)->value.ptr;
if (v && *v) {
char *e;
long l = strtol(v, &e, 10);
if (e != v && !*e && l >= 0 && l <= 65535) {
cpv->v.shrt = (unsigned short)l;
break;
}
}
log_error(srv->errh, __FILE__, __LINE__,
"got a string but expected a short: %s %s", cpk[i].k, v);
rc = 0;
continue;
}
default:
log_error(srv->errh, __FILE__, __LINE__,
"unexpected type for key: %s %d expected a short integer, "
"range 0 ... 65535", cpk[i].k, du->type);
rc = 0;
continue;
}
break;
case T_CONFIG_INT:
switch(du->type) {
case TYPE_INTEGER:
cpv->v.u = ((const data_integer *)du)->value;
break;
case TYPE_STRING: {
const char * const v = ((const data_string *)du)->value.ptr;
if (v && *v) {
char *e;
long l = strtol(v, &e, 10);
if (e != v && !*e && l >= 0) {
cpv->v.shrt = (unsigned int)l;
break;
}
}
log_error(srv->errh, __FILE__, __LINE__,
"got a string but expected an integer: %s %s",cpk[i].k,v);
rc = 0;
continue;
}
default:
log_error(srv->errh, __FILE__, __LINE__,
"unexpected type for key: %s %d expected an integer, "
"range 0 ... 4294967295", cpk[i].k, du->type);
rc = 0;
continue;
}
break;
case T_CONFIG_BOOL:
{
int v = config_plugin_value_tobool(du, -1);
if (-1 == v) {
log_error(srv->errh, __FILE__, __LINE__,
"ERROR: unexpected type for key: %s (string) "
"\"(enable|disable)\"", cpk[i].k);
rc = 0;
continue;
}
cpv->v.u = v;
}
break;
case T_CONFIG_LOCAL:
case T_CONFIG_UNSET:
continue;
case T_CONFIG_UNSUPPORTED:
log_error(srv->errh, __FILE__, __LINE__,
"ERROR: found unsupported key: %s (%s)", cpk[i].k, mname);
srv->srvconf.config_unsupported = 1;
continue;
case T_CONFIG_DEPRECATED:
log_error(srv->errh, __FILE__, __LINE__,
"ERROR: found deprecated key: %s (%s)", cpk[i].k, mname);
srv->srvconf.config_deprecated = 1;
continue;
}
++cpv;
}
cpv->k_id = -1; /* indicate list end */
return rc;
}
int config_plugin_values_init(server * const srv, void *p_d, const config_plugin_keys_t * const cpk, const char * const mname) {
plugin_data_base * const p = (plugin_data_base *)p_d;
array * const touched = srv->srvconf.config_touched;
unsigned char matches[4096]; /*directives matches (4k is way too many!)*/
unsigned short contexts[4096]; /*conditions matches (4k is way too many!)*/
uint32_t n = 0;
int rc = 1; /* default is success */
force_assert(sizeof(matches) >= srv->config_context->used);
/* save config reference data for later internal use
* (config_plugin_values_init() is called with same srv->config_context) */
config_reference.data = (const data_config * const *)srv->config_context->data;
config_reference.used = srv->config_context->used;
/* traverse config contexts twice: once to count, once to store matches */
for (uint32_t u = 0; u < srv->config_context->used; ++u) {
const array *ca =
((data_config const *)srv->config_context->data[u])->value;
matches[n] = 0;
for (int i = 0; cpk[i].ktype != T_CONFIG_UNSET; ++i) {
const data_unset * const du =
array_get_element_klen(ca, cpk[i].k, cpk[i].klen);
if (NULL == du) continue; /* not found */
++matches[n];
array_set_key_value(touched,cpk[i].k,cpk[i].klen,CONST_STR_LEN(""));
if (cpk[i].scope == T_CONFIG_SCOPE_SERVER && 0 != u) {
/* server scope options should be set only in server scope */
log_error(srv->errh, __FILE__, __LINE__,
"DEPRECATED: do not set server options in conditionals, "
"variable: %s", cpk[i].k);
}
}
if (matches[n]) contexts[n++] = (unsigned short)u;
}
uint32_t elts = 0;
for (uint32_t u = 0; u < n; ++u) elts += matches[u];
p->nconfig = n;
/*(+1 to include global scope, whether or not any directives exist)*/
/*(+n for extra element to end each list)*/
p->cvlist = (config_plugin_value_t *)
calloc(1+n+n+elts, sizeof(config_plugin_value_t));
force_assert(p->cvlist);
elts = 1+n;
/* shift past first element if no directives in global scope */
const uint32_t shft = (0 != n && 0 != contexts[0]);
if (shft) ++p->nconfig;
for (uint32_t u = 0; u < n; ++u) {
config_plugin_value_t * const cpv = p->cvlist+shft+u;
cpv->k_id = (int)contexts[u];
cpv->v.u2[0] = elts;
cpv->v.u2[1] = matches[u];
elts += matches[u]+1; /* +1 to end list with cpv->k_id = -1 */
}
for (uint32_t u = 0; u < n; ++u) {
const array *ca =
((data_config const *)srv->config_context->data[contexts[u]])->value;
config_plugin_value_t *cpv = p->cvlist + p->cvlist[shft+u].v.u2[0];
if (!config_plugin_values_init_block(srv, ca, cpk, mname, cpv))
rc = 0;
}
return rc;
}
__attribute_cold__
__attribute_noinline__
static void config_cond_result_trace(request_st * const r, const data_config * const dc, const int cached) {
cond_cache_t * const cache = &r->cond_cache[dc->context_ndx];
const char *msg;
switch (cache->result) {
case COND_RESULT_UNSET: msg = "unset"; break;
case COND_RESULT_SKIP: msg = "skipped"; break;
case COND_RESULT_FALSE: msg = "false"; break;
case COND_RESULT_TRUE: msg = "true"; break;
default: msg = "invalid cond_result_t"; break;
}
log_error(r->conf.errh, __FILE__, __LINE__, "%d (%s) result: %s (cond: %s)",
dc->context_ndx, &"uncached"[cached ? 2 : 0], msg, dc->key.ptr);
}
static cond_result_t config_check_cond_nocache(request_st *r, const data_config *dc, int debug_cond, cond_cache_t *cache);
static cond_result_t config_check_cond_nocache_calc(request_st * const r, const data_config * const dc, const int debug_cond, cond_cache_t * const cache) {
cache->result = config_check_cond_nocache(r, dc, debug_cond, cache);
if (debug_cond) config_cond_result_trace(r, dc, 0);
return cache->result;
}
static cond_result_t config_check_cond_cached(request_st * const r, const data_config * const dc, const int debug_cond) {
cond_cache_t * const cache = &r->cond_cache[dc->context_ndx];
if (COND_RESULT_UNSET != cache->result) {
if (debug_cond) config_cond_result_trace(r, dc, 1);
return cache->result;
}
return config_check_cond_nocache_calc(r, dc, debug_cond, cache);
}
static int data_config_pcre_exec(const data_config *dc, cond_cache_t *cache, const buffer *b, cond_match_t *cond_match);
static cond_result_t config_check_cond_nocache(request_st * const r, const data_config * const dc, const int debug_cond, cond_cache_t * const cache) {
/* check parent first */
if (dc->parent && dc->parent->context_ndx) {
/**
* a nested conditional
*
* if the parent is not decided yet or false, we can't be true either
*/
if (debug_cond) {
log_error(r->conf.errh, __FILE__, __LINE__, "go parent %s", dc->parent->key.ptr);
}
switch (config_check_cond_cached(r, dc->parent, debug_cond)) {
case COND_RESULT_UNSET:
/* decide later */
return COND_RESULT_UNSET;
case COND_RESULT_SKIP:
case COND_RESULT_FALSE:
/* failed precondition */
return COND_RESULT_SKIP;
case COND_RESULT_TRUE:
/* proceed */
break;
}
}
if (dc->prev) {
/**
* a else branch; can only be executed if the previous branch
* was evaluated as "false" (not unset/skipped/true)
*/
if (debug_cond) {
log_error(r->conf.errh, __FILE__, __LINE__, "go prev %s", dc->prev->key.ptr);
}
/* make sure prev is checked first */
switch (config_check_cond_cached(r, dc->prev, debug_cond)) {
case COND_RESULT_UNSET:
/* decide later */
return COND_RESULT_UNSET;
case COND_RESULT_SKIP:
case COND_RESULT_TRUE:
/* failed precondition */
return COND_RESULT_SKIP;
case COND_RESULT_FALSE:
/* proceed */
break;
}
}
if (!(r->conditional_is_valid & (1 << dc->comp))) {
if (debug_cond) {
log_error(r->conf.errh, __FILE__, __LINE__,
"%d %s not available yet", dc->comp, dc->key.ptr);
}
return COND_RESULT_UNSET;
}
/* if we had a real result before and weren't cleared just return it */
switch (cache->local_result) {
case COND_RESULT_TRUE:
case COND_RESULT_FALSE:
return cache->local_result;
default:
break;
}
if (CONFIG_COND_ELSE == dc->cond)
return (cache->local_result = COND_RESULT_TRUE);
/* remember result of local condition for a partial reset */
/* pass the rules */
static struct const_char_buffer {
const char *ptr;
uint32_t used;
uint32_t size;
} empty_string = { "", 1, 0 };
const buffer *l;
switch (dc->comp) {
case COMP_HTTP_HOST:
l = &r->uri.authority;
break;
case COMP_HTTP_REMOTE_IP:
l = &r->con->dst_addr_buf;
break;
case COMP_HTTP_SCHEME:
l = &r->uri.scheme;
break;
case COMP_HTTP_URL:
l = &r->uri.path;
break;
case COMP_HTTP_QUERY_STRING:
l = &r->uri.query;
break;
case COMP_SERVER_SOCKET:
l = r->con->srv_socket->srv_token;
break;
case COMP_HTTP_REQUEST_HEADER:
l = http_header_request_get(r, dc->ext, BUF_PTR_LEN(&dc->comp_tag));
if (NULL == l) l = (buffer *)&empty_string;
break;
case COMP_HTTP_REQUEST_METHOD:
l = http_method_buf(r->http_method);
break;
default:
return (cache->local_result = COND_RESULT_FALSE);
}
if (__builtin_expect( (buffer_is_blank(l)), 0))
l = (buffer *)&empty_string;
if (debug_cond)
log_error(r->conf.errh, __FILE__, __LINE__,
"%s compare to %s", dc->comp_key, l->ptr);
int match;
switch(dc->cond) {
case CONFIG_COND_NE:
case CONFIG_COND_EQ:
match = (dc->cond == CONFIG_COND_EQ);
if (dc->comp == COMP_HTTP_HOST && dc->string.ptr[0] != '/') {
uint_fast32_t llen = buffer_clen(l);
uint_fast32_t dlen = buffer_clen(&dc->string);
/* check names match, whether or not :port suffix present */
/*(not strictly checking for port match for alt-svc flexibility,
* though if strings are same length, port is checked for match)*/
/*(r->uri.authority not strictly checked here for excess ':')*/
/*(r->uri.authority lowercased during request parsing)*/
if (llen && llen != dlen) {
match ^= ((llen > dlen)
? l->ptr[dlen] == ':' && llen - dlen <= 5
: dc->string.ptr[(dlen = llen)] == ':')
&& 0 == memcmp(l->ptr, dc->string.ptr, dlen);
break;
}
}
else if (dc->comp == COMP_HTTP_REMOTE_IP && dc->string.ptr[0] != '/') {
/* CIDR mask comparisons only supported for COND_EQ, COND_NE */
/* compare using structure data after end of string
* (generated at startup when parsing config) */
const sock_addr * const addr = (sock_addr *)
(((uintptr_t)dc->string.ptr + dc->string.used + 1 + 7) & ~7);
int bits = ((unsigned char *)dc->string.ptr)[dc->string.used];
match ^= (bits)
? sock_addr_is_addr_eq_bits(addr, &r->con->dst_addr, bits)
: sock_addr_is_addr_eq(addr, &r->con->dst_addr);
break;
}
match ^= (buffer_is_equal(l, &dc->string));
break;
case CONFIG_COND_NOMATCH:
case CONFIG_COND_MATCH: {
cond_match_t *cond_match = r->cond_match + dc->context_ndx;
match = (dc->cond == CONFIG_COND_MATCH);
match ^= (data_config_pcre_exec(dc, cache, l, cond_match) > 0);
break;
}
default:
match = 1; /* return (cache->local_result = COND_RESULT_FALSE); below */
}
/* remember result of local condition for a partial reset */
cache->local_result = match ? COND_RESULT_FALSE : COND_RESULT_TRUE;
return cache->local_result;
}
__attribute_noinline__
static cond_result_t config_check_cond_calc(request_st * const r, const int context_ndx, cond_cache_t * const cache) {
const data_config * const dc = config_reference.data[context_ndx];
const int debug_cond = r->conf.log_condition_handling;
if (debug_cond) {
log_error(r->conf.errh, __FILE__, __LINE__,
"=== start of condition block ===");
}
return config_check_cond_nocache_calc(r, dc, debug_cond, cache);
}
/* future: might make static inline in header for plugins */
int config_check_cond(request_st * const r, const int context_ndx) {
cond_cache_t * const cache = &r->cond_cache[context_ndx];
return COND_RESULT_TRUE
== (COND_RESULT_UNSET != cache->result
? (cond_result_t)cache->result
: config_check_cond_calc(r, context_ndx, cache));
}
/* if we reset the cache result for a node, we also need to clear all
* child nodes and else-branches*/
static void config_cond_clear_node(cond_cache_t * const cond_cache, const data_config * const dc) {
/* if a node is "unset" all children are unset too */
if (cond_cache[dc->context_ndx].result != COND_RESULT_UNSET) {
cond_cache[dc->context_ndx].result = COND_RESULT_UNSET;
for (uint32_t i = 0; i < dc->children.used; ++i) {
const data_config *dc_child = dc->children.data[i];
if (NULL == dc_child->prev) {
/* only call for first node in if-else chain */
config_cond_clear_node(cond_cache, dc_child);
}
}
if (NULL != dc->next) config_cond_clear_node(cond_cache, dc->next);
}
}
/**
* reset the config-cache for a named item
*
* if the item is COND_LAST_ELEMENT we reset all items
*/
void config_cond_cache_reset_item(request_st * const r, comp_key_t item) {
cond_cache_t * const cond_cache = r->cond_cache;
const data_config * const * const data = config_reference.data;
const uint32_t used = config_reference.used;
for (uint32_t i = 0; i < used; ++i) {
const data_config * const dc = data[i];
if (item == dc->comp) {
/* clear local_result */
cond_cache[i].local_result = COND_RESULT_UNSET;
/* clear result in subtree (including the node itself) */
config_cond_clear_node(cond_cache, dc);
}
}
}
/**
* reset the config cache to its initial state at connection start
*/
void config_cond_cache_reset(request_st * const r) {
/* resetting all entries; no need to follow children as in config_cond_cache_reset_item */
/* static_assert(0 == COND_RESULT_UNSET); */
const uint32_t used = config_reference.used;
if (used > 1)
memset(r->cond_cache, 0, used*sizeof(cond_cache_t));
}
#ifdef HAVE_PCRE_H
#include <pcre.h>
#endif
static int data_config_pcre_exec(const data_config *dc, cond_cache_t *cache, const buffer *b, cond_match_t *cond_match) {
#ifdef HAVE_PCRE_H
#ifndef elementsof
#define elementsof(x) (sizeof(x) / sizeof(x[0]))
#endif
cache->patterncount =
pcre_exec(dc->regex, dc->regex_study, BUF_PTR_LEN(b), 0, 0,
cond_match->matches, elementsof(cond_match->matches));
if (cache->patterncount > 0)
cond_match->comp_value = b; /*holds pointer to b (!) for pattern subst*/
return cache->patterncount;
#else
UNUSED(dc);
UNUSED(cache);
UNUSED(b);
UNUSED(cond_match);
return 0;
#endif
}