--- loncom/lond 2016/08/11 16:37:54 1.489.2.21 +++ loncom/lond 2017/03/20 03:21:08 1.489.2.27 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.489.2.21 2016/08/11 16:37:54 raeburn Exp $ +# $Id: lond,v 1.489.2.27 2017/03/20 03:21:08 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.21 $'; #' stupid emacs +my $VERSION='$Revision: 1.489.2.27 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -1425,6 +1425,20 @@ sub du2_handler { # selected directory the filename followed by the full output of # the stat function is returned. The returned info for each # file are separated by ':'. The stat fields are separated by &'s. +# +# 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/ +# (b) /home/httpd/html/res/userfiles/ +# (c) /home/httpd/lonUsers//<1>/<2>/<3>//userfiles +# or is: +# +# 2. for a file, and the path (after prepending) does not begin with: +# /home/httpd/lonUsers//<1>/<2>/<3>// +# +# the response will be "refused". +# # Parameters: # $cmd - The command that dispatched us (ls). # $ulsdir - The directory path to list... I'm not sure what this @@ -1446,8 +1460,17 @@ sub ls_handler { my $rights; my $ulsout=''; my $ulsfn; + if ($ulsdir =~m{/\.\./}) { + &Failure($client,"refused\n",$userinput); + return 1; + } 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/})) { + &Failure($client,"refused\n",$userinput); + return 1; + } if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { undef($obs); @@ -1471,6 +1494,10 @@ sub ls_handler { closedir(LSDIR); } } else { + unless ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/}) { + &Failure($client,"refused\n",$userinput); + return 1; + } my @ulsstats=stat($ulsdir); $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; } @@ -1495,6 +1522,20 @@ sub ls_handler { # selected directory the filename followed by the full output of # the stat function is returned. The returned info for each # file are separated by ':'. The stat fields are separated by &'s. +# +# 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/ +# (b) /home/httpd/html/res/userfiles/ +# (c) /home/httpd/lonUsers//<1>/<2>/<3>//userfiles +# or is: +# +# 2. for a file, and the path (after prepending) does not begin with: +# /home/httpd/lonUsers//<1>/<2>/<3>// +# +# the response will be "refused". +# # Parameters: # $cmd - The command that dispatched us (ls). # $ulsdir - The directory path to list... I'm not sure what this @@ -1515,8 +1556,17 @@ sub ls2_handler { my $rights; my $ulsout=''; my $ulsfn; + if ($ulsdir =~m{/\.\./}) { + &Failure($client,"refused\n",$userinput); + return 1; + } 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/})) { + &Failure($client,"refused\n","$userinput"); + return 1; + } if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { undef($obs); @@ -1541,6 +1591,10 @@ sub ls2_handler { closedir(LSDIR); } } else { + unless ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/}) { + &Failure($client,"refused\n",$userinput); + return 1; + } my @ulsstats=stat($ulsdir); $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; } @@ -1557,6 +1611,22 @@ sub ls2_handler { # selected directory the filename followed by the full output of # the stat function is returned. The returned info for each # file are separated by ':'. The stat fields are separated by &'s. +# +# 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/ +# (b) /home/httpd/html/res/userfiles/ +# (c) /home/httpd/lonUsers//<1>/<2>/<3>//userfiles +# (d) /home/httpd/html/priv// and client is the homeserver +# +# or is: +# +# 2. for a file, and the path (after prepending) does not begin with: +# /home/httpd/lonUsers//<1>/<2>/<3>// +# +# the response will be "refused". +# # Parameters: # $cmd - The command that dispatched us (ls). # $tail - The tail of the request that invoked us. @@ -1596,22 +1666,12 @@ sub ls3_handler { } my $dir_root = $perlvar{'lonDocRoot'}; - if ($getpropath) { + if (($getpropath) || ($getuserdir)) { if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { $dir_root = &propath($udom,$uname); $dir_root =~ s/\/$//; } else { - &Failure($client,"refused\n","$cmd:$tail"); - return 1; - } - } elsif ($getuserdir) { - if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { - my $subdir=$uname.'__'; - $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/; - $dir_root = $Apache::lonnet::perlvar{'lonUsersDir'} - ."/$udom/$subdir/$uname"; - } else { - &Failure($client,"refused\n","$cmd:$tail"); + &Failure($client,"refused\n",$userinput); return 1; } } elsif ($alternate_root ne '') { @@ -1624,12 +1684,28 @@ sub ls3_handler { $ulsdir = $dir_root.'/'.$ulsdir; } } + if ($ulsdir =~m{/\.\./}) { + &Failure($client,"refused\n",$userinput); + return 1; + } + my $islocal; + my @machine_ids = &Apache::lonnet::current_machine_ids(); + if (grep(/^\Q$clientname\E$/,@machine_ids)) { + $islocal = 1; + } my $obs; my $rights; my $ulsout=''; my $ulsfn; 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))) { + &Failure($client,"refused\n",$userinput); + return 1; + } if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { undef($obs); @@ -1654,6 +1730,11 @@ sub ls3_handler { closedir(LSDIR); } } else { + unless (($getpropath) || ($getuserdir) || + ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/})) { + &Failure($client,"refused\n",$userinput); + return 1; + } my @ulsstats=stat($ulsdir); $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; } @@ -2070,12 +2151,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 { @@ -3713,6 +3790,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); @@ -5330,13 +5438,59 @@ sub create_auto_enroll_password_handler ®ister_handler("autocreatepassword", \&create_auto_enroll_password_handler, 0, 1, 0); +sub auto_export_grades_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($cdom,$cnum,$info,$data) = split(/:/,$tail); + my $inforef = &Apache::lonnet::thaw_unescape($info); + my $dataref = &Apache::lonnet::thaw_unescape($data); + my ($outcome,$result);; + eval { + local($SIG{__DIE__})='DEFAULT'; + my %rtnhash; + $outcome=&localenroll::export_grades($cdom,$cnum,$inforef,$dataref,\%rtnhash); + if ($outcome eq 'ok') { + foreach my $key (keys(%rtnhash)) { + $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rtnhash{$key}).'&'; + } + $result =~ s/\&$//; + } + }; + if (!$@) { + if ($outcome eq 'ok') { + if ($cipher) { + my $cmdlength=length($result); + $result.=" "; + my $encresult=''; + for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encresult.= unpack("H16", + $cipher->encrypt(substr($result, + $encidx, + 8))); + } + &Reply( $client, "enc:$cmdlength:$encresult\n", $userinput); + } else { + &Failure( $client, "error:no_key\n", $userinput); + } + } else { + &Reply($client, "$outcome\n", $userinput); + } + } else { + &Failure($client,"export_error\n",$userinput); + } + return 1; +} +®ister_handler("autoexportgrades", \&auto_export_grades_handler, + 0, 1, 0); + + # Retrieve and remove temporary files created by/during autoenrollment. # # Formal Parameters: # $cmd - The command that got us dispatched. # $tail - The tail of the command. In our case this is a colon # separated list that will be split into: -# $filename - The name of the file to remove. +# $filename - The name of the file to retrieve. # The filename is given as a path relative to # the LonCAPA temp file directory. # $client - Socket open on the client. @@ -5352,6 +5506,8 @@ sub retrieve_auto_file_handler { my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename; if ($filename =~m{/\.\./}) { &Failure($client, "refused\n", $userinput); + } elsif ($filename !~ /^$LONCAPA::match_domain\_$LONCAPA::match_courseid\_.+_classlist\.xml$/) { + &Failure($client, "refused\n", $userinput); } elsif ( (-e $source) && ($filename ne '') ) { my $reply = ''; if (open(my $fh,$source)) { @@ -6809,7 +6965,6 @@ sub make_new_child { ."Attempted insecure connection disallowed "); close $client; $clientok = 0; - } } } else { @@ -6818,7 +6973,6 @@ sub make_new_child { ."$clientip failed to initialize: >$remotereq< "); &status('No init '.$clientip); } - } else { &logthis( "WARNING: Unknown client $clientip"); @@ -6976,15 +7130,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"; @@ -7075,20 +7239,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. @@ -7158,24 +7329,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])