--- loncom/lond 2017/03/20 13:53:22 1.535 +++ loncom/lond 2018/08/09 14:07:40 1.547 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.535 2017/03/20 13:53:22 raeburn Exp $ +# $Id: lond,v 1.547 2018/08/09 14:07:40 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -65,7 +65,7 @@ my $DEBUG = 0; # Non zero to ena my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.535 $'; #' stupid emacs +my $VERSION='$Revision: 1.547 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -108,6 +108,10 @@ my %perlvar; # Will have the apache co my %secureconf; # Will have requirements for security # of lond connections +my %crlchecked; # Will contain clients for which the client's SSL + # has been checked against the cluster's Certificate + # Revocation List. + my $dist; # @@ -229,6 +233,7 @@ my %trust = ( dump => {remote => 1, enroll => 1, domroles => 1}, edit => {institutiononly => 1}, #not used currently eget => {remote => 1, domroles => 1, enroll => 1}, #not used currently + egetdom => {remote => 1, domroles => 1, enroll => 1, }, ekey => {}, #not used currently exit => {anywhere => 1}, fetchuserfile => {remote => 1, enroll => 1}, @@ -419,10 +424,19 @@ sub SSLConnection { Debug("Approving promotion -> ssl"); # And do so: + my $CRLFile; + unless ($crlchecked{$clientname}) { + $CRLFile = lonssl::CRLFile(); + $crlchecked{$clientname} = 1; + } + my $SSLSocket = lonssl::PromoteServerSocket($Socket, $CACertificate, $Certificate, - $KeyFile); + $KeyFile, + $clientname, + $CRLFile, + $clientversion); if(! ($SSLSocket) ) { # SSL socket promotion failed. my $err = lonssl::LastError(); &logthis(" CRITICAL " @@ -1605,12 +1619,14 @@ sub du2_handler { # # 1. for a directory, and the path does not begin with one of: # (a) /home/httpd/html/res/ -# (b) /home/httpd/html/res/userfiles/ +# (b) /home/httpd/html/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>// +# 2. for a file, and the path (after prepending) does not begin with one of: +# (a) /home/httpd/lonUsers//<1>/<2>/<3>// +# (b) /home/httpd/html/res/// +# (c) /home/httpd/html/userfiles/// # # the response will be "refused". # @@ -1641,8 +1657,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; } @@ -1669,7 +1685,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; } @@ -1702,12 +1719,14 @@ sub ls_handler { # # 1. for a directory, and the path does not begin with one of: # (a) /home/httpd/html/res/ -# (b) /home/httpd/html/res/userfiles/ +# (b) /home/httpd/html/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>// +# 2. for a file, and the path (after prepending) does not begin with one of: +# (a) /home/httpd/lonUsers//<1>/<2>/<3>// +# (b) /home/httpd/html/res/// +# (c) /home/httpd/html/userfiles/// # # the response will be "refused". # @@ -1737,8 +1756,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; } @@ -1766,7 +1785,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; } @@ -1791,14 +1811,17 @@ sub ls2_handler { # # 1. for a directory, and the path does not begin with one of: # (a) /home/httpd/html/res/ -# (b) /home/httpd/html/res/userfiles/ +# (b) /home/httpd/html/userfiles/ # (c) /home/httpd/lonUsers//<1>/<2>/<3>//userfiles -# (d) /home/httpd/html/priv// and client is the homeserver +# (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>// +# 2. for a file, and the path (after prepending) does not begin with one of: +# (a) /home/httpd/lonUsers//<1>/<2>/<3>// +# (b) /home/httpd/html/res/// +# (c) /home/httpd/html/userfiles/// +# (d) /home/httpd/html/priv/// and client is the homeserver # # the response will be "refused". # @@ -1872,17 +1895,43 @@ sub ls3_handler { my $rights; my $ulsout=''; my $ulsfn; + + my ($crscheck,$toplevel,$currdom,$currnum,$skip); + unless ($islocal) { + my ($major,$minor) = split(/\./,$clientversion); + if (($major < 2) || ($major == 2 && $minor < 12)) { + $crscheck = 1; + } + } 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; } - if (opendir(LSDIR,$ulsdir)) { + if (($crscheck) && + ($ulsdir =~ m{^/home/httpd/html/res/($LONCAPA::match_domain)(/?$|/$LONCAPA::match_courseid)})) { + ($currdom,my $posscnum) = ($1,$2); + if (($posscnum eq '') || ($posscnum eq '/')) { + $toplevel = 1; + } else { + $posscnum =~ s{^/+}{}; + if (&LONCAPA::Lond::is_course($currdom,$posscnum)) { + $skip = 1; + } + } + } + if ((!$skip) && (opendir(LSDIR,$ulsdir))) { while ($ulsfn=readdir(LSDIR)) { + if (($crscheck) && ($toplevel) && ($currdom ne '') && + ($ulsfn =~ /^$LONCAPA::match_courseid$/) && (-d "$ulsdir/$ulsfn")) { + if (&LONCAPA::Lond::is_course($currdom,$ulsfn)) { + next; + } + } undef($obs); undef($rights); my @ulsstats=stat($ulsdir.'/'.$ulsfn); @@ -1906,7 +1955,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; } @@ -2590,32 +2641,26 @@ sub update_resource_handler { my $transname="$fname.in.transfer"; my $remoteurl=&Apache::lonnet::reply("sub:$fname","$clientname"); my $response; -# FIXME: cannot replicate files that take more than two minutes to transfer? -# alarm(120); -# FIXME: this should use the LWP mechanism, not internal alarms. - alarm(1200); - { - my $request=new HTTP::Request('GET',"$remoteurl"); - $response=&LONCAPA::LWPReq::makerequest($clientname,$request,$transname,\%perlvar,1200,0,1); - } - alarm(0); +# FIXME: cannot replicate files that take more than two minutes to transfer -- needs checking now 1200s timeout used +# for LWP request. + my $request=new HTTP::Request('GET',"$remoteurl"); + $response=&LONCAPA::LWPReq::makerequest($clientname,$request,$transname,\%perlvar,1200,0,1); if ($response->is_error()) { -# FIXME: we should probably clean up here instead of just whine - unlink($transname); + my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname"); + &devalidate_meta_cache($fname); + if (-e $transname) { + unlink($transname); + } + unlink($fname); my $message=$response->status_line; &logthis("LWP GET: $message for $fname ($remoteurl)"); } else { if ($remoteurl!~/\.meta$/) { -# FIXME: isn't there an internal LWP mechanism for this? - alarm(120); - { - my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta'); - my $mresponse = &LONCAPA::LWPReq::makerequest($clientname,$mrequest,$fname.'.meta',\%perlvar,120,0,1); - if ($mresponse->is_error()) { - unlink($fname.'.meta'); - } + my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta'); + my $mresponse = &LONCAPA::LWPReq::makerequest($clientname,$mrequest,$fname.'.meta',\%perlvar,120,0,1); + if ($mresponse->is_error()) { + unlink($fname.'.meta'); } - alarm(0); } # we successfully transfered, copy file over to real name rename($transname,$fname); @@ -2685,17 +2730,13 @@ sub fetch_user_file_handler { my $remoteurl=$clientprotocol.'://'.$clienthost.'/userfiles/'.$fname; my $response; Debug("Remote URL : $remoteurl Transfername $transname Destname: $destname"); - alarm(1200); - { - my $request=new HTTP::Request('GET',"$remoteurl"); - my $verifycert = 1; - my @machine_ids = &Apache::lonnet::current_machine_ids(); - if (grep(/^\Q$clientname\E$/,@machine_ids)) { - $verifycert = 0; - } - $response = &LONCAPA::LWPReq::makerequest($clientname,$request,$transname,\%perlvar,1200,$verifycert); - } - alarm(0); + my $request=new HTTP::Request('GET',"$remoteurl"); + my $verifycert = 1; + my @machine_ids = &Apache::lonnet::current_machine_ids(); + if (grep(/^\Q$clientname\E$/,@machine_ids)) { + $verifycert = 0; + } + $response = &LONCAPA::LWPReq::makerequest($clientname,$request,$transname,\%perlvar,1200,$verifycert); if ($response->is_error()) { unlink($transname); my $message=$response->status_line; @@ -3393,7 +3434,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 @@ -4876,7 +4918,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; +} +®ister_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); @@ -4889,19 +4965,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; } -®ister_handler("getdom", \&get_domain_handler, 0, 1, 0); +®ister_handler("egetdom", \&encrypted_get_domain_handler, 1, 1, 0); # # Puts an id to a domains id database. @@ -5697,9 +5785,10 @@ sub validate_course_section_handler { # Formal Parameters: # $cmd - The command request that got us dispatched. # $tail - The tail of the command. In this case this is a colon separated -# set of words that will be split into: +# set of values that will be split into: # $inst_class - Institutional code for the specific class section -# $courseowner - The escaped username:domain of the course owner +# $ownerlist - An escaped comma-separated list of username:domain +# of the course owner, and co-owner(s). # $cdom - The domain of the course from the institution's # point of view. # $client - The socket open on the client. @@ -5724,6 +5813,56 @@ sub validate_class_access_handler { ®ister_handler("autovalidateclass_sec", \&validate_class_access_handler, 0, 1, 0); # +# Validate course owner or co-owners(s) access to enrollment data for all sections +# and crosslistings for a particular course. +# +# +# Formal Parameters: +# $cmd - The command request that got us dispatched. +# $tail - The tail of the command. In this case this is a colon separated +# set of values that will be split into: +# $ownerlist - An escaped comma-separated list of username:domain +# of the course owner, and co-owner(s). +# $cdom - The domain of the course from the institution's +# point of view. +# $classes - Frozen hash of institutional course sections and +# crosslistings. +# $client - The socket open on the client. +# Returns: +# 1 - continue processing. +# + +sub validate_classes_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($ownerlist,$cdom,$classes) = split(/:/, $tail); + my $classesref = &Apache::lonnet::thaw_unescape($classes); + my $owners = &unescape($ownerlist); + my $result; + eval { + local($SIG{__DIE__})='DEFAULT'; + my %validations; + my $response = &localenroll::check_instclasses($owners,$cdom,$classesref, + \%validations); + if ($response eq 'ok') { + foreach my $key (keys(%validations)) { + $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($validations{$key}).'&'; + } + $result =~ s/\&$//; + } else { + $result = 'error'; + } + }; + if (!$@) { + &Reply($client, \$result, $userinput); + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } + return 1; +} +®ister_handler("autovalidateinstclasses", \&validate_classes_handler, 0, 1, 0); + +# # Create a password for a new LON-CAPA user added by auto-enrollment. # Only used for case where authentication method for new user is localauth # @@ -5801,7 +5940,7 @@ sub auto_export_grades_handler { return 1; } ®ister_handler("autoexportgrades", \&auto_export_grades_handler, - 0, 1, 0); + 1, 1, 0); # Retrieve and remove temporary files created by/during autoenrollment. # @@ -6892,10 +7031,10 @@ sub UpdateHosts { my %oldconf = %secureconf; my %connchange; - if (lonssl::Read_Connect_Config(\%secureconf,\%perlvar) eq 'ok') { - logthis(' Reloaded SSL connection rules '); + if (lonssl::Read_Connect_Config(\%secureconf,\%crlchecked,\%perlvar) eq 'ok') { + logthis(' Reloaded SSL connection rules and cleared CRL checking history '); } else { - logthis(' Failed to reload SSL connection rules '); + logthis(' Failed to reload SSL connection rules and clear CRL checking history '); } if ((ref($oldconf{'connfrom'}) eq 'HASH') && (ref($secureconf{'connfrom'}) eq 'HASH')) { foreach my $type ('dom','intdom','other') { @@ -7174,7 +7313,7 @@ if ($arch eq 'unknown') { chomp($arch); } -unless (lonssl::Read_Connect_Config(\%secureconf,\%perlvar) eq 'ok') { +unless (lonssl::Read_Connect_Config(\%secureconf,\%crlchecked,\%perlvar) eq 'ok') { &logthis('No connectionrules table. Will fallback to loncapa.conf'); } @@ -8269,6 +8408,14 @@ sub make_passwd_file { $result = "pass_file_failed_error"; } } + } elsif ($umode eq 'lti') { + my $pf = IO::File->new(">$passfilename"); + if($pf) { + print $pf "lti:\n"; + &update_passwd_history($uname,$udom,$umode,$action); + } else { + $result = "pass_file_failed_error"; + } } else { $result="auth_mode_error"; } @@ -8694,7 +8841,6 @@ IO::File Apache::File POSIX Crypt::IDEA -LWP::UserAgent() GDBM_File Authen::Krb4 Authen::Krb5