--- loncom/lonnet/perl/lonnet.pm 2009/08/15 00:25:53 1.1017 +++ loncom/lonnet/perl/lonnet.pm 2011/10/17 15:35:07 1.1137 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1017 2009/08/15 00:25:53 raeburn Exp $ +# $Id: lonnet.pm,v 1.1137 2011/10/17 15:35:07 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -76,7 +76,7 @@ use HTTP::Date; use Image::Magick; use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir - $_64bit %env %protocol); + $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease); my (%badServerCache, $memcache, %courselogs, %accesshash, %domainrolehash, %userrolehash, $processmarker, $dumpcount, %coursedombuf, @@ -92,14 +92,15 @@ use Time::HiRes qw( gettimeofday tv_inte use Cache::Memcached; use Digest::MD5; use Math::Random; +use File::MMagic; use LONCAPA qw(:DEFAULT :match); use LONCAPA::Configuration; +use File::Copy; + my $readit; my $max_connection_retries = 10; # Or some such value. -my $upload_photo_form = 0; #Variable to check when user upload a photo 0=not 1=true - require Exporter; our @ISA = qw (Exporter); @@ -196,8 +197,31 @@ sub get_server_timezone { } } +sub get_server_distarch { + my ($lonhost,$ignore_cache) = @_; + if (defined($lonhost)) { + if (!defined(&hostname($lonhost))) { + return; + } + my $cachetime = 12*3600; + if (!$ignore_cache) { + my ($distarch,$cached)=&is_cached_new('serverdistarch',$lonhost); + if (defined($cached)) { + return $distarch; + } + } + my $rep = &reply('serverdistarch',$lonhost); + unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' || + $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' || + $rep eq '') { + return &do_cache_new('serverdistarch',$lonhost,$rep,$cachetime); + } + } + return; +} + sub get_server_loncaparev { - my ($dom,$lonhost) = @_; + my ($dom,$lonhost,$ignore_cache,$caller) = @_; if (defined($lonhost)) { if (!defined(&hostname($lonhost))) { undef($lonhost); @@ -212,15 +236,120 @@ sub get_server_loncaparev { } } if (defined($lonhost)) { - my $cachetime = 24*3600; - my ($loncaparev,$cached)=&is_cached_new('serverloncaparev',$lonhost); + my $cachetime = 12*3600; + if (!$ignore_cache) { + my ($loncaparev,$cached)=&is_cached_new('serverloncaparev',$lonhost); + if (defined($cached)) { + return $loncaparev; + } + } + my ($answer,$loncaparev); + my @ids=¤t_machine_ids(); + if (grep(/^\Q$lonhost\E$/,@ids)) { + $answer = $perlvar{'lonVersion'}; + if ($answer =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/) { + $loncaparev = $1; + } + } else { + $answer = &reply('serverloncaparev',$lonhost); + if (($answer eq 'unknown_cmd') || ($answer eq 'con_lost')) { + if ($caller eq 'loncron') { + my $ua=new LWP::UserAgent; + $ua->timeout(4); + my $protocol = $protocol{$lonhost}; + $protocol = 'http' if ($protocol ne 'https'); + my $url = $protocol.'://'.&hostname($lonhost).'/adm/about.html'; + my $request=new HTTP::Request('GET',$url); + my $response=$ua->request($request); + unless ($response->is_error()) { + my $content = $response->content; + if ($content =~ /

VERSION\:\s*([\w.\-]+)<\/p>/) { + $loncaparev = $1; + } + } + } else { + $loncaparev = $loncaparevs{$lonhost}; + } + } elsif ($answer =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/) { + $loncaparev = $1; + } + } + return &do_cache_new('serverloncaparev',$lonhost,$loncaparev,$cachetime); + } +} + +sub get_server_homeID { + my ($hostname,$ignore_cache,$caller) = @_; + unless ($ignore_cache) { + my ($serverhomeID,$cached)=&is_cached_new('serverhomeID',$hostname); if (defined($cached)) { - return $loncaparev; + return $serverhomeID; + } + } + my $cachetime = 12*3600; + my $serverhomeID; + if ($caller eq 'loncron') { + my @machine_ids = &machine_ids($hostname); + foreach my $id (@machine_ids) { + my $response = &reply('serverhomeID',$id); + unless (($response eq 'unknown_cmd') || ($response eq 'con_lost')) { + $serverhomeID = $response; + last; + } + } + if ($serverhomeID eq '') { + $serverhomeID = $machine_ids[-1]; + } + } else { + $serverhomeID = $serverhomeIDs{$hostname}; + } + return &do_cache_new('serverhomeID',$hostname,$serverhomeID,$cachetime); +} + +sub get_remote_globals { + my ($lonhost,$whathash,$ignore_cache) = @_; + my ($result,%returnhash,%whatneeded); + if (ref($whathash) eq 'HASH') { + foreach my $what (sort(keys(%{$whathash}))) { + my $hashid = $lonhost.'-'.$what; + my ($response,$cached); + unless ($ignore_cache) { + ($response,$cached)=&is_cached_new('lonnetglobal',$hashid); + } + if (defined($cached)) { + $returnhash{$what} = $response; + } else { + $whatneeded{$what} = 1; + } + } + if (keys(%whatneeded) == 0) { + $result = 'ok'; } else { - my $loncaparev = &reply('serverloncaparev',$lonhost); - return &do_cache_new('serverloncaparev',$lonhost,$loncaparev,$cachetime); + my $requested = &freeze_escape(\%whatneeded); + my $rep=&reply('readlonnetglobal:'.$requested,$lonhost); + if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') || + ($rep eq 'unknown_cmd')) { + $result = $rep; + } else { + $result = 'ok'; + my @pairs=split(/\&/,$rep); + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + my $what = &unescape($key); + my $hashid = $lonhost.'-'.$what; + $returnhash{$what}=&thaw_unescape($value); + &do_cache_new('lonnetglobal',$hashid,$returnhash{$what},600); + } + } } } + return ($result,\%returnhash); +} + +sub remote_devalidate_cache { + my ($lonhost,$name,$id) = @_; + my $response = &reply('devalidatecache:'.&escape($name).':'.&escape($id),$lonhost); + return $response; } # -------------------------------------------------- Non-critical communication @@ -553,11 +682,20 @@ sub appenv { # ----------------------------------------------------- Delete from Environment sub delenv { - my ($delthis,$regexp) = @_; - if (($delthis=~/user\.role/) || ($delthis=~/user\.priv/)) { - &logthis("WARNING: ". - "Attempt to delete from environment ".$delthis); - return 'error'; + my ($delthis,$regexp,$roles) = @_; + if (($delthis=~/^user\.role/) || ($delthis=~/^user\.priv/)) { + my $refused = 1; + if (ref($roles) eq 'ARRAY') { + my ($type,$role) = ($delthis =~ /^user\.(role|priv)\.([^.]+)\./); + if (grep(/^\Q$role\E$/,@{$roles})) { + $refused = 0; + } + } + if ($refused) { + &logthis("WARNING: ". + "Attempt to delete from environment ".$delthis); + return 'error'; + } } my $opened = open(my $env_file,'+<',$env{'user.environment'}); if ($opened @@ -666,51 +804,48 @@ sub userload { return $userloadpercent; } -# ------------------------------------------ Fight off request when overloaded - -sub overloaderror { - my ($r,$checkserver)=@_; - unless ($checkserver) { $checkserver=$perlvar{'lonHostID'}; } - my $loadavg; - if ($checkserver eq $perlvar{'lonHostID'}) { - open(my $loadfile,'/proc/loadavg'); - $loadavg=<$loadfile>; - $loadavg =~ s/\s.*//g; - $loadavg = 100*$loadavg/$perlvar{'lonLoadLim'}; - close($loadfile); - } else { - $loadavg=&reply('load',$checkserver); - } - my $overload=$loadavg-100; - if ($overload>0) { - $r->err_headers_out->{'Retry-After'}=$overload; - $r->log_error('Overload of '.$overload.' on '.$checkserver); - return 413; - } - return ''; -} - # ------------------------------ Find server with least workload from spare.tab sub spareserver { - my ($loadpercent,$userloadpercent,$want_server_name) = @_; + my ($loadpercent,$userloadpercent,$want_server_name,$udom) = @_; my $spare_server; if ($userloadpercent !~ /\d/) { $userloadpercent=0; } my $lowest_load=($loadpercent > $userloadpercent) ? $loadpercent : $userloadpercent; - - foreach my $try_server (@{ $spareid{'primary'} }) { - ($spare_server, $lowest_load) = - &compare_server_load($try_server, $spare_server, $lowest_load); - } - - my $found_server = ($spare_server ne '' && $lowest_load < 100); + my ($uint_dom,$remotesessions); + if (($udom ne '') && (&domain($udom) ne '')) { + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + $uint_dom = &Apache::lonnet::internet_dom($uprimary_id); + my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom); + $remotesessions = $udomdefaults{'remotesessions'}; + } + my $spareshash = &this_host_spares($udom); + if (ref($spareshash) eq 'HASH') { + if (ref($spareshash->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'primary'} }) { + if ($uint_dom) { + next unless (&spare_can_host($udom,$uint_dom,$remotesessions, + $try_server)); + } + ($spare_server, $lowest_load) = + &compare_server_load($try_server, $spare_server, $lowest_load); + } + } - if (!$found_server) { - foreach my $try_server (@{ $spareid{'default'} }) { - ($spare_server, $lowest_load) = - &compare_server_load($try_server, $spare_server, $lowest_load); - } + my $found_server = ($spare_server ne '' && $lowest_load < 100); + + if (!$found_server) { + if (ref($spareshash->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'default'} }) { + if ($uint_dom) { + next unless (&spare_can_host($udom,$uint_dom, + $remotesessions,$try_server)); + } + ($spare_server, $lowest_load) = + &compare_server_load($try_server, $spare_server, $lowest_load); + } + } + } } if (!$want_server_name) { @@ -720,7 +855,7 @@ sub spareserver { } if (defined($spare_server)) { my $hostname = &hostname($spare_server); - if (defined($hostname)) { + if (defined($hostname)) { $spare_server = $protocol.'://'.$hostname; } } @@ -735,7 +870,7 @@ sub compare_server_load { my $userloadans = &reply('userload',$try_server); if ($loadans !~ /\d/ && $userloadans !~ /\d/) { - next; #didn't get a number from the server + return ($spare_server, $lowest_load); #didn't get a number from the server } my $load; @@ -761,9 +896,18 @@ sub compare_server_load { # --------------------------- ask offload servers if user already has a session sub find_existing_session { my ($udom,$uname) = @_; - foreach my $try_server (@{ $spareid{'primary'} }, - @{ $spareid{'default'} }) { - return $try_server if (&has_user_session($try_server, $udom, $uname)); + my $spareshash = &this_host_spares($udom); + if (ref($spareshash) eq 'HASH') { + if (ref($spareshash->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'primary'} }) { + return $try_server if (&has_user_session($try_server, $udom, $uname)); + } + } + if (ref($spareshash->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{ $spareshash->{'default'} }) { + return $try_server if (&has_user_session($try_server, $udom, $uname)); + } + } } return; } @@ -778,13 +922,51 @@ sub has_user_session { return 0; } +# --------- determine least loaded server in a user's domain which allows login + +sub choose_server { + my ($udom,$checkloginvia) = @_; + my %domconfhash = &Apache::loncommon::get_domainconf($udom); + my %servers = &get_servers($udom); + my $lowest_load = 30000; + my ($login_host,$hostname,$portal_path); + foreach my $lonhost (keys(%servers)) { + my $loginvia; + if ($checkloginvia) { + $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost}; + if ($loginvia) { + my ($server,$path) = split(/:/,$loginvia); + ($login_host, $lowest_load) = + &compare_server_load($server, $login_host, $lowest_load); + if ($login_host eq $server) { + $portal_path = $path; + } + } else { + ($login_host, $lowest_load) = + &compare_server_load($lonhost, $login_host, $lowest_load); + if ($login_host eq $lonhost) { + $portal_path = ''; + } + } + } else { + ($login_host, $lowest_load) = + &compare_server_load($lonhost, $login_host, $lowest_load); + } + } + if ($login_host ne '') { + $hostname = &hostname($login_host); + } + return ($login_host,$hostname,$portal_path); +} + # --------------------------------------------- Try to change a user's password sub changepass { my ($uname,$udom,$currentpass,$newpass,$server,$context)=@_; $currentpass = &escape($currentpass); $newpass = &escape($newpass); - my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context", + my $lonhost = $perlvar{'lonHostID'}; + my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context:$lonhost", $server); if (! $answer) { &logthis("No reply on password change request to $server ". @@ -809,6 +991,9 @@ sub changepass { } elsif ($answer =~ "^refused") { &logthis("$server refused to change $uname in $udom password because ". "it was sent an unencrypted request to change the password."); + } elsif ($answer =~ "invalid_client") { + &logthis("$server refused to change $uname in $udom password because ". + "it was a reset by e-mail originating from an invalid server."); } return $answer; } @@ -832,7 +1017,7 @@ sub queryauthenticate { # --------- Try to authenticate user from domain's lib servers (first this one) sub authenticate { - my ($uname,$upass,$udom,$checkdefauth)=@_; + my ($uname,$upass,$udom,$checkdefauth,$clientcancheckhost)=@_; $upass=&escape($upass); $uname= &LONCAPA::clean_username($uname); my $uhome=&homeserver($uname,$udom,1); @@ -855,7 +1040,7 @@ sub authenticate { return 'no_host'; } } - my $answer=reply("encrypt:auth:$udom:$uname:$upass:$checkdefauth",$uhome); + my $answer=reply("encrypt:auth:$udom:$uname:$upass:$checkdefauth:$clientcancheckhost",$uhome); if ($answer eq 'authorized') { if ($newhome) { &logthis("User $uname at $udom authorized by $uhome, but needs account"); @@ -873,6 +1058,384 @@ sub authenticate { return 'no_host'; } +sub can_host_session { + my ($udom,$lonhost,$remoterev,$remotesessions,$hostedsessions) = @_; + my $canhost = 1; + my $host_idn = &Apache::lonnet::internet_dom($lonhost); + if (ref($remotesessions) eq 'HASH') { + if (ref($remotesessions->{'excludedomain'}) eq 'ARRAY') { + if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'excludedomain'}})) { + $canhost = 0; + } else { + $canhost = 1; + } + } + if (ref($remotesessions->{'includedomain'}) eq 'ARRAY') { + if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'includedomain'}})) { + $canhost = 1; + } else { + $canhost = 0; + } + } + if ($canhost) { + if ($remotesessions->{'version'} ne '') { + my ($reqmajor,$reqminor) = ($remotesessions->{'version'} =~ /^(\d+)\.(\d+)$/); + if ($reqmajor ne '' && $reqminor ne '') { + if ($remoterev =~ /^\'?(\d+)\.(\d+)/) { + my $major = $1; + my $minor = $2; + if (($major < $reqmajor ) || + (($major == $reqmajor) && ($minor < $reqminor))) { + $canhost = 0; + } + } else { + $canhost = 0; + } + } + } + } + } + if ($canhost) { + if (ref($hostedsessions) eq 'HASH') { + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + my $uint_dom = &Apache::lonnet::internet_dom($uprimary_id); + if (ref($hostedsessions->{'excludedomain'}) eq 'ARRAY') { + if (($uint_dom ne '') && + (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'excludedomain'}}))) { + $canhost = 0; + } else { + $canhost = 1; + } + } + if (ref($hostedsessions->{'includedomain'}) eq 'ARRAY') { + if (($uint_dom ne '') && + (grep(/^\Q$uint_dom\E$/,@{$hostedsessions->{'includedomain'}}))) { + $canhost = 1; + } else { + $canhost = 0; + } + } + } + } + return $canhost; +} + +sub spare_can_host { + my ($udom,$uint_dom,$remotesessions,$try_server)=@_; + my $canhost=1; + my @intdoms; + my $internet_names = &Apache::lonnet::get_internet_names($try_server); + if (ref($internet_names) eq 'ARRAY') { + @intdoms = @{$internet_names}; + } + unless (grep(/^\Q$uint_dom\E$/,@intdoms)) { + my $serverhomeID = &Apache::lonnet::get_server_homeID($try_server); + my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID); + my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom); + my $remoterev = &Apache::lonnet::get_server_loncaparev(undef,$try_server); + $canhost = &can_host_session($udom,$try_server,$remoterev, + $remotesessions, + $defdomdefaults{'hostedsessions'}); + } + return $canhost; +} + +sub this_host_spares { + my ($dom) = @_; + my ($dom_in_use,$lonhost_in_use,$result); + my @hosts = ¤t_machine_ids(); + foreach my $lonhost (@hosts) { + if (&host_domain($lonhost) eq $dom) { + $dom_in_use = $dom; + $lonhost_in_use = $lonhost; + last; + } + } + if ($dom_in_use ne '') { + $result = &spares_for_offload($dom_in_use,$lonhost_in_use); + } + if (ref($result) ne 'HASH') { + $lonhost_in_use = $perlvar{'lonHostID'}; + $dom_in_use = &host_domain($lonhost_in_use); + $result = &spares_for_offload($dom_in_use,$lonhost_in_use); + if (ref($result) ne 'HASH') { + $result = \%spareid; + } + } + return $result; +} + +sub spares_for_offload { + my ($dom_in_use,$lonhost_in_use) = @_; + my ($result,$cached)=&is_cached_new('spares',$dom_in_use); + if (defined($cached)) { + return $result; + } else { + my $cachetime = 60*60*24; + my %domconfig = + &Apache::lonnet::get_dom('configuration',['usersessions'],$dom_in_use); + if (ref($domconfig{'usersessions'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'spares'}{$lonhost_in_use}) eq 'HASH') { + return &do_cache_new('spares',$dom_in_use,$domconfig{'usersessions'}{'spares'}{$lonhost_in_use},$cachetime); + } + } + } + } + return; +} + +sub get_lonbalancer_config { + my ($servers) = @_; + my ($currbalancer,$currtargets); + if (ref($servers) eq 'HASH') { + foreach my $server (keys(%{$servers})) { + my %what = ( + spareid => 1, + perlvar => 1, + ); + my ($result,$returnhash) = &get_remote_globals($server,\%what); + if ($result eq 'ok') { + if (ref($returnhash) eq 'HASH') { + if (ref($returnhash->{'perlvar'}) eq 'HASH') { + if ($returnhash->{'perlvar'}->{'lonBalancer'} eq 'yes') { + $currbalancer = $server; + $currtargets = {}; + if (ref($returnhash->{'spareid'}) eq 'HASH') { + if (ref($returnhash->{'spareid'}->{'primary'}) eq 'ARRAY') { + $currtargets->{'primary'} = $returnhash->{'spareid'}->{'primary'}; + } + if (ref($returnhash->{'spareid'}->{'default'}) eq 'ARRAY') { + $currtargets->{'default'} = $returnhash->{'spareid'}->{'default'}; + } + } + last; + } + } + } + } + } + } + return ($currbalancer,$currtargets); +} + +sub check_loadbalancing { + my ($uname,$udom) = @_; + my ($is_balancer,$dom_in_use,$homeintdom,$rule_in_effect, + $offloadto,$otherserver); + my $lonhost = $perlvar{'lonHostID'}; + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + my $uintdom = &Apache::lonnet::internet_dom($uprimary_id); + my $intdom = &Apache::lonnet::internet_dom($lonhost); + my $serverhomedom = &host_domain($lonhost); + + my $cachetime = 60*60*24; + + if (($uintdom ne '') && ($uintdom eq $intdom)) { + $dom_in_use = $udom; + $homeintdom = 1; + } else { + $dom_in_use = $serverhomedom; + } + my ($result,$cached)=&is_cached_new('loadbalancing',$dom_in_use); + unless (defined($cached)) { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + } + } + if (ref($result) eq 'HASH') { + my $currbalancer = $result->{'lonhost'}; + my $currtargets = $result->{'targets'}; + my $currrules = $result->{'rules'}; + if ($currbalancer ne '') { + my @hosts = ¤t_machine_ids(); + if (grep(/^\Q$currbalancer\E$/,@hosts)) { + $is_balancer = 1; + } + } + if ($is_balancer) { + if (ref($currrules) eq 'HASH') { + if ($homeintdom) { + if ($uname ne '') { + if (($currrules->{'_LC_adv'} ne '') || ($currrules->{'_LC_author'} ne '')) { + my ($is_adv,$is_author) = &is_advanced_user($udom,$uname); + if (($currrules->{'_LC_author'} ne '') && ($is_author)) { + $rule_in_effect = $currrules->{'_LC_author'}; + } elsif (($currrules->{'_LC_adv'} ne '') && ($is_adv)) { + $rule_in_effect = $currrules->{'_LC_adv'} + } + } + if ($rule_in_effect eq '') { + my %userenv = &userenvironment($udom,$uname,'inststatus'); + if ($userenv{'inststatus'} ne '') { + my @statuses = map { &unescape($_); } split(/:/,$userenv{'inststatus'}); + my ($othertitle,$usertypes,$types) = + &Apache::loncommon::sorted_inst_types($udom); + if (ref($types) eq 'ARRAY') { + foreach my $type (@{$types}) { + if (grep(/^\Q$type\E$/,@statuses)) { + if (exists($currrules->{$type})) { + $rule_in_effect = $currrules->{$type}; + } + } + } + } + } else { + if (exists($currrules->{'default'})) { + $rule_in_effect = $currrules->{'default'}; + } + } + } + } else { + if (exists($currrules->{'default'})) { + $rule_in_effect = $currrules->{'default'}; + } + } + } else { + if ($currrules->{'_LC_external'} ne '') { + $rule_in_effect = $currrules->{'_LC_external'}; + } + } + $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets, + $uname,$udom); + } + } + } elsif (($homeintdom) && ($udom ne $serverhomedom)) { + my ($result,$cached)=&is_cached_new('loadbalancing',$serverhomedom); + unless (defined($cached)) { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + } + } + if (ref($result) eq 'HASH') { + my $currbalancer = $result->{'lonhost'}; + my $currtargets = $result->{'targets'}; + my $currrules = $result->{'rules'}; + + if ($currbalancer eq $lonhost) { + $is_balancer = 1; + if (ref($currrules) eq 'HASH') { + if ($currrules->{'_LC_internetdom'} ne '') { + $rule_in_effect = $currrules->{'_LC_internetdom'}; + } + } + $offloadto = &get_loadbalancer_targets($rule_in_effect,$currtargets, + $uname,$udom); + } + } else { + if ($perlvar{'lonBalancer'} eq 'yes') { + $is_balancer = 1; + $offloadto = &this_host_spares($dom_in_use); + } + } + } else { + if ($perlvar{'lonBalancer'} eq 'yes') { + $is_balancer = 1; + $offloadto = &this_host_spares($dom_in_use); + } + } + my $lowest_load = 30000; + if (ref($offloadto) eq 'HASH') { + if (ref($offloadto->{'primary'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'primary'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + my $found_server = ($otherserver ne '' && $lowest_load < 100); + + if (!$found_server) { + if (ref($offloadto->{'default'}) eq 'ARRAY') { + foreach my $try_server (@{$offloadto->{'default'}}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + } elsif (ref($offloadto) eq 'ARRAY') { + if (@{$offloadto} == 1) { + $otherserver = $offloadto->[0]; + } elsif (@{$offloadto} > 1) { + foreach my $try_server (@{$offloadto}) { + ($otherserver,$lowest_load) = + &compare_server_load($try_server,$otherserver,$lowest_load); + } + } + } + return ($is_balancer,$otherserver); +} + +sub get_loadbalancer_targets { + my ($rule_in_effect,$currtargets,$uname,$udom) = @_; + my $offloadto; + if ($rule_in_effect eq '') { + $offloadto = $currtargets; + } else { + if ($rule_in_effect eq 'homeserver') { + my $homeserver = &homeserver($uname,$udom); + if ($homeserver ne 'no_host') { + $offloadto = [$homeserver]; + } + } elsif ($rule_in_effect eq 'externalbalancer') { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['loadbalancing'],$udom); + if (ref($domconfig{'loadbalancing'}) eq 'HASH') { + if ($domconfig{'loadbalancing'}{'lonhost'} ne '') { + if (&hostname($domconfig{'loadbalancing'}{'lonhost'}) ne '') { + $offloadto = [$domconfig{'loadbalancing'}{'lonhost'}]; + } + } + } else { + my %servers = &dom_servers($udom); + my ($remotebalancer,$remotetargets) = &get_lonbalancer_config(\%servers); + if (&hostname($remotebalancer) ne '') { + $offloadto = [$remotebalancer]; + } + } + } elsif (&hostname($rule_in_effect) ne '') { + $offloadto = [$rule_in_effect]; + } + } + return $offloadto; +} + +sub internet_dom_servers { + my ($dom) = @_; + my (%uniqservers,%servers); + my $primaryserver = &hostname(&domain($dom,'primary')); + my @machinedoms = &machine_domains($primaryserver); + foreach my $mdom (@machinedoms) { + my %currservers = %servers; + my %server = &get_servers($mdom); + %servers = (%currservers,%server); + } + my %by_hostname; + foreach my $id (keys(%servers)) { + push(@{$by_hostname{$servers{$id}}},$id); + } + foreach my $hostname (sort(keys(%by_hostname))) { + if (@{$by_hostname{$hostname}} > 1) { + my $match = 0; + foreach my $id (@{$by_hostname{$hostname}}) { + if (&host_domain($id) eq $dom) { + $uniqservers{$id} = $hostname; + $match = 1; + } + } + unless ($match) { + $uniqservers{$by_hostname{$hostname}[0]} = $hostname; + } + } else { + $uniqservers{$by_hostname{$hostname}[0]} = $hostname; + } + } + return %uniqservers; +} + # ---------------------- Find the homebase for a user from domain's lib servers my %homecache; @@ -958,44 +1521,21 @@ sub idput { } } -# ------------------------------------------------ dump from domain db files - +# ------------------------------dump from db file owned by domainconfig user sub dump_dom { - my ($namespace,$udom,$uhome,$regexp,$range)=@_; + my ($namespace,$udom,$regexp,$range)=@_; if (!$udom) { $udom=$env{'user.domain'}; - if (defined(&domain($udom,'primary'))) { - $uhome=&domain($udom,'primary'); - } else { - undef($uhome); - } - } else { - if (!$uhome) { - if (defined(&domain($udom,'primary'))) { - $uhome=&domain($udom,'primary'); - } - } } my %returnhash; - if ($udom && $uhome && ($uhome ne 'no_host')) { - if ($regexp) { - $regexp=&escape($regexp); - } else { - $regexp='.'; - } - my $rep=&reply("dumpdom:$udom:$namespace:$regexp:$range",$uhome); - my @pairs=split(/\&/,$rep); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - $key = &unescape($key); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); - } + if ($udom) { + my $uname = &get_domainconfiguser($udom); + %returnhash = &dump($namespace,$udom,$uname,$regexp,$range); } return %returnhash; } -# ------------------------------------------- get items from domain db files +# ------------------------------------------ get items from domain db files sub get_dom { my ($namespace,$storearr,$udom,$uhome)=@_; @@ -1069,70 +1609,40 @@ sub put_dom { } } -# -------------------------------------- newput for items in domain db files - +# --------------------- newput for items in db file owned by domainconfig user sub newput_dom { - my ($namespace,$storehash,$udom,$uhome) = @_; + my ($namespace,$storehash,$udom) = @_; my $result; if (!$udom) { $udom=$env{'user.domain'}; - if (defined(&domain($udom,'primary'))) { - $uhome=&domain($udom,'primary'); - } else { - undef($uhome); - } - } else { - if (!$uhome) { - if (defined(&domain($udom,'primary'))) { - $uhome=&domain($udom,'primary'); - } - } } - if ($udom && $uhome && ($uhome ne 'no_host')) { - my $items=''; - if (ref($storehash) eq 'HASH') { - foreach my $key (keys(%$storehash)) { - $items.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&'; - } - $items=~s/\&$//; - $result = &reply("newputdom:$udom:$namespace:$items",$uhome); - } - } else { - &logthis("put_dom failed - no homeserver and/or domain"); + if ($udom) { + my $uname = &get_domainconfiguser($udom); + $result = &newput($namespace,$storehash,$udom,$uname); } return $result; } +# --------------------- delete for items in db file owned by domainconfig user sub del_dom { - my ($namespace,$storearr,$udom,$uhome)=@_; + my ($namespace,$storearr,$udom)=@_; if (ref($storearr) eq 'ARRAY') { - my $items=''; - foreach my $item (@$storearr) { - $items.=&escape($item).'&'; - } - $items=~s/\&$//; if (!$udom) { $udom=$env{'user.domain'}; - if (defined(&domain($udom,'primary'))) { - $uhome=&domain($udom,'primary'); - } else { - undef($uhome); - } - } else { - if (!$uhome) { - if (defined(&domain($udom,'primary'))) { - $uhome=&domain($udom,'primary'); - } - } } - if ($udom && $uhome && ($uhome ne 'no_host')) { - return &reply("deldom:$udom:$namespace:$items",$uhome); - } else { - &logthis("del_dom failed - no homeserver and/or domain"); + if ($udom) { + my $uname = &get_domainconfiguser($udom); + return &del($namespace,$storearr,$udom,$uname); } } } +# ----------------------------------construct domainconfig user for a domain +sub get_domainconfiguser { + my ($udom) = @_; + return $udom.'-domainconfig'; +} + sub retrieve_inst_usertypes { my ($udom) = @_; my (%returnhash,@order); @@ -1401,7 +1911,8 @@ sub get_domain_defaults { my %domdefaults; my %domconfig = &Apache::lonnet::get_dom('configuration',['defaults','quotas', - 'requestcourses','inststatus'],$domain); + 'requestcourses','inststatus', + 'coursedefaults','usersessions'],$domain); if (ref($domconfig{'defaults'}) eq 'HASH') { $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'}; @@ -1436,6 +1947,19 @@ sub get_domain_defaults { $domdefaults{$item} = $domconfig{'inststatus'}{$item}; } } + if (ref($domconfig{'coursedefaults'}) eq 'HASH') { + foreach my $item ('canuse_pdfforms') { + $domdefaults{$item} = $domconfig{'coursedefaults'}{$item}; + } + } + if (ref($domconfig{'usersessions'}) eq 'HASH') { + if (ref($domconfig{'usersessions'}{'remote'}) eq 'HASH') { + $domdefaults{'remotesessions'} = $domconfig{'usersessions'}{'remote'}; + } + if (ref($domconfig{'usersessions'}{'hosted'}) eq 'HASH') { + $domdefaults{'hostedsessions'} = $domconfig{'usersessions'}{'hosted'}; + } + } &Apache::lonnet::do_cache_new('domdefaults',$domain,\%domdefaults, $cachetime); return %domdefaults; @@ -1621,7 +2145,8 @@ sub getsection { # If there is a role which has expired, return it. # $courseid = &courseid_to_courseurl($courseid); - my %roleshash = &dump('roles',$udom,$unam,$courseid); + my $extra = &freeze_escape({'skipcheck' => 1}); + my %roleshash = &dump('roles',$udom,$unam,$courseid,undef,$extra); foreach my $key (keys(%roleshash)) { next if ($key !~/^\Q$courseid\E(?:\/)*(\w+)*\_st$/); my $section=$1; @@ -1687,7 +2212,7 @@ sub is_cached_new { my ($name,$id,$debug) = @_; $id=&make_key($name,$id); if (exists($remembered{$id})) { - if ($debug) { &Apache::lonnet::logthis("Earyl return $id of $remembered{$id} "); } + if ($debug) { &Apache::lonnet::logthis("Early return $id of $remembered{$id} "); } $accessed{$id}=[&gettimeofday()]; $hits++; return ($remembered{$id},1); @@ -1770,6 +2295,9 @@ sub userenvironment { unless ($uhome eq 'no_host') { my @answer=split(/\&/, &reply('get:'.$udom.':'.$unam.':environment:'.$items,$uhome)); + if ($#answer==0 && $answer[0] =~ /^(con_lost|error:|no_such_host)/i) { + return %returnhash; + } my $i; for ($i=0;$i<=$#what;$i++) { $returnhash{$what[$i]}=&unescape($answer[$i]); @@ -1850,20 +2378,29 @@ sub getversion { sub currentversion { my $fname=shift; - my ($result,$cached)=&is_cached_new('resversion',$fname); - if (defined($cached)) { return $result; } my $author=$fname; $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; my ($udom,$uname)=split(/\//,$author); - my $home=homeserver($uname,$udom); + my $home=&homeserver($uname,$udom); if ($home eq 'no_host') { return -1; } - my $answer=reply("currentversion:$fname",$home); + my $answer=&reply("currentversion:$fname",$home); if (($answer eq 'con_lost') || ($answer eq 'rejected')) { return -1; } - return &do_cache_new('resversion',$fname,$answer,600); + return $answer; +} + +# +# Return special version number of resource if set by override, empty otherwise +# +sub usedversion { + my $fname=shift; + unless ($fname) { $fname=$env{'request.uri'}; } + my ($urlversion)=($fname=~/\.(\d+)\.\w+$/); + if ($urlversion) { return $urlversion; } + return ''; } # ----------------------------- Subscribe to a resource, return URL if possible @@ -2059,6 +2596,8 @@ sub allowuploaded { # path to file, source of file, instruction to parse file for objects, # ref to hash for embedded objects, # ref to hash for codebase of java objects. +# reference to scalar to accommodate mime type determined +# from File::MMagic if $parser = parse. # # output: url to file (if action was uploaddoc), # ok if successful, or diagnostic message otherwise (if action was propagate or copy) @@ -2085,7 +2624,8 @@ sub allowuploaded { # sub process_coursefile { - my ($action,$docuname,$docudom,$file,$source,$parser,$allfiles,$codebase)=@_; + my ($action,$docuname,$docudom,$file,$source,$parser,$allfiles,$codebase, + $mimetype)=@_; my $fetchresult; my $home=&homeserver($docuname,$docudom); if ($action eq 'propagate') { @@ -2112,10 +2652,17 @@ sub process_coursefile { print $fh $env{'form.'.$source}; close($fh); if ($parser eq 'parse') { - my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase); - unless ($parse_result eq 'ok') { - &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result); + my $mm = new File::MMagic; + my $type = $mm->checktype_filename($filepath.'/'.$fname); + if ($type eq 'text/html') { + my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase); + unless ($parse_result eq 'ok') { + &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result); + } } + if (ref($mimetype)) { + $$mimetype = $type; + } } $fetchresult= &reply('fetchuserfile:'.$docudom.'/'.$docuname.'/'.$file, $home); @@ -2191,38 +2738,53 @@ sub clean_filename { $fname=~s/\.(\d+)(?=\.)/_$1/g; return $fname; } -#This Function check if a Image max 400px width and height 500px. If not then scale the image down +# This Function checks if an Image's dimensions exceed either $resizewidth (width) +# or $resizeheight (height) - both pixels. If so, the image is scaled to produce an +# image with the same aspect ratio as the original, but with dimensions which do +# not exceed $resizewidth and $resizeheight. + sub resizeImage { - my($img_url) = @_; - my $ima = Image::Magick->new; - $ima->Read($img_url); - if($ima->Get('width') > 400) - { - my $factor = $ima->Get('width')/400; - $ima->Scale( width=>400, height=>$ima->Get('height')/$factor ); - } - if($ima->Get('height') > 500) - { - my $factor = $ima->Get('height')/500; - $ima->Scale( width=>$ima->Get('width')/$factor, height=>500); - } - - $ima->Write($img_url); -} - -#Wrapper function for userphotoupload -sub userphotoupload -{ - my($formname,$subdir) = @_; - $upload_photo_form = 1; - return &userfileupload($formname,undef,$subdir); + my ($img_path,$resizewidth,$resizeheight) = @_; + my $ima = Image::Magick->new; + my $resized; + if (-e $img_path) { + $ima->Read($img_path); + if (($resizewidth =~ /^\d+$/) && ($resizeheight > 0)) { + my $width = $ima->Get('width'); + my $height = $ima->Get('height'); + if ($width > $resizewidth) { + my $factor = $width/$resizewidth; + my $newheight = $height/$factor; + $ima->Scale(width=>$resizewidth,height=>$newheight); + $resized = 1; + } + } + if (($resizeheight =~ /^\d+$/) && ($resizeheight > 0)) { + my $width = $ima->Get('width'); + my $height = $ima->Get('height'); + if ($height > $resizeheight) { + my $factor = $height/$resizeheight; + my $newwidth = $width/$factor; + $ima->Scale(width=>$newwidth,height=>$resizeheight); + $resized = 1; + } + } + if ($resized) { + $ima->Write($img_path); + } + } + return; } # --------------- Take an uploaded file and put it into the userfiles directory # input: $formname - the contents of the file are in $env{"form.$formname"} -# the desired filenam is in $env{"form.$formname.filename"} -# $coursedoc - if true up to the current course -# if false +# the desired filename is in $env{"form.$formname.filename"} +# $context - possible values: coursedoc, existingfile, overwrite, +# canceloverwrite, or ''. +# if 'coursedoc': upload to the current course +# if 'existingfile': write file to tmp/overwrites directory +# if 'canceloverwrite': delete file written to tmp/overwrites directory +# $context is passed as argument to &finishuserfileupload # $subdir - directory in userfile to store the file into # $parser - instruction to parse file for objects ($parser = parse) # $allfiles - reference to hash for embedded objects @@ -2231,38 +2793,62 @@ sub userphotoupload # $dsetudom - domain for permanaent storage of uploaded file # $thumbwidth - width (pixels) of thumbnail to make for uploaded image # $thumbheight - height (pixels) of thumbnail to make for uploaded image +# $resizewidth - width (pixels) to which to resize uploaded image +# $resizeheight - height (pixels) to which to resize uploaded image +# $mimetype - reference to scalar to accommodate mime type determined +# from File::MMagic if $parser = parse. # # output: url of file in userspace, or error: # or /adm/notfound.html if failure to upload occurse - sub userfileupload { - my ($formname,$coursedoc,$subdir,$parser,$allfiles,$codebase,$destuname, - $destudom,$thumbwidth,$thumbheight)=@_; + my ($formname,$context,$subdir,$parser,$allfiles,$codebase,$destuname, + $destudom,$thumbwidth,$thumbheight,$resizewidth,$resizeheight,$mimetype)=@_; if (!defined($subdir)) { $subdir='unknown'; } my $fname=$env{'form.'.$formname.'.filename'}; $fname=&clean_filename($fname); -# See if there is anything left + # See if there is anything left unless ($fname) { return 'error: no uploaded file'; } - chop($env{'form.'.$formname}); - if (($formname eq 'screenshot') && ($subdir eq 'helprequests')) { #files uploaded to help request form are handled differently + # Files uploaded to help request form, or uploaded to "create course" page are handled differently + if ((($formname eq 'screenshot') && ($subdir eq 'helprequests')) || + (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) || + ($context eq 'existingfile') || ($context eq 'canceloverwrite')) { my $now = time; - my $filepath = 'tmp/helprequests/'.$now; - my @parts=split(/\//,$filepath); - my $fullpath = $perlvar{'lonDaemons'}; - for (my $i=0;$i<@parts;$i++) { - $fullpath .= '/'.$parts[$i]; - if ((-e $fullpath)!=1) { - mkdir($fullpath,0777); + my $filepath; + if (($formname eq 'screenshot') && ($subdir eq 'helprequests')) { + $filepath = 'tmp/helprequests/'.$now; + } elsif (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) { + $filepath = 'tmp/addcourse/'.$destudom.'/web/'.$env{'user.name'}. + '_'.$env{'user.domain'}.'/pending'; + } elsif (($context eq 'existingfile') || ($context eq 'canceloverwrite')) { + my ($docuname,$docudom); + if ($destudom) { + $docudom = $destudom; + } else { + $docudom = $env{'user.domain'}; + } + if ($destuname) { + $docuname = $destuname; + } else { + $docuname = $env{'user.name'}; + } + if (exists($env{'form.group'})) { + $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; + $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'}; + } + $filepath = 'tmp/overwrites/'.$docudom.'/'.$docuname.'/'.$subdir; + if ($context eq 'canceloverwrite') { + my $tempfile = $perlvar{'lonDaemons'}.'/'.$filepath.'/'.$fname; + if (-e $tempfile) { + my @info = stat($tempfile); + if ($info[9] eq $env{'form.timestamp'}) { + unlink($tempfile); + } + } + return; } } - open(my $fh,'>'.$fullpath.'/'.$fname); - print $fh $env{'form.'.$formname}; - close($fh); - return $fullpath.'/'.$fname; - } elsif (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) { #files uploaded to create course page are handled differently - my $filepath = 'tmp/addcourse/'.$destudom.'/web/'.$env{'user.name'}. - '_'.$env{'user.domain'}.'/pending'; + # Create the directory if not present my @parts=split(/\//,$filepath); my $fullpath = $perlvar{'lonDaemons'}; for (my $i=0;$i<@parts;$i++) { @@ -2274,34 +2860,39 @@ sub userfileupload { open(my $fh,'>'.$fullpath.'/'.$fname); print $fh $env{'form.'.$formname}; close($fh); - return $fullpath.'/'.$fname; + if ($context eq 'existingfile') { + my @info = stat($fullpath.'/'.$fname); + return ($fullpath.'/'.$fname,$info[9]); + } else { + return $fullpath.'/'.$fname; + } } if ($subdir eq 'scantron') { $fname = 'scantron_orig_'.$fname; - } else { -# Create the directory if not present + } else { $fname="$subdir/$fname"; } - if ($coursedoc) { + if ($context eq 'coursedoc') { my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'}; if ($env{'form.folder'} =~ m/^(default|supplemental)/) { return &finishuserfileupload($docuname,$docudom, $formname,$fname,$parser,$allfiles, - $codebase,$thumbwidth,$thumbheight); + $codebase,$thumbwidth,$thumbheight, + $resizewidth,$resizeheight,$context,$mimetype); } else { $fname=$env{'form.folder'}.'/'.$fname; return &process_coursefile('uploaddoc',$docuname,$docudom, $fname,$formname,$parser, - $allfiles,$codebase); + $allfiles,$codebase,$mimetype); } } elsif (defined($destuname)) { my $docuname=$destuname; my $docudom=$destudom; return &finishuserfileupload($docuname,$docudom,$formname,$fname, $parser,$allfiles,$codebase, - $thumbwidth,$thumbheight); - + $thumbwidth,$thumbheight, + $resizewidth,$resizeheight,$context,$mimetype); } else { my $docuname=$env{'user.name'}; my $docudom=$env{'user.domain'}; @@ -2311,13 +2902,14 @@ sub userfileupload { } return &finishuserfileupload($docuname,$docudom,$formname,$fname, $parser,$allfiles,$codebase, - $thumbwidth,$thumbheight); + $thumbwidth,$thumbheight, + $resizewidth,$resizeheight,$context,$mimetype); } } sub finishuserfileupload { my ($docuname,$docudom,$formname,$fname,$parser,$allfiles,$codebase, - $thumbwidth,$thumbheight) = @_; + $thumbwidth,$thumbheight,$resizewidth,$resizeheight,$context,$mimetype) = @_; my $path=$docudom.'/'.$docuname.'/'; my $filepath=$perlvar{'lonDocRoot'}; @@ -2343,24 +2935,49 @@ sub finishuserfileupload { print STDERR ('Failed to create '.$filepath.'/'.$file."\n"); return '/adm/notfound.html'; } - if (!print FH ($env{'form.'.$formname})) { + if ($context eq 'overwrite') { + my $source = LONCAPA::tempdir().'/overwrites/'.$docudom.'/'.$docuname.'/'.$fname; + my $target = $filepath.'/'.$file; + if (-e $source) { + my @info = stat($source); + if ($info[9] eq $env{'form.timestamp'}) { + unless (&File::Copy::move($source,$target)) { + &logthis('Failed to overwrite '.$filepath.'/'.$file); + return "Moving from $source failed"; + } + } else { + return "Temporary file: $source had unexpected date/time for last modification"; + } + } else { + return "Temporary file: $source missing"; + } + } elsif (!print FH ($env{'form.'.$formname})) { &logthis('Failed to write to '.$filepath.'/'.$file); print STDERR ('Failed to write to '.$filepath.'/'.$file."\n"); return '/adm/notfound.html'; } close(FH); - if($upload_photo_form==1) - { - resizeImage($filepath.'/'.$file); - $upload_photo_form = 0; + if ($resizewidth && $resizeheight) { + my $mm = new File::MMagic; + my $mime_type = $mm->checktype_filename($filepath.'/'.$file); + if ($mime_type =~ m{^image/}) { + &resizeImage($filepath.'/'.$file,$resizewidth,$resizeheight); + } } } if ($parser eq 'parse') { - my $parse_result = &extract_embedded_items($filepath.'/'.$file,$allfiles, - $codebase); - unless ($parse_result eq 'ok') { - &logthis('Failed to parse '.$filepath.$file. - ' for embedded media: '.$parse_result); + my $mm = new File::MMagic; + my $type = $mm->checktype_filename($filepath.'/'.$file); + if ($type eq 'text/html') { + my $parse_result = &extract_embedded_items($filepath.'/'.$file, + $allfiles,$codebase); + unless ($parse_result eq 'ok') { + &logthis('Failed to parse '.$filepath.$file. + ' for embedded media: '.$parse_result); + } + } + if (ref($mimetype)) { + $$mimetype = $type; } } if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) { @@ -2769,7 +3386,7 @@ sub userrolelog { if (($trole=~/^ca/) || ($trole=~/^aa/) || ($trole=~/^in/) || ($trole=~/^cc/) || ($trole=~/^ep/) || ($trole=~/^cr/) || - ($trole=~/^ta/)) { + ($trole=~/^ta/) || ($trole=~/^co/)) { my (undef,$rudom,$runame,$rsec)=split(/\//,$area); $userrolehash {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec} @@ -2778,7 +3395,8 @@ sub userrolelog { if (($env{'request.role'} =~ /dc\./) && (($trole=~/^au/) || ($trole=~/^in/) || ($trole=~/^cc/) || ($trole=~/^ep/) || - ($trole=~/^cr/) || ($trole=~/^ta/))) { + ($trole=~/^cr/) || ($trole=~/^ta/) || + ($trole=~/^co/))) { $userrolehash {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'} =$tend.':'.$tstart; @@ -2799,7 +3417,8 @@ sub courserolelog { if (($trole eq 'cc') || ($trole eq 'in') || ($trole eq 'ep') || ($trole eq 'ad') || ($trole eq 'ta') || ($trole eq 'st') || - ($trole=~/^cr/) || ($trole eq 'gr')) { + ($trole=~/^cr/) || ($trole eq 'gr') || + ($trole eq 'co')) { if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) { my $cdom = $1; my $cnum = $2; @@ -2893,8 +3512,9 @@ sub get_my_roles { unless (defined($uname)) { $uname=$env{'user.name'}; } unless (defined($udom)) { $udom=$env{'user.domain'}; } my (%dumphash,%nothide); - if ($context eq 'userroles') { - %dumphash = &dump('roles',$udom,$uname); + if ($context eq 'userroles') { + my $extra = &freeze_escape({'skipcheck' => 1}); + %dumphash = &dump('roles',$udom,$uname,'.',undef,$extra); } else { %dumphash= &dump('nohist_userroles',$udom,$uname); @@ -2954,6 +3574,10 @@ sub get_my_roles { if (!grep(/^cr$/,@{$roles})) { next; } + } elsif ($role =~ /^gr\//) { + if (!grep(/^gr$/,@{$roles})) { + next; + } } else { next; } @@ -3033,6 +3657,7 @@ sub getannounce { sub courseidput { my ($domain,$storehash,$coursehome,$caller) = @_; + return unless (ref($storehash) eq 'HASH'); my $outcome; if ($caller eq 'timeonly') { my $cids = ''; @@ -3071,7 +3696,8 @@ sub courseidput { sub courseiddump { my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter, $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok, - $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone,$cloneonly)=@_; + $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone, + $cloneonly,$createdbefore,$createdafter,$creationcontext,$domcloner)=@_; my $as_hash = 1; my %returnhash; if (!$domfilter) { $domfilter=''; } @@ -3091,7 +3717,10 @@ sub courseiddump { ':'.&escape($regexp_ok).':'.$as_hash.':'. &escape($selfenrollonly).':'.&escape($catfilter).':'. $showhidden.':'.$caller.':'.&escape($cloner).':'. - &escape($cc_clone).':'.$cloneonly,$tryserver); + &escape($cc_clone).':'.$cloneonly.':'. + &escape($createdbefore).':'.&escape($createdafter).':'. + &escape($creationcontext).':'.$domcloner, + $tryserver); my @pairs=split(/\&/,$rep); foreach my $item (@pairs) { my ($key,$value)=split(/\=/,$item,2); @@ -3114,6 +3743,49 @@ sub courseiddump { return %returnhash; } +sub courselastaccess { + my ($cdom,$cnum,$hostidref) = @_; + my %returnhash; + if ($cdom && $cnum) { + my $chome = &homeserver($cnum,$cdom); + if ($chome ne 'no_host') { + my $rep = &reply('courselastaccess:'.$cdom.':'.$cnum,$chome); + &extract_lastaccess(\%returnhash,$rep); + } + } else { + if (!$cdom) { $cdom=''; } + my %libserv = &all_library(); + foreach my $tryserver (keys(%libserv)) { + if (ref($hostidref) eq 'ARRAY') { + next unless (grep(/^\Q$tryserver\E$/,@{$hostidref})); + } + if (($cdom eq '') || (&host_domain($tryserver) eq $cdom)) { + my $rep = &reply('courselastaccess:'.&host_domain($tryserver).':',$tryserver); + &extract_lastaccess(\%returnhash,$rep); + } + } + } + return %returnhash; +} + +sub extract_lastaccess { + my ($returnhash,$rep) = @_; + if (ref($returnhash) eq 'HASH') { + unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' || + $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' || + $rep eq '') { + my @pairs=split(/\&/,$rep); + foreach my $item (@pairs) { + my ($key,$value)=split(/\=/,$item,2); + $key = &unescape($key); + next if ($key =~ /^error: 2 /); + $returnhash->{$key} = &thaw_unescape($value); + } + } + } + return; +} + # ---------------------------------------------------------- DC e-mail sub dcmailput { @@ -3146,10 +3818,10 @@ sub dcmaildump { sub get_domain_roles { my ($dom,$roles,$startdate,$enddate)=@_; - if (undef($startdate) || $startdate eq '') { + if ((!defined($startdate)) || ($startdate eq '')) { $startdate = '.'; } - if (undef($enddate) || $enddate eq '') { + if ((!defined($enddate)) || ($enddate eq '')) { $enddate = '.'; } my $rolelist; @@ -3174,7 +3846,7 @@ sub get_domain_roles { return %personnel; } -# ----------------------------------------------------------- Check out an item +# ----------------------------------------------------------- Interval timing sub get_first_access { my ($type,$argsymb)=@_; @@ -3210,91 +3882,6 @@ sub set_first_access { return 'already_set'; } -sub checkout { - my ($symb,$tuname,$tudom,$tcrsid)=@_; - my $now=time; - my $lonhost=$perlvar{'lonHostID'}; - my $infostr=&escape( - 'CHECKOUTTOKEN&'. - $tuname.'&'. - $tudom.'&'. - $tcrsid.'&'. - $symb.'&'. - $now.'&'.$ENV{'REMOTE_ADDR'}); - my $token=&reply('tmpput:'.$infostr,$lonhost); - if ($token=~/^error\:/) { - &logthis("WARNING: ". - "Checkout tmpput failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - return ''; - } - - $token=~s/^(\d+)\_.*\_(\d+)$/$1\*$2\*$lonhost/; - $token=~tr/a-z/A-Z/; - - my %infohash=('resource.0.outtoken' => $token, - 'resource.0.checkouttime' => $now, - 'resource.0.outremote' => $ENV{'REMOTE_ADDR'}); - - unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') { - return ''; - } else { - &logthis("WARNING: ". - "Checkout cstore failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - } - - if (&log($tudom,$tuname,&homeserver($tuname,$tudom), - &escape('Checkout '.$infostr.' - '. - $token)) ne 'ok') { - return ''; - } else { - &logthis("WARNING: ". - "Checkout log failed ".$tudom.' - '.$tuname.' - '.$symb. - ""); - } - return $token; -} - -# ------------------------------------------------------------ Check in an item - -sub checkin { - my $token=shift; - my $now=time; - my ($ta,$tb,$lonhost)=split(/\*/,$token); - $lonhost=~tr/A-Z/a-z/; - my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb; - $dtoken=~s/\W/\_/g; - my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)= - split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost))); - - unless (($tuname) && ($tudom)) { - &logthis('Check in '.$token.' ('.$dtoken.') failed'); - return ''; - } - - unless (&allowed('mgr',$tcrsid)) { - &logthis('Check in '.$token.' ('.$dtoken.') unauthorized: '. - $env{'user.name'}.' - '.$env{'user.domain'}); - return ''; - } - - my %infohash=('resource.0.intoken' => $token, - 'resource.0.checkintime' => $now, - 'resource.0.inremote' => $ENV{'REMOTE_ADDR'}); - - unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') { - return ''; - } - - if (&log($tudom,$tuname,&homeserver($tuname,$tudom), - &escape('Checkin - '.$token)) ne 'ok') { - return ''; - } - - return ($symb,$tuname,$tudom,$tcrsid); -} - # --------------------------------------------- Set Expire Date for Spreadsheet sub expirespread { @@ -3399,7 +3986,7 @@ sub hashref2str { $result.='='; #print("Got a ref of ".(ref($key))." skipping."); } else { - if ($key) {$result.=&escape($key).'=';} else { last; } + if (defined($key)) {$result.=&escape($key).'=';} else { last; } } if(ref($hashref->{$key}) eq 'ARRAY') { @@ -3551,7 +4138,7 @@ sub tmpreset { if ($domain eq 'public' && $stuname eq 'public') { $stuname=$ENV{'REMOTE_ADDR'}; } - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); my %hash; if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', @@ -3590,7 +4177,7 @@ sub tmpstore { } my $now=time; my %hash; - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', &GDBM_WRCREAT(),0640)) { @@ -3636,7 +4223,7 @@ sub tmprestore { $namespace=~s/\//\_/g; $namespace=~s/\W//g; my %hash; - my $path=$perlvar{'lonDaemons'}.'/tmp'; + my $path=LONCAPA::tempdir(); if (tie(%hash,'GDBM_File', $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', &GDBM_READER(),0640)) { @@ -3773,6 +4360,8 @@ sub restore { } # ---------------------------------------------------------- Course Description +# +# sub coursedescription { my ($courseid,$args)=@_; @@ -3802,7 +4391,8 @@ sub coursedescription { return %returnhash; } - # get the data agin + # get the data again + if (!$args->{'one_time'}) { $envhash{'course.'.$normalid.'.last_cache'}=time; } @@ -3810,6 +4400,10 @@ sub coursedescription { if ($chome ne 'no_host') { %returnhash=&dump('environment',$cdomain,$cnum); if (!exists($returnhash{'con_lost'})) { + my $username = $env{'user.name'}; # Defult username + if(defined $args->{'user'}) { + $username = $args->{'user'}; + } $returnhash{'home'}= $chome; $returnhash{'domain'} = $cdomain; $returnhash{'num'} = $cnum; @@ -3820,8 +4414,8 @@ sub coursedescription { $envhash{'course.'.$normalid.'.'.$name}=$value; } $returnhash{'url'}=&clutter($returnhash{'url'}); - $returnhash{'fn'}=$perlvar{'lonDaemons'}.'/tmp/'. - $env{'user.name'}.'_'.$cdomain.'_'.$cnum; + $returnhash{'fn'}=LONCAPA::tempdir() . + $username.'_'.$cdomain.'_'.$cnum; $envhash{'course.'.$normalid.'.home'}=$chome; $envhash{'course.'.$normalid.'.domain'}=$cdomain; $envhash{'course.'.$normalid.'.num'}=$cnum; @@ -3833,13 +4427,54 @@ sub coursedescription { return %returnhash; } +sub update_released_required { + my ($needsrelease,$cdom,$cnum,$chome,$cid) = @_; + if ($cdom eq '' || $cnum eq '' || $chome eq '' || $cid eq '') { + $cid = $env{'request.course.id'}; + $cdom = $env{'course.'.$cid.'.domain'}; + $cnum = $env{'course.'.$cid.'.num'}; + $chome = $env{'course.'.$cid.'.home'}; + } + if ($needsrelease) { + my %curr_reqd_hash = &userenvironment($cdom,$cnum,'internal.releaserequired'); + my $needsupdate; + if ($curr_reqd_hash{'internal.releaserequired'} eq '') { + $needsupdate = 1; + } else { + my ($currmajor,$currminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'}); + my ($needsmajor,$needsminor) = split(/\./,$needsrelease); + if (($currmajor < $needsmajor) || ($currmajor == $needsmajor && $currminor < $needsminor)) { + $needsupdate = 1; + } + } + if ($needsupdate) { + my %needshash = ( + 'internal.releaserequired' => $needsrelease, + ); + my $putresult = &put('environment',\%needshash,$cdom,$cnum); + if ($putresult eq 'ok') { + &appenv({'course.'.$cid.'.internal.releaserequired' => $needsrelease}); + my %crsinfo = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.'); + if (ref($crsinfo{$cid}) eq 'HASH') { + $crsinfo{$cid}{'releaserequired'} = $needsrelease; + &courseidput($cdom,\%crsinfo,$chome,'notime'); + } + } + } + } + return; +} + # -------------------------------------------------See if a user is privileged sub privileged { my ($username,$domain)=@_; my $rolesdump=&reply("dump:$domain:$username:roles", &homeserver($username,$domain)); - if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return 0; } + if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || + ($rolesdump =~ /^error:/)) { + return 0; + } my $now=time; if ($rolesdump ne '') { foreach my $entry (split(/&/,$rolesdump)) { @@ -3867,14 +4502,16 @@ sub privileged { sub rolesinit { my ($domain,$username,$authhost)=@_; - my %userroles; - my $rolesdump=reply("dump:$domain:$username:roles",$authhost); - if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return \%userroles; } + my $now=time; + my %userroles = ('user.login.time' => $now); + my $extra = &freeze_escape({'skipcheck' => 1}); + my $rolesdump=reply("dump:$domain:$username:roles:.::$extra",$authhost); + if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || + ($rolesdump =~ /^error:/)) { + return \%userroles; + } my %allroles=(); my %allgroups=(); - my $now=time; - %userroles = ('user.login.time' => $now); - my $group_privs; if ($rolesdump ne '') { foreach my $entry (split(/&/,$rolesdump)) { @@ -3891,6 +4528,7 @@ sub rolesinit { } } elsif ($role =~ m|^gr/|) { ($trole,$tend,$tstart) = split(/_/,$role); + next if ($tstart eq '-1'); ($trole,$group_privs) = split(/\//,$trole); $group_privs = &unescape($group_privs); } else { @@ -3939,6 +4577,9 @@ sub custom_roleprivs { if (($rdummy ne 'con_lost') && ($roledef ne '')) { my ($syspriv,$dompriv,$coursepriv)=split(/\_/,$roledef); if (defined($syspriv)) { + if ($trest =~ /^$match_community$/) { + $syspriv =~ s/bre\&S//; + } $$allroles{'cm./'}.=':'.$syspriv; $$allroles{$spec.'./'}.=':'.$syspriv; } @@ -3987,23 +4628,36 @@ sub standard_roleprivs { } sub set_userprivs { - my ($userroles,$allroles,$allgroups) = @_; + my ($userroles,$allroles,$allgroups,$groups_roles) = @_; my $author=0; my $adv=0; my %grouproles = (); if (keys(%{$allgroups}) > 0) { + my @groupkeys; foreach my $role (keys(%{$allroles})) { - my ($trole,$area,$sec,$extendedarea); - if ($role =~ m-^(\w+|cr/$match_domain/$match_username/\w+)\.(/$match_domain/$match_courseid)(/?\w*)\.-) { - $trole = $1; - $area = $2; - $sec = $3; - $extendedarea = $area.$sec; - if (exists($$allgroups{$area})) { - foreach my $group (keys(%{$$allgroups{$area}})) { - my $spec = $trole.'.'.$extendedarea; - $grouproles{$spec.'.'.$area.'/'.$group} = + push(@groupkeys,$role); + } + if (ref($groups_roles) eq 'HASH') { + foreach my $key (keys(%{$groups_roles})) { + unless (grep(/^\Q$key\E$/,@groupkeys)) { + push(@groupkeys,$key); + } + } + } + if (@groupkeys > 0) { + foreach my $role (@groupkeys) { + my ($trole,$area,$sec,$extendedarea); + if ($role =~ m-^(\w+|cr/$match_domain/$match_username/\w+)\.(/$match_domain/$match_courseid)(/?\w*)\.-) { + $trole = $1; + $area = $2; + $sec = $3; + $extendedarea = $area.$sec; + if (exists($$allgroups{$area})) { + foreach my $group (keys(%{$$allgroups{$area}})) { + my $spec = $trole.'.'.$extendedarea; + $grouproles{$spec.'.'.$area.'/'.$group} = $$allgroups{$area}{$group}; + } } } } @@ -4027,7 +4681,7 @@ sub set_userprivs { } } my $thesestr=''; - foreach my $priv (keys(%thesepriv)) { + foreach my $priv (sort(keys(%thesepriv))) { $thesestr.=':'.$priv.'&'.$thesepriv{$priv}; } $userroles->{'user.priv.'.$role} = $thesestr; @@ -4036,7 +4690,7 @@ sub set_userprivs { } sub role_status { - my ($rolekey,$then,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_; + my ($rolekey,$update,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_; my @pwhere = (); if (exists($env{$rolekey}) && $env{$rolekey} ne '') { (undef,undef,$$role,@pwhere)=split(/\./,$rolekey); @@ -4045,46 +4699,48 @@ sub role_status { $$trolecode=$$role.'.'.$$where; ($$tstart,$$tend)=split(/\./,$env{$rolekey}); $$tstatus='is'; - if ($$tstart && $$tstart>$then) { + if ($$tstart && $$tstart>$update) { $$tstatus='future'; - if ($$tstart && $$tstart>$refresh) { - if ($$tstart<$now) { + if ($$tstart<$now) { + if ($$tstart && $$tstart>$refresh) { if (($$where ne '') && ($$role ne '')) { - my (%allroles,%allgroups,$group_privs); + my (%allroles,%allgroups,$group_privs, + %groups_roles,@rolecodes); my %userroles = ( 'user.role.'.$$role.'.'.$$where => $$tstart.'.'.$$tend ); + @rolecodes = ('cm'); my $spec=$$role.'.'.$$where; my ($tdummy,$tdomain,$trest)=split(/\//,$$where); - if ($$role eq 'gr') { - my %rolehash = &get('roles',[$$where.'_'.$$role],$env{'user.domain'}, - $env{'user.name'})=@_; - my ($trole) = split('_',$role,1); - (undef,my $group_privs) = split(/\//,$trole); - $group_privs = &unescape($group_privs); - } if ($$role =~ /^cr\//) { &custom_roleprivs(\%allroles,$$role,$tdomain,$trest,$spec,$$where); + push(@rolecodes,'cr'); } elsif ($$role eq 'gr') { + push(@rolecodes,$$role); my %rolehash = &get('roles',[$$where.'_'.$$role],$env{'user.domain'}, $env{'user.name'}); - my $trole = split('_',$rolehash{$$where.'_'.$$role},1); + my ($trole) = split('_',$rolehash{$$where.'_'.$$role},2); (undef,my $group_privs) = split(/\//,$trole); $group_privs = &unescape($group_privs); &group_roleprivs(\%allgroups,$$where,$group_privs,$$tend,$$tstart); + my %course_roles = &get_my_roles($env{'user.name'},$env{'user.domain'},'userroles',['active'],['cc','co','in','ta','ep','ad','st','cr'],[$tdomain],1); + &get_groups_roles($tdomain,$trest, + \%course_roles,\@rolecodes, + \%groups_roles); } else { + push(@rolecodes,$$role); &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where); } - my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups); - &appenv(\%userroles,[$$role,'cm']); + my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups,\%groups_roles); + &appenv(\%userroles,\@rolecodes); &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role); - $$tstatus = 'is'; } } + $$tstatus = 'is'; } } if ($$tend) { - if ($$tend<$then) { + if ($$tend<$update) { $$tstatus='expired'; } elsif ($$tend<$now) { $$tstatus='will_not'; @@ -4094,23 +4750,81 @@ sub role_status { } } +sub get_groups_roles { + my ($cdom,$rest,$cdom_courseroles,$rolecodes,$groups_roles) = @_; + return unless((ref($cdom_courseroles) eq 'HASH') && + (ref($rolecodes) eq 'ARRAY') && + (ref($groups_roles) eq 'HASH')); + if (keys(%{$cdom_courseroles}) > 0) { + my ($cnum) = ($rest =~ /^($match_courseid)/); + if ($cdom ne '' && $cnum ne '') { + foreach my $key (keys(%{$cdom_courseroles})) { + if ($key =~ /^\Q$cnum\E:\Q$cdom\E:([^:]+):?([^:]*)/) { + my $crsrole = $1; + my $crssec = $2; + if ($crsrole =~ /^cr/) { + unless (grep(/^cr$/,@{$rolecodes})) { + push(@{$rolecodes},'cr'); + } + } else { + unless(grep(/^\Q$crsrole\E$/,@{$rolecodes})) { + push(@{$rolecodes},$crsrole); + } + } + my $rolekey = "$crsrole./$cdom/$cnum"; + if ($crssec ne '') { + $rolekey .= "/$crssec"; + } + $rolekey .= './'; + $groups_roles->{$rolekey} = $rolecodes; + } + } + } + } + return; +} + +sub delete_env_groupprivs { + my ($where,$courseroles,$possroles) = @_; + return unless((ref($courseroles) eq 'HASH') && (ref($possroles) eq 'ARRAY')); + my ($dummy,$udom,$uname,$group) = split(/\//,$where); + unless (ref($courseroles->{$udom}) eq 'HASH') { + %{$courseroles->{$udom}} = + &get_my_roles('','','userroles',['active'], + $possroles,[$udom],1); + } + if (ref($courseroles->{$udom}) eq 'HASH') { + foreach my $item (keys(%{$courseroles->{$udom}})) { + my ($cnum,$cdom,$crsrole,$crssec) = split(/:/,$item); + my $area = '/'.$cdom.'/'.$cnum; + my $privkey = "user.priv.$crsrole.$area"; + if ($crssec ne '') { + $privkey .= '/'.$crssec; + } + $privkey .= ".$area/$group"; + &Apache::lonnet::delenv($privkey,undef,[$crsrole]); + } + } + return; +} + sub check_adhoc_privs { - my ($cdom,$cnum,$then,$refresh,$now,$checkrole) = @_; + my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller) = @_; my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum; if ($env{$cckey}) { my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend); - &role_status($cckey,$then,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); + &role_status($cckey,$update,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); unless (($tstatus eq 'is') || ($tstatus eq 'will_not')) { - &set_adhoc_privileges($cdom,$cnum,$checkrole); + &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); } } else { - &set_adhoc_privileges($cdom,$cnum,$checkrole); + &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); } } sub set_adhoc_privileges { # role can be cc or ca - my ($dcdom,$pickedcourse,$role) = @_; + my ($dcdom,$pickedcourse,$role,$caller) = @_; my $area = '/'.$dcdom.'/'.$pickedcourse; my $spec = $role.'.'.$area; my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'}, @@ -4120,14 +4834,16 @@ sub set_adhoc_privileges { my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole); &appenv(\%userroles,[$role,'cm']); &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role); - &appenv( {'request.role' => $spec, - 'request.role.domain' => $dcdom, - 'request.course.sec' => '' - } - ); - my $tadv=0; - if (&allowed('adv') eq 'F') { $tadv=1; } - &appenv({'request.role.adv' => $tadv}); + unless ($caller eq 'constructaccess' && $env{'request.course.id'}) { + &appenv( {'request.role' => $spec, + 'request.role.domain' => $dcdom, + 'request.course.sec' => '' + } + ); + my $tadv=0; + if (&allowed('adv') eq 'F') { $tadv=1; } + &appenv({'request.role.adv' => $tadv}); + } } # --------------------------------------------------------------- get interface @@ -4176,7 +4892,7 @@ sub del { # -------------------------------------------------------------- dump interface sub dump { - my ($namespace,$udomain,$uname,$regexp,$range)=@_; + my ($namespace,$udomain,$uname,$regexp,$range,$extra)=@_; if (!$udomain) { $udomain=$env{'user.domain'}; } if (!$uname) { $uname=$env{'user.name'}; } my $uhome=&homeserver($uname,$udomain); @@ -4185,18 +4901,21 @@ sub dump { } else { $regexp='.'; } - my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome); + my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range:$extra",$uhome); my @pairs=split(/\&/,$rep); my %returnhash=(); - foreach my $item (@pairs) { - my ($key,$value)=split(/=/,$item,2); - $key = &unescape($key); - next if ($key =~ /^error: 2 /); - $returnhash{$key}=&thaw_unescape($value); + if (!($rep =~ /^error/ )) { + foreach my $item (@pairs) { + my ($key,$value)=split(/=/,$item,2); + $key = &unescape($key); + next if ($key =~ /^error: 2 /); + $returnhash{$key}=&thaw_unescape($value); + } } return %returnhash; } + # --------------------------------------------------------- dumpstore interface sub dumpstore { @@ -4479,7 +5198,7 @@ sub tmpget { return %returnhash; } -# ------------------------------------------------------------ tmpget interface +# ------------------------------------------------------------ tmpdel interface sub tmpdel { my ($token,$server)=@_; if (!defined($server)) { $server = $perlvar{'lonHostID'}; } @@ -4590,7 +5309,7 @@ sub get_portfolio_access { my (%allgroups,%allroles); my ($start,$end,$role,$sec,$group); foreach my $envkey (%env) { - if ($envkey =~ m-^user\.role\.(gr|cc|in|ta|ep|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) { + if ($envkey =~ m-^user\.role\.(gr|cc|co|in|ta|ep|ad|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) { my $cid = $2.'_'.$3; if ($1 eq 'gr') { $group = $4; @@ -4730,7 +5449,7 @@ sub is_portfolio_file { } sub usertools_access { - my ($uname,$udom,$tool,$action,$context) = @_; + my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_; my ($access,%tools); if ($context eq '') { $context = 'tools'; @@ -4772,9 +5491,14 @@ sub usertools_access { $toolstatus = $env{'environment.'.$context.'.'.$tool}; $inststatus = $env{'environment.inststatus'}; } else { - my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool); - $toolstatus = $userenv{$context.'.'.$tool}; - $inststatus = $userenv{'inststatus'}; + if (ref($userenvref) eq 'HASH') { + $toolstatus = $userenvref->{$context.'.'.$tool}; + $inststatus = $userenvref->{'inststatus'}; + } else { + my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus'); + $toolstatus = $userenv{$context.'.'.$tool}; + $inststatus = $userenv{'inststatus'}; + } } if ($toolstatus ne '') { @@ -4786,8 +5510,17 @@ sub usertools_access { return $access; } - my $is_adv = &is_advanced_user($udom,$uname); - my %domdef = &get_domain_defaults($udom); + my ($is_adv,%domdef); + if (ref($is_advref) eq 'HASH') { + $is_adv = $is_advref->{'is_adv'}; + } else { + $is_adv = &is_advanced_user($udom,$uname); + } + if (ref($domdefref) eq 'HASH') { + %domdef = %{$domdefref}; + } else { + %domdef = &get_domain_defaults($udom); + } if (ref($domdef{$tool}) eq 'HASH') { if ($is_adv) { if ($domdef{$tool}{'_LC_adv'} ne '') { @@ -4838,11 +5571,41 @@ sub usertools_access { } } +sub is_course_owner { + my ($cdom,$cnum,$udom,$uname) = @_; + if (($udom eq '') || ($uname eq '')) { + $udom = $env{'user.domain'}; + $uname = $env{'user.name'}; + } + unless (($udom eq '') || ($uname eq '')) { + if (exists($env{'course.'.$cdom.'_'.$cnum.'.internal.courseowner'})) { + if ($env{'course.'.$cdom.'_'.$cnum.'.internal.courseowner'} eq $uname.':'.$udom) { + return 1; + } else { + my %courseinfo = &Apache::lonnet::coursedescription($cdom.'/'.$cnum); + if ($courseinfo{'internal.courseowner'} eq $uname.':'.$udom) { + return 1; + } + } + } + } + return; +} + sub is_advanced_user { my ($udom,$uname) = @_; + if ($udom ne '' && $uname ne '') { + if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) { + if (wantarray) { + return ($env{'user.adv'},$env{'user.author'}); + } else { + return $env{'user.adv'}; + } + } + } my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1); my %allroles; - my $is_adv; + my ($is_adv,$is_author); foreach my $role (keys(%roleshash)) { my ($trest,$tdomain,$trole,$sec) = split(/:/,$role); my $area = '/'.$tdomain.'/'.$trest; @@ -4856,6 +5619,9 @@ sub is_advanced_user { } elsif ($trole ne 'gr') { &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area); } + if ($trole eq 'au') { + $is_author = 1; + } } } foreach my $role (keys(%allroles)) { @@ -4870,9 +5636,61 @@ sub is_advanced_user { } } } + if (wantarray) { + return ($is_adv,$is_author); + } return $is_adv; } +sub check_can_request { + my ($dom,$can_request,$request_domains) = @_; + my $canreq = 0; + my ($types,$typename) = &Apache::loncommon::course_types(); + my @options = ('approval','validate','autolimit'); + my $optregex = join('|',@options); + if ((ref($can_request) eq 'HASH') && (ref($types) eq 'ARRAY')) { + foreach my $type (@{$types}) { + if (&usertools_access($env{'user.name'}, + $env{'user.domain'}, + $type,undef,'requestcourses')) { + $canreq ++; + if (ref($request_domains) eq 'HASH') { + push(@{$request_domains->{$type}},$env{'user.domain'}); + } + if ($dom eq $env{'user.domain'}) { + $can_request->{$type} = 1; + } + } + if ($env{'environment.reqcrsotherdom.'.$type} ne '') { + my @curr = split(',',$env{'environment.reqcrsotherdom.'.$type}); + if (@curr > 0) { + foreach my $item (@curr) { + if (ref($request_domains) eq 'HASH') { + my ($otherdom) = ($item =~ /^($match_domain):($optregex)(=?\d*)$/); + if ($otherdom ne '') { + if (ref($request_domains->{$type}) eq 'ARRAY') { + unless (grep(/^\Q$otherdom\E$/,@{$request_domains->{$type}})) { + push(@{$request_domains->{$type}},$otherdom); + } + } else { + push(@{$request_domains->{$type}},$otherdom); + } + } + } + } + unless($dom eq $env{'user.domain'}) { + $canreq ++; + if (grep(/^\Q$dom\E:($optregex)(=?\d*)$/,@curr)) { + $can_request->{$type} = 1; + } + } + } + } + } + } + return $canreq; +} + # ---------------------------------------------- Custom access rule evaluation sub customaccess { @@ -5027,17 +5845,68 @@ sub allowed { my $statecond=0; my $courseprivid=''; + my $ownaccess; + # Community Coordinator or Assistant Co-author browsing resource space. + if (($priv eq 'bro') && ($env{'user.author'})) { + if ($uri eq '') { + $ownaccess = 1; + } else { + if (($env{'user.domain'} ne '') && ($env{'user.name'} ne '')) { + my $udom = $env{'user.domain'}; + my $uname = $env{'user.name'}; + if ($uri =~ m{^\Q$udom\E/?$}) { + $ownaccess = 1; + } elsif ($uri =~ m{^\Q$udom\E/\Q$uname\E/?}) { + unless ($uri =~ m{\.\./}) { + $ownaccess = 1; + } + } elsif (($udom ne 'public') && ($uname ne 'public')) { + my $now = time; + if ($uri =~ m{^([^/]+)/?$}) { + my $adom = $1; + foreach my $key (keys(%env)) { + if ($key =~ m{^user\.role\.(ca|aa)/\Q$adom\E}) { + my ($start,$end) = split('.',$env{$key}); + if (($now >= $start) && (!$end || $end < $now)) { + $ownaccess = 1; + last; + } + } + } + } elsif ($uri =~ m{^([^/]+)/([^/]+)/?}) { + my $adom = $1; + my $aname = $2; + foreach my $role ('ca','aa') { + if ($env{"user.role.$role./$adom/$aname"}) { + my ($start,$end) = + split('.',$env{"user.role.$role./$adom/$aname"}); + if (($now >= $start) && (!$end || $end < $now)) { + $ownaccess = 1; + last; + } + } + } + } + } + } + } + } + # Course if ($env{'user.priv.'.$env{'request.role'}.'./'}=~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + unless (($priv eq 'bro') && (!$ownaccess)) { + $thisallowed.=$1; + } } # Domain if ($env{'user.priv.'.$env{'request.role'}.'./'.(split(/\//,$uri))[0].'/'} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + unless (($priv eq 'bro') && (!$ownaccess)) { + $thisallowed.=$1; + } } # Course: uri itself is a course @@ -5047,7 +5916,9 @@ sub allowed { if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseuri} =~/\Q$priv\E\&([^\:]*)/) { - $thisallowed.=$1; + unless (($priv eq 'bro') && (!$ownaccess)) { + $thisallowed.=$1; + } } # URI is an uploaded document for this course, default permissions don't matter @@ -5253,7 +6124,7 @@ sub allowed { my $unamedom=$env{'user.name'}.':'.$env{'user.domain'}; if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.roles.denied'} =~/\Q$rolecode\E/) { - if ($priv ne 'pch') { + if (($priv ne 'pch') && ($priv ne 'plc')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'. 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '. $env{'request.course.id'}); @@ -5263,7 +6134,7 @@ sub allowed { if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.users.denied'} =~/\Q$unamedom\E/) { - if ($priv ne 'pch') { + if (($priv ne 'pch') && ($priv ne 'plc')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}. 'Denied by user: '.$priv.' for '.$uri.' as '.$unamedom.' in '. $env{'request.course.id'}); @@ -5277,7 +6148,7 @@ sub allowed { if ($thisallowed=~/R/) { my $rolecode=(split(/\./,$env{'request.role'}))[0]; if (&metadata($uri,'roledeny')=~/\Q$rolecode\E/) { - if ($priv ne 'pch') { + if (($priv ne 'pch') && ($priv ne 'plc')) { &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'. 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode); } @@ -5308,7 +6179,27 @@ sub allowed { } return 'F'; } - +# +# Removes the versino from a URI and +# splits it in to its filename and path to the filename. +# Seems like File::Basename could have done this more clearly. +# Parameters: +# $uri - input URI +# Returns: +# Two element list consisting of +# $pathname - the URI up to and excluding the trailing / +# $filename - The part of the URI following the last / +# NOTE: +# Another realization of this is simply: +# use File::Basename; +# ... +# $uri = shift; +# $filename = basename($uri); +# $path = dirname($uri); +# return ($filename, $path); +# +# The implementation below is probably faster however. +# sub split_uri_for_cond { my $uri=&deversion(&declutter(shift)); my @uriparts=split(/\//,$uri); @@ -5460,8 +6351,7 @@ sub update_allusers_table { 'generation='.&escape($names->{'generation'}).'%%'. 'permanentemail='.&escape($names->{'permanentemail'}).'%%'. 'id='.&escape($names->{'id'}),$homeserver); - my $reply = &get_query_reply($queryid); - return $reply; + return; } # ------- Request retrieval of institutional classlists for course(s) @@ -5505,7 +6395,7 @@ sub fetch_enrollment_query { $$replyref{$key} = $value; } } else { - my $pathname = $perlvar{'lonDaemons'}.'/tmp'; + my $pathname = LONCAPA::tempdir(); foreach my $line (@responses) { my ($key,$value) = split(/=/,$line); $$replyref{$key} = $value; @@ -5535,7 +6425,7 @@ sub fetch_enrollment_query { sub get_query_reply { my $queryid=shift; - my $replyfile=$perlvar{'lonDaemons'}.'/tmp/'.$queryid; + my $replyfile=LONCAPA::tempdir().$queryid; my $reply=''; for (1..100) { sleep 2; @@ -5631,9 +6521,9 @@ sub auto_get_sections { } sub auto_new_course { - my ($cnum,$cdom,$inst_course_id,$owner) = @_; + my ($cnum,$cdom,$inst_course_id,$owner,$coowners) = @_; my $homeserver = &homeserver($cnum,$cdom); - my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.$owner.':'.$cdom,$homeserver)); + my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.&escape($owner).':'.$cdom.':'.&escape($coowners),$homeserver)); return $response; } @@ -5655,9 +6545,10 @@ sub auto_validate_instcode { $homeserver = &domain($cdom,'primary'); } } - my $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'. - &escape($instcode).':'.&escape($owner),$homeserver)); - return $response; + $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'. + &escape($instcode).':'.&escape($owner),$homeserver)); + my ($outcome,$description) = map { &unescape($_); } split('&',$response,2); + return ($outcome,$description); } sub auto_create_password { @@ -5774,6 +6665,13 @@ sub auto_instcode_format { push(@homeservers,$tryserver); } } + } elsif ($caller eq 'requests') { + if ($codedom =~ /^$match_domain$/) { + my $chome = &domain($codedom,'primary'); + unless ($chome eq 'no_host') { + push(@homeservers,$chome); + } + } } else { push(@homeservers,&homeserver($caller,$codedom)); } @@ -5874,13 +6772,37 @@ sub auto_possible_instcodes { sub auto_courserequest_checks { my ($dom) = @_; - my %validations; + my ($homeserver,%validations); + if ($dom =~ /^$match_domain$/) { + $homeserver = &domain($dom,'primary'); + } + unless ($homeserver eq 'no_host') { + my $response=&reply('autocrsreqchecks:'.$dom,$homeserver); + unless ($response =~ /(con_lost|error|no_such_host|refused)/) { + my @items = split(/&/,$response); + foreach my $item (@items) { + my ($key,$value) = split('=',$item); + $validations{&unescape($key)} = &thaw_unescape($value); + } + } + } return %validations; } sub auto_courserequest_validation { - my ($dom,$details,$inststatuses,$message) = @_; - return 'pending'; + my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = @_; + my ($homeserver,$response); + if ($dom =~ /^$match_domain$/) { + $homeserver = &domain($dom,'primary'); + } + unless ($homeserver eq 'no_host') { + + $response=&unescape(&reply('autocrsreqvalidation:'.$dom.':'.&escape($owner). + ':'.&escape($crstype).':'.&escape($inststatuslist). + ':'.&escape($instcode).':'.&escape($instseclist), + $homeserver)); + } + return $response; } sub auto_validate_class_sec { @@ -5992,7 +6914,8 @@ sub get_users_groups { } else { $grouplist = ''; my $courseurl = &courseid_to_courseurl($courseid); - my %roleshash = &dump('roles',$udom,$uname,$courseurl); + my $extra = &freeze_escape({'skipcheck' => 1}); + my %roleshash = &dump('roles',$udom,$uname,$courseurl,undef,$extra); my $access_end = $env{'course.'.$courseid. '.default_enrollment_end_date'}; my $now = time; @@ -6035,30 +6958,37 @@ sub devalidate_getgroups_cache { sub plaintext { my ($short,$type,$cid,$forcedefault) = @_; - if ($short =~ /^cr/) { + if ($short =~ m{^cr/}) { return (split('/',$short))[-1]; } if (!defined($cid)) { $cid = $env{'request.course.id'}; } - if (defined($cid) && ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '')) { - unless ($forcedefault) { - my $roletext = $env{'course.'.$cid.'.'.$short.'.plaintext'}; - &Apache::lonlocal::mt_escape(\$roletext); - return &Apache::lonlocal::mt($roletext); - } - } my %rolenames = ( Course => 'std', Community => 'alt1', ); - if (defined($type) && - defined($rolenames{$type}) && - defined($prp{$short}{$rolenames{$type}})) { + if ($cid ne '') { + if ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '') { + unless ($forcedefault) { + my $roletext = $env{'course.'.$cid.'.'.$short.'.plaintext'}; + &Apache::lonlocal::mt_escape(\$roletext); + return &Apache::lonlocal::mt($roletext); + } + } + } + if ((defined($type)) && (defined($rolenames{$type})) && + (defined($rolenames{$type})) && + (defined($prp{$short}{$rolenames{$type}}))) { return &Apache::lonlocal::mt($prp{$short}{$rolenames{$type}}); - } else { - return &Apache::lonlocal::mt($prp{$short}{'std'}); + } elsif ($cid ne '') { + my $crstype = $env{'course.'.$cid.'.type'}; + if (($crstype ne '') && (defined($rolenames{$crstype})) && + (defined($prp{$short}{$rolenames{$crstype}}))) { + return &Apache::lonlocal::mt($prp{$short}{$rolenames{$crstype}}); + } } + return &Apache::lonlocal::mt($prp{$short}{'std'}); } # ----------------------------------------------------------------- Assign Role @@ -6071,10 +7001,27 @@ sub assignrole { my $cwosec=$url; $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/; unless (&allowed('ccr',$cwosec)) { - &logthis('Refused custom assignrole: '. - $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '. - $env{'user.name'}.' at '.$env{'user.domain'}); - return 'refused'; + my $refused = 1; + if ($context eq 'requestcourses') { + if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) { + if ($role =~ m{^cr/($match_domain)/($match_username)/([^/]+)$}) { + if (($1 eq $env{'user.domain'}) && ($2 eq $env{'user.name'})) { + my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$}); + my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner')); + if ($crsenv{'internal.courseowner'} eq + $env{'user.name'}.':'.$env{'user.domain'}) { + $refused = ''; + } + } + } + } + } + if ($refused) { + &logthis('Refused custom assignrole: '. + $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start. + ' by '.$env{'user.name'}.' at '.$env{'user.domain'}); + return 'refused'; + } } $mrole='cr'; } elsif ($role =~ /^gr\//) { @@ -6100,15 +7047,44 @@ sub assignrole { $refused = 1; } if ($refused) { - if (($selfenroll == 1) && ($role eq 'st') && ($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) { + my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$}); + if (!$selfenroll && $context eq 'course') { + my %crsenv; + if ($role eq 'cc' || $role eq 'co') { + %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner')); + if (($role eq 'cc') && ($cnum !~ /^$match_community$/)) { + if ($env{'request.role'} eq 'cc./'.$cdom.'/'.$cnum) { + if ($crsenv{'internal.courseowner'} eq + $env{'user.name'}.':'.$env{'user.domain'}) { + $refused = ''; + } + } + } elsif (($role eq 'co') && ($cnum =~ /^$match_community$/)) { + if ($env{'request.role'} eq 'co./'.$cdom.'/'.$cnum) { + if ($crsenv{'internal.courseowner'} eq + $env{'user.name'}.':'.$env{'user.domain'}) { + $refused = ''; + } + } + } + } + } elsif (($selfenroll == 1) && ($role eq 'st') && ($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) { $refused = ''; } elsif ($context eq 'requestcourses') { - if (($role eq 'cc') && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) { - my ($cdom,$cnum) = ($cwosec =~ m{^/($match_domain)/($match_courseid)$}); - my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner')); - if ($crsenv{'internal.courseowner'} eq - $env{'user.name'}.':'.$env{'user.domain'}) { - $refused = ''; + my @possroles = ('st','ta','ep','in','cc','co'); + if ((grep(/^\Q$role\E$/,@possroles)) && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) { + my $wrongcc; + if ($cnum =~ /^$match_community$/) { + $wrongcc = 1 if ($role eq 'cc'); + } else { + $wrongcc = 1 if ($role eq 'co'); + } + unless ($wrongcc) { + my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner')); + if ($crsenv{'internal.courseowner'} eq + $env{'user.name'}.':'.$env{'user.domain'}) { + $refused = ''; + } } } } @@ -6119,6 +7095,13 @@ sub assignrole { return 'refused'; } } + } elsif ($role eq 'au') { + if ($url ne '/'.$udom.'/') { + &logthis('Attempt by '.$env{'user.name'}.':'.$env{'user.domain'}. + ' to assign author role for '.$uname.':'.$udom. + ' in domain: '.$url.' refused (wrong domain).'); + return 'refused'; + } } $mrole=$role; } @@ -6159,10 +7142,97 @@ sub assignrole { &Apache::longroup::group_changes($udom,$uname,$url,$role,$origend, $origstart,$selfenroll,$context); } + if ($role eq 'cc') { + &autoupdate_coowners($url,$end,$start,$uname,$udom); + } } return $answer; } +sub autoupdate_coowners { + my ($url,$end,$start,$uname,$udom) = @_; + my ($cdom,$cnum) = ($url =~ m{^/($match_domain)/($match_courseid)}); + if (($cdom ne '') && ($cnum ne '')) { + my $now = time; + my %domdesign = &Apache::loncommon::get_domainconf($cdom); + if ($domdesign{$cdom.'.autoassign.co-owners'}) { + my %coursehash = &coursedescription($cdom.'_'.$cnum); + my $instcode = $coursehash{'internal.coursecode'}; + if ($instcode ne '') { + if (($start && $start <= $now) && ($end == 0) || ($end > $now)) { + unless ($coursehash{'internal.courseowner'} eq $uname.':'.$udom) { + my ($delcoowners,@newcoowners,$putresult,$delresult,$coowners); + my ($result,$desc) = &auto_validate_instcode($cnum,$cdom,$instcode,$uname.':'.$udom); + if ($result eq 'valid') { + if ($coursehash{'internal.co-owners'}) { + foreach my $coowner (split(',',$coursehash{'internal.co-owners'})) { + push(@newcoowners,$coowner); + } + unless (grep(/^\Q$uname\E:\Q$udom\E$/,@newcoowners)) { + push(@newcoowners,$uname.':'.$udom); + } + @newcoowners = sort(@newcoowners); + } else { + push(@newcoowners,$uname.':'.$udom); + } + } else { + if ($coursehash{'internal.co-owners'}) { + foreach my $coowner (split(',',$coursehash{'internal.co-owners'})) { + unless ($coowner eq $uname.':'.$udom) { + push(@newcoowners,$coowner); + } + } + unless (@newcoowners > 0) { + $delcoowners = 1; + $coowners = ''; + } + } + } + if (@newcoowners || $delcoowners) { + &store_coowners($cdom,$cnum,$coursehash{'home'}, + $delcoowners,@newcoowners); + } + } + } + } + } + } +} + +sub store_coowners { + my ($cdom,$cnum,$chome,$delcoowners,@newcoowners) = @_; + my $cid = $cdom.'_'.$cnum; + my ($coowners,$delresult,$putresult); + if (@newcoowners) { + $coowners = join(',',@newcoowners); + my %coownershash = ( + 'internal.co-owners' => $coowners, + ); + $putresult = &put('environment',\%coownershash,$cdom,$cnum); + if ($putresult eq 'ok') { + if ($env{'course.'.$cid.'.num'} eq $cnum) { + &appenv({'course.'.$cid.'.internal.co-owners' => $coowners}); + } + } + } + if ($delcoowners) { + $delresult = &Apache::lonnet::del('environment',['internal.co-owners'],$cdom,$cnum); + if ($delresult eq 'ok') { + if ($env{'course.'.$cid.'.internal.co-owners'}) { + &Apache::lonnet::delenv('course.'.$cid.'.internal.co-owners'); + } + } + } + if (($putresult eq 'ok') || ($delresult eq 'ok')) { + my %crsinfo = + &Apache::lonnet::courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.'); + if (ref($crsinfo{$cid}) eq 'HASH') { + $crsinfo{$cid}{'co-owners'} = \@newcoowners; + my $cidput = &Apache::lonnet::courseidput($cdom,\%crsinfo,$chome,'notime'); + } + } +} + # -------------------------------------------------- Modify user authentication # Overrides without validation @@ -6195,17 +7265,27 @@ sub modifyuser { my ($udom, $uname, $uid, $umode, $upass, $first, $middle, $last, $gene, - $forceid, $desiredhome, $email, $inststatus)=@_; + $forceid, $desiredhome, $email, $inststatus, $candelete)=@_; $udom= &LONCAPA::clean_domain($udom); $uname=&LONCAPA::clean_username($uname); + my $showcandelete = 'none'; + if (ref($candelete) eq 'ARRAY') { + if (@{$candelete} > 0) { + $showcandelete = join(', ',@{$candelete}); + } + } &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '. $umode.', '.$first.', '.$middle.', '. - $last.', '.$gene.'(forceid: '.$forceid.')'. + $last.', '.$gene.'(forceid: '.$forceid.'; candelete: '.$showcandelete.')'. (defined($desiredhome) ? ' desiredhome = '.$desiredhome : ' desiredhome not specified'). ' by '.$env{'user.name'}.' at '.$env{'user.domain'}. ' in domain '.$env{'request.role.domain'}); my $uhome=&homeserver($uname,$udom,'true'); + my $newuser; + if ($uhome eq 'no_host') { + $newuser = 1; + } # ----------------------------------------------------------------- Create User if (($uhome eq 'no_host') && (($umode && $upass) || ($umode eq 'localauth'))) { @@ -6258,16 +7338,41 @@ sub modifyuser { ['firstname','middlename','lastname','generation','id', 'permanentemail','inststatus'], $udom,$uname); - my %names; + my (%names,%oldnames); if ($tmp[0] =~ m/^error:.*/) { %names=(); } else { %names = @tmp; + %oldnames = %names; } # -# Make sure to not trash student environment if instructor does not bother -# to supply name and email information -# +# If name, email and/or uid are blank (e.g., because an uploaded file +# of users did not contain them), do not overwrite existing values +# unless field is in $candelete array ref. +# + + my @fields = ('firstname','middlename','lastname','generation', + 'permanentemail','id'); + my %newvalues; + if (ref($candelete) eq 'ARRAY') { + foreach my $field (@fields) { + if (grep(/^\Q$field\E$/,@{$candelete})) { + if ($field eq 'firstname') { + $names{$field} = $first; + } elsif ($field eq 'middlename') { + $names{$field} = $middle; + } elsif ($field eq 'lastname') { + $names{$field} = $last; + } elsif ($field eq 'generation') { + $names{$field} = $gene; + } elsif ($field eq 'permanentemail') { + $names{$field} = $email; + } elsif ($field eq 'id') { + $names{$field} = $uid; + } + } + } + } if ($first) { $names{'firstname'} = $first; } if (defined($middle)) { $names{'middlename'} = $middle; } if ($last) { $names{'lastname'} = $last; } @@ -6292,18 +7397,40 @@ sub modifyuser { } } } - my $reply = &put('environment', \%names, $udom,$uname); - if ($reply ne 'ok') { return 'error: '.$reply; } - my $sqlresult = &update_allusers_table($uname,$udom,\%names); - &devalidate_cache_new('namescache',$uname.':'.$udom); - my $logmsg = 'Success modifying user '.$udom.', '.$uname.', '.$uid.', '. + my $logmsg = $udom.', '.$uname.', '.$uid.', '. $umode.', '.$first.', '.$middle.', '. - $last.', '.$gene.', '.$email.', '.$inststatus; + $last.', '.$gene.', '.$email.', '.$inststatus; if ($env{'user.name'} ne '' && $env{'user.domain'}) { $logmsg .= ' by '.$env{'user.name'}.' at '.$env{'user.domain'}; } else { $logmsg .= ' during self creation'; } + my $changed; + if ($newuser) { + $changed = 1; + } else { + foreach my $field (@fields) { + if ($names{$field} ne $oldnames{$field}) { + $changed = 1; + last; + } + } + } + unless ($changed) { + $logmsg = 'No changes in user information needed for: '.$logmsg; + &logthis($logmsg); + return 'ok'; + } + my $reply = &put('environment', \%names, $udom,$uname); + if ($reply ne 'ok') { + return 'error: '.$reply; + } + if ($names{'permanentemail'} ne $oldnames{'permanentemail'}) { + &Apache::lonnet::devalidate_cache_new('emailscache',$uname.':'.$udom); + } + my $sqlresult = &update_allusers_table($uname,$udom,\%names); + &devalidate_cache_new('namescache',$uname.':'.$udom); + $logmsg = 'Success modifying user '.$logmsg; &logthis($logmsg); return 'ok'; } @@ -6439,14 +7566,39 @@ sub createcourse { $course_owner,$crstype,$cnum,$context,$category)=@_; $url=&declutter($url); my $cid=''; - unless (&allowed('ccc',$udom)) { - if ($context eq 'requestcourses') { - unless (&usertools_access($course_owner,$udom,$category,undef,$context)) { - return 'refused'; + if ($context eq 'requestcourses') { + my $can_create = 0; + my ($ownername,$ownerdom) = split(':',$course_owner); + if ($udom eq $ownerdom) { + if (&usertools_access($ownername,$ownerdom,$category,undef, + $context)) { + $can_create = 1; + } + } else { + my %userenv = &userenvironment($ownerdom,$ownername,'reqcrsotherdom.'. + $category); + if ($userenv{'reqcrsotherdom.'.$category} ne '') { + my @curr = split(',',$userenv{'reqcrsotherdom.'.$category}); + if (@curr > 0) { + my @options = qw(approval validate autolimit); + my $optregex = join('|',@options); + if (grep(/^\Q$udom\E:($optregex)(=?\d*)$/,@curr)) { + $can_create = 1; + } + } + } + } + if ($can_create) { + unless ($ownername eq $env{'user.name'} && $ownerdom eq $env{'user.domain'}) { + unless (&allowed('ccc',$udom)) { + return 'refused'; + } } } else { return 'refused'; } + } elsif (!&allowed('ccc',$udom)) { + return 'refused'; } # --------------------------------------------------------------- Get Unique ID my $uname; @@ -6455,16 +7607,24 @@ sub createcourse { if (($chome eq '') || ($chome eq 'no_host')) { $uname = $cnum; } else { - $uname = &generate_coursenum($udom); + $uname = &generate_coursenum($udom,$crstype); } } else { - $uname = &generate_coursenum($udom); + $uname = &generate_coursenum($udom,$crstype); } return $uname if ($uname =~ /^error/); # -------------------------------------------------- Check supplied server name - $course_server = $env{'user.homeserver'} if (! defined($course_server)); - if (! &is_library($course_server)) { - return 'error:bad server name '.$course_server; + if (!defined($course_server)) { + if (defined(&domain($udom,'primary'))) { + $course_server = &domain($udom,'primary'); + } else { + $course_server = $env{'user.home'}; + } + } + my %host_servers = + &Apache::lonnet::get_servers($udom,'library'); + unless ($host_servers{$course_server}) { + return 'error: invalid home server for course: '.$course_server; } # ------------------------------------------------------------- Make the course my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':none::', @@ -6476,12 +7636,17 @@ sub createcourse { } # ----------------------------------------------------------------- Course made # log existence + my $now = time; my $newcourse = { $udom.'_'.$uname => { description => $description, inst_code => $inst_code, owner => $course_owner, type => $crstype, + creator => $env{'user.name'}.':'. + $env{'user.domain'}, + created => $now, + context => $context, }, }; &courseidput($udom,$newcourse,$uhome,'notime'); @@ -6506,24 +7671,40 @@ ENDINITMAP } # ----------------------------------------------------------- Write preferences &writecoursepref($udom.'_'.$uname, - ('description' => $description, - 'url' => $topurl)); + ('description' => $description, + 'url' => $topurl, + 'internal.creator' => $env{'user.name'}.':'. + $env{'user.domain'}, + 'internal.created' => $now, + 'internal.creationcontext' => $context) + ); return '/'.$udom.'/'.$uname; } # ------------------------------------------------------------------- Create ID sub generate_coursenum { - my ($udom) = @_; + my ($udom,$crstype) = @_; my $domdesc = &domain($udom); return 'error: invalid domain' if ($domdesc eq ''); - my $uname=int(1+rand(9)). + my $first; + if ($crstype eq 'Community') { + $first = '0'; + } else { + $first = int(1+rand(9)); + } + my $uname=$first. ('a'..'z','A'..'Z','0'..'9')[int(rand(62))]. substr($$.time,0,5).unpack("H8",pack("I32",time)). unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'}; # ----------------------------------------------- Make sure that does not exist my $uhome=&homeserver($uname,$udom,'true'); unless (($uhome eq '') || ($uhome eq 'no_host')) { - $uname=int(1+rand(9)). + if ($crstype eq 'Community') { + $first = '0'; + } else { + $first = int(1+rand(9)); + } + $uname=$first. ('a'..'z','A'..'Z','0'..'9')[int(rand(62))]. substr($$.time,0,5).unpack("H8",pack("I32",time)). unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'}; @@ -6566,8 +7747,8 @@ sub store_userdata { $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&'; } $namevalue=~s/\&$//; - $result = &reply("store:$env{'user.domain'}:$env{'user.name'}:". - "$namespace:$datakey:$namevalue",$uhome); + $result = &reply("store:$udom:$uname:$namespace:$datakey:". + $namevalue,$uhome); } } else { $result = 'error: data to store was not a hash reference'; @@ -6620,10 +7801,10 @@ sub diskusage { } sub is_locked { - my ($file_name, $domain, $user) = @_; + my ($file_name, $domain, $user, $which) = @_; my @check; my $is_locked; - push @check, $file_name; + push (@check,$file_name); my %locked = &get('file_permissions',\@check, $env{'user.domain'},$env{'user.name'}); my ($tmp)=keys(%locked); @@ -6632,14 +7813,19 @@ sub is_locked { if (ref($locked{$file_name}) eq 'ARRAY') { $is_locked = 'false'; foreach my $entry (@{$locked{$file_name}}) { - if (ref($entry) eq 'ARRAY') { + if (ref($entry) eq 'ARRAY') { $is_locked = 'true'; - last; + if (ref($which) eq 'ARRAY') { + push(@{$which},$entry); + } else { + last; + } } } } else { $is_locked = 'false'; } + return $is_locked; } sub declutter_portfile { @@ -6683,7 +7869,7 @@ sub save_selected_files { sub clear_selected_files { my ($user) = @_; my $filename = $user."savedfiles"; - open (OUT, '>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open (OUT, '>'.LONCAPA::tempdir().$filename); print (OUT undef); close (OUT); return ("ok"); @@ -6693,7 +7879,7 @@ sub files_in_path { my ($user, $path) = @_; my $filename = $user."savedfiles"; my %return_files; - open (IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open (IN, '<'.LONCAPA::tempdir().$filename); while (my $line_in = ) { chomp ($line_in); my @paths_and_file = split (m!/!, $line_in); @@ -6715,7 +7901,7 @@ sub files_not_in_path { my $filename = $user."savedfiles"; my @return_files; my $path_part; - open(IN, '<'.$Apache::lonnet::perlvar{'lonDaemons'}.'/tmp/'.$filename); + open(IN, '<'.LONCAPA::.$filename); while (my $line = ) { #ok, I know it's clunky, but I want it to work my @paths_and_file = split(m|/|, $line); @@ -7063,26 +8249,33 @@ sub dirlist { if($udom) { if($uname) { + my $uhome = &homeserver($uname,$udom); + if ($uhome eq 'no_host') { + return ([],'no_host'); + } $listing = &reply('ls3:'.&escape('/'.$uri).':'.$getpropath.':' .$getuserdir.':'.&escape($dirRoot) - .':'.&escape($uname).':'.&escape($udom), - &homeserver($uname,$udom)); + .':'.&escape($uname).':'.&escape($udom),$uhome); if ($listing eq 'unknown_cmd') { - $listing = &reply('ls2:'.$dirRoot.'/'.$uri, - &homeserver($uname,$udom)); + $listing = &reply('ls2:'.$dirRoot.'/'.$uri,$uhome); } else { @listing_results = map { &unescape($_); } split(/:/,$listing); } if ($listing eq 'unknown_cmd') { - $listing = &reply('ls:'.$dirRoot.'/'.$uri, - &homeserver($uname,$udom)); + $listing = &reply('ls:'.$dirRoot.'/'.$uri,$uhome); @listing_results = split(/:/,$listing); } else { @listing_results = map { &unescape($_); } split(/:/,$listing); } - return @listing_results; + if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || + ($listing eq 'rejected') || ($listing eq 'refused') || + ($listing eq 'no_such_dir') || ($listing eq 'empty')) { + return ([],$listing); + } else { + return (\@listing_results); + } } elsif(!$alternateRoot) { - my %allusers; + my (%allusers,%listerror); my %servers = &get_servers($udom,'library'); foreach my $tryserver (keys(%servers)) { $listing = &reply('ls3:'.&escape("/res/$udom").':::::'. @@ -7101,32 +8294,31 @@ sub dirlist { @listing_results = map { &unescape($_); } split(/:/,$listing); } - if ($listing_results[0] ne 'no_such_dir' && - $listing_results[0] ne 'empty' && - $listing_results[0] ne 'con_lost') { + if (($listing eq 'no_such_host') || ($listing eq 'con_lost') || + ($listing eq 'rejected') || ($listing eq 'refused') || + ($listing eq 'no_such_dir') || ($listing eq 'empty')) { + $listerror{$tryserver} = $listing; + } else { foreach my $line (@listing_results) { my ($entry) = split(/&/,$line,2); $allusers{$entry} = 1; } } } - my $alluserstr=''; + my @alluserslist=(); foreach my $user (sort(keys(%allusers))) { - $alluserstr.=$user.'&user:'; + push(@alluserslist,$user.'&user'); } - $alluserstr=~s/:$//; - return split(/:/,$alluserstr); + return (\@alluserslist); } else { - return ('missing user name'); + return ([],'missing username'); } } elsif(!defined($getpropath)) { - my @all_domains = sort(&all_domains()); - foreach my $domain (@all_domains) { - $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain'; - } - return @all_domains; + my $path = $perlvar{'lonDocRoot'}.'/res/'; + my @all_domains = map { $path.$_.'/&domain'; } (sort(&all_domains())); + return (\@all_domains); } else { - return ('missing domain'); + return ([],'missing domain'); } } @@ -7139,11 +8331,13 @@ sub GetFileTimestamp { my ($studentDomain,$studentName,$filename,$getuserdir)=@_; $studentDomain = &LONCAPA::clean_domain($studentDomain); $studentName = &LONCAPA::clean_username($studentName); - my ($fileStat) = - &Apache::lonnet::dirlist($filename,$studentDomain,$studentName, - undef,$getuserdir); - my @stats = split('&', $fileStat); - if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { + my ($fileref,$error) = &dirlist($filename,$studentDomain,$studentName, + undef,$getuserdir); + if (($error eq 'empty') || ($error eq 'no_such_dir')) { + return -1; + } + if (ref($fileref) eq 'ARRAY') { + my @stats = split('&',$fileref->[0]); # @stats contains first the filename, then the stat output return $stats[10]; # so this is 10 instead of 9. } else { @@ -7175,12 +8369,15 @@ sub stat_file { if ($file =~ /^userfiles\//) { $getpropath = 1; } - my ($result) = &dirlist($file,$udom,$uname,$getpropath); - my @stats = split('&', $result); - - if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') { - shift(@stats); #filename is first - return @stats; + my ($listref,$error) = &dirlist($file,$udom,$uname,$getpropath); + if (($error eq 'empty') || ($error eq 'no_such_dir')) { + return (); + } else { + if (ref($listref) eq 'ARRAY') { + my @stats = split('&',$listref->[0]); + shift(@stats); #filename is first + return @stats; + } } return (); } @@ -7782,6 +8979,7 @@ sub add_prefix_and_part { # ---------------------------------------------------------------- Get metadata my %metaentry; +my %importedpartids; sub metadata { my ($uri,$what,$liburi,$prefix,$depthcount)=@_; $uri=&declutter($uri); @@ -7789,7 +8987,7 @@ sub metadata { if (($uri eq '') || (($uri =~ m|^/*adm/|) && ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) || - ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) ) { + ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) { return undef; } if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) @@ -7808,6 +9006,10 @@ sub metadata { if (defined($cached)) { return $result->{':'.$what}; } } { +# Imported parts would go here + my %importedids=(); + my @origfileimportpartids=(); + my $importedparts=0; # # Is this a recursive call for a library? # @@ -7831,7 +9033,8 @@ sub metadata { &Apache::lonnet::ssi_body($which, ('grade_target' => 'meta')); $cachetime = 1; # only want this cached in the child not long term - } elsif ($uri !~ m -^(editupload)/-) { + } elsif (($uri !~ m -^(editupload)/-) && + ($uri !~ m{^/*uploaded/$match_domain/$match_courseid/docs/})) { my $file=&filelocation('',&clutter($filename)); #push(@{$metaentry{$uri.'.file'}},$file); $metastring=&getfile($file); @@ -7891,27 +9094,55 @@ sub metadata { # This is not a package - some other kind of start tag # my $entry=$token->[1]; - my $unikey; - if ($entry eq 'import') { - $unikey=''; - } else { - $unikey=$entry; - } - $unikey.=&add_prefix_and_part($prefix,$token->[2]->{'part'}); - - if (defined($token->[2]->{'id'})) { - $unikey.='_'.$token->[2]->{'id'}; - } + my $unikey=''; if ($entry eq 'import') { # # Importing a library here # + my $location=$parser->get_text('/import'); + my $dir=$filename; + $dir=~s|[^/]*$||; + $location=&filelocation($dir,$location); + + my $importmode=$token->[2]->{'importmode'}; + if ($importmode eq 'problem') { +# Import as problem/response + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'}); + } elsif ($importmode eq 'part') { +# Import as part(s) + $importedparts=1; +# We need to get the original file and the imported file to get the part order correct +# Good news: we do not need to worry about nested libraries, since parts cannot be nested +# Load and inspect original file + if ($#origfileimportpartids<0) { + undef(%importedpartids); + my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri); + my $origfile=&getfile($origfilelocation); + @origfileimportpartids=($origfile=~/<(part|import)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs); + } + +# Load and inspect imported file + my $impfile=&getfile($location); + my @impfilepartids=($impfile=~/]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs); + if ($#impfilepartids>=0) { +# This problem had parts + $importedpartids{$token->[2]->{'id'}}=join(',',@impfilepartids); + } else { +# Importing by turning a single problem into a problem part +# It gets the import-tags ID as part-ID + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'id'}); + $importedpartids{$token->[2]->{'id'}}=$token->[2]->{'id'}; + } + } else { +# Normal import + $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'}); + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } + } + if ($depthcount<20) { - my $location=$parser->get_text('/import'); - my $dir=$filename; - $dir=~s|[^/]*$||; - $location=&filelocation($dir,$location); my $metadata = &metadata($uri,'keys', $location,$unikey, $depthcount+1); @@ -7919,9 +9150,16 @@ sub metadata { $metaentry{':'.$meta}=$metaentry{':'.$meta}; $metathesekeys{$meta}=1; } - } - } else { + } + } else { +# +# Not importing, some other kind of non-package, non-library start tag +# + $unikey=$entry.&add_prefix_and_part($prefix,$token->[2]->{'part'}); + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } if (defined($token->[2]->{'name'})) { $unikey.='_'.$token->[2]->{'name'}; } @@ -7995,6 +9233,22 @@ sub metadata { grep { ! $seen{$_} ++ } (split(',',$metaentry{':packages'})); $metaentry{':packages'} = join(',',@uniq_packages); + if ($importedparts) { +# We had imported parts and need to rebuild partorder + $metaentry{':partorder'}=''; + $metathesekeys{'partorder'}=1; + for (my $index=0;$index<$#origfileimportpartids;$index+=2) { + if ($origfileimportpartids[$index] eq 'part') { +# original part, part of the problem + $metaentry{':partorder'}.=','.$origfileimportpartids[$index+1]; + } else { +# we have imported parts at this position + $metaentry{':partorder'}.=','.$importedpartids{$origfileimportpartids[$index+1]}; + } + } + $metaentry{':partorder'}=~s/^\,//; + } + $metaentry{':keys'} = join(',',keys(%metathesekeys)); &metadata_generate_part0(\%metathesekeys,\%metaentry,$uri); $metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys); @@ -8175,19 +9429,27 @@ sub symbverify { if (tie(%bighash,'GDBM_File',$env{'request.course.fn'}.'.db', &GDBM_READER(),0640)) { + if (($thisurl =~ m{^/adm/wrapper/ext/}) || ($thisurl =~ m{^ext/})) { + $thisurl =~ s/\?.+$//; + } my $ids=$bighash{'ids_'.&clutter($thisurl)}; - unless ($ids) { - $ids=$bighash{'ids_/'.$thisurl}; + unless ($ids) { + my $idkey = 'ids_'.($thisurl =~ m{^/}? '' : '/').$thisurl; + $ids=$bighash{$idkey}; } if ($ids) { # ------------------------------------------------------------------- Has ID(s) foreach my $id (split(/\,/,$ids)) { my ($mapid,$resid)=split(/\./,$id); + if ($thisfn =~ m{^/adm/wrapper/ext/}) { + $symb =~ s/\?.+$//; + } if ( &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn) eq $symb) { if (($env{'request.role.adv'}) || - $bighash{'encrypted_'.$id} eq $env{'request.enc'}) { + ($bighash{'encrypted_'.$id} eq $env{'request.enc'}) || + ($thisurl eq '/adm/navmaps')) { $okay=1; } } @@ -8462,9 +9724,26 @@ sub getCODE { } return undef; } - +# +# Determines the random seed for a specific context: +# +# parameters: +# symb - in course context the symb for the seed. +# course_id - The course id of the form domain_coursenum. +# domain - Domain for the user. +# course - Course for the user. +# cenv - environment of the course. +# +# NOTE: +# All parameters are picked out of the environment if missing +# or not defined. +# If a symb cannot be determined the current time is used instead. +# +# For a given well defined symb, courside, domain, username, +# and course environment, the seed is reproducible. +# sub rndseed { - my ($symb,$courseid,$domain,$username)=@_; + my ($symb,$courseid,$domain,$username, $cenv)=@_; my ($wsymb,$wcourseid,$wdomain,$wusername)=&whichuser(); if (!defined($symb)) { unless ($symb=$wsymb) { return time; } @@ -8472,9 +9751,16 @@ sub rndseed { if (!$courseid) { $courseid=$wcourseid; } if (!$domain) { $domain=$wdomain; } if (!$username) { $username=$wusername } - my $which=&get_rand_alg(); + + my $which; + if (defined($cenv->{'rndseed'})) { + $which = $cenv->{'rndseed'}; + } else { + $which =&get_rand_alg($courseid); + } if (defined(&getCODE())) { + if ($which eq '64bit5') { return &rndseed_CODE_64bit5($symb,$courseid,$domain,$username); } elsif ($which eq '64bit4') { @@ -8944,7 +10230,7 @@ sub filelocation { my @ids=¤t_machine_ids(); foreach my $id (@ids) { if ($id eq $home) { $is_me=1; } } if ($is_me) { - $location=&propath($udom,$uname).'/userfiles/'.$filename; + $location=propath($udom,$uname).'/userfiles/'.$filename; } else { $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'. $udom.'/'.$uname.'/'.$filename; @@ -9059,7 +10345,9 @@ sub declutter { $thisfn=~s|^adm/wrapper/||; $thisfn=~s|^adm/coursedocs/showdoc/||; $thisfn=~s/^res\///; - $thisfn=~s/\?.+$//; + unless (($thisfn =~ /^ext/) || ($thisfn =~ /\.(page|sequence)___\d+___ext/)) { + $thisfn=~s/\?.+$//; + } return $thisfn; } @@ -9071,8 +10359,8 @@ sub clutter { || $thisfn =~ m{^/adm/(includes|pages)} ) { $thisfn='/res'.$thisfn; } - if ($thisfn !~m|/adm|) { - if ($thisfn =~ m|/ext/|) { + if ($thisfn !~m|^/adm|) { + if ($thisfn =~ m|^/ext/|) { $thisfn='/adm/wrapper'.$thisfn; } else { my ($ext) = ($thisfn =~ /\.(\w+)$/); @@ -9180,6 +10468,7 @@ sub get_dns { while (%alldns) { my ($dns) = keys(%alldns); my $ua=new LWP::UserAgent; + $ua->timeout(30); my $request=new HTTP::Request('GET',"$alldns{$dns}://$dns$url"); my $response=$ua->request($request); delete($alldns{$dns}); @@ -9263,14 +10552,21 @@ sub get_dns { my %libserv; my $loaded; my %name_to_host; + my %internetdom; + my %LC_dns_serv; sub parse_hosts_tab { my ($file) = @_; foreach my $configline (@$file) { next if ($configline =~ /^(\#|\s*$ )/x); - next if ($configline =~ /^\^/); - chomp($configline); - my ($id,$domain,$role,$name,$protocol)=split(/:/,$configline); + chomp($configline); + if ($configline =~ /^\^/) { + if ($configline =~ /^\^([\w.\-]+)/) { + $LC_dns_serv{$1} = 1; + } + next; + } + my ($id,$domain,$role,$name,$protocol,$intdom)=split(/:/,$configline); $name=~s/\s//g; if ($id && $domain && $role && $name) { $hostname{$id}=$name; @@ -9286,6 +10582,9 @@ sub get_dns { } else { $protocol{$id} = 'http'; } + if (defined($intdom)) { + $internetdom{$id} = $intdom; + } } } } @@ -9347,6 +10646,12 @@ sub get_dns { return %libserv; } + sub unique_library { + #2x reverse removes all hostnames that appear more than once + my %unique = reverse &all_library(); + return reverse %unique; + } + sub get_servers { &load_hosts_tab() if (!$loaded); @@ -9370,6 +10675,11 @@ sub get_dns { return %result; } + sub get_unique_servers { + my %unique = reverse &get_servers(@_); + return reverse %unique; + } + sub host_domain { &load_hosts_tab() if (!$loaded); @@ -9384,6 +10694,21 @@ sub get_dns { my @uniq = grep(!$seen{$_}++, values(%hostdom)); return @uniq; } + + sub internet_dom { + &load_hosts_tab() if (!$loaded); + + my ($lonid) = @_; + return $internetdom{$lonid}; + } + + sub is_LC_dns { + &load_hosts_tab() if (!$loaded); + + my ($hostname) = @_; + return exists($LC_dns_serv{$hostname}); + } + } { @@ -9501,6 +10826,40 @@ sub get_dns { return undef; } + sub get_internet_names { + my ($lonid) = @_; + return if ($lonid eq ''); + my ($idnref,$cached)= + &Apache::lonnet::is_cached_new('internetnames',$lonid); + if ($cached) { + return $idnref; + } + my $ip = &get_host_ip($lonid); + my @hosts = &get_hosts_from_ip($ip); + my %iphost = &get_iphost(); + my (@idns,%seen); + foreach my $id (@hosts) { + my $dom = &host_domain($id); + my $prim_id = &domain($dom,'primary'); + my $prim_ip = &get_host_ip($prim_id); + next if ($seen{$prim_ip}); + if (ref($iphost{$prim_ip}) eq 'ARRAY') { + foreach my $id (@{$iphost{$prim_ip}}) { + my $intdom = &internet_dom($id); + unless (grep(/^\Q$intdom\E$/,@idns)) { + push(@idns,$intdom); + } + } + } + $seen{$prim_ip} = 1; + } + return &Apache::lonnet::do_cache_new('internetnames',$lonid,\@idns,12*60*60); + } + +} + +sub all_loncaparevs { + return qw(1.1 1.2 1.3 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10); } BEGIN { @@ -9578,9 +10937,56 @@ BEGIN { close($config); } +# ---------------------------------------------------------- Read loncaparev table +{ + if (-e "$perlvar{'lonTabDir'}/loncaparevs.tab") { + if (open(my $config,"<$perlvar{'lonTabDir'}/loncaparevs.tab")) { + while (my $configline=<$config>) { + chomp($configline); + my ($hostid,$loncaparev)=split(/:/,$configline); + $loncaparevs{$hostid}=$loncaparev; + } + close($config); + } + } +} + +# ---------------------------------------------------------- Read serverhostID table +{ + if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") { + if (open(my $config,"<$perlvar{'lonTabDir'}/serverhomeIDs.tab")) { + while (my $configline=<$config>) { + chomp($configline); + my ($name,$id)=split(/:/,$configline); + $serverhomeIDs{$name}=$id; + } + close($config); + } + } +} + +{ + my $file = $Apache::lonnet::perlvar{'lonTabDir'}.'/releaseslist.xml'; + if (-e $file) { + my $parser = HTML::LCParser->new($file); + while (my $token = $parser->get_token()) { + if ($token->[0] eq 'S') { + my $item = $token->[1]; + my $name = $token->[2]{'name'}; + my $value = $token->[2]{'value'}; + if ($item ne '' && $name ne '' && $value ne '') { + my $release = $parser->get_text(); + $release =~ s/(^\s*|\s*$ )//gx; + $needsrelease{$item.':'.$name.':'.$value} = $release; + } + } + } + } +} + # ------------- set up temporary directory { - $tmpdir = $perlvar{'lonDaemons'}.'/tmp/'; + $tmpdir = LONCAPA::tempdir(); } @@ -9808,9 +11214,14 @@ authentication scheme =item * X -B: try to +B: try to authenticate user from domain's lib servers (first use the current one). C<$upass> should be the users password. +$checkdefauth is optional (value is 1 if a check should be made to + authenticate user using default authentication method, and allow + account creation if username does not have account in the domain). +$clientcancheckhost is optional (value is 1 if checking whether the + server can host will occur on the client side in lonauth.pm). =item * X @@ -9932,9 +11343,16 @@ modifyuserauth($udom,$uname,$umode,$upas =item * -modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene, - $forceid,$desiredhome,$email,$inststatus) : -modify user +modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last, $gene, + $forceid,$desiredhome,$email,$inststatus,$candelete) : + +will update user information (firstname,middlename,lastname,generation, +permanentemail), and if forceid is true, student/employee ID also. +A user's institutional affiliation(s) can also be updated. +User information fields will not be overwritten with empty entries +unless the field is included in the $candelete array reference. +This array is included when a single user is modified via "Manage Users", +or when Autoupdate.pl is run by cron in a domain. =item * @@ -10062,11 +11480,32 @@ revokecustomrole($udom,$uname,$url,$role =item * -coursedescription($courseid) : returns a hash of information about the +coursedescription($courseid,$options) : returns a hash of information about the specified course id, including all environment settings for the course, the description of the course will be in the hash under the key 'description' +$options is an optional parameter that if supplied is a hash reference that controls +what how this function works. It has the following key/values: + +=over 4 + +=item freshen_cache + +If defined, and the environment cache for the course is valid, it is +returned in the returned hash. + +=item one_time + +If defined, the last cache time is set to _now_ + +=item user + +If defined, the supplied username is used instead of the current user. + + +=back + =item * resdata($name,$domain,$type,@which) : request for current parameter @@ -10099,7 +11538,7 @@ createcourse($udom,$description,$url,$co =item * -generate_coursenum($udom) : get a unique (unused) course number in domain $udom +generate_coursenum($udom,$crstype) : get a unique (unused) course number in domain $udom for course type $crstype (Course or Community). =back @@ -10397,7 +11836,82 @@ or lonTabs/domain.tab. =item * -dirlist($uri) : return directory list based on URI +dirlist() : return directory list based on URI (first arg). + +Inputs: 1 required, 5 optional. + +=over + +=item +$uri - path to file in filesystem (starts: /res or /userfiles/). Required. + +=item +$userdomain - domain of user/course to be listed. Extracted from $uri if absent. + +=item +$username - username of user/course to be listed. Extracted from $uri if absent. + +=item +$getpropath - boolean: 1 if prepend path using &propath(). + +=item +$getuserdir - boolean: 1 if prepend path for "userfiles". + +=item +$alternateRoot - path to prepend in place of path from $uri. + +=back + +Returns: Array of up to two items. + +=over + +a reference to an array of files/subdirectories + +=over + +Each element in the array of files/subdirectories is a & separated list of +item name and the result of running stat on the item. If dirlist was requested +for a file instead of a directory, the item name will be ''. For a directory +listing, if the item is a metadata file, the element will end &N&M +(where N amd M are either 0 or 1, corresponding to obsolete set (1), or +default copyright set (1). + +=back + +a scalar containing error condition (if encountered). + +=over + +=item +no_host (no homeserver identified for $username:$domain). + +=item +no_such_host (server contacted for listing not identified as valid host). + +=item +con_lost (connection to remote server failed). + +=item +refused (invalid $username:$domain received on lond side). + +=item +no_such_dir (directory at specified path on lond side does not exist). + +=item +empty (directory at specified path on lond side is empty). + +=over + +This is currently not encountered because the &ls3, &ls2, +&ls (_handler) routines on the lond side do not filter out +. and .. from a directory listing. + +=back + +=back + +=back =item * @@ -10459,11 +11973,12 @@ splitting on '&', supports elements that =head2 Logging Routines -=over 4 These routines allow one to make log messages in the lonnet.log and lonnet.perm logfiles. +=over 4 + =item * logtouch() : make sure the logfile, lonnet.log, exists @@ -10479,6 +11994,7 @@ logperm() : append a permanent message t file never gets deleted by any automated portion of the system, only messages of critical importance should go in here. + =back =head2 General File Helper Routines @@ -10552,8 +12068,10 @@ userfileupload(): main rotine for puttin filename, and the contents of the file to create/modifed exist the filename is in $env{'form.'.$formname.'.filename'} and the contents of the file is located in $env{'form.'.$formname} - coursedoc - if true, store the file in the course of the active role - of the current user + context - if coursedoc, store the file in the course of the active role + of the current user; + if 'existingfile': store in 'overwrites' in /home/httpd/perl/tmp + if 'canceloverwrite': delete file in tmp/overwrites directory subdir - required - subdirectory to put the file in under ../userfiles/ if undefined, it will be placed in "unknown" @@ -10575,16 +12093,29 @@ returns: the new clean filename =item * -finishuserfileupload(): routine that creaes and sends the file to +finishuserfileupload(): routine that creates and sends the file to userspace, probably shouldn't be called directly docuname: username or courseid of destination for the file docudom: domain of user/course of destination for the file formname: same as for userfileupload() - fname: filename (inculding subdirectories) for the file + fname: filename (including subdirectories) for the file + parser: if 'parse', will parse (html) file to extract references to objects, links etc. + allfiles: reference to hash used to store objects found by parser + codebase: reference to hash used for codebases of java objects found by parser + thumbwidth: width (pixels) of thumbnail to be created for uploaded image + thumbheight: height (pixels) of thumbnail to be created for uploaded image + resizewidth: width to be used to resize image using resizeImage from ImageMagick + resizeheight: height to be used to resize image using resizeImage from ImageMagick + context: if 'overwrite', will move the uploaded file from its temporary location to + userfiles to facilitate overwriting a previously uploaded file with same name. + mimetype: reference to scalar to accommodate mime type determined + from File::MMagic if $parser = parse. returns either the url of the uploaded file (/uploaded/....) if successful - and /adm/notfound.html if unsuccessful + and /adm/notfound.html if unsuccessful (or an error message if context + was 'overwrite'). + =item *