Merge sshkeys branch from Stephen and Mark
authorJoerg Jaspert <joerg@debian.org>
Fri, 16 May 2008 21:00:43 +0000 (23:00 +0200)
committerJoerg Jaspert <joerg@debian.org>
Fri, 16 May 2008 21:00:43 +0000 (23:00 +0200)
1  2 
debian/changelog
ud-generate
ud-replicate

diff --combined debian/changelog
@@@ -1,11 -1,12 +1,18 @@@
- userdir-ldap (0.3.24) UNRELEASED; urgency=low
+ userdir-ldap (0.3.23+common1) unstable; urgency=low
  
-  -- Andreas Barth <aba@not.so.argh.org>  Fri, 16 May 2008 17:35:19 +0000
++  [ Andreas Barth ]
 +  * Add compatibility to dchroot-dsa to ud-replicate.
 +  * Add (disabled) generation of authorized_keys suiteable for sshdist.
 +  * Add performance optimization by caching IP adresses in ud-generate
 +    (as a precondition for automatically adding aliases)
 +
+   [ Stephen Gran ]
+   * ud-replicate: handle individual ssh keys
+   [ Mark Hymers ]
+   * ud-generate: handle individual ssh keys
+  -- Mark Hymers <mhy@debian.org>  Wed, 14 May 2008 22:09:22 +0100
  
  userdir-ldap (0.3.23) unstable; urgency=low
  
diff --combined ud-generate
@@@ -7,7 -7,7 +7,8 @@@
  #   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 Andreas Barth <aba@not.so.argh.org>
+ #   Copyright (c) 2008 Mark Hymers <mhy@debian.org>
  #
  #   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
@@@ -23,7 -23,7 +24,7 @@@
  #   along with this program; if not, write to the Free Software
  #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  
- import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil
+ import string, re, time, ldap, getopt, sys, os, pwd, posix, socket, base64, sha, shutil, errno, tarfile, grp
  from userdir_ldap import *;
  
  global Allowed;
@@@ -40,6 -40,24 +41,24 @@@ DNSZone = ".debian.net
  Keyrings = [ "/org/keyring.debian.org/keyrings/debian-keyring.gpg",
               "/org/keyring.debian.org/keyrings/debian-keyring.pgp" ]
  
+ def safe_makedirs(dir):
+     try:
+         os.makedirs(dir)
+     except OSError, e:
+         if e.errno == errno.EEXIST:
+             pass
+         else:
+             raise e
+ def safe_rmtree(dir):
+     try:
+         shutil.rmtree(dir)
+     except OSError, e:
+         if e.errno == errno.ENOENT:
+             pass
+         else:
+             raise e
  def Sanitize(Str):
    return Str.translate(string.maketrans("\n\r\t","$$$"))
  
@@@ -97,6 -115,7 +116,7 @@@ def GenPasswd(l,File,HomePrefix,PwdMark
    try:
     F = open(File + ".tdb.tmp","w");
  
+    userlist = {}
     # Fetch all the users
     global PasswdAttrs;
     if PasswdAttrs == None:
        if len(GetAttr(x,"gecos")) > 100 or len(GetAttr(x,"loginShell")) > 50:
           continue;
  
+       userlist[GetAttr(x, "uid")] = GetAttr(x, "gidNumber")
        Line = "%s:%s:%s:%s:%s:%s%s:%s" % (GetAttr(x,"uid"),\
                PwdMarker,\
                GetAttr(x,"uidNumber"),GetAttr(x,"gidNumber"),\
     raise;
    Done(File,None,F);
  
+   # Return the list of users so we know which keys to export
+   return userlist
  # Generate the shadow list
  def GenShadow(l,File):
    F = None;
    Done(File,None,F);
  
  # Generate the shadow list
- def GenSSHShadow(l,File):
-   F = None;
-   try:
-    OldMask = os.umask(0077);
-    F = open(File + ".tmp","w",0600);
-    os.umask(OldMask);
+ def GenSSHShadow(l,masterFileName):
     # Fetch all the users
+    singlefile = None
+    userfiles = []
+    # Depending on config, we write out either a single file,
+    # multiple files, or both
+    if SingleSSHFile:
+        try:
+            OldMask = os.umask(0077);
+            masterFile = open(masterFileName + ".tmp","w",0600);
+            os.umask(OldMask);
+        except IOError:
+            Die(masterFileName,masterFile,None)
+            raise
     global PasswdAttrs;
     if PasswdAttrs == None:
        raise "No Users";
  
+    # If we're going to be dealing with multiple keys, empty the
+    # directory before we start to avoid old keys hanging around
+    if MultipleSSHFiles:
+       safe_rmtree(os.path.join(GlobalDir, 'userkeys'))
+       safe_makedirs(os.path.join(GlobalDir, 'userkeys'))
+       
     for x in PasswdAttrs:
        # If the account is locked, do not write it.
        # This is a partial stop-gap. The ssh also needs to change this
        if x[1].has_key("uidNumber") == 0 or \
           x[1].has_key("sshRSAAuthKey") == 0:
           continue;
-       for I in x[1]["sshRSAAuthKey"]:
-          User = GetAttr(x,"uid");
-          Line = "%s: %s" %(User,I);
-          Line = Sanitize(Line) + "\n";
-          F.write(Line);
-   # Oops, something unspeakable happened.
-   except:
-    Die(File,F,None);
-    raise;
-   Done(File,F,None);
+       User = GetAttr(x,"uid");
+       F = None;
+       try:
+          if MultipleSSHFiles:
+              OldMask = os.umask(0077);
+              File = os.path.join(GlobalDir, 'userkeys', User)
+              F = open(File + ".tmp","w",0600);
+              os.umask(OldMask);
+          for I in x[1]["sshRSAAuthKey"]:
+              if MultipleSSHFiles:
+                  MultipleLine = "%s" % I
+                  MultipleLine = Sanitize(MultipleLine) + "\n"
+                  F.write(MultipleLine)
+              if SingleSSHFile:
+                  SingleLine = "%s: %s" % (User, I)
+                  SingleLine = Sanitize(SingleLine) + "\n"
+                  masterFile.write(SingleLine)
+          if MultipleSSHFiles:
+              Done(File,F,None);
+              userfiles.append(os.path.basename(File))
+       # Oops, something unspeakable happened.
+       except IOError:
+           Die(File,F,None)
+           Die(masterFileName,masterFile,None)
+           raise;
+    if SingleSSHFile:
+        Done(masterFileName,masterFile,None)
+        singlefile = os.path.basename(masterFileName)
+    return singlefile, userfiles
  
  # Generate the group list
  def GenGroup(l,File):
+   grouprevmap = {}
    F = None;
    try:
     F = open(File + ".tdb.tmp","w");
     # 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]);
     raise;
    Done(File,None,F);
  
+   return grouprevmap
  # Generate the email forwarding list
  def GenForward(l,File):
    F = None;
@@@ -702,26 -767,8 +768,26 @@@ def GenBSMTP(l,File,HomePrefix)
     raise;
    Done(File,F,None);
  
 +# cache IP adresses
 +HostToIPCache = {}
 +def HostToIP(Host):
 +    global HostToIPCache
 +    if not Host in HostToIPCache:
 +        IPAdressesT = None
 +        try:
 +            IPAdressesT = list(set([ (a[0],a[4][0]) for a in socket.getaddrinfo(Host, None)]))
 +        except socket.gaierror, (code):
 +            if code[0] != -2: raise
 +        IPAdresses = []
 +        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]
 +
 +
  # Generate the ssh known hosts file
 -def GenSSHKnown(l,File):
 +def GenSSHKnown(l,File,mode=None):
    F = None;
    try:
     OldMask = os.umask(0022);
        SHost = Host.find(".")
        if SHost != None: HostNames += [Host[0:SHost]]
  
 -      IPAdressesT = None
 -      IPAdresses = []
 -      # get IP adresses back as "proto adress" to distinguish between v4 and v6
 -      try:
 -         IPAdressesT = set([ (a[0],a[4][0]) for a in socket.getaddrinfo(Host, None)])
 -      except:
 -         if code[0] != -2: raise
 -      for addr in IPAdressesT:
 -         if addr[0] == socket.AF_INET: IPAdresses += [addr[1], "::ffff:"+addr[1]]
 -       else: IPAdresses += [addr[1]]
 -
        for I in x[1]["sshRSAHostKey"]:
 -         Line = "%s %s" %(",".join(HostNames + IPAdresses), I);
 +         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.
@@@ -829,13 -883,12 +895,13 @@@ else
  
  # Generate global things
  GlobalDir = GenerateDir+"/";
- GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
SSHGlobal, SSHFiles = GenSSHShadow(l,GlobalDir+"ssh-rsa-shadow");
  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");
@@@ -879,23 -932,74 +945,74 @@@ while(1)
       Allowed = None
     CurrentHost = Split[0];
  
-    DoLink(GlobalDir,OutDir,"ssh-rsa-shadow");
+    # If we're using a single SSH file, deal with it
+    if SSHGlobal is not None:
+       DoLink(GlobalDir, OutDir, SSHGlobal)
     DoLink(GlobalDir,OutDir,"debianhosts");
     DoLink(GlobalDir,OutDir,"ssh_known_hosts");
     DoLink(GlobalDir,OutDir,"disabled-accounts")
  
     sys.stdout.flush();
     if ExtraList.has_key("[NOPASSWD]"):
-       GenPasswd(l,OutDir+"passwd",Split[1], "*");
+       userlist = GenPasswd(l,OutDir+"passwd",Split[1], "*");
     else:
-       GenPasswd(l,OutDir+"passwd",Split[1], "x");
+       userlist = GenPasswd(l,OutDir+"passwd",Split[1], "x");
     sys.stdout.flush();
-    GenGroup(l,OutDir+"group");
+    grouprevmap = GenGroup(l,OutDir+"group");
     if ExtraList.has_key("[UNTRUSTED]"):
        continue;
     if not ExtraList.has_key("[NOPASSWD]"):
       GenShadow(l,OutDir+"shadow");
-       
+    # Now we know who we're allowing on the machine, export
+    # the relevant ssh keys
+    if MultipleSSHFiles:
+       tf = tarfile.open(name=os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost), mode='w:gz')
+       for f in userlist.keys():
+         if f not in SSHFiles:
+             continue
+         # If we're not exporting their primary group, don't export 
+         # the key and warn
+         grname = None
+         if userlist[f] in grouprevmap.keys():
+             grname = grouprevmap[userlist[f]]
+         else:
+             try:
+                 if int(userlist[f]) <= 100:
+                     # In these cases, look it up in the normal way so we
+                     # deal with cases where, for instance, users are in group
+                     # users as their primary group.
+                     grname = grp.getgrgid(int(userlist[f]))[0]
+             except Exception, e:
+                 pass
+         if grname is None:
+             print "User %s is supposed to have their key exported to host %s but their primary group (gid: %s) isn't in LDAP" % (f, CurrentHost, userlist[f])
+             continue
+         to = tf.gettarinfo(os.path.join(GlobalDir, '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
+         # be owned by root but group nobody.  This deals with
+         # the bloody obscure case where the group fails to exist
+         # whilst the user does (in which case we want to avoid
+         # ending up with a file which is owned user:root to avoid
+         # a fairly obvious attack vector)
+         to.uid = 0
+         to.gid = 65534
+         # Using the username / groupname fields avoids any need
+         # to give a shit^W^W^Wcare about the UIDoffset stuff.
+         to.uname = f
+         to.gname = grname
+         to.mode  = 0600
+         tf.addfile(to, file(os.path.join(GlobalDir, 'userkeys', f)))
+       tf.close()
+       os.rename(os.path.join(GlobalDir, 'ssh-keys-%s.tar.gz' % CurrentHost),
+                 os.path.join(OutDir, 'ssh-keys.tar.gz'))
     # Link in global things   
     DoLink(GlobalDir,OutDir,"markers");
     DoLink(GlobalDir,OutDir,"mail-forward.cdb");
diff --combined ud-replicate
@@@ -4,6 -4,7 +4,7 @@@
  #   Copyright (c) 2002-2003,2006  Ryan Murray <rmurray@debian.org>
  #   Copyright (c) 2004-2005  Joey Schulze <joey@infodrom.org>
  #   Copyright (c) 2008 Peter Palfrader <peter@palfrader.org>
+ #   Copyright (©) 2008 Stephen Gran <sgran@debian.org>
  #
  #   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
@@@ -30,6 -31,14 +31,14 @@@ els
      verbose=-v
  fi
  
+ tempdir=''
+ cleanup ()
+ {
+   rm -f lock
+   rm -rf $tempdir
+ }
  PATH=/sbin:/usr/sbin:/bin:/usr/bin
  export PATH
  HOST=`hostname -f`
@@@ -38,7 -47,7 +47,7 @@@ LOCALSYNCON=`ud-config localsyncon`
  cd /tmp/
  cd /var/lib/misc || cd /var/state/glibc/ || cd /var/db/
  lockfile -r 1 -l 3600 lock
- trap "rm -f lock" exit
+ trap cleanup exit
  
  case $HOST in
  $LOCALSYNCON)
@@@ -68,13 -77,19 +77,24 @@@ don
  ln -sf `pwd -P`/ssh-rsa-shadow /etc/ssh
  ln -sf `pwd -P`/ssh_known_hosts /etc/ssh
  
+ if [ -e ${HOST}/ssh-keys.tar.gz ]; then
+   export TMPDIR='/tmp/' 
+   tempdir=$(mktemp -d)
+   old=$(pwd -P)
+   cd $tempdir && tar -xf ${old}/${HOST}/ssh-keys.tar.gz
+   cd $old
+   mkdir userkeys 2> /dev/null || true
+   chmod 755 $tempdir
+   rsync -a --delete-after $tempdir/ userkeys/
+ fi
  if [ -x /usr/bin/dchroot ]; then
        CHROOTS=`dchroot --listpaths`
 +fi
 +if [ -x /usr/bin/dchroot-dsa ]; then
 +        CHROOTS=$(dchroot-dsa -i | grep Location | awk '{print $2}')
 +fi
 +if [ -n "$CHROOTS" ]; then
        for c in $CHROOTS; do
                if [ -x "$c/usr/bin/makedb" ]
                then