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 a = UDLdap.Account(x[0], x[1])
233 if not IsInGroup(x): continue
235 # If the account is locked, mark it as such in shadow
236 # See Debian Bug #308229 for why we set it to 1 instead of 0
237 if not a.pw_active(): ShadowExpire = '1'
238 elif 'shadowExpire' in a: ShadowExpire = str(a['shadowExpire'])
239 else: ShadowExpire = ''
242 values.append(a['uid'])
243 values.append(a.get_password())
244 for key in 'shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive':
245 if key in a: values.append(a[key])
246 else: values.append('')
247 values.append(ShadowExpire)
248 line = ':'.join(values)+':'
249 line = Sanitize(line) + "\n"
250 F.write("0%u %s" % (i, line))
251 F.write(".%s %s" % (a['uid'], line))
254 # Oops, something unspeakable happened.
260 # Generate the sudo passwd file
261 def GenShadowSudo(File, untrusted):
264 OldMask = os.umask(0077)
265 F = open(File + ".tmp", "w", 0600)
268 # Fetch all the users
271 for x in PasswdAttrs:
272 a = UDLdap.Account(x[0], x[1])
274 if not IsInGroup(x): continue
276 if 'sudoPassword' in a:
277 for entry in a['sudoPassword']:
278 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
281 uuid = Match.group(1)
282 status = Match.group(2)
283 hosts = Match.group(3)
284 cryptedpass = Match.group(4)
286 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass):
288 for_all = hosts == "*"
289 for_this_host = CurrentHost in hosts.split(',')
290 if not (for_all or for_this_host):
292 # ignore * passwords for untrusted hosts, but copy host specific passwords
293 if for_all and untrusted:
296 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
301 Line = "%s:%s" % (a['uid'], Pass)
302 Line = Sanitize(Line) + "\n"
303 F.write("%s" % (Line))
305 # Oops, something unspeakable happened.
311 # Generate the shadow list
313 # Fetch all the users
318 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
319 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
321 for x in PasswdAttrs:
322 a = UDLdap.Account(x[0], x[1])
323 if not 'sshRSAAuthKey' in a: continue
327 OldMask = os.umask(0077)
328 File = os.path.join(GlobalDir, 'userkeys', a['uid'])
329 F = open(File + ".tmp", "w", 0600)
332 for I in a['sshRSAAuthKey']:
333 MultipleLine = "%s" % I
334 MultipleLine = Sanitize(MultipleLine) + "\n"
335 F.write(MultipleLine)
338 userfiles.append(os.path.basename(File))
340 # Oops, something unspeakable happened.
343 # As neither masterFileName nor masterFile are defined at any point
344 # this will raise a NameError.
345 Die(masterFileName, masterFile, None)
350 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
351 OldMask = os.umask(0077)
352 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
354 for f in userlist.keys():
355 if f not in SSHFiles:
357 # If we're not exporting their primary group, don't export
360 if userlist[f] in grouprevmap.keys():
361 grname = grouprevmap[userlist[f]]
364 if int(userlist[f]) <= 100:
365 # In these cases, look it up in the normal way so we
366 # deal with cases where, for instance, users are in group
367 # users as their primary group.
368 grname = grp.getgrgid(userlist[f])[0]
373 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])
376 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
377 # These will only be used where the username doesn't
378 # exist on the target system for some reason; hence,
379 # in those cases, the safest thing is for the file to
380 # be owned by root but group nobody. This deals with
381 # the bloody obscure case where the group fails to exist
382 # whilst the user does (in which case we want to avoid
383 # ending up with a file which is owned user:root to avoid
384 # a fairly obvious attack vector)
387 # Using the username / groupname fields avoids any need
388 # to give a shit^W^W^Wcare about the UIDoffset stuff.
393 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
395 for line in contents.splitlines():
396 if line.startswith("allowed_hosts=") and ' ' in line:
397 machines, line = line.split('=', 1)[1].split(' ', 1)
398 if CurrentHost not in machines.split(','):
399 continue # skip this key
402 continue # no keys for this host
403 contents = "\n".join(lines) + "\n"
404 to.size = len(contents)
405 tf.addfile(to, StringIO(contents))
408 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
410 # add a list of groups to existing groups,
411 # including all subgroups thereof, recursively.
412 # basically this proceduces the transitive hull of the groups in
414 def addGroups(existingGroups, newGroups, uid):
415 for group in newGroups:
416 # if it's a <group>@host, split it and verify it's on the current host.
417 s = group.split('@', 1)
418 if len(s) == 2 and s[1] != CurrentHost:
422 # let's see if we handled this group already
423 if group in existingGroups:
426 if not GroupIDMap.has_key(group):
427 print "Group", group, "does not exist but", uid, "is in it"
430 existingGroups.append(group)
432 if SubGroupMap.has_key(group):
433 addGroups(existingGroups, SubGroupMap[group], uid)
435 # Generate the group list
440 F = open(File + ".tdb.tmp", "w")
442 # Generate the GroupMap
444 for x in GroupIDMap.keys():
446 GroupHasPrimaryMembers = {}
448 # Fetch all the users
451 # Sort them into a list of groups having a set of users
452 for x in PasswdAttrs:
453 a = UDLdap.Account(x[0], x[1])
454 GroupHasPrimaryMembers[ a['gidNumber'] ] = True
455 if not IsInGroup(x): continue
456 if not 'supplementaryGid' in a: continue
459 addGroups(supgroups, a['supplementaryGid'], a['uid'])
461 GroupMap[g].append(a['uid'])
463 # Output the group file.
465 for x in GroupMap.keys():
466 if GroupIDMap.has_key(x) == 0:
469 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
472 grouprevmap[GroupIDMap[x]] = x
474 Line = "%s:x:%u:" % (x, GroupIDMap[x])
476 for I in GroupMap[x]:
477 Line = Line + ("%s%s" % (Comma, I))
479 Line = Sanitize(Line) + "\n"
480 F.write("0%u %s" % (J, Line))
481 F.write(".%s %s" % (x, Line))
482 F.write("=%u %s" % (GroupIDMap[x], Line))
485 # Oops, something unspeakable happened.
495 for x in PasswdAttrs:
496 if x[1].has_key("emailForward") == 0:
500 x[1].pop("emailForward")
503 # Do not allow people to try to buffer overflow busted parsers
504 if len(GetAttr(x, "emailForward")) > 200:
505 x[1].pop("emailForward")
508 # Check the forwarding address
509 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
510 x[1].pop("emailForward")
512 # Generate the email forwarding list
513 def GenForward(File):
516 OldMask = os.umask(0022)
517 F = open(File + ".tmp", "w", 0644)
520 # Fetch all the users
523 # Write out the email address for each user
524 for x in PasswdAttrs:
525 a = UDLdap.Account(x[0], x[1])
526 if not 'emailForward' in a: continue
527 Line = "%s: %s" % (a['uid'], a['emailForward'])
528 Line = Sanitize(Line) + "\n"
531 # Oops, something unspeakable happened.
537 def GenCDB(File, Users, key):
540 OldMask = os.umask(0022)
541 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
544 # Write out the email address for each user
546 a = UDLdap.Account(x[0], x[1])
547 if not key in a: continue
550 Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
553 # Oops, something unspeakable happened.
557 if Fdb.close() != None:
558 raise "cdbmake gave an error"
560 # Generate the anon XEarth marker file
561 def GenMarkers(File):
564 F = open(File + ".tmp", "w")
566 # Fetch all the users
569 # Write out the position for each user
570 for x in PasswdAttrs:
571 a = UDLdap.Account(x[0], x[1])
572 if not ('latitude' in a and 'longitude' in a): continue
574 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
575 Line = Sanitize(Line) + "\n"
580 # Oops, something unspeakable happened.
586 # Generate the debian-private subscription list
587 def GenPrivate(File):
590 F = open(File + ".tmp", "w")
592 # Fetch all the users
595 # Write out the position for each user
596 for x in DebianDDUsers:
597 a = UDLdap.Account(x[0], x[1])
598 if not a.is_active_user(): continue
599 if not 'privateSub' in a: continue
601 Line = "%s"%(a['privateSub'])
602 Line = Sanitize(Line) + "\n"
607 # Oops, something unspeakable happened.
613 # Generate a list of locked accounts
614 def GenDisabledAccounts(File):
617 F = open(File + ".tmp", "w")
619 # Fetch all the users
624 for x in PasswdAttrs:
625 a = UDLdap.Account(x[0], x[1])
626 if a.pw_active(): continue
627 Line = "%s:%s" % (a['uid'], "Account is locked")
628 DisabledUsers.append(x)
629 F.write(Sanitize(Line) + "\n")
631 # Oops, something unspeakable happened.
637 # Generate the list of local addresses that refuse all mail
638 def GenMailDisable(File):
641 F = open(File + ".tmp", "w")
643 # Fetch all the users
646 for x in PasswdAttrs:
647 a = UDLdap.Account(x[0], x[1])
648 if not 'mailDisableMessage' in a: continue
649 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
650 Line = Sanitize(Line) + "\n"
653 # Oops, something unspeakable happened.
659 # Generate a list of uids that should have boolean affects applied
660 def GenMailBool(File, key):
663 F = open(File + ".tmp", "w")
665 # Fetch all the users
668 for x in PasswdAttrs:
669 a = UDLdap.Account(x[0], x[1])
670 if not key in a: continue
671 if not a[key] == 'TRUE': continue
672 Line = "%s"%(a['uid'])
673 Line = Sanitize(Line) + "\n"
676 # Oops, something unspeakable happened.
682 # Generate a list of hosts for RBL or whitelist purposes.
683 def GenMailList(File, key):
686 F = open(File + ".tmp", "w")
688 # Fetch all the users
691 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
692 else: validregex = re.compile('^[-\w.]+$')
694 for x in PasswdAttrs:
695 a = UDLdap.Account(x[0], x[1])
696 if not key in a: continue
698 filtered = filter(lambda z: validregex.match(z), a[key])
699 if len(filtered) == 0: continue
700 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
701 line = a['uid'] + ': ' + ' : '.join(filtered)
702 line = Sanitize(line) + "\n"
705 # Oops, something unspeakable happened.
711 def isRoleAccount(pwEntry):
712 if not pwEntry.has_key("objectClass"):
713 raise "pwEntry has no objectClass"
714 oc = pwEntry['objectClass']
716 i = oc.index('debianRoleAccount')
721 # Generate the DNS Zone file
725 F = open(File + ".tmp", "w")
727 # Fetch all the users
731 # Write out the zone file entry for each user
732 for x in PasswdAttrs:
733 if x[1].has_key("dnsZoneEntry") == 0:
736 # If the account has no PGP key, do not write it
737 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
740 F.write("; %s\n"%(EmailAddress(x)))
741 for z in x[1]["dnsZoneEntry"]:
742 Split = z.lower().split()
743 if Split[1].lower() == 'in':
744 for y in range(0, len(Split)):
747 Line = " ".join(Split) + "\n"
750 Host = Split[0] + DNSZone
751 if BSMTPCheck.match(Line) != None:
752 F.write("; Has BSMTP\n")
754 # Write some identification information
755 if not RRs.has_key(Host):
756 if Split[2].lower() in ["a", "aaaa"]:
757 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
758 for y in x[1]["keyFingerPrint"]:
759 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
763 Line = "; Err %s"%(str(Split))
768 F.write("; Errors\n")
771 # Oops, something unspeakable happened.
777 def ExtractDNSInfo(x):
781 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
784 if x[1].has_key("ipHostNumber"):
785 for I in x[1]["ipHostNumber"]:
786 if IsV6Addr.match(I) != None:
787 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
789 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
793 if 'sshRSAHostKey' in x[1]:
794 for I in x[1]["sshRSAHostKey"]:
796 if Split[0] == 'ssh-rsa':
798 if Split[0] == 'ssh-dss':
800 if Algorithm == None:
802 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
803 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
805 if 'architecture' in x[1]:
806 Arch = GetAttr(x, "architecture")
808 if x[1].has_key("machine"):
809 Mach = " " + GetAttr(x, "machine")
810 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
812 if x[1].has_key("mXRecord"):
813 for I in x[1]["mXRecord"]:
814 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
818 # Generate the DNS records
819 def GenZoneRecords(File):
822 F = open(File + ".tmp", "w")
824 # Fetch all the hosts
828 if x[1].has_key("hostname") == 0:
831 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
834 DNSInfo = ExtractDNSInfo(x)
838 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
841 Line = "\t\t\t%s" % (Line)
845 # this would write sshfp lines for services on machines
846 # but we can't yet, since some are cnames and we'll make
847 # an invalid zonefile
849 # for i in x[1].get("purpose", []):
850 # m = PurposeHostField.match(i)
853 # # we ignore [[*..]] entries
854 # if m.startswith('*'):
856 # if m.startswith('-'):
859 # if not m.endswith(HostDomain):
861 # if not m.endswith('.'):
863 # for Line in DNSInfo:
864 # if isSSHFP.match(Line):
865 # Line = "%s\t%s" % (m, Line)
866 # F.write(Line + "\n")
868 # Oops, something unspeakable happened.
874 # Generate the BSMTP file
875 def GenBSMTP(File, HomePrefix):
878 F = open(File + ".tmp", "w")
880 # Fetch all the users
883 # Write out the zone file entry for each user
884 for x in PasswdAttrs:
885 if x[1].has_key("dnsZoneEntry") == 0:
888 # If the account has no PGP key, do not write it
889 if x[1].has_key("keyFingerPrint") == 0:
892 for z in x[1]["dnsZoneEntry"]:
893 Split = z.lower().split()
894 if Split[1].lower() == 'in':
895 for y in range(0, len(Split)):
898 Line = " ".join(Split) + "\n"
900 Host = Split[0] + DNSZone
901 if BSMTPCheck.match(Line) != None:
902 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
903 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
906 F.write("; Errors\n")
909 # Oops, something unspeakable happened.
915 def HostToIP(Host, mapped=True):
919 if Host[1].has_key("ipHostNumber"):
920 for addr in Host[1]["ipHostNumber"]:
921 IPAdresses.append(addr)
922 if IsV6Addr.match(addr) is None and mapped == "True":
923 IPAdresses.append("::ffff:"+addr)
927 # Generate the ssh known hosts file
928 def GenSSHKnown(File, mode=None):
931 OldMask = os.umask(0022)
932 F = open(File + ".tmp", "w", 0644)
938 if x[1].has_key("hostname") == 0 or \
939 x[1].has_key("sshRSAHostKey") == 0:
941 Host = GetAttr(x, "hostname")
943 if Host.endswith(HostDomain):
944 HostNames.append(Host[:-(len(HostDomain) + 1)])
946 # in the purpose field [[host|some other text]] (where some other text is optional)
947 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
948 # file. But so that we don't have to add everything we link we can add an asterisk
949 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
950 # http linking it we also support [[-hostname]] entries.
951 for i in x[1].get("purpose", []):
952 m = PurposeHostField.match(i)
955 # we ignore [[*..]] entries
956 if m.startswith('*'):
958 if m.startswith('-'):
962 if m.endswith(HostDomain):
963 HostNames.append(m[:-(len(HostDomain) + 1)])
965 for I in x[1]["sshRSAHostKey"]:
966 if mode and mode == 'authorized_keys':
968 if 'sshdistAuthKeysHost' in x[1]:
969 hosts += x[1]['sshdistAuthKeysHost']
970 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)
972 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
973 Line = Sanitize(Line) + "\n"
975 # Oops, something unspeakable happened.
981 # Generate the debianhosts file (list of all IP addresses)
985 OldMask = os.umask(0022)
986 F = open(File + ".tmp", "w", 0644)
995 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
998 if not 'ipHostNumber' in x[1]:
1001 addrs = x[1]["ipHostNumber"]
1003 if addr not in seen:
1005 addr = Sanitize(addr) + "\n"
1008 # Oops, something unspeakable happened.
1014 def GenKeyrings(OutDir):
1016 shutil.copy(k, OutDir)
1018 # Connect to the ldap server
1020 # for testing purposes it's sometimes useful to pass username/password
1021 # via the environment
1022 if 'UD_CREDENTIALS' in os.environ:
1023 Pass = os.environ['UD_CREDENTIALS'].split()
1025 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1026 Pass = F.readline().strip().split(" ")
1028 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1030 # Fetch all the groups
1032 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1033 ["gid", "gidNumber", "subGroup"])
1035 # Generate the SubGroupMap and GroupIDMap
1037 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1039 if x[1].has_key("gidNumber") == 0:
1041 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1042 if x[1].has_key("subGroup") != 0:
1043 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1045 # Fetch all the users
1046 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
1047 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1048 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1049 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1050 "shadowExpire", "emailForward", "latitude", "longitude",\
1051 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1052 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1053 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1054 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1055 "mailContentInspectionAction"])
1057 if PasswdAttrs is None:
1058 raise UDEmptyList, "No Users"
1060 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1062 # Fetch all the hosts
1063 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1064 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1065 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1067 if HostAttrs == None:
1068 raise UDEmptyList, "No Hosts"
1070 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1072 # override globaldir for testing
1073 if 'UD_GENERATEDIR' in os.environ:
1074 GenerateDir = os.environ['UD_GENERATEDIR']
1076 # Generate global things
1077 GlobalDir = GenerateDir + "/"
1078 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1080 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1081 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1085 GenMailDisable(GlobalDir + "mail-disable")
1086 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1087 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1088 GenPrivate(GlobalDir + "debian-private")
1089 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1090 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1091 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1092 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1093 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1094 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1095 GenKeyrings(GlobalDir)
1098 GenForward(GlobalDir + "forward-alias")
1100 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1102 SSHFiles = GenSSHShadow()
1103 GenMarkers(GlobalDir + "markers")
1104 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1105 GenHosts(GlobalDir + "debianhosts")
1107 for host in HostAttrs:
1108 if not "hostname" in host[1]:
1111 CurrentHost = host[1]['hostname'][0]
1112 OutDir = GenerateDir + '/' + CurrentHost + '/'
1118 # Get the group list and convert any named groups to numerics
1120 for groupname in AllowedGroupsPreload.strip().split(" "):
1121 GroupList[groupname] = True
1122 if 'allowedGroups' in host[1]:
1123 for groupname in host[1]['allowedGroups']:
1124 GroupList[groupname] = True
1125 for groupname in GroupList.keys():
1126 if groupname in GroupIDMap:
1127 GroupList[str(GroupIDMap[groupname])] = True
1130 if 'exportOptions' in host[1]:
1131 for extra in host[1]['exportOptions']:
1132 ExtraList[extra.upper()] = True
1138 DoLink(GlobalDir, OutDir, "debianhosts")
1139 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1140 DoLink(GlobalDir, OutDir, "disabled-accounts")
1143 if 'NOPASSWD' in ExtraList:
1144 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1146 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1148 grouprevmap = GenGroup(OutDir + "group")
1149 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1151 # Now we know who we're allowing on the machine, export
1152 # the relevant ssh keys
1153 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1155 if not 'NOPASSWD' in ExtraList:
1156 GenShadow(OutDir + "shadow")
1158 # Link in global things
1159 if not 'NOMARKERS' in ExtraList:
1160 DoLink(GlobalDir, OutDir, "markers")
1161 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1162 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1163 DoLink(GlobalDir, OutDir, "mail-disable")
1164 DoLink(GlobalDir, OutDir, "mail-greylist")
1165 DoLink(GlobalDir, OutDir, "mail-callout")
1166 DoLink(GlobalDir, OutDir, "mail-rbl")
1167 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1168 DoLink(GlobalDir, OutDir, "mail-whitelist")
1169 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1170 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1171 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1174 DoLink(GlobalDir, OutDir, "forward-alias")
1176 if 'DNS' in ExtraList:
1177 GenDNS(OutDir + "dns-zone")
1178 GenZoneRecords(OutDir + "dns-sshfp")
1180 if 'AUTHKEYS' in ExtraList:
1181 DoLink(GlobalDir, OutDir, "authorized_keys")
1183 if 'BSMTP' in ExtraList:
1184 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1186 if 'PRIVATE' in ExtraList:
1187 DoLink(GlobalDir, OutDir, "debian-private")
1189 if 'KEYRING' in ExtraList:
1191 DoLink(GlobalDir, OutDir, os.path.basename(k))
1195 posix.remove(OutDir + os.path.basename(k))
1201 # vim:set shiftwidth=3: