GenShadow
[mirror/userdir-ldap.git] / ud-generate
index b60e171..f4f8b58 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) 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) 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
 #
 #   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 *
 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 *
+import UDLdap
+try:
+   from cStringIO import StringIO
+except ImportError:
+   from StringIO import StringIO
 
 global Allowed
 global CurrentHost
 
 
 global Allowed
 global CurrentHost
 
+if os.getuid() == 0:
+   sys.stderr.write("You should probably not run ud-generate as root.\n")
+   sys.exit(1)
+
 PasswdAttrs = None
 DebianUsers = None
 DisabledUsers = []
 PasswdAttrs = None
 DebianUsers = None
 DisabledUsers = []
@@ -47,6 +57,9 @@ UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
 EmailCheck = re.compile("^([^ <>@]+@[^ ,<>@]+)?$")
 BSMTPCheck = re.compile(".*mx 0 (master)\.debian\.org\..*",re.DOTALL)
 PurposeHostField = re.compile(r".*\[\[([\*\-]?[a-z0-9.\-]*)(?:\|.*)?\]\]")
 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)
+isSSHFP = re.compile("^\s*IN\s+SSHFP")
 DNSZone = ".debian.net"
 Keyrings = ConfModule.sync_keyrings.split(":")
 
 DNSZone = ".debian.net"
 Keyrings = ConfModule.sync_keyrings.split(":")
 
@@ -214,35 +227,30 @@ def GenShadow(File):
       # Fetch all the users
       global PasswdAttrs
      
       # Fetch all the users
       global PasswdAttrs
      
-      I = 0
+      i = 0
       for x in PasswdAttrs:
       for x in PasswdAttrs:
-         if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
-            continue
-     
-         Pass = GetAttr(x, "userPassword")
-         if Pass[0:7] != "{crypt}" or len(Pass) > 50:
-            Pass = '*'
-         else:
-            Pass = Pass[7:]
-     
+         a = UDLdap.Account(x[0], x[1])
+         if not IsInGroup(x): continue
+
          # If the account is locked, mark it as such in shadow
          # See Debian Bug #308229 for why we set it to 1 instead of 0
          # If the account is locked, mark it as such in shadow
          # See Debian Bug #308229 for why we set it to 1 instead of 0
-         if (GetAttr(x, "userPassword").find("*LK*") != -1) \
-             or GetAttr(x, "userPassword").startswith("!"):
-            ShadowExpire = '1'
-         else:
-            ShadowExpire = GetAttr(x, "shadowExpire")
-     
-         Line = "%s:%s:%s:%s:%s:%s:%s:%s:" % (GetAttr(x, "uid"),\
-                 Pass, GetAttr(x, "shadowLastChange"),\
-                 GetAttr(x, "shadowMin"), GetAttr(x, "shadowMax"),\
-                 GetAttr(x, "shadowWarning"), GetAttr(x, "shadowInactive"),\
-                 ShadowExpire)
-         Line = Sanitize(Line) + "\n"
-         F.write("0%u %s" % (I, Line))
-         F.write(".%s %s" % (GetAttr(x, "uid"), Line))
-         I = I + 1
-  
+         if not a.pw_active():     ShadowExpire = '1'
+         elif 'shadowExpire' in a: ShadowExpire = str(a['shadowExpire'])
+         else:                     ShadowExpire = ''
+
+         values = []
+         values.append(a['uid'])
+         values.append(a.get_password())
+         for key in 'shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive':
+            if key in a: values.append(a[key])
+            else:        values.append('')
+         values.append(ShadowExpire)
+         line = ':'.join(values)+':'
+         line = Sanitize(line) + "\n"
+         F.write("0%u %s" % (i, line))
+         F.write(".%s %s" % (a['uid'], line))
+         i = i + 1
+
    # Oops, something unspeakable happened.
    except:
       Die(File, None, F)
    # Oops, something unspeakable happened.
    except:
       Die(File, None, F)
@@ -261,12 +269,12 @@ def GenShadowSudo(File, untrusted):
       global PasswdAttrs
      
       for x in PasswdAttrs:
       global PasswdAttrs
      
       for x in PasswdAttrs:
+         a = UDLdap.Account(x[0], x[1])
          Pass = '*'
          Pass = '*'
-         if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
-            continue
+         if not IsInGroup(x): continue
      
      
-         if x[1].has_key('sudoPassword'):
-            for entry in x[1]['sudoPassword']:
+         if 'sudoPassword' in a:
+            for entry in a['sudoPassword']:
                Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
                if Match == None:
                   continue
                Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
                if Match == None:
                   continue
@@ -275,7 +283,7 @@ def GenShadowSudo(File, untrusted):
                hosts = Match.group(3)
                cryptedpass = Match.group(4)
      
                hosts = Match.group(3)
                cryptedpass = Match.group(4)
      
-               if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', x[1]['uid'][0], uuid, hosts, cryptedpass):
+               if status != 'confirmed:'+make_passwd_hmac('password-is-confirmed', 'sudo', a['uid'], uuid, hosts, cryptedpass):
                   continue
                for_all = hosts == "*"
                for_this_host = CurrentHost in hosts.split(',')
                   continue
                for_all = hosts == "*"
                for_this_host = CurrentHost in hosts.split(',')
@@ -290,7 +298,7 @@ def GenShadowSudo(File, untrusted):
             if len(Pass) > 50:
                Pass = '*'
      
             if len(Pass) > 50:
                Pass = '*'
      
-         Line = "%s:%s" % (GetAttr(x, "uid"), Pass)
+         Line = "%s:%s" % (a['uid'], Pass)
          Line = Sanitize(Line) + "\n"
          F.write("%s" % (Line))
   
          Line = Sanitize(Line) + "\n"
          F.write("%s" % (Line))
   
@@ -311,21 +319,17 @@ def GenSSHShadow():
    safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
 
    for x in PasswdAttrs:
    safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
 
    for x in PasswdAttrs:
+      a = UDLdap.Account(x[0], x[1])
+      if not 'sshRSAAuthKey' in a: continue
 
 
-      if x[1].has_key("uidNumber") == 0 or \
-         x[1].has_key("sshRSAAuthKey") == 0:
-         continue
-
-      User = GetAttr(x, "uid")
       F = None
       F = None
-
       try:
          OldMask = os.umask(0077)
       try:
          OldMask = os.umask(0077)
-         File = os.path.join(GlobalDir, 'userkeys', User)
+         File = os.path.join(GlobalDir, 'userkeys', a['uid'])
          F = open(File + ".tmp", "w", 0600)
          os.umask(OldMask)
 
          F = open(File + ".tmp", "w", 0600)
          os.umask(OldMask)
 
-         for I in x[1]["sshRSAAuthKey"]:
+         for I in a['sshRSAAuthKey']:
             MultipleLine = "%s" % I
             MultipleLine = Sanitize(MultipleLine) + "\n"
             F.write(MultipleLine)
             MultipleLine = "%s" % I
             MultipleLine = Sanitize(MultipleLine) + "\n"
             F.write(MultipleLine)
@@ -336,6 +340,8 @@ def GenSSHShadow():
       # Oops, something unspeakable happened.
       except IOError:
          Die(File, F, None)
       # 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
 
          Die(masterFileName, masterFile, None)
          raise
 
@@ -383,7 +389,20 @@ def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
       to.uname = f
       to.gname = grname
       to.mode  = 0400
       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)
 
    tf.close()
    os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
@@ -424,29 +443,34 @@ def GenGroup(File):
       GroupMap = {}
       for x in GroupIDMap.keys():
          GroupMap[x] = []
       GroupMap = {}
       for x in GroupIDMap.keys():
          GroupMap[x] = []
+      GroupHasPrimaryMembers = {}
      
       # Fetch all the users
       global PasswdAttrs
      
       # Sort them into a list of groups having a set of users
       for x in PasswdAttrs:
      
       # Fetch all the users
       global PasswdAttrs
      
       # Sort them into a list of groups having a set of users
       for x in PasswdAttrs:
-         uid = GetAttr(x, "uid")
-         if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
-            continue
-         if x[1].has_key("supplementaryGid") == 0:
-            continue
-     
+         a = UDLdap.Account(x[0], x[1])
+         GroupHasPrimaryMembers[ a['gidNumber'] ] = True
+         if not IsInGroup(x): continue
+         if not 'supplementaryGid' in a: continue
+
          supgroups=[]
          supgroups=[]
-         addGroups(supgroups, x[1]["supplementaryGid"], uid)
+         addGroups(supgroups, a['supplementaryGid'], a['uid'])
          for g in supgroups:
          for g in supgroups:
-            GroupMap[g].append(uid)
-     
+            GroupMap[g].append(a['uid'])
+
       # Output the group file.
       J = 0
       for x in GroupMap.keys():
       # Output the group file.
       J = 0
       for x in GroupMap.keys():
-         grouprevmap[GroupIDMap[x]] = x
          if GroupIDMap.has_key(x) == 0:
             continue
          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]:
          Line = "%s:x:%u:" % (x, GroupIDMap[x])
          Comma = ''
          for I in GroupMap[x]:
@@ -467,8 +491,8 @@ def GenGroup(File):
    return grouprevmap
 
 def CheckForward():
    return grouprevmap
 
 def CheckForward():
-   global DebianUsers
-   for x in DebianUsers:
+   global PasswdAttrs
+   for x in PasswdAttrs:
       if x[1].has_key("emailForward") == 0:
          continue
    
       if x[1].has_key("emailForward") == 0:
          continue
    
@@ -494,14 +518,13 @@ def GenForward(File):
       os.umask(OldMask)
      
       # Fetch all the users
       os.umask(OldMask)
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
       # Write out the email address for each user
      
       # Write out the email address for each user
-      for x in DebianUsers:
-         if x[1].has_key("emailForward") == 0:
-            continue
-     
-         Line = "%s: %s" % (GetAttr(x, "uid"), GetAttr(x, "emailForward"))
+      for x in PasswdAttrs:
+         a = UDLdap.Account(x[0], x[1])
+         if not 'emailForward' in a: continue
+         Line = "%s: %s" % (a['uid'], a['emailForward'])
          Line = Sanitize(Line) + "\n"
          F.write(Line)
   
          Line = Sanitize(Line) + "\n"
          F.write(Line)
   
@@ -511,23 +534,20 @@ def GenForward(File):
       raise
    Done(File, F, None)
 
       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)
 
    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
       # Write out the email address for each user
-      for x in DebianUsers:
-         if not Key in x[1]:
-            continue
-         Value = GetAttr(x, Key)
-         User = GetAttr(x, "uid")
-         Fdb.write("+%d,%d:%s->%s\n" % (len(User), len(Value), User, Value))
+      for x in Users:
+         a = UDLdap.Account(x[0], x[1])
+         if not key in a: continue
+         value = a[key]
+         user = a['uid']
+         Fdb.write("+%d,%d:%s->%s\n" % (len(user), len(value), user, value))
 
       Fdb.write("\n")
    # Oops, something unspeakable happened.
 
       Fdb.write("\n")
    # Oops, something unspeakable happened.
@@ -544,14 +564,14 @@ def GenMarkers(File):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
       # Write out the position for each user
      
       # Write out the position for each user
-      for x in DebianUsers:
-         if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
-            continue
+      for x in PasswdAttrs:
+         a = UDLdap.Account(x[0], x[1])
+         if not ('latitude' in a and 'longitude' in a): continue
          try:
          try:
-            Line = "%8s %8s \"\""%(DecDegree(GetAttr(x, "latitude"), 1), DecDegree(GetAttr(x, "longitude"), 1))
+            Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
             Line = Sanitize(Line) + "\n"
             F.write(Line)
          except:
             Line = Sanitize(Line) + "\n"
             F.write(Line)
          except:
@@ -570,19 +590,15 @@ def GenPrivate(File):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global DebianDDUsers
      
       # Write out the position for each user
      
       # Write out the position for each user
-      for x in DebianUsers:
-         if x[1].has_key("privateSub") == 0:
-            continue
-     
-         # If the account has no PGP key, do not write it
-         if x[1].has_key("keyFingerPrint") == 0:
-            continue
-     
+      for x in DebianDDUsers:
+         a = UDLdap.Account(x[0], x[1])
+         if not a.is_active_user(): continue
+         if not 'privateSub' in a: continue
          try:
          try:
-            Line = "%s"%(GetAttr(x, "privateSub"))
+            Line = "%s"%(a['privateSub'])
             Line = Sanitize(Line) + "\n"
             F.write(Line)
          except:
             Line = Sanitize(Line) + "\n"
             F.write(Line)
          except:
@@ -606,22 +622,12 @@ def GenDisabledAccounts(File):
      
       I = 0
       for x in PasswdAttrs:
      
       I = 0
       for x in PasswdAttrs:
-         if x[1].has_key("uidNumber") == 0:
-            continue
-     
-         Pass = GetAttr(x, "userPassword")
-         Line = ""
-         # *LK* is the reference value for a locked account
-         # password starting with ! is also a locked account
-         if Pass.find("*LK*") != -1 or Pass.startswith("!"):
-            # Format is <login>:<reason>
-            Line = "%s:%s" % (GetAttr(x, "uid"), "Account is locked")
-            DisabledUsers.append(x)
-     
-         if Line != "":
-            F.write(Sanitize(Line) + "\n")
-     
-   
+         a = UDLdap.Account(x[0], x[1])
+         if a.pw_active(): continue
+         Line = "%s:%s" % (a['uid'], "Account is locked")
+         DisabledUsers.append(x)
+         F.write(Sanitize(Line) + "\n")
+
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
@@ -635,22 +641,14 @@ def GenMailDisable(File):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
-     
-      for x in DebianUsers:
-         Reason = None
-     
-         if x[1].has_key("mailDisableMessage"):
-            Reason = GetAttr(x, "mailDisableMessage")
-         else:
-            continue
+      global PasswdAttrs
      
      
-         try:
-            Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
-            Line = Sanitize(Line) + "\n"
-            F.write(Line)
-         except:
-            pass
+      for x in PasswdAttrs:
+         a = UDLdap.Account(x[0], x[1])
+         if not 'mailDisableMessage' in a: continue
+         Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
+         Line = Sanitize(Line) + "\n"
+         F.write(Line)
   
    # Oops, something unspeakable happened.
    except:
   
    # Oops, something unspeakable happened.
    except:
@@ -659,30 +657,22 @@ def GenMailDisable(File):
    Done(File, F, None)
 
 # Generate a list of uids that should have boolean affects applied
    Done(File, F, None)
 
 # Generate a list of uids that should have boolean affects applied
-def GenMailBool(File, Key):
+def GenMailBool(File, key):
    F = None
    try:
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
    F = None
    try:
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
-     
-      for x in DebianUsers:
-         Reason = None
-     
-         if x[1].has_key(Key) == 0:
-            continue
-     
-         if GetAttr(x, Key) != "TRUE":
-            continue
+      global PasswdAttrs
      
      
-         try:
-            Line = "%s"%(GetAttr(x, "uid"))
-            Line = Sanitize(Line) + "\n"
-            F.write(Line)
-         except:
-            pass
-  
+      for x in PasswdAttrs:
+         a = UDLdap.Account(x[0], x[1])
+         if not key in a: continue
+         if not a[key] == 'TRUE': continue
+         Line = "%s"%(a['uid'])
+         Line = Sanitize(Line) + "\n"
+         F.write(Line)
+
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
@@ -690,45 +680,28 @@ def GenMailBool(File, Key):
    Done(File, F, None)
 
 # Generate a list of hosts for RBL or whitelist purposes.
    Done(File, F, None)
 
 # Generate a list of hosts for RBL or whitelist purposes.
-def GenMailList(File, Key):
+def GenMailList(File, key):
    F = None
    try:
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
    F = None
    try:
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
-     
-      for x in DebianUsers:
-         Reason = None
-     
-         if x[1].has_key(Key) == 0:
-            continue
-     
-         try:
-            found = 0
-            Line = None
-            for z in x[1][Key]:
-                if Key == "mailWhitelist":
-                   if re.match('^[-\w.]+(/[\d]+)?$', z) == None:
-                      continue
-                else:
-                   if re.match('^[-\w.]+$', z) == None:
-                      continue
-                if found == 0:
-                   found = 1
-                   Line = GetAttr(x, "uid")
-                else:
-                    Line += " "
-                Line += ": " + z
-                if Key == "mailRHSBL":
-                   Line += "/$sender_address_domain"
+      global PasswdAttrs
      
      
-            if Line != None:
-               Line = Sanitize(Line) + "\n"
-               F.write(Line)
-         except:
-            pass
-  
+      if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
+      else:                      validregex = re.compile('^[-\w.]+$')
+
+      for x in PasswdAttrs:
+         a = UDLdap.Account(x[0], x[1])
+         if not key in a: continue
+
+         filtered = filter(lambda z: validregex.match(z), a[key])
+         if len(filtered) == 0: continue
+         if key == "mailRHSBL": filtered = map(lambda z: z+"/$sender_address_domain", filtered)
+         line = a['uid'] + ': ' + ' : '.join(filtered)
+         line = Sanitize(line) + "\n"
+         F.write(line)
+
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
@@ -753,6 +726,7 @@ def GenDNS(File):
      
       # Fetch all the users
       global PasswdAttrs
      
       # Fetch all the users
       global PasswdAttrs
+      RRs = {}
      
       # Write out the zone file entry for each user
       for x in PasswdAttrs:
      
       # Write out the zone file entry for each user
       for x in PasswdAttrs:
@@ -778,11 +752,13 @@ def GenDNS(File):
                      F.write("; Has BSMTP\n")
      
                   # Write some identification information
                      F.write("; Has BSMTP\n")
      
                   # Write some identification information
-                  if Split[2].lower() == "a":
-                     Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
-                     for y in x[1]["keyFingerPrint"]:
-                        Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
-                     F.write(Line)
+                  if not RRs.has_key(Host):
+                     if Split[2].lower() in ["a", "aaaa"]:
+                        Line = "%s IN TXT \"%s\"\n"%(Split[0], EmailAddress(x))
+                        for y in x[1]["keyFingerPrint"]:
+                           Line = Line + "%s IN TXT \"PGP %s\"\n"%(Split[0], FormatPGPKey(y))
+                           F.write(Line)
+                        RRs[Host] = 1
                else:
                   Line = "; Err %s"%(str(Split))
                   F.write(Line)
                else:
                   Line = "; Err %s"%(str(Split))
                   F.write(Line)
@@ -798,35 +774,97 @@ def GenDNS(File):
       raise
    Done(File, F, None)
 
       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")
    F = None
    try:
       F = open(File + ".tmp", "w")
-     
+
       # Fetch all the hosts
       global HostAttrs
       # Fetch all the hosts
       global HostAttrs
-      if HostAttrs == None:
-         raise UDEmptyList, "No Hosts"
-     
+
       for x in HostAttrs:
       for x in HostAttrs:
-         if x[1].has_key("hostname") == 0 or \
-            x[1].has_key("sshRSAHostKey") == 0:
+         if x[1].has_key("hostname") == 0:
             continue
             continue
-         Host = GetAttr(x, "hostname")
-         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)
+
+         if IsDebianHost.match(GetAttr(x, "hostname")) is 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")
+
+        # this would write sshfp lines for services on machines
+        # but we can't yet, since some are cnames and we'll make
+        # an invalid zonefile
+        #
+        # for i in x[1].get("purpose", []):
+        #    m = PurposeHostField.match(i)
+        #    if m:
+        #       m = m.group(1)
+        #       # we ignore [[*..]] entries
+        #       if m.startswith('*'):
+        #          continue
+        #       if m.startswith('-'):
+        #          m = m[1:]
+        #       if m:
+        #          if not m.endswith(HostDomain):
+        #             continue
+        #          if not m.endswith('.'):
+        #             m = m + "."
+        #          for Line in DNSInfo:
+        #             if isSSHFP.match(Line):
+        #                Line = "%s\t%s" % (m, Line)
+        #                F.write(Line + "\n")
+
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
@@ -840,10 +878,10 @@ def GenBSMTP(File, HomePrefix):
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
       F = open(File + ".tmp", "w")
      
       # Fetch all the users
-      global DebianUsers
+      global PasswdAttrs
      
       # Write out the zone file entry for each user
      
       # 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
      
          if x[1].has_key("dnsZoneEntry") == 0:
             continue
      
@@ -874,26 +912,17 @@ def GenBSMTP(File, HomePrefix):
       raise
    Done(File, F, None)
   
       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):
 
 # Generate the ssh known hosts file
 def GenSSHKnown(File, mode=None):
@@ -904,8 +933,6 @@ def GenSSHKnown(File, mode=None):
       os.umask(OldMask)
      
       global HostAttrs
       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 \
      
       for x in HostAttrs:
          if x[1].has_key("hostname") == 0 or \
@@ -937,10 +964,12 @@ def GenSSHKnown(File, mode=None):
      
          for I in x[1]["sshRSAHostKey"]:
             if mode and mode == 'authorized_keys':
      
          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)
-               #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (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)
             else:
             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.
             Line = Sanitize(Line) + "\n"
             F.write(Line)
    # Oops, something unspeakable happened.
@@ -950,40 +979,32 @@ def GenSSHKnown(File, mode=None):
    Done(File, F, None)
 
 # Generate the debianhosts file (list of all IP addresses)
    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)
      
    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()
       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)
    # Oops, something unspeakable happened.
    except:
       Die(File, F, None)
@@ -996,9 +1017,14 @@ def GenKeyrings(OutDir):
 
 # Connect to the ldap server
 l = connectLDAP()
 
 # Connect to the ldap server
 l = connectLDAP()
-F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
-Pass = F.readline().strip().split(" ")
-F.close()
+# for testing purposes it's sometimes useful to pass username/password
+# via the environment
+if 'UD_CREDENTIALS' in os.environ:
+   Pass = os.environ['UD_CREDENTIALS'].split()
+else:
+   F = open(PassDir + "/pass-" + pwd.getpwuid(os.getuid())[0], "r")
+   Pass = F.readline().strip().split(" ")
+   F.close()
 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
 
 # Fetch all the groups
 l.simple_bind_s("uid=" + Pass[0] + "," + BaseDn, Pass[1])
 
 # Fetch all the groups
@@ -1008,6 +1034,8 @@ Attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
 
 # Generate the SubGroupMap and GroupIDMap
 for x in Attrs:
 
 # 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])
    if x[1].has_key("gidNumber") == 0:
       continue
    GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0])
@@ -1015,7 +1043,7 @@ for x in Attrs:
       SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
 
 # Fetch all the users
       SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
 
 # Fetch all the users
-PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
+PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
                 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
                  "gecos", "loginShell", "userPassword", "shadowLastChange",\
                  "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
                 ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
                  "gecos", "loginShell", "userPassword", "shadowLastChange",\
                  "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
@@ -1029,23 +1057,34 @@ PasswdAttrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "uid=*",\
 if PasswdAttrs is None:
    raise UDEmptyList, "No Users"
 
 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",\
 # Fetch all the hosts
 HostAttrs    = l.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
-                ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions"])
+                ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
+                 "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()))
+
+# override globaldir for testing
+if 'UD_GENERATEDIR' in os.environ:
+   GenerateDir = os.environ['UD_GENERATEDIR']
 
 # Generate global things
 GlobalDir = GenerateDir + "/"
 GenDisabledAccounts(GlobalDir + "disabled-accounts")
 
 PasswdAttrs = filter(lambda x: not IsRetired(x), PasswdAttrs)
 
 # 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")
 
 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")
 GenPrivate(GlobalDir + "debian-private")
 GenSSHKnown(GlobalDir+"authorized_keys", 'authorized_keys')
 GenMailBool(GlobalDir + "mail-greylist", "mailGreylisting")
@@ -1063,7 +1102,7 @@ PasswdAttrs = filter(lambda x: not x in DisabledUsers, PasswdAttrs)
 SSHFiles = GenSSHShadow()
 GenMarkers(GlobalDir + "markers")
 GenSSHKnown(GlobalDir + "ssh_known_hosts")
 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]:
 
 for host in HostAttrs:
    if not "hostname" in host[1]:
@@ -1127,13 +1166,19 @@ for host in HostAttrs:
    DoLink(GlobalDir, OutDir, "mail-rbl")
    DoLink(GlobalDir, OutDir, "mail-rhsbl")
    DoLink(GlobalDir, OutDir, "mail-whitelist")
    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")
 
    # 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")
 
    if 'BSMTP' in ExtraList:
       GenBSMTP(OutDir + "bsmtp", HomePrefix)
 
    if 'BSMTP' in ExtraList:
       GenBSMTP(OutDir + "bsmtp", HomePrefix)