3 # Generates passwd, shadow and group files from the ldap directory.
5 # Copyright (c) 2000-2001 Jason Gunthorpe <jgg@debian.org>
6 # Copyright (c) 2003-2004 James Troup <troup@debian.org>
7 # Copyright (c) 2004-2005,7 Joey Schulze <joey@infodrom.org>
8 # Copyright (c) 2001-2007 Ryan Murray <rmurray@debian.org>
9 # Copyright (c) 2008,2009,2010 Peter Palfrader <peter@palfrader.org>
10 # Copyright (c) 2008 Andreas Barth <aba@not.so.argh.org>
11 # Copyright (c) 2008 Mark Hymers <mhy@debian.org>
12 # Copyright (c) 2008 Luk Claes <luk@debian.org>
13 # Copyright (c) 2008 Thomas Viehmann <tv@beamnet.de>
14 # Copyright (c) 2009 Stephen Gran <steve@lobefin.net>
15 # Copyright (c) 2010 Helmut Grohne <helmut@subdivi.de>
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with this program; if not, write to the Free Software
29 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
32 from userdir_ldap import *
33 from userdir_exceptions import *
35 from cStringIO import StringIO
37 from StringIO import StringIO
43 sys.stderr.write("You should probably not run ud-generate as root.\n")
54 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
56 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
57 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
58 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
59 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
60 IsDebianHost = re.compile(ConfModule.dns_hostmatch)
61 isSSHFP = re.compile("^\s*IN\s+SSHFP")
62 DNSZone = ".debian.net"
63 Keyrings = ConfModule.sync_keyrings.split(":")
65 def safe_makedirs(dir):
69 if e.errno == errno.EEXIST:
78 if e.errno == errno.ENOENT:
84 return Str.translate(string.maketrans("\n\r\t", "$$$"))
86 def DoLink(From, To, File):
88 posix.remove(To + File)
91 posix.link(From + File, To + File)
93 def IsRetired(DnRecord):
95 Looks for accountStatus in the LDAP record and tries to
96 match it against one of the known retired statuses
99 status = GetAttr(DnRecord, "accountStatus", None)
103 line = status.split()
106 if status == "inactive":
109 elif status == "memorial":
112 elif status == "retiring":
113 # We'll give them a few extra days over what we said
114 age = 6 * 31 * 24 * 60 * 60
116 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
126 return int(GetAttr(x, "gidNumber", 0)) == 800
130 # See if this user is in the group list
131 def IsInGroup(DnRecord):
135 # See if the primary group is in the list
136 if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0:
139 # Check the host based ACL
140 if DnRecord[1].has_key("allowedHost") != 0:
141 if CurrentHost in DnRecord[1]["allowedHost"]:
144 # See if there are supplementary groups
145 if DnRecord[1].has_key("supplementaryGid") == 0:
149 addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "uid"))
151 if Allowed.has_key(g):
155 def Die(File, F, Fdb):
161 os.remove(File + ".tmp")
165 os.remove(File + ".tdb.tmp")
169 def Done(File, F, Fdb):
172 os.rename(File + ".tmp", File)
175 os.rename(File + ".tdb.tmp", File + ".tdb")
177 # Generate the password list
178 def GenPasswd(File, HomePrefix, PwdMarker):
181 F = open(File + ".tdb.tmp", "w")
184 # Fetch all the users
188 for x in PasswdAttrs:
189 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
192 # Do not let people try to buffer overflow some busted passwd parser.
193 if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50:
196 userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
197 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\
199 GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\
200 GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\
201 GetAttr(x, "loginShell"))
203 Line = Sanitize(Line) + "\n"
204 F.write("0%u %s" % (I, Line))
205 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
206 F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line))
209 # Oops, something unspeakable happened.
215 # Return the list of users so we know which keys to export
218 # Generate the shadow list
222 OldMask = os.umask(0077)
223 F = open(File + ".tdb.tmp", "w", 0600)
226 # Fetch all the users
230 for x in PasswdAttrs:
231 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
234 Pass = GetAttr(x, "userPassword")
235 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
240 # If the account is locked, mark it as such in shadow
241 # See Debian Bug #308229 for why we set it to 1 instead of 0
242 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
243 or GetAttr(x, "userPassword").startswith("!"):
246 ShadowExpire = GetAttr(x, "shadowExpire")
248 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
249 Pass, GetAttr(x, "shadowLastChange"),\
250 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
251 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
253 Line = Sanitize(Line) + "\n"
254 F.write("0%u %s" % (I, Line))
255 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
258 # Oops, something unspeakable happened.
264 # Generate the sudo passwd file
265 def GenShadowSudo(File, untrusted):
268 OldMask = os.umask(0077)
269 F = open(File + ".tmp", "w", 0600)
272 # Fetch all the users
275 for x in PasswdAttrs:
277 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
280 if x[1].has_key('sudoPassword'):
281 for entry in x[1]['sudoPassword']:
282 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
285 uuid = Match.group(1)
286 status = Match.group(2)
287 hosts = Match.group(3)
288 cryptedpass = Match.group(4)
290 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
292 for_all = hosts == "*"
293 for_this_host = CurrentHost in hosts.split(',')
294 if not (for_all or for_this_host):
296 # ignore * passwords for untrusted hosts, but copy host specific passwords
297 if for_all and untrusted:
300 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
305 Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
306 Line = Sanitize(Line) + "\n"
307 F.write("%s" % (Line))
309 # Oops, something unspeakable happened.
315 # Generate the shadow list
317 # Fetch all the users
322 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
323 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
325 for x in PasswdAttrs:
327 if x[1].has_key("uidNumber") == 0 or \
328 x[1].has_key("sshRSAAuthKey") == 0:
331 User = GetAttr(x, "uid")
335 OldMask = os.umask(0077)
336 File = os.path.join(GlobalDir, 'userkeys', User)
337 F = open(File + ".tmp", "w", 0600)
340 for I in x[1]["sshRSAAuthKey"]:
341 MultipleLine = "%s" % I
342 MultipleLine = Sanitize(MultipleLine) + "\n"
343 F.write(MultipleLine)
346 userfiles.append(os.path.basename(File))
348 # Oops, something unspeakable happened.
351 # As neither masterFileName nor masterFile are defined at any point
352 # this will raise a NameError.
353 Die(masterFileName, masterFile, None)
358 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
359 OldMask = os.umask(0077)
360 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
362 for f in userlist.keys():
363 if f not in SSHFiles:
365 # If we're not exporting their primary group, don't export
368 if userlist[f] in grouprevmap.keys():
369 grname = grouprevmap[userlist[f]]
372 if int(userlist[f]) <= 100:
373 # In these cases, look it up in the normal way so we
374 # deal with cases where, for instance, users are in group
375 # users as their primary group.
376 grname = grp.getgrgid(userlist[f])[0]
381 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])
384 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
385 # These will only be used where the username doesn't
386 # exist on the target system for some reason; hence,
387 # in those cases, the safest thing is for the file to
388 # be owned by root but group nobody. This deals with
389 # the bloody obscure case where the group fails to exist
390 # whilst the user does (in which case we want to avoid
391 # ending up with a file which is owned user:root to avoid
392 # a fairly obvious attack vector)
395 # Using the username / groupname fields avoids any need
396 # to give a shit^W^W^Wcare about the UIDoffset stuff.
401 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
403 for line in contents.splitlines():
404 if line.startswith("allowed_hosts=") and ' ' in line:
405 machines, line = line.split('=', 1)[1].split(' ', 1)
406 if CurrentHost not in machines.split(','):
407 continue # skip this key
410 continue # no keys for this host
411 contents = "\n".join(lines) + "\n"
412 to.size = len(contents)
413 tf.addfile(to, StringIO(contents))
416 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
418 # add a list of groups to existing groups,
419 # including all subgroups thereof, recursively.
420 # basically this proceduces the transitive hull of the groups in
422 def addGroups(existingGroups, newGroups, uid):
423 for group in newGroups:
424 # if it's a <group>@host, split it and verify it's on the current host.
425 s = group.split('@', 1)
426 if len(s) == 2 and s[1] != CurrentHost:
430 # let's see if we handled this group already
431 if group in existingGroups:
434 if not GroupIDMap.has_key(group):
435 print "Group", group, "does not exist but", uid, "is in it"
438 existingGroups.append(group)
440 if SubGroupMap.has_key(group):
441 addGroups(existingGroups, SubGroupMap[group], uid)
443 # Generate the group list
448 F = open(File + ".tdb.tmp", "w")
450 # Generate the GroupMap
452 for x in GroupIDMap.keys():
454 GroupHasPrimaryMembers = {}
456 # Fetch all the users
459 # Sort them into a list of groups having a set of users
460 for x in PasswdAttrs:
461 uid = GetAttr(x, "uid")
462 if 'gidNumber' in x[1]:
463 GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
464 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
466 if x[1].has_key("supplementaryGid") == 0:
470 addGroups(supgroups, x[1]["supplementaryGid"], uid)
472 GroupMap[g].append(uid)
474 # Output the group file.
476 for x in GroupMap.keys():
477 if GroupIDMap.has_key(x) == 0:
480 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
483 grouprevmap[GroupIDMap[x]] = x
485 Line = "%s:x:%u:" % (x, GroupIDMap[x])
487 for I in GroupMap[x]:
488 Line = Line + ("%s%s" % (Comma, I))
490 Line = Sanitize(Line) + "\n"
491 F.write("0%u %s" % (J, Line))
492 F.write(".%s %s" % (x, Line))
493 F.write("=%u %s" % (GroupIDMap[x], Line))
496 # Oops, something unspeakable happened.
506 for x in PasswdAttrs:
507 if x[1].has_key("emailForward") == 0:
511 x[1].pop("emailForward")
514 # Do not allow people to try to buffer overflow busted parsers
515 if len(GetAttr(x, "emailForward")) > 200:
516 x[1].pop("emailForward")
519 # Check the forwarding address
520 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
521 x[1].pop("emailForward")
523 # Generate the email forwarding list
524 def GenForward(File):
527 OldMask = os.umask(0022)
528 F = open(File + ".tmp", "w", 0644)
531 # Fetch all the users
534 # Write out the email address for each user
535 for x in PasswdAttrs:
536 if x[1].has_key("emailForward") == 0:
539 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
540 Line = Sanitize(Line) + "\n"
543 # Oops, something unspeakable happened.
549 def GenCDB(File, Users, Key):
552 OldMask = os.umask(0022)
553 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
556 # Write out the email address for each user
560 Value = GetAttr(x, Key)
561 User = GetAttr(x, "uid")
562 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
565 # Oops, something unspeakable happened.
569 if Fdb.close() != None:
570 raise "cdbmake gave an error"
572 # Generate the anon XEarth marker file
573 def GenMarkers(File):
576 F = open(File + ".tmp", "w")
578 # Fetch all the users
581 # Write out the position for each user
582 for x in PasswdAttrs:
583 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
586 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
587 Line = Sanitize(Line) + "\n"
592 # Oops, something unspeakable happened.
598 # Generate the debian-private subscription list
599 def GenPrivate(File):
602 F = open(File + ".tmp", "w")
604 # Fetch all the users
607 # Write out the position for each user
608 for x in DebianDDUsers:
609 if x[1].has_key("privateSub") == 0:
612 # If the account has no PGP key, do not write it
613 if x[1].has_key("keyFingerPrint") == 0:
617 Line = "%s"%(GetAttr(x, "privateSub"))
618 Line = Sanitize(Line) + "\n"
623 # Oops, something unspeakable happened.
629 # Generate a list of locked accounts
630 def GenDisabledAccounts(File):
633 F = open(File + ".tmp", "w")
635 # Fetch all the users
640 for x in PasswdAttrs:
641 if x[1].has_key("uidNumber") == 0:
644 Pass = GetAttr(x, "userPassword")
646 # *LK* is the reference value for a locked account
647 # password starting with ! is also a locked account
648 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
649 # Format is <login>:<reason>
650 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
651 DisabledUsers.append(x)
654 F.write(Sanitize(Line) + "\n")
657 # Oops, something unspeakable happened.
663 # Generate the list of local addresses that refuse all mail
664 def GenMailDisable(File):
667 F = open(File + ".tmp", "w")
669 # Fetch all the users
672 for x in PasswdAttrs:
675 if x[1].has_key("mailDisableMessage"):
676 Reason = GetAttr(x, "mailDisableMessage")
681 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
682 Line = Sanitize(Line) + "\n"
687 # Oops, something unspeakable happened.
693 # Generate a list of uids that should have boolean affects applied
694 def GenMailBool(File, Key):
697 F = open(File + ".tmp", "w")
699 # Fetch all the users
702 for x in PasswdAttrs:
705 if x[1].has_key(Key) == 0:
708 if GetAttr(x, Key) != "TRUE":
712 Line = "%s"%(GetAttr(x, "uid"))
713 Line = Sanitize(Line) + "\n"
718 # Oops, something unspeakable happened.
724 # Generate a list of hosts for RBL or whitelist purposes.
725 def GenMailList(File, Key):
728 F = open(File + ".tmp", "w")
730 # Fetch all the users
733 for x in PasswdAttrs:
736 if x[1].has_key(Key) == 0:
743 if Key == "mailWhitelist":
744 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
747 if re.match('^[-\w.]+$', z) == None:
751 Line = GetAttr(x, "uid")
755 if Key == "mailRHSBL":
756 Line += "/$sender_address_domain"
759 Line = Sanitize(Line) + "\n"
764 # Oops, something unspeakable happened.
770 def isRoleAccount(pwEntry):
771 if not pwEntry.has_key("objectClass"):
772 raise "pwEntry has no objectClass"
773 oc = pwEntry['objectClass']
775 i = oc.index('debianRoleAccount')
780 # Generate the DNS Zone file
784 F = open(File + ".tmp", "w")
786 # Fetch all the users
790 # Write out the zone file entry for each user
791 for x in PasswdAttrs:
792 if x[1].has_key("dnsZoneEntry") == 0:
795 # If the account has no PGP key, do not write it
796 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
799 F.write("; %s\n"%(EmailAddress(x)))
800 for z in x[1]["dnsZoneEntry"]:
801 Split = z.lower().split()
802 if Split[1].lower() == 'in':
803 for y in range(0, len(Split)):
806 Line = " ".join(Split) + "\n"
809 Host = Split[0] + DNSZone
810 if BSMTPCheck.match(Line) != None:
811 F.write("; Has BSMTP\n")
813 # Write some identification information
814 if not RRs.has_key(Host):
815 if Split[2].lower() in ["a", "aaaa"]:
816 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
817 for y in x[1]["keyFingerPrint"]:
818 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
822 Line = "; Err %s"%(str(Split))
827 F.write("; Errors\n")
830 # Oops, something unspeakable happened.
836 def ExtractDNSInfo(x):
840 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
843 if x[1].has_key("ipHostNumber"):
844 for I in x[1]["ipHostNumber"]:
845 if IsV6Addr.match(I) != None:
846 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
848 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
852 if 'sshRSAHostKey' in x[1]:
853 for I in x[1]["sshRSAHostKey"]:
855 if Split[0] == 'ssh-rsa':
857 if Split[0] == 'ssh-dss':
859 if Algorithm == None:
861 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
862 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
864 if 'architecture' in x[1]:
865 Arch = GetAttr(x, "architecture")
867 if x[1].has_key("machine"):
868 Mach = " " + GetAttr(x, "machine")
869 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
871 if x[1].has_key("mXRecord"):
872 for I in x[1]["mXRecord"]:
873 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
877 # Generate the DNS records
878 def GenZoneRecords(File):
881 F = open(File + ".tmp", "w")
883 # Fetch all the hosts
887 if x[1].has_key("hostname") == 0:
890 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
893 DNSInfo = ExtractDNSInfo(x)
897 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
900 Line = "\t\t\t%s" % (Line)
904 # this would write sshfp lines for services on machines
905 # but we can't yet, since some are cnames and we'll make
906 # an invalid zonefile
908 # for i in x[1].get("purpose", []):
909 # m = PurposeHostField.match(i)
912 # # we ignore [[*..]] entries
913 # if m.startswith('*'):
915 # if m.startswith('-'):
918 # if not m.endswith(HostDomain):
920 # if not m.endswith('.'):
922 # for Line in DNSInfo:
923 # if isSSHFP.match(Line):
924 # Line = "%s\t%s" % (m, Line)
925 # F.write(Line + "\n")
927 # Oops, something unspeakable happened.
933 # Generate the BSMTP file
934 def GenBSMTP(File, HomePrefix):
937 F = open(File + ".tmp", "w")
939 # Fetch all the users
942 # Write out the zone file entry for each user
943 for x in PasswdAttrs:
944 if x[1].has_key("dnsZoneEntry") == 0:
947 # If the account has no PGP key, do not write it
948 if x[1].has_key("keyFingerPrint") == 0:
951 for z in x[1]["dnsZoneEntry"]:
952 Split = z.lower().split()
953 if Split[1].lower() == 'in':
954 for y in range(0, len(Split)):
957 Line = " ".join(Split) + "\n"
959 Host = Split[0] + DNSZone
960 if BSMTPCheck.match(Line) != None:
961 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
962 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
965 F.write("; Errors\n")
968 # Oops, something unspeakable happened.
974 def HostToIP(Host, mapped=True):
978 if Host[1].has_key("ipHostNumber"):
979 for addr in Host[1]["ipHostNumber"]:
980 IPAdresses.append(addr)
981 if IsV6Addr.match(addr) is None and mapped == "True":
982 IPAdresses.append("::ffff:"+addr)
986 # Generate the ssh known hosts file
987 def GenSSHKnown(File, mode=None):
990 OldMask = os.umask(0022)
991 F = open(File + ".tmp", "w", 0644)
997 if x[1].has_key("hostname") == 0 or \
998 x[1].has_key("sshRSAHostKey") == 0:
1000 Host = GetAttr(x, "hostname")
1001 HostNames = [ Host ]
1002 if Host.endswith(HostDomain):
1003 HostNames.append(Host[:-(len(HostDomain) + 1)])
1005 # in the purpose field [[host|some other text]] (where some other text is optional)
1006 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
1007 # file. But so that we don't have to add everything we link we can add an asterisk
1008 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
1009 # http linking it we also support [[-hostname]] entries.
1010 for i in x[1].get("purpose", []):
1011 m = PurposeHostField.match(i)
1014 # we ignore [[*..]] entries
1015 if m.startswith('*'):
1017 if m.startswith('-'):
1021 if m.endswith(HostDomain):
1022 HostNames.append(m[:-(len(HostDomain) + 1)])
1024 for I in x[1]["sshRSAHostKey"]:
1025 if mode and mode == 'authorized_keys':
1027 if 'sshdistAuthKeysHost' in x[1]:
1028 hosts += x[1]['sshdistAuthKeysHost']
1029 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)
1031 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1032 Line = Sanitize(Line) + "\n"
1034 # Oops, something unspeakable happened.
1040 # Generate the debianhosts file (list of all IP addresses)
1044 OldMask = os.umask(0022)
1045 F = open(File + ".tmp", "w", 0644)
1054 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1057 if not 'ipHostNumber' in x[1]:
1060 addrs = x[1]["ipHostNumber"]
1062 if addr not in seen:
1064 addr = Sanitize(addr) + "\n"
1067 # Oops, something unspeakable happened.
1073 def GenKeyrings(OutDir):
1075 shutil.copy(k, OutDir)
1077 # Connect to the ldap server
1079 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1080 Pass = F.readline().strip().split(" ")
1082 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1084 # Fetch all the groups
1086 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1087 ["gid", "gidNumber", "subGroup"])
1089 # Generate the SubGroupMap and GroupIDMap
1091 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1093 if x[1].has_key("gidNumber") == 0:
1095 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1096 if x[1].has_key("subGroup") != 0:
1097 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1099 # Fetch all the users
1100 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1101 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1102 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1103 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1104 "shadowExpire", "emailForward", "latitude", "longitude",\
1105 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1106 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1107 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1108 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1109 "mailContentInspectionAction"])
1111 if PasswdAttrs is None:
1112 raise UDEmptyList, "No Users"
1114 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1116 # Fetch all the hosts
1117 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1118 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1119 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1121 if HostAttrs == None:
1122 raise UDEmptyList, "No Hosts"
1124 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1126 # Generate global things
1127 GlobalDir = GenerateDir + "/"
1128 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1130 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1131 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1135 GenMailDisable(GlobalDir + "mail-disable")
1136 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1137 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1138 GenPrivate(GlobalDir + "debian-private")
1139 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1140 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1141 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1142 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1143 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1144 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1145 GenKeyrings(GlobalDir)
1148 GenForward(GlobalDir + "forward-alias")
1150 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1152 SSHFiles = GenSSHShadow()
1153 GenMarkers(GlobalDir + "markers")
1154 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1155 GenHosts(GlobalDir + "debianhosts")
1157 for host in HostAttrs:
1158 if not "hostname" in host[1]:
1161 CurrentHost = host[1]['hostname'][0]
1162 OutDir = GenerateDir + '/' + CurrentHost + '/'
1168 # Get the group list and convert any named groups to numerics
1170 for groupname in AllowedGroupsPreload.strip().split(" "):
1171 GroupList[groupname] = True
1172 if 'allowedGroups' in host[1]:
1173 for groupname in host[1]['allowedGroups']:
1174 GroupList[groupname] = True
1175 for groupname in GroupList.keys():
1176 if groupname in GroupIDMap:
1177 GroupList[str(GroupIDMap[groupname])] = True
1180 if 'exportOptions' in host[1]:
1181 for extra in host[1]['exportOptions']:
1182 ExtraList[extra.upper()] = True
1188 DoLink(GlobalDir, OutDir, "debianhosts")
1189 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1190 DoLink(GlobalDir, OutDir, "disabled-accounts")
1193 if 'NOPASSWD' in ExtraList:
1194 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1196 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1198 grouprevmap = GenGroup(OutDir + "group")
1199 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1201 # Now we know who we're allowing on the machine, export
1202 # the relevant ssh keys
1203 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1205 if not 'NOPASSWD' in ExtraList:
1206 GenShadow(OutDir + "shadow")
1208 # Link in global things
1209 if not 'NOMARKERS' in ExtraList:
1210 DoLink(GlobalDir, OutDir, "markers")
1211 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1212 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1213 DoLink(GlobalDir, OutDir, "mail-disable")
1214 DoLink(GlobalDir, OutDir, "mail-greylist")
1215 DoLink(GlobalDir, OutDir, "mail-callout")
1216 DoLink(GlobalDir, OutDir, "mail-rbl")
1217 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1218 DoLink(GlobalDir, OutDir, "mail-whitelist")
1219 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1220 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1221 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1224 DoLink(GlobalDir, OutDir, "forward-alias")
1226 if 'DNS' in ExtraList:
1227 GenDNS(OutDir + "dns-zone")
1228 GenZoneRecords(OutDir + "dns-sshfp")
1230 if 'AUTHKEYS' in ExtraList:
1231 DoLink(GlobalDir, OutDir, "authorized_keys")
1233 if 'BSMTP' in ExtraList:
1234 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1236 if 'PRIVATE' in ExtraList:
1237 DoLink(GlobalDir, OutDir, "debian-private")
1239 if 'KEYRING' in ExtraList:
1241 DoLink(GlobalDir, OutDir, os.path.basename(k))
1245 posix.remove(OutDir + os.path.basename(k))
1251 # vim:set shiftwidth=3: