summaryrefslogtreecommitdiffstats
path: root/userconfig/unixauthdb.py
diff options
context:
space:
mode:
Diffstat (limited to 'userconfig/unixauthdb.py')
-rwxr-xr-xuserconfig/unixauthdb.py1154
1 files changed, 1154 insertions, 0 deletions
diff --git a/userconfig/unixauthdb.py b/userconfig/unixauthdb.py
new file mode 100755
index 0000000..92ffcc8
--- /dev/null
+++ b/userconfig/unixauthdb.py
@@ -0,0 +1,1154 @@
+#!/usr/bin/python
+###########################################################################
+# Copyright (C) 2004-2006 by Simon Edwards
+# <simon@simonzone.com>
+#
+# Copyright: See COPYING file that comes with this distribution
+#
+###########################################################################
+# An API for querying and modifying the authorisation database on Unix systems.
+#
+# The first function that you need to use is getContext(). It returns a
+# Context object that contains all relevant information concerning
+# the current authorisation database on this machine.
+
+import crypt
+import random
+import fcntl
+import time
+import os
+import os.path
+import stat
+import shutil
+import codecs
+import locale
+import tempfile
+
+ldaperror = ""
+try:
+ import ldap
+except ImportError:
+ ldaperror = "The LDAP Python Module is not installed, but needed to use LDAP. Install it."
+
+def createTempFile(origfile):
+ origstat = os.stat(origfile)
+ tmp_prefix = os.path.basename(origfile) + "."
+ tmp_dir = os.path.dirname(origfile)
+ try:
+ ret = tempfile.mkstemp(prefix=tmp_prefix, dir=tmp_dir)
+ except:
+ raise IOError, "Unable to create a new temporary file for " + origfile
+ (fd, tmpfile) = ret
+ shutil.copymode(origfile, tmpfile)
+ os.chown(tmpfile, origstat.st_uid, origstat.st_gid)
+
+ return ret
+
+def getContext(editmode=False):
+ """Get a Context object describing the system's authorisation database.
+
+ Parameters:
+
+ editmode - Set to true if you also wish change the information in this
+ context. Root access is required. Defaults to false.
+
+ Returns a Context object.
+
+ If the environmental variable "USERCONFIG_USES_LDAP" is set to "true",
+ userconfig will use LDAP as the backend. This feature is in development
+ and using it is not recommended, it won't work.
+ """
+
+ # Detect what kind of auth system we are running on and create
+ # and initialise the corresponding Context object type.
+
+ # Check for Mandrake
+
+ # Check libuser.conf
+ try:
+ if os.environ["USERCONFIG_USES_LDAP"].lower() == "true":
+ use_ldap = True
+ except KeyError,e:
+ use_ldap = False
+ if not use_ldap:
+ return PwdContext(editmode)
+ else:
+ print "==================================================================="
+ print "Warning:"
+ print "\tYou are using LDAP as backend. This feature is under development"
+ print "\tand it is currently not recommended to use it."
+ print "\tIf you do not want to use LDAP as backend, set the environmental"
+ print "\tvariabale 'USERCONFIG_USES_LDAP' to 'False'."
+ print "==================================================================="
+ return LdapContext(editmode)
+
+###########################################################################
+# Base classes.
+#
+class Context(object):
+ """Contains all of the information about the current authorisation
+ database, plus some useful methods for modify this information.
+
+ """
+ def __init__(self):
+ self._users = []
+ self._groups = []
+ self._shells = None
+ self._setDefaultValues()
+
+ def newUser(self,defaults=False,systemuser=False):
+ """Create a new UnixUser object.
+
+ Creates a new blank UnixUser object. The object is not part of the
+ current Context. You need to add it yourself using addUser().
+
+ Newly allocated UIDs are unique with respect to the list of UnixUser
+ objects in the Context.
+
+ Keyword arguments:
+ defaults -- Set to true if the new object should be filled in with
+ reasonable default values for the UID and username.
+ (default False)
+ systemuser -- Should the new user be allocated a UID from the system
+ range of UIDs. (default is False)
+
+ Returns a new UnixUser object.
+ """
+ newuserobj = self._createUser()
+ if defaults:
+ if systemuser:
+ r = xrange(0,self.last_system_uid)
+ else:
+ r = xrange(self.first_uid,self.last_uid)
+ for candiate in r:
+ for u in self._users:
+ if u.getUID()==candiate:
+ break
+ else:
+ newuserobj.setUID(candiate)
+ break
+
+ if self.lookupUsername(u'new_user') is None:
+ newuserobj.setUsername(u'new_user')
+ else:
+ i = 1
+ while 1:
+ if self.lookupUsername(u'new_user_'+str(i)) is None:
+ newuserobj.setUsername(u'new_user_'+str(i))
+ break
+ i += 1
+ return newuserobj
+
+ def getUsers(self):
+ """Get a list of all existing users.
+
+ Returns an array of UnixUser objects.
+ """
+ #print "USERS:", self._users
+ return self._users[:]
+
+ def getGroups(self):
+ """Get a list of all existing groups.
+
+ Returns an array of UnixGroup objects.
+ """
+ try:
+ self._groups.remove("new_user")
+ except ValueError:
+ print "no user removed"
+ pass
+ return self._groups[:]
+
+ def newGroup(self,defaults=False,systemgroup=False):
+ """Create a new UnixGroup object.
+
+ Creates a new blank UnixGroup object. The object is not part of the
+ current Context. You need to add it yourself using addGroup().
+
+ Newly allocated GIDs are unique with respect to the list of UnixGroup
+ objects in the Context.
+
+ Keyword arguments:
+ defaults -- Set to true if the new object should be filled in with
+ reasonable default values for the GID and groupname.
+ (default False)
+ systemgroup -- Set to True if the newly allocated GID should come
+ from the pool of system group IDs. (default False)
+
+ Returns a new UnixGroup object.
+ """
+ newgroupobj = self._createGroup()
+ if defaults:
+ if systemgroup:
+ r = xrange(0,self.last_system_gid)
+ else:
+ r = xrange(self.first_gid,self.last_gid)
+ for candiate in r:
+ for u in self._groups:
+ if u.getGID()==candiate:
+ break
+ else:
+ newgroupobj.setGID(candiate)
+ break
+ if self.lookupGroupname(u'new_group') is None:
+ newgroupobj.setGroupname(u'new_group')
+ else:
+ i = 1
+ while 1:
+ if self.lookupGroupname(u'new_user_'+str(i)) is None:
+ newgroupobj.setGroupname(u'new_user_'+str(i))
+ break
+ i += 1
+ return newgroupobj
+
+ def _createGroup(self):
+ raise NotImplementedError, "Context.newGroup()"
+
+ def addUser(self,userobj):
+ """Adds the given user to the authorisation database.
+
+ This change only takes effect after calling context.save().
+
+ Keyword arguments:
+ userobj -- The UnixUser object to add.
+ """
+ self._users.append(userobj)
+
+ def addGroup(self,groupobj):
+ """Adds the given group to the authorisation database.
+
+ This change only takes effect after calling context.save().
+
+ Keyword arguments:
+ groupobj -- The UnixGroup object to add.
+ """
+ if groupobj not in self._groups:
+ self._groups.append(groupobj)
+
+ def removeUser(self,userobj):
+ """Removes the given user object from the authorisation database.
+
+ The user is also removed from all groups.
+
+ This change only takes effect after calling context.save().
+ """
+ for g in userobj.getGroups():
+ userobj.removeFromGroup(g)
+
+ self._users.remove(userobj)
+
+ def removeGroup(self,groupobj):
+ """Removes the given group object from the authorisation database.
+
+ All users are removed from the group.
+
+ This change only takes effect after calling context.save().
+ """
+ for u in groupobj.getUsers():
+ u.removeFromGroup(groupobj)
+
+ self._groups.remove(groupobj)
+
+ def lookupUID(self,uid):
+ """Lookup a UnixUser object by its numeric user ID.
+
+ Keyword arguments:
+ uid -- User ID to lookup, integer.
+
+ Returns the matching UnixUser object or None if it was not found.
+ """
+ for user in self._users:
+ if user.getUID()==uid:
+ return user
+ return None
+
+ def lookupUsername(self,username):
+ """Lookup a UnixUser object by username.
+
+ Keyword arguments:
+ username -- Username to lookup, string.
+
+ Returns the matching UnixUser object or None if it was not found.
+ """
+ for user in self._users:
+ if user.getUsername()==username:
+ return user
+ return None
+
+ def lookupGID(self,gid):
+ """Lookup a UnixGroup object by its numeric group ID.
+
+ Keyword arguments:
+ gid -- Group ID to lookup, integer.
+
+ Returns the matching UnixGroup object or None if it was not found.
+ """
+ for group in self._groups:
+ if group.getGID()==gid:
+ return group
+ return None
+
+ def lookupGroupname(self,groupname):
+ """Lookup a UnixGroup object by groupname.
+
+ Returns the matching UnixGroup object or None if it was not found.
+ """
+ for group in self._groups:
+ if group.getGroupname()==groupname:
+ return group
+ return None
+
+ def getUserShells(self):
+ """Get the list of available login shells.
+
+ Returns an array of strings.
+ """
+ if self._shells is None:
+ self._shells = []
+ fhandle = codecs.open('/etc/shells','r',locale.getpreferredencoding())
+ for l in fhandle.readlines():
+ # TODO: strangely this lets some comented lines slip through
+ if len(l.strip()) > 1 and l.strip()[0] is not "#":
+ # Only show existing shells
+ if os.path.isfile(l.strip()):
+ self._shells.append(l.strip())
+ fhandle.close()
+ return self._shells[:]
+
+ def save(self):
+ """Synchronises the Context with the underlying operating system.
+
+ After a successful save, any changes to the Context will be reflected
+ system wide.
+ """
+ raise NotImplementedError, "Context.save()"
+
+ def createHomeDirectory(self,userobj):
+ if os.path.exists(userobj.getHomeDirectory()):
+ raise IOError, u"Home directory %s already exists." % userobj.getHomeDirectory()
+
+ # Copy the skeleton directory over
+ shutil.copytree(self._getSkeletonDirectory(),userobj.getHomeDirectory(),True)
+
+ # Fix the file ownership stuff
+ uid = userobj.getUID()
+ gid = userobj.getPrimaryGroup().getGID()
+ os.chmod(userobj.getHomeDirectory(),self.dir_mode)
+ #os.system("chmod "+self.dir_mode+" "+userobj.getHomeDirectory())
+ #print "Setting permissions:", userobj.getHomeDirectory(),self.dir_mode
+ os.lchown(userobj.getHomeDirectory(),uid,gid)
+ for root,dirs,files in os.walk(userobj.getHomeDirectory()):
+ for d in dirs:
+ os.lchown(os.path.join(root,d),uid,gid)
+ for f in files:
+ os.lchown(os.path.join(root,f),uid,gid)
+
+ def removeHomeDirectory(self,userobj):
+ if os.path.exists(userobj.getHomeDirectory()):
+ shutil.rmtree(userobj.getHomeDirectory())
+
+ def _createUser(self):
+ raise NotImplementedError, "Context._createUser()"
+
+ def _sanityCheck(self):
+ userids = []
+ for u in self._users:
+ if isinstance(u,UnixUser)==False:
+ raise TypeError,"Found an object in the list of users that is not a UnixUser object."
+ uid = u.getUID()
+ if uid in userids:
+ raise ValueError, "User ID %i appears more than once." % uid
+ userids.append(uid)
+ u._sanityCheck()
+
+ groupids = []
+ for g in self._groups:
+ if isinstance(g,UnixGroup)==False:
+ raise TypeError,"Found an object in the list of groups that is not a UnixGroup object."
+ gid = g.getGID()
+ if gid in groupids:
+ raise ValueError, "Group ID %i appears more than once." % gid
+ groupids.append(gid)
+ g._sanityCheck()
+
+ def _getSkeletonDirectory(self):
+ return self.skel
+
+ def _readAdduserConf(self):
+ """ Fill a dictionary with the values from /etc/adduser.conf
+ which then can be used as default values, if the file exists
+ at least.
+ Attention: We're not validating!"""
+ self.defaults = {}
+ self.adduserconf = '/etc/adduser.conf'
+ if not os.path.isfile(self.adduserconf):
+ return
+ fhandle = codecs.open(self.adduserconf,'r',locale.getpreferredencoding())
+ for line in fhandle.readlines():
+ line = line.strip()
+ parts = line.split("=")
+ if len(parts) == 2:
+ self.defaults[str(parts[0].strip())] = parts[1].strip()
+
+ def _setDefaultValues(self):
+ """ Set a lot of default values for UIDs and GIDs, try to use the values
+ from /etc/adduser.conf."""
+ self._readAdduserConf()
+
+ try:
+ self.skel = self.defaults["SKEL"]
+ except KeyError:
+ self.skel = '/etc/skel'
+
+ # IDs for new users and groups.
+ try:
+ self.first_uid = int(self.defaults['FIRST_UID'])
+ except (KeyError,ValueError):
+ self.first_uid = 1000
+
+ try:
+ self.last_uid = int(self.defaults["LAST_UID"])
+ except (KeyError,ValueError):
+ self.last_uid = 29999
+
+ try:
+ self.first_gid = int(self.defaults["FIRST_GID"])
+ except (KeyError,ValueError):
+ self.first_gid = 1000
+
+ try:
+ self.last_gid = int(self.defaults["LAST_GID"])
+ except (KeyError,ValueError):
+ self.last_gid = 65534
+
+ # Which IDs are system user and system groups?
+ try:
+ self.first_system_uid = int(self.defaults["FIRST_SYSTEM_UID"])
+ except (KeyError,ValueError):
+ self.first_system_uid = 500
+
+ try:
+ self.last_system_uid = int(self.defaults["LAST_SYSTEM_UID"])
+ except (KeyError,ValueError):
+ self.last_system_uid = 65534
+
+ try:
+ self.first_system_gid = int(self.defaults["FIRST_SYSTEM_GID"])
+ except (KeyError,ValueError):
+ self.first_system_gid = 500
+
+ try:
+ self.last_system_gid = int(self.defaults["LAST_SYSTEM_GID"])
+ except (KeyError,ValueError):
+ self.last_system_gid = 65534
+
+ # More defaults which might make sense.
+ try:
+ self.dir_mode = int(self.defaults["DIR_MODE"],8)
+ except (KeyError,ValueError):
+ self.dir_mode = int("0755",8)
+ print "Didn't read default DIR_MODE"
+
+ try:
+ self.dhome = self.defaults["DHOME"]
+ except KeyError:
+ self.dhome = "/home"
+
+ try:
+ self.dshell = self.defaults["DSHELL"]
+ except KeyError:
+ # Will be set in showNewUser()
+ self.dshell = None
+
+###########################################################################
+class UnixUser(object):
+ def __init__(self,context):
+ self._context = context
+ self._uid = None
+ self._username = None
+
+ # UnixGroup object.
+ self._primarygroup = None
+
+ # List of UnixGroup objects.
+ self._groups = []
+
+ self._gecos = None
+ self._homedirectory = None
+ self._loginshell = None
+
+ self._islocked = False
+
+ self._encpass = ""
+
+ # FIXME : This should actually be days since epoch or something like this
+ self._passlastchange = 0
+ self._passminimumagebeforechange = 0
+ self._passmaximumage = None
+ self._passexpirewarn = 7
+ self._passexpiredisabledays = None
+ self._disableddays = None
+
+ def polish(self):
+ primary_group = self._context.lookupGID(self._gid)
+ if primary_group is None:
+ # The GID didn't match an existing group. Quickly make a new group.
+ new_group = self._context.newGroup()
+ new_group.setGID(self._gid)
+
+ new_group_name = u"group%i" % self._gid
+ i = 0
+ while self._context.lookupGroupname(new_group_name) is not None:
+ i += 1
+ new_group_name = u"group%i_%i" % (self._gid,i)
+ new_group.setGroupname(new_group_name)
+
+ self._context.addGroup(new_group)
+ primary_group = new_group
+
+ self.setPrimaryGroup(primary_group)
+ for group in self._context._groups:
+ if group.contains(self):
+ self._groups.append(group)
+
+ def getUID(self):
+ """Get the unix user ID.
+
+ Returns the integer.
+ """
+ return self._uid
+
+ def setUID(self,uid):
+ """Set the unix user ID.
+
+ Keyword arguments:
+ uid -- Integer user id.
+ """
+ uid = int(uid)
+ if uid<0:
+ raise ValueError, "User ID (%i) is a negative number." % uid
+ self._uid = uid
+
+ def isSystemUser(self):
+ """See if this user is a system user.
+
+ Returns True or False.
+ """
+ return not (self._context.first_uid <= self._uid < self._context.last_uid)
+
+ def getUsername(self): return self._username
+
+ def setUsername(self,username): self._username = username
+
+ def getPrimaryGroup(self):
+ """Get the primary group for this user.
+
+ Returns a UnixGroup object.
+ """
+ return self._primarygroup
+
+ def setPrimaryGroup(self,groupobj):
+ """Set the primary group for this user.
+
+ If the given group is not part of this user's list of groups, then
+ it will be added.
+
+ Keyword arguments:
+ groupobj -- The group to set as the primary group.
+ """
+ self.addToGroup(groupobj)
+ self._primarygroup = groupobj
+
+ def getGroups(self):
+ """Get the list of groups that this user belongs to.
+
+ The user's primary group is also included in the returned list.
+
+ Returns a list of UnixGroup objects. Modify the list does not affect
+ this UnixUser object.
+ """
+ return self._groups[:]
+
+ def addToGroup(self,groupobj):
+ """Add this user to the given group.
+
+ Keyword arguments:
+ groupobj -- UnixGroup object.
+ """
+ groupobj._addUser(self)
+ if groupobj not in self._groups:
+ self._groups.append(groupobj)
+
+ def removeFromGroup(self,groupobj):
+ """Remove this user from the given group.
+
+ If group is current this user's primary group, then
+
+ Keyword arguments:
+ groupobj -- UnixGroup object.
+ """
+ groupobj._removeUser(self)
+ try:
+ self._groups.remove(groupobj)
+ except ValueError:
+ pass
+ if self._primarygroup is groupobj:
+ if len(self._groups)==0:
+ self._primarygroup = None
+ else:
+ self._primarygroup = self._groups[0]
+
+ def setPassword(self,password):
+ # Make some salt.
+ space = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQSRTUVWXYZ0123456789./'
+ salt = ""
+ for x in range(8):
+ salt += space[random.randint(0,len(space)-1)]
+ self._encpass = crypt.crypt(password,'$1$'+salt+'$')
+
+ def isLocked(self): return self._islocked
+ def setLocked(self,locked): self._islocked = locked
+
+ def getRealName(self):
+ if not self._gecos:
+ return ""
+ try:
+ return self._gecos.split(",")[0]
+ except AttributeError:
+ return self._gecos
+
+ def setRealName(self,realname): self._gecos = realname
+ def getHomeDirectory(self): return self._homedirectory
+ def setHomeDirectory(self,homedirectory): self._homedirectory = homedirectory
+ def getLoginShell(self): return self._loginshell
+ def setLoginShell(self,loginshell): self._loginshell = loginshell
+
+ # 'None' means that there is no maximum password age.
+ def getMaximumPasswordAge(self): return self._passmaximumage
+ def setMaximumPasswordAge(self,days): self._passmaximumage = days
+
+ def getMinimumPasswordAgeBeforeChange(self): return self._passminimumagebeforechange
+ def setMinimumPasswordAgeBeforeChange(self,days): self._passminimumagebeforechange = days
+ def getPasswordDisableAfterExpire(self): return self._passexpiredisabledays
+ def setPasswordDisableAfterExpire(self,days): self._passexpiredisabledays = days
+ def getPasswordExpireWarning(self): return self._passexpirewarn
+ def setPasswordExpireWarning(self,days): self._passexpirewarn = days
+ def getLastPasswordChange(self): return self._passlastchange
+ def getExpirationDate(self): return self._disableddays
+ def setExpirationDate(self,unixdate): self._disableddays = unixdate
+
+ def __str__(self):
+ return "%s(%i)" % (self._username,self._uid)
+
+ def _sanityCheck(self):
+ if self._primarygroup is None:
+ raise ValueError,"Userobj has no primary group!"
+ if self._uid is None:
+ raise ValueError,"Userobj has no UID!"
+
+###########################################################################
+class UnixGroup(object):
+ def __init__(self,context):
+ self._context = context
+
+ # List of UnixUser objects.
+ self._members = []
+
+ self._gid = None
+ self._groupname = None
+
+ def contains(self,userobj):
+ """Check if a the given user is a member of this group.
+
+ Returns True or False.
+ """
+ return userobj in self._members
+
+ def polish(self): pass
+ def isSystemGroup(self):
+ """Check if this group is a system group.
+
+ Returns True or False.
+ """
+ return not (self._context.first_gid <= self._gid < self._context.last_gid)
+ #return not (500 <= self._gid < 65534)
+
+ def getGID(self):
+ """Get the unix group ID.
+
+ Returns the integer group id.
+ """
+ return self._gid
+
+ def setGID(self,gid):
+ """Set the unix group ID.
+
+ Keyword arguments:
+ gid -- new group id, integer.
+ """
+ self._gid = gid
+
+ def getGroupname(self): return self._groupname
+ def setGroupname(self,groupname): self._groupname = groupname
+ def getUsers(self): return self._members[:]
+ def _addUser(self,userobj):
+ if not self.contains(userobj):
+ self._members.append(userobj)
+
+ def _removeUser(self,userobj):
+ try:
+ self._members.remove(userobj)
+ except ValueError:
+ pass
+
+ def __str__(self):
+ # FIXME encoding
+ return str(self._groupname) + " (" + str(self._gid) + ") " + str([str(u) for u in self._members])
+
+ def _sanityCheck(self):
+ pass
+
+###########################################################################
+class PwdContext(Context):
+ #def __init__(self,editmode,passwordfile="etc-passwd",groupfile='etc-group',shadowfile="etc-shadow"):
+ def __init__(self,editmode,passwordfile="/etc/passwd",groupfile='/etc/group',shadowfile="/etc/shadow"):
+ Context.__init__(self)
+ self.__editmode = editmode
+ self.__passwordfile = passwordfile
+ self.__groupfile = groupfile
+ self.__shadowfile = shadowfile
+ self._setDefaultValues()
+
+ # Read in the password file
+ fhandle = codecs.open(passwordfile,'r',locale.getpreferredencoding())
+ if LockFDRead(fhandle.fileno())==False:
+ raise IOError,"Unable to lock the "+passwordfile+" file."
+ try:
+ for line in fhandle.readlines():
+ if line.strip()!="":
+ newuserobj = self.newUser(False)
+ newuserobj._initString(line)
+ self._users.append(newuserobj)
+ finally:
+ UnlockFD(fhandle.fileno())
+ fhandle.close()
+
+ # Read the group file
+ fhandle = codecs.open(groupfile,'r',locale.getpreferredencoding())
+ if LockFDRead(fhandle.fileno())==False:
+ raise IOError,"Unable to lock the "+groupfile+" file."
+ try:
+ for line in fhandle.readlines():
+ if line.strip()!="":
+ newgroupobj = self.newGroup(False)
+ newgroupobj._initString(line)
+ self._groups.append(newgroupobj)
+ finally:
+ UnlockFD(fhandle.fileno())
+ fhandle.close()
+
+ if self.__editmode:
+ # Load up the info from the shadow file too.
+ fhandle = codecs.open(shadowfile,'r',locale.getpreferredencoding())
+ if LockFDRead(fhandle.fileno())==False:
+ raise IOError,"Unable to lock the "+shadowfile+" file."
+ try:
+ for line in fhandle.readlines():
+ if line.strip()!="":
+ try:
+ (username,encpass,passlastchange,passminimumagebeforechange,passmaximumage, \
+ passexpirewarn,passexpiredisabledays,disableddays,reserve) = \
+ tuple(line.strip().split(":"))
+ userobj = self.lookupUsername(username)
+ if userobj is not None:
+ if encpass=="":
+ encpass = u"*"
+ userobj._encpass = encpass
+ if userobj._encpass[0]=='!':
+ userobj._islocked = True
+ userobj._encpass = userobj._encpass[1:]
+ else:
+ userobj._islocked = False
+ # FIXME : set time
+ if passlastchange and passlastchange!=u"None":
+ userobj._passlastchange = int(passlastchange)
+ else:
+ passlastchange = 0
+
+ if passminimumagebeforechange=="":
+ passminimumagebeforechange = None
+ else:
+ passminimumagebeforechange = int(passminimumagebeforechange)
+ if passminimumagebeforechange>=99999:
+ passminimumagebeforechange = None
+ userobj._passminimumagebeforechange = passminimumagebeforechange
+
+ if passmaximumage=="":
+ passmaximumage = None
+ else:
+ passmaximumage = int(passmaximumage)
+ if passmaximumage>=99999:
+ passmaximumage = None
+ userobj._passmaximumage = passmaximumage
+
+ if passexpirewarn=="":
+ passexpirewarn = None
+ else:
+ passexpirewarn = int(passexpirewarn)
+ if passexpirewarn>=99999:
+ passexpirewarn = None
+ userobj._passexpirewarn = passexpirewarn
+
+ if passexpiredisabledays=="":
+ userobj._passexpiredisabledays = None
+ else:
+ userobj._passexpiredisabledays = int(passexpiredisabledays)
+
+ if disableddays=="" or disableddays==u"99999":
+ userobj._disableddays = None
+ else:
+ userobj._disableddays = int(disableddays)
+
+ userobj._reserve = reserve
+ else:
+ print "Couldn't find",username
+ except ValueError:
+ pass
+ finally:
+ UnlockFD(fhandle.fileno())
+ fhandle.close()
+
+ for group in self._groups:
+ group.polish()
+ for user in self._users:
+ user.polish()
+
+ def _createUser(self):
+ return PwdUser(self)
+
+ def _createGroup(self):
+ return PwdGroup(self)
+
+ def save(self):
+ if self.__editmode==False:
+ raise IOError, "Can't save, the context was created Read only."
+
+ self._sanityCheck()
+
+ # Write out the new password file.
+ (fd, tmpname) = createTempFile(self.__passwordfile)
+ for u in self._users:
+ os.write(fd, u._getPasswdEntry().encode(locale.getpreferredencoding(),'replace'))
+ #print u._getPasswdEntry()
+ os.close(fd)
+
+ # Update the passwd file
+ passwordlock = os.open(self.__passwordfile, os.O_WRONLY) # FIXME encoding
+ if LockFDWrite(passwordlock)==False:
+ raise IOError,"Couldn't get a write lock on "+self.__passwordfile
+ try:
+ os.rename(tmpname, self.__passwordfile)
+ finally:
+ UnlockFD(passwordlock)
+ os.close(passwordlock)
+
+ # Write out the new group file
+ (fd, tmpname) = createTempFile(self.__groupfile)
+ origstat = os.stat(self.__groupfile)
+ for g in self._groups:
+ os.write(fd,g._getGroupFileEntry().encode(locale.getpreferredencoding()))
+ #print g._getGroupFileEntry()[:-1]
+ os.close(fd)
+ os.chown(tmpname, origstat.st_uid, origstat.st_gid)
+
+ # Update the group file.
+ grouplock = os.open(self.__groupfile, os.O_WRONLY)
+ if LockFDWrite(grouplock)==False:
+ raise IOError,"Couldn't get write lock on "+self.__groupfile
+ try:
+ os.rename(tmpname, self.__groupfile)
+ finally:
+ UnlockFD(grouplock)
+ os.close(grouplock)
+
+ # Write out the new shadow file
+ origstat = os.stat(self.__shadowfile)
+ (fd, tmpname) = createTempFile(self.__shadowfile)
+ for u in self._users:
+ os.write(fd,u._getShadowEntry().encode(locale.getpreferredencoding()))
+ #print u._getShadowEntry()[:-1]
+ os.close(fd)
+
+ # Update the shadow file.
+
+ # Make sure that it is writable.
+ if (origstat.st_mode & stat.S_IWUSR)==0:
+ os.chmod(self.__shadowfile,origstat.st_mode|stat.S_IWUSR)
+
+ shadowlock = os.open(self.__shadowfile, os.O_WRONLY)
+ if LockFDWrite(shadowlock)==False:
+ raise IOError,"Couldn't get write lock on "+self.__shadowfile
+ try:
+ os.rename(tmpname, self.__shadowfile)
+ finally:
+ UnlockFD(shadowlock)
+ os.close(shadowlock)
+
+ # set the permissions back to thier default.
+ if (origstat.st_mode & stat.S_IWUSR)==0:
+ os.chmod(self.__shadowfile,origstat.st_mode)
+
+###########################################################################
+class LdapContext(Context):
+
+ def __init__(self,editmode,server="localhost",admin_dn="",admin_pass=""):
+ """ Connect to the LDAP server and invoke further actions.
+ """
+ Context.__init__(self)
+ # admin_dn is DistinguishedName? (or dn, for short)
+ self.server = server
+ self.baseDN = "dc=vizZzion,dc=net"
+
+ self.url = "ldap://"+self.server
+
+ self.ldapserver = ldap.initialize(self.url)
+ self.ldapserver.protocol_version = ldap.VERSION3
+
+ self.editmode = editmode
+ if not self.editmode:
+ self.ldapserver.simple_bind("admin",admin_pass)
+ print "Connected to ", self.url
+
+ self._users = self._getUsers()
+
+ def _getUsers(self):
+ """ Retrieve a list of users from the LDAP server.
+ """
+ _users = []
+ print "LdapContext._getUsers"
+ searchScope = ldap.SCOPE_SUBTREE
+ retrieveAttributes = None
+ searchFilter = "cn=*"
+ try:
+ ldap_result_id = self.ldapserver.search(self.baseDN, searchScope, searchFilter, retrieveAttributes)
+ result_set = []
+ while 1:
+ result_type, result_data = self.ldapserver.result(ldap_result_id, 0)
+ if (result_data == []):
+ break
+ else:
+ if result_type == ldap.RES_SEARCH_ENTRY:
+ #print result_data[0][1]
+ #print " --------------------- "
+ result_set.append(result_data[0][1])
+ #print result_set
+ except ldap.LDAPError, e:
+ print "ERROR: ",e
+
+ if len(result_set) == 0:
+ print "No Results."
+ return
+ count = 0
+ """
+ for entry in result_set:
+ for d in entry.keys():
+ print d, "::", entry[d]
+ print "======== Next User =============="
+ """
+ # Walk through result_set and create users.
+ for entry in result_set:
+ try:
+ name = entry['cn'][0]
+ login = entry['uid'][0]
+ loginshell = entry['loginShell'][0]
+ homedirectory = entry['homeDirectory'][0]
+ uid = entry['uidNumber'][0]
+ gid = entry['gidNumber'][0]
+ count = count + 1
+ #print "\n%d. User: %s\n\tName: %s\n\tShell: %s\n\tHomeDir: %s\n\tUID: %s\n\tGID: %s\n" %\
+ # (count, login, name, loginshell, homedirectory, uid, gid)
+ # Create a new userobject
+ new_user = self._createUser()
+ new_user.setHomeDirectory(homedirectory)
+ new_user.setUID(uid)
+ new_user.setRealName(name)
+ new_user.setLoginShell(loginshell)
+ new_user.setUsername(login)
+ _users.append(new_user)
+ print "Number of Users:", len(self._users)
+
+ except KeyError, e:
+ # Debugging output...
+ print "ERR:: ",e
+ print 'err:: ',entry
+ return _users
+
+ def _createUser(self):
+ return LdapUser(self)
+
+ def _createGroup(self):
+ return LdapGroup(self)
+
+ def save(self):
+ print "LdapContext.save() does nothing yet."
+
+###########################################################################
+class LdapUser(UnixUser):
+
+ def __str__(self):
+ return "LdapUser: %s(%i)" % (self._username,self._uid)
+
+
+###########################################################################
+class LdapGroup(UnixGroup):
+
+ def __str__(self):
+ return "LdapGroup: %s(%i)" % (self._username,self._uid)
+
+
+###########################################################################
+class PwdUser(UnixUser):
+ def __init__(self,context):
+ UnixUser.__init__(self,context)
+ self._reserve = u""
+
+ def _initString(self,line):
+ (self._username,x,self._uid,self._gid,self._gecos,self._homedirectory, \
+ self._loginshell) = tuple(line.strip().split(":"))
+ self._uid = int(self._uid)
+ self._gid = int(self._gid)
+
+ def _getPasswdEntry(self):
+ return u":".join( [self._username,
+ u"x",
+ unicode(self._uid),
+ unicode(self._primarygroup.getGID()),
+ self._gecos,
+ self._homedirectory,
+ self._loginshell ] ) + u"\n"
+
+ def _getShadowEntry(self):
+ if self._islocked:
+ encpass = u'!' + self._encpass
+ else:
+ encpass = self._encpass
+
+ if self._passminimumagebeforechange==None:
+ passminimumagebeforechange = ""
+ else:
+ passminimumagebeforechange = str(self._passminimumagebeforechange)
+
+ if self._passmaximumage==None:
+ passmaximumage = u"99999"
+ else:
+ passmaximumage = unicode(self._passmaximumage)
+
+ if self._disableddays==None:
+ disableddays = u""
+ else:
+ disableddays = unicode(self._disableddays)
+
+ if self._passexpiredisabledays==None:
+ passexpiredisabledays = u""
+ else:
+ passexpiredisabledays = unicode(self._passexpiredisabledays)
+
+ if self._passexpirewarn==None:
+ passexpirewarn = u""
+ else:
+ passexpirewarn = unicode(self._passexpirewarn)
+
+ return u":".join( [self._username,
+ encpass,
+ unicode(self._passlastchange),
+ passminimumagebeforechange,
+ passmaximumage,
+ passexpirewarn,
+ passexpiredisabledays,
+ disableddays,
+ self._reserve ])+ u"\n"
+
+###########################################################################
+class PwdGroup(UnixGroup):
+ def __init__(self,context):
+ UnixGroup.__init__(self,context)
+ self._memberids = u""
+ self._encpass = u""
+
+ def _initString(self,line):
+ (self._groupname,self._encpass,self._gid,self._memberids) = tuple(line.strip().split(":"))
+ self._gid = int(self._gid)
+
+ def polish(self):
+ membernames = self._memberids.split(",")
+ for username in membernames:
+ userobj = self._context.lookupUsername(username)
+ if userobj!=None:
+ self._members.append(userobj)
+
+ def _getGroupFileEntry(self):
+ return u":".join( [ self._groupname,
+ self._encpass,
+ unicode(self._gid),
+ u",".join([u.getUsername() for u in self._members if u.getPrimaryGroup() is not self])]) + u"\n"
+
+###########################################################################
+def LockFDRead(fd):
+ retries = 6
+ while retries!=0:
+ try:
+ fcntl.lockf(fd,fcntl.LOCK_SH | fcntl.LOCK_NB)
+ return True
+ except IOError:
+ # Wait a moment
+ time.sleep(1)
+ return False
+
+def LockFDWrite(fd):
+ retries = 6
+ while retries!=0:
+ try:
+ fcntl.lockf(fd,fcntl.LOCK_EX | fcntl.LOCK_NB)
+ return True
+ except IOError:
+ # Wait a moment
+ time.sleep(1)
+ return False
+
+def UnlockFD(fd):
+ fcntl.lockf(fd,fcntl.LOCK_UN)
+
+###########################################################################
+
+if __name__=='__main__':
+ print "Testing"
+ context = getContext(True)
+
+ print "Stopping here..."
+ #import sys
+ #sys.exit(0) ## Remove.
+ #print "Users:"
+ #for user in context.getUsers():
+ for user in context._users:
+ print "--------------------------------------------------"
+ print "UID:",user.getUID()
+ print "Is system user:",user.isSystemUser()
+ print "Username:",user.getUsername()
+ print "Primary Group:",str(user.getPrimaryGroup())
+ print "Groups:",[str(u) for u in user.getGroups()]
+ print "Is locked:",user.isLocked()
+ print "Real name:",user.getRealName()
+ print "Home Dir:",user.getHomeDirectory()
+ print "Maximum password age:",user.getMaximumPasswordAge()
+ print "Minimum password age before change:",user.getMinimumPasswordAgeBeforeChange()
+ print "Expire warning:",user.getPasswordExpireWarning()
+ print "Disable after Expire:",user.getPasswordDisableAfterExpire()
+ #print user._getPasswdEntry()
+
+ print "Groups"
+ for group in context.getGroups():
+ print str(group)
+ #print group._getGroupFileEntry()
+
+ print "Saving"
+ context.save()