--- loncom/lonnet/perl/lonnet.pm 2001/12/06 21:03:02 1.185 +++ loncom/lonnet/perl/lonnet.pm 2002/06/24 14:16:58 1.242 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.185 2001/12/06 21:03:02 www Exp $ +# $Id: lonnet.pm,v 1.242 2002/06/24 14:16:58 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -62,109 +62,14 @@ # 11/17,11/20,11/22,11/29 Gerd Kortemeyer # 12/5 Matthew Hall # 12/5 Guy Albertelli -# 12/6 Gerd Kortemeyer -# -# $Id: lonnet.pm,v 1.185 2001/12/06 21:03:02 www Exp $ +# 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 # ### -# Functions for use by content handlers: -# -# metadata_query(sql-query-string,custom-metadata-regex) : -# returns file handle of where sql and -# regex results will be stored for query -# 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 privileges 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(hashref,symb,courseid,udom,uname) -# : 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 -# cstore(hashref,symb,courseid,udom,uname) -# : same as store but uses the critical interface to -# guarentee a store -# restore(symb,courseid,udom,uname) -# : returns hash for this symb, all args are optional -# if they aren't given they will be derived from the -# current enviroment -# -# -# for the next 6 functions udom and uname are optional -# if supplied they use udom as the domain and uname -# as the username for the function (supply a courseid -# for the uname if you want a course database) -# if not supplied it uses %ENV and looks at -# user. attribute for the values -# -# eget(namesp,arrayref,udom,uname) -# : returns hash with keys from array reference filled -# in from namesp (encrypts the return communication) -# get(namesp,arrayref,udom,uname) -# : returns hash with keys from array reference filled -# in from namesp -# dump(namesp,udom,uname) : dumps the complete namespace into a hash -# del(namesp,array,udom,uname) : deletes keys out of array from namesp -# put(namesp,hash,udom,uname) : stores hash in namesp -# cput(namesp,hash,udom,uname) : critical put -# -# -# 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([symb,courseid,domain,uname]) -# : returns a random seed, all arguments are optional, -# if they aren't sent it use 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 it's return -# receipt() : returns a receipt to be given out to users -# 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 fairly 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 -# usection(domain,user,courseid) : output of section name/number or '' for -# "not in course" and '-1' for "no section" -# userenvironment(domain,user,what) : puts out any environment parameter -# for a user -# idput(domain,hash) : writes IDs for users from hash (name=>id,name=>id) -# idget(domain,array): returns hash with usernames (id=>name,id=>name) for -# an array of IDs -# idrget(domain,array): returns hash with IDs for usernames (name=>id,...) for -# an array of names -# metadata(file,entry): returns the metadata entry for a file. entry='keys' -# returns a comma separated list of keys -# - package Apache::lonnet; use strict; @@ -172,12 +77,16 @@ use Apache::File; use LWP::UserAgent(); use HTTP::Headers; use vars -qw(%perlvar %hostname %homecache %hostip %spareid %hostdom %libserv %pr %prp %fe %fd %metacache %packagetab %courselogs %accesshash $processmarker); +qw(%perlvar %hostname %homecache %badServerCache %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::TokeParser; +use HTML::LCParser; use Fcntl qw(:flock); +my $readit; # --------------------------------------------------------------------- Logging @@ -228,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"); @@ -324,7 +249,7 @@ 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{$_} @@ -333,7 +258,7 @@ sub appenv { } else { $ENV{$_}=$newenv{$_}; } - } keys %newenv; + } my $lockfh; unless ($lockfh=Apache::File->new("$ENV{'user.environment'}")) { @@ -415,9 +340,9 @@ sub delenv { $fh->close(); return 'error: '.$!; } - map { + foreach (@oldenv) { unless ($_=~/^$delthis/) { print $fh $_; } - } @oldenv; + } $fh->close(); } return 'ok'; @@ -439,6 +364,41 @@ 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 { @@ -482,6 +442,7 @@ sub queryauthenticate { 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'}); @@ -520,19 +481,23 @@ sub authenticate { # ---------------------- Find the homebase for a user from domain's lib servers sub homeserver { - my ($uname,$udom)=@_; - + my ($uname,$udom,$ignoreBadCache)=@_; my $index="$uname:$udom"; - if ($homecache{$index}) { return "$homecache{$index}"; } - + if ($homecache{$index}) { + return "$homecache{$index}"; + } my $tryserver; foreach $tryserver (keys %libserv) { + next if ($ignoreBadCache ne 'true' && + exists($badServerCache{$tryserver})); if ($hostdom{$tryserver} eq $udom) { my $answer=reply("home:$udom:$uname",$tryserver); if ($answer eq 'found') { - $homecache{$index}=$tryserver; + $homecache{$index}=$tryserver; return $tryserver; - } + } elsif ($answer eq 'no_host') { + $badServerCache{$tryserver}=1; + } } } return 'no_host'; @@ -570,9 +535,9 @@ sub idget { sub idrget { my ($udom,@unames)=@_; my %returnhash=(); - map { + foreach (@unames) { $returnhash{$_}=(&userenvironment($udom,$_,'id'))[1]; - } @unames; + } return %returnhash; } @@ -581,7 +546,7 @@ sub idrget { sub idput { my ($udom,%ids)=@_; my %servers=(); - map { + foreach (keys %ids) { my $uhom=&homeserver($_,$udom); if ($uhom ne 'no_host') { my $id=&escape($ids{$_}); @@ -594,10 +559,10 @@ sub idput { } &critical('put:'.$udom.':'.$unam.':environment:id='.$id,$uhom); } - } keys %ids; - map { + } + foreach (keys %servers) { &critical('idput:'.$udom.':'.$servers{$_},$_); - } keys %servers; + } } # ------------------------------------- Find the section of student in a course @@ -606,7 +571,8 @@ sub usection { my ($udom,$unam,$courseid)=@_; $courseid=~s/\_/\//g; $courseid=~s/^(\w)/\/$1/; - map { + foreach (split(/\&/,&reply('dump:'.$udom.':'.$unam.':roles', + &homeserver($unam,$udom)))) { my ($key,$value)=split(/\=/,$_); $key=&unescape($key); if ($key=~/^$courseid(?:\/)*(\w+)*\_st$/) { @@ -623,8 +589,7 @@ sub usection { } unless ($notactive) { return $section; } } - } split(/\&/,&reply('dump:'.$udom.':'.$unam.':roles', - &homeserver($unam,$udom))); + } return '-1'; } @@ -666,6 +631,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); @@ -731,7 +697,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); } @@ -753,12 +719,11 @@ sub log { sub flushcourselogs { &logthis('Flushing course log buffers'); - map { + foreach (keys %courselogs) { my $crsid=$_; - if (&reply('log:'.$ENV{'course.'.$crsid.'.domain'}.':'. - $ENV{'course.'.$crsid.'.num'}.':'. - &escape($courselogs{$crsid}), - $ENV{'course.'.$crsid.'.home'}) eq 'ok') { + if (&reply('log:'.$coursedombuf{$crsid}.':'. + &escape($courselogs{$crsid}), + $coursehombuf{$crsid}) eq 'ok') { delete $courselogs{$crsid}; } else { &logthis('Failed to flush log buffer for '.$crsid); @@ -768,22 +733,28 @@ sub flushcourselogs { delete $courselogs{$crsid}; } } - } keys %courselogs; + } &logthis('Flushing access logs'); - map { + 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}; } - } keys %accesshash; + } + $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 { @@ -798,12 +769,13 @@ sub courseacclog { my $fnsymb=shift; unless ($ENV{'request.course.id'}) { return ''; } my $what=$fnsymb.':'.$ENV{'user.name'}.':'.$ENV{'user.domain'}; - if ($what=~/(problem|exam|quiz|assess|survey|form)$/) { - map { + if ($fnsymb=~/(problem|exam|quiz|assess|survey|form)$/) { + $what.=':POST'; + foreach (keys %ENV) { if ($_=~/^form\.(.*)/) { $what.=':'.$1.'='.$ENV{$_}; } - } keys %ENV; + } } &courselog($what); } @@ -812,7 +784,7 @@ sub countacc { my $url=&declutter(shift); unless ($ENV{'request.course.id'}) { return ''; } $accesshash{$ENV{'request.course.id'}.'___'.$url.'___course'}=1; - my $key=$processmarker.'___'.$url.'___count'; + my $key=$processmarker.'_'.$dumpcount.'___'.$url.'___count'; if (defined($accesshash{$key})) { $accesshash{$key}++; } else { @@ -827,6 +799,7 @@ sub checkout { my $now=time; my $lonhost=$perlvar{'lonHostID'}; my $infostr=&escape( + 'CHECKOUTTOKEN&'. $tuname.'&'. $tudom.'&'. $tcrsid.'&'. @@ -876,7 +849,7 @@ sub checkin { $lonhost=~tr/A-Z/a-z/; my $dtoken=$ta.'_'.$hostip{$lonhost}.'_'.$tb; $dtoken=~s/\W/\_/g; - my ($tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)= + my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)= split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost))); unless (($tuname) && ($tudom)) { @@ -946,10 +919,55 @@ sub devalidate { } } +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=''; - map { $result.=escape($_).'='.escape($hash{$_}).'&'; } keys %hash; + 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; } @@ -957,11 +975,41 @@ sub hash2str { sub str2hash { my ($string) = @_; my %returnhash; - map { + foreach (split(/\&/,$string)) { my ($name,$value)=split(/\=/,$_); - $returnhash{&unescape($name)}=&unescape($value); - } split(/\&/,$string); - return %returnhash; + $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 @@ -1096,20 +1144,26 @@ sub store { 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 (!$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 { + foreach (keys %$storehash) { $namevalue.=escape($_).'='.escape($$storehash{$_}).'&'; - } keys %$storehash; + } $namevalue=~s/\&$//; + &courselog($symb.':'.$stuname.':'.$domain.':STORE:'.$namevalue); return reply("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); } @@ -1121,22 +1175,29 @@ sub cstore { 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 (!$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 { + foreach (keys %$storehash) { $namevalue.=escape($_).'='.escape($$storehash{$_}).'&'; - } keys %$storehash; + } $namevalue=~s/\&$//; - return critical("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); + &courselog($symb.':'.$stuname.':'.$domain.':CSTORE:'.$namevalue); + return critical + ("store:$domain:$stuname:$namespace:$symb:$namevalue","$home"); } # --------------------------------------------------------------------- Restore @@ -1150,24 +1211,28 @@ sub restore { if (!$symb) { unless ($symb=escape(&symbread())) { return ''; } } else { - $symb=&escape($symb); + $symb=&escape(&symbclean($symb)); + } + if (!$namespace) { + unless ($namespace=$ENV{'request.course.id'}) { + return ''; + } } - 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); + } my $version; for ($version=1;$version<=$returnhash{'version'};$version++) { - map { + foreach (split(/\:/,$returnhash{$version.':keys'})) { $returnhash{$_}=$returnhash{$version.':'.$_}; - } split(/\:/,$returnhash{$version.':keys'}); + } } return %returnhash; } @@ -1218,7 +1283,7 @@ sub rolesinit { my $thesestr; if ($rolesdump ne '') { - map { + foreach (split(/&/,$rolesdump)) { if ($_!~/^rolesdef\&/) { my ($area,$role)=split(/=/,$_); $area=~s/\_\w\w$//; @@ -1274,14 +1339,14 @@ sub rolesinit { } } } - } split(/&/,$rolesdump); + } my $adv=0; my $author=0; - map { + foreach (keys %allroles) { %thesepriv=(); if (($_!~/^st/) && ($_!~/^ta/) && ($_!~/^cm/)) { $adv=1; } if (($_=~/^au/) || ($_=~/^ca/)) { $author=1; } - map { + foreach (split(/:/,$allroles{$_})) { if ($_ ne '') { my ($privilege,$restrictions)=split(/&/,$_); if ($restrictions eq '') { @@ -1292,11 +1357,11 @@ sub rolesinit { } } } - } 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; @@ -1309,9 +1374,9 @@ sub rolesinit { sub get { my ($namespace,$storearr,$udomain,$uname)=@_; my $items=''; - map { + foreach (@$storearr) { $items.=escape($_).'&'; - } @$storearr; + } $items=~s/\&$//; if (!$udomain) { $udomain=$ENV{'user.domain'}; } if (!$uname) { $uname=$ENV{'user.name'}; } @@ -1321,10 +1386,10 @@ sub get { my @pairs=split(/\&/,$rep); my %returnhash=(); my $i=0; - map { + foreach (@$storearr) { $returnhash{$_}=unescape($pairs[$i]); $i++; - } @$storearr; + } return %returnhash; } @@ -1333,9 +1398,9 @@ sub get { sub del { my ($namespace,$storearr,$udomain,$uname)=@_; my $items=''; - map { + foreach (@$storearr) { $items.=escape($_).'&'; - } @$storearr; + } $items=~s/\&$//; if (!$udomain) { $udomain=$ENV{'user.domain'}; } if (!$uname) { $uname=$ENV{'user.name'}; } @@ -1347,17 +1412,22 @@ sub del { # -------------------------------------------------------------- dump interface sub dump { - my ($namespace,$udomain,$uname)=@_; + my ($namespace,$udomain,$uname,$regexp)=@_; if (!$udomain) { $udomain=$ENV{'user.domain'}; } if (!$uname) { $uname=$ENV{'user.name'}; } my $uhome=&homeserver($uname,$udomain); - my $rep=reply("dump:$udomain:$uname:$namespace",$uhome); + 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; } @@ -1369,9 +1439,9 @@ sub put { if (!$uname) { $uname=$ENV{'user.name'}; } my $uhome=&homeserver($uname,$udomain); my $items=''; - map { + foreach (keys %$storehash) { $items.=&escape($_).'='.&escape($$storehash{$_}).'&'; - } keys %$storehash; + } $items=~s/\&$//; return &reply("put:$udomain:$uname:$namespace:$items",$uhome); } @@ -1384,9 +1454,9 @@ sub cput { if (!$uname) { $uname=$ENV{'user.name'}; } my $uhome=&homeserver($uname,$udomain); my $items=''; - map { + foreach (keys %$storehash) { $items.=escape($_).'='.escape($$storehash{$_}).'&'; - } keys %$storehash; + } $items=~s/\&$//; return &critical("put:$udomain:$uname:$namespace:$items",$uhome); } @@ -1396,9 +1466,9 @@ sub cput { sub eget { my ($namespace,$storearr,$udomain,$uname)=@_; my $items=''; - map { + foreach (@$storearr) { $items.=escape($_).'&'; - } @$storearr; + } $items=~s/\&$//; if (!$udomain) { $udomain=$ENV{'user.domain'}; } if (!$uname) { $uname=$ENV{'user.name'}; } @@ -1407,10 +1477,10 @@ sub eget { my @pairs=split(/\&/,$rep); my %returnhash=(); my $i=0; - map { + foreach (@$storearr) { $returnhash{$_}=unescape($pairs[$i]); $i++; - } @$storearr; + } return %returnhash; } @@ -1431,7 +1501,21 @@ sub allowed { # Free bre to public access if ($priv eq 'bre') { - if (&metadata($uri,'copyright') eq 'public') { return 'F'; } + my $copyright=&metadata($uri,'copyright'); + if ($copyright eq 'public') { return 'F'; } + if ($copyright eq 'priv') { + $uri=~/([^\/]+)\/([^\/]+)\//; + unless (($ENV{'user.name'} eq $2) && ($ENV{'user.domain'} eq $1)) { + return ''; + } + } + if ($copyright eq 'domain') { + $uri=~/([^\/]+)\/([^\/]+)\//; + unless (($ENV{'user.domain'} eq $1) || + ($ENV{'course.'.$ENV{'request.course.id'}.'.domain'} eq $1)) { + return ''; + } + } } my $thisallowed=''; @@ -1479,19 +1563,16 @@ sub allowed { # the course if ($ENV{'request.course.id'}) { + $courseprivid=$ENV{'request.course.id'}; if ($ENV{'request.course.sec'}) { $courseprivid.='/'.$ENV{'request.course.sec'}; } $courseprivid=~s/\_/\//; my $checkreferer=1; - my @uriparts=split(/\//,$uri); - my $filename=$uriparts[$#uriparts]; - my $pathname=$uri; - $pathname=~s/\/$filename$//; - if ($ENV{'acc.res.'.$ENV{'request.course.id'}.'.'.$pathname}=~ - /\&$filename\:([\d\|]+)\&/) { - $statecond=$1; + my ($match,$cond)=&is_on_map($uri); + if ($match) { + $statecond=$cond; if ($ENV{'user.priv.'.$ENV{'request.role'}.'./'.$courseprivid} =~/$priv\&([^\:]*)/) { $thisallowed.=$1; @@ -1501,9 +1582,8 @@ sub allowed { if ($checkreferer) { my $refuri=$ENV{'httpref.'.$orguri}; - unless ($refuri) { - map { + foreach (keys %ENV) { if ($_=~/^httpref\..*\*/) { my $pattern=$_; $pattern=~s/^httpref\.\/res\///; @@ -1513,17 +1593,14 @@ sub allowed { $refuri=$ENV{$_}; } } - } keys %ENV; + } } + if ($refuri) { $refuri=&declutter($refuri); - my @uriparts=split(/\//,$refuri); - my $filename=$uriparts[$#uriparts]; - my $pathname=$refuri; - $pathname=~s/\/$filename$//; - if ($ENV{'acc.res.'.$ENV{'request.course.id'}.'.'.$pathname}=~ - /\&$filename\:([\d\|]+)\&/) { - my $refstatecond=$1; + my ($match,$cond)=&is_on_map($refuri); + if ($match) { + my $refstatecond=$cond; if ($ENV{'user.priv.'.$ENV{'request.role'}.'./'.$courseprivid} =~/$priv\&([^\:]*)/) { $thisallowed.=$1; @@ -1582,7 +1659,7 @@ sub allowed { || ($ENV{$prefix.'res.'.$uri.'.lock.sections'} eq 'all')) { if ($ENV{$prefix.'res.'.$uri.'.lock.expire'}>time) { &log($ENV{'user.domain'},$ENV{'user.name'}, - $ENV{'user.host'}, + $ENV{'user.home'}, 'Locked by res: '.$priv.' for '.$uri.' due to '. $cdom.'/'.$cnum.'/'.$csec.' expire '. $ENV{$prefix.'priv.'.$priv.'.lock.expire'}); @@ -1593,7 +1670,7 @@ sub allowed { || ($ENV{$prefix.'priv.'.$priv.'.lock.sections'} eq 'all')) { if ($ENV{'priv.'.$priv.'.lock.expire'}>time) { &log($ENV{'user.domain'},$ENV{'user.name'}, - $ENV{'user.host'}, + $ENV{'user.home'}, 'Locked by priv: '.$priv.' for '.$uri.' due to '. $cdom.'/'.$cnum.'/'.$csec.' expire '. $ENV{$prefix.'priv.'.$priv.'.lock.expire'}); @@ -1621,13 +1698,22 @@ sub allowed { if ($thisallowed=~/C/) { my $rolecode=(split(/\./,$ENV{'request.role'}))[0]; + my $unamedom=$ENV{'user.name'}.':'.$ENV{'user.domain'}; 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'}); return ''; } + + if ($ENV{'course.'.$ENV{'request.course.id'}.'.'.$priv.'.users.denied'} + =~/$unamedom/) { + &log($ENV{'user.domain'},$ENV{'user.name'},$ENV{'user.host'}, + 'Denied by user: '.$priv.' for '.$uri.' as '.$unamedom.' in '. + $ENV{'request.course.id'}); + return ''; + } } # Resource preferences @@ -1664,12 +1750,29 @@ sub allowed { return 'F'; } +# --------------------------------------------------- Is a resource on the map? + +sub is_on_map { + my $uri=&declutter(shift); + my @uriparts=split(/\//,$uri); + my $filename=$uriparts[$#uriparts]; + my $pathname=$uri; + $pathname=~s/\/$filename$//; + my $match=($ENV{'acc.res.'.$ENV{'request.course.id'}.'.'.$pathname}=~ + /\&$filename\:([\d\|]+)\&/); + if ($match) { + return (1,$1); + } else { + return (0,0); + } +} + # ----------------------------------------------------------------- Define Role 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\&/) { @@ -1677,8 +1780,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\&/) { @@ -1686,8 +1789,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\&/) { @@ -1695,7 +1798,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=". @@ -1726,25 +1829,69 @@ sub metadata_query { return \%rhash; } -# ------------------------------------------------------------------ Plain Text +# ----------------------------------------- Send log queries and wait for reply -sub plaintext { - my $short=shift; - return $prp{$short}; +sub log_query { + my ($uname,$udom,$query,%filters)=@_; + my $uhome=&homeserver($uname,$udom); + if ($uhome eq 'no_host') { return 'error: no_host'; } + my $uhost=$hostname{$uhome}; + my $command=&escape(join(':',map{$_.'='.$filters{$_}} keys %filters)); + my $queryid=&reply("querysend:".$query.':'.$udom.':'.$uname.':'.$command, + $uhome); + unless ($queryid=~/^$uhost\_/) { return 'error: '.$queryid; } + return get_query_reply($queryid); +} + +sub get_query_reply { + my $queryid=shift; + my $replyfile=$perlvar{'lonDaemons'}.'/tmp/'.$queryid; + my $reply=''; + for (1..100) { + sleep 2; + if (-e $replyfile.'.end') { + if (my $fh=Apache::File->new($replyfile)) { + $reply.=<$fh>; + $fh->close; + } else { return 'error: reply_file_error'; } + return &unescape($reply); + } + } + return 'timeout:'.$queryid; } -# ------------------------------------------------------------------ Plain Text - -sub fileembstyle { - my $ending=lc(shift); - return $fe{$ending}; +sub courselog_query { +# +# possible filters: +# url: url or symb +# username +# domain +# action: view, submit, grade +# start: timestamp +# end: timestamp +# + my (%filters)=@_; + unless ($ENV{'request.course.id'}) { return 'no_course'; } + if ($filters{'url'}) { + $filters{'url'}=&symbclean(&declutter($filters{'url'})); + $filters{'url'}=~s/\.(\w+)$/(\\.\\d+)*\\.$1/; + $filters{'url'}=~s/\.(\w+)\_\_\_/(\\.\\d+)*\\.$1/; + } + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + return &log_query($cname,$cdom,'courselog',%filters); +} + +sub userlog_query { + my ($uname,$udom,%filters)=@_; + return &log_query($uname,$udom,'userlog',%filters); } -# ------------------------------------------------------------ Description Text +# ------------------------------------------------------------------ Plain Text -sub filedescription { - my $ending=lc(shift); - return $fd{$ending}; +sub plaintext { + my $short=shift; + return $prp{$short}; } # ----------------------------------------------------------------- Assign Role @@ -1785,14 +1932,25 @@ sub assignrole { } # -------------------------------------------------- Modify user authentication +# Overrides without validation + sub modifyuserauth { my ($udom,$uname,$umode,$upass)=@_; my $uhome=&homeserver($uname,$udom); - &logthis('Call to modify user authentication'.$udom.', '.$uname.', '. + 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'; @@ -1800,20 +1958,28 @@ sub modifyuserauth { # --------------------------------------------------------------- Modify a user - sub modifyuser { - my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene)=@_; + 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.' by '. - $ENV{'user.name'}.' at '.$ENV{'user.domain'}); - my $uhome=&homeserver($uname,$udom); + $last.', '.$gene.'(forceid: '.$forceid.')'. + (defined($desiredhome) ? ' desiredhome = '.$desiredhome : + ' desiredhome not specified'). + ' by '.$ENV{'user.name'}.' at '.$ENV{'user.domain'}); + my $uhome=&homeserver($uname,$udom,'true'); # ----------------------------------------------------------------- Create User if (($uhome eq 'no_host') && ($umode) && ($upass)) { my $unhome=''; - if ($ENV{'course.'.$ENV{'request.course.id'}.'.domain'} eq $udom) { + 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 { + } else { # load balancing routine for determining $unhome my $tryserver; my $loadm=10000000; foreach $tryserver (keys %libserv) { @@ -1827,23 +1993,25 @@ sub modifyuser { } } if (($unhome eq '') || ($unhome eq 'no_host')) { - return 'error: find home'; + 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); + $uhome=&homeserver($uname,$udom,'true'); 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\:/)) { + if (($uidhash{$uname}) && ($uidhash{$uname}!~/error\:/) + && (!$forceid)) { unless ($uid eq $uidhash{$uname}) { return 'error: mismatch '.$uidhash{$uname}.' versus '.$uid; } @@ -1855,6 +2023,7 @@ sub modifyuser { 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; } @@ -1872,14 +2041,15 @@ sub modifyuser { sub modifystudent { my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec, - $end,$start)=@_; + $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); + ($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')) { @@ -1915,9 +2085,9 @@ sub writecoursepref { return 'error: no such course'; } my $cstring=''; - map { + foreach (keys %prefs) { $cstring.=escape($_).'='.escape($prefs{$_}).'&'; - } keys %prefs; + } $cstring=~s/\&$//; return reply('put:'.$cdomain.':'.$cnum.':environment:'.$cstring,$chome); } @@ -1938,11 +2108,11 @@ sub createcourse { 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); + my $uhome=&homeserver($uname,$udom,'true'); 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); + $uhome=&homeserver($uname,$udom,'true'); unless (($uhome eq '') || ($uhome eq 'no_host')) { return 'error: unable to generate unique course-ID'; } @@ -1951,7 +2121,7 @@ sub createcourse { my $reply=&reply('encrypt:makeuser:'.$udom.':'.$uname.':none::', $ENV{'user.home'}); unless ($reply eq 'ok') { return 'error: '.$reply; } - $uhome=&homeserver($uname,$udom); + $uhome=&homeserver($uname,$udom,'true'); if (($uhome eq '') || ($uhome eq 'no_host')) { return 'error: no such course'; } @@ -2006,17 +2176,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); } @@ -2027,9 +2197,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); } @@ -2050,18 +2220,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 ')') { @@ -2079,19 +2249,62 @@ 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,$symbparm)=@_; + my ($varname,$symbparm,$udom,$uname)=@_; + unless ($varname) { return ''; } + + #get real user name/domain, courseid and symb + my $courseid; + if (!($uname && $udom)) { + (my $cursymb,$courseid,$udom,$uname)=&Apache::lonxml::whichuser(); + if (!$symbparm) { $symbparm=$cursymb; } + } else { + $courseid=$ENV{'request.course.id'}; + } + my ($realm,$space,$qualifier,@therest)=split(/\./,$varname); my $rest; if ($therest[0]) { @@ -2106,19 +2319,28 @@ sub EXT { if ($realm eq 'user') { # --------------------------------------------------------------- user.resource if ($space eq 'resource') { - my %restored=&restore(); + my %restored=&restore(undef,undef,$udom,$uname); return $restored{$qualifierrest}; # ----------------------------------------------------------------- user.access } elsif ($space eq 'access') { + # FIXME - not supporting calls for a specific user return &allowed($qualifier,$rest); # ------------------------------------------ user.preferences, user.environment } elsif (($space eq 'preferences') || ($space eq 'environment')) { - return $ENV{join('.',('environment',$qualifierrest))}; + if (($uname eq $ENV{'user.name'}) && + ($udom eq $ENV{'user.domain'})) { + return $ENV{join('.',('environment',$qualifierrest))}; + } else { + my %returnhash=&userenvironment($udom,$uname,$qualifierrest); + return $returnhash{$qualifierrest}; + } # ----------------------------------------------------------------- user.course } elsif ($space eq 'course') { + # FIXME - not supporting calls for a specific user return $ENV{join('.',('request.course',$qualifier))}; # ------------------------------------------------------------------- user.role } elsif ($space eq 'role') { + # FIXME - not supporting calls for a specific user my ($role,$where)=split(/\./,$ENV{'request.role'}); if ($qualifier eq 'value') { return $role; @@ -2127,17 +2349,21 @@ sub EXT { } # ----------------------------------------------------------------- user.domain } elsif ($space eq 'domain') { - return $ENV{'user.domain'}; + return $udom; # ------------------------------------------------------------------- user.name } elsif ($space eq 'name') { - return $ENV{'user.name'}; + return $uname; # ---------------------------------------------------- Any other user namespace } else { my $item=($rest)?$qualifier.'.'.$rest:$qualifier; my %reply=&get($space,[$item]); return $reply{$item}; } - } elsif ($realm eq 'request') { + } elsif ($realm eq 'query') { +# ---------------------------------------------- pull stuff out of query string + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},[$space]); + return $ENV{'form.'.$space}; + } elsif ($realm eq 'request') { # ------------------------------------------------------------- request.browser if ($space eq 'browser') { return $ENV{'browser.'.$qualifier}; @@ -2147,127 +2373,112 @@ sub EXT { } } elsif ($realm eq 'course') { # ---------------------------------------------------------- course.description - return $ENV{'course.'.$ENV{'request.course.id'}.'.'. - $spacequalifierrest}; + return $ENV{'course.'.$courseid.'.'.$spacequalifierrest}; } elsif ($realm eq 'resource') { - if ($ENV{'request.course.id'}) { -# print '
'.$space.' - '.$qualifier.' - '.$spacequalifierrest; + if ($courseid eq $ENV{'request.course.id'}) { + #print '
'.$space.' - '.$qualifier.' - '.$spacequalifierrest; # ----------------------------------------------------- Cascading lookup scheme - 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; + if (!$symbparm) { $symbparm=&symbread(); } + my $symbp=$symbparm; + my $mapp=(split(/\_\_\_/,$symbp))[0]; + + my $symbparm=$symbp.'.'.$spacequalifierrest; + my $mapparm=$mapp.'___(all).'.$spacequalifierrest; + + my $section; + if (($ENV{'user.name'} eq $uname) && + ($ENV{'user.domain'} eq $udom)) { + $section={'request.course.sec'}; + } else { + $section=&usection($udom,$uname,$courseid); + } -# ----------------------------------------------------------- first, check user - 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}; } + my $seclevel=$courseid.'.['.$section.'].'.$spacequalifierrest; + my $seclevelr=$courseid.'.['.$section.'].'.$symbparm; + my $seclevelm=$courseid.'.['.$section.'].'.$mapparm; + + my $courselevel=$courseid.'.'.$spacequalifierrest; + my $courselevelr=$courseid.'.'.$symbparm; + my $courselevelm=$courseid.'.'.$mapparm; - } else { - if ($resourcedata{$courselevelr}!~/No such file/) { - &logthis("WARNING:". - " Trying to get resource data for ".$ENV{'user.name'}." at " - .$ENV{'user.domain'}.": ".$resourcedata{$courselevelr}. - ""); - } - } +# ----------------------------------------------------------- first, check user + my %resourcedata=&get('resourcedata', + [$courselevelr,$courselevelm,$courselevel], + $udom,$uname); + 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 ". + $uname." at ".$udom.": ". + $resourcedata{$courselevelr}.""); + } + } # -------------------------------------------------------- second, check course - my $reply=&reply('get:'. - $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. - $ENV{'course.'.$ENV{'request.course.id'}.'.num'}. - ':resourcedata:'. - &escape($seclevelr).'&'.&escape($seclevelm).'&'.&escape($seclevel).'&'. - &escape($courselevelr).'&'.&escape($courselevelm).'&'.&escape($courselevel), - $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); - if ($reply!~/^error\:/) { - map { - if ($_) { return &unescape($_); } - } split(/\&/,$reply); - } - if (($reply=~/^con_lost/) || ($reply=~/^error\:/)) { - &logthis("WARNING:". - " Getting ".$reply." asking for ".$varname." for ". - $ENV{'course.'.$ENV{'request.course.id'}.'.num'}. - ' at '. - $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}. - ' from '. - $ENV{'course.'.$ENV{'request.course.id'}.'.home'}. - ""); - } + my $coursereply=&courseresdata($ENV{'course.'.$courseid.'.num'}, + $ENV{'course.'.$courseid.'.domain'}, + ($seclevelr,$seclevelm,$seclevel, + $courselevelr,$courselevelm, + $courselevel)); + if ($coursereply) { return $coursereply; } + # ------------------------------------------------------ third, check map parms - my %parmhash=(); - my $thisparm=''; - if (tie(%parmhash,'GDBM_File', - $ENV{'request.course.fn'}.'_parms.db',&GDBM_READER,0640)) { - $thisparm=$parmhash{$symbparm}; - untie(%parmhash); - } - if ($thisparm) { return $thisparm; } - } - + my %parmhash=(); + my $thisparm=''; + if (tie(%parmhash,'GDBM_File', + $ENV{'request.course.fn'}.'_parms.db', + &GDBM_READER,0640)) { + $thisparm=$parmhash{$symbparm}; + untie(%parmhash); + } + if ($thisparm) { return $thisparm; } + } # --------------------------------------------- last, look in resource metadata - $spacequalifierrest=~s/\./\_/; - my $metadata=&metadata($ENV{'request.filename'},$spacequalifierrest); - if ($metadata) { return $metadata; } - $metadata=&metadata($ENV{'request.filename'}, - 'parameter_'.$spacequalifierrest); - if ($metadata) { return $metadata; } + $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; } - } - } + unless ($space eq '0') { + my ($part,$id)=split(/\_/,$space); + if ($id) { + my $partgeneral=&EXT('resource.'.$part.'.'.$qualifierrest, + $symbparm,$udom,$uname); + if ($partgeneral) { return $partgeneral; } + } else { + my $resourcegeneral=&EXT('resource.0.'.$qualifierrest, + $symbparm,$udom,$uname); + if ($resourcegeneral) { return $resourcegeneral; } + } + } # ---------------------------------------------------- Any other user namespace } elsif ($realm eq 'environment') { # ----------------------------------------------------------------- environment - return $ENV{'environment.'.$spacequalifierrest}; + if (($uname eq $ENV{'user.name'})&&($udom eq $ENV{'user.domain'})) { + return $ENV{'environment.'.$spacequalifierrest}; + } else { + my %returnhash=&userenvironment($udom,$uname, + $spacequalifierrest); + return $returnhash{$spacequalifierrest}; + } } elsif ($realm eq 'system') { # ----------------------------------------------------------------- system.time if ($space eq 'time') { @@ -2301,7 +2512,7 @@ sub metadata { my %metathesekeys=(); unless ($filename=~/\.meta$/) { $filename.='.meta'; } my $metastring=&getfile($perlvar{'lonDocRoot'}.'/res/'.$filename); - my $parser=HTML::TokeParser->new(\$metastring); + my $parser=HTML::LCParser->new(\$metastring); my $token; undef %metathesekeys; while ($token=$parser->get_token) { @@ -2327,7 +2538,7 @@ sub metadata { } else { $metacache{$uri.':packages'}=$package.$keyroot; } - map { + foreach (keys %packagetab) { if ($_=~/^$package\&/) { my ($pack,$name,$subp)=split(/\&/,$_); my $value=$packagetab{$_}; @@ -2344,7 +2555,7 @@ sub metadata { $metacache{$uri.':'.$unikey.'.'.$subp}=$value; } } - } keys %packagetab; + } } else { # # This is not a package - some other kind of start tag @@ -2374,11 +2585,11 @@ sub metadata { if (defined($depthcount)) { $depthcount++; } else { $depthcount=0; } if ($depthcount<20) { - map { - $metathesekeys{$_}=1; - } split(/\,/,&metadata($uri,'keys', + foreach (split(/\,/,&metadata($uri,'keys', $parser->get_text('/import'),$unikey, - $depthcount)); + $depthcount))) { + $metathesekeys{$_}=1; + } } } else { @@ -2386,11 +2597,11 @@ sub metadata { $unikey.='_'.$token->[2]->{'name'}; } $metathesekeys{$unikey}=1; - map { + foreach (@{$token->[3]}) { $metacache{$uri.':'.$unikey.'.'.$_}=$token->[2]->{$_}; - } @{$token->[3]}; + } unless ( - $metacache{$uri.':'.$unikey}=$parser->get_text('/'.$entry) + $metacache{$uri.':'.$unikey}=&HTML::Entities::decode($parser->get_text('/'.$entry)) ) { $metacache{$uri.':'.$unikey}= $metacache{$uri.':'.$unikey.'.default'}; } @@ -2417,9 +2628,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'; } @@ -2428,14 +2639,70 @@ 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; +# no filename provided? try from environment unless ($thisfn) { - if ($ENV{'request.symb'}) { return $ENV{'request.symb'}; } + if ($ENV{'request.symb'}) { return &symbclean($ENV{'request.symb'}); } $thisfn=$ENV{'request.filename'}; } +# is that filename actually a symb? Verify, clean, and return + if ($thisfn=~/\_\_\_\d+\_\_\_(.*)$/) { + if (&symbverify($thisfn,$1)) { return &symbclean($thisfn); } + } $thisfn=declutter($thisfn); my %hash; my %bighash; @@ -2464,6 +2731,10 @@ sub symbread { unless ($ids) { $ids=$bighash{'ids_/'.$thisfn}; } + unless ($ids) { +# alias? + $ids=$bighash{'mapalias_'.$thisfn}; + } if ($ids) { # ------------------------------------------------------------------- Has ID(s) my @possibilities=split(/\,/,$ids); @@ -2474,7 +2745,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(/\./,$_); @@ -2484,7 +2755,7 @@ sub symbread { '___'.$resid; } } - } @possibilities; + } if ($realpossible!=1) { $syval=''; } } } @@ -2492,7 +2763,7 @@ sub symbread { } } if ($syval) { - return $syval.'___'.$thisfn; + return &symbclean($syval.'___'.$thisfn); } } &appenv('request.ambiguous' => $thisfn); @@ -2593,9 +2864,10 @@ 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//; + $finalpath=~s-/home/(\w+)/public_html/-/~$1/-; return $finalpath; } else { return $file; @@ -2609,6 +2881,7 @@ sub declutter { $thisfn=~s/^$perlvar{'lonDocRoot'}//; $thisfn=~s/^\///; $thisfn=~s/^res\///; + $thisfn=~s/\?.+$//; return $thisfn; } @@ -2631,17 +2904,30 @@ sub unescape { # ================================================================ Main Program sub goodbye { + &logthis("Starting Shut down"); &flushcourselogs(); &logthis("Shutting down"); } BEGIN { -# ------------------------------------------------------------ Read access.conf +# ----------------------------------- Read loncapa.conf and loncapa_apache.conf + unless ($readit) { +{ + my $config=Apache::File->new("/etc/httpd/conf/loncapa.conf"); + + while (my $configline=<$config>) { + if ($configline =~ /^[^\#]*PerlSetVar/) { + my ($dummy,$varname,$varvalue)=split(/\s+/,$configline); + chomp($varvalue); + $perlvar{$varname}=$varvalue; + } + } +} { - my $config=Apache::File->new("/etc/httpd/conf/access.conf"); + my $config=Apache::File->new("/etc/httpd/conf/loncapa_apache.conf"); while (my $configline=<$config>) { - if ($configline =~ /PerlSetVar/) { + if ($configline =~ /^[^\#]*PerlSetVar/) { my ($dummy,$varname,$varvalue)=split(/\s+/,$configline); chomp($varvalue); $perlvar{$varname}=$varvalue; @@ -2715,27 +3001,425 @@ BEGIN { } } -# ------------------------------------------------------------- Read file types -{ - my $config=Apache::File->new("$perlvar{'lonTabDir'}/filetypes.tab"); - - while (my $configline=<$config>) { - next if ($configline =~ /^\#/); - chomp($configline); - my ($ending,$emb,@descr)=split(/\s+/,$configline); - if ($descr[0] ne '') { - $fe{$ending}=lc($emb); - $fd{$ending}=join(' ',@descr); - } - } -} - %metacache=(); $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, $dir is a directory that relative $file lookups are to looked in ($dir of /a/dir and a file of ../bob will become /a/bob) + +=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