« Return to Thread: Question about getspnam/getpwnam and ldap

Re: Question about getspnam/getpwnam and ldap

by Markus Moeller :: Rate this Message:

Reply to Author | View in Thread


----- Original Message -----
From: "Buchan Milne" <bgmilne@...>
To: "Markus Moeller" <huaraz@...>
Cc: <nssldap@...>; <ldap-nis@...>
Sent: Wednesday, January 09, 2008 4:24 PM
Subject: Re: [nssldap] Question about getspnam/getpwnam and ldap


> On Wednesday 09 January 2008 15:03:07 Markus Moeller wrote:
>> Hi,
>>
>>  I am looking into replacing NIS with LDAP and came to a point where I
>> still need to support getspnam/getpwnam for password checks as I have
>> legacy applications which can not be changed.
>>
>> The question I have is how rfc 2307 is and should be enforced as I
>> noticed
>> differences between Linux and Solaris.  The rfc says:
>>
>>    userPassword values which do not adhere to this syntax MUST NOT be
>>    used for authentication. The DUA MUST iterate through the values of
>>    the attribute until a value matching the above syntax is found. Only
>>    if encryptedpassword is an empty string does the user have no
>>    password. DUAs are not required to consider encryption schemes which
>>    the client will not recognize; in most cases, it may be sufficient to
>>    consider only "crypt".
>>
>>
>> Firstly does/should getspnam enforce "userPassword values which do not
>> adhere to this syntax MUST NOT be used for authentication" by rejecting
>> userpassword entries which don't follow rfc 2307 or is it up to the
>> application since the rfc states "DUAs are not required to consider
>> encryption schemes which  the client will not recognize; in most cases,
>> it
>> may be sufficient to  consider only "crypt"." too (assuming getspnam in
>> nss_ldap is the DUA) ?  Solaris seems to enforce it in getspnam whereas
>> Linux doesn't.
>
> Can you be more specific ? Do you mean you are using something besides
> clear
> (no encryption scheme identifier) or crypt, and seeing the password hash ?
>

The unixuserpassword attribute in AD does not use an encryption scheme
identifier (but stores the crypt password). It has the same value as a
"normal" /etc/shadow entry would have in the password field (e.g.
1c8n1k6cGDbSo).

A getent shadow markus   (with markus being an ldap user in AD) gives on
Linux:
markus:1c8n1k6cGDbSo:13565::::::

and the following checkpass succeeds:

#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <shadow.h>
#include <crypt.h>
#include <string.h>

int main (int argc,char **argv)
{
    struct passwd *pwd_info;
    struct spwd *spwd_info;
    char *ct_passwd;
    char *enc_passwd;
    char *password;
    int i;

    if ( argc < 2) {
            exit(1);
    } else {
            if ((pwd_info = getpwnam(argv[1])) == NULL )  {
                  fprintf (stderr, "Call to getpwuid failed\n");
                  exit (1);
            }
    }

    for (i=0;i<3;i++) {
        ct_passwd = getpass("Enter password: ");

        if ((spwd_info = getspnam (pwd_info -> pw_name)) == NULL) {
            fprintf (stderr, "Call to getspnam failed. Try password field
from getpwnam\n");
            password=pwd_info->pw_passwd;
        } else {
            password=spwd_info->sp_pwdp;
        }
        enc_passwd = crypt (ct_passwd, password);

        if (strcmp (enc_passwd, password) == 0) {
            printf ("Passwords match.\n");
            break;
        } else
            printf ("Passwords don't match.\n");
    }

    return (0);
}

A getent shadow does not work on OpenSolaris. But the below test program
(using OpenSolaris native nss_ldap libraries) gives:
./test_shadow markus
passwd: name=markus
passwd: passwd=x
passwd: uid=500
passwd: gid=10000
passwd: gecos=Markus Moeller
passwd: homedir=/export/home/markus
passwd: shell=/bin/ksh


shadow: name=markus
shadow: passwd=*NP*
shadow: lastchg=-1(Wed Dec 31 01:00:00 1969)
shadow: min=-1
shadow: max=-1
shadow: warn=-1
shadow: inactive=-1
shadow: expire=-1(Wed Dec 31 01:00:00 1969)

If I modify the unixuserpassword value in AD from 1c8n1k6cGDbSo to
{crypt}1c8n1k6cGDbSo OpenSolaris gives:
./test_shadow markus
passwd: name=markus
passwd: passwd=x
passwd: uid=500
passwd: gid=10000
passwd: gecos=Markus Moeller
passwd: homedir=/export/home/markus
passwd: shell=/bin/ksh


shadow: name=markus
shadow: passwd=1c8n1k6cGDbSo
shadow: lastchg=-1(Wed Dec 31 01:00:00 1969)
shadow: min=-1
shadow: max=-1
shadow: warn=-1
shadow: inactive=-1
shadow: expire=-1(Wed Dec 31 01:00:00 1969)

and the checkpass succeeds too.

test_shadow.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <pwd.h>
#include <shadow.h>

int main(int argc, char ** argv) {
    char *user=NULL;
    char *date=NULL,*dp;
    time_t time;
    struct passwd *pw=NULL;
    struct spwd *spw=NULL;

    if ( argc > 1 ) {
        user = argv[1];
    } else {
        exit(99);
    }

    pw = getpwnam(user);
    spw = getspnam(user);

    if ( pw ) {
        printf("passwd: name=%s\n",pw->pw_name);
        printf("passwd: passwd=%s\n",pw->pw_passwd);
        printf("passwd: uid=%d\n",pw->pw_uid);
        printf("passwd: gid=%d\n",pw->pw_gid);
        printf("passwd: gecos=%s\n",pw->pw_gecos);
        printf("passwd: homedir=%s\n",pw->pw_dir);
        printf("passwd: shell=%s\n",pw->pw_shell);
        printf("\n\n");
    }
    if ( spw ) {
        printf("shadow: name=%s\n",spw->sp_namp);
        printf("shadow: passwd=%s\n",spw->sp_pwdp);
        time=spw->sp_lstchg*3600*24;
        date=ctime(&time);
        if ((dp=strchr(date,'\n')) != NULL ) {
            *dp = '\0';
        }
        printf("shadow: lastchg=%ld(%s)\n",spw->sp_lstchg,date);
        printf("shadow: min=%d\n",spw->sp_min);
        printf("shadow: max=%d\n",spw->sp_max);
        printf("shadow: warn=%d\n",spw->sp_warn);
        printf("shadow: inactive=%d\n",spw->sp_inact);
        time=spw->sp_expire*3600*24;
        date=ctime(&time);
        if ((dp=strchr(date,'\n')) != NULL ) {
            *dp = '\0';
        }
        printf("shadow: expire=%d(%s)\n",spw->sp_expire,date);
    }

}

My problem is now that AD does not have the {crypt} prefix and Solaris
requires it and I wanted to understand if the RFC is the reason for
requiring the {crypt} prefix. From how I read it is optional.

At the end it may mean I have to use AD as a NIS server and not as an ldap
server.

> I tested with just {ssha}, and 'getent shadow bgmilne' did not show a
> hash:
>
> [root@tiger ~]# getent shadow bgmilne
> bgmilne:*:12920::99999:7:::0
>
> I added a {crypt} password (so I have both {ssha} and {crypt}), and
> then 'getent shadow bgmilne' showed just the {crypt} hash in place of the
> *.
> I did not test clear text passwords (with no identifier).
>
>> Also Windows 2003 R2 uses unixuserpassword (this entry can
>> be synchronised with the Kerberos password in AD and is therefore
>> preferred) which does not follow rfc2307 as it contains the hash without
>> {crypt}.
>
> Maybe they expect you to type the hash :-P.
>
>> The rfc states also:
>>
>>   A DUA MAY utilise the attributes in the shadowAccount class to
>>    provide shadow password service (getspnam() and getspent()). In such
>>    cases, the DUA MUST NOT make use of the userPassword attribute for
>>    getpwnam() et al, and MUST return a non-matchable password (such as
>>    "x") to the client instead.
>>
>> I noticed on Linux that provides the user with both the userpassword in
>> getpwnam and in getspnam.
>
> I tested as follows:
>
> $ perl -e 'my @foo = getpwnam("bgmilne");print "$foo[1]\n";'
>
> and I get the password hash if the proxyuser (binddn in nss_ldap's
> ldap.conf)
> has read access to userPassword (which usually isn't required or
> desirable).
> Otherwise, I get:
>
> [bgmilne@tiger ~]$ perl -e 'my @foo = getpwnam("bgmilne");print
> "$foo[1]\n";'
> *
>
> If under these conditions, the rootbinddn has read access to userPassword,
> root will still get the hash via getpwnam. I haven't tested with getspnam,
> as
> there is no perl builtin ...
>
>> Is that against RFC 2307 ? Does this mean a
>> getpwnam call need internally do a getspnam call to check if a password
>> is
>> provided before returning to the application ?
>
> Please provide more information on how you arrive at your conclusions
> (including your configuration - at least what the DNs you use for binddn
> and
> rootbinddn have read access to).
>
> In the typical configuration, where the proxy user does not have read
> access
> to userPassword, it seems nss_ldap under Linux behaves (mostly) correctly.
>

In my case the proxy user has access to the userpassword attribute
(unixuserpassword in AD) and  my test_shadow test would give:
./test_shadow markus
passwd: name=markus
passwd: passwd=1c8n1k6cGDbSo
passwd: uid=500
passwd: gid=10000
passwd: gecos=Markus Moeller
passwd: homedir=/export/home/markus
passwd: shell=/bin/ksh


shadow: name=markus
shadow: passwd=1c8n1k6cGDbSo
shadow: lastchg=-1(Wed Dec 31 01:00:00 1969)
shadow: min=-1
shadow: max=-1
shadow: warn=-1
shadow: inactive=-1
shadow: expire=-1(Wed Dec 31 01:00:00 1969)

and if I understand the rfc right getpwnam should return x and not the
password.


> Regards,
> Buchan
>

Regards
Markus

 « Return to Thread: Question about getspnam/getpwnam and ldap