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 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>
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
31 from userdir_ldap import *
32 from userdir_exceptions import *
45 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
47 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
48 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
49 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
50 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
51 IsDebianHost = re.compile(ConfModule.dns_hostmatch)
52 DNSZone = ".debian.net"
53 Keyrings = ConfModule.sync_keyrings.split(":")
55 def safe_makedirs(dir):
59 if e.errno == errno.EEXIST:
68 if e.errno == errno.ENOENT:
74 return Str.translate(string.maketrans("\n\r\t", "$$$"))
76 def DoLink(From, To, File):
78 posix.remove(To + File)
81 posix.link(From + File, To + File)
83 def IsRetired(DnRecord):
85 Looks for accountStatus in the LDAP record and tries to
86 match it against one of the known retired statuses
89 status = GetAttr(DnRecord, "accountStatus", None)
96 if status == "inactive":
99 elif status == "memorial":
102 elif status == "retiring":
103 # We'll give them a few extra days over what we said
104 age = 6 * 31 * 24 * 60 * 60
106 return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
116 return int(GetAttr(x, "gidNumber", 0)) == 800
120 # See if this user is in the group list
121 def IsInGroup(DnRecord):
125 # See if the primary group is in the list
126 if Allowed.has_key(GetAttr(DnRecord, "gidNumber")) != 0:
129 # Check the host based ACL
130 if DnRecord[1].has_key("allowedHost") != 0:
131 if CurrentHost in DnRecord[1]["allowedHost"]:
134 # See if there are supplementary groups
135 if DnRecord[1].has_key("supplementaryGid") == 0:
139 addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord, "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(File, HomePrefix, PwdMarker):
171 F = open(File + ".tdb.tmp", "w")
174 # Fetch all the users
178 for x in PasswdAttrs:
179 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
182 # Do not let people try to buffer overflow some busted passwd parser.
183 if len(GetAttr(x, "gecos")) > 100 or len(GetAttr(x, "loginShell")) > 50:
186 userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
187 Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x, "uid"),\
189 GetAttr(x, "uidNumber"), GetAttr(x, "gidNumber"),\
190 GetAttr(x, "gecos"), HomePrefix, GetAttr(x, "uid"),\
191 GetAttr(x, "loginShell"))
193 Line = Sanitize(Line) + "\n"
194 F.write("0%u %s" % (I, Line))
195 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
196 F.write("=%s %s" % (GetAttr(x, "uidNumber"), Line))
199 # Oops, something unspeakable happened.
205 # Return the list of users so we know which keys to export
208 # Generate the shadow list
212 OldMask = os.umask(0077)
213 F = open(File + ".tdb.tmp", "w", 0600)
216 # Fetch all the users
220 for x in PasswdAttrs:
221 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
224 Pass = GetAttr(x, "userPassword")
225 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
230 # If the account is locked, mark it as such in shadow
231 # See Debian Bug #308229 for why we set it to 1 instead of 0
232 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
233 or GetAttr(x, "userPassword").startswith("!"):
236 ShadowExpire = GetAttr(x, "shadowExpire")
238 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
239 Pass, GetAttr(x, "shadowLastChange"),\
240 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
241 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
243 Line = Sanitize(Line) + "\n"
244 F.write("0%u %s" % (I, Line))
245 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
248 # Oops, something unspeakable happened.
254 # Generate the sudo passwd file
255 def GenShadowSudo(File, untrusted):
258 OldMask = os.umask(0077)
259 F = open(File + ".tmp", "w", 0600)
262 # Fetch all the users
265 for x in PasswdAttrs:
267 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
270 if x[1].has_key('sudoPassword'):
271 for entry in x[1]['sudoPassword']:
272 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
275 uuid = Match.group(1)
276 status = Match.group(2)
277 hosts = Match.group(3)
278 cryptedpass = Match.group(4)
280 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
282 for_all = hosts == "*"
283 for_this_host = CurrentHost in hosts.split(',')
284 if not (for_all or for_this_host):
286 # ignore * passwords for untrusted hosts, but copy host specific passwords
287 if for_all and untrusted:
290 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
295 Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
296 Line = Sanitize(Line) + "\n"
297 F.write("%s" % (Line))
299 # Oops, something unspeakable happened.
305 # Generate the shadow list
307 # Fetch all the users
312 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
313 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
315 for x in PasswdAttrs:
317 if x[1].has_key("uidNumber") == 0 or \
318 x[1].has_key("sshRSAAuthKey") == 0:
321 User = GetAttr(x, "uid")
325 OldMask = os.umask(0077)
326 File = os.path.join(GlobalDir, 'userkeys', User)
327 F = open(File + ".tmp", "w", 0600)
330 for I in x[1]["sshRSAAuthKey"]:
331 MultipleLine = "%s" % I
332 MultipleLine = Sanitize(MultipleLine) + "\n"
333 F.write(MultipleLine)
336 userfiles.append(os.path.basename(File))
338 # Oops, something unspeakable happened.
341 Die(masterFileName, masterFile, None)
346 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
347 OldMask = os.umask(0077)
348 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
350 for f in userlist.keys():
351 if f not in SSHFiles:
353 # If we're not exporting their primary group, don't export
356 if userlist[f] in grouprevmap.keys():
357 grname = grouprevmap[userlist[f]]
360 if int(userlist[f]) <= 100:
361 # In these cases, look it up in the normal way so we
362 # deal with cases where, for instance, users are in group
363 # users as their primary group.
364 grname = grp.getgrgid(userlist[f])[0]
369 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])
372 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
373 # These will only be used where the username doesn't
374 # exist on the target system for some reason; hence,
375 # in those cases, the safest thing is for the file to
376 # be owned by root but group nobody. This deals with
377 # the bloody obscure case where the group fails to exist
378 # whilst the user does (in which case we want to avoid
379 # ending up with a file which is owned user:root to avoid
380 # a fairly obvious attack vector)
383 # Using the username / groupname fields avoids any need
384 # to give a shit^W^W^Wcare about the UIDoffset stuff.
388 tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
391 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
393 # add a list of groups to existing groups,
394 # including all subgroups thereof, recursively.
395 # basically this proceduces the transitive hull of the groups in
397 def addGroups(existingGroups, newGroups, uid):
398 for group in newGroups:
399 # if it's a <group>@host, split it and verify it's on the current host.
400 s = group.split('@', 1)
401 if len(s) == 2 and s[1] != CurrentHost:
405 # let's see if we handled this group already
406 if group in existingGroups:
409 if not GroupIDMap.has_key(group):
410 print "Group", group, "does not exist but", uid, "is in it"
413 existingGroups.append(group)
415 if SubGroupMap.has_key(group):
416 addGroups(existingGroups, SubGroupMap[group], uid)
418 # Generate the group list
423 F = open(File + ".tdb.tmp", "w")
425 # Generate the GroupMap
427 for x in GroupIDMap.keys():
429 GroupHasPrimaryMembers = {}
431 # Fetch all the users
434 # Sort them into a list of groups having a set of users
435 for x in PasswdAttrs:
436 uid = GetAttr(x, "uid")
437 if 'gidNumber' in x[1]:
438 GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
439 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
441 if x[1].has_key("supplementaryGid") == 0:
445 addGroups(supgroups, x[1]["supplementaryGid"], uid)
447 GroupMap[g].append(uid)
449 # Output the group file.
451 for x in GroupMap.keys():
452 if GroupIDMap.has_key(x) == 0:
455 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
458 grouprevmap[GroupIDMap[x]] = x
460 Line = "%s:x:%u:" % (x, GroupIDMap[x])
462 for I in GroupMap[x]:
463 Line = Line + ("%s%s" % (Comma, I))
465 Line = Sanitize(Line) + "\n"
466 F.write("0%u %s" % (J, Line))
467 F.write(".%s %s" % (x, Line))
468 F.write("=%u %s" % (GroupIDMap[x], Line))
471 # Oops, something unspeakable happened.
481 for x in PasswdAttrs:
482 if x[1].has_key("emailForward") == 0:
486 x[1].pop("emailForward")
489 # Do not allow people to try to buffer overflow busted parsers
490 if len(GetAttr(x, "emailForward")) > 200:
491 x[1].pop("emailForward")
494 # Check the forwarding address
495 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
496 x[1].pop("emailForward")
498 # Generate the email forwarding list
499 def GenForward(File):
502 OldMask = os.umask(0022)
503 F = open(File + ".tmp", "w", 0644)
506 # Fetch all the users
509 # Write out the email address for each user
510 for x in PasswdAttrs:
511 if x[1].has_key("emailForward") == 0:
514 Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
515 Line = Sanitize(Line) + "\n"
518 # Oops, something unspeakable happened.
524 def GenCDB(File, Users, Key):
527 OldMask = os.umask(0022)
528 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
531 # Write out the email address for each user
535 Value = GetAttr(x, Key)
536 User = GetAttr(x, "uid")
537 Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
540 # Oops, something unspeakable happened.
544 if Fdb.close() != None:
545 raise "cdbmake gave an error"
547 # Generate the anon XEarth marker file
548 def GenMarkers(File):
551 F = open(File + ".tmp", "w")
553 # Fetch all the users
556 # Write out the position for each user
557 for x in PasswdAttrs:
558 if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
561 Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
562 Line = Sanitize(Line) + "\n"
567 # Oops, something unspeakable happened.
573 # Generate the debian-private subscription list
574 def GenPrivate(File):
577 F = open(File + ".tmp", "w")
579 # Fetch all the users
582 # Write out the position for each user
583 for x in DebianDDUsers:
584 if x[1].has_key("privateSub") == 0:
587 # If the account has no PGP key, do not write it
588 if x[1].has_key("keyFingerPrint") == 0:
592 Line = "%s"%(GetAttr(x, "privateSub"))
593 Line = Sanitize(Line) + "\n"
598 # Oops, something unspeakable happened.
604 # Generate a list of locked accounts
605 def GenDisabledAccounts(File):
608 F = open(File + ".tmp", "w")
610 # Fetch all the users
615 for x in PasswdAttrs:
616 if x[1].has_key("uidNumber") == 0:
619 Pass = GetAttr(x, "userPassword")
621 # *LK* is the reference value for a locked account
622 # password starting with ! is also a locked account
623 if Pass.find("*LK*") != -1 or Pass.startswith("!"):
624 # Format is <login>:<reason>
625 Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
626 DisabledUsers.append(x)
629 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:
650 if x[1].has_key("mailDisableMessage"):
651 Reason = GetAttr(x, "mailDisableMessage")
656 Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
657 Line = Sanitize(Line) + "\n"
662 # Oops, something unspeakable happened.
668 # Generate a list of uids that should have boolean affects applied
669 def GenMailBool(File, Key):
672 F = open(File + ".tmp", "w")
674 # Fetch all the users
677 for x in PasswdAttrs:
680 if x[1].has_key(Key) == 0:
683 if GetAttr(x, Key) != "TRUE":
687 Line = "%s"%(GetAttr(x, "uid"))
688 Line = Sanitize(Line) + "\n"
693 # Oops, something unspeakable happened.
699 # Generate a list of hosts for RBL or whitelist purposes.
700 def GenMailList(File, Key):
703 F = open(File + ".tmp", "w")
705 # Fetch all the users
708 for x in PasswdAttrs:
711 if x[1].has_key(Key) == 0:
718 if Key == "mailWhitelist":
719 if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
722 if re.match('^[-\w.]+$', z) == None:
726 Line = GetAttr(x, "uid")
730 if Key == "mailRHSBL":
731 Line += "/$sender_address_domain"
734 Line = Sanitize(Line) + "\n"
739 # Oops, something unspeakable happened.
745 def isRoleAccount(pwEntry):
746 if not pwEntry.has_key("objectClass"):
747 raise "pwEntry has no objectClass"
748 oc = pwEntry['objectClass']
750 i = oc.index('debianRoleAccount')
755 # Generate the DNS Zone file
759 F = open(File + ".tmp", "w")
763 # for x in HostAttrs:
764 # if x[1].has_key("hostname") == 0 or \
765 # x[1].has_key("architecture") == 0 or\
766 # x[1].has_key("sshRSAHostKey") == 0:
769 # if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
772 # DNSInfo = ExtractDNSInfo(x)
774 # for Line in DNSInfo:
776 # Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
779 # Line = "\t\t\t%s" % (Line)
780 # F.write(Line + "\n")
782 # Fetch all the users
785 # Write out the zone file entry for each user
786 for x in PasswdAttrs:
787 if x[1].has_key("dnsZoneEntry") == 0:
790 # If the account has no PGP key, do not write it
791 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
794 F.write("; %s\n"%(EmailAddress(x)))
795 for z in x[1]["dnsZoneEntry"]:
796 Split = z.lower().split()
797 if Split[1].lower() == 'in':
798 for y in range(0, len(Split)):
801 Line = " ".join(Split) + "\n"
804 Host = Split[0] + DNSZone
805 if BSMTPCheck.match(Line) != None:
806 F.write("; Has BSMTP\n")
808 # Write some identification information
809 if Split[2].lower() == "a":
810 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
811 for y in x[1]["keyFingerPrint"]:
812 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
815 Line = "; Err %s"%(str(Split))
820 F.write("; Errors\n")
823 # Oops, something unspeakable happened.
829 def ExtractDNSInfo(x):
833 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
836 if x[1].has_key("ipHostNumber"):
837 for I in x[1]["ipHostNumber"]:
838 if IsV6Addr.match(I) != None:
839 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
841 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
845 if 'sshRSAHostKey' in x[1]:
846 for I in x[1]["sshRSAHostKey"]:
848 if Split[0] == 'ssh-rsa':
850 if Split[0] == 'ssh-dss':
852 if Algorithm == None:
854 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
855 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
857 if 'architecture' in x[1]:
858 Arch = GetAttr(x, "architecture")
860 if x[1].has_key("machine"):
861 Mach = " " + GetAttr(x, "machine")
862 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
864 if x[1].has_key("mXRecord"):
865 for I in x[1]["mXRecord"]:
866 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
870 # Generate the DNS records
871 def GenZoneRecords(File):
874 F = open(File + ".tmp", "w")
876 # Fetch all the hosts
880 if x[1].has_key("hostname") == 0:
883 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
886 DNSInfo = ExtractDNSInfo(x)
890 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
893 Line = "\t\t\t%s" % (Line)
897 # Oops, something unspeakable happened.
903 # Generate the BSMTP file
904 def GenBSMTP(File, HomePrefix):
907 F = open(File + ".tmp", "w")
909 # Fetch all the users
912 # Write out the zone file entry for each user
913 for x in PasswdAttrs:
914 if x[1].has_key("dnsZoneEntry") == 0:
917 # If the account has no PGP key, do not write it
918 if x[1].has_key("keyFingerPrint") == 0:
921 for z in x[1]["dnsZoneEntry"]:
922 Split = z.lower().split()
923 if Split[1].lower() == 'in':
924 for y in range(0, len(Split)):
927 Line = " ".join(Split) + "\n"
929 Host = Split[0] + DNSZone
930 if BSMTPCheck.match(Line) != None:
931 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
932 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
935 F.write("; Errors\n")
938 # Oops, something unspeakable happened.
944 def HostToIP(Host, mapped=True):
948 if Host[1].has_key("ipHostNumber"):
949 for addr in Host[1]["ipHostNumber"]:
950 IPAdresses.append(addr)
951 if IsV6Addr.match(addr) is None and mapped == "True":
952 IPAdresses.append("::ffff:"+addr)
956 # Generate the ssh known hosts file
957 def GenSSHKnown(File, mode=None):
960 OldMask = os.umask(0022)
961 F = open(File + ".tmp", "w", 0644)
967 if x[1].has_key("hostname") == 0 or \
968 x[1].has_key("sshRSAHostKey") == 0:
970 Host = GetAttr(x, "hostname")
972 if Host.endswith(HostDomain):
973 HostNames.append(Host[:-(len(HostDomain) + 1)])
975 # in the purpose field [[host|some other text]] (where some other text is optional)
976 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
977 # file. But so that we don't have to add everything we link we can add an asterisk
978 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
979 # http linking it we also support [[-hostname]] entries.
980 for i in x[1].get("purpose", []):
981 m = PurposeHostField.match(i)
984 # we ignore [[*..]] entries
985 if m.startswith('*'):
987 if m.startswith('-'):
991 if m.endswith(HostDomain):
992 HostNames.append(m[:-(len(HostDomain) + 1)])
994 for I in x[1]["sshRSAHostKey"]:
995 if mode and mode == 'authorized_keys':
997 if 'sshdistAuthKeysHost' in x[1]:
998 hosts += x[1]['sshdistAuthKeysHost']
999 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)
1000 #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
1002 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
1003 Line = Sanitize(Line) + "\n"
1005 # Oops, something unspeakable happened.
1011 # Generate the debianhosts file (list of all IP addresses)
1015 OldMask = os.umask(0022)
1016 F = open(File + ".tmp", "w", 0644)
1025 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1028 if not 'ipHostNumber' in x[1]:
1031 addrs = x[1]["ipHostNumber"]
1033 if addr not in seen:
1035 addr = Sanitize(addr) + "\n"
1038 # Oops, something unspeakable happened.
1044 def GenKeyrings(OutDir):
1046 shutil.copy(k, OutDir)
1048 # Connect to the ldap server
1050 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1051 Pass = F.readline().strip().split(" ")
1053 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1055 # Fetch all the groups
1057 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1058 ["gid", "gidNumber", "subGroup"])
1060 # Generate the SubGroupMap and GroupIDMap
1062 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1064 if x[1].has_key("gidNumber") == 0:
1066 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1067 if x[1].has_key("subGroup") != 0:
1068 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1070 # Fetch all the users
1071 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
1072 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1073 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1074 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1075 "shadowExpire", "emailForward", "latitude", "longitude",\
1076 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1077 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1078 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1079 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1080 "mailContentInspectionAction"])
1082 if PasswdAttrs is None:
1083 raise UDEmptyList, "No Users"
1085 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1087 # Fetch all the hosts
1088 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1089 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1090 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1092 if HostAttrs == None:
1093 raise UDEmptyList, "No Hosts"
1095 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1097 # Generate global things
1098 GlobalDir = GenerateDir + "/"
1099 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1101 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1102 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1106 GenMailDisable(GlobalDir + "mail-disable")
1107 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1108 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1109 GenPrivate(GlobalDir + "debian-private")
1110 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1111 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1112 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1113 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1114 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1115 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1116 GenKeyrings(GlobalDir)
1119 GenForward(GlobalDir + "forward-alias")
1121 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1123 SSHFiles = GenSSHShadow()
1124 GenMarkers(GlobalDir + "markers")
1125 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1126 GenHosts(GlobalDir + "debianhosts")
1128 for host in HostAttrs:
1129 if not "hostname" in host[1]:
1132 CurrentHost = host[1]['hostname'][0]
1133 OutDir = GenerateDir + '/' + CurrentHost + '/'
1139 # Get the group list and convert any named groups to numerics
1141 for groupname in AllowedGroupsPreload.strip().split(" "):
1142 GroupList[groupname] = True
1143 if 'allowedGroups' in host[1]:
1144 for groupname in host[1]['allowedGroups']:
1145 GroupList[groupname] = True
1146 for groupname in GroupList.keys():
1147 if groupname in GroupIDMap:
1148 GroupList[str(GroupIDMap[groupname])] = True
1151 if 'exportOptions' in host[1]:
1152 for extra in host[1]['exportOptions']:
1153 ExtraList[extra.upper()] = True
1159 DoLink(GlobalDir, OutDir, "debianhosts")
1160 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1161 DoLink(GlobalDir, OutDir, "disabled-accounts")
1164 if 'NOPASSWD' in ExtraList:
1165 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1167 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1169 grouprevmap = GenGroup(OutDir + "group")
1170 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1172 # Now we know who we're allowing on the machine, export
1173 # the relevant ssh keys
1174 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1176 if not 'NOPASSWD' in ExtraList:
1177 GenShadow(OutDir + "shadow")
1179 # Link in global things
1180 if not 'NOMARKERS' in ExtraList:
1181 DoLink(GlobalDir, OutDir, "markers")
1182 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1183 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1184 DoLink(GlobalDir, OutDir, "mail-disable")
1185 DoLink(GlobalDir, OutDir, "mail-greylist")
1186 DoLink(GlobalDir, OutDir, "mail-callout")
1187 DoLink(GlobalDir, OutDir, "mail-rbl")
1188 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1189 DoLink(GlobalDir, OutDir, "mail-whitelist")
1190 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1191 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1192 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1195 DoLink(GlobalDir, OutDir, "forward-alias")
1197 if 'DNS' in ExtraList:
1198 GenDNS(OutDir + "dns-zone")
1199 GenZoneRecords(OutDir + "dns-sshfp")
1201 if 'AUTHKEYS' in ExtraList:
1202 DoLink(GlobalDir, OutDir, "authorized_keys")
1204 if 'BSMTP' in ExtraList:
1205 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1207 if 'PRIVATE' in ExtraList:
1208 DoLink(GlobalDir, OutDir, "debian-private")
1210 if 'KEYRING' in ExtraList:
1212 DoLink(GlobalDir, OutDir, os.path.basename(k))
1216 posix.remove(OutDir + os.path.basename(k))
1222 # vim:set shiftwidth=3: