--- loncom/lond	2016/09/24 19:44:15	1.489.2.24
+++ loncom/lond	2017/06/22 02:11:23	1.489.2.28.2.1
@@ -2,7 +2,7 @@
 # The LearningOnline Network
 # lond "LON Daemon" Server (port "LOND" 5663)
 #
-# $Id: lond,v 1.489.2.24 2016/09/24 19:44:15 raeburn Exp $
+# $Id: lond,v 1.489.2.28.2.1 2017/06/22 02:11:23 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -64,7 +64,7 @@ my $DEBUG = 0;		       # Non zero to ena
 my $status='';
 my $lastlog='';
 
-my $VERSION='$Revision: 1.489.2.24 $'; #' stupid emacs
+my $VERSION='$Revision: 1.489.2.28.2.1 $'; #' stupid emacs
 my $remoteVERSION;
 my $currenthostid="default";
 my $currentdomainid;
@@ -1429,13 +1429,15 @@ sub du2_handler {
 #    If the requested path contains /../ or is:
 #
 #    1. for a directory, and the path does not begin with one of:
-#        (a) /home/httpd/html/res/<domain>/
-#        (b) /home/httpd/html/res/userfiles/
+#        (a) /home/httpd/html/res/<domain>
+#        (b) /home/httpd/html/userfiles/
 #        (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles
 #    or is:
 #
-#    2. for a file, and the path (after prepending) does not begin with:
-#    /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
+#    2. for a file, and the path (after prepending) does not begin with one of:
+#        (a) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
+#        (b) /home/httpd/html/res/<domain>/<username>/
+#        (c) /home/httpd/html/userfiles/<domain>/<username>/
 #
 #    the response will be "refused".
 #
@@ -1466,8 +1468,8 @@ sub ls_handler {
     }
     if (-e $ulsdir) {
 	if(-d $ulsdir) {
-            unless (($ulsdir =~ m{/home/httpd/html/(res/$LONCAPA::match_domain|userfiles)/}) ||
-                    ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/userfiles/})) {
+            unless (($ulsdir =~ m{^/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) ||
+                    ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/userfiles})) {
                 &Failure($client,"refused\n",$userinput);
                 return 1;
             }
@@ -1494,7 +1496,8 @@ sub ls_handler {
 		closedir(LSDIR);
 	    }
 	} else {
-            unless ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/}) {
+            unless (($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/}) ||
+                    ($ulsdir =~ m{^/home/httpd/html/(?:res|userfiles)/$LONCAPA::match_domain/$LONCAPA::match_name/})) {
                 &Failure($client,"refused\n",$userinput);
                 return 1;
             }
@@ -1526,13 +1529,15 @@ sub ls_handler {
 #    If the requested path contains /../ or is:
 #
 #    1. for a directory, and the path does not begin with one of:
-#        (a) /home/httpd/html/res/<domain>/
-#        (b) /home/httpd/html/res/userfiles/
+#        (a) /home/httpd/html/res/<domain>
+#        (b) /home/httpd/html/userfiles/
 #        (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles
 #    or is:
 #
-#    2. for a file, and the path (after prepending) does not begin with:
-#    /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
+#    2. for a file, and the path (after prepending) does not begin with one of:
+#        (a) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
+#        (b) /home/httpd/html/res/<domain>/<username>/
+#        (c) /home/httpd/html/userfiles/<domain>/<username>/
 #
 #    the response will be "refused".
 #
@@ -1562,8 +1567,8 @@ sub ls2_handler {
     }
     if (-e $ulsdir) {
         if(-d $ulsdir) {
-            unless (($ulsdir =~ m{/home/httpd/html/(res/$LONCAPA::match_domain|userfiles)/}) ||
-                    ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/userfiles/})) {
+            unless (($ulsdir =~ m{^/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) ||
+                    ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/userfiles})) {
                 &Failure($client,"refused\n","$userinput");
                 return 1;
             }
@@ -1591,7 +1596,8 @@ sub ls2_handler {
                 closedir(LSDIR);
             }
         } else {
-            unless ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/}) {
+            unless (($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/}) ||
+                    ($ulsdir =~ m{^/home/httpd/html/(?:res|userfiles)/$LONCAPA::match_domain/$LONCAPA::match_name/})) {
                 &Failure($client,"refused\n",$userinput);
                 return 1;
             }
@@ -1615,15 +1621,18 @@ sub ls2_handler {
 #    If the requested path (after prepending) contains /../ or is:
 #
 #    1. for a directory, and the path does not begin with one of:
-#        (a) /home/httpd/html/res/<domain>/
-#        (b) /home/httpd/html/res/userfiles/
+#        (a) /home/httpd/html/res/<domain>
+#        (b) /home/httpd/html/userfiles/
 #        (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles
-#        (d) /home/httpd/html/priv/<domain>/ and client is the homeserver
+#        (d) /home/httpd/html/priv/<domain> and client is the homeserver
 #
 #    or is:
 #
-#    2. for a file, and the path (after prepending) does not begin with:
-#    /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
+#    2. for a file, and the path (after prepending) does not begin with one of:
+#        (a) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
+#        (b) /home/httpd/html/res/<domain>/<username>/
+#        (c) /home/httpd/html/userfiles/<domain>/<username>/
+#        (d) /home/httpd/html/priv/<domain>/<username>/ and client is the homeserver
 #
 #    the response will be "refused".
 #
@@ -1700,9 +1709,9 @@ sub ls3_handler {
     if (-e $ulsdir) {
         if(-d $ulsdir) {
             unless (($getpropath) || ($getuserdir) ||
-                    ($ulsdir =~ m{/home/httpd/html/(res/$LONCAPA::match_domain|userfiles)/}) ||
-                    ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/userfiles/}) ||
-                    (($ulsdir =~ m{/home/httpd/html/priv/$LONCAPA::match_domain/}) && ($islocal))) {
+                    ($ulsdir =~ m{^/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) ||
+                    ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/userfiles}) ||
+                    (($ulsdir =~ m{^/home/httpd/html/priv/$LONCAPA::match_domain}) && ($islocal))) {
                 &Failure($client,"refused\n",$userinput);
                 return 1;
             }
@@ -1731,7 +1740,9 @@ sub ls3_handler {
             }
         } else {
             unless (($getpropath) || ($getuserdir) ||
-                    ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/})) {
+                    ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/}) ||
+                    ($ulsdir =~ m{^/home/httpd/html/(?:res|userfiles)/$LONCAPA::match_domain/$LONCAPA::match_name/}) ||
+                    (($ulsdir =~ m{^/home/httpd/html/priv/$LONCAPA::match_domain/$LONCAPA::match_name/}) && ($islocal))) {
                 &Failure($client,"refused\n",$userinput);
                 return 1;
             }
@@ -2151,12 +2162,8 @@ sub hash_passwd {
         my $plainsalt = substr($rest[1],0,22);
         $salt = Crypt::Eksblowfish::Bcrypt::de_base64($plainsalt);
     } else {
-        my $defaultcost;
-        my %domconfig =
-            &Apache::lonnet::get_dom('configuration',['password'],$domain);
-        if (ref($domconfig{'password'}) eq 'HASH') {
-            $defaultcost = $domconfig{'password'}{'cost'};
-        }
+        my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
+        my $defaultcost = $domdefaults{'intauth_cost'};
         if (($defaultcost eq '') || ($defaultcost =~ /D/)) {
             $cost = 10;
         } else {
@@ -3212,7 +3219,8 @@ sub get_profile_entry {
 #
 #  Parameters:
 #     $cmd               - Command keyword of request (eget).
-#     $tail              - Tail of the command.  See GetProfileEntry
#                          for more information about this.
+#     $tail              - Tail of the command.  See GetProfileEntry
+#                          for more information about this.
 #     $client            - File open on the client.
 #  Returns:
 #     1      - Continue processing
@@ -3794,6 +3802,37 @@ sub send_query_handler {
 
     my ($query,$arg1,$arg2,$arg3)=split(/\:/,$tail);
     $query=~s/\n*$//g;
+    if (($query eq 'usersearch') || ($query eq 'instdirsearch')) {
+        my $usersearchconf = &get_usersearch_config($currentdomainid,'directorysrch');
+        my $earlyout;
+        if (ref($usersearchconf) eq 'HASH') {
+            if ($currentdomainid eq $clienthomedom) {
+                if ($query eq 'usersearch') {
+                    if ($usersearchconf->{'lcavailable'} eq '0') {
+                        $earlyout = 1;
+                    }
+                } else {
+                    if ($usersearchconf->{'available'} eq '0') {
+                        $earlyout = 1;
+                    }
+                }
+            } else {
+                if ($query eq 'usersearch') {
+                    if ($usersearchconf->{'lclocalonly'}) {
+                        $earlyout = 1;
+                    }
+                } else {
+                    if ($usersearchconf->{'localonly'}) {
+                        $earlyout = 1;
+                    }
+                }
+            }
+        }
+        if ($earlyout) {
+            &Reply($client, "query_not_authorized\n");
+            return 1;
+        }
+    }
     &Reply($client, "". &sql_reply("$clientname\&$query".
 				"\&$arg1"."\&$arg2"."\&$arg3")."\n",
 	  $userinput);
@@ -4529,7 +4568,41 @@ sub get_domain_handler {
     my ($cmd, $tail, $client) = @_;
 
 
-    my $userinput = "$client:$tail";
+    my $userinput = "$cmd:$tail";
+
+    my ($udom,$namespace,$what)=split(/:/,$tail,3);
+    chomp($what);
+    if ($namespace =~ /^enc/) {
+        &Failure( $client, "refused\n", $userinput);
+    } else {
+        my @queries=split(/\&/,$what);
+        my $qresult='';
+        my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_READER());
+        if ($hashref) {
+            for (my $i=0;$i<=$#queries;$i++) {
+                $qresult.="$hashref->{$queries[$i]}&";
+            }
+            if (&untie_domain_hash($hashref)) {
+                $qresult=~s/\&$//;
+                &Reply($client, \$qresult, $userinput);
+            } else {
+                &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
+                          "while attempting getdom\n",$userinput);
+            }
+        } else {
+            &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
+                     "while attempting getdom\n",$userinput);
+        }
+    }
+
+    return 1;
+}
+&register_handler("getdom", \&get_domain_handler, 0, 1, 0);
+
+sub encrypted_get_domain_handler {
+    my ($cmd, $tail, $client) = @_;
+
+    my $userinput = "$cmd:$tail";
 
     my ($udom,$namespace,$what)=split(/:/,$tail,3);
     chomp($what);
@@ -4542,19 +4615,31 @@ sub get_domain_handler {
         }
         if (&untie_domain_hash($hashref)) {
             $qresult=~s/\&$//;
-            &Reply($client, \$qresult, $userinput);
+            if ($cipher) {
+                my $cmdlength=length($qresult);
+                $qresult.="         ";
+                my $encqresult='';
+                for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
+                    $encqresult.= unpack("H16",
+                                         $cipher->encrypt(substr($qresult,
+                                                                 $encidx,
+                                                                 8)));
+                }
+                &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput);
+            } else {
+                &Failure( $client, "error:no_key\n", $userinput);
+            }
         } else {
             &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
-                      "while attempting getdom\n",$userinput);
+                      "while attempting egetdom\n",$userinput);
         }
     } else {
         &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
-                 "while attempting getdom\n",$userinput);
+                 "while attempting egetdom\n",$userinput);
     }
-
     return 1;
 }
-&register_handler("getdom", \&get_domain_handler, 0, 1, 0);
+&register_handler("egetdom", \&encrypted_get_domain_handler, 1, 1, 0);
 
 #
 #  Puts an id to a domains id database. 
@@ -5454,7 +5539,7 @@ sub auto_export_grades_handler {
     return 1;
 }
 &register_handler("autoexportgrades", \&auto_export_grades_handler,
-                  0, 1, 0);
+                  1, 1, 0);
 
 
 #   Retrieve and remove temporary files created by/during autoenrollment.
@@ -6938,7 +7023,6 @@ sub make_new_child {
 				  ."Attempted insecure connection disallowed </font>");
 			close $client;
 			$clientok = 0;
-			
 		    }
 		}
 	    } else {
@@ -6947,7 +7031,6 @@ sub make_new_child {
 			 ."$clientip failed to initialize: >$remotereq< </font>");
 		&status('No init '.$clientip);
 	    }
-	    
 	} else {
 	    &logthis(
 		     "<font color='blue'>WARNING: Unknown client $clientip</font>");
@@ -7105,15 +7188,25 @@ sub password_filename {
 #    domain    - domain of the user.
 #    name      - User's name.
 #    contents  - New contents of the file.
+#    saveold   - (optional). If true save old file in a passwd.bak file.
 # Returns:
 #   0    - Failed.
 #   1    - Success.
 #
 sub rewrite_password_file {
-    my ($domain, $user, $contents) = @_;
+    my ($domain, $user, $contents, $saveold) = @_;
 
     my $file = &password_filename($domain, $user);
     if (defined $file) {
+        if ($saveold) {
+            my $bakfile = $file.'.bak';
+            if (CopyFile($file,$bakfile)) {
+                chmod(0400,$bakfile);
+                &logthis("Old password saved in passwd.bak for internally authenticated user: $user:$domain");
+            } else {
+                &logthis("Failed to save old password in passwd.bak for internally authenticated user: $user:$domain");
+            }
+        }
 	my $pf = IO::File->new(">$file");
 	if($pf) {
 	    print $pf "$contents\n";
@@ -7204,20 +7297,27 @@ sub validate_user {
                 $contentpwd = $domdefaults{'auth_arg_def'}; 
             }
         }
-    } 
+    }
     if ($howpwd ne 'nouser') {
 	if($howpwd eq "internal") { # Encrypted is in local password file.
             if (length($contentpwd) == 13) {
                 $validated = (crypt($password,$contentpwd) eq $contentpwd);
                 if ($validated) {
-                    my $ncpass = &hash_passwd($domain,$password);
-                    if (&rewrite_password_file($domain,$user,"$howpwd:$ncpass")) {
-                        &update_passwd_history($user,$domain,$howpwd,'conversion');
-                        &logthis("Validated password hashed with bcrypt for $user:$domain");
+                    my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
+                    if ($domdefaults{'intauth_switch'}) {
+                        my $ncpass = &hash_passwd($domain,$password);
+                        my $saveold;
+                        if ($domdefaults{'intauth_switch'} == 2) {
+                            $saveold = 1;
+                        }
+                        if (&rewrite_password_file($domain,$user,"$howpwd:$ncpass",$saveold)) {
+                            &update_passwd_history($user,$domain,$howpwd,'conversion');
+                            &logthis("Validated password hashed with bcrypt for $user:$domain");
+                        }
                     }
                 }
             } else {
-                $validated = &check_internal_passwd($password,$contentpwd,$domain);
+                $validated = &check_internal_passwd($password,$contentpwd,$domain,$user);
             }
 	}
 	elsif ($howpwd eq "unix") { # User is a normal unix user.
@@ -7287,24 +7387,35 @@ sub validate_user {
 }
 
 sub check_internal_passwd {
-    my ($plainpass,$stored,$domain) = @_;
+    my ($plainpass,$stored,$domain,$user) = @_;
     my (undef,$method,@rest) = split(/!/,$stored);
-    if ($method eq "bcrypt") {
+    if ($method eq 'bcrypt') {
         my $result = &hash_passwd($domain,$plainpass,@rest);
         if ($result ne $stored) {
             return 0;
         }
-        # Upgrade to a larger number of rounds if necessary
-        my $defaultcost;
-        my %domconfig =
-            &Apache::lonnet::get_dom('configuration',['password'],$domain);
-        if (ref($domconfig{'password'}) eq 'HASH') {
-            $defaultcost = $domconfig{'password'}{'cost'};
-        }
-        if (($defaultcost eq '') || ($defaultcost =~ /D/)) {
-            $defaultcost = 10;
+        my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
+        if ($domdefaults{'intauth_check'}) {
+            # Upgrade to a larger number of rounds if necessary
+            my $defaultcost = $domdefaults{'intauth_cost'};
+            if (($defaultcost eq '') || ($defaultcost =~ /D/)) {
+                $defaultcost = 10;
+            }
+            if (int($rest[0])<int($defaultcost)) {
+                if ($domdefaults{'intauth_check'} == 1) {
+                    my $ncpass = &hash_passwd($domain,$plainpass);
+                    if (&rewrite_password_file($domain,$user,"internal:$ncpass")) {
+                        &update_passwd_history($user,$domain,'internal','update cost');
+                        &logthis("Validated password hashed with bcrypt for $user:$domain");
+                    }
+                    return 1;
+                } elsif ($domdefaults{'intauth_check'} == 2) {
+                    return 0;
+                }
+            }
+        } else {
+            return 1;
         }
-        return 1 unless($rest[0]<$defaultcost);
     }
     return 0;
 }
@@ -7734,6 +7845,18 @@ sub get_usersession_config {
     return;
 }
 
+sub get_usersearch_config {
+    my ($dom,$name) = @_;
+    my ($usersearchconf,$cached)=&Apache::lonnet::is_cached_new($name,$dom);
+    if (defined($cached)) {
+        return $usersearchconf;
+    } else {
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['directorysrch'],$dom);
+        &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'directorysrch'},3600);
+        return $domconfig{'directorysrch'};
+    }
+    return;
+}
 
 sub distro_and_arch {
     return $dist.':'.$arch;