--- loncom/lond 2014/06/09 16:58:22 1.467.2.8 +++ loncom/lond 2012/04/11 06:22:04 1.489 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.467.2.8 2014/06/09 16:58:22 raeburn Exp $ +# $Id: lond,v 1.489 2012/04/11 06:22:04 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -15,6 +15,7 @@ # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # @@ -59,7 +60,7 @@ my $DEBUG = 0; # Non zero to ena my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.467.2.8 $'; #' stupid emacs +my $VERSION='$Revision: 1.489 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -91,6 +92,8 @@ my %managers; # Ip -> manager names my %perlvar; # Will have the apache conf defined perl vars. +my $dist; + # # The hash below is used for command dispatching, and is therefore keyed on the request keyword. # Each element of the hash contains a reference to an array that contains: @@ -600,8 +603,8 @@ sub InstallFile { # # ConfigFileFromSelector: converts a configuration file selector # into a configuration file pathname. -# Supports the following file selectors: -# hosts, domain, dns_hosts, dns_domain +# Supports the following file selectors: +# hosts, domain, dns_hosts, dns_domain # # # Parameters: @@ -614,12 +617,11 @@ sub ConfigFileFromSelector { my $tablefile; my $tabledir = $perlvar{'lonTabDir'}.'/'; - if (($selector eq "hosts") || ($selector eq "domain") || + if (($selector eq "hosts") || ($selector eq "domain") || ($selector eq "dns_hosts") || ($selector eq "dns_domain")) { - $tablefile = $tabledir.$selector.'.tab'; + $tablefile = $tabledir.$selector.'.tab'; } return $tablefile; - } # # PushFile: Called to do an administrative push of a file. @@ -637,7 +639,7 @@ sub ConfigFileFromSelector { # String to send to client ("ok" or "refused" if bad file). # sub PushFile { - my $request = shift; + my $request = shift; my ($command, $filename, $contents) = split(":", $request, 3); &Debug("PushFile"); @@ -646,7 +648,7 @@ sub PushFile { # hosts.tab ($filename eq host). # domain.tab ($filename eq domain). # dns_hosts.tab ($filename eq dns_host). - # dns_domain.tab ($filename eq dns_domain). + # dns_domain.tab ($filename eq dns_domain). # Construct the destination filename or reject the request. # # lonManage is supposed to ensure this, however this session could be @@ -667,44 +669,6 @@ sub PushFile { if($filename eq "host") { $contents = AdjustHostContents($contents); - } elsif ($filename eq 'dns_host' || $filename eq 'dns_domain') { - if ($contents eq '') { - &logthis(' Pushfile: unable to install ' - .$tablefile." - no data received from push. "); - return 'error: push had no data'; - } - if (&Apache::lonnet::get_host_ip($clientname)) { - my $clienthost = &Apache::lonnet::hostname($clientname); - if ($managers{$clientip} eq $clientname) { - my $clientprotocol = $Apache::lonnet::protocol{$clientname}; - $clientprotocol = 'http' if ($clientprotocol ne 'https'); - my $url = '/adm/'.$filename; - $url =~ s{_}{/}; - my $ua=new LWP::UserAgent; - $ua->timeout(60); - my $request=new HTTP::Request('GET',"$clientprotocol://$clienthost$url"); - my $response=$ua->request($request); - if ($response->is_error()) { - &logthis(' Pushfile: unable to install ' - .$tablefile." - error attempting to pull data. "); - return 'error: pull failed'; - } else { - my $result = $response->content; - chomp($result); - unless ($result eq $contents) { - &logthis(' Pushfile: unable to install ' - .$tablefile." - pushed data and pulled data differ. "); - my $pushleng = length($contents); - my $pullleng = length($result); - if ($pushleng != $pullleng) { - return "error: $pushleng vs $pullleng bytes"; - } else { - return "error: mismatch push and pull"; - } - } - } - } - } } # Install the new file: @@ -715,8 +679,8 @@ sub PushFile { .$tablefile." $! "); return "error:$!"; } else { - &logthis(' Installed new '.$tablefile - ." - transaction by: $clientname ($clientip)"); + &logthis(' Installed new '.$tablefile + ." - transaction by: $clientname ($clientip)"); my $adminmail = $perlvar{'lonAdmEMail'}; my $admindom = &Apache::lonnet::host_domain($perlvar{'lonHostID'}); if ($admindom ne '') { @@ -1679,6 +1643,74 @@ sub ls3_handler { } ®ister_handler("ls3", \&ls3_handler, 0, 1, 0); +sub read_lonnet_global { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + my $requested = &Apache::lonnet::thaw_unescape($tail); + my $result; + my %packagevars = ( + spareid => \%Apache::lonnet::spareid, + perlvar => \%Apache::lonnet::perlvar, + ); + my %limit_to = ( + perlvar => { + lonOtherAuthen => 1, + lonBalancer => 1, + lonVersion => 1, + lonSysEMail => 1, + lonHostID => 1, + lonRole => 1, + lonDefDomain => 1, + lonLoadLim => 1, + lonUserLoadLim => 1, + } + ); + if (ref($requested) eq 'HASH') { + foreach my $what (keys(%{$requested})) { + my $response; + my $items = {}; + if (exists($packagevars{$what})) { + if (ref($limit_to{$what}) eq 'HASH') { + foreach my $varname (keys(%{$packagevars{$what}})) { + if ($limit_to{$what}{$varname}) { + $items->{$varname} = $packagevars{$what}{$varname}; + } + } + } else { + $items = $packagevars{$what}; + } + if ($what eq 'perlvar') { + if (!exists($packagevars{$what}{'lonBalancer'})) { + if ($dist =~ /^(centos|rhes|fedora|scientific)/) { + my $othervarref=LONCAPA::Configuration::read_conf('httpd.conf'); + if (ref($othervarref) eq 'HASH') { + $items->{'lonBalancer'} = $othervarref->{'lonBalancer'}; + } + } + } + } + $response = &Apache::lonnet::freeze_escape($items); + } + $result .= &escape($what).'='.$response.'&'; + } + } + $result =~ s/\&$//; + &Reply($client,\$result,$userinput); + return 1; +} +®ister_handler("readlonnetglobal", \&read_lonnet_global, 0, 1, 0); + +sub server_devalidatecache_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + my ($name,$id) = map { &unescape($_); } split(/:/,$tail); + &Apache::lonnet::devalidate_cache_new($name,$id); + my $result = 'ok'; + &Reply($client,\$result,$userinput); + return 1; +} +®ister_handler("devalidatecache", \&server_devalidatecache_handler, 0, 1, 0); + sub server_timezone_handler { my ($cmd,$tail,$client) = @_; my $userinput = "$cmd:$tail"; @@ -2041,7 +2073,7 @@ sub add_user_handler { ."makeuser"; } unless ($fperror) { - my $result=&make_passwd_file($uname, $umode,$npass, $passfilename); + my $result=&make_passwd_file($uname,$udom,$umode,$npass, $passfilename); &Reply($client,\$result, $userinput); #BUGBUG - could be fail } else { &Failure($client, \$fperror, $userinput); @@ -2116,7 +2148,7 @@ sub change_authentication_handler { &Failure($client, \$result); } } else { - my $result=&make_passwd_file($uname, $umode,$npass,$passfilename); + my $result=&make_passwd_file($uname,$udom,$umode,$npass,$passfilename); # # If the current auth mode is internal, and the old auth mode was # unix, or krb*, and the user is an author for this domain, @@ -3213,6 +3245,12 @@ sub dump_profile_database { # that is matched against # database keywords to do # selective dumps. +# range - optional range of entries +# e.g., 10-20 would return the +# 10th to 19th items, etc. +# extra - optional ref to hash of +# additional args. currently +# skipcheck is only key used. # $client - Channel open on the client. # Returns: # 1 - Continue processing. @@ -3247,12 +3285,26 @@ sub dump_with_regexp { if ($hashref) { my $qresult=''; my $count=0; +# +# When dump is for roles.db, determine if LON-CAPA version checking is needed. +# Sessions on 2.10 and later will include skipcheck => 1 in extra args ref, +# to indicate no version checking is needed (in this case, checking occurs +# on the server hosting the user session, when constructing the roles/courses +# screen). +# if ($extra ne '') { $extra = &Apache::lonnet::thaw_unescape($extra); $skipcheck = $extra->{'skipcheck'}; } my @ids = &Apache::lonnet::current_machine_ids(); my (%homecourses,$major,$minor,$now); +# +# If dump is for roles.db from a pre-2.10 server, determine the LON-CAPA +# version on the server which requested the data. For LON-CAPA 2.9, the +# client session will have sent its LON-CAPA version when initiating the +# connection. For LON-CAPA 2.8 and older, the version is retrieved from +# the global %loncaparevs in lonnet.pm. +# if (($namespace eq 'roles') && (!$skipcheck)) { my $loncaparev = $clientversion; if ($loncaparev eq '') { @@ -3270,8 +3322,18 @@ sub dump_with_regexp { my $cdom = $1; my $cnum = $2; unless ($skipcheck) { - my ($role,$end,$start) = split(/\_/,$value); - if (!$end || $end > $now) { + my ($role,$roleend,$rolestart) = split(/\_/,$value); + if (!$roleend || $roleend > $now) { +# +# For active course roles, check that requesting server is running a LON-CAPA +# version which meets any version requirements for the course. Do not include +# the role amongst the results returned if the requesting server's version is +# too old. +# +# This determination is handled differently depending on whether the course's +# homeserver is the current server, or whether it is a different server. +# In both cases, the course's version requirement needs to be retrieved. +# next unless (&releasereqd_check($cnum,$cdom,$key,$value,$major, $minor,\%homecourses,\@ids)); } @@ -3294,9 +3356,16 @@ sub dump_with_regexp { } } if (&untie_user_hash($hashref)) { +# +# If dump is for roles.db from a pre-2.10 server, check if the LON-CAPA +# version requirements for courses for which the current server is the home +# server permit course roles to be usable on the client server hosting the +# user's session. If so, include those role results in the data returned to +# the client server. +# if (($namespace eq 'roles') && (!$skipcheck)) { if (keys(%homecourses) > 0) { - $qresult .= &check_homecourses(\%homecourses,$udom,$regexp,$count, + $qresult .= &check_homecourses(\%homecourses,$regexp,$count, $range,$start,$end,$major,$minor); } } @@ -5079,7 +5148,7 @@ sub validate_course_owner_handler { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; my ($inst_course_id, $owner, $cdom, $coowners) = split(/:/, $tail); - + $owner = &unescape($owner); $coowners = &unescape($coowners); my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom,$coowners); @@ -6070,7 +6139,7 @@ if (-e $pidfile) { $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'}, Type => SOCK_STREAM, Proto => 'tcp', - ReuseAddr => 1, + ReuseAddr => 1, Listen => 10 ) or die "making socket: $@\n"; @@ -6133,6 +6202,7 @@ sub HUPSMAN { # sig # a setuid perl script that can be root for us to do this job. # sub ReloadApache { +# --------------------------- Handle case of another apachereload process (locking) if (&LONCAPA::try_to_lock('/tmp/lock_apachereload')) { my $execdir = $perlvar{'lonDaemons'}; my $script = $execdir."/apachereload"; @@ -6402,7 +6472,7 @@ $SIG{USR2} = \&UpdateHosts; &Apache::lonnet::load_hosts_tab(); my %iphost = &Apache::lonnet::get_iphost(1); -my $dist=`$perlvar{'lonDaemons'}/distprobe`; +$dist=`$perlvar{'lonDaemons'}/distprobe`; my $arch = `uname -i`; chomp($arch); @@ -6477,13 +6547,12 @@ sub make_new_child { #don't get intercepted $SIG{USR1}= \&logstatus; $SIG{ALRM}= \&timeout; - - # - # Block sigpipe as it gets thrownon socket disconnect and we want to - # deal with that as a read faiure instead. - # - my $blockset = POSIX::SigSet->new(SIGPIPE); - sigprocmask(SIG_BLOCK, $blockset); + # + # Block sigpipe as it gets thrownon socket disconnect and we want to + # deal with that as a read faiure instead. + # + my $blockset = POSIX::SigSet->new(SIGPIPE); + sigprocmask(SIG_BLOCK, $blockset); $lastlog='Forked '; $status='Forked'; @@ -6685,22 +6754,29 @@ sub is_author { # Author role should show up as a key /domain/_au - my $key = "/$domain/_au"; my $value; - if (defined($hashref)) { - $value = $hashref->{$key}; - } + if ($hashref) { - if(defined($value)) { - &Debug("$user @ $domain is an author"); + my $key = "/$domain/_au"; + if (defined($hashref)) { + $value = $hashref->{$key}; + if(!untie_user_hash($hashref)) { + return 'error: ' . ($!+0)." untie (GDBM) Failed"; + } + } + + if(defined($value)) { + &Debug("$user @ $domain is an author"); + } + } else { + return 'error: '.($!+0)." tie (GDBM) Failed"; } return defined($value); } # # Checks to see if the input roleput request was to set -# an author role. If so, invokes the lchtmldir script to set -# up a correct public_html +# an author role. If so, creates construction space # Parameters: # request - The request sent to the rolesput subchunk. # We're looking for /domain/_au @@ -6710,16 +6786,15 @@ sub is_author { # sub manage_permissions { my ($request, $domain, $user, $authtype) = @_; - - &Debug("manage_permissions: $request $domain $user $authtype"); - # See if the request is of the form /$domain/_au if($request =~ /^(\/\Q$domain\E\/_au)$/) { # It's an author rolesput... - my $execdir = $perlvar{'lonDaemons'}; - my $userhome= "/home/$user" ; - &logthis("system $execdir/lchtmldir $userhome $user $authtype"); - &Debug("Setting homedir permissions for $userhome"); - system("$execdir/lchtmldir $userhome $user $authtype"); + my $path=$perlvar{'lonDocRoot'}."/priv/$domain"; + unless (-e $path) { + mkdir($path); + } + unless (-e $path.'/'.$user) { + mkdir($path.'/'.$user); + } } } @@ -7252,7 +7327,7 @@ sub change_unix_password { sub make_passwd_file { - my ($uname, $umode,$npass,$passfilename)=@_; + my ($uname,$udom,$umode,$npass,$passfilename)=@_; my $result="ok"; if ($umode eq 'krb4' or $umode eq 'krb5') { { @@ -7293,7 +7368,7 @@ sub make_passwd_file { # my $uid = getpwnam($uname); if((defined $uid) && ($uid == 0)) { - &logthis(">>>Attempted to create privilged account blocked"); + &logthis(">>>Attempt to create privileged account blocked"); return "no_priv_account_error\n"; } @@ -7305,6 +7380,7 @@ sub make_passwd_file { &Debug("user = ".$uname.", Password =". $npass); my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log"); print $se "$uname\n"; + print $se "$udom\n"; print $se "$npass\n"; print $se "$npass\n"; print $se "$lc_error_file\n"; # Status -> unique file. @@ -7398,6 +7474,20 @@ sub get_usersession_config { return; } +# +# releasereqd_check() will determine if a LON-CAPA version (defined in the +# $major,$minor args passed) is not too old to allow use of a role in a +# course ($cnum,$cdom args passed), if at least one of the following applies: +# (a) the course is a Community, (b) the course's home server is *not* the +# current server, or (c) cached course information is not stale. +# +# For the case where none of these apply, the course is added to the +# $homecourse hash ref (keys = courseIDs, values = array of a hash of roles). +# The $homecourse hash ref is for courses for which the current server is the +# home server. LON-CAPA version requirements are checked elsewhere for the +# items in $homecourse. +# + sub releasereqd_check { my ($cnum,$cdom,$key,$value,$major,$minor,$homecourses,$ids) = @_; my $home = &Apache::lonnet::homeserver($cnum,$cdom); @@ -7427,10 +7517,18 @@ sub releasereqd_check { if (ref($ids) eq 'ARRAY') { if (grep(/^\Q$home\E$/,@{$ids})) { if (ref($homecourses) eq 'HASH') { - if (ref($homecourses->{$hashid}) eq 'ARRAY') { - push(@{$homecourses->{$hashid}},{$key=>$value}); + if (ref($homecourses->{$cdom}) eq 'HASH') { + if (ref($homecourses->{$cdom}{$cnum}) eq 'HASH') { + if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') { + push(@{$homecourses->{$cdom}{$cnum}},{$key=>$value}); + } else { + $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; + } + } else { + $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; + } } else { - $homecourses->{$hashid} = [{$key=>$value}]; + $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; } } return; @@ -7449,6 +7547,17 @@ sub releasereqd_check { return 1; } +# +# get_courseinfo_hash() is used to retrieve course information from the db +# file: nohist_courseids.db for a course for which the current server is *not* +# the home server. +# +# A hash of a hash will be retrieved. The outer hash contains a single key -- +# courseID -- for the course for which the data are being requested. +# The contents of the inner hash, for that single item in the outer hash +# are returned (and cached in memcache for 10 minutes). +# + sub get_courseinfo_hash { my ($cnum,$cdom,$home) = @_; my %info; @@ -7474,43 +7583,64 @@ sub get_courseinfo_hash { return; } +# +# check_homecourses() will retrieve course information for those courses which +# are keys of the $homecourses hash ref (first arg). The nohist_courseids.db +# GDBM file is tied and course information for each course retrieved. Last +# visit (lasttime key) is also retrieved for each, and cached values updated +# for any courses last visited less than 24 hours ago. Cached values are also +# updated for any courses included in the $homecourses hash ref. +# +# The reason for the 24 hours constraint is that the cron entry in +# /etc/cron.d/loncapa for /home/httpd/perl/refresh_courseids_db.pl causes +# cached course information to be updated nightly for courses with activity +# within the past 24 hours. +# +# Role information for the user (included in a ref to an array of hashes as the +# value for each key in $homecourses) is appended to the result returned by the +# routine, which will in turn be appended to the string returned to the client +# hosting the user's session. +# + sub check_homecourses { - my ($homecourses,$udom,$regexp,$count,$range,$start,$end,$major,$minor) = @_; + my ($homecourses,$regexp,$count,$range,$start,$end,$major,$minor) = @_; my ($result,%addtocache); my $yesterday = time - 24*3600; if (ref($homecourses) eq 'HASH') { my (%okcourses,%courseinfo,%recent); - my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()); - if ($hashref) { - while (my ($key,$value) = each(%$hashref)) { - my $unesc_key = &unescape($key); - if ($unesc_key =~ /^lasttime:(\w+)$/) { - my $cid = $1; - $cid =~ s/_/:/; - if ($value > $yesterday ) { - $recent{$cid} = 1; + foreach my $domain (keys(%{$homecourses})) { + my $hashref = + &tie_domain_hash($domain, "nohist_courseids", &GDBM_WRCREAT()); + if (ref($hashref) eq 'HASH') { + while (my ($key,$value) = each(%$hashref)) { + my $unesc_key = &unescape($key); + if ($unesc_key =~ /^lasttime:(\w+)$/) { + my $cid = $1; + $cid =~ s/_/:/; + if ($value > $yesterday ) { + $recent{$cid} = 1; + } + next; } - next; - } - my $items = &Apache::lonnet::thaw_unescape($value); - if (ref($items) eq 'HASH') { - my $hashid = $unesc_key; - $hashid =~ s/_/:/; - $courseinfo{$hashid} = $items; - if (ref($homecourses->{$hashid}) eq 'ARRAY') { - my ($reqdmajor,$reqdminor) = split(/\./,$items->{'releaserequired'}); - if (&useable_role($reqdmajor,$reqdminor,$major,$minor)) { - $okcourses{$hashid} = 1; + my $items = &Apache::lonnet::thaw_unescape($value); + if (ref($items) eq 'HASH') { + my ($cdom,$cnum) = split(/_/,$unesc_key); + my $hashid = $cdom.':'.$cnum; + $courseinfo{$hashid} = $items; + if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') { + my ($reqdmajor,$reqdminor) = split(/\./,$items->{'releaserequired'}); + if (&useable_role($reqdmajor,$reqdminor,$major,$minor)) { + $okcourses{$hashid} = 1; + } } } } + unless (&untie_domain_hash($hashref)) { + &logthis("Failed to untie tied hash for nohist_courseids.db for $domain"); + } + } else { + &logthis("Failed to tie hash for nohist_courseids.db for $domain"); } - unless (&untie_domain_hash($hashref)) { - &logthis('Failed to untie tied hash for nohist_courseids.db'); - } - } else { - &logthis('Failed to tie hash for nohist_courseids.db'); - return; } foreach my $hashid (keys(%recent)) { my ($result,$cached)=&Apache::lonnet::is_cached_new('courseinfo',$hashid); @@ -7518,13 +7648,20 @@ sub check_homecourses { &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); } } - foreach my $hashid (keys(%{$homecourses})) { - next if ($recent{$hashid}); - &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); + foreach my $cdom (keys(%{$homecourses})) { + if (ref($homecourses->{$cdom}) eq 'HASH') { + foreach my $cnum (keys(%{$homecourses->{$cdom}})) { + my $hashid = $cdom.':'.$cnum; + next if ($recent{$hashid}); + &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); + } + } } foreach my $hashid (keys(%okcourses)) { - if (ref($homecourses->{$hashid}) eq 'ARRAY') { - foreach my $role (@{$homecourses->{$hashid}}) { + my ($cdom,$cnum) = split(/:/,$hashid); + if ((ref($homecourses->{$cdom}) eq 'HASH') && + (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY')) { + foreach my $role (@{$homecourses->{$cdom}{$cnum}}) { if (ref($role) eq 'HASH') { while (my ($key,$value) = each(%{$role})) { if ($regexp eq '.') { @@ -7550,6 +7687,12 @@ sub check_homecourses { return $result; } +# +# useable_role() will compare the LON-CAPA version required by a course with +# the version available on the client server. If the client server's version +# is compatible, 1 will be returned. +# + sub useable_role { my ($reqdmajor,$reqdminor,$major,$minor) = @_; if ($reqdmajor ne '' && $reqdminor ne '') {