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 9a8092bf414902b61bfbe757e53378d0f9e22498
parent 7e8371366fe84309f7bf8d1ca3f3a9a954833c7f
Author: Joris Vink <joris@coders.se>
Date:   Wed, 22 Jan 2014 22:55:10 +0100

Add authentication blocks for Kore.

Using authentication blocks one can define "authentication" mechanisms
in Kore for page handlers.

This can be used to require a session cookie (validated by your own validator)
for certain page handlers, and hopefully in the future provide a framework
for adding more authentication things (like HTTP Auth).

Right now only cookie checking is available.

Diffstat:
Makefile | 7++++---
includes/http.h | 3+++
includes/kore.h | 26+++++++++++++++++++++++---
modules/example/media/private.html | 16++++++++++++++++
modules/example/media/private_test.html | 15+++++++++++++++
modules/example/module.conf | 46+++++++++++++++++++++++++++++++++++++++++++---
modules/example/src/example.c | 42++++++++++++++++++++++++++++++++++++++++++
src/auth.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/config.c | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
src/http.c | 23++++++++++++++++++-----
src/kore.c | 1+
src/module.c | 16+++++++++++++---
12 files changed, 463 insertions(+), 18 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,9 +3,10 @@ CC=gcc BIN=kore -S_SRC+= src/kore.c src/accesslog.c src/buf.c src/config.c src/connection.c \ - src/domain.c src/http.c src/mem.c src/module.c src/net.c src/pool.c \ - src/spdy.c src/validator.c src/utils.c src/worker.c src/zlib_dict.c +S_SRC+= src/kore.c src/accesslog.c src/auth.c src/buf.c src/config.c \ + src/connection.c src/domain.c src/http.c src/mem.c src/module.c \ + src/net.c src/pool.c src/spdy.c src/validator.c src/utils.c \ + src/worker.c src/zlib_dict.c S_OBJS= $(S_SRC:.c=.o) CFLAGS+=-Wall -Wstrict-prototypes -Wmissing-prototypes diff --git a/includes/http.h b/includes/http.h @@ -25,6 +25,7 @@ #define HTTP_USERAGENT_LEN 256 #define HTTP_REQ_HEADER_MAX 25 #define HTTP_MAX_QUERY_ARGS 10 +#define HTTP_MAX_COOKIES 10 #define HTTP_ARG_TYPE_RAW 0 #define HTTP_ARG_TYPE_BYTE 1 @@ -133,8 +134,10 @@ struct http_request { void *hdlr_extra; char *query_string; u_int8_t *multipart_body; + struct kore_module_handle *hdlr; + TAILQ_HEAD(, http_header) req_headers; TAILQ_HEAD(, http_header) resp_headers; TAILQ_HEAD(, http_arg) arguments; diff --git a/includes/kore.h b/includes/kore.h @@ -171,12 +171,24 @@ struct kore_handler_params { TAILQ_ENTRY(kore_handler_params) list; }; -#define HANDLER_TYPE_STATIC 1 -#define HANDLER_TYPE_DYNAMIC 2 +#define KORE_AUTH_TYPE_COOKIE 1 + +struct kore_auth { + u_int8_t type; + char *name; + char *value; + char *redirect; + struct kore_validator *validator; + + TAILQ_ENTRY(kore_auth) list; +}; #define KORE_MODULE_LOAD 1 #define KORE_MODULE_UNLOAD 2 +#define HANDLER_TYPE_STATIC 1 +#define HANDLER_TYPE_DYNAMIC 2 + struct kore_module { void *handle; char *path; @@ -195,6 +207,7 @@ struct kore_module_handle { int type; int errors; regex_t rctx; + struct kore_auth *auth; TAILQ_HEAD(, kore_handler_params) params; TAILQ_ENTRY(kore_module_handle) list; @@ -329,6 +342,13 @@ void kore_accesslog_init(void); int kore_accesslog_wait(void); void kore_accesslog_worker_init(void); +int kore_auth(struct http_request *, struct kore_auth *); +int kore_auth_cookie(struct http_request *, struct kore_auth *); + +void kore_auth_init(void); +int kore_auth_new(char *); +struct kore_auth *kore_auth_lookup(char *); + int kore_ssl_sni_cb(SSL *, int *, void *); int kore_server_bind(const char *, const char *); int kore_ssl_npn_cb(SSL *, const u_char **, unsigned int *, void *); @@ -380,7 +400,7 @@ void kore_domain_closelogs(void); void *kore_module_getsym(char *); void kore_module_load(char *, char *); void kore_domain_sslstart(struct kore_domain *); -int kore_module_handler_new(char *, char *, char *, int); +int kore_module_handler_new(char *, char *, char *, char *, int); struct kore_domain *kore_domain_lookup(const char *); struct kore_module_handle *kore_module_handler_find(char *, char *); diff --git a/modules/example/media/private.html b/modules/example/media/private.html @@ -0,0 +1,16 @@ +<!DOCTYPE> +<html> +<head> + <link rel="stylesheet" href="/css/style.css" type="text/css"> + <title>Kore Authentication tests</title> +</head> + +<body> + +<div class="content"> + <p style="font-size: small">The cookie session_id should now be set.</p> + <p style="font-size: small">You can continue to <a href="/private/test">view page handler in auth block</a></p> +</div> + +</body> +</html> diff --git a/modules/example/media/private_test.html b/modules/example/media/private_test.html @@ -0,0 +1,15 @@ +<!DOCTYPE> +<html> +<head> + <link rel="stylesheet" href="/css/style.css" type="text/css"> + <title>Kore Authentication tests</title> +</head> + +<body> + +<div class="content"> + <p style="font-size: small">If you see this, the authentication worked. This page should redirect back to /private once you remove your session_id cookie.</p> +</div> + +</body> +</html> diff --git a/modules/example/module.conf b/modules/example/module.conf @@ -58,6 +58,7 @@ load modules/example/example.module example_load validator v_example function v_example_func validator v_regex regex ^/test/[a-z]*$ validator v_number regex ^[0-9]*$ +validator v_session function v_session_validate # Specify the SSL ciphers that will be used. #ssl_cipher ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK @@ -73,6 +74,38 @@ ssl_no_compression # You can keep it open indefinately by setting this to 0. #spdy_idle_time 120 +# Authentication configuration +# +# Using authentication blocks you can define a standard way for +# Kore to validate your users. In the example below we create +# a authentication block called auth_example, which requires +# a cookie (session_id) to be set. +# +# If no cookie is present or the cookie is not valid according +# to the set validator, Kore will redirect the browser to the +# URI set in authentication_uri. +# +# Page handlers can be bound to authentication by specifying +# authentication block at the end of the page directive (see below). +authentication auth_example { + # The authentication type denotes the way the user should + # be authenticated. Right now only cookie is available. + authentication_type cookie + + # The name of the cookie to look for. + authentication_value session_id + + # The validator that will be called to verify the cookie. + # Note this is YOUR validator, Kore does not have built-in + # session support. You must add this manually using your + # preferred method (Storing it in postgres, redis, ...) + authentication_validator v_session + + # The URI Kore will redirect to if a authentication fails. + # If this is not set, Kore will return a simple 403. + authentication_uri /private +} + # Domain configuration # # Each domain configuration starts with listing what domain @@ -92,7 +125,11 @@ ssl_no_compression # Dynamic handlers take a POSIX regular expression as its path. # # Syntax: -# handler path module_callback +# handler path module_callback [auth block] +# +# Note that the auth block is optional and if set will force Kore to +# authenticate the user according to the authentication block its settings +# before allowing access to the page. # Example domain that responds to localhost. domain localhost { @@ -100,7 +137,7 @@ domain localhost { certkey cert/server.key accesslog /var/log/kore_access.log - # Static page handlers + # Page handlers with no authentication required. static /css/style.css serve_style_css static / serve_index static /intro.jpg serve_intro @@ -109,8 +146,11 @@ domain localhost { static /upload serve_file_upload static /lock-test serve_lock_test static /validator serve_validator - static /params-test serve_params_test + static /private serve_private + + # Page handlers with authentication. + static /private/test serve_private_test auth_example # Configure /params-test POST to only accept the following parameters. # They are automatically tested against the validator listed. diff --git a/modules/example/src/example.c b/modules/example/src/example.c @@ -20,6 +20,7 @@ #include "static.h" void example_load(int); + int serve_style_css(struct http_request *); int serve_index(struct http_request *); int serve_intro(struct http_request *); @@ -29,9 +30,12 @@ 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 *); +int serve_private(struct http_request *); +int serve_private_test(struct http_request *); void my_callback(void); int v_example_func(char *); +int v_session_validate(char *); void test_base64(u_int8_t *, u_int32_t, struct kore_buf *); char *b64tests[] = { @@ -309,6 +313,33 @@ serve_params_test(struct http_request *req) return (r); } +int +serve_private(struct http_request *req) +{ + int r; + + http_response_header_add(req, "content-type", "text/html"); + http_response_header_add(req, "set-cookie", "session_id=test123"); + + r = http_response(req, 200, static_html_private, + static_len_html_private); + + return (r); +} + +int +serve_private_test(struct http_request *req) +{ + int r; + + http_response_header_add(req, "content-type", "text/html"); + + r = http_response(req, 200, static_html_private_test, + static_len_html_private_test); + + return (r); +} + void my_callback(void) { @@ -328,3 +359,14 @@ v_example_func(char *data) return (KORE_RESULT_ERROR); } + +int +v_session_validate(char *data) +{ + kore_log(LOG_NOTICE, "v_session_validate: %s", data); + + if (!strcmp(data, "test123")) + return (KORE_RESULT_OK); + + return (KORE_RESULT_ERROR); +} diff --git a/src/auth.c b/src/auth.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2014 Joris Vink <joris@coders.se> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> + +#include <ctype.h> + +#include "kore.h" +#include "http.h" + +TAILQ_HEAD(, kore_auth) auth_list; + +void +kore_auth_init(void) +{ + TAILQ_INIT(&auth_list); +} + +int +kore_auth_new(char *name) +{ + struct kore_auth *auth; + + if ((auth = kore_auth_lookup(name)) != NULL) + return (KORE_RESULT_ERROR); + + auth = kore_malloc(sizeof(*auth)); + auth->type = 0; + auth->value = NULL; + auth->redirect = NULL; + auth->validator = NULL; + auth->name = kore_strdup(name); + + TAILQ_INSERT_TAIL(&auth_list, auth, list); + + return (KORE_RESULT_OK); +} + +int +kore_auth(struct http_request *req, struct kore_auth *auth) +{ + int r; + + kore_debug("kore_auth(%p, %p)", req, auth); + + switch (auth->type) { + case KORE_AUTH_TYPE_COOKIE: + r = kore_auth_cookie(req, auth); + break; + default: + kore_log(LOG_NOTICE, "unknown auth type %d", auth->type); + return (KORE_RESULT_ERROR); + } + + if (r == KORE_RESULT_OK) { + kore_debug("kore_auth() for %s successful", req->path); + return (KORE_RESULT_OK); + } + + kore_debug("kore_auth() for %s failed", req->path); + + if (auth->redirect == NULL) { + http_response(req, 403, NULL, 0); + return (KORE_RESULT_ERROR); + } + + http_response_header_add(req, "location", auth->redirect); + http_response(req, 302, NULL, 0); + + return (KORE_RESULT_ERROR); +} + +int +kore_auth_cookie(struct http_request *req, struct kore_auth *auth) +{ + int i, v; + size_t len, slen; + char *value, *c, *cookie, *cookies[HTTP_MAX_COOKIES]; + + if (!http_request_header_get(req, "cookie", &cookie)) + return (KORE_RESULT_ERROR); + + slen = strlen(auth->value); + v = kore_split_string(cookie, ";", cookies, HTTP_MAX_COOKIES); + for (i = 0; i < v; i++) { + for (c = cookies[i]; isspace(*c); c++) + ; + + len = MIN(slen, strlen(cookies[i])); + if (!strncmp(c, auth->value, len)) + break; + } + + if (i == v) { + kore_mem_free(cookie); + return (KORE_RESULT_ERROR); + } + + c = cookies[i]; + if ((value = strchr(c, '=')) == NULL) { + kore_mem_free(cookie); + return (KORE_RESULT_ERROR); + } + + i = kore_validator_check(auth->validator, ++value); + kore_mem_free(cookie); + + return (i); +} + +struct kore_auth * +kore_auth_lookup(char *name) +{ + struct kore_auth *auth; + + TAILQ_FOREACH(auth, &auth_list, list) { + if (!strcmp(auth->name, name)) + return (auth); + } + + return (NULL); +} diff --git a/src/config.c b/src/config.c @@ -22,6 +22,8 @@ #include "kore.h" #include "http.h" +/* XXX - This is becoming a clusterfuck. Fix it. */ + static int configure_bind(char **); static int configure_load(char **); static int configure_handler(char **); @@ -49,6 +51,12 @@ static int configure_validator(char **); static int configure_params(char **); static int configure_validate(char **); static int configure_require_client_cert(char **); +static int configure_authentication(char **); +static int configure_authentication_uri(char **); +static int configure_authentication_type(char **); +static int configure_authentication_value(char **); +static int configure_authentication_validator(char **); + static void domain_sslstart(void); static struct { @@ -83,11 +91,17 @@ static struct { { "validator", configure_validator }, { "params", configure_params }, { "validate", configure_validate }, + { "authentication", configure_authentication }, + { "authentication_uri", configure_authentication_uri }, + { "authentication_type", configure_authentication_type }, + { "authentication_value", configure_authentication_value }, + { "authentication_validator", configure_authentication_validator }, { NULL, NULL }, }; char *config_file = NULL; static u_int8_t current_method = 0; +static struct kore_auth *current_auth = NULL; static struct kore_domain *current_domain = NULL; static struct kore_module_handle *current_handler = NULL; @@ -127,6 +141,17 @@ kore_parse_config(void) continue; } + if (!strcmp(p, "}") && current_auth != NULL) { + if (current_auth->validator == NULL) { + fatal("no authentication validator for %s", + current_auth->name); + } + + lineno++; + current_auth = NULL; + continue; + } + if (!strcmp(p, "}") && current_domain != NULL) domain_sslstart(); @@ -296,7 +321,7 @@ configure_handler(char **argv) return (KORE_RESULT_ERROR); if (!kore_module_handler_new(argv[1], - current_domain->domain, argv[2], type)) { + current_domain->domain, argv[2], argv[3], type)) { kore_debug("cannot create handler for %s", argv[1]); return (KORE_RESULT_ERROR); } @@ -723,6 +748,130 @@ configure_validate(char **argv) return (KORE_RESULT_OK); } +static int +configure_authentication(char **argv) +{ + if (argv[2] == NULL) { + printf("Missing name for authentication block\n"); + return (KORE_RESULT_ERROR); + } + + if (current_auth != NULL) { + printf("Previous authentication block not closed\n"); + return (KORE_RESULT_ERROR); + } + + if (strcmp(argv[2], "{")) { + printf("missing { for authentication block\n"); + return (KORE_RESULT_ERROR); + } + + if (!kore_auth_new(argv[1])) + return (KORE_RESULT_ERROR); + + current_auth = kore_auth_lookup(argv[1]); + + return (KORE_RESULT_OK); +} + +static int +configure_authentication_type(char **argv) +{ + if (current_auth == NULL) { + printf("authentication_type outside authentication block\n"); + return (KORE_RESULT_ERROR); + } + + if (argv[1] == NULL) { + printf("missing parameter for authentication_type\n"); + return (KORE_RESULT_ERROR); + } + + if (!strcmp(argv[1], "cookie")) { + current_auth->type = KORE_AUTH_TYPE_COOKIE; + } else { + printf("unknown authentication type '%s'\n", argv[1]); + return (KORE_RESULT_ERROR); + } + + return (KORE_RESULT_OK); +} + +static int +configure_authentication_value(char **argv) +{ + if (current_auth == NULL) { + printf("authentication_value outside authentication block\n"); + return (KORE_RESULT_ERROR); + } + + if (argv[1] == NULL) { + printf("missing parameter for authentication_value\n"); + return (KORE_RESULT_ERROR); + } + + if (current_auth->value != NULL) { + printf("duplicate authentication_value found\n"); + return (KORE_RESULT_ERROR); + } + + current_auth->value = kore_strdup(argv[1]); + return (KORE_RESULT_OK); +} + +static int +configure_authentication_validator(char **argv) +{ + struct kore_validator *val; + + if (current_auth == NULL) { + printf("authentication_validator outside authentication\n"); + return (KORE_RESULT_ERROR); + } + + if (argv[1] == NULL) { + printf("missing parameter for authentication_validator\n"); + return (KORE_RESULT_ERROR); + } + + if (current_auth->validator != NULL) { + printf("duplicate authentication_validator found\n"); + return (KORE_RESULT_ERROR); + } + + if ((val = kore_validator_lookup(argv[1])) == NULL) { + printf("authentication validator '%s' not found\n", argv[1]); + return (KORE_RESULT_ERROR); + } + + current_auth->validator = val; + + return (KORE_RESULT_OK); +} + +static int +configure_authentication_uri(char **argv) +{ + if (current_auth == NULL) { + printf("authentication_uri outside authentication block\n"); + return (KORE_RESULT_ERROR); + } + + if (argv[1] == NULL) { + printf("missing parameter for authentication_uri\n"); + return (KORE_RESULT_ERROR); + } + + if (current_auth->redirect != NULL) { + printf("duplicate authentication_uri found\n"); + return (KORE_RESULT_ERROR); + } + + current_auth->redirect = kore_strdup(argv[1]); + + return (KORE_RESULT_OK); +} + static void domain_sslstart(void) { diff --git a/src/http.c b/src/http.c @@ -150,11 +150,24 @@ http_process(void) if (hdlr == NULL) { r = http_generic_404(req); } else { - req->hdlr = hdlr; - cb = hdlr->addr; - worker->active_hdlr = hdlr; - r = cb(req); - worker->active_hdlr = NULL; + if (hdlr->auth != NULL) + r = kore_auth(req, hdlr->auth); + else + r = KORE_RESULT_OK; + + if (r == KORE_RESULT_OK) { + req->hdlr = hdlr; + cb = hdlr->addr; + worker->active_hdlr = hdlr; + r = cb(req); + worker->active_hdlr = NULL; + } else { + /* + * Set r to KORE_RESULT_OK so we can properly + * flush the result from kore_auth(). + */ + r = KORE_RESULT_OK; + } } req->end = kore_time_ms(); diff --git a/src/kore.c b/src/kore.c @@ -87,6 +87,7 @@ main(int argc, char *argv[]) kore_log_init(); kore_mem_init(); + kore_auth_init(); kore_domain_init(); kore_module_init(); kore_validator_init(); diff --git a/src/module.c b/src/module.c @@ -136,14 +136,16 @@ kore_module_loaded(void) } int -kore_module_handler_new(char *path, char *domain, char *func, int type) +kore_module_handler_new(char *path, char *domain, char *func, + char *auth, int type) { + struct kore_auth *ap; void *addr; struct kore_domain *dom; struct kore_module_handle *hdlr; - kore_debug("kore_module_handler_new(%s, %s, %s, %d)", path, - domain, func, type); + kore_debug("kore_module_handler_new(%s, %s, %s, %s, %d)", path, + domain, func, auth, type); addr = kore_module_getsym(func); if (addr == NULL) { @@ -154,7 +156,15 @@ kore_module_handler_new(char *path, char *domain, char *func, int type) if ((dom = kore_domain_lookup(domain)) == NULL) return (KORE_RESULT_ERROR); + if (auth != NULL) { + if ((ap = kore_auth_lookup(auth)) == NULL) + fatal("no authentication block '%s' found", auth); + } else { + ap = NULL; + } + hdlr = kore_malloc(sizeof(*hdlr)); + hdlr->auth = ap; hdlr->errors = 0; hdlr->addr = addr; hdlr->type = type;