--- loncom/lond	2009/09/28 19:13:32	1.427
+++ loncom/lond	2010/07/17 20:01:56	1.447
@@ -2,7 +2,7 @@
 # The LearningOnline Network
 # lond "LON Daemon" Server (port "LOND" 5663)
 #
-# $Id: lond,v 1.427 2009/09/28 19:13:32 raeburn Exp $
+# $Id: lond,v 1.447 2010/07/17 20:01:56 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -42,7 +42,6 @@ use Crypt::IDEA;
 use LWP::UserAgent();
 use Digest::MD5 qw(md5_hex);
 use GDBM_File;
-use Authen::Krb4;
 use Authen::Krb5;
 use localauth;
 use localenroll;
@@ -59,7 +58,7 @@ my $DEBUG = 0;		       # Non zero to ena
 my $status='';
 my $lastlog='';
 
-my $VERSION='$Revision: 1.427 $'; #' stupid emacs
+my $VERSION='$Revision: 1.447 $'; #' stupid emacs
 my $remoteVERSION;
 my $currenthostid="default";
 my $currentdomainid;
@@ -67,6 +66,8 @@ my $currentdomainid;
 my $client;
 my $clientip;			# IP address of client.
 my $clientname;			# LonCAPA name of client.
+my $clientversion;              # LonCAPA version running on client
+my @clientdoms;                 # Array of domains on $clientip
 
 my $server;
 
@@ -975,6 +976,9 @@ sub read_profile {
 				 &GDBM_READER());
     if ($hashref) {
         my @queries=split(/\&/,$what);
+        if ($namespace eq 'roles') {
+            @queries = map { &unescape($_); } @queries; 
+        }
         my $qresult='';
 	
 	for (my $i=0;$i<=$#queries;$i++) {
@@ -1762,15 +1766,37 @@ sub authenticate_handler {
     #  upass   - User's password.
     #  checkdefauth - Pass to validate_user() to try authentication
     #                 with default auth type(s) if no user account.
+    #  clientcancheckhost - Passed by clients with functionality in lonauth.pm
+    #                       to check if session can be hosted.
     
-    my ($udom, $uname, $upass, $checkdefauth)=split(/:/,$tail);
+    my ($udom, $uname, $upass, $checkdefauth, $clientcancheckhost)=split(/:/,$tail);
     &Debug(" Authenticate domain = $udom, user = $uname, password = $upass,  checkdefauth = $checkdefauth");
     chomp($upass);
     $upass=&unescape($upass);
 
     my $pwdcorrect = &validate_user($udom,$uname,$upass,$checkdefauth);
     if($pwdcorrect) {
-	&Reply( $client, "authorized\n", $userinput);
+        my $canhost = 1;
+        unless ($clientcancheckhost) {
+            unless (grep(/^\Q$udom\E$/,@clientdoms)) {
+                my ($remote,$hosted);
+                my $remotesession = &get_usersession_config($udom,'remotesession');
+                if (ref($remotesession) eq 'HASH') {
+                    $remote = $remotesession->{'remote'}
+                }
+                my $hostedsession = &get_usersession_config($clientdoms[0],'hostedsession');
+                if (ref($hostedsession) eq 'HASH') {
+                    $hosted = $hostedsession->{'hosted'};
+                }
+                $canhost = &Apache::lonnet::can_host_session($udom,$currentdomainid,$clientversion,
+                                                             $remote,$hosted);
+            }
+        }
+        if ($canhost) {               
+            &Reply( $client, "authorized\n", $userinput);
+        } else {
+            &Reply( $client, "not_allowed_to_host\n", $userinput);
+        }
 	#
 	#  Bad credentials: Failed to authorize
 	#
@@ -1815,8 +1841,9 @@ sub change_password_handler {
     #  npass - New password.
     #  context - Context in which this was called 
     #            (preferences or reset_by_email).
+    #  lonhost - HostID of server where request originated 
    
-    my ($udom,$uname,$upass,$npass,$context)=split(/:/,$tail);
+    my ($udom,$uname,$upass,$npass,$context,$lonhost)=split(/:/,$tail);
 
     $upass=&unescape($upass);
     $npass=&unescape($npass);
@@ -1825,9 +1852,13 @@ sub change_password_handler {
     # First require that the user can be authenticated with their
     # old password unless context was 'reset_by_email':
     
-    my $validated;
+    my ($validated,$failure);
     if ($context eq 'reset_by_email') {
-        $validated = 1;
+        if ($lonhost eq '') {
+            $failure = 'invalid_client';
+        } else {
+            $validated = 1;
+        }
     } else {
         $validated = &validate_user($udom, $uname, $upass);
     }
@@ -1841,8 +1872,11 @@ sub change_password_handler {
 	    $salt=substr($salt,6,2);
 	    my $ncpass=crypt($npass,$salt);
 	    if(&rewrite_password_file($udom, $uname, "internal:$ncpass")) {
-		&logthis("Result of password change for "
-			 ."$uname: pwchange_success");
+		my $msg="Result of password change for $uname: pwchange_success";
+                if ($lonhost) {
+                    $msg .= " - request originated from: $lonhost";
+                }
+                &logthis($msg);
 		&Reply($client, "ok\n", $userinput);
 	    } else {
 		&logthis("Unable to open $uname passwd "               
@@ -1863,7 +1897,10 @@ sub change_password_handler {
 	}  
 	
     } else {
-	&Failure( $client, "non_authorized\n", $userinput);
+	if ($failure eq '') {
+	    $failure = 'non_authorized';
+	}
+	&Failure( $client, "$failure\n", $userinput);
     }
 
     return 1;
@@ -2050,16 +2087,10 @@ sub is_home_handler {
 &register_handler("home", \&is_home_handler, 0,1,0);
 
 #
-#   Process an update request for a resource?? I think what's going on here is
-#   that a resource has been modified that we hold a subscription to.
+#   Process an update request for a resource.
+#   A resource has been modified that we hold a subscription to.
 #   If the resource is not local, then we must update, or at least invalidate our
 #   cached copy of the resource. 
-#   FUTURE WORK:
-#      I need to look at this logic carefully.  My druthers would be to follow
-#      typical caching logic, and simple invalidate the cache, drop any subscription
-#      an let the next fetch start the ball rolling again... however that may
-#      actually be more difficult than it looks given the complex web of
-#      proxy servers.
 # Parameters:
 #    $cmd      - The command that got us here.
 #    $tail     - Tail of the command (remaining parameters).
@@ -2083,16 +2114,23 @@ sub update_resource_handler {
     my $ownership=ishome($fname);
     if ($ownership eq 'not_owner') {
 	if (-e $fname) {
+            # Delete preview file, if exists
+            unlink("$fname.tmp");
+            # Get usage stats
 	    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
 		$atime,$mtime,$ctime,$blksize,$blocks)=stat($fname);
 	    my $now=time;
 	    my $since=$now-$atime;
+            # If the file has not been used within lonExpire seconds,
+            # unsubscribe from it and delete local copy
 	    if ($since>$perlvar{'lonExpire'}) {
 		my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname");
 		&devalidate_meta_cache($fname);
 		unlink("$fname");
 		unlink("$fname.meta");
 	    } else {
+            # Yes, this is in active use. Get a fresh copy. Since it might be in
+            # very active use and huge (like a movie), copy it to "in.transfer" filename first.
 		my $transname="$fname.in.transfer";
 		my $remoteurl=&Apache::lonnet::reply("sub:$fname","$clientname");
 		my $response;
@@ -2120,6 +2158,7 @@ sub update_resource_handler {
 			}
 			alarm(0);
 		    }
+                    # we successfully transfered, copy file over to real name
 		    rename($transname,$fname);
 		    &devalidate_meta_cache($fname);
 		}
@@ -3110,6 +3149,20 @@ sub dump_with_regexp {
         my $qresult='';
 	my $count=0;
 	while (my ($key,$value) = each(%$hashref)) {
+            if ($namespace eq 'roles') {
+                if ($key =~ m{^/($LONCAPA::match_domain)/($LONCAPA::match_community)_(cc|co|in|ta|ep|ad|st|cr)}) {
+                    my $cdom = $1;
+                    my $cnum = $2;
+                    if ($clientversion =~ /^\'?(\d+)\.(\d+)/) {
+                        my $major = $1;
+                        my $minor = $2;
+                        next if (($major < 2) || (($major == 2) && ($minor < 9)));
+                    } else {
+                        my $homeserver = &Apache::lonnet::homeserver($cnum,$cdom);
+                        next unless ($currenthostid eq $homeserver);
+                    }
+                }
+            }
 	    if ($regexp eq '.') {
 		$count++;
 		if (defined($range) && $count >= $end)   { last; }
@@ -3703,6 +3756,9 @@ sub put_course_id_hash_handler {
 #                 createdafter - include courses for which creation date followed this date.
 #                 creationcontext - include courses created in specified context 
 #
+#                 domcloner - flag to indicate if user can create CCs in course's domain.
+#                             If so, ability to clone course is automatic. 
+#
 #     $client  - The socket open on the client.
 # Returns:
 #    1     - Continue processing.
@@ -3715,7 +3771,7 @@ sub dump_course_id_handler {
     my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter,
         $typefilter,$regexp_ok,$rtn_as_hash,$selfenrollonly,$catfilter,$showhidden,
         $caller,$cloner,$cc_clone_list,$cloneonly,$createdbefore,$createdafter,
-        $creationcontext) =split(/:/,$tail);
+        $creationcontext,$domcloner) =split(/:/,$tail);
     my $now = time;
     my ($cloneruname,$clonerudom,%cc_clone);
     if (defined($description)) {
@@ -3773,22 +3829,21 @@ sub dump_course_id_handler {
             $cc_clone{$clonedom.'_'.$clonenum} = 1;
         } 
     }
-    if (defined($createdbefore)) {
+    if ($createdbefore ne '') {
         $createdbefore = &unescape($createdbefore);
     } else {
        $createdbefore = 0;
     }
-    if (defined($createdafter)) {
+    if ($createdafter ne '') {
         $createdafter = &unescape($createdafter);
     } else {
         $createdafter = 0;
     }
-    if (defined($creationcontext)) {
+    if ($creationcontext ne '') {
         $creationcontext = &unescape($creationcontext);
     } else {
         $creationcontext = '.';
     }
-    
     my $unpack = 1;
     if ($description eq '.' && $instcodefilter eq '.' && $coursefilter eq '.' && 
         $typefilter eq '.') {
@@ -3815,8 +3870,13 @@ sub dump_course_id_handler {
             my ($canclone,$valchange);
             my $items = &Apache::lonnet::thaw_unescape($value);
             if (ref($items) eq 'HASH') {
+                if ($hashref->{$lasttime_key} eq '') {
+                    next if ($since > 1);
+                }
                 $is_hash =  1;
-                if (defined($clonerudom)) {
+                if ($domcloner) {
+                    $canclone = 1;
+                } elsif (defined($clonerudom)) {
                     if ($items->{'cloners'}) {
                         my @cloneable = split(',',$items->{'cloners'});
                         if (@cloneable) {
@@ -3844,6 +3904,19 @@ sub dump_course_id_handler {
                             $items->{'cloners'} = $cloneruname.':'.$clonerudom;
                             $valchange = 1;
                         }
+                        unless ($canclone) {
+                            if ($items->{'owner'} =~ /:/) {
+                                if ($items->{'owner'} eq $cloner) {
+                                    $canclone = 1;
+                                }
+                            } elsif ($cloner eq $items->{'owner'}.':'.$udom) {
+                                $canclone = 1;
+                            }
+                            if ($canclone) {
+                                $items->{'cloners'} = $cloneruname.':'.$clonerudom;
+                                $valchange = 1;
+                            }
+                        }
                     }
                 }
                 if ($unpack || !$rtn_as_hash) {
@@ -4044,6 +4117,53 @@ sub dump_course_id_handler {
 }
 &register_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0);
 
+sub course_lastaccess_handler {
+    my ($cmd, $tail, $client) = @_;
+    my $userinput = "$cmd:$tail";
+    my ($cdom,$cnum) = split(':',$tail); 
+    my (%lastaccess,$qresult);
+    my $hashref = &tie_domain_hash($cdom, "nohist_courseids", &GDBM_WRCREAT());
+    if ($hashref) {
+        while (my ($key,$value) = each(%$hashref)) {
+            my ($unesc_key,$lasttime);
+            $unesc_key = &unescape($key);
+            if ($cnum) {
+                next unless ($unesc_key =~ /\Q$cdom\E_\Q$cnum\E$/);
+            }
+            if ($unesc_key =~ /^lasttime:($LONCAPA::match_domain\_$LONCAPA::match_courseid)/) {
+                $lastaccess{$1} = $value;
+            } else {
+                my $items = &Apache::lonnet::thaw_unescape($value);
+                if (ref($items) eq 'HASH') {
+                    unless ($lastaccess{$unesc_key}) {
+                        $lastaccess{$unesc_key} = '';
+                    }
+                } else {
+                    my @courseitems = split(':',$value);
+                    $lastaccess{$unesc_key} = pop(@courseitems);
+                }
+            }
+        }
+        foreach my $cid (sort(keys(%lastaccess))) {
+            $qresult.=&escape($cid).'='.$lastaccess{$cid}.'&'; 
+        }
+        if (&untie_domain_hash($hashref)) {
+            if ($qresult) {
+                chop($qresult);
+            }
+            &Reply($client, \$qresult, $userinput);
+        } else {
+            &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
+                    "while attempting lastacourseaccess\n", $userinput);
+        }
+    } else {
+        &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
+                "while attempting lastcourseaccess\n", $userinput);
+    }
+    return 1;
+}
+&register_handler("courselastaccess",\&course_lastaccess_handler, 0, 1, 0);
+
 #
 # Puts an unencrypted entry in a namespace db file at the domain level 
 #
@@ -6153,6 +6273,7 @@ $SIG{USR2} = \&UpdateHosts;
 
 #  Read the host hashes:
 &Apache::lonnet::load_hosts_tab();
+my %iphost = &Apache::lonnet::get_iphost(1);
 
 my $dist=`$perlvar{'lonDaemons'}/distprobe`;
 
@@ -6249,7 +6370,7 @@ sub make_new_child {
 	&ReadManagerTable();
 	my $clientrec=defined(&Apache::lonnet::get_hosts_from_ip($outsideip));
 	my $ismanager=($managers{$outsideip}    ne undef);
-	$clientname  = "[unknonwn]";
+	$clientname  = "[unknown]";
 	if($clientrec) {	# Establish client type.
 	    $ConnectionType = "client";
 	    $clientname = (&Apache::lonnet::get_hosts_from_ip($outsideip))[-1];
@@ -6277,7 +6398,7 @@ sub make_new_child {
 		#
 		#  If the remote is attempting a local init... give that a try:
 		#
-		my ($i, $inittype) = split(/:/, $remotereq);
+		(my $i, my $inittype, $clientversion) = split(/:/, $remotereq);
 
 		# If the connection type is ssl, but I didn't get my
 		# certificate files yet, then I'll drop  back to 
@@ -6297,6 +6418,7 @@ sub make_new_child {
 		}
 
 		if($inittype eq "local") {
+                    $clientversion = $perlvar{'lonVersion'};
 		    my $key = LocalConnection($client, $remotereq);
 		    if($key) {
 			Debug("Got local key $key");
@@ -6304,7 +6426,7 @@ sub make_new_child {
 			my $cipherkey = pack("H32", $key);
 			$cipher       = new IDEA($cipherkey);
 			print $client "ok:local\n";
-			&logthis('<font color="green"'
+			&logthis('<font color="green">'
 				 . "Successful local authentication </font>");
 			$keymode = "local"
 		    } else {
@@ -6368,6 +6490,15 @@ sub make_new_child {
 # ------------------------------------------------------------ Process requests
 	    my $keep_going = 1;
 	    my $user_input;
+            @clientdoms = ();
+            if (ref($iphost{$clientip}) eq 'ARRAY') {
+                foreach my $id (@{$iphost{$clientip}}) {
+                    my $clientdom = &Apache::lonnet::host_domain($id);
+                    unless (grep(/^\Q$clientdom\E/,@clientdoms)) {
+                        push(@clientdoms,$clientdom);
+                    }
+                }
+            }
 	    while(($user_input = get_request) && $keep_going) {
 		alarm(120);
 		Debug("Main: Got $user_input\n");
@@ -6526,9 +6657,7 @@ sub rewrite_password_file {
 
 #     Returns the authorization type or nouser if there is no such user.
 #
-sub get_auth_type 
-{
-
+sub get_auth_type {
     my ($domain, $user)  = @_;
 
     Debug("get_auth_type( $domain, $user ) \n");
@@ -6623,54 +6752,24 @@ sub validate_user {
 	    } else {
 		$validated = 0;
 	    }
-	}
-	elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain.
-	    if(! ($password =~ /$null/) ) {
-		my $k4error = &Authen::Krb4::get_pw_in_tkt($user,
-							   "",
-							   $contentpwd,,
-							   'krbtgt',
-							   $contentpwd,
-							   1,
-							   $password);
-		if(!$k4error) {
-		    $validated = 1;
-		} else {
-		    $validated = 0;
-		    &logthis('krb4: '.$user.', '.$contentpwd.', '.
-			     &Authen::Krb4::get_err_txt($Authen::Krb4::error));
-		}
-	    } else {
-		$validated = 0; # Password has a match with null.
-	    }
+	} elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain.
+            my $checkwithkrb5 = 0;
+            if ($dist =~/^fedora(\d+)$/) {
+                if ($1 > 11) {
+                    $checkwithkrb5 = 1;
+                }
+            } elsif ($dist =~ /^suse([\d.]+)$/) {
+                if ($1 > 11.1) {
+                    $checkwithkrb5 = 1; 
+                }
+            }
+            if ($checkwithkrb5) {
+                $validated = &krb5_authen($password,$null,$user,$contentpwd);
+            } else {
+                $validated = &krb4_authen($password,$null,$user,$contentpwd);
+            }
 	} elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain.
-	    if(!($password =~ /$null/)) { # Null password not allowed.
-		my $krbclient = &Authen::Krb5::parse_name($user.'@'
-							  .$contentpwd);
-		my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd;
-		my $krbserver  = &Authen::Krb5::parse_name($krbservice);
-		my $credentials= &Authen::Krb5::cc_default();
-		$credentials->initialize(&Authen::Krb5::parse_name($user.'@'
-                                                                 .$contentpwd));
-                my $krbreturn;
-                if (exists(&Authen::Krb5::get_init_creds_password)) {
-                    $krbreturn = 
-                        &Authen::Krb5::get_init_creds_password($krbclient,$password,
-                                                               $krbservice);
-                    $validated = (ref($krbreturn) eq 'Authen::Krb5::Creds');
-                } else {
-		    $krbreturn  = 
-                        &Authen::Krb5::get_in_tkt_with_password($krbclient,$krbserver,
-		 						$password,$credentials);
-		    $validated = ($krbreturn == 1);
-                }
-		if (!$validated) {
-		    &logthis('krb5: '.$user.', '.$contentpwd.', '.
-			     &Authen::Krb5::error());
-		}
-	    } else {
-		$validated = 0;
-	    }
+            $validated = &krb5_authen($password,$null,$user,$contentpwd);
 	} elsif ($howpwd eq "localauth") { 
 	    #  Authenticate via installation specific authentcation method:
 	    $validated = &localauth::localauth($user, 
@@ -6701,6 +6800,65 @@ sub validate_user {
     return $validated;
 }
 
+sub krb4_authen {
+    my ($password,$null,$user,$contentpwd) = @_;
+    my $validated = 0;
+    if (!($password =~ /$null/) ) {  # Null password not allowed.
+        eval {
+            require Authen::Krb4;
+        };
+        if (!$@) {
+            my $k4error = &Authen::Krb4::get_pw_in_tkt($user,
+                                                       "",
+                                                       $contentpwd,,
+                                                       'krbtgt',
+                                                       $contentpwd,
+                                                       1,
+                                                       $password);
+            if(!$k4error) {
+                $validated = 1;
+            } else {
+                $validated = 0;
+                &logthis('krb4: '.$user.', '.$contentpwd.', '.
+                          &Authen::Krb4::get_err_txt($Authen::Krb4::error));
+            }
+        } else {
+            $validated = krb5_authen($password,$null,$user,$contentpwd);
+        }
+    }
+    return $validated;
+}
+
+sub krb5_authen {
+    my ($password,$null,$user,$contentpwd) = @_;
+    my $validated = 0;
+    if(!($password =~ /$null/)) { # Null password not allowed.
+        my $krbclient = &Authen::Krb5::parse_name($user.'@'
+                                                  .$contentpwd);
+        my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd;
+        my $krbserver  = &Authen::Krb5::parse_name($krbservice);
+        my $credentials= &Authen::Krb5::cc_default();
+        $credentials->initialize(&Authen::Krb5::parse_name($user.'@'
+                                                            .$contentpwd));
+        my $krbreturn;
+        if (exists(&Authen::Krb5::get_init_creds_password)) {
+            $krbreturn =
+                &Authen::Krb5::get_init_creds_password($krbclient,$password,
+                                                          $krbservice);
+            $validated = (ref($krbreturn) eq 'Authen::Krb5::Creds');
+        } else {
+            $krbreturn  =
+                &Authen::Krb5::get_in_tkt_with_password($krbclient,$krbserver,
+                                                         $password,$credentials);
+            $validated = ($krbreturn == 1);
+        }
+        if (!$validated) {
+            &logthis('krb5: '.$user.', '.$contentpwd.', '.
+                     &Authen::Krb5::error());
+        }
+    }
+    return $validated;
+}
 
 sub addline {
     my ($fname,$hostid,$ip,$newline)=@_;
@@ -7071,7 +7229,7 @@ sub sethost {
 	eq &Apache::lonnet::get_host_ip($hostid)) {
 	$currenthostid  =$hostid;
 	$currentdomainid=&Apache::lonnet::host_domain($hostid);
-	&logthis("Setting hostid to $hostid, and domain to $currentdomainid");
+#	&logthis("Setting hostid to $hostid, and domain to $currentdomainid");
     } else {
 	&logthis("Requested host id $hostid not an alias of ".
 		 $perlvar{'lonHostID'}." refusing connection");
@@ -7086,6 +7244,20 @@ sub version {
     return "version:$VERSION";
 }
 
+sub get_usersession_config {
+    my ($dom,$name) = @_;
+    my ($usersessionconf,$cached)=&Apache::lonnet::is_cached_new($name,$dom);
+    if (defined($cached)) {
+        return $usersessionconf;
+    } else {
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['usersessions'],$dom);
+        if (ref($domconfig{'usersessions'}) eq 'HASH') {
+            &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'usersessions'},3600);
+            return $domconfig{'usersessions'};
+        }
+    }
+    return;
+}
 
 # ----------------------------------- POD (plain old documentation, CPAN style)
 
@@ -7821,5 +7993,7 @@ string.
 
 =back
 
+=back
+
 
 =cut