summaryrefslogtreecommitdiff
path: root/data/lighttpd/lighttpd-1.4.53/src/configfile-glue.c
diff options
context:
space:
mode:
Diffstat (limited to 'data/lighttpd/lighttpd-1.4.53/src/configfile-glue.c')
-rw-r--r--data/lighttpd/lighttpd-1.4.53/src/configfile-glue.c584
1 files changed, 584 insertions, 0 deletions
diff --git a/data/lighttpd/lighttpd-1.4.53/src/configfile-glue.c b/data/lighttpd/lighttpd-1.4.53/src/configfile-glue.c
new file mode 100644
index 000000000..94c8909ab
--- /dev/null
+++ b/data/lighttpd/lighttpd-1.4.53/src/configfile-glue.c
@@ -0,0 +1,584 @@
+#include "first.h"
+
+#include "base.h"
+#include "buffer.h"
+#include "array.h"
+#include "log.h"
+#include "fdevent.h"
+#include "http_header.h"
+#include "sock_addr.h"
+
+#include "configfile.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+/**
+ * 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.
+ *
+ */
+
+
+/* handle global options */
+
+/* parse config array */
+int config_insert_values_internal(server *srv, array *ca, const config_values_t cv[], config_scope_type_t scope) {
+ size_t i;
+ data_unset *du;
+
+ for (i = 0; cv[i].key; i++) {
+
+ if (NULL == (du = array_get_element_klen(ca, cv[i].key, strlen(cv[i].key)))) {
+ /* no found */
+
+ continue;
+ }
+
+ if ((T_CONFIG_SCOPE_SERVER == cv[i].scope)
+ && (T_CONFIG_SCOPE_SERVER != scope)) {
+ /* server scope options should only be set in server scope, not in conditionals */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "DEPRECATED: don't set server options in conditionals, variable:",
+ cv[i].key);
+ }
+
+ switch (cv[i].type) {
+ case T_CONFIG_ARRAY:
+ if (du->type == TYPE_ARRAY) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ data_unset *ds = da->value->data[j];
+ if (ds->type == TYPE_STRING || ds->type == TYPE_INTEGER || ds->type == TYPE_ARRAY) {
+ array_insert_unique(cv[i].destination, ds->fn->copy(ds));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssbsd",
+ "the value of an array can only be a string, variable:",
+ cv[i].key, "[", ds->key, "], type:", ds->type);
+
+ return -1;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss", cv[i].key, "should have been a array of strings like ... = ( \"...\" )");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_STRING:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ buffer_copy_buffer(cv[i].destination, ds->value);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss", cv[i].key, "should have been a string like ... = \"...\"");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_SHORT:
+ switch(du->type) {
+ case TYPE_INTEGER: {
+ data_integer *di = (data_integer *)du;
+
+ *((unsigned short *)(cv[i].destination)) = di->value;
+ break;
+ }
+ case TYPE_STRING: {
+ data_string *ds = (data_string *)du;
+
+ /* 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 (eg, port number).
+ */
+ if (ds->value->ptr && *ds->value->ptr) {
+ char *e;
+ long l = strtol(ds->value->ptr, &e, 10);
+ if (e != ds->value->ptr && !*e && l >=0 && l <= 65535) {
+ *((unsigned short *)(cv[i].destination)) = l;
+ break;
+ }
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "got a string but expected a short:", cv[i].key, ds->value);
+
+ return -1;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssds", "unexpected type for key:", cv[i].key, du->type, "expected a short integer, range 0 ... 65535");
+ return -1;
+ }
+ break;
+ case T_CONFIG_INT:
+ switch(du->type) {
+ case TYPE_INTEGER: {
+ data_integer *di = (data_integer *)du;
+
+ *((unsigned int *)(cv[i].destination)) = di->value;
+ break;
+ }
+ case TYPE_STRING: {
+ data_string *ds = (data_string *)du;
+
+ if (ds->value->ptr && *ds->value->ptr) {
+ char *e;
+ long l = strtol(ds->value->ptr, &e, 10);
+ if (e != ds->value->ptr && !*e && l >= 0) {
+ *((unsigned int *)(cv[i].destination)) = l;
+ break;
+ }
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "got a string but expected an integer:", cv[i].key, ds->value);
+
+ return -1;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssds", "unexpected type for key:", cv[i].key, du->type, "expected an integer, range 0 ... 4294967295");
+ return -1;
+ }
+ break;
+ case T_CONFIG_BOOLEAN:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ if (buffer_is_equal_string(ds->value, CONST_STR_LEN("enable"))) {
+ *((unsigned short *)(cv[i].destination)) = 1;
+ } else if (buffer_is_equal_string(ds->value, CONST_STR_LEN("disable"))) {
+ *((unsigned short *)(cv[i].destination)) = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs", "ERROR: unexpected value for key:", cv[i].key, ds->value, "(enable|disable)");
+
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: unexpected type for key:", cv[i].key, "(string)", "\"(enable|disable)\"");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_LOCAL:
+ case T_CONFIG_UNSET:
+ break;
+ case T_CONFIG_UNSUPPORTED:
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: found unsupported key:", cv[i].key, "-", (char *)(cv[i].destination));
+
+ srv->config_unsupported = 1;
+
+ break;
+ case T_CONFIG_DEPRECATED:
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: found deprecated key:", cv[i].key, "-", (char *)(cv[i].destination));
+
+ srv->config_deprecated = 1;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int config_insert_values_global(server *srv, array *ca, const config_values_t cv[], config_scope_type_t scope) {
+ size_t i;
+ data_unset *du;
+
+ for (i = 0; cv[i].key; i++) {
+ if (NULL == (du = array_get_element_klen(ca, cv[i].key, strlen(cv[i].key)))) {
+ /* no found */
+
+ continue;
+ }
+ array_set_key_value(srv->config_touched, CONST_BUF_LEN(du->key), CONST_STR_LEN(""));
+ }
+
+ return config_insert_values_internal(srv, ca, cv, scope);
+}
+
+static const char* cond_result_to_string(cond_result_t cond_result) {
+ switch (cond_result) {
+ case COND_RESULT_UNSET: return "unset";
+ case COND_RESULT_SKIP: return "skipped";
+ case COND_RESULT_FALSE: return "false";
+ case COND_RESULT_TRUE: return "true";
+ default: return "invalid cond_result_t";
+ }
+}
+
+static int config_addrstr_eq_remote_ip_mask(server *srv, const char *addrstr, int nm_bits, sock_addr *rmt) {
+ /* special-case 0 == nm_bits to mean "all bits of the address" in addrstr */
+ sock_addr addr;
+ if (1 == sock_addr_inet_pton(&addr, addrstr, AF_INET, 0)) {
+ if (nm_bits > 32) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "ERROR: ipv4 netmask too large:", nm_bits);
+ return -1;
+ }
+ } else if (1 == sock_addr_inet_pton(&addr, addrstr, AF_INET6, 0)) {
+ if (nm_bits > 128) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "ERROR: ipv6 netmask too large:", nm_bits);
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ERROR: ip addr is invalid:", addrstr);
+ return -1;
+ }
+ return sock_addr_is_addr_eq_bits(&addr, rmt, nm_bits);
+}
+
+static int config_addrbuf_eq_remote_ip_mask(server *srv, buffer *string, char *nm_slash, sock_addr *rmt) {
+ char *err;
+ int nm_bits = strtol(nm_slash + 1, &err, 10);
+ size_t addrstrlen = (size_t)(nm_slash - string->ptr);
+ char addrstr[64]; /*(larger than INET_ADDRSTRLEN and INET6_ADDRSTRLEN)*/
+
+ if (*err) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: non-digit found in netmask:", string, err);
+ return -1;
+ }
+
+ if (nm_bits <= 0) {
+ if (*(nm_slash+1) == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: no number after / ", string);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: invalid netmask <= 0:", string, err);
+ }
+ return -1;
+ }
+
+ if (addrstrlen >= sizeof(addrstr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: address string too long:", string);
+ return -1;
+ }
+
+ memcpy(addrstr, string->ptr, addrstrlen);
+ addrstr[addrstrlen] = '\0';
+
+ return config_addrstr_eq_remote_ip_mask(srv, addrstr, nm_bits, rmt);
+}
+
+static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc);
+
+static cond_result_t config_check_cond_nocache(server *srv, connection *con, data_config *dc) {
+ buffer *l;
+ server_socket *srv_sock = con->srv_socket;
+ cond_cache_t *cache = &con->cond_cache[dc->context_ndx];
+
+ /* 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 (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "go parent", dc->parent->key);
+ }
+
+ switch (config_check_cond_cached(srv, con, dc->parent)) {
+ 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 (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "go prev", dc->prev->key);
+ }
+
+ /* make sure prev is checked first */
+ switch (config_check_cond_cached(srv, con, dc->prev)) {
+ 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 (!con->conditional_is_valid[dc->comp]) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "dss",
+ dc->comp,
+ dc->key->ptr,
+ "not available yet");
+ }
+
+ 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 COND_RESULT_TRUE;
+
+ /* pass the rules */
+
+ switch (dc->comp) {
+ case COMP_HTTP_HOST: {
+ char *ck_colon = NULL, *val_colon = NULL;
+ unsigned short port;
+
+ if (!buffer_string_is_empty(con->uri.authority)) {
+
+ /*
+ * append server-port to the HTTP_POST if necessary
+ */
+
+ l = con->uri.authority;
+
+ switch(dc->cond) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ port = sock_addr_get_port(&srv_sock->addr);
+ if (0 == port) break;
+ ck_colon = strchr(dc->string->ptr, ':');
+ val_colon = strchr(l->ptr, ':');
+
+ if (NULL != ck_colon && NULL == val_colon) {
+ /* condition "host:port" but client send "host" */
+ buffer_copy_buffer(srv->cond_check_buf, l);
+ buffer_append_string_len(srv->cond_check_buf, CONST_STR_LEN(":"));
+ buffer_append_int(srv->cond_check_buf, port);
+ l = srv->cond_check_buf;
+ } else if (NULL != val_colon && NULL == ck_colon) {
+ /* condition "host" but client send "host:port" */
+ buffer_copy_string_len(srv->cond_check_buf, l->ptr, val_colon - l->ptr);
+ l = srv->cond_check_buf;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ l = srv->empty_string;
+ }
+ break;
+ }
+ case COMP_HTTP_REMOTE_IP: {
+ char *nm_slash;
+ /* handle remoteip limitations
+ *
+ * "10.0.0.1" is provided for all comparisions
+ *
+ * only for == and != we support
+ *
+ * "10.0.0.1/24"
+ */
+
+ if ((dc->cond == CONFIG_COND_EQ ||
+ dc->cond == CONFIG_COND_NE) &&
+ (NULL != (nm_slash = strchr(dc->string->ptr, '/')))) {
+ switch (config_addrbuf_eq_remote_ip_mask(srv, dc->string, nm_slash, &con->dst_addr)) {
+ case 1: return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE;
+ case 0: return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE;
+ case -1: return COND_RESULT_FALSE; /*(error parsing configfile entry)*/
+ }
+ }
+ l = con->dst_addr_buf;
+ break;
+ }
+ case COMP_HTTP_SCHEME:
+ l = con->uri.scheme;
+ break;
+
+ case COMP_HTTP_URL:
+ l = con->uri.path;
+ break;
+
+ case COMP_HTTP_QUERY_STRING:
+ l = con->uri.query;
+ break;
+
+ case COMP_SERVER_SOCKET:
+ l = srv_sock->srv_token;
+ break;
+
+ case COMP_HTTP_REQUEST_HEADER:
+ l = http_header_request_get(con, HTTP_HEADER_UNSPECIFIED, CONST_BUF_LEN(dc->comp_tag));
+ if (NULL == l) l = srv->empty_string;
+ break;
+ case COMP_HTTP_REQUEST_METHOD:
+ l = srv->tmp_buf;
+ buffer_clear(l);
+ http_method_append(l, con->request.http_method);
+ break;
+ default:
+ return COND_RESULT_FALSE;
+ }
+
+ if (NULL == l) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "bsbs", dc->comp_key,
+ "(", l, ") compare to NULL");
+ }
+ return COND_RESULT_FALSE;
+ }
+
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "bsbsb", dc->comp_key,
+ "(", l, ") compare to ", dc->string);
+ }
+ switch(dc->cond) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ if (buffer_is_equal(l, dc->string)) {
+ return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE;
+ } else {
+ return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE;
+ }
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+ if (data_config_pcre_exec(dc, cache, l) > 0) {
+ return (dc->cond == CONFIG_COND_MATCH) ? COND_RESULT_TRUE : COND_RESULT_FALSE;
+ } else {
+ /* cache is already cleared */
+ return (dc->cond == CONFIG_COND_MATCH) ? COND_RESULT_FALSE : COND_RESULT_TRUE;
+ }
+ }
+ default:
+ /* no way */
+ break;
+ }
+
+ return COND_RESULT_FALSE;
+}
+
+static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc) {
+ cond_cache_t *caches = con->cond_cache;
+
+ if (COND_RESULT_UNSET == caches[dc->context_ndx].result) {
+ caches[dc->context_ndx].result = config_check_cond_nocache(srv, con, dc);
+ switch (caches[dc->context_ndx].result) {
+ case COND_RESULT_FALSE:
+ case COND_RESULT_TRUE:
+ /* remember result of local condition for a partial reset */
+ caches[dc->context_ndx].local_result = caches[dc->context_ndx].result;
+ break;
+ default:
+ break;
+ }
+
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "dss",
+ dc->context_ndx,
+ "(uncached) result:",
+ cond_result_to_string(caches[dc->context_ndx].result));
+ }
+ } else {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "dss",
+ dc->context_ndx,
+ "(cached) result:",
+ cond_result_to_string(caches[dc->context_ndx].result));
+ }
+ }
+ return caches[dc->context_ndx].result;
+}
+
+/* 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(server *srv, connection *con, data_config *dc) {
+ /* if a node is "unset" all children are unset too */
+ if (con->cond_cache[dc->context_ndx].result != COND_RESULT_UNSET) {
+ size_t i;
+
+ #if 0
+ /* (redundant; matches not relevant unless COND_RESULT_TRUE) */
+ switch (con->cond_cache[dc->context_ndx].local_result) {
+ case COND_RESULT_TRUE:
+ case COND_RESULT_FALSE:
+ break;
+ default:
+ con->cond_cache[dc->context_ndx].patterncount = 0;
+ con->cond_cache[dc->context_ndx].comp_value = NULL;
+ }
+ #endif
+ con->cond_cache[dc->context_ndx].result = COND_RESULT_UNSET;
+
+ for (i = 0; i < dc->children.used; ++i) {
+ 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(srv, con, dc_child);
+ }
+ }
+ if (NULL != dc->next) config_cond_clear_node(srv, con, 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(server *srv, connection *con, comp_key_t item) {
+ size_t i;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+
+ if (item == dc->comp) {
+ /* clear local_result */
+ con->cond_cache[i].local_result = COND_RESULT_UNSET;
+ /* clear result in subtree (including the node itself) */
+ config_cond_clear_node(srv, con, dc);
+ }
+ }
+}
+
+/**
+ * reset the config cache to its initial state at connection start
+ */
+void config_cond_cache_reset(server *srv, connection *con) {
+ size_t i;
+
+ /* resetting all entries; no need to follow children as in config_cond_cache_reset_item */
+ for (i = 0; i < srv->config_context->used; i++) {
+ con->cond_cache[i].result = COND_RESULT_UNSET;
+ con->cond_cache[i].local_result = COND_RESULT_UNSET;
+ con->cond_cache[i].patterncount = 0;
+ con->cond_cache[i].comp_value = NULL;
+ }
+
+ for (i = 0; i < COMP_LAST_ELEMENT; i++) {
+ con->conditional_is_valid[i] = 0;
+ }
+}
+
+int config_check_cond(server *srv, connection *con, data_config *dc) {
+ if (con->conf.log_condition_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "=== start of condition block ===");
+ }
+ return (config_check_cond_cached(srv, con, dc) == COND_RESULT_TRUE);
+}