changeset 470:b01e80ee7c88

Added basic authentication!
author Jorge Arellano Cid <jcid@dillo.org>
date Sat, 20 Dec 2008 17:19:27 -0300
parents 06a11189d75f
children f2280b602ff7 0179a1a1fd0c
files ChangeLog src/IO/http.c src/Makefile.am src/auth.c src/auth.h src/bw.h src/cache.c src/dialog.cc src/dialog.hh src/dillo.cc
diffstat 10 files changed, 702 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Dec 19 20:18:35 2008 -0300
+++ b/ChangeLog	Sat Dec 20 17:19:27 2008 -0300
@@ -5,7 +5,7 @@
 dillo-2.1
 
 +- Added ipv6 addresses iteration and ipv4 fallback.
-   Patch: James Turner, Jorge Arellano
+   Patch: James Turner, Jorge Arellano Cid
 +- Added support for numeric IPv6 addresses entered into the url bar.
  - Used the URL authority part instead of stripped default port in HTTP query.
    Patches: Justus Winter
@@ -26,6 +26,8 @@
  - Set the ScrollGroup as the resizable widget in downloads dpi.
  - Cleaned up and normalized D_SUN_LEN usage.
    Patches: Jeremy Henty
++- Implemented Basic authentication!
+   Patch: Jeremy Henty, Jorge Arellano Cid
 +- Allowed compilation with older machines by removing a few C99isms.
  - Added use of inttypes.h when stdint.h isn't found.
    Patches: Dan Fandrich
@@ -36,12 +38,12 @@
 +- Made the parser recognize "[^ ]/>"-terminated XML elements.
    Patch: Johannes Hofmann
 +- Added the "middle_click_drags_page" dillorc option.
-   Patch: Jorge Arellano, Thomas Orgis
+   Patch: Jorge Arellano Cid, Thomas Orgis
 +- Set the File menu label to hide when the File menu-button is shown.
  ? Trying a new iconv() test in configure.in.
  - Allowed the rc parser to skip whitespace around the equal sign.
  - Fixed the parser not to call Html_tag_close_* functions twice.
-   Patches: Jorge Arellano
+   Patches: Jorge Arellano Cid
 
 dw
 
--- a/src/IO/http.c	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/IO/http.c	Sat Dec 20 17:19:27 2008 -0300
@@ -33,6 +33,7 @@
 #include "../dns.h"
 #include "../web.hh"
 #include "../cookies.h"
+#include "../auth.h"
 #include "../prefs.h"
 #include "../misc.h"
 
@@ -196,7 +197,7 @@
  */
 Dstr *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy)
 {
-   char *ptr, *cookies, *referer;
+   char *ptr, *cookies, *auth, *referer;
    Dstr *query      = dStr_new(""),
         *full_path  = dStr_new(""),
         *proxy_auth = dStr_new("");
@@ -219,6 +220,7 @@
    }
 
    cookies = a_Cookies_get_query(url);
+   auth = a_Auth_get_auth_str(url);
    referer = Http_get_referer(url);
    if (URL_FLAGS(url) & URL_Post) {
       Dstr *content_type = Http_make_content_type(url);
@@ -228,15 +230,16 @@
          "Connection: close\r\n"
          "Accept-Charset: utf-8,*;q=0.8\r\n"
          "Accept-Encoding: gzip\r\n"
+         "%s" /* auth */
          "Host: %s\r\n"
          "%s"
          "%s"
          "User-Agent: Dillo/%s\r\n"
          "Content-Length: %ld\r\n"
          "Content-Type: %s\r\n"
-         "%s"
+         "%s" /* cookies */
          "\r\n",
-         full_path->str, URL_AUTHORITY(url),
+         full_path->str, auth ? auth : "", URL_AUTHORITY(url),
          proxy_auth->str, referer, VERSION,
          URL_DATA(url)->len, content_type->str,
          cookies);
@@ -250,16 +253,17 @@
          "Connection: close\r\n"
          "Accept-Charset: utf-8,*;q=0.8\r\n"
          "Accept-Encoding: gzip\r\n"
+         "%s" /* auth */
          "Host: %s\r\n"
          "%s"
          "%s"
          "User-Agent: Dillo/%s\r\n"
-         "%s"
+         "%s" /* cookies */
          "\r\n",
          full_path->str,
          (URL_FLAGS(url) & URL_E2EQuery) ?
             "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "",
-         URL_AUTHORITY(url),
+         auth ? auth : "", URL_AUTHORITY(url),
          proxy_auth->str, referer, VERSION, cookies);
    }
    dFree(referer);
--- a/src/Makefile.am	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/Makefile.am	Sat Dec 20 17:19:27 2008 -0300
@@ -28,6 +28,8 @@
 	bw.c \
 	cookies.c \
 	cookies.h \
+	auth.c \
+	auth.h \
 	colors.c \
 	colors.h \
 	binaryconst.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth.c	Sat Dec 20 17:19:27 2008 -0300
@@ -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;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth.h	Sat Dec 20 17:19:27 2008 -0300
@@ -0,0 +1,19 @@
+#ifndef __AUTH_H__
+#define __AUTH_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "url.h"
+
+
+char *a_Auth_get_auth_str(const DilloUrl *request_url);
+int a_Auth_do_auth(Dlist *auth_string, const DilloUrl *url);
+void a_Auth_init(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* !__AUTH_H__ */
--- a/src/bw.h	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/bw.h	Sat Dec 20 17:19:27 2008 -0300
@@ -84,6 +84,7 @@
 void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url);
 void a_Bw_cleanup(BrowserWindow *bw);
 
+typedef void (*BwCallback_t)(BrowserWindow *bw, const void *data);
 
 #ifdef __cplusplus
 }
--- a/src/cache.c	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/cache.c	Sat Dec 20 17:19:27 2008 -0300
@@ -32,6 +32,7 @@
 #include "misc.h"
 #include "capi.h"
 #include "decode.h"
+#include "auth.h"
 
 #include "timeout.hh"
 #include "uicmd.hh"
@@ -54,6 +55,7 @@
    char *TypeMeta;           /* MIME type string from META HTTP-EQUIV */
    Dstr *Header;             /* HTTP header */
    const DilloUrl *Location; /* New URI for redirects */
+   Dlist *Auth;              /* Authentication fields */
    Dstr *Data;               /* Pointer to raw data */
    Dstr *UTF8Data;           /* Data after charset translation */
    int DataRefcount;         /* Reference count */
@@ -87,6 +89,7 @@
  */
 static void Cache_process_queue(CacheEntry_t *entry);
 static void Cache_delayed_process_queue(CacheEntry_t *entry);
+static void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw);
 
 
 /*
@@ -206,6 +209,7 @@
    NewEntry->TypeMeta = NULL;
    NewEntry->Header = dStr_new("");
    NewEntry->Location = NULL;
+   NewEntry->Auth = NULL;
    NewEntry->Data = dStr_sized_new(8*1024);
    NewEntry->UTF8Data = NULL;
    NewEntry->DataRefcount = 0;
@@ -288,6 +292,18 @@
 }
 
 /*
+ *  Free Authentication fields.
+ */
+static void Cache_auth_free(Dlist *auth)
+{
+   int i;
+   void *auth_field;
+   for (i = 0; (auth_field = dList_nth_data(auth, i)); ++i)
+      dFree(auth_field);
+   dList_free(auth);
+}
+
+/*
  *  Free the components of a CacheEntry_t struct.
  */
 static void Cache_entry_free(CacheEntry_t *entry)
@@ -298,6 +314,7 @@
    dFree(entry->TypeMeta);
    dStr_free(entry->Header, TRUE);
    a_Url_free((DilloUrl *)entry->Location);
+   Cache_auth_free(entry->Auth);
    dStr_free(entry->Data, 1);
    dStr_free(entry->UTF8Data, 1);
    if (entry->CharsetDecoder)
@@ -655,6 +672,9 @@
          }
          dFree(location_str);
 
+      } else if (strncmp(header + 9, "401", 3) == 0) {
+         entry->Auth =
+            Cache_parse_multiple_fields(header, "WWW-Authenticate");
       } else if (strncmp(header + 9, "404", 3) == 0) {
          entry->Flags |= CA_NotFound;
       }
@@ -927,6 +947,50 @@
    return 0;
 }
 
+typedef struct {
+   Dlist *auth;
+   DilloUrl *url;
+   BrowserWindow *bw;
+} CacheAuthData_t;
+
+/*
+ * Ask for user/password and reload the page.
+ */
+static void Cache_auth_callback(void *vdata)
+{
+   CacheAuthData_t *data = (CacheAuthData_t *)vdata;
+   if (a_Auth_do_auth(data->auth, data->url))
+      a_Nav_reload(data->bw);
+   Cache_auth_free(data->auth);
+   a_Url_free(data->url);
+   dFree(data);
+   Cache_auth_entry(NULL, NULL);
+   a_Timeout_remove();
+}
+
+/*
+ * Set a timeout function to ask for user/password.
+ */ 
+static void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw)
+{
+   static int busy = 0;
+   CacheAuthData_t *data;
+
+   if (!entry) {
+      busy = 0;
+   } else if (busy) {
+      MSG_WARN("Cache_auth_entry: caught busy!\n");
+   } else if (entry->Auth) {
+      busy = 1;
+      data = dNew(CacheAuthData_t, 1);
+      data->auth = entry->Auth;
+      data->url = a_Url_dup(entry->Url);
+      data->bw = bw;
+      entry->Auth = NULL;
+      a_Timeout_add(0.0, Cache_auth_callback, data);
+   }
+}
+
 /*
  * Check whether a URL scheme is downloadable.
  * Return: 1 enabled, 0 disabled.
@@ -1114,6 +1178,9 @@
          a_UIcmd_save_link(Client_bw, url);
       }
       a_Url_free(url);
+
+   } else if (entry->Auth && (entry->Flags & CA_GotData)) {
+      Cache_auth_entry(entry, Client_bw);
    }
 
    /* Trigger cleanup when there are no cache clients */
--- a/src/dialog.cc	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/dialog.cc	Sat Dec 20 17:19:27 2008 -0300
@@ -20,6 +20,9 @@
 #include <fltk/ReturnButton.h>
 #include <fltk/TextDisplay.h>
 #include <fltk/HighlightButton.h>
+#include <fltk/WordwrapOutput.h>
+#include <fltk/Input.h>
+#include <fltk/SecretInput.h>
 
 #include "msg.h"
 #include "dialog.hh"
@@ -227,3 +230,79 @@
    return choice5_answer;
 }
 
+
+/*--------------------------------------------------------------------------*/
+static int ok_answer = 1, cancel_answer = 0;
+static void Dialog_user_password_cb(Widget *button, void *vIntPtr)
+{
+   int ok = VOIDP2INT(vIntPtr);
+  _MSG("Dialog_user_password_cb: %d\n", ok);
+  button->window()->make_exec_return(ok);
+}
+
+/*
+ * Make a user/password dialog.
+ * Call the callback with the result (OK or not) and the given user and
+ *   password if OK.
+ */
+int a_Dialog_user_password(const char *message, UserPasswordCB cb, void *vp)
+{
+   int ok,
+      window_w = 300, window_h = 280,
+      input_x = 80, input_w = 200, input_h = 30,
+      button_y = 230, button_h = 30;
+
+   Window *window =
+      new Window(window_w,window_h,"User/Password");
+   window->begin();
+
+   /* message */
+   WordwrapOutput *message_output =
+      new WordwrapOutput(20,20,window_w-40,100);
+   message_output->box(DOWN_BOX);
+   message_output->text(message);
+   message_output->textfont(HELVETICA_BOLD_ITALIC);
+   message_output->textsize(14);
+
+   /* inputs */
+   Input *user_input =
+      new Input(input_x,140,input_w,input_h,"User");
+   user_input->labelsize(14);
+   user_input->textsize(14);
+   SecretInput *password_input =
+      new SecretInput(input_x,180,input_w,input_h,"Password");
+   password_input->labelsize(14);
+   password_input->textsize(14);
+
+   /* "OK" button */
+   Button *ok_button =
+      new Button(200,button_y,50,button_h,"OK");
+   ok_button->labelsize(14);
+   ok_button->callback(Dialog_user_password_cb);
+   ok_button->user_data(&ok_answer);
+
+   /* "Cancel" button */
+   Button *cancel_button =
+      new Button(50,button_y,100,button_h,"Cancel");
+   cancel_button->labelsize(14);
+   cancel_button->callback(Dialog_user_password_cb);
+   cancel_button->user_data(&cancel_answer);
+
+   window->end();
+   window->size_range(window_w,window_h,window_w,window_h);
+   window->resizable(window);
+
+   if ((ok = window->exec())) {
+      /* call the callback */
+      const char *user, *password;
+      user = user_input->value();
+      password = password_input->value();
+      _MSG("a_Dialog_user_passwd: ok = %d\n", ok);
+      (*cb)(user, password, vp);
+   }
+
+   delete window;
+
+   return ok;
+}
+
--- a/src/dialog.hh	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/dialog.hh	Sat Dec 20 17:19:27 2008 -0300
@@ -5,12 +5,16 @@
 extern "C" {
 #endif /* __cplusplus */
 
+typedef void (*UserPasswordCB)(const char *user, const char *password,
+                               void *vp);
+
 void a_Dialog_msg(const char *msg);
 int a_Dialog_choice3(const char *msg,
                      const char *b0, const char *b1, const char *b2);
 int a_Dialog_choice5(const char *QuestionTxt,
                      const char *alt1, const char *alt2, const char *alt3,
                      const char *alt4, const char *alt5);
+int a_Dialog_user_password(const char *message, UserPasswordCB cb, void *vp);
 const char *a_Dialog_input(const char *msg);
 const char *a_Dialog_passwd(const char *msg);
 const char *a_Dialog_save_file(const char *msg,
--- a/src/dillo.cc	Fri Dec 19 20:18:35 2008 -0300
+++ b/src/dillo.cc	Sat Dec 20 17:19:27 2008 -0300
@@ -45,6 +45,7 @@
 #include "capi.h"
 #include "dicache.h"
 #include "cookies.h"
+#include "auth.h"
 
 
 /*
@@ -102,6 +103,7 @@
    a_Dicache_init();
    a_Bw_init();
    a_Cookies_init();
+   a_Auth_init();
 
    // Sets WM_CLASS hint on X11
    fltk::Window::xclass("dillo");