From: Peter Palfrader Date: Mon, 2 Aug 2010 23:33:12 +0000 (+0000) Subject: Merge branch 'refactor-udgen' X-Git-Tag: userdir-ldap-0.3.78~6 X-Git-Url: https://git.adam-barratt.org.uk/?a=commitdiff_plain;h=d6a45065258ad2edcf9e83684937b8b36ca7ff64;hp=b3c7339d03900caf99f2419df37b4a88836af3d9;p=mirror%2Fuserdir-ldap.git Merge branch 'refactor-udgen' * refactor-udgen: (24 commits) Get rid of global variable PasswdAttrs GenBSMTP GenDNS GenPasswd GenShadow Do not forget that passwords start with {crypt} GenShadowSudo GenSSHShadow fix not-array-value-but-multiple-values check GenGroup partially GenForward GenCDB And GenMailList whitespace fixes And GenMailBool Let disable-main-msg generation use Account class Let disabled-users generation use Account class Let private generation use Account class Catch the case where attributes that are not declared as an array value have more than one value. This indicates a bug in the data, code, or ldap schema Some improvement over the last path ... --- diff --git a/UDLdap.py b/UDLdap.py new file mode 100644 index 0000000..2e45092 --- /dev/null +++ b/UDLdap.py @@ -0,0 +1,115 @@ +import ldap +import time +import userdir_ldap + +class Account: + array_values = ['objectClass', 'keyFingerPrint', 'mailWhitelist', 'mailRBL', + 'mailRHSBL', 'supplementaryGid', 'sshRSAAuthKey', + 'sudoPassword', 'dnsZoneEntry', 'allowedHost'] + int_values = ['shadowExpire', 'gidNumber', 'uidNumber'] + defaults = { + 'accountStatus': 'active', + 'keyFingerPrint': [] + } + + @staticmethod + def from_search(ldap_connection, base, user): + searchresult = ldap_connection.search_s(base, ldap.SCOPE_SUBTREE, 'uid=%s'%(user)) + if len(searchresult) < 1: + sys.stderr.write("No such user: %s\n"%(user)) + return + elif len(searchresult) > 1: + sys.stderr.write("More than one hit when getting %s\n"%(user)) + return + else: + return Account(searchresult[0][0], searchresult[0][1]) + + def __init__(self, dn, attributes): + self.dn = dn + self.attributes = attributes + + def __getitem__(self, key): + if key in self.attributes: + if key in self.array_values: + return self.attributes[key] + + if not len(self.attributes[key]) == 1: + raise ValueError, 'non-array value has not exactly one value' + + if key in self.int_values: + return int(self.attributes[key][0]) + else: + return self.attributes[key][0] + elif key in self.defaults: + return self.defaults[key] + else: + raise IndexError + + def __contains__(self, key): + return key in self.attributes + + def has_mail(self): + if 'mailDisableMessage' in self.attributes: + return False + return True + + # not locked locked, just reset to something invalid like {crypt}*SSLRESET* is still active + def pw_active(self): + if self['userPassword'] == '{crypt}*LK*': + return False + if self['userPassword'].startswith("{crypt}!"): + return False + return True + + def get_password(self): + p = self['userPassword'] + if not p.startswith('{crypt}') or len(p) > 50: + return p + else: + return p[7:] + + # not expired + def shadow_active(self): + if 'shadowExpire' in self and \ + self['shadowExpire'] < (time.time() / 3600 / 24): + return False + return True + + def numkeys(self): + return len(self['keyFingerPrint']) + + def is_active_user(self): + return self['accountStatus'] == 'active' and self.numkeys() != 0 + + def latitude_dec(self, anonymized=False): + return userdir_ldap.DecDegree(self['latitude'], anonymized) + def longitude_dec(self, anonymized=False): + return userdir_ldap.DecDegree(self['longitude'], anonymized) + + def verbose_status(self): + status = [] + status.append('mail: %s' %(['disabled', 'active'][ self.has_mail() ])) + status.append('pw: %s' %(['locked', 'active'][ self.pw_active() ])) + status.append('shadow: %s'%(['expired', 'active'][ self.shadow_active() ])) + status.append('keys: %d' %( self.numkeys() )) + status.append('status: %s'%( self['accountStatus'] )) + + return '(%s)'%(', '.join(status)) + + def delete_mailforward(self): + del self.attributes['emailForward'] + + def get_dn(self): + return self.dn + + def email_address(self): + mailbox = "<%s@%s>" % (self['uid'], userdir_ldap.EmailAppend) + tokens = [] + if 'cn' in self: tokens.append(self['cn']) + if 'sn' in self: tokens.append(self['sn']) + tokens.append(mailbox) + return ' '.join(tokens) + +# vim:set et: +# vim:set ts=4: +# vim:set shiftwidth=4: diff --git a/debian/install b/debian/install index 104e1e9..0a06f86 100644 --- a/debian/install +++ b/debian/install @@ -1,3 +1,4 @@ +UDLdap.py usr/share/python-support/userdir_ldap/ userdir_ldap.pth usr/share/python-support/ userdir_ldap.py usr/share/python-support/userdir_ldap/ userdir_gpg.py usr/share/python-support/userdir_ldap/ diff --git a/ud-generate b/ud-generate index fc6dbb2..850bb51 100755 --- a/ud-generate +++ b/ud-generate @@ -31,6 +31,7 @@ import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp from userdir_ldap import * from userdir_exceptions import * +import UDLdap try: from cStringIO import StringIO except ImportError: @@ -43,9 +44,7 @@ if os.getuid() == 0: sys.stderr.write("You should probably not run ud-generate as root.\n") sys.exit(1) -PasswdAttrs = None DebianUsers = None -DisabledUsers = [] GroupIDMap = {} SubGroupMap = {} Allowed = None @@ -90,19 +89,17 @@ def DoLink(From, To, File): pass posix.link(From + File, To + File) -def IsRetired(DnRecord): +def IsRetired(account): """ Looks for accountStatus in the LDAP record and tries to match it against one of the known retired statuses """ - status = GetAttr(DnRecord, "accountStatus", None) - if status is None: - return False + status = account['accountStatus'] line = status.split() status = line[0] - + if status == "inactive": return True @@ -121,32 +118,25 @@ def IsRetired(DnRecord): return False -def IsGidDebian(x): - try: - return int(GetAttr(x, "gidNumber", 0)) == 800 - except ValueError: - return False +#def IsGidDebian(account): +# return account['gidNumber'] == 800 # See if this user is in the group list -def IsInGroup(DnRecord): +def IsInGroup(account): if Allowed is None: return True # See if the primary group is in the list - if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0: - return True + if str(account['gidNumber']) in Allowed: return True # Check the host based ACL - if DnRecord[1].has_key("allowedHost") != 0: - if CurrentHost in DnRecord[1]["allowedHost"]: - return True + if 'allowedHost' in account and CurrentHost in account['allowedHost']: return True # See if there are supplementary groups - if DnRecord[1].has_key("supplementaryGid") == 0: - return False + if not 'supplementaryGid' in account: return False supgroups=[] - addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "uid")) + addGroups(supgroups, account['supplementaryGid'], account['uid']) for g in supgroups: if Allowed.has_key(g): return True @@ -175,37 +165,34 @@ def Done(File, F, Fdb): os.rename(File + ".tdb.tmp", File + ".tdb") # Generate the password list -def GenPasswd(File, HomePrefix, PwdMarker): +def GenPasswd(accounts, File, HomePrefix, PwdMarker): F = None try: F = open(File + ".tdb.tmp", "w") - + userlist = {} - # Fetch all the users - global PasswdAttrs - - I = 0 - for x in PasswdAttrs: - if x[1].has_key("uidNumber") == 0 or not IsInGroup(x): - continue - + i = 0 + for a in accounts: + if not IsInGroup(a): continue + # Do not let people try to buffer overflow some busted passwd parser. - if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50: - continue - - userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber")) - Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\ - PwdMarker,\ - GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\ - GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\ - GetAttr(x, "loginShell")) - - Line = Sanitize(Line) + "\n" - F.write("0%u %s" % (I, Line)) - F.write(".%s %s" % (GetAttr(x, "uid"), Line)) - F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line)) - I = I + 1 - + if len(a['gecos']) > 100 or len(a['loginShell']) > 50: continue + + userlist[a['uid']] = a['gidNumber'] + line = "%s:%s:%d:%d:%s:%s%s:%s" % ( + a['uid'], + PwdMarker, + a['uidNumber'], + a['gidNumber'], + a['gecos'], + HomePrefix, a['uid'], + a['loginShell']) + line = Sanitize(line) + "\n" + F.write("0%u %s" % (i, line)) + F.write(".%s %s" % (a['uid'], line)) + F.write("=%d %s" % (a['uidNumber'], line)) + i = i + 1 + # Oops, something unspeakable happened. except: Die(File, None, F) @@ -216,45 +203,37 @@ def GenPasswd(File, HomePrefix, PwdMarker): return userlist # Generate the shadow list -def GenShadow(File): +def GenShadow(accounts, File): F = None try: OldMask = os.umask(0077) F = open(File + ".tdb.tmp", "w", 0600) os.umask(OldMask) - - # Fetch all the users - global PasswdAttrs - - I = 0 - for x in PasswdAttrs: - if x[1].has_key("uidNumber") == 0 or not IsInGroup(x): - continue - - Pass = GetAttr(x, "userPassword") - if Pass[0:7] != "{crypt}" or len(Pass) > 50: - Pass = '*' - else: - Pass = Pass[7:] - + + i = 0 + for a in accounts: + Pass = '*' + if not IsInGroup(a): continue + # If the account is locked, mark it as such in shadow # See Debian Bug #308229 for why we set it to 1 instead of 0 - if (GetAttr(x, "userPassword").find("*LK*") != -1) \ - or GetAttr(x, "userPassword").startswith("!"): - ShadowExpire = '1' - else: - ShadowExpire = GetAttr(x, "shadowExpire") - - Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\ - Pass, GetAttr(x, "shadowLastChange"),\ - GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\ - GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\ - ShadowExpire) - Line = Sanitize(Line) + "\n" - F.write("0%u %s" % (I, Line)) - F.write(".%s %s" % (GetAttr(x, "uid"), Line)) - I = I + 1 - + if not a.pw_active(): ShadowExpire = '1' + elif 'shadowExpire' in a: ShadowExpire = str(a['shadowExpire']) + else: ShadowExpire = '' + + values = [] + values.append(a['uid']) + values.append(a.get_password()) + for key in 'shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive': + if key in a: values.append(a[key]) + else: values.append('') + values.append(ShadowExpire) + line = ':'.join(values)+':' + line = Sanitize(line) + "\n" + F.write("0%u %s" % (i, line)) + F.write(".%s %s" % (a['uid'], line)) + i = i + 1 + # Oops, something unspeakable happened. except: Die(File, None, F) @@ -262,23 +241,19 @@ def GenShadow(File): Done(File, None, F) # Generate the sudo passwd file -def GenShadowSudo(File, untrusted): +def GenShadowSudo(accounts, File, untrusted): F = None try: OldMask = os.umask(0077) F = open(File + ".tmp", "w", 0600) os.umask(OldMask) - - # Fetch all the users - global PasswdAttrs - - for x in PasswdAttrs: + + for a in accounts: Pass = '*' - if x[1].has_key("uidNumber") == 0 or not IsInGroup(x): - continue + if not IsInGroup(a): continue - if x[1].has_key('sudoPassword'): - for entry in x[1]['sudoPassword']: + if 'sudoPassword' in a: + for entry in a['sudoPassword']: Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry) if Match == None: continue @@ -287,7 +262,7 @@ def GenShadowSudo(File, untrusted): hosts = Match.group(3) cryptedpass = Match.group(4) - if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass): + if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass): continue for_all = hosts == "*" for_this_host = CurrentHost in hosts.split(',') @@ -302,7 +277,7 @@ def GenShadowSudo(File, untrusted): if len(Pass) > 50: Pass = '*' - Line = "%s:%s" % (GetAttr(x, "uid"), Pass) + Line = "%s:%s" % (a['uid'], Pass) Line = Sanitize(Line) + "\n" F.write("%s" % (Line)) @@ -313,31 +288,24 @@ def GenShadowSudo(File, untrusted): Done(File, F, None) # Generate the shadow list -def GenSSHShadow(): +def GenSSHShadow(accounts): # Fetch all the users userfiles = [] - global PasswdAttrs - safe_rmtree(os.path.join(GlobalDir, 'userkeys')) safe_makedirs(os.path.join(GlobalDir, 'userkeys')) - for x in PasswdAttrs: - - if x[1].has_key("uidNumber") == 0 or \ - x[1].has_key("sshRSAAuthKey") == 0: - continue + for a in accounts: + if not 'sshRSAAuthKey' in a: continue - User = GetAttr(x, "uid") F = None - try: OldMask = os.umask(0077) - File = os.path.join(GlobalDir, 'userkeys', User) + File = os.path.join(GlobalDir, 'userkeys', a['uid']) F = open(File + ".tmp", "w", 0600) os.umask(OldMask) - for I in x[1]["sshRSAAuthKey"]: + for I in a['sshRSAAuthKey']: MultipleLine = "%s" % I MultipleLine = Sanitize(MultipleLine) + "\n" F.write(MultipleLine) @@ -441,7 +409,7 @@ def addGroups(existingGroups, newGroups, uid): addGroups(existingGroups, SubGroupMap[group], uid) # Generate the group list -def GenGroup(File): +def GenGroup(accounts, File): grouprevmap = {} F = None try: @@ -452,25 +420,18 @@ def GenGroup(File): for x in GroupIDMap.keys(): GroupMap[x] = [] GroupHasPrimaryMembers = {} - - # Fetch all the users - global PasswdAttrs - + # Sort them into a list of groups having a set of users - for x in PasswdAttrs: - uid = GetAttr(x, "uid") - if 'gidNumber' in x[1]: - GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True - if x[1].has_key("uidNumber") == 0 or not IsInGroup(x): - continue - if x[1].has_key("supplementaryGid") == 0: - continue - + for a in accounts: + GroupHasPrimaryMembers[ a['gidNumber'] ] = True + if not IsInGroup(a): continue + if not 'supplementaryGid' in a: continue + supgroups=[] - addGroups(supgroups, x[1]["supplementaryGid"], uid) + addGroups(supgroups, a['supplementaryGid'], a['uid']) for g in supgroups: - GroupMap[g].append(uid) - + GroupMap[g].append(a['uid']) + # Output the group file. J = 0 for x in GroupMap.keys(): @@ -501,52 +462,43 @@ def GenGroup(File): return grouprevmap -def CheckForward(): - global PasswdAttrs - for x in PasswdAttrs: - if x[1].has_key("emailForward") == 0: - continue - - if not IsInGroup(x): - x[1].pop("emailForward") - continue +def CheckForward(accounts): + for a in accounts: + if not 'emailForward' in a: continue - # Do not allow people to try to buffer overflow busted parsers - if len(GetAttr(x, "emailForward")) > 200: - x[1].pop("emailForward") - continue + delete = False + + if not IsInGroup(a): delete = True + # Do not allow people to try to buffer overflow busted parsers + elif len(a['emailForward']) > 200: delete = True # Check the forwarding address - if EmailCheck.match(GetAttr(x, "emailForward")) == None: - x[1].pop("emailForward") + elif EmailCheck.match(a['emailForward']) is None: delete = True + + if delete: + a.delete_mailforward() # Generate the email forwarding list -def GenForward(File): +def GenForward(accounts, File): F = None try: OldMask = os.umask(0022) F = open(File + ".tmp", "w", 0644) os.umask(OldMask) - - # Fetch all the users - global PasswdAttrs - - # Write out the email address for each user - for x in PasswdAttrs: - if x[1].has_key("emailForward") == 0: - continue - - Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward")) + + for a in accounts: + if not 'emailForward' in a: continue + Line = "%s: %s" % (a['uid'], a['emailForward']) Line = Sanitize(Line) + "\n" F.write(Line) - + # Oops, something unspeakable happened. except: Die(File, F, None) raise Done(File, F, None) -def GenCDB(File, Users, Key): +def GenCDB(accounts, File, key): Fdb = None try: OldMask = os.umask(0022) @@ -554,12 +506,11 @@ def GenCDB(File, Users, Key): os.umask(OldMask) # Write out the email address for each user - for x in Users: - if not Key in x[1]: - continue - Value = GetAttr(x, Key) - User = GetAttr(x, "uid") - Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value)) + for a in accounts: + if not key in a: continue + value = a[key] + user = a['uid'] + Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value)) Fdb.write("\n") # Oops, something unspeakable happened. @@ -570,20 +521,16 @@ def GenCDB(File, Users, Key): raise "cdbmake gave an error" # Generate the anon XEarth marker file -def GenMarkers(File): +def GenMarkers(accounts, File): F = None try: F = open(File + ".tmp", "w") - - # Fetch all the users - global PasswdAttrs - + # Write out the position for each user - for x in PasswdAttrs: - if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0: - continue + for a in accounts: + if not ('latitude' in a and 'longitude' in a): continue try: - Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1)) + Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True)) Line = Sanitize(Line) + "\n" F.write(Line) except: @@ -596,25 +543,17 @@ def GenMarkers(File): Done(File, F, None) # Generate the debian-private subscription list -def GenPrivate(File): +def GenPrivate(accounts, File): F = None try: F = open(File + ".tmp", "w") - - # Fetch all the users - global DebianDDUsers - + # Write out the position for each user - for x in DebianDDUsers: - if x[1].has_key("privateSub") == 0: - continue - - # If the account has no PGP key, do not write it - if x[1].has_key("keyFingerPrint") == 0: - continue - + for a in accounts: + if not a.is_active_user(): continue + if not 'privateSub' in a: continue try: - Line = "%s"%(GetAttr(x, "privateSub")) + Line = "%s"%(a['privateSub']) Line = Sanitize(Line) + "\n" F.write(Line) except: @@ -627,63 +566,38 @@ def GenPrivate(File): Done(File, F, None) # Generate a list of locked accounts -def GenDisabledAccounts(File): +def GenDisabledAccounts(accounts, File): F = None try: F = open(File + ".tmp", "w") - + disabled_accounts = [] + # Fetch all the users - global PasswdAttrs - global DisabledUsers - - I = 0 - for x in PasswdAttrs: - if x[1].has_key("uidNumber") == 0: - continue - - Pass = GetAttr(x, "userPassword") - Line = "" - # *LK* is the reference value for a locked account - # password starting with ! is also a locked account - if Pass.find("*LK*") != -1 or Pass.startswith("!"): - # Format is : - Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked") - DisabledUsers.append(x) - - if Line != "": - F.write(Sanitize(Line) + "\n") - - + for a in accounts: + if a.pw_active(): continue + Line = "%s:%s" % (a['uid'], "Account is locked") + disabled_accounts.append(a) + F.write(Sanitize(Line) + "\n") + # Oops, something unspeakable happened. except: Die(File, F, None) raise Done(File, F, None) + return disabled_accounts # Generate the list of local addresses that refuse all mail -def GenMailDisable(File): +def GenMailDisable(accounts, File): F = None try: F = open(File + ".tmp", "w") - - # Fetch all the users - global PasswdAttrs - - for x in PasswdAttrs: - Reason = None - - if x[1].has_key("mailDisableMessage"): - Reason = GetAttr(x, "mailDisableMessage") - else: - continue - - try: - Line = "%s: %s"%(GetAttr(x, "uid"), Reason) - Line = Sanitize(Line) + "\n" - F.write(Line) - except: - pass - + + for a in accounts: + if not 'mailDisableMessage' in a: continue + Line = "%s: %s"%(a['uid'], a['mailDisableMessage']) + Line = Sanitize(Line) + "\n" + F.write(Line) + # Oops, something unspeakable happened. except: Die(File, F, None) @@ -691,30 +605,18 @@ def GenMailDisable(File): Done(File, F, None) # Generate a list of uids that should have boolean affects applied -def GenMailBool(File, Key): +def GenMailBool(accounts, File, key): F = None try: F = open(File + ".tmp", "w") - - # Fetch all the users - global PasswdAttrs - - for x in PasswdAttrs: - Reason = None - - if x[1].has_key(Key) == 0: - continue - - if GetAttr(x, Key) != "TRUE": - continue - - try: - Line = "%s"%(GetAttr(x, "uid")) - Line = Sanitize(Line) + "\n" - F.write(Line) - except: - pass - + + for a in accounts: + if not key in a: continue + if not a[key] == 'TRUE': continue + Line = "%s"%(a['uid']) + Line = Sanitize(Line) + "\n" + F.write(Line) + # Oops, something unspeakable happened. except: Die(File, F, None) @@ -722,82 +624,50 @@ def GenMailBool(File, Key): Done(File, F, None) # Generate a list of hosts for RBL or whitelist purposes. -def GenMailList(File, Key): +def GenMailList(accounts, File, key): F = None try: F = open(File + ".tmp", "w") - - # Fetch all the users - global PasswdAttrs - - for x in PasswdAttrs: - Reason = None - - if x[1].has_key(Key) == 0: - continue - - try: - found = 0 - Line = None - for z in x[1][Key]: - if Key == "mailWhitelist": - if re.match('^[-\w.]+(/[\d]+)?$', z) == None: - continue - else: - if re.match('^[-\w.]+$', z) == None: - continue - if found == 0: - found = 1 - Line = GetAttr(x, "uid") - else: - Line += " " - Line += ": " + z - if Key == "mailRHSBL": - Line += "/$sender_address_domain" - - if Line != None: - Line = Sanitize(Line) + "\n" - F.write(Line) - except: - pass - + + if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$') + else: validregex = re.compile('^[-\w.]+$') + + for a in accounts: + if not key in a: continue + + filtered = filter(lambda z: validregex.match(z), a[key]) + if len(filtered) == 0: continue + if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered) + line = a['uid'] + ': ' + ' : '.join(filtered) + line = Sanitize(line) + "\n" + F.write(line) + # Oops, something unspeakable happened. except: Die(File, F, None) raise Done(File, F, None) -def isRoleAccount(pwEntry): - if not pwEntry.has_key("objectClass"): - raise "pwEntry has no objectClass" - oc = pwEntry['objectClass'] - try: - i = oc.index('debianRoleAccount') - return True - except ValueError: - return False +def isRoleAccount(account): + return 'debianRoleAccount' in account['objectClass'] # Generate the DNS Zone file -def GenDNS(File): +def GenDNS(accounts, File): F = None try: F = open(File + ".tmp", "w") - + # Fetch all the users - global PasswdAttrs RRs = {} - + # Write out the zone file entry for each user - for x in PasswdAttrs: - if x[1].has_key("dnsZoneEntry") == 0: - continue - - # If the account has no PGP key, do not write it - if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]): - continue + for a in accounts: + if not 'dnsZoneEntry' in a: continue + if not a.is_active_user() and not isRoleAccount(a): continue + try: - F.write("; %s\n"%(EmailAddress(x))) - for z in x[1]["dnsZoneEntry"]: + F.write("; %s\n"%(a.email_address())) + for z in a["dnsZoneEntry"]: Split = z.lower().split() if Split[1].lower() == 'in': for y in range(0, len(Split)): @@ -813,8 +683,8 @@ def GenDNS(File): # Write some identification information if not RRs.has_key(Host): if Split[2].lower() in ["a", "aaaa"]: - Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x)) - for y in x[1]["keyFingerPrint"]: + Line = "%s IN TXT \"%s\"\n"%(Split[0], a.email_address()) + for y in a["keyFingerPrint"]: Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y)) F.write(Line) RRs[Host] = 1 @@ -823,8 +693,10 @@ def GenDNS(File): F.write(Line) F.write("\n") - except: - F.write("; Errors\n") + except Exception, e: + F.write("; Errors:\n") + for line in str(e).split("\n"): + F.write("; %s\n"%(line)) pass # Oops, something unspeakable happened. @@ -931,24 +803,18 @@ def GenZoneRecords(File): Done(File, F, None) # Generate the BSMTP file -def GenBSMTP(File, HomePrefix): +def GenBSMTP(accounts, File, HomePrefix): F = None try: F = open(File + ".tmp", "w") - # Fetch all the users - global PasswdAttrs - # Write out the zone file entry for each user - for x in PasswdAttrs: - if x[1].has_key("dnsZoneEntry") == 0: - continue - - # If the account has no PGP key, do not write it - if x[1].has_key("keyFingerPrint") == 0: - continue + for a in accounts: + if not 'dnsZoneEntry' in a: continue + if not a.is_active_user(): continue + try: - for z in x[1]["dnsZoneEntry"]: + for z in a["dnsZoneEntry"]: Split = z.lower().split() if Split[1].lower() == 'in': for y in range(0, len(Split)): @@ -959,7 +825,7 @@ def GenBSMTP(File, HomePrefix): Host = Split[0] + DNSZone if BSMTPCheck.match(Line) != None: F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host, - GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host)) + a['uid'], HomePrefix, a['uid'], Host)) except: F.write("; Errors\n") @@ -1076,9 +942,14 @@ def GenKeyrings(OutDir): # Connect to the ldap server l = connectLDAP() -F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r") -Pass = F.readline().strip().split(" ") -F.close() +# for testing purposes it's sometimes useful to pass username/password +# via the environment +if 'UD_CREDENTIALS' in os.environ: + Pass = os.environ['UD_CREDENTIALS'].split() +else: + F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r") + Pass = F.readline().strip().split(" ") + F.close() l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1]) # Fetch all the groups @@ -1097,7 +968,7 @@ for x in Attrs: SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"]) # Fetch all the users -PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\ +passwd_attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\ ["uid", "uidNumber", "gidNumber", "supplementaryGid",\ "gecos", "loginShell", "userPassword", "shadowLastChange",\ "shadowMin", "shadowMax", "shadowWarning", "shadowInactive", @@ -1108,10 +979,10 @@ PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\ "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\ "mailContentInspectionAction"]) -if PasswdAttrs is None: +if passwd_attrs is None: raise UDEmptyList, "No Users" - -PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower())) +accounts = map(lambda x: UDLdap.Account(x[0], x[1]), passwd_attrs) +accounts.sort(lambda x,y: cmp(x['uid'].lower(), y['uid'].lower())) # Fetch all the hosts HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\ @@ -1123,34 +994,38 @@ if HostAttrs == None: HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower())) +# override globaldir for testing +if 'UD_GENERATEDIR' in os.environ: + GenerateDir = os.environ['UD_GENERATEDIR'] + # Generate global things GlobalDir = GenerateDir + "/" -GenDisabledAccounts(GlobalDir + "disabled-accounts") +accounts_disabled = GenDisabledAccounts(accounts, GlobalDir + "disabled-accounts") -PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs) -DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs) +accounts = filter(lambda x: not IsRetired(x), accounts) +#accounts_DDs = filter(lambda x: IsGidDebian(x), accounts) -CheckForward() +CheckForward(accounts) -GenMailDisable(GlobalDir + "mail-disable") -GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward') -GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction') -GenPrivate(GlobalDir + "debian-private") +GenMailDisable(accounts, GlobalDir + "mail-disable") +GenCDB(accounts, GlobalDir + "mail-forward.cdb", 'emailForward') +GenCDB(accounts, GlobalDir + "mail-contentinspectionaction.cdb", 'mailContentInspectionAction') +GenPrivate(accounts, GlobalDir + "debian-private") GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys') -GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting") -GenMailBool(GlobalDir + "mail-callout", "mailCallout") -GenMailList(GlobalDir + "mail-rbl", "mailRBL") -GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL") -GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist") +GenMailBool(accounts, GlobalDir + "mail-greylist", "mailGreylisting") +GenMailBool(accounts, GlobalDir + "mail-callout", "mailCallout") +GenMailList(accounts, GlobalDir + "mail-rbl", "mailRBL") +GenMailList(accounts, GlobalDir + "mail-rhsbl", "mailRHSBL") +GenMailList(accounts, GlobalDir + "mail-whitelist", "mailWhitelist") GenKeyrings(GlobalDir) # Compatibility. -GenForward(GlobalDir + "forward-alias") +GenForward(accounts, GlobalDir + "forward-alias") -PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs) +accounts = filter(lambda a: not a in accounts_disabled, accounts) -SSHFiles = GenSSHShadow() -GenMarkers(GlobalDir + "markers") +SSHFiles = GenSSHShadow(accounts) +GenMarkers(accounts, GlobalDir + "markers") GenSSHKnown(GlobalDir + "ssh_known_hosts") GenHosts(GlobalDir + "debianhosts") @@ -1191,19 +1066,19 @@ for host in HostAttrs: sys.stdout.flush() if 'NOPASSWD' in ExtraList: - userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*") + userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "*") else: - userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x") + userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "x") sys.stdout.flush() - grouprevmap = GenGroup(OutDir + "group") - GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList)) + grouprevmap = GenGroup(accounts, OutDir + "group") + GenShadowSudo(accounts, OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList)) # Now we know who we're allowing on the machine, export # the relevant ssh keys GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz')) if not 'NOPASSWD' in ExtraList: - GenShadow(OutDir + "shadow") + GenShadow(accounts, OutDir + "shadow") # Link in global things if not 'NOMARKERS' in ExtraList: @@ -1216,22 +1091,22 @@ for host in HostAttrs: DoLink(GlobalDir, OutDir, "mail-rbl") DoLink(GlobalDir, OutDir, "mail-rhsbl") DoLink(GlobalDir, OutDir, "mail-whitelist") - GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward') - GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken') - GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions') + GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "user-forward.cdb", 'emailForward') + GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "batv-tokens.cdb", 'bATVToken') + GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "default-mail-options.cdb", 'mailDefaultOptions') # Compatibility. DoLink(GlobalDir, OutDir, "forward-alias") if 'DNS' in ExtraList: - GenDNS(OutDir + "dns-zone") + GenDNS(accounts, OutDir + "dns-zone") GenZoneRecords(OutDir + "dns-sshfp") if 'AUTHKEYS' in ExtraList: DoLink(GlobalDir, OutDir, "authorized_keys") if 'BSMTP' in ExtraList: - GenBSMTP(OutDir + "bsmtp", HomePrefix) + GenBSMTP(accounts, OutDir + "bsmtp", HomePrefix) if 'PRIVATE' in ExtraList: DoLink(GlobalDir, OutDir, "debian-private") diff --git a/ud-lock b/ud-lock index 6d56ddb..a274274 100755 --- a/ud-lock +++ b/ud-lock @@ -27,6 +27,7 @@ import os import pwd import time from userdir_ldap import *; +import UDLdap dry_run = False @@ -46,65 +47,9 @@ def connect(user): sys.exit(1) return l - -class Account: - def __init__(self, user): - searchresult = lc.search_s(BaseDn,ldap.SCOPE_SUBTREE, 'uid=%s'%(user)) - if len(searchresult) < 1: - sys.stderr.write("No such user: %s\n"%(user)) - return - elif len(searchresult) > 1: - sys.stderr.write("More than one hit when getting %s\n"%(user)) - return - - self.dn, self.attributes = searchresult[0] - - - def has_mail(self): - if 'mailDisableMessage' in self.attributes: - return False - return True - - # not locked locked, just reset to something invalid like {crypt}*SSLRESET* is still active - def pw_active(self): - if self.attributes['userPassword'][0] == '{crypt}*LK*': - return False - return True - - # not expired - def shadow_active(self): - if 'shadowExpire' in self.attributes and \ - int(self.attributes['shadowExpire'][0]) < (time.time() / 3600 / 24): - return False - return True - - def numkeys(self): - if 'keyFingerPrint' in self.attributes: - return len(self.attributes['keyFingerPrint']) - return 0 - - def account_status(self): - if 'accountStatus' in self.attributes: - return self.attributes['accountStatus'][0] - return 'active' - - - def verbose_status(self): - status = [] - status.append('mail: %s' %(['disabled', 'active'][ self.has_mail() ])) - status.append('pw: %s' %(['locked', 'active'][ self.pw_active() ])) - status.append('shadow: %s'%(['expired', 'active'][ self.shadow_active() ])) - status.append('keys: %d' %( self.numkeys() )) - status.append('status: %s'%( self.account_status() )) - - return '(%s)'%(', '.join(status)) - - def get_dn(self): - return self.dn - def do_one_user(lc, user, ticket): - u = Account(user) - if not u.account_status() == 'active': + u = UDLdap.Account.from_search(lc, BaseDn, user) + if not u['accountStatus'] == 'active': sys.stderr.write('%s: Account is not active, skipping. (details: %s)\n'%(user, u.verbose_status())) return diff --git a/userdir_ldap.py b/userdir_ldap.py index 109c7ce..c76992c 100644 --- a/userdir_ldap.py +++ b/userdir_ldap.py @@ -432,9 +432,12 @@ def Group2GID(l, name): return -1 def make_hmac(str): - File = open(PassDir+"/key-hmac-"+pwd.getpwuid(os.getuid())[0],"r"); - HmacKey = File.readline().strip() - File.close(); + if 'UD_HMAC_KEY' in os.environ: + HmacKey = os.environ['UD_HMAC_KEY'] + else: + File = open(PassDir+"/key-hmac-"+pwd.getpwuid(os.getuid())[0],"r"); + HmacKey = File.readline().strip() + File.close(); return hmac.new(HmacKey, str, sha1_module).hexdigest() def make_passwd_hmac(status, purpose, uid, uuid, hosts, cryptedpass):