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
785 # Write out the zone file entry for each user
786 for x in PasswdAttrs:
787 if x[1].has_key("dnsZoneEntry") == 0:
790 # If the account has no PGP key, do not write it
791 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
794 F.write("; %s\n"%(EmailAddress(x)))
795 for z in x[1]["dnsZoneEntry"]:
796 Split = z.lower().split()
797 if Split[1].lower() == 'in':
798 for y in range(0, len(Split)):
801 Line = " ".join(Split) + "\n"
804 Host = Split[0] + DNSZone
805 if BSMTPCheck.match(Line) != None:
806 F.write("; Has BSMTP\n")
808 # Write some identification information
809 if not RRs.has_key(Host):
810 if Split[2].lower() in ["a", "aaaa"]:
811 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
812 for y in x[1]["keyFingerPrint"]:
813 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
817 Line = "; Err %s"%(str(Split))
822 F.write("; Errors\n")
825 # Oops, something unspeakable happened.
831 def ExtractDNSInfo(x):
835 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
838 if x[1].has_key("ipHostNumber"):
839 for I in x[1]["ipHostNumber"]:
840 if IsV6Addr.match(I) != None:
841 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
843 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
847 if 'sshRSAHostKey' in x[1]:
848 for I in x[1]["sshRSAHostKey"]:
850 if Split[0] == 'ssh-rsa':
852 if Split[0] == 'ssh-dss':
854 if Algorithm == None:
856 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
857 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
859 if 'architecture' in x[1]:
860 Arch = GetAttr(x, "architecture")
862 if x[1].has_key("machine"):
863 Mach = " " + GetAttr(x, "machine")
864 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
866 if x[1].has_key("mXRecord"):
867 for I in x[1]["mXRecord"]:
868 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
872 # Generate the DNS records
873 def GenZoneRecords(File):
876 F = open(File + ".tmp", "w")
878 # Fetch all the hosts
882 if x[1].has_key("hostname") == 0:
885 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
888 DNSInfo = ExtractDNSInfo(x)
892 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
895 Line = "\t\t\t%s" % (Line)
899 # Oops, something unspeakable happened.
905 # Generate the BSMTP file
906 def GenBSMTP(File, HomePrefix):
909 F = open(File + ".tmp", "w")
911 # Fetch all the users
914 # Write out the zone file entry for each user
915 for x in PasswdAttrs:
916 if x[1].has_key("dnsZoneEntry") == 0:
919 # If the account has no PGP key, do not write it
920 if x[1].has_key("keyFingerPrint") == 0:
923 for z in x[1]["dnsZoneEntry"]:
924 Split = z.lower().split()
925 if Split[1].lower() == 'in':
926 for y in range(0, len(Split)):
929 Line = " ".join(Split) + "\n"
931 Host = Split[0] + DNSZone
932 if BSMTPCheck.match(Line) != None:
933 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
934 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
937 F.write("; Errors\n")
940 # Oops, something unspeakable happened.
946 def HostToIP(Host, mapped=True):
950 if Host[1].has_key("ipHostNumber"):
951 for addr in Host[1]["ipHostNumber"]:
952 IPAdresses.append(addr)
953 if IsV6Addr.match(addr) is None and mapped == "True":
954 IPAdresses.append("::ffff:"+addr)
958 # Generate the ssh known hosts file
959 def GenSSHKnown(File, mode=None):
962 OldMask = os.umask(0022)
963 F = open(File + ".tmp", "w", 0644)
969 if x[1].has_key("hostname") == 0 or \
970 x[1].has_key("sshRSAHostKey") == 0:
972 Host = GetAttr(x, "hostname")
974 if Host.endswith(HostDomain):
975 HostNames.append(Host[:-(len(HostDomain) + 1)])
977 # in the purpose field [[host|some other text]] (where some other text is optional)
978 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
979 # file. But so that we don't have to add everything we link we can add an asterisk
980 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
981 # http linking it we also support [[-hostname]] entries.
982 for i in x[1].get("purpose", []):
983 m = PurposeHostField.match(i)
986 # we ignore [[*..]] entries
987 if m.startswith('*'):
989 if m.startswith('-'):
993 if m.endswith(HostDomain):
994 HostNames.append(m[:-(len(HostDomain) + 1)])
996 for I in x[1]["sshRSAHostKey"]:
997 if mode and mode == 'authorized_keys':
999 if 'sshdistAuthKeysHost' in x[1]:
1000 hosts += x[1]['sshdistAuthKeysHost']
1001 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)
1003 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1004 Line = Sanitize(Line) + "\n"
1006 # Oops, something unspeakable happened.
1012 # Generate the debianhosts file (list of all IP addresses)
1016 OldMask = os.umask(0022)
1017 F = open(File + ".tmp", "w", 0644)
1026 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1029 if not 'ipHostNumber' in x[1]:
1032 addrs = x[1]["ipHostNumber"]
1034 if addr not in seen:
1036 addr = Sanitize(addr) + "\n"
1039 # Oops, something unspeakable happened.
1045 def GenKeyrings(OutDir):
1047 shutil.copy(k, OutDir)
1049 # Connect to the ldap server
1051 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1052 Pass = F.readline().strip().split(" ")
1054 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1056 # Fetch all the groups
1058 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1059 ["gid", "gidNumber", "subGroup"])
1061 # Generate the SubGroupMap and GroupIDMap
1063 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1065 if x[1].has_key("gidNumber") == 0:
1067 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1068 if x[1].has_key("subGroup") != 0:
1069 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1071 # Fetch all the users
1072 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1073 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1074 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1075 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1076 "shadowExpire", "emailForward", "latitude", "longitude",\
1077 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1078 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1079 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1080 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1081 "mailContentInspectionAction"])
1083 if PasswdAttrs is None:
1084 raise UDEmptyList, "No Users"
1086 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1088 # Fetch all the hosts
1089 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1090 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1091 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1093 if HostAttrs == None:
1094 raise UDEmptyList, "No Hosts"
1096 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1098 # Generate global things
1099 GlobalDir = GenerateDir + "/"
1100 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1102 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1103 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1107 GenMailDisable(GlobalDir + "mail-disable")
1108 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1109 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1110 GenPrivate(GlobalDir + "debian-private")
1111 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1112 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1113 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1114 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1115 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1116 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1117 GenKeyrings(GlobalDir)
1120 GenForward(GlobalDir + "forward-alias")
1122 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1124 SSHFiles = GenSSHShadow()
1125 GenMarkers(GlobalDir + "markers")
1126 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1127 GenHosts(GlobalDir + "debianhosts")
1129 for host in HostAttrs:
1130 if not "hostname" in host[1]:
1133 CurrentHost = host[1]['hostname'][0]
1134 OutDir = GenerateDir + '/' + CurrentHost + '/'
1140 # Get the group list and convert any named groups to numerics
1142 for groupname in AllowedGroupsPreload.strip().split(" "):
1143 GroupList[groupname] = True
1144 if 'allowedGroups' in host[1]:
1145 for groupname in host[1]['allowedGroups']:
1146 GroupList[groupname] = True
1147 for groupname in GroupList.keys():
1148 if groupname in GroupIDMap:
1149 GroupList[str(GroupIDMap[groupname])] = True
1152 if 'exportOptions' in host[1]:
1153 for extra in host[1]['exportOptions']:
1154 ExtraList[extra.upper()] = True
1160 DoLink(GlobalDir, OutDir, "debianhosts")
1161 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1162 DoLink(GlobalDir, OutDir, "disabled-accounts")
1165 if 'NOPASSWD' in ExtraList:
1166 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1168 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1170 grouprevmap = GenGroup(OutDir + "group")
1171 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1173 # Now we know who we're allowing on the machine, export
1174 # the relevant ssh keys
1175 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1177 if not 'NOPASSWD' in ExtraList:
1178 GenShadow(OutDir + "shadow")
1180 # Link in global things
1181 if not 'NOMARKERS' in ExtraList:
1182 DoLink(GlobalDir, OutDir, "markers")
1183 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1184 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1185 DoLink(GlobalDir, OutDir, "mail-disable")
1186 DoLink(GlobalDir, OutDir, "mail-greylist")
1187 DoLink(GlobalDir, OutDir, "mail-callout")
1188 DoLink(GlobalDir, OutDir, "mail-rbl")
1189 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1190 DoLink(GlobalDir, OutDir, "mail-whitelist")
1191 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1192 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1193 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1196 DoLink(GlobalDir, OutDir, "forward-alias")
1198 if 'DNS' in ExtraList:
1199 GenDNS(OutDir + "dns-zone")
1200 GenZoneRecords(OutDir + "dns-sshfp")
1202 if 'AUTHKEYS' in ExtraList:
1203 DoLink(GlobalDir, OutDir, "authorized_keys")
1205 if 'BSMTP' in ExtraList:
1206 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1208 if 'PRIVATE' in ExtraList:
1209 DoLink(GlobalDir, OutDir, "debian-private")
1211 if 'KEYRING' in ExtraList:
1213 DoLink(GlobalDir, OutDir, os.path.basename(k))
1217 posix.remove(OutDir + os.path.basename(k))
1223 # vim:set shiftwidth=3: