--- loncom/lonnet/perl/lonnet.pm 2000/10/31 19:28:11 1.56 +++ loncom/lonnet/perl/lonnet.pm 2001/03/22 16:13:22 1.115 @@ -3,6 +3,9 @@ # # 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 @@ -13,7 +16,7 @@ # 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 +# 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 @@ -42,17 +45,28 @@ # directcondval(index) : reading condition value of single condition from # state string # condval(index) : value of condition index based on state -# varval(name) : value of a variable -# refreshstate() : refresh the state information string +# 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 +# rndseed() : returns a random seed +# 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 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 +# 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 # # 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, @@ -68,7 +82,18 @@ # 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 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 +# 02/27/01 Scott Harrison +# 3/2 Gerd Kortemeyer +# 3/15 Scott Harrison +# 3/19,3/20 Gerd Kortemeyer +# 3/22 Scott Harrison package Apache::lonnet; @@ -77,10 +102,12 @@ 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 %spareid %hostdom %libserv %pr %prp %fe %fd $readit %metacache); use IO::Socket; use GDBM_File; use Apache::Constants qw(:common :http); +use HTML::TokeParser; +use Fcntl qw(:flock); # --------------------------------------------------------------------- Logging @@ -123,8 +150,7 @@ sub reply { my ($cmd,$server)=@_; my $answer=subreply($cmd,$server); if ($answer eq 'con_lost') { $answer=subreply($cmd,$server); } - if (($answer=~/^error:/) || ($answer=~/^refused/) || - ($answer=~/^rejected/)) { + if (($answer=~/^refused/) || ($answer=~/^rejected/)) { &logthis("WARNING:". " $cmd to $server returned $answer"); } @@ -164,6 +190,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); @@ -222,13 +253,26 @@ sub appenv { $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]); @@ -248,7 +292,10 @@ sub appenv { foreach $newname (keys %newenv) { print $fh "$newname=$newenv{$newname}\n"; } + $fh->close(); } + + $lockfh->close(); return 'ok'; } # ----------------------------------------------------- Delete from Environment @@ -267,16 +314,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'; } + unless (flock($fh,LOCK_EX)) { + &logthis("WARNING: ". + 'Could not obtain exclusive lock in delenv: '.$!); + $fh->close(); + return 'error: '.$!; + } map { unless ($_=~/^$delthis/) { print $fh $_; } } @oldenv; + $fh->close(); } return 'ok'; } @@ -358,6 +419,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=(); + map { + $returnhash{$_}=(&userenvironment($udom,$_,'id'))[1]; + } @unames; + return %returnhash; +} + +# ------------------------------- Store away a list of names and associated IDs + +sub idput { + my ($udom,%ids)=@_; + my %servers=(); + map { + 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); + } + } keys %ids; + map { + &critical('idput:'.$udom.':'.$servers{$_},$_); + } keys %servers; +} + +# ------------------------------------- Find the section of student in a course + +sub usection { + my ($udom,$unam,$courseid)=@_; + $courseid=~s/\_/\//g; + $courseid=~s/^(\w)/\/$1/; + map { + 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; } + } + } split(/\&/,&reply('dump:'.$udom.':'.$unam.':roles', + &homeserver($unam,$udom))); + 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 { @@ -370,6 +536,9 @@ sub subscribe { return 'not_found'; } my $answer=reply("sub:$fname",$home); + if (($answer eq 'con_lost') || ($answer eq 'rejected')) { + $answer.=' by '.$home; + } return $answer; } @@ -381,14 +550,14 @@ sub repcopy { my $transname="$filename.in.transfer"; if ((-e $filename) || (-e $transname)) { return OK; } my $remoteurl=subscribe($filename); - if ($remoteurl eq 'con_lost') { - &logthis("Subscribe returned con_lost: $filename"); + if ($remoteurl =~ /^con_lost by/) { + &logthis("Subscribe returned $remoteurl: $filename"); return HTTP_SERVICE_UNAVAILABLE; } elsif ($remoteurl eq 'not_found') { &logthis("Subscribe returned not_found: $filename"); return HTTP_NOT_FOUND; - } elsif ($remoteurl eq 'rejected') { - &logthis("Subscribe returned rejected: $filename"); + } elsif ($remoteurl =~ /^rejected by/) { + &logthis("Subscribe returned $remoteurl: $filename"); return FORBIDDEN; } elsif ($remoteurl eq 'directory') { return OK; @@ -461,12 +630,60 @@ sub log { return critical("log:$dom:$nam:$what",$hom); } +# --------------------------------------------- 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= + &reply('del:'.$ENV{'course.'.$cid.'.domain'}.':'. + $ENV{'course.'.$cid.'.num'}. + ':nohist_calculatedsheets:'. + &escape($key.'studentcalc:'), + $ENV{'course.'.$cid.'.home'}) + .' '. + &reply('del:'.$ENV{'user.domain'}.':'. + $ENV{'user.name'}. + ':nohist_calculatedsheets_'.$cid.':'. + &escape($key.'assesscalc:'.$symb), + $ENV{'user.home'}); + unless ($status eq 'ok ok') { + &logthis('Could not devalidate spreadsheet '. + $ENV{'user.name'}.' at '.$ENV{'user.domain'}.' for '. + $symb.': '.$status); + } + } +} + # ----------------------------------------------------------------------- Store sub store { my %storehash=@_; my $symb; - unless ($symb=escape(&symbread())) { return ''; } + unless ($symb=&symbread()) { return ''; } + + &devalidate($symb); + + $symb=escape($symb); my $namespace; unless ($namespace=$ENV{'request.course.id'}) { return ''; } my $namevalue=''; @@ -484,7 +701,11 @@ sub store { sub cstore { my %storehash=@_; my $symb; - unless ($symb=escape(&symbread())) { return ''; } + unless ($symb=&symbread()) { return ''; } + + &devalidate($symb); + + $symb=escape($symb); my $namespace; unless ($namespace=$ENV{'request.course.id'}) { return ''; } my $namevalue=''; @@ -512,9 +733,12 @@ sub restore { 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++) { + map { + $returnhash{$_}=$returnhash{$version.':'.$_}; + } split(/\:/,$returnhash{$version.':keys'}); + } return %returnhash; } @@ -529,8 +753,7 @@ sub coursedescription { 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 $normalid=$cdomain.'_'.$cnum; my %envhash=(); my %returnhash=('home' => $chome, 'domain' => $cdomain, @@ -546,6 +769,9 @@ sub coursedescription { $returnhash{'fn'}=$perlvar{'lonDaemons'}.'/tmp/'. $ENV{'user.name'}.'_'.$cdomain.'_'.$cnum; $envhash{'course.'.$normalid.'.last_cache'}=time; + $envhash{'course.'.$normalid.'.home'}=$chome; + $envhash{'course.'.$normalid.'.domain'}=$cdomain; + $envhash{'course.'.$normalid.'.num'}=$cnum; &appenv(%envhash); return %returnhash; } @@ -553,7 +779,7 @@ sub coursedescription { return (); } -# -------------------------------------------------------- Get user priviledges +# -------------------------------------------------------- Get user privileges sub rolesinit { my ($domain,$username,$authhost)=@_; @@ -627,12 +853,12 @@ sub rolesinit { %thesepriv=(); map { 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; } } } @@ -742,7 +968,7 @@ sub eget { return %returnhash; } -# ------------------------------------------------- Check for a user priviledge +# ------------------------------------------------- Check for a user privilege sub allowed { my ($priv,$uri)=@_; @@ -772,8 +998,11 @@ sub allowed { } # Course: uri itself is a course + my $courseuri=$uri; + $courseuri=~s/\_(\d)/\/$1/; + $courseuri=~s/^([^\/])/\/$1/; - if ($ENV{'user.priv.'.$ENV{'request.role'}.'./'.$uri} + if ($ENV{'user.priv.'.$ENV{'request.role'}.'.'.$courseuri} =~/$priv\&([^\:]*)/) { $thisallowed.=$1; } @@ -790,7 +1019,7 @@ sub allowed { 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 @@ -815,7 +1044,7 @@ sub allowed { $checkreferer=0; } } - + if (($ENV{'HTTP_REFERER'}) && ($checkreferer)) { my $refuri=$ENV{'HTTP_REFERER'}; $refuri=~s/^http\:\/\/$ENV{'request.host'}//i; @@ -841,7 +1070,7 @@ sub allowed { } # -# Gathered now: all priviledges that could apply, and condition number +# Gathered now: all privileges that could apply, and condition number # # # Full or no access? @@ -873,6 +1102,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; @@ -885,7 +1115,9 @@ sub allowed { if (($ENV{$prefix.'res.'.$uri.'.lock.sections'}=~/\,$csec\,/) || ($ENV{$prefix.'res.'.$uri.'.lock.sections'} eq 'all')) { if ($ENV{$prefix.'res.'.$uri.'.lock.expire'}>time) { - &log('Locked by res: '.$priv.' for '.$uri.' due to '. + &log($ENV{'user.domain'},$ENV{'user.name'}, + $ENV{'user.host'}, + 'Locked by res: '.$priv.' for '.$uri.' due to '. $cdom.'/'.$cnum.'/'.$csec.' expire '. $ENV{$prefix.'priv.'.$priv.'.lock.expire'}); return ''; @@ -894,7 +1126,9 @@ sub allowed { if (($ENV{$prefix.'priv.'.$priv.'.lock.sections'}=~/\,$csec\,/) || ($ENV{$prefix.'priv.'.$priv.'.lock.sections'} eq 'all')) { if ($ENV{'priv.'.$priv.'.lock.expire'}>time) { - &log('Locked by priv: '.$priv.' for '.$uri.' due to '. + &log($ENV{'user.domain'},$ENV{'user.name'}, + $ENV{'user.host'}, + 'Locked by priv: '.$priv.' for '.$uri.' due to '. $cdom.'/'.$cnum.'/'.$csec.' expire '. $ENV{$prefix.'priv.'.$priv.'.lock.expire'}); return ''; @@ -923,7 +1157,8 @@ sub allowed { my $rolecode=(split(/\./,$ENV{'request.role'}))[0]; if ($ENV{'course.'.$ENV{'request.course.id'}.'.'.$priv.'.roles.denied'} =~/\,$rolecode\,/) { - &log('Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '. + &log($ENV{'user.domain'},$ENV{'user.name'},$ENV{'user.host'}, + 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '. $ENV{'request.course.id'}); return ''; } @@ -942,7 +1177,8 @@ sub allowed { } if (join('',@content)=~ /\]*\>[^\<]*$rolecode[^\<]*\<\/roledeny\>/) { - &log('Denied by role: '.$priv.' for '.$uri.' as '.$rolecode); + &log($ENV{'user.domain'},$ENV{'user.name'},$ENV{'user.host'}, + 'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode); return ''; } @@ -962,11 +1198,6 @@ sub allowed { return 'F'; } -# ---------------------------------------------------------- Refresh State Info - -sub refreshstate { -} - # ----------------------------------------------------------------- Define Role sub definerole { @@ -1009,6 +1240,24 @@ sub definerole { } } +# ---------------- Make a metadata query against the network of library servers + +sub metadata_query { + my ($query,$custom)=@_; + # need to put in a loop here and return list + unless ($custom) { + my $reply=&reply("querysend:".&escape($query),'msul3'); + return $reply; + } + else { + my $reply=&reply("querysend:".&escape($query).':'. + &escape($custom),'msul3'); + return $reply; + } + my ($query)=@_; + my $reply=&reply("querysend:".&escape($query),'msul3'); +} + # ------------------------------------------------------------------ Plain Text sub plaintext { @@ -1025,7 +1274,7 @@ sub fileembstyle { # ------------------------------------------------------------ Description Text -sub filedecription { +sub filedescription { my $ending=shift; return $fd{$ending}; } @@ -1035,29 +1284,208 @@ 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 a user + + +sub modifyuser { + my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene)=@_; + &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); +# ----------------------------------------------------------------- Create User + if (($uhome eq 'no_host') && ($umode) && ($upass)) { + my $unhome=''; + if ($ENV{'course.'.$ENV{'request.course.id'}.'.domain'} eq $udom) { + $unhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'}; + } else { + 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: find home'; + } + 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'; + } + } +# ---------------------------------------------------------------------- Add ID + if ($uid) { + $uid=~tr/A-Z/a-z/; + my %uidhash=&idrget($udom,$uname); + if (($uidhash{$uname}) && ($uidhash{$uname}!~/error\:/)) { + unless ($uid eq $uidhash{$uname}) { + return 'error: mismatch '.$uidhash{$uname}.' versus '.$uid; + } + } else { + &idput($udom,($uname => $uid)); + } + } +# -------------------------------------------------------------- Add names, etc + my $names=&reply('get:'.$udom.':'.$uname. + ':environment:firstname&middlename&lastname&generation', + $uhome); + my ($efirst,$emiddle,$elast,$egene)=split(/\&/,$names); + if ($first) { $efirst = &escape($first); } + if ($middle) { $emiddle = &escape($middle); } + if ($last) { $elast = &escape($last); } + if ($gene) { $egene = &escape($gene); } + my $reply=&reply('put:'.$udom.':'.$uname. + ':environment:firstname='.$efirst. + '&middlename='.$emiddle. + '&lastname='.$elast. + '&generation='.$egene,$uhome); + 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)=@_; + 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); + 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 + my $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=''; + map { + $cstring.=escape($_).'='.escape($prefs{$_}).'&'; + } keys %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; } + my $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 { @@ -1186,8 +1614,9 @@ sub condval { # --------------------------------------------------------- Value of a Variable -sub varval { +sub EXT { my $varname=shift; + unless ($varname) { return ''; } my ($realm,$space,$qualifier,@therest)=split(/\./,$varname); my $rest; if ($therest[0]) { @@ -1195,15 +1624,21 @@ sub varval { } else { $rest=''; } + my $qualifierrest=$qualifier; + if ($rest) { $qualifierrest.='.'.$rest; } + my $spacequalifierrest=$space; + if ($qualifierrest) { $spacequalifierrest.='.'.$qualifierrest; } if ($realm eq 'user') { # --------------------------------------------------------------- user.resource if ($space eq 'resource') { + my %restored=&restore; + return $restored{$qualifierrest}; # ----------------------------------------------------------------- user.access } elsif ($space eq 'access') { return &allowed($qualifier,$rest); # ------------------------------------------ user.preferences, user.environment } elsif (($space eq 'preferences') || ($space eq 'environment')) { - return $ENV{join('.',('environment',$qualifier,$rest))}; + return $ENV{join('.',('environment',$qualifierrest))}; # ----------------------------------------------------------------- user.course } elsif ($space eq 'course') { return $ENV{join('.',('request.course',$qualifier))}; @@ -1231,33 +1666,113 @@ sub varval { # ------------------------------------------------------------- request.browser if ($space eq 'browser') { return $ENV{'browser.'.$qualifier}; - } elsif ($space eq 'filename') { - return $ENV{'request.filename'}; +# ------------------------------------------------------------ request.filename + } else { + return $ENV{'request.'.$spacequalifierrest}; } } elsif ($realm eq 'course') { # ---------------------------------------------------------- course.description - if ($space eq 'description') { - my %reply=&coursedescription($ENV{'request.course.id'}); - return $reply{'description'}; -# ------------------------------------------------------------------- course.id - } elsif ($space eq 'id') { - return $ENV{'request.course.id'}; -# -------------------------------------------------- Any other course namespace - } else { - my ($cdom,$cnam)=split(/\_/,$ENV{'request.course.id'}); - my $chome=&homeserver($cnam,$cdom); - my $item=join('.',($qualifier,$rest)); - return &unescape - (&reply('get:'.$cdom.':'.$cnam.':'.&escape($space).':'. - &escape($item),$chome)); - } - } elsif ($realm eq 'userdata') { - my $uhome=&homeserver($qualifier,$space); -# ----------------------------------------------- userdata.domain.name.resource + my $section=''; + if ($ENV{'request.course.sec'}) { + $section='_'.$ENV{'request.course.sec'}; + } + return $ENV{'course.'.$ENV{'request.course.id'}.$section.'.'. + $spacequalifierrest}; + } elsif ($realm eq 'resource') { + if ($ENV{'request.course.id'}) { +# ----------------------------------------------------- Cascading lookup scheme + my $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', + ($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 $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'}. + ""); + } +# ------------------------------------------------------ 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; } + } + +# --------------------------------------------- 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; } + # ---------------------------------------------------- Any other user namespace } elsif ($realm eq 'environment') { # ----------------------------------------------------------------- environment - return $ENV{join('.',($space,$qualifier,$rest))}; + return $ENV{$spacequalifierrest}; } elsif ($realm eq 'system') { # ----------------------------------------------------------------- system.time if ($space eq 'time') { @@ -1267,6 +1782,48 @@ sub varval { return ''; } +# ---------------------------------------------------------------- Get metadata + +sub metadata { + my ($uri,$what)=@_; + + $uri=&declutter($uri); + my $filename=$uri; + $uri=~s/\.meta$//; + unless ($metacache{$uri.':keys'}) { + unless ($filename=~/\.meta$/) { $filename.='.meta'; } + my $metastring=&getfile($perlvar{'lonDocRoot'}.'/res/'.$filename); + my $parser=HTML::TokeParser->new(\$metastring); + my $token; + while ($token=$parser->get_token) { + if ($token->[0] eq 'S') { + my $entry=$token->[1]; + my $unikey=$entry; + if (defined($token->[2]->{'part'})) { + $unikey.='_'.$token->[2]->{'part'}; + } + if (defined($token->[2]->{'name'})) { + $unikey.='_'.$token->[2]->{'name'}; + } + if ($metacache{$uri.':keys'}) { + $metacache{$uri.':keys'}.=','.$unikey; + } else { + $metacache{$uri.':keys'}=$unikey; + } + map { + $metacache{$uri.':'.$unikey.'.'.$_}=$token->[2]->{$_}; + } @{$token->[3]}; + unless ( + $metacache{$uri.':'.$unikey}=$parser->get_text('/'.$entry) + ) { $metacache{$uri.':'.$unikey}= + $metacache{$uri.':'.$unikey.'.default'}; + } + } + } + } + return $metacache{$uri.':'.$what}; +} + # ------------------------------------------------- Update symbolic store links sub symblist { @@ -1319,6 +1876,9 @@ sub symbread { &GDBM_READER,0640)) { # ---------------------------------------------- Get ID(s) for current resource my $ids=$bighash{'ids_/res/'.$thisfn}; + unless ($ids) { + $ids=$bighash{'ids_/'.$thisfn}; + } if ($ids) { # ------------------------------------------------------------------- Has ID(s) my @possibilities=split(/\,/,$ids); @@ -1346,7 +1906,9 @@ sub symbread { untie(%bighash) } } - if ($syval) { return $syval.'___'.$thisfn; } + if ($syval) { + return $syval.'___'.$thisfn; + } } &appenv('request.ambiguous' => $thisfn); return ''; @@ -1369,18 +1931,43 @@ 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); + { + use integer; + my $symbchck=unpack("%32C*",$symb) << 27; + my $symbseed=numval($symb) << 22; + my $namechck=unpack("%32C*",$ENV{'user.name'}) << 17; + my $nameseed=numval($ENV{'user.name'}) << 12; + my $domainseed=unpack("%32C*",$ENV{'user.domain'}) << 7; + my $courseseed=unpack("%32C*",$ENV{'request.course.id'}); + 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 { @@ -1397,12 +1984,17 @@ sub filelocation { my ($dir,$file) = @_; my $location; $file=~ s/^\s*(\S+)\s*$/$1/; ## strip off leading and trailing spaces - $file=~s/^$perlvar{'lonDocRoot'}//; - $file=~s:^/*res::; - if ( !( $file =~ m:^/:) ) { - $location = $dir. '/'.$file; + if ($file=~m:^/~:) { # is a contruction space reference + $location = $file; + $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:; } else { - $location = '/home/httpd/html/res'.$file; + $file=~s/^$perlvar{'lonDocRoot'}//; + $file=~s:^/*res::; + if ( !( $file =~ m:^/:) ) { + $location = $dir. '/'.$file; + } else { + $location = '/home/httpd/html/res'.$file; + } } $location=~s://+:/:g; # remove duplicate / while ($location=~m:/\.\./:) {$location=~ s:/[^/]+/\.\./:/:g;} #remove dir/.. @@ -1522,6 +2114,7 @@ if ($readit ne 'done') { } } +%metacache=(); $readit='done'; &logthis('INFO: Read configuration');