aboutsummaryrefslogtreecommitdiff
path: root/src/auth.c
diff options
context:
space:
mode:
authorJohannes Hofmann <Johannes.Hofmann@gmx.de>2008-12-20 23:46:06 +0100
committerJohannes Hofmann <Johannes.Hofmann@gmx.de>2008-12-20 23:46:06 +0100
commit022e339b4417b119cc75fcb2cef057bfce41f8e8 (patch)
tree3fdb96c920a290556f83d0fe6ca0f9a810d9fb80 /src/auth.c
parent25e1cd690f1f9c939060294d092313481d5d06cd (diff)
parent9b532026d3fc5c379827351c4da8b219012cb1c7 (diff)
merge with main
Diffstat (limited to 'src/auth.c')
-rw-r--r--src/auth.c514
1 files changed, 514 insertions, 0 deletions
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 00000000..81e97188
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,514 @@
+/*
+ * File: auth.c
+ *
+ * Copyright 2008 Jeremy Henty <onepoint@starurchin.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ */
+
+/* Handling of HTTP AUTH takes place here.
+ * This implementation aims to follow RFC 2617:
+ * http://www.ietf.org/rfc/rfc2617.txt
+ */
+
+
+#include <ctype.h> /* for parsing */
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "auth.h"
+#include "msg.h"
+#include "misc.h"
+#include "dialog.hh"
+#include "IO/Url.h"
+#include "../dlib/dlib.h"
+
+
+typedef struct {
+ int ok;
+ const char *realm;
+} AuthParse_t;
+
+typedef struct {
+ char *name;
+ Dlist *paths; /* stripped of any trailing '/', so the root path is "" */
+ char *authorization; /* the authorization request header */
+} AuthRealm_t;
+
+typedef struct {
+ char *scheme;
+ char *authority;
+ Dlist *realms;
+} AuthHost_t;
+
+typedef struct {
+ const char *realm_name;
+ const DilloUrl *url;
+} AuthDialogData_t;
+
+/*
+ * Local data
+ */
+static Dlist *auth_hosts;
+
+/*
+ * Initialize the auth module.
+ */
+void a_Auth_init(void)
+{
+ auth_hosts = dList_new(1);
+}
+
+static AuthParse_t *Auth_parse_new()
+{
+ AuthParse_t *auth_parse = dNew(AuthParse_t, 1);
+ auth_parse->ok = 0;
+ auth_parse->realm = NULL;
+ return auth_parse;
+}
+
+static void Auth_parse_free(AuthParse_t *auth_parse)
+{
+ if (auth_parse) {
+ dFree((void *) auth_parse->realm);
+ }
+}
+
+static int Auth_path_is_inside(const char *path1, const char *path2, int len)
+{
+ /*
+ * path2 is effectively truncated to length len. Typically len will be
+ * strlen(path2), or 1 less when we want to ignore a trailing '/'.
+ */
+ return
+ strncmp(path1, path2, len) == 0 &&
+ (path1[len] == '\0' || path1[len] == '/');
+}
+
+/*
+ * Check valid chars.
+ * Return: 0 if invalid, 1 otherwise.
+ */
+static int Auth_is_token_char(char c)
+{
+ const char *invalid = "\"()<>@,;:\\[]¿?=/{} \t";
+ return (strchr(invalid, c) || iscntrl(c)) ? 0 : 1;
+}
+
+static void Auth_parse_auth_basic(AuthParse_t *auth_parse, char *auth)
+{
+ int token_value_pairs_found;
+ char *realm = NULL;
+ static const char realm_token[] = "realm";
+
+ /* parse comma-separated token-value pairs */
+ token_value_pairs_found = 0;
+ while (1) {
+ char *token, *value;
+ int token_size, value_size;
+
+ /* skip host and comma characters */
+ while (*auth == ' ' || *auth == ',')
+ auth++;
+
+ /* end of string? */
+ if (!*auth)
+ goto end_parse;
+
+ /* parse a token */
+ token = auth;
+ token_size = 0;
+ while (Auth_is_token_char(*auth)) {
+ auth++;
+ token_size++;
+ }
+ if (token_size == 0) {
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "missing Basic auth token\n");
+ goto end_parse;
+ }
+
+ /* skip space characters */
+ while (*auth == ' ')
+ auth++;
+
+ /* parse the '=' */
+ switch (*auth++) {
+ case '=':
+ break;
+ case '\0':
+ case ',':
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "missing Basic auth token value\n");
+ goto end_parse;
+ break;
+ default:
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "garbage after Basic auth token\n");
+ goto end_parse;
+ break;
+ }
+
+ /* skip space characters */
+ while (*auth == ' ')
+ auth++;
+
+ /* parse a quoted string */
+
+ /* parse a '"' */
+ switch (*auth++) {
+ case '"':
+ break;
+ case '\0':
+ case ',':
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "missing Basic auth token value after '='\n");
+ goto end_parse;
+ break;
+ default:
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "garbage in Basic auth after '='\n");
+ goto end_parse;
+ break;
+ }
+
+ /* parse the rest of a quoted string */
+ value = auth;
+ value_size = 0;
+ while (1) {
+ switch (*auth++) {
+ case '"':
+ goto end_quoted_string;
+ break;
+ case '\0':
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "auth string ended inside quoted string value\n");
+ goto end_parse;
+ break;
+ case '\\':
+ /* end of string? */
+ if (!*auth++) {
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "auth string ended inside quoted string value "
+ "immediately after \\\n");
+ goto end_parse;
+ }
+ /* fall through to the next case */
+ default:
+ value_size++;
+ break;
+ }
+ } /* parse quoted string */
+ end_quoted_string:
+
+ token_value_pairs_found = 1;
+
+ if (realm == NULL &&
+ strncasecmp(realm_token,token,token_size) == 0 &&
+ strlen(realm_token) == token_size) {
+ /* unquote and save the value */
+ char c, *value_ptr, *realm_ptr;
+ realm = dNew(char, value_size + 1);
+ value_ptr = value;
+ realm_ptr = realm;
+ while ((c = *value_ptr++) != '"')
+ *realm_ptr++ = (c == '\\') ? *value_ptr++ : c;
+ *realm_ptr = '\0';
+ auth_parse->ok = 1;
+ auth_parse->realm = realm;
+ _MSG("auth.c: Auth_parse_auth_basic: realm: '%s'\n", realm);
+ return;
+ }
+ }
+ end_parse:
+
+ if (!token_value_pairs_found) {
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "missing Basic auth token-value pairs\n");
+ return;
+ }
+
+ if (!realm) {
+ MSG("auth.c: Auth_parse_auth_basic: "
+ "missing Basic auth realm\n");
+ return;
+ }
+}
+
+static void Auth_parse_auth(AuthParse_t *auth_parse, char *auth)
+{
+ _MSG("auth.c: Auth_parse_auth: auth = '%s'\n", auth);
+ if (strncasecmp(auth, "Basic ", 6) == 0) {
+ Auth_parse_auth_basic(auth_parse, auth + 6);
+ } else {
+ MSG("auth.c: Auth_parse_auth: "
+ "unknown authorization scheme: auth = {%s}\n",
+ auth);
+ }
+}
+
+/*
+ * Return the host that contains a URL, or NULL if there is no such host.
+ */
+static AuthHost_t *Auth_host_by_url(const DilloUrl *url)
+{
+ AuthHost_t *host;
+ int i;
+
+ for (i = 0; (host = dList_nth_data(auth_hosts, i)); i++)
+ if (((dStrcasecmp(URL_SCHEME(url), host->scheme) == 0) &&
+ (dStrcasecmp(URL_AUTHORITY(url), host->authority) == 0)))
+ return host;
+
+ return NULL;
+}
+
+/*
+ * Search all realms for the one with the given name.
+ */
+static AuthRealm_t *Auth_realm_by_name(const AuthHost_t *host,
+ const char *name)
+{
+ AuthRealm_t *realm;
+ int i;
+
+ for (i = 0; (realm = dList_nth_data(host->realms, i)); i++)
+ if (strcmp(realm->name,name) == 0)
+ return realm;
+
+ return NULL;
+}
+
+/*
+ * Search all realms for the one with the best-matching path.
+ */
+static AuthRealm_t *Auth_realm_by_path(const AuthHost_t *host,
+ const char *path)
+{
+ AuthRealm_t *realm_best, *realm;
+ int i, j;
+ int match_length;
+
+ realm_best = NULL;
+ for (i = 0; (realm = dList_nth_data(host->realms, i)); i++) {
+ char *realm_path;
+
+ for (j = 0; (realm_path = dList_nth_data(realm->paths, j)); j++) {
+ int realm_path_length;
+
+ realm_path_length = strlen(realm_path);
+ if (Auth_path_is_inside(path, realm_path, realm_path_length) &&
+ !(realm_best && match_length >= realm_path_length)) {
+ realm_best = realm;
+ match_length = realm_path_length;
+ }
+ } /* for (j = 0; (path = ... */
+ } /* for (i = 0; (realm = ... */
+
+ return realm_best;
+}
+
+static int Auth_realm_includes_path(const AuthRealm_t *realm, const char *path)
+{
+ int i;
+ char *realm_path;
+
+ for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++)
+ if (Auth_path_is_inside(path, realm_path, strlen(realm_path)))
+ return 1;
+
+ return 0;
+}
+
+static void Auth_realm_add_path(AuthRealm_t *realm, const char *path)
+{
+ int len, i;
+ char *realm_path, *n_path;
+
+ n_path = strdup(path);
+ len = strlen(n_path);
+
+ /* remove trailing '/' */
+ if (len && n_path[len - 1] == '/')
+ n_path[--len] = 0;
+
+ /* delete existing paths that are inside the new one */
+ for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++) {
+ if (Auth_path_is_inside(realm_path, path, len)) {
+ dList_remove_fast(realm->paths, realm_path);
+ dFree(realm_path);
+ i--; /* reconsider this slot */
+ }
+ }
+
+ dList_append(realm->paths, n_path);
+}
+
+/*
+ * Return the authorization header for an HTTP query.
+ */
+char *a_Auth_get_auth_str(const DilloUrl *url)
+{
+ AuthHost_t *host;
+ AuthRealm_t *realm;
+
+ return
+ ((host = Auth_host_by_url(url)) &&
+ (realm = Auth_realm_by_path(host, URL_PATH(url)))) ?
+ realm->authorization : NULL;
+}
+
+/*
+ * Determine whether the user needs to authenticate.
+ */
+static int Auth_do_auth_required(const char *realm_name, const DilloUrl *url)
+{
+ /*
+ * TO DO: I dislike the way that this code must decide whether we
+ * sent authentication during the request and trust us to resend it
+ * after the reload. Could it be more robust if every DilloUrl
+ * recorded its authentication, and whether it was accepted? (JCH)
+ */
+
+ AuthHost_t *host;
+ AuthRealm_t *realm;
+
+ /*
+ * The size of the following comments reflects the concerns in the
+ * TO DO at the top of this function. It should not be so hard to
+ * explain why code is correct! (JCH)
+ */
+
+ /*
+ * If we have authentication but did not send it (because we did
+ * not know this path was in the realm) then we update the realm.
+ * We do not re-authenticate because our authentication is probably
+ * OK. Thanks to the updated realm the forthcoming reload will
+ * make us send the authentication. If our authentication is not
+ * OK the server will challenge us again after the reload and then
+ * we will re-authenticate.
+ */
+ if ((host = Auth_host_by_url(url)) &&
+ (realm = Auth_realm_by_name(host, realm_name)) &&
+ (!Auth_realm_includes_path(realm, URL_PATH(url)))) {
+ _MSG("Auth_do_auth_required: updating realm '%s' with URL '%s'\n",
+ realm_name, URL_STR(url));
+ Auth_realm_add_path(realm, URL_PATH(url));
+ return 0;
+ }
+
+ /*
+ * Either we had no authentication or we sent it and the server
+ * rejected it, so we must re-authenticate.
+ */
+ return 1;
+}
+
+static void Auth_do_auth_dialog_cb(const char *user, const char *password,
+ void *vData)
+{
+ AuthDialogData_t *data;
+ AuthHost_t *host;
+ AuthRealm_t *realm;
+ char *user_password, *response, *authorization, *authorization_old;
+
+ data = (AuthDialogData_t *)vData;
+
+ /* find or create the host */
+ if (!(host = Auth_host_by_url(data->url))) {
+ /* create a new host */
+ host = dNew(AuthHost_t, 1);
+ host->scheme = dStrdup(URL_SCHEME(data->url));
+ host->authority = dStrdup(URL_AUTHORITY(data->url));
+ host->realms = dList_new(1);
+ dList_append(auth_hosts, host);
+ }
+
+ /* find or create the realm */
+ if (!(realm = Auth_realm_by_name(host, data->realm_name))) {
+ /* create a new realm */
+ realm = dNew(AuthRealm_t, 1);
+ realm->name = dStrdup(data->realm_name);
+ realm->paths = dList_new(1);
+ realm->authorization = NULL;
+ dList_append(host->realms, realm);
+ }
+
+ Auth_realm_add_path(realm, URL_PATH(data->url));
+
+ /* create and set the authorization */
+ user_password = dStrconcat(user, ":", password, NULL);
+ response = a_Misc_encode_base64(user_password);
+ authorization =
+ dStrconcat("Authorization: Basic ", response, "\r\n", NULL);
+ authorization_old = realm->authorization;
+ realm->authorization = authorization;
+ dFree(authorization_old);
+ dFree(user_password);
+ dFree(response);
+}
+
+static int Auth_do_auth_dialog(const char *realm, const DilloUrl *url)
+{
+ int ret;
+ char *message;
+ AuthDialogData_t *data;
+
+ _MSG("auth.c: Auth_do_auth_dialog: realm = '%s'\n", realm);
+ message = dStrconcat("Enter a user and password for \"",
+ realm, "\".", NULL);
+ data = dNew(AuthDialogData_t, 1);
+ data->realm_name = dStrdup(realm);
+ data->url = a_Url_dup(url);
+ ret = a_Dialog_user_password(message, Auth_do_auth_dialog_cb, data);
+ dFree(message);
+ dFree((void*)data->realm_name);
+ a_Url_free((void*)data->url);
+ dFree(data);
+ return ret;
+}
+
+/*
+ * Do authorization for an auth string.
+ */
+static int Auth_do_auth(char *auth, const DilloUrl *url)
+{
+ int reload;
+ AuthParse_t *auth_parse;
+
+ _MSG("auth.c: Auth_do_auth: auth={%s}\n", auth);
+ reload = 0;
+ auth_parse = Auth_parse_new();
+ Auth_parse_auth(auth_parse, auth);
+ if (auth_parse->ok)
+ reload =
+ Auth_do_auth_required(auth_parse->realm, url) ?
+ Auth_do_auth_dialog(auth_parse->realm, url)
+ : 1;
+ Auth_parse_free(auth_parse);
+
+ return reload;
+}
+
+/*
+ * Do authorization for a set of auth strings.
+ */
+int a_Auth_do_auth(Dlist *auths, const DilloUrl *url)
+{
+ int reload, i;
+ char *auth;
+
+ reload = 0;
+ for (i = 0; (auth = dList_nth_data(auths, i)); ++i)
+ if (Auth_do_auth(auth, url))
+ reload = 1;
+
+ return reload;
+}
+