Validating A User/Password Pair + Getting Groups On Unix

Kanenas kanenas_ at t_comcast_d.t_net
Tue Mar 1 07:35:33 CET 2005


On 28 Feb 2005 20:17:58 EST, Tim Daneliuk <tundra at tundraware.com>
wrote:

[...]
>Given a username and a password (plain text):
>
>   1) Validate that the password is correct for that user *without actually logging in*.
>
The naive solution is to use the 'crypt' module to encrypt the alleged
password, use 'pwd.getpwuid' or 'pwd.getpwnam' to get the user's
encrypted password (assuming the python process has appropriate access
privileges) and compare the two.  This is naive in that:
* 'pwd.getpw*' may not retrieve the encrypted password even though the
current process has appropriate access privileges 
* the password may be for an encryption or authentication scheme other
than that provided by 'crypt'.  
Using the local authentication scheme shouldn't have these
shortcomings.

There may not be a Python module which handles your local
authentication scheme (there's a 'krb5' module for Kerberos
authentication), so you may need to write one.  This could be done by
an extension module in C or C++ which wraps around whatever local
authentication functions are appropriate (e.g. a 'pam' module for PAM,
an 'auth' module for BSD).  You'd only need to wrap the functions
needed for simple pass/fail authentication (e.g. 'auth_userokay'), but
the other functions could easily be added to the extension later if
needed.  

If you're not sure what authentication scheme your system uses, try
`man -s 3 authenticate` or examine "/usr/src/usr.bin/login/login.c".

Whichever approach you use, the process that calls the authentication
functions needs special access privileges so that the functions can
succesfully accept or reject the password.  The man pages for the
authentication functions should have details.  For example, 'getpwnam'
(used by 'auth_userokay' and the 'pwd' module) requires the effective
uid to be 0 (or, on some systems, the user to be in the "_shadow"
group) for it to include the encrypted password in the returned passwd
entry.

'crypt' and 'pwd' modules:
http://www.python.org/doc/2.4/lib/module-crypt.html
http://www.python.org/doc/2.4/lib/module-pwd.html

extending Python:
http://www.python.org/doc/2.4/ext/ext.html

Python/C API:
http://www.python.org/doc/2.4/api/api.html

Information on Linux-PAM
http://www.kernel.org/pub/linux/libs/pam/

You could even add support for the full authentication API to your
module and contribute the extension to the Python community.
http://www.python.org/download/Contributed.html.


>   2) If the password is valid, return a list of all the groups the user belongs to.
>      Otherwise, return some error string.
>
[...]
>I can do 2) by brute force - just parse through /etc/group - but this
>misses the primary group a given user may belong to - and that requires
>also scanning /etc/passwd and then looking up the corresponding primary
>group in /etc/group.  Is there a better way?
>
Slightly better would be to use the 'grp' and 'pwd' modules.  One
advantage of this is it should support networked user databases (such
as YP).
http://www.python.org/doc/2.4/lib/module-grp.html
http://www.python.org/doc/2.4/lib/module-pwd.html

If you've grabbed the password entry for a user during authentication,
you've already got the login group but you'll still need to check for
additional groups.  You could create a dictionary which maps user
names or IDs to groups.  This would still require processing all
groups (via 'grp.getpwall()'), but is more efficient if you need to
fetch the groups of more than one user in the life of the process
(from the outline, I'm guessing this will only be the case if the
program is a server of some sort).  Just make sure you have a method
to re-process the group database into the group dictionary in case the
group file changes.

Even better would be to write an extension or add to the grp module to
wrap around local group database access functions (e.g. getgrouplist).
See the 'getgrouplist' man page for more information and examine the
source of the `groups` command (probably
"/usr/src/usr.bin/groups/groups.c") or `id` command (should be
"/usr/src/usr.bin/id/id.c") for other group DB access functions.

You could also call the `groups` command via 'os.popen(...)'.




More information about the Python-list mailing list