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>
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")
783 # for x in HostAttrs:
784 # if x[1].has_key("hostname") == 0 or \
785 # x[1].has_key("architecture") == 0 or\
786 # x[1].has_key("sshRSAHostKey") == 0:
789 # if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
792 # DNSInfo = ExtractDNSInfo(x)
794 # for Line in DNSInfo:
796 # Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
799 # Line = "\t\t\t%s" % (Line)
800 # F.write(Line + "\n")
802 # Fetch all the users
805 # Write out the zone file entry for each user
806 for x in PasswdAttrs:
807 if x[1].has_key("dnsZoneEntry") == 0:
810 # If the account has no PGP key, do not write it
811 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
814 F.write("; %s\n"%(EmailAddress(x)))
815 for z in x[1]["dnsZoneEntry"]:
816 Split = z.lower().split()
817 if Split[1].lower() == 'in':
818 for y in range(0, len(Split)):
821 Line = " ".join(Split) + "\n"
824 Host = Split[0] + DNSZone
825 if BSMTPCheck.match(Line) != None:
826 F.write("; Has BSMTP\n")
828 # Write some identification information
829 if Split[2].lower() == "a":
830 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
831 for y in x[1]["keyFingerPrint"]:
832 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
835 Line = "; Err %s"%(str(Split))
840 F.write("; Errors\n")
843 # Oops, something unspeakable happened.
849 def ExtractDNSInfo(x):
853 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
856 if x[1].has_key("ipHostNumber"):
857 for I in x[1]["ipHostNumber"]:
858 if IsV6Addr.match(I) != None:
859 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
861 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
865 if 'sshRSAHostKey' in x[1]:
866 for I in x[1]["sshRSAHostKey"]:
868 if Split[0] == 'ssh-rsa':
870 if Split[0] == 'ssh-dss':
872 if Algorithm == None:
874 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
875 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
877 if 'architecture' in x[1]:
878 Arch = GetAttr(x, "architecture")
880 if x[1].has_key("machine"):
881 Mach = " " + GetAttr(x, "machine")
882 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
884 if x[1].has_key("mXRecord"):
885 for I in x[1]["mXRecord"]:
886 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
890 # Generate the DNS records
891 def GenZoneRecords(File):
894 F = open(File + ".tmp", "w")
896 # Fetch all the hosts
900 if x[1].has_key("hostname") == 0:
903 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
906 DNSInfo = ExtractDNSInfo(x)
910 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
913 Line = "\t\t\t%s" % (Line)
917 # Oops, something unspeakable happened.
923 # Generate the BSMTP file
924 def GenBSMTP(File, HomePrefix):
927 F = open(File + ".tmp", "w")
929 # Fetch all the users
932 # Write out the zone file entry for each user
933 for x in PasswdAttrs:
934 if x[1].has_key("dnsZoneEntry") == 0:
937 # If the account has no PGP key, do not write it
938 if x[1].has_key("keyFingerPrint") == 0:
941 for z in x[1]["dnsZoneEntry"]:
942 Split = z.lower().split()
943 if Split[1].lower() == 'in':
944 for y in range(0, len(Split)):
947 Line = " ".join(Split) + "\n"
949 Host = Split[0] + DNSZone
950 if BSMTPCheck.match(Line) != None:
951 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
952 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
955 F.write("; Errors\n")
958 # Oops, something unspeakable happened.
964 def HostToIP(Host, mapped=True):
968 if Host[1].has_key("ipHostNumber"):
969 for addr in Host[1]["ipHostNumber"]:
970 IPAdresses.append(addr)
971 if IsV6Addr.match(addr) is None and mapped == "True":
972 IPAdresses.append("::ffff:"+addr)
976 # Generate the ssh known hosts file
977 def GenSSHKnown(File, mode=None):
980 OldMask = os.umask(0022)
981 F = open(File + ".tmp", "w", 0644)
987 if x[1].has_key("hostname") == 0 or \
988 x[1].has_key("sshRSAHostKey") == 0:
990 Host = GetAttr(x, "hostname")
992 if Host.endswith(HostDomain):
993 HostNames.append(Host[:-(len(HostDomain) + 1)])
995 # in the purpose field [[host|some other text]] (where some other text is optional)
996 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
997 # file. But so that we don't have to add everything we link we can add an asterisk
998 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
999 # http linking it we also support [[-hostname]] entries.
1000 for i in x[1].get("purpose", []):
1001 m = PurposeHostField.match(i)
1004 # we ignore [[*..]] entries
1005 if m.startswith('*'):
1007 if m.startswith('-'):
1011 if m.endswith(HostDomain):
1012 HostNames.append(m[:-(len(HostDomain) + 1)])
1014 for I in x[1]["sshRSAHostKey"]:
1015 if mode and mode == 'authorized_keys':
1017 if 'sshdistAuthKeysHost' in x[1]:
1018 hosts += x[1]['sshdistAuthKeysHost']
1019 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)
1020 #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
1022 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1023 Line = Sanitize(Line) + "\n"
1025 # Oops, something unspeakable happened.
1031 # Generate the debianhosts file (list of all IP addresses)
1035 OldMask = os.umask(0022)
1036 F = open(File + ".tmp", "w", 0644)
1045 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1048 if not 'ipHostNumber' in x[1]:
1051 addrs = x[1]["ipHostNumber"]
1053 if addr not in seen:
1055 addr = Sanitize(addr) + "\n"
1058 # Oops, something unspeakable happened.
1064 def GenKeyrings(OutDir):
1066 shutil.copy(k, OutDir)
1068 # Connect to the ldap server
1070 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1071 Pass = F.readline().strip().split(" ")
1073 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1075 # Fetch all the groups
1077 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1078 ["gid", "gidNumber", "subGroup"])
1080 # Generate the SubGroupMap and GroupIDMap
1082 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1084 if x[1].has_key("gidNumber") == 0:
1086 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1087 if x[1].has_key("subGroup") != 0:
1088 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1090 # Fetch all the users
1091 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1092 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1093 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1094 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1095 "shadowExpire", "emailForward", "latitude", "longitude",\
1096 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1097 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1098 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1099 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1100 "mailContentInspectionAction"])
1102 if PasswdAttrs is None:
1103 raise UDEmptyList, "No Users"
1105 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1107 # Fetch all the hosts
1108 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1109 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1110 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1112 if HostAttrs == None:
1113 raise UDEmptyList, "No Hosts"
1115 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1117 # Generate global things
1118 GlobalDir = GenerateDir + "/"
1119 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1121 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1122 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1126 GenMailDisable(GlobalDir + "mail-disable")
1127 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1128 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1129 GenPrivate(GlobalDir + "debian-private")
1130 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1131 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1132 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1133 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1134 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1135 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1136 GenKeyrings(GlobalDir)
1139 GenForward(GlobalDir + "forward-alias")
1141 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1143 SSHFiles = GenSSHShadow()
1144 GenMarkers(GlobalDir + "markers")
1145 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1146 GenHosts(GlobalDir + "debianhosts")
1148 for host in HostAttrs:
1149 if not "hostname" in host[1]:
1152 CurrentHost = host[1]['hostname'][0]
1153 OutDir = GenerateDir + '/' + CurrentHost + '/'
1159 # Get the group list and convert any named groups to numerics
1161 for groupname in AllowedGroupsPreload.strip().split(" "):
1162 GroupList[groupname] = True
1163 if 'allowedGroups' in host[1]:
1164 for groupname in host[1]['allowedGroups']:
1165 GroupList[groupname] = True
1166 for groupname in GroupList.keys():
1167 if groupname in GroupIDMap:
1168 GroupList[str(GroupIDMap[groupname])] = True
1171 if 'exportOptions' in host[1]:
1172 for extra in host[1]['exportOptions']:
1173 ExtraList[extra.upper()] = True
1179 DoLink(GlobalDir, OutDir, "debianhosts")
1180 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1181 DoLink(GlobalDir, OutDir, "disabled-accounts")
1184 if 'NOPASSWD' in ExtraList:
1185 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1187 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1189 grouprevmap = GenGroup(OutDir + "group")
1190 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1192 # Now we know who we're allowing on the machine, export
1193 # the relevant ssh keys
1194 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1196 if not 'NOPASSWD' in ExtraList:
1197 GenShadow(OutDir + "shadow")
1199 # Link in global things
1200 if not 'NOMARKERS' in ExtraList:
1201 DoLink(GlobalDir, OutDir, "markers")
1202 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1203 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1204 DoLink(GlobalDir, OutDir, "mail-disable")
1205 DoLink(GlobalDir, OutDir, "mail-greylist")
1206 DoLink(GlobalDir, OutDir, "mail-callout")
1207 DoLink(GlobalDir, OutDir, "mail-rbl")
1208 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1209 DoLink(GlobalDir, OutDir, "mail-whitelist")
1210 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1211 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1212 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1215 DoLink(GlobalDir, OutDir, "forward-alias")
1217 if 'DNS' in ExtraList:
1218 GenDNS(OutDir + "dns-zone")
1219 GenZoneRecords(OutDir + "dns-sshfp")
1221 if 'AUTHKEYS' in ExtraList:
1222 DoLink(GlobalDir, OutDir, "authorized_keys")
1224 if 'BSMTP' in ExtraList:
1225 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1227 if 'PRIVATE' in ExtraList:
1228 DoLink(GlobalDir, OutDir, "debian-private")
1230 if 'KEYRING' in ExtraList:
1232 DoLink(GlobalDir, OutDir, os.path.basename(k))
1236 posix.remove(OutDir + os.path.basename(k))
1242 # vim:set shiftwidth=3: