--- loncom/lond 2016/09/12 20:20:44 1.525 +++ loncom/lond 2017/02/28 05:42:06 1.532 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.525 2016/09/12 20:20:44 raeburn Exp $ +# $Id: lond,v 1.532 2017/02/28 05:42:06 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -35,6 +35,7 @@ use LONCAPA; use LONCAPA::Configuration; use LONCAPA::Lond; +use Socket; use IO::Socket; use IO::File; #use Apache::File; @@ -64,7 +65,7 @@ my $DEBUG = 0; # Non zero to ena my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.525 $'; #' stupid emacs +my $VERSION='$Revision: 1.532 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -75,6 +76,8 @@ my $clientname; # LonCAPA name of clie my $clientversion; # LonCAPA version running on client. my $clienthomedom; # LonCAPA domain of homeID for client. my $clientintdom; # LonCAPA "internet domain" for client. +my $clientsamedom; # LonCAPA domain same for this host + # and client. my $clientsameinst; # LonCAPA "internet domain" same for # this host and client. my $clientremoteok; # Client allowed to host domain's users. @@ -102,6 +105,9 @@ my %managers; # Ip -> manager names my %perlvar; # Will have the apache conf defined perl vars. +my %secureconf; # Will have requirements for security + # of lond connections + my $dist; # @@ -267,7 +273,8 @@ my %trust = ( restore => {remote => 1, enroll => 1, reqcrs => 1,}, rolesdel => {remote => 1, enroll => 1, domroles => 1, coaurem => 1}, rolesput => {remote => 1, enroll => 1, domroles => 1, coaurem => 1}, - serverdistarch => {manageronly => 1}, + servercerts => {institutiononly => 1}, + serverdistarch => {anywhere => 1}, serverhomeID => {anywhere => 1}, serverloncaparev => {anywhere => 1}, servertimezone => {remote => 1, enroll => 1}, @@ -444,8 +451,23 @@ sub InsecureConnection { my $Socket = shift; # Don't even start if insecure connections are not allowed. - - if(! $perlvar{londAllowInsecure}) { # Insecure connections not allowed. + # return 0 if Insecure connections not allowed. + # + if (ref($secureconf{'connfrom'}) eq 'HASH') { + if ($clientsamedom) { + if ($secureconf{'connfrom'}{'dom'} eq 'req') { + return 0; + } + } elsif ($clientsameinst) { + if ($secureconf{'connfrom'}{'intdom'} eq 'req') { + return 0; + } + } else { + if ($secureconf{'connfrom'}{'other'} eq 'req') { + return 0; + } + } + } elsif (!$perlvar{londAllowInsecure}) { return 0; } @@ -1567,6 +1589,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 @@ -1588,8 +1624,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); @@ -1613,6 +1658,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).':'; } @@ -1637,6 +1686,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 @@ -1657,8 +1720,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); @@ -1683,6 +1755,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).':'; } @@ -1699,6 +1775,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. @@ -1738,22 +1830,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 '') { @@ -1766,12 +1848,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); @@ -1796,6 +1894,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).':'; } @@ -1819,15 +1922,17 @@ sub read_lonnet_global { ); my %limit_to = ( perlvar => { - lonOtherAuthen => 1, - lonBalancer => 1, - lonVersion => 1, - lonSysEMail => 1, - lonHostID => 1, - lonRole => 1, - lonDefDomain => 1, - lonLoadLim => 1, - lonUserLoadLim => 1, + lonOtherAuthen => 1, + lonBalancer => 1, + lonVersion => 1, + lonAdmEMail => 1, + lonSupportEMail => 1, + lonSysEMail => 1, + lonHostID => 1, + lonRole => 1, + lonDefDomain => 1, + lonLoadLim => 1, + lonUserLoadLim => 1, } ); if (ref($requested) eq 'HASH') { @@ -5667,7 +5772,7 @@ sub auto_export_grades_handler { # $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. @@ -5684,6 +5789,8 @@ sub retrieve_auto_file_handler { 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)) { @@ -6717,6 +6824,7 @@ sub UpdateHosts { # will take care of new and changed hosts as connections come into being. &Apache::lonnet::reset_hosts_info(); + my %active; foreach my $child (keys(%children)) { my $childip = $children{$child}; @@ -6726,15 +6834,62 @@ sub UpdateHosts { ." $child for ip $childip "); kill('INT', $child); } else { + $active{$child} = $childip; logthis(' keeping child for ip ' ." $childip (pid=$child) "); } } + + my %oldconf = %secureconf; + my %connchange; + if (lonssl::Read_Connect_Config(\%secureconf,\%perlvar) eq 'ok') { + logthis(' Reloaded SSL connection rules '); + } else { + logthis(' Failed to reload SSL connection rules '); + } + if ((ref($oldconf{'connfrom'}) eq 'HASH') && (ref($secureconf{'connfrom'}) eq 'HASH')) { + foreach my $type ('dom','intdom','other') { + if ((($oldconf{'connfrom'}{$type} eq 'no') && ($secureconf{'connfrom'}{$type} eq 'req')) || + (($oldconf{'connfrom'}{$type} eq 'req') && ($secureconf{'connfrom'}{$type} eq 'no'))) { + $connchange{$type} = 1; + } + } + } + if (keys(%connchange)) { + foreach my $child (keys(%active)) { + my $childip = $active{$child}; + if ($childip ne '127.0.0.1') { + my $childhostname = gethostbyaddr(Socket::inet_aton($childip),AF_INET); + if ($childhostname ne '') { + my $childlonhost = &Apache::lonnet::get_server_homeID($childhostname); + my ($samedom,$sameinst) = &set_client_info($childlonhost); + if ($samedom) { + if ($connchange{'dom'}) { + logthis(' UpdateHosts killing child ' + ." $child for ip $childip "); + kill('INT', $child); + } + } elsif ($sameinst) { + if ($connchange{'intdom'}) { + logthis(' UpdateHosts killing child ' + ." $child for ip $childip "); + kill('INT', $child); + } + } else { + if ($connchange{'other'}) { + logthis(' UpdateHosts killing child ' + ." $child for ip $childip "); + kill('INT', $child); + } + } + } + } + } + } ReloadApache; &status("Finished reloading hosts.tab"); } - sub checkchildren { &status("Checking on the children (sending signals)"); &initnewstatus(); @@ -6969,6 +7124,10 @@ if ($arch eq 'unknown') { chomp($arch); } +unless (lonssl::Read_Connect_Config(\%secureconf,\%perlvar) eq 'ok') { + &logthis('No connectionrules table. Will fallback to loncapa.conf'); +} + # -------------------------------------------------------------- # Accept connections. When a connection comes in, it is validated # and if good, a child process is created to process transactions @@ -7099,7 +7258,7 @@ sub make_new_child { $ConnectionType = "manager"; $clientname = $managers{$outsideip}; } - my $clientok; + my ($clientok,$clientinfoset); if ($clientrec || $ismanager) { &status("Waiting for init from $clientip $clientname"); @@ -7127,7 +7286,32 @@ sub make_new_child { # If the connection type is ssl, but I didn't get my # certificate files yet, then I'll drop back to # insecure (if allowed). - + + if ($inittype eq "ssl") { + my $context; + if ($clientsamedom) { + $context = 'dom'; + if ($secureconf{'connfrom'}{'dom'} eq 'no') { + $inittype = ""; + } + } elsif ($clientsameinst) { + $context = 'intdom'; + if ($secureconf{'connfrom'}{'intdom'} eq 'no') { + $inittype = ""; + } + } else { + $context = 'other'; + if ($secureconf{'connfrom'}{'other'} eq 'no') { + $inittype = ""; + } + } + if ($inittype eq '') { + &logthis(" Domain config set " + ."to no ssl for $clientname (context: $context)" + ." -- trying insecure auth"); + } + } + if($inittype eq "ssl") { my ($ca, $cert) = lonssl::CertificateFile; my $kfile = lonssl::KeyFile; @@ -7160,7 +7344,7 @@ sub make_new_child { close $client; } } elsif ($inittype eq "ssl") { - my $key = SSLConnection($client); + my $key = SSLConnection($client,$clientname); if ($key) { $clientok = 1; my $cipherkey = pack("H32", $key); @@ -7175,6 +7359,7 @@ sub make_new_child { } } else { + $clientinfoset = &set_client_info(); my $ok = InsecureConnection($client); if($ok) { $clientok = 1; @@ -7214,18 +7399,8 @@ sub make_new_child { # ------------------------------------------------------------ Process requests my $keep_going = 1; my $user_input; - my $clienthost = &Apache::lonnet::hostname($clientname); - my $clientserverhomeID = &Apache::lonnet::get_server_homeID($clienthost); - $clienthomedom = &Apache::lonnet::host_domain($clientserverhomeID); - $clientintdom = &Apache::lonnet::internet_dom($clientserverhomeID); - $clientsameinst = 0; - if ($clientintdom ne '') { - my $internet_names = &Apache::lonnet::get_internet_names($currenthostid); - if (ref($internet_names) eq 'ARRAY') { - if (grep(/^\Q$clientintdom\E$/,@{$internet_names})) { - $clientsameinst = 1; - } - } + unless ($clientinfoset) { + $clientinfoset = &set_client_info(); } $clientremoteok = 0; unless ($clientsameinst) { @@ -7281,6 +7456,60 @@ sub make_new_child { exit; } + +# +# Used to determine if a particular client is from the same domain +# as the current server, or from the same internet domain. +# +# Optional input -- the client to check for domain and internet domain. +# If not specified, defaults to the package variable: $clientname +# +# If called in array context will not set package variables, but will +# instead return an array of two values - (a) true if client is in the +# same domain as the server, and (b) true if client is in the same internet +# domain. +# +# If called in scalar context, sets package variables for current client: +# +# $clienthomedom - LonCAPA domain of homeID for client. +# $clientsamedom - LonCAPA domain same for this host and client. +# $clientintdom - LonCAPA "internet domain" for client. +# $clientsameinst - LonCAPA "internet domain" same for this host & client. +# +# returns 1 to indicate package variables have been set for current client. +# + +sub set_client_info { + my ($lonhost) = @_; + $lonhost ||= $clientname; + my $clienthost = &Apache::lonnet::hostname($lonhost); + my $clientserverhomeID = &Apache::lonnet::get_server_homeID($clienthost); + my $homedom = &Apache::lonnet::host_domain($clientserverhomeID); + my $samedom = 0; + if ($perlvar{'lonDefDom'} eq $homedom) { + $samedom = 1; + } + my $intdom = &Apache::lonnet::internet_dom($clientserverhomeID); + my $sameinst = 0; + if ($intdom ne '') { + my $internet_names = &Apache::lonnet::get_internet_names($currenthostid); + if (ref($internet_names) eq 'ARRAY') { + if (grep(/^\Q$intdom\E$/,@{$internet_names})) { + $sameinst = 1; + } + } + } + if (wantarray) { + return ($samedom,$sameinst); + } else { + $clienthomedom = $homedom; + $clientsamedom = $samedom; + $clientintdom = $intdom; + $clientsameinst = $sameinst; + return 1; + } +} + # # Determine if a user is an author for the indicated domain. #