---------------------
PatchSet 626 
Date: 2000/10/10 10:12:33
Author: kinkie
Branch: ntlm
Tag: (none) 
Log:
Added cache expiration, challenge hash renewal, PDC disconnect for
inactivity, support for multiple DCs
Preliminary support for DC failover and load-balancing (untested!).
Command line format changed.
Moved stuff out of main().

Members: 
	ntlm_auth_modules/NTLMSSP/ntlm_auth.c:1.1.2.7->1.1.2.8 

Index: squid/ntlm_auth_modules/NTLMSSP/ntlm_auth.c
===================================================================
RCS file: /cvsroot/squid-sf//squid/ntlm_auth_modules/NTLMSSP/Attic/ntlm_auth.c,v
retrieving revision 1.1.2.7
retrieving revision 1.1.2.8
diff -u -r1.1.2.7 -r1.1.2.8
--- squid/ntlm_auth_modules/NTLMSSP/ntlm_auth.c	18 Aug 2000 15:03:16 -0000	1.1.2.7
+++ squid/ntlm_auth_modules/NTLMSSP/ntlm_auth.c	10 Oct 2000 10:12:33 -0000	1.1.2.8
@@ -19,12 +19,11 @@
  *  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:
- * implement cache expiration
- * PDC disconnect, after X minutes of inactivity
- * challenge hash expiry and renewal.
- *  This is somewhat parallel to the PDC disconnect issue
  * 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
@@ -72,11 +71,23 @@
 #include <signal.h>
 #endif
 
-
+time_t last_request;
+char load_balance=0, failover_enabled=0;
 char *domain = NULL, *domain_controller = NULL;
 
 /* cache-related hash and management functions */
 
+typedef struct _dc dc;
+struct _dc {
+	char *domain;
+	char *controller;
+	dc *next;
+};
+
+dc* controllers=NULL;
+int numcontrollers=0;
+dc *current_dc;
+
 hash_table *cache;
 typedef struct _hash_entry {
 	char *k;											/* the entry's value */
@@ -100,17 +111,19 @@
 }
 
 /* housekeeping cycle and periodic operations */
-static void cleanup_cache() {
+
+/* cleans the auth-strings cache up. */
+static void cleanup_cache(int allowed_age) {
 	time_t now=time(NULL);
 	hash_link *h;
 	hash_first(cache);
-	while (h=((hash_link *)hash_next(cache))) {
+	while ((h=((hash_link *)hash_next(cache)))) {
 		hash_entry *e=(hash_entry *)h->key;
 #ifdef SHOW_CACHE_CONTENTS_UPON_HOUSEKEEP
 		debug("cache entry:\n\tchallenge: %s\n\tcreds: %s\n\tage: %d\n",
 					e->k, e->creds, now-e->atime);
 #endif /* SHOW_CACHE_CONTENTS_UPON_HOUSEKEEP */
-		if (now - e->atime < ENTRY_EXPIRY) /* recently accessed */
+		if (now - e->atime < allowed_age) /* recently accessed */
 			continue;
 		debug("freeing entry for creds %s\n",e->creds);
 		hash_remove_link(cache,h);
@@ -120,6 +133,24 @@
 	}
 }
 
+/* makes a null-terminated string upper-case. Changes CONTENTS! */
+static void uc (char *string) {
+	char *p=string, c;
+	while ((c=*p)) {
+		*p=toupper(c);
+		p++;
+	}
+}
+
+/* makes a null-terminated string lower-case. Changes CONTENTS! */
+static void lc (char *string) {
+	char *p=string, c;
+	while ((c=*p)) {
+		*p=tolower(c);
+		p++;
+	}
+}
+
 /* I know, it's a dumb function, and ugly to boot.
 	 Let's get stuff working first, and worry about beauty later, OK? */
 static time_t last_refresh=0;
@@ -133,67 +164,305 @@
 	/* we really want to do this synchronously, otherwise it might catch
 		 us between challenge and authenticate of an uncached request:
 		 BAMF! instant credentials request!*/
-	if ((now-last_refresh)>CHALLENGE_REFLESH) {
+	if ((now-last_refresh)>challenge_refresh) {
 		debug("Asking for challenge refresh\n");
 		need_challenge_refresh=1;
 	}
 }
 
+/* checks that an inactivity timeout hasn't expired. If so,
+ * disconnect from the DC and clear cache.*/
+static void check_for_inactivity(void) {
+	time_t now=time(NULL);
+	if (connectedp() && now-last_request > inactivity_timeout) {
+		dc_disconnect();
+		cleanup_cache(0); /* force clearing all entries */
+	}
+}
+
 /* Huh? It seems to be invoked more often than planned... */
 static void housekeep(int ignored) {
 	debug("housekeeping..\n");
 	signal(SIGALRM,housekeep);
-	alarm(CYCLE);
-	cleanup_cache();
+	alarm(cleanup_cycle);
+	cleanup_cache(entry_expiry);
 	refresh_challenge();
+	check_for_inactivity();
 }
 
 /*
  * options:
- * -d domain
- * -s domain controller (backup DCs will be added later)
+ * -k housekeeping-interval
+ * -c challenge-refresh interval
+ * -e positive user-auth caching period
+ * -b try load-balancing the domain-controllers
+ * -f fail-over to another DC if DC connection fails.
+ * domain\controller ...
  */
 void process_options (int argc, char *argv[]) {
-	int opt;
-	char *c;
-	while (-1!=(opt=getopt(argc,argv,"d:s:"))) {
+	int opt,j,tmp,had_error=0;
+	dc *new_dc=NULL, *last_dc=NULL;
+	while (-1!=(opt=getopt(argc,argv,"d:s:k:c:e:bf"))) {
 		switch(opt) {
-		case 'd':
-			domain=optarg;
-			for (c=optarg;*c!='\0';c++) 
-				*c=(char)toupper(*c);
+		case 'b':
+			load_balance=1;
+			break;
+		case 'f':
+			failover_enabled=1;
+			break;
+		case 'k':
+			tmp=atoi(optarg);
+			if (tmp && tmp > 0) 
+				cleanup_cycle=tmp;
+			else {
+				fprintf(stderr,
+								"House-keep-cycle setting ('-k') is invalid\n");
+				had_error=1;
+			}
+			break;
+		case 'c':
+			tmp=atoi(optarg);
+			if (tmp && tmp > 0) 
+				challenge_refresh=tmp;
+			else {
+				fprintf(stderr,
+								"Challenge-refresh setting ('-c') is invalid\n");
+				had_error=1;;
+			}
 			break;
-		case 's':
-			domain_controller=optarg;
-			for (c=optarg;*c!='\0';c++) 
-				*c=(char)toupper(*c);
+		case 'e':
+			tmp=atoi(optarg);
+			if (tmp && tmp > 0) 
+				entry_expiry=tmp;
+			else {
+				fprintf(stderr,
+								"Cache keep-alive setting ('-e') is invalid\n");
+				had_error=1;;
+			}
 			break;
 		default:
 			fprintf(stderr,"unknown option: -%c. Exiting\n",opt);
-			exit(1);
+			had_error=1;
 		}
 	}
+	if (had_error)
+		exit(1);
+	/* Okay, now begin filling controllers up */
+	/* we can avoid memcpy-ing, and just reuse argv[] */
+	for (j=optind;j<argc;j++) {
+		char *d, *c;
+		d=argv[j];
+		if (NULL==(c=strchr(d,'\\')) && NULL==(c=strchr(d,'/'))) {
+			fprintf(stderr,"Couldn't grok domain-controller %s\n",d);
+			continue;
+		}
+		*c++='\0';
+		new_dc=(dc *)malloc(sizeof(dc));
+		if (!new_dc) {
+			fprintf(stderr,"Malloc error while parsing DC options\n");
+			continue;
+		}
+		/* capitalize */
+		uc(c);
+		uc(d);
+		/*
+		for (u=c;*u!='\0';u++)
+			*u=(char)toupper(*u);
+		for (u=d;*u!='\0';u++)
+			*u=(char)toupper(*u);
+		*/
+		numcontrollers++;
+		new_dc->domain=d;
+		new_dc->controller=c;
+		if (controllers==NULL) {		/* first controller */
+			controllers=new_dc;
+			last_dc=new_dc;
+		} else {
+			last_dc->next=new_dc;			/* can't be null */
+			last_dc=new_dc;
+		}
+	}
+	if (numcontrollers==0) {
+		fprintf(stderr,"You must specify at least one domain-controller!\n");
+		exit(1);
+	}
+	last_dc->next=controllers;		/* close the queue, now it's circular */
 }
 
-int status=0;										/* 0=waiting, 1=got neg, waiting for auth */
+/* tries connecting to the domain controllers in the "controllers" ring,
+ * with failover if the adequate option is specified.
+ */
+char *obtain_challenge() {
+	int j=0;
+	char *ch;
+	for (j=0;j<numcontrollers;j++) {
+		ch=make_challenge(current_dc->domain,current_dc->controller);
+		if (ch)
+			return ch;
+		if (failover_enabled==0)
+			break;
+		current_dc=current_dc->next;
+	}
+	return NULL;
+}
 
-int main(int argc, char *argv[]) {
+int status=0;										/* 0=waiting, 1=got neg, waiting for auth */
+void manage_request () {
 	char buf[10240];
 	char *ch, *decoded, *cred;
 	ntlmhdr *fast_header;
 	int plen;
 	hash_link *lnk;
 	hash_entry he;								/* dummy, used for hash lookups */
+	hash_entry *h;
 
-	process_options(argc,argv);
-	if (domain==NULL) {
-		fprintf(stderr,"You must specify a domain (with the -d argument)\n");
-		exit(1);
+	if(fgets(buf, 10240, stdin) == NULL) exit(0);
+	ch=strchr(buf,'\n');
+	if (ch)
+		*ch='\0';									/* terminate the string at newline. */
+	debug("ntlm authenticator. Got '%s' from cache\n",buf);
+	
+	if (strncmp(buf,"RESET",5)==0) {
+		debug("ntlm authenticator resetting, sending OK\n");
+		debug("status %d\n",status);
+		status=0;
+		printf("RESET OK\n");
+		return;
+	}
+	
+	last_request=time(NULL);
+	/* ultra-fast-track credentials checking. We don't even try to understand
+		 what's going on. If it matches anything we got in cache, it's OK for
+		 us.*/
+	debug("Trying the fast-track way\n");
+	he.k=buf;
+	lnk=hash_lookup(cache,&he); /* look up the cache */
+	/* 		debug("lookup done\n"); */
+	if (lnk) {
+		hash_entry *v=(hash_entry *)lnk->key;
+		debug("cache hit. Fast-track-granting.\n");
+		OK(v->creds);
+		v->atime=time(NULL);
+		status=0;
+		return;
+	} else {
+		debug("cache miss. taking the long route, stopping by Redmond.\n");
+	}
+	
+	/* figure out what we got */
+	decoded=base64_decode(buf);
+	/* Note: we don't need to manage memory at this point, since
+	 *  base64_decode returns a pointer to static storage.
+	 */
+	
+	if (!decoded) {							/* decoding failure, return error */
+		ERR("WHAT? Packet format error, couldn't base64-decode");
+		status=0;
+		return;
+	}
+	
+	/* fast-track-decode request type. */
+	fast_header=(ntlmhdr *)decoded;
+	
+	/* sanity-check: it IS a NTLMSSP packet, isn't it? */
+	if (memcmp(fast_header->signature,"NTLMSSP",8) != 0) {
+		ERR("not an NTLMSSP packet. What'cha talking 'bout, Willis?");
+		status=0;
+		return;
+	}
+	
+	/* fast-determine the packet type */
+	switch (fast_header->type) {
+	case NTLM_NEGOTIATE:
+		debug("status %d\n",status);
+		if (status==1) {
+			fprintf(stderr,"Huh? Got two negotiations in a row\n");
+		}
+		status=1;
+		/* Challenge refresh. It's done here so to be done synchronously */
+		/* It would be nice to be able to do it while idle though... */
+		/* let's leave it like this - for now */
+		if (need_challenge_refresh) {
+			debug("Closing connection to the DC for challenge renewal\n");
+			dc_disconnect();
+			need_challenge_refresh=0;
+		}
+		ch=obtain_challenge();
+		if (ch == NULL) {
+			ERR("couldn't obtain a challenge from the Domain Controller\n");
+			status=0;
+			return;
+		}
+		printf("CH %s\n",ch);
+		break;
+		case NTLM_CHALLENGE:
+			ERR("got a challenge. We refuse to have our authority disputed");
+			return;
+			break;										/* useless */
+	case NTLM_AUTHENTICATE:
+		if (status==0) {
+			fprintf(stderr,"Huh? Got two authentications in a row\n");
+		}
+		plen=strlen(buf)*3/4; /* we only need it here. Optimization */
+		status=0;
+		do {
+			cred=ntlm_check_auth((struct ntlm_authenticate *)decoded, plen);
+			if (cred==NULL) {
+				switch (ntlm_errno) {
+				case NTLM_SERVER_ERROR:
+				case NTLM_PROTOCOL_ERROR:
+				case NTLM_NOT_CONNECTED:
+					/* reconnect */
+					dc_disconnect();
+					current_dc=current_dc->next;
+					obtain_challenge();
+					ERR("domain controller failure");
+					return;
+					break;									/* not reached */
+				case NTLM_LOGON_ERROR:
+					ERR("authentication failure");
+					return;
+				}
+			}
+		} while (cred==NULL);
+		lc(cred); /* let's lowercase them for our convenience */
+		OK(cred);
+		/* set the value in the cache */
+		debug("storing in cache entry for creds %s\n",cred);
+		h=malloc(sizeof(hash_entry));
+		h->k=malloc(strlen(buf)+1);
+		strcpy(h->k,buf);
+		h->creds=malloc(sizeof(cred)+1);
+		strcpy(h->creds,cred);
+		h->atime=time(NULL);
+		lnk=malloc(sizeof(hash_link));
+		lnk->key=(char *)h;
+		hash_join(cache,lnk);
+	default:
+		ERR("unknown authentication packet type");
+		status=0;
+		return;
 	}
-	if (domain_controller==NULL) {
-		fprintf(stderr,"You must specify a domain controller "
-						"(with the -s argument)\n");
-			exit(1);
+}
+
+int main(int argc, char *argv[]) {
+
+	debug("starting up...\n");
+	
+	process_options(argc,argv);
+
+	debug("options processed OK\n");
+
+	/* select the first domain controller we're going to use */
+	current_dc=controllers;
+	if (load_balance!=0 && numcontrollers > 1) {
+		pid_t pid=getpid();
+		unsigned int n;
+		n=pid%numcontrollers;
+		debug("load balancing. Selected controller #%d\n",n);
+		while (--n>=0) {
+			current_dc=current_dc->next;
+		}
 	}
 
 	/* initialize FDescs */
@@ -201,125 +470,22 @@
 	setbuf(stderr,NULL);
 
 	/* initialize cache hashtable */
+	debug("creating cache structure\n");
 	cache=hash_create(hashcmp,467,hashhash);
 
 	/* initialize housekeeping cycle */
+	debug("Firing housekeeping routines up\n");
 	signal(SIGALRM,housekeep);
-	alarm(CYCLE);
+	alarm(cleanup_cycle);
 	
 	while (1) {
-		if(fgets(buf, 10240, stdin) == NULL) exit(0);
-		ch=strchr(buf,'\n');
-		if (ch)
-			*ch='\0';									/* terminate the string at newline. */
-		debug("ntlm authenticator. Got '%s' from cache\n",buf);
-		
-		if (strncmp(buf,"RESET",5)==0) {
-			debug("ntlm authenticator resetting, sending OK\n");
-			debug("status %d\n",status);
-			status=0;
-			printf("RESET OK\n");
-			continue;
-		}
-
-		/* ultra-fast-track credentials checking. We don't even try to understand
-			 what's going on. If it matches anything we got in cache, it's OK for
-			 us.*/
-		debug("Trying the fast-track way\n");
-		he.k=buf;
-		lnk=hash_lookup(cache,&he); /* look up the cache */
-/* 		debug("lookup done\n"); */
-		if (lnk) {
-			hash_entry *v=(hash_entry *)lnk->key;
-			debug("cache hit. Fast-track-granting.\n");
-			OK(v->creds);
-			v->atime=time(NULL);
-			status=0;
-			continue;
-		} else {
-			debug("cache miss. taking the long route, stopping by Redmond.\n");
-		}
-		
-		/* figure out what we got */
-		decoded=base64_decode(buf);
-		/* Note: we don't need to manage memory at this point, since
-		 *  base64_decode returns a pointer to static storage.
-		 */
-
-		if (!decoded) {							/* decoding failure, return error */
-			ERR("WHAT? Packet format error, couldn't base64-decode");
-			status=0;
-			continue;
-		}
-
-		/* fast-track-decode request type. */
-		fast_header=(ntlmhdr *)decoded;
-
-		/* sanity-check: it IS a NTLMSSP packet, isn't it? */
-		if (memcmp(fast_header->signature,"NTLMSSP",8) != 0) {
-			ERR("not an NTLMSSP packet. What'cha talking 'bout, Willis?");
-			status=0;
-			continue;
-		}
-
-		/* fast-determine the packet type */
-		switch (fast_header->type) {
-		case NTLM_NEGOTIATE:
-			debug("status %d\n",status);
-			if (status==1) {
-				fprintf(stderr,"Huh? Got two negotiations in a row\n");
-			}
-			status=1;
-			/* Challenge refresh. It's done here so to be done synchronously */
-			/* It would be nice to be able to do it while idle though... */
-			/* let's leave it like this - for now */
-			if (need_challenge_refresh) {
-				debug("Closing connection to the DC for challenge renewal\n");
-				dc_disconnect();
-				need_challenge_refresh=0;
-			}
-			ch=make_challenge();
-			if (ch == NULL) {
-				ERR("couldn't obtain a challenge from the Domain Controller\n");
-				status=0;
-				continue;
-			}
-			printf("CH %s\n",ch);
-			break;
-		case NTLM_CHALLENGE:
-			ERR("got a challenge. We refuse to have our authority disputed");
-			continue;
-			break;
-		case NTLM_AUTHENTICATE:
-			if (status==0) {
-				fprintf(stderr,"Huh? Got two authentications in a row\n");
-			}
-			plen=strlen(buf)*3/4; /* we only need it here. Optimization */
-			status=0;
-			cred=ntlm_check_auth((struct ntlm_authenticate *)decoded, plen);
-			if (cred!=NULL) {
-				hash_entry *h;
-				OK(cred);
-				/* set the value in the cache */
-				debug("storing in cache entry for creds %s\n",cred);
-				h=malloc(sizeof(hash_entry));
-				h->k=malloc(strlen(buf)+1);
-				strcpy(h->k,buf);
-				h->creds=malloc(sizeof(cred)+1);
-				strcpy(h->creds,cred);
-				h->atime=time(NULL);
-				lnk=malloc(sizeof(hash_link));
-				lnk->key=(char *)h;
-				hash_join(cache,lnk);
-			} else {
-				ERR("authentication failure");
-			}
-			break;
-		default:
-			ERR("unknown authentication packet type");
-			status=0;
-			continue;
-		}
+		debug("managing request\n");
+		manage_request();
 	}
 	return 0;
 }
+
+int cleanup_cycle=DEFAULT_CLEANUP_CYCLE;
+int challenge_refresh=DEFAULT_CHALLENGE_REFLESH;
+int entry_expiry=DEFAULT_ENTRY_EXPIRY;
+time_t inactivity_timeout=DEFAULT_INACTIVITY_TIMEOUT;
