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 Die(masterFileName, masterFile, None)
351 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
352 OldMask = os.umask(0077)
353 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
355 for f in userlist.keys():
356 if f not in SSHFiles:
358 # If we're not exporting their primary group, don't export
361 if userlist[f] in grouprevmap.keys():
362 grname = grouprevmap[userlist[f]]
365 if int(userlist[f]) <= 100:
366 # In these cases, look it up in the normal way so we
367 # deal with cases where, for instance, users are in group
368 # users as their primary group.
369 grname = grp.getgrgid(userlist[f])[0]
374 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])
377 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
378 # These will only be used where the username doesn't
379 # exist on the target system for some reason; hence,
380 # in those cases, the safest thing is for the file to
381 # be owned by root but group nobody. This deals with
382 # the bloody obscure case where the group fails to exist
383 # whilst the user does (in which case we want to avoid
384 # ending up with a file which is owned user:root to avoid
385 # a fairly obvious attack vector)
388 # Using the username / groupname fields avoids any need
389 # to give a shit^W^W^Wcare about the UIDoffset stuff.
394 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
396 for line in contents.splitlines():
397 if line.startswith("allowed_hosts=") and ' ' in line:
398 machines, line = line.split('=', 1)[1].split(' ', 1)
399 if CurrentHost not in machines.split(','):
400 continue # skip this key
403 continue # no keys for this host
404 contents = "\n".join(lines)
405 to.size = len(contents)
406 tf.addfile(to, StringIO(contents))
409 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
411 # add a list of groups to existing groups,
412 # including all subgroups thereof, recursively.
413 # basically this proceduces the transitive hull of the groups in
415 def addGroups(existingGroups, newGroups, uid):
416 for group in newGroups:
417 # if it's a <group>@host, split it and verify it's on the current host.
418 s = group.split('@', 1)
419 if len(s) == 2 and s[1] != CurrentHost:
423 # let's see if we handled this group already
424 if group in existingGroups:
427 if not GroupIDMap.has_key(group):
428 print "Group", group, "does not exist but", uid, "is in it"
431 existingGroups.append(group)
433 if SubGroupMap.has_key(group):
434 addGroups(existingGroups, SubGroupMap[group], uid)
436 # Generate the group list
441 F = open(File + ".tdb.tmp", "w")
443 # Generate the GroupMap
445 for x in GroupIDMap.keys():
447 GroupHasPrimaryMembers = {}
449 # Fetch all the users
452 # Sort them into a list of groups having a set of users
453 for x in PasswdAttrs:
454 uid = GetAttr(x, "uid")
455 if 'gidNumber' in x[1]:
456 GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
457 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
459 if x[1].has_key("supplementaryGid") == 0:
463 addGroups(supgroups, x[1]["supplementaryGid"], uid)
465 GroupMap[g].append(uid)
467 # Output the group file.
469 for x in GroupMap.keys():
470 if GroupIDMap.has_key(x) == 0:
473 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
476 grouprevmap[GroupIDMap[x]] = x
478 Line = "%s:x:%u:" % (x, GroupIDMap[x])
480 for I in GroupMap[x]:
481 Line = Line + ("%s%s" % (Comma, I))
483 Line = Sanitize(Line) + "\n"
484 F.write("0%u %s" % (J, Line))
485 F.write(".%s %s" % (x, Line))
486 F.write("=%u %s" % (GroupIDMap[x], Line))
489 # Oops, something unspeakable happened.
499 for x in PasswdAttrs:
500 if x[1].has_key("emailForward") == 0:
504 x[1].pop("emailForward")
507 # Do not allow people to try to buffer overflow busted parsers
508 if len(GetAttr(x, "emailForward")) > 200:
509 x[1].pop("emailForward")
512 # Check the forwarding address
513 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
514 x[1].pop("emailForward")
516 # Generate the email forwarding list
517 def GenForward(File):
520 OldMask = os.umask(0022)
521 F = open(File + ".tmp", "w", 0644)
524 # Fetch all the users
527 # Write out the email address for each user
528 for x in PasswdAttrs:
529 if x[1].has_key("emailForward") == 0:
532 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
533 Line = Sanitize(Line) + "\n"
536 # Oops, something unspeakable happened.
542 def GenCDB(File, Users, Key):
545 OldMask = os.umask(0022)
546 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
549 # Write out the email address for each user
553 Value = GetAttr(x, Key)
554 User = GetAttr(x, "uid")
555 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
558 # Oops, something unspeakable happened.
562 if Fdb.close() != None:
563 raise "cdbmake gave an error"
565 # Generate the anon XEarth marker file
566 def GenMarkers(File):
569 F = open(File + ".tmp", "w")
571 # Fetch all the users
574 # Write out the position for each user
575 for x in PasswdAttrs:
576 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
579 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
580 Line = Sanitize(Line) + "\n"
585 # Oops, something unspeakable happened.
591 # Generate the debian-private subscription list
592 def GenPrivate(File):
595 F = open(File + ".tmp", "w")
597 # Fetch all the users
600 # Write out the position for each user
601 for x in DebianDDUsers:
602 if x[1].has_key("privateSub") == 0:
605 # If the account has no PGP key, do not write it
606 if x[1].has_key("keyFingerPrint") == 0:
610 Line = "%s"%(GetAttr(x, "privateSub"))
611 Line = Sanitize(Line) + "\n"
616 # Oops, something unspeakable happened.
622 # Generate a list of locked accounts
623 def GenDisabledAccounts(File):
626 F = open(File + ".tmp", "w")
628 # Fetch all the users
633 for x in PasswdAttrs:
634 if x[1].has_key("uidNumber") == 0:
637 Pass = GetAttr(x, "userPassword")
639 # *LK* is the reference value for a locked account
640 # password starting with ! is also a locked account
641 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
642 # Format is <login>:<reason>
643 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
644 DisabledUsers.append(x)
647 F.write(Sanitize(Line) + "\n")
650 # Oops, something unspeakable happened.
656 # Generate the list of local addresses that refuse all mail
657 def GenMailDisable(File):
660 F = open(File + ".tmp", "w")
662 # Fetch all the users
665 for x in PasswdAttrs:
668 if x[1].has_key("mailDisableMessage"):
669 Reason = GetAttr(x, "mailDisableMessage")
674 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
675 Line = Sanitize(Line) + "\n"
680 # Oops, something unspeakable happened.
686 # Generate a list of uids that should have boolean affects applied
687 def GenMailBool(File, Key):
690 F = open(File + ".tmp", "w")
692 # Fetch all the users
695 for x in PasswdAttrs:
698 if x[1].has_key(Key) == 0:
701 if GetAttr(x, Key) != "TRUE":
705 Line = "%s"%(GetAttr(x, "uid"))
706 Line = Sanitize(Line) + "\n"
711 # Oops, something unspeakable happened.
717 # Generate a list of hosts for RBL or whitelist purposes.
718 def GenMailList(File, Key):
721 F = open(File + ".tmp", "w")
723 # Fetch all the users
726 for x in PasswdAttrs:
729 if x[1].has_key(Key) == 0:
736 if Key == "mailWhitelist":
737 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
740 if re.match('^[-\w.]+$', z) == None:
744 Line = GetAttr(x, "uid")
748 if Key == "mailRHSBL":
749 Line += "/$sender_address_domain"
752 Line = Sanitize(Line) + "\n"
757 # Oops, something unspeakable happened.
763 def isRoleAccount(pwEntry):
764 if not pwEntry.has_key("objectClass"):
765 raise "pwEntry has no objectClass"
766 oc = pwEntry['objectClass']
768 i = oc.index('debianRoleAccount')
773 # Generate the DNS Zone file
777 F = open(File + ".tmp", "w")
781 # for x in HostAttrs:
782 # if x[1].has_key("hostname") == 0 or \
783 # x[1].has_key("architecture") == 0 or\
784 # x[1].has_key("sshRSAHostKey") == 0:
787 # if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
790 # DNSInfo = ExtractDNSInfo(x)
792 # for Line in DNSInfo:
794 # Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
797 # Line = "\t\t\t%s" % (Line)
798 # F.write(Line + "\n")
800 # Fetch all the users
803 # Write out the zone file entry for each user
804 for x in PasswdAttrs:
805 if x[1].has_key("dnsZoneEntry") == 0:
808 # If the account has no PGP key, do not write it
809 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
812 F.write("; %s\n"%(EmailAddress(x)))
813 for z in x[1]["dnsZoneEntry"]:
814 Split = z.lower().split()
815 if Split[1].lower() == 'in':
816 for y in range(0, len(Split)):
819 Line = " ".join(Split) + "\n"
822 Host = Split[0] + DNSZone
823 if BSMTPCheck.match(Line) != None:
824 F.write("; Has BSMTP\n")
826 # Write some identification information
827 if Split[2].lower() == "a":
828 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
829 for y in x[1]["keyFingerPrint"]:
830 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
833 Line = "; Err %s"%(str(Split))
838 F.write("; Errors\n")
841 # Oops, something unspeakable happened.
847 def ExtractDNSInfo(x):
851 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
854 if x[1].has_key("ipHostNumber"):
855 for I in x[1]["ipHostNumber"]:
856 if IsV6Addr.match(I) != None:
857 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
859 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
863 if 'sshRSAHostKey' in x[1]:
864 for I in x[1]["sshRSAHostKey"]:
866 if Split[0] == 'ssh-rsa':
868 if Split[0] == 'ssh-dss':
870 if Algorithm == None:
872 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
873 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
875 if 'architecture' in x[1]:
876 Arch = GetAttr(x, "architecture")
878 if x[1].has_key("machine"):
879 Mach = " " + GetAttr(x, "machine")
880 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
882 if x[1].has_key("mXRecord"):
883 for I in x[1]["mXRecord"]:
884 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
888 # Generate the DNS records
889 def GenZoneRecords(File):
892 F = open(File + ".tmp", "w")
894 # Fetch all the hosts
898 if x[1].has_key("hostname") == 0:
901 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
904 DNSInfo = ExtractDNSInfo(x)
908 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
911 Line = "\t\t\t%s" % (Line)
915 # Oops, something unspeakable happened.
921 # Generate the BSMTP file
922 def GenBSMTP(File, HomePrefix):
925 F = open(File + ".tmp", "w")
927 # Fetch all the users
930 # Write out the zone file entry for each user
931 for x in PasswdAttrs:
932 if x[1].has_key("dnsZoneEntry") == 0:
935 # If the account has no PGP key, do not write it
936 if x[1].has_key("keyFingerPrint") == 0:
939 for z in x[1]["dnsZoneEntry"]:
940 Split = z.lower().split()
941 if Split[1].lower() == 'in':
942 for y in range(0, len(Split)):
945 Line = " ".join(Split) + "\n"
947 Host = Split[0] + DNSZone
948 if BSMTPCheck.match(Line) != None:
949 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
950 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
953 F.write("; Errors\n")
956 # Oops, something unspeakable happened.
962 def HostToIP(Host, mapped=True):
966 if Host[1].has_key("ipHostNumber"):
967 for addr in Host[1]["ipHostNumber"]:
968 IPAdresses.append(addr)
969 if IsV6Addr.match(addr) is None and mapped == "True":
970 IPAdresses.append("::ffff:"+addr)
974 # Generate the ssh known hosts file
975 def GenSSHKnown(File, mode=None):
978 OldMask = os.umask(0022)
979 F = open(File + ".tmp", "w", 0644)
985 if x[1].has_key("hostname") == 0 or \
986 x[1].has_key("sshRSAHostKey") == 0:
988 Host = GetAttr(x, "hostname")
990 if Host.endswith(HostDomain):
991 HostNames.append(Host[:-(len(HostDomain) + 1)])
993 # in the purpose field [[host|some other text]] (where some other text is optional)
994 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
995 # file. But so that we don't have to add everything we link we can add an asterisk
996 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
997 # http linking it we also support [[-hostname]] entries.
998 for i in x[1].get("purpose", []):
999 m = PurposeHostField.match(i)
1002 # we ignore [[*..]] entries
1003 if m.startswith('*'):
1005 if m.startswith('-'):
1009 if m.endswith(HostDomain):
1010 HostNames.append(m[:-(len(HostDomain) + 1)])
1012 for I in x[1]["sshRSAHostKey"]:
1013 if mode and mode == 'authorized_keys':
1015 if 'sshdistAuthKeysHost' in x[1]:
1016 hosts += x[1]['sshdistAuthKeysHost']
1017 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)
1018 #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
1020 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1021 Line = Sanitize(Line) + "\n"
1023 # Oops, something unspeakable happened.
1029 # Generate the debianhosts file (list of all IP addresses)
1033 OldMask = os.umask(0022)
1034 F = open(File + ".tmp", "w", 0644)
1043 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1046 if not 'ipHostNumber' in x[1]:
1049 addrs = x[1]["ipHostNumber"]
1051 if addr not in seen:
1053 addr = Sanitize(addr) + "\n"
1056 # Oops, something unspeakable happened.
1062 def GenKeyrings(OutDir):
1064 shutil.copy(k, OutDir)
1066 # Connect to the ldap server
1068 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1069 Pass = F.readline().strip().split(" ")
1071 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1073 # Fetch all the groups
1075 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1076 ["gid", "gidNumber", "subGroup"])
1078 # Generate the SubGroupMap and GroupIDMap
1080 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1082 if x[1].has_key("gidNumber") == 0:
1084 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1085 if x[1].has_key("subGroup") != 0:
1086 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1088 # Fetch all the users
1089 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1090 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1091 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1092 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1093 "shadowExpire", "emailForward", "latitude", "longitude",\
1094 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1095 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1096 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1097 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1098 "mailContentInspectionAction"])
1100 if PasswdAttrs is None:
1101 raise UDEmptyList, "No Users"
1103 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1105 # Fetch all the hosts
1106 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1107 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1108 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1110 if HostAttrs == None:
1111 raise UDEmptyList, "No Hosts"
1113 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1115 # Generate global things
1116 GlobalDir = GenerateDir + "/"
1117 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1119 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1120 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1124 GenMailDisable(GlobalDir + "mail-disable")
1125 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1126 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1127 GenPrivate(GlobalDir + "debian-private")
1128 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1129 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1130 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1131 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1132 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1133 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1134 GenKeyrings(GlobalDir)
1137 GenForward(GlobalDir + "forward-alias")
1139 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1141 SSHFiles = GenSSHShadow()
1142 GenMarkers(GlobalDir + "markers")
1143 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1144 GenHosts(GlobalDir + "debianhosts")
1146 for host in HostAttrs:
1147 if not "hostname" in host[1]:
1150 CurrentHost = host[1]['hostname'][0]
1151 OutDir = GenerateDir + '/' + CurrentHost + '/'
1157 # Get the group list and convert any named groups to numerics
1159 for groupname in AllowedGroupsPreload.strip().split(" "):
1160 GroupList[groupname] = True
1161 if 'allowedGroups' in host[1]:
1162 for groupname in host[1]['allowedGroups']:
1163 GroupList[groupname] = True
1164 for groupname in GroupList.keys():
1165 if groupname in GroupIDMap:
1166 GroupList[str(GroupIDMap[groupname])] = True
1169 if 'exportOptions' in host[1]:
1170 for extra in host[1]['exportOptions']:
1171 ExtraList[extra.upper()] = True
1177 DoLink(GlobalDir, OutDir, "debianhosts")
1178 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1179 DoLink(GlobalDir, OutDir, "disabled-accounts")
1182 if 'NOPASSWD' in ExtraList:
1183 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1185 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1187 grouprevmap = GenGroup(OutDir + "group")
1188 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1190 # Now we know who we're allowing on the machine, export
1191 # the relevant ssh keys
1192 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1194 if not 'NOPASSWD' in ExtraList:
1195 GenShadow(OutDir + "shadow")
1197 # Link in global things
1198 if not 'NOMARKERS' in ExtraList:
1199 DoLink(GlobalDir, OutDir, "markers")
1200 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1201 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1202 DoLink(GlobalDir, OutDir, "mail-disable")
1203 DoLink(GlobalDir, OutDir, "mail-greylist")
1204 DoLink(GlobalDir, OutDir, "mail-callout")
1205 DoLink(GlobalDir, OutDir, "mail-rbl")
1206 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1207 DoLink(GlobalDir, OutDir, "mail-whitelist")
1208 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1209 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1210 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1213 DoLink(GlobalDir, OutDir, "forward-alias")
1215 if 'DNS' in ExtraList:
1216 GenDNS(OutDir + "dns-zone")
1217 GenZoneRecords(OutDir + "dns-sshfp")
1219 if 'AUTHKEYS' in ExtraList:
1220 DoLink(GlobalDir, OutDir, "authorized_keys")
1222 if 'BSMTP' in ExtraList:
1223 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1225 if 'PRIVATE' in ExtraList:
1226 DoLink(GlobalDir, OutDir, "debian-private")
1228 if 'KEYRING' in ExtraList:
1230 DoLink(GlobalDir, OutDir, os.path.basename(k))
1234 posix.remove(OutDir + os.path.basename(k))
1240 # vim:set shiftwidth=3: