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 uid = GetAttr(x, "uid")
463 if 'gidNumber' in x[1]:
464 GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
465 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
467 if x[1].has_key("supplementaryGid") == 0:
471 addGroups(supgroups, x[1]["supplementaryGid"], uid)
473 GroupMap[g].append(uid)
475 # Output the group file.
477 for x in GroupMap.keys():
478 if GroupIDMap.has_key(x) == 0:
481 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
484 grouprevmap[GroupIDMap[x]] = x
486 Line = "%s:x:%u:" % (x, GroupIDMap[x])
488 for I in GroupMap[x]:
489 Line = Line + ("%s%s" % (Comma, I))
491 Line = Sanitize(Line) + "\n"
492 F.write("0%u %s" % (J, Line))
493 F.write(".%s %s" % (x, Line))
494 F.write("=%u %s" % (GroupIDMap[x], Line))
497 # Oops, something unspeakable happened.
507 for x in PasswdAttrs:
508 if x[1].has_key("emailForward") == 0:
512 x[1].pop("emailForward")
515 # Do not allow people to try to buffer overflow busted parsers
516 if len(GetAttr(x, "emailForward")) > 200:
517 x[1].pop("emailForward")
520 # Check the forwarding address
521 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
522 x[1].pop("emailForward")
524 # Generate the email forwarding list
525 def GenForward(File):
528 OldMask = os.umask(0022)
529 F = open(File + ".tmp", "w", 0644)
532 # Fetch all the users
535 # Write out the email address for each user
536 for x in PasswdAttrs:
537 if x[1].has_key("emailForward") == 0:
540 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
541 Line = Sanitize(Line) + "\n"
544 # Oops, something unspeakable happened.
550 def GenCDB(File, Users, Key):
553 OldMask = os.umask(0022)
554 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
557 # Write out the email address for each user
561 Value = GetAttr(x, Key)
562 User = GetAttr(x, "uid")
563 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
566 # Oops, something unspeakable happened.
570 if Fdb.close() != None:
571 raise "cdbmake gave an error"
573 # Generate the anon XEarth marker file
574 def GenMarkers(File):
577 F = open(File + ".tmp", "w")
579 # Fetch all the users
582 # Write out the position for each user
583 for x in PasswdAttrs:
584 a = UDLdap.Account(x[0], x[1])
585 if not ('latitude' in a and 'longitude' in a):
588 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
589 Line = Sanitize(Line) + "\n"
594 # Oops, something unspeakable happened.
600 # Generate the debian-private subscription list
601 def GenPrivate(File):
604 F = open(File + ".tmp", "w")
606 # Fetch all the users
609 # Write out the position for each user
610 for x in DebianDDUsers:
611 if x[1].has_key("privateSub") == 0:
614 # If the account has no PGP key, do not write it
615 if x[1].has_key("keyFingerPrint") == 0:
619 Line = "%s"%(GetAttr(x, "privateSub"))
620 Line = Sanitize(Line) + "\n"
625 # Oops, something unspeakable happened.
631 # Generate a list of locked accounts
632 def GenDisabledAccounts(File):
635 F = open(File + ".tmp", "w")
637 # Fetch all the users
642 for x in PasswdAttrs:
643 if x[1].has_key("uidNumber") == 0:
646 Pass = GetAttr(x, "userPassword")
648 # *LK* is the reference value for a locked account
649 # password starting with ! is also a locked account
650 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
651 # Format is <login>:<reason>
652 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
653 DisabledUsers.append(x)
656 F.write(Sanitize(Line) + "\n")
659 # Oops, something unspeakable happened.
665 # Generate the list of local addresses that refuse all mail
666 def GenMailDisable(File):
669 F = open(File + ".tmp", "w")
671 # Fetch all the users
674 for x in PasswdAttrs:
677 if x[1].has_key("mailDisableMessage"):
678 Reason = GetAttr(x, "mailDisableMessage")
683 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
684 Line = Sanitize(Line) + "\n"
689 # Oops, something unspeakable happened.
695 # Generate a list of uids that should have boolean affects applied
696 def GenMailBool(File, Key):
699 F = open(File + ".tmp", "w")
701 # Fetch all the users
704 for x in PasswdAttrs:
707 if x[1].has_key(Key) == 0:
710 if GetAttr(x, Key) != "TRUE":
714 Line = "%s"%(GetAttr(x, "uid"))
715 Line = Sanitize(Line) + "\n"
720 # Oops, something unspeakable happened.
726 # Generate a list of hosts for RBL or whitelist purposes.
727 def GenMailList(File, Key):
730 F = open(File + ".tmp", "w")
732 # Fetch all the users
735 for x in PasswdAttrs:
738 if x[1].has_key(Key) == 0:
745 if Key == "mailWhitelist":
746 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
749 if re.match('^[-\w.]+$', z) == None:
753 Line = GetAttr(x, "uid")
757 if Key == "mailRHSBL":
758 Line += "/$sender_address_domain"
761 Line = Sanitize(Line) + "\n"
766 # Oops, something unspeakable happened.
772 def isRoleAccount(pwEntry):
773 if not pwEntry.has_key("objectClass"):
774 raise "pwEntry has no objectClass"
775 oc = pwEntry['objectClass']
777 i = oc.index('debianRoleAccount')
782 # Generate the DNS Zone file
786 F = open(File + ".tmp", "w")
788 # Fetch all the users
792 # Write out the zone file entry for each user
793 for x in PasswdAttrs:
794 if x[1].has_key("dnsZoneEntry") == 0:
797 # If the account has no PGP key, do not write it
798 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
801 F.write("; %s\n"%(EmailAddress(x)))
802 for z in x[1]["dnsZoneEntry"]:
803 Split = z.lower().split()
804 if Split[1].lower() == 'in':
805 for y in range(0, len(Split)):
808 Line = " ".join(Split) + "\n"
811 Host = Split[0] + DNSZone
812 if BSMTPCheck.match(Line) != None:
813 F.write("; Has BSMTP\n")
815 # Write some identification information
816 if not RRs.has_key(Host):
817 if Split[2].lower() in ["a", "aaaa"]:
818 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
819 for y in x[1]["keyFingerPrint"]:
820 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
824 Line = "; Err %s"%(str(Split))
829 F.write("; Errors\n")
832 # Oops, something unspeakable happened.
838 def ExtractDNSInfo(x):
842 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
845 if x[1].has_key("ipHostNumber"):
846 for I in x[1]["ipHostNumber"]:
847 if IsV6Addr.match(I) != None:
848 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
850 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
854 if 'sshRSAHostKey' in x[1]:
855 for I in x[1]["sshRSAHostKey"]:
857 if Split[0] == 'ssh-rsa':
859 if Split[0] == 'ssh-dss':
861 if Algorithm == None:
863 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
864 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
866 if 'architecture' in x[1]:
867 Arch = GetAttr(x, "architecture")
869 if x[1].has_key("machine"):
870 Mach = " " + GetAttr(x, "machine")
871 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
873 if x[1].has_key("mXRecord"):
874 for I in x[1]["mXRecord"]:
875 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
879 # Generate the DNS records
880 def GenZoneRecords(File):
883 F = open(File + ".tmp", "w")
885 # Fetch all the hosts
889 if x[1].has_key("hostname") == 0:
892 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
895 DNSInfo = ExtractDNSInfo(x)
899 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
902 Line = "\t\t\t%s" % (Line)
906 # this would write sshfp lines for services on machines
907 # but we can't yet, since some are cnames and we'll make
908 # an invalid zonefile
910 # for i in x[1].get("purpose", []):
911 # m = PurposeHostField.match(i)
914 # # we ignore [[*..]] entries
915 # if m.startswith('*'):
917 # if m.startswith('-'):
920 # if not m.endswith(HostDomain):
922 # if not m.endswith('.'):
924 # for Line in DNSInfo:
925 # if isSSHFP.match(Line):
926 # Line = "%s\t%s" % (m, Line)
927 # F.write(Line + "\n")
929 # Oops, something unspeakable happened.
935 # Generate the BSMTP file
936 def GenBSMTP(File, HomePrefix):
939 F = open(File + ".tmp", "w")
941 # Fetch all the users
944 # Write out the zone file entry for each user
945 for x in PasswdAttrs:
946 if x[1].has_key("dnsZoneEntry") == 0:
949 # If the account has no PGP key, do not write it
950 if x[1].has_key("keyFingerPrint") == 0:
953 for z in x[1]["dnsZoneEntry"]:
954 Split = z.lower().split()
955 if Split[1].lower() == 'in':
956 for y in range(0, len(Split)):
959 Line = " ".join(Split) + "\n"
961 Host = Split[0] + DNSZone
962 if BSMTPCheck.match(Line) != None:
963 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
964 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
967 F.write("; Errors\n")
970 # Oops, something unspeakable happened.
976 def HostToIP(Host, mapped=True):
980 if Host[1].has_key("ipHostNumber"):
981 for addr in Host[1]["ipHostNumber"]:
982 IPAdresses.append(addr)
983 if IsV6Addr.match(addr) is None and mapped == "True":
984 IPAdresses.append("::ffff:"+addr)
988 # Generate the ssh known hosts file
989 def GenSSHKnown(File, mode=None):
992 OldMask = os.umask(0022)
993 F = open(File + ".tmp", "w", 0644)
999 if x[1].has_key("hostname") == 0 or \
1000 x[1].has_key("sshRSAHostKey") == 0:
1002 Host = GetAttr(x, "hostname")
1003 HostNames = [ Host ]
1004 if Host.endswith(HostDomain):
1005 HostNames.append(Host[:-(len(HostDomain) + 1)])
1007 # in the purpose field [[host|some other text]] (where some other text is optional)
1008 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
1009 # file. But so that we don't have to add everything we link we can add an asterisk
1010 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
1011 # http linking it we also support [[-hostname]] entries.
1012 for i in x[1].get("purpose", []):
1013 m = PurposeHostField.match(i)
1016 # we ignore [[*..]] entries
1017 if m.startswith('*'):
1019 if m.startswith('-'):
1023 if m.endswith(HostDomain):
1024 HostNames.append(m[:-(len(HostDomain) + 1)])
1026 for I in x[1]["sshRSAHostKey"]:
1027 if mode and mode == 'authorized_keys':
1029 if 'sshdistAuthKeysHost' in x[1]:
1030 hosts += x[1]['sshdistAuthKeysHost']
1031 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)
1033 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1034 Line = Sanitize(Line) + "\n"
1036 # Oops, something unspeakable happened.
1042 # Generate the debianhosts file (list of all IP addresses)
1046 OldMask = os.umask(0022)
1047 F = open(File + ".tmp", "w", 0644)
1056 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1059 if not 'ipHostNumber' in x[1]:
1062 addrs = x[1]["ipHostNumber"]
1064 if addr not in seen:
1066 addr = Sanitize(addr) + "\n"
1069 # Oops, something unspeakable happened.
1075 def GenKeyrings(OutDir):
1077 shutil.copy(k, OutDir)
1079 # Connect to the ldap server
1081 # for testing purposes it's sometimes useful to pass username/password
1082 # via the environment
1083 if 'UD_CREDENTIALS' in os.environ:
1084 Pass = os.environ['UD_CREDENTIALS'].split()
1086 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1087 Pass = F.readline().strip().split(" ")
1089 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1091 # Fetch all the groups
1093 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1094 ["gid", "gidNumber", "subGroup"])
1096 # Generate the SubGroupMap and GroupIDMap
1098 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1100 if x[1].has_key("gidNumber") == 0:
1102 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1103 if x[1].has_key("subGroup") != 0:
1104 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1106 # Fetch all the users
1107 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1108 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1109 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1110 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1111 "shadowExpire", "emailForward", "latitude", "longitude",\
1112 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1113 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1114 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1115 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1116 "mailContentInspectionAction"])
1118 if PasswdAttrs is None:
1119 raise UDEmptyList, "No Users"
1121 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1123 # Fetch all the hosts
1124 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1125 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1126 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1128 if HostAttrs == None:
1129 raise UDEmptyList, "No Hosts"
1131 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1133 # override globaldir for testing
1134 if 'UD_GENERATEDIR' in os.environ:
1135 GenerateDir = os.environ['UD_GENERATEDIR']
1137 # Generate global things
1138 GlobalDir = GenerateDir + "/"
1139 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1141 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1142 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1146 GenMailDisable(GlobalDir + "mail-disable")
1147 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1148 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1149 GenPrivate(GlobalDir + "debian-private")
1150 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1151 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1152 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1153 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1154 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1155 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1156 GenKeyrings(GlobalDir)
1159 GenForward(GlobalDir + "forward-alias")
1161 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1163 SSHFiles = GenSSHShadow()
1164 GenMarkers(GlobalDir + "markers")
1165 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1166 GenHosts(GlobalDir + "debianhosts")
1168 for host in HostAttrs:
1169 if not "hostname" in host[1]:
1172 CurrentHost = host[1]['hostname'][0]
1173 OutDir = GenerateDir + '/' + CurrentHost + '/'
1179 # Get the group list and convert any named groups to numerics
1181 for groupname in AllowedGroupsPreload.strip().split(" "):
1182 GroupList[groupname] = True
1183 if 'allowedGroups' in host[1]:
1184 for groupname in host[1]['allowedGroups']:
1185 GroupList[groupname] = True
1186 for groupname in GroupList.keys():
1187 if groupname in GroupIDMap:
1188 GroupList[str(GroupIDMap[groupname])] = True
1191 if 'exportOptions' in host[1]:
1192 for extra in host[1]['exportOptions']:
1193 ExtraList[extra.upper()] = True
1199 DoLink(GlobalDir, OutDir, "debianhosts")
1200 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1201 DoLink(GlobalDir, OutDir, "disabled-accounts")
1204 if 'NOPASSWD' in ExtraList:
1205 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1207 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1209 grouprevmap = GenGroup(OutDir + "group")
1210 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1212 # Now we know who we're allowing on the machine, export
1213 # the relevant ssh keys
1214 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1216 if not 'NOPASSWD' in ExtraList:
1217 GenShadow(OutDir + "shadow")
1219 # Link in global things
1220 if not 'NOMARKERS' in ExtraList:
1221 DoLink(GlobalDir, OutDir, "markers")
1222 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1223 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1224 DoLink(GlobalDir, OutDir, "mail-disable")
1225 DoLink(GlobalDir, OutDir, "mail-greylist")
1226 DoLink(GlobalDir, OutDir, "mail-callout")
1227 DoLink(GlobalDir, OutDir, "mail-rbl")
1228 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1229 DoLink(GlobalDir, OutDir, "mail-whitelist")
1230 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1231 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1232 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1235 DoLink(GlobalDir, OutDir, "forward-alias")
1237 if 'DNS' in ExtraList:
1238 GenDNS(OutDir + "dns-zone")
1239 GenZoneRecords(OutDir + "dns-sshfp")
1241 if 'AUTHKEYS' in ExtraList:
1242 DoLink(GlobalDir, OutDir, "authorized_keys")
1244 if 'BSMTP' in ExtraList:
1245 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1247 if 'PRIVATE' in ExtraList:
1248 DoLink(GlobalDir, OutDir, "debian-private")
1250 if 'KEYRING' in ExtraList:
1252 DoLink(GlobalDir, OutDir, os.path.basename(k))
1256 posix.remove(OutDir + os.path.basename(k))
1262 # vim:set shiftwidth=3: