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:
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;