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 a = UDLdap.Account(x[0], x[1])
538 if not 'emailForward' in a: continue
539 Line = "%s: %s" % (a['uid'], a['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
558 a = UDLdap.Account(x[0], x[1])
559 if not key in a: continue
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 a = UDLdap.Account(x[0], x[1])
584 if not ('latitude' in a and 'longitude' in a): continue
586 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
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 a = UDLdap.Account(x[0], x[1])
610 if not a.is_active_user(): continue
611 if not 'privateSub' in a: continue
613 Line = "%s"%(a['privateSub'])
614 Line = Sanitize(Line) + "\n"
619 # Oops, something unspeakable happened.
625 # Generate a list of locked accounts
626 def GenDisabledAccounts(File):
629 F = open(File + ".tmp", "w")
631 # Fetch all the users
636 for x in PasswdAttrs:
637 a = UDLdap.Account(x[0], x[1])
638 if a.pw_active(): continue
639 Line = "%s:%s" % (a['uid'], "Account is locked")
640 DisabledUsers.append(x)
641 F.write(Sanitize(Line) + "\n")
643 # Oops, something unspeakable happened.
649 # Generate the list of local addresses that refuse all mail
650 def GenMailDisable(File):
653 F = open(File + ".tmp", "w")
655 # Fetch all the users
658 for x in PasswdAttrs:
659 a = UDLdap.Account(x[0], x[1])
660 if not 'mailDisableMessage' in a: continue
661 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
662 Line = Sanitize(Line) + "\n"
665 # Oops, something unspeakable happened.
671 # Generate a list of uids that should have boolean affects applied
672 def GenMailBool(File, key):
675 F = open(File + ".tmp", "w")
677 # Fetch all the users
680 for x in PasswdAttrs:
681 a = UDLdap.Account(x[0], x[1])
682 if not key in a: continue
683 if not a[key] == 'TRUE': continue
684 Line = "%s"%(a['uid'])
685 Line = Sanitize(Line) + "\n"
688 # Oops, something unspeakable happened.
694 # Generate a list of hosts for RBL or whitelist purposes.
695 def GenMailList(File, key):
698 F = open(File + ".tmp", "w")
700 # Fetch all the users
703 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
704 else: validregex = re.compile('^[-\w.]+$')
706 for x in PasswdAttrs:
707 a = UDLdap.Account(x[0], x[1])
708 if not key in a: continue
710 filtered = filter(lambda z: validregex.match(z), a[key])
711 if len(filtered) == 0: continue
712 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
713 line = a['uid'] + ': ' + ' : '.join(filtered)
714 line = Sanitize(line) + "\n"
717 # Oops, something unspeakable happened.
723 def isRoleAccount(pwEntry):
724 if not pwEntry.has_key("objectClass"):
725 raise "pwEntry has no objectClass"
726 oc = pwEntry['objectClass']
728 i = oc.index('debianRoleAccount')
733 # Generate the DNS Zone file
737 F = open(File + ".tmp", "w")
739 # Fetch all the users
743 # Write out the zone file entry for each user
744 for x in PasswdAttrs:
745 if x[1].has_key("dnsZoneEntry") == 0:
748 # If the account has no PGP key, do not write it
749 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
752 F.write("; %s\n"%(EmailAddress(x)))
753 for z in x[1]["dnsZoneEntry"]:
754 Split = z.lower().split()
755 if Split[1].lower() == 'in':
756 for y in range(0, len(Split)):
759 Line = " ".join(Split) + "\n"
762 Host = Split[0] + DNSZone
763 if BSMTPCheck.match(Line) != None:
764 F.write("; Has BSMTP\n")
766 # Write some identification information
767 if not RRs.has_key(Host):
768 if Split[2].lower() in ["a", "aaaa"]:
769 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
770 for y in x[1]["keyFingerPrint"]:
771 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
775 Line = "; Err %s"%(str(Split))
780 F.write("; Errors\n")
783 # Oops, something unspeakable happened.
789 def ExtractDNSInfo(x):
793 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
796 if x[1].has_key("ipHostNumber"):
797 for I in x[1]["ipHostNumber"]:
798 if IsV6Addr.match(I) != None:
799 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
801 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
805 if 'sshRSAHostKey' in x[1]:
806 for I in x[1]["sshRSAHostKey"]:
808 if Split[0] == 'ssh-rsa':
810 if Split[0] == 'ssh-dss':
812 if Algorithm == None:
814 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
815 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
817 if 'architecture' in x[1]:
818 Arch = GetAttr(x, "architecture")
820 if x[1].has_key("machine"):
821 Mach = " " + GetAttr(x, "machine")
822 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
824 if x[1].has_key("mXRecord"):
825 for I in x[1]["mXRecord"]:
826 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
830 # Generate the DNS records
831 def GenZoneRecords(File):
834 F = open(File + ".tmp", "w")
836 # Fetch all the hosts
840 if x[1].has_key("hostname") == 0:
843 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
846 DNSInfo = ExtractDNSInfo(x)
850 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
853 Line = "\t\t\t%s" % (Line)
857 # this would write sshfp lines for services on machines
858 # but we can't yet, since some are cnames and we'll make
859 # an invalid zonefile
861 # for i in x[1].get("purpose", []):
862 # m = PurposeHostField.match(i)
865 # # we ignore [[*..]] entries
866 # if m.startswith('*'):
868 # if m.startswith('-'):
871 # if not m.endswith(HostDomain):
873 # if not m.endswith('.'):
875 # for Line in DNSInfo:
876 # if isSSHFP.match(Line):
877 # Line = "%s\t%s" % (m, Line)
878 # F.write(Line + "\n")
880 # Oops, something unspeakable happened.
886 # Generate the BSMTP file
887 def GenBSMTP(File, HomePrefix):
890 F = open(File + ".tmp", "w")
892 # Fetch all the users
895 # Write out the zone file entry for each user
896 for x in PasswdAttrs:
897 if x[1].has_key("dnsZoneEntry") == 0:
900 # If the account has no PGP key, do not write it
901 if x[1].has_key("keyFingerPrint") == 0:
904 for z in x[1]["dnsZoneEntry"]:
905 Split = z.lower().split()
906 if Split[1].lower() == 'in':
907 for y in range(0, len(Split)):
910 Line = " ".join(Split) + "\n"
912 Host = Split[0] + DNSZone
913 if BSMTPCheck.match(Line) != None:
914 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
915 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
918 F.write("; Errors\n")
921 # Oops, something unspeakable happened.
927 def HostToIP(Host, mapped=True):
931 if Host[1].has_key("ipHostNumber"):
932 for addr in Host[1]["ipHostNumber"]:
933 IPAdresses.append(addr)
934 if IsV6Addr.match(addr) is None and mapped == "True":
935 IPAdresses.append("::ffff:"+addr)
939 # Generate the ssh known hosts file
940 def GenSSHKnown(File, mode=None):
943 OldMask = os.umask(0022)
944 F = open(File + ".tmp", "w", 0644)
950 if x[1].has_key("hostname") == 0 or \
951 x[1].has_key("sshRSAHostKey") == 0:
953 Host = GetAttr(x, "hostname")
955 if Host.endswith(HostDomain):
956 HostNames.append(Host[:-(len(HostDomain) + 1)])
958 # in the purpose field [[host|some other text]] (where some other text is optional)
959 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
960 # file. But so that we don't have to add everything we link we can add an asterisk
961 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
962 # http linking it we also support [[-hostname]] entries.
963 for i in x[1].get("purpose", []):
964 m = PurposeHostField.match(i)
967 # we ignore [[*..]] entries
968 if m.startswith('*'):
970 if m.startswith('-'):
974 if m.endswith(HostDomain):
975 HostNames.append(m[:-(len(HostDomain) + 1)])
977 for I in x[1]["sshRSAHostKey"]:
978 if mode and mode == 'authorized_keys':
980 if 'sshdistAuthKeysHost' in x[1]:
981 hosts += x[1]['sshdistAuthKeysHost']
982 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)
984 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
985 Line = Sanitize(Line) + "\n"
987 # Oops, something unspeakable happened.
993 # Generate the debianhosts file (list of all IP addresses)
997 OldMask = os.umask(0022)
998 F = open(File + ".tmp", "w", 0644)
1007 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1010 if not 'ipHostNumber' in x[1]:
1013 addrs = x[1]["ipHostNumber"]
1015 if addr not in seen:
1017 addr = Sanitize(addr) + "\n"
1020 # Oops, something unspeakable happened.
1026 def GenKeyrings(OutDir):
1028 shutil.copy(k, OutDir)
1030 # Connect to the ldap server
1032 # for testing purposes it's sometimes useful to pass username/password
1033 # via the environment
1034 if 'UD_CREDENTIALS' in os.environ:
1035 Pass = os.environ['UD_CREDENTIALS'].split()
1037 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1038 Pass = F.readline().strip().split(" ")
1040 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1042 # Fetch all the groups
1044 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1045 ["gid", "gidNumber", "subGroup"])
1047 # Generate the SubGroupMap and GroupIDMap
1049 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1051 if x[1].has_key("gidNumber") == 0:
1053 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1054 if x[1].has_key("subGroup") != 0:
1055 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1057 # Fetch all the users
1058 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
1059 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1060 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1061 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1062 "shadowExpire", "emailForward", "latitude", "longitude",\
1063 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1064 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1065 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1066 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1067 "mailContentInspectionAction"])
1069 if PasswdAttrs is None:
1070 raise UDEmptyList, "No Users"
1072 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1074 # Fetch all the hosts
1075 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1076 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1077 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1079 if HostAttrs == None:
1080 raise UDEmptyList, "No Hosts"
1082 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1084 # override globaldir for testing
1085 if 'UD_GENERATEDIR' in os.environ:
1086 GenerateDir = os.environ['UD_GENERATEDIR']
1088 # Generate global things
1089 GlobalDir = GenerateDir + "/"
1090 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1092 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1093 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1097 GenMailDisable(GlobalDir + "mail-disable")
1098 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1099 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1100 GenPrivate(GlobalDir + "debian-private")
1101 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1102 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1103 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1104 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1105 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1106 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1107 GenKeyrings(GlobalDir)
1110 GenForward(GlobalDir + "forward-alias")
1112 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1114 SSHFiles = GenSSHShadow()
1115 GenMarkers(GlobalDir + "markers")
1116 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1117 GenHosts(GlobalDir + "debianhosts")
1119 for host in HostAttrs:
1120 if not "hostname" in host[1]:
1123 CurrentHost = host[1]['hostname'][0]
1124 OutDir = GenerateDir + '/' + CurrentHost + '/'
1130 # Get the group list and convert any named groups to numerics
1132 for groupname in AllowedGroupsPreload.strip().split(" "):
1133 GroupList[groupname] = True
1134 if 'allowedGroups' in host[1]:
1135 for groupname in host[1]['allowedGroups']:
1136 GroupList[groupname] = True
1137 for groupname in GroupList.keys():
1138 if groupname in GroupIDMap:
1139 GroupList[str(GroupIDMap[groupname])] = True
1142 if 'exportOptions' in host[1]:
1143 for extra in host[1]['exportOptions']:
1144 ExtraList[extra.upper()] = True
1150 DoLink(GlobalDir, OutDir, "debianhosts")
1151 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1152 DoLink(GlobalDir, OutDir, "disabled-accounts")
1155 if 'NOPASSWD' in ExtraList:
1156 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1158 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1160 grouprevmap = GenGroup(OutDir + "group")
1161 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1163 # Now we know who we're allowing on the machine, export
1164 # the relevant ssh keys
1165 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1167 if not 'NOPASSWD' in ExtraList:
1168 GenShadow(OutDir + "shadow")
1170 # Link in global things
1171 if not 'NOMARKERS' in ExtraList:
1172 DoLink(GlobalDir, OutDir, "markers")
1173 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1174 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1175 DoLink(GlobalDir, OutDir, "mail-disable")
1176 DoLink(GlobalDir, OutDir, "mail-greylist")
1177 DoLink(GlobalDir, OutDir, "mail-callout")
1178 DoLink(GlobalDir, OutDir, "mail-rbl")
1179 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1180 DoLink(GlobalDir, OutDir, "mail-whitelist")
1181 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1182 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1183 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1186 DoLink(GlobalDir, OutDir, "forward-alias")
1188 if 'DNS' in ExtraList:
1189 GenDNS(OutDir + "dns-zone")
1190 GenZoneRecords(OutDir + "dns-sshfp")
1192 if 'AUTHKEYS' in ExtraList:
1193 DoLink(GlobalDir, OutDir, "authorized_keys")
1195 if 'BSMTP' in ExtraList:
1196 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1198 if 'PRIVATE' in ExtraList:
1199 DoLink(GlobalDir, OutDir, "debian-private")
1201 if 'KEYRING' in ExtraList:
1203 DoLink(GlobalDir, OutDir, os.path.basename(k))
1207 posix.remove(OutDir + os.path.basename(k))
1213 # vim:set shiftwidth=3: