--- loncom/lonnet/perl/lonnet.pm 2000/11/21 12:23:25 1.67 +++ loncom/lonnet/perl/lonnet.pm 2002/05/08 17:40:03 1.216 @@ -1,57 +1,29 @@ # The LearningOnline Network # TCP networking package # -# Functions for use by content handlers: +# $Id: lonnet.pm,v 1.216 2002/05/08 17:40:03 www Exp $ # -# plaintext(short) : plain text explanation of short term -# fileembstyle(ext) : embed style in page for file extension -# filedescription(ext) : descriptor text for file extension -# allowed(short,url) : returns codes for allowed actions -# F: full access -# U,I,K: authentication modes (cxx only) -# '': forbidden -# 1: user needs to choose course -# 2: browse allowed -# definerole(rolename,sys,dom,cou) : define a custom role rolename -# set priviledges in format of lonTabs/roles.tab for -# system, domain and course level, -# assignrole(udom,uname,url,role,end,start) : give a role to a user for the -# level given by url. Optional start and end dates -# (leave empty string or zero for "no date") -# assigncustomrole (udom,uname,url,rdom,rnam,rolename,end,start) : give a -# custom role to a user for the level given by url. -# Specify name and domain of role author, and role name -# revokerole (udom,uname,url,role) : Revoke a role for url -# revokecustomrole (udom,uname,url,rdom,rnam,rolename) : Revoke a custom role -# appenv(hash) : adds hash to session environment -# delenv(varname) : deletes all environment entries starting with varname -# store(hash) : stores hash permanently for this url -# cstore(hash) : critical store -# restore : returns hash for this url -# eget(namesp,array) : returns hash with keys from array filled in from namesp -# get(namesp,array) : returns hash with keys from array filled in from namesp -# del(namesp,array) : deletes keys out of array from namesp -# put(namesp,hash) : stores hash in namesp -# cput(namesp,hash) : critical put -# dump(namesp) : dumps the complete namespace into a hash -# ssi(url,hash) : does a complete request cycle on url to localhost, posts -# hash -# coursedescription(id) : returns and caches course description for id -# repcopy(filename) : replicate file -# dirlist(url) : gets a directory listing -# directcondval(index) : reading condition value of single condition from -# state string -# condval(index) : value of condition index based on state -# EXT(name) : value of a variable -# symblist(map,hash) : Updates symbolic storage links -# symbread([filename]) : returns the data handle (filename optional) -# rndseed() : returns a random seed -# getfile(filename) : returns the contents of filename, or a -1 if it can't -# be found, replicates and subscribes to the file -# filelocation(dir,file) : returns a farily clean absolute reference to file -# from the directory dir -# hreflocation(dir,file) : same as filelocation, but for hrefs -# log(domain,user,home,msg) : write to permanent log for user +# Copyright Michigan State University Board of Trustees +# +# This file is part of the LearningOnline Network with CAPA (LON-CAPA). +# +# LON-CAPA is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with LON-CAPA; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# /home/httpd/html/adm/gpl.txt +# +# http://www.lon-capa.org/ # # 6/1/99,6/2,6/10,6/11,6/12,6/14,6/26,6/28,6/29,6/30, # 7/1,7/2,7/9,7/10,7/12,7/14,7/15,7/19, @@ -67,7 +39,36 @@ # 10/04 Gerd Kortemeyer # 10/04 Guy Albertelli # 10/06,10/09,10/10,10/11,10/14,10/20,10/23,10/25,10/26,10/27,10/28,10/29, -# 10/30,10/31,11/2,11/14,11/15,11/16,11/20,11/21 Gerd Kortemeyer +# 10/30,10/31, +# 11/2,11/14,11/15,11/16,11/20,11/21,11/22,11/25,11/27, +# 12/02,12/12,12/13,12/14,12/28,12/29 Gerd Kortemeyer +# 05/01/01 Guy Albertelli +# 05/01,06/01,09/01 Gerd Kortemeyer +# 09/01 Guy Albertelli +# 09/01,10/01,11/01 Gerd Kortemeyer +# YEAR=2001 +# 02/27/01 Scott Harrison +# 3/2 Gerd Kortemeyer +# 3/15,3/19 Scott Harrison +# 3/19,3/20 Gerd Kortemeyer +# 3/22,3/27,4/2,4/16,4/17 Scott Harrison +# 5/26,5/28 Gerd Kortemeyer +# 5/30 H. K. Ng +# 6/1 Gerd Kortemeyer +# July Guy Albertelli +# 8/4,8/7,8/8,8/9,8/11,8/16,8/17,8/18,8/20,8/23,9/20,9/21,9/26, +# 10/2 Gerd Kortemeyer +# 10/5,10/10,11/13,11/15 Scott Harrison +# 11/17,11/20,11/22,11/29 Gerd Kortemeyer +# 12/5 Matthew Hall +# 12/5 Guy Albertelli +# 12/6,12/7,12/12 Gerd Kortemeyer +# 12/18 Scott Harrison +# 12/21,12/22,12/27,12/28 Gerd Kortemeyer +# YEAR=2002 +# 1/4,2/4,2/7 Gerd Kortemeyer +# +### package Apache::lonnet; @@ -76,13 +77,29 @@ use Apache::File; use LWP::UserAgent(); use HTTP::Headers; use vars -qw(%perlvar %hostname %homecache %spareid %hostdom %libserv %pr %prp %fe %fd $readit); +qw(%perlvar %hostname %homecache %hostip %spareid %hostdom + %libserv %pr %prp %metacache %packagetab + %courselogs %accesshash $processmarker $dumpcount + %coursedombuf %coursehombuf %courseresdatacache); use IO::Socket; use GDBM_File; use Apache::Constants qw(:common :http); +use HTML::LCParser; +use Fcntl qw(:flock); +my $readit; # --------------------------------------------------------------------- Logging +sub logtouch { + my $execdir=$perlvar{'lonDaemons'}; + unless (-e "$execdir/logs/lonnet.log") { + my $fh=Apache::File->new(">>$execdir/logs/lonnet.log"); + close $fh; + } + my ($wwwuid,$wwwgid)=(getpwnam('www'))[2,3]; + chown($wwwuid,$wwwgid,$execdir.'/logs/lonnet.log'); +} + sub logthis { my $message=shift; my $execdir=$perlvar{'lonDaemons'}; @@ -120,8 +137,24 @@ sub subreply { sub reply { my ($cmd,$server)=@_; + unless (defined($hostname{$server})) { return 'no_such_host'; } my $answer=subreply($cmd,$server); - if ($answer eq 'con_lost') { $answer=subreply($cmd,$server); } + if ($answer eq 'con_lost') { + sleep 5; + $answer=subreply($cmd,$server); + if ($answer eq 'con_lost') { + &logthis("Second attempt con_lost on $server"); + my $peerfile="$perlvar{'lonSockDir'}/$server"; + my $client=IO::Socket::UNIX->new(Peer =>"$peerfile", + Type => SOCK_STREAM, + Timeout => 10) + or return "con_lost"; + &logthis("Killing socket"); + print $client "close_connection_exit\n"; + sleep 5; + $answer=subreply($cmd,$server); + } + } if (($answer=~/^refused/) || ($answer=~/^rejected/)) { &logthis("WARNING:". " $cmd to $server returned $answer"); @@ -162,6 +195,11 @@ sub reconlonc { sub critical { my ($cmd,$server)=@_; + unless ($hostname{$server}) { + &logthis("WARNING:". + " Critical message to unknown server ($server)"); + return 'no_such_host'; + } my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { my $pingreply=reply('ping',$server); @@ -211,22 +249,36 @@ sub critical { sub appenv { my %newenv=@_; - map { + foreach (keys %newenv) { if (($newenv{$_}=~/^user\.role/) || ($newenv{$_}=~/^user\.priv/)) { &logthis("WARNING: ". - "Attempt to modify environment ".$_." to ".$newenv{$_}); + "Attempt to modify environment ".$_." to ".$newenv{$_} + .''); delete($newenv{$_}); } else { $ENV{$_}=$newenv{$_}; } - } keys %newenv; + } + + my $lockfh; + unless ($lockfh=Apache::File->new("$ENV{'user.environment'}")) { + return 'error: '.$!; + } + unless (flock($lockfh,LOCK_EX)) { + &logthis("WARNING: ". + 'Could not obtain exclusive lock in appenv: '.$!); + $lockfh->close(); + return 'error: '.$!; + } + my @oldenv; { my $fh; unless ($fh=Apache::File->new("$ENV{'user.environment'}")) { - return 'error'; + return 'error: '.$!; } @oldenv=<$fh>; + $fh->close(); } for (my $i=0; $i<=$#oldenv; $i++) { chomp($oldenv[$i]); @@ -246,7 +298,10 @@ sub appenv { foreach $newname (keys %newenv) { print $fh "$newname=$newenv{$newname}\n"; } + $fh->close(); } + + $lockfh->close(); return 'ok'; } # ----------------------------------------------------- Delete from Environment @@ -265,16 +320,30 @@ sub delenv { unless ($fh=Apache::File->new("$ENV{'user.environment'}")) { return 'error'; } + unless (flock($fh,LOCK_SH)) { + &logthis("WARNING: ". + 'Could not obtain shared lock in delenv: '.$!); + $fh->close(); + return 'error: '.$!; + } @oldenv=<$fh>; + $fh->close(); } { my $fh; unless ($fh=Apache::File->new(">$ENV{'user.environment'}")) { return 'error'; } - map { + unless (flock($fh,LOCK_EX)) { + &logthis("WARNING: ". + 'Could not obtain exclusive lock in delenv: '.$!); + $fh->close(); + return 'error: '.$!; + } + foreach (@oldenv) { unless ($_=~/^$delthis/) { print $fh $_; } - } @oldenv; + } + $fh->close(); } return 'ok'; } @@ -295,11 +364,85 @@ sub spareserver { return $spareserver; } +# --------------------------------------------- Try to change a user's password + +sub changepass { + my ($uname,$udom,$currentpass,$newpass,$server)=@_; + $currentpass = &escape($currentpass); + $newpass = &escape($newpass); + my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass", + $server); + if (! $answer) { + &logthis("No reply on password change request to $server ". + "by $uname in domain $udom."); + } elsif ($answer =~ "^ok") { + &logthis("$uname in $udom successfully changed their password ". + "on $server."); + } elsif ($answer =~ "^pwchange_failure") { + &logthis("$uname in $udom was unable to change their password ". + "on $server. The action was blocked by either lcpasswd ". + "or pwchange"); + } elsif ($answer =~ "^non_authorized") { + &logthis("$uname in $udom did not get their password correct when ". + "attempting to change it on $server."); + } elsif ($answer =~ "^auth_mode_error") { + &logthis("$uname in $udom attempted to change their password despite ". + "not being locally or internally authenticated on $server."); + } elsif ($answer =~ "^unknown_user") { + &logthis("$uname in $udom attempted to change their password ". + "on $server but were unable to because $server is not ". + "their home server."); + } elsif ($answer =~ "^refused") { + &logthis("$server refused to change $uname in $udom password because ". + "it was sent an unencrypted request to change the password."); + } + return $answer; +} + +# ----------------------- Try to determine user's current authentication scheme + +sub queryauthenticate { + my ($uname,$udom)=@_; + if (($perlvar{'lonRole'} eq 'library') && + ($udom eq $perlvar{'lonDefDomain'})) { + my $answer=reply("encrypt:currentauth:$udom:$uname", + $perlvar{'lonHostID'}); + unless ($answer eq 'unknown_user' or $answer eq 'refused') { + if (length($answer)) { + return $answer; + } + else { + &logthis("User $uname at $udom lacks an authentication mechanism"); + return 'no_host'; + } + } + } + + my $tryserver; + foreach $tryserver (keys %libserv) { + if ($hostdom{$tryserver} eq $udom) { + my $answer=reply("encrypt:currentauth:$udom:$uname",$tryserver); + unless ($answer eq 'unknown_user' or $answer eq 'refused') { + if (length($answer)) { + return $answer; + } + else { + &logthis("User $uname at $udom lacks an authentication mechanism"); + return 'no_host'; + } + } + } + } + &logthis("User $uname at $udom lacks an authentication mechanism"); + return 'no_host'; +} + # --------- Try to authenticate user from domain's lib servers (first this one) sub authenticate { my ($uname,$upass,$udom)=@_; $upass=escape($upass); + $uname=~s/\W//g; if (($perlvar{'lonRole'} eq 'library') && ($udom eq $perlvar{'lonDefDomain'})) { my $answer=reply("encrypt:auth:$udom:$uname:$upass",$perlvar{'lonHostID'}); @@ -356,6 +499,111 @@ sub homeserver { return 'no_host'; } +# ------------------------------------- Find the usernames behind a list of IDs + +sub idget { + my ($udom,@ids)=@_; + my %returnhash=(); + + my $tryserver; + foreach $tryserver (keys %libserv) { + if ($hostdom{$tryserver} eq $udom) { + my $idlist=join('&',@ids); + $idlist=~tr/A-Z/a-z/; + my $reply=&reply("idget:$udom:".$idlist,$tryserver); + my @answer=(); + if (($reply ne 'con_lost') && ($reply!~/^error\:/)) { + @answer=split(/\&/,$reply); + } ; + my $i; + for ($i=0;$i<=$#ids;$i++) { + if ($answer[$i]) { + $returnhash{$ids[$i]}=$answer[$i]; + } + } + } + } + return %returnhash; +} + +# ------------------------------------- Find the IDs behind a list of usernames + +sub idrget { + my ($udom,@unames)=@_; + my %returnhash=(); + foreach (@unames) { + $returnhash{$_}=(&userenvironment($udom,$_,'id'))[1]; + } + return %returnhash; +} + +# ------------------------------- Store away a list of names and associated IDs + +sub idput { + my ($udom,%ids)=@_; + my %servers=(); + foreach (keys %ids) { + my $uhom=&homeserver($_,$udom); + if ($uhom ne 'no_host') { + my $id=&escape($ids{$_}); + $id=~tr/A-Z/a-z/; + my $unam=&escape($_); + if ($servers{$uhom}) { + $servers{$uhom}.='&'.$id.'='.$unam; + } else { + $servers{$uhom}=$id.'='.$unam; + } + &critical('put:'.$udom.':'.$unam.':environment:id='.$id,$uhom); + } + } + foreach (keys %servers) { + &critical('idput:'.$udom.':'.$servers{$_},$_); + } +} + +# ------------------------------------- Find the section of student in a course + +sub usection { + my ($udom,$unam,$courseid)=@_; + $courseid=~s/\_/\//g; + $courseid=~s/^(\w)/\/$1/; + foreach (split(/\&/,&reply('dump:'.$udom.':'.$unam.':roles', + &homeserver($unam,$udom)))) { + my ($key,$value)=split(/\=/,$_); + $key=&unescape($key); + if ($key=~/^$courseid(?:\/)*(\w+)*\_st$/) { + my $section=$1; + if ($key eq $courseid.'_st') { $section=''; } + my ($dummy,$end,$start)=split(/\_/,&unescape($value)); + my $now=time; + my $notactive=0; + if ($start) { + if ($now<$start) { $notactive=1; } + } + if ($end) { + if ($now>$end) { $notactive=1; } + } + unless ($notactive) { return $section; } + } + } + return '-1'; +} + +# ------------------------------------- Read an entry from a user's environment + +sub userenvironment { + my ($udom,$unam,@what)=@_; + my %returnhash=(); + my @answer=split(/\&/, + &reply('get:'.$udom.':'.$unam.':environment:'.join('&',@what), + &homeserver($unam,$udom))); + my $i; + for ($i=0;$i<=$#what;$i++) { + $returnhash{$what[$i]}=&unescape($answer[$i]); + } + return %returnhash; +} + # ----------------------------- Subscribe to a resource, return URL if possible sub subscribe { @@ -379,6 +627,7 @@ sub subscribe { sub repcopy { my $filename=shift; $filename=~s/\/+/\//g; + if ($filename=~/^\/home\/httpd\/html\/adm\//) { return OK; } my $transname="$filename.in.transfer"; if ((-e $filename) || (-e $transname)) { return OK; } my $remoteurl=subscribe($filename); @@ -444,7 +693,7 @@ sub ssi { if (%form) { $request=new HTTP::Request('POST',"http://".$ENV{'HTTP_HOST'}.$fn); - $request->content(join '&', map { "$_=$form{$_}" } keys %form); + $request->content(join('&',map { &escape($_).'='.&escape($form{$_}) } keys %form)); } else { $request=new HTTP::Request('GET',"http://".$ENV{'HTTP_HOST'}.$fn); } @@ -462,60 +711,524 @@ sub log { return critical("log:$dom:$nam:$what",$hom); } +# ------------------------------------------------------------------ Course Log + +sub flushcourselogs { + &logthis('Flushing course log buffers'); + foreach (keys %courselogs) { + my $crsid=$_; + if (&reply('log:'.$coursedombuf{$crsid}.':'. + &escape($courselogs{$crsid}), + $coursehombuf{$crsid}) eq 'ok') { + delete $courselogs{$crsid}; + } else { + &logthis('Failed to flush log buffer for '.$crsid); + if (length($courselogs{$crsid})>40000) { + &logthis("WARNING: Buffer for ".$crsid. + " exceeded maximum size, deleting."); + delete $courselogs{$crsid}; + } + } + } + &logthis('Flushing access logs'); + foreach (keys %accesshash) { + my $entry=$_; + $entry=~/\_\_\_(\w+)\/(\w+)\/(.*)\_\_\_(\w+)$/; + my %temphash=($entry => $accesshash{$entry}); + if (&Apache::lonnet::put('resevaldata',\%temphash,$1,$2) eq 'ok') { + delete $accesshash{$entry}; + } + } + $dumpcount++; +} + +sub courselog { + my $what=shift; + $what=time.':'.$what; + unless ($ENV{'request.course.id'}) { return ''; } + $coursedombuf{$ENV{'request.course.id'}}= + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + $coursehombuf{$ENV{'request.course.id'}}= + $ENV{'course.'.$ENV{'request.course.id'}.'.home'}; + if (defined $courselogs{$ENV{'request.course.id'}}) { + $courselogs{$ENV{'request.course.id'}}.='&'.$what; + } else { + $courselogs{$ENV{'request.course.id'}}.=$what; + } + if (length($courselogs{$ENV{'request.course.id'}})>4048) { + &flushcourselogs(); + } +} + +sub courseacclog { + my $fnsymb=shift; + unless ($ENV{'request.course.id'}) { return ''; } + my $what=$fnsymb.':'.$ENV{'user.name'}.':'.$ENV{'user.domain'}; + if ($fnsymb=~/(problem|exam|quiz|assess|survey|form)$/) { + $what.=':POST'; + foreach (keys %ENV) { + if ($_=~/^form\.(.*)/) { + $what.=':'.$1.'='.$ENV{$_}; + } + } + } + &courselog($what); +} + +sub countacc { + my $url=&declutter(shift); + unless ($ENV{'request.course.id'}) { return ''; } + $accesshash{$ENV{'request.course.id'}.'___'.$url.'___course'}=1; + my $key=$processmarker.'_'.$dumpcount.'___'.$url.'___count'; + if (defined($accesshash{$key})) { + $accesshash{$key}++; + } else { + $accesshash{$key}=1; + } +} + +# ----------------------------------------------------------- Check out an item + +sub checkout { + my ($symb,$tuname,$tudom,$tcrsid)=@_; + my $now=time; + my $lonhost=$perlvar{'lonHostID'}; + my $infostr=&escape( + $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.'_'.$hostip{$lonhost}.'_'.$tb; + $dtoken=~s/\W/\_/g; + my ($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 { + my ($uname,$udom,$stype,$usymb)=@_; + my $cid=$ENV{'request.course.id'}; + if ($cid) { + my $now=time; + my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; + return &reply('put:'.$ENV{'course.'.$cid.'.domain'}.':'. + $ENV{'course.'.$cid.'.num'}. + ':nohist_expirationdates:'. + &escape($key).'='.$now, + $ENV{'course.'.$cid.'.home'}) + } + return 'ok'; +} + +# ----------------------------------------------------- Devalidate Spreadsheets + +sub devalidate { + my $symb=shift; + my $cid=$ENV{'request.course.id'}; + if ($cid) { + my $key=$ENV{'user.name'}.':'.$ENV{'user.domain'}.':'; + my $status= + &del('nohist_calculatedsheet', + [$key.'studentcalc'], + $ENV{'course.'.$cid.'.domain'}, + $ENV{'course.'.$cid.'.num'}) + .' '. + &del('nohist_calculatedsheets_'.$cid, + [$key.'assesscalc:'.$symb]); + unless ($status eq 'ok ok') { + &logthis('Could not devalidate spreadsheet '. + $ENV{'user.name'}.' at '.$ENV{'user.domain'}.' for '. + $symb.': '.$status); + } + } +} + +sub arrayref2str { + my ($arrayref) = @_; + my $result='_ARRAY_REF__'; + foreach my $elem (@$arrayref) { + if (ref($elem) eq 'ARRAY') { + $result.=&escape(&arrayref2str($elem)).'&'; + } elsif (ref($elem) eq 'HASH') { + $result.=&escape(&hashref2str($elem)).'&'; + } elsif (ref($elem)) { + &logthis("Got a ref of ".(ref($elem))." skipping."); + } else { + $result.=&escape($elem).'&'; + } + } + $result=~s/\&$//; + return $result; +} + +sub hash2str { + my (%hash) = @_; + my $result=&hashref2str(\%hash); + $result=~s/^_HASH_REF__//; + return $result; +} + +sub hashref2str { + my ($hashref)=@_; + my $result='_HASH_REF__'; + foreach (keys(%$hashref)) { + if (ref($_) eq 'ARRAY') { + $result.=&escape(&arrayref2str($_)).'='; + } elsif (ref($_) eq 'HASH') { + $result.=&escape(&hashref2str($_)).'='; + } elsif (ref($_)) { + &logthis("Got a ref of ".(ref($_))." skipping."); + } else { + $result.=&escape($_).'='; + } + + if (ref($$hashref{$_}) eq 'ARRAY') { + $result.=&escape(&arrayref2str($$hashref{$_})).'&'; + } elsif (ref($$hashref{$_}) eq 'HASH') { + $result.=&escape(&hashref2str($$hashref{$_})).'&'; + } elsif (ref($$hashref{$_})) { + &logthis("Got a ref of ".(ref($$hashref{$_}))." skipping."); + } else { + $result.=&escape($$hashref{$_}).'&'; + } + } + $result=~s/\&$//; + return $result; +} + +sub str2hash { + my ($string) = @_; + my %returnhash; + foreach (split(/\&/,$string)) { + my ($name,$value)=split(/\=/,$_); + $name=&unescape($name); + $value=&unescape($value); + if ($value =~ /^_HASH_REF__/) { + $value =~ s/^_HASH_REF__//; + my %hash=&str2hash($value); + $value=\%hash; + } elsif ($value =~ /^_ARRAY_REF__/) { + $value =~ s/^_ARRAY_REF__//; + my @array=&str2array($value); + $value=\@array; + } + $returnhash{$name}=$value; + } + return (%returnhash); +} + +sub str2array { + my ($string) = @_; + my @returnarray; + foreach my $value (split(/\&/,$string)) { + $value=&unescape($value); + if ($value =~ /^_HASH_REF__/) { + $value =~ s/^_HASH_REF__//; + my %hash=&str2hash($value); + $value=\%hash; + } elsif ($value =~ /^_ARRAY_REF__/) { + $value =~ s/^_ARRAY_REF__//; + my @array=&str2array($value); + $value=\@array; + } + push(@returnarray,$value); + } + return (@returnarray); +} + +# -------------------------------------------------------------------Temp Store + +sub tmpreset { + my ($symb,$namespace,$domain,$stuname) = @_; + if (!$symb) { + $symb=&symbread(); + if (!$symb) { $symb= $ENV{'REQUEST_URI'}; } + } + $symb=escape($symb); + + if (!$namespace) { $namespace=$ENV{'request.state'}; } + $namespace=~s/\//\_/g; + $namespace=~s/\W//g; + + #FIXME needs to do something for /pub resources + if (!$domain) { $domain=$ENV{'user.domain'}; } + if (!$stuname) { $stuname=$ENV{'user.name'}; } + my $path=$perlvar{'lonDaemons'}.'/tmp'; + my %hash; + if (tie(%hash,'GDBM_File', + $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', + &GDBM_WRCREAT,0640)) { + foreach my $key (keys %hash) { + if ($key=~ /:$symb/) { + delete($hash{$key}); + } + } + } +} + +sub tmpstore { + my ($storehash,$symb,$namespace,$domain,$stuname) = @_; + + if (!$symb) { + $symb=&symbread(); + if (!$symb) { $symb= $ENV{'request.url'}; } + } + $symb=escape($symb); + + if (!$namespace) { + # I don't think we would ever want to store this for a course. + # it seems this will only be used if we don't have a course. + #$namespace=$ENV{'request.course.id'}; + #if (!$namespace) { + $namespace=$ENV{'request.state'}; + #} + } + $namespace=~s/\//\_/g; + $namespace=~s/\W//g; +#FIXME needs to do something for /pub resources + if (!$domain) { $domain=$ENV{'user.domain'}; } + if (!$stuname) { $stuname=$ENV{'user.name'}; } + my $now=time; + my %hash; + my $path=$perlvar{'lonDaemons'}.'/tmp'; + if (tie(%hash,'GDBM_File', + $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', + &GDBM_WRCREAT,0640)) { + $hash{"version:$symb"}++; + my $version=$hash{"version:$symb"}; + my $allkeys=''; + foreach my $key (keys(%$storehash)) { + $allkeys.=$key.':'; + $hash{"$version:$symb:$key"}=$$storehash{$key}; + } + $hash{"$version:$symb:timestamp"}=$now; + $allkeys.='timestamp'; + $hash{"$version:keys:$symb"}=$allkeys; + if (untie(%hash)) { + return 'ok'; + } else { + return "error:$!"; + } + } else { + return "error:$!"; + } +} + +# -----------------------------------------------------------------Temp Restore + +sub tmprestore { + my ($symb,$namespace,$domain,$stuname) = @_; + + if (!$symb) { + $symb=&symbread(); + if (!$symb) { $symb= $ENV{'request.url'}; } + } + $symb=escape($symb); + + if (!$namespace) { $namespace=$ENV{'request.state'}; } + #FIXME needs to do something for /pub resources + if (!$domain) { $domain=$ENV{'user.domain'}; } + if (!$stuname) { $stuname=$ENV{'user.name'}; } + + my %returnhash; + $namespace=~s/\//\_/g; + $namespace=~s/\W//g; + my %hash; + my $path=$perlvar{'lonDaemons'}.'/tmp'; + if (tie(%hash,'GDBM_File', + $path.'/tmpstore_'.$stuname.'_'.$domain.'_'.$namespace.'.db', + &GDBM_READER,0640)) { + my $version=$hash{"version:$symb"}; + $returnhash{'version'}=$version; + my $scope; + for ($scope=1;$scope<=$version;$scope++) { + my $vkeys=$hash{"$scope:keys:$symb"}; + my @keys=split(/:/,$vkeys); + my $key; + $returnhash{"$scope:keys"}=$vkeys; + foreach $key (@keys) { + $returnhash{"$scope:$key"}=$hash{"$scope:$symb:$key"}; + $returnhash{"$key"}=$hash{"$scope:$symb:$key"}; + } + } + if (!(untie(%hash))) { + return "error:$!"; + } + } else { + return "error:$!"; + } + return %returnhash; +} + # ----------------------------------------------------------------------- Store sub store { - my %storehash=@_; - my $symb; - unless ($symb=escape(&symbread())) { return ''; } - my $namespace; - unless ($namespace=$ENV{'request.course.id'}) { return ''; } + my ($storehash,$symb,$namespace,$domain,$stuname) = @_; + my $home=''; + + if ($stuname) { $home=&homeserver($stuname,$domain); } + + $symb=&symbclean($symb); + if (!$symb) { unless ($symb=&symbread()) { return ''; } } + + &devalidate($symb); + + $symb=escape($symb); + if (!$namespace) { + unless ($namespace=$ENV{'request.course.id'}) { + return ''; + } + } + if (!$domain) { $domain=$ENV{'user.domain'}; } + if (!$stuname) { $stuname=$ENV{'user.name'}; } + if (!$home) { $home=$ENV{'user.home'}; } my $namevalue=''; - map { - $namevalue.=escape($_).'='.escape($storehash{$_}).'&'; - } keys %storehash; + foreach (keys %$storehash) { + $namevalue.=escape($_).'='.escape($$storehash{$_}).'&'; + } $namevalue=~s/\&$//; - return reply( - "store:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$symb:$namevalue", - "$ENV{'user.home'}"); + &courselog($symb.':'.$stuname.':'.$domain.':STORE:'.$namevalue); + return reply("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); } # -------------------------------------------------------------- Critical Store sub cstore { - my %storehash=@_; - my $symb; - unless ($symb=escape(&symbread())) { return ''; } - my $namespace; - unless ($namespace=$ENV{'request.course.id'}) { return ''; } + my ($storehash,$symb,$namespace,$domain,$stuname) = @_; + my $home=''; + + if ($stuname) { $home=&homeserver($stuname,$domain); } + + $symb=&symbclean($symb); + if (!$symb) { unless ($symb=&symbread()) { return ''; } } + + &devalidate($symb); + + $symb=escape($symb); + if (!$namespace) { + unless ($namespace=$ENV{'request.course.id'}) { + return ''; + } + } + if (!$domain) { $domain=$ENV{'user.domain'}; } + if (!$stuname) { $stuname=$ENV{'user.name'}; } + if (!$home) { $home=$ENV{'user.home'}; } + my $namevalue=''; - map { - $namevalue.=escape($_).'='.escape($storehash{$_}).'&'; - } keys %storehash; + foreach (keys %$storehash) { + $namevalue.=escape($_).'='.escape($$storehash{$_}).'&'; + } $namevalue=~s/\&$//; - return critical( - "store:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$symb:$namevalue", - "$ENV{'user.home'}"); + &courselog($symb.':'.$stuname.':'.$domain.':CSTORE:'.$namevalue); + return critical + ("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); } # --------------------------------------------------------------------- Restore sub restore { - my $symb; - unless ($symb=escape(&symbread())) { return ''; } - my $namespace; - unless ($namespace=$ENV{'request.course.id'}) { return ''; } - my $answer=reply( - "restore:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$symb", - "$ENV{'user.home'}"); + my ($symb,$namespace,$domain,$stuname) = @_; + my $home=''; + + if ($stuname) { $home=&homeserver($stuname,$domain); } + + if (!$symb) { + unless ($symb=escape(&symbread())) { return ''; } + } else { + $symb=&escape(&symbclean($symb)); + } + if (!$namespace) { + unless ($namespace=$ENV{'request.course.id'}) { + return ''; + } + } + if (!$domain) { $domain=$ENV{'user.domain'}; } + if (!$stuname) { $stuname=$ENV{'user.name'}; } + if (!$home) { $home=$ENV{'user.home'}; } + my $answer=&reply("restore:$domain:$stuname:$namespace:$symb","$home"); + my %returnhash=(); - map { + foreach (split(/\&/,$answer)) { my ($name,$value)=split(/\=/,$_); $returnhash{&unescape($name)}=&unescape($value); - } split(/\&/,$answer); - map { - $returnhash{$_}=$returnhash{$returnhash{'version'}.':'.$_}; - } split(/\:/,$returnhash{$returnhash{'version'}.':keys'}); + } + my $version; + for ($version=1;$version<=$returnhash{'version'};$version++) { + foreach (split(/\:/,$returnhash{$version.':keys'})) { + $returnhash{$_}=$returnhash{$version.':'.$_}; + } + } return %returnhash; } @@ -526,23 +1239,18 @@ sub coursedescription { $courseid=~s/^\///; $courseid=~s/\_/\//g; my ($cdomain,$cnum)=split(/\//,$courseid); - my $chome=homeserver($cnum,$cdomain); + my $chome=&homeserver($cnum,$cdomain); if ($chome ne 'no_host') { - my $rep=reply("dump:$cdomain:$cnum:environment",$chome); - if ($rep ne 'con_lost') { - my $normalid=$courseid; - $normalid=~s/\//\_/g; + my %returnhash=&dump('environment',$cdomain,$cnum); + if (!exists($returnhash{'con_lost'})) { + my $normalid=$cdomain.'_'.$cnum; my %envhash=(); - my %returnhash=('home' => $chome, - 'domain' => $cdomain, - 'num' => $cnum); - map { - my ($name,$value)=split(/\=/,$_); - $name=&unescape($name); - $value=&unescape($value); - $returnhash{$name}=$value; + $returnhash{'home'}= $chome; + $returnhash{'domain'} = $cdomain; + $returnhash{'num'} = $cnum; + while (my ($name,$value) = each %returnhash) { $envhash{'course.'.$normalid.'.'.$name}=$value; - } split(/\&/,$rep); + } $returnhash{'url'}='/res/'.declutter($returnhash{'url'}); $returnhash{'fn'}=$perlvar{'lonDaemons'}.'/tmp/'. $ENV{'user.name'}.'_'.$cdomain.'_'.$cnum; @@ -557,7 +1265,7 @@ sub coursedescription { return (); } -# -------------------------------------------------------- Get user priviledges +# -------------------------------------------------------- Get user privileges sub rolesinit { my ($domain,$username,$authhost)=@_; @@ -570,7 +1278,7 @@ sub rolesinit { my $thesestr; if ($rolesdump ne '') { - map { + foreach (split(/&/,$rolesdump)) { if ($_!~/^rolesdef\&/) { my ($area,$role)=split(/=/,$_); $area=~s/\_\w\w$//; @@ -626,25 +1334,32 @@ sub rolesinit { } } } - } split(/&/,$rolesdump); - map { + } + my $adv=0; + my $author=0; + foreach (keys %allroles) { %thesepriv=(); - map { + if (($_!~/^st/) && ($_!~/^ta/) && ($_!~/^cm/)) { $adv=1; } + if (($_=~/^au/) || ($_=~/^ca/)) { $author=1; } + foreach (split(/:/,$allroles{$_})) { if ($_ ne '') { - my ($priviledge,$restrictions)=split(/&/,$_); + my ($privilege,$restrictions)=split(/&/,$_); if ($restrictions eq '') { - $thesepriv{$priviledge}='F'; + $thesepriv{$privilege}='F'; } else { - if ($thesepriv{$priviledge} ne 'F') { - $thesepriv{$priviledge}.=$restrictions; + if ($thesepriv{$privilege} ne 'F') { + $thesepriv{$privilege}.=$restrictions; } } } - } split(/:/,$allroles{$_}); + } $thesestr=''; - map { $thesestr.=':'.$_.'&'.$thesepriv{$_}; } keys %thesepriv; + foreach (keys %thesepriv) { $thesestr.=':'.$_.'&'.$thesepriv{$_}; } $userroles.='user.priv.'.$_.'='.$thesestr."\n"; - } keys %allroles; + } + $userroles.='user.adv='.$adv."\n". + 'user.author='.$author."\n"; + $ENV{'user.adv'}=$adv; } return $userroles; } @@ -652,104 +1367,124 @@ sub rolesinit { # --------------------------------------------------------------- get interface sub get { - my ($namespace,@storearr)=@_; + my ($namespace,$storearr,$udomain,$uname)=@_; my $items=''; - map { + foreach (@$storearr) { $items.=escape($_).'&'; - } @storearr; + } $items=~s/\&$//; - my $rep=reply("get:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$items", - $ENV{'user.home'}); + if (!$udomain) { $udomain=$ENV{'user.domain'}; } + if (!$uname) { $uname=$ENV{'user.name'}; } + my $uhome=&homeserver($uname,$udomain); + + my $rep=&reply("get:$udomain:$uname:$namespace:$items",$uhome); my @pairs=split(/\&/,$rep); my %returnhash=(); my $i=0; - map { + foreach (@$storearr) { $returnhash{$_}=unescape($pairs[$i]); $i++; - } @storearr; + } return %returnhash; } # --------------------------------------------------------------- del interface sub del { - my ($namespace,@storearr)=@_; + my ($namespace,$storearr,$udomain,$uname)=@_; my $items=''; - map { + foreach (@$storearr) { $items.=escape($_).'&'; - } @storearr; + } $items=~s/\&$//; - return reply("del:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$items", - $ENV{'user.home'}); + if (!$udomain) { $udomain=$ENV{'user.domain'}; } + if (!$uname) { $uname=$ENV{'user.name'}; } + my $uhome=&homeserver($uname,$udomain); + + return &reply("del:$udomain:$uname:$namespace:$items",$uhome); } # -------------------------------------------------------------- dump interface sub dump { - my $namespace=shift; - my $rep=reply("dump:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace", - $ENV{'user.home'}); + my ($namespace,$udomain,$uname,$regexp)=@_; + if (!$udomain) { $udomain=$ENV{'user.domain'}; } + if (!$uname) { $uname=$ENV{'user.name'}; } + my $uhome=&homeserver($uname,$udomain); + if ($regexp) { + $regexp=&escape($regexp); + } else { + $regexp='.'; + } + my $rep=reply("dump:$udomain:$uname:$namespace:$regexp",$uhome); my @pairs=split(/\&/,$rep); my %returnhash=(); - map { + foreach (@pairs) { my ($key,$value)=split(/=/,$_); $returnhash{unescape($key)}=unescape($value); - } @pairs; + } return %returnhash; } # --------------------------------------------------------------- put interface sub put { - my ($namespace,%storehash)=@_; + my ($namespace,$storehash,$udomain,$uname)=@_; + if (!$udomain) { $udomain=$ENV{'user.domain'}; } + if (!$uname) { $uname=$ENV{'user.name'}; } + my $uhome=&homeserver($uname,$udomain); my $items=''; - map { - $items.=escape($_).'='.escape($storehash{$_}).'&'; - } keys %storehash; + foreach (keys %$storehash) { + $items.=&escape($_).'='.&escape($$storehash{$_}).'&'; + } $items=~s/\&$//; - return reply("put:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$items", - $ENV{'user.home'}); + return &reply("put:$udomain:$uname:$namespace:$items",$uhome); } # ------------------------------------------------------ critical put interface sub cput { - my ($namespace,%storehash)=@_; + my ($namespace,$storehash,$udomain,$uname)=@_; + if (!$udomain) { $udomain=$ENV{'user.domain'}; } + if (!$uname) { $uname=$ENV{'user.name'}; } + my $uhome=&homeserver($uname,$udomain); my $items=''; - map { - $items.=escape($_).'='.escape($storehash{$_}).'&'; - } keys %storehash; + foreach (keys %$storehash) { + $items.=escape($_).'='.escape($$storehash{$_}).'&'; + } $items=~s/\&$//; - return critical - ("put:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$items", - $ENV{'user.home'}); + return &critical("put:$udomain:$uname:$namespace:$items",$uhome); } # -------------------------------------------------------------- eget interface sub eget { - my ($namespace,@storearr)=@_; + my ($namespace,$storearr,$udomain,$uname)=@_; my $items=''; - map { + foreach (@$storearr) { $items.=escape($_).'&'; - } @storearr; + } $items=~s/\&$//; - my $rep=reply("eget:$ENV{'user.domain'}:$ENV{'user.name'}:$namespace:$items", - $ENV{'user.home'}); + if (!$udomain) { $udomain=$ENV{'user.domain'}; } + if (!$uname) { $uname=$ENV{'user.name'}; } + my $uhome=&homeserver($uname,$udomain); + my $rep=&reply("eget:$udomain:$uname:$namespace:$items",$uhome); my @pairs=split(/\&/,$rep); my %returnhash=(); my $i=0; - map { + foreach (@$storearr) { $returnhash{$_}=unescape($pairs[$i]); $i++; - } @storearr; + } return %returnhash; } -# ------------------------------------------------- Check for a user priviledge +# ------------------------------------------------- Check for a user privilege sub allowed { my ($priv,$uri)=@_; + + my $orguri=$uri; $uri=&declutter($uri); # Free bre access to adm and meta resources @@ -758,6 +1493,12 @@ sub allowed { return 'F'; } +# Free bre to public access + + if ($priv eq 'bre') { + if (&metadata($uri,'copyright') eq 'public') { return 'F'; } + } + my $thisallowed=''; my $statecond=0; my $courseprivid=''; @@ -778,7 +1519,9 @@ sub allowed { # Course: uri itself is a course my $courseuri=$uri; $courseuri=~s/\_(\d)/\/$1/; - if ($ENV{'user.priv.'.$ENV{'request.role'}.'./'.$courseuri} + $courseuri=~s/^([^\/])/\/$1/; + + if ($ENV{'user.priv.'.$ENV{'request.role'}.'.'.$courseuri} =~/$priv\&([^\:]*)/) { $thisallowed.=$1; } @@ -791,11 +1534,11 @@ sub allowed { # If this is generating or modifying users, exit with special codes - if (':csu:cdc:ccc:cin:cta:cep:ccr:cst:cad:cli:cau:cdg:'=~/\:$priv\:/) { + if (':csu:cdc:ccc:cin:cta:cep:ccr:cst:cad:cli:cau:cdg:cca:'=~/\:$priv\:/) { return $thisallowed; } # -# Gathered so far: system, domain and course wide priviledges +# Gathered so far: system, domain and course wide privileges # # Course: See if uri or referer is an individual resource that is part of # the course @@ -820,17 +1563,29 @@ sub allowed { $checkreferer=0; } } - - if (($ENV{'HTTP_REFERER'}) && ($checkreferer)) { - my $refuri=$ENV{'HTTP_REFERER'}; - $refuri=~s/^http\:\/\/$ENV{'request.host'}//i; - $refuri=&declutter($refuri); + + if ($checkreferer) { + my $refuri=$ENV{'httpref.'.$orguri}; + + unless ($refuri) { + foreach (keys %ENV) { + if ($_=~/^httpref\..*\*/) { + my $pattern=$_; + $pattern=~s/^httpref\.\/res\///; + $pattern=~s/\*/\[\^\/\]\+/g; + $pattern=~s/\//\\\//g; + if ($orguri=~/$pattern/) { + $refuri=$ENV{$_}; + } + } + } + } + if ($refuri) { + $refuri=&declutter($refuri); my @uriparts=split(/\//,$refuri); my $filename=$uriparts[$#uriparts]; my $pathname=$refuri; $pathname=~s/\/$filename$//; - my @filenameparts=split(/\./,$uri); - if (&fileembstyle($filenameparts[$#filenameparts]) ne 'ssi') { if ($ENV{'acc.res.'.$ENV{'request.course.id'}.'.'.$pathname}=~ /\&$filename\:([\d\|]+)\&/) { my $refstatecond=$1; @@ -840,13 +1595,13 @@ sub allowed { $uri=$refuri; $statecond=$refstatecond; } - } } + } } } # -# Gathered now: all priviledges that could apply, and condition number +# Gathered now: all privileges that could apply, and condition number # # # Full or no access? @@ -878,6 +1633,7 @@ sub allowed { if ($envkey=~/^user\.role\.(st|ta)\.([^\.]*)/) { my $courseid=$2; my $roleid=$1.'.'.$2; + $courseid=~s/^\///; my $expiretime=600; if ($ENV{'request.role'} eq $roleid) { $expiretime=120; @@ -931,7 +1687,7 @@ sub allowed { if ($thisallowed=~/C/) { my $rolecode=(split(/\./,$ENV{'request.role'}))[0]; if ($ENV{'course.'.$ENV{'request.course.id'}.'.'.$priv.'.roles.denied'} - =~/\,$rolecode\,/) { + =~/$rolecode/) { &log($ENV{'user.domain'},$ENV{'user.name'},$ENV{'user.host'}, 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '. $ENV{'request.course.id'}); @@ -978,7 +1734,7 @@ sub allowed { sub definerole { if (allowed('mcr','/')) { my ($rolename,$sysrole,$domrole,$courole)=@_; - map { + foreach (split('/',$sysrole)) { my ($crole,$cqual)=split(/\&/,$_); if ($pr{'cr:s'}!~/$crole/) { return "refused:s:$crole"; } if ($pr{'cr:s'}=~/$crole\&/) { @@ -986,8 +1742,8 @@ sub definerole { return "refused:s:$crole&$cqual"; } } - } split('/',$sysrole); - map { + } + foreach (split('/',$domrole)) { my ($crole,$cqual)=split(/\&/,$_); if ($pr{'cr:d'}!~/$crole/) { return "refused:d:$crole"; } if ($pr{'cr:d'}=~/$crole\&/) { @@ -995,8 +1751,8 @@ sub definerole { return "refused:d:$crole&$cqual"; } } - } split('/',$domrole); - map { + } + foreach (split('/',$courole)) { my ($crole,$cqual)=split(/\&/,$_); if ($pr{'cr:c'}!~/$crole/) { return "refused:c:$crole"; } if ($pr{'cr:c'}=~/$crole\&/) { @@ -1004,7 +1760,7 @@ sub definerole { return "refused:c:$crole&$cqual"; } } - } split('/',$courole); + } my $command="encrypt:rolesput:$ENV{'user.domain'}:$ENV{'user.name'}:". "$ENV{'user.domain'}:$ENV{'user.name'}:". "rolesdef_$rolename=". @@ -1015,25 +1771,31 @@ sub definerole { } } -# ------------------------------------------------------------------ Plain Text +# ---------------- Make a metadata query against the network of library servers -sub plaintext { - my $short=shift; - return $prp{$short}; +sub metadata_query { + my ($query,$custom,$customshow)=@_; + my %rhash; + for my $server (keys %libserv) { + unless ($custom or $customshow) { + my $reply=&reply("querysend:".&escape($query),$server); + $rhash{$server}=$reply; + } + else { + my $reply=&reply("querysend:".&escape($query).':'. + &escape($custom).':'.&escape($customshow), + $server); + $rhash{$server}=$reply; + } + } + return \%rhash; } # ------------------------------------------------------------------ Plain Text -sub fileembstyle { - my $ending=shift; - return $fe{$ending}; -} - -# ------------------------------------------------------------ Description Text - -sub filedecription { - my $ending=shift; - return $fd{$ending}; +sub plaintext { + my $short=shift; + return $prp{$short}; } # ----------------------------------------------------------------- Assign Role @@ -1041,29 +1803,238 @@ sub filedecription { sub assignrole { my ($udom,$uname,$url,$role,$end,$start)=@_; my $mrole; - $url=declutter($url); if ($role =~ /^cr\//) { - unless ($url=~/\.course$/) { return 'invalid'; } - unless (allowed('ccr',$url)) { return 'refused'; } + unless (&allowed('ccr',$url)) { + &logthis('Refused custom assignrole: '. + $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '. + $ENV{'user.name'}.' at '.$ENV{'user.domain'}); + return 'refused'; + } $mrole='cr'; } else { - unless (($url=~/\.course$/) || ($url=~/\/$/)) { return 'invalid'; } - unless (allowed('c'+$role)) { return 'refused'; } + my $cwosec=$url; + $cwosec=~s/^\/(\w+)\/(\w+)\/.*/$1\/$2/; + unless (&allowed('c'.$role,$cwosec)) { + &logthis('Refused assignrole: '. + $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '. + $ENV{'user.name'}.' at '.$ENV{'user.domain'}); + return 'refused'; + } $mrole=$role; } my $command="encrypt:rolesput:$ENV{'user.domain'}:$ENV{'user.name'}:". "$udom:$uname:$url".'_'."$mrole=$role"; - if ($end) { $command.='_$end'; } + if ($end) { $command.='_'.$end; } if ($start) { if ($end) { - $command.='_$start'; + $command.='_'.$start; } else { - $command.='_0_$start'; + $command.='_0_'.$start; } } return &reply($command,&homeserver($uname,$udom)); } +# -------------------------------------------------- Modify user authentication +# Overrides without validation + +sub modifyuserauth { + my ($udom,$uname,$umode,$upass)=@_; + my $uhome=&homeserver($uname,$udom); + unless (&allowed('mau',$udom)) { return 'refused'; } + &logthis('Call to modify user authentication '.$udom.', '.$uname.', '. + $umode.' by '.$ENV{'user.name'}.' at '.$ENV{'user.domain'}); + my $reply=&reply('encrypt:changeuserauth:'.$udom.':'.$uname.':'.$umode.':'. + &escape($upass),$uhome); + &log($ENV{'user.domain'},$ENV{'user.name'},$ENV{'user.home'}, + 'Authentication changed for '.$udom.', '.$uname.', '.$umode. + '(Remote '.$ENV{'REMOTE_ADDR'}.'): '.$reply); + &log($udom,,$uname,$uhome, + 'Authentication changed by '.$ENV{'user.domain'}.', '. + $ENV{'user.name'}.', '.$umode. + '(Remote '.$ENV{'REMOTE_ADDR'}.'): '.$reply); + unless ($reply eq 'ok') { + &logthis('Authentication mode error: '.$reply); + return 'error: '.$reply; + } + return 'ok'; +} + +# --------------------------------------------------------------- Modify a user + +sub modifyuser { + my ($udom, $uname, $uid, + $umode, $upass, $first, + $middle, $last, $gene, + $forceid, $desiredhome)=@_; + $udom=~s/\W//g; + $uname=~s/\W//g; + &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '. + $umode.', '.$first.', '.$middle.', '. + $last.', '.$gene.'(forceid: '.$forceid.')'. + (defined($desiredhome) ? ' desiredhome = '.$desiredhome : + ' desiredhome not specified'). + ' by '.$ENV{'user.name'}.' at '.$ENV{'user.domain'}); + my $uhome=&homeserver($uname,$udom); +# ----------------------------------------------------------------- Create User + if (($uhome eq 'no_host') && ($umode) && ($upass)) { + my $unhome=''; + if (defined($desiredhome) && $hostdom{$desiredhome} eq $udom) { + $unhome = $desiredhome; + } elsif($ENV{'course.'.$ENV{'request.course.id'}.'.domain'} eq $udom) { + $unhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'}; + } else { # load balancing routine for determining $unhome + my $tryserver; + my $loadm=10000000; + foreach $tryserver (keys %libserv) { + if ($hostdom{$tryserver} eq $udom) { + my $answer=reply('load',$tryserver); + if (($answer=~/\d+/) && ($answer<$loadm)) { + $loadm=$answer; + $unhome=$tryserver; + } + } + } + } + if (($unhome eq '') || ($unhome eq 'no_host')) { + return 'error: unable to find a home server for '.$uname. + ' in domain '.$udom; + } + my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':'.$umode.':'. + &escape($upass),$unhome); + unless ($reply eq 'ok') { + return 'error: '.$reply; + } + $uhome=&homeserver($uname,$udom); + if (($uhome eq '') || ($uhome eq 'no_host') || ($uhome ne $unhome)) { + return 'error: verify home'; + } + } # End of creation of new user +# ---------------------------------------------------------------------- Add ID + if ($uid) { + $uid=~tr/A-Z/a-z/; + my %uidhash=&idrget($udom,$uname); + if (($uidhash{$uname}) && ($uidhash{$uname}!~/error\:/) + && (!$forceid)) { + unless ($uid eq $uidhash{$uname}) { + return 'error: mismatch '.$uidhash{$uname}.' versus '.$uid; + } + } else { + &idput($udom,($uname => $uid)); + } + } +# -------------------------------------------------------------- Add names, etc + my %names=&get('environment', + ['firstname','middlename','lastname','generation'], + $udom,$uname); + if ($names{'firstname'} =~ m/^error:.*/) { %names=(); } + if ($first) { $names{'firstname'} = $first; } + if ($middle) { $names{'middlename'} = $middle; } + if ($last) { $names{'lastname'} = $last; } + if ($gene) { $names{'generation'} = $gene; } + my $reply = &put('environment', \%names, $udom,$uname); + if ($reply ne 'ok') { return 'error: '.$reply; } + &logthis('Success modifying user '.$udom.', '.$uname.', '.$uid.', '. + $umode.', '.$first.', '.$middle.', '. + $last.', '.$gene.' by '. + $ENV{'user.name'}.' at '.$ENV{'user.domain'}); + return 'ok'; +} + +# -------------------------------------------------------------- Modify student + +sub modifystudent { + my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec, + $end,$start,$forceid,$desiredhome)=@_; + my $cid=''; + unless ($cid=$ENV{'request.course.id'}) { + return 'not_in_class'; + } +# --------------------------------------------------------------- Make the user + my $reply=&modifyuser + ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$forceid, + $desiredhome); + unless ($reply eq 'ok') { return $reply; } + my $uhome=&homeserver($uname,$udom); + if (($uhome eq '') || ($uhome eq 'no_host')) { + return 'error: no such user'; + } +# -------------------------------------------------- Add student to course list + $reply=critical('put:'.$ENV{'course.'.$cid.'.domain'}.':'. + $ENV{'course.'.$cid.'.num'}.':classlist:'. + &escape($uname.':'.$udom).'='. + &escape($end.':'.$start), + $ENV{'course.'.$cid.'.home'}); + unless (($reply eq 'ok') || ($reply eq 'delayed')) { + return 'error: '.$reply; + } +# ---------------------------------------------------- Add student role to user + my $uurl='/'.$cid; + $uurl=~s/\_/\//g; + if ($usec) { + $uurl.='/'.$usec; + } + return &assignrole($udom,$uname,$uurl,'st',$end,$start); +} + +# ------------------------------------------------- Write to course preferences + +sub writecoursepref { + my ($courseid,%prefs)=@_; + $courseid=~s/^\///; + $courseid=~s/\_/\//g; + my ($cdomain,$cnum)=split(/\//,$courseid); + my $chome=homeserver($cnum,$cdomain); + if (($chome eq '') || ($chome eq 'no_host')) { + return 'error: no such course'; + } + my $cstring=''; + foreach (keys %prefs) { + $cstring.=escape($_).'='.escape($prefs{$_}).'&'; + } + $cstring=~s/\&$//; + return reply('put:'.$cdomain.':'.$cnum.':environment:'.$cstring,$chome); +} + +# ---------------------------------------------------------- Make/modify course + +sub createcourse { + my ($udom,$description,$url)=@_; + $url=&declutter($url); + my $cid=''; + unless (&allowed('ccc',$ENV{'user.domain'})) { + return 'refused'; + } + unless ($udom eq $ENV{'user.domain'}) { + return 'refused'; + } +# ------------------------------------------------------------------- Create ID + my $uname=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); + unless (($uhome eq '') || ($uhome eq 'no_host')) { + $uname=substr($$.time,0,5).unpack("H8",pack("I32",time)). + unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'}; + $uhome=&homeserver($uname,$udom); + unless (($uhome eq '') || ($uhome eq 'no_host')) { + return 'error: unable to generate unique course-ID'; + } + } +# ------------------------------------------------------------- Make the course + my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':none::', + $ENV{'user.home'}); + unless ($reply eq 'ok') { return 'error: '.$reply; } + $uhome=&homeserver($uname,$udom); + if (($uhome eq '') || ($uhome eq 'no_host')) { + return 'error: no such course'; + } + &writecoursepref($udom.'_'.$uname, + ('description' => $description, + 'url' => $url)); + return '/'.$udom.'/'.$uname; +} + # ---------------------------------------------------------- Assign Custom Role sub assigncustomrole { @@ -1109,17 +2080,17 @@ sub dirlist { $tryserver); if (($listing ne 'no_such_dir') && ($listing ne 'empty') && ($listing ne 'con_lost')) { - map { + foreach (split(/:/,$listing)) { my ($entry,@stat)=split(/&/,$_); $allusers{$entry}=1; - } split(/:/,$listing); + } } } } my $alluserstr=''; - map { + foreach (sort keys %allusers) { $alluserstr.=$_.'&user:'; - } sort keys %allusers; + } $alluserstr=~s/:$//; return split(/:/,$alluserstr); } @@ -1130,9 +2101,9 @@ sub dirlist { $alldom{$hostdom{$tryserver}}=1; } my $alldomstr=''; - map { + foreach (sort keys %alldom) { $alldomstr.=$perlvar{'lonDocRoot'}.'/res/'.$_.'&domain:'; - } sort keys %alldom; + } $alldomstr=~s/:$//; return split(/:/,$alldomstr); } @@ -1153,18 +2124,18 @@ sub condval { my $condidx=shift; my $result=0; my $allpathcond=''; - map { + foreach (split(/\|/,$condidx)) { if (defined($ENV{'acc.cond.'.$ENV{'request.course.id'}.'.'.$_})) { $allpathcond.= '('.$ENV{'acc.cond.'.$ENV{'request.course.id'}.'.'.$_}.')|'; } - } split(/\|/,$condidx); + } $allpathcond=~s/\|$//; if ($ENV{'request.course.id'}) { if ($allpathcond) { my $operand='|'; my @stack; - map { + foreach ($allpathcond=~/(\d+|\(|\)|\&|\|)/g) { if ($_ eq '(') { push @stack,($operand,$result) } elsif ($_ eq ')') { @@ -1182,18 +2153,51 @@ sub condval { $result=$result>$new?$new:$result; } else { $result=$result>$new?$result:$new; - } + } } - } ($allpathcond=~/(\d+|\(|\)|\&|\|)/g); + } } } return $result; } +# --------------------------------------------------- Course Resourcedata Query + +sub courseresdata { + my ($coursenum,$coursedomain,@which)=@_; + my $coursehom=&homeserver($coursenum,$coursedomain); + my $hashid=$coursenum.':'.$coursedomain; + unless (defined($courseresdatacache{$hashid.'.time'})) { + unless (time-$courseresdatacache{$hashid.'.time'}<300) { + my $coursehom=&homeserver($coursenum,$coursedomain); + if ($coursehom) { + my $dumpreply=&reply('dump:'.$coursedomain.':'.$coursenum. + ':resourcedata:.',$coursehom); + unless ($dumpreply=~/^error\:/) { + $courseresdatacache{$hashid.'.time'}=time; + $courseresdatacache{$hashid}=$dumpreply; + } + } + } + } + my @pairs=split(/\&/,$courseresdatacache{$hashid}); + my %returnhash=(); + foreach (@pairs) { + my ($key,$value)=split(/=/,$_); + $returnhash{unescape($key)}=unescape($value); + } + my $item; + foreach $item (@which) { + if ($returnhash{$item}) { return $returnhash{$item}; } + } + return ''; +} + # --------------------------------------------------------- Value of a Variable sub EXT { - my $varname=shift; + my ($varname,$symbparm)=@_; + unless ($varname) { return ''; } my ($realm,$space,$qualifier,@therest)=split(/\./,$varname); my $rest; if ($therest[0]) { @@ -1208,7 +2212,7 @@ sub EXT { if ($realm eq 'user') { # --------------------------------------------------------------- user.resource if ($space eq 'resource') { - my %restored=&restore; + my %restored=&restore(); return $restored{$qualifierrest}; # ----------------------------------------------------------------- user.access } elsif ($space eq 'access') { @@ -1236,7 +2240,7 @@ sub EXT { # ---------------------------------------------------- Any other user namespace } else { my $item=($rest)?$qualifier.'.'.$rest:$qualifier; - my %reply=&get($space,$item); + my %reply=&get($space,[$item]); return $reply{$item}; } } elsif ($realm eq 'request') { @@ -1249,51 +2253,72 @@ sub EXT { } } elsif ($realm eq 'course') { # ---------------------------------------------------------- course.description - my $section=''; - if ($ENV{'request.course.sec'}) { - $section='_'.$ENV{'request.course.sec'}; - } - return $ENV{'course.'.$ENV{'request.course.id'}.$section.'.'. + return $ENV{'course.'.$ENV{'request.course.id'}.'.'. $spacequalifierrest}; } elsif ($realm eq 'resource') { - if ($ENV{'request.course.id'}) { + if ($ENV{'request.course.id'}) { + +# print '
'.$space.' - '.$qualifier.' - '.$spacequalifierrest; + + # ----------------------------------------------------- Cascading lookup scheme - my $symbparm=&symbread().'.'.$spacequalifierrest; - my $reslevel= - $ENV{'request.course.id'}.'.'.$symbparm; - my $seclevel= - $ENV{'request.course.id'}.'.'. - $ENV{'request.course.sec'}.'.'.$spacequalifierrest; - my $courselevel= + my $symbp; + if ($symbparm) { + $symbp=$symbparm; + } else { + $symbp=&symbread(); + } + my $mapp=(split(/\_\_\_/,$symbp))[0]; + + my $symbparm=$symbp.'.'.$spacequalifierrest; + my $mapparm=$mapp.'___(all).'.$spacequalifierrest; + + my $seclevel= + $ENV{'request.course.id'}.'.['. + $ENV{'request.course.sec'}.'].'.$spacequalifierrest; + my $seclevelr= + $ENV{'request.course.id'}.'.['. + $ENV{'request.course.sec'}.'].'.$symbparm; + my $seclevelm= + $ENV{'request.course.id'}.'.['. + $ENV{'request.course.sec'}.'].'.$mapparm; + + my $courselevel= $ENV{'request.course.id'}.'.'.$spacequalifierrest; + my $courselevelr= + $ENV{'request.course.id'}.'.'.$symbparm; + my $courselevelm= + $ENV{'request.course.id'}.'.'.$mapparm; # ----------------------------------------------------------- first, check user - my %resourcedata=get('resourcedata',($reslevel,$seclevel,$courselevel)); - if ($resourcedata{$reslevel}!~/^error\:/) { - if ($resourcedata{$reslevel}) { return $resourcedata{$reslevel}; } - if ($resourcedata{$seclevel}) { return $resourcedata{$seclevel}; } - if ($resourcedata{$courselevel}) { return $resourcedata{$courselevel}; } + my %resourcedata=get('resourcedata', + [$courselevelr,$courselevelm,$courselevel]); + if (($resourcedata{$courselevelr}!~/^error\:/) && + ($resourcedata{$courselevelr}!~/^con_lost/)) { + + if ($resourcedata{$courselevelr}) { + return $resourcedata{$courselevelr}; } + if ($resourcedata{$courselevelm}) { + return $resourcedata{$courselevelm}; } + if ($resourcedata{$courselevel}) { return $resourcedata{$courselevel}; } + + } else { + if ($resourcedata{$courselevelr}!~/No such file/) { + &logthis("WARNING:". + " Trying to get resource data for ".$ENV{'user.name'}." at " + .$ENV{'user.domain'}.": ".$resourcedata{$courselevelr}. + ""); + } } + # -------------------------------------------------------- second, check course - my $section=''; - if ($ENV{'request.course.sec'}) { - $section='_'.$ENV{'request.course.sec'}; - } - my $reply=&reply('get:'. - $ENV{'course.'.$ENV{'request.course.id'}.$section.'.domain'}.':'. - $ENV{'course.'.$ENV{'request.course.id'}.$section.'.num'}. - ':resourcedata:'. - escape($reslevel).':'.escape($seclevel).':'.escape($courselevel), - $ENV{'course.'.$ENV{'request.course.id'}.$section.'.home'}); - if ($reply!~/^error\:/) { - map { - my ($name,$value)=split(/\=/,$_); - $resourcedata{unescape($name)}=unescape($value); - } split(/\&/,$reply); - if ($resourcedata{$reslevel}) { return $resourcedata{$reslevel}; } - if ($resourcedata{$seclevel}) { return $resourcedata{$seclevel}; } - if ($resourcedata{$courselevel}) { return $resourcedata{$courselevel}; } - } + + my $coursereply=&courseresdata( + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + ($seclevelr,$seclevelm,$seclevel, + $courselevelr,$courselevelm,$courselevel)); + if ($coursereply) { return $coursereply; } # ------------------------------------------------------ third, check map parms my %parmhash=(); @@ -1307,23 +2332,33 @@ sub EXT { } # --------------------------------------------- last, look in resource metadata - my $uri=&declutter($ENV{'request.filename'}); - my $filename=$perlvar{'lonDocRoot'}.'/res/'.$uri.'.meta'; - if (-e $filename) { - my @content; - { - my $fh=Apache::File->new($filename); - @content=<$fh>; - } - if (join('',@content)=~ - /\<$space[^\>]*\>([^\<]*)\<\/$space\>/) { - return $1; - } - } + + $spacequalifierrest=~s/\./\_/; + my $metadata=&metadata($ENV{'request.filename'},$spacequalifierrest); + if ($metadata) { return $metadata; } + $metadata=&metadata($ENV{'request.filename'}, + 'parameter_'.$spacequalifierrest); + if ($metadata) { return $metadata; } + +# ------------------------------------------------------------------ Cascade up + + unless ($space eq '0') { + my ($part,$id)=split(/\_/,$space); + if ($id) { + my $partgeneral=&EXT('resource.'.$part.'.'.$qualifierrest, + $symbparm); + if ($partgeneral) { return $partgeneral; } + } else { + my $resourcegeneral=&EXT('resource.0.'.$qualifierrest, + $symbparm); + if ($resourcegeneral) { return $resourcegeneral; } + } + } + # ---------------------------------------------------- Any other user namespace } elsif ($realm eq 'environment') { # ----------------------------------------------------------------- environment - return $ENV{$spacequalifierrest}; + return $ENV{'environment.'.$spacequalifierrest}; } elsif ($realm eq 'system') { # ----------------------------------------------------------------- system.time if ($space eq 'time') { @@ -1333,6 +2368,137 @@ sub EXT { return ''; } +# ---------------------------------------------------------------- Get metadata + +sub metadata { + my ($uri,$what,$liburi,$prefix,$depthcount)=@_; + + $uri=&declutter($uri); + my $filename=$uri; + $uri=~s/\.meta$//; +# +# Is the metadata already cached? +# Look at timestamp of caching +# Everything is cached by the main uri, libraries are never directly cached +# + unless (abs($metacache{$uri.':cachedtimestamp'}-time)<600) { +# +# Is this a recursive call for a library? +# + if ($liburi) { + $liburi=&declutter($liburi); + $filename=$liburi; + } + my %metathesekeys=(); + unless ($filename=~/\.meta$/) { $filename.='.meta'; } + my $metastring=&getfile($perlvar{'lonDocRoot'}.'/res/'.$filename); + my $parser=HTML::LCParser->new(\$metastring); + my $token; + undef %metathesekeys; + while ($token=$parser->get_token) { + if ($token->[0] eq 'S') { + if (defined($token->[2]->{'package'})) { +# +# This is a package - get package info +# + my $package=$token->[2]->{'package'}; + my $keyroot=''; + if ($prefix) { + $keyroot.='_'.$prefix; + } else { + if (defined($token->[2]->{'part'})) { + $keyroot.='_'.$token->[2]->{'part'}; + } + } + if (defined($token->[2]->{'id'})) { + $keyroot.='_'.$token->[2]->{'id'}; + } + if ($metacache{$uri.':packages'}) { + $metacache{$uri.':packages'}.=','.$package.$keyroot; + } else { + $metacache{$uri.':packages'}=$package.$keyroot; + } + foreach (keys %packagetab) { + if ($_=~/^$package\&/) { + my ($pack,$name,$subp)=split(/\&/,$_); + my $value=$packagetab{$_}; + my $part=$keyroot; + $part=~s/^\_//; + if ($subp eq 'display') { + $value.=' [Part: '.$part.']'; + } + my $unikey='parameter'.$keyroot.'_'.$name; + $metathesekeys{$unikey}=1; + $metacache{$uri.':'.$unikey.'.part'}=$part; + unless + (defined($metacache{$uri.':'.$unikey.'.'.$subp})) { + $metacache{$uri.':'.$unikey.'.'.$subp}=$value; + } + } + } + } else { +# +# 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; + } + if ($prefix) { + $unikey.=$prefix; + } else { + if (defined($token->[2]->{'part'})) { + $unikey.='_'.$token->[2]->{'part'}; + } + } + if (defined($token->[2]->{'id'})) { + $unikey.='_'.$token->[2]->{'id'}; + } + + if ($entry eq 'import') { +# +# Importing a library here +# + if (defined($depthcount)) { $depthcount++; } else + { $depthcount=0; } + if ($depthcount<20) { + foreach (split(/\,/,&metadata($uri,'keys', + $parser->get_text('/import'),$unikey, + $depthcount))) { + $metathesekeys{$_}=1; + } + } + } else { + + if (defined($token->[2]->{'name'})) { + $unikey.='_'.$token->[2]->{'name'}; + } + $metathesekeys{$unikey}=1; + foreach (@{$token->[3]}) { + $metacache{$uri.':'.$unikey.'.'.$_}=$token->[2]->{$_}; + } + unless ( + $metacache{$uri.':'.$unikey}=&HTML::Entities::decode($parser->get_text('/'.$entry)) + ) { $metacache{$uri.':'.$unikey}= + $metacache{$uri.':'.$unikey.'.default'}; + } +# end of not-a-package not-a-library import + } +# end of not-a-package start tag + } +# the next is the end of "start tag" + } + } + $metacache{$uri.':keys'}=join(',',keys %metathesekeys); + $metacache{$uri.':cachedtimestamp'}=time; +# this is the end of "was not already recently cached + } + return $metacache{$uri.':'.$what}; +} + # ------------------------------------------------- Update symbolic store links sub symblist { @@ -1342,9 +2508,9 @@ sub symblist { if (($ENV{'request.course.fn'}) && (%newhash)) { if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.'_symb.db', &GDBM_WRCREAT,0640)) { - map { + foreach (keys %newhash) { $hash{declutter($_)}=$mapname.'___'.$newhash{$_}; - } keys %newhash; + } if (untie(%hash)) { return 'ok'; } @@ -1353,11 +2519,63 @@ sub symblist { return 'error'; } +# --------------------------------------------------------------- Verify a symb + +sub symbverify { + my ($symb,$thisfn)=@_; + $thisfn=&declutter($thisfn); +# direct jump to resource in page or to a sequence - will construct own symbs + if ($thisfn=~/\.(page|sequence)$/) { return 1; } +# check URL part + my ($map,$resid,$url)=split(/\_\_\_/,$symb); + unless (&symbclean($url) eq &symbclean($thisfn)) { return 0; } + + $symb=&symbclean($symb); + + my %bighash; + my $okay=0; + if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', + &GDBM_READER,0640)) { + my $ids=$bighash{'ids_/res/'.$thisfn}; + unless ($ids) { + $ids=$bighash{'ids_/'.$thisfn}; + } + if ($ids) { +# ------------------------------------------------------------------- Has ID(s) + foreach (split(/\,/,$ids)) { + my ($mapid,$resid)=split(/\./,$_); + if ( + &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn) + eq $symb) { + $okay=1; + } + } + } + untie(%bighash); + } + return $okay; +} + +# --------------------------------------------------------------- Clean-up symb + +sub symbclean { + my $symb=shift; + +# remove version from map + $symb=~s/\.(\d+)\.(\w+)\_\_\_/\.$2\_\_\_/; + +# remove version from URL + $symb=~s/\.(\d+)\.(\w+)$/\.$2/; + + return $symb; +} + # ------------------------------------------------------ Return symb list entry sub symbread { my $thisfn=shift; unless ($thisfn) { + if ($ENV{'request.symb'}) { return &symbclean($ENV{'request.symb'}); } $thisfn=$ENV{'request.filename'}; } $thisfn=declutter($thisfn); @@ -1398,7 +2616,7 @@ sub symbread { } else { # ------------------------------------------ There is more than one possibility my $realpossible=0; - map { + foreach (@possibilities) { my $file=$bighash{'src_'.$_}; if (&allowed('bre',$file)) { my ($mapid,$resid)=split(/\./,$_); @@ -1408,7 +2626,7 @@ sub symbread { '___'.$resid; } } - } @possibilities; + } if ($realpossible!=1) { $syval=''; } } } @@ -1416,7 +2634,7 @@ sub symbread { } } if ($syval) { - return $syval.'___'.$thisfn; + return &symbclean($syval.'___'.$thisfn); } } &appenv('request.ambiguous' => $thisfn); @@ -1438,20 +2656,50 @@ sub numval { } sub rndseed { - my $symb; - unless ($symb=&symbread()) { return time; } - my $symbchck=unpack("%32C*",$symb); - my $symbseed=numval($symb)%$symbchck; - my $namechck=unpack("%32C*",$ENV{'user.name'}); - my $nameseed=numval($ENV{'user.name'})%$namechck; - return int( $symbseed - .$nameseed - .unpack("%32C*",$ENV{'user.domain'}) - .unpack("%32C*",$ENV{'request.course.id'}) - .$namechck - .$symbchck); + my ($symb,$courseid,$domain,$username)=@_; + if (!$symb) { + unless ($symb=&symbread()) { return time; } + } + if (!$courseid) { $courseid=$ENV{'request.course.id'};} + if (!$domain) {$domain=$ENV{'user.domain'};} + if (!$username) {$username=$ENV{'user.name'};} + { + use integer; + my $symbchck=unpack("%32C*",$symb) << 27; + my $symbseed=numval($symb) << 22; + my $namechck=unpack("%32C*",$username) << 17; + my $nameseed=numval($username) << 12; + my $domainseed=unpack("%32C*",$domain) << 7; + my $courseseed=unpack("%32C*",$courseid); + my $num=$symbseed+$nameseed+$domainseed+$courseseed+$namechck+$symbchck; + #uncommenting these lines can break things! + #&Apache::lonxml::debug("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck"); + #&Apache::lonxml::debug("rndseed :$num:$symb"); + return $num; + } } +sub ireceipt { + my ($funame,$fudom,$fucourseid,$fusymb)=@_; + my $cuname=unpack("%32C*",$funame); + my $cudom=unpack("%32C*",$fudom); + my $cucourseid=unpack("%32C*",$fucourseid); + my $cusymb=unpack("%32C*",$fusymb); + my $cunique=unpack("%32C*",$perlvar{'lonReceipt'}); + return unpack("%32C*",$perlvar{'lonHostID'}).'-'. + ($cunique%$cuname+ + $cunique%$cudom+ + $cusymb%$cuname+ + $cusymb%$cudom+ + $cucourseid%$cuname+ + $cucourseid%$cudom); +} + +sub receipt { + return &ireceipt($ENV{'user.name'},$ENV{'user.domain'}, + $ENV{'request.course.id'},&symbread()); +} + # ------------------------------------------------------------ Serves up a file # returns either the contents of the file or a -1 sub getfile { @@ -1487,7 +2735,7 @@ sub filelocation { sub hreflocation { my ($dir,$file)=@_; - unless (($_=~/^http:\/\//i) || ($_=~/^\//)) { + unless (($file=~/^http:\/\//i) || ($file=~/^\//)) { my $finalpath=filelocation($dir,$file); $finalpath=~s/^\/home\/httpd\/html//; return $finalpath; @@ -1524,9 +2772,15 @@ sub unescape { # ================================================================ Main Program -sub BEGIN { -if ($readit ne 'done') { +sub goodbye { + &logthis("Starting Shut down"); + &flushcourselogs(); + &logthis("Shutting down"); +} + +BEGIN { # ------------------------------------------------------------ Read access.conf + unless ($readit) { { my $config=Apache::File->new("/etc/httpd/conf/access.conf"); @@ -1544,9 +2798,11 @@ if ($readit ne 'done') { my $config=Apache::File->new("$perlvar{'lonTabDir'}/hosts.tab"); while (my $configline=<$config>) { + chomp($configline); my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); $hostname{$id}=$name; $hostdom{$id}=$domain; + $hostip{$id}=$ip; if ($role eq 'library') { $libserv{$id}=$name; } } } @@ -1568,8 +2824,10 @@ if ($readit ne 'done') { while (my $configline=<$config>) { chomp($configline); + if ($configline) { my ($role,$perm)=split(/ /,$configline); if ($perm ne '') { $pr{$role}=$perm; } + } } } @@ -1579,28 +2837,447 @@ if ($readit ne 'done') { while (my $configline=<$config>) { chomp($configline); + if ($configline) { my ($short,$plain)=split(/:/,$configline); if ($plain ne '') { $prp{$short}=$plain; } + } } } -# ------------------------------------------------------------- Read file types +# ---------------------------------------------------------- Read package table { - my $config=Apache::File->new("$perlvar{'lonTabDir'}/filetypes.tab"); + my $config=Apache::File->new("$perlvar{'lonTabDir'}/packages.tab"); while (my $configline=<$config>) { chomp($configline); - my ($ending,$emb,@descr)=split(/\s+/,$configline); - if ($descr[0] ne '') { - $fe{$ending}=$emb; - $fd{$ending}=join(' ',@descr); + my ($short,$plain)=split(/:/,$configline); + my ($pack,$name)=split(/\&/,$short); + if ($plain ne '') { + $packagetab{$pack.'&'.$name.'&name'}=$name; + $packagetab{$short}=$plain; } } } +%metacache=(); -$readit='done'; +$processmarker=$$.'_'.time.'_'.$perlvar{'lonHostID'}; +$dumpcount=0; + +&logtouch(); &logthis('INFO: Read configuration'); +$readit=1; } } + 1; +__END__ + +=head1 NAME + +Apache::lonnet - TCP networking package + +=head1 SYNOPSIS + +Invoked by other LON-CAPA modules. + + &Apache::lonnet::SUBROUTINENAME(ARGUMENTS); + +=head1 INTRODUCTION + +This module provides subroutines which interact with the +lonc/lond (TCP) network layer of LON-CAPA. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + +=head1 HANDLER SUBROUTINE + +There is no handler routine for this module. + +=head1 OTHER SUBROUTINES + +=over 4 + +=item * + +logtouch() : make sure the logfile, lonnet.log, exists + +=item * + +logthis() : append message to lonnet.log + +=item * + +logperm() : append a permanent message to lonnet.perm.log + +=item * + +subreply() : non-critical communication, called by &reply + +=item * + +reply() : makes two attempts to pass message; logs refusals and rejections + +=item * + +reconlonc() : tries to reconnect lonc client processes. + +=item * + +critical() : passes a critical message to another server; if cannot get +through then place message in connection buffer + +=item * + +appenv(%hash) : read in current user environment, append new environment +values to make new user environment + +=item * + +delenv($varname) : read in current user environment, remove all values +beginning with $varname, write new user environment (note: flock is used +to prevent conflicting shared read/writes with file) + +=item * + +spareserver() : find server with least workload from spare.tab + +=item * + +queryauthenticate($uname,$udom) : try to determine user's current +authentication scheme + +=item * + +authenticate($uname,$upass,$udom) : try to authenticate user from domain's lib +servers (first use the current one) + +=item * + +homeserver($uname,$udom) : find the homebase for a user from domain's lib +servers + +=item * + +idget($udom,@ids) : find the usernames behind a list of IDs (returns hash: +id=>name,id=>name) + +=item * + +idrget($udom,@unames) : find the IDs behind a list of usernames (returns hash: +name=>id,name=>id) + +=item * + +idput($udom,%ids) : store away a list of names and associated IDs + +=item * + +usection($domain,$user,$courseid) : output of section name/number or '' for +"not in course" and '-1' for "no section" + +=item * + +userenvironment($domain,$user,$what) : puts out any environment parameter +for a user + +=item * + +subscribe($fname) : subscribe to a resource, return URL if possible + +=item * + +repcopy($filename) : replicate file + +=item * + +ssi($url,%hash) : server side include, does a complete request cycle on url to +localhost, posts hash + +=item * + +log($domain,$name,$home,$message) : write to permanent log for user; use +critical subroutine + +=item * + +flushcourselogs() : flush (save) buffer logs and access logs + +=item * + +courselog($what) : save message for course in hash + +=item * + +courseacclog($what) : save message for course using &courselog(). Perform +special processing for specific resource types (problems, exams, quizzes, etc). + +=item * + +countacc($url) : count the number of accesses to a given URL + +=item * + +sub checkout($symb,$tuname,$tudom,$tcrsid) : check out an item + +=item * + +sub checkin($token) : check in an item + +=item * + +sub expirespread($uname,$udom,$stype,$usymb) : set expire date for spreadsheet + +=item * + +devalidate($symb) : devalidate spreadsheets + +=item * + +hash2str(%hash) : convert a hash into a string complete with escaping and '=' +and '&' separators, supports elements that are arrayrefs and hashrefs + +=item * + +hashref2str($hashref) : convert a hashref into a string complete with +escaping and '=' and '&' separators, supports elements that are +arrayrefs and hashrefs + +=item * + +arrayref2str($arrayref) : convert an arrayref into a string complete +with escaping and '&' separators, supports elements that are arrayrefs +and hashrefs + +=item * + +str2hash($string) : convert string to hash using unescaping and +splitting on '=' and '&', supports elements that are arrayrefs and +hashrefs + +=item * + +str2array($string) : convert string to hash using unescaping and +splitting on '&', supports elements that are arrayrefs and hashrefs + +=item * + +tmpreset($symb,$namespace,$domain,$stuname) : temporary storage + +=item * + +tmprestore($symb,$namespace,$domain,$stuname) : temporary restore + +=item * + +store($storehash,$symb,$namespace,$domain,$stuname) : stores hash permanently +for this url; hashref needs to be given and should be a \%hashname; the +remaining args aren't required and if they aren't passed or are '' they will +be derived from the ENV + +=item * + +cstore($storehash,$symb,$namespace,$domain,$stuname) : same as store but +uses critical subroutine + +=item * + +restore($symb,$namespace,$domain,$stuname) : returns hash for this symb; +all args are optional + +=item * + +coursedescription($courseid) : course description + +=item * + +rolesinit($domain,$username,$authhost) : get user privileges + +=item * + +get($namespace,$storearr,$udomain,$uname) : returns hash with keys from array +reference filled in from namesp ($udomain and $uname are optional) + +=item * + +del($namespace,$storearr,$udomain,$uname) : deletes keys out of array from +namesp ($udomain and $uname are optional) + +=item * + +dump($namespace,$udomain,$uname,$regexp) : +dumps the complete (or key matching regexp) namespace into a hash +($udomain, $uname and $regexp are optional) + +=item * + +put($namespace,$storehash,$udomain,$uname) : stores hash in namesp +($udomain and $uname are optional) + +=item * + +cput($namespace,$storehash,$udomain,$uname) : critical put +($udomain and $uname are optional) + +=item * + +eget($namespace,$storearr,$udomain,$uname) : returns hash with keys from array +reference filled in from namesp (encrypts the return communication) +($udomain and $uname are optional) + +=item * + +allowed($priv,$uri) : check for a user privilege; returns codes for allowed +actions + F: full access + U,I,K: authentication modes (cxx only) + '': forbidden + 1: user needs to choose course + 2: browse allowed + +=item * + +definerole($rolename,$sysrole,$domrole,$courole) : define role; define a custom +role rolename set privileges in format of lonTabs/roles.tab for system, domain, +and course level + +=item * + +metadata_query($query,$custom,$customshow) : make a metadata query against the +network of library servers; returns file handle of where SQL and regex results +will be stored for query + +=item * + +plaintext($short) : return value in %prp hash (rolesplain.tab); plain text +explanation of a user role term + +=item * + +assignrole($udom,$uname,$url,$role,$end,$start) : assign role; give a role to a +user for the level given by URL. Optional start and end dates (leave empty +string or zero for "no date") + +=item * + +modifyuserauth($udom,$uname,$umode,$upass) : modify user authentication + +=item * + +modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene) : +modify user + +=item * + +modifystudent($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec, +$end,$start) : modify student + +=item * + +writecoursepref($courseid,%prefs) : write preferences for a course + +=item * + +createcourse($udom,$description,$url) : make/modify course + +=item * + +assigncustomrole($udom,$uname,$url,$rdom,$rnam,$rolename,$end,$start) : assign +custom role; give a custom role to a user for the level given by URL. Specify +name and domain of role author, and role name + +=item * + +revokerole($udom,$uname,$url,$role) : revoke a role for url + +=item * + +revokecustomrole($udom,$uname,$url,$role) : revoke a custom role + +=item * + +dirlist($uri) : return directory list based on URI + +=item * + +directcondval($number) : get current value of a condition; reads from a state +string + +=item * + +condval($condidx) : value of condition index based on state + +=item * + +EXT($varname,$symbparm) : value of a variable + +=item * + +metadata($uri,$what,$liburi,$prefix,$depthcount) : get metadata; returns the +metadata entry for a file; entry='keys', returns a comma separated list of keys + +=item * + +symblist($mapname,%newhash) : update symbolic storage links + +=item * + +symbread($filename) : return symbolic list entry (filename argument optional); +returns the data handle + +=item * + +numval($salt) : return random seed value (addend for rndseed) + +=item * + +rndseed($symb,$courseid,$domain,$username) : create a random sum; returns +a random seed, all arguments are optional, if they aren't sent it uses the +environment to derive them. Note: if symb isn't sent and it can't get one +from &symbread it will use the current time as its return value + +=item * + +ireceipt($funame,$fudom,$fucourseid,$fusymb) : return unique, +unfakeable, receipt + +=item * + +receipt() : API to ireceipt working off of ENV values; given out to users + +=item * + +getfile($file) : serves up a file, returns the contents of a file or -1; +replicates and subscribes to the file + +=item * + +filelocation($dir,$file) : returns file system location of a file based on URI; +meant to be "fairly clean" absolute reference + +=item * + +hreflocation($dir,$file) : returns file system location or a URL; same as +filelocation except for hrefs + +=item * + +declutter() : declutters URLs (remove docroot, beginning slashes, 'res' etc) + +=item * + +escape() : unpack non-word characters into CGI-compatible hex codes + +=item * + +unescape() : pack CGI-compatible hex codes into actual non-word ASCII character + +=item * + +goodbye() : flush course logs and log shutting down; it is called in srm.conf +as a PerlChildExitHandler + +=back + +=cut