--- loncom/lond 2006/02/01 22:08:48 1.316 +++ loncom/lond 2006/11/27 16:34:38 1.350 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.316 2006/02/01 22:08:48 albertel Exp $ +# $Id: lond,v 1.350 2006/11/27 16:34:38 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -31,15 +31,16 @@ 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; @@ -53,15 +54,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.316 $'; #' stupid emacs +my $VERSION='$Revision: 1.350 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -837,16 +836,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 +889,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 +938,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 +1251,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 +1575,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 +1611,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 +1853,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 +2131,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 +2608,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); @@ -3093,6 +2945,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 @@ -3122,10 +3053,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); + $namespace = &LONCAPA::clean_username($namespace); + my ($udom,$uname,$namespace,$rid) = split(/:/,$tail); $namespace=~s/\//\_/g; - $namespace=~s/\W//g; + chomp($rid); my $qresult=''; my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()); @@ -3161,15 +3092,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. @@ -3184,8 +3117,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; @@ -3193,7 +3126,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. @@ -3203,6 +3136,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 @@ -3215,9 +3150,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/\:$//; @@ -3294,7 +3229,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")) { @@ -3367,10 +3302,10 @@ sub put_course_id_handler { my @new_items = split(/:/,$courseinfo); 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; @@ -3414,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. @@ -3430,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 { @@ -3441,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 '.') { @@ -3471,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)) { @@ -3487,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.'&'; } @@ -3509,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: @@ -3879,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 { @@ -4104,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); @@ -4160,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); @@ -4200,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. @@ -4336,6 +4471,115 @@ 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. +# Returns outcome (ok) if no processing errors, and whether course owner is +# required to accept conditions of use (yes/no). +# +# +sub photo_permission_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my $cdom = $tail; + my ($perm_reqd,$conditions); + 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); + +# +# Checks if student photo is available for a user in the domain, in the user's +# directory (in /userfiles/internal/studentphoto.jpg). +# Uses localstudentphoto:fetch() to ensure there is an up to date copy of +# the student's photo. + +sub photo_check_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($udom,$uname,$pid) = split(/:/,$tail); + $udom = &unescape($udom); + $uname = &unescape($uname); + $pid = &unescape($pid); + my $path=&propath($udom,$uname).'/userfiles/internal/'; + if (!-e $path) { + &mkpath($path); + } + my $response; + 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); + +# +# Retrieve information from localenroll about whether to provide a button +# for users who have enbled import of student photos to initiate an +# update of photo files for registered students. Also include +# comment to display alongside button. + +sub photo_choice_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my $cdom = &unescape($tail); + 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); + # # Gets a student's photo to exist (in the correct image type) in the user's # directory. @@ -4348,24 +4592,36 @@ sub get_institutional_code_format_handle # $client - The socket open on the client. # Returns: # 1 - continue processing. + sub student_photo_handler { my ($cmd, $tail, $client) = @_; - my ($domain,$uname,$type) = split(/:/, $tail); + my ($domain,$uname,$ext,$type) = split(/:/, $tail); - my $path=&propath($domain,$uname). - '/userfiles/internal/studentphoto.'.$type; - if (-e $path) { + my $path=&propath($domain,$uname). '/userfiles/internal/'; + my $filename = 'studentphoto.'.$ext; + if ($type eq 'thumbnail') { + $filename = 'studentphoto_tn.'.$ext; + } + if (-e $path.$filename) { &Reply($client,"ok\n","$cmd:$tail"); return 1; } &mkpath($path); - my $file=&localstudentphoto::fetch($domain,$uname); + my $file; + if ($type eq 'thumbnail') { + eval { + local($SIG{__DIE__})='DEFAULT'; + $file=&localstudentphoto::fetch_thumbnail($domain,$uname); + }; + } else { + $file=&localstudentphoto::fetch($domain,$uname); + } if (!$file) { &Failure($client,"unavailable\n","$cmd:$tail"); return 1; } - if (!-e $path) { &convert_photo($file,$path); } - if (-e $path) { + if (!-e $path.$filename) { &convert_photo($file,$path.$filename); } + if (-e $path.$filename) { &Reply($client,"ok\n","$cmd:$tail"); return 1; } @@ -4426,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; @@ -4989,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 { @@ -5079,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 { @@ -5222,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(); } @@ -5263,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'}"); # @@ -5414,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"); @@ -5688,10 +5936,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)); @@ -5699,7 +5953,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; @@ -5714,10 +5968,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)); @@ -5740,7 +6000,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); @@ -6015,6 +6275,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; @@ -6440,7 +6705,6 @@ to the client, and the connection is clo IO::Socket IO::File Apache::File -Symbol POSIX Crypt::IDEA LWP::UserAgent() 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.