--- loncom/lond 2006/02/09 23:41:22 1.318.2.4 +++ loncom/lond 2007/01/19 02:58:17 1.357 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.318.2.4 2006/02/09 23:41:22 albertel Exp $ +# $Id: lond,v 1.357 2007/01/19 02:58:17 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -31,19 +31,19 @@ use strict; use lib '/home/httpd/lib/perl/'; +use LONCAPA; use LONCAPA::Configuration; use IO::Socket; use IO::File; #use Apache::File; -use Symbol; use POSIX; use Crypt::IDEA; use LWP::UserAgent(); +use Digest::MD5 qw(md5_hex); use GDBM_File; use Authen::Krb4; use Authen::Krb5; -use lib '/home/httpd/lib/perl/'; use localauth; use localenroll; use localstudentphoto; @@ -53,15 +53,13 @@ use LONCAPA::ConfigFileEdit; use LONCAPA::lonlocal; use LONCAPA::lonssl; use Fcntl qw(:flock); -use Symbol; my $DEBUG = 0; # Non zero to enable debug log entries. my $status=''; my $lastlog=''; -my $lond_max_wait_time = 13; -my $VERSION='$Revision: 1.318.2.4 $'; #' stupid emacs +my $VERSION='$Revision: 1.357 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -505,30 +503,30 @@ sub AdjustHostContents { my $adjusted; my $me = $perlvar{'lonHostID'}; - foreach my $line (split(/\n/,$contents)) { + foreach my $line (split(/\n/,$contents)) { if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) { chomp($line); my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line); if ($id eq $me) { - my $ip = gethostbyname($name); - my $ipnew = inet_ntoa($ip); - $ip = $ipnew; + my $ip = gethostbyname($name); + my $ipnew = inet_ntoa($ip); + $ip = $ipnew; # Reconstruct the host line and append to adjusted: - my $newline = "$id:$domain:$role:$name:$ip"; - if($maxcon ne "") { # Not all hosts have loncnew tuning params - $newline .= ":$maxcon:$idleto:$mincon"; - } - $adjusted .= $newline."\n"; + my $newline = "$id:$domain:$role:$name:$ip"; + if($maxcon ne "") { # Not all hosts have loncnew tuning params + $newline .= ":$maxcon:$idleto:$mincon"; + } + $adjusted .= $newline."\n"; - } else { # Not me, pass unmodified. - $adjusted .= $line."\n"; - } + } else { # Not me, pass unmodified. + $adjusted .= $line."\n"; + } } else { # Blank or comment never re-written. $adjusted .= $line."\n"; # Pass blanks and comments as is. } - } - return $adjusted; + } + return $adjusted; } # # InstallFile: Called to install an administrative file: @@ -837,16 +835,14 @@ sub AdjustOurHost { # Use the config line to get my hostname. # Use gethostbyname to translate that into an IP address. # - my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine); - my $BinaryIp = gethostbyname($name); - my $ip = inet_ntoa($ip); + my ($id,$domain,$role,$name,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine); # # Reassemble the config line from the elements in the list. # Note that if the loncnew items were not present before, they will # be now even if they would be empty # my $newConfigLine = $id; - foreach my $item ($domain, $role, $name, $ip, $maxcon, $idleto, $mincon) { + foreach my $item ($domain, $role, $name, $maxcon, $idleto, $mincon) { $newConfigLine .= ":".$item; } # Replace the line: @@ -892,11 +888,11 @@ sub EditFile { # Split the command into it's pieces: edit:filetype:script - my ($request, $filetype, $script) = split(/:/, $request,3); # : in script + my ($cmd, $filetype, $script) = split(/:/, $request,3); # : in script # Check the pre-coditions for success: - if($request != "edit") { # Something is amiss afoot alack. + if($cmd != "edit") { # Something is amiss afoot alack. return "error:edit request detected, but request != 'edit'\n"; } if( ($filetype ne "hosts") && @@ -941,169 +937,6 @@ sub EditFile { return "ok\n"; } -#--------------------------------------------------------------- -# -# Manipulation of hash based databases (factoring out common code -# for later use as we refactor. -# -# Ties a domain level resource file to a hash. -# If requested a history entry is created in the associated hist file. -# -# Parameters: -# domain - Name of the domain in which the resource file lives. -# namespace - Name of the hash within that domain. -# how - How to tie the hash (e.g. GDBM_WRCREAT()). -# loghead - Optional parameter, if present a log entry is created -# in the associated history file and this is the first part -# of that entry. -# logtail - Goes along with loghead, The actual logentry is of the -# form $loghead::logtail. -# Returns: -# Reference to a hash bound to the db file or alternatively undef -# if the tie failed. -# -sub tie_domain_hash { - my ($domain,$namespace,$how,$loghead,$logtail) = @_; - - # Filter out any whitespace in the domain name: - - $domain =~ s/\W//g; - - # We have enough to go on to tie the hash: - - my $user_top_dir = $perlvar{'lonUsersDir'}; - my $domain_dir = $user_top_dir."/$domain"; - my $resource_file = $domain_dir."/$namespace"; - return &_locking_hash_tie($resource_file,$namespace,$how,$loghead,$logtail); -} - -sub untie_domain_hash { - return &_locking_hash_untie(@_); -} -# -# Ties a user's resource file to a hash. -# If necessary, an appropriate history -# log file entry is made as well. -# This sub factors out common code from the subs that manipulate -# the various gdbm files that keep keyword value pairs. -# Parameters: -# domain - Name of the domain the user is in. -# user - Name of the 'current user'. -# namespace - Namespace representing the file to tie. -# how - What the tie is done to (e.g. GDBM_WRCREAT(). -# loghead - Optional first part of log entry if there may be a -# history file. -# what - Optional tail of log entry if there may be a history -# file. -# Returns: -# hash to which the database is tied. It's up to the caller to untie. -# undef if the has could not be tied. -# -sub tie_user_hash { - my ($domain,$user,$namespace,$how,$loghead,$what) = @_; - - $namespace=~s/\//\_/g; # / -> _ - $namespace=~s/\W//g; # whitespace eliminated. - my $proname = propath($domain, $user); - - my $file_prefix="$proname/$namespace"; - return &_locking_hash_tie($file_prefix,$namespace,$how,$loghead,$what); -} - -sub untie_user_hash { - return &_locking_hash_untie(@_); -} - -# internal routines that handle the actual tieing and untieing process - -sub _do_hash_tie { - my ($file_prefix,$namespace,$how,$loghead,$what) = @_; - my %hash; - if(tie(%hash, 'GDBM_File', "$file_prefix.db", $how, 0640)) { - # If this is a namespace for which a history is kept, - # make the history log entry: - if (($namespace !~/^nohist\_/) && (defined($loghead))) { - my $args = scalar @_; - Debug(" Opening history: $file_prefix $args"); - my $hfh = IO::File->new(">>$file_prefix.hist"); - if($hfh) { - my $now = time; - print $hfh "$loghead:$now:$what\n"; - } - $hfh->close; - } - return \%hash; - } else { - return undef; - } -} - -sub _do_hash_untie { - my ($hashref) = @_; - my $result = untie(%$hashref); - return $result; -} - -{ - my $sym; - - sub _locking_hash_tie { - my ($file_prefix,$namespace,$how,$loghead,$what) = @_; - - my ($lock); - - if ($how eq &GDBM_READER()) { - $lock=LOCK_SH; - $how=$how|&GDBM_NOLOCK(); - #if the db doesn't exist we can't read from it - if (! -e "$file_prefix.db") { - $! = 2; - return undef; - } - } elsif ($how eq &GDBM_WRCREAT()) { - $lock=LOCK_EX; - $how=$how|&GDBM_NOLOCK(); - if (! -e "$file_prefix.db") { - # doesn't exist but we need it to in order to successfully - # lock it so bring it into existance - open(TOUCH,">>$file_prefix.db"); - close(TOUCH); - } - } else { - &logthis("Unknown method $how for $file_prefix"); - die(); - } - - $sym=&Symbol::gensym(); - open($sym,"$file_prefix.db"); - my $failed=0; - eval { - local $SIG{__DIE__}='DEFAULT'; - local $SIG{ALRM}=sub { - $failed=1; - die("failed lock"); - }; - alarm($lond_max_wait_time); - flock($sym,$lock); - alarm(0); - }; - if ($failed) { - $! = 100; # throwing error # 100 - return undef; - } - return &_do_hash_tie($file_prefix,$namespace,$how,$loghead,$what); - } - - sub _locking_hash_untie { - my ($hashref) = @_; - my $result = untie(%$hashref); - flock($sym,LOCK_UN); - close($sym); - undef($sym); - return $result; - } -} - # read_profile # # Returns a set of specific entries from a user's profile file. @@ -1417,7 +1250,7 @@ sub push_file_handler { # sub du_handler { my ($cmd, $ududir, $client) = @_; - my ($ududir) = split(/:/,$ududir); # Make 'telnet' testing easier. + ($ududir) = split(/:/,$ududir); # Make 'telnet' testing easier. my $userinput = "$cmd:$ududir"; if ($ududir=~/\.\./ || $ududir!~m|^/home/httpd/|) { @@ -1741,17 +1574,24 @@ sub change_password_handler { # uname - Username. # upass - Current password. # npass - New password. + # context - Context in which this was called + # (preferences or reset_by_email). - my ($udom,$uname,$upass,$npass)=split(/:/,$tail); + my ($udom,$uname,$upass,$npass,$context)=split(/:/,$tail); $upass=&unescape($upass); $npass=&unescape($npass); &Debug("Trying to change password for $uname"); # First require that the user can be authenticated with their - # old password: - - my $validated = &validate_user($udom, $uname, $upass); + # old password unless context was 'reset_by_email': + + my $validated; + if ($context eq 'reset_by_email') { + $validated = 1; + } else { + $validated = &validate_user($udom, $uname, $upass); + } if($validated) { my $realpasswd = &get_auth_type($udom, $uname); # Defined since authd. @@ -1770,7 +1610,7 @@ sub change_password_handler { ."to change password"); &Failure( $client, "non_authorized\n",$userinput); } - } elsif ($howpwd eq 'unix') { + } elsif ($howpwd eq 'unix' && $context ne 'reset_by_email') { my $result = &change_unix_password($uname, $npass); &logthis("Result of password change for $uname: ". $result); @@ -2012,6 +1852,7 @@ sub update_resource_handler { my $reply=&reply("unsub:$fname","$clientname"); &devalidate_meta_cache($fname); unlink("$fname"); + unlink("$fname.meta"); } else { my $transname="$fname.in.transfer"; my $remoteurl=&reply("sub:$fname","$clientname"); @@ -2289,11 +2130,21 @@ sub token_auth_user_file_handler { chomp($session); my $reply="non_auth\n"; - if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'. - $session.'.id')) { - while (my $line=) { - if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply="ok\n"; } + my $file = $perlvar{'lonIDsDir'}.'/'.$session.'.id'; + if (open(ENVIN,"$file")) { + flock(ENVIN,LOCK_SH); + tie(my %disk_env,'GDBM_File',"$file",&GDBM_READER(),0640); + if (exists($disk_env{"userfile.$fname"})) { + $reply="ok\n"; + } else { + foreach my $envname (keys(%disk_env)) { + if ($envname=~ m|^userfile\.\Q$fname\E|) { + $reply="ok\n"; + last; + } + } } + untie(%disk_env); close(ENVIN); &Reply($client, $reply, "$cmd:$tail"); } else { @@ -2756,7 +2607,7 @@ sub get_profile_entry_encrypted { my $userinput = "$cmd:$tail"; - my ($cmd,$udom,$uname,$namespace,$what) = split(/:/,$userinput); + my ($udom,$uname,$namespace,$what) = split(/:/,$tail); chomp($what); my $qresult = read_profile($udom, $uname, $namespace, $what); my ($first) = split(/:/, $qresult); @@ -2915,17 +2766,11 @@ sub dump_profile_database { while (my ($key,$value) = each(%$hashref)) { my ($v,$symb,$param) = split(/:/,$key); next if ($v eq 'version' || $symb eq 'keys'); - # making old style store entries '$ver:$symb:$key = $value' - # look like new '$ver:$symb = "$key=$value"' - if (defined($param)) { $value = $param.'='.$value; } - foreach my $pair (split(/\&/,$value)) { - my ($param,$value)=split(/=/,$pair); - next if (exists($data{$symb}) && - exists($data{$symb}->{$param}) && - $data{$symb}->{'v.'.$param} > $v); - $data{$symb}->{$param}=$value; - $data{$symb}->{'v.'.$param}=$v; - } + next if (exists($data{$symb}) && + exists($data{$symb}->{$param}) && + $data{$symb}->{'v.'.$param} > $v); + $data{$symb}->{$param}=$value; + $data{$symb}->{'v.'.$param}=$v; } if (&untie_user_hash($hashref)) { while (my ($symb,$param_hash) = each(%data)) { @@ -3074,10 +2919,11 @@ sub store_handler { my $version=$hashref->{"version:$rid"}; my $allkeys=''; foreach my $pair (@pairs) { - my ($key)=split(/=/,$pair); + my ($key,$value)=split(/=/,$pair); $allkeys.=$key.':'; + $hashref->{"$version:$rid:$key"}=$value; } - $hashref->{"$version:$rid"}=$what."\×tamp=$now"; + $hashref->{"$version:$rid:timestamp"}=$now; $allkeys.='timestamp'; $hashref->{"$version:keys:$rid"}=$allkeys; if (&untie_user_hash($hashref)) { @@ -3098,6 +2944,85 @@ sub store_handler { } ®ister_handler("store", \&store_handler, 0, 1, 0); +# Modify a set of key=value pairs associated with a versioned name. +# +# Parameters: +# $cmd - Request command keyword. +# $tail - Tail of the request. This is a colon +# separated list containing: +# domain/user - User and authentication domain. +# namespace - Name of the database being modified +# rid - Resource keyword to modify. +# v - Version item to modify +# what - new value associated with rid. +# +# $client - Socket open on the client. +# +# +# Returns: +# 1 (keep on processing). +# Side-Effects: +# Writes to the client +sub putstore_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($udom,$uname,$namespace,$rid,$v,$what) =split(/:/,$tail); + if ($namespace ne 'roles') { + + chomp($what); + my $hashref = &tie_user_hash($udom, $uname, $namespace, + &GDBM_WRCREAT(), "M", + "$rid:$v:$what"); + if ($hashref) { + my $now = time; + my %data = &hash_extract($what); + my @allkeys; + while (my($key,$value) = each(%data)) { + push(@allkeys,$key); + $hashref->{"$v:$rid:$key"} = $value; + } + my $allkeys = join(':',@allkeys); + $hashref->{"$v:keys:$rid"}=$allkeys; + + if (&untie_user_hash($hashref)) { + &Reply($client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting store\n", $userinput); + } + } else { + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting store\n", $userinput); + } + } else { + &Failure($client, "refused\n", $userinput); + } + + return 1; +} +®ister_handler("putstore", \&putstore_handler, 0, 1, 0); + +sub hash_extract { + my ($str)=@_; + my %hash; + foreach my $pair (split(/\&/,$str)) { + my ($key,$value)=split(/=/,$pair); + $hash{$key}=$value; + } + return (%hash); +} +sub hash_to_str { + my ($hash_ref)=@_; + my $str; + foreach my $key (keys(%$hash_ref)) { + $str.=$key.'='.$hash_ref->{$key}.'&'; + } + $str=~s/\&$//; + return $str; +} + # # Dump out all versions of a resource that has key=value pairs associated # with it for each version. These resources are built up via the store @@ -3127,10 +3052,10 @@ sub restore_handler { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; # Only used for logging purposes. - - my ($cmd,$udom,$uname,$namespace,$rid) = split(/:/,$userinput); + my ($udom,$uname,$namespace,$rid) = split(/:/,$tail); $namespace=~s/\//\_/g; - $namespace=~s/\W//g; + $namespace = &LONCAPA::clean_username($namespace); + chomp($rid); my $qresult=''; my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()); @@ -3143,17 +3068,9 @@ sub restore_handler { my @keys=split(/:/,$vkeys); my $key; $qresult.="$scope:keys=$vkeys&"; - if (exists($hashref->{"$scope:$rid"})) { - my $what=$hashref->{"$scope:$rid"}; - foreach my $pair (split(/\&/,$hashref->{"$scope:$rid"})) { - my ($key,$value)=split(/=/,$pair); - $qresult.="$scope:".$pair."&"; - } - } else { - foreach $key (@keys) { - $qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&"; - } - } + foreach $key (@keys) { + $qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&"; + } } if (&untie_user_hash($hashref)) { $qresult=~s/\&$//; @@ -3174,15 +3091,17 @@ sub restore_handler { ®ister_handler("restore", \&restore_handler, 0,1,0); # -# Add a chat message to to a discussion board. +# Add a chat message to a synchronous discussion board. # # Parameters: # $cmd - Request keyword. # $tail - Tail of the command. A colon separated list # containing: # cdom - Domain on which the chat board lives -# cnum - Identifier of the discussion group. -# post - Body of the posting. +# cnum - Course containing the chat board. +# newpost - Body of the posting. +# group - Optional group, if chat board is only +# accessible in a group within the course # $client - Socket open on the client. # Returns: # 1 - Indicating caller should keep on processing. @@ -3197,8 +3116,8 @@ sub send_chat_handler { my $userinput = "$cmd:$tail"; - my ($cdom,$cnum,$newpost)=split(/\:/,$tail); - &chat_add($cdom,$cnum,$newpost); + my ($cdom,$cnum,$newpost,$group)=split(/\:/,$tail); + &chat_add($cdom,$cnum,$newpost,$group); &Reply($client, "ok\n", $userinput); return 1; @@ -3206,7 +3125,7 @@ sub send_chat_handler { ®ister_handler("chatsend", \&send_chat_handler, 0, 1, 0); # -# Retrieve the set of chat messagss from a discussion board. +# Retrieve the set of chat messages from a discussion board. # # Parameters: # $cmd - Command keyword that initiated the request. @@ -3216,6 +3135,8 @@ sub send_chat_handler { # chat id - Discussion thread(?) # domain/user - Authentication domain and username # of the requesting person. +# group - Optional course group containing +# the board. # $client - Socket open on the client program. # Returns: # 1 - continue processing @@ -3228,9 +3149,9 @@ sub retrieve_chat_handler { my $userinput = "$cmd:$tail"; - my ($cdom,$cnum,$udom,$uname)=split(/\:/,$tail); + my ($cdom,$cnum,$udom,$uname,$group)=split(/\:/,$tail); my $reply=''; - foreach (&get_chat($cdom,$cnum,$udom,$uname)) { + foreach (&get_chat($cdom,$cnum,$udom,$uname,$group)) { $reply.=&escape($_).':'; } $reply=~s/\:$//; @@ -3307,7 +3228,7 @@ sub reply_query_handler { my $userinput = "$cmd:$tail"; - my ($cmd,$id,$reply)=split(/:/,$userinput); + my ($id,$reply)=split(/:/,$tail); my $store; my $execdir=$perlvar{'lonDaemons'}; if ($store=IO::File->new(">$execdir/tmp/$id")) { @@ -3371,19 +3292,20 @@ sub put_course_id_handler { foreach my $pair (@pairs) { my ($key,$courseinfo) = split(/=/,$pair,2); $courseinfo =~ s/=/:/g; - - my @current_items = split(/:/,$hashref->{$key}); + my @current_items = split(/:/,$hashref->{$key},-1); shift(@current_items); # remove description pop(@current_items); # remove last access my $numcurrent = scalar(@current_items); - - my @new_items = split(/:/,$courseinfo); + if ($numcurrent > 3) { + $numcurrent = 3; + } + my @new_items = split(/:/,$courseinfo,-1); my $numnew = scalar(@new_items); if ($numcurrent > 0) { - if ($numnew == 1) { # flushcourselogs() from 1.1 or earlier - $courseinfo .= ':'.join(':',@current_items); - } elsif ($numnew == 2) { # flushcourselogs() from 1.2.X - $courseinfo .= ':'.$current_items[$numcurrent-1]; + if ($numnew <= $numcurrent) { # flushcourselogs() from pre 2.2 + for (my $j=$numcurrent-$numnew; $j>=0; $j--) { + $courseinfo .= ':'.$current_items[$numcurrent-$j-1]; + } } } $hashref->{$key}=$courseinfo.':'.$now; @@ -3427,12 +3349,11 @@ sub put_course_id_handler { # institutional code - optional supplied code to filter # the dump. Only courses with an institutional code # that match the supplied code will be returned. -# owner - optional supplied username of owner to filter -# the dump. Only courses for which the course -# owner matches the supplied username will be -# returned. Implicit assumption that owner -# is a user in the domain in which the -# course database is defined. +# owner - optional supplied username and domain of owner to +# filter the dump. Only courses for which the course +# owner matches the supplied username and/or domain +# will be returned. Pre-2.2.0 legacy entries from +# nohist_courseiddump will only contain usernames. # $client - The socket open on the client. # Returns: # 1 - Continue processing. @@ -3443,7 +3364,8 @@ sub dump_course_id_handler { my $userinput = "$cmd:$tail"; - my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter) =split(/:/,$tail); + my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter, + $typefilter,$regexp_ok) =split(/:/,$tail); if (defined($description)) { $description=&unescape($description); } else { @@ -3454,26 +3376,45 @@ sub dump_course_id_handler { } else { $instcodefilter='.'; } + my ($ownerunamefilter,$ownerdomfilter); if (defined($ownerfilter)) { $ownerfilter=&unescape($ownerfilter); + if ($ownerfilter ne '.' && defined($ownerfilter)) { + if ($ownerfilter =~ /^([^:]*):([^:]*)$/) { + $ownerunamefilter = $1; + $ownerdomfilter = $2; + } else { + $ownerunamefilter = $ownerfilter; + $ownerdomfilter = ''; + } + } } else { $ownerfilter='.'; } + if (defined($coursefilter)) { $coursefilter=&unescape($coursefilter); } else { $coursefilter='.'; } + if (defined($typefilter)) { + $typefilter=&unescape($typefilter); + } else { + $typefilter='.'; + } + if (defined($regexp_ok)) { + $regexp_ok=&unescape($regexp_ok); + } unless (defined($since)) { $since=0; } my $qresult=''; my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()); if ($hashref) { while (my ($key,$value) = each(%$hashref)) { - my ($descr,$lasttime,$inst_code,$owner); + my ($descr,$lasttime,$inst_code,$owner,$type); my @courseitems = split(/:/,$value); $lasttime = pop(@courseitems); - ($descr,$inst_code,$owner)=@courseitems; + ($descr,$inst_code,$owner,$type)=@courseitems; if ($lasttime<$since) { next; } my $match = 1; unless ($description eq '.') { @@ -3484,14 +3425,49 @@ sub dump_course_id_handler { } unless ($instcodefilter eq '.' || !defined($instcodefilter)) { my $unescapeInstcode = &unescape($inst_code); - unless (eval('$unescapeInstcode=~/\Q$instcodefilter\E/i')) { - $match = 0; + if ($regexp_ok) { + unless (eval('$unescapeInstcode=~/$instcodefilter/')) { + $match = 0; + } + } else { + unless (eval('$unescapeInstcode=~/\Q$instcodefilter\E/i')) { + $match = 0; + } } } unless ($ownerfilter eq '.' || !defined($ownerfilter)) { my $unescapeOwner = &unescape($owner); - unless (eval('$unescapeOwner=~/\Q$ownerfilter\E/i')) { - $match = 0; + if (($ownerunamefilter ne '') && ($ownerdomfilter ne '')) { + if ($unescapeOwner =~ /:/) { + if (eval('$unescapeOwner !~ + /\Q$ownerunamefilter\E:\Q$ownerdomfilter\E$/i')) { + $match = 0; + } + } else { + if (eval('$unescapeOwner!~/\Q$ownerunamefilter\E/i')) { + $match = 0; + } + } + } elsif ($ownerunamefilter ne '') { + if ($unescapeOwner =~ /:/) { + if (eval('$unescapeOwner!~/\Q$ownerunamefilter\E:[^:]+$/i')) { + $match = 0; + } + } else { + if (eval('$unescapeOwner!~/\Q$ownerunamefilter\E/i')) { + $match = 0; + } + } + } elsif ($ownerdomfilter ne '') { + if ($unescapeOwner =~ /:/) { + if (eval('$unescapeOwner!~/^[^:]+:\Q$ownerdomfilter\E/')) { + $match = 0; + } + } else { + if ($ownerdomfilter ne $udom) { + $match = 0; + } + } } } unless ($coursefilter eq '.' || !defined($coursefilter)) { @@ -3500,6 +3476,18 @@ sub dump_course_id_handler { $match = 0; } } + unless ($typefilter eq '.' || !defined($typefilter)) { + my $unescapeType = &unescape($type); + if ($type eq '') { + if ($typefilter ne 'Course') { + $match = 0; + } + } else { + unless (eval('$unescapeType=~/^\Q$typefilter\E$/')) { + $match = 0; + } + } + } if ($match == 1) { $qresult.=$key.'='.$descr.':'.$inst_code.':'.$owner.'&'; } @@ -3522,6 +3510,99 @@ sub dump_course_id_handler { ®ister_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0); # +# Puts an unencrypted entry in a namespace db file at the domain level +# +# Parameters: +# $cmd - The command that got us here. +# $tail - Tail of the command (remaining parameters). +# $client - File descriptor connected to client. +# Returns +# 0 - Requested to exit, caller should shut down. +# 1 - Continue processing. +# Side effects: +# reply is written to $client. +# +sub put_domain_handler { + my ($cmd,$tail,$client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($udom,$namespace,$what) =split(/:/,$tail,3); + chomp($what); + my @pairs=split(/\&/,$what); + my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_WRCREAT(), + "P", $what); + if ($hashref) { + foreach my $pair (@pairs) { + my ($key,$value)=split(/=/,$pair); + $hashref->{$key}=$value; + } + if (&untie_domain_hash($hashref)) { + &Reply($client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting putdom\n", $userinput); + } + } else { + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting putdom\n", $userinput); + } + + return 1; +} +®ister_handler("putdom", \&put_domain_handler, 0, 1, 0); + +# Unencrypted get from the namespace database file at the domain level. +# This function retrieves a keyed item from a specific named database in the +# domain directory. +# +# Parameters: +# $cmd - Command request keyword (get). +# $tail - Tail of the command. This is a colon separated list +# consisting of the domain and the 'namespace' +# which selects the gdbm file to do the lookup in, +# & separated list of keys to lookup. Note that +# the values are returned as an & separated list too. +# $client - File descriptor open on the client. +# Returns: +# 1 - Continue processing. +# 0 - Exit. +# Side effects: +# reply is written to $client. +# + +sub get_domain_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$client:$tail"; + + my ($udom,$namespace,$what)=split(/:/,$tail,3); + chomp($what); + my @queries=split(/\&/,$what); + my $qresult=''; + my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_READER()); + if ($hashref) { + for (my $i=0;$i<=$#queries;$i++) { + $qresult.="$hashref->{$queries[$i]}&"; + } + if (&untie_domain_hash($hashref)) { + $qresult=~s/\&$//; + &Reply($client, "$qresult\n", $userinput); + } else { + &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting getdom\n",$userinput); + } + } else { + &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting getdom\n",$userinput); + } + + return 1; +} +®ister_handler("getdom", \&get_id_handler, 0, 1, 0); + + +# # Puts an id to a domains id database. # # Parameters: @@ -3892,15 +3973,23 @@ sub tmp_put_handler { my $userinput = "$cmd:$what"; # Reconstruct for logging. - - my $store; + my ($record,$context) = split(/:/,$what); + if ($context ne '') { + chomp($context); + $context = &unescape($context); + } + my ($id,$store); $tmpsnum++; - my $id=$$.'_'.$clientip.'_'.$tmpsnum; + if ($context eq 'resetpw') { + $id = &md5_hex(&md5_hex(time.{}.rand().$$)); + } else { + $id = $$.'_'.$clientip.'_'.$tmpsnum; + } $id=~s/\W/\_/g; - $what=~s/\n//g; + $record=~s/\n//g; my $execdir=$perlvar{'lonDaemons'}; if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) { - print $store $what; + print $store $record; close $store; &Reply($client, "$id\n", $userinput); } else { @@ -4117,7 +4206,8 @@ sub enrollment_enabled_handler { my $userinput = $cmd.":".$tail; # For logging purposes. - my $cdom = split(/:/, $tail); # Domain we're asking about. + my ($cdom) = split(/:/, $tail, 2); # Domain we're asking about. + my $outcome = &localenroll::run($cdom); &Reply($client, "$outcome\n", $userinput); @@ -4173,6 +4263,7 @@ sub validate_course_owner_handler { my $userinput = "$cmd:$tail"; my ($inst_course_id, $owner, $cdom) = split(/:/, $tail); + $owner = &unescape($owner); my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom); &Reply($client, "$outcome\n", $userinput); @@ -4213,16 +4304,47 @@ sub validate_course_section_handler { ®ister_handler("autovalidatecourse", \&validate_course_section_handler, 0, 1, 0); # -# Create a password for a new auto-enrollment user. -# I think/guess, this password allows access to the institutions -# AIS class list server/services. Stuart can correct this comment -# when he finds out how wrong I am. +# Validate course owner's access to enrollment data for specific class section. +# +# +# Formal Parameters: +# $cmd - The command request that got us dispatched. +# $tail - The tail of the command. In this case this is a colon separated +# set of words that will be split into: +# $inst_class - Institutional code for the specific class section +# $courseowner - The escaped username:domain of the course owner +# $cdom - The domain of the course from the institution's +# point of view. +# $client - The socket open on the client. +# Returns: +# 1 - continue processing. +# + +sub validate_class_access_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($inst_class,$courseowner,$cdom) = split(/:/, $tail); + $courseowner = &unescape($courseowner); + my $outcome; + eval { + local($SIG{__DIE__})='DEFAULT'; + $outcome=&localenroll::check_section($inst_class,$courseowner,$cdom); + }; + &Reply($client,"$outcome\n", $userinput); + + return 1; +} +®ister_handler("autovalidateclass_sec", \&validate_class_access_handler, 0, 1, 0); + +# +# Create a password for a new LON-CAPA user added by auto-enrollment. +# Only used for case where authentication method for new user is localauth # # Formal Parameters: # $cmd - The command request that got us dispatched. # $tail - The tail of the command. In this case this is a colon separated # set of words that will be split into: -# $authparam - An authentication parameter (username??). +# $authparam - An authentication parameter (localauth parameter). # $cdom - The domain of the course from the institution's # point of view. # $client - The socket open on the client. @@ -4349,6 +4471,38 @@ sub get_institutional_code_format_handle ®ister_handler("autoinstcodeformat", \&get_institutional_code_format_handler,0,1,0); +sub get_institutional_defaults_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + + my $dom = $tail; + my %defaults_hash; + my @code_order; + my $outcome; + eval { + local($SIG{__DIE__})='DEFAULT'; + $outcome = &localenroll::instcode_defaults($dom,\%defaults_hash, + \@code_order); + }; + if (!$@) { + if ($outcome eq 'ok') { + my $result=''; + while (my ($key,$value) = each(%defaults_hash)) { + $result.=&escape($key).'='.&escape($value).'&'; + } + $result .= 'code_order='.&escape(join('&',@code_order)); + &Reply($client,$result."\n",$userinput); + } else { + &Reply($client,"error\n", $userinput); + } + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } +} +®ister_handler("autoinstcodedefaults", + \&get_institutional_defaults_handler,0,1,0); + + # Get domain specific conditions for import of student photographs to a course # # Retrieves information from photo_permission subroutine in localenroll. @@ -4361,10 +4515,19 @@ sub photo_permission_handler { my $userinput = "$cmd:$tail"; my $cdom = $tail; my ($perm_reqd,$conditions); - my $outcome = &localenroll::photo_permission($cdom,\$perm_reqd, - \$conditions); - &Reply($client, &escape($outcome.':'.$perm_reqd.':'. $conditions)."\n", - $userinput); + my $outcome; + eval { + local($SIG{__DIE__})='DEFAULT'; + $outcome = &localenroll::photo_permission($cdom,\$perm_reqd, + \$conditions); + }; + if (!$@) { + &Reply($client, &escape($outcome.':'.$perm_reqd.':'. $conditions)."\n", + $userinput); + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } + return 1; } ®ister_handler("autophotopermission",\&photo_permission_handler,0,1,0); @@ -4389,6 +4552,7 @@ sub photo_check_handler { my $result = &localstudentphoto::fetch($udom,$uname,$pid,\$response); $result .= ':'.$response; &Reply($client, &escape($result)."\n",$userinput); + return 1; } ®ister_handler("autophotocheck",\&photo_check_handler,0,1,0); @@ -4402,8 +4566,17 @@ sub photo_choice_handler { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; my $cdom = &unescape($tail); - my ($update,$comment) = &localenroll::manager_photo_update($cdom); - &Reply($client,&escape($update).':'.&escape($comment)."\n",$userinput); + my ($update,$comment); + eval { + local($SIG{__DIE__})='DEFAULT'; + ($update,$comment) = &localenroll::manager_photo_update($cdom); + }; + if (!$@) { + &Reply($client,&escape($update).':'.&escape($comment)."\n",$userinput); + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } + return 1; } ®ister_handler("autophotochoice",\&photo_choice_handler,0,1,0); @@ -4436,7 +4609,10 @@ sub student_photo_handler { &mkpath($path); my $file; if ($type eq 'thumbnail') { - $file=&localstudentphoto::fetch_thumbnail($domain,$uname); + eval { + local($SIG{__DIE__})='DEFAULT'; + $file=&localstudentphoto::fetch_thumbnail($domain,$uname); + }; } else { $file=&localstudentphoto::fetch($domain,$uname); } @@ -4506,6 +4682,22 @@ sub process_request { # fix all the userinput -> user_input. my $wasenc = 0; # True if request was encrypted. # ------------------------------------------------------------ See if encrypted + # for command + # sethost: + # : + # we just send it to the processor + # for + # sethost::: + # we do the implict set host and then do the command + if ($userinput =~ /^sethost:/) { + (my $cmd,my $newid,$userinput) = split(':',$userinput,3); + if (defined($userinput)) { + &sethost("$cmd:$newid"); + } else { + $userinput = "$cmd:$newid"; + } + } + if ($userinput =~ /^enc/) { $userinput = decipher($userinput); $wasenc=1; @@ -5069,22 +5261,6 @@ sub status { $0='lond: '.$what.' '.$local; } -# -------------------------------------------------------- Escape Special Chars - -sub escape { - my $str=shift; - $str =~ s/(\W)/"%".unpack('H2',$1)/eg; - return $str; -} - -# ----------------------------------------------------- Un-Escape Special Chars - -sub unescape { - my $str=shift; - $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; - return $str; -} - # ----------------------------------------------------------- Send USR1 to lonc sub reconlonc { @@ -5159,25 +5335,13 @@ sub sub_sql_reply { Type => SOCK_STREAM, Timeout => 10) or return "con_lost"; - print $sclient "$cmd\n"; + print $sclient "$cmd:$currentdomainid\n"; my $answer=<$sclient>; chomp($answer); if (!$answer) { $answer="con_lost"; } return $answer; } -# -------------------------------------------- Return path to profile directory - -sub propath { - my ($udom,$uname)=@_; - $udom=~s/\W//g; - $uname=~s/\W//g; - my $subdir=$uname.'__'; - $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/; - my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname"; - return $proname; -} - # --------------------------------------- Is this the home server of an author? sub ishome { @@ -5302,7 +5466,8 @@ sub make_new_child { # my $tmpsnum=0; # Now global #---------------------------------------------------- kerberos 5 initialization &Authen::Krb5::init_context(); - unless (($dist eq 'fedora4') || ($dist eq 'suse9.3')) { + unless (($dist eq 'fedora5') || ($dist eq 'fedora4') + || ($dist eq 'suse9.3')) { &Authen::Krb5::init_ets(); } @@ -5343,7 +5508,7 @@ sub make_new_child { my $remotereq=<$client>; chomp($remotereq); Debug("Got init: $remotereq"); - my $inikeyword = split(/:/, $remotereq); + if ($remotereq =~ /^init/) { &sethost("sethost:$perlvar{'lonHostID'}"); # @@ -5494,8 +5659,11 @@ sub is_author { # Author role should show up as a key /domain/_au - my $key = "/$domain/_au"; - my $value = $hashref->{$key}; + my $key = "/$domain/_au"; + my $value; + if (defined($hashref)) { + $value = $hashref->{$key}; + } if(defined($value)) { &Debug("$user @ $domain is an author"); @@ -5725,7 +5893,12 @@ sub validate_user { # Authenticate via installation specific authentcation method: $validated = &localauth::localauth($user, $password, - $contentpwd); + $contentpwd, + $domain); + if ($validate < 0) { + &logthis("localauth for $contentpwd $user:$domain returned a $validated"); + $validated = 0; + } } else { # Unrecognized auth is also bad. $validated = 0; } @@ -5751,8 +5924,7 @@ sub addline { my ($fname,$hostid,$ip,$newline)=@_; my $contents; my $found=0; - my $expr='^'.$hostid.':'.$ip.':'; - $expr =~ s/\./\\\./g; + my $expr='^'.quotemeta($hostid).':'.quotemeta($ip).':'; my $sh; if ($sh=IO::File->new("$fname.subscription")) { while (my $subline=<$sh>) { @@ -5768,10 +5940,16 @@ sub addline { } sub get_chat { - my ($cdom,$cname,$udom,$uname)=@_; + my ($cdom,$cname,$udom,$uname,$group)=@_; my @entries=(); - my $hashref = &tie_user_hash($cdom, $cname, 'nohist_chatroom', + my $namespace = 'nohist_chatroom'; + my $namespace_inroom = 'nohist_inchatroom'; + if ($group ne '') { + $namespace .= '_'.$group; + $namespace_inroom .= '_'.$group; + } + my $hashref = &tie_user_hash($cdom, $cname, $namespace, &GDBM_READER()); if ($hashref) { @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref)); @@ -5779,7 +5957,7 @@ sub get_chat { } my @participants=(); my $cutoff=time-60; - $hashref = &tie_user_hash($cdom, $cname, 'nohist_inchatroom', + $hashref = &tie_user_hash($cdom, $cname, $namespace_inroom, &GDBM_WRCREAT()); if ($hashref) { $hashref->{$uname.':'.$udom}=time; @@ -5794,10 +5972,16 @@ sub get_chat { } sub chat_add { - my ($cdom,$cname,$newchat)=@_; + my ($cdom,$cname,$newchat,$group)=@_; my @entries=(); my $time=time; - my $hashref = &tie_user_hash($cdom, $cname, 'nohist_chatroom', + my $namespace = 'nohist_chatroom'; + my $logfile = 'chatroom.log'; + if ($group ne '') { + $namespace .= '_'.$group; + $logfile = 'chatroom_'.$group.'.log'; + } + my $hashref = &tie_user_hash($cdom, $cname, $namespace, &GDBM_WRCREAT()); if ($hashref) { @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref)); @@ -5820,7 +6004,7 @@ sub chat_add { } { my $proname=&propath($cdom,$cname); - if (open(CHATLOG,">>$proname/chatroom.log")) { + if (open(CHATLOG,">>$proname/$logfile")) { print CHATLOG ("$time:".&unescape($newchat)."\n"); } close(CHATLOG); @@ -6095,6 +6279,11 @@ sub convert_photo { sub sethost { my ($remotereq) = @_; my (undef,$hostid)=split(/:/,$remotereq); + # ignore sethost if we are already correct + if ($hostid eq $currenthostid) { + return 'ok'; + } + if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; } if ($hostip{$perlvar{'lonHostID'}} eq $hostip{$hostid}) { $currenthostid =$hostid; @@ -6520,7 +6709,6 @@ to the client, and the connection is clo IO::Socket IO::File Apache::File -Symbol POSIX Crypt::IDEA LWP::UserAgent()