diff --git a/tmcd/common/libsetup.pm b/tmcd/common/libsetup.pm
index fc51cb269780aef92f89d0fc580a38363c6acaed..382d92180f579aa5fbfebdc4426dfbefe8f8f95b 100644
--- a/tmcd/common/libsetup.pm
+++ b/tmcd/common/libsetup.pm
@@ -79,6 +79,7 @@ sub TMNICKNAME()	{ "$SETUPDIR/nickname"; }
 sub FINDIF()		{ "$SETUPDIR/findif"; }
 sub HOSTSFILE()		{ "/etc/hosts"; }
 sub TMMOUNTDB()		{ "$SETUPDIR/mountdb"; }
+sub TMSFSMOUNTDB()	{ "$SETUPDIR/sfsmountdb"; }
 sub TMROUTECONFIG()     { ($vnodedir ? $vnodedir : $SETUPDIR) . "/rc.route";}
 sub TMTRAFFICCONFIG()	{ ($vnodedir ? $vnodedir : $SETUPDIR) . "/rc.traffic";}
 sub TMTUNNELCONFIG()	{ ($vnodedir ? $vnodedir : $SETUPDIR) . "/rc.tunnel";}
@@ -86,13 +87,21 @@ sub TMVTUNDCONFIG()	{ ($vnodedir ? $vnodedir : $SETUPDIR) . "/vtund.conf";}
 sub TMPASSDB()		{ "$SETUPDIR/passdb"; }
 sub TMGROUPDB()		{ "$SETUPDIR/groupdb"; }
 
+#
+# Whether or not to use SFS (the self-certifying file system).  If this
+# is 0, fall back to NFS.  Note that it doesn't hurt to set this to 1
+# even if TMCD is not serving out SFS mounts, or if this node is not
+# running SFS.  It'll deal and fall back to NFS.
+#
+my $USESFS		= 1;
+
 #
 # This is the VERSION. We send it through to tmcd so it knows what version
 # responses this file is expecting.
 #
 # BE SURE TO BUMP THIS AS INCOMPATIBILE CHANGES TO TMCD ARE MADE!
 #
-sub TMCD_VERSION()	{ 5; };
+sub TMCD_VERSION()	{ 6; };
 
 #
 # These are the TMCC commands. 
@@ -117,6 +126,8 @@ sub TMCCCMD_TUNNEL()	{ "tunnels"; }
 sub TMCCCMD_NSECONFIGS(){ "nseconfigs"; }
 sub TMCCCMD_VNODELIST() { "vnodelist"; }
 sub TMCCCMD_ISALIVE()   { "isalive"; }
+sub TMCCCMD_SFSHOSTID()	{ "sfshostid"; }
+sub TMCCCMD_SFSMOUNTS() { "sfsmounts"; }
 
 #
 # Some things never change.
@@ -129,7 +140,8 @@ my $VTUND       = "/usr/local/sbin/vtund";
 # This is a debugging thing for my home network.
 # 
 #my $NODE	= "-p 7778 REDIRECT=192.168.100.1";
-$NODE		= "";
+my $NODE	= "-p 7779";
+#$NODE		= "";
 
 # Locals
 my $pid		= "";
@@ -324,13 +336,21 @@ sub domounts()
     my %MDB;
     my %mounts;
     my %deletes;
-    
-    $TM = OPENTMCC(TMCCCMD_MOUNTS);
+    my %sfsmounts;
+    my %sfsdeletes;
+
+    $TM = OPENTMCC(TMCCCMD_MOUNTS, "USESFS=$USESFS");
 
     while (<$TM>) {
-	if ($_ =~ /REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
+	if ($_ =~ /^REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
 	    $mounts{$1} = $2;
 	}
+	elsif ($_ =~ /^SFS REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
+	    $sfsmounts{$1} = $2;
+	}
+	else {
+	    warn "*** WARNING: Malformed mount information: $_\n";
+	}
     }
     CLOSETMCC($TM);
     
@@ -419,6 +439,269 @@ sub domounts()
     # Write the DB back out!
     dbmclose(%MDB);
 
+    #
+    # Now, do basically the same thing over again, but this time for
+    # SFS mounted stuff
+    #
+
+    if (scalar(%sfsmounts)) {
+	dbmopen(%MDB, TMSFSMOUNTDB, 0660);
+	
+	#
+	# First symlink all the mounts we are told to. For each one
+	# that is not currently symlinked, and can be, add it to the
+	# DB.
+	#
+	while (($remote, $local) = each %sfsmounts) {
+	    if (-l $local) {
+		if (readlink($local) eq ("/sfs/" . $remote)) {
+		    $MDB{$remote} = $local;
+		    next;
+		}
+		if (readlink($local) ne ("/sfs/" . $remote)) {
+		    print STDOUT "  Unlinking incorrect symlink $local\n";
+		    if ( ! unlink($local)) {
+			warn "*** WARNING: Could not unlink $local: $!\n";
+			next;
+		    }
+		}
+	    }
+	    
+	    $dir = $local;
+	    $dir =~ s/(.*)\/[^\/]*$/$1/;
+	    if (! -e $dir) {
+		print STDOUT "  Making directory $dir\n";
+		if (! os_mkdir($dir, 755)) {
+		    warn "*** WARNING: Could not make directory $local: $!\n";
+		    next;
+		}
+	    }
+	    print STDOUT "  Symlinking $remote on $local\n";
+	    if (! symlink("/sfs/" . $remote . "/", $local)) {
+		warn "*** WARNING: Could not make symlink $local: $!\n";
+		next;
+	    }
+	    
+	    $MDB{$remote} = $local;
+	}
+
+	#
+	# Now delete the ones that we symlinked previously, but are
+	# now no longer in the mount set (as told to us by the TMCD).
+	# Note, we cannot delete them directly from MDB since that
+	# would mess up the foreach loop, so just stick them in temp
+	# and postpass it.
+	#
+	while (($remote, $local) = each %MDB) {
+	    if (defined($sfsmounts{$remote})) {
+		next;
+	    }
+	    
+	    if (! -e $local) {
+		$sfsdeletes{$remote} = $local;
+		next;
+	    }
+	    
+	    print STDOUT "  Deleting symlink $local\n";
+	    if (! unlink($local)) {
+		warn "*** WARNING: Could not delete $local: $!\n";
+		next;
+	    }
+	    
+	    #
+	    # Only delete from set if we can actually unlink it.  This way
+	    # we can retry it later (or next time).
+	    #
+	    $sfsdeletes{$remote} = $local;
+	}
+	while (($remote, $local) = each %sfsdeletes) {
+	    delete($MDB{$remote});
+	}
+
+	# Write the DB back out!
+	dbmclose(%MDB);	
+    }
+    else {
+	# There were no SFS mounts reported, so disable SFS
+	$usesfs = 0;
+    }
+
+    return 0;
+}
+
+#
+# Do SFS hostid setup.
+# Creates an SFS host key for this node, if it doesn't already exist,
+# and sends it to TMCD
+#
+sub dosfshostid ()
+{
+    my $TM;
+    my $myhostid;
+
+    # Do I already have a host key?
+#      if (-e "/etc/sfs/sfs_host_key") {
+#  	print STDOUT "  This node already has a host key\n";
+#      }
+#      else {
+#          # Create my host key
+#  	if (! -e "/etc/sfs") {
+#  	    if (! os_mkdir("/etc/sfs", 0755)) {
+#  		warn "*** WARNING: Could not make directory /etc/sfs: $!\n";
+#  	    }
+#  	}
+	
+#  	if (! -e "/etc/sfs/sfs_host_key") {
+#  	    print STDOUT "  Creating SFS host key\n";
+#  	    if (system("sfskey gen -KPn $vname.$eid.$pid ".
+#  		       "/etc/sfs/sfs_host_key")) {
+#  		warn "*** WARNING: Could not generate SFS host key: $!\n";
+#  		$USESFS = 0;
+#  		return 1;
+#  	    }
+#  	}
+#      }
+    if (! -e "/etc/sfs/sfs_host_key") {
+	warn "*** This node does not have a host key, skipping SFS stuff\n";
+	$USESFS = 0;
+	return 1;
+    }
+
+    # Start SFS server and client (this has to be done now, as opposed
+    # to in an rc script, because the hostkey has to exist)
+#      if (system($SFSSD)) {
+#  	warn "*** Failed to start sfssd\n";
+#  	$USESFS = 0;
+#  	return 1;
+#      }
+#      if (system($SFSCD)) {
+#  	warn "*** Failed to start sfscd\n";
+#  	$USESFS = 0;
+#  	return 1;
+#      }
+
+    # Give hostid to TMCD
+    # IAMHERE: sfssd needs to be running for this call to succeed
+    #   Directory needs to exist on fs/proj
+    open(SFSKEY, "sfskey hostid - |")
+	or die "Cannot start sfskey";
+    $myhostid = <SFSKEY>;
+    close(SFSKEY);
+    if (defined($myhostid)) {
+	if ( $myhostid =~ /^([-\.\w_]*:[a-z0-9]*)$/ ) {
+	    $myhostid = $1;
+	    print STDOUT "  Hostid: $myhostid\n";
+	    RUNTMCC(TMCCCMD_SFSHOSTID, "$myhostid");
+	}
+	else {
+	    warn "*** WARNING: Invalid hostid\n";
+	}
+    }
+    else {
+	warn "*** WARNING: Could not retrieve this node's hostid (is sfssd running?)\n";
+	$USESFS = 0;
+    }
+
+    return 0;
+}
+
+#
+# Create SFS "mounts" (which are really just symlinks).  The remote side
+# of the mount is relative to /sfs and includes the server hostname, the
+# server's hostid, and the path on the server.  The local side is simply
+# the path relative to / to symlink to the remote sfs directory.  This
+# closely follows domounts()
+#
+sub dosfsmounts()
+{
+    my $TM;
+    my %MBD;
+    my %mounts;
+    my %deletes;
+
+    die("*** Don't call dosfsmounts!");
+    
+    $TM = OPENTMCC(TMCCCMD_SFSMOUNTS);
+
+    while (<$TM>) {
+	if ($_ =~ /REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
+	    $mounts{$1} = $2;
+	}
+    }
+    CLOSETMCC($TM);
+
+    dbmopen(%MDB, TMSFSMOUNTDB, 0660);
+    
+    #
+    # First symlink all the mounts we are told to. For each one that is
+    # not currently symlinked, and can be, add it to the DB.
+    #
+    while (($remote, $local) = each %mounts) {
+	if (-l $local && readlink($local) eq ("/sfs/" . $remote)) {
+	    $MDB{$remote} = $local;
+	    next;
+	}
+	if ( ! -l $local ) {
+	    print STDOUT "  Unlinking incorrect symlink $local\n";
+	    if ( ! unlink($local)) {
+		warn "*** WARNING: Could not unlink $local: $!\n";
+		next;
+	    }
+	}
+
+	$dir = $local;
+	$dir =~ s/(.*)\/[^\/]*$/$1/;
+	if (! -e $dir) {
+	    print STDOUT "  Making directory $dir\n";
+	    if (! os_mkdir($dir, 755)) {
+		warn "*** WARNING: Could not make directory $local: $!\n";
+		next;
+	    }
+	}
+	print STDOUT "  Symlinking $remote on $local\n";
+	if (! symlink("/sfs/" . $remote, $local)) {
+	    warn "*** WARNING: Could not make symlink $local: $!\n";
+	    next;
+	}
+
+	$MDB{$remote} = $local;
+    }
+
+    #
+    # Now delete the ones that we symlinked previously, but are now no
+    # longer in the mount set (as told to us by the TMCD). Note, we
+    # cannot delete them directly from MDB since that would mess up the
+    # foreach loop, so just stick them in temp and postpass it.
+    #
+    while (($remote, $local) = each %MDB) {
+	if (defined($mounts{$remote})) {
+	    next;
+	}
+
+	if (! -e $local) {
+	    $deletes{$remote} = $local;
+	    next;
+	}
+
+	print STDOUT "  Deleting symlink $local\n";
+	if (! unlink($local)) {
+	    warn "*** WARNING: Could not delete $local: $!\n";
+	    next;
+	}
+
+	#
+	# Only delete from set if we can actually unlink it.  This way
+	# we can retry it later (or next time).
+	#
+	$deletes{$remote} = $local;
+    }
+    while (($remote, $local) = each %deletes) {
+	delete($MDB{$remote});
+    }
+
+    # Write the DB back out!
+    dbmclose(%MDB);
+
     return 0;
 }
 
@@ -696,6 +979,7 @@ sub doaccounts()
     my %newaccounts = ();
     my %newgroups   = ();
     my %pubkeys     = ();
+    my @sfskeys     = ();
     my %deletes     = ();
     my %lastmod     = ();
     my %PWDDB;
@@ -740,6 +1024,13 @@ sub doaccounts()
 	    push(@{$pubkeys{$1}}, $2);
 	    next;
 	}
+	elsif ($_ =~ /^SFSKEY KEY="(.*)"/) {
+	    #
+	    # SFS key goes into the array.
+	    #
+	    push(@sfskeys, $1);
+	    next;
+	}
 	else {
 	    warn "*** WARNING: Bad accounts line: $_\n";
 	}
@@ -966,7 +1257,7 @@ sub doaccounts()
 		    next;
 		}
 	    }
-		
+	    
 	    if (!open(AUTHKEYS, "> $sshdir/authorized_keys.new")) {
 		warn("*** WARNING: Could not open $sshdir/keys.new: $!\n");
 		next;
@@ -1016,6 +1307,78 @@ sub doaccounts()
     # Write the DB back out!
     dbmclose(%PWDDB);
 
+    #
+    # Create sfs_users file and populate it with public SFS keys
+    #
+    do {
+	if (!open(SFSKEYS, "> /etc/sfs/sfs_users.new")) {
+	    warn("*** WARNING: Could not open /etc/sfs/sfs_users.new: $!\n");
+	    next;
+	}
+
+	print SFSKEYS "#\n";
+	print SFSKEYS "# DO NOT EDIT! This file auto generated by ".
+	    "Emulab.Net account software.\n";
+	print SFSKEYS "#\n";
+	print SFSKEYS "# Please use the web interface to edit your ".
+	    "SFS public key list.\n";
+	print SFSKEYS "#\n";
+	foreach my $key (@sfskeys) {
+	    print SFSKEYS "$key\n";
+	}
+	close(SFSKEYS);
+
+	# Because sfs_users only contains public keys, sfs_users.pub is
+	# exactly the same
+	if (system("cp -p -f /etc/sfs/sfs_users.new ".
+		   "/etc/sfs/sfs_users.pub.new")) {
+	    warn("*** Could not copy /etc/sfs/sfs_users.new to ".
+		 "sfs_users.pub.new: $!\n");
+	    next;
+	}
+	
+	if (!chown(0, 0, "/etc/sfs/sfs_users.new")) {
+	    warn("*** WARNING: Could not chown /etc/sfs/sfs_users.new: $!\n");
+	    next;
+	}
+	if (!chmod(0600, "/etc/sfs/sfs_users.new")) {
+	    warn("*** WARNING: Could not chmod /etc/sfs/sfs_users.new: $!\n");
+	    next;
+	}
+	
+	if (!chown(0, 0, "/etc/sfs/sfs_users.pub.new")) {
+	    warn("*** WARNING: Could not chown /etc/sfs/sfs_users.pub.new: $!\n");
+	    next;
+	}
+	if (!chmod(0644, "/etc/sfs/sfs_users.pub.new")) {
+	    warn("*** WARNING: Could not chmod /etc/sfs/sfs_users.pub.new: $!\n");
+	    next;
+	}
+
+	# Save off old key files and move in new ones
+	foreach my $keyfile ("/etc/sfs/sfs_users",
+			     "/etc/sfs/sfs_users.pub") {
+	    if (-e $keyfile) {
+		if (system("cp -p -f $keyfile $keyfile.old")) {
+		    warn("*** Could not save off $keyfile: $!\n");
+		    next;
+		}
+		if (!chown(0, 0, "$keyfile")) {
+		    warn("*** Could not chown $keyfile: $!\n");
+		}
+		if (!chmod(0600, "$keyfile")) {
+		    warn("*** Could not chmod $keyfile: $!\n");
+		}
+	    }
+	    if (system("mv -f $keyfile.new $keyfile")) {
+		warn("*** Could not mv $keyfile: ~!\n");
+	    }
+	}
+	
+	# The do-while is an easy way out in case of errors
+    }
+    while 0;
+    
     return 0;
 }
 
@@ -1469,8 +1832,17 @@ sub bootsetup()
     #
     create_nicknames();
 
+    if ($USESFS && ! MFS()) {
+	#
+	# Setup SFS hostid.
+	#
+	print STDOUT "Creating node SFS hostid ... \n";
+	dosfshostid();
+    }
+
     #
-    # Mount the project and user directories
+    # Mount the project and user directories and symlink SFS "mounted"
+    # directories
     #
     print STDOUT "Mounting project and home directories ... \n";
     domounts();
@@ -1563,8 +1935,17 @@ sub nodeupdate()
 	return 0;
     }
 
+    if ($USESFS && ! MFS()) {
+	#
+	# Setup SFS hostid.
+	#
+	print STDOUT "Creating node SFS hostid ... \n";
+	dosfshostid();
+    }
+
     #
-    # Mount the project and user directories
+    # Mount the project and user directories and symlink SFS "mounted"
+    # directories
     #
     print STDOUT "Mounting project and home directories ... \n";
     domounts();
diff --git a/tmcd/decls.h b/tmcd/decls.h
index 864959d4f2a5c4d090e64d410183faf1caf34300..a99d03862c003c20fbd9921bdb50fef7ec918c0a 100644
--- a/tmcd/decls.h
+++ b/tmcd/decls.h
@@ -24,4 +24,4 @@
  * NB: See ron/libsetup.pm. That is version 4! I'll merge that in. 
  */
 #define DEFAULT_VERSION		2
-#define CURRENT_VERSION		5
+#define CURRENT_VERSION		6
diff --git a/tmcd/freebsd/liblocsetup.pm b/tmcd/freebsd/liblocsetup.pm
index ac84f60284b57e53d8d62b17214e36e51cefcbb5..97bb3df718fa1e40afdf0b7080e2438c06a20213 100644
--- a/tmcd/freebsd/liblocsetup.pm
+++ b/tmcd/freebsd/liblocsetup.pm
@@ -14,7 +14,7 @@ package liblocsetup;
 use Exporter;
 @ISA = "Exporter";
 @EXPORT =
-    qw ( $CP $EGREP $MOUNT $UMOUNT $TMPASSWD
+    qw ( $CP $EGREP $MOUNT $UMOUNT $TMPASSWD $SFSSD $SFSCD
 	 os_cleanup_node os_ifconfig_line os_etchosts_line
 	 os_setup os_groupadd os_useradd os_userdel os_usermod os_mkdir
 	 os_rpminstall_line update_delays
@@ -41,6 +41,8 @@ $MOUNT		= "/sbin/mount";
 $UMOUNT		= "/sbin/umount";
 $TMGROUP	= "$SETUPDIR/group";
 $TMPASSWD	= "$SETUPDIR/master.passwd";
+$SFSSD		= "/usr/local/sbin/sfssd";
+$SFSCD		= "/usr/local/sbin/sfscd";
 
 #
 # These are not exported
diff --git a/tmcd/libsetup.pm b/tmcd/libsetup.pm
index fc51cb269780aef92f89d0fc580a38363c6acaed..382d92180f579aa5fbfebdc4426dfbefe8f8f95b 100644
--- a/tmcd/libsetup.pm
+++ b/tmcd/libsetup.pm
@@ -79,6 +79,7 @@ sub TMNICKNAME()	{ "$SETUPDIR/nickname"; }
 sub FINDIF()		{ "$SETUPDIR/findif"; }
 sub HOSTSFILE()		{ "/etc/hosts"; }
 sub TMMOUNTDB()		{ "$SETUPDIR/mountdb"; }
+sub TMSFSMOUNTDB()	{ "$SETUPDIR/sfsmountdb"; }
 sub TMROUTECONFIG()     { ($vnodedir ? $vnodedir : $SETUPDIR) . "/rc.route";}
 sub TMTRAFFICCONFIG()	{ ($vnodedir ? $vnodedir : $SETUPDIR) . "/rc.traffic";}
 sub TMTUNNELCONFIG()	{ ($vnodedir ? $vnodedir : $SETUPDIR) . "/rc.tunnel";}
@@ -86,13 +87,21 @@ sub TMVTUNDCONFIG()	{ ($vnodedir ? $vnodedir : $SETUPDIR) . "/vtund.conf";}
 sub TMPASSDB()		{ "$SETUPDIR/passdb"; }
 sub TMGROUPDB()		{ "$SETUPDIR/groupdb"; }
 
+#
+# Whether or not to use SFS (the self-certifying file system).  If this
+# is 0, fall back to NFS.  Note that it doesn't hurt to set this to 1
+# even if TMCD is not serving out SFS mounts, or if this node is not
+# running SFS.  It'll deal and fall back to NFS.
+#
+my $USESFS		= 1;
+
 #
 # This is the VERSION. We send it through to tmcd so it knows what version
 # responses this file is expecting.
 #
 # BE SURE TO BUMP THIS AS INCOMPATIBILE CHANGES TO TMCD ARE MADE!
 #
-sub TMCD_VERSION()	{ 5; };
+sub TMCD_VERSION()	{ 6; };
 
 #
 # These are the TMCC commands. 
@@ -117,6 +126,8 @@ sub TMCCCMD_TUNNEL()	{ "tunnels"; }
 sub TMCCCMD_NSECONFIGS(){ "nseconfigs"; }
 sub TMCCCMD_VNODELIST() { "vnodelist"; }
 sub TMCCCMD_ISALIVE()   { "isalive"; }
+sub TMCCCMD_SFSHOSTID()	{ "sfshostid"; }
+sub TMCCCMD_SFSMOUNTS() { "sfsmounts"; }
 
 #
 # Some things never change.
@@ -129,7 +140,8 @@ my $VTUND       = "/usr/local/sbin/vtund";
 # This is a debugging thing for my home network.
 # 
 #my $NODE	= "-p 7778 REDIRECT=192.168.100.1";
-$NODE		= "";
+my $NODE	= "-p 7779";
+#$NODE		= "";
 
 # Locals
 my $pid		= "";
@@ -324,13 +336,21 @@ sub domounts()
     my %MDB;
     my %mounts;
     my %deletes;
-    
-    $TM = OPENTMCC(TMCCCMD_MOUNTS);
+    my %sfsmounts;
+    my %sfsdeletes;
+
+    $TM = OPENTMCC(TMCCCMD_MOUNTS, "USESFS=$USESFS");
 
     while (<$TM>) {
-	if ($_ =~ /REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
+	if ($_ =~ /^REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
 	    $mounts{$1} = $2;
 	}
+	elsif ($_ =~ /^SFS REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
+	    $sfsmounts{$1} = $2;
+	}
+	else {
+	    warn "*** WARNING: Malformed mount information: $_\n";
+	}
     }
     CLOSETMCC($TM);
     
@@ -419,6 +439,269 @@ sub domounts()
     # Write the DB back out!
     dbmclose(%MDB);
 
+    #
+    # Now, do basically the same thing over again, but this time for
+    # SFS mounted stuff
+    #
+
+    if (scalar(%sfsmounts)) {
+	dbmopen(%MDB, TMSFSMOUNTDB, 0660);
+	
+	#
+	# First symlink all the mounts we are told to. For each one
+	# that is not currently symlinked, and can be, add it to the
+	# DB.
+	#
+	while (($remote, $local) = each %sfsmounts) {
+	    if (-l $local) {
+		if (readlink($local) eq ("/sfs/" . $remote)) {
+		    $MDB{$remote} = $local;
+		    next;
+		}
+		if (readlink($local) ne ("/sfs/" . $remote)) {
+		    print STDOUT "  Unlinking incorrect symlink $local\n";
+		    if ( ! unlink($local)) {
+			warn "*** WARNING: Could not unlink $local: $!\n";
+			next;
+		    }
+		}
+	    }
+	    
+	    $dir = $local;
+	    $dir =~ s/(.*)\/[^\/]*$/$1/;
+	    if (! -e $dir) {
+		print STDOUT "  Making directory $dir\n";
+		if (! os_mkdir($dir, 755)) {
+		    warn "*** WARNING: Could not make directory $local: $!\n";
+		    next;
+		}
+	    }
+	    print STDOUT "  Symlinking $remote on $local\n";
+	    if (! symlink("/sfs/" . $remote . "/", $local)) {
+		warn "*** WARNING: Could not make symlink $local: $!\n";
+		next;
+	    }
+	    
+	    $MDB{$remote} = $local;
+	}
+
+	#
+	# Now delete the ones that we symlinked previously, but are
+	# now no longer in the mount set (as told to us by the TMCD).
+	# Note, we cannot delete them directly from MDB since that
+	# would mess up the foreach loop, so just stick them in temp
+	# and postpass it.
+	#
+	while (($remote, $local) = each %MDB) {
+	    if (defined($sfsmounts{$remote})) {
+		next;
+	    }
+	    
+	    if (! -e $local) {
+		$sfsdeletes{$remote} = $local;
+		next;
+	    }
+	    
+	    print STDOUT "  Deleting symlink $local\n";
+	    if (! unlink($local)) {
+		warn "*** WARNING: Could not delete $local: $!\n";
+		next;
+	    }
+	    
+	    #
+	    # Only delete from set if we can actually unlink it.  This way
+	    # we can retry it later (or next time).
+	    #
+	    $sfsdeletes{$remote} = $local;
+	}
+	while (($remote, $local) = each %sfsdeletes) {
+	    delete($MDB{$remote});
+	}
+
+	# Write the DB back out!
+	dbmclose(%MDB);	
+    }
+    else {
+	# There were no SFS mounts reported, so disable SFS
+	$usesfs = 0;
+    }
+
+    return 0;
+}
+
+#
+# Do SFS hostid setup.
+# Creates an SFS host key for this node, if it doesn't already exist,
+# and sends it to TMCD
+#
+sub dosfshostid ()
+{
+    my $TM;
+    my $myhostid;
+
+    # Do I already have a host key?
+#      if (-e "/etc/sfs/sfs_host_key") {
+#  	print STDOUT "  This node already has a host key\n";
+#      }
+#      else {
+#          # Create my host key
+#  	if (! -e "/etc/sfs") {
+#  	    if (! os_mkdir("/etc/sfs", 0755)) {
+#  		warn "*** WARNING: Could not make directory /etc/sfs: $!\n";
+#  	    }
+#  	}
+	
+#  	if (! -e "/etc/sfs/sfs_host_key") {
+#  	    print STDOUT "  Creating SFS host key\n";
+#  	    if (system("sfskey gen -KPn $vname.$eid.$pid ".
+#  		       "/etc/sfs/sfs_host_key")) {
+#  		warn "*** WARNING: Could not generate SFS host key: $!\n";
+#  		$USESFS = 0;
+#  		return 1;
+#  	    }
+#  	}
+#      }
+    if (! -e "/etc/sfs/sfs_host_key") {
+	warn "*** This node does not have a host key, skipping SFS stuff\n";
+	$USESFS = 0;
+	return 1;
+    }
+
+    # Start SFS server and client (this has to be done now, as opposed
+    # to in an rc script, because the hostkey has to exist)
+#      if (system($SFSSD)) {
+#  	warn "*** Failed to start sfssd\n";
+#  	$USESFS = 0;
+#  	return 1;
+#      }
+#      if (system($SFSCD)) {
+#  	warn "*** Failed to start sfscd\n";
+#  	$USESFS = 0;
+#  	return 1;
+#      }
+
+    # Give hostid to TMCD
+    # IAMHERE: sfssd needs to be running for this call to succeed
+    #   Directory needs to exist on fs/proj
+    open(SFSKEY, "sfskey hostid - |")
+	or die "Cannot start sfskey";
+    $myhostid = <SFSKEY>;
+    close(SFSKEY);
+    if (defined($myhostid)) {
+	if ( $myhostid =~ /^([-\.\w_]*:[a-z0-9]*)$/ ) {
+	    $myhostid = $1;
+	    print STDOUT "  Hostid: $myhostid\n";
+	    RUNTMCC(TMCCCMD_SFSHOSTID, "$myhostid");
+	}
+	else {
+	    warn "*** WARNING: Invalid hostid\n";
+	}
+    }
+    else {
+	warn "*** WARNING: Could not retrieve this node's hostid (is sfssd running?)\n";
+	$USESFS = 0;
+    }
+
+    return 0;
+}
+
+#
+# Create SFS "mounts" (which are really just symlinks).  The remote side
+# of the mount is relative to /sfs and includes the server hostname, the
+# server's hostid, and the path on the server.  The local side is simply
+# the path relative to / to symlink to the remote sfs directory.  This
+# closely follows domounts()
+#
+sub dosfsmounts()
+{
+    my $TM;
+    my %MBD;
+    my %mounts;
+    my %deletes;
+
+    die("*** Don't call dosfsmounts!");
+    
+    $TM = OPENTMCC(TMCCCMD_SFSMOUNTS);
+
+    while (<$TM>) {
+	if ($_ =~ /REMOTE=([-:\@\w\.\/]+) LOCAL=([-\@\w\.\/]+)/) {
+	    $mounts{$1} = $2;
+	}
+    }
+    CLOSETMCC($TM);
+
+    dbmopen(%MDB, TMSFSMOUNTDB, 0660);
+    
+    #
+    # First symlink all the mounts we are told to. For each one that is
+    # not currently symlinked, and can be, add it to the DB.
+    #
+    while (($remote, $local) = each %mounts) {
+	if (-l $local && readlink($local) eq ("/sfs/" . $remote)) {
+	    $MDB{$remote} = $local;
+	    next;
+	}
+	if ( ! -l $local ) {
+	    print STDOUT "  Unlinking incorrect symlink $local\n";
+	    if ( ! unlink($local)) {
+		warn "*** WARNING: Could not unlink $local: $!\n";
+		next;
+	    }
+	}
+
+	$dir = $local;
+	$dir =~ s/(.*)\/[^\/]*$/$1/;
+	if (! -e $dir) {
+	    print STDOUT "  Making directory $dir\n";
+	    if (! os_mkdir($dir, 755)) {
+		warn "*** WARNING: Could not make directory $local: $!\n";
+		next;
+	    }
+	}
+	print STDOUT "  Symlinking $remote on $local\n";
+	if (! symlink("/sfs/" . $remote, $local)) {
+	    warn "*** WARNING: Could not make symlink $local: $!\n";
+	    next;
+	}
+
+	$MDB{$remote} = $local;
+    }
+
+    #
+    # Now delete the ones that we symlinked previously, but are now no
+    # longer in the mount set (as told to us by the TMCD). Note, we
+    # cannot delete them directly from MDB since that would mess up the
+    # foreach loop, so just stick them in temp and postpass it.
+    #
+    while (($remote, $local) = each %MDB) {
+	if (defined($mounts{$remote})) {
+	    next;
+	}
+
+	if (! -e $local) {
+	    $deletes{$remote} = $local;
+	    next;
+	}
+
+	print STDOUT "  Deleting symlink $local\n";
+	if (! unlink($local)) {
+	    warn "*** WARNING: Could not delete $local: $!\n";
+	    next;
+	}
+
+	#
+	# Only delete from set if we can actually unlink it.  This way
+	# we can retry it later (or next time).
+	#
+	$deletes{$remote} = $local;
+    }
+    while (($remote, $local) = each %deletes) {
+	delete($MDB{$remote});
+    }
+
+    # Write the DB back out!
+    dbmclose(%MDB);
+
     return 0;
 }
 
@@ -696,6 +979,7 @@ sub doaccounts()
     my %newaccounts = ();
     my %newgroups   = ();
     my %pubkeys     = ();
+    my @sfskeys     = ();
     my %deletes     = ();
     my %lastmod     = ();
     my %PWDDB;
@@ -740,6 +1024,13 @@ sub doaccounts()
 	    push(@{$pubkeys{$1}}, $2);
 	    next;
 	}
+	elsif ($_ =~ /^SFSKEY KEY="(.*)"/) {
+	    #
+	    # SFS key goes into the array.
+	    #
+	    push(@sfskeys, $1);
+	    next;
+	}
 	else {
 	    warn "*** WARNING: Bad accounts line: $_\n";
 	}
@@ -966,7 +1257,7 @@ sub doaccounts()
 		    next;
 		}
 	    }
-		
+	    
 	    if (!open(AUTHKEYS, "> $sshdir/authorized_keys.new")) {
 		warn("*** WARNING: Could not open $sshdir/keys.new: $!\n");
 		next;
@@ -1016,6 +1307,78 @@ sub doaccounts()
     # Write the DB back out!
     dbmclose(%PWDDB);
 
+    #
+    # Create sfs_users file and populate it with public SFS keys
+    #
+    do {
+	if (!open(SFSKEYS, "> /etc/sfs/sfs_users.new")) {
+	    warn("*** WARNING: Could not open /etc/sfs/sfs_users.new: $!\n");
+	    next;
+	}
+
+	print SFSKEYS "#\n";
+	print SFSKEYS "# DO NOT EDIT! This file auto generated by ".
+	    "Emulab.Net account software.\n";
+	print SFSKEYS "#\n";
+	print SFSKEYS "# Please use the web interface to edit your ".
+	    "SFS public key list.\n";
+	print SFSKEYS "#\n";
+	foreach my $key (@sfskeys) {
+	    print SFSKEYS "$key\n";
+	}
+	close(SFSKEYS);
+
+	# Because sfs_users only contains public keys, sfs_users.pub is
+	# exactly the same
+	if (system("cp -p -f /etc/sfs/sfs_users.new ".
+		   "/etc/sfs/sfs_users.pub.new")) {
+	    warn("*** Could not copy /etc/sfs/sfs_users.new to ".
+		 "sfs_users.pub.new: $!\n");
+	    next;
+	}
+	
+	if (!chown(0, 0, "/etc/sfs/sfs_users.new")) {
+	    warn("*** WARNING: Could not chown /etc/sfs/sfs_users.new: $!\n");
+	    next;
+	}
+	if (!chmod(0600, "/etc/sfs/sfs_users.new")) {
+	    warn("*** WARNING: Could not chmod /etc/sfs/sfs_users.new: $!\n");
+	    next;
+	}
+	
+	if (!chown(0, 0, "/etc/sfs/sfs_users.pub.new")) {
+	    warn("*** WARNING: Could not chown /etc/sfs/sfs_users.pub.new: $!\n");
+	    next;
+	}
+	if (!chmod(0644, "/etc/sfs/sfs_users.pub.new")) {
+	    warn("*** WARNING: Could not chmod /etc/sfs/sfs_users.pub.new: $!\n");
+	    next;
+	}
+
+	# Save off old key files and move in new ones
+	foreach my $keyfile ("/etc/sfs/sfs_users",
+			     "/etc/sfs/sfs_users.pub") {
+	    if (-e $keyfile) {
+		if (system("cp -p -f $keyfile $keyfile.old")) {
+		    warn("*** Could not save off $keyfile: $!\n");
+		    next;
+		}
+		if (!chown(0, 0, "$keyfile")) {
+		    warn("*** Could not chown $keyfile: $!\n");
+		}
+		if (!chmod(0600, "$keyfile")) {
+		    warn("*** Could not chmod $keyfile: $!\n");
+		}
+	    }
+	    if (system("mv -f $keyfile.new $keyfile")) {
+		warn("*** Could not mv $keyfile: ~!\n");
+	    }
+	}
+	
+	# The do-while is an easy way out in case of errors
+    }
+    while 0;
+    
     return 0;
 }
 
@@ -1469,8 +1832,17 @@ sub bootsetup()
     #
     create_nicknames();
 
+    if ($USESFS && ! MFS()) {
+	#
+	# Setup SFS hostid.
+	#
+	print STDOUT "Creating node SFS hostid ... \n";
+	dosfshostid();
+    }
+
     #
-    # Mount the project and user directories
+    # Mount the project and user directories and symlink SFS "mounted"
+    # directories
     #
     print STDOUT "Mounting project and home directories ... \n";
     domounts();
@@ -1563,8 +1935,17 @@ sub nodeupdate()
 	return 0;
     }
 
+    if ($USESFS && ! MFS()) {
+	#
+	# Setup SFS hostid.
+	#
+	print STDOUT "Creating node SFS hostid ... \n";
+	dosfshostid();
+    }
+
     #
-    # Mount the project and user directories
+    # Mount the project and user directories and symlink SFS "mounted"
+    # directories
     #
     print STDOUT "Mounting project and home directories ... \n";
     domounts();
diff --git a/tmcd/tmcd.c b/tmcd/tmcd.c
index 6647c935f2f6e62ad9061bce40c1334666e03122..0679d45ecf10fab08ab9a5857fcfc84aacdc6f49 100644
--- a/tmcd/tmcd.c
+++ b/tmcd/tmcd.c
@@ -21,6 +21,7 @@
 #include <sys/wait.h>
 #include <sys/fcntl.h>
 #include <paths.h>
+#include <setjmp.h>
 #include <mysql/mysql.h>
 #include "decls.h"
 #include "config.h"
@@ -43,12 +44,15 @@
 #define USERDIR		"/users"
 #define RELOADPID	"emulab-ops"
 #define RELOADEID	"reloading"
+//#define FSHOSTID	"/usr/testbed/etc/fshostid"
+#define FSHOSTID	"./fshostid"
 
 #define TESTMODE
 #define NETMASK		"255.255.255.0"
 
 /* Defined in configure and passed in via the makefile */
 #define DBNAME_SIZE	64
+#define HOSTID_SIZE	(32+64)
 #define DEFAULT_DBNAME	TBDBNAME
 
 int		debug = 0;
@@ -56,6 +60,7 @@ static int	insecure = 0;
 static int	portnum = TBSERVER_PORT;
 static char     dbname[DBNAME_SIZE];
 static struct in_addr myipaddr;
+static char	fshostid[HOSTID_SIZE];
 static int	nodeidtoexp(char *nodeid, char *pid, char *eid, char *gid);
 static int	iptonodeid(struct in_addr, char *,char *,char *,char *,int *);
 static int	nodeidtonickname(char *nodeid, char *nickname);
@@ -106,6 +111,8 @@ COMMAND_PROTOTYPE(doready);
 COMMAND_PROTOTYPE(doreadycount);
 COMMAND_PROTOTYPE(dolog);
 COMMAND_PROTOTYPE(domounts);
+COMMAND_PROTOTYPE(dosfshostid);
+//COMMAND_PROTOTYPE(dosfsmounts);
 COMMAND_PROTOTYPE(doloadinfo);
 COMMAND_PROTOTYPE(doreset);
 COMMAND_PROTOTYPE(dorouting);
@@ -139,6 +146,8 @@ struct command {
 	{ "ready",	doready },
 	{ "log",	dolog },
 	{ "mounts",	domounts },
+	{ "sfshostid",	dosfshostid },
+//	{ "sfsmounts",	dosfsmounts },
 	{ "loadinfo",	doloadinfo},
 	{ "reset",	doreset},
 	{ "routing",	dorouting},
@@ -241,6 +250,29 @@ main(int argc, char **argv)
 	info("daemon starting (version %d)\n", CURRENT_VERSION);
 	info("%s\n", build_info);
 
+	/*
+	 * Get FS's SFS hostid
+	 * XXX This approach is somewhat kludgy
+	 */
+	strcpy(fshostid, "");
+	if (access(FSHOSTID,R_OK) == 0) {
+		fp = fopen(FSHOSTID, "r");
+		if (!fp) {
+			error("Failed to get FS's hostid");
+		}
+		else {
+			fgets(fshostid, HOSTID_SIZE, fp);
+			if (rindex(fshostid, '\n')) {
+				*rindex(fshostid, '\n') = 0;
+			}
+			else {
+				error("fshostid from %s may be corrupt: %s",
+				      FSHOSTID, fshostid);
+			}
+			fclose(fp);
+		}
+	}
+	
 	/*
 	 * Grab our IP for security check below.
 	 */
@@ -1073,8 +1105,9 @@ COMMAND_PROTOTYPE(doaccounts)
 	row = mysql_fetch_row(res);
 	while (nrows) {
 		MYSQL_ROW	nextrow;
-		MYSQL_RES	*pubkeys_res;	
-		int		pubkeys_nrows, i, root = 0;
+		MYSQL_RES	*pubkeys_res;
+		MYSQL_RES	*sfskeys_res;
+		int		pubkeys_nrows, sfskeys_nrows, i, root = 0;
 		int		auxgids[128], gcount = 0;
 		char		glist[BUFSIZ];
 
@@ -1213,6 +1246,40 @@ COMMAND_PROTOTYPE(doaccounts)
 			}
 		}
 		mysql_free_result(pubkeys_res);
+
+		if (vers < 6)
+			goto skipkeys;
+
+		/*
+		 * Need a list of SFS keys for this user.
+		 */
+		sfskeys_res = mydb_query("select comment,pubkey "
+					 " from user_sfskeys "
+					 "where uid='%s'",
+					 2, row[0]);
+
+		if (!sfskeys_res) {
+			error("ACCOUNTS: %s: DB Error getting SFS keys\n", row[0]);
+			goto skipkeys;
+		}
+		if ((sfskeys_nrows = (int)mysql_num_rows(sfskeys_res))) {
+			while (sfskeys_nrows) {
+				MYSQL_ROW	sfskey_row;
+
+				sfskey_row = mysql_fetch_row(sfskeys_res);
+
+				sprintf(buf, "SFSKEY KEY=\"%s\"\n",
+					sfskey_row[1]);
+
+				client_writeback(sock, buf, strlen(buf), tcp);
+				sfskeys_nrows--;
+
+				info("ACCOUNTS: SFSKEY LOGIN=%s COMMENT=%s\n",
+				     row[0], sfskey_row[0]);
+			}
+		}
+		mysql_free_result(sfskeys_res);
+		
 	skipkeys:
 		row = nextrow;
 	}
@@ -2018,6 +2085,8 @@ COMMAND_PROTOTYPE(domounts)
 	char		gid[64];
 	char		buf[MYBUFSIZE];
 	int		nrows;
+	int		usesfs;
+	char		*bp;
 
 	/*
 	 * Now check reserved table
@@ -2028,22 +2097,74 @@ COMMAND_PROTOTYPE(domounts)
 	}
 
 	/*
-	 * Return project mount first. 
+	 * Should SFS mounts be served?
 	 */
-	sprintf(buf, "REMOTE=%s/%s LOCAL=%s/%s\n",
-		FSPROJDIR, pid, PROJDIR, pid);
-	client_writeback(sock, buf, strlen(buf), tcp);
+	usesfs = 0;
+	if (vers >= 6 && strlen(fshostid)) {
+		while ((bp = strsep(&rdata, " ")) != NULL) {
+			if (sscanf(bp, "USESFS=%d", &usesfs) == 1) {
+				continue;
+			}
 
+			error("Unknown parameter to domounts: %s\n",
+			      bp);
+			break;
+		}
+
+		if (debug) {
+			if (usesfs) {
+				info("Using SFS\n");
+			}
+			else {
+				info("Not using SFS\n");
+			}
+		}
+	}
+	
 	/*
-	 * If pid!=gid, then this is group experiment, and we return
-	 * a mount for the group directory too.
+	 * If SFS is in use, the project mount is done via SFS.
 	 */
-	if (strcmp(pid, gid)) {
-		sprintf(buf, "REMOTE=%s/%s/%s LOCAL=%s/%s/%s\n",
-			FSGROUPDIR, pid, gid, GROUPDIR, pid, gid);
+	if (!usesfs) {
+		/*
+		 * Return project mount first. 
+		 */
+		sprintf(buf, "REMOTE=%s/%s LOCAL=%s/%s\n",
+			FSPROJDIR, pid, PROJDIR, pid);
 		client_writeback(sock, buf, strlen(buf), tcp);
+		info("MOUNTS: %s", buf);
+		
+		/*
+		 * If pid!=gid, then this is group experiment, and we return
+		 * a mount for the group directory too.
+		 */
+		if (strcmp(pid, gid)) {
+			sprintf(buf, "REMOTE=%s/%s/%s LOCAL=%s/%s/%s\n",
+				FSGROUPDIR, pid, gid, GROUPDIR, pid, gid);
+			client_writeback(sock, buf, strlen(buf), tcp);
+			info("MOUNTS: %s", buf);
+		}
 	}
+	else {
+		/*
+		 * Return SFS-based project mount.
+		 */
+		sprintf(buf, "SFS REMOTE=%s%s/%s LOCAL=%s/%s\n",
+			fshostid, FSDIR_PROJ, pid, PROJDIR, pid);
+		client_writeback(sock, buf, strlen(buf), tcp);
+		info("MOUNTS: %s", buf);
 
+		/*
+		 * Return SFS-based group mount.
+		 */
+		if (strcmp(pid, gid)) {
+			sprintf(buf, "SFS REMOTE=%s%s/%s/%s LOCAL=%s/%s/%s\n",
+				fshostid, FSDIR_GROUPS, pid, gid,
+				GROUPDIR, pid, gid);
+			client_writeback(sock, buf, strlen(buf), tcp);
+			info("MOUNTS: %s", buf);
+		}
+	}
+	
 	/*
 	 * Now check for aux project access. Return a list of mounts for
 	 * those projects.
@@ -2115,6 +2236,126 @@ COMMAND_PROTOTYPE(domounts)
 	return 0;
 }
 
+/*
+ * Used by dosfshostid to make sure NFS doesn't give us problems.
+ * (This code really unnerves me)
+ */
+int sfshostiddeadfl;
+jmp_buf sfshostiddeadline;
+static void
+dosfshostiddead()
+{
+	sfshostiddeadfl = 1;
+	longjmp(sfshostiddeadline, 1);
+}
+
+/*
+ * Create dirsearch entry for node.
+ */
+COMMAND_PROTOTYPE(dosfshostid)
+{
+	char	pid[64];
+	char	eid[64];
+	char	gid[64];
+	char	nickname[128];
+	char	nodehostid[HOSTID_SIZE];
+	char	sfspath[MYBUFSIZE], dspath[MYBUFSIZE];
+
+	if (!strlen(fshostid)) {
+		/* SFS not being used */
+		info("dosfshostid: Called while SFS is not in use\n");
+		return 0;
+	}
+	
+	/*
+	 * Now check reserved table
+	 */
+	if (nodeidtoexp(nodeid, pid, eid, gid)) {
+		error("dosfshostid: %s: Node is free\n", nodeid);
+		return 1;
+	}
+
+	if (nodeidtonickname(nodeid, nickname))
+		strcpy(nickname, nodeid);
+
+	/* XXX Make sure that the dirsearch dir exists (more NFS goop) */
+	
+	/*
+	 * Create symlink names
+	 */
+	if (! sscanf(rdata, "%s", nodehostid)) {
+		error("dosfshostid: No hostid reported!\n");
+		return 1;
+	}
+	sprintf(sfspath, "/sfs/%s", nodehostid);
+	// sprintf(dspath, "/proj/%s/.sfs/%s/%s", pid, eid, nickname);
+	sprintf(dspath, "/proj/.sfs/%s.%s.%s", nickname, eid, pid);
+	
+	/*
+	 * Really, there should be a cleaner way of doing this, but
+	 * this works, at least for now.  Perhaps using the DB and a
+	 * symlinking deamon alone would be better.
+	 */
+	if (setjmp(sfshostiddeadline) == 0) {
+		sfshostiddeadfl = 0;
+		signal(SIGALRM, dosfshostiddead);
+		alarm(1);
+
+		unlink(dspath);
+		if (symlink(sfspath, dspath) < 0)
+			sfshostiddeadfl = 1;
+	}
+	alarm(0);
+	if (sfshostiddeadfl) {
+		errorc("symlinking %s to %s", dspath, sfspath);
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Return SFS-based mounts.
+ * XXX This is _completely_ unused, and will be removed.
+ */
+COMMAND_PROTOTYPE(dosfsmounts)
+{
+	char		pid[64];
+	char		eid[64];
+	char		gid[64];
+	char		buf[MYBUFSIZE];
+
+	/*
+	 * Now check reserved table
+	 */
+	if (nodeidtoexp(nodeid, pid, eid, gid)) {
+		error("MOUNTS: %s: Node is free\n", nodeid);
+		return 1;
+	}
+
+	if (strlen(fshostid)) {
+		/*
+		 * Return project mount first.
+		 */
+		sprintf(buf, "REMOTE=%s%s/%s LOCAL=%s/%s\n",
+			fshostid, FSDIR_PROJ, pid, PROJDIR, pid);
+		client_writeback(sock, buf, strlen(buf), tcp);
+
+		/*
+		 * If pid!= gid, then this is group experiment, and we return
+		 * a mount for the group directory too.
+		 */
+		if (strcmp(pid, gid)) {
+			sprintf(buf, "REMOTE=%s%s/%s/%s LOCAL=%s/%s/%s\n",
+				fshostid, FSDIR_GROUPS, pid, gid,
+				GROUPDIR, pid, gid);
+			client_writeback(sock, buf, strlen(buf), tcp);
+		}
+	}
+
+	return 0;
+}
+
 /*
  * Return routing stuff.
  */