kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

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:
includes/http.h | 5+++--
includes/kore.h | 11+++++++++++
modules/example/module.conf | 24+++++++++++++++++++++++-
modules/example/src/example.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/config.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/http.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
src/module.c | 3++-
src/validator.c | 60++++++++++++++++++++++++++++++++++++++++--------------------
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); +}