GenCDB
[mirror/userdir-ldap.git] / ud-generate
index 9355965..0a6e981 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 = []
@@ -48,7 +58,8 @@ 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:]+$")
 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("[a-zA-Z0-9\.]+\.debian\.org$")
+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(":")
 
@@ -338,6 +349,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
 
@@ -385,7 +398,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)
@@ -426,6 +452,7 @@ 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
      
       # Fetch all the users
       global PasswdAttrs
@@ -433,6 +460,8 @@ def GenGroup(File):
       # Sort them into a list of groups having a set of users
       for x in PasswdAttrs:
          uid = GetAttr(x, "uid")
       # Sort them into a list of groups having a set of users
       for x in PasswdAttrs:
          uid = GetAttr(x, "uid")
+         if 'gidNumber' in x[1]:
+            GroupHasPrimaryMembers[ int(x[1]["gidNumber"][0]) ] = True
          if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
             continue
          if x[1].has_key("supplementaryGid") == 0:
          if x[1].has_key("uidNumber") == 0 or not IsInGroup(x):
             continue
          if x[1].has_key("supplementaryGid") == 0:
@@ -446,11 +475,10 @@ def GenGroup(File):
       # Output the group file.
       J = 0
       for x in GroupMap.keys():
       # Output the group file.
       J = 0
       for x in GroupMap.keys():
-
-         if len(GroupMap[x]) == 0:
+         if GroupIDMap.has_key(x) == 0:
             continue
 
             continue
 
-         if GroupIDMap.has_key(x) == 0:
+         if len(GroupMap[x]) == 0 and GroupIDMap[x] not in GroupHasPrimaryMembers:
             continue
 
          grouprevmap[GroupIDMap[x]] = x
             continue
 
          grouprevmap[GroupIDMap[x]] = x
@@ -519,7 +547,7 @@ def GenForward(File):
       raise
    Done(File, F, None)
 
       raise
    Done(File, F, None)
 
-def GenCDB(File, Users, Key):
+def GenCDB(File, Users, key):
    Fdb = None
    try:
       OldMask = os.umask(0022)
    Fdb = None
    try:
       OldMask = os.umask(0022)
@@ -528,11 +556,11 @@ def GenCDB(File, Users, Key):
 
       # Write out the email address for each user
       for x in Users:
 
       # Write out the email address for each user
       for x in Users:
-         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))
+         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.
@@ -553,10 +581,10 @@ def GenMarkers(File):
      
       # Write out the position for each user
       for x in PasswdAttrs:
      
       # Write out the position for each user
       for x in PasswdAttrs:
-         if x[1].has_key("latitude") == 0 or x[1].has_key("longitude") == 0:
-            continue
+         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:
@@ -579,15 +607,11 @@ def GenPrivate(File):
      
       # Write out the position for each user
       for x in DebianDDUsers:
      
       # Write out the position for each user
       for x in DebianDDUsers:
-         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
-     
+         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:
@@ -611,22 +635,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)
@@ -643,19 +657,11 @@ def GenMailDisable(File):
       global PasswdAttrs
      
       for x in PasswdAttrs:
       global PasswdAttrs
      
       for x in PasswdAttrs:
-         Reason = None
-     
-         if x[1].has_key("mailDisableMessage"):
-            Reason = GetAttr(x, "mailDisableMessage")
-         else:
-            continue
-     
-         try:
-            Line = "%s: %s"%(GetAttr(x, "uid"), Reason)
-            Line = Sanitize(Line) + "\n"
-            F.write(Line)
-         except:
-            pass
+         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:
@@ -664,7 +670,7 @@ 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")
    F = None
    try:
       F = open(File + ".tmp", "w")
@@ -673,21 +679,13 @@ def GenMailBool(File, Key):
       global PasswdAttrs
      
       for x in PasswdAttrs:
       global PasswdAttrs
      
       for x in PasswdAttrs:
-         Reason = None
-     
-         if x[1].has_key(Key) == 0:
-            continue
-     
-         if GetAttr(x, Key) != "TRUE":
-            continue
-     
-         try:
-            Line = "%s"%(GetAttr(x, "uid"))
-            Line = Sanitize(Line) + "\n"
-            F.write(Line)
-         except:
-            pass
-  
+         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)
@@ -695,7 +693,7 @@ 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")
    F = None
    try:
       F = open(File + ".tmp", "w")
@@ -703,37 +701,20 @@ def GenMailList(File, Key):
       # Fetch all the users
       global PasswdAttrs
      
       # Fetch all the users
       global PasswdAttrs
      
+      if key == "mailWhitelist": validregex = re.compile('^[-\w.]+(/[\d]+)?$')
+      else:                      validregex = re.compile('^[-\w.]+$')
+
       for x in PasswdAttrs:
       for x in PasswdAttrs:
-         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"
-     
-            if Line != None:
-               Line = Sanitize(Line) + "\n"
-               F.write(Line)
-         except:
-            pass
-  
+         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)
@@ -756,29 +737,9 @@ def GenDNS(File):
    try:
       F = open(File + ".tmp", "w")
      
    try:
       F = open(File + ".tmp", "w")
      
-#      global HostAttrs
-#
-#      for x in HostAttrs:
-#         if x[1].has_key("hostname") == 0 or \
-#            x[1].has_key("architecture") == 0 or\
-#            x[1].has_key("sshRSAHostKey") == 0:
-#            continue
-#
-#         if IsDebianHost.match(GetAttr(x, "hostname")) is not None:
-#            continue
-#
-#         DNSInfo = ExtractDNSInfo(x)
-#         start = True
-#         for Line in DNSInfo:
-#            if start == True:
-#               Line = "%s.\t%s" % (GetAttr(x, "hostname"), Line)
-#               start = False
-#            else:
-#               Line = "\t\t\t%s" % (Line)
-#            F.write(Line + "\n")
-
       # Fetch all the users
       global PasswdAttrs
       # 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:
@@ -804,11 +765,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)
@@ -838,25 +801,26 @@ def ExtractDNSInfo(x):
          else:
             DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
 
          else:
             DNSInfo.append("%sIN\tA\t%s" % (TTLprefix, I))
 
-   Host = GetAttr(x, "hostname")
-   Arch = GetAttr(x, "architecture")
    Algorithm = None
 
    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()
-      DNSInfo.append("%sIN\tSSHFP\t%u 1 %s" % (TTLprefix, Algorithm, Fingerprint))
+   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))
 
 
-   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 '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"]:
 
    if x[1].has_key("mXRecord"):
       for I in x[1]["mXRecord"]:
@@ -874,9 +838,7 @@ def GenZoneRecords(File):
       global HostAttrs
 
       for x in HostAttrs:
       global HostAttrs
 
       for x in HostAttrs:
-         if x[1].has_key("hostname") == 0 or \
-            x[1].has_key("architecture") == 0 or\
-            x[1].has_key("sshRSAHostKey") == 0:
+         if x[1].has_key("hostname") == 0:
             continue
 
          if IsDebianHost.match(GetAttr(x, "hostname")) is None:
             continue
 
          if IsDebianHost.match(GetAttr(x, "hostname")) is None:
@@ -893,6 +855,29 @@ def GenZoneRecords(File):
 
             F.write(Line + "\n")
 
 
             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)
@@ -996,7 +981,6 @@ def GenSSHKnown(File, mode=None):
                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)
                if 'sshdistAuthKeysHost' in x[1]:
                   hosts += x[1]['sshdistAuthKeysHost']
                Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,from="%s" %s' % (Host, ",".join(hosts), I)
-               #Line = 'command="rsync --server --sender -pr . /var/cache/userdir-ldap/hosts/%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s' % (Host,I)
             else:
                Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
             Line = Sanitize(Line) + "\n"
             else:
                Line = "%s %s" %(",".join(HostNames + HostToIP(x, False)), I)
             Line = Sanitize(Line) + "\n"
@@ -1046,9 +1030,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
@@ -1067,7 +1056,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",
@@ -1093,6 +1082,10 @@ if HostAttrs == None:
 
 HostAttrs.sort(lambda x, y: cmp((GetAttr(x, "hostname")).lower(), (GetAttr(y, "hostname")).lower()))
 
 
 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")
 # Generate global things
 GlobalDir = GenerateDir + "/"
 GenDisabledAccounts(GlobalDir + "disabled-accounts")