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 *
36 from cStringIO import StringIO
38 from StringIO import StringIO
44 sys.stderr.write("You should probably not run ud-generate as root.\n")
55 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
57 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
58 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
59 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
60 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
61 IsDebianHost = re.compile(ConfModule.dns_hostmatch)
62 isSSHFP = re.compile("^\s*IN\s+SSHFP")
63 DNSZone = ".debian.net"
64 Keyrings = ConfModule.sync_keyrings.split(":")
66 def safe_makedirs(dir):
70 if e.errno == errno.EEXIST:
79 if e.errno == errno.ENOENT:
85 return Str.translate(string.maketrans("\n\r\t", "$$$"))
87 def DoLink(From, To, File):
89 posix.remove(To + File)
92 posix.link(From + File, To + File)
94 def IsRetired(DnRecord):
96 Looks for accountStatus in the LDAP record and tries to
97 match it against one of the known retired statuses
100 status = GetAttr(DnRecord, "accountStatus", None)
104 line = status.split()
107 if status == "inactive":
110 elif status == "memorial":
113 elif status == "retiring":
114 # We'll give them a few extra days over what we said
115 age = 6 * 31 * 24 * 60 * 60
117 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
127 return int(GetAttr(x, "gidNumber", 0)) == 800
131 # See if this user is in the group list
132 def IsInGroup(DnRecord):
136 # See if the primary group is in the list
137 if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0:
140 # Check the host based ACL
141 if DnRecord[1].has_key("allowedHost") != 0:
142 if CurrentHost in DnRecord[1]["allowedHost"]:
145 # See if there are supplementary groups
146 if DnRecord[1].has_key("supplementaryGid") == 0:
150 addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "uid"))
152 if Allowed.has_key(g):
156 def Die(File, F, Fdb):
162 os.remove(File + ".tmp")
166 os.remove(File + ".tdb.tmp")
170 def Done(File, F, Fdb):
173 os.rename(File + ".tmp", File)
176 os.rename(File + ".tdb.tmp", File + ".tdb")
178 # Generate the password list
179 def GenPasswd(File, HomePrefix, PwdMarker):
182 F = open(File + ".tdb.tmp", "w")
185 # Fetch all the users
189 for x in PasswdAttrs:
190 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
193 # Do not let people try to buffer overflow some busted passwd parser.
194 if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50:
197 userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
198 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\
200 GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\
201 GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\
202 GetAttr(x, "loginShell"))
204 Line = Sanitize(Line) + "\n"
205 F.write("0%u %s" % (I, Line))
206 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
207 F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line))
210 # Oops, something unspeakable happened.
216 # Return the list of users so we know which keys to export
219 # Generate the shadow list
223 OldMask = os.umask(0077)
224 F = open(File + ".tdb.tmp", "w", 0600)
227 # Fetch all the users
231 for x in PasswdAttrs:
232 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
235 Pass = GetAttr(x, "userPassword")
236 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
241 # If the account is locked, mark it as such in shadow
242 # See Debian Bug #308229 for why we set it to 1 instead of 0
243 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
244 or GetAttr(x, "userPassword").startswith("!"):
247 ShadowExpire = GetAttr(x, "shadowExpire")
249 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
250 Pass, GetAttr(x, "shadowLastChange"),\
251 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
252 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
254 Line = Sanitize(Line) + "\n"
255 F.write("0%u %s" % (I, Line))
256 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
259 # Oops, something unspeakable happened.
265 # Generate the sudo passwd file
266 def GenShadowSudo(File, untrusted):
269 OldMask = os.umask(0077)
270 F = open(File + ".tmp", "w", 0600)
273 # Fetch all the users
276 for x in PasswdAttrs:
278 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
281 if x[1].has_key('sudoPassword'):
282 for entry in x[1]['sudoPassword']:
283 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
286 uuid = Match.group(1)
287 status = Match.group(2)
288 hosts = Match.group(3)
289 cryptedpass = Match.group(4)
291 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
293 for_all = hosts == "*"
294 for_this_host = CurrentHost in hosts.split(',')
295 if not (for_all or for_this_host):
297 # ignore * passwords for untrusted hosts, but copy host specific passwords
298 if for_all and untrusted:
301 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
306 Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
307 Line = Sanitize(Line) + "\n"
308 F.write("%s" % (Line))
310 # Oops, something unspeakable happened.
316 # Generate the shadow list
318 # Fetch all the users
323 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
324 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
326 for x in PasswdAttrs:
328 if x[1].has_key("uidNumber") == 0 or \
329 x[1].has_key("sshRSAAuthKey") == 0:
332 User = GetAttr(x, "uid")
336 OldMask = os.umask(0077)
337 File = os.path.join(GlobalDir, 'userkeys', User)
338 F = open(File + ".tmp", "w", 0600)
341 for I in x[1]["sshRSAAuthKey"]:
342 MultipleLine = "%s" % I
343 MultipleLine = Sanitize(MultipleLine) + "\n"
344 F.write(MultipleLine)
347 userfiles.append(os.path.basename(File))
349 # Oops, something unspeakable happened.
352 # As neither masterFileName nor masterFile are defined at any point
353 # this will raise a NameError.
354 Die(masterFileName, masterFile, None)
359 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
360 OldMask = os.umask(0077)
361 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
363 for f in userlist.keys():
364 if f not in SSHFiles:
366 # If we're not exporting their primary group, don't export
369 if userlist[f] in grouprevmap.keys():
370 grname = grouprevmap[userlist[f]]
373 if int(userlist[f]) <= 100:
374 # In these cases, look it up in the normal way so we
375 # deal with cases where, for instance, users are in group
376 # users as their primary group.
377 grname = grp.getgrgid(userlist[f])[0]
382 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])
385 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
386 # These will only be used where the username doesn't
387 # exist on the target system for some reason; hence,
388 # in those cases, the safest thing is for the file to
389 # be owned by root but group nobody. This deals with
390 # the bloody obscure case where the group fails to exist
391 # whilst the user does (in which case we want to avoid
392 # ending up with a file which is owned user:root to avoid
393 # a fairly obvious attack vector)
396 # Using the username / groupname fields avoids any need
397 # to give a shit^W^W^Wcare about the UIDoffset stuff.
402 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
404 for line in contents.splitlines():
405 if line.startswith("allowed_hosts=") and ' ' in line:
406 machines, line = line.split('=', 1)[1].split(' ', 1)
407 if CurrentHost not in machines.split(','):
408 continue # skip this key
411 continue # no keys for this host
412 contents = "\n".join(lines) + "\n"
413 to.size = len(contents)
414 tf.addfile(to, StringIO(contents))
417 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
419 # add a list of groups to existing groups,
420 # including all subgroups thereof, recursively.
421 # basically this proceduces the transitive hull of the groups in
423 def addGroups(existingGroups, newGroups, uid):
424 for group in newGroups:
425 # if it's a <group>@host, split it and verify it's on the current host.
426 s = group.split('@', 1)
427 if len(s) == 2 and s[1] != CurrentHost:
431 # let's see if we handled this group already
432 if group in existingGroups:
435 if not GroupIDMap.has_key(group):
436 print "Group", group, "does not exist but", uid, "is in it"
439 existingGroups.append(group)
441 if SubGroupMap.has_key(group):
442 addGroups(existingGroups, SubGroupMap[group], uid)
444 # Generate the group list
449 F = open(File + ".tdb.tmp", "w")
451 # Generate the GroupMap
453 for x in GroupIDMap.keys():
455 GroupHasPrimaryMembers = {}
457 # Fetch all the users
460 # Sort them into a list of groups having a set of users
461 for x in PasswdAttrs:
462 a = UDLdap.Account(x[0], x[1])
463 GroupHasPrimaryMembers[ a['gidNumber'] ] = True
464 if not IsInGroup(x): continue
465 if not 'supplementaryGid' in a: continue
468 addGroups(supgroups, a['supplementaryGid'], a['uid'])
470 GroupMap[g].append(a['uid'])
472 # Output the group file.
474 for x in GroupMap.keys():
475 if GroupIDMap.has_key(x) == 0:
478 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
481 grouprevmap[GroupIDMap[x]] = x
483 Line = "%s:x:%u:" % (x, GroupIDMap[x])
485 for I in GroupMap[x]:
486 Line = Line + ("%s%s" % (Comma, I))
488 Line = Sanitize(Line) + "\n"
489 F.write("0%u %s" % (J, Line))
490 F.write(".%s %s" % (x, Line))
491 F.write("=%u %s" % (GroupIDMap[x], Line))
494 # Oops, something unspeakable happened.
504 for x in PasswdAttrs:
505 if x[1].has_key("emailForward") == 0:
509 x[1].pop("emailForward")
512 # Do not allow people to try to buffer overflow busted parsers
513 if len(GetAttr(x, "emailForward")) > 200:
514 x[1].pop("emailForward")
517 # Check the forwarding address
518 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
519 x[1].pop("emailForward")
521 # Generate the email forwarding list
522 def GenForward(File):
525 OldMask = os.umask(0022)
526 F = open(File + ".tmp", "w", 0644)
529 # Fetch all the users
532 # Write out the email address for each user
533 for x in PasswdAttrs:
534 a = UDLdap.Account(x[0], x[1])
535 if not 'emailForward' in a: continue
536 Line = "%s: %s" % (a['uid'], a['emailForward'])
537 Line = Sanitize(Line) + "\n"
540 # Oops, something unspeakable happened.
546 def GenCDB(File, Users, key):
549 OldMask = os.umask(0022)
550 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
553 # Write out the email address for each user
555 a = UDLdap.Account(x[0], x[1])
556 if not key in a: continue
559 Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
562 # Oops, something unspeakable happened.
566 if Fdb.close() != None:
567 raise "cdbmake gave an error"
569 # Generate the anon XEarth marker file
570 def GenMarkers(File):
573 F = open(File + ".tmp", "w")
575 # Fetch all the users
578 # Write out the position for each user
579 for x in PasswdAttrs:
580 a = UDLdap.Account(x[0], x[1])
581 if not ('latitude' in a and 'longitude' in a): continue
583 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
584 Line = Sanitize(Line) + "\n"
589 # Oops, something unspeakable happened.
595 # Generate the debian-private subscription list
596 def GenPrivate(File):
599 F = open(File + ".tmp", "w")
601 # Fetch all the users
604 # Write out the position for each user
605 for x in DebianDDUsers:
606 a = UDLdap.Account(x[0], x[1])
607 if not a.is_active_user(): continue
608 if not 'privateSub' in a: continue
610 Line = "%s"%(a['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 a = UDLdap.Account(x[0], x[1])
635 if a.pw_active(): continue
636 Line = "%s:%s" % (a['uid'], "Account is locked")
637 DisabledUsers.append(x)
638 F.write(Sanitize(Line) + "\n")
640 # Oops, something unspeakable happened.
646 # Generate the list of local addresses that refuse all mail
647 def GenMailDisable(File):
650 F = open(File + ".tmp", "w")
652 # Fetch all the users
655 for x in PasswdAttrs:
656 a = UDLdap.Account(x[0], x[1])
657 if not 'mailDisableMessage' in a: continue
658 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
659 Line = Sanitize(Line) + "\n"
662 # Oops, something unspeakable happened.
668 # Generate a list of uids that should have boolean affects applied
669 def GenMailBool(File, key):
672 F = open(File + ".tmp", "w")
674 # Fetch all the users
677 for x in PasswdAttrs:
678 a = UDLdap.Account(x[0], x[1])
679 if not key in a: continue
680 if not a[key] == 'TRUE': continue
681 Line = "%s"%(a['uid'])
682 Line = Sanitize(Line) + "\n"
685 # Oops, something unspeakable happened.
691 # Generate a list of hosts for RBL or whitelist purposes.
692 def GenMailList(File, key):
695 F = open(File + ".tmp", "w")
697 # Fetch all the users
700 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
701 else: validregex = re.compile('^[-\w.]+$')
703 for x in PasswdAttrs:
704 a = UDLdap.Account(x[0], x[1])
705 if not key in a: continue
707 filtered = filter(lambda z: validregex.match(z), a[key])
708 if len(filtered) == 0: continue
709 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
710 line = a['uid'] + ': ' + ' : '.join(filtered)
711 line = Sanitize(line) + "\n"
714 # Oops, something unspeakable happened.
720 def isRoleAccount(pwEntry):
721 if not pwEntry.has_key("objectClass"):
722 raise "pwEntry has no objectClass"
723 oc = pwEntry['objectClass']
725 i = oc.index('debianRoleAccount')
730 # Generate the DNS Zone file
734 F = open(File + ".tmp", "w")
736 # Fetch all the users
740 # Write out the zone file entry for each user
741 for x in PasswdAttrs:
742 if x[1].has_key("dnsZoneEntry") == 0:
745 # If the account has no PGP key, do not write it
746 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
749 F.write("; %s\n"%(EmailAddress(x)))
750 for z in x[1]["dnsZoneEntry"]:
751 Split = z.lower().split()
752 if Split[1].lower() == 'in':
753 for y in range(0, len(Split)):
756 Line = " ".join(Split) + "\n"
759 Host = Split[0] + DNSZone
760 if BSMTPCheck.match(Line) != None:
761 F.write("; Has BSMTP\n")
763 # Write some identification information
764 if not RRs.has_key(Host):
765 if Split[2].lower() in ["a", "aaaa"]:
766 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
767 for y in x[1]["keyFingerPrint"]:
768 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
772 Line = "; Err %s"%(str(Split))
777 F.write("; Errors\n")
780 # Oops, something unspeakable happened.
786 def ExtractDNSInfo(x):
790 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
793 if x[1].has_key("ipHostNumber"):
794 for I in x[1]["ipHostNumber"]:
795 if IsV6Addr.match(I) != None:
796 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
798 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
802 if 'sshRSAHostKey' in x[1]:
803 for I in x[1]["sshRSAHostKey"]:
805 if Split[0] == 'ssh-rsa':
807 if Split[0] == 'ssh-dss':
809 if Algorithm == None:
811 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
812 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
814 if 'architecture' in x[1]:
815 Arch = GetAttr(x, "architecture")
817 if x[1].has_key("machine"):
818 Mach = " " + GetAttr(x, "machine")
819 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
821 if x[1].has_key("mXRecord"):
822 for I in x[1]["mXRecord"]:
823 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
827 # Generate the DNS records
828 def GenZoneRecords(File):
831 F = open(File + ".tmp", "w")
833 # Fetch all the hosts
837 if x[1].has_key("hostname") == 0:
840 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
843 DNSInfo = ExtractDNSInfo(x)
847 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
850 Line = "\t\t\t%s" % (Line)
854 # this would write sshfp lines for services on machines
855 # but we can't yet, since some are cnames and we'll make
856 # an invalid zonefile
858 # for i in x[1].get("purpose", []):
859 # m = PurposeHostField.match(i)
862 # # we ignore [[*..]] entries
863 # if m.startswith('*'):
865 # if m.startswith('-'):
868 # if not m.endswith(HostDomain):
870 # if not m.endswith('.'):
872 # for Line in DNSInfo:
873 # if isSSHFP.match(Line):
874 # Line = "%s\t%s" % (m, Line)
875 # F.write(Line + "\n")
877 # Oops, something unspeakable happened.
883 # Generate the BSMTP file
884 def GenBSMTP(File, HomePrefix):
887 F = open(File + ".tmp", "w")
889 # Fetch all the users
892 # Write out the zone file entry for each user
893 for x in PasswdAttrs:
894 if x[1].has_key("dnsZoneEntry") == 0:
897 # If the account has no PGP key, do not write it
898 if x[1].has_key("keyFingerPrint") == 0:
901 for z in x[1]["dnsZoneEntry"]:
902 Split = z.lower().split()
903 if Split[1].lower() == 'in':
904 for y in range(0, len(Split)):
907 Line = " ".join(Split) + "\n"
909 Host = Split[0] + DNSZone
910 if BSMTPCheck.match(Line) != None:
911 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
912 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
915 F.write("; Errors\n")
918 # Oops, something unspeakable happened.
924 def HostToIP(Host, mapped=True):
928 if Host[1].has_key("ipHostNumber"):
929 for addr in Host[1]["ipHostNumber"]:
930 IPAdresses.append(addr)
931 if IsV6Addr.match(addr) is None and mapped == "True":
932 IPAdresses.append("::ffff:"+addr)
936 # Generate the ssh known hosts file
937 def GenSSHKnown(File, mode=None):
940 OldMask = os.umask(0022)
941 F = open(File + ".tmp", "w", 0644)
947 if x[1].has_key("hostname") == 0 or \
948 x[1].has_key("sshRSAHostKey") == 0:
950 Host = GetAttr(x, "hostname")
952 if Host.endswith(HostDomain):
953 HostNames.append(Host[:-(len(HostDomain) + 1)])
955 # in the purpose field [[host|some other text]] (where some other text is optional)
956 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
957 # file. But so that we don't have to add everything we link we can add an asterisk
958 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
959 # http linking it we also support [[-hostname]] entries.
960 for i in x[1].get("purpose", []):
961 m = PurposeHostField.match(i)
964 # we ignore [[*..]] entries
965 if m.startswith('*'):
967 if m.startswith('-'):
971 if m.endswith(HostDomain):
972 HostNames.append(m[:-(len(HostDomain) + 1)])
974 for I in x[1]["sshRSAHostKey"]:
975 if mode and mode == 'authorized_keys':
977 if 'sshdistAuthKeysHost' in x[1]:
978 hosts += x[1]['sshdistAuthKeysHost']
979 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)
981 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
982 Line = Sanitize(Line) + "\n"
984 # Oops, something unspeakable happened.
990 # Generate the debianhosts file (list of all IP addresses)
994 OldMask = os.umask(0022)
995 F = open(File + ".tmp", "w", 0644)
1004 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1007 if not 'ipHostNumber' in x[1]:
1010 addrs = x[1]["ipHostNumber"]
1012 if addr not in seen:
1014 addr = Sanitize(addr) + "\n"
1017 # Oops, something unspeakable happened.
1023 def GenKeyrings(OutDir):
1025 shutil.copy(k, OutDir)
1027 # Connect to the ldap server
1029 # for testing purposes it's sometimes useful to pass username/password
1030 # via the environment
1031 if 'UD_CREDENTIALS' in os.environ:
1032 Pass = os.environ['UD_CREDENTIALS'].split()
1034 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1035 Pass = F.readline().strip().split(" ")
1037 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1039 # Fetch all the groups
1041 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1042 ["gid", "gidNumber", "subGroup"])
1044 # Generate the SubGroupMap and GroupIDMap
1046 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1048 if x[1].has_key("gidNumber") == 0:
1050 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1051 if x[1].has_key("subGroup") != 0:
1052 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1054 # Fetch all the users
1055 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
1056 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1057 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1058 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1059 "shadowExpire", "emailForward", "latitude", "longitude",\
1060 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1061 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1062 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1063 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1064 "mailContentInspectionAction"])
1066 if PasswdAttrs is None:
1067 raise UDEmptyList, "No Users"
1069 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1071 # Fetch all the hosts
1072 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1073 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1074 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1076 if HostAttrs == None:
1077 raise UDEmptyList, "No Hosts"
1079 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1081 # override globaldir for testing
1082 if 'UD_GENERATEDIR' in os.environ:
1083 GenerateDir = os.environ['UD_GENERATEDIR']
1085 # Generate global things
1086 GlobalDir = GenerateDir + "/"
1087 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1089 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1090 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1094 GenMailDisable(GlobalDir + "mail-disable")
1095 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1096 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1097 GenPrivate(GlobalDir + "debian-private")
1098 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1099 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1100 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1101 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1102 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1103 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1104 GenKeyrings(GlobalDir)
1107 GenForward(GlobalDir + "forward-alias")
1109 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1111 SSHFiles = GenSSHShadow()
1112 GenMarkers(GlobalDir + "markers")
1113 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1114 GenHosts(GlobalDir + "debianhosts")
1116 for host in HostAttrs:
1117 if not "hostname" in host[1]:
1120 CurrentHost = host[1]['hostname'][0]
1121 OutDir = GenerateDir + '/' + CurrentHost + '/'
1127 # Get the group list and convert any named groups to numerics
1129 for groupname in AllowedGroupsPreload.strip().split(" "):
1130 GroupList[groupname] = True
1131 if 'allowedGroups' in host[1]:
1132 for groupname in host[1]['allowedGroups']:
1133 GroupList[groupname] = True
1134 for groupname in GroupList.keys():
1135 if groupname in GroupIDMap:
1136 GroupList[str(GroupIDMap[groupname])] = True
1139 if 'exportOptions' in host[1]:
1140 for extra in host[1]['exportOptions']:
1141 ExtraList[extra.upper()] = True
1147 DoLink(GlobalDir, OutDir, "debianhosts")
1148 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1149 DoLink(GlobalDir, OutDir, "disabled-accounts")
1152 if 'NOPASSWD' in ExtraList:
1153 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1155 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1157 grouprevmap = GenGroup(OutDir + "group")
1158 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1160 # Now we know who we're allowing on the machine, export
1161 # the relevant ssh keys
1162 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1164 if not 'NOPASSWD' in ExtraList:
1165 GenShadow(OutDir + "shadow")
1167 # Link in global things
1168 if not 'NOMARKERS' in ExtraList:
1169 DoLink(GlobalDir, OutDir, "markers")
1170 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1171 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1172 DoLink(GlobalDir, OutDir, "mail-disable")
1173 DoLink(GlobalDir, OutDir, "mail-greylist")
1174 DoLink(GlobalDir, OutDir, "mail-callout")
1175 DoLink(GlobalDir, OutDir, "mail-rbl")
1176 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1177 DoLink(GlobalDir, OutDir, "mail-whitelist")
1178 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1179 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1180 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1183 DoLink(GlobalDir, OutDir, "forward-alias")
1185 if 'DNS' in ExtraList:
1186 GenDNS(OutDir + "dns-zone")
1187 GenZoneRecords(OutDir + "dns-sshfp")
1189 if 'AUTHKEYS' in ExtraList:
1190 DoLink(GlobalDir, OutDir, "authorized_keys")
1192 if 'BSMTP' in ExtraList:
1193 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1195 if 'PRIVATE' in ExtraList:
1196 DoLink(GlobalDir, OutDir, "debian-private")
1198 if 'KEYRING' in ExtraList:
1200 DoLink(GlobalDir, OutDir, os.path.basename(k))
1204 posix.remove(OutDir + os.path.basename(k))
1210 # vim:set shiftwidth=3: