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
40 import simplejson as json
43 if not '__author__' in json.__dict__:
44 sys.stderr.write("Warning: This is probably the wrong json module. We want python 2.6's json\n")
45 sys.stderr.write("module, or simplejson on pytyon 2.5. Let's see if/how stuff blows up.\n")
51 sys.stderr.write("You should probably not run ud-generate as root.\n")
60 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
62 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
63 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
64 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
65 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
66 IsDebianHost = re.compile(ConfModule.dns_hostmatch)
67 isSSHFP = re.compile("^\s*IN\s+SSHFP")
68 DNSZone = ".debian.net"
69 Keyrings = ConfModule.sync_keyrings.split(":")
71 def safe_makedirs(dir):
75 if e.errno == errno.EEXIST:
84 if e.errno == errno.ENOENT:
90 return Str.translate(string.maketrans("\n\r\t", "$$$"))
92 def DoLink(From, To, File):
94 posix.remove(To + File)
97 posix.link(From + File, To + File)
99 def IsRetired(account):
101 Looks for accountStatus in the LDAP record and tries to
102 match it against one of the known retired statuses
105 status = account['accountStatus']
107 line = status.split()
110 if status == "inactive":
113 elif status == "memorial":
116 elif status == "retiring":
117 # We'll give them a few extra days over what we said
118 age = 6 * 31 * 24 * 60 * 60
120 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
128 #def IsGidDebian(account):
129 # return account['gidNumber'] == 800
131 # See if this user is in the group list
132 def IsInGroup(account):
136 # See if the primary group is in the list
137 if str(account['gidNumber']) in Allowed: return True
139 # Check the host based ACL
140 if account.is_allowed_by_hostacl(CurrentHost): return True
142 # See if there are supplementary groups
143 if not 'supplementaryGid' in account: return False
146 addGroups(supgroups, account['supplementaryGid'], account['uid'])
148 if Allowed.has_key(g):
152 def Die(File, F, Fdb):
158 os.remove(File + ".tmp")
162 os.remove(File + ".tdb.tmp")
166 def Done(File, F, Fdb):
169 os.rename(File + ".tmp", File)
172 os.rename(File + ".tdb.tmp", File + ".tdb")
174 # Generate the password list
175 def GenPasswd(accounts, File, HomePrefix, PwdMarker):
178 F = open(File + ".tdb.tmp", "w")
183 if not IsInGroup(a): continue
185 # Do not let people try to buffer overflow some busted passwd parser.
186 if len(a['gecos']) > 100 or len(a['loginShell']) > 50: continue
188 userlist[a['uid']] = a['gidNumber']
189 line = "%s:%s:%d:%d:%s:%s%s:%s" % (
195 HomePrefix, a['uid'],
197 line = Sanitize(line) + "\n"
198 F.write("0%u %s" % (i, line))
199 F.write(".%s %s" % (a['uid'], line))
200 F.write("=%d %s" % (a['uidNumber'], line))
203 # Oops, something unspeakable happened.
209 # Return the list of users so we know which keys to export
212 def GenAllUsers(accounts, file):
215 OldMask = os.umask(0022)
216 f = open(file + ".tmp", "w", 0644)
221 all.append( { 'uid': a['uid'],
222 'uidNumber': a['uidNumber'],
223 'active': a.pw_active() and a.shadow_active() } )
226 # Oops, something unspeakable happened.
232 # Generate the shadow list
233 def GenShadow(accounts, File):
236 OldMask = os.umask(0077)
237 F = open(File + ".tdb.tmp", "w", 0600)
243 if not IsInGroup(a): continue
245 # If the account is locked, mark it as such in shadow
246 # See Debian Bug #308229 for why we set it to 1 instead of 0
247 if not a.pw_active(): ShadowExpire = '1'
248 elif 'shadowExpire' in a: ShadowExpire = str(a['shadowExpire'])
249 else: ShadowExpire = ''
252 values.append(a['uid'])
253 values.append(a.get_password())
254 for key in 'shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive':
255 if key in a: values.append(a[key])
256 else: values.append('')
257 values.append(ShadowExpire)
258 line = ':'.join(values)+':'
259 line = Sanitize(line) + "\n"
260 F.write("0%u %s" % (i, line))
261 F.write(".%s %s" % (a['uid'], line))
264 # Oops, something unspeakable happened.
270 # Generate the sudo passwd file
271 def GenShadowSudo(accounts, File, untrusted):
274 OldMask = os.umask(0077)
275 F = open(File + ".tmp", "w", 0600)
280 if not IsInGroup(a): continue
282 if 'sudoPassword' in a:
283 for entry in a['sudoPassword']:
284 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
287 uuid = Match.group(1)
288 status = Match.group(2)
289 hosts = Match.group(3)
290 cryptedpass = Match.group(4)
292 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass):
294 for_all = hosts == "*"
295 for_this_host = CurrentHost in hosts.split(',')
296 if not (for_all or for_this_host):
298 # ignore * passwords for untrusted hosts, but copy host specific passwords
299 if for_all and untrusted:
302 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
307 Line = "%s:%s" % (a['uid'], Pass)
308 Line = Sanitize(Line) + "\n"
309 F.write("%s" % (Line))
311 # Oops, something unspeakable happened.
317 # Generate the shadow list
318 def GenSSHShadow(accounts):
319 # Fetch all the users
322 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
323 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
326 if not 'sshRSAAuthKey' in a: continue
330 OldMask = os.umask(0077)
331 File = os.path.join(GlobalDir, 'userkeys', a['uid'])
332 F = open(File + ".tmp", "w", 0600)
335 for I in a['sshRSAAuthKey']:
336 MultipleLine = "%s" % I
337 MultipleLine = Sanitize(MultipleLine) + "\n"
338 F.write(MultipleLine)
341 userfiles.append(os.path.basename(File))
343 # Oops, something unspeakable happened.
346 # As neither masterFileName nor masterFile are defined at any point
347 # this will raise a NameError.
348 Die(masterFileName, masterFile, None)
353 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
354 OldMask = os.umask(0077)
355 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
357 for f in userlist.keys():
358 if f not in SSHFiles:
360 # If we're not exporting their primary group, don't export
363 if userlist[f] in grouprevmap.keys():
364 grname = grouprevmap[userlist[f]]
367 if int(userlist[f]) <= 100:
368 # In these cases, look it up in the normal way so we
369 # deal with cases where, for instance, users are in group
370 # users as their primary group.
371 grname = grp.getgrgid(userlist[f])[0]
376 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])
379 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
380 # These will only be used where the username doesn't
381 # exist on the target system for some reason; hence,
382 # in those cases, the safest thing is for the file to
383 # be owned by root but group nobody. This deals with
384 # the bloody obscure case where the group fails to exist
385 # whilst the user does (in which case we want to avoid
386 # ending up with a file which is owned user:root to avoid
387 # a fairly obvious attack vector)
390 # Using the username / groupname fields avoids any need
391 # to give a shit^W^W^Wcare about the UIDoffset stuff.
396 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
398 for line in contents.splitlines():
399 if line.startswith("allowed_hosts=") and ' ' in line:
400 machines, line = line.split('=', 1)[1].split(' ', 1)
401 if CurrentHost not in machines.split(','):
402 continue # skip this key
405 continue # no keys for this host
406 contents = "\n".join(lines) + "\n"
407 to.size = len(contents)
408 tf.addfile(to, StringIO(contents))
411 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
413 # add a list of groups to existing groups,
414 # including all subgroups thereof, recursively.
415 # basically this proceduces the transitive hull of the groups in
417 def addGroups(existingGroups, newGroups, uid):
418 for group in newGroups:
419 # if it's a <group>@host, split it and verify it's on the current host.
420 s = group.split('@', 1)
421 if len(s) == 2 and s[1] != CurrentHost:
425 # let's see if we handled this group already
426 if group in existingGroups:
429 if not GroupIDMap.has_key(group):
430 print "Group", group, "does not exist but", uid, "is in it"
433 existingGroups.append(group)
435 if SubGroupMap.has_key(group):
436 addGroups(existingGroups, SubGroupMap[group], uid)
438 # Generate the group list
439 def GenGroup(accounts, File):
443 F = open(File + ".tdb.tmp", "w")
445 # Generate the GroupMap
447 for x in GroupIDMap.keys():
449 GroupHasPrimaryMembers = {}
451 # Sort them into a list of groups having a set of users
453 GroupHasPrimaryMembers[ a['gidNumber'] ] = True
454 if not IsInGroup(a): continue
455 if not 'supplementaryGid' in a: continue
458 addGroups(supgroups, a['supplementaryGid'], a['uid'])
460 GroupMap[g].append(a['uid'])
462 # Output the group file.
464 for x in GroupMap.keys():
465 if GroupIDMap.has_key(x) == 0:
468 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
471 grouprevmap[GroupIDMap[x]] = x
473 Line = "%s:x:%u:" % (x, GroupIDMap[x])
475 for I in GroupMap[x]:
476 Line = Line + ("%s%s" % (Comma, I))
478 Line = Sanitize(Line) + "\n"
479 F.write("0%u %s" % (J, Line))
480 F.write(".%s %s" % (x, Line))
481 F.write("=%u %s" % (GroupIDMap[x], Line))
484 # Oops, something unspeakable happened.
492 def CheckForward(accounts):
494 if not 'emailForward' in a: continue
499 if not IsInGroup(a): delete = True
500 # Do not allow people to try to buffer overflow busted parsers
501 elif len(a['emailForward']) > 200: delete = True
502 # Check the forwarding address
503 elif EmailCheck.match(a['emailForward']) is None: delete = True
506 a.delete_mailforward()
508 # Generate the email forwarding list
509 def GenForward(accounts, File):
512 OldMask = os.umask(0022)
513 F = open(File + ".tmp", "w", 0644)
517 if not 'emailForward' in a: continue
518 Line = "%s: %s" % (a['uid'], a['emailForward'])
519 Line = Sanitize(Line) + "\n"
522 # Oops, something unspeakable happened.
528 def GenCDB(accounts, File, key):
531 OldMask = os.umask(0022)
532 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
535 # Write out the email address for each user
537 if not key in a: continue
540 Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
543 # Oops, something unspeakable happened.
547 if Fdb.close() != None:
548 raise "cdbmake gave an error"
550 # Generate the anon XEarth marker file
551 def GenMarkers(accounts, File):
554 F = open(File + ".tmp", "w")
556 # Write out the position for each user
558 if not ('latitude' in a and 'longitude' in a): continue
560 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
561 Line = Sanitize(Line) + "\n"
566 # Oops, something unspeakable happened.
572 # Generate the debian-private subscription list
573 def GenPrivate(accounts, File):
576 F = open(File + ".tmp", "w")
578 # Write out the position for each user
580 if not a.is_active_user(): continue
581 if not 'privateSub' in a: continue
583 Line = "%s"%(a['privateSub'])
584 Line = Sanitize(Line) + "\n"
589 # Oops, something unspeakable happened.
595 # Generate a list of locked accounts
596 def GenDisabledAccounts(accounts, File):
599 F = open(File + ".tmp", "w")
600 disabled_accounts = []
602 # Fetch all the users
604 if a.pw_active(): continue
605 Line = "%s:%s" % (a['uid'], "Account is locked")
606 disabled_accounts.append(a)
607 F.write(Sanitize(Line) + "\n")
609 # Oops, something unspeakable happened.
614 return disabled_accounts
616 # Generate the list of local addresses that refuse all mail
617 def GenMailDisable(accounts, File):
620 F = open(File + ".tmp", "w")
623 if not 'mailDisableMessage' in a: continue
624 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
625 Line = Sanitize(Line) + "\n"
628 # Oops, something unspeakable happened.
634 # Generate a list of uids that should have boolean affects applied
635 def GenMailBool(accounts, File, key):
638 F = open(File + ".tmp", "w")
641 if not key in a: continue
642 if not a[key] == 'TRUE': continue
643 Line = "%s"%(a['uid'])
644 Line = Sanitize(Line) + "\n"
647 # Oops, something unspeakable happened.
653 # Generate a list of hosts for RBL or whitelist purposes.
654 def GenMailList(accounts, File, key):
657 F = open(File + ".tmp", "w")
659 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
660 else: validregex = re.compile('^[-\w.]+$')
663 if not key in a: continue
665 filtered = filter(lambda z: validregex.match(z), a[key])
666 if len(filtered) == 0: continue
667 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
668 line = a['uid'] + ': ' + ' : '.join(filtered)
669 line = Sanitize(line) + "\n"
672 # Oops, something unspeakable happened.
678 def isRoleAccount(account):
679 return 'debianRoleAccount' in account['objectClass']
681 # Generate the DNS Zone file
682 def GenDNS(accounts, File):
685 F = open(File + ".tmp", "w")
687 # Fetch all the users
690 # Write out the zone file entry for each user
692 if not 'dnsZoneEntry' in a: continue
693 if not a.is_active_user() and not isRoleAccount(a): continue
696 F.write("; %s\n"%(a.email_address()))
697 for z in a["dnsZoneEntry"]:
698 Split = z.lower().split()
699 if Split[1].lower() == 'in':
700 for y in range(0, len(Split)):
703 Line = " ".join(Split) + "\n"
706 Host = Split[0] + DNSZone
707 if BSMTPCheck.match(Line) != None:
708 F.write("; Has BSMTP\n")
710 # Write some identification information
711 if not RRs.has_key(Host):
712 if Split[2].lower() in ["a", "aaaa"]:
713 Line = "%s IN TXT \"%s\"\n"%(Split[0], a.email_address())
714 for y in a["keyFingerPrint"]:
715 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
719 Line = "; Err %s"%(str(Split))
724 F.write("; Errors:\n")
725 for line in str(e).split("\n"):
726 F.write("; %s\n"%(line))
729 # Oops, something unspeakable happened.
735 def ExtractDNSInfo(x):
739 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
742 if x[1].has_key("ipHostNumber"):
743 for I in x[1]["ipHostNumber"]:
744 if IsV6Addr.match(I) != None:
745 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
747 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
751 if 'sshRSAHostKey' in x[1]:
752 for I in x[1]["sshRSAHostKey"]:
754 if Split[0] == 'ssh-rsa':
756 if Split[0] == 'ssh-dss':
758 if Algorithm == None:
760 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
761 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
763 if 'architecture' in x[1]:
764 Arch = GetAttr(x, "architecture")
766 if x[1].has_key("machine"):
767 Mach = " " + GetAttr(x, "machine")
768 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
770 if x[1].has_key("mXRecord"):
771 for I in x[1]["mXRecord"]:
772 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
776 # Generate the DNS records
777 def GenZoneRecords(File):
780 F = open(File + ".tmp", "w")
782 # Fetch all the hosts
786 if x[1].has_key("hostname") == 0:
789 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
792 DNSInfo = ExtractDNSInfo(x)
796 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
799 Line = "\t\t\t%s" % (Line)
803 # this would write sshfp lines for services on machines
804 # but we can't yet, since some are cnames and we'll make
805 # an invalid zonefile
807 # for i in x[1].get("purpose", []):
808 # m = PurposeHostField.match(i)
811 # # we ignore [[*..]] entries
812 # if m.startswith('*'):
814 # if m.startswith('-'):
817 # if not m.endswith(HostDomain):
819 # if not m.endswith('.'):
821 # for Line in DNSInfo:
822 # if isSSHFP.match(Line):
823 # Line = "%s\t%s" % (m, Line)
824 # F.write(Line + "\n")
826 # Oops, something unspeakable happened.
832 # Generate the BSMTP file
833 def GenBSMTP(accounts, File, HomePrefix):
836 F = open(File + ".tmp", "w")
838 # Write out the zone file entry for each user
840 if not 'dnsZoneEntry' in a: continue
841 if not a.is_active_user(): continue
844 for z in a["dnsZoneEntry"]:
845 Split = z.lower().split()
846 if Split[1].lower() == 'in':
847 for y in range(0, len(Split)):
850 Line = " ".join(Split) + "\n"
852 Host = Split[0] + DNSZone
853 if BSMTPCheck.match(Line) != None:
854 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
855 a['uid'], HomePrefix, a['uid'], Host))
858 F.write("; Errors\n")
861 # Oops, something unspeakable happened.
867 def HostToIP(Host, mapped=True):
871 if Host[1].has_key("ipHostNumber"):
872 for addr in Host[1]["ipHostNumber"]:
873 IPAdresses.append(addr)
874 if IsV6Addr.match(addr) is None and mapped == "True":
875 IPAdresses.append("::ffff:"+addr)
879 # Generate the ssh known hosts file
880 def GenSSHKnown(File, mode=None):
883 OldMask = os.umask(0022)
884 F = open(File + ".tmp", "w", 0644)
890 if x[1].has_key("hostname") == 0 or \
891 x[1].has_key("sshRSAHostKey") == 0:
893 Host = GetAttr(x, "hostname")
895 if Host.endswith(HostDomain):
896 HostNames.append(Host[:-(len(HostDomain) + 1)])
898 # in the purpose field [[host|some other text]] (where some other text is optional)
899 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
900 # file. But so that we don't have to add everything we link we can add an asterisk
901 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
902 # http linking it we also support [[-hostname]] entries.
903 for i in x[1].get("purpose", []):
904 m = PurposeHostField.match(i)
907 # we ignore [[*..]] entries
908 if m.startswith('*'):
910 if m.startswith('-'):
914 if m.endswith(HostDomain):
915 HostNames.append(m[:-(len(HostDomain) + 1)])
917 for I in x[1]["sshRSAHostKey"]:
918 if mode and mode == 'authorized_keys':
920 if 'sshdistAuthKeysHost' in x[1]:
921 hosts += x[1]['sshdistAuthKeysHost']
922 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)
924 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
925 Line = Sanitize(Line) + "\n"
927 # Oops, something unspeakable happened.
933 # Generate the debianhosts file (list of all IP addresses)
937 OldMask = os.umask(0022)
938 F = open(File + ".tmp", "w", 0644)
947 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
950 if not 'ipHostNumber' in x[1]:
953 addrs = x[1]["ipHostNumber"]
957 addr = Sanitize(addr) + "\n"
960 # Oops, something unspeakable happened.
966 def GenKeyrings(OutDir):
968 shutil.copy(k, OutDir)
970 # Connect to the ldap server
972 # for testing purposes it's sometimes useful to pass username/password
973 # via the environment
974 if 'UD_CREDENTIALS' in os.environ:
975 Pass = os.environ['UD_CREDENTIALS'].split()
977 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
978 Pass = F.readline().strip().split(" ")
980 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
982 # Fetch all the groups
984 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
985 ["gid", "gidNumber", "subGroup"])
987 # Generate the SubGroupMap and GroupIDMap
989 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
991 if x[1].has_key("gidNumber") == 0:
993 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
994 if x[1].has_key("subGroup") != 0:
995 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
997 # Fetch all the users
998 passwd_attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
999 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1000 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1001 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1002 "shadowExpire", "emailForward", "latitude", "longitude",\
1003 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1004 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1005 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1006 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1007 "mailContentInspectionAction"])
1009 if passwd_attrs is None:
1010 raise UDEmptyList, "No Users"
1011 accounts = map(lambda x: UDLdap.Account(x[0], x[1]), passwd_attrs)
1012 accounts.sort(lambda x,y: cmp(x['uid'].lower(), y['uid'].lower()))
1014 # Fetch all the hosts
1015 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1016 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1017 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1019 if HostAttrs == None:
1020 raise UDEmptyList, "No Hosts"
1022 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1024 # override globaldir for testing
1025 if 'UD_GENERATEDIR' in os.environ:
1026 GenerateDir = os.environ['UD_GENERATEDIR']
1028 # Generate global things
1029 GlobalDir = GenerateDir + "/"
1030 accounts_disabled = GenDisabledAccounts(accounts, GlobalDir + "disabled-accounts")
1032 accounts = filter(lambda x: not IsRetired(x), accounts)
1033 #accounts_DDs = filter(lambda x: IsGidDebian(x), accounts)
1035 CheckForward(accounts)
1037 GenMailDisable(accounts, GlobalDir + "mail-disable")
1038 GenCDB(accounts, GlobalDir + "mail-forward.cdb", 'emailForward')
1039 GenCDB(accounts, GlobalDir + "mail-contentinspectionaction.cdb", 'mailContentInspectionAction')
1040 GenPrivate(accounts, GlobalDir + "debian-private")
1041 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1042 GenMailBool(accounts, GlobalDir + "mail-greylist", "mailGreylisting")
1043 GenMailBool(accounts, GlobalDir + "mail-callout", "mailCallout")
1044 GenMailList(accounts, GlobalDir + "mail-rbl", "mailRBL")
1045 GenMailList(accounts, GlobalDir + "mail-rhsbl", "mailRHSBL")
1046 GenMailList(accounts, GlobalDir + "mail-whitelist", "mailWhitelist")
1047 GenKeyrings(GlobalDir)
1050 GenForward(accounts, GlobalDir + "forward-alias")
1052 GenAllUsers(accounts, GlobalDir + 'all-accounts.json')
1053 accounts = filter(lambda a: not a in accounts_disabled, accounts)
1055 SSHFiles = GenSSHShadow(accounts)
1056 GenMarkers(accounts, GlobalDir + "markers")
1057 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1058 GenHosts(GlobalDir + "debianhosts")
1060 for host in HostAttrs:
1061 if not "hostname" in host[1]:
1064 CurrentHost = host[1]['hostname'][0]
1065 OutDir = GlobalDir + CurrentHost + '/'
1071 # Get the group list and convert any named groups to numerics
1073 for groupname in AllowedGroupsPreload.strip().split(" "):
1074 GroupList[groupname] = True
1075 if 'allowedGroups' in host[1]:
1076 for groupname in host[1]['allowedGroups']:
1077 GroupList[groupname] = True
1078 for groupname in GroupList.keys():
1079 if groupname in GroupIDMap:
1080 GroupList[str(GroupIDMap[groupname])] = True
1083 if 'exportOptions' in host[1]:
1084 for extra in host[1]['exportOptions']:
1085 ExtraList[extra.upper()] = True
1091 DoLink(GlobalDir, OutDir, "debianhosts")
1092 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1093 DoLink(GlobalDir, OutDir, "disabled-accounts")
1096 if 'NOPASSWD' in ExtraList:
1097 userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "*")
1099 userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "x")
1101 grouprevmap = GenGroup(accounts, OutDir + "group")
1102 GenShadowSudo(accounts, OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1104 # Now we know who we're allowing on the machine, export
1105 # the relevant ssh keys
1106 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1108 if not 'NOPASSWD' in ExtraList:
1109 GenShadow(accounts, OutDir + "shadow")
1111 # Link in global things
1112 if not 'NOMARKERS' in ExtraList:
1113 DoLink(GlobalDir, OutDir, "markers")
1114 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1115 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1116 DoLink(GlobalDir, OutDir, "mail-disable")
1117 DoLink(GlobalDir, OutDir, "mail-greylist")
1118 DoLink(GlobalDir, OutDir, "mail-callout")
1119 DoLink(GlobalDir, OutDir, "mail-rbl")
1120 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1121 DoLink(GlobalDir, OutDir, "mail-whitelist")
1122 DoLink(GlobalDir, OutDir, "all-accounts.json")
1123 GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "user-forward.cdb", 'emailForward')
1124 GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "batv-tokens.cdb", 'bATVToken')
1125 GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "default-mail-options.cdb", 'mailDefaultOptions')
1128 DoLink(GlobalDir, OutDir, "forward-alias")
1130 if 'DNS' in ExtraList:
1131 GenDNS(accounts, OutDir + "dns-zone")
1132 GenZoneRecords(OutDir + "dns-sshfp")
1134 if 'AUTHKEYS' in ExtraList:
1135 DoLink(GlobalDir, OutDir, "authorized_keys")
1137 if 'BSMTP' in ExtraList:
1138 GenBSMTP(accounts, OutDir + "bsmtp", HomePrefix)
1140 if 'PRIVATE' in ExtraList:
1141 DoLink(GlobalDir, OutDir, "debian-private")
1143 if 'KEYRING' in ExtraList:
1145 DoLink(GlobalDir, OutDir, os.path.basename(k))
1149 posix.remove(OutDir + os.path.basename(k))
1155 # vim:set shiftwidth=3: