commit 1e250c14731bd4ff992f05e3754902c3a11e3eea
parent 34c2f31a93016062ca38c04d049eccc96ad6b39c
Author: Joris Vink <joris@coders.se>
Date: Sun, 10 Nov 2013 15:17:15 +0100
Kore now supports GET parameters and automatic validation of GET/POST parameters.
Kore will automatically removes invalid parameters as a security measure.
See modules/examples/module.conf for an example of how this works.
Diffstat:
8 files changed, 293 insertions(+), 30 deletions(-)
diff --git a/includes/http.h b/includes/http.h
@@ -53,8 +53,9 @@ struct http_file {
#define HTTP_METHOD_GET 0
#define HTTP_METHOD_POST 1
-#define HTTP_REQUEST_COMPLETE 0x01
-#define HTTP_REQUEST_DELETE 0x02
+#define HTTP_REQUEST_COMPLETE 0x01
+#define HTTP_REQUEST_DELETE 0x02
+#define HTTP_REQUEST_PARSED_PARAMS 0x04
struct http_request {
u_int8_t method;
diff --git a/includes/kore.h b/includes/kore.h
@@ -157,6 +157,14 @@ struct connection {
TAILQ_ENTRY(connection) flush_list;
};
+struct kore_handler_params {
+ char *name;
+ u_int8_t method;
+ struct kore_validator *validator;
+
+ TAILQ_ENTRY(kore_handler_params) list;
+};
+
#define HANDLER_TYPE_STATIC 1
#define HANDLER_TYPE_DYNAMIC 2
@@ -168,6 +176,7 @@ struct kore_module_handle {
int errors;
regex_t rctx;
+ TAILQ_HEAD(, kore_handler_params) params;
TAILQ_ENTRY(kore_module_handle) list;
};
@@ -356,6 +365,8 @@ void kore_validator_init(void);
void kore_validator_reload(void);
int kore_validator_add(char *, u_int8_t, char *);
int kore_validator_run(char *, char *);
+int kore_validator_check(struct kore_validator *, char *);
+struct kore_validator *kore_validator_lookup(char *);
void fatal(const char *, ...);
void kore_debug_internal(char *, int, const char *, ...);
diff --git a/modules/example/module.conf b/modules/example/module.conf
@@ -96,7 +96,7 @@ domain localhost {
certkey cert/server.key
accesslog /var/log/kore_access.log
- # Page handlers
+ # Static page handlers
static /css/style.css serve_style_css
static / serve_index
static /intro.jpg serve_intro
@@ -105,6 +105,28 @@ domain localhost {
static /upload serve_file_upload
static /lock-test serve_lock_test
static /validator serve_validator
+
+ #
+ # We can use a dynamic handler to accept GET parameters.
+ #
+ dynamic ^/params-test(\??)[A-Z0-9a-z=&]*$ serve_params_test
+
+ # Configure /params-test POST to only accept the following parameters.
+ # They are automatically tested against the validator listed.
+ # If the validator would fail Kore will automatically remove the
+ # failing parameter, indicating something was wrong with it.
+ # Any parameters not present in the params block are also filtered out.
+ params post ^/params-test(\??)[A-Z0-9a-z=&]*$ {
+ validate test1 v_example
+ validate test2 v_regex
+ }
+
+ # Configure a GET parameter that /params-test can received. As before
+ # this is validated against the validator and removed if validation
+ # fails. All extra parameters in the GET query are filtered out.
+ params get ^/params-test(\??)[A-Z0-9a-z=&]*$ {
+ validate arg1 v_example
+ }
}
#domain domain.com {
diff --git a/modules/example/src/example.c b/modules/example/src/example.c
@@ -27,6 +27,7 @@ int serve_spdyreset(struct http_request *);
int serve_file_upload(struct http_request *);
int serve_lock_test(struct http_request *);
int serve_validator(struct http_request *);
+int serve_params_test(struct http_request *);
void my_callback(void);
int v_example_func(char *);
@@ -227,6 +228,72 @@ serve_validator(struct http_request *req)
return (http_response(req, 200, (u_int8_t *)"OK", 2));
}
+int
+serve_params_test(struct http_request *req)
+{
+ struct kore_buf *b;
+ u_int8_t *d;
+ u_int32_t len;
+ int r, i;
+ char *test, name[10];
+
+ b = kore_buf_create(static_len_html_params);
+ kore_buf_append(b, static_html_params, static_len_html_params);
+
+ /*
+ * The GET parameters will be filtered out on POST.
+ */
+ if (http_argument_lookup(req, "arg1", &test)) {
+ kore_buf_replace_string(b, "$arg1$", test, strlen(test));
+ kore_mem_free(test);
+ } else {
+ kore_buf_replace_string(b, "$arg1$", NULL, 0);
+ }
+
+ if (http_argument_lookup(req, "$arg2$", &test)) {
+ kore_buf_replace_string(b, "$arg2$", test, strlen(test));
+ kore_mem_free(test);
+ } else {
+ kore_buf_replace_string(b, "$arg2$", NULL, 0);
+ }
+
+ if (req->method == HTTP_METHOD_GET) {
+ kore_buf_replace_string(b, "$test1$", NULL, 0);
+ kore_buf_replace_string(b, "$test2$", NULL, 0);
+ kore_buf_replace_string(b, "$test3$", NULL, 0);
+
+ http_response_header_add(req, "content-type", "text/html");
+ d = kore_buf_release(b, &len);
+ r = http_response(req, 200, d, len);
+ kore_mem_free(d);
+
+ return (r);
+ }
+
+ /*
+ * No need to call http_populate_arguments(), it's been done
+ * by the parameter validation automatically.
+ */
+ for (i = 1; i < 4; i++) {
+ snprintf(name, sizeof(name), "test%d", i);
+ if (http_argument_lookup(req, name, &test)) {
+ snprintf(name, sizeof(name), "$test%d$", i);
+ kore_buf_replace_string(b, name, test, strlen(test));
+ kore_mem_free(test);
+ } else {
+ snprintf(name, sizeof(name), "$test%d$", i);
+ kore_buf_replace_string(b, name, NULL, 0);
+ }
+ }
+
+ http_response_header_add(req, "content-type", "text/html");
+ d = kore_buf_release(b, &len);
+ r = http_response(req, 200, d, len);
+ kore_mem_free(d);
+
+ return (r);
+}
+
void
my_callback(void)
{
diff --git a/src/config.c b/src/config.c
@@ -47,6 +47,8 @@ static int configure_http_postbody_max(char **);
static int configure_http_hsts_enable(char **);
static int configure_http_keepalive_time(char **);
static int configure_validator(char **);
+static int configure_params(char **);
+static int configure_validate(char **);
static void domain_sslstart(void);
static struct {
@@ -79,11 +81,15 @@ static struct {
{ "http_hsts_enable", configure_http_hsts_enable },
{ "http_keepalive_time", configure_http_keepalive_time },
{ "validator", configure_validator },
+ { "params", configure_params },
+ { "validate", configure_validate },
{ NULL, NULL },
};
-char *config_file = NULL;
-static struct kore_domain *current_domain = NULL;
+char *config_file = NULL;
+static u_int8_t current_method = 0;
+static struct kore_domain *current_domain = NULL;
+static struct kore_module_handle *current_handler = NULL;
void
kore_parse_config(void)
@@ -115,6 +121,12 @@ kore_parse_config(void)
*t = ' ';
}
+ if (!strcmp(p, "}") && current_handler != NULL) {
+ lineno++;
+ current_handler = NULL;
+ continue;
+ }
+
if (!strcmp(p, "}") && current_domain != NULL)
domain_sslstart();
@@ -626,6 +638,78 @@ configure_validator(char **argv)
return (KORE_RESULT_OK);
}
+static int
+configure_params(char **argv)
+{
+ struct kore_module_handle *hdlr;
+
+ if (current_domain == NULL) {
+ printf("params keyword used in wrong context\n");
+ return (KORE_RESULT_ERROR);
+ }
+
+ if (current_handler != NULL) {
+ printf("previous params block not closed\n");
+ return (KORE_RESULT_ERROR);
+ }
+
+ if (argv[2] == NULL)
+ return (KORE_RESULT_ERROR);
+
+ if (!strcasecmp(argv[1], "post")) {
+ current_method = HTTP_METHOD_POST;
+ } else if (!strcasecmp(argv[1], "get")) {
+ current_method = HTTP_METHOD_GET;
+ } else {
+ printf("unknown method: %s in params block for %s\n",
+ argv[1], argv[2]);
+ return (KORE_RESULT_ERROR);
+ }
+
+ /*
+ * Find the handler ourselves, otherwise the regex is applied
+ * in case of a dynamic page.
+ */
+ TAILQ_FOREACH(hdlr, &(current_domain->handlers), list) {
+ if (!strcmp(hdlr->path, argv[2])) {
+ current_handler = hdlr;
+ return (KORE_RESULT_OK);
+ }
+ }
+
+ printf("params for unknown page handler: %s\n", argv[2]);
+ return (KORE_RESULT_ERROR);
+}
+
+static int
+configure_validate(char **argv)
+{
+ struct kore_handler_params *p;
+ struct kore_validator *val;
+
+ if (current_handler == NULL) {
+ printf("validate keyword used in wrong context\n");
+ return (KORE_RESULT_ERROR);
+ }
+
+ if (argv[2] == NULL)
+ return (KORE_RESULT_ERROR);
+
+ if ((val = kore_validator_lookup(argv[2])) == NULL) {
+ printf("unknown validator %s for %s\n", argv[2], argv[1]);
+ return (KORE_RESULT_ERROR);
+ }
+
+ p = kore_malloc(sizeof(*p));
+ p->validator = val;
+ p->method = current_method;
+ p->name = kore_strdup(argv[1]);
+
+ TAILQ_INSERT_TAIL(&(current_handler->params), p, list);
+
+ return (KORE_RESULT_OK);
+}
+
static void
domain_sslstart(void)
{
diff --git a/src/http.c b/src/http.c
@@ -25,6 +25,8 @@
static char *http_status_text(int);
static int http_post_data_recv(struct netbuf *);
+static void http_validate_params(struct http_request *,
+ struct kore_module_handle *);
static TAILQ_HEAD(, http_request) http_requests;
static struct kore_pool http_request_pool;
@@ -134,8 +136,13 @@ http_process(void)
if (hdlr == NULL) {
r = http_generic_404(req);
} else {
- cb = hdlr->addr;
+ if (!TAILQ_EMPTY(&(hdlr->params)) &&
+ !(req->flags & HTTP_REQUEST_PARSED_PARAMS)) {
+ http_validate_params(req, hdlr);
+ req->flags |= HTTP_REQUEST_PARSED_PARAMS;
+ }
+ cb = hdlr->addr;
worker->active_hdlr = hdlr;
r = cb(req);
worker->active_hdlr = NULL;
@@ -495,13 +502,15 @@ http_populate_arguments(struct http_request *req)
{
u_int32_t len;
int i, v, c, count;
- char *query, *args[HTTP_MAX_QUERY_ARGS], *val[3];
+ char *p, *query, *args[HTTP_MAX_QUERY_ARGS], *val[3];
if (req->method == HTTP_METHOD_POST) {
query = http_post_data_text(req);
} else {
- kore_debug("HTTP_METHOD_GET not supported for arguments");
- return (0);
+ if ((p = strchr(req->path, '?')) == NULL)
+ return (0);
+ p++;
+ query = kore_strdup(p);
}
count = 0;
@@ -868,6 +877,54 @@ http_post_data_recv(struct netbuf *nb)
return (KORE_RESULT_OK);
}
+static void
+http_validate_params(struct http_request *req, struct kore_module_handle *hdlr)
+{
+ int r;
+ struct kore_handler_params *p;
+ struct http_arg *q, *next;
+
+ http_populate_arguments(req);
+
+ for (q = TAILQ_FIRST(&(req->arguments)); q != NULL; q = next) {
+ next = TAILQ_NEXT(q, list);
+
+ p = NULL;
+ TAILQ_FOREACH(p, &(hdlr->params), list) {
+ if (p->method != req->method)
+ continue;
+ if (!strcmp(p->name, q->name))
+ break;
+ }
+
+ if (q->value != NULL)
+ http_argument_urldecode(q->value);
+
+ r = KORE_RESULT_ERROR;
+ if (p != NULL && q->value != NULL) {
+ r = kore_validator_check(p->validator, q->value);
+ if (r != KORE_RESULT_OK) {
+ kore_log(LOG_NOTICE,
+ "validator %s(%s) for %s failed",
+ p->validator->name, p->name, req->path);
+ }
+ } else if (p == NULL) {
+ kore_log(LOG_NOTICE,
+ "received unexpected parameter %s for %s",
+ q->name, req->path);
+ }
+
+ if (r == KORE_RESULT_ERROR) {
+ TAILQ_REMOVE(&(req->arguments), q, list);
+ kore_mem_free(q->name);
+ if (q->value != NULL)
+ kore_mem_free(q->value);
+ kore_mem_free(q);
+ continue;
+ }
+ }
+}
+
static char *
http_status_text(int status)
{
diff --git a/src/module.c b/src/module.c
@@ -133,11 +133,12 @@ kore_module_handler_new(char *path, char *domain, char *func, int type)
hdlr->errors = 0;
hdlr->addr = addr;
hdlr->type = type;
+ TAILQ_INIT(&(hdlr->params));
hdlr->path = kore_strdup(path);
hdlr->func = kore_strdup(func);
if (hdlr->type == HANDLER_TYPE_DYNAMIC) {
- if (regcomp(&(hdlr->rctx), hdlr->path, REG_NOSUB)) {
+ if (regcomp(&(hdlr->rctx), hdlr->path, REG_EXTENDED | REG_NOSUB)) {
kore_mem_free(hdlr->func);
kore_mem_free(hdlr->path);
kore_mem_free(hdlr);
diff --git a/src/validator.c b/src/validator.c
@@ -34,7 +34,7 @@ kore_validator_add(char *name, u_int8_t type, char *arg)
switch (val->type) {
case KORE_VALIDATOR_TYPE_REGEX:
- if (regcomp(&(val->rctx), arg, REG_NOSUB)) {
+ if (regcomp(&(val->rctx), arg, REG_EXTENDED | REG_NOSUB)) {
kore_mem_free(val);
kore_log(LOG_NOTICE,
"validator %s has bad regex %s", name, arg);
@@ -65,36 +65,43 @@ kore_validator_add(char *name, u_int8_t type, char *arg)
int
kore_validator_run(char *name, char *data)
{
- int r;
struct kore_validator *val;
TAILQ_FOREACH(val, &validators, list) {
if (strcmp(val->name, name))
continue;
- switch (val->type) {
- case KORE_VALIDATOR_TYPE_REGEX:
- if (!regexec(&(val->rctx), data, 0, NULL, 0))
- r = KORE_RESULT_OK;
- else
- r = KORE_RESULT_ERROR;
- break;
- case KORE_VALIDATOR_TYPE_FUNCTION:
- r = val->func(data);
- break;
- default:
- r = KORE_RESULT_ERROR;
- kore_log(LOG_NOTICE, "invalid type %d for validator %s",
- val->type, val->name);
- break;
- }
-
- return (r);
+ return (kore_validator_check(val, data));
}
return (KORE_RESULT_ERROR);
}
+int
+kore_validator_check(struct kore_validator *val, char *data)
+{
+ int r;
+
+ switch (val->type) {
+ case KORE_VALIDATOR_TYPE_REGEX:
+ if (!regexec(&(val->rctx), data, 0, NULL, 0))
+ r = KORE_RESULT_OK;
+ else
+ r = KORE_RESULT_ERROR;
+ break;
+ case KORE_VALIDATOR_TYPE_FUNCTION:
+ r = val->func(data);
+ break;
+ default:
+ r = KORE_RESULT_ERROR;
+ kore_log(LOG_NOTICE, "invalid type %d for validator %s",
+ val->type, val->name);
+ break;
+ }
+
+ return (r);
+}
+
void
kore_validator_reload(void)
{
@@ -108,3 +115,16 @@ kore_validator_reload(void)
fatal("no function for validator %s found", val->name);
}
}
+
+struct kore_validator *
+kore_validator_lookup(char *name)
+{
+ struct kore_validator *val;
+
+ TAILQ_FOREACH(val, &validators, list) {
+ if (!strcmp(val->name, name))
+ return (val);
+ }
+
+ return (NULL);
+}