A set of copyright headers
[mirror/userdir-ldap.git] / ud-generate
index 0239c3f..e5c1ae3 100755 (executable)
@@ -6,12 +6,13 @@
 #   Copyright (c) 2003-2004  James Troup <troup@debian.org>
 #   Copyright (c) 2004-2005,7  Joey Schulze <joey@infodrom.org>
 #   Copyright (c) 2001-2007  Ryan Murray <rmurray@debian.org>
-#   Copyright (c) 2008 Peter Palfrader <peter@palfrader.org>
+#   Copyright (c) 2008,2009,2010 Peter Palfrader <peter@palfrader.org>
 #   Copyright (c) 2008 Andreas Barth <aba@not.so.argh.org>
 #   Copyright (c) 2008 Mark Hymers <mhy@debian.org>
 #   Copyright (c) 2008 Luk Claes <luk@debian.org>
 #   Copyright (c) 2008 Thomas Viehmann <tv@beamnet.de>
 #   Copyright (c) 2009 Stephen Gran <steve@lobefin.net>
+#   Copyright (c) 2010 Helmut Grohne <helmut@subdivi.de>
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
 from userdir_ldap import *
 from userdir_exceptions import *
+try:
+   from cStringIO import StringIO
+except ImportError:
+   from StringIO import StringIO
 
 global Allowed
 global CurrentHost
@@ -48,6 +53,7 @@ EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
 IsV6Addr = re.compile("^[a-fA-F0-9:]+$")
+IsDebianHost = re.compile(ConfModule.dns_hostmatch)
 DNSZone = ".debian.net"
 Keyrings = ConfModule.sync_keyrings.split(":")
 
@@ -337,6 +343,8 @@ def GenSSHShadow():
       # Oops, something unspeakable happened.
       except IOError:
          Die(File, F, None)
+         # As neither masterFileName nor masterFile are defined at any point
+         # this will raise a NameError.
          Die(masterFileName, masterFile, None)
          raise
 
@@ -384,7 +392,20 @@ def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
       to.uname = f
       to.gname = grname
       to.mode  = 0400
-      tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
+
+      contents = file(os.path.join(GlobalDir, 'userkeys', f)).read()
+      lines = []
+      for line in contents.splitlines():
+         if line.startswith("allowed_hosts=") and ' ' in line:
+            machines, line = line.split('=', 1)[1].split(' ', 1)
+            if CurrentHost not in machines.split(','):
+               continue # skip this key
+         lines.append(line)
+      if not lines:
+         continue # no keys for this host
+      contents = "\n".join(lines) + "\n"
+      to.size = len(contents)
+      tf.addfile(to, StringIO(contents))
 
    tf.close()
    os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
@@ -425,6 +446,7 @@ def GenGroup(File):
       GroupMap = {}
       for x in GroupIDMap.keys():
          GroupMap[x] = []
+      GroupHasPrimaryMembers = {}
      
       # Fetch all the users
       global PasswdAttrs
@@ -432,6 +454,8 @@ def GenGroup(File):
       # Sort them into a list of groups having a set of users
       for x in PasswdAttrs:
          uid = GetAttr(x, "uid")
+         if 'gidNumber' in x[1]:
+            GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
          if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
             continue
          if x[1].has_key("supplementaryGid") == 0:
@@ -445,9 +469,14 @@ def GenGroup(File):
       # Output the group file.
       J = 0
       for x in GroupMap.keys():
-         grouprevmap[GroupIDMap[x]] = x
          if GroupIDMap.has_key(x) == 0:
             continue
+
+         if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
+            continue
+
+         grouprevmap[GroupIDMap[x]] = x
+
          Line = "%s:x:%u:" % (x, GroupIDMap[x])
          Comma = ''
          for I in GroupMap[x]:
@@ -468,8 +497,8 @@ def GenGroup(File):
    return grouprevmap
 
 def CheckForward():
-   global DebianUsers
-   for x in DebianUsers:
+   global PasswdAttrs
+   for x in PasswdAttrs:
       if x[1].has_key("emailForward") == 0:
          continue
    
@@ -495,10 +524,10 @@ def GenForward(File):
       os.umask(OldMask)
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
       # Write out the email address for each user
-      for x in DebianUsers:
+      for x in PasswdAttrs:
          if x[1].has_key("emailForward") == 0:
             continue
      
@@ -512,18 +541,15 @@ def GenForward(File):
       raise
    Done(File, F, None)
 
-def GenCDB(File, Key):
+def GenCDB(File, Users, Key):
    Fdb = None
    try:
       OldMask = os.umask(0022)
       Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
       os.umask(OldMask)
 
-      # Fetch all the users
-      global DebianUsers
-
       # Write out the email address for each user
-      for x in DebianUsers:
+      for x in Users:
          if not Key in x[1]:
             continue
          Value = GetAttr(x, Key)
@@ -545,10 +571,10 @@ def GenMarkers(File):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
       # Write out the position for each user
-      for x in DebianUsers:
+      for x in PasswdAttrs:
          if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
             continue
          try:
@@ -571,10 +597,10 @@ def GenPrivate(File):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global DebianDDUsers
      
       # Write out the position for each user
-      for x in DebianUsers:
+      for x in DebianDDUsers:
          if x[1].has_key("privateSub") == 0:
             continue
      
@@ -636,9 +662,9 @@ def GenMailDisable(File):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
-      for x in DebianUsers:
+      for x in PasswdAttrs:
          Reason = None
      
          if x[1].has_key("mailDisableMessage"):
@@ -666,9 +692,9 @@ def GenMailBool(File, Key):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
-      for x in DebianUsers:
+      for x in PasswdAttrs:
          Reason = None
      
          if x[1].has_key(Key) == 0:
@@ -697,9 +723,9 @@ def GenMailList(File, Key):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
-      for x in DebianUsers:
+      for x in PasswdAttrs:
          Reason = None
      
          if x[1].has_key(Key) == 0:
@@ -752,6 +778,27 @@ def GenDNS(File):
    try:
       F = open(File + ".tmp", "w")
      
+#      global HostAttrs
+#
+#      for x in HostAttrs:
+#         if x[1].has_key("hostname") == 0 or \
+#            x[1].has_key("architecture") == 0 or\
+#            x[1].has_key("sshRSAHostKey") == 0:
+#            continue
+#
+#         if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
+#            continue
+#
+#         DNSInfo = ExtractDNSInfo(x)
+#         start = True
+#         for Line in DNSInfo:
+#            if start == True:
+#               Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
+#               start = False
+#            else:
+#               Line = "\t\t\t%s" % (Line)
+#            F.write(Line + "\n")
+
       # Fetch all the users
       global PasswdAttrs
      
@@ -799,60 +846,73 @@ def GenDNS(File):
       raise
    Done(File, F, None)
 
-# Generate the DNS SSHFP records
-def GenSSHFP(File):
+def ExtractDNSInfo(x):
+
+   TTLprefix="\t"
+   if 'dnsTTL' in x[1]:
+      TTLprefix="%s\t"%(x[1]["dnsTTL"][0])
+
+   DNSInfo = []
+   if x[1].has_key("ipHostNumber"):
+      for I in x[1]["ipHostNumber"]:
+         if IsV6Addr.match(I) != None:
+            DNSInfo.append("%sIN\tAAAA\t%s" % (TTLprefix, I))
+         else:
+            DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
+
+   Algorithm = None
+
+   if 'sshRSAHostKey' in x[1]:
+      for I in x[1]["sshRSAHostKey"]:
+         Split = I.split()
+         if Split[0] == 'ssh-rsa':
+            Algorithm = 1
+         if Split[0] == 'ssh-dss':
+            Algorithm = 2
+         if Algorithm == None:
+            continue
+         Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
+         DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
+
+   if 'architecture' in x[1]:
+      Arch = GetAttr(x, "architecture")
+      Mach = ""
+      if x[1].has_key("machine"):
+         Mach = " " + GetAttr(x, "machine")
+      DNSInfo.append("%sIN\tHINFO\t\"%s%s\" \"%s\"" % (TTLprefix, Arch, Mach, "Debian GNU/Linux"))
+
+   if x[1].has_key("mXRecord"):
+      for I in x[1]["mXRecord"]:
+         DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
+
+   return DNSInfo
+
+# Generate the DNS records
+def GenZoneRecords(File):
    F = None
    try:
       F = open(File + ".tmp", "w")
-     
+
       # Fetch all the hosts
       global HostAttrs
-      if HostAttrs == None:
-         raise UDEmptyList, "No Hosts"
-     
+
       for x in HostAttrs:
-         if x[1].has_key("hostname") == 0 or \
-            x[1].has_key("architecture") == 0 or\
-            x[1].has_key("sshRSAHostKey") == 0:
+         if x[1].has_key("hostname") == 0:
             continue
-         Host = GetAttr(x, "hostname")
-         Arch = GetAttr(x, "architecture")
-         Algorithm = None
-
-         for I in x[1]["sshRSAHostKey"]:
-            Split = I.split()
-            if Split[0] == 'ssh-rsa':
-               Algorithm = 1
-            if Split[0] == 'ssh-dss':
-               Algorithm = 2
-            if Algorithm == None:
-               continue
-            Fingerprint = sha.new(base64.decodestring(Split[1])).hexdigest()
-            Line = "%s. IN SSHFP %u 1 %s" % (Host, Algorithm, Fingerprint)
-            Line = Sanitize(Line) + "\n"
-            F.write(Line)
 
-         Mach = ""
-         if x[1].has_key("machine"):
-            Mach = " " + GetAttr(x, "machine")
-         Line = "%s. IN HINFO \"%s%s\" \"%s\"" % (Host, Arch, Mach, "Debian GNU/Linux")
-         Line = Sanitize(Line) + "\n"
-         F.write(Line)
+         if IsDebianHost.match(GetAttr(x, "hostname")) is None:
+            continue
 
-         if x[1].has_key("ipHostNumber"):
-            for I in x[1]["ipHostNumber"]:
-               if IsV6Addr.match(I) != None:
-                  Line = "%s. IN AAAA %s" % (Host, I)
-               else:
-                  Line = "%s. IN A %s" % (Host, I)
-               Line = Sanitize(Line) + "\n"
-               F.write(Line)
+         DNSInfo = ExtractDNSInfo(x)
+         start = True
+         for Line in DNSInfo:
+            if start == True:
+               Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
+               start = False
+            else:
+               Line = "\t\t\t%s" % (Line)
 
-         if x[1].has_key("mXRecord"):
-            for I in x[1]["mXRecord"]:
-               Line = "%s. IN MX %s" % (Host, I)
-               Line = Sanitize(Line) + "\n"
-               F.write(Line)
+            F.write(Line + "\n")
 
    # Oops, something unspeakable happened.
    except:
@@ -867,10 +927,10 @@ def GenBSMTP(File, HomePrefix):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
       # Write out the zone file entry for each user
-      for x in DebianUsers:
+      for x in PasswdAttrs:
          if x[1].has_key("dnsZoneEntry") == 0:
             continue
      
@@ -901,26 +961,17 @@ def GenBSMTP(File, HomePrefix):
       raise
    Done(File, F, None)
   
-#  cache IP adresses
-HostToIPCache = {}
-def HostToIP(Host):
-   global HostToIPCache
-   if not Host in HostToIPCache:
-      IPAdressesT = None
-      try:
-         IPAdressesT = list(set([ (a[0], a[4][0]) for a in socket.getaddrinfo(Host, None)]))
-      except socket.gaierror, (code):
-         if code[0] != -2:
-            raise
-      IPAdresses = []
-      if not IPAdressesT is None:
-         for addr in IPAdressesT:
-            if addr[0] == socket.AF_INET:
-               IPAdresses += [addr[1], "::ffff:"+addr[1]]
-            else:
-               IPAdresses += [addr[1]]
-      HostToIPCache[Host] = IPAdresses
-   return HostToIPCache[Host]
+def HostToIP(Host, mapped=True):
+
+   IPAdresses = []
+
+   if Host[1].has_key("ipHostNumber"):
+      for addr in Host[1]["ipHostNumber"]:
+         IPAdresses.append(addr)
+         if IsV6Addr.match(addr) is None and mapped == "True":
+            IPAdresses.append("::ffff:"+addr)
+
+   return IPAdresses
 
 # Generate the ssh known hosts file
 def GenSSHKnown(File, mode=None):
@@ -931,8 +982,6 @@ def GenSSHKnown(File, mode=None):
       os.umask(OldMask)
      
       global HostAttrs
-      if HostAttrs is None:
-         raise UDEmptyList, "No Hosts"
      
       for x in HostAttrs:
          if x[1].has_key("hostname") == 0 or \
@@ -964,10 +1013,13 @@ def GenSSHKnown(File, mode=None):
      
          for I in x[1]["sshRSAHostKey"]:
             if mode and mode == 'authorized_keys':
-               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(HostToIP(Host)), I)
+               hosts = HostToIP(x)
+               if 'sshdistAuthKeysHost' in x[1]:
+                  hosts += x[1]['sshdistAuthKeysHost']
+               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)
                #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
             else:
-               Line = "%s %s" %(",".join(HostNames + HostToIP(Host)), I)
+               Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
             Line = Sanitize(Line) + "\n"
             F.write(Line)
    # Oops, something unspeakable happened.
@@ -977,40 +1029,32 @@ def GenSSHKnown(File, mode=None):
    Done(File, F, None)
 
 # Generate the debianhosts file (list of all IP addresses)
-def GenHosts(l, File):
+def GenHosts(File):
    F = None
    try:
       OldMask = os.umask(0022)
       F = open(File + ".tmp", "w", 0644)
       os.umask(OldMask)
      
-      # Fetch all the hosts
-      hostnames = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "hostname=*",
-                             ["hostname"])
-     
-      if hostnames is None:
-         raise UDEmptyList, "No Hosts"
-     
       seen = set()
-      for x in hostnames:
-         host = GetAttr(x, "hostname", None)
-         if host:
-            addrs = []
-            try:
-               addrs += socket.getaddrinfo(host, None, socket.AF_INET)
-            except socket.error:
-               pass
-            try:
-               addrs += socket.getaddrinfo(host, None, socket.AF_INET6)
-            except socket.error:
-               pass
-           
-            for addrinfo in addrs:
-               if addrinfo[0] in (socket.AF_INET, socket.AF_INET6):
-                  addr = addrinfo[4][0]
-                  if addr not in seen:
-                     print >> F, addrinfo[4][0]
-                     seen.add(addr)
+
+      global HostAttrs
+
+      for x in HostAttrs:
+
+         if IsDebianHost.match(GetAttr(x, "hostname")) is None:
+            continue
+
+         if not 'ipHostNumber' in x[1]:
+            continue
+
+         addrs = x[1]["ipHostNumber"]
+         for addr in addrs:
+            if addr not in seen:
+               seen.add(addr)
+               addr = Sanitize(addr) + "\n"
+               F.write(addr)
+
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
@@ -1035,6 +1079,8 @@ Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
 
 # Generate the SubGroupMap and GroupIDMap
 for x in Attrs:
+   if x[1].has_key("accountStatus") and x[1]['accountStatus'] == "disabled":
+      continue
    if x[1].has_key("gidNumber") == 0:
       continue
    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
@@ -1056,24 +1102,30 @@ PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
 if PasswdAttrs is None:
    raise UDEmptyList, "No Users"
 
+PasswdAttrs.sort(lambda x, y: cmp((GetAttr(x, "uid")).lower(), (GetAttr(y, "uid")).lower()))
+
 # Fetch all the hosts
 HostAttrs    = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
                 ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
-                 "mXRecord", "ipHostNumber", "machine", "architecture"])
+                 "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
+
+if HostAttrs == None:
+   raise UDEmptyList, "No Hosts"
+
+HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
 
 # Generate global things
 GlobalDir = GenerateDir + "/"
 GenDisabledAccounts(GlobalDir + "disabled-accounts")
 
 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
-#DebianUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
-DebianUsers = PasswdAttrs
+DebianDDUsers = filter(lambda x: IsGidDebian(x), PasswdAttrs)
 
 CheckForward()
 
 GenMailDisable(GlobalDir + "mail-disable")
-GenCDB(GlobalDir + "mail-forward.cdb", 'emailForward')
-GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", 'mailContentInspectionAction')
+GenCDB(GlobalDir + "mail-forward.cdb", PasswdAttrs, 'emailForward')
+GenCDB(GlobalDir + "mail-contentinspectionaction.cdb", PasswdAttrs, 'mailContentInspectionAction')
 GenPrivate(GlobalDir + "debian-private")
 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
@@ -1091,7 +1143,7 @@ PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
 SSHFiles = GenSSHShadow()
 GenMarkers(GlobalDir + "markers")
 GenSSHKnown(GlobalDir + "ssh_known_hosts")
-GenHosts(l, GlobalDir + "debianhosts")
+GenHosts(GlobalDir + "debianhosts")
 
 for host in HostAttrs:
    if not "hostname" in host[1]:
@@ -1155,13 +1207,16 @@ for host in HostAttrs:
    DoLink(GlobalDir, OutDir, "mail-rbl")
    DoLink(GlobalDir, OutDir, "mail-rhsbl")
    DoLink(GlobalDir, OutDir, "mail-whitelist")
+   GenCDB(OutDir + "user-forward.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'emailForward')
+   GenCDB(OutDir + "batv-tokens.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'bATVToken')
+   GenCDB(OutDir + "default-mail-options.cdb", filter(lambda x: IsInGroup(x), PasswdAttrs), 'mailDefaultOptions')
 
    # Compatibility.
    DoLink(GlobalDir, OutDir, "forward-alias")
 
    if 'DNS' in ExtraList:
       GenDNS(OutDir + "dns-zone")
-      GenSSHFP(OutDir + "dns-sshfp")
+      GenZoneRecords(OutDir + "dns-sshfp")
 
    if 'AUTHKEYS' in ExtraList:
       DoLink(GlobalDir, OutDir, "authorized_keys")