One way to solve slow logins with NT-domains

Luke Kenneth Casson Leighton lkcl at switchboard.net
Thu May 6 17:51:47 GMT 1999


EXCELLENT!

you wat to do one for getgrp() code as well?

also, what's performance hit on doing re-read of pwd cache each and every
time prior to login?  at least that way you can guarantee that it will be
up-to-date on each login.

On Fri, 7 May 1999, Jani Jaakkola wrote:

> 
> When i was experimenting with the NT-domain code, it turned out to be too
> slow for our environment. We have 2300 users in our /etc/passwd and 200
> groups in our /etc/group. It turned out that samba does hundreds of
> getpwnam() and getpwuid() calls when someone logins to domain, which will
> take time, since Linux glibc-2.0 C-library doesn't have any caching
> for passwords and our passwd file is 160K in size.
> 
> So I wrote a simple cache wrapper around sambas getpwnam() and getpwuid()
> calls. The cache is built the first time when either of them is called and
> is kept around for 15 seconds, after which it will be rebuilt. 
> 
> Oh yes.. If you happen to have multiple user names sharing the same uid or
> multiple uids sharing the same user name, the cache will return the last
> one in /etc/passwd instead of the first one. If you think this is a
> promlem, it can be easily fixed.
> 
> I'm including the patch in this email. It is agains the current CVS
> version (well, current version at least an hour ago). 
> I hope no one will get upset, since it is 355 lines. It isn't tested very 
> throughly (i just got it ready an hour ago). Anyway, I can log on to NT
> domain in 1 second instead of 15-20 seconds and I won't get the annoying
> slow network dialog :)
> 
> 
> --- samba/source/include/proto.h	Tue May  4 01:00:31 1999
> +++ samba.patched/source/include/proto.h	Thu May  6 13:16:07 1999
> @@ -382,6 +382,7 @@
>  
>  /*The following definitions come from  lib/username.c  */
>  
> +struct passwd *hashed_getpwnam(const char *name) ;
>  char *get_home_dir(char *user);
>  BOOL map_username(char *user);
>  struct passwd *Get_Pwnam(char *user,BOOL allow_change);
> diff -x CVS -ru samba/source/lib/username.c samba.patched/source/lib/username.c
> --- samba/source/lib/username.c	Mon Dec 14 22:21:39 1998
> +++ samba.patched/source/lib/username.c	Thu May  6 19:21:37 1999
> @@ -27,6 +27,276 @@
>  static struct passwd *uname_string_combinations2(char *s, int offset, struct passwd * (*fn) (char *), int N);
>  
>  /****************************************************************************
> +  Since getpwnam() makes samba really slow with the NT-domain code
> +  (reading /etc/passwd again and again and again), here is an implementation
> +  of very simple passwd cache
> +****************************************************************************/
> +#define PASSWD_HASH_SIZE 1009
> +/* The hashtable is rebuild every 15 seconds */
> +#define PASSWD_HASH_AGE 15
> +struct passwd_hash_entry {
> +  int entry;
> +  int next;
> +};
> +
> +struct passwd_hash_table_s {
> +  struct passwd *passwds;
> +  int passwds_size;  
> +  int *names;
> +  int *uids;
> +  struct passwd_hash_entry *entries;
> +  int entries_size;
> +  struct timeval build_time;
> +} passwd_hash_table = {
> +  NULL,0,NULL,NULL,NULL,0,{0,0}
> +};
> +
> +int name_hash_function(const char *name) 
> +{
> +  /* I guess that there must be better hash functions. This one was the
> +   * first to come into mind :) */
> +  unsigned int value=0;
> +  while (*name) {
> +    value=(value<<8)|(unsigned char)(*name);
> +    if (value>1048576) value=value%PASSWD_HASH_SIZE;
> +    name++;
> +  }
> +  value=value%PASSWD_HASH_SIZE;
> +  return value;
> +}
> +    
> +int uid_hash_function(uid_t uid) 
> +{
> +  return uid%PASSWD_HASH_SIZE;
> +}
> +
> +
> +BOOL build_passwd_hash_table() 
> +{
> +  struct passwd_hash_table_s *pht=&passwd_hash_table; /* Convenience */
> +  int num_passwds=0;
> +  int num_entries=0;
> +  struct passwd *pass;
> +  int i;
> +  int name_i,uid_i;
> +
> +  DEBUG(3,("Building passwd hash table\n"));
> +  /* Free the allocated strings in old hash table */
> +  for (i=0;i<pht->passwds_size;i++) {
> +    free(pht->passwds[i].pw_name);
> +    free(pht->passwds[i].pw_passwd);
> +    free(pht->passwds[i].pw_gecos);
> +    free(pht->passwds[i].pw_dir);
> +    free(pht->passwds[i].pw_shell);    
> +  }
> +
> +  /* Initialize hash table if first table build */
> +  if (pht->passwds_size==0) {
> +    DEBUG(3,("Building passwd hash table for the first time\n"));
> +    pht->passwds=malloc(sizeof(struct passwd)*64); /* A reasonable default */
> +    pht->passwds_size=64;
> +  }
> +  if (pht->names==NULL) {
> +    pht->names=malloc(sizeof(struct passwd_hash_entry *)*PASSWD_HASH_SIZE);
> +  }
> +  if (pht->uids==NULL) {
> +    pht->uids=malloc(sizeof(struct passwd_hash_entry *)*PASSWD_HASH_SIZE);
> +  }
> +  if (pht->entries==NULL) {
> +    pht->entries=malloc(sizeof(struct passwd_hash_entry)*128);
> +    pht->entries_size=128;
> +  }
> +  if (pht->passwds==NULL || pht->names==NULL || 
> +      pht->uids==NULL || pht->entries==NULL) {
> +    goto fail;
> +  }
> +  
> +  /* Clear out the hash table */
> +  for(i=0;i<PASSWD_HASH_SIZE;i++) pht->uids[i]=-1;
> +  for(i=0;i<PASSWD_HASH_SIZE;i++) pht->names[i]=-1;
> +
> +  /* Now do the build */
> +  setpwent();
> +
> +  while((pass=getpwent())) {
> +
> +    /* Check that we have enough space */
> +    if (num_passwds==pht->passwds_size) {
> +      struct passwd *new_passwds=NULL;
> +      pht->passwds_size+=pht->passwds_size/2;
> +      new_passwds=realloc(pht->passwds,
> +			   sizeof(struct passwd)*pht->passwds_size);
> +      if (new_passwds==NULL) goto fail;
> +      pht->passwds=new_passwds;
> +    }
> +    if (num_entries+1>=pht->entries_size) {
> +      pht->entries_size+=pht->entries_size/2;
> +      pht->entries=realloc(pht->entries,
> +			   sizeof(struct passwd_hash_entry)*pht->entries_size);
> +      if (pht->entries==NULL) goto fail;
> +    }
> +
> +    /* Copy the passwd struct */
> +    memset(&pht->passwds[num_passwds],0,sizeof(struct passwd));
> +    pht->passwds[num_passwds].pw_uid=pass->pw_uid;
> +    pht->passwds[num_passwds].pw_gid=pass->pw_gid;  
> +    if (
> +	(pht->passwds[num_passwds].pw_name=strdup(pass->pw_name))==NULL ||
> +	(pht->passwds[num_passwds].pw_passwd=strdup(pass->pw_passwd))==NULL ||
> +	(pht->passwds[num_passwds].pw_gecos=strdup(pass->pw_gecos))==NULL ||
> +	(pht->passwds[num_passwds].pw_dir=strdup(pass->pw_dir))==NULL ||
> +	(pht->passwds[num_passwds].pw_shell=strdup(pass->pw_shell))==NULL ) {
> +      num_passwds++;
> +      goto fail;
> +    }
> +    
> +    /* Add to the hash table */
> +    /* Add the name */
> +    pht->entries[num_entries].entry=num_passwds;
> +    name_i=name_hash_function(pass->pw_name);
> +    pht->entries[num_entries].next=pht->names[name_i];
> +    pht->names[name_i]=num_entries;
> +    num_entries++;
> +    /* Add the uid */
> +    pht->entries[num_entries].entry=num_passwds;
> +    uid_i=uid_hash_function(pass->pw_uid);
> +    pht->entries[num_entries].next=pht->uids[uid_i];
> +    pht->uids[uid_i]=num_entries;
> +    num_entries++;
> +
> +    /* This entry has been done */
> +    num_passwds++;
> +  }    
> +  endpwent();
> +  
> +  if (pht->passwds_size>num_passwds) {
> +    struct passwd *passwds;
> +    passwds=realloc(pht->passwds,sizeof(pht->passwds[0])*num_passwds);
> +    if (passwds==NULL) goto fail;
> +    pht->passwds=passwds;
> +    pht->passwds_size=num_passwds;
> +  }
> +  if (pht->entries_size>num_entries) {
> +    struct passwd_hash_entry *entries;
> +    entries=realloc(pht->entries,sizeof(pht->entries[0])*num_entries);
> +    if (entries==NULL) goto fail;
> +    pht->entries=entries;
> +    pht->entries_size=num_entries;
> +  }
> +
> +  /* Mark the creation time */
> +  GetTimeOfDay(&pht->build_time);
> +  /* Everything went smoothly. */
> +  return True;
> +
> + fail:
> +  DEBUG(0,("Failed to create passwd hash table: %s",strerror(errno)));
> +  /* OK: now the untested part. Normally this should never happen:
> +   * Only running out of memory could cause this and even then
> +   * we have enough trouble already. */
> +  while (num_passwds>0) {
> +    num_passwds--;
> +    free(pht->passwds[num_passwds].pw_name);
> +    free(pht->passwds[num_passwds].pw_passwd);
> +    free(pht->passwds[num_passwds].pw_gecos);
> +    free(pht->passwds[num_passwds].pw_dir);
> +    free(pht->passwds[num_passwds].pw_shell);    
> +  }
> +  free(pht->entries);
> +  free(pht->uids);
> +  free(pht->names);
> +  free(pht->passwds);
> +  pht->passwds_size=0;
> +  pht->entries_size=0;    
> +  /* Also mark fail time, so that retry will happen after PASSWD_HASH_AGE */
> +  GetTimeOfDay(&pht->build_time);
> +  return False;
> +}
> +
> +BOOL have_passwd_hash() {
> +  struct passwd_hash_table_s *pht=&passwd_hash_table;
> +  struct timeval tv;
> +  GetTimeOfDay(&tv);
> +  /* I'm ignoring microseconds. If you think they matter, go ahead
> +   * and implement them */
> +  if (tv.tv_sec - pht->build_time.tv_sec > PASSWD_HASH_AGE) {
> +    return build_passwd_hash_table();
> +  }
> +  return pht->passwds_size>0;
> +}
> +
> +struct passwd *hashed_getpwnam(const char *name)
> +{
> +  struct passwd_hash_table_s *pht=&passwd_hash_table;
> +
> +  DEBUG(5,("getpwnam(%s)\n", name));
> +
> +  if (have_passwd_hash()) {
> +    int name_i=name_hash_function(name);
> +    int index=pht->names[name_i];
> +    while(index!=-1) {
> +      struct passwd *pass=&pht->passwds[pht->entries[index].entry];
> +      if (strcmp(name,pass->pw_name)==0) {
> +	DEBUG(5,("Found: %s:%s:%d:%d:%s:%s:%s\n",
> +		 pass->pw_name,
> +		 pass->pw_passwd,
> +		 pass->pw_uid,
> +		 pass->pw_gid,
> +		 pass->pw_gecos,
> +		 pass->pw_dir,
> +		 pass->pw_shell));
> +	return pass;      
> +      }
> +      index=pht->entries[index].next;
> +    }
> +
> +    /* Not found */
> +    DEBUG(5,("%s not found\n",name));
> +    return NULL;
> +  } 
> +  /* Fall back to real getpwnam() */
> +  return getpwnam(name);
> +}
> +
> +/*******************************************************************
> +turn a uid into a user name
> +********************************************************************/
> +char *uidtoname(uid_t uid)
> +{
> +  static char name[40];
> +  struct passwd_hash_table_s *pht=&passwd_hash_table;
> +  struct passwd *pass=NULL;
> +
> +  DEBUG(5,("uidtoname(%d)\n",uid));
> +  if (have_passwd_hash()) {
> +    int index=pht->uids[uid_hash_function(uid)];
> +    while(index!=-1) {
> +      pass=&pht->passwds[pht->entries[index].entry];
> +      if (pass->pw_uid==uid) {
> +	DEBUG(5,("Found: %s:%s:%d:%d:%s:%s:%s\n",
> +		 pass->pw_name,
> +		 pass->pw_passwd,
> +		 pass->pw_uid,
> +		 pass->pw_gid,
> +		 pass->pw_gecos,
> +		 pass->pw_dir,
> +		 pass->pw_shell));
> +	return pass->pw_name;      
> +      }
> +      index=pht->entries[index].next;
> +    }
> +    DEBUG(5,("Hash miss"));
> +    pass=NULL;
> +  } else {
> +    /* No hash table, fall back to getpwuid */
> +    pass = getpwuid(uid);
> +  }
> +  if (pass) return(pass->pw_name);
> +  slprintf(name, sizeof(name) - 1, "%d",(int)uid);
> +  return(name);
> +}
> +
> +/****************************************************************************
>  get a users home directory.
>  ****************************************************************************/
>  char *get_home_dir(char *user)
> @@ -154,7 +424,7 @@
>  {
>    struct passwd *ret;
>  
> -  ret = getpwnam(s);
> +  ret = hashed_getpwnam(s);
>    if (ret)
>      {
>  #ifdef HAVE_GETPWANAM
> diff -x CVS -ru samba/source/lib/util.c samba.patched/source/lib/util.c
> --- samba/source/lib/util.c	Mon Apr 12 04:39:46 1999
> +++ samba.patched/source/lib/util.c	Thu May  6 17:49:40 1999
> @@ -2456,19 +2456,6 @@
>  }
>  
>  /*******************************************************************
> -turn a uid into a user name
> -********************************************************************/
> -char *uidtoname(uid_t uid)
> -{
> -  static char name[40];
> -  struct passwd *pass = getpwuid(uid);
> -  if (pass) return(pass->pw_name);
> -  slprintf(name, sizeof(name) - 1, "%d",(int)uid);
> -  return(name);
> -}
> -
> -
> -/*******************************************************************
>  turn a gid into a group name
>  ********************************************************************/
>  
> diff -x CVS -ru samba/source/passdb/sampassdb.c samba.patched/source/passdb/sampassdb.c
> --- samba/source/passdb/sampassdb.c	Thu Mar 25 23:32:04 1999
> +++ samba.patched/source/passdb/sampassdb.c	Thu May  6 12:18:05 1999
> @@ -678,7 +678,7 @@
>  
>  	if (sam->unix_gid == (gid_t)-1 && sam->group_rid == 0xffffffff)
>  	{
> -		struct passwd *pass = getpwnam(unix_name);
> +		struct passwd *pass = hashed_getpwnam(unix_name);
>  		if (pass != NULL)
>  		{
>  			sam->unix_gid = pass->pw_gid;
> diff -x CVS -ru samba/source/smbd/password.c samba.patched/source/smbd/password.c
> --- samba/source/smbd/password.c	Thu Mar 25 15:54:31 1999
> +++ samba.patched/source/smbd/password.c	Thu May  6 19:15:13 1999
> @@ -224,7 +224,7 @@
>    DEBUG(3, ("Clearing default real name\n"));
>    fstrcpy(vuser->real_name, "<Full Name>\0");
>    if (lp_unix_realname()) {
> -    if ((pwfile=getpwnam(vuser->name))!= NULL)
> +    if ((pwfile=hashed_getpwnam(vuser->name))!= NULL)
>        {
>        DEBUG(3, ("User name: %s\tReal name: %s\n",vuser->name,pwfile->pw_gecos));
>        fstrcpy(vuser->real_name, pwfile->pw_gecos);
> @@ -586,7 +586,7 @@
>  	{
>  	  struct passwd *pwd;
>  	  static fstring tm;
> -	  
> +
>  	  setpwent ();
>  	  while (pwd = getpwent ()) {
>  	    if (*(pwd->pw_passwd) && pwd->pw_gid == gptr->gr_gid) {
> 
> 

<a href="mailto:lkcl at samba.org"   > Luke Kenneth Casson Leighton  </a>
<a href="http://www.cb1.com/~lkcl"> Samba and Network Development </a>
<a href="http://samba.org"        > Samba Web site                </a>

=====================================================================
Luke Kenneth Casson Leighton        |  Direct Dial   : (678) 443-6183
Systems Engineer / ISS XForce Team  |  ISS Front Desk: (678) 443-6000
Internet Security Systems, Inc.     |  ISS Fax       : (678) 443-6477

http://www.iss.net/    *Adaptive Network Security for the Enterprise*
     ISS Connect   -   International User Conference   -  May '99
=====================================================================



More information about the samba-technical mailing list