[LON-CAPA-cvs] cvs: modules /raeburn monitor.pl

raeburn lon-capa-cvs@mail.lon-capa.org
Tue, 30 Jan 2007 15:51:51 -0000


This is a MIME encoded message

--raeburn1170172311
Content-Type: text/plain

raeburn		Tue Jan 30 10:51:51 2007 EDT

  Modified files:              
    /modules/raeburn	monitor.pl 
  Log:
  Additional optional fourth and fifth arguments (loadbal and sso) accepted by command line invocation of monitor.pl
  If loadbal, script parses switch server page returned after log-in and performs roles initialization, course initialization and logout on the destination server.
  
  If sso, log-in is against the SSO log-in page, and success is checked against the expected redirect destination.  Roles selection, course initialization and logout follow on appropriate LON-CAPA server (depends on loadbal). 
  
  
--raeburn1170172311
Content-Type: text/plain
Content-Disposition: attachment; filename="raeburn-20070130105151.txt"

Index: modules/raeburn/monitor.pl
diff -u modules/raeburn/monitor.pl:1.4 modules/raeburn/monitor.pl:1.5
--- modules/raeburn/monitor.pl:1.4	Mon Jul 11 16:37:26 2005
+++ modules/raeburn/monitor.pl	Tue Jan 30 10:51:49 2007
@@ -11,6 +11,7 @@
 # monitor.pl
 #
 # Stuart Raeburn, March 28, 2005
+# updated January 28, 2007
 #
 #########################################################
 # 
@@ -70,7 +71,7 @@
 # of any of the failure types occurred; whereas itds@msu.edu would only be
 # contacted if a conlost failure (no response from web server) occurred.
 #
-# The failure types are as follows:
+# The failure types for non-SSO servers are as follows:
 # conlost  - Request for login page times out ( > 20s) or returns error
 # unavailable - Server is unable to talk to itself via lonc/lond.
 # missingparam - Log-in page returned too few parameters  -- 4 required   
@@ -84,6 +85,21 @@
 # error (401,403,404,500 etc.)
 # logoutfailed - attempt to logout after loading navmap failed.
 #
+# For the SSO server at MSU, the following failure modified types apply:
+# conlost  - Request for login page times out ( > 20s) or returns error
+# missingparam - Log-in page returned too few parameters -- 2 required
+#                 name of password form element, EncryptedStamp
+# unauthenticated - authentication failed -- response from Sentinel server 
+#                                        was not a redirect.
+# invalidcookie - (a) redirect was not to welcome page;
+#                 (b) lonID cookie could not be extracted from HTTP header    
+# nologin - attempt to login resulted in error (401,403,404,500 etc.)
+# uninitialized - attempt to enter a course failed - could not initialize
+# rolesfailed - attempt to enter a course resulted in error (401,403,404,500)
+# navmapfailed - attempt to retrieve navmap failed - e.g., took  > 60 s, or
+# error (401,403,404,500 etc.)
+# logoutfailed - attempt to logout after loading navmap failed.
+# 
 # Page requests are submitted sequentially.  When a failure occurs, requests
 # stop and actions are carried out for the specific failure type.
 #
@@ -96,11 +112,32 @@
 # times and add to MRTG monitoring as a means of tracking server performance
 # under different load conditions.
 # 
-# This script itself should be called with three parameters:
+# This script itself should be called with a minimum of three parameters:
 # encryption key (key used for DES encryption of the auto-user's password), 
 # hostname of the server (e.g., s1.lite.ms.edu), 
 # server nickname (e.g., s1).
-
+# 
+# An optional fourth parameter is included if the log-in server is acting as
+# a loadbalancer, in which case, switch to a different server occurs after
+# successful authentication 
+#
+# An optional fifth parameter is included if SSO is being used
+# In this case two additional files must exist: ssoconfig.txt & ssourls.txt
+#
+# The format of these files is as follows:
+#   - ssoconfig.txt (Content POSTed to SSO log-in page)
+#     (a) Fixed fields (no screen-scraped data required)
+#     name=value
+#     (b) Variable fields (either element name or value screen-scraped)
+#     field:name=value              (name and/or value may be blank)
+#     examples are - username:AlternateID=username, password:=password
+#          username field - name is AlternateID
+#          password field - name must be screen-scraped
+#
+#   - ssourls.txt (URLs for login, and expected redirect when authenticated)
+#     login=https://login.msu.edu/AppLogin.Asp
+#     redirect=https://login.msu.edu/AppWelcome.Asp?
+#
 ########################################################
 # Configuration
 #
@@ -108,6 +145,8 @@
 #my $path_to_java = '/usr/java/j2sdk1.4.2_05/bin/java';
 my $path_to_java = '/usr/java/j2re1.4.2_07/bin/java';
 my $contact_email = 'helpdesk@loncapa.org';
+my $ssoconfig = $monitordir.'/ssoconfig.txt';
+my $ssourls = $monitordir.'/ssourls.txt';
 #
 #########################################################
 
@@ -117,13 +156,43 @@
 my ($uname,$udom,$role,$upass);
 
 if (@ARGV < 3) {
-    print "Usage: $0 - encryptionkey hostname servernickname\n";
+    print "Usage: $0 - encryptionkey hostname servernickname [loadbal] [sso]\n";
     exit(0);
 }
 
 my $keyphrase = $ARGV[0];
 my $server = $ARGV[1];
 my $serveralias = $ARGV[2];
+my $loadbalance = $ARGV[3];
+my $sso = $ARGV[4];
+my (%ssoparam,%ssourl,%recipients,@formitems);
+if ($sso) {
+    if (open (my $fh, "<$ssoconfig") ) {
+        my @buffer = <$fh>;
+        chomp(@buffer);
+        foreach my $line (@buffer) {
+            if ($line =~ /:/) {
+                my ($field,$what) = split(/:/,$line);
+                my ($name,$value) = split(/=/,$what);
+                if ($value eq '') {
+                    push(@formitems,$field);
+                }
+                $ssoparam{'variable'}{$field} = {$name => $value};
+            } else {
+                my ($name,$value) = split(/=/,$line);
+                push(@{$ssoparam{'fixed'}},$name => $value);
+            }
+        }
+    }
+    if (open (my $fh, "<$ssourls") ) {
+        my @buffer = <$fh>;
+        chomp(@buffer);
+        foreach my $line (@buffer) {
+            my ($name,$value) = split(/=/,$line);
+            $ssourl{$name} = $value;
+        }
+    }
+}
 my $authkeyfile = $monitordir.'/'.$serveralias.'/autouser.dat';
 
 if (open (my $fh, "<$authkeyfile") ) {
@@ -133,7 +202,6 @@
     $upass = $cipher->decrypt_hex($upass);
     close($fh);
 }
-my %recipients = ();
 if (open (my $fh, "<$monitordir/$serveralias/recipients")) {
     my @buffer = <$fh>;
     close($fh);
@@ -148,10 +216,13 @@
     }
 }
 
-my @formitems = ('lextkey','uextkey','logtoken','serverid');
+if (!$sso) {
+    @formitems = ('lextkey','uextkey','logtoken','serverid');
+}
+
 my %formvalues = ();
 my %loadtimes = ();
-my ($outcome,$loginpage,$lonid);
+my ($outcome,$loginpage,$lonid,$loadbalserver);
 my @failures = ('conlost','unavailable','missingparam','unauthenticated','invalidcookie','nologin','uninitialized','rolesfailed','navmapfailed','logoutfailed');
 
 if (!-e "$monitordir/$serveralias") {
@@ -166,7 +237,7 @@
 
 my $logfile = $monitordir.'/'.$serveralias.'/log'; 
 
-$outcome = &attempt_access($outcome,$loginpage,$lonid,$role,\@formitems,\%formvalues,\%loadtimes,$monitordir,$path_to_java);
+($outcome,$loadbalserver) = &attempt_access($outcome,$server,$loginpage,$lonid,$role,\@formitems,\%formvalues,\%loadtimes,$monitordir,$path_to_java,$loncookie_file,$loadbalance,$sso,\%ssoparam,\%ssourl);
 my $mailflag = &alertstatus($outcome,$server,$serveralias,\@failures,$logfile,$monitordir);
 if ($mailflag) {
     my $mailresult = &mailalert($server,$outcome,$contact_email,\%recipients);
@@ -175,16 +246,39 @@
     close($logfh);
 }
 
+open(TIMES,">>$monitordir/$serveralias/times");
+my $serverid;
+if ($sso) {
+    print TIMES localtime(time)." sso $loadtimes{'sso'}\n";
+}
+if ($loadbalance) {
+    $serverid = ' '.$loadbalserver;
+}
+foreach my $key ('login','initialize','navmap') {
+    print TIMES localtime(time).' '.$key.' '.$loadtimes{$key}.$serverid."\n";
+}
+close(TIMES);
+
 sub attempt_access {
-    my ($outcome,$loginpage,$lonid,$role,$formitems,$formvalues,$loadtimes,$monitordir,$path_to_java) = @_;
-    ($outcome,$loginpage) = &get_loginpage($server,$ua,$loadtimes);
+    my ($outcome,$server,$loginpage,$lonid,$role,$formitems,$formvalues,
+        $loadtimes,$monitordir,$path_to_java,$loncookie_file,$loadbalance,
+        $sso,$ssoparamref,$ssourlref) = @_;
+    ($outcome,$loginpage) = &get_loginpage($server,$ua,$loncookie_file,$sso);
+    my $loadbalserver;
     if ($outcome eq 'ok') {
         $ua->timeout(30);
         if ($loginpage) {
-            $outcome = &parse_loginpage($loginpage,$formitems,$formvalues);
+            $outcome = &parse_loginpage($loginpage,$formitems,$formvalues,$sso);
             if ($outcome eq 'ok') {
-                ($outcome,$lonid) = &logmein($server,$uname,$udom,$upass,$ua,$loncookie_file,$formvalues,$loadtimes,$monitordir,$path_to_java);
+                if ($sso) {
+                    ($outcome,$lonid) = &ssologmein(\$server,$uname,$udom,$upass,$ua,$loncookie_file,$formvalues,$loadtimes,$monitordir,$loadbalance,$ssoparamref,$ssourlref);
+                } else {
+                    ($outcome,$lonid) = &logmein(\$server,$uname,$udom,$upass,$ua,$loncookie_file,$formvalues,$loadtimes,$monitordir,$path_to_java,$loadbalance);
+                }
                 if ($outcome eq 'ok')  {
+                    if ($loadbalance) {
+                        ($loadbalserver) = ($server =~ /^(s\d+)\./);
+                    }
                     if ($lonid) {
                         &setcookie($loncookie_file,$lonid,$server);
                         $outcome = &pickrole($server,$loncookie_file,$ua,$role,$loadtimes);
@@ -204,20 +298,27 @@
             }
         }
     }
-    return $outcome;
+    return ($outcome,$loadbalserver);
 }
 
 sub get_loginpage {
-    my ($server,$ua) = @_;
+    my ($server,$ua,$loncookie_file,$sso) = @_;
     my ($outcome,$loginpage);
-    my $URL = 'http://'.$server.'/adm/login';
+    my $URL = 'http://'.$server.'/';
+    if ($sso) {
+        $URL .= 'adm/roles';
+    } else {
+        $URL .= 'adm/login';
+    }
     my $request = new HTTP::Request;
     $request =  GET $URL;
+    $ua->cookie_jar( $loncookie_file );
     my $res = $ua->request($request);
     if ($res->is_success) {
         my $dump = $res->content;
         $outcome = 'ok';
         $loginpage = $dump;
+        $loncookie_file->extract_cookies($res);
     } else {
         $outcome = 'conlost';
     }
@@ -225,28 +326,40 @@
 }
 
 sub parse_loginpage {
-    my ($loginpage,$formitems,$formvalues) = @_;
+    my ($loginpage,$formitems,$formvalues,$sso) = @_;
     my $outcome = 0;
     my $paramcount = 0;
-    if ($loginpage =~ m-src="/adm/lonKaputt/lonlogo_broken\.gif"-) {
-        $outcome = "unavailable";
-    } else {
-        foreach (@{$formitems}) {
-            if ($loginpage =~ m/<input\stype="hidden"\sname="$_"\svalue="(-?\w+)"\s?\/?>/) {
-                $$formvalues{$_} = $1;
-                $paramcount ++;
-            }
+    if ($sso) {
+        if ($loginpage =~ m/<input\sname="([^"]+)"\stype="password"\svalue=""\ssize="10"\smaxlength="64"\s\/>/) {
+            $formvalues->{'password'} = $1;
+            $paramcount ++;
         }
-        if ($paramcount == scalar(@formitems)) {
-            $outcome = 'ok';
+        if ($loginpage =~ m/<input\sname="EncryptedStamp"\svalue="([^"]+)"\stype="hidden"\s\/>/) {
+            $formvalues->{'EncryptedStamp'} = $1;
+            $paramcount ++;
+        }
+    } else {
+        if ($loginpage =~ m-src="/adm/lonKaputt/lonlogo_broken\.gif"-) {
+            $outcome = "unavailable";
         } else {
-            $outcome = 'missingparam';
+            foreach my $item (@{$formitems}) {
+                if ($loginpage =~ m/<input\stype="hidden"\sname="\Q$item\E"\svalue="(-?\w+)"\s?\/?>/) {
+                    $formvalues->{$item} = $1;
+                    $paramcount ++;
+                }
+            }
         }
     }
+    if ($paramcount == scalar(@{$formitems})) {
+        $outcome = 'ok';
+    } else {
+        $outcome = 'missingparam';
+    }
     return $outcome;
 }
+
 sub logmein {
-    my ($server,$uname,$udom,$upass,$ua,$loncookie_file,$formvalues,$loadtimes,$monitordir,$path_to_java) = @_;
+    my ($serverref,$uname,$udom,$upass,$ua,$loncookie_file,$formvalues,$loadtimes,$monitordir,$path_to_java,$loadbalance) = @_;
     my ($outcome,$lonid);
     my $upass0 = substr($upass,0,15);
     my $upass1 = substr($upass,15,15);
@@ -262,7 +375,7 @@
     open(PIPE,"-|") || exec "$path_to_java -classpath $classpath org.mozilla.javascript.tools.shell.Main $londesfile $$formvalues{uextkey} $$formvalues{lextkey} $upass2";
     my $cryppass2 = <PIPE>;
     close PIPE;
-    my $URL = 'http://'.$server.'/adm/authenticate';
+    my $URL = 'http://'.$$serverref.'/adm/authenticate';
     $ua->cookie_jar( $loncookie_file );
     my $req = POST $URL,
       Content_Type => 'application/x-www-form-urlencoded',
@@ -284,6 +397,15 @@
             $outcome = 'missingparam';
         } elsif ($dump =~ m-Username\sand\/or\spassword\scould\snot\sbe\sauthenticated-) {
             $outcome = 'unauthenticated';
+        } elsif ($loadbalance) {
+            (my $switchurl,$$serverref) = &parse_switchpage($res->content,
+                                                            $uname,$udom);
+            if ($switchurl) {
+                ($outcome,$lonid) =
+                    &switchserver($switchurl,$ua,$loadtimes,$loncookie_file);
+            } else {
+                $outcome = 'nologin';
+            }
         } else {
             $loncookie_file->extract_cookies($res);
             my $cookie = $loncookie_file->as_string;
@@ -303,6 +425,140 @@
     return ($outcome,$lonid);
 }
 
+sub ssologmein {
+    my ($serverref,$uname,$udom,$upass,$ua,$loncookie_file,$formvalues,$loadtimes,$monitordir,$loadbalance,$ssoparamref,$ssourlref) = @_;
+    my ($outcome,$lonid);
+    my $URL = $ssourlref->{'login'};
+    $ua->cookie_jar( $loncookie_file );
+    my $content;
+    if (ref($ssoparamref->{'fixed'}) eq 'ARRAY') {
+        $content = $ssoparamref->{'fixed'};
+    }
+    if (ref($ssoparamref->{'variable'}) eq 'HASH') {
+        foreach my $field (keys(%{$ssoparamref->{'variable'}})) {
+            if ($field eq 'password') {
+                my $passfield;
+                if ($formvalues->{'password'} ne '') {
+                    $passfield = $formvalues->{'password'};
+                } else {
+                    my @items = keys(%{$$ssoparamref{'variable'}{'password'}});
+                    $passfield = $items[0];
+                }
+                push(@{$content},$passfield => $upass);
+            } elsif ($field eq 'username') {
+                my $userfield;
+                if ($formvalues->{'username'} ne '') {
+                    $userfield = $formvalues->{'username'};
+                } else {
+                    my @items = keys(%{$$ssoparamref{'variable'}{'username'}});
+                    $userfield = $items[0];
+                }
+                push(@{$content},$userfield => $uname);
+            } else {
+               my @items = keys(%{$$ssoparamref{'variable'}{$field}});
+               my $fieldname = $items[0];
+               my $fieldvalue;
+               if ($formvalues->{$field} ne '') {
+                   $fieldvalue = $formvalues->{$field};      
+               }
+               push (@{$content},$fieldname => $fieldvalue);
+            }
+        }
+    }
+    my $req = POST $URL,
+              Content_Type => 'application/x-www-form-urlencoded',
+              Content => $content;
+    $loncookie_file->add_cookie_header($req);
+    my $start = [gettimeofday];
+    my $res = $ua->request($req);
+    $loncookie_file->extract_cookies($res);
+    my $cookie = $loncookie_file->as_string;
+    if ($res->is_redirect()) {
+        my $newurl = $res->header( "location" );
+        if ($newurl eq $ssourlref->{'redirect'}) {
+            my $end = [gettimeofday];
+            $loadtimes{sso} = tv_interval $start, $end;
+            $loadtimes{sso} *= 1000;
+            ($outcome,$lonid) = 
+                &welcomepage($ua,$newurl,$serverref,$loncookie_file,$uname,$udom,$loadtimes,$monitordir);
+        } else {
+            $outcome = 'invalidcookie';
+        }
+    } else {
+        $outcome = 'unauthenticated';
+    }
+    return ($outcome,$lonid);
+}
+
+sub welcomepage {
+    my ($ua,$newurl,$serverref,$loncookie_file,$uname,$udom,$loadtimes,$monitordir) = @_;
+    my ($outcome,$lonid);
+    my $start = [gettimeofday];
+    my $request = GET $newurl;
+    $loncookie_file->add_cookie_header($request);
+    my $response = $ua->request($request);
+    if ($response->is_success) {
+        if ($loadbalance) {
+            (my $switchurl,$$serverref) = &parse_switchpage($response->content,
+                                                            $uname,$udom); 
+            if ($switchurl) {  
+                ($outcome,$lonid) = 
+                    &switchserver($switchurl,$ua,$loadtimes,$loncookie_file);
+            } else {
+                $outcome = 'nologin';
+            }
+        } else {
+            $loncookie_file->extract_cookies($response);
+            my $cookie = $loncookie_file->as_string;
+            if ($cookie =~ m/lonID=(\w+);/) {
+                $lonid = $1;
+                $outcome = 'ok';
+            } else {
+                $outcome = 'invalidcookie';
+            }
+        } 
+    } else {
+        $outcome = 'nologin';
+    }
+    my $end = [gettimeofday];
+    $loadtimes{login} = tv_interval $start, $end;
+    $loadtimes{login} *= 1000;
+    return ($outcome,$lonid);
+}
+
+sub parse_switchpage {
+    my ($content,$uname,$udom) = @_;
+    if ($content =~ m{<meta\sHTTP\-EQUIV="Refresh"\sCONTENT="0\.5;\surl=(http://)([^/]+)(/adm/login\?domain=\Q$udom\E&username=\Q$uname\E&token=\w+)">}s ) {
+         return ($1.$2.$3, $2);
+    }
+    return;
+}
+
+sub switchserver {
+    my ($url,$ua,$loadtimes,$loncookie_file) = @_;
+    my ($outcome,$lonid);
+    my $request = GET $url;
+    $loncookie_file->add_cookie_header($request);
+    my $res = $ua->request($request);
+    my $start = [gettimeofday];
+    if ($res->is_success) {
+        $loncookie_file->extract_cookies($res);
+        my $cookie = $loncookie_file->as_string;
+        if ($cookie =~ m/lonID=(\w+);/) {
+            $lonid = $1;
+            $outcome = 'ok';
+        } else {
+            $outcome = 'invalidcookie';
+        }
+        my $end = [gettimeofday];
+        $loadtimes{login} = tv_interval $start, $end;
+        $loadtimes{login} *= 1000;
+    } else {
+        $outcome = 'nologin';
+    }
+    return ($outcome,$lonid);
+}
+
 sub pickrole {
     my ($server,$loncookie_file,$ua,$role,$loadtimes) = @_;
     my ($start,$end,$outcome);

--raeburn1170172311--