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 if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
235 Pass = GetAttr(x, "userPassword")
236 if Pass[0:7] != "{crypt}" or len(Pass) > 50:
241 # If the account is locked, mark it as such in shadow
242 # See Debian Bug #308229 for why we set it to 1 instead of 0
243 if (GetAttr(x, "userPassword").find("*LK*") != -1) \
244 or GetAttr(x, "userPassword").startswith("!"):
247 ShadowExpire = GetAttr(x, "shadowExpire")
249 Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
250 Pass, GetAttr(x, "shadowLastChange"),\
251 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
252 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
254 Line = Sanitize(Line) + "\n"
255 F.write("0%u %s" % (I, Line))
256 F.write(".%s %s" % (GetAttr(x, "uid"), Line))
259 # Oops, something unspeakable happened.
265 # Generate the sudo passwd file
266 def GenShadowSudo(File, untrusted):
269 OldMask = os.umask(0077)
270 F = open(File + ".tmp", "w", 0600)
273 # Fetch all the users
276 for x in PasswdAttrs:
277 a = UDLdap.Account(x[0], x[1])
279 if not IsInGroup(x): continue
281 if 'sudoPassword' in a:
282 for entry in a['sudoPassword']:
283 Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
286 uuid = Match.group(1)
287 status = Match.group(2)
288 hosts = Match.group(3)
289 cryptedpass = Match.group(4)
291 if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass):
293 for_all = hosts == "*"
294 for_this_host = CurrentHost in hosts.split(',')
295 if not (for_all or for_this_host):
297 # ignore * passwords for untrusted hosts, but copy host specific passwords
298 if for_all and untrusted:
301 if for_this_host: # this makes sure we take a per-host entry over the for-all entry
306 Line = "%s:%s" % (a['uid'], Pass)
307 Line = Sanitize(Line) + "\n"
308 F.write("%s" % (Line))
310 # Oops, something unspeakable happened.
316 # Generate the shadow list
318 # Fetch all the users
323 safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
324 safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
326 for x in PasswdAttrs:
327 a = UDLdap.Account(x[0], x[1])
328 if not 'sshRSAAuthKey' in a: continue
332 OldMask = os.umask(0077)
333 File = os.path.join(GlobalDir, 'userkeys', a['uid'])
334 F = open(File + ".tmp", "w", 0600)
337 for I in a['sshRSAAuthKey']:
338 MultipleLine = "%s" % I
339 MultipleLine = Sanitize(MultipleLine) + "\n"
340 F.write(MultipleLine)
343 userfiles.append(os.path.basename(File))
345 # Oops, something unspeakable happened.
348 # As neither masterFileName nor masterFile are defined at any point
349 # this will raise a NameError.
350 Die(masterFileName, masterFile, None)
355 def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
356 OldMask = os.umask(0077)
357 tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
359 for f in userlist.keys():
360 if f not in SSHFiles:
362 # If we're not exporting their primary group, don't export
365 if userlist[f] in grouprevmap.keys():
366 grname = grouprevmap[userlist[f]]
369 if int(userlist[f]) <= 100:
370 # In these cases, look it up in the normal way so we
371 # deal with cases where, for instance, users are in group
372 # users as their primary group.
373 grname = grp.getgrgid(userlist[f])[0]
378 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])
381 to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
382 # These will only be used where the username doesn't
383 # exist on the target system for some reason; hence,
384 # in those cases, the safest thing is for the file to
385 # be owned by root but group nobody. This deals with
386 # the bloody obscure case where the group fails to exist
387 # whilst the user does (in which case we want to avoid
388 # ending up with a file which is owned user:root to avoid
389 # a fairly obvious attack vector)
392 # Using the username / groupname fields avoids any need
393 # to give a shit^W^W^Wcare about the UIDoffset stuff.
398 contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
400 for line in contents.splitlines():
401 if line.startswith("allowed_hosts=") and ' ' in line:
402 machines, line = line.split('=', 1)[1].split(' ', 1)
403 if CurrentHost not in machines.split(','):
404 continue # skip this key
407 continue # no keys for this host
408 contents = "\n".join(lines) + "\n"
409 to.size = len(contents)
410 tf.addfile(to, StringIO(contents))
413 os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
415 # add a list of groups to existing groups,
416 # including all subgroups thereof, recursively.
417 # basically this proceduces the transitive hull of the groups in
419 def addGroups(existingGroups, newGroups, uid):
420 for group in newGroups:
421 # if it's a <group>@host, split it and verify it's on the current host.
422 s = group.split('@', 1)
423 if len(s) == 2 and s[1] != CurrentHost:
427 # let's see if we handled this group already
428 if group in existingGroups:
431 if not GroupIDMap.has_key(group):
432 print "Group", group, "does not exist but", uid, "is in it"
435 existingGroups.append(group)
437 if SubGroupMap.has_key(group):
438 addGroups(existingGroups, SubGroupMap[group], uid)
440 # Generate the group list
445 F = open(File + ".tdb.tmp", "w")
447 # Generate the GroupMap
449 for x in GroupIDMap.keys():
451 GroupHasPrimaryMembers = {}
453 # Fetch all the users
456 # Sort them into a list of groups having a set of users
457 for x in PasswdAttrs:
458 a = UDLdap.Account(x[0], x[1])
459 GroupHasPrimaryMembers[ a['gidNumber'] ] = True
460 if not IsInGroup(x): continue
461 if not 'supplementaryGid' in a: continue
464 addGroups(supgroups, a['supplementaryGid'], a['uid'])
466 GroupMap[g].append(a['uid'])
468 # Output the group file.
470 for x in GroupMap.keys():
471 if GroupIDMap.has_key(x) == 0:
474 if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
477 grouprevmap[GroupIDMap[x]] = x
479 Line = "%s:x:%u:" % (x, GroupIDMap[x])
481 for I in GroupMap[x]:
482 Line = Line + ("%s%s" % (Comma, I))
484 Line = Sanitize(Line) + "\n"
485 F.write("0%u %s" % (J, Line))
486 F.write(".%s %s" % (x, Line))
487 F.write("=%u %s" % (GroupIDMap[x], Line))
490 # Oops, something unspeakable happened.
500 for x in PasswdAttrs:
501 if x[1].has_key("emailForward") == 0:
505 x[1].pop("emailForward")
508 # Do not allow people to try to buffer overflow busted parsers
509 if len(GetAttr(x, "emailForward")) > 200:
510 x[1].pop("emailForward")
513 # Check the forwarding address
514 if EmailCheck.match(GetAttr(x, "emailForward")) == None:
515 x[1].pop("emailForward")
517 # Generate the email forwarding list
518 def GenForward(File):
521 OldMask = os.umask(0022)
522 F = open(File + ".tmp", "w", 0644)
525 # Fetch all the users
528 # Write out the email address for each user
529 for x in PasswdAttrs:
530 a = UDLdap.Account(x[0], x[1])
531 if not 'emailForward' in a: continue
532 Line = "%s: %s" % (a['uid'], a['emailForward'])
533 Line = Sanitize(Line) + "\n"
536 # Oops, something unspeakable happened.
542 def GenCDB(File, Users, key):
545 OldMask = os.umask(0022)
546 Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
549 # Write out the email address for each user
551 a = UDLdap.Account(x[0], x[1])
552 if not key in a: continue
555 Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
558 # Oops, something unspeakable happened.
562 if Fdb.close() != None:
563 raise "cdbmake gave an error"
565 # Generate the anon XEarth marker file
566 def GenMarkers(File):
569 F = open(File + ".tmp", "w")
571 # Fetch all the users
574 # Write out the position for each user
575 for x in PasswdAttrs:
576 a = UDLdap.Account(x[0], x[1])
577 if not ('latitude' in a and 'longitude' in a): continue
579 Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
580 Line = Sanitize(Line) + "\n"
585 # Oops, something unspeakable happened.
591 # Generate the debian-private subscription list
592 def GenPrivate(File):
595 F = open(File + ".tmp", "w")
597 # Fetch all the users
600 # Write out the position for each user
601 for x in DebianDDUsers:
602 a = UDLdap.Account(x[0], x[1])
603 if not a.is_active_user(): continue
604 if not 'privateSub' in a: continue
606 Line = "%s"%(a['privateSub'])
607 Line = Sanitize(Line) + "\n"
612 # Oops, something unspeakable happened.
618 # Generate a list of locked accounts
619 def GenDisabledAccounts(File):
622 F = open(File + ".tmp", "w")
624 # Fetch all the users
629 for x in PasswdAttrs:
630 a = UDLdap.Account(x[0], x[1])
631 if a.pw_active(): continue
632 Line = "%s:%s" % (a['uid'], "Account is locked")
633 DisabledUsers.append(x)
634 F.write(Sanitize(Line) + "\n")
636 # Oops, something unspeakable happened.
642 # Generate the list of local addresses that refuse all mail
643 def GenMailDisable(File):
646 F = open(File + ".tmp", "w")
648 # Fetch all the users
651 for x in PasswdAttrs:
652 a = UDLdap.Account(x[0], x[1])
653 if not 'mailDisableMessage' in a: continue
654 Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
655 Line = Sanitize(Line) + "\n"
658 # Oops, something unspeakable happened.
664 # Generate a list of uids that should have boolean affects applied
665 def GenMailBool(File, key):
668 F = open(File + ".tmp", "w")
670 # Fetch all the users
673 for x in PasswdAttrs:
674 a = UDLdap.Account(x[0], x[1])
675 if not key in a: continue
676 if not a[key] == 'TRUE': continue
677 Line = "%s"%(a['uid'])
678 Line = Sanitize(Line) + "\n"
681 # Oops, something unspeakable happened.
687 # Generate a list of hosts for RBL or whitelist purposes.
688 def GenMailList(File, key):
691 F = open(File + ".tmp", "w")
693 # Fetch all the users
696 if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
697 else: validregex = re.compile('^[-\w.]+$')
699 for x in PasswdAttrs:
700 a = UDLdap.Account(x[0], x[1])
701 if not key in a: continue
703 filtered = filter(lambda z: validregex.match(z), a[key])
704 if len(filtered) == 0: continue
705 if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
706 line = a['uid'] + ': ' + ' : '.join(filtered)
707 line = Sanitize(line) + "\n"
710 # Oops, something unspeakable happened.
716 def isRoleAccount(pwEntry):
717 if not pwEntry.has_key("objectClass"):
718 raise "pwEntry has no objectClass"
719 oc = pwEntry['objectClass']
721 i = oc.index('debianRoleAccount')
726 # Generate the DNS Zone file
730 F = open(File + ".tmp", "w")
732 # Fetch all the users
736 # Write out the zone file entry for each user
737 for x in PasswdAttrs:
738 if x[1].has_key("dnsZoneEntry") == 0:
741 # If the account has no PGP key, do not write it
742 if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
745 F.write("; %s\n"%(EmailAddress(x)))
746 for z in x[1]["dnsZoneEntry"]:
747 Split = z.lower().split()
748 if Split[1].lower() == 'in':
749 for y in range(0, len(Split)):
752 Line = " ".join(Split) + "\n"
755 Host = Split[0] + DNSZone
756 if BSMTPCheck.match(Line) != None:
757 F.write("; Has BSMTP\n")
759 # Write some identification information
760 if not RRs.has_key(Host):
761 if Split[2].lower() in ["a", "aaaa"]:
762 Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
763 for y in x[1]["keyFingerPrint"]:
764 Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
768 Line = "; Err %s"%(str(Split))
773 F.write("; Errors\n")
776 # Oops, something unspeakable happened.
782 def ExtractDNSInfo(x):
786 TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
789 if x[1].has_key("ipHostNumber"):
790 for I in x[1]["ipHostNumber"]:
791 if IsV6Addr.match(I) != None:
792 DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
794 DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
798 if 'sshRSAHostKey' in x[1]:
799 for I in x[1]["sshRSAHostKey"]:
801 if Split[0] == 'ssh-rsa':
803 if Split[0] == 'ssh-dss':
805 if Algorithm == None:
807 Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
808 DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
810 if 'architecture' in x[1]:
811 Arch = GetAttr(x, "architecture")
813 if x[1].has_key("machine"):
814 Mach = " " + GetAttr(x, "machine")
815 DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
817 if x[1].has_key("mXRecord"):
818 for I in x[1]["mXRecord"]:
819 DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
823 # Generate the DNS records
824 def GenZoneRecords(File):
827 F = open(File + ".tmp", "w")
829 # Fetch all the hosts
833 if x[1].has_key("hostname") == 0:
836 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
839 DNSInfo = ExtractDNSInfo(x)
843 Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
846 Line = "\t\t\t%s" % (Line)
850 # this would write sshfp lines for services on machines
851 # but we can't yet, since some are cnames and we'll make
852 # an invalid zonefile
854 # for i in x[1].get("purpose", []):
855 # m = PurposeHostField.match(i)
858 # # we ignore [[*..]] entries
859 # if m.startswith('*'):
861 # if m.startswith('-'):
864 # if not m.endswith(HostDomain):
866 # if not m.endswith('.'):
868 # for Line in DNSInfo:
869 # if isSSHFP.match(Line):
870 # Line = "%s\t%s" % (m, Line)
871 # F.write(Line + "\n")
873 # Oops, something unspeakable happened.
879 # Generate the BSMTP file
880 def GenBSMTP(File, HomePrefix):
883 F = open(File + ".tmp", "w")
885 # Fetch all the users
888 # Write out the zone file entry for each user
889 for x in PasswdAttrs:
890 if x[1].has_key("dnsZoneEntry") == 0:
893 # If the account has no PGP key, do not write it
894 if x[1].has_key("keyFingerPrint") == 0:
897 for z in x[1]["dnsZoneEntry"]:
898 Split = z.lower().split()
899 if Split[1].lower() == 'in':
900 for y in range(0, len(Split)):
903 Line = " ".join(Split) + "\n"
905 Host = Split[0] + DNSZone
906 if BSMTPCheck.match(Line) != None:
907 F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
908 GetAttr(x, "uid"), HomePrefix, GetAttr(x, "uid"), Host))
911 F.write("; Errors\n")
914 # Oops, something unspeakable happened.
920 def HostToIP(Host, mapped=True):
924 if Host[1].has_key("ipHostNumber"):
925 for addr in Host[1]["ipHostNumber"]:
926 IPAdresses.append(addr)
927 if IsV6Addr.match(addr) is None and mapped == "True":
928 IPAdresses.append("::ffff:"+addr)
932 # Generate the ssh known hosts file
933 def GenSSHKnown(File, mode=None):
936 OldMask = os.umask(0022)
937 F = open(File + ".tmp", "w", 0644)
943 if x[1].has_key("hostname") == 0 or \
944 x[1].has_key("sshRSAHostKey") == 0:
946 Host = GetAttr(x, "hostname")
948 if Host.endswith(HostDomain):
949 HostNames.append(Host[:-(len(HostDomain) + 1)])
951 # in the purpose field [[host|some other text]] (where some other text is optional)
952 # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
953 # file. But so that we don't have to add everything we link we can add an asterisk
954 # and say [[*... to ignore it. In order to be able to add stuff to ssh without
955 # http linking it we also support [[-hostname]] entries.
956 for i in x[1].get("purpose", []):
957 m = PurposeHostField.match(i)
960 # we ignore [[*..]] entries
961 if m.startswith('*'):
963 if m.startswith('-'):
967 if m.endswith(HostDomain):
968 HostNames.append(m[:-(len(HostDomain) + 1)])
970 for I in x[1]["sshRSAHostKey"]:
971 if mode and mode == 'authorized_keys':
973 if 'sshdistAuthKeysHost' in x[1]:
974 hosts += x[1]['sshdistAuthKeysHost']
975 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)
977 Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
978 Line = Sanitize(Line) + "\n"
980 # Oops, something unspeakable happened.
986 # Generate the debianhosts file (list of all IP addresses)
990 OldMask = os.umask(0022)
991 F = open(File + ".tmp", "w", 0644)
1000 if IsDebianHost.match(GetAttr(x, "hostname")) is None:
1003 if not 'ipHostNumber' in x[1]:
1006 addrs = x[1]["ipHostNumber"]
1008 if addr not in seen:
1010 addr = Sanitize(addr) + "\n"
1013 # Oops, something unspeakable happened.
1019 def GenKeyrings(OutDir):
1021 shutil.copy(k, OutDir)
1023 # Connect to the ldap server
1025 # for testing purposes it's sometimes useful to pass username/password
1026 # via the environment
1027 if 'UD_CREDENTIALS' in os.environ:
1028 Pass = os.environ['UD_CREDENTIALS'].split()
1030 F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
1031 Pass = F.readline().strip().split(" ")
1033 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
1035 # Fetch all the groups
1037 Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
1038 ["gid", "gidNumber", "subGroup"])
1040 # Generate the SubGroupMap and GroupIDMap
1042 if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
1044 if x[1].has_key("gidNumber") == 0:
1046 GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
1047 if x[1].has_key("subGroup") != 0:
1048 SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
1050 # Fetch all the users
1051 PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
1052 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
1053 "gecos", "loginShell", "userPassword", "shadowLastChange",\
1054 "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
1055 "shadowExpire", "emailForward", "latitude", "longitude",\
1056 "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
1057 "keyFingerPrint", "privateSub", "mailDisableMessage",\
1058 "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
1059 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
1060 "mailContentInspectionAction"])
1062 if PasswdAttrs is None:
1063 raise UDEmptyList, "No Users"
1065 PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
1067 # Fetch all the hosts
1068 HostAttrs = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
1069 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
1070 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
1072 if HostAttrs == None:
1073 raise UDEmptyList, "No Hosts"
1075 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
1077 # override globaldir for testing
1078 if 'UD_GENERATEDIR' in os.environ:
1079 GenerateDir = os.environ['UD_GENERATEDIR']
1081 # Generate global things
1082 GlobalDir = GenerateDir + "/"
1083 GenDisabledAccounts(GlobalDir + "disabled-accounts")
1085 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
1086 DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
1090 GenMailDisable(GlobalDir + "mail-disable")
1091 GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
1092 GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
1093 GenPrivate(GlobalDir + "debian-private")
1094 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
1095 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
1096 GenMailBool(GlobalDir + "mail-callout", "mailCallout")
1097 GenMailList(GlobalDir + "mail-rbl", "mailRBL")
1098 GenMailList(GlobalDir + "mail-rhsbl", "mailRHSBL")
1099 GenMailList(GlobalDir + "mail-whitelist", "mailWhitelist")
1100 GenKeyrings(GlobalDir)
1103 GenForward(GlobalDir + "forward-alias")
1105 PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
1107 SSHFiles = GenSSHShadow()
1108 GenMarkers(GlobalDir + "markers")
1109 GenSSHKnown(GlobalDir + "ssh_known_hosts")
1110 GenHosts(GlobalDir + "debianhosts")
1112 for host in HostAttrs:
1113 if not "hostname" in host[1]:
1116 CurrentHost = host[1]['hostname'][0]
1117 OutDir = GenerateDir + '/' + CurrentHost + '/'
1123 # Get the group list and convert any named groups to numerics
1125 for groupname in AllowedGroupsPreload.strip().split(" "):
1126 GroupList[groupname] = True
1127 if 'allowedGroups' in host[1]:
1128 for groupname in host[1]['allowedGroups']:
1129 GroupList[groupname] = True
1130 for groupname in GroupList.keys():
1131 if groupname in GroupIDMap:
1132 GroupList[str(GroupIDMap[groupname])] = True
1135 if 'exportOptions' in host[1]:
1136 for extra in host[1]['exportOptions']:
1137 ExtraList[extra.upper()] = True
1143 DoLink(GlobalDir, OutDir, "debianhosts")
1144 DoLink(GlobalDir, OutDir, "ssh_known_hosts")
1145 DoLink(GlobalDir, OutDir, "disabled-accounts")
1148 if 'NOPASSWD' in ExtraList:
1149 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "*")
1151 userlist = GenPasswd(OutDir + "passwd", HomePrefix, "x")
1153 grouprevmap = GenGroup(OutDir + "group")
1154 GenShadowSudo(OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
1156 # Now we know who we're allowing on the machine, export
1157 # the relevant ssh keys
1158 GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
1160 if not 'NOPASSWD' in ExtraList:
1161 GenShadow(OutDir + "shadow")
1163 # Link in global things
1164 if not 'NOMARKERS' in ExtraList:
1165 DoLink(GlobalDir, OutDir, "markers")
1166 DoLink(GlobalDir, OutDir, "mail-forward.cdb")
1167 DoLink(GlobalDir, OutDir, "mail-contentinspectionaction.cdb")
1168 DoLink(GlobalDir, OutDir, "mail-disable")
1169 DoLink(GlobalDir, OutDir, "mail-greylist")
1170 DoLink(GlobalDir, OutDir, "mail-callout")
1171 DoLink(GlobalDir, OutDir, "mail-rbl")
1172 DoLink(GlobalDir, OutDir, "mail-rhsbl")
1173 DoLink(GlobalDir, OutDir, "mail-whitelist")
1174 GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
1175 GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
1176 GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
1179 DoLink(GlobalDir, OutDir, "forward-alias")
1181 if 'DNS' in ExtraList:
1182 GenDNS(OutDir + "dns-zone")
1183 GenZoneRecords(OutDir + "dns-sshfp")
1185 if 'AUTHKEYS' in ExtraList:
1186 DoLink(GlobalDir, OutDir, "authorized_keys")
1188 if 'BSMTP' in ExtraList:
1189 GenBSMTP(OutDir + "bsmtp", HomePrefix)
1191 if 'PRIVATE' in ExtraList:
1192 DoLink(GlobalDir, OutDir, "debian-private")
1194 if 'KEYRING' in ExtraList:
1196 DoLink(GlobalDir, OutDir, os.path.basename(k))
1200 posix.remove(OutDir + os.path.basename(k))
1206 # vim:set shiftwidth=3: