# Copyright (c) 2008 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>
#
# 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 *
global Allowed;
global CurrentHost;
PasswdAttrs = None;
+DisabledUsers = []
+RetiredUsers = []
GroupIDMap = {};
+SubGroupMap = {};
Allowed = None;
CurrentHost = "";
EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$");
BSMTPCheck = re.compile(".*mx 0 (gluck)\.debian\.org\..*",re.DOTALL);
+PurposeHostField = re.compile(r"\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
DNSZone = ".debian.net"
Keyrings = ConfModule.sync_keyrings.split(":")
except: pass;
posix.link(From+File,To+File);
+def IsRetired(DnRecord):
+ """
+ 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
+
+ line = status.split()
+ status = line[0]
+
+ if status == "inactive":
+ return True
+
+ elif status == "memorial":
+ return True
+
+ elif status == "retiring":
+ # We'll give them a few extra days over what we said
+ age = 6 * 31 * 24 * 60 * 60
+ try:
+ if (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age:
+ return True
+ except IndexError:
+ return False
+
+ return False
+
# See if this user is in the group list
def IsInGroup(DnRecord):
if Allowed == None:
if DnRecord[1].has_key("supplementaryGid") == 0:
return 0;
- # Check the supplementary groups
- for I in DnRecord[1]["supplementaryGid"]:
- if Allowed.has_key(I):
+ supgroups=[]
+ addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord,"uid"))
+ for g in supgroups:
+ if Allowed.has_key(g):
return 1;
return 0;
userlist = {}
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
I = 0;
for x in PasswdAttrs:
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
I = 0;
for x in PasswdAttrs:
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
for x in PasswdAttrs:
Pass = '*'
userfiles = []
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
for x in PasswdAttrs:
- # If the account is locked, do not write it.
- # This is a partial stop-gap. The ssh also needs to change this
- # to ignore ~/.ssh/authorized* files.
- if (GetAttr(x,"userPassword").find("*LK*") != -1) \
- or GetAttr(x,"userPassword").startswith("!"):
- continue;
+
+ if x in DisabledUsers:
+ continue
if x[1].has_key("uidNumber") == 0 or \
x[1].has_key("sshRSAAuthKey") == 0:
continue;
+
User = GetAttr(x,"uid");
F = None;
tf.close()
os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
+# add a list of groups to existing groups,
+# including all subgroups thereof, recursively.
+# basically this proceduces the transitive hull of the groups in
+# addgroups.
+def addGroups(existingGroups, newGroups, uid):
+ for group in newGroups:
+ # if it's a <group>@host, split it and verify it's on the current host.
+ s = group.split('@', 1)
+ if len(s) == 2 and s[1] != CurrentHost:
+ continue;
+ group = s[0]
+
+ # let's see if we handled this group already
+ if group in existingGroups:
+ continue
+
+ if not GroupIDMap.has_key(group):
+ print "Group", group, "does not exist but", uid, "is in it"
+ continue
+
+ existingGroups.append(group)
+
+ if SubGroupMap.has_key(group):
+ addGroups(existingGroups, SubGroupMap[group], uid)
+
# Generate the group list
def GenGroup(l,File):
grouprevmap = {}
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# 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 IsInGroup(x) == 0:
continue;
if x[1].has_key("supplementaryGid") == 0:
continue;
- for I in x[1]["supplementaryGid"]:
- if GroupMap.has_key(I):
- GroupMap[I].append(GetAttr(x,"uid"));
- else:
- print "Group does not exist ",I,"but",GetAttr(x,"uid"),"is in it";
+ supgroups=[]
+ addGroups(supgroups, x[1]["supplementaryGid"], uid)
+ for g in supgroups:
+ GroupMap[g].append(uid);
# Output the group file.
J = 0;
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# Write out the email address for each user
for x in PasswdAttrs:
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# Write out the email address for each user
for x in PasswdAttrs:
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# Write out the position for each user
for x in PasswdAttrs:
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# Write out the position for each user
for x in PasswdAttrs:
if x[1].has_key("privateSub") == 0:
continue;
- # If the account is locked, do not write it
- if (GetAttr(x,"userPassword").find("*LK*") != -1) \
- or GetAttr(x,"userPassword").startswith("!"):
- continue;
-
# If the account has no PGP key, do not write it
if x[1].has_key("keyFingerPrint") == 0:
continue;
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
+ global DisabledUsers
I = 0;
for x in PasswdAttrs:
if Line != "":
F.write(Sanitize(Line) + "\n")
+ DisabledUsers.append(x)
+
# Oops, something unspeakable happened.
except:
Die(File,F,None);
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
for x in PasswdAttrs:
Reason = None
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
for x in PasswdAttrs:
Reason = None
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
for x in PasswdAttrs:
Reason = 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
+
# Generate the DNS Zone file
def GenDNS(l,File,HomePrefix):
F = None;
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# Write out the zone file entry for each user
for x in PasswdAttrs:
continue;
# If the account has no PGP key, do not write it
- if x[1].has_key("keyFingerPrint") == 0:
+ if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
continue;
try:
F.write("; %s\n"%(EmailAddress(x)));
# Fetch all the hosts
global HostAttrs
if HostAttrs == None:
- raise "No Hosts"
+ raise UDEmptyList, "No Hosts"
for x in HostAttrs:
if x[1].has_key("hostname") == 0 or \
# Fetch all the users
global PasswdAttrs;
- if PasswdAttrs == None:
- raise "No Users";
# Write out the zone file entry for each user
for x in PasswdAttrs:
except socket.gaierror, (code):
if code[0] != -2: raise
IPAdresses = []
- for addr in IPAdressesT:
- if addr[0] == socket.AF_INET: IPAdresses += [addr[1], "::ffff:"+addr[1]]
- else: IPAdresses += [addr[1]]
+ 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]
global HostAttrs
if HostAttrs == None:
- raise "No Hosts";
+ raise UDEmptyList, "No Hosts"
for x in HostAttrs:
if x[1].has_key("hostname") == 0 or \
continue;
Host = GetAttr(x,"hostname");
HostNames = [ Host ]
- SHost = Host.find(".")
- if SHost != None: HostNames += [Host[0:SHost]]
+ if Host.endswith(HostDomain):
+ HostNames.append(Host[:-(len(HostDomain)+1)])
+
+ # in the purpose field [[host|some other text]] (where some other text is optional)
+ # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
+ # file. But so that we don't have to add everything we link we can add an asterisk
+ # and say [[*... to ignore it. In order to be able to add stuff to ssh without
+ # http linking it we also support [[-hostname]] entries.
+ 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:
+ HostNames.append(m)
+ if m.endswith(HostDomain):
+ HostNames.append(m[:-(len(HostDomain)+1)])
for I in x[1]["sshRSAHostKey"]:
if mode and mode == 'authorized_keys':
# Generate the debianhosts file (list of all IP addresses)
def GenHosts(l,File):
- F = None;
+ F = None
try:
- OldMask = os.umask(0022);
- F = open(File + ".tmp","w",0644);
- os.umask(OldMask);
-
- # Fetch all the hosts
- HostNames = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"hostname=*",\
- ["hostname"]);
-
- if HostNames == None:
- raise "No Hosts";
-
- for x in HostNames:
- if x[1].has_key("hostname") == 0:
- continue;
- Host = GetAttr(x,"hostname");
- try:
- Addr = socket.gethostbyname(Host);
- F.write(Addr + "\n");
- except:
- pass
+ OldMask = os.umask(0022)
+ F = open(File + ".tmp","w",0644)
+ os.umask(OldMask)
+
+ # Fetch all the hosts
+ hostnames = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "hostname=*",
+ ["hostname"])
+
+ if hostnames == None:
+ raise UDEmptyList, "No Hosts"
+
+ seen = set()
+ for x in hostnames:
+ host = GetAttr(x,"hostname", None)
+ if host:
+ addrs = []
+ try:
+ addrs += socket.getaddrinfo(host, None, socket.AF_INET)
+ except socket.error:
+ pass
+ try:
+ addrs += socket.getaddrinfo(host, None, socket.AF_INET6)
+ except socket.error:
+ pass
+
+ for addrinfo in addrs:
+ if addrinfo[0] in (socket.AF_INET, socket.AF_INET6):
+ addr = addrinfo[4][0]
+ if addr not in seen:
+ print >> F, addrinfo[4][0]
+ seen.add(addr)
# Oops, something unspeakable happened.
except:
- Die(File,F,None);
- raise;
- Done(File,F,None);
+ Die(File,F,None)
+ raise
+ Done(File,F,None)
def GenKeyrings(l,OutDir):
for k in Keyrings:
# Fetch all the groups
GroupIDMap = {};
Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
- ["gid","gidNumber"]);
+ ["gid","gidNumber","subGroup"]);
-# Generate the GroupMap and GroupIDMap
+# Generate the SubGroupMap and GroupIDMap
for x in Attrs:
if x[1].has_key("gidNumber") == 0:
continue;
GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0]);
+ if x[1].has_key("subGroup") != 0:
+ SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"]);
# Fetch all the users
PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
"allowedHost","sshRSAAuthKey","dnsZoneEntry","cn","sn",\
"keyFingerPrint","privateSub","mailDisableMessage",\
"mailGreylisting","mailCallout","mailRBL","mailRHSBL",\
- "mailWhitelist", "sudoPassword"]);
+ "mailWhitelist", "sudoPassword", "objectClass", "accountStatus"])
+
+if PasswdAttrs is None:
+ raise UDEmptyList, "No Users"
+
# Fetch all the hosts
HostAttrs = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshRSAHostKey=*",\
- ["hostname","sshRSAHostKey"]);
+ ["hostname","sshRSAHostKey","purpose"]);
# Open the control file
if len(sys.argv) == 1:
# Generate global things
GlobalDir = GenerateDir+"/";
+GenMailDisable(l,GlobalDir+"mail-disable")
+
+for x in PasswdAttrs:
+ if IsRetired(x):
+ RetiredUsers.append(x)
+
+PasswdAttrs = filter(lambda x: not x in RetiredUsers, PasswdAttrs)
+
SSHFiles = GenSSHShadow(l);
GenAllForward(l,GlobalDir+"mail-forward.cdb");
GenMarkers(l,GlobalDir+"markers");
GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
#GenSSHKnown(l,GlobalDir+"authorized_keys", 'authorized_keys');
GenHosts(l,GlobalDir+"debianhosts");
-GenMailDisable(l,GlobalDir+"mail-disable");
GenMailBool(l,GlobalDir+"mail-greylist","mailGreylisting");
GenMailBool(l,GlobalDir+"mail-callout","mailCallout");
GenMailList(l,GlobalDir+"mail-rbl","mailRBL");
# Compatibility.
GenForward(l,GlobalDir+"forward-alias");
+PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
+
while(1):
Line = F.readline();
if Line == "":