--------------------- 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 #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;jdomain=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;jdomain,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;