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 a = UDLdap.Account(x[0], x[1])
191 if not IsInGroup(x): continue
193 # Do not let people try to buffer overflow some busted passwd parser.
194 if len(a['gecos']) > 100 or len(a['loginShell']) > 50: continue
196 userlist[a['uid']] = a['gidNumber']
197 line = "%s:%s:%d:%d:%s:%s%s:%s" % (
203 HomePrefix, a['uid'],
205 line = Sanitize(line) + "\n"
206 F.write("0%u %s" % (i, line))
207 F.write(".%s %s" % (a['uid'], line))
208 F.write("=%d %s" % (a['uidNumber'], line))
211 # Oops, something unspeakable happened.
217 # Return the list of users so we know which keys to export
220 # Generate the shadow list
224 OldMask = os.umask(0077)
225 F = open(File + ".tdb.tmp", "w", 0600)
228 # Fetch all the users
232 for x in PasswdAttrs:
233 a = UDLdap.Account(x[0], x[1])
234 if not IsInGroup(x): continue
236 # If the account is locked, mark it as such in shadow
237 # See Debian Bug #308229 for why we set it to 1 instead of 0
238 if not a.pw_active(): ShadowExpire = '1'
239 elif 'shadowExpire' in a: ShadowExpire = str(a['shadowExpire'])
240 else: ShadowExpire = ''
243 values.append(a['uid'])
244 values.append(a.get_password())
245 for key in 'shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive':
246 if key in a: values.append(a[key])
247 else: values.append('')
248 values.append(ShadowExpire)
249 line = ':'.join(values)+':'
250 line = Sanitize(line) + "\n"
251 F.write("0%u %s" % (i, line))
252 F.write(".%s %s" % (a['uid'], line))
255 # Oops, something unspeakable happened.
261 # Generate the sudo passwd file
262 def GenShadowSudo(File, untrusted):
265 OldMask = os.umask(0077)
266 F = open(File + ".tmp", "w", 0600)
269 # Fetch all the users
272 for x in PasswdAttrs:
273 a = UDLdap.Account(x[0], x[1])
275 if not IsInGroup(x): continue
277 if 'sudoPassword' in a:
278 for entry in a['sudoPassword']:
279 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
282 uuid = Match.group(1)
283 status = Match.group(2)
284 hosts = Match.group(3)
285 cryptedpass = Match.group(4)
287 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass):
289 for_all = hosts == "*"
290 for_this_host = CurrentHost in hosts.split(',')
291 if not (for_all or for_this_host):
293 # ignore * passwords for untrusted hosts, but copy host specific passwords
294 if for_all and untrusted:
297 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
302 Line = "%s:%s" % (a['uid'], Pass)
303 Line = Sanitize(Line) + "\n"
304 F.write("%s" % (Line))
306 # Oops, something unspeakable happened.
312 # Generate the shadow list
314 # Fetch all the users
319 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
320 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
322 for x in PasswdAttrs:
323 a = UDLdap.Account(x[0], x[1])
324 if not 'sshRSAAuthKey' in a: continue
328 OldMask = os.umask(0077)
329 File = os.path.join(GlobalDir, 'userkeys', a['uid'])
330 F = open(File + ".tmp", "w", 0600)
333 for I in a['sshRSAAuthKey']:
334 MultipleLine = "%s" % I
335 MultipleLine = Sanitize(MultipleLine) + "\n"
336 F.write(MultipleLine)
339 userfiles.append(os.path.basename(File))
341 # Oops, something unspeakable happened.
344 # As neither masterFileName nor masterFile are defined at any point
345 # this will raise a NameError.
346 Die(masterFileName, masterFile, None)
351 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
352 OldMask = os.umask(0077)
353 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
355 for f in userlist.keys():
356 if f not in SSHFiles:
358 # If we're not exporting their primary group, don't export
361 if userlist[f] in grouprevmap.keys():
362 grname = grouprevmap[userlist[f]]
365 if int(userlist[f]) <= 100:
366 # In these cases, look it up in the normal way so we
367 # deal with cases where, for instance, users are in group
368 # users as their primary group.
369 grname = grp.getgrgid(userlist[f])[0]
374 print "User %s is supposed to have their key exported to host %s but their primary group (gid: %d) isn't in LDAP" % (f, CurrentHost, userlist[f])
377 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
378 # These will only be used where the username doesn't
379 # exist on the target system for some reason; hence,
380 # in those cases, the safest thing is for the file to
381 # be owned by root but group nobody. This deals with
382 # the bloody obscure case where the group fails to exist
383 # whilst the user does (in which case we want to avoid
384 # ending up with a file which is owned user:root to avoid
385 # a fairly obvious attack vector)
388 # Using the username / groupname fields avoids any need
389 # to give a shit^W^W^Wcare about the UIDoffset stuff.
394 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
396 for line in contents.splitlines():
397 if line.startswith("allowed_hosts=") and ' ' in line:
398 machines, line = line.split('=', 1)[1].split(' ', 1)
399 if CurrentHost not in machines.split(','):
400 continue # skip this key
403 continue # no keys for this host
404 contents = "\n".join(lines) + "\n"
405 to.size = len(contents)
406 tf.addfile(to, StringIO(contents))
409 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
411 # add a list of groups to existing groups,
412 # including all subgroups thereof, recursively.
413 # basically this proceduces the transitive hull of the groups in
415 def addGroups(existingGroups, newGroups, uid):
416 for group in newGroups:
417 # if it's a <group>@host, split it and verify it's on the current host.
418 s = group.split('@', 1)
419 if len(s) == 2 and s[1] != CurrentHost:
423 # let's see if we handled this group already
424 if group in existingGroups:
427 if not GroupIDMap.has_key(group):
428 print "Group", group, "does not exist but", uid, "is in it"
431 existingGroups.append(group)
433 if SubGroupMap.has_key(group):
434 addGroups(existingGroups, SubGroupMap[group], uid)
436 # Generate the group list
441 F = open(File + ".tdb.tmp", "w")
443 # Generate the GroupMap
445 for x in GroupIDMap.keys():
447 GroupHasPrimaryMembers = {}
449 # Fetch all the users
452 # Sort them into a list of groups having a set of users
453 for x in PasswdAttrs:
454 a = UDLdap.Account(x[0], x[1])
455 GroupHasPrimaryMembers[ a['gidNumber'] ] = True
456 if not IsInGroup(x): continue
457 if not 'supplementaryGid' in a: continue
460 addGroups(supgroups, a['supplementaryGid'], a['uid'])
462 GroupMap[g].append(a['uid'])
464 # Output the group file.
466 for x in GroupMap.keys():
467 if GroupIDMap.has_key(x) == 0:
470 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
473 grouprevmap[GroupIDMap[x]] = x
475 Line = "%s:x:%u:" % (x, GroupIDMap[x])
477 for I in GroupMap[x]:
478 Line = Line + ("%s%s" % (Comma, I))
480 Line = Sanitize(Line) + "\n"
481 F.write("0%u %s" % (J, Line))
482 F.write(".%s %s" % (x, Line))
483 F.write("=%u %s" % (GroupIDMap[x], Line))
486 # Oops, something unspeakable happened.
496 for x in PasswdAttrs:
497 if x[1].has_key("emailForward") == 0:
501 x[1].pop("emailForward")
504 # Do not allow people to try to buffer overflow busted parsers
505 if len(GetAttr(x, "emailForward")) > 200:
506 x[1].pop("emailForward")
509 # Check the forwarding address
510 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
511 x[1].pop("emailForward")
513 # Generate the email forwarding list
514 def GenForward(File):
517 OldMask = os.umask(0022)
518 F = open(File + ".tmp", "w", 0644)
521 # Fetch all the users
524 # Write out the email address for each user
525 for x in PasswdAttrs:
526 a = UDLdap.Account(x[0], x[1])
527 if not 'emailForward' in a: continue
528 Line = "%s: %s" % (a['uid'], a['emailForward'])
529 Line = Sanitize(Line) + "\n"
532 # Oops, something unspeakable happened.
538 def GenCDB(File, Users, key):
541 OldMask = os.umask(0022)
542 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
545 # Write out the email address for each user
547 a = UDLdap.Account(x[0], x[1])
548 if not key in a: continue
551 Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
554 # Oops, something unspeakable happened.
558 if Fdb.close() != None:
559 raise "cdbmake gave an error"
561 # Generate the anon XEarth marker file
562 def GenMarkers(File):
565 F = open(File + ".tmp", "w")
567 # Fetch all the users
570 # Write out the position for each user
571 for x in PasswdAttrs:
572 a = UDLdap.Account(x[0], x[1])
573 if not ('latitude' in a and 'longitude' in a): continue
575 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
576 Line = Sanitize(Line) + "\n"
581 # Oops, something unspeakable happened.
587 # Generate the debian-private subscription list
588 def GenPrivate(File):
591 F = open(File + ".tmp", "w")
593 # Fetch all the users
596 # Write out the position for each user
597 for x in DebianDDUsers:
598 a = UDLdap.Account(x[0], x[1])
599 if not a.is_active_user(): continue
600 if not 'privateSub' in a: continue
602 Line = "%s"%(a['privateSub'])
603 Line = Sanitize(Line) + "\n"
608 # Oops, something unspeakable happened.
614 # Generate a list of locked accounts
615 def GenDisabledAccounts(File):
618 F = open(File + ".tmp", "w")
620 # Fetch all the users
625 for x in PasswdAttrs:
626 a = UDLdap.Account(x[0], x[1])
627 if a.pw_active(): continue
628 Line = "%s:%s" % (a['uid'], "Account is locked")
629 DisabledUsers.append(x)
630 F.write(Sanitize(Line) + "\n")
632 # Oops, something unspeakable happened.
638 # Generate the list of local addresses that refuse all mail
639 def GenMailDisable(File):
642 F = open(File + ".tmp", "w")
644 # Fetch all the users
647 for x in PasswdAttrs:
648 a = UDLdap.Account(x[0], x[1])
649 if not 'mailDisableMessage' in a: continue
650 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
651 Line = Sanitize(Line) + "\n"
654 # Oops, something unspeakable happened.
660 # Generate a list of uids that should have boolean affects applied
661 def GenMailBool(File, key):
664 F = open(File + ".tmp", "w")
666 # Fetch all the users
669 for x in PasswdAttrs:
670 a = UDLdap.Account(x[0], x[1])
671 if not key in a: continue
672 if not a[key] == 'TRUE': continue
673 Line = "%s"%(a['uid'])
674 Line = Sanitize(Line) + "\n"
677 # Oops, something unspeakable happened.
683 # Generate a list of hosts for RBL or whitelist purposes.
684 def GenMailList(File, key):
687 F = open(File + ".tmp", "w")
689 # Fetch all the users
692 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
693 else: validregex = re.compile('^[-\w.]+$')
695 for x in PasswdAttrs:
696 a = UDLdap.Account(x[0], x[1])
697 if not key in a: continue
699 filtered = filter(lambda z: validregex.match(z), a[key])
700 if len(filtered) == 0: continue
701 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
702 line = a['uid'] + ': ' + ' : '.join(filtered)
703 line = Sanitize(line) + "\n"
706 # Oops, something unspeakable happened.
712 def isRoleAccount(pwEntry):
713 if not pwEntry.has_key("objectClass"):
714 raise "pwEntry has no objectClass"
715 oc = pwEntry['objectClass']
717 i = oc.index('debianRoleAccount')
722 # Generate the DNS Zone file
726 F = open(File + ".tmp", "w")
728 # Fetch all the users
732 # Write out the zone file entry for each user
733 for x in PasswdAttrs:
734 if x[1].has_key("dnsZoneEntry") == 0:
737 # If the account has no PGP key, do not write it
738 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
741 F.write("; %s\n"%(EmailAddress(x)))
742 for z in x[1]["dnsZoneEntry"]:
743 Split = z.lower().split()
744 if Split[1].lower() == 'in':
745 for y in range(0, len(Split)):
748 Line = " ".join(Split) + "\n"
751 Host = Split[0] + DNSZone
752 if BSMTPCheck.match(Line) != None:
753 F.write("; Has BSMTP\n")
755 # Write some identification information
756 if not RRs.has_key(Host):
757 if Split[2].lower() in ["a", "aaaa"]:
758 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
759 for y in x[1]["keyFingerPrint"]:
760 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
764 Line = "; Err %s"%(str(Split))
769 F.write("; Errors\n")
772 # Oops, something unspeakable happened.
778 def ExtractDNSInfo(x):
782 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
785 if x[1].has_key("ipHostNumber"):
786 for I in x[1]["ipHostNumber"]:
787 if IsV6Addr.match(I) != None:
788 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
790 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
794 if 'sshRSAHostKey' in x[1]:
795 for I in x[1]["sshRSAHostKey"]:
797 if Split[0] == 'ssh-rsa':
799 if Split[0] == 'ssh-dss':
801 if Algorithm == None:
803 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
804 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
806 if 'architecture' in x[1]:
807 Arch = GetAttr(x, "architecture")
809 if x[1].has_key("machine"):
810 Mach = " " + GetAttr(x, "machine")
811 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
813 if x[1].has_key("mXRecord"):
814 for I in x[1]["mXRecord"]:
815 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
819 # Generate the DNS records
820 def GenZoneRecords(File):
823 F = open(File + ".tmp", "w")
825 # Fetch all the hosts
829 if x[1].has_key("hostname") == 0:
832 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
835 DNSInfo = ExtractDNSInfo(x)
839 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
842 Line = "\t\t\t%s" % (Line)
846 # this would write sshfp lines for services on machines
847 # but we can't yet, since some are cnames and we'll make
848 # an invalid zonefile
850 # for i in x[1].get("purpose", []):
851 # m = PurposeHostField.match(i)
854 # # we ignore [[*..]] entries
855 # if m.startswith('*'):
857 # if m.startswith('-'):
860 # if not m.endswith(HostDomain):
862 # if not m.endswith('.'):
864 # for Line in DNSInfo:
865 # if isSSHFP.match(Line):
866 # Line = "%s\t%s" % (m, Line)
867 # F.write(Line + "\n")
869 # Oops, something unspeakable happened.
875 # Generate the BSMTP file
876 def GenBSMTP(File, HomePrefix):
879 F = open(File + ".tmp", "w")
881 # Fetch all the users
884 # Write out the zone file entry for each user
885 for x in PasswdAttrs:
886 if x[1].has_key("dnsZoneEntry") == 0:
889 # If the account has no PGP key, do not write it
890 if x[1].has_key("keyFingerPrint") == 0:
893 for z in x[1]["dnsZoneEntry"]:
894 Split = z.lower().split()
895 if Split[1].lower() == 'in':
896 for y in range(0, len(Split)):
899 Line = " ".join(Split) + "\n"
901 Host = Split[0] + DNSZone
902 if BSMTPCheck.match(Line) != None:
903 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
904 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
907 F.write("; Errors\n")
910 # Oops, something unspeakable happened.
916 def HostToIP(Host, mapped=True):
920 if Host[1].has_key("ipHostNumber"):
921 for addr in Host[1]["ipHostNumber"]:
922 IPAdresses.append(addr)
923 if IsV6Addr.match(addr) is None and mapped == "True":
924 IPAdresses.append("::ffff:"+addr)
928 # Generate the ssh known hosts file
929 def GenSSHKnown(File, mode=None):
932 OldMask = os.umask(0022)
933 F = open(File + ".tmp", "w", 0644)
939 if x[1].has_key("hostname") == 0 or \
940 x[1].has_key("sshRSAHostKey") == 0:
942 Host = GetAttr(x, "hostname")
944 if Host.endswith(HostDomain):
945 HostNames.append(Host[:-(len(HostDomain) + 1)])
947 # in the purpose field [[host|some other text]] (where some other text is optional)
948 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
949 # file. But so that we don't have to add everything we link we can add an asterisk
950 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
951 # http linking it we also support [[-hostname]] entries.
952 for i in x[1].get("purpose", []):
953 m = PurposeHostField.match(i)
956 # we ignore [[*..]] entries
957 if m.startswith('*'):
959 if m.startswith('-'):
963 if m.endswith(HostDomain):
964 HostNames.append(m[:-(len(HostDomain) + 1)])
966 for I in x[1]["sshRSAHostKey"]:
967 if mode and mode == 'authorized_keys':
969 if 'sshdistAuthKeysHost' in x[1]:
970 hosts += x[1]['sshdistAuthKeysHost']
971 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)
973 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
974 Line = Sanitize(Line) + "\n"
976 # Oops, something unspeakable happened.
982 # Generate the debianhosts file (list of all IP addresses)
986 OldMask = os.umask(0022)
987 F = open(File + ".tmp", "w", 0644)
996 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
999 if not 'ipHostNumber' in x[1]:
1002 addrs = x[1]["ipHostNumber"]
1004 if addr not in seen:
1006 addr = Sanitize(addr) + "\n"
1009 # Oops, something unspeakable happened.
1015 def GenKeyrings(OutDir):
1017 shutil.copy(k, OutDir)
1019 # Connect to the ldap server
1021 # for testing purposes it's sometimes useful to pass username/password
1022 # via the environment
1023 if 'UD_CREDENTIALS' in os.environ:
1024 Pass = os.environ['UD_CREDENTIALS'].split()
1026 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1027 Pass = F.readline().strip().split(" ")
1029 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1031 # Fetch all the groups
1033 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1034 ["gid", "gidNumber", "subGroup"])
1036 # Generate the SubGroupMap and GroupIDMap
1038 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1040 if x[1].has_key("gidNumber") == 0:
1042 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1043 if x[1].has_key("subGroup") != 0:
1044 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1046 # Fetch all the users
1047 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
1048 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1049 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1050 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1051 "shadowExpire", "emailForward", "latitude", "longitude",\
1052 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1053 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1054 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1055 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1056 "mailContentInspectionAction"])
1058 if PasswdAttrs is None:
1059 raise UDEmptyList, "No Users"
1061 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1063 # Fetch all the hosts
1064 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1065 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1066 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1068 if HostAttrs == None:
1069 raise UDEmptyList, "No Hosts"
1071 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1073 # override globaldir for testing
1074 if 'UD_GENERATEDIR' in os.environ:
1075 GenerateDir = os.environ['UD_GENERATEDIR']
1077 # Generate global things
1078 GlobalDir = GenerateDir + "/"
1079 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1081 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1082 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1086 GenMailDisable(GlobalDir + "mail-disable")
1087 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1088 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1089 GenPrivate(GlobalDir + "debian-private")
1090 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1091 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1092 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1093 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1094 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1095 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1096 GenKeyrings(GlobalDir)
1099 GenForward(GlobalDir + "forward-alias")
1101 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1103 SSHFiles = GenSSHShadow()
1104 GenMarkers(GlobalDir + "markers")
1105 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1106 GenHosts(GlobalDir + "debianhosts")
1108 for host in HostAttrs:
1109 if not "hostname" in host[1]:
1112 CurrentHost = host[1]['hostname'][0]
1113 OutDir = GenerateDir + '/' + CurrentHost + '/'
1119 # Get the group list and convert any named groups to numerics
1121 for groupname in AllowedGroupsPreload.strip().split(" "):
1122 GroupList[groupname] = True
1123 if 'allowedGroups' in host[1]:
1124 for groupname in host[1]['allowedGroups']:
1125 GroupList[groupname] = True
1126 for groupname in GroupList.keys():
1127 if groupname in GroupIDMap:
1128 GroupList[str(GroupIDMap[groupname])] = True
1131 if 'exportOptions' in host[1]:
1132 for extra in host[1]['exportOptions']:
1133 ExtraList[extra.upper()] = True
1139 DoLink(GlobalDir, OutDir, "debianhosts")
1140 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1141 DoLink(GlobalDir, OutDir, "disabled-accounts")
1144 if 'NOPASSWD' in ExtraList:
1145 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1147 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1149 grouprevmap = GenGroup(OutDir + "group")
1150 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1152 # Now we know who we're allowing on the machine, export
1153 # the relevant ssh keys
1154 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1156 if not 'NOPASSWD' in ExtraList:
1157 GenShadow(OutDir + "shadow")
1159 # Link in global things
1160 if not 'NOMARKERS' in ExtraList:
1161 DoLink(GlobalDir, OutDir, "markers")
1162 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1163 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1164 DoLink(GlobalDir, OutDir, "mail-disable")
1165 DoLink(GlobalDir, OutDir, "mail-greylist")
1166 DoLink(GlobalDir, OutDir, "mail-callout")
1167 DoLink(GlobalDir, OutDir, "mail-rbl")
1168 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1169 DoLink(GlobalDir, OutDir, "mail-whitelist")
1170 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1171 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1172 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1175 DoLink(GlobalDir, OutDir, "forward-alias")
1177 if 'DNS' in ExtraList:
1178 GenDNS(OutDir + "dns-zone")
1179 GenZoneRecords(OutDir + "dns-sshfp")
1181 if 'AUTHKEYS' in ExtraList:
1182 DoLink(GlobalDir, OutDir, "authorized_keys")
1184 if 'BSMTP' in ExtraList:
1185 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1187 if 'PRIVATE' in ExtraList:
1188 DoLink(GlobalDir, OutDir, "debian-private")
1190 if 'KEYRING' in ExtraList:
1192 DoLink(GlobalDir, OutDir, os.path.basename(k))
1196 posix.remove(OutDir + os.path.basename(k))
1202 # vim:set shiftwidth=3: