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")
53 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
55 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
56 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
57 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
58 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
59 IsDebianHost = re.compile(ConfModule.dns_hostmatch)
60 isSSHFP = re.compile("^\s*IN\s+SSHFP")
61 DNSZone = ".debian.net"
62 Keyrings = ConfModule.sync_keyrings.split(":")
64 def safe_makedirs(dir):
68 if e.errno == errno.EEXIST:
77 if e.errno == errno.ENOENT:
83 return Str.translate(string.maketrans("\n\r\t", "$$$"))
85 def DoLink(From, To, File):
87 posix.remove(To + File)
90 posix.link(From + File, To + File)
92 def IsRetired(account):
94 Looks for accountStatus in the LDAP record and tries to
95 match it against one of the known retired statuses
98 status = account['accountStatus']
100 line = status.split()
103 if status == "inactive":
106 elif status == "memorial":
109 elif status == "retiring":
110 # We'll give them a few extra days over what we said
111 age = 6 * 31 * 24 * 60 * 60
113 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
121 #def IsGidDebian(account):
122 # return account['gidNumber'] == 800
124 # See if this user is in the group list
125 def IsInGroup(account):
129 # See if the primary group is in the list
130 if str(account['gidNumber']) in Allowed: return True
132 # Check the host based ACL
133 if 'allowedHost' in account and CurrentHost in account['allowedHost']: return True
135 # See if there are supplementary groups
136 if not 'supplementaryGid' in account: return False
139 addGroups(supgroups, account['supplementaryGid'], account['uid'])
141 if Allowed.has_key(g):
145 def Die(File, F, Fdb):
151 os.remove(File + ".tmp")
155 os.remove(File + ".tdb.tmp")
159 def Done(File, F, Fdb):
162 os.rename(File + ".tmp", File)
165 os.rename(File + ".tdb.tmp", File + ".tdb")
167 # Generate the password list
168 def GenPasswd(accounts, File, HomePrefix, PwdMarker):
171 F = open(File + ".tdb.tmp", "w")
176 if not IsInGroup(a): continue
178 # Do not let people try to buffer overflow some busted passwd parser.
179 if len(a['gecos']) > 100 or len(a['loginShell']) > 50: continue
181 userlist[a['uid']] = a['gidNumber']
182 line = "%s:%s:%d:%d:%s:%s%s:%s" % (
188 HomePrefix, a['uid'],
190 line = Sanitize(line) + "\n"
191 F.write("0%u %s" % (i, line))
192 F.write(".%s %s" % (a['uid'], line))
193 F.write("=%d %s" % (a['uidNumber'], line))
196 # Oops, something unspeakable happened.
202 # Return the list of users so we know which keys to export
205 # Generate the shadow list
206 def GenShadow(accounts, File):
209 OldMask = os.umask(0077)
210 F = open(File + ".tdb.tmp", "w", 0600)
216 if not IsInGroup(a): continue
218 # If the account is locked, mark it as such in shadow
219 # See Debian Bug #308229 for why we set it to 1 instead of 0
220 if not a.pw_active(): ShadowExpire = '1'
221 elif 'shadowExpire' in a: ShadowExpire = str(a['shadowExpire'])
222 else: ShadowExpire = ''
225 values.append(a['uid'])
226 values.append(a.get_password())
227 for key in 'shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive':
228 if key in a: values.append(a[key])
229 else: values.append('')
230 values.append(ShadowExpire)
231 line = ':'.join(values)+':'
232 line = Sanitize(line) + "\n"
233 F.write("0%u %s" % (i, line))
234 F.write(".%s %s" % (a['uid'], line))
237 # Oops, something unspeakable happened.
243 # Generate the sudo passwd file
244 def GenShadowSudo(accounts, File, untrusted):
247 OldMask = os.umask(0077)
248 F = open(File + ".tmp", "w", 0600)
253 if not IsInGroup(a): continue
255 if 'sudoPassword' in a:
256 for entry in a['sudoPassword']:
257 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
260 uuid = Match.group(1)
261 status = Match.group(2)
262 hosts = Match.group(3)
263 cryptedpass = Match.group(4)
265 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass):
267 for_all = hosts == "*"
268 for_this_host = CurrentHost in hosts.split(',')
269 if not (for_all or for_this_host):
271 # ignore * passwords for untrusted hosts, but copy host specific passwords
272 if for_all and untrusted:
275 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
280 Line = "%s:%s" % (a['uid'], Pass)
281 Line = Sanitize(Line) + "\n"
282 F.write("%s" % (Line))
284 # Oops, something unspeakable happened.
290 # Generate the shadow list
291 def GenSSHShadow(accounts):
292 # Fetch all the users
295 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
296 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
299 if not 'sshRSAAuthKey' in a: continue
303 OldMask = os.umask(0077)
304 File = os.path.join(GlobalDir, 'userkeys', a['uid'])
305 F = open(File + ".tmp", "w", 0600)
308 for I in a['sshRSAAuthKey']:
309 MultipleLine = "%s" % I
310 MultipleLine = Sanitize(MultipleLine) + "\n"
311 F.write(MultipleLine)
314 userfiles.append(os.path.basename(File))
316 # Oops, something unspeakable happened.
319 # As neither masterFileName nor masterFile are defined at any point
320 # this will raise a NameError.
321 Die(masterFileName, masterFile, None)
326 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
327 OldMask = os.umask(0077)
328 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
330 for f in userlist.keys():
331 if f not in SSHFiles:
333 # If we're not exporting their primary group, don't export
336 if userlist[f] in grouprevmap.keys():
337 grname = grouprevmap[userlist[f]]
340 if int(userlist[f]) <= 100:
341 # In these cases, look it up in the normal way so we
342 # deal with cases where, for instance, users are in group
343 # users as their primary group.
344 grname = grp.getgrgid(userlist[f])[0]
349 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])
352 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
353 # These will only be used where the username doesn't
354 # exist on the target system for some reason; hence,
355 # in those cases, the safest thing is for the file to
356 # be owned by root but group nobody. This deals with
357 # the bloody obscure case where the group fails to exist
358 # whilst the user does (in which case we want to avoid
359 # ending up with a file which is owned user:root to avoid
360 # a fairly obvious attack vector)
363 # Using the username / groupname fields avoids any need
364 # to give a shit^W^W^Wcare about the UIDoffset stuff.
369 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
371 for line in contents.splitlines():
372 if line.startswith("allowed_hosts=") and ' ' in line:
373 machines, line = line.split('=', 1)[1].split(' ', 1)
374 if CurrentHost not in machines.split(','):
375 continue # skip this key
378 continue # no keys for this host
379 contents = "\n".join(lines) + "\n"
380 to.size = len(contents)
381 tf.addfile(to, StringIO(contents))
384 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
386 # add a list of groups to existing groups,
387 # including all subgroups thereof, recursively.
388 # basically this proceduces the transitive hull of the groups in
390 def addGroups(existingGroups, newGroups, uid):
391 for group in newGroups:
392 # if it's a <group>@host, split it and verify it's on the current host.
393 s = group.split('@', 1)
394 if len(s) == 2 and s[1] != CurrentHost:
398 # let's see if we handled this group already
399 if group in existingGroups:
402 if not GroupIDMap.has_key(group):
403 print "Group", group, "does not exist but", uid, "is in it"
406 existingGroups.append(group)
408 if SubGroupMap.has_key(group):
409 addGroups(existingGroups, SubGroupMap[group], uid)
411 # Generate the group list
412 def GenGroup(accounts, File):
416 F = open(File + ".tdb.tmp", "w")
418 # Generate the GroupMap
420 for x in GroupIDMap.keys():
422 GroupHasPrimaryMembers = {}
424 # Sort them into a list of groups having a set of users
426 GroupHasPrimaryMembers[ a['gidNumber'] ] = True
427 if not IsInGroup(a): continue
428 if not 'supplementaryGid' in a: continue
431 addGroups(supgroups, a['supplementaryGid'], a['uid'])
433 GroupMap[g].append(a['uid'])
435 # Output the group file.
437 for x in GroupMap.keys():
438 if GroupIDMap.has_key(x) == 0:
441 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
444 grouprevmap[GroupIDMap[x]] = x
446 Line = "%s:x:%u:" % (x, GroupIDMap[x])
448 for I in GroupMap[x]:
449 Line = Line + ("%s%s" % (Comma, I))
451 Line = Sanitize(Line) + "\n"
452 F.write("0%u %s" % (J, Line))
453 F.write(".%s %s" % (x, Line))
454 F.write("=%u %s" % (GroupIDMap[x], Line))
457 # Oops, something unspeakable happened.
465 def CheckForward(accounts):
467 if not 'emailForward' in a: continue
472 if not IsInGroup(a): delete = True
473 # Do not allow people to try to buffer overflow busted parsers
474 elif len(a['emailForward']) > 200: delete = True
475 # Check the forwarding address
476 elif EmailCheck.match(a['emailForward']) is None: delete = True
479 a.delete_mailforward()
481 # Generate the email forwarding list
482 def GenForward(accounts, File):
485 OldMask = os.umask(0022)
486 F = open(File + ".tmp", "w", 0644)
490 if not 'emailForward' in a: continue
491 Line = "%s: %s" % (a['uid'], a['emailForward'])
492 Line = Sanitize(Line) + "\n"
495 # Oops, something unspeakable happened.
501 def GenCDB(accounts, File, key):
504 OldMask = os.umask(0022)
505 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
508 # Write out the email address for each user
510 if not key in a: continue
513 Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
516 # Oops, something unspeakable happened.
520 if Fdb.close() != None:
521 raise "cdbmake gave an error"
523 # Generate the anon XEarth marker file
524 def GenMarkers(accounts, File):
527 F = open(File + ".tmp", "w")
529 # Write out the position for each user
531 if not ('latitude' in a and 'longitude' in a): continue
533 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
534 Line = Sanitize(Line) + "\n"
539 # Oops, something unspeakable happened.
545 # Generate the debian-private subscription list
546 def GenPrivate(accounts, File):
549 F = open(File + ".tmp", "w")
551 # Write out the position for each user
553 if not a.is_active_user(): continue
554 if not 'privateSub' in a: continue
556 Line = "%s"%(a['privateSub'])
557 Line = Sanitize(Line) + "\n"
562 # Oops, something unspeakable happened.
568 # Generate a list of locked accounts
569 def GenDisabledAccounts(accounts, File):
572 F = open(File + ".tmp", "w")
573 disabled_accounts = []
575 # Fetch all the users
577 if a.pw_active(): continue
578 Line = "%s:%s" % (a['uid'], "Account is locked")
579 disabled_accounts.append(a)
580 F.write(Sanitize(Line) + "\n")
582 # Oops, something unspeakable happened.
587 return disabled_accounts
589 # Generate the list of local addresses that refuse all mail
590 def GenMailDisable(accounts, File):
593 F = open(File + ".tmp", "w")
596 if not 'mailDisableMessage' in a: continue
597 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
598 Line = Sanitize(Line) + "\n"
601 # Oops, something unspeakable happened.
607 # Generate a list of uids that should have boolean affects applied
608 def GenMailBool(accounts, File, key):
611 F = open(File + ".tmp", "w")
614 if not key in a: continue
615 if not a[key] == 'TRUE': continue
616 Line = "%s"%(a['uid'])
617 Line = Sanitize(Line) + "\n"
620 # Oops, something unspeakable happened.
626 # Generate a list of hosts for RBL or whitelist purposes.
627 def GenMailList(accounts, File, key):
630 F = open(File + ".tmp", "w")
632 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
633 else: validregex = re.compile('^[-\w.]+$')
636 if not key in a: continue
638 filtered = filter(lambda z: validregex.match(z), a[key])
639 if len(filtered) == 0: continue
640 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
641 line = a['uid'] + ': ' + ' : '.join(filtered)
642 line = Sanitize(line) + "\n"
645 # Oops, something unspeakable happened.
651 def isRoleAccount(account):
652 return 'debianRoleAccount' in account['objectClass']
654 # Generate the DNS Zone file
655 def GenDNS(accounts, File):
658 F = open(File + ".tmp", "w")
660 # Fetch all the users
663 # Write out the zone file entry for each user
665 if not 'dnsZoneEntry' in a: continue
666 if not a.is_active_user() and not isRoleAccount(a): continue
669 F.write("; %s\n"%(a.email_address()))
670 for z in a["dnsZoneEntry"]:
671 Split = z.lower().split()
672 if Split[1].lower() == 'in':
673 for y in range(0, len(Split)):
676 Line = " ".join(Split) + "\n"
679 Host = Split[0] + DNSZone
680 if BSMTPCheck.match(Line) != None:
681 F.write("; Has BSMTP\n")
683 # Write some identification information
684 if not RRs.has_key(Host):
685 if Split[2].lower() in ["a", "aaaa"]:
686 Line = "%s IN TXT \"%s\"\n"%(Split[0], a.email_address())
687 for y in a["keyFingerPrint"]:
688 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
692 Line = "; Err %s"%(str(Split))
697 F.write("; Errors:\n")
698 for line in str(e).split("\n"):
699 F.write("; %s\n"%(line))
702 # Oops, something unspeakable happened.
708 def ExtractDNSInfo(x):
712 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
715 if x[1].has_key("ipHostNumber"):
716 for I in x[1]["ipHostNumber"]:
717 if IsV6Addr.match(I) != None:
718 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
720 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
724 if 'sshRSAHostKey' in x[1]:
725 for I in x[1]["sshRSAHostKey"]:
727 if Split[0] == 'ssh-rsa':
729 if Split[0] == 'ssh-dss':
731 if Algorithm == None:
733 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
734 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
736 if 'architecture' in x[1]:
737 Arch = GetAttr(x, "architecture")
739 if x[1].has_key("machine"):
740 Mach = " " + GetAttr(x, "machine")
741 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
743 if x[1].has_key("mXRecord"):
744 for I in x[1]["mXRecord"]:
745 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
749 # Generate the DNS records
750 def GenZoneRecords(File):
753 F = open(File + ".tmp", "w")
755 # Fetch all the hosts
759 if x[1].has_key("hostname") == 0:
762 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
765 DNSInfo = ExtractDNSInfo(x)
769 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
772 Line = "\t\t\t%s" % (Line)
776 # this would write sshfp lines for services on machines
777 # but we can't yet, since some are cnames and we'll make
778 # an invalid zonefile
780 # for i in x[1].get("purpose", []):
781 # m = PurposeHostField.match(i)
784 # # we ignore [[*..]] entries
785 # if m.startswith('*'):
787 # if m.startswith('-'):
790 # if not m.endswith(HostDomain):
792 # if not m.endswith('.'):
794 # for Line in DNSInfo:
795 # if isSSHFP.match(Line):
796 # Line = "%s\t%s" % (m, Line)
797 # F.write(Line + "\n")
799 # Oops, something unspeakable happened.
805 # Generate the BSMTP file
806 def GenBSMTP(accounts, File, HomePrefix):
809 F = open(File + ".tmp", "w")
811 # Write out the zone file entry for each user
813 if not 'dnsZoneEntry' in a: continue
814 if not a.is_active_user(): continue
817 for z in a["dnsZoneEntry"]:
818 Split = z.lower().split()
819 if Split[1].lower() == 'in':
820 for y in range(0, len(Split)):
823 Line = " ".join(Split) + "\n"
825 Host = Split[0] + DNSZone
826 if BSMTPCheck.match(Line) != None:
827 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
828 a['uid'], HomePrefix, a['uid'], Host))
831 F.write("; Errors\n")
834 # Oops, something unspeakable happened.
840 def HostToIP(Host, mapped=True):
844 if Host[1].has_key("ipHostNumber"):
845 for addr in Host[1]["ipHostNumber"]:
846 IPAdresses.append(addr)
847 if IsV6Addr.match(addr) is None and mapped == "True":
848 IPAdresses.append("::ffff:"+addr)
852 # Generate the ssh known hosts file
853 def GenSSHKnown(File, mode=None):
856 OldMask = os.umask(0022)
857 F = open(File + ".tmp", "w", 0644)
863 if x[1].has_key("hostname") == 0 or \
864 x[1].has_key("sshRSAHostKey") == 0:
866 Host = GetAttr(x, "hostname")
868 if Host.endswith(HostDomain):
869 HostNames.append(Host[:-(len(HostDomain) + 1)])
871 # in the purpose field [[host|some other text]] (where some other text is optional)
872 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
873 # file. But so that we don't have to add everything we link we can add an asterisk
874 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
875 # http linking it we also support [[-hostname]] entries.
876 for i in x[1].get("purpose", []):
877 m = PurposeHostField.match(i)
880 # we ignore [[*..]] entries
881 if m.startswith('*'):
883 if m.startswith('-'):
887 if m.endswith(HostDomain):
888 HostNames.append(m[:-(len(HostDomain) + 1)])
890 for I in x[1]["sshRSAHostKey"]:
891 if mode and mode == 'authorized_keys':
893 if 'sshdistAuthKeysHost' in x[1]:
894 hosts += x[1]['sshdistAuthKeysHost']
895 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)
897 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
898 Line = Sanitize(Line) + "\n"
900 # Oops, something unspeakable happened.
906 # Generate the debianhosts file (list of all IP addresses)
910 OldMask = os.umask(0022)
911 F = open(File + ".tmp", "w", 0644)
920 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
923 if not 'ipHostNumber' in x[1]:
926 addrs = x[1]["ipHostNumber"]
930 addr = Sanitize(addr) + "\n"
933 # Oops, something unspeakable happened.
939 def GenKeyrings(OutDir):
941 shutil.copy(k, OutDir)
943 # Connect to the ldap server
945 # for testing purposes it's sometimes useful to pass username/password
946 # via the environment
947 if 'UD_CREDENTIALS' in os.environ:
948 Pass = os.environ['UD_CREDENTIALS'].split()
950 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
951 Pass = F.readline().strip().split(" ")
953 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
955 # Fetch all the groups
957 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
958 ["gid", "gidNumber", "subGroup"])
960 # Generate the SubGroupMap and GroupIDMap
962 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
964 if x[1].has_key("gidNumber") == 0:
966 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
967 if x[1].has_key("subGroup") != 0:
968 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
970 # Fetch all the users
971 passwd_attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
972 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
973 "gecos", "loginShell", "userPassword", "shadowLastChange",\
974 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
975 "shadowExpire", "emailForward", "latitude", "longitude",\
976 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
977 "keyFingerPrint", "privateSub", "mailDisableMessage",\
978 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
979 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
980 "mailContentInspectionAction"])
982 if passwd_attrs is None:
983 raise UDEmptyList, "No Users"
984 accounts = map(lambda x: UDLdap.Account(x[0], x[1]), passwd_attrs)
985 accounts.sort(lambda x,y: cmp(x['uid'].lower(), y['uid'].lower()))
987 # Fetch all the hosts
988 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
989 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
990 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
992 if HostAttrs == None:
993 raise UDEmptyList, "No Hosts"
995 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
997 # override globaldir for testing
998 if 'UD_GENERATEDIR' in os.environ:
999 GenerateDir = os.environ['UD_GENERATEDIR']
1001 # Generate global things
1002 GlobalDir = GenerateDir + "/"
1003 accounts_disabled = GenDisabledAccounts(accounts, GlobalDir + "disabled-accounts")
1005 accounts = filter(lambda x: not IsRetired(x), accounts)
1006 #accounts_DDs = filter(lambda x: IsGidDebian(x), accounts)
1008 CheckForward(accounts)
1010 GenMailDisable(accounts, GlobalDir + "mail-disable")
1011 GenCDB(accounts, GlobalDir + "mail-forward.cdb", 'emailForward')
1012 GenCDB(accounts, GlobalDir + "mail-contentinspectionaction.cdb", 'mailContentInspectionAction')
1013 GenPrivate(accounts, GlobalDir + "debian-private")
1014 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1015 GenMailBool(accounts, GlobalDir + "mail-greylist", "mailGreylisting")
1016 GenMailBool(accounts, GlobalDir + "mail-callout", "mailCallout")
1017 GenMailList(accounts, GlobalDir + "mail-rbl", "mailRBL")
1018 GenMailList(accounts, GlobalDir + "mail-rhsbl", "mailRHSBL")
1019 GenMailList(accounts, GlobalDir + "mail-whitelist", "mailWhitelist")
1020 GenKeyrings(GlobalDir)
1023 GenForward(accounts, GlobalDir + "forward-alias")
1025 accounts = filter(lambda a: not a in accounts_disabled, accounts)
1027 SSHFiles = GenSSHShadow(accounts)
1028 GenMarkers(accounts, GlobalDir + "markers")
1029 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1030 GenHosts(GlobalDir + "debianhosts")
1032 for host in HostAttrs:
1033 if not "hostname" in host[1]:
1036 CurrentHost = host[1]['hostname'][0]
1037 OutDir = GenerateDir + '/' + CurrentHost + '/'
1043 # Get the group list and convert any named groups to numerics
1045 for groupname in AllowedGroupsPreload.strip().split(" "):
1046 GroupList[groupname] = True
1047 if 'allowedGroups' in host[1]:
1048 for groupname in host[1]['allowedGroups']:
1049 GroupList[groupname] = True
1050 for groupname in GroupList.keys():
1051 if groupname in GroupIDMap:
1052 GroupList[str(GroupIDMap[groupname])] = True
1055 if 'exportOptions' in host[1]:
1056 for extra in host[1]['exportOptions']:
1057 ExtraList[extra.upper()] = True
1063 DoLink(GlobalDir, OutDir, "debianhosts")
1064 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1065 DoLink(GlobalDir, OutDir, "disabled-accounts")
1068 if 'NOPASSWD' in ExtraList:
1069 userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "*")
1071 userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "x")
1073 grouprevmap = GenGroup(accounts, OutDir + "group")
1074 GenShadowSudo(accounts, OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1076 # Now we know who we're allowing on the machine, export
1077 # the relevant ssh keys
1078 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1080 if not 'NOPASSWD' in ExtraList:
1081 GenShadow(accounts, OutDir + "shadow")
1083 # Link in global things
1084 if not 'NOMARKERS' in ExtraList:
1085 DoLink(GlobalDir, OutDir, "markers")
1086 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1087 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1088 DoLink(GlobalDir, OutDir, "mail-disable")
1089 DoLink(GlobalDir, OutDir, "mail-greylist")
1090 DoLink(GlobalDir, OutDir, "mail-callout")
1091 DoLink(GlobalDir, OutDir, "mail-rbl")
1092 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1093 DoLink(GlobalDir, OutDir, "mail-whitelist")
1094 GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "user-forward.cdb", 'emailForward')
1095 GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "batv-tokens.cdb", 'bATVToken')
1096 GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "default-mail-options.cdb", 'mailDefaultOptions')
1099 DoLink(GlobalDir, OutDir, "forward-alias")
1101 if 'DNS' in ExtraList:
1102 GenDNS(accounts, OutDir + "dns-zone")
1103 GenZoneRecords(OutDir + "dns-sshfp")
1105 if 'AUTHKEYS' in ExtraList:
1106 DoLink(GlobalDir, OutDir, "authorized_keys")
1108 if 'BSMTP' in ExtraList:
1109 GenBSMTP(accounts, OutDir + "bsmtp", HomePrefix)
1111 if 'PRIVATE' in ExtraList:
1112 DoLink(GlobalDir, OutDir, "debian-private")
1114 if 'KEYRING' in ExtraList:
1116 DoLink(GlobalDir, OutDir, os.path.basename(k))
1120 posix.remove(OutDir + os.path.basename(k))
1126 # vim:set shiftwidth=3: