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 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>
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
31 from userdir_ldap import *
32 from userdir_exceptions import *
45 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
47 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
48 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
49 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
50 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
51 IsDebianHost = re.compile("[a-zA-Z0-9\.]+\.debian\.org$")
52 DNSZone = ".debian.net"
53 Keyrings = ConfModule.sync_keyrings.split(":")
55 def safe_makedirs(dir):
59 if e.errno == errno.EEXIST:
68 if e.errno == errno.ENOENT:
74 return Str.translate(string.maketrans("\n\r\t", "$$$"))
76 def DoLink(From, To, File):
78 posix.remove(To + File)
81 posix.link(From + File, To + File)
83 def IsRetired(DnRecord):
85 Looks for accountStatus in the LDAP record and tries to
86 match it against one of the known retired statuses
89 status = GetAttr(DnRecord, "accountStatus", None)
96 if status == "inactive":
99 elif status == "memorial":
102 elif status == "retiring":
103 # We'll give them a few extra days over what we said
104 age = 6 * 31 * 24 * 60 * 60
106 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
116 return int(GetAttr(x, "gidNumber", 0)) == 800
120 # See if this user is in the group list
121 def IsInGroup(DnRecord):
125 # See if the primary group is in the list
126 if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0:
129 # Check the host based ACL
130 if DnRecord[1].has_key("allowedHost") != 0:
131 if CurrentHost in DnRecord[1]["allowedHost"]:
134 # See if there are supplementary groups
135 if DnRecord[1].has_key("supplementaryGid") == 0:
139 addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "uid"))
141 if Allowed.has_key(g):
145 def Die(File, F, Fdb):
151 os.remove(File + ".tmp")
155 os.remove(File + ".tdb.tmp")
159 def Done(File, F, Fdb):
162 os.rename(File + ".tmp", File)
165 os.rename(File + ".tdb.tmp", File + ".tdb")
167 # Generate the password list
168 def GenPasswd(File, HomePrefix, PwdMarker):
171 F = open(File + ".tdb.tmp", "w")
174 # Fetch all the users
178 for x in PasswdAttrs:
179 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
182 # Do not let people try to buffer overflow some busted passwd parser.
183 if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50:
186 userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
187 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\
189 GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\
190 GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\
191 GetAttr(x, "loginShell"))
193 Line = Sanitize(Line) + "\n"
194 F.write("0%u %s" % (I, Line))
195 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
196 F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line))
199 # Oops, something unspeakable happened.
205 # Return the list of users so we know which keys to export
208 # Generate the shadow list
212 OldMask = os.umask(0077)
213 F = open(File + ".tdb.tmp", "w", 0600)
216 # Fetch all the users
220 for x in PasswdAttrs:
221 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
224 Pass = GetAttr(x, "userPassword")
225 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
230 # If the account is locked, mark it as such in shadow
231 # See Debian Bug #308229 for why we set it to 1 instead of 0
232 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
233 or GetAttr(x, "userPassword").startswith("!"):
236 ShadowExpire = GetAttr(x, "shadowExpire")
238 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
239 Pass, GetAttr(x, "shadowLastChange"),\
240 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
241 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
243 Line = Sanitize(Line) + "\n"
244 F.write("0%u %s" % (I, Line))
245 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
248 # Oops, something unspeakable happened.
254 # Generate the sudo passwd file
255 def GenShadowSudo(File, untrusted):
258 OldMask = os.umask(0077)
259 F = open(File + ".tmp", "w", 0600)
262 # Fetch all the users
265 for x in PasswdAttrs:
267 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
270 if x[1].has_key('sudoPassword'):
271 for entry in x[1]['sudoPassword']:
272 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
275 uuid = Match.group(1)
276 status = Match.group(2)
277 hosts = Match.group(3)
278 cryptedpass = Match.group(4)
280 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
282 for_all = hosts == "*"
283 for_this_host = CurrentHost in hosts.split(',')
284 if not (for_all or for_this_host):
286 # ignore * passwords for untrusted hosts, but copy host specific passwords
287 if for_all and untrusted:
290 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
295 Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
296 Line = Sanitize(Line) + "\n"
297 F.write("%s" % (Line))
299 # Oops, something unspeakable happened.
305 # Generate the shadow list
307 # Fetch all the users
312 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
313 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
315 for x in PasswdAttrs:
317 if x[1].has_key("uidNumber") == 0 or \
318 x[1].has_key("sshRSAAuthKey") == 0:
321 User = GetAttr(x, "uid")
325 OldMask = os.umask(0077)
326 File = os.path.join(GlobalDir, 'userkeys', User)
327 F = open(File + ".tmp", "w", 0600)
330 for I in x[1]["sshRSAAuthKey"]:
331 MultipleLine = "%s" % I
332 MultipleLine = Sanitize(MultipleLine) + "\n"
333 F.write(MultipleLine)
336 userfiles.append(os.path.basename(File))
338 # Oops, something unspeakable happened.
341 Die(masterFileName, masterFile, None)
346 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
347 OldMask = os.umask(0077)
348 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
350 for f in userlist.keys():
351 if f not in SSHFiles:
353 # If we're not exporting their primary group, don't export
356 if userlist[f] in grouprevmap.keys():
357 grname = grouprevmap[userlist[f]]
360 if int(userlist[f]) <= 100:
361 # In these cases, look it up in the normal way so we
362 # deal with cases where, for instance, users are in group
363 # users as their primary group.
364 grname = grp.getgrgid(userlist[f])[0]
369 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])
372 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
373 # These will only be used where the username doesn't
374 # exist on the target system for some reason; hence,
375 # in those cases, the safest thing is for the file to
376 # be owned by root but group nobody. This deals with
377 # the bloody obscure case where the group fails to exist
378 # whilst the user does (in which case we want to avoid
379 # ending up with a file which is owned user:root to avoid
380 # a fairly obvious attack vector)
383 # Using the username / groupname fields avoids any need
384 # to give a shit^W^W^Wcare about the UIDoffset stuff.
388 tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
391 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
393 # add a list of groups to existing groups,
394 # including all subgroups thereof, recursively.
395 # basically this proceduces the transitive hull of the groups in
397 def addGroups(existingGroups, newGroups, uid):
398 for group in newGroups:
399 # if it's a <group>@host, split it and verify it's on the current host.
400 s = group.split('@', 1)
401 if len(s) == 2 and s[1] != CurrentHost:
405 # let's see if we handled this group already
406 if group in existingGroups:
409 if not GroupIDMap.has_key(group):
410 print "Group", group, "does not exist but", uid, "is in it"
413 existingGroups.append(group)
415 if SubGroupMap.has_key(group):
416 addGroups(existingGroups, SubGroupMap[group], uid)
418 # Generate the group list
423 F = open(File + ".tdb.tmp", "w")
425 # Generate the GroupMap
427 for x in GroupIDMap.keys():
430 # Fetch all the users
433 # Sort them into a list of groups having a set of users
434 for x in PasswdAttrs:
435 uid = GetAttr(x, "uid")
436 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
438 if x[1].has_key("supplementaryGid") == 0:
442 addGroups(supgroups, x[1]["supplementaryGid"], uid)
444 GroupMap[g].append(uid)
446 # Output the group file.
448 for x in GroupMap.keys():
450 if len(GroupMap[x]) == 0:
453 if GroupIDMap.has_key(x) == 0:
456 grouprevmap[GroupIDMap[x]] = x
458 Line = "%s:x:%u:" % (x, GroupIDMap[x])
460 for I in GroupMap[x]:
461 Line = Line + ("%s%s" % (Comma, I))
463 Line = Sanitize(Line) + "\n"
464 F.write("0%u %s" % (J, Line))
465 F.write(".%s %s" % (x, Line))
466 F.write("=%u %s" % (GroupIDMap[x], Line))
469 # Oops, something unspeakable happened.
479 for x in PasswdAttrs:
480 if x[1].has_key("emailForward") == 0:
484 x[1].pop("emailForward")
487 # Do not allow people to try to buffer overflow busted parsers
488 if len(GetAttr(x, "emailForward")) > 200:
489 x[1].pop("emailForward")
492 # Check the forwarding address
493 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
494 x[1].pop("emailForward")
496 # Generate the email forwarding list
497 def GenForward(File):
500 OldMask = os.umask(0022)
501 F = open(File + ".tmp", "w", 0644)
504 # Fetch all the users
507 # Write out the email address for each user
508 for x in PasswdAttrs:
509 if x[1].has_key("emailForward") == 0:
512 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
513 Line = Sanitize(Line) + "\n"
516 # Oops, something unspeakable happened.
522 def GenCDB(File, Users, Key):
525 OldMask = os.umask(0022)
526 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
529 # Write out the email address for each user
533 Value = GetAttr(x, Key)
534 User = GetAttr(x, "uid")
535 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
538 # Oops, something unspeakable happened.
542 if Fdb.close() != None:
543 raise "cdbmake gave an error"
545 # Generate the anon XEarth marker file
546 def GenMarkers(File):
549 F = open(File + ".tmp", "w")
551 # Fetch all the users
554 # Write out the position for each user
555 for x in PasswdAttrs:
556 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
559 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
560 Line = Sanitize(Line) + "\n"
565 # Oops, something unspeakable happened.
571 # Generate the debian-private subscription list
572 def GenPrivate(File):
575 F = open(File + ".tmp", "w")
577 # Fetch all the users
580 # Write out the position for each user
581 for x in DebianDDUsers:
582 if x[1].has_key("privateSub") == 0:
585 # If the account has no PGP key, do not write it
586 if x[1].has_key("keyFingerPrint") == 0:
590 Line = "%s"%(GetAttr(x, "privateSub"))
591 Line = Sanitize(Line) + "\n"
596 # Oops, something unspeakable happened.
602 # Generate a list of locked accounts
603 def GenDisabledAccounts(File):
606 F = open(File + ".tmp", "w")
608 # Fetch all the users
613 for x in PasswdAttrs:
614 if x[1].has_key("uidNumber") == 0:
617 Pass = GetAttr(x, "userPassword")
619 # *LK* is the reference value for a locked account
620 # password starting with ! is also a locked account
621 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
622 # Format is <login>:<reason>
623 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
624 DisabledUsers.append(x)
627 F.write(Sanitize(Line) + "\n")
630 # Oops, something unspeakable happened.
636 # Generate the list of local addresses that refuse all mail
637 def GenMailDisable(File):
640 F = open(File + ".tmp", "w")
642 # Fetch all the users
645 for x in PasswdAttrs:
648 if x[1].has_key("mailDisableMessage"):
649 Reason = GetAttr(x, "mailDisableMessage")
654 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
655 Line = Sanitize(Line) + "\n"
660 # Oops, something unspeakable happened.
666 # Generate a list of uids that should have boolean affects applied
667 def GenMailBool(File, Key):
670 F = open(File + ".tmp", "w")
672 # Fetch all the users
675 for x in PasswdAttrs:
678 if x[1].has_key(Key) == 0:
681 if GetAttr(x, Key) != "TRUE":
685 Line = "%s"%(GetAttr(x, "uid"))
686 Line = Sanitize(Line) + "\n"
691 # Oops, something unspeakable happened.
697 # Generate a list of hosts for RBL or whitelist purposes.
698 def GenMailList(File, Key):
701 F = open(File + ".tmp", "w")
703 # Fetch all the users
706 for x in PasswdAttrs:
709 if x[1].has_key(Key) == 0:
716 if Key == "mailWhitelist":
717 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
720 if re.match('^[-\w.]+$', z) == None:
724 Line = GetAttr(x, "uid")
728 if Key == "mailRHSBL":
729 Line += "/$sender_address_domain"
732 Line = Sanitize(Line) + "\n"
737 # Oops, something unspeakable happened.
743 def isRoleAccount(pwEntry):
744 if not pwEntry.has_key("objectClass"):
745 raise "pwEntry has no objectClass"
746 oc = pwEntry['objectClass']
748 i = oc.index('debianRoleAccount')
753 # Generate the DNS Zone file
757 F = open(File + ".tmp", "w")
761 # for x in HostAttrs:
762 # if x[1].has_key("hostname") == 0 or \
763 # x[1].has_key("architecture") == 0 or\
764 # x[1].has_key("sshRSAHostKey") == 0:
767 # if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
770 # DNSInfo = ExtractDNSInfo(x)
772 # for Line in DNSInfo:
774 # Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
777 # Line = "\t\t\t%s" % (Line)
778 # F.write(Line + "\n")
780 # Fetch all the users
783 # Write out the zone file entry for each user
784 for x in PasswdAttrs:
785 if x[1].has_key("dnsZoneEntry") == 0:
788 # If the account has no PGP key, do not write it
789 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
792 F.write("; %s\n"%(EmailAddress(x)))
793 for z in x[1]["dnsZoneEntry"]:
794 Split = z.lower().split()
795 if Split[1].lower() == 'in':
796 for y in range(0, len(Split)):
799 Line = " ".join(Split) + "\n"
802 Host = Split[0] + DNSZone
803 if BSMTPCheck.match(Line) != None:
804 F.write("; Has BSMTP\n")
806 # Write some identification information
807 if Split[2].lower() == "a":
808 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
809 for y in x[1]["keyFingerPrint"]:
810 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
813 Line = "; Err %s"%(str(Split))
818 F.write("; Errors\n")
821 # Oops, something unspeakable happened.
827 def ExtractDNSInfo(x):
831 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
834 if x[1].has_key("ipHostNumber"):
835 for I in x[1]["ipHostNumber"]:
836 if IsV6Addr.match(I) != None:
837 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
839 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
841 Host = GetAttr(x, "hostname")
842 Arch = GetAttr(x, "architecture")
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))
857 if x[1].has_key("machine"):
858 Mach = " " + GetAttr(x, "machine")
859 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
861 if x[1].has_key("mXRecord"):
862 for I in x[1]["mXRecord"]:
863 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
867 # Generate the DNS records
868 def GenZoneRecords(File):
871 F = open(File + ".tmp", "w")
873 # Fetch all the hosts
877 if x[1].has_key("hostname") == 0 or \
878 x[1].has_key("architecture") == 0 or\
879 x[1].has_key("sshRSAHostKey") == 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)
999 #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
1001 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1002 Line = Sanitize(Line) + "\n"
1004 # Oops, something unspeakable happened.
1010 # Generate the debianhosts file (list of all IP addresses)
1014 OldMask = os.umask(0022)
1015 F = open(File + ".tmp", "w", 0644)
1024 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1027 if not 'ipHostNumber' in x[1]:
1030 addrs = x[1]["ipHostNumber"]
1032 if addr not in seen:
1034 addr = Sanitize(addr) + "\n"
1037 # Oops, something unspeakable happened.
1043 def GenKeyrings(OutDir):
1045 shutil.copy(k, OutDir)
1047 # Connect to the ldap server
1049 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1050 Pass = F.readline().strip().split(" ")
1052 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1054 # Fetch all the groups
1056 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1057 ["gid", "gidNumber", "subGroup"])
1059 # Generate the SubGroupMap and GroupIDMap
1061 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1063 if x[1].has_key("gidNumber") == 0:
1065 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1066 if x[1].has_key("subGroup") != 0:
1067 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1069 # Fetch all the users
1070 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1071 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1072 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1073 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1074 "shadowExpire", "emailForward", "latitude", "longitude",\
1075 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1076 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1077 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1078 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1079 "mailContentInspectionAction"])
1081 if PasswdAttrs is None:
1082 raise UDEmptyList, "No Users"
1084 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1086 # Fetch all the hosts
1087 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1088 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1089 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1091 if HostAttrs == None:
1092 raise UDEmptyList, "No Hosts"
1094 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1096 # Generate global things
1097 GlobalDir = GenerateDir + "/"
1098 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1100 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1101 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1105 GenMailDisable(GlobalDir + "mail-disable")
1106 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1107 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1108 GenPrivate(GlobalDir + "debian-private")
1109 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1110 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1111 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1112 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1113 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1114 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1115 GenKeyrings(GlobalDir)
1118 GenForward(GlobalDir + "forward-alias")
1120 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1122 SSHFiles = GenSSHShadow()
1123 GenMarkers(GlobalDir + "markers")
1124 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1125 GenHosts(GlobalDir + "debianhosts")
1127 for host in HostAttrs:
1128 if not "hostname" in host[1]:
1131 CurrentHost = host[1]['hostname'][0]
1132 OutDir = GenerateDir + '/' + CurrentHost + '/'
1138 # Get the group list and convert any named groups to numerics
1140 for groupname in AllowedGroupsPreload.strip().split(" "):
1141 GroupList[groupname] = True
1142 if 'allowedGroups' in host[1]:
1143 for groupname in host[1]['allowedGroups']:
1144 GroupList[groupname] = True
1145 for groupname in GroupList.keys():
1146 if groupname in GroupIDMap:
1147 GroupList[str(GroupIDMap[groupname])] = True
1150 if 'exportOptions' in host[1]:
1151 for extra in host[1]['exportOptions']:
1152 ExtraList[extra.upper()] = True
1158 DoLink(GlobalDir, OutDir, "debianhosts")
1159 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1160 DoLink(GlobalDir, OutDir, "disabled-accounts")
1163 if 'NOPASSWD' in ExtraList:
1164 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1166 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1168 grouprevmap = GenGroup(OutDir + "group")
1169 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1171 # Now we know who we're allowing on the machine, export
1172 # the relevant ssh keys
1173 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1175 if not 'NOPASSWD' in ExtraList:
1176 GenShadow(OutDir + "shadow")
1178 # Link in global things
1179 if not 'NOMARKERS' in ExtraList:
1180 DoLink(GlobalDir, OutDir, "markers")
1181 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1182 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1183 DoLink(GlobalDir, OutDir, "mail-disable")
1184 DoLink(GlobalDir, OutDir, "mail-greylist")
1185 DoLink(GlobalDir, OutDir, "mail-callout")
1186 DoLink(GlobalDir, OutDir, "mail-rbl")
1187 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1188 DoLink(GlobalDir, OutDir, "mail-whitelist")
1189 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1190 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1191 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1194 DoLink(GlobalDir, OutDir, "forward-alias")
1196 if 'DNS' in ExtraList:
1197 GenDNS(OutDir + "dns-zone")
1198 GenZoneRecords(OutDir + "dns-sshfp")
1200 if 'AUTHKEYS' in ExtraList:
1201 DoLink(GlobalDir, OutDir, "authorized_keys")
1203 if 'BSMTP' in ExtraList:
1204 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1206 if 'PRIVATE' in ExtraList:
1207 DoLink(GlobalDir, OutDir, "debian-private")
1209 if 'KEYRING' in ExtraList:
1211 DoLink(GlobalDir, OutDir, os.path.basename(k))
1215 posix.remove(OutDir + os.path.basename(k))
1221 # vim:set shiftwidth=3: