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