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 isSSHFP = re.compile("^\s*IN\s+SSHFP")
58 DNSZone = ".debian.net"
59 Keyrings = ConfModule.sync_keyrings.split(":")
61 def safe_makedirs(dir):
65 if e.errno == errno.EEXIST:
74 if e.errno == errno.ENOENT:
80 return Str.translate(string.maketrans("\n\r\t", "$$$"))
82 def DoLink(From, To, File):
84 posix.remove(To + File)
87 posix.link(From + File, To + File)
89 def IsRetired(DnRecord):
91 Looks for accountStatus in the LDAP record and tries to
92 match it against one of the known retired statuses
95 status = GetAttr(DnRecord, "accountStatus", None)
102 if status == "inactive":
105 elif status == "memorial":
108 elif status == "retiring":
109 # We'll give them a few extra days over what we said
110 age = 6 * 31 * 24 * 60 * 60
112 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
122 return int(GetAttr(x, "gidNumber", 0)) == 800
126 # See if this user is in the group list
127 def IsInGroup(DnRecord):
131 # See if the primary group is in the list
132 if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0:
135 # Check the host based ACL
136 if DnRecord[1].has_key("allowedHost") != 0:
137 if CurrentHost in DnRecord[1]["allowedHost"]:
140 # See if there are supplementary groups
141 if DnRecord[1].has_key("supplementaryGid") == 0:
145 addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "uid"))
147 if Allowed.has_key(g):
151 def Die(File, F, Fdb):
157 os.remove(File + ".tmp")
161 os.remove(File + ".tdb.tmp")
165 def Done(File, F, Fdb):
168 os.rename(File + ".tmp", File)
171 os.rename(File + ".tdb.tmp", File + ".tdb")
173 # Generate the password list
174 def GenPasswd(File, HomePrefix, PwdMarker):
177 F = open(File + ".tdb.tmp", "w")
180 # Fetch all the users
184 for x in PasswdAttrs:
185 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
188 # Do not let people try to buffer overflow some busted passwd parser.
189 if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50:
192 userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
193 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\
195 GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\
196 GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\
197 GetAttr(x, "loginShell"))
199 Line = Sanitize(Line) + "\n"
200 F.write("0%u %s" % (I, Line))
201 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
202 F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line))
205 # Oops, something unspeakable happened.
211 # Return the list of users so we know which keys to export
214 # Generate the shadow list
218 OldMask = os.umask(0077)
219 F = open(File + ".tdb.tmp", "w", 0600)
222 # Fetch all the users
226 for x in PasswdAttrs:
227 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
230 Pass = GetAttr(x, "userPassword")
231 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
236 # If the account is locked, mark it as such in shadow
237 # See Debian Bug #308229 for why we set it to 1 instead of 0
238 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
239 or GetAttr(x, "userPassword").startswith("!"):
242 ShadowExpire = GetAttr(x, "shadowExpire")
244 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
245 Pass, GetAttr(x, "shadowLastChange"),\
246 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
247 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
249 Line = Sanitize(Line) + "\n"
250 F.write("0%u %s" % (I, Line))
251 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
254 # Oops, something unspeakable happened.
260 # Generate the sudo passwd file
261 def GenShadowSudo(File, untrusted):
264 OldMask = os.umask(0077)
265 F = open(File + ".tmp", "w", 0600)
268 # Fetch all the users
271 for x in PasswdAttrs:
273 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
276 if x[1].has_key('sudoPassword'):
277 for entry in x[1]['sudoPassword']:
278 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
281 uuid = Match.group(1)
282 status = Match.group(2)
283 hosts = Match.group(3)
284 cryptedpass = Match.group(4)
286 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
288 for_all = hosts == "*"
289 for_this_host = CurrentHost in hosts.split(',')
290 if not (for_all or for_this_host):
292 # ignore * passwords for untrusted hosts, but copy host specific passwords
293 if for_all and untrusted:
296 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
301 Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
302 Line = Sanitize(Line) + "\n"
303 F.write("%s" % (Line))
305 # Oops, something unspeakable happened.
311 # Generate the shadow list
313 # Fetch all the users
318 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
319 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
321 for x in PasswdAttrs:
323 if x[1].has_key("uidNumber") == 0 or \
324 x[1].has_key("sshRSAAuthKey") == 0:
327 User = GetAttr(x, "uid")
331 OldMask = os.umask(0077)
332 File = os.path.join(GlobalDir, 'userkeys', User)
333 F = open(File + ".tmp", "w", 0600)
336 for I in x[1]["sshRSAAuthKey"]:
337 MultipleLine = "%s" % I
338 MultipleLine = Sanitize(MultipleLine) + "\n"
339 F.write(MultipleLine)
342 userfiles.append(os.path.basename(File))
344 # Oops, something unspeakable happened.
347 # As neither masterFileName nor masterFile are defined at any point
348 # this will raise a NameError.
349 Die(masterFileName, masterFile, None)
354 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
355 OldMask = os.umask(0077)
356 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
358 for f in userlist.keys():
359 if f not in SSHFiles:
361 # If we're not exporting their primary group, don't export
364 if userlist[f] in grouprevmap.keys():
365 grname = grouprevmap[userlist[f]]
368 if int(userlist[f]) <= 100:
369 # In these cases, look it up in the normal way so we
370 # deal with cases where, for instance, users are in group
371 # users as their primary group.
372 grname = grp.getgrgid(userlist[f])[0]
377 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])
380 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
381 # These will only be used where the username doesn't
382 # exist on the target system for some reason; hence,
383 # in those cases, the safest thing is for the file to
384 # be owned by root but group nobody. This deals with
385 # the bloody obscure case where the group fails to exist
386 # whilst the user does (in which case we want to avoid
387 # ending up with a file which is owned user:root to avoid
388 # a fairly obvious attack vector)
391 # Using the username / groupname fields avoids any need
392 # to give a shit^W^W^Wcare about the UIDoffset stuff.
397 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
399 for line in contents.splitlines():
400 if line.startswith("allowed_hosts=") and ' ' in line:
401 machines, line = line.split('=', 1)[1].split(' ', 1)
402 if CurrentHost not in machines.split(','):
403 continue # skip this key
406 continue # no keys for this host
407 contents = "\n".join(lines) + "\n"
408 to.size = len(contents)
409 tf.addfile(to, StringIO(contents))
412 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
414 # add a list of groups to existing groups,
415 # including all subgroups thereof, recursively.
416 # basically this proceduces the transitive hull of the groups in
418 def addGroups(existingGroups, newGroups, uid):
419 for group in newGroups:
420 # if it's a <group>@host, split it and verify it's on the current host.
421 s = group.split('@', 1)
422 if len(s) == 2 and s[1] != CurrentHost:
426 # let's see if we handled this group already
427 if group in existingGroups:
430 if not GroupIDMap.has_key(group):
431 print "Group", group, "does not exist but", uid, "is in it"
434 existingGroups.append(group)
436 if SubGroupMap.has_key(group):
437 addGroups(existingGroups, SubGroupMap[group], uid)
439 # Generate the group list
444 F = open(File + ".tdb.tmp", "w")
446 # Generate the GroupMap
448 for x in GroupIDMap.keys():
450 GroupHasPrimaryMembers = {}
452 # Fetch all the users
455 # Sort them into a list of groups having a set of users
456 for x in PasswdAttrs:
457 uid = GetAttr(x, "uid")
458 if 'gidNumber' in x[1]:
459 GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
460 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
462 if x[1].has_key("supplementaryGid") == 0:
466 addGroups(supgroups, x[1]["supplementaryGid"], uid)
468 GroupMap[g].append(uid)
470 # Output the group file.
472 for x in GroupMap.keys():
473 if GroupIDMap.has_key(x) == 0:
476 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
479 grouprevmap[GroupIDMap[x]] = x
481 Line = "%s:x:%u:" % (x, GroupIDMap[x])
483 for I in GroupMap[x]:
484 Line = Line + ("%s%s" % (Comma, I))
486 Line = Sanitize(Line) + "\n"
487 F.write("0%u %s" % (J, Line))
488 F.write(".%s %s" % (x, Line))
489 F.write("=%u %s" % (GroupIDMap[x], Line))
492 # Oops, something unspeakable happened.
502 for x in PasswdAttrs:
503 if x[1].has_key("emailForward") == 0:
507 x[1].pop("emailForward")
510 # Do not allow people to try to buffer overflow busted parsers
511 if len(GetAttr(x, "emailForward")) > 200:
512 x[1].pop("emailForward")
515 # Check the forwarding address
516 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
517 x[1].pop("emailForward")
519 # Generate the email forwarding list
520 def GenForward(File):
523 OldMask = os.umask(0022)
524 F = open(File + ".tmp", "w", 0644)
527 # Fetch all the users
530 # Write out the email address for each user
531 for x in PasswdAttrs:
532 if x[1].has_key("emailForward") == 0:
535 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
536 Line = Sanitize(Line) + "\n"
539 # Oops, something unspeakable happened.
545 def GenCDB(File, Users, Key):
548 OldMask = os.umask(0022)
549 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
552 # Write out the email address for each user
556 Value = GetAttr(x, Key)
557 User = GetAttr(x, "uid")
558 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
561 # Oops, something unspeakable happened.
565 if Fdb.close() != None:
566 raise "cdbmake gave an error"
568 # Generate the anon XEarth marker file
569 def GenMarkers(File):
572 F = open(File + ".tmp", "w")
574 # Fetch all the users
577 # Write out the position for each user
578 for x in PasswdAttrs:
579 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
582 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
583 Line = Sanitize(Line) + "\n"
588 # Oops, something unspeakable happened.
594 # Generate the debian-private subscription list
595 def GenPrivate(File):
598 F = open(File + ".tmp", "w")
600 # Fetch all the users
603 # Write out the position for each user
604 for x in DebianDDUsers:
605 if x[1].has_key("privateSub") == 0:
608 # If the account has no PGP key, do not write it
609 if x[1].has_key("keyFingerPrint") == 0:
613 Line = "%s"%(GetAttr(x, "privateSub"))
614 Line = Sanitize(Line) + "\n"
619 # Oops, something unspeakable happened.
625 # Generate a list of locked accounts
626 def GenDisabledAccounts(File):
629 F = open(File + ".tmp", "w")
631 # Fetch all the users
636 for x in PasswdAttrs:
637 if x[1].has_key("uidNumber") == 0:
640 Pass = GetAttr(x, "userPassword")
642 # *LK* is the reference value for a locked account
643 # password starting with ! is also a locked account
644 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
645 # Format is <login>:<reason>
646 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
647 DisabledUsers.append(x)
650 F.write(Sanitize(Line) + "\n")
653 # Oops, something unspeakable happened.
659 # Generate the list of local addresses that refuse all mail
660 def GenMailDisable(File):
663 F = open(File + ".tmp", "w")
665 # Fetch all the users
668 for x in PasswdAttrs:
671 if x[1].has_key("mailDisableMessage"):
672 Reason = GetAttr(x, "mailDisableMessage")
677 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
678 Line = Sanitize(Line) + "\n"
683 # Oops, something unspeakable happened.
689 # Generate a list of uids that should have boolean affects applied
690 def GenMailBool(File, Key):
693 F = open(File + ".tmp", "w")
695 # Fetch all the users
698 for x in PasswdAttrs:
701 if x[1].has_key(Key) == 0:
704 if GetAttr(x, Key) != "TRUE":
708 Line = "%s"%(GetAttr(x, "uid"))
709 Line = Sanitize(Line) + "\n"
714 # Oops, something unspeakable happened.
720 # Generate a list of hosts for RBL or whitelist purposes.
721 def GenMailList(File, Key):
724 F = open(File + ".tmp", "w")
726 # Fetch all the users
729 for x in PasswdAttrs:
732 if x[1].has_key(Key) == 0:
739 if Key == "mailWhitelist":
740 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
743 if re.match('^[-\w.]+$', z) == None:
747 Line = GetAttr(x, "uid")
751 if Key == "mailRHSBL":
752 Line += "/$sender_address_domain"
755 Line = Sanitize(Line) + "\n"
760 # Oops, something unspeakable happened.
766 def isRoleAccount(pwEntry):
767 if not pwEntry.has_key("objectClass"):
768 raise "pwEntry has no objectClass"
769 oc = pwEntry['objectClass']
771 i = oc.index('debianRoleAccount')
776 # Generate the DNS Zone file
780 F = open(File + ".tmp", "w")
782 # Fetch all the users
786 # Write out the zone file entry for each user
787 for x in PasswdAttrs:
788 if x[1].has_key("dnsZoneEntry") == 0:
791 # If the account has no PGP key, do not write it
792 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
795 F.write("; %s\n"%(EmailAddress(x)))
796 for z in x[1]["dnsZoneEntry"]:
797 Split = z.lower().split()
798 if Split[1].lower() == 'in':
799 for y in range(0, len(Split)):
802 Line = " ".join(Split) + "\n"
805 Host = Split[0] + DNSZone
806 if BSMTPCheck.match(Line) != None:
807 F.write("; Has BSMTP\n")
809 # Write some identification information
810 if not RRs.has_key(Host):
811 if Split[2].lower() in ["a", "aaaa"]:
812 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
813 for y in x[1]["keyFingerPrint"]:
814 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
818 Line = "; Err %s"%(str(Split))
823 F.write("; Errors\n")
826 # Oops, something unspeakable happened.
832 def ExtractDNSInfo(x):
836 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
839 if x[1].has_key("ipHostNumber"):
840 for I in x[1]["ipHostNumber"]:
841 if IsV6Addr.match(I) != None:
842 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
844 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
848 if 'sshRSAHostKey' in x[1]:
849 for I in x[1]["sshRSAHostKey"]:
851 if Split[0] == 'ssh-rsa':
853 if Split[0] == 'ssh-dss':
855 if Algorithm == None:
857 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
858 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
860 if 'architecture' in x[1]:
861 Arch = GetAttr(x, "architecture")
863 if x[1].has_key("machine"):
864 Mach = " " + GetAttr(x, "machine")
865 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
867 if x[1].has_key("mXRecord"):
868 for I in x[1]["mXRecord"]:
869 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
873 # Generate the DNS records
874 def GenZoneRecords(File):
877 F = open(File + ".tmp", "w")
879 # Fetch all the hosts
883 if x[1].has_key("hostname") == 0:
886 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
889 DNSInfo = ExtractDNSInfo(x)
893 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
896 Line = "\t\t\t%s" % (Line)
900 # this would write sshfp lines for services on machines
901 # but we can't yet, since some are cnames and we'll make
902 # an invalid zonefile
904 # for i in x[1].get("purpose", []):
905 # m = PurposeHostField.match(i)
908 # # we ignore [[*..]] entries
909 # if m.startswith('*'):
911 # if m.startswith('-'):
914 # if not m.endswith(HostDomain):
916 # if not m.endswith('.'):
918 # for Line in DNSInfo:
919 # if isSSHFP.match(Line):
920 # Line = "%s\t%s" % (m, Line)
921 # F.write(Line + "\n")
923 # Oops, something unspeakable happened.
929 # Generate the BSMTP file
930 def GenBSMTP(File, HomePrefix):
933 F = open(File + ".tmp", "w")
935 # Fetch all the users
938 # Write out the zone file entry for each user
939 for x in PasswdAttrs:
940 if x[1].has_key("dnsZoneEntry") == 0:
943 # If the account has no PGP key, do not write it
944 if x[1].has_key("keyFingerPrint") == 0:
947 for z in x[1]["dnsZoneEntry"]:
948 Split = z.lower().split()
949 if Split[1].lower() == 'in':
950 for y in range(0, len(Split)):
953 Line = " ".join(Split) + "\n"
955 Host = Split[0] + DNSZone
956 if BSMTPCheck.match(Line) != None:
957 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
958 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
961 F.write("; Errors\n")
964 # Oops, something unspeakable happened.
970 def HostToIP(Host, mapped=True):
974 if Host[1].has_key("ipHostNumber"):
975 for addr in Host[1]["ipHostNumber"]:
976 IPAdresses.append(addr)
977 if IsV6Addr.match(addr) is None and mapped == "True":
978 IPAdresses.append("::ffff:"+addr)
982 # Generate the ssh known hosts file
983 def GenSSHKnown(File, mode=None):
986 OldMask = os.umask(0022)
987 F = open(File + ".tmp", "w", 0644)
993 if x[1].has_key("hostname") == 0 or \
994 x[1].has_key("sshRSAHostKey") == 0:
996 Host = GetAttr(x, "hostname")
998 if Host.endswith(HostDomain):
999 HostNames.append(Host[:-(len(HostDomain) + 1)])
1001 # in the purpose field [[host|some other text]] (where some other text is optional)
1002 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
1003 # file. But so that we don't have to add everything we link we can add an asterisk
1004 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
1005 # http linking it we also support [[-hostname]] entries.
1006 for i in x[1].get("purpose", []):
1007 m = PurposeHostField.match(i)
1010 # we ignore [[*..]] entries
1011 if m.startswith('*'):
1013 if m.startswith('-'):
1017 if m.endswith(HostDomain):
1018 HostNames.append(m[:-(len(HostDomain) + 1)])
1020 for I in x[1]["sshRSAHostKey"]:
1021 if mode and mode == 'authorized_keys':
1023 if 'sshdistAuthKeysHost' in x[1]:
1024 hosts += x[1]['sshdistAuthKeysHost']
1025 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)
1027 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1028 Line = Sanitize(Line) + "\n"
1030 # Oops, something unspeakable happened.
1036 # Generate the debianhosts file (list of all IP addresses)
1040 OldMask = os.umask(0022)
1041 F = open(File + ".tmp", "w", 0644)
1050 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1053 if not 'ipHostNumber' in x[1]:
1056 addrs = x[1]["ipHostNumber"]
1058 if addr not in seen:
1060 addr = Sanitize(addr) + "\n"
1063 # Oops, something unspeakable happened.
1069 def GenKeyrings(OutDir):
1071 shutil.copy(k, OutDir)
1073 # Connect to the ldap server
1075 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1076 Pass = F.readline().strip().split(" ")
1078 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1080 # Fetch all the groups
1082 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1083 ["gid", "gidNumber", "subGroup"])
1085 # Generate the SubGroupMap and GroupIDMap
1087 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1089 if x[1].has_key("gidNumber") == 0:
1091 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1092 if x[1].has_key("subGroup") != 0:
1093 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1095 # Fetch all the users
1096 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1097 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1098 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1099 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1100 "shadowExpire", "emailForward", "latitude", "longitude",\
1101 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1102 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1103 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1104 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1105 "mailContentInspectionAction"])
1107 if PasswdAttrs is None:
1108 raise UDEmptyList, "No Users"
1110 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1112 # Fetch all the hosts
1113 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1114 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1115 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1117 if HostAttrs == None:
1118 raise UDEmptyList, "No Hosts"
1120 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1122 # Generate global things
1123 GlobalDir = GenerateDir + "/"
1124 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1126 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1127 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1131 GenMailDisable(GlobalDir + "mail-disable")
1132 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1133 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1134 GenPrivate(GlobalDir + "debian-private")
1135 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1136 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1137 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1138 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1139 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1140 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1141 GenKeyrings(GlobalDir)
1144 GenForward(GlobalDir + "forward-alias")
1146 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1148 SSHFiles = GenSSHShadow()
1149 GenMarkers(GlobalDir + "markers")
1150 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1151 GenHosts(GlobalDir + "debianhosts")
1153 for host in HostAttrs:
1154 if not "hostname" in host[1]:
1157 CurrentHost = host[1]['hostname'][0]
1158 OutDir = GenerateDir + '/' + CurrentHost + '/'
1164 # Get the group list and convert any named groups to numerics
1166 for groupname in AllowedGroupsPreload.strip().split(" "):
1167 GroupList[groupname] = True
1168 if 'allowedGroups' in host[1]:
1169 for groupname in host[1]['allowedGroups']:
1170 GroupList[groupname] = True
1171 for groupname in GroupList.keys():
1172 if groupname in GroupIDMap:
1173 GroupList[str(GroupIDMap[groupname])] = True
1176 if 'exportOptions' in host[1]:
1177 for extra in host[1]['exportOptions']:
1178 ExtraList[extra.upper()] = True
1184 DoLink(GlobalDir, OutDir, "debianhosts")
1185 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1186 DoLink(GlobalDir, OutDir, "disabled-accounts")
1189 if 'NOPASSWD' in ExtraList:
1190 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1192 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1194 grouprevmap = GenGroup(OutDir + "group")
1195 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1197 # Now we know who we're allowing on the machine, export
1198 # the relevant ssh keys
1199 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1201 if not 'NOPASSWD' in ExtraList:
1202 GenShadow(OutDir + "shadow")
1204 # Link in global things
1205 if not 'NOMARKERS' in ExtraList:
1206 DoLink(GlobalDir, OutDir, "markers")
1207 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1208 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1209 DoLink(GlobalDir, OutDir, "mail-disable")
1210 DoLink(GlobalDir, OutDir, "mail-greylist")
1211 DoLink(GlobalDir, OutDir, "mail-callout")
1212 DoLink(GlobalDir, OutDir, "mail-rbl")
1213 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1214 DoLink(GlobalDir, OutDir, "mail-whitelist")
1215 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1216 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1217 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1220 DoLink(GlobalDir, OutDir, "forward-alias")
1222 if 'DNS' in ExtraList:
1223 GenDNS(OutDir + "dns-zone")
1224 GenZoneRecords(OutDir + "dns-sshfp")
1226 if 'AUTHKEYS' in ExtraList:
1227 DoLink(GlobalDir, OutDir, "authorized_keys")
1229 if 'BSMTP' in ExtraList:
1230 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1232 if 'PRIVATE' in ExtraList:
1233 DoLink(GlobalDir, OutDir, "debian-private")
1235 if 'KEYRING' in ExtraList:
1237 DoLink(GlobalDir, OutDir, os.path.basename(k))
1241 posix.remove(OutDir + os.path.basename(k))
1247 # vim:set shiftwidth=3: