3 # Generates passwd, shadow and group files from the ldap directory.
5 # Copyright (c) 2000-2001 Jason Gunthorpe <jgg@debian.org>
6 # Copyright (c) 2003-2004 James Troup <troup@debian.org>
7 # Copyright (c) 2004-2005,7 Joey Schulze <joey@infodrom.org>
8 # Copyright (c) 2001-2007 Ryan Murray <rmurray@debian.org>
9 # Copyright (c) 2008,2009,2010 Peter Palfrader <peter@palfrader.org>
10 # Copyright (c) 2008 Andreas Barth <aba@not.so.argh.org>
11 # Copyright (c) 2008 Mark Hymers <mhy@debian.org>
12 # Copyright (c) 2008 Luk Claes <luk@debian.org>
13 # Copyright (c) 2008 Thomas Viehmann <tv@beamnet.de>
14 # Copyright (c) 2009 Stephen Gran <steve@lobefin.net>
15 # Copyright (c) 2010 Helmut Grohne <helmut@subdivi.de>
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with this program; if not, write to the Free Software
29 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
32 from userdir_ldap import *
33 from userdir_exceptions import *
35 from cStringIO import StringIO
37 from StringIO import StringIO
50 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
52 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
53 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
54 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
55 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
56 IsDebianHost = re.compile(ConfModule.dns_hostmatch)
57 DNSZone = ".debian.net"
58 Keyrings = ConfModule.sync_keyrings.split(":")
60 def safe_makedirs(dir):
64 if e.errno == errno.EEXIST:
73 if e.errno == errno.ENOENT:
79 return Str.translate(string.maketrans("\n\r\t", "$$$"))
81 def DoLink(From, To, File):
83 posix.remove(To + File)
86 posix.link(From + File, To + File)
88 def IsRetired(DnRecord):
90 Looks for accountStatus in the LDAP record and tries to
91 match it against one of the known retired statuses
94 status = GetAttr(DnRecord, "accountStatus", None)
101 if status == "inactive":
104 elif status == "memorial":
107 elif status == "retiring":
108 # We'll give them a few extra days over what we said
109 age = 6 * 31 * 24 * 60 * 60
111 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
121 return int(GetAttr(x, "gidNumber", 0)) == 800
125 # See if this user is in the group list
126 def IsInGroup(DnRecord):
130 # See if the primary group is in the list
131 if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0:
134 # Check the host based ACL
135 if DnRecord[1].has_key("allowedHost") != 0:
136 if CurrentHost in DnRecord[1]["allowedHost"]:
139 # See if there are supplementary groups
140 if DnRecord[1].has_key("supplementaryGid") == 0:
144 addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "uid"))
146 if Allowed.has_key(g):
150 def Die(File, F, Fdb):
156 os.remove(File + ".tmp")
160 os.remove(File + ".tdb.tmp")
164 def Done(File, F, Fdb):
167 os.rename(File + ".tmp", File)
170 os.rename(File + ".tdb.tmp", File + ".tdb")
172 # Generate the password list
173 def GenPasswd(File, HomePrefix, PwdMarker):
176 F = open(File + ".tdb.tmp", "w")
179 # Fetch all the users
183 for x in PasswdAttrs:
184 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
187 # Do not let people try to buffer overflow some busted passwd parser.
188 if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50:
191 userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
192 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\
194 GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\
195 GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\
196 GetAttr(x, "loginShell"))
198 Line = Sanitize(Line) + "\n"
199 F.write("0%u %s" % (I, Line))
200 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
201 F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line))
204 # Oops, something unspeakable happened.
210 # Return the list of users so we know which keys to export
213 # Generate the shadow list
217 OldMask = os.umask(0077)
218 F = open(File + ".tdb.tmp", "w", 0600)
221 # Fetch all the users
225 for x in PasswdAttrs:
226 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
229 Pass = GetAttr(x, "userPassword")
230 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
235 # If the account is locked, mark it as such in shadow
236 # See Debian Bug #308229 for why we set it to 1 instead of 0
237 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
238 or GetAttr(x, "userPassword").startswith("!"):
241 ShadowExpire = GetAttr(x, "shadowExpire")
243 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
244 Pass, GetAttr(x, "shadowLastChange"),\
245 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
246 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
248 Line = Sanitize(Line) + "\n"
249 F.write("0%u %s" % (I, Line))
250 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
253 # Oops, something unspeakable happened.
259 # Generate the sudo passwd file
260 def GenShadowSudo(File, untrusted):
263 OldMask = os.umask(0077)
264 F = open(File + ".tmp", "w", 0600)
267 # Fetch all the users
270 for x in PasswdAttrs:
272 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
275 if x[1].has_key('sudoPassword'):
276 for entry in x[1]['sudoPassword']:
277 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
280 uuid = Match.group(1)
281 status = Match.group(2)
282 hosts = Match.group(3)
283 cryptedpass = Match.group(4)
285 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
287 for_all = hosts == "*"
288 for_this_host = CurrentHost in hosts.split(',')
289 if not (for_all or for_this_host):
291 # ignore * passwords for untrusted hosts, but copy host specific passwords
292 if for_all and untrusted:
295 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
300 Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
301 Line = Sanitize(Line) + "\n"
302 F.write("%s" % (Line))
304 # Oops, something unspeakable happened.
310 # Generate the shadow list
312 # Fetch all the users
317 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
318 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
320 for x in PasswdAttrs:
322 if x[1].has_key("uidNumber") == 0 or \
323 x[1].has_key("sshRSAAuthKey") == 0:
326 User = GetAttr(x, "uid")
330 OldMask = os.umask(0077)
331 File = os.path.join(GlobalDir, 'userkeys', User)
332 F = open(File + ".tmp", "w", 0600)
335 for I in x[1]["sshRSAAuthKey"]:
336 MultipleLine = "%s" % I
337 MultipleLine = Sanitize(MultipleLine) + "\n"
338 F.write(MultipleLine)
341 userfiles.append(os.path.basename(File))
343 # Oops, something unspeakable happened.
346 # As neither masterFileName nor masterFile are defined at any point
347 # this will raise a NameError.
348 Die(masterFileName, masterFile, None)
353 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
354 OldMask = os.umask(0077)
355 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
357 for f in userlist.keys():
358 if f not in SSHFiles:
360 # If we're not exporting their primary group, don't export
363 if userlist[f] in grouprevmap.keys():
364 grname = grouprevmap[userlist[f]]
367 if int(userlist[f]) <= 100:
368 # In these cases, look it up in the normal way so we
369 # deal with cases where, for instance, users are in group
370 # users as their primary group.
371 grname = grp.getgrgid(userlist[f])[0]
376 print "User %s is supposed to have their key exported to host %s but their primary group (gid: %d) isn't in LDAP" % (f, CurrentHost, userlist[f])
379 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
380 # These will only be used where the username doesn't
381 # exist on the target system for some reason; hence,
382 # in those cases, the safest thing is for the file to
383 # be owned by root but group nobody. This deals with
384 # the bloody obscure case where the group fails to exist
385 # whilst the user does (in which case we want to avoid
386 # ending up with a file which is owned user:root to avoid
387 # a fairly obvious attack vector)
390 # Using the username / groupname fields avoids any need
391 # to give a shit^W^W^Wcare about the UIDoffset stuff.
396 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
398 for line in contents.splitlines():
399 if line.startswith("allowed_hosts=") and ' ' in line:
400 machines, line = line.split('=', 1)[1].split(' ', 1)
401 if CurrentHost not in machines.split(','):
402 continue # skip this key
405 continue # no keys for this host
406 contents = "\n".join(lines) + "\n"
407 to.size = len(contents)
408 tf.addfile(to, StringIO(contents))
411 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
413 # add a list of groups to existing groups,
414 # including all subgroups thereof, recursively.
415 # basically this proceduces the transitive hull of the groups in
417 def addGroups(existingGroups, newGroups, uid):
418 for group in newGroups:
419 # if it's a <group>@host, split it and verify it's on the current host.
420 s = group.split('@', 1)
421 if len(s) == 2 and s[1] != CurrentHost:
425 # let's see if we handled this group already
426 if group in existingGroups:
429 if not GroupIDMap.has_key(group):
430 print "Group", group, "does not exist but", uid, "is in it"
433 existingGroups.append(group)
435 if SubGroupMap.has_key(group):
436 addGroups(existingGroups, SubGroupMap[group], uid)
438 # Generate the group list
443 F = open(File + ".tdb.tmp", "w")
445 # Generate the GroupMap
447 for x in GroupIDMap.keys():
449 GroupHasPrimaryMembers = {}
451 # Fetch all the users
454 # Sort them into a list of groups having a set of users
455 for x in PasswdAttrs:
456 uid = GetAttr(x, "uid")
457 if 'gidNumber' in x[1]:
458 GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
459 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
461 if x[1].has_key("supplementaryGid") == 0:
465 addGroups(supgroups, x[1]["supplementaryGid"], uid)
467 GroupMap[g].append(uid)
469 # Output the group file.
471 for x in GroupMap.keys():
472 if GroupIDMap.has_key(x) == 0:
475 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
478 grouprevmap[GroupIDMap[x]] = x
480 Line = "%s:x:%u:" % (x, GroupIDMap[x])
482 for I in GroupMap[x]:
483 Line = Line + ("%s%s" % (Comma, I))
485 Line = Sanitize(Line) + "\n"
486 F.write("0%u %s" % (J, Line))
487 F.write(".%s %s" % (x, Line))
488 F.write("=%u %s" % (GroupIDMap[x], Line))
491 # Oops, something unspeakable happened.
501 for x in PasswdAttrs:
502 if x[1].has_key("emailForward") == 0:
506 x[1].pop("emailForward")
509 # Do not allow people to try to buffer overflow busted parsers
510 if len(GetAttr(x, "emailForward")) > 200:
511 x[1].pop("emailForward")
514 # Check the forwarding address
515 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
516 x[1].pop("emailForward")
518 # Generate the email forwarding list
519 def GenForward(File):
522 OldMask = os.umask(0022)
523 F = open(File + ".tmp", "w", 0644)
526 # Fetch all the users
529 # Write out the email address for each user
530 for x in PasswdAttrs:
531 if x[1].has_key("emailForward") == 0:
534 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
535 Line = Sanitize(Line) + "\n"
538 # Oops, something unspeakable happened.
544 def GenCDB(File, Users, Key):
547 OldMask = os.umask(0022)
548 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
551 # Write out the email address for each user
555 Value = GetAttr(x, Key)
556 User = GetAttr(x, "uid")
557 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
560 # Oops, something unspeakable happened.
564 if Fdb.close() != None:
565 raise "cdbmake gave an error"
567 # Generate the anon XEarth marker file
568 def GenMarkers(File):
571 F = open(File + ".tmp", "w")
573 # Fetch all the users
576 # Write out the position for each user
577 for x in PasswdAttrs:
578 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
581 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
582 Line = Sanitize(Line) + "\n"
587 # Oops, something unspeakable happened.
593 # Generate the debian-private subscription list
594 def GenPrivate(File):
597 F = open(File + ".tmp", "w")
599 # Fetch all the users
602 # Write out the position for each user
603 for x in DebianDDUsers:
604 if x[1].has_key("privateSub") == 0:
607 # If the account has no PGP key, do not write it
608 if x[1].has_key("keyFingerPrint") == 0:
612 Line = "%s"%(GetAttr(x, "privateSub"))
613 Line = Sanitize(Line) + "\n"
618 # Oops, something unspeakable happened.
624 # Generate a list of locked accounts
625 def GenDisabledAccounts(File):
628 F = open(File + ".tmp", "w")
630 # Fetch all the users
635 for x in PasswdAttrs:
636 if x[1].has_key("uidNumber") == 0:
639 Pass = GetAttr(x, "userPassword")
641 # *LK* is the reference value for a locked account
642 # password starting with ! is also a locked account
643 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
644 # Format is <login>:<reason>
645 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
646 DisabledUsers.append(x)
649 F.write(Sanitize(Line) + "\n")
652 # Oops, something unspeakable happened.
658 # Generate the list of local addresses that refuse all mail
659 def GenMailDisable(File):
662 F = open(File + ".tmp", "w")
664 # Fetch all the users
667 for x in PasswdAttrs:
670 if x[1].has_key("mailDisableMessage"):
671 Reason = GetAttr(x, "mailDisableMessage")
676 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
677 Line = Sanitize(Line) + "\n"
682 # Oops, something unspeakable happened.
688 # Generate a list of uids that should have boolean affects applied
689 def GenMailBool(File, Key):
692 F = open(File + ".tmp", "w")
694 # Fetch all the users
697 for x in PasswdAttrs:
700 if x[1].has_key(Key) == 0:
703 if GetAttr(x, Key) != "TRUE":
707 Line = "%s"%(GetAttr(x, "uid"))
708 Line = Sanitize(Line) + "\n"
713 # Oops, something unspeakable happened.
719 # Generate a list of hosts for RBL or whitelist purposes.
720 def GenMailList(File, Key):
723 F = open(File + ".tmp", "w")
725 # Fetch all the users
728 for x in PasswdAttrs:
731 if x[1].has_key(Key) == 0:
738 if Key == "mailWhitelist":
739 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
742 if re.match('^[-\w.]+$', z) == None:
746 Line = GetAttr(x, "uid")
750 if Key == "mailRHSBL":
751 Line += "/$sender_address_domain"
754 Line = Sanitize(Line) + "\n"
759 # Oops, something unspeakable happened.
765 def isRoleAccount(pwEntry):
766 if not pwEntry.has_key("objectClass"):
767 raise "pwEntry has no objectClass"
768 oc = pwEntry['objectClass']
770 i = oc.index('debianRoleAccount')
775 # Generate the DNS Zone file
779 F = open(File + ".tmp", "w")
781 # Fetch all the users
784 # Write out the zone file entry for each user
785 for x in PasswdAttrs:
786 if x[1].has_key("dnsZoneEntry") == 0:
789 # If the account has no PGP key, do not write it
790 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
793 F.write("; %s\n"%(EmailAddress(x)))
794 for z in x[1]["dnsZoneEntry"]:
795 Split = z.lower().split()
796 if Split[1].lower() == 'in':
797 for y in range(0, len(Split)):
800 Line = " ".join(Split) + "\n"
803 Host = Split[0] + DNSZone
804 if BSMTPCheck.match(Line) != None:
805 F.write("; Has BSMTP\n")
807 # Write some identification information
808 if Split[2].lower() == "a":
809 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
810 for y in x[1]["keyFingerPrint"]:
811 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
814 Line = "; Err %s"%(str(Split))
819 F.write("; Errors\n")
822 # Oops, something unspeakable happened.
828 def ExtractDNSInfo(x):
832 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
835 if x[1].has_key("ipHostNumber"):
836 for I in x[1]["ipHostNumber"]:
837 if IsV6Addr.match(I) != None:
838 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
840 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
844 if 'sshRSAHostKey' in x[1]:
845 for I in x[1]["sshRSAHostKey"]:
847 if Split[0] == 'ssh-rsa':
849 if Split[0] == 'ssh-dss':
851 if Algorithm == None:
853 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
854 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
856 if 'architecture' in x[1]:
857 Arch = GetAttr(x, "architecture")
859 if x[1].has_key("machine"):
860 Mach = " " + GetAttr(x, "machine")
861 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
863 if x[1].has_key("mXRecord"):
864 for I in x[1]["mXRecord"]:
865 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
869 # Generate the DNS records
870 def GenZoneRecords(File):
873 F = open(File + ".tmp", "w")
875 # Fetch all the hosts
879 if x[1].has_key("hostname") == 0:
882 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
885 DNSInfo = ExtractDNSInfo(x)
889 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
892 Line = "\t\t\t%s" % (Line)
896 # Oops, something unspeakable happened.
902 # Generate the BSMTP file
903 def GenBSMTP(File, HomePrefix):
906 F = open(File + ".tmp", "w")
908 # Fetch all the users
911 # Write out the zone file entry for each user
912 for x in PasswdAttrs:
913 if x[1].has_key("dnsZoneEntry") == 0:
916 # If the account has no PGP key, do not write it
917 if x[1].has_key("keyFingerPrint") == 0:
920 for z in x[1]["dnsZoneEntry"]:
921 Split = z.lower().split()
922 if Split[1].lower() == 'in':
923 for y in range(0, len(Split)):
926 Line = " ".join(Split) + "\n"
928 Host = Split[0] + DNSZone
929 if BSMTPCheck.match(Line) != None:
930 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
931 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
934 F.write("; Errors\n")
937 # Oops, something unspeakable happened.
943 def HostToIP(Host, mapped=True):
947 if Host[1].has_key("ipHostNumber"):
948 for addr in Host[1]["ipHostNumber"]:
949 IPAdresses.append(addr)
950 if IsV6Addr.match(addr) is None and mapped == "True":
951 IPAdresses.append("::ffff:"+addr)
955 # Generate the ssh known hosts file
956 def GenSSHKnown(File, mode=None):
959 OldMask = os.umask(0022)
960 F = open(File + ".tmp", "w", 0644)
966 if x[1].has_key("hostname") == 0 or \
967 x[1].has_key("sshRSAHostKey") == 0:
969 Host = GetAttr(x, "hostname")
971 if Host.endswith(HostDomain):
972 HostNames.append(Host[:-(len(HostDomain) + 1)])
974 # in the purpose field [[host|some other text]] (where some other text is optional)
975 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
976 # file. But so that we don't have to add everything we link we can add an asterisk
977 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
978 # http linking it we also support [[-hostname]] entries.
979 for i in x[1].get("purpose", []):
980 m = PurposeHostField.match(i)
983 # we ignore [[*..]] entries
984 if m.startswith('*'):
986 if m.startswith('-'):
990 if m.endswith(HostDomain):
991 HostNames.append(m[:-(len(HostDomain) + 1)])
993 for I in x[1]["sshRSAHostKey"]:
994 if mode and mode == 'authorized_keys':
996 if 'sshdistAuthKeysHost' in x[1]:
997 hosts += x[1]['sshdistAuthKeysHost']
998 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)
1000 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1001 Line = Sanitize(Line) + "\n"
1003 # Oops, something unspeakable happened.
1009 # Generate the debianhosts file (list of all IP addresses)
1013 OldMask = os.umask(0022)
1014 F = open(File + ".tmp", "w", 0644)
1023 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1026 if not 'ipHostNumber' in x[1]:
1029 addrs = x[1]["ipHostNumber"]
1031 if addr not in seen:
1033 addr = Sanitize(addr) + "\n"
1036 # Oops, something unspeakable happened.
1042 def GenKeyrings(OutDir):
1044 shutil.copy(k, OutDir)
1046 # Connect to the ldap server
1048 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1049 Pass = F.readline().strip().split(" ")
1051 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1053 # Fetch all the groups
1055 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1056 ["gid", "gidNumber", "subGroup"])
1058 # Generate the SubGroupMap and GroupIDMap
1060 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1062 if x[1].has_key("gidNumber") == 0:
1064 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1065 if x[1].has_key("subGroup") != 0:
1066 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1068 # Fetch all the users
1069 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1070 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1071 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1072 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1073 "shadowExpire", "emailForward", "latitude", "longitude",\
1074 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1075 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1076 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1077 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1078 "mailContentInspectionAction"])
1080 if PasswdAttrs is None:
1081 raise UDEmptyList, "No Users"
1083 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1085 # Fetch all the hosts
1086 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1087 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1088 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1090 if HostAttrs == None:
1091 raise UDEmptyList, "No Hosts"
1093 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1095 # Generate global things
1096 GlobalDir = GenerateDir + "/"
1097 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1099 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1100 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1104 GenMailDisable(GlobalDir + "mail-disable")
1105 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1106 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1107 GenPrivate(GlobalDir + "debian-private")
1108 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1109 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1110 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1111 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1112 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1113 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1114 GenKeyrings(GlobalDir)
1117 GenForward(GlobalDir + "forward-alias")
1119 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1121 SSHFiles = GenSSHShadow()
1122 GenMarkers(GlobalDir + "markers")
1123 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1124 GenHosts(GlobalDir + "debianhosts")
1126 for host in HostAttrs:
1127 if not "hostname" in host[1]:
1130 CurrentHost = host[1]['hostname'][0]
1131 OutDir = GenerateDir + '/' + CurrentHost + '/'
1137 # Get the group list and convert any named groups to numerics
1139 for groupname in AllowedGroupsPreload.strip().split(" "):
1140 GroupList[groupname] = True
1141 if 'allowedGroups' in host[1]:
1142 for groupname in host[1]['allowedGroups']:
1143 GroupList[groupname] = True
1144 for groupname in GroupList.keys():
1145 if groupname in GroupIDMap:
1146 GroupList[str(GroupIDMap[groupname])] = True
1149 if 'exportOptions' in host[1]:
1150 for extra in host[1]['exportOptions']:
1151 ExtraList[extra.upper()] = True
1157 DoLink(GlobalDir, OutDir, "debianhosts")
1158 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1159 DoLink(GlobalDir, OutDir, "disabled-accounts")
1162 if 'NOPASSWD' in ExtraList:
1163 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1165 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1167 grouprevmap = GenGroup(OutDir + "group")
1168 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1170 # Now we know who we're allowing on the machine, export
1171 # the relevant ssh keys
1172 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1174 if not 'NOPASSWD' in ExtraList:
1175 GenShadow(OutDir + "shadow")
1177 # Link in global things
1178 if not 'NOMARKERS' in ExtraList:
1179 DoLink(GlobalDir, OutDir, "markers")
1180 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1181 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1182 DoLink(GlobalDir, OutDir, "mail-disable")
1183 DoLink(GlobalDir, OutDir, "mail-greylist")
1184 DoLink(GlobalDir, OutDir, "mail-callout")
1185 DoLink(GlobalDir, OutDir, "mail-rbl")
1186 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1187 DoLink(GlobalDir, OutDir, "mail-whitelist")
1188 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1189 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1190 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1193 DoLink(GlobalDir, OutDir, "forward-alias")
1195 if 'DNS' in ExtraList:
1196 GenDNS(OutDir + "dns-zone")
1197 GenZoneRecords(OutDir + "dns-sshfp")
1199 if 'AUTHKEYS' in ExtraList:
1200 DoLink(GlobalDir, OutDir, "authorized_keys")
1202 if 'BSMTP' in ExtraList:
1203 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1205 if 'PRIVATE' in ExtraList:
1206 DoLink(GlobalDir, OutDir, "debian-private")
1208 if 'KEYRING' in ExtraList:
1210 DoLink(GlobalDir, OutDir, os.path.basename(k))
1214 posix.remove(OutDir + os.path.basename(k))
1220 # vim:set shiftwidth=3: