Lock ud-generate process
[mirror/userdir-ldap.git] / ud-generate
index bbddc2f..5abadf6 100755 (executable)
@@ -6,11 +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,2011 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) 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
 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
 #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
-from userdir_ldap import *;
+import lockfile
+from userdir_ldap import *
+from userdir_exceptions import *
+import UDLdap
+try:
+   from cStringIO import StringIO
+except ImportError:
+   from StringIO import StringIO
+try:
+   import simplejson as json
+except ImportError:
+   import json
+   if not '__author__' in json.__dict__:
+      sys.stderr.write("Warning: This is probably the wrong json module.  We want python 2.6's json\n")
+      sys.stderr.write("module, or simplejson on pytyon 2.5.  Let's see if/how stuff blows up.\n")
+
+if os.getuid() == 0:
+   sys.stderr.write("You should probably not run ud-generate as root.\n")
+   sys.exit(1)
+
+
+#
+# GLOBAL STATE
+#
+GroupIDMap = {}
+SubGroupMap = {}
+Allowed = None
+CurrentHost = ""
 
 
-global Allowed;
-global CurrentHost;
 
 
-PasswdAttrs = None;
-disabledusers = []
-GroupIDMap = {};
-SubGroupMap = {};
-Allowed = None;
-CurrentHost = "";
 
 UUID_FORMAT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
 
 
 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 (gluck)\.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(":")
 
 def safe_makedirs(dir):
 DNSZone = ".debian.net"
 Keyrings = ConfModule.sync_keyrings.split(":")
 
 def safe_makedirs(dir):
-    try:
-        os.makedirs(dir)
-    except OSError, e:
-        if e.errno == errno.EEXIST:
-            pass
-        else:
-            raise e
+   try:
+      os.makedirs(dir)
+   except OSError, e:
+      if e.errno == errno.EEXIST:
+         pass
+      else:
+         raise e
 
 def safe_rmtree(dir):
 
 def safe_rmtree(dir):
-    try:
-        shutil.rmtree(dir)
-    except OSError, e:
-        if e.errno == errno.ENOENT:
-            pass
-        else:
-            raise e
+   try:
+      shutil.rmtree(dir)
+   except OSError, e:
+      if e.errno == errno.ENOENT:
+         pass
+      else:
+         raise e
+
+def get_lock(fn, wait=5*60, max_age=3600*6):
+   try:
+      stat = os.stat(fn)
+      if stat[ST_MTIME] < time.time() - max_age:
+         sys.stderr.write("Removing stale lock %s"%(fn))
+         os.unlink(fn)
+   except OSError, error:
+      if error.errno == errno.ENOENT:
+         pass
+      else:
+         raise
+
+   lock = lockfile.FileLock(fn)
+   try:
+      lock.acquire(timeout=wait)
+   except lockfile.LockTimeout:
+      return None
+
+   return lock
+
 
 def Sanitize(Str):
 
 def Sanitize(Str):
-  return Str.translate(string.maketrans("\n\r\t","$$$"))
+   return Str.translate(string.maketrans("\n\r\t", "$$$"))
 
 
-def DoLink(From,To,File):
-   try: posix.remove(To+File);
-   except: pass;
-   posix.link(From+File,To+File);
+def DoLink(From, To, File):
+   try: 
+      posix.remove(To + File)
+   except: 
+      pass
+   posix.link(From + File, To + File)
 
 
-def IsRetired(DnRecord):
+def IsRetired(account):
    """
    Looks for accountStatus in the LDAP record and tries to
    match it against one of the known retired statuses
    """
 
    """
    Looks for accountStatus in the LDAP record and tries to
    match it against one of the known retired statuses
    """
 
-   status = GetAttr(DnRecord,"accountStatus", None)
-   if status is None:
-      return False
+   status = account['accountStatus']
+
+   line = status.split()
+   status = line[0]
 
 
-   if status.find("inactive") != -1:
+   if status == "inactive":
       return True
 
       return True
 
-   if status.find("memorial") != -1:
+   elif status == "memorial":
       return True
 
       return True
 
-   if status.find("retiring") != -1:
-      line = status.split()
+   elif status == "retiring":
       # We'll give them a few extra days over what we said
       age = 6 * 31 * 24 * 60 * 60
       # We'll give them a few extra days over what we said
       age = 6 * 31 * 24 * 60 * 60
-      if (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d")) > (age):
-            return True
+      try:
+         return (time.time() - time.mktime(time.strptime(line[1], "%Y-%m-%d"))) > age
+      except IndexError:
+         return False
+      except ValueError:
+         return False
 
    return False
 
 
    return False
 
+#def IsGidDebian(account):
+#   return account['gidNumber'] == 800
+
 # See if this user is in the group list
 # See if this user is in the group list
-def IsInGroup(DnRecord):
-  if Allowed == None:
-     return 1;
+def IsInGroup(account):
+  if Allowed is None:
+     return True
 
   # See if the primary group is in the list
 
   # See if the primary group is in the list
-  if Allowed.has_key(GetAttr(DnRecord,"gidNumber")) != 0:
-     return 1;
+  if str(account['gidNumber']) in Allowed: return True
 
   # Check the host based ACL
 
   # Check the host based ACL
-  if DnRecord[1].has_key("allowedHost") != 0:
-     for I in DnRecord[1]["allowedHost"]:
-        if CurrentHost == I:
-           return 1;
+  if account.is_allowed_by_hostacl(CurrentHost): return True
 
   # See if there are supplementary groups
 
   # See if there are supplementary groups
-  if DnRecord[1].has_key("supplementaryGid") == 0:
-     return 0;
+  if not 'supplementaryGid' in account: return False
 
   supgroups=[]
 
   supgroups=[]
-  addGroups(supgroups, DnRecord[1]["supplementaryGid"], GetAttr(DnRecord,"uid"))
+  addGroups(supgroups, account['supplementaryGid'], account['uid'])
   for g in supgroups:
      if Allowed.has_key(g):
   for g in supgroups:
      if Allowed.has_key(g):
-        return 1;
-  return 0;
+        return True
+  return False
 
 
-def Die(File,F,Fdb):
+def Die(File, F, Fdb):
+   if F != None:
+      F.close()
+   if Fdb != None:
+      Fdb.close()
+   try: 
+      os.remove(File + ".tmp")
+   except:
+      pass
+   try: 
+      os.remove(File + ".tdb.tmp")
+   except: 
+      pass
+
+def Done(File, F, Fdb):
    if F != None:
    if F != None:
-      F.close();
+      F.close()
+      os.rename(File + ".tmp", File)
    if Fdb != None:
    if Fdb != None:
-      Fdb.close();
-   try: os.remove(File + ".tmp");
-   except: pass;
-   try: os.remove(File + ".tdb.tmp");
-   except: pass;
-
-def Done(File,F,Fdb):
-  if F != None:
-    F.close();
-    os.rename(File + ".tmp",File);
-  if Fdb != None:
-    Fdb.close();
-    os.rename(File + ".tdb.tmp",File+".tdb");
+      Fdb.close()
+      os.rename(File + ".tdb.tmp", File + ".tdb")
 
 # Generate the password list
 
 # Generate the password list
-def GenPasswd(l,File,HomePrefix,PwdMarker):
-  F = None;
-  try:
-   F = open(File + ".tdb.tmp","w");
-
-   userlist = {}
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   I = 0;
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
-
-      if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
-         continue;
-
-      # Do not let people try to buffer overflow some busted passwd parser.
-      if len(GetAttr(x,"gecos")) > 100 or len(GetAttr(x,"loginShell")) > 50:
-         continue;
-
-      userlist[GetAttr(x, "uid")] = int(GetAttr(x, "gidNumber"))
-      Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x,"uid"),\
-              PwdMarker,\
-              GetAttr(x,"uidNumber"),GetAttr(x,"gidNumber"),\
-              GetAttr(x,"gecos"),HomePrefix,GetAttr(x,"uid"),\
-              GetAttr(x,"loginShell"));
-
-      Line = Sanitize(Line) + "\n";
-      F.write("0%u %s" % (I,Line));
-      F.write(".%s %s" % (GetAttr(x,"uid"),Line));
-      F.write("=%s %s" % (GetAttr(x,"uidNumber"),Line));
-      I = I + 1;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,None,F);
-   raise;
-  Done(File,None,F);
-
-  # Return the list of users so we know which keys to export
-  return userlist
+def GenPasswd(accounts, File, HomePrefix, PwdMarker):
+   F = None
+   try:
+      F = open(File + ".tdb.tmp", "w")
+
+      userlist = {}
+      i = 0
+      for a in accounts:
+         if not IsInGroup(a): continue
+
+         # Do not let people try to buffer overflow some busted passwd parser.
+         if len(a['gecos']) > 100 or len(a['loginShell']) > 50: continue
+
+         userlist[a['uid']] = a['gidNumber']
+         line = "%s:%s:%d:%d:%s:%s%s:%s" % (
+                 a['uid'],
+                 PwdMarker,
+                 a['uidNumber'],
+                 a['gidNumber'],
+                 a['gecos'],
+                 HomePrefix, a['uid'],
+                 a['loginShell'])
+         line = Sanitize(line) + "\n"
+         F.write("0%u %s" % (i, line))
+         F.write(".%s %s" % (a['uid'], line))
+         F.write("=%d %s" % (a['uidNumber'], line))
+         i = i + 1
+
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, None, F)
+      raise
+   Done(File, None, F)
+
+   # Return the list of users so we know which keys to export
+   return userlist
+
+def GenAllUsers(accounts, file):
+   f = None
+   try:
+      OldMask = os.umask(0022)
+      f = open(file + ".tmp", "w", 0644)
+      os.umask(OldMask)
+
+      all = []
+      for a in accounts:
+         all.append( { 'uid': a['uid'],
+                       'uidNumber': a['uidNumber'],
+                       'active': a.pw_active() and a.shadow_active() } )
+      json.dump(all, f)
+
+   # Oops, something unspeakable happened.
+   except:
+      Die(file, f, None)
+      raise
+   Done(file, f, None)
 
 # Generate the shadow list
 
 # Generate the shadow list
-def GenShadow(l,File):
-  F = None;
-  try:
-   OldMask = os.umask(0077);
-   F = open(File + ".tdb.tmp","w",0600);
-   os.umask(OldMask);
-
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   I = 0;
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
-
-      if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
-         continue;
-
-      Pass = GetAttr(x,"userPassword");
-      if Pass[0:7] != "{crypt}" or len(Pass) > 50:
-         Pass = '*';
-      else:
-         Pass = Pass[7:];
-
-      # 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;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,None,F);
-   raise;
-  Done(File,None,F);
+def GenShadow(accounts, File):
+   F = None
+   try:
+      OldMask = os.umask(0077)
+      F = open(File + ".tdb.tmp", "w", 0600)
+      os.umask(OldMask)
+
+      i = 0
+      for a in accounts:
+         Pass = '*'
+         if not IsInGroup(a): 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 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)
+      raise
+   Done(File, None, F)
 
 # Generate the sudo passwd file
 
 # Generate the sudo passwd file
-def GenShadowSudo(l,File, untrusted):
-  F = None;
-  try:
-   OldMask = os.umask(0077);
-   F = open(File + ".tmp","w",0600);
-   os.umask(OldMask);
-
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
-
-      Pass = '*'
-      if x[1].has_key("uidNumber") == 0 or IsInGroup(x) == 0:
-         continue;
-
-      if x[1].has_key('sudoPassword'):
-         for entry in x[1]['sudoPassword']:
-            Match = re.compile('^('+UUID_FORMAT+') (confirmed:[0-9a-f]{40}|unconfirmed) ([a-z0-9.,*]+) ([^ ]+)$').match(entry)
-            if Match == None:
-               continue
-            uuid = Match.group(1)
-            status = Match.group(2)
-            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):
-               continue
-            for_all = hosts == "*"
-            for_this_host = CurrentHost in hosts.split(',')
-            if not (for_all or for_this_host):
-               continue
-            # ignore * passwords for untrusted hosts, but copy host specific passwords
-            if for_all and untrusted:
-               continue
-            Pass = cryptedpass
-            if for_this_host: # this makes sure we take a per-host entry over the for-all entry
-              break
-         if len(Pass) > 50:
-            Pass = '*'
-
-      Line = "%s:%s" % (GetAttr(x,"uid"), Pass)
-      Line = Sanitize(Line) + "\n";
-      F.write("%s" % (Line));
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+def GenShadowSudo(accounts, File, untrusted):
+   F = None
+   try:
+      OldMask = os.umask(0077)
+      F = open(File + ".tmp", "w", 0600)
+      os.umask(OldMask)
+
+      for a in accounts:
+         Pass = '*'
+         if not IsInGroup(a): continue
+     
+         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
+               uuid = Match.group(1)
+               status = Match.group(2)
+               hosts = Match.group(3)
+               cryptedpass = Match.group(4)
+     
+               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(',')
+               if not (for_all or for_this_host):
+                  continue
+               # ignore * passwords for untrusted hosts, but copy host specific passwords
+               if for_all and untrusted:
+                  continue
+               Pass = cryptedpass
+               if for_this_host: # this makes sure we take a per-host entry over the for-all entry
+                  break
+            if len(Pass) > 50:
+               Pass = '*'
+     
+         Line = "%s:%s" % (a['uid'], Pass)
+         Line = Sanitize(Line) + "\n"
+         F.write("%s" % (Line))
+  
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 # Generate the shadow list
 
 # Generate the shadow list
-def GenSSHShadow(l):
+def GenSSHShadow(global_dir, accounts):
    # Fetch all the users
    # Fetch all the users
-   singlefile = None
    userfiles = []
 
    userfiles = []
 
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
-   safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
-
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
-
-      # If the account is locked, do not write it.
-      # This is a partial stop-gap. The ssh also needs to change this
-      # to ignore ~/.ssh/authorized* files.
-      if (GetAttr(x,"userPassword").find("*LK*") != -1) \
-             or GetAttr(x,"userPassword").startswith("!"):
-         continue;
+   safe_rmtree(os.path.join(global_dir, 'userkeys'))
+   safe_makedirs(os.path.join(global_dir, 'userkeys'))
 
 
-      if x[1].has_key("uidNumber") == 0 or \
-         x[1].has_key("sshRSAAuthKey") == 0:
-         continue;
-      User = GetAttr(x,"uid");
-      F = None;
+   for a in accounts:
+      if not 'sshRSAAuthKey' in a: continue
 
 
+      F = None
       try:
       try:
-         OldMask = os.umask(0077);
-         File = os.path.join(GlobalDir, 'userkeys', User)
-         F = open(File + ".tmp","w",0600);
-         os.umask(OldMask);
+         OldMask = os.umask(0077)
+         File = os.path.join(global_dir, 'userkeys', a['uid'])
+         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)
 
-         Done(File,F,None);
+         Done(File, F, None)
          userfiles.append(os.path.basename(File))
 
       # Oops, something unspeakable happened.
       except IOError:
          userfiles.append(os.path.basename(File))
 
       # Oops, something unspeakable happened.
       except IOError:
-          Die(File,F,None)
-          Die(masterFileName,masterFile,None)
-          raise;
+         Die(File, F, None)
+         # As neither masterFileName nor masterFile are defined at any point
+         # this will raise a NameError.
+         Die(masterFileName, masterFile, None)
+         raise
 
    return userfiles
 
 
    return userfiles
 
-def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
-   OldMask = os.umask(0077);
-   tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
-   os.umask(OldMask);
+def GenSSHtarballs(global_dir, userlist, SSHFiles, grouprevmap, target):
+   OldMask = os.umask(0077)
+   tf = tarfile.open(name=os.path.join(global_dir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
+   os.umask(OldMask)
    for f in userlist.keys():
       if f not in SSHFiles:
          continue
    for f in userlist.keys():
       if f not in SSHFiles:
          continue
@@ -373,7 +400,7 @@ def GenSSHtarballs(userlist, SSHFiles, grouprevmap, target):
          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])
          continue
 
          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])
          continue
 
-      to = tf.gettarinfo(os.path.join(GlobalDir, 'userkeys', f), f)
+      to = tf.gettarinfo(os.path.join(global_dir, 'userkeys', f), f)
       # These will only be used where the username doesn't
       # exist on the target system for some reason; hence,
       # in those cases, the safest thing is for the file to
       # These will only be used where the username doesn't
       # exist on the target system for some reason; hence,
       # in those cases, the safest thing is for the file to
@@ -389,10 +416,23 @@ 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(global_dir, '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()
 
    tf.close()
-   os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
+   os.rename(os.path.join(global_dir, 'ssh-keys-%s.tar.gz' % CurrentHost), target)
 
 # add a list of groups to existing groups,
 # including all subgroups thereof, recursively.
 
 # add a list of groups to existing groups,
 # including all subgroups thereof, recursively.
@@ -403,12 +443,12 @@ def addGroups(existingGroups, newGroups, uid):
       # if it's a <group>@host, split it and verify it's on the current host.
       s = group.split('@', 1)
       if len(s) == 2 and s[1] != CurrentHost:
       # if it's a <group>@host, split it and verify it's on the current host.
       s = group.split('@', 1)
       if len(s) == 2 and s[1] != CurrentHost:
-         continue;
+         continue
       group = s[0]
 
       # let's see if we handled this group already
       if group in existingGroups:
       group = s[0]
 
       # let's see if we handled this group already
       if group in existingGroups:
-        continue
+         continue
 
       if not GroupIDMap.has_key(group):
          print "Group", group, "does not exist but", uid, "is in it"
 
       if not GroupIDMap.has_key(group):
          print "Group", group, "does not exist but", uid, "is in it"
@@ -420,793 +460,750 @@ def addGroups(existingGroups, newGroups, uid):
          addGroups(existingGroups, SubGroupMap[group], uid)
 
 # Generate the group list
          addGroups(existingGroups, SubGroupMap[group], uid)
 
 # Generate the group list
-def GenGroup(l,File):
-  grouprevmap = {}
-  F = None;
-  try:
-   F = open(File + ".tdb.tmp","w");
+def GenGroup(accounts, File):
+   grouprevmap = {}
+   F = None
+   try:
+      F = open(File + ".tdb.tmp", "w")
+     
+      # Generate the GroupMap
+      GroupMap = {}
+      for x in GroupIDMap.keys():
+         GroupMap[x] = []
+      GroupHasPrimaryMembers = {}
+
+      # Sort them into a list of groups having a set of users
+      for a in accounts:
+         GroupHasPrimaryMembers[ a['gidNumber'] ] = True
+         if not IsInGroup(a): continue
+         if not 'supplementaryGid' in a: continue
+
+         supgroups=[]
+         addGroups(supgroups, a['supplementaryGid'], a['uid'])
+         for g in supgroups:
+            GroupMap[g].append(a['uid'])
+
+      # Output the group file.
+      J = 0
+      for x in GroupMap.keys():
+         if GroupIDMap.has_key(x) == 0:
+            continue
 
 
-   # Generate the GroupMap
-   GroupMap = {};
-   for x in GroupIDMap.keys():
-      GroupMap[x] = [];
+         if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
+            continue
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   # 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 IsInGroup(x) == 0:
-         continue;
-      if x[1].has_key("supplementaryGid") == 0:
-         continue;
-
-      supgroups=[]
-      addGroups(supgroups, x[1]["supplementaryGid"], uid)
-      for g in supgroups:
-         GroupMap[g].append(uid);
-
-   # Output the group file.
-   J = 0;
-   for x in GroupMap.keys():
-      grouprevmap[GroupIDMap[x]] = x
-      if GroupIDMap.has_key(x) == 0:
-         continue;
-      Line = "%s:x:%u:" % (x,GroupIDMap[x]);
-      Comma = '';
-      for I in GroupMap[x]:
-        Line = Line + ("%s%s" % (Comma,I));
-        Comma = ',';
-      Line = Sanitize(Line) + "\n";
-      F.write("0%u %s" % (J,Line));
-      F.write(".%s %s" % (x,Line));
-      F.write("=%u %s" % (GroupIDMap[x],Line));
-      J = J + 1;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,None,F);
-   raise;
-  Done(File,None,F);
-
-  return grouprevmap
+         grouprevmap[GroupIDMap[x]] = x
 
 
-# Generate the email forwarding list
-def GenForward(l,File):
-  F = None;
-  try:
-   OldMask = os.umask(0022);
-   F = open(File + ".tmp","w",0644);
-   os.umask(OldMask);
+         Line = "%s:x:%u:" % (x, GroupIDMap[x])
+         Comma = ''
+         for I in GroupMap[x]:
+            Line = Line + ("%s%s" % (Comma, I))
+            Comma = ','
+         Line = Sanitize(Line) + "\n"
+         F.write("0%u %s" % (J, Line))
+         F.write(".%s %s" % (x, Line))
+         F.write("=%u %s" % (GroupIDMap[x], Line))
+         J = J + 1
+  
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, None, F)
+      raise
+   Done(File, None, F)
+  
+   return grouprevmap
+
+def CheckForward(accounts):
+   for a in accounts:
+      if not 'emailForward' in a: continue
+
+
+      delete = False
+
+      if not IsInGroup(a): delete = True
+      # Do not allow people to try to buffer overflow busted parsers
+      elif len(a['emailForward']) > 200: delete = True
+      # Check the forwarding address
+      elif EmailCheck.match(a['emailForward']) is None: delete = True
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+      if delete:
+         a.delete_mailforward()
 
 
-   # Write out the email address for each user
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
+# Generate the email forwarding list
+def GenForward(accounts, File):
+   F = None
+   try:
+      OldMask = os.umask(0022)
+      F = open(File + ".tmp", "w", 0644)
+      os.umask(OldMask)
 
 
-      if x[1].has_key("emailForward") == 0 or IsInGroup(x) == 0:
-         continue;
+      for a in accounts:
+         if not 'emailForward' in a: continue
+         Line = "%s: %s" % (a['uid'], a['emailForward'])
+         Line = Sanitize(Line) + "\n"
+         F.write(Line)
 
 
-      # Do not allow people to try to buffer overflow busted parsers
-      if len(GetAttr(x,"emailForward")) > 200:
-         continue;
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-      # Check the forwarding address
-      if EmailCheck.match(GetAttr(x,"emailForward")) == None:
-         continue;
-      Line = "%s: %s" % (GetAttr(x,"uid"),GetAttr(x,"emailForward"));
-      Line = Sanitize(Line) + "\n";
-      F.write(Line);
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
-
-def GenAllForward(l,File):
-  Fdb = None;
-  try:
-   OldMask = os.umask(0022);
-   Fdb = os.popen("cdbmake %s %s.tmp"%(File,File),"w");
-   os.umask(OldMask);
+def GenCDB(accounts, File, key):
+   Fdb = None
+   try:
+      OldMask = os.umask(0022)
+      Fdb = os.popen("cdbmake %s %s.tmp"%(File, File), "w")
+      os.umask(OldMask)
+
+      # Write out the email address for each user
+      for a in accounts:
+         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.
+   except:
+      Fdb.close()
+      raise
+   if Fdb.close() != None:
+      raise "cdbmake gave an error"
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+# Generate the anon XEarth marker file
+def GenMarkers(accounts, File):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-   # Write out the email address for each user
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
+      # Write out the position for each user
+      for a in accounts:
+         if not ('latitude' in a and 'longitude' in a): continue
+         try:
+            Line = "%8s %8s \"\""%(a.latitude_dec(True), a.longitude_dec(True))
+            Line = Sanitize(Line) + "\n"
+            F.write(Line)
+         except:
+            pass
+  
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-      if x[1].has_key("emailForward") == 0:
-         continue;
+# Generate the debian-private subscription list
+def GenPrivate(accounts, File):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-      # Do not allow people to try to buffer overflow busted parsers
-      Forward = GetAttr(x,"emailForward");
-      if len(Forward) > 200:
-         continue;
+      # Write out the position for each user
+      for a in accounts:
+         if not a.is_active_user(): continue
+         if not 'privateSub' in a: continue
+         try:
+            Line = "%s"%(a['privateSub'])
+            Line = Sanitize(Line) + "\n"
+            F.write(Line)
+         except:
+            pass
+  
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-      # Check the forwarding address
-      if EmailCheck.match(Forward) == None:
-         continue;
-
-      User = GetAttr(x,"uid");
-      Fdb.write("+%d,%d:%s->%s\n"%(len(User),len(Forward),User,Forward));
-   Fdb.write("\n");
-  # Oops, something unspeakable happened.
-  except:
-    Fdb.close();
-    raise;
-  if Fdb.close() != None:
-    raise "cdbmake gave an error";
+# Generate a list of locked accounts
+def GenDisabledAccounts(accounts, File):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
+      disabled_accounts = []
+
+      # Fetch all the users
+      for a in accounts:
+         if a.pw_active(): continue
+         Line = "%s:%s" % (a['uid'], "Account is locked")
+         disabled_accounts.append(a)
+         F.write(Sanitize(Line) + "\n")
 
 
-# Generate the anon XEarth marker file
-def GenMarkers(l,File):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
+   return disabled_accounts
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+# Generate the list of local addresses that refuse all mail
+def GenMailDisable(accounts, File):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-   # Write out the position for each user
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
+      for a in accounts:
+         if not 'mailDisableMessage' in a: continue
+         Line = "%s: %s"%(a['uid'], a['mailDisableMessage'])
+         Line = Sanitize(Line) + "\n"
+         F.write(Line)
 
 
-      if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
-         continue;
-      try:
-         Line = "%8s %8s \"\""%(DecDegree(GetAttr(x,"latitude"),1),DecDegree(GetAttr(x,"longitude"),1));
-         Line = Sanitize(Line) + "\n";
-         F.write(Line);
-      except:
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-# Generate the debian-private subscription list
-def GenPrivate(l,File):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+# Generate a list of uids that should have boolean affects applied
+def GenMailBool(accounts, File, key):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+      for a in accounts:
+         if not key in a: continue
+         if not a[key] == 'TRUE': continue
+         Line = "%s"%(a['uid'])
+         Line = Sanitize(Line) + "\n"
+         F.write(Line)
 
 
-   # Write out the position for each user
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-      if x[1].has_key("privateSub") == 0:
-         continue;
+# Generate a list of hosts for RBL or whitelist purposes.
+def GenMailList(accounts, File, key):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-      # If the account is locked, do not write it
-      if (GetAttr(x,"userPassword").find("*LK*") != -1) \
-             or GetAttr(x,"userPassword").startswith("!"):
-         continue;
+      if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
+      else:                      validregex = re.compile('^[-\w.]+$')
 
 
-      # If the account has no PGP key, do not write it
-      if x[1].has_key("keyFingerPrint") == 0:
-         continue;
+      for a in accounts:
+         if not key in a: continue
 
 
-      # Must be in the Debian group (yuk, hard coded for now)
-      if GetAttr(x,"gidNumber") != "800":
-         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)
 
 
-      try:
-         Line = "%s"%(GetAttr(x,"privateSub"));
-         Line = Sanitize(Line) + "\n";
-         F.write(Line);
-      except:
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-# Generate a list of locked accounts
-def GenDisabledAccounts(l,File):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+def isRoleAccount(account):
+   return 'debianRoleAccount' in account['objectClass']
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   global disabledusers
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   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")
-
-      if Line != "":
-         F.write(Sanitize(Line) + "\n")
+# Generate the DNS Zone file
+def GenDNS(accounts, File):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-      disabledusers.append(x)
+      # Fetch all the users
+      RRs = {}
 
 
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+      # Write out the zone file entry for each user
+      for a in accounts:
+         if not 'dnsZoneEntry' in a: continue
+         if not a.is_active_user() and not isRoleAccount(a): continue
 
 
-# Generate the list of local addresses that refuse all mail
-def GenMailDisable(l,File):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+         try:
+            F.write("; %s\n"%(a.email_address()))
+            for z in a["dnsZoneEntry"]:
+               Split = z.lower().split()
+               if Split[1].lower() == 'in':
+                  for y in range(0, len(Split)):
+                     if Split[y] == "$":
+                        Split[y] = "\n\t"
+                  Line = " ".join(Split) + "\n"
+                  F.write(Line)
+     
+                  Host = Split[0] + DNSZone
+                  if BSMTPCheck.match(Line) != None:
+                     F.write("; Has BSMTP\n")
+     
+                  # Write some identification information
+                  if not RRs.has_key(Host):
+                     if Split[2].lower() in ["a", "aaaa"]:
+                        Line = "%s IN TXT \"%s\"\n"%(Split[0], a.email_address())
+                        for y in a["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)
+     
+            F.write("\n")
+         except Exception, e:
+            F.write("; Errors:\n")
+            for line in str(e).split("\n"):
+               F.write("; %s\n"%(line))
+            pass
+  
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
+
+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))
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+   Algorithm = None
 
 
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
+   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))
 
 
-      Reason = None
+   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("mailDisableMessage"):
-         Reason = GetAttr(x,"mailDisableMessage")
-      else:
-         continue
+   if x[1].has_key("mXRecord"):
+      for I in x[1]["mXRecord"]:
+         DNSInfo.append("%sIN\tMX\t%s" % (TTLprefix, I))
 
 
-      # Must be in the Debian group (yuk, hard coded for now)
-      if GetAttr(x,"gidNumber") != "800":
-         continue;
+   return DNSInfo
 
 
-      try:
-         Line = "%s: %s"%(GetAttr(x,"uid"),Reason);
-         Line = Sanitize(Line) + "\n";
-         F.write(Line);
-      except:
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+# Generate the DNS records
+def GenZoneRecords(host_attrs, File):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
 
 
-# Generate a list of uids that should have boolean affects applied
-def GenMailBool(l,File,Key):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+      # Fetch all the hosts
+      for x in host_attrs:
+         if x[1].has_key("hostname") == 0:
+            continue
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+         if IsDebianHost.match(GetAttr(x, "hostname")) is None:
+            continue
 
 
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         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)
+      raise
+   Done(File, F, None)
 
 
-      Reason = None
+# Generate the BSMTP file
+def GenBSMTP(accounts, File, HomePrefix):
+   F = None
+   try:
+      F = open(File + ".tmp", "w")
+     
+      # Write out the zone file entry for each user
+      for a in accounts:
+         if not 'dnsZoneEntry' in a: continue
+         if not a.is_active_user(): continue
 
 
-      if x[1].has_key(Key) == 0:
-         continue
+         try:
+            for z in a["dnsZoneEntry"]:
+               Split = z.lower().split()
+               if Split[1].lower() == 'in':
+                  for y in range(0, len(Split)):
+                     if Split[y] == "$":
+                        Split[y] = "\n\t"
+                  Line = " ".join(Split) + "\n"
+     
+                  Host = Split[0] + DNSZone
+                  if BSMTPCheck.match(Line) != None:
+                      F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
+                                  a['uid'], HomePrefix, a['uid'], Host))
+     
+         except:
+            F.write("; Errors\n")
+            pass
+  
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
+  
+def HostToIP(Host, mapped=True):
 
 
-      # Must be in the Debian group (yuk, hard coded for now)
-      if GetAttr(x,"gidNumber") != "800":
-         continue
+   IPAdresses = []
 
 
-      if GetAttr(x,Key) != "TRUE":
-         continue
+   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)
 
 
-      try:
-         Line = "%s"%(GetAttr(x,"uid"));
-         Line = Sanitize(Line) + "\n";
-         F.write(Line);
-      except:
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+   return IPAdresses
 
 
-# Generate a list of hosts for RBL or whitelist purposes.
-def GenMailList(l,File,Key):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+# Generate the ssh known hosts file
+def GenSSHKnown(host_attrs, File, mode=None):
+   F = None
+   try:
+      OldMask = os.umask(0022)
+      F = open(File + ".tmp", "w", 0644)
+      os.umask(OldMask)
+     
+      for x in host_attrs:
+         if x[1].has_key("hostname") == 0 or \
+            x[1].has_key("sshRSAHostKey") == 0:
+            continue
+         Host = GetAttr(x, "hostname")
+         HostNames = [ Host ]
+         if Host.endswith(HostDomain):
+            HostNames.append(Host[:-(len(HostDomain) + 1)])
+     
+         # in the purpose field [[host|some other text]] (where some other text is optional)
+         # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
+         # file.  But so that we don't have to add everything we link we can add an asterisk
+         # and say [[*... to ignore it.  In order to be able to add stuff to ssh without
+         # http linking it we also support [[-hostname]] entries.
+         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:
+                  HostNames.append(m)
+                  if m.endswith(HostDomain):
+                     HostNames.append(m[:-(len(HostDomain) + 1)])
+     
+         for I in x[1]["sshRSAHostKey"]:
+            if mode and mode == 'authorized_keys':
+               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:
+               Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
+            Line = Sanitize(Line) + "\n"
+            F.write(Line)
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
+# Generate the debianhosts file (list of all IP addresses)
+def GenHosts(host_attrs, File):
+   F = None
+   try:
+      OldMask = os.umask(0022)
+      F = open(File + ".tmp", "w", 0644)
+      os.umask(OldMask)
+     
+      seen = set()
 
 
-   for x in PasswdAttrs:
-      if IsRetired(x):
-         continue
+      for x in host_attrs:
 
 
-      Reason = None
+         if IsDebianHost.match(GetAttr(x, "hostname")) is None:
+            continue
 
 
-      if x[1].has_key(Key) == 0:
-         continue
+         if not 'ipHostNumber' in x[1]:
+            continue
 
 
-      # Must be in the Debian group (yuk, hard coded for now)
-      if GetAttr(x,"gidNumber") != "800":
-         continue
+         addrs = x[1]["ipHostNumber"]
+         for addr in addrs:
+            if addr not in seen:
+               seen.add(addr)
+               addr = Sanitize(addr) + "\n"
+               F.write(addr)
 
 
-      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"
-
-         if Line != None:
-             Line = Sanitize(Line) + "\n";
-             F.write(Line);
-      except:
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
-
-def isRoleAccount(pwEntry):
-   if not pwEntry.has_key("objectClass"):
-      raise "pwEntry has no objectClass"
-   oc =  pwEntry['objectClass']
-   try:
-      i = oc.index('debianRoleAccount')
-      return True
-   except ValueError:
-      return False
+   # Oops, something unspeakable happened.
+   except:
+      Die(File, F, None)
+      raise
+   Done(File, F, None)
 
 
-# Generate the DNS Zone file
-def GenDNS(l,File,HomePrefix):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+def GenKeyrings(OutDir):
+   for k in Keyrings:
+      shutil.copy(k, OutDir)
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   # Write out the zone file entry for each user
-   for x in PasswdAttrs:
-      if x[1].has_key("dnsZoneEntry") == 0:
-         continue;
-
-      # If the account has no PGP key, do not write it
-      if x[1].has_key("keyFingerPrint") == 0 and not isRoleAccount(x[1]):
-         continue;
-      try:
-         F.write("; %s\n"%(EmailAddress(x)));
-         for z in x[1]["dnsZoneEntry"]:
-            Split = z.lower().split()
-            if Split[1].lower() == 'in':
-               for y in range(0,len(Split)):
-                  if Split[y] == "$":
-                     Split[y] = "\n\t";
-               Line = " ".join(Split) + "\n";
-               F.write(Line);
-
-               Host = Split[0] + DNSZone;
-               if BSMTPCheck.match(Line) != None:
-                   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);
-            else:
-               Line = "; Err %s"%(str(Split));
-               F.write(Line);
-
-         F.write("\n");
-      except:
-         F.write("; Errors\n");
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
-
-# Generate the DNS SSHFP records
-def GenSSHFP(l,File,HomePrefix):
-  F = None
-  try:
-   F = open(File + ".tmp","w")
 
 
+def get_accounts(ldap_conn):
+   # Fetch all the users
+   passwd_attrs = ldap_conn.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "(&(uid=*)(!(uidNumber=0)))",\
+                   ["uid", "uidNumber", "gidNumber", "supplementaryGid",\
+                    "gecos", "loginShell", "userPassword", "shadowLastChange",\
+                    "shadowMin", "shadowMax", "shadowWarning", "shadowInactive",
+                    "shadowExpire", "emailForward", "latitude", "longitude",\
+                    "allowedHost", "sshRSAAuthKey", "dnsZoneEntry", "cn", "sn",\
+                    "keyFingerPrint", "privateSub", "mailDisableMessage",\
+                    "mailGreylisting", "mailCallout", "mailRBL", "mailRHSBL",\
+                    "mailWhitelist", "sudoPassword", "objectClass", "accountStatus",\
+                    "mailContentInspectionAction"])
+
+   if passwd_attrs is None:
+      raise UDEmptyList, "No Users"
+   accounts = map(lambda x: UDLdap.Account(x[0], x[1]), passwd_attrs)
+   accounts.sort(lambda x,y: cmp(x['uid'].lower(), y['uid'].lower()))
+
+   return accounts
+
+def get_hosts(ldap_conn):
    # Fetch all the hosts
    # Fetch all the hosts
-   global HostAttrs
+   HostAttrs    = ldap_conn.search_s(HostBaseDn, ldap.SCOPE_ONELEVEL, "objectClass=debianServer",\
+                   ["hostname", "sshRSAHostKey", "purpose", "allowedGroups", "exportOptions",\
+                    "mXRecord", "ipHostNumber", "dnsTTL", "machine", "architecture"])
+
    if HostAttrs == None:
    if HostAttrs == None:
-      raise "No Hosts"
+      raise UDEmptyList, "No Hosts"
 
 
-   for x in HostAttrs:
-      if x[1].has_key("hostname") == 0 or \
-         x[1].has_key("sshRSAHostKey") == 0:
-         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)
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None)
-   raise;
-  Done(File,F,None)
+   HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
 
 
-# Generate the BSMTP file
-def GenBSMTP(l,File,HomePrefix):
-  F = None;
-  try:
-   F = open(File + ".tmp","w");
+   return HostAttrs
 
 
-   # Fetch all the users
-   global PasswdAttrs;
-   if PasswdAttrs == None:
-      raise "No Users";
-
-   # Write out the zone file entry for each user
-   for x in PasswdAttrs:
-      if x[1].has_key("dnsZoneEntry") == 0:
-         continue;
-
-      # If the account has no PGP key, do not write it
-      if x[1].has_key("keyFingerPrint") == 0:
-         continue;
-      try:
-         for z in x[1]["dnsZoneEntry"]:
-            Split = z.lower().split()
-            if Split[1].lower() == 'in':
-               for y in range(0,len(Split)):
-                  if Split[y] == "$":
-                     Split[y] = "\n\t";
-               Line = " ".join(Split) + "\n";
-
-               Host = Split[0] + DNSZone;
-               if BSMTPCheck.match(Line) != None:
-                   F.write("%s: user=%s group=Debian file=%s%s/bsmtp/%s\n"%(Host,
-                               GetAttr(x,"uid"),HomePrefix,GetAttr(x,"uid"),Host));
-
-      except:
-         F.write("; Errors\n");
-         pass;
-
-  # Oops, something unspeakable happened.
-  except:
-   Die(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 make_ldap_conn():
+   # Connect to the ldap server
+   l = connectLDAP()
+   # 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])
+
+   return l
+
+def generate_all(global_dir, ldap_conn):
+   accounts = get_accounts(ldap_conn)
+   host_attrs = get_hosts(ldap_conn)
+
+   global_dir += '/'
+   # Generate global things
+   accounts_disabled = GenDisabledAccounts(accounts, global_dir + "disabled-accounts")
+
+   accounts = filter(lambda x: not IsRetired(x), accounts)
+   #accounts_DDs = filter(lambda x: IsGidDebian(x), accounts)
+
+   CheckForward(accounts)
+
+   GenMailDisable(accounts, global_dir + "mail-disable")
+   GenCDB(accounts, global_dir + "mail-forward.cdb", 'emailForward')
+   GenCDB(accounts, global_dir + "mail-contentinspectionaction.cdb", 'mailContentInspectionAction')
+   GenPrivate(accounts, global_dir + "debian-private")
+   GenSSHKnown(host_attrs, global_dir+"authorized_keys", 'authorized_keys')
+   GenMailBool(accounts, global_dir + "mail-greylist", "mailGreylisting")
+   GenMailBool(accounts, global_dir + "mail-callout", "mailCallout")
+   GenMailList(accounts, global_dir + "mail-rbl", "mailRBL")
+   GenMailList(accounts, global_dir + "mail-rhsbl", "mailRHSBL")
+   GenMailList(accounts, global_dir + "mail-whitelist", "mailWhitelist")
+   GenKeyrings(global_dir)
 
 
-# Generate the ssh known hosts file
-def GenSSHKnown(l,File,mode=None):
-  F = None;
-  try:
-   OldMask = os.umask(0022);
-   F = open(File + ".tmp","w",0644);
-   os.umask(OldMask);
-
-   global HostAttrs
-   if HostAttrs == None:
-      raise "No Hosts";
-
-   for x in HostAttrs:
-      if x[1].has_key("hostname") == 0 or \
-         x[1].has_key("sshRSAHostKey") == 0:
-         continue;
-      Host = GetAttr(x,"hostname");
-      HostNames = [ Host ]
-      if Host.endswith(HostDomain):
-         HostNames.append(Host[:-(len(HostDomain)+1)])
-
-      # in the purpose field [[host|some other text]] (where some other text is optional)
-      # makes a hyperlink on the web thing. we now also add these hosts to the ssh known_hosts
-      # file.  But so that we don't have to add everything we link we can add an asterisk
-      # and say [[*... to ignore it.  In order to be able to add stuff to ssh without
-      # http linking it we also support [[-hostname]] entries.
-      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:
-               HostNames.append(m)
-               if m.endswith(HostDomain):
-                  HostNames.append(m[:-(len(HostDomain)+1)])
+   # Compatibility.
+   GenForward(accounts, global_dir + "forward-alias")
 
 
-      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(HNames + 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)
-         else:
-            Line = "%s %s" %(",".join(HostNames + HostToIP(Host)), I);
-         Line = Sanitize(Line) + "\n";
-         F.write(Line);
-  # Oops, something unspeakable happened.
-  except:
-   Die(File,F,None);
-   raise;
-  Done(File,F,None);
+   GenAllUsers(accounts, global_dir + 'all-accounts.json')
+   accounts = filter(lambda a: not a in accounts_disabled, accounts)
 
 
-# Generate the debianhosts file (list of all IP addresses)
-def GenHosts(l,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 == None:
-       raise "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)
-  # Oops, something unspeakable happened.
-  except:
-    Die(File,F,None)
-    raise
-  Done(File,F,None)
-
-def GenKeyrings(l,OutDir):
-  for k in Keyrings:
-    shutil.copy(k, OutDir)
-
-
-# Connect to the ldap server
-l = connectLDAP()
-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]);
+   ssh_files = GenSSHShadow(global_dir, accounts)
+   GenMarkers(accounts, global_dir + "markers")
+   GenSSHKnown(host_attrs, global_dir + "ssh_known_hosts")
+   GenHosts(host_attrs, global_dir + "debianhosts")
 
 
-# Fetch all the groups
-GroupIDMap = {};
-Attrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"gid=*",\
-                  ["gid","gidNumber","subGroup"]);
+   GenDNS(accounts, global_dir + "dns-zone")
+   GenZoneRecords(host_attrs, global_dir + "dns-sshfp")
 
 
-# Generate the SubGroupMap and GroupIDMap
-for x in Attrs:
-   if x[1].has_key("gidNumber") == 0:
-      continue;
-   GroupIDMap[x[1]["gid"][0]] = int(x[1]["gidNumber"][0]);
-   if x[1].has_key("subGroup") != 0:
-      SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"]);
-
-# Fetch all the users
-PasswdAttrs = l.search_s(BaseDn,ldap.SCOPE_ONELEVEL,"uid=*",\
-                ["uid","uidNumber","gidNumber","supplementaryGid",\
-                 "gecos","loginShell","userPassword","shadowLastChange",\
-                 "shadowMin","shadowMax","shadowWarning","shadowInactive",
-                 "shadowExpire","emailForward","latitude","longitude",\
-                 "allowedHost","sshRSAAuthKey","dnsZoneEntry","cn","sn",\
-                 "keyFingerPrint","privateSub","mailDisableMessage",\
-                 "mailGreylisting","mailCallout","mailRBL","mailRHSBL",\
-                 "mailWhitelist", "sudoPassword", "objectClass", "accountStatus"]);
-# Fetch all the hosts
-HostAttrs    = l.search_s(HostBaseDn,ldap.SCOPE_ONELEVEL,"sshRSAHostKey=*",\
-                ["hostname","sshRSAHostKey","purpose"]);
-
-# Open the control file
-if len(sys.argv) == 1:
-   F = open(GenerateConf,"r");
-else:
-   F = open(sys.argv[1],"r")
-
-# Generate global things
-GlobalDir = GenerateDir+"/";
-SSHFiles = GenSSHShadow(l);
-GenAllForward(l,GlobalDir+"mail-forward.cdb");
-GenMarkers(l,GlobalDir+"markers");
-GenPrivate(l,GlobalDir+"debian-private");
-GenDisabledAccounts(l,GlobalDir+"disabled-accounts");
-GenSSHKnown(l,GlobalDir+"ssh_known_hosts");
-#GenSSHKnown(l,GlobalDir+"authorized_keys", 'authorized_keys');
-GenHosts(l,GlobalDir+"debianhosts");
-GenMailDisable(l,GlobalDir+"mail-disable");
-GenMailBool(l,GlobalDir+"mail-greylist","mailGreylisting");
-GenMailBool(l,GlobalDir+"mail-callout","mailCallout");
-GenMailList(l,GlobalDir+"mail-rbl","mailRBL");
-GenMailList(l,GlobalDir+"mail-rhsbl","mailRHSBL");
-GenMailList(l,GlobalDir+"mail-whitelist","mailWhitelist");
-GenKeyrings(l,GlobalDir);
-
-# Compatibility.
-GenForward(l,GlobalDir+"forward-alias");
-
-PasswdAttrs = filter(lambda x: not x in disabledusers, PasswdAttrs)
-
-while(1):
-   Line = F.readline();
-   if Line == "":
-      break;
-   Line = Line.strip()
-   if Line == "":
-      continue;
-   if Line[0] == '#':
-      continue;
-
-   Split = Line.split(" ")
-   OutDir = GenerateDir + '/' + Split[0] + '/';
-   try: os.mkdir(OutDir);
-   except: pass;
+   for host in host_attrs:
+      if not "hostname" in host[1]:
+         continue
+      generate_host(host, global_dir, accounts, ssh_files)
+
+def generate_host(host, global_dir, accounts, ssh_files):
+   global CurrentHost
+
+   CurrentHost = host[1]['hostname'][0]
+   OutDir = global_dir + CurrentHost + '/'
+   try:
+      os.mkdir(OutDir)
+   except:
+      pass
 
    # Get the group list and convert any named groups to numerics
 
    # Get the group list and convert any named groups to numerics
-   GroupList = {};
-   ExtraList = {};
-   for I in Split[2:]:
-      if I[0] == '[':
-         ExtraList[I] = None;
-         continue;
-      GroupList[I] = None;
-      if GroupIDMap.has_key(I):
-         GroupList[str(GroupIDMap[I])] = None;
-
-   Allowed = GroupList;
+   GroupList = {}
+   for groupname in AllowedGroupsPreload.strip().split(" "):
+      GroupList[groupname] = True
+   if 'allowedGroups' in host[1]:
+      for groupname in host[1]['allowedGroups']:
+         GroupList[groupname] = True
+   for groupname in GroupList.keys():
+      if groupname in GroupIDMap:
+         GroupList[str(GroupIDMap[groupname])] = True
+
+   ExtraList = {}
+   if 'exportOptions' in host[1]:
+      for extra in host[1]['exportOptions']:
+         ExtraList[extra.upper()] = True
+
+   global Allowed
+   Allowed = GroupList
    if Allowed == {}:
    if Allowed == {}:
-     Allowed = None
-   CurrentHost = Split[0];
+      Allowed = None
 
 
-   DoLink(GlobalDir,OutDir,"debianhosts");
-   DoLink(GlobalDir,OutDir,"ssh_known_hosts");
-   DoLink(GlobalDir,OutDir,"disabled-accounts")
+   DoLink(global_dir, OutDir, "debianhosts")
+   DoLink(global_dir, OutDir, "ssh_known_hosts")
+   DoLink(global_dir, OutDir, "disabled-accounts")
 
 
-   sys.stdout.flush();
-   if ExtraList.has_key("[NOPASSWD]"):
-      userlist = GenPasswd(l,OutDir+"passwd",Split[1], "*");
+   sys.stdout.flush()
+   if 'NOPASSWD' in ExtraList:
+      userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "*")
    else:
    else:
-      userlist = GenPasswd(l,OutDir+"passwd",Split[1], "x");
-   sys.stdout.flush();
-   grouprevmap = GenGroup(l,OutDir+"group");
-   GenShadowSudo(l, OutDir+"sudo-passwd", ExtraList.has_key("[UNTRUSTED]") or ExtraList.has_key("[NOPASSWD]"))
+      userlist = GenPasswd(accounts, OutDir + "passwd", HomePrefix, "x")
+   sys.stdout.flush()
+   grouprevmap = GenGroup(accounts, OutDir + "group")
+   GenShadowSudo(accounts, OutDir + "sudo-passwd", ('UNTRUSTED' in ExtraList) or ('NOPASSWD' in ExtraList))
 
    # Now we know who we're allowing on the machine, export
    # the relevant ssh keys
 
    # Now we know who we're allowing on the machine, export
    # the relevant ssh keys
-   GenSSHtarballs(userlist, SSHFiles, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
+   GenSSHtarballs(global_dir, userlist, ssh_files, grouprevmap, os.path.join(OutDir, 'ssh-keys.tar.gz'))
 
 
-   if ExtraList.has_key("[UNTRUSTED]"):
-     print "[UNTRUSTED] tag is obsolete and may be removed in the future."
-     continue;
-   if not ExtraList.has_key("[NOPASSWD]"):
-     GenShadow(l,OutDir+"shadow");
+   if not 'NOPASSWD' in ExtraList:
+      GenShadow(accounts, OutDir + "shadow")
 
    # Link in global things
 
    # Link in global things
-   if not ExtraList.has_key("[NOMARKERS]"):
-     DoLink(GlobalDir,OutDir,"markers");
-   DoLink(GlobalDir,OutDir,"mail-forward.cdb");
-   DoLink(GlobalDir,OutDir,"mail-disable");
-   DoLink(GlobalDir,OutDir,"mail-greylist");
-   DoLink(GlobalDir,OutDir,"mail-callout");
-   DoLink(GlobalDir,OutDir,"mail-rbl");
-   DoLink(GlobalDir,OutDir,"mail-rhsbl");
-   DoLink(GlobalDir,OutDir,"mail-whitelist");
+   if not 'NOMARKERS' in ExtraList:
+      DoLink(global_dir, OutDir, "markers")
+   DoLink(global_dir, OutDir, "mail-forward.cdb")
+   DoLink(global_dir, OutDir, "mail-contentinspectionaction.cdb")
+   DoLink(global_dir, OutDir, "mail-disable")
+   DoLink(global_dir, OutDir, "mail-greylist")
+   DoLink(global_dir, OutDir, "mail-callout")
+   DoLink(global_dir, OutDir, "mail-rbl")
+   DoLink(global_dir, OutDir, "mail-rhsbl")
+   DoLink(global_dir, OutDir, "mail-whitelist")
+   DoLink(global_dir, OutDir, "all-accounts.json")
+   GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "user-forward.cdb", 'emailForward')
+   GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "batv-tokens.cdb", 'bATVToken')
+   GenCDB(filter(lambda x: IsInGroup(x), accounts), OutDir + "default-mail-options.cdb", 'mailDefaultOptions')
 
    # Compatibility.
 
    # Compatibility.
-   DoLink(GlobalDir,OutDir,"forward-alias");
+   DoLink(global_dir, OutDir, "forward-alias")
+
+   if 'DNS' in ExtraList:
+      DoLink(global_dir, OutDir, "dns-zone")
+      DoLink(global_dir, OutDir, "dns-sshfp")
 
 
-   if ExtraList.has_key("[DNS]"):
-      GenDNS(l,OutDir+"dns-zone",Split[1]);
-      GenSSHFP(l,OutDir+"dns-sshfp",Split[1])
+   if 'AUTHKEYS' in ExtraList:
+      DoLink(global_dir, OutDir, "authorized_keys")
 
 
-   if ExtraList.has_key("[BSMTP]"):
-      GenBSMTP(l,OutDir+"bsmtp",Split[1])
+   if 'BSMTP' in ExtraList:
+      GenBSMTP(accounts, OutDir + "bsmtp", HomePrefix)
 
 
-   if ExtraList.has_key("[PRIVATE]"):
-      DoLink(GlobalDir,OutDir,"debian-private")
+   if 'PRIVATE' in ExtraList:
+      DoLink(global_dir, OutDir, "debian-private")
 
 
-   if ExtraList.has_key("[KEYRING]"):
+   if 'KEYRING' in ExtraList:
       for k in Keyrings:
       for k in Keyrings:
-        DoLink(GlobalDir,OutDir,os.path.basename(k))
+        DoLink(global_dir, OutDir, os.path.basename(k))
    else:
    else:
-     for k in Keyrings:
-       try: posix.remove(OutDir+os.path.basename(k));
-       except: pass;
+      for k in Keyrings:
+         try:
+            posix.remove(OutDir + os.path.basename(k))
+         except:
+            pass
+
+l = make_ldap_conn()
+
+# Fetch all the groups
+GroupIDMap = {}
+attrs = l.search_s(BaseDn, ldap.SCOPE_ONELEVEL, "gid=*",\
+                  ["gid", "gidNumber", "subGroup"])
+
+# 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("subGroup") != 0:
+      SubGroupMap.setdefault(x[1]["gid"][0], []).extend(x[1]["subGroup"])
+
+# override globaldir for testing
+if 'UD_GENERATEDIR' in os.environ:
+   GenerateDir = os.environ['UD_GENERATEDIR']
+
+try:
+   lock = get_lock( os.path.join(GenerateDir, 'ud-generate.lock') )
+   if lock is None:
+      sys.stderr.write("Could not acquire lock %s.\n"%(fn))
+      sys.exit(1)
+
+   generate_all(GenerateDir, l)
+
+finally:
+   if not lock is None:
+      lock.release()
 
 # vim:set et:
 # vim:set ts=3:
 
 # vim:set et:
 # vim:set ts=3: