From: hno <> Date: Thu, 1 Feb 2001 05:16:35 +0000 (+0000) Subject: Major update from auth_rewrite X-Git-Tag: SQUID_3_0_PRE1~1620 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2d70df7262cac3e29d34bed03b758695f5961b74;p=thirdparty%2Fsquid.git Major update from auth_rewrite Robert Collins: Digest (RFC2617) proxy authentication implementation Chemolli Francesco: Several bugs in NTLM authentication when dealing with untrusted domains, wrong passwords, helper arguments and more have been fixed --- diff --git a/configure.in b/configure.in index ed9a72c4a2..6d387f89ac 100644 --- a/configure.in +++ b/configure.in @@ -3,13 +3,13 @@ dnl Configuration input file for Squid dnl dnl Duane Wessels, wessels@nlanr.net, February 1996 (autoconf v2.9) dnl -dnl $Id: configure.in,v 1.219 2001/01/18 17:57:04 wessels Exp $ +dnl $Id: configure.in,v 1.220 2001/01/31 22:16:35 hno Exp $ dnl dnl dnl AC_INIT(src/main.c) AC_CONFIG_HEADER(include/autoconf.h) -AC_REVISION($Revision: 1.219 $)dnl +AC_REVISION($Revision: 1.220 $)dnl AC_PREFIX_DEFAULT(/usr/local/squid) AC_CONFIG_AUX_DIR(cfgaux) @@ -785,6 +785,32 @@ if test -n "$NTLM_AUTH_HELPERS"; then fi AC_SUBST(NTLM_AUTH_HELPERS) +dnl Select digest auth scheme helpers to build +DIGEST_AUTH_HELPERS= +AC_ARG_ENABLE(digest-auth-helpers, +[ --enable-digest-auth-helpers=\"list of helpers\" + This option selects which digest scheme authentication + helpers to build and install as part of the normal build + process. For a list of available modules see the + src/auth/digest/helpers directory.], +[ case "$enableval" in + yes) + for helper in $srcdir/src/auth/digest/helpers/*; do + if test -f $helper/Makefile.in; then + DIGEST_AUTH_HELPERS="$DIGEST_AUTH_HELPERS `basename $helper`" + fi + done + ;; + no) + ;; + *) + DIGEST_AUTH_HELPERS="`echo $enableval| sed -e 's/,/ /g;s/ */ /g'`" + esac +]) +if test -n "$DIGEST_AUTH_HELPERS"; then + echo "Digest auth helpers built: $DIGEST_AUTH_HELPERS" +fi +AC_SUBST(DIGEST_AUTH_HELPERS) dnl Disable "unlinkd" code AC_ARG_ENABLE(unlinkd, diff --git a/doc/Programming-Guide/prog-guide.sgml b/doc/Programming-Guide/prog-guide.sgml index b5e3ce89b8..be443093ed 100644 --- a/doc/Programming-Guide/prog-guide.sgml +++ b/doc/Programming-Guide/prog-guide.sgml @@ -2,7 +2,7 @@
Squid Programmers Guide Duane Wessels, Squid Developers -$Id: prog-guide.sgml,v 1.34 2001/01/07 23:36:35 hno Exp $ +$Id: prog-guide.sgml,v 1.35 2001/01/31 22:16:36 hno Exp $ Squid is a WWW Cache application developed by the National Laboratory @@ -2063,10 +2063,15 @@ coupling between the storage layer and the replacement policy.

typedef int AUTHSACTIVE();

The Active function is used by squid to determine whether the auth - module has successfully configured and initialised itself. If Active - returns 0 no other module functions except Shutdown/Dump/Parse/FreeConfig - will be called by Squid. - + module has successfully initialised itself with the current configuration. + +

typedef int AUTHSCONFIGURED(); + +

The configured function is used to see if the auth module has been given + valid parameters and is able to handle authentication requests if initialised. + If configured returns 0 no other module functions except + Shutdown/Dump/Parse/FreeConfig will be called by Squid. +

typedef void AUTHSSETUP(authscheme_entry_t *);

functions of type AUTHSSETUP are used to register an auth module with diff --git a/helpers/digest_auth/Makefile.in b/helpers/digest_auth/Makefile.in new file mode 100644 index 0000000000..4fb2616083 --- /dev/null +++ b/helpers/digest_auth/Makefile.in @@ -0,0 +1,38 @@ +# Makefile for digest auth helpers in the Squid Object Cache server +# +# $Id: Makefile.in,v 1.1 2001/01/31 22:16:42 hno Exp $ +# + +# The 'nop' is in the SUBDIRS list because some Unixes that can't +# handle empty for lists. + +SUBDIRS = @DIGEST_AUTH_HELPERS@ nop + +all: + @for dir in $(SUBDIRS); do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) all" || exit 1; \ + fi; \ + done; + +clean: + -for dir in *; do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) clean"; \ + fi; \ + done + +distclean: + -rm -f Makefile + -for dir in *; do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) distclean"; \ + fi; \ + done + +.DEFAULT: + @for dir in $(SUBDIRS); do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) $@" || exit 1; \ + fi; \ + done; diff --git a/helpers/digest_auth/password/Makefile.in b/helpers/digest_auth/password/Makefile.in new file mode 100644 index 0000000000..ec9b4b868f --- /dev/null +++ b/helpers/digest_auth/password/Makefile.in @@ -0,0 +1,100 @@ +# +# Makefile for the Squid Object Cache server +# +# $Id: Makefile.in,v 1.1 2001/01/31 22:16:42 hno Exp $ +# +# Uncomment and customize the following to suit your needs: +# + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +exec_suffix = @exec_suffix@ +cgi_suffix = @cgi_suffix@ +top_srcdir = @top_srcdir@ +bindir = @bindir@ +libexecdir = @libexecdir@ +sysconfdir = @sysconfdir@ +localstatedir = @localstatedir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +# Gotta love the DOS legacy +# +DIGEST_PW_AUTH_EXE = digest_pw_auth$(exec_suffix) + +DEFAULT_PASSWD_FILE = $(sysconfdir)/digest_passwd + +CC = @CC@ +MAKEDEPEND = @MAKEDEPEND@ +INSTALL = @INSTALL@ +INSTALL_BIN = @INSTALL_PROGRAM@ +INSTALL_FILE = @INSTALL_DATA@ +INSTALL_SUID = @INSTALL_PROGRAM@ -o root -m 4755 +RANLIB = @RANLIB@ +LN_S = @LN_S@ +PERL = @PERL@ +CRYPTLIB = @CRYPTLIB@ +REGEXLIB = @REGEXLIB@ +PTHREADLIB = @PTHREADLIB@ +SNMPLIB = @SNMPLIB@ +MALLOCLIB = @LIB_MALLOC@ +AC_CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +XTRA_LIBS = @XTRA_LIBS@ +XTRA_OBJS = @XTRA_OBJS@ +MV = @MV@ +RM = @RM@ +SHELL = /bin/sh + + +INCLUDE = -I. -I../../../../../include -I$(top_srcdir)/include +CFLAGS = $(AC_CFLAGS) $(INCLUDE) $(DEFINES) +AUTH_LIBS = -L../../../../../lib -lmiscutil $(CRYPTLIB) $(XTRA_LIBS) + +PROGS = $(DIGEST_PW_AUTH_EXE) +OBJS = digest_pw_auth.o + +all: $(DIGEST_PW_AUTH_EXE) + +$(OBJS): $(top_srcdir)/include/version.h + +$(DIGEST_PW_AUTH_EXE): digest_pw_auth.o + $(CC) $(LDFLAGS) digest_pw_auth.o -o $@ $(AUTH_LIBS) + +install-mkdirs: + -@if test ! -d $(prefix); then \ + echo "mkdir $(prefix)"; \ + mkdir $(prefix); \ + fi + -@if test ! -d $(bindir); then \ + echo "mkdir $(bindir)"; \ + mkdir $(bindir); \ + fi + +# Michael Lupp wants to know about additions +# to the install target. +install: all install-mkdirs + @for f in $(PROGS); do \ + if test -f $(bindir)/$$f; then \ + echo $(MV) $(bindir)/$$f $(bindir)/-$$f; \ + $(MV) $(bindir)/$$f $(bindir)/-$$f; \ + fi; \ + echo $(INSTALL_BIN) $$f $(bindir); \ + $(INSTALL_BIN) $$f $(bindir); \ + if test -f $(bindir)/-$$f; then \ + echo $(RM) -f $(bindir)/-$$f; \ + $(RM) -f $(bindir)/-$$f; \ + fi; \ + done + +clean: + -rm -rf *.o *pure_* core $(PROGS) + +distclean: clean + -rm -f Makefile + +tags: + ctags *.[ch] ../include/*.h ../lib/*.[ch] + +depend: + $(MAKEDEPEND) -I../include -I. -fMakefile *.c diff --git a/helpers/digest_auth/password/digest_pw_auth.c b/helpers/digest_auth/password/digest_pw_auth.c new file mode 100644 index 0000000000..dc921a1d05 --- /dev/null +++ b/helpers/digest_auth/password/digest_pw_auth.c @@ -0,0 +1,158 @@ +/* + * digest_pw_auth.c + * + * AUTHOR: Robert Collins. Based on ncsa_auth.c by Arjan de Vet + * + * Example digest authentication program for Squid, based on the original + * proxy_auth code from client_side.c, written by + * Jon Thackray . + * + * - comment lines are possible and should start with a '#'; + * - empty or blank lines are possible; + * - file format is username:password + * + * To build a directory integrated backend, you need to be able to + * calculate the HA1 returned to squid. To avoid storing a plaintext + * password you can calculate MD5(username:realm:password) when the user changes their + * password, and store the tuple username:realm:HA1. then find the matching + * username:realm when squid asks for the HA1. + * + * This implementation could be improved by using such a triple for the file format. + * However storing such a triple does little to improve security: If compromised the + * username:realm:HA1 combination is "plaintext equivalent" - for the purposes of + * digest authentication they allow the user access. Password syncronisation + * is not tackled by digest - just preventing on the wire compromise. + * + */ + +#include "config.h" +#if HAVE_STDIO_H +#include +#endif +#if HAVE_STDLIB_H +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_STRING_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_CRYPT_H +#include +#endif + +#include "util.h" +#include "hash.h" +#include "rfc2617.h" + +static hash_table *hash = NULL; +static HASHFREE my_free; + +typedef struct _user_data { + /* first two items must be same as hash_link */ + char *user; + struct _user_data *next; + char *passwd; + char *realm; +} user_data; + +static void +my_free(void *p) +{ + user_data *u = p; + xfree(u->user); + xfree(u->passwd); + xfree(u); +} + +static void +read_passwd_file(const char *passwdfile) +{ + FILE *f; + char buf[8192]; + user_data *u; + char *user; + char *passwd; + if (hash != NULL) { + hashFreeItems(hash, my_free); + } + /* initial setup */ + hash = hash_create((HASHCMP *) strcmp, 7921, hash_string); + if (NULL == hash) { + fprintf(stderr, "digest_pw_auth: cannot create hash table\n"); + exit(1); + } + f = fopen(passwdfile, "r"); + while (fgets(buf, 8192, f) != NULL) { + if ((buf[0] == '#') || (buf[0] == ' ') || (buf[0] == '\t') || + (buf[0] == '\n')) + continue; + user = strtok(buf, ":\n"); + passwd = strtok(NULL, ":\n"); + if ((strlen(user) > 0) && passwd) { + u = xmalloc(sizeof(*u)); + u->user = xstrdup(user); + u->passwd = xstrdup(passwd); + hash_join(hash, (hash_link *) u); + } + } + fclose(f); +} + +int +main(int argc, char **argv) +{ + struct stat sb; + time_t change_time = 0; + char buf[256]; + char *user, *realm, *p; + user_data *u; + HASH HA1; + HASHHEX HHA1; + setbuf(stdout, NULL); + if (argc != 2) { + fprintf(stderr, "Usage: digest_pw_auth \n"); + exit(1); + } + if (stat(argv[1], &sb) != 0) { + fprintf(stderr, "cannot stat %s\n", argv[1]); + exit(1); + } + while (fgets(buf, 256, stdin) != NULL) { + if ((p = strchr(buf, '\n')) != NULL) + *p = '\0'; /* strip \n */ + if (stat(argv[1], &sb) == 0) { + if (sb.st_mtime != change_time) { + read_passwd_file(argv[1]); + change_time = sb.st_mtime; + } + } + if ((user = strtok(buf, "\"")) == NULL) { + printf("ERR\n"); + continue; + } + if ((realm = strtok(NULL, "\"")) == NULL) { + printf("ERR\n"); + continue; + } + if ((realm = strtok(NULL, "\"")) == NULL) { + printf("ERR\n"); + continue; + } + u = hash_lookup(hash, user); + if (u == NULL) { + printf("ERR\n"); + } else { + DigestCalcHA1("md5", user, realm, u->passwd, NULL, NULL, HA1, HHA1); + printf("%s\n", HHA1); + } + } + exit(0); +} diff --git a/helpers/ntlm_auth/SMB/libntlmssp.c b/helpers/ntlm_auth/SMB/libntlmssp.c index 6c9ef0859f..f888ebf5bf 100644 --- a/helpers/ntlm_auth/SMB/libntlmssp.c +++ b/helpers/ntlm_auth/SMB/libntlmssp.c @@ -28,11 +28,15 @@ #include #endif +/* these are part of rfcnb-priv.h and smblib-priv.h */ +extern int SMB_Get_Error_Msg(int msg, char *msgbuf, int len); +extern int SMB_Get_Last_Error(); +extern int RFCNB_Get_Last_Errno(); + #include "smblib-priv.h" /* for SMB_Handle_Type */ /* a few forward-declarations. Hackish, but I don't care right now */ -SMB_Handle_Type SMB_Connect_Server(SMB_Handle_Type Con_Handle, - char *server, char *NTdomain); +SMB_Handle_Type SMB_Connect_Server(SMB_Handle_Type Con_Handle, char *server, char *NTdomain); /* this one is reallllly haackiish. We really should be using anything from smblib-priv.h */ @@ -49,13 +53,13 @@ static char *SMB_Prots[] = "Samba", "NT LM 0.12", "NT LANMAN 1.0", - NULL}; + NULL +}; #if 0 int SMB_Discon(SMB_Handle_Type Con_Handle, BOOL KeepHandle); int SMB_Negotiate(void *Con_Handle, char *Prots[]); -int SMB_Logon_Server(SMB_Handle_Type Con_Handle, char *UserName, - char *PassWord, char *Domain, int precrypted); +int SMB_Logon_Server(SMB_Handle_Type Con_Handle, char *UserName, char *PassWord, char *Domain, int precrypted); #endif #ifdef DEBUG @@ -87,8 +91,7 @@ connectedp() /* Tries to connect to a DC. Returns 0 on failure, 1 on OK */ int -is_dc_ok(char *domain, - char *domain_controller) +is_dc_ok(char *domain, char *domain_controller) { SMB_Handle_Type h = SMB_Connect_Server(NULL, domain_controller, domain); if (h == NULL) @@ -137,21 +140,46 @@ init_challenge(char *domain, char *domain_controller) const char * make_challenge(char *domain, char *domain_controller) { - if (init_challenge(domain, domain_controller) > 0) + if (init_challenge(domain, domain_controller) > 0) { return NULL; - return ntlm_make_challenge(domain, domain_controller, challenge, - NONCE_LEN); + } + return ntlm_make_challenge(domain, domain_controller, challenge, NONCE_LEN); } #define min(A,B) (Adomain); + if (tmp.str == NULL) + return NULL; + memcpy(p, tmp.str, tmp.l); + p += tmp.l; + *p++ = '\\'; + tmp = ntlm_fetch_string((char *) auth, auth_length, &auth->user); + if (tmp.str == NULL) + return NULL; + *(p + tmp.l) = '\0'; + return credentials; +} + /* returns NULL on failure, or a pointer to * the user's credentials (domain\\username) * upon success. WARNING. It's pointing to static storage. * In case of problem sets as side-effect ntlm_errno to one of the * codes defined in ntlm.h */ -int ntlm_errno; -static char credentials[1024]; /* we can afford to waste */ char * ntlm_check_auth(ntlm_authenticate * auth, int auth_length) { @@ -200,21 +228,13 @@ ntlm_check_auth(ntlm_authenticate * auth, int auth_length) debug("checking domain: '%s', user: '%s', pass='%s'\n", domain, user, pass); rv = SMB_Logon_Server(handle, user, pass, domain, 1); - - while ((rv == NTLM_BAD_PROTOCOL || rv == NTLM_SERVER_ERROR) - && retries < BAD_DC_RETRIES_NUMBER) { - retries++; - usleep((unsigned long) 100000); - rv = SMB_Logon_Server(handle, user, pass, domain, 1); - } - - debug("\tresult is %d\n", rv); + debug("Login attempt had result %d\n", rv); if (rv != NTV_NO_ERROR) { /* failed */ ntlm_errno = rv; return NULL; } - *(user - 1) = '\\'; + *(user - 1) = '\\'; /* hack. Performing, but ugly. */ debug("credentials: %s\n", credentials); return credentials; diff --git a/helpers/ntlm_auth/SMB/ntlm.h b/helpers/ntlm_auth/SMB/ntlm.h index 65b65915a5..df48fb2468 100644 --- a/helpers/ntlm_auth/SMB/ntlm.h +++ b/helpers/ntlm_auth/SMB/ntlm.h @@ -33,12 +33,9 @@ /* * define this if you want debugging */ -#define DEBUG - -/* - * Number of authentication attempts to perform in case of certain errors - */ -#define BAD_DC_RETRIES_NUMBER 3 +#ifndef DEBUG +/* #define DEBUG */ +#endif /************* END CONFIGURATION ***************/ @@ -74,12 +71,14 @@ extern int ntlm_errno; #define NTLM_SERVER_ERROR 1 #define NTLM_PROTOCOL_ERROR 2 #define NTLM_LOGON_ERROR 3 +#define NTLM_UNTRUSTED_DOMAIN 4 #define NTLM_BAD_PROTOCOL -1 #define NTLM_NOT_CONNECTED 10 const char *make_challenge(char *domain, char *controller); extern char *ntlm_check_auth(ntlm_authenticate * auth, int auth_length); +extern char *fetch_credentials(ntlm_authenticate * auth, int auth_length); void dc_disconnect(void); int connectedp(void); int is_dc_ok(char *domain, char *domain_controller); diff --git a/helpers/ntlm_auth/SMB/ntlm_auth.c b/helpers/ntlm_auth/SMB/ntlm_auth.c index 8656f64a9b..774c8eacd7 100644 --- a/helpers/ntlm_auth/SMB/ntlm_auth.c +++ b/helpers/ntlm_auth/SMB/ntlm_auth.c @@ -12,24 +12,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * - * Warning! We MIGHT be open to buffer overflows caused by malformed headers - * - * DONE list: - * use hashtable to cache authentications. Yummy performance-boost, security - * loss should be negligible for two reasons: - * - if they-re using NT, there's no security to speak of anyways - * - it can't get worse than basic authentication. - * cache expiration - * challenge hash expiry and renewal. - * PDC disconnect, after X minutes of inactivity - * - * TODO list: - * change syntax from options-driven to args-driven, with args domain - * or domain[/\]server, and an arbitrary number of backup Domain Controllers - * we don't really need the "status" management, it's more for debugging - * purposes. Remove it. - * Maybe we can cache the created challenge, saving more time? - * */ @@ -38,19 +20,20 @@ #include "ntlm.h" #include "util.h" +/* these are part of rfcnb-priv.h and smblib-priv.h */ +extern int SMB_Get_Error_Msg(int msg, char *msgbuf, int len); +extern int SMB_Get_Last_Error(); +extern int SMB_Get_Last_SMB_Err(); + + #define BUFFER_SIZE 10240 #if HAVE_STDLIB_H #include #endif - - #if HAVE_GETOPT_H #include #endif - - - #ifdef HAVE_STRING_H #include #endif @@ -58,12 +41,18 @@ #include #endif -char load_balance = 0, failover_enabled = 0, protocol_pedantic = 0; +#ifdef DEBUG +char error_messages_buffer[BUFFER_SIZE]; +#endif + +char load_balance = 0, failover_enabled = 0, protocol_pedantic = 0, last_ditch_enabled = 0; dc *controllers = NULL; int numcontrollers = 0; dc *current_dc; +char smb_error_buffer[1000]; + /* housekeeping cycle and periodic operations */ static unsigned char need_dc_resurrection = 0; static void @@ -100,10 +89,28 @@ lc(char *string) } } + +void +send_bh_or_ld(char *bhmessage, ntlm_authenticate * failedauth, int authlen) +{ + char *creds = NULL; + if (last_ditch_enabled) { + creds = fetch_credentials(failedauth, authlen); + if (creds) { + SEND2("LD %s", creds); + } else { + SEND("NA last-ditch on, but no credentials"); + } + } else { + SEND(bhmessage); + } +} + /* * options: * -b try load-balancing the domain-controllers * -f fail-over to another DC if DC connection fails. + * -l last-ditch-mode * domain\controller ... */ char *my_program_name = NULL; @@ -115,12 +122,12 @@ usage() "%s usage:\n" "%s [-b] [-f] domain\\controller [domain\\controller ...]\n" "-b, if specified, enables load-balancing among controllers\n" - "-f, if specified, enables failover among controllers\n\n" - "You MUST specify at least one Domain Controller.\n" + "-f, if specified, enables failover among controllers\n" + "-l, if specified, changes behavior on domain controller failyures to" + "\tlast-ditch\n\n" "You MUST specify at least one Domain Controller.\n" "You can use either \\ or / as separator between the domain name \n" - "\tand the controller name\n" - ,my_program_name - ,my_program_name); + "\tand the controller name\n", + my_program_name, my_program_name); } @@ -129,7 +136,7 @@ process_options(int argc, char *argv[]) { int opt, j, had_error = 0; dc *new_dc = NULL, *last_dc = NULL; - while (-1 != (opt = getopt(argc, argv, "bf"))) { + while (-1 != (opt = getopt(argc, argv, "bfl"))) { switch (opt) { case 'b': load_balance = 1; @@ -137,6 +144,9 @@ process_options(int argc, char *argv[]) case 'f': failover_enabled = 1; break; + case 'l': + last_ditch_enabled = 1; + break; default: fprintf(stderr, "unknown option: -%c. Exiting\n", opt); usage(); @@ -149,15 +159,27 @@ process_options(int argc, char *argv[]) /* we can avoid memcpy-ing, and just reuse argv[] */ for (j = optind; j < argc; j++) { char *d, *c; - d = argv[j]; + /* d will not be freed in case of non-error. Since we don't reconfigure, + * it's going to live as long as the process anyways */ + d = malloc(strlen(argv[j]) + 1); + strcpy(d, argv[j]); + debug("Adding domain-controller %s\n", d); if (NULL == (c = strchr(d, '\\')) && NULL == (c = strchr(d, '/'))) { fprintf(stderr, "Couldn't grok domain-controller %s\n", d); + free(d); + continue; + } + /* more than one delimiter is not allowed */ + if (NULL != strchr(c + 1, '\\') || NULL != strchr(c + 1, '/')) { + fprintf(stderr, "Broken domain-controller %s\n", d); + free(d); continue; } *c++ = '\0'; new_dc = (dc *) malloc(sizeof(dc)); if (!new_dc) { fprintf(stderr, "Malloc error while parsing DC options\n"); + free(d); continue; } /* capitalize */ @@ -191,12 +213,15 @@ obtain_challenge() { int j = 0; const char *ch; + debug("obtain_challenge: getting new challenge\n"); for (j = 0; j < numcontrollers; j++) { if (current_dc->status == DC_OK) { + debug("getting challenge from %s\%s\n", current_dc->domain, current_dc->controller); ch = make_challenge(current_dc->domain, current_dc->controller); if (ch) return ch; /* All went OK, returning */ /* Huston, we've got a problem. Take this DC out of the loop */ + debug("Marking DC as DEAD\n"); current_dc->status = DC_DEAD; need_dc_resurrection = 1; } @@ -205,14 +230,16 @@ obtain_challenge() /* Try with the next */ current_dc = current_dc->next; } + /* DC (all DCs if failover is enabled) failed. */ return NULL; } + void manage_request() { ntlmhdr *fast_header; - char buf[10240]; + char buf[BUFFER_SIZE]; const char *ch; char *ch2, *decoded, *cred; int plen; @@ -259,36 +286,79 @@ manage_request() plen = strlen(buf) * 3 / 4; /* we only need it here. Optimization */ cred = ntlm_check_auth((ntlm_authenticate *) decoded, plen); if (cred == NULL) { + int errorclass, errorcode; +#ifdef DEBUG + SMB_Get_Error_Msg(SMB_Get_Last_SMB_Err(), + error_messages_buffer, BUFFER_SIZE); + debug("Authentication failure. SMB error: %d: %s\n. Class=%d, " + "Code=%d\n", + SMB_Get_Last_SMB_Err(), error_messages_buffer, + SMB_Get_Last_SMB_Err() & 0xff, SMB_Get_Last_SMB_Err() >> 16); +#endif + /* This is kind of a special case, which happens when the + * client sends credentials in a domain which is not trusted + * by the domain we're using when authenticating. Unfortunately, + * it can't currently be accommodated in the current framework so + * I'll leave it hanging here, waiting for the general framework + * to be expanded to better accommodate the generale case. */ + errorclass = SMB_Get_Last_SMB_Err() & 0xff; + errorcode = SMB_Get_Last_SMB_Err() >> 16; + if (errorclass == 1 && errorcode == 5) { + SEND("NA Wrong password or untrusted domain"); + return; + } switch (ntlm_errno) { case NTLM_LOGON_ERROR: SEND("NA authentication failure"); - dc_disconnect(); - current_dc = current_dc->next; + /* I must have been drugged when I wrote the following two lines */ + /* dc_disconnect(); + * current_dc = current_dc->next; */ return; case NTLM_SERVER_ERROR: - SEND("BH Domain Controller Error"); - dc_disconnect(); - current_dc = current_dc->next; + send_bh_or_ld("BH Domain Controller Error", (ntlm_authenticate *) decoded, plen); + /* SEND("BH Domain Controller Error"); */ + /* we don't really need to disconnect NOW. + * Besides, we asked squid to force a reconnect. This way, if we + * have authentications in flight, we might even succeed. + */ + /* dc_disconnect(); */ + + SMB_Get_Error_Msg(SMB_Get_Last_Error(), smb_error_buffer, 1000); + debug("Last error was: %s, RFC errno=%d\n", smb_error_buffer, + RFCNB_Get_Last_Errno()); + if (failover_enabled) + current_dc = current_dc->next; return; case NTLM_PROTOCOL_ERROR: - SEND("BH Domain Controller communication error"); - dc_disconnect(); - current_dc = current_dc->next; + send_bh_or_ld("BH Domain Controller communication error", (ntlm_authenticate *) decoded, plen); + /* SEND("BH Domain Controller communication error"); */ + /* dc_disconnect(); */ + if (failover_enabled) + current_dc = current_dc->next; return; case NTLM_NOT_CONNECTED: - SEND("BH Domain Controller (or network) died on us"); - dc_disconnect(); - current_dc = current_dc->next; + send_bh_or_ld("BH Domain Controller (or network) died on us", (ntlm_authenticate *) decoded, plen); + /* SEND("BH Domain Controller (or network) died on us"); */ + /* dc_disconnect(); */ + if (failover_enabled) + current_dc = current_dc->next; return; case NTLM_BAD_PROTOCOL: - SEND("BH Domain controller failure"); - dc_disconnect(); - current_dc = current_dc->next; + send_bh_or_ld("BH Domain controller failure", (ntlm_authenticate *) decoded, plen); + /* SEND("BH Domain controller failure"); */ + /* dc_disconnect(); *//* maybe we're overreacting? */ + SMB_Get_Error_Msg(SMB_Get_Last_Error(), smb_error_buffer, 1000); + debug("Last error was: %s. RFCNB errno=%d\n", smb_error_buffer, + RFCNB_Get_Last_Errno()); + if (failover_enabled) + current_dc = current_dc->next; return; default: - SEND("BH Unhandled error while talking to Domain Controller"); - dc_disconnect(); - current_dc = current_dc->next; + send_bh_or_ld("BH Unhandled error while talking to Domain Controller", (ntlm_authenticate *) decoded, plen); + /* SEND("BH Unhandled error while talking to Domain Controller"); */ + /* dc_disconnect(); *//* maybe we're overreacting? */ + if (failover_enabled) + current_dc = current_dc->next; return; } } @@ -306,6 +376,8 @@ manage_request() if (memcmp(buf, "YR", 2) == 0) { /* refresh-request */ dc_disconnect(); ch = obtain_challenge(); + /* Robert says we can afford to wait forever. I'll trust him on this + * one */ while (ch == NULL) { sleep(30); ch = obtain_challenge(); @@ -326,7 +398,7 @@ int main(int argc, char *argv[]) { - debug("starting up...\n"); + debug("ntlm_auth build " __DATE__ ", " __TIME__ " starting up...\n"); my_program_name = argv[0]; process_options(argc, argv); diff --git a/include/md5.h b/include/md5.h index 001bf9bfe4..cfbfa3297e 100644 --- a/include/md5.h +++ b/include/md5.h @@ -1,5 +1,5 @@ /* - * $Id: md5.h,v 1.6 1998/09/23 17:19:59 wessels Exp $ + * $Id: md5.h,v 1.7 2001/01/31 22:16:36 hno Exp $ */ #ifndef MD5_H @@ -37,7 +37,7 @@ typedef struct { } MD5_CTX; void MD5Init(MD5_CTX *); -void MD5Update(MD5_CTX *, unsigned char *, unsigned int); +void MD5Update(MD5_CTX *, const unsigned char *, unsigned int); void MD5Final(unsigned char[16], MD5_CTX *); #define MD5_DIGEST_CHARS 16 diff --git a/include/rfc2617.h b/include/rfc2617.h new file mode 100644 index 0000000000..189ec88a02 --- /dev/null +++ b/include/rfc2617.h @@ -0,0 +1,88 @@ +/* The source in this file is derived from the reference implementation + * in RFC 2617. + * RFC 2617 is Copyright (C) The Internet Society (1999). All Rights Reserved. + * + * The following copyright and licence statement covers all changes made to the + * reference implementation. + * + * Key changes to the reference implementation were: + * alteration to a plain C layout. + * Create CvtBin function + * Allow CalcHA1 to make use of precaculated username:password:realm hash's + * to prevent squid knowing the users password (idea suggested in RFC 2617). + */ + + +/* + * $Id: rfc2617.h,v 1.1 2001/01/31 22:16:36 hno Exp $ + * + * DEBUG: + * AUTHOR: RFC 2617 & Robert Collins + * + * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from the + * Internet community. Development is led by Duane Wessels of the + * National Laboratory for Applied Network Research and funded by the + * National Science Foundation. Squid is Copyrighted (C) 1998 by + * the Regents of the University of California. Please see the + * COPYRIGHT file for full details. Squid incorporates software + * developed and/or copyrighted by other sources. Please see the + * CREDITS file for full details. + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#ifndef RFC2617_H +#define RFC2617_H +#include "md5.h" + +#define HASHLEN 16 +typedef char HASH[HASHLEN]; +#define HASHHEXLEN 32 +typedef char HASHHEX[HASHHEXLEN + 1]; + +/* calculate H(A1) as per HTTP Digest spec */ +void DigestCalcHA1( + const char *pszAlg, + const char *pszUserName, + const char *pszRealm, + const char *pszPassword, + const char *pszNonce, + const char *pszCNonce, + HASH HA1, + HASHHEX SessionKey +); + +/* calculate request-digest/response-digest as per HTTP Digest spec */ +void DigestCalcResponse( + const HASHHEX HA1, /* H(A1) */ + const char *pszNonce, /* nonce from server */ + const char *pszNonceCount, /* 8 hex digits */ + const char *pszCNonce, /* client nonce */ + const char *pszQop, /* qop-value: "", "auth", "auth-int" */ + const char *pszMethod, /* method from the request */ + const char *pszDigestUri, /* requested URL */ + const HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ + HASHHEX Response /* request-digest or response-digest */ +); + +void CvtHex(const HASH Bin, HASHHEX Hex); + +void CvtBin(const HASHHEX Hex, HASH Bin); + +#endif /* RFC 2617 */ diff --git a/lib/Makefile.in b/lib/Makefile.in index 5ae7ab5cd0..1969455ace 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -1,5 +1,5 @@ # -# $Id: Makefile.in,v 1.48 2001/01/07 23:36:36 hno Exp $ +# $Id: Makefile.in,v 1.49 2001/01/31 22:16:37 hno Exp $ # prefix = @prefix@ top_srcdir = @top_srcdir@ @@ -24,6 +24,7 @@ INCLUDE = -I../include -I$(top_srcdir)/include UTILOBJS = rfc1123.o \ rfc1738.o \ rfc1035.o \ + rfc2617.o \ util.o \ getfullhostname.o \ base64.o \ diff --git a/lib/md5.c b/lib/md5.c index ef95a437fc..0775fbf4e5 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -1,5 +1,5 @@ /* - * $Id: md5.c,v 1.9 1999/05/04 21:20:39 wessels Exp $ + * $Id: md5.c,v 1.10 2001/01/31 22:16:37 hno Exp $ */ /* taken from RFC-1321/Appendix A.3 */ @@ -63,9 +63,9 @@ #define S43 15 #define S44 21 -static void MD5Transform(u_num32[4], unsigned char[64]); +static void MD5Transform(u_num32[4], const unsigned char[64]); static void Encode(unsigned char *, u_num32 *, unsigned int); -static void Decode(u_num32 *, unsigned char *, unsigned int); +static void Decode(u_num32 *, const unsigned char *, unsigned int); #if HAVE_MEMCPY #define MD5_memcpy(to,from,count) memcpy(to,from,count) @@ -145,7 +145,7 @@ MD5Init(MD5_CTX * context) * processing another message block, and updating the context. */ void -MD5Update(MD5_CTX * context, unsigned char *input, unsigned int inputLen) +MD5Update(MD5_CTX * context, const unsigned char *input, unsigned int inputLen) { unsigned int i, index, partLen; @@ -213,7 +213,7 @@ MD5Final(unsigned char digest[16], MD5_CTX * context) * MD5 basic transformation. Transforms state based on block. */ static void -MD5Transform(u_num32 state[4], unsigned char block[64]) +MD5Transform(u_num32 state[4], const unsigned char block[64]) { u_num32 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; @@ -324,7 +324,7 @@ Encode(unsigned char *output, u_num32 * input, unsigned int len) * multiple of 4. */ static void -Decode(u_num32 * output, unsigned char *input, unsigned int len) +Decode(u_num32 * output, const unsigned char *input, unsigned int len) { unsigned int i, j; diff --git a/lib/rfc2617.c b/lib/rfc2617.c new file mode 100644 index 0000000000..01942fbd90 --- /dev/null +++ b/lib/rfc2617.c @@ -0,0 +1,177 @@ +/* The source in this file is derived from the reference implementation + * in RFC 2617. + * RFC 2617 is Copyright (C) The Internet Society (1999). All Rights Reserved. + * + * The following copyright and licence statement covers all changes made to the + * reference implementation. + * + * Key changes were: alteration to a plain C layout. + * Create CvtBin function + * Allow CalcHA1 to make use of precaculated username:password:realm hash's + * to prevent squid knowing the users password (idea suggested in RFC 2617). + */ + + +/* + * $Id: rfc2617.c,v 1.1 2001/01/31 22:16:37 hno Exp $ + * + * DEBUG: + * AUTHOR: RFC 2617 & Robert Collins + * + * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from the + * Internet community. Development is led by Duane Wessels of the + * National Laboratory for Applied Network Research and funded by the + * National Science Foundation. Squid is Copyrighted (C) 1998 by + * the Regents of the University of California. Please see the + * COPYRIGHT file for full details. Squid incorporates software + * developed and/or copyrighted by other sources. Please see the + * CREDITS file for full details. + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#include "config.h" +#include +#include "rfc2617.h" + +void +CvtHex(const HASH Bin, HASHHEX Hex) +{ + unsigned short i; + unsigned char j; + + for (i = 0; i < HASHLEN; i++) { + j = (Bin[i] >> 4) & 0xf; + if (j <= 9) + Hex[i * 2] = (j + '0'); + else + Hex[i * 2] = (j + 'a' - 10); + j = Bin[i] & 0xf; + if (j <= 9) + Hex[i * 2 + 1] = (j + '0'); + else + Hex[i * 2 + 1] = (j + 'a' - 10); + }; + Hex[HASHHEXLEN] = '\0'; +}; + +void +CvtBin(const HASHHEX Hex, HASH Bin) +{ + unsigned short i; + unsigned char j; + + for (i = 0; i < HASHHEXLEN; i++) { + j = Hex[i]; + if (('0' <= j) && (j <= '9')) + Bin[i / 2] |= ((j - '0') << ((i % 2 == 0) ? 4 : 0)); + else + Bin[i / 2] |= ((j - 'a' + 10) << ((i % 2 == 0) ? 4 : 0)); + }; + Bin[HASHLEN] = '\0'; +}; + + +/* calculate H(A1) as per spec */ +void +DigestCalcHA1( + const char *pszAlg, + const char *pszUserName, + const char *pszRealm, + const char *pszPassword, + const char *pszNonce, + const char *pszCNonce, + HASH HA1, + HASHHEX SessionKey +) +{ + MD5_CTX Md5Ctx; + + if (pszUserName) { + MD5Init(&Md5Ctx); + MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName)); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm)); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword)); + MD5Final(HA1, &Md5Ctx); + } + if (stricmp(pszAlg, "md5-sess") == 0) { + MD5Init(&Md5Ctx); + MD5Update(&Md5Ctx, HA1, HASHLEN); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce)); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce)); + MD5Final(HA1, &Md5Ctx); + }; + CvtHex(HA1, SessionKey); +}; + +/* calculate request-digest/response-digest as per HTTP Digest spec */ +void +DigestCalcResponse( + const HASHHEX HA1, /* H(A1) */ + const char *pszNonce, /* nonce from server */ + const char *pszNonceCount, /* 8 hex digits */ + const char *pszCNonce, /* client nonce */ + const char *pszQop, /* qop-value: "", "auth", "auth-int" */ + const char *pszMethod, /* method from the request */ + const char *pszDigestUri, /* requested URL */ + const HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ + HASHHEX Response /* request-digest or response-digest */ +) +{ + MD5_CTX Md5Ctx; + HASH HA2; + HASH RespHash; + HASHHEX HA2Hex; + + /* calculate H(A2) + */ + MD5Init(&Md5Ctx); + MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod)); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri)); + if (stricmp(pszQop, "auth-int") == 0) { + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, HEntity, HASHHEXLEN); + }; + MD5Final(HA2, &Md5Ctx); + CvtHex(HA2, HA2Hex); + + /* calculate response + */ + MD5Init(&Md5Ctx); + MD5Update(&Md5Ctx, HA1, HASHHEXLEN); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce)); + MD5Update(&Md5Ctx, ":", 1); + if (*pszQop) { + MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount)); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce)); + MD5Update(&Md5Ctx, ":", 1); + MD5Update(&Md5Ctx, pszQop, strlen(pszQop)); + MD5Update(&Md5Ctx, ":", 1); + }; + MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN); + MD5Final(RespHash, &Md5Ctx); + CvtHex(RespHash, Response); +}; diff --git a/src/acl.cc b/src/acl.cc index 63f29b0da2..8bfe738b6b 100644 --- a/src/acl.cc +++ b/src/acl.cc @@ -1,6 +1,6 @@ /* - * $Id: acl.cc,v 1.243 2001/01/31 21:48:24 hno Exp $ + * $Id: acl.cc,v 1.244 2001/01/31 22:16:38 hno Exp $ * * DEBUG: section 28 Access Control * AUTHOR: Duane Wessels @@ -1217,7 +1217,7 @@ aclMatchProxyAuth(void *data, http_hdr_type headertype, if (!authenticateValidateUser(auth_user_request = authenticateGetAuthUser(proxy_auth))) { /* the decode might have left a username for logging, or a message to * the user */ - if (auth_user_request) { + if (authenticateUserRequestUsername(auth_user_request)) { /* lock the user for the request structure link */ authenticateAuthUserRequestLock(auth_user_request); checklist->request->auth_user_request = auth_user_request; diff --git a/src/auth/basic/auth_basic.cc b/src/auth/basic/auth_basic.cc index 13e4fcaebb..82ed246c39 100644 --- a/src/auth/basic/auth_basic.cc +++ b/src/auth/basic/auth_basic.cc @@ -1,6 +1,6 @@ /* - * $Id: auth_basic.cc,v 1.4 2001/01/12 00:37:28 wessels Exp $ + * $Id: auth_basic.cc,v 1.5 2001/01/31 22:16:40 hno Exp $ * * DEBUG: section 29 Authenticator * AUTHOR: Duane Wessels @@ -53,6 +53,7 @@ static HLPCB authenticateBasicHandleReply; static AUTHSACTIVE authenticateBasicActive; static AUTHSAUTHED authenticateBasicAuthenticated; static AUTHSAUTHUSER authenticateBasicAuthenticateUser; +static AUTHSCONFIGURED authBasicConfigured; static AUTHSDIRECTION authenticateBasicDirection; static AUTHSDECODE authenticateBasicDecodeAuth; static AUTHSDUMP authBasicCfgDump; @@ -107,6 +108,7 @@ authSchemeSetup_basic(authscheme_entry_t * authscheme) authscheme->init = authBasicInit; authscheme->authAuthenticate = authenticateBasicAuthenticateUser; authscheme->authenticated = authenticateBasicAuthenticated; + authscheme->configured = authBasicConfigured; authscheme->authFixHeader = authenticateBasicFixErrorHeader; authscheme->FreeUser = authenticateBasicFreeUser; authscheme->freeconfig = authBasicFreeConfig; @@ -121,10 +123,20 @@ authSchemeSetup_basic(authscheme_entry_t * authscheme) int authenticateBasicActive() +{ + return (authbasic_initialised == 1) ? 1 : 0; +} + +int +authBasicConfigured() { if ((basicConfig != NULL) && (basicConfig->authenticate != NULL) && - (basicConfig->authenticateChildren != 0) && (basicConfig->basicAuthRealm != NULL)) + (basicConfig->authenticateChildren != 0) && + (basicConfig->basicAuthRealm != NULL)) { + debug(29, 9) ("authBasicConfigured: returning configured\n"); return 1; + } + debug(29, 9) ("authBasicConfigured: returning unconfigured\n"); return 0; } @@ -308,6 +320,8 @@ authBasicParse(authScheme * scheme, int n_configured, char *param_str) } basicConfig = scheme->scheme_data; if (strcasecmp(param_str, "program") == 0) { + if (basicConfig->authenticate) + wordlistDestroy(&basicConfig->authenticate); parse_wordlist(&basicConfig->authenticate); requirePathnameExists("authparam basic program", basicConfig->authenticate->key); } else if (strcasecmp(param_str, "children") == 0) { diff --git a/src/auth/digest/Makefile.in b/src/auth/digest/Makefile.in new file mode 100644 index 0000000000..d71dc59dfe --- /dev/null +++ b/src/auth/digest/Makefile.in @@ -0,0 +1,70 @@ +# +# Makefile for the Digest authentication scheme modulefor the Squid Object Cache server +# +# $Id: Makefile.in,v 1.1 2001/01/31 22:16:41 hno Exp $ +# + +AUTH = digest + +SUBDIRS = helpers + +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +CC = @CC@ +MAKEDEPEND = @MAKEDEPEND@ +AR_R = @AR_R@ +RANLIB = @RANLIB@ +AC_CFLAGS = @CFLAGS@ +SHELL = /bin/sh + +INCLUDE = -I../../../include -I$(top_srcdir)/include -I$(top_srcdir)/src/ +CFLAGS = $(AC_CFLAGS) $(INCLUDE) $(DEFINES) + +OUT = ../$(AUTH).a + +OBJS = \ + auth_digest.o + + +all install: $(OUT) + @for dir in $(SUBDIRS); do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) $@" || exit 1; \ + fi; \ + done; + +$(OUT): $(OBJS) + @rm -f ../stamp + $(AR_R) $(OUT) $(OBJS) + $(RANLIB) $(OUT) + +$(OBJS): $(top_srcdir)/include/version.h ../../../include/autoconf.h + +.c.o: + @rm -f ../stamp + $(CC) $(CFLAGS) -c $< + +clean: + -rm -rf *.o *pure_* core ../$(AUTH).a + -for dir in *; do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) clean"; \ + fi; \ + done + +distclean: clean + -rm -f Makefile + -rm -f Makefile.bak + -rm -f tags + -for dir in *; do \ + if [ -f $$dir/Makefile ]; then \ + sh -c "cd $$dir && $(MAKE) distclean"; \ + fi; \ + done + +tags: + ctags *.[ch] $(top_srcdir)/src/*.[ch] $(top_srcdir)/include/*.h $(top_srcdir)/lib/*.[ch] + +depend: + $(MAKEDEPEND) $(INCLUDE) -fMakefile *.c diff --git a/src/auth/digest/auth_digest.cc b/src/auth/digest/auth_digest.cc new file mode 100644 index 0000000000..ae909dcb5e --- /dev/null +++ b/src/auth/digest/auth_digest.cc @@ -0,0 +1,1323 @@ + +/* + * $Id: auth_digest.cc,v 1.1 2001/01/31 22:16:41 hno Exp $ + * + * DEBUG: section 29 Authenticator + * AUTHOR: Robert Collins + * + * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from the + * Internet community. Development is led by Duane Wessels of the + * National Laboratory for Applied Network Research and funded by the + * National Science Foundation. Squid is Copyrighted (C) 1998 by + * the Regents of the University of California. Please see the + * COPYRIGHT file for full details. Squid incorporates software + * developed and/or copyrighted by other sources. Please see the + * CREDITS file for full details. + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +/* The functions in this file handle authentication. + * They DO NOT perform access control or auditing. + * See acl.c for access control and client_side.c for auditing */ + + +#include "squid.h" +#include "rfc2617.h" +#include "auth_digest.h" + +static void +authenticateStateFree(authenticateStateData * r) +{ + cbdataFree(r); +} + +/* Digest Scheme */ + +static HLPCB authenticateDigestHandleReply; +static AUTHSACTIVE authenticateDigestActive; +static AUTHSADDHEADER authDigestAddHeader; +#if WAITING_FOR_TE +static AUTHSADDTRAILER authDigestAddTrailer; +#endif +static AUTHSAUTHED authDigestAuthenticated; +static AUTHSAUTHUSER authenticateDigestAuthenticateUser; +static AUTHSCONFIGURED authDigestConfigured; +static AUTHSDIRECTION authenticateDigestDirection; +static AUTHSDECODE authenticateDigestDecodeAuth; +static AUTHSDUMP authDigestCfgDump; +static AUTHSFIXERR authenticateDigestFixHeader; +static AUTHSFREE authenticateDigestUserFree; +static AUTHSFREECONFIG authDigestFreeConfig; +static AUTHSINIT authDigestInit; +static AUTHSPARSE authDigestParse; +static AUTHSREQFREE authDigestAURequestFree; +static AUTHSSTART authenticateDigestStart; +static AUTHSSTATS authenticateDigestStats; +static AUTHSUSERNAME authenticateDigestUsername; +static AUTHSSHUTDOWN authDigestDone; + +static helper *digestauthenticators = NULL; + +static hash_table *digest_nonce_cache; + +static auth_digest_config *digestConfig = NULL; + +static int authdigest_initialised = 0; +MemPool *digest_user_pool = NULL; +MemPool *digest_request_pool = NULL; +MemPool *digest_nonce_pool = NULL; + +CBDATA_TYPE(authenticateStateData); + +/* + * + * Nonce Functions + * + */ + +static void authenticateDigestNonceCacheCleanup(void *data); +static digest_nonce_h *authenticateDigestNonceFindNonce(const char *nonceb64); +digest_nonce_h *authenticateDigestNonceNew(); +void authenticateDigestNonceDelete(digest_nonce_h * nonce); +void authenticateDigestNonceSetup(); +void authenticateDigestNonceShutdown(); +void authenticateDigestNonceReconfigure(); +const char *authenticateDigestNonceNonceb64(digest_nonce_h * nonce); +int authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]); +int authDigestNonceIsStale(digest_nonce_h * nonce); +void authDigestNonceEncode(digest_nonce_h * nonce); +int authDigestNonceLastRequest(digest_nonce_h * nonce); +void authDigestNonceLink(digest_nonce_h * nonce); +void authDigestNonceUnlink(digest_nonce_h * nonce); +int authDigestNonceLinks(digest_nonce_h * nonce); +void authDigestNonceUserUnlink(digest_nonce_h * nonce); +void authDigestNoncePurge(digest_nonce_h * nonce); + +void +authDigestNonceEncode(digest_nonce_h * nonce) +{ + if (!nonce) + return; + if (nonce->nonceb64) + xfree(nonce->nonceb64); + nonce->nonceb64 = xstrdup(base64_encode_bin((char *) &(nonce->noncedata), sizeof(digest_nonce_data))); +} + +digest_nonce_h * +authenticateDigestNonceNew() +{ + digest_nonce_h *newnonce = memPoolAlloc(digest_nonce_pool); + digest_nonce_h *temp; + +/* NONCE CREATION - NOTES AND REASONING. RBC 20010108 + * === EXCERPT FROM RFC 2617 === + * The contents of the nonce are implementation dependent. The quality + * of the implementation depends on a good choice. A nonce might, for + * example, be constructed as the base 64 encoding of + * + * time-stamp H(time-stamp ":" ETag ":" private-key) + * + * where time-stamp is a server-generated time or other non-repeating + * value, ETag is the value of the HTTP ETag header associated with + * the requested entity, and private-key is data known only to the + * server. With a nonce of this form a server would recalculate the + * hash portion after receiving the client authentication header and + * reject the request if it did not match the nonce from that header + * or if the time-stamp value is not recent enough. In this way the + * server can limit the time of the nonce's validity. The inclusion of + * the ETag prevents a replay request for an updated version of the + * resource. (Note: including the IP address of the client in the + * nonce would appear to offer the server the ability to limit the + * reuse of the nonce to the same client that originally got it. + * However, that would break proxy farms, where requests from a single + * user often go through different proxies in the farm. Also, IP + * address spoofing is not that hard.) + * ==== + * + * Now for my reasoning: + * We will not accept a unrecognised nonce->we have all recognisable nonces stored + * If we send out unique base64 encodings we guarantee that a given nonce applies + * to only one user (barring attacks or really bad timing with expiry and creation). + * Using a random component in the nonce allows us to loop to find a unique nonce. + * We use H(nonce_data) so the nonce is meaningless to the reciever. + * So our nonce looks like base64(H(timestamp,pointertohash,randomdata)) + * And even if our randomness is not very random (probably due to bad coding on my + * part) we don't really care - the timestamp and memory pointer should provide + * enough protection for the users authentication. + */ + + /* create a new nonce */ + newnonce->nc = 0; + newnonce->flags.valid = 1; + newnonce->noncedata.self = newnonce; + newnonce->noncedata.creationtime = current_time.tv_sec; + newnonce->noncedata.randomdata = random(); + + authDigestNonceEncode(newnonce); + /* loop until we get a unique nonce. The nonce creation must have a random factor */ + while ((temp = authenticateDigestNonceFindNonce(newnonce->nonceb64))) { + /* create a new nonce */ + newnonce->noncedata.randomdata = random(); + authDigestNonceEncode(newnonce); + } + hash_join(digest_nonce_cache, (hash_link *) newnonce); + /* the cache's link */ + authDigestNonceLink(newnonce); + newnonce->flags.incache = 1; + debug(29, 5) ("authenticateDigestNonceNew: created nonce %0p at %d\n", newnonce, newnonce->noncedata.creationtime); + return newnonce; +} + +void +authenticateDigestNonceDelete(digest_nonce_h * nonce) +{ + if (nonce) { + assert(nonce->references == 0); +#if UNREACHABLECODE + if (nonce->flags.incache) + hash_remove_link(digest_nonce_cache, (hash_link *) nonce); +#endif + assert(nonce->flags.incache == 0); + safe_free(nonce->nonceb64); + memPoolFree(digest_nonce_pool, nonce); + } +} + +void +authenticateDigestNonceSetup() +{ + if (!digest_nonce_pool) + digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h)); + if (!digest_nonce_cache) { + digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string); + assert(digest_nonce_cache); + eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig->nonceGCInterval, 1); + } +} + +void +authenticateDigestNonceShutdown() +{ + /* + * We empty the cache of any nonces left in there. + */ + digest_nonce_h *nonce; + if (digest_nonce_cache) { + debug(29, 2) ("authenticateDigestNonceShutdown: Shutting down nonce cache \n"); + hash_first(digest_nonce_cache); + while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) { + assert(nonce->flags.incache); + authDigestNoncePurge(nonce); + } + } + if (digest_nonce_pool) { + assert(memPoolInUseCount(digest_nonce_pool) == 0); + memPoolDestroy(digest_nonce_pool); + digest_nonce_pool = NULL; + } + debug(29, 2) ("authenticateDigestNonceShutdown: Nonce cache shutdown\n"); +} + +void +authenticateDigestNonceReconfigure() +{ +} + +void +authenticateDigestNonceCacheCleanup(void *data) +{ + /* + * We walk the hash by nonceb64 as that is the unique key we use. + * For big hashs tables we could consider stepping through the cache, 100/200 + * entries at a time. Lets see how it flys first. + */ + digest_nonce_h *nonce; + debug(29, 3) ("authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now\n"); + debug(29, 3) ("authenticateDigestNonceCacheCleanup: Current time: %d\n", + current_time.tv_sec); + hash_first(digest_nonce_cache); + while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) { + debug(29, 3) ("authenticateDigestNonceCacheCleanup: nonce entry : '%s'\n", nonce->nonceb64); + debug(29, 4) ("authenticateDigestNonceCacheCleanup: Creation time: %d\n", nonce->noncedata.creationtime); + if (authDigestNonceIsStale(nonce)) { + debug(29, 4) ("authenticateDigestNonceCacheCleanup: Removing nonce %s from cache due to timeout.\n", nonce->nonceb64); + assert(nonce->flags.incache); + /* invalidate nonce so future requests fail */ + nonce->flags.valid = 0; + /* if it is tied to a auth_user, remove the tie */ + authDigestNonceUserUnlink(nonce); + authDigestNoncePurge(nonce); + } + } + debug(29, 3) ("authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.\n"); + if (authenticateDigestActive()) + eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig->nonceGCInterval, 1); +} + +void +authDigestNonceLink(digest_nonce_h * nonce) +{ + assert(nonce != NULL); + nonce->references++; + debug(29, 9) ("authDigestNonceLink: nonce '%d' now at '%d'.\n", nonce, nonce->references); +} + +int +authDigestNonceLinks(digest_nonce_h * nonce) +{ + if (!nonce) + return -1; + return nonce->references; +} + +void +authDigestNonceUnlink(digest_nonce_h * nonce) +{ + assert(nonce != NULL); + if (nonce->references > 0) { + nonce->references--; + } else { + debug(29, 1) ("authDigestNonceUnlink; Attempt to lower nonce %d refcount below 0!\n", nonce); + } + debug(29, 9) ("authDigestNonceUnlink: nonce '%d' now at '%d'.\n", nonce, nonce->references); + if (nonce->references == 0) + authenticateDigestNonceDelete(nonce); +} + +const char * +authenticateDigestNonceNonceb64(digest_nonce_h * nonce) +{ + if (!nonce) + return NULL; + return nonce->nonceb64; +} + +digest_nonce_h * +authenticateDigestNonceFindNonce(const char *nonceb64) +{ + digest_nonce_h *nonce = NULL; + if (nonceb64 == NULL) + return NULL; + debug(29, 9) ("authDigestNonceFindNonce:looking for nonceb64 '%s' in the nonce cache.\n", nonceb64); + if ((nonce = hash_lookup(digest_nonce_cache, nonceb64))) + while ((strcmp(nonce->nonceb64, nonceb64)) && (nonce->next)) + nonce = nonce->next; + if ((nonce == NULL) || (strcmp(nonce->nonceb64, nonceb64))) + return NULL; + debug(29, 9) ("authDigestNonceFindNonce: Found nonce '%d'\n", nonce); + return nonce; +} + +int +authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]) +{ + int intnc; + /* do we have a nonce ? */ + if (!nonce) + return 0; + intnc = atoi(nc); + if (intnc != nonce->nc + 1) { + debug(29, 4) ("authDigestNonceIsValid: Nonce count doesn't match\n"); + nonce->flags.valid = 0; + return 0; + } + /* has it already been invalidated ? */ + if (!nonce->flags.valid) { + debug(29, 4) ("authDigestNonceIsValid: Nonce already invalidated\n"); + return 0; + } + /* seems ok */ + return -1; +} + +int +authDigestNonceIsStale(digest_nonce_h * nonce) +{ + /* do we have a nonce ? */ + if (!nonce) + return -1; + /* has it's max duration expired? */ + if (nonce->noncedata.creationtime + digestConfig->noncemaxduration < current_time.tv_sec) { + debug(29, 4) ("authDigestNonceIsStale: Nonce is too old. %d %d %d\n", nonce->noncedata.creationtime, digestConfig->noncemaxduration, current_time.tv_sec); + nonce->flags.valid = 0; + return -1; + } + if (nonce->nc > 99999998) { + debug(29, 4) ("authDigestNonceIsStale: Nonce count overflow\n"); + nonce->flags.valid = 0; + return -1; + } + if (nonce->nc > digestConfig->noncemaxuses) { + debug(29, 4) ("authDigestNoncelastRequest: Nonce count over user limit\n"); + nonce->flags.valid = 0; + return -1; + } + /* seems ok */ + return 0; +} + +/* return -1 if the digest will be stale on the next request */ +int +authDigestNonceLastRequest(digest_nonce_h * nonce) +{ + if (!nonce) + return -1; + if (nonce->nc == 99999997) { + debug(29, 4) ("authDigestNoncelastRequest: Nonce count about to overflow\n"); + return -1; + } + if (nonce->nc == digestConfig->noncemaxuses - 1) { + debug(29, 4) ("authDigestNoncelastRequest: Nonce count about to hit user limit\n"); + return -1; + } + /* and other tests are possible. */ + return 0; +} + +void +authDigestNoncePurge(digest_nonce_h * nonce) +{ + if (!nonce) + return; + if (!nonce->flags.incache) + return; + hash_remove_link(digest_nonce_cache, (hash_link *) nonce); + nonce->flags.incache = 0; + /* the cache's link */ + authDigestNonceUnlink(nonce); +} + +/* USER related functions */ + + +int +authDigestUsercmpname(digest_user_h * u1, digest_user_h * u2) +{ + return strcmp(u1->username, u2->username); +} + +auth_user_t * +authDigestUserFindUsername(const char *username) +{ + auth_user_hash_pointer *usernamehash; + auth_user_t *auth_user; + debug(29, 9) ("authDigestUserFindUsername: Looking for user '%s'\n", username); + if (username && (usernamehash = hash_lookup(proxy_auth_username_cache, username))) { + while ((usernamehash->auth_user->auth_type != AUTH_DIGEST) && + (usernamehash->next)) + usernamehash = usernamehash->next; + auth_user = NULL; + if (usernamehash->auth_user->auth_type == AUTH_DIGEST) { + auth_user = usernamehash->auth_user; + } + return auth_user; + } + return NULL; +} + +digest_user_h * +authDigestUserNew() +{ + return memPoolAlloc(digest_user_pool); +} + +void +authDigestUserSetup() +{ + if (!digest_user_pool) + digest_user_pool = memPoolCreate("Digest Scheme User Data", sizeof(digest_user_h)); +} + +void +authDigestUserShutdown() +{ + /* + * Future work: the auth framework could flush it's cache + */ + auth_user_hash_pointer *usernamehash; + auth_user_t *auth_user; + hash_first(proxy_auth_username_cache); + while ((usernamehash = ((auth_user_hash_pointer *) hash_next(proxy_auth_username_cache)))) { + auth_user = usernamehash->auth_user; + if (strcmp(authscheme_list[auth_user->auth_module - 1].typestr, "digest") == 0) + /* it's digest */ + authenticateAuthUserUnlock(auth_user); + } + if (digest_user_pool) { + assert(memPoolInUseCount(digest_user_pool) == 0); + memPoolDestroy(digest_user_pool); + digest_user_pool = NULL; + } +} + + +/* request related functions */ + +/* delete the digest reuqest structure. Does NOT delete related structures */ +void +authDigestRequestDelete(digest_request_h * digest_request) +{ + if (digest_request->nonceb64) + xfree(digest_request->nonceb64); + if (digest_request->cnonce) + xfree(digest_request->cnonce); + if (digest_request->realm) + xfree(digest_request->realm); + if (digest_request->pszPass) + xfree(digest_request->pszPass); + if (digest_request->algorithm) + xfree(digest_request->algorithm); + if (digest_request->pszMethod) + xfree(digest_request->pszMethod); + if (digest_request->qop) + xfree(digest_request->qop); + if (digest_request->uri) + xfree(digest_request->uri); + if (digest_request->response) + xfree(digest_request->response); + if (digest_request->nonce) + authDigestNonceUnlink(digest_request->nonce); + memPoolFree(digest_request_pool, digest_request); +} + +void +authDigestAURequestFree(auth_user_request_t * auth_user_request) +{ + if (auth_user_request->scheme_data != NULL) + authDigestRequestDelete((digest_request_h *) auth_user_request->scheme_data); +} + +digest_request_h * +authDigestRequestNew() +{ + digest_request_h *tmp; + tmp = memPoolAlloc(digest_request_pool); + assert(tmp != NULL); + return tmp; +} + +void +authDigestRequestSetup() +{ + if (!digest_request_pool) + digest_request_pool = memPoolCreate("Digest Scheme Request Data", sizeof(digest_request_h)); +} + +void +authDigestRequestShutdown() +{ + /* No requests should be in progress when we get here */ + if (digest_request_pool) { + assert(memPoolInUseCount(digest_request_pool) == 0); + memPoolDestroy(digest_request_pool); + digest_request_pool = NULL; + } +} + + +void +authDigestDone(void) +{ + if (digestauthenticators) + helperShutdown(digestauthenticators); + authdigest_initialised = 0; + if (!shutting_down) { + authenticateDigestNonceReconfigure(); + return; + } + if (digestauthenticators) { + helperFree(digestauthenticators); + digestauthenticators = NULL; + } + authDigestRequestShutdown(); + authDigestUserShutdown(); + authenticateDigestNonceShutdown(); + debug(29, 2) ("authenticateDigestDone: Digest authentication shut down.\n"); +} + +static void +authDigestCfgDump(StoreEntry * entry, const char *name, authScheme * scheme) +{ + auth_digest_config *config = scheme->scheme_data; + wordlist *list = config->authenticate; + debug(29, 9) ("authDigestCfgDump: Dumping configuration\n"); + storeAppendPrintf(entry, "%s %s", name, "digest"); + while (list != NULL) { + storeAppendPrintf(entry, " %s", list->key); + list = list->next; + } + storeAppendPrintf(entry, "\n%s %s realm %s\n%s %s children %d\n%s %s nonce_max_count %d\n%s %s nonce_max_duration %d seconds\n%s %s nonce_garbage_interval %d seconds\n", + name, "digest", config->digestAuthRealm, + name, "digest", config->authenticateChildren, + name, "digest", config->noncemaxuses, + name, "digest", config->noncemaxduration, + name, "digest", config->nonceGCInterval); +} + +void +authSchemeSetup_digest(authscheme_entry_t * authscheme) +{ + assert(!authdigest_initialised); + authscheme->Active = authenticateDigestActive; + authscheme->configured = authDigestConfigured; + authscheme->parse = authDigestParse; + authscheme->freeconfig = authDigestFreeConfig; + authscheme->dump = authDigestCfgDump; + authscheme->init = authDigestInit; + authscheme->authAuthenticate = authenticateDigestAuthenticateUser; + authscheme->authenticated = authDigestAuthenticated; + authscheme->authFixHeader = authenticateDigestFixHeader; + authscheme->FreeUser = authenticateDigestUserFree; + authscheme->AddHeader = authDigestAddHeader; +#if WAITING_FOR_TE + authscheme->AddTrailer = authDigestAddTrailer; +#endif + authscheme->authStart = authenticateDigestStart; + authscheme->authStats = authenticateDigestStats; + authscheme->authUserUsername = authenticateDigestUsername; + authscheme->getdirection = authenticateDigestDirection; + authscheme->oncloseconnection = NULL; + authscheme->decodeauth = authenticateDigestDecodeAuth; + authscheme->donefunc = authDigestDone; + authscheme->requestFree = authDigestAURequestFree; +} + +int +authenticateDigestActive() +{ + return (authdigest_initialised == 1) ? 1 : 0; +} +int +authDigestConfigured() +{ + if ((digestConfig != NULL) && (digestConfig->authenticate != NULL) && + (digestConfig->authenticateChildren != 0) && + (digestConfig->digestAuthRealm != NULL) && (digestConfig->noncemaxduration > -1)) + return 1; + return 0; +} + +int +authDigestAuthenticated(auth_user_request_t * auth_user_request) +{ + if (auth_user_request->auth_user->flags.credentials_ok == 1) + return 1; + else + return 0; +} + +/* log a digest user in + */ +static void +authenticateDigestAuthenticateUser(auth_user_request_t * auth_user_request, request_t * request, ConnStateData * conn, http_hdr_type type) +{ + auth_user_t *auth_user; + digest_request_h *digest_request; + digest_user_h *digest_user; + + HASHHEX SESSIONKEY; + HASHHEX HA2 = ""; + HASHHEX Response; + + assert(auth_user_request->auth_user != NULL); + auth_user = auth_user_request->auth_user; + + /* if the check has corrupted the user, just return */ + if (auth_user_request->auth_user->flags.credentials_ok == 3) { + return; + } + assert(auth_user->scheme_data != NULL); + digest_user = auth_user->scheme_data; + + assert(auth_user_request->scheme_data != NULL); + digest_request = auth_user_request->scheme_data; + + /* do we have the HA1 */ + if (!digest_user->HA1created) { + auth_user_request->auth_user->flags.credentials_ok = 2; + return; + } + if (digest_request->nonce == NULL) { + /* this isn't a nonce we issued */ + /* TODO: record breaks in authentication at the request level + * This is probably best done with support changes at the auth_rewrite level -RBC + * and can wait for auth_rewrite V2. + */ + auth_user->flags.credentials_ok = 3; + return; + } + DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL, + authenticateDigestNonceNonceb64(digest_request->nonce), + digest_request->cnonce, + digest_user->HA1, SESSIONKEY); + DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce), + digest_request->nc, digest_request->cnonce, digest_request->qop, + RequestMethodStr[request->method], digest_request->uri, HA2, Response); + + debug(29, 9) ("\nResponse = '%s'\n" + "squid is = '%s'\n", digest_request->response, Response); + + if (strcasecmp(digest_request->response, Response)) { + auth_user->flags.credentials_ok = 3; + return; + } + auth_user->flags.credentials_ok = 1; + /* password was checked and did match */ + debug(29, 4) ("authenticateDigestAuthenticateuser: user '%s' validated OK\n", + digest_user->username); + + /* auth_user is now linked, we reset these values + * after external auth occurs anyway */ + auth_user->expiretime = current_time.tv_sec; + auth_user->ip_expiretime = squid_curtime; + return; +} + +int +authenticateDigestDirection(auth_user_request_t * auth_user_request) +{ + digest_request_h *digest_request; +/* null auth_user is checked for by authenticateDirection */ + switch (auth_user_request->auth_user->flags.credentials_ok) { + case 0: /* not checked */ + return -1; + case 1: /* checked & ok */ + digest_request = auth_user_request->scheme_data; + if (authDigestNonceIsStale(digest_request->nonce)) + /* send stale response to the client agent */ + return -2; + return 0; + case 2: /* partway through checking. */ + return -1; + case 3: /* authentication process failed. */ + return -2; + } + return -2; +} + +/* add the [proxy]authorisation header */ +void +authDigestAddHeader(auth_user_request_t * auth_user_request, HttpReply * rep, int accel) +{ + int type; + digest_request_h *digest_request; + if (!auth_user_request) + return; + digest_request = auth_user_request->scheme_data; + /* don't add to authentication error pages */ + if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED) + || (accel && rep->sline.status == HTTP_UNAUTHORIZED)) + return; + type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO; + +#if WAITING_FOR_TE + /* test for http/1.1 transfer chunked encoding */ + if (chunkedtest) + return; +#endif + + if ((digestConfig->authenticate) && authDigestNonceLastRequest(digest_request->nonce)) { + digest_request->flags.authinfo_sent = 1; + debug(29, 9) ("authDigestAddHead: Sending type:%d header: 'nextnonce=\"%s\"", type, authenticateDigestNonceNonceb64(digest_request->nonce)); + httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(digest_request->nonce)); + } +} + +#if WAITING_FOR_TE +/* add the [proxy]authorisation header */ +void +authDigestAddTrailer(auth_user_request_t * auth_user_request, HttpReply * rep, int accel) +{ + int type; + digest_request_h *digest_request; + if (!auth_user_request) + return; + digest_request = auth_user_request->scheme_data; + /* has the header already been send? */ + if (digest_request->flags.authinfo_sent) + return; + /* don't add to authentication error pages */ + if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED) + || (accel && rep->sline.status == HTTP_UNAUTHORIZED)) + return; + type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO; + + if ((digestConfig->authenticate) && authDigestNonceLastRequest(digest_request->nonce)) { + debug(29, 9) ("authDigestAddTrailer: Sending type:%d header: 'nextnonce=\"%s\"", type, authenticateDigestNonceNonceb64(digest_request->nonce)); + httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(digest_request->nonce)); + } +} +#endif + +/* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */ +void +authenticateDigestFixHeader(auth_user_request_t * auth_user_request, HttpReply * rep, http_hdr_type type, request_t * request) +{ + digest_request_h *digest_request; + int stale = 0; + digest_nonce_h *nonce = authenticateDigestNonceNew(); + if (auth_user_request && authDigestAuthenticated(auth_user_request) && auth_user_request->scheme_data) { + digest_request = auth_user_request->scheme_data; + stale = authDigestNonceIsStale(digest_request->nonce); + } + if (digestConfig->authenticate) { + debug(29, 9) ("authenticateFixHeader: Sending type:%d header: 'Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s\n", type, digestConfig->digestAuthRealm, authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false"); + /* in the future, for WWW auth we may want to support the domain entry */ + httpHeaderPutStrf(&rep->header, type, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestConfig->digestAuthRealm, authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false"); + } +} + +void +authenticateDigestUserFree(auth_user_t * auth_user) +{ + digest_user_h *digest_user = auth_user->scheme_data; + dlink_node *link, *tmplink; + debug(29, 9) ("authenticateDigestFreeUser: Clearing Digest scheme data\n"); + if (!digest_user) + return; + safe_free(digest_user->username); + + link = digest_user->nonces.head; + while (link) { + tmplink = link; + link = link->next; + dlinkDelete(tmplink, &digest_user->nonces); + authDigestNoncePurge(tmplink->data); + authDigestNonceUnlink(tmplink->data); + dlinkNodeDelete(tmplink); + } + + memPoolFree(digest_user_pool, auth_user->scheme_data); + auth_user->scheme_data = NULL; +} + +static void +authenticateDigestHandleReply(void *data, char *reply) +{ + authenticateStateData *r = data; + auth_user_request_t *auth_user_request; + digest_request_h *digest_request; + digest_user_h *digest_user; + int valid; + char *t = NULL; + debug(29, 9) ("authenticateDigestHandleReply: {%s}\n", reply ? reply : ""); + if (reply) { + if ((t = strchr(reply, ' '))) + *t = '\0'; + if (*reply == '\0') + reply = NULL; + } + assert(r->auth_user_request != NULL); + auth_user_request = r->auth_user_request; + assert(auth_user_request->scheme_data != NULL); + digest_request = auth_user_request->scheme_data; + digest_user = auth_user_request->auth_user->scheme_data; + if (reply && (strncasecmp(reply, "ERR", 3) == 0)) + auth_user_request->auth_user->flags.credentials_ok = 3; + else { + CvtBin(reply, digest_user->HA1); + digest_user->HA1created = 1; + } + valid = cbdataValid(r->data); + if (valid) + r->handler(r->data, NULL); + cbdataUnlock(r->data); + authenticateStateFree(r); +} + +/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the + * config file */ +static void +authDigestInit(authScheme * scheme) +{ + static int init = 0; + if (digestConfig->authenticate) { + authDigestUserSetup(); + authDigestRequestSetup(); + authenticateDigestNonceSetup(); + authdigest_initialised = 1; + if (digestauthenticators == NULL) + digestauthenticators = helperCreate("digestauthenticator"); + digestauthenticators->cmdline = digestConfig->authenticate; + digestauthenticators->n_to_start = digestConfig->authenticateChildren; + digestauthenticators->ipc_type = IPC_TCP_SOCKET; + helperOpenServers(digestauthenticators); + if (!init) { + cachemgrRegister("digestauthenticator", "User Authenticator Stats", + authenticateDigestStats, 0, 1); + init++; + } + CBDATA_INIT_TYPE(authenticateStateData); + } +} + + +/* free any allocated configuration details */ +void +authDigestFreeConfig(authScheme * scheme) +{ + if (digestConfig == NULL) + return; + assert(digestConfig == scheme->scheme_data); + if (digestConfig->authenticate) + wordlistDestroy(&digestConfig->authenticate); + if (digestConfig->digestAuthRealm) + safe_free(digestConfig->digestAuthRealm); + xfree(digestConfig); + digestConfig = NULL; +} + +static void +authDigestParse(authScheme * scheme, int n_configured, char *param_str) +{ + if (scheme->scheme_data == NULL) { + assert(digestConfig == NULL); + /* this is the first param to be found */ + scheme->scheme_data = xmalloc(sizeof(auth_digest_config)); + memset(scheme->scheme_data, 0, sizeof(auth_digest_config)); + digestConfig = scheme->scheme_data; + digestConfig->authenticateChildren = 5; + /* 5 minutes */ + digestConfig->nonceGCInterval = 5 * 60; + /* 30 minutes */ + digestConfig->noncemaxduration = 30 * 60; + /* 50 requests */ + digestConfig->noncemaxuses = 50; + } + digestConfig = scheme->scheme_data; + if (strcasecmp(param_str, "program") == 0) { + if (digestConfig->authenticate) + wordlistDestroy(&digestConfig->authenticate); + parse_wordlist(&digestConfig->authenticate); + requirePathnameExists("authparam digest program", digestConfig->authenticate->key); + } else if (strcasecmp(param_str, "children") == 0) { + parse_int(&digestConfig->authenticateChildren); + } else if (strcasecmp(param_str, "realm") == 0) { + parse_eol(&digestConfig->digestAuthRealm); + } else if (strcasecmp(param_str, "nonce_garbage_interval") == 0) { + parse_time_t(&digestConfig->nonceGCInterval); + } else if (strcasecmp(param_str, "nonce_max_duration") == 0) { + parse_time_t(&digestConfig->noncemaxduration); + } else if (strcasecmp(param_str, "nonce_max_count") == 0) { + parse_int(&digestConfig->noncemaxuses); + } else { + debug(28, 0) ("unrecognised digest auth scheme parameter '%s'\n", param_str); + } +} + + +static void +authenticateDigestStats(StoreEntry * sentry) +{ + storeAppendPrintf(sentry, "Digest Authenticator Statistics:\n"); + helperStats(sentry, digestauthenticators); +} + +/* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */ + +void +authDigestNonceUserUnlink(digest_nonce_h * nonce) +{ + digest_user_h *digest_user; + dlink_node *link, *tmplink; + if (!nonce) + return; + if (!nonce->auth_user) + return; + digest_user = nonce->auth_user->scheme_data; + /* unlink from the user list. Yes we're crossing structures but this is the only + * time this code is needed + */ + link = digest_user->nonces.head; + while (link) { + tmplink = link; + link = link->next; + if (tmplink->data == nonce) { + dlinkDelete(tmplink, &digest_user->nonces); + authDigestNonceUnlink(tmplink->data); + dlinkNodeDelete(tmplink); + link = NULL; + } + } + /* this reference to auth_user was not locked because freeeing the auth_user frees + * the nonce too. + */ + nonce->auth_user = NULL; +} + +/* authDigestUserLinkNonce: add a nonce to a given user's struct */ + +void +authDigestUserLinkNonce(auth_user_t * auth_user, digest_nonce_h * nonce) +{ + dlink_node *node; + digest_user_h *digest_user; + if (!auth_user || !nonce) + return; + if (!auth_user->scheme_data) + return; + digest_user = auth_user->scheme_data; + node = digest_user->nonces.head; + while (node && (node->data != nonce)) + node = node->next; + if (node) + return; + node = dlinkNodeNew(); + dlinkAddTail(nonce, node, &digest_user->nonces); + authDigestNonceLink(nonce); + /* ping this nonce to this auth user */ + assert((nonce->auth_user == NULL) || (nonce->auth_user = auth_user)); + /* we don't lock this reference because removing the auth_user removes the + * hash too. Of course if that changes we're stuffed so read the code huh? + */ + nonce->auth_user = auth_user; +} + +/* authenticateDigestUsername: return a pointer to the username in the */ +char * +authenticateDigestUsername(auth_user_t * auth_user) +{ + digest_user_h *digest_user = auth_user->scheme_data; + if (digest_user) + return digest_user->username; + return NULL; +} + +/* setup the necessary info to log the username */ +void +authDigestLogUsername(auth_user_request_t * auth_user_request, char *username) +{ + auth_user_t *auth_user; + digest_user_h *digest_user; + dlink_node *node; + + /* log the username */ + debug(29, 9) ("authBasicDecodeAuth: Creating new user for logging '%s'\n", username); + /* new auth_user */ + auth_user = authenticateAuthUserNew("digest"); + /* new scheme data */ + digest_user = authDigestUserNew(); + /* save the credentials */ + digest_user->username = username; + /* link the scheme data in */ + auth_user->scheme_data = digest_user; + /* set the auth_user type */ + auth_user->auth_type = AUTH_BROKEN; + /* link the request to the user */ + auth_user_request->auth_user = auth_user; + /* lock for the auth_user_request link */ + authenticateAuthUserLock(auth_user); + node = dlinkNodeNew(); + dlinkAdd(auth_user_request, node, &auth_user->requests); +} + +/* + * Decode a Digest [Proxy-]Auth string, placing the results in the passed + * Auth_user structure. + */ + +static void +authenticateDigestDecodeAuth(auth_user_request_t * auth_user_request, const char *proxy_auth) +{ + String temp; + const char *item; + const char *p; + const char *pos = NULL; + char *username = NULL; + digest_nonce_h *nonce; + int ilen; + digest_request_h *digest_request; + digest_user_h *digest_user; + auth_user_t *auth_user; + dlink_node *node; + + debug(29, 9) ("authenticateDigestDecodeAuth: beginning\n"); + assert(auth_user_request != NULL); + + digest_request = authDigestRequestNew(); + + /* trim DIGEST from string */ + while (!xisspace(*proxy_auth)) + proxy_auth++; + + /* Trim leading whitespace before decoding */ + while (xisspace(*proxy_auth)) + proxy_auth++; + + stringInit(&temp, proxy_auth); + while (strListGetItem(&temp, ',', &item, &ilen, &pos)) { + if ((p = strchr(item, '=')) && (p - item < ilen)) + ilen = p++ - item; + if (!strncmp(item, "username", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + username = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found Username '%s'\n", username); + } else if (!strncmp(item, "realm", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->realm = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found realm '%s'\n", digest_request->realm); + } else if (!strncmp(item, "qop", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->qop = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found qop '%s'\n", digest_request->qop); + } else if (!strncmp(item, "algorithm", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->algorithm = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found algorithm '%s'\n", digest_request->algorithm); + } else if (!strncmp(item, "uri", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->uri = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found uri '%s'\n", digest_request->uri); + } else if (!strncmp(item, "nonce", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->nonceb64 = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found nonce '%s'\n", digest_request->nonceb64); + } else if (!strncmp(item, "nc", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + xstrncpy(digest_request->nc, p, 9); + debug(29, 9) ("authDigestDecodeAuth: Found noncecount '%s'\n", digest_request->nc); + } else if (!strncmp(item, "cnonce", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->cnonce = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found cnonce '%s'\n", digest_request->cnonce); + } else if (!strncmp(item, "response", ilen)) { + /* white space */ + while (xisspace(*p)) + p++; + /* quote mark */ + p++; + digest_request->response = xstrndup(p, strchr(p, '"') + 1 - p); + debug(29, 9) ("authDigestDecodeAuth: Found response '%s'\n", digest_request->response); + } + } + stringClean(&temp); + + + /* now we validate the data given to us */ + + /* TODO: on invalid parameters we should return 400, not 407. Find some clean way + * of doing this. perhaps return a valid struct, and set the direction to clientwards + * combined with a change to the clientwards handling code (ie let the clientwards + * call set the error type (but limited to known correct values - 400/401/407 */ + + /* first the NONCE count */ + if (digest_request->cnonce && strlen(digest_request->nc) != 8) { + debug(29, 4) ("authenticateDigestDecode: nonce count length invalid\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* now the nonce */ + nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64); + if ((nonce == NULL) || !(authDigestNonceIsValid(nonce, digest_request->nc))) { + /* we couldn't find a matching nonce! */ + debug(29, 4) ("authenticateDigestDecode: Unexpected or invalid nonce recieved\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + digest_request->nonce = nonce; + /* increment the nonce count */ + nonce->nc++; + authDigestNonceLink(nonce); + + /* check the qop is what we expected */ + if (digest_request->qop && strcmp(digest_request->qop, QOP_AUTH)) { + /* we recieved a qop option we didn't send */ + debug(29, 4) ("authenticateDigestDecode: Invalid qop option recieved\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* we can't check the URI just yet. We'll check it in the authenticate phase */ + + /* is the response the correct length? */ + + if (!digest_request->response || strlen(digest_request->response) != 32) { + debug(29, 4) ("authenticateDigestDecode: Response length invalid\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* do we have a username ? */ + if (!username || username[0] == '\0') { + debug(29, 4) ("authenticateDigestDecode: Empty or not present username\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* check that we're not being hacked / the username hasn't changed */ + if (nonce->auth_user && strcmp(username, authenticateUserUsername(nonce->auth_user))) { + debug(29, 4) ("authenticateDigestDecode: Username for the nonce does not equal the username for the request\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* if we got a qop, did we get a cnonce or did we get a cnonce wihtout a qop? */ + if ((digest_request->qop && !digest_request->cnonce) + || (!digest_request->qop && digest_request->cnonce)) { + debug(29, 4) ("authenticateDigestDecode: qop without cnonce, or vice versa!\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* check the algorithm is present and supported */ + if (digest_request->algorithm + && strcmp(digest_request->algorithm, "MD5") + && strcmp(digest_request->algorithm, "MD5-sess")) { + debug(29, 4) ("authenticateDigestDecode: invalid algorithm specified!\n"); + authDigestLogUsername(auth_user_request, username); + + /* we don't need the scheme specific data anymore */ + authDigestRequestDelete(digest_request); + auth_user_request->scheme_data = NULL; + return; + } + /* the method we'll check at the authenticate step as well */ + + + /* we don't send or parse opaques. Ok so we're flexable ... */ + + /* find the user */ + + if ((auth_user = authDigestUserFindUsername(username)) == NULL) { + /* the user doesn't exist in the username cache yet */ + debug(29, 9) ("authDigestDecodeAuth: Creating new digest user '%s'\n", username); + /* new auth_user */ + auth_user = authenticateAuthUserNew("digest"); + /* new scheme user data */ + digest_user = authDigestUserNew(); + /* save the username */ + digest_user->username = username; + /* link the primary struct in */ + auth_user->scheme_data = digest_user; + /* set the user type */ + auth_user->auth_type = AUTH_DIGEST; + /* this auth_user struct is the one to get added to the username cache */ + /* store user in hash's */ + authenticateUserNameCacheAdd(auth_user); + /* + * Add the digest to the user so we can tell if a hacking or spoofing attack + * is taking place. We do this by assuming the user agent won't change user + * name without warning. + */ + authDigestUserLinkNonce(auth_user, nonce); + } else { + debug(29, 9) ("authDigestDecodeAuth: Found user '%s' in the user cache as '%d'\n", username, auth_user); + digest_user = auth_user->scheme_data; + xfree(username); + } + /*link the request and the user */ + auth_user_request->auth_user = auth_user; + auth_user_request->scheme_data = digest_request; + /* lock for the request link */ + authenticateAuthUserLock(auth_user); + node = dlinkNodeNew(); + dlinkAdd(auth_user_request, node, &auth_user->requests); + + debug(29, 9) ("username = '%s'\nrealm = '%s'\nqop = '%s'\nalgorithm = '%s'\nuri = '%s'\nnonce = '%s'\nnc = '%s'\ncnonce = '%s'\nresponse = '%s'\ndigestnonce = '%d'\n", + digest_user->username, digest_request->realm, + digest_request->qop, digest_request->algorithm, + digest_request->uri, digest_request->nonceb64, + digest_request->nc, digest_request->cnonce, digest_request->response, nonce); + + return; +} + +/* send the initial data to a digest authenticator module */ +static void +authenticateDigestStart(auth_user_request_t * auth_user_request, RH * handler, void *data) +{ + authenticateStateData *r = NULL; + char buf[8192]; + digest_request_h *digest_request; + digest_user_h *digest_user; + assert(auth_user_request); + assert(handler); + assert(auth_user_request->auth_user->auth_type == AUTH_DIGEST); + assert(auth_user_request->auth_user->scheme_data != NULL); + assert(auth_user_request->scheme_data != NULL); + digest_request = auth_user_request->scheme_data; + digest_user = auth_user_request->auth_user->scheme_data; + debug(29, 9) ("authenticateStart: '\"%s\":\"%s\"'\n", digest_user->username, + digest_request->realm); + if (digestConfig->authenticate == NULL) { + handler(data, NULL); + return; + } + r = CBDATA_ALLOC(authenticateStateData, NULL); + r->handler = handler; + cbdataLock(data); + r->data = data; + r->auth_user_request = auth_user_request; + snprintf(buf, 8192, "\"%s\":\"%s\"\n", digest_user->username, digest_request->realm); + helperSubmit(digestauthenticators, buf, authenticateDigestHandleReply, r); +} diff --git a/src/auth/digest/auth_digest.h b/src/auth/digest/auth_digest.h new file mode 100644 index 0000000000..d500c16653 --- /dev/null +++ b/src/auth/digest/auth_digest.h @@ -0,0 +1,100 @@ +/* + * auth_digest.h + * Internal declarations for the digest auth module + */ + +#ifndef __AUTH_DIGEST_H__ +#define __AUTH_DIGEST_H__ +#include "rfc2617.h" + +/* Generic */ +typedef struct { + void *data; + auth_user_request_t *auth_user_request; + RH *handler; +} authenticateStateData; + +typedef struct _digest_request_h digest_request_h; +typedef struct _digest_user_h digest_user_h; +typedef struct _digest_nonce_data digest_nonce_data; + +typedef struct _digest_nonce_h digest_nonce_h; + +struct _digest_user_h { + char *username; + HASH HA1; + int HA1created; + /* what nonces have been allocated to this user */ + dlink_list nonces; +}; + +/* the digest_request structure is what follows the http_request around */ +struct _digest_request_h { + char *nonceb64; // = "dcd98b7102dd2f0e8b11d0f600bfb0c093"; + + char *cnonce; // = "0a4f113b"; + + char *realm; // = "testrealm@host.com"; + + char *pszPass; // = "Circle Of Life"; + + char *algorithm; // = "md5"; + + char nc[9]; // = "00000001"; + + char *pszMethod; // = "GET"; + + char *qop; // = "auth"; + + char *uri; // = "/dir/index.html"; + + char *response; + struct { + unsigned int authinfo_sent:1; + } flags; + digest_nonce_h *nonce; +}; + +/* data to be encoded into the nonce's b64 representation */ +struct _digest_nonce_data { + time_t creationtime; + /* in memory address of the nonce struct (similar purpose to an ETag) */ + digest_nonce_h *self; + long randomdata; +}; + +/* the nonce structure we'll pass around */ +struct _digest_nonce_h { + /* the first two items are (hash_link) */ + char *nonceb64; + digest_nonce_h *next; + digest_nonce_data noncedata; + /* number of uses we've seen of this nonce */ + long nc; + /* reference count */ + short references; + /* the auth_user this nonce has been tied to */ + auth_user_t *auth_user; + /* has this nonce been invalidated ? */ + struct { + unsigned int valid:1; + unsigned int incache:1; + } flags; +}; + +/* configuration runtime data */ +struct _auth_digest_config { + int authenticateChildren; + char *digestAuthRealm; + wordlist *authenticate; + time_t nonceGCInterval; + time_t noncemaxduration; + int noncemaxuses; +}; + +typedef struct _auth_digest_config auth_digest_config; + +/* strings */ +#define QOP_AUTH "auth" + +#endif diff --git a/src/auth/ntlm/auth_ntlm.cc b/src/auth/ntlm/auth_ntlm.cc index 9c7e775e0e..31079c1e07 100644 --- a/src/auth/ntlm/auth_ntlm.cc +++ b/src/auth/ntlm/auth_ntlm.cc @@ -1,6 +1,6 @@ /* - * $Id: auth_ntlm.cc,v 1.5 2001/01/12 00:37:30 wessels Exp $ + * $Id: auth_ntlm.cc,v 1.6 2001/01/31 22:16:43 hno Exp $ * * DEBUG: section 29 NTLM Authenticator * AUTHOR: Robert Collins @@ -53,6 +53,7 @@ static HLPSCB authenticateNTLMHandleplaceholder; static AUTHSACTIVE authenticateNTLMActive; static AUTHSAUTHED authNTLMAuthenticated; static AUTHSAUTHUSER authenticateNTLMAuthenticateUser; +static AUTHSCONFIGURED authNTLMConfigured; static AUTHSFIXERR authenticateNTLMFixErrorHeader; static AUTHSFREE authenticateNTLMFreeUser; static AUTHSDIRECTION authenticateNTLMDirection; @@ -94,6 +95,7 @@ static hash_table *proxy_auth_cache = NULL; void authNTLMDone(void) { + debug(29, 2) ("authNTLMDone: shutting down NTLM authentication.\n"); if (ntlmauthenticators) helperStatefulShutdown(ntlmauthenticators); authntlm_initialised = 0; @@ -165,6 +167,8 @@ authNTLMParse(authScheme * scheme, int n_configured, char *param_str) } ntlmConfig = scheme->scheme_data; if (strcasecmp(param_str, "program") == 0) { + if (ntlmConfig->authenticate) + wordlistDestroy(&ntlmConfig->authenticate); parse_wordlist(&ntlmConfig->authenticate); requirePathnameExists("authparam ntlm program", ntlmConfig->authenticate->key); } else if (strcasecmp(param_str, "children") == 0) { @@ -184,6 +188,7 @@ authSchemeSetup_ntlm(authscheme_entry_t * authscheme) { assert(!authntlm_initialised); authscheme->Active = authenticateNTLMActive; + authscheme->configured = authNTLMConfigured; authscheme->parse = authNTLMParse; authscheme->dump = authNTLMCfgDump; authscheme->requestFree = authNTLMAURequestFree; @@ -242,11 +247,21 @@ authNTLMInit(authScheme * scheme) int authenticateNTLMActive() +{ + return (authntlm_initialised == 1) ? 1 : 0; +} + + +int +authNTLMConfigured() { if ((ntlmConfig != NULL) && (ntlmConfig->authenticate != NULL) && (ntlmConfig->authenticateChildren != 0) && (ntlmConfig->challengeuses > -1) - && (ntlmConfig->challengelifetime > -1)) + && (ntlmConfig->challengelifetime > -1)) { + debug(29, 9) ("authNTLMConfigured: returning configured\n"); return 1; + } + debug(29, 9) ("authNTLMConfigured: returning unconfigured\n"); return 0; } @@ -456,7 +471,7 @@ authenticateNTLMHandleReply(void *data, void *lastserver, char *reply) result = S_HELPER_RELEASE; /*some error has occured. no more requests */ ntlm_request->authhelper = NULL; auth_user->flags.credentials_ok = 2; /* Login/Usercode failed */ - debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM.\n"); + debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); ntlm_request->auth_state = AUTHENTICATE_STATE_NONE; if ((t = strchr(reply, ' '))) /* strip after a space */ *t = '\0'; @@ -482,7 +497,7 @@ authenticateNTLMHandleReply(void *data, void *lastserver, char *reply) /* The helper broke on YR. It automatically * resets */ auth_user->flags.credentials_ok = 3; /* cannot process */ - debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %d.\n", lastserver); + debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %d. Error returned '%s'\n", lastserver, reply); /* mark it for starving */ helperstate->starve = 1; /* resubmit the request. This helper is currently busy, so we will get @@ -492,7 +507,7 @@ authenticateNTLMHandleReply(void *data, void *lastserver, char *reply) /* the helper broke on a KK */ /* first the standard KK stuff */ auth_user->flags.credentials_ok = 2; /* Login/Usercode failed */ - debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM.\n"); + debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); ntlm_request->auth_state = AUTHENTICATE_STATE_NONE; if ((t = strchr(reply, ' '))) /* strip after a space */ *t = '\0'; @@ -845,6 +860,10 @@ authenticateNTLMAuthenticateUser(auth_user_request_t * auth_user_request, reques /* we've recieved a negotiate request. pass to a helper */ debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n", proxy_auth); + if (auth_user->flags.credentials_ok == 2) { + /* the authentication fialed badly... */ + return; + } ntlm_request->auth_state = AUTHENTICATE_STATE_NEGOTIATE; ntlm_request->ntlmnegotiate = xstrndup(proxy_auth, NTLM_CHALLENGE_SZ + 5); conn->auth_type = AUTH_NTLM; diff --git a/src/authenticate.cc b/src/authenticate.cc index 2c0d6f205b..ebd57c9ed3 100644 --- a/src/authenticate.cc +++ b/src/authenticate.cc @@ -1,6 +1,6 @@ /* - * $Id: authenticate.cc,v 1.19 2001/01/12 00:37:14 wessels Exp $ + * $Id: authenticate.cc,v 1.20 2001/01/31 22:16:38 hno Exp $ * * DEBUG: section 29 Authenticator * AUTHOR: Duane Wessels @@ -61,7 +61,8 @@ authenticateAuthSchemeConfigured(const char *proxy_auth) int i; for (i = 0; i < Config.authConfig.n_configured; i++) { scheme = Config.authConfig.schemes + i; - if (strncasecmp(proxy_auth, scheme->typestr, strlen(scheme->typestr)) == 0) + if ((strncasecmp(proxy_auth, scheme->typestr, strlen(scheme->typestr)) == 0) && + (authscheme_list[scheme->Id].Active())) return 1; } return 0; @@ -296,7 +297,10 @@ char * authenticateUserRequestUsername(auth_user_request_t * auth_user_request) { assert(auth_user_request != NULL); - return authenticateUserUsername(auth_user_request->auth_user); + if (auth_user_request->auth_user) + return authenticateUserUsername(auth_user_request->auth_user); + else + return NULL; } /* returns @@ -322,7 +326,7 @@ authenticateActiveSchemeCount() { int i = 0, rv = 0; for (i = 0; authscheme_list && authscheme_list[i].typestr; i++) - if (authscheme_list[i].Active()) + if (authscheme_list[i].configured()) rv++; debug(29, 9) ("authenticateActiveSchemeCount: %d active.\n", rv); return rv; @@ -351,7 +355,7 @@ authenticateInit(authConfig * config) authScheme *scheme; for (i = 0; i < config->n_configured; i++) { scheme = config->schemes + i; - if (authscheme_list[scheme->Id].init) { + if (authscheme_list[scheme->Id].init && authscheme_list[scheme->Id].configured()) { authscheme_list[scheme->Id].init(scheme); } } @@ -370,7 +374,7 @@ authenticateShutdown(void) authscheme_list[i].donefunc(); else debug(29, 2) ("authenticateShutdown: scheme %s has not registered a shutdown function.\n", authscheme_list[i].typestr); - if (!reconfiguring) + if (shutting_down) authscheme_list[i].typestr = NULL; } } @@ -409,7 +413,7 @@ authenticateFixHeader(HttpReply * rep, auth_user_request_t * auth_user_request, else { int i; authScheme *scheme; - /* call each configured authscheme */ + /* call each configured & running authscheme */ for (i = 0; i < Config.authConfig.n_configured; i++) { scheme = Config.authConfig.schemes + i; if (authscheme_list[scheme->Id].Active()) diff --git a/src/cf.data.pre b/src/cf.data.pre index c967e4247a..b477dbdca6 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1,6 +1,6 @@ # -# $Id: cf.data.pre,v 1.211 2001/01/30 21:31:05 hno Exp $ +# $Id: cf.data.pre,v 1.212 2001/01/31 22:16:38 hno Exp $ # # # SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -1141,6 +1141,11 @@ DOC_START basic) then either put basic first, or disable the other schemes (by commenting out their program entry). + Once an authentication scheme is fully configured, it can only be shutdown + by shutting squid down and restarting. Changes can be made on the fly and + activated with a reconfigure. I.E. You can change to a different helper, + but not unconfigure the helper completely. + === Parameters for the basic scheme follow. === "program" cmdline @@ -1183,6 +1188,51 @@ DOC_START If you are using such a system, you will be vulnerable to replay attacks unless you also enable the IP ttl is strict option. + === Parameters for the digest scheme follow === + + "program" cmdline + Specify the command for the external authenticator. Such a + program reads a line containing "username":"realm" and replies + with the appropriate H(A1) value base64 encoded. See rfc 2616 for + the definition of H(A1). If you use an authenticator, + make sure you have 1 acl of type proxy_auth. By default, + authentication is not used. + + If you want to use build a authenticator, + jump over to the ../digest_auth_modules directory and choose the + authenticator to use. It it's directory type + % make + % make install + + Then, set this line to something like + + auth_param digest program @DEFAULT_PREFIX@/bin/digest_auth_pw @DEFAULT_PREFIX@/etc/digpass + + + "children" numberofchildren + The number of authenticator processes to spawn (no default). If you + start too few Squid will have to wait for them to process a backlog + of H(A1) calculations, slowing it down. When the H(A1) calculations + are done via a (slow) network you are likely to need lots of + authenticator processes. + auth_param digest children 5 + + "realm" realmstring + Specifies the realm name which is to be reported to the client for + the digest proxy authentication scheme (part of the text the user will + see when prompted their username and password). There is no default. + auth_param digest realm Squid proxy-caching web server + + "nonce_garbage_interval" timeinterval + Specifies the interval that nonces that have been issued to client_agent's + are checked for validity. + + "nonce_max_duration" timeinterval + Specifies the maximum length of time a given nonce will be valid for. + + "nonce_max_count" number + Specifies the maximum number of times a given nonce can be used. + === NTLM scheme options follow === "program" cmdline @@ -1219,10 +1269,16 @@ DOC_START NOCOMMENT_START #Recommended minimum configuration: +#auth_param digest program +#auth_param digest children 5 +#auth_param digest realm Squid proxy-caching web server +#auth_param digest nonce_garbage_interval 5 minutes +#auth_param digest nonce_max_duration 30 minutes +#auth_param digest nonce_max_count 50 #auth_param ntlm program -auth_param ntlm children 5 -auth_param ntlm max_challenge_reuses 0 -auth_param ntlm max_challenge_lifetime 2 minutes +#auth_param ntlm children 5 +#auth_param ntlm max_challenge_reuses 0 +#auth_param ntlm max_challenge_lifetime 2 minutes #auth_param basic program auth_param basic children 5 auth_param basic realm Squid proxy-caching web server diff --git a/src/enums.h b/src/enums.h index f6ac60feed..71c424cdec 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,6 +1,6 @@ /* - * $Id: enums.h,v 1.183 2001/01/12 00:37:17 wessels Exp $ + * $Id: enums.h,v 1.184 2001/01/31 22:16:38 hno Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -496,6 +496,7 @@ typedef enum { AUTH_UNKNOWN, /* default */ AUTH_BASIC, AUTH_NTLM, + AUTH_DIGEST, AUTH_BROKEN /* known type, but broken data */ } auth_type_t; diff --git a/src/helper.cc b/src/helper.cc index fde66cd3e7..97aea67012 100644 --- a/src/helper.cc +++ b/src/helper.cc @@ -1,6 +1,6 @@ /* - * $Id: helper.cc,v 1.24 2001/01/12 00:37:18 wessels Exp $ + * $Id: helper.cc,v 1.25 2001/01/31 22:16:38 hno Exp $ * * DEBUG: section 29 Helper process maintenance * AUTHOR: Harvest Derived? @@ -842,7 +842,23 @@ StatefulServerEnqueue(helper_stateful_server * srv, helper_stateful_request * r) { dlink_node *link = memAllocate(MEM_DLINK_NODE); dlinkAddTail(r, link, &srv->queue); - /* XXX No queue length check here? */ +/* TODO: warning if the queue on this server is more than X + * We don't check the queue size at the moment, because + * requests hitting here are deferrable + */ +/* hlp->stats.queue_size++; + * if (hlp->stats.queue_size < hlp->n_running) + * return; + * if (squid_curtime - hlp->last_queue_warn < 600) + * return; + * if (shutting_down || reconfiguring) + * return; + * hlp->last_queue_warn = squid_curtime; + * debug(14, 0) ("WARNING: All %s processes are busy.\n", hlp->id_name); + * debug(14, 0) ("WARNING: %d pending requests queued\n", hlp->stats.queue_size); + * if (hlp->stats.queue_size > hlp->n_running * 2) + * fatalf("Too many queued %s requests", hlp->id_name); + * debug(14, 1) ("Consider increasing the number of %s processes in your config file.\n", hlp->id_name); */ } diff --git a/src/structs.h b/src/structs.h index 1eded00a68..670b9b9372 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1,6 +1,6 @@ /* - * $Id: structs.h,v 1.379 2001/01/12 00:37:22 wessels Exp $ + * $Id: structs.h,v 1.380 2001/01/31 22:16:38 hno Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -139,6 +139,7 @@ struct _authscheme_entry { AUTHSADDTRAILER *AddTrailer; AUTHSAUTHED *authenticated; AUTHSAUTHUSER *authAuthenticate; + AUTHSCONFIGURED *configured; AUTHSDUMP *dump; AUTHSFIXERR *authFixHeader; AUTHSFREE *FreeUser; diff --git a/src/typedefs.h b/src/typedefs.h index e1fbc0d7f6..ae4fca5eeb 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -1,6 +1,6 @@ /* - * $Id: typedefs.h,v 1.120 2001/01/12 00:37:23 wessels Exp $ + * $Id: typedefs.h,v 1.121 2001/01/31 22:16:39 hno Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -293,6 +293,7 @@ typedef void StatHistBinDumper(StoreEntry *, int idx, double val, double size, i typedef int AUTHSACTIVE(); typedef int AUTHSAUTHED(auth_user_request_t *); typedef void AUTHSAUTHUSER(auth_user_request_t *, request_t *, ConnStateData *, http_hdr_type); +typedef int AUTHSCONFIGURED(); typedef void AUTHSDECODE(auth_user_request_t *, const char *); typedef int AUTHSDIRECTION(auth_user_request_t *); typedef void AUTHSDUMP(StoreEntry *, const char *, authScheme *);