#!/usr/bin/env python
#
# userdb.py:		OO-user database with authentication
#
# Author:		Christopher Arndt <chris.arndt@web.de>
# Version:		1.1
# Date:			26.04.2002
# Copyright:		LGPL
"""OO-user database using plain text password files.
"""

__all__ = ['UserDB']

__author__  = "Christopher Arndt <chris.arndt@web.de>"
__version__ = "1.0b"

import fileinput
import authlib
from getpass import getpass

class UserDB:
    """Represents a user database and provides authentication methods.
    """
    
    def __init__(self, dbname=None, crypt_type='des', dbtype='flat'):
        if not dbname:
            if dbtype == 'unix':
                self.dbname = '/etc/passwd'
            else:
                raise TypeError, \
                  "Please specify a database filename or set dbtype='unix'"
        else:
            self.dbname = dbname
        if self.dbname == '/etc/passwd' or self.dbname == '/etc/shadow':
            self.dbtype = 'unix'
        else:
            self.dbtype = dbtype
        self.crypt_type = crypt_type
        self.user_prompt = 'Login: '
        self.pass_prompt = 'Password: '
    

    def add_user(self, user, passwd=None):
        """Add a new user to the database.
        """

        try:
            self.get_user(user)
            raise ValueError, "User '%s' already exists." % user
        except KeyError:
            pass
        
        if self.dbtype != 'flat':
            raise NotImplementedError, \
              "Can't add a new user to database of type '%s'." % self.dbtype
        if passwd == None:
            passwd = '!!'
        elif passwd != '':
            passwd = passcrypt(passwd, method=self.crypt_type)
        
        if self.dbtype == 'flat':
            f = open(self.dbname, 'r+b')
            try:
                f.seek(-1,2)
            except IOError:
                pass
            if f.tell() != 0 and f.read(1) != '\n': f.write('\n')
            f.write('%s:%s\n' % (user, passwd))
            f.close()
                

    def del_user(self, user):
        """Delete a user from the database.
        """

        try:
            self.get_user(user)
        except KeyError:
            raise ValueError, "User '%s' does not exist." % user
        
        if self.dbtype not in ['flat', 'unix']:
            raise NotImplementedError, \
              "Can't delete a user from database of type '%s'." % self.dbtype
        
        if self.dbtype == 'flat':
            for line in fileinput.input(self.dbname, inplace=1):
                if line:
                    if not line.strip().split(':', 1)[0] == user:
                        print line,
        

    def set_passwd(self, user, passwd=None):
        """Change the passwd of a user.

        Setting password to an empty string deletes the password,
        setting it to None disables login.
        """
        
        try:
            self.get_user(user)
        except KeyError:
            raise ValueError, "User '%s' does not exist." % user
        
        if self.dbtype != 'flat':
            raise NotImplementedError, \
              "Can't change password in database of type '%s'." % self.dbtype
        
        if passwd == None:
            passwd = '!!'
        elif passwd != '':
            passwd = passcrypt(passwd, method=self.crypt_type)
        if self.dbtype == 'flat':
            for line in fileinput.input(self.dbname, inplace=1):
                if line:
                    entry = line.strip().split(':', 1)
                    if entry[0] == user:
                        print "%s:%s\n" % (entry[0], passwd),
                    else:
                        print line,
        

    def get_passwd(self, user):
        """Return encrypted password for the given user.
        """

        return self.get_user()[1]

    def get_user(self, user):
        """Return database entry for the given user name.
        
        Returns a tuple of at most 7 items starting with username and 
        encrypted password.
        """

        return authlib.getpwnam(user, self.dbname)

    def check_passwd(self, user, passwd):
        """Validate given user, passwd pair against database.
        """

        return authlib.check_passwd(user, passwd, self.dbname)

    def login(self, user=None, user_prompt=None, pass_prompt=None, 
      max_tries=3):
        """Generate a login screen and validate the login.
        
        Returns user name or None on failure.
        """

        return authlib.login(user, self, user_prompt, pass_prompt, max_tries)


if __name__ == '__main__':
    import os
    db = UserDB(dbtype='unix')
    if os.path.exists('/etc/shadow'):
        if os.getuid() != 0:
            print "Your system uses shadow password files!"
            print "Login will probably fail because you are not root."
        db.dbname = '/etc/shadow'
    if db.login(max_tries=1):
        print "Access granted"
    else:
        print "Access denied"