From 9310dedbe9be25a5de7ebc8dc33ef6d6a7b381bd Mon Sep 17 00:00:00 2001
From: "Leigh B. Stoller" <stoller@flux.utah.edu>
Date: Mon, 28 Mar 2005 17:23:02 +0000
Subject: [PATCH] Implement a cross machine login so that user is automatically
 logged into the Wiki when clicking on the My Wiki's link. Works like this:

* The My Wiki's link points to new page, gotowiki.php3, on boss.

* The gotowiki page looks for a new cookie in the user's browser
  which holds a key (the usual random data run through md5).

* If the key does not exist, generate it and store it in the user
  browser (expires when browser is closed or emulab login times out).
  Also invoke backend script wikixlogin, which will send the key over
  to the wiki server (via ssh), which will write the key into a file
  named by the user account.

* The user's browser is redirected to the wiki server's login script
  (twiki/bin/newlogon), but instead of username and password, we send
  over username and key (as well as redurl= parameter which is the
  page on the wiki server to redirect to later).

* The new login script looks for this case, and opens the file named
  by the user and compares the key it gets with what is in the file.
  If they match, the user login succeeds and the browser is once again
  redirected, but this time to the page it wants on the wiki server.
  If the key does not match, the browser is redirected to the login
  page (so user can enter username password normally). The redurl
  parameter is passed along as well.

* Subsequent clicks on My Wiki's will not need to invoke the backend
  script, since the cookie will be in the browser.
---
 wiki/GNUmakefile.in |   7 ++-
 wiki/newlogon       |  78 ++++++++++++++++++++++-----
 wiki/wikiproxy.in   |  29 ++++++++++
 wiki/wikixlogin.in  | 127 ++++++++++++++++++++++++++++++++++++++++++++
 www/defs.php3.in    |   3 +-
 www/gotowiki.php3   |  49 +++++++++++++++++
 www/menu.php3       |   8 +--
 www/showstuff.php3  |   8 +--
 www/tbauth.php3     |   5 ++
 9 files changed, 290 insertions(+), 24 deletions(-)
 create mode 100644 wiki/wikixlogin.in
 create mode 100644 www/gotowiki.php3

diff --git a/wiki/GNUmakefile.in b/wiki/GNUmakefile.in
index cdf9536049..26000ecab9 100644
--- a/wiki/GNUmakefile.in
+++ b/wiki/GNUmakefile.in
@@ -13,6 +13,7 @@ include $(OBJDIR)/Makeconf
 
 SBIN_SCRIPTS		= addwikiuser addwikiproj wikisetup delwikiuser \
 			  setwikigroups
+LIBEXEC_SCRIPTS		= wikixlogin
 
 CTRL_SBIN_SCRIPTS	= wikiproxy
 CTRL_LIB_FILES		= usertemplate webhometemplate
@@ -21,11 +22,13 @@ CTRL_LIB_FILES		= usertemplate webhometemplate
 # Force dependencies on the scripts so that they will be rerun through
 # configure if the .in file is changed.
 # 
-all:	$(SBIN_SCRIPTS) $(CTRL_SBIN_SCRIPTS) $(CTRL_LIB_FILES)
+all:	$(SBIN_SCRIPTS) $(CTRL_SBIN_SCRIPTS) $(CTRL_LIB_FILES) \
+	$(LIBEXEC_SCRIPTS)
 
 include $(TESTBED_SRCDIR)/GNUmakerules
 
 install: $(addprefix $(INSTALL_SBINDIR)/, $(SBIN_SCRIPTS)) \
+	 $(addprefix $(INSTALL_LIBEXECDIR)/, $(LIBEXEC_SCRIPTS)) \
 	 $(addprefix $(INSTALL_DIR)/opsdir/sbin/, $(CTRL_SBIN_SCRIPTS)) \
 	 $(addprefix $(INSTALL_DIR)/opsdir/lib/wiki/, $(CTRL_LIB_FILES))
 
@@ -42,6 +45,8 @@ post-install:
 	chmod u+s $(INSTALL_SBINDIR)/addwikiproj
 	chown root $(INSTALL_SBINDIR)/setwikigroups
 	chmod u+s $(INSTALL_SBINDIR)/setwikigroups
+	chown root $(INSTALL_LIBEXECDIR)/wikixlogin
+	chmod u+s $(INSTALL_LIBEXECDIR)/wikixlogin
 
 #
 # Control node installation (okay, plastic)
diff --git a/wiki/newlogon b/wiki/newlogon
index 1c9651fbf9..8fc4e21ae3 100755
--- a/wiki/newlogon
+++ b/wiki/newlogon
@@ -24,9 +24,18 @@ use TWiki;
 use TWiki::Plugins::SessionPlugin;
 
 my $oopsurl = "oopsloginfail";
+my $CREDDIR = "/var/db/cgisess";
 
 $query= new CGI;
 
+sub myerror($)
+{
+    my ($msg) = @_;
+
+    my $url = &TWiki::getOopsUrl(undef, "", $oopsurl, $msg);
+    TWiki::redirect($query, $url);
+}
+
 &main();
 
 sub main
@@ -34,17 +43,61 @@ sub main
     my $username = $query->param('username');
     my $password = $query->param('password');
     my $redurl   = $query->param('redurl');
+    my $bosscred = $query->param('bosscred');
 
-    chomp($username);
-    chomp($password);
+    #
+    # If bosscred provided, boss is trying to autologin the user using
+    # a key that it sent across via a backend script and stashed in the
+    # cookie dir. Find that file, compare the keys and if the key is not
+    # too terribly old, give the user the nod.
+    #
+    if (defined($bosscred)) {
+	if (!defined($username) || $username eq "") {
+	    myerror("Missing username argument");
+	    return;
+	}
+
+	my $file = "${CREDDIR}/$username";
+
+	if (! -e $file) {
+	    myerror("Cred file does not exist!");
+	    return;
+	}
+	if (!open(COK, $file)) {
+	    myerror("Cannot open cred file!");
+	    return;
+	}
+	my $cred  = <COK>;
+	my $stamp = <COK>;
+	close(COK);
+
+	# Compare credentials.
+	if (defined($cred)) {
+	    chomp($cred);
+	    if ($cred eq $bosscred) {
+		goto accepted;
+	    }
+	}
+	# Does not match. Redirect to login page.
+      dologon:
+	my $url = &TWiki::getViewUrl("TWiki", "DoLogin");
+	$url .= "?username=${username}";
+	$url .= "&redurl=${redurl}"
+	    if (defined($redurl) && $redurl ne "");
+	
+	TWiki::redirect($query, $url);
+	return;
+    }
 
+    #
+    # Normal login.
+    # 
     if (! ($username && $password)) {
-        my $url = &TWiki::getOopsUrl(undef, "", $oopsurl,
-			     "Missing arguments (username or password)");
-
-        TWiki::redirect( $query, $url );
+	myerror("Missing arguments (username or password)");
 	return;
     }
+    chomp($username);
+    chomp($password);
 
     #
     # Suck out the password entry.
@@ -63,9 +116,7 @@ sub main
     close(HTP);
 
     if (!defined($pwentry)) {
-        my $url = &TWiki::getOopsUrl(undef, "", $oopsurl,
-				     "No such user: '$username'");
-        TWiki::redirect( $query, $url );
+	myerror("No such user: '$username'");
 	return;
     }
 
@@ -78,15 +129,14 @@ sub main
     my $str = crypt($password, $encryptedpasswd);
 
     if ($str ne $encryptedpasswd) {
-        my $url = &TWiki::getOopsUrl(undef, "", $oopsurl,
-				     "Incorrect Password");
-        TWiki::redirect( $query, $url );
+	myerror("Incorrect Password");
 	return;
     }
-
+ 
     # This causes the query object to suddenly have a remote_user() value.
     # SessionPlugin uses that ...
-    $ENV{REMOTE_USER} = $username;
+ accepted:
+   $ENV{REMOTE_USER} = $username;
 
     #
     # Stuff we need to pass down. Note that I am not bothering with the
diff --git a/wiki/wikiproxy.in b/wiki/wikiproxy.in
index 3e0cde7b52..a2b80c89cf 100644
--- a/wiki/wikiproxy.in
+++ b/wiki/wikiproxy.in
@@ -40,6 +40,7 @@ my $WIKIGROUPDIR = "$WIKIDATADIR/Main";
 my $WIKIARCHIVE  = "$WIKIDATADIR/_archive";
 my $WIKIPASSWD   = "$WIKIDIR/data/.htpasswd";
 my $USERMAPDB    = "$WIKIDIR/data/.usermap";
+my $COOKIEDIR    = "/var/db/cgisess";
 my $WIKIUSER     = "nobody";
 my $WIKIGROUP    = "nobody";
 my $CI		 = "ci";
@@ -99,6 +100,9 @@ elsif ($action eq "addproject") {
 elsif ($action eq "setgroups") {
     exit(SetWikiGroups(@ARGV));
 }
+elsif ($action eq "xlogin") {
+    exit(WikixLogin(@ARGV));
+}
 else {
     die("*** $0:\n".
 	"    Do not know what to do with '$action'!\n");
@@ -724,6 +728,31 @@ sub CI($$) {
     }
     return $? >> 8;
 }
+
+#
+# Backdoor Login
+#
+sub WikixLogin(@)
+{
+    usage()
+	if (@_ != 2);
+
+    my ($user, $secretkey) = @_;
+
+    #
+    # Create a little file that holds the secret key, named by the user.
+    # The TWiki login script will check for the existence of this file,
+    # and use the key inside it to match against the key provided by the
+    # client browser.
+    #
+    open(KEY, ">${COOKIEDIR}/$user") or
+	fatal("Could not open ${COOKIEDIR}/$user for writing!");
+
+    print KEY "$secretkey\n";
+    print KEY time() . "\n";
+    close(KEY);
+    return 0;
+}
      
 sub fatal($)
 {
diff --git a/wiki/wikixlogin.in b/wiki/wikixlogin.in
new file mode 100644
index 0000000000..6c865130a1
--- /dev/null
+++ b/wiki/wikixlogin.in
@@ -0,0 +1,127 @@
+#!/usr/bin/perl -wT
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2005 University of Utah and the Flux Group.
+# All rights reserved.
+#
+use English;
+use Getopt::Std;
+use Fcntl ':flock';
+
+#
+# Add a user to the wiki on ops. Also allow update of password.
+#
+sub usage()
+{
+    print STDOUT "Usage: wikixlogin <uid> <key>\n";
+    exit(-1);
+}
+my $optlist = "d";
+my $debug   = 0;
+
+#
+# Configure variables
+#
+my $TB		= "@prefix@";
+my $TBOPS       = "@TBOPSEMAIL@";
+my $CONTROL     = "@USERNODE@";
+my $BOSSNODE	= "@BOSSNODE@";
+my $WIKISUPPORT = @WIKISUPPORT@;
+my $SSH         = "$TB/bin/sshtb";
+my $WIKIPROXY   = "$TB/sbin/wikiproxy";
+
+#
+# Untaint the path
+# 
+$ENV{'PATH'} = "/bin:/usr/bin";
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+#
+# Turn off line buffering on output
+#
+$| = 1;
+
+#
+# Load the Testbed support stuff. 
+#
+use lib "@prefix@/lib";
+use libdb;
+use libtestbed;
+
+#
+# We do not want to run this script unless its the real version.
+#
+if ($EUID != 0) {
+    die("*** $0:\n".
+	"    Must be setuid! Maybe its a development version?\n");
+}
+
+#
+# This script is setuid, so please do not run it as root. Hard to track
+# what has happened.
+# 
+if ($UID == 0) {
+    die("*** $0:\n".
+	"    Please do not run this as root! Its already setuid!\n");
+}
+
+#
+# If no wiki support, just exit. 
+#
+if (! $WIKISUPPORT) {
+    print "WIKI support is not enabled. Exit ...\n";
+    exit(0);
+}
+
+#
+# Parse command arguments. Once we return from getopts, all that should be
+# left are the required arguments.
+#
+%options = ();
+if (! getopts($optlist, \%options)) {
+    usage();
+}
+if (defined($options{"d"})) {
+    $debug = 1;
+}
+if (@ARGV != 2) {
+    usage();
+}
+my $user = $ARGV[0];
+my $key  = $ARGV[1];
+
+#
+# Untaint args.
+#
+if ($user =~ /^([-\w]+)$/) {
+    $user = $1;
+}
+else {
+    die("Bad data in user: $user.");
+}
+if ($key =~ /^([\w]+)$/) {
+    $key = $1;
+}
+else {
+    fatal("Bad data in secretkey!");
+}
+
+#
+# For ssh.
+#
+$UID = $EUID;
+
+if ($CONTROL ne $BOSSNODE) {
+    if (system("$SSH -host $CONTROL $WIKIPROXY xlogin $user '$key'")) {
+	fatal("$WIKIPROXY failed on $CONTROL!");
+    }
+}
+exit(0);
+
+sub fatal($)
+{
+    my($mesg) = $_[0];
+
+    die("*** $0:\n".
+	"    $mesg\n");
+}
diff --git a/www/defs.php3.in b/www/defs.php3.in
index 747746fb77..031e1f649c 100644
--- a/www/defs.php3.in
+++ b/www/defs.php3.in
@@ -16,7 +16,8 @@ $TBWWW		= "@TBWWW@";
 $THISHOMEBASE	= "@THISHOMEBASE@";
 $ELABINELAB     = @ELABINELAB@;
 $WIKISUPPORT    = @WIKISUPPORT@;
-$WIKIURL        = "https://${USERNODE}/twiki/bin/view";
+$WIKIURL        = "https://${USERNODE}/twiki/bin/newlogon";
+$WIKICOOKIENAME = "WikiCookie";
 
 $TBMAILADDR_OPS		= "@TBOPSEMAIL_NOSLASH@";
 $TBMAILADDR_WWW		= "@TBWWWEMAIL_NOSLASH@";
diff --git a/www/gotowiki.php3 b/www/gotowiki.php3
new file mode 100644
index 0000000000..97d8346488
--- /dev/null
+++ b/www/gotowiki.php3
@@ -0,0 +1,49 @@
+<?php
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2000-2005 University of Utah and the Flux Group.
+# All rights reserved.
+#
+include("defs.php3");
+
+if (!$WIKISUPPORT) {
+    header("Location: index.php3");
+    return;
+}
+
+# No Pageheader since we spit out a redirection below.
+$uid = GETLOGIN();
+LOGGEDINORDIE($uid, CHECKLOGIN_USERSTATUS|
+	      CHECKLOGIN_WEBONLY|CHECKLOGIN_WIKIONLY);
+
+#
+# The page to zap to on the other side
+#
+if (isset($redurl) && $redurl == "") {
+    unset($redurl);
+}
+
+#
+# Look for our wikicookie. If the browser has it, then there is nothing
+# more to do; just redirect the user over to the wiki.
+#
+if (isset($_COOKIE[$WIKICOOKIENAME])) {
+    $wikihash = $_COOKIE[$WIKICOOKIENAME];
+    
+    header("Location: ${WIKIURL}?username=${uid}&bosscred=${wikihash}" .
+	   (isset($redurl) ? "&redurl=${redurl}" : ""));
+    return;
+}
+
+#
+# Generate a cookie. Send it over to the wiki server and stash it into
+# the users browser for subsequent requests (until logout).
+# 
+$wikihash = GENHASH();
+
+SUEXEC("nobody", "nobody", "wikixlogin $uid $wikihash", SUEXEC_ACTION_DIE);
+
+setcookie($WIKICOOKIENAME, $wikihash, 0, "/", $TBAUTHDOMAIN, $TBSECURECOOKIES);
+header("Location: ${WIKIURL}?username=${uid}&bosscred=${wikihash}" .
+	   (isset($redurl) ? "&redurl=${redurl}" : ""));
+?>
diff --git a/www/menu.php3 b/www/menu.php3
index 00e8527f80..345d9f54ae 100644
--- a/www/menu.php3
+++ b/www/menu.php3
@@ -366,8 +366,8 @@ function WRITESIDEBAR() {
 		    $wikiname = $CHECKLOGIN_WIKINAME;
 		
 		    WRITESIDEBARBUTTON_ABSCOOL("My Wikis",
-					       "${WIKIURL}/Main/$wikiname",
-					       "${WIKIURL}/Main/$wikiname");
+			       "gotowiki.php3?redurl=Main/$wikiname",
+			       "gotowiki.php3?redurl=Main/$wikiname");
 		}
 	    
 		WRITESIDEBARBUTTON("Update User Information",
@@ -391,8 +391,8 @@ function WRITESIDEBAR() {
 		    $wikiname = $CHECKLOGIN_WIKINAME;
 		
 		    WRITESIDEBARBUTTON_ABSCOOL("My Wikis",
-					       "${WIKIURL}/Main/$wikiname",
-					       "${WIKIURL}/Main/$wikiname");
+			       "gotowiki.php3?redurl=Main/$wikiname",
+			       "gotowiki.php3?redurl=Main/$wikiname");
 		}
 	    
                 # Since a user can be a member of more than one project,
diff --git a/www/showstuff.php3 b/www/showstuff.php3
index 9cab86341d..f2c80c87ce 100644
--- a/www/showstuff.php3
+++ b/www/showstuff.php3
@@ -14,7 +14,7 @@
 # A project
 #
 function SHOWPROJECT($pid, $thisuid) {
-    global $WIKISUPPORT, $WIKIURL;
+    global $WIKISUPPORT;
 
     $query_result =
 	DBQueryFatal("select p.*,g.wikiname from projects as p ".
@@ -92,7 +92,7 @@ function SHOWPROJECT($pid, $thisuid) {
           </tr>\n";
 
     if ($WIKISUPPORT && isset($wikiname)) {
-	$wikiurl = "${WIKIURL}/$wikiname/WebHome";
+	$wikiurl = "gotowiki.php3?redurl=$wikiname/WebHome";
 	
 	echo "<tr>
                   <td>Project Wiki:</td>
@@ -383,7 +383,7 @@ function SHOWGROUPMEMBERSHIP($uid) {
 # A User
 #
 function SHOWUSER($uid) {
-    global $WIKISUPPORT, $WIKIURL;
+    global $WIKISUPPORT;
 
     $userinfo_result =
 	DBQueryFatal("SELECT * from users where uid='$uid'");
@@ -468,7 +468,7 @@ function SHOWUSER($uid) {
           </tr>\n";
 
     if ($WIKISUPPORT && isset($wikiname)) {
-	$wikiurl = "${WIKIURL}/Main/$wikiname";
+	$wikiurl = "gotowiki.php3?redurl=Main/$wikiname";
 	
 	echo "<tr>
                   <td>Emulab Wiki Page:</td>
diff --git a/www/tbauth.php3 b/www/tbauth.php3
index 9424c7ff30..7e0185bbf6 100644
--- a/www/tbauth.php3
+++ b/www/tbauth.php3
@@ -530,6 +530,7 @@ function DOLOGIN($token, $password, $adminmode = 0) {
     global $TBAUTHCOOKIE, $TBAUTHDOMAIN, $TBAUTHTIMEOUT;
     global $TBNAMECOOKIE, $TBLOGINCOOKIE, $TBSECURECOOKIES;
     global $TBMAIL_OPS, $TBMAIL_AUDIT, $TBMAIL_WWW;
+    global $WIKISUPPORT, $WIKICOOKIENAME;
     
     # Caller makes these checks too.
     if ((!TBvalid_uid($token) && !TBvalid_email($token)) ||
@@ -785,6 +786,7 @@ function VERIFYPASSWD($uid, $password) {
 #
 function DOLOGOUT($uid) {
     global $CHECKLOGIN_STATUS, $TBAUTHCOOKIE, $TBLOGINCOOKIE, $TBAUTHDOMAIN;
+    global $WIKISUPPORT, $WIKICOOKIENAME;
 
     # Pedantic check.
     if (!TBvalid_uid($uid)) {
@@ -811,6 +813,9 @@ function DOLOGOUT($uid) {
     #
     setcookie($TBAUTHCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
     setcookie($TBLOGINCOOKIE, "", $timeout, "/", $TBAUTHDOMAIN, 0);
+    if ($WIKISUPPORT) {
+	setcookie($WIKICOOKIENAME, "", $timeout, "/", $TBAUTHDOMAIN, 0);
+    }
 
     return 0;
 }
-- 
GitLab