# Copyright (c) 2003-2004 James Troup <troup@debian.org>
# Copyright (c) 2004-2005,7 Joey Schulze <joey@infodrom.org>
# Copyright (c) 2001-2007 Ryan Murray <rmurray@debian.org>
-# Copyright (c) 2008 Peter Palfrader <peter@palfrader.org>
+# Copyright (c) 2008,2009,2010 Peter Palfrader <peter@palfrader.org>
# Copyright (c) 2008 Andreas Barth <aba@not.so.argh.org>
# Copyright (c) 2008 Mark Hymers <mhy@debian.org>
# Copyright (c) 2008 Luk Claes <luk@debian.org>
# Copyright (c) 2008 Thomas Viehmann <tv@beamnet.de>
# Copyright (c) 2009 Stephen Gran <steve@lobefin.net>
+# Copyright (c) 2010 Helmut Grohne <helmut@subdivi.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
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:
+ from StringIO import StringIO
+try:
+ import simplejson as json
+except ImportError:
+ import json
+ if not '__author__' in json.__dict__:
+ sys.stderr.write("Warning: This is probably the wrong json module. We want python 2.6's json\n")
+ sys.stderr.write("module, or simplejson on pytyon 2.5. Let's see if/how stuff blows up.\n")
global Allowed
global CurrentHost
-PasswdAttrs = None
+if os.getuid() == 0:
+ sys.stderr.write("You should probably not run ud-generate as root.\n")
+ sys.exit(1)
+
DebianUsers = None
-DisabledUsers = []
GroupIDMap = {}
SubGroupMap = {}
Allowed = None
BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
-IsDebianHost = re.compile("[a-zA-Z0-9\.]+\.debian\.org$")
+IsDebianHost = re.compile(ConfModule.dns_hostmatch)
+isSSHFP = re.compile("^\s*IN\s+SSHFP")
DNSZone = ".debian.net"
Keyrings = ConfModule.sync_keyrings.split(":")
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
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
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)
# Return the list of users so we know which keys to export
return userlist
+def GenAllUsers(accounts, file):
+ f = None
+ try:
+ OldMask = os.umask(0022)
+ f = open(file + ".tmp", "w", 0644)
+ os.umask(OldMask)
+
+ all = []
+ for a in accounts:
+ all.append( { 'uid': a['uid'],
+ 'uidNumber': a['uidNumber'],
+ 'active': a.pw_active() and a.shadow_active() } )
+ json.dump(all, f)
+
+ # Oops, something unspeakable happened.
+ except:
+ Die(file, f, None)
+ raise
+ Done(file, f, None)
+
# 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)
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
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(',')
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))
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:
+ for a in accounts:
+ if not 'sshRSAAuthKey' in a: continue
- if x[1].has_key("uidNumber") == 0 or \
- x[1].has_key("sshRSAAuthKey") == 0:
- 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)
# Oops, something unspeakable happened.
except IOError:
Die(File, F, None)
+ # As neither masterFileName nor masterFile are defined at any point
+ # this will raise a NameError.
Die(masterFileName, masterFile, None)
raise
to.uname = f
to.gname = grname
to.mode = 0400
- tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
+
+ contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
+ lines = []
+ for line in contents.splitlines():
+ if line.startswith("allowed_hosts=") and ' ' in line:
+ machines, line = line.split('=', 1)[1].split(' ', 1)
+ if CurrentHost not in machines.split(','):
+ continue # skip this key
+ lines.append(line)
+ if not lines:
+ continue # no keys for this host
+ contents = "\n".join(lines) + "\n"
+ to.size = len(contents)
+ tf.addfile(to, StringIO(contents))
tf.close()
os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
addGroups(existingGroups, SubGroupMap[group], uid)
# Generate the group list
-def GenGroup(File):
+def GenGroup(accounts, File):
grouprevmap = {}
F = None
try:
GroupMap = {}
for x in GroupIDMap.keys():
GroupMap[x] = []
-
- # Fetch all the users
- global PasswdAttrs
-
+ GroupHasPrimaryMembers = {}
+
# Sort them into a list of groups having a set of users
- for x in PasswdAttrs:
- uid = GetAttr(x, "uid")
- 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():
- grouprevmap[GroupIDMap[x]] = x
if GroupIDMap.has_key(x) == 0:
continue
+
+ if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
+ continue
+
+ grouprevmap[GroupIDMap[x]] = x
+
Line = "%s:x:%u:" % (x, GroupIDMap[x])
Comma = ''
for I in GroupMap[x]:
return grouprevmap
-def CheckForward():
- global DebianUsers
- for x in DebianUsers:
- 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 DebianUsers
-
- # Write out the email address for each user
- for x in DebianUsers:
- 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, Key):
+def GenCDB(accounts, File, key):
Fdb = None
try:
OldMask = os.umask(0022)
Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
os.umask(OldMask)
- # Fetch all the users
- global DebianUsers
-
# Write out the email address for each user
- for x in DebianUsers:
- 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.
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 DebianUsers
-
+
# Write out the position for each user
- for x in DebianUsers:
- 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:
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 DebianUsers
-
+
# Write out the position for each user
- for x in DebianUsers:
- 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:
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 <login>:<reason>
- 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 DebianUsers
-
- for x in DebianUsers:
- 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)
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 DebianUsers
-
- for x in DebianUsers:
- 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)
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 DebianUsers
-
- for x in DebianUsers:
- 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")
-
- global HostAttrs
-
- for x in HostAttrs:
- if x[1].has_key("hostname") == 0 or \
- x[1].has_key("architecture") == 0 or\
- x[1].has_key("sshRSAHostKey") == 0:
- continue
-
- if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
- continue
-
- DNSInfo = ExtractDNSInfo(x)
- for Line in DNSInfo:
- Line = Sanitize(Line) + "\n"
- F.write(Line)
# 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)):
F.write("; Has BSMTP\n")
# Write some identification information
- if Split[2].lower() == "a":
- Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
- for y in x[1]["keyFingerPrint"]:
- Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
- F.write(Line)
+ if not RRs.has_key(Host):
+ if Split[2].lower() in ["a", "aaaa"]:
+ 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
else:
Line = "; Err %s"%(str(Split))
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.
def ExtractDNSInfo(x):
- DNSInfo = []
- Host = GetAttr(x, "hostname")
- Arch = GetAttr(x, "architecture")
- Algorithm = None
-
- for I in x[1]["sshRSAHostKey"]:
- Split = I.split()
- if Split[0] == 'ssh-rsa':
- Algorithm = 1
- if Split[0] == 'ssh-dss':
- Algorithm = 2
- if Algorithm == None:
- continue
- Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
- DNSInfo.append("%s. IN SSHFP %u 1 %s" % (Host, Algorithm, Fingerprint))
-
- Mach = ""
- if x[1].has_key("machine"):
- Mach = " " + GetAttr(x, "machine")
- DNSInfo.append("%s. IN HINFO \"%s%s\" \"%s\"" % (Host, Arch, Mach, "Debian GNU/Linux"))
+ TTLprefix="\t"
+ if 'dnsTTL' in x[1]:
+ TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
+ DNSInfo = []
if x[1].has_key("ipHostNumber"):
for I in x[1]["ipHostNumber"]:
if IsV6Addr.match(I) != None:
- DNSInfo.append("%s. IN AAAA %s" % (Host, I))
+ DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
else:
- DNSInfo.append("%s. IN A %s" % (Host, I))
+ DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
+
+ Algorithm = None
+
+ if 'sshRSAHostKey' in x[1]:
+ for I in x[1]["sshRSAHostKey"]:
+ Split = I.split()
+ if Split[0] == 'ssh-rsa':
+ Algorithm = 1
+ if Split[0] == 'ssh-dss':
+ Algorithm = 2
+ if Algorithm == None:
+ continue
+ Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
+ DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
+
+ if 'architecture' in x[1]:
+ Arch = GetAttr(x, "architecture")
+ Mach = ""
+ if x[1].has_key("machine"):
+ Mach = " " + GetAttr(x, "machine")
+ DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
if x[1].has_key("mXRecord"):
for I in x[1]["mXRecord"]:
- DNSInfo.append("%s. IN MX %s" % (Host, I))
+ DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
return DNSInfo
global HostAttrs
for x in HostAttrs:
- if x[1].has_key("hostname") == 0 or \
- x[1].has_key("architecture") == 0 or\
- x[1].has_key("sshRSAHostKey") == 0:
+ if x[1].has_key("hostname") == 0:
continue
if IsDebianHost.match(GetAttr(x, "hostname")) is None:
continue
DNSInfo = ExtractDNSInfo(x)
+ start = True
for Line in DNSInfo:
- Line = Sanitize(Line) + "\n"
- F.write(Line)
+ if start == True:
+ Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
+ start = False
+ else:
+ Line = "\t\t\t%s" % (Line)
+
+ F.write(Line + "\n")
+
+ # this would write sshfp lines for services on machines
+ # but we can't yet, since some are cnames and we'll make
+ # an invalid zonefile
+ #
+ # for i in x[1].get("purpose", []):
+ # m = PurposeHostField.match(i)
+ # if m:
+ # m = m.group(1)
+ # # we ignore [[*..]] entries
+ # if m.startswith('*'):
+ # continue
+ # if m.startswith('-'):
+ # m = m[1:]
+ # if m:
+ # if not m.endswith(HostDomain):
+ # continue
+ # if not m.endswith('.'):
+ # m = m + "."
+ # for Line in DNSInfo:
+ # if isSSHFP.match(Line):
+ # Line = "%s\t%s" % (m, Line)
+ # F.write(Line + "\n")
# Oops, something unspeakable happened.
except:
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 DebianUsers
-
# Write out the zone file entry for each user
- for x in DebianUsers:
- 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)):
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")
raise
Done(File, F, None)
-# cache IP adresses
-HostToIPCache = {}
-def HostToIP(Host):
- global HostToIPCache
- if not Host in HostToIPCache:
- IPAdressesT = None
- try:
- IPAdressesT = list(set([ (a[0], a[4][0]) for a in socket.getaddrinfo(Host, None)]))
- except socket.gaierror, (code):
- if code[0] != -2:
- raise
- IPAdresses = []
- if not IPAdressesT is None:
- for addr in IPAdressesT:
- if addr[0] == socket.AF_INET:
- IPAdresses += [addr[1], "::ffff:"+addr[1]]
- else:
- IPAdresses += [addr[1]]
- HostToIPCache[Host] = IPAdresses
- return HostToIPCache[Host]
+def HostToIP(Host, mapped=True):
+
+ IPAdresses = []
+
+ if Host[1].has_key("ipHostNumber"):
+ for addr in Host[1]["ipHostNumber"]:
+ IPAdresses.append(addr)
+ if IsV6Addr.match(addr) is None and mapped == "True":
+ IPAdresses.append("::ffff:"+addr)
+
+ return IPAdresses
# Generate the ssh known hosts file
def GenSSHKnown(File, mode=None):
for I in x[1]["sshRSAHostKey"]:
if mode and mode == 'authorized_keys':
- Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,from="%s" %s' % (Host, ",".join(HostToIP(Host)), I)
- #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
+ hosts = HostToIP(x)
+ if 'sshdistAuthKeysHost' in x[1]:
+ hosts += x[1]['sshdistAuthKeysHost']
+ Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,from="%s" %s' % (Host, ",".join(hosts), I)
else:
- Line = "%s %s" %(",".join(HostNames + HostToIP(Host)), I)
+ Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
Line = Sanitize(Line) + "\n"
F.write(Line)
# Oops, something unspeakable happened.
Done(File, F, None)
# Generate the debianhosts file (list of all IP addresses)
-def GenHosts(l, File):
+def GenHosts(File):
F = None
try:
OldMask = os.umask(0022)
if IsDebianHost.match(GetAttr(x, "hostname")) is None:
continue
- addrs = x[0]["ipHostNumber"]
+ if not 'ipHostNumber' in x[1]:
+ continue
+
+ addrs = x[1]["ipHostNumber"]
for addr in addrs:
if addr not in seen:
seen.add(addr)
# 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
# Generate the SubGroupMap and GroupIDMap
for x in Attrs:
+ if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
+ continue
if x[1].has_key("gidNumber") == 0:
continue
GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
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",
"mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
"mailContentInspectionAction"])
-if PasswdAttrs is None:
+if passwd_attrs is None:
raise UDEmptyList, "No Users"
+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",\
["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
- "mXRecord", "ipHostNumber", "machine", "architecture"])
+ "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
if HostAttrs == None:
raise UDEmptyList, "No Hosts"
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)
-#DebianUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
-DebianUsers = 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", 'emailForward')
-GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", '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)
+GenAllUsers(accounts, GlobalDir + 'all-accounts.json')
+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(l, GlobalDir + "debianhosts")
+GenHosts(GlobalDir + "debianhosts")
for host in HostAttrs:
if not "hostname" in host[1]:
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:
DoLink(GlobalDir, OutDir, "mail-rbl")
DoLink(GlobalDir, OutDir, "mail-rhsbl")
DoLink(GlobalDir, OutDir, "mail-whitelist")
+ DoLink(GlobalDir, OutDir, "all-accounts.json")
+ 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")