--- loncom/lond 2005/06/21 11:01:35 1.285 +++ loncom/lond 2006/02/09 23:41:22 1.318.2.4 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.285 2005/06/21 11:01:35 foxr Exp $ +# $Id: lond,v 1.318.2.4 2006/02/09 23:41:22 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -48,17 +48,20 @@ use localauth; use localenroll; use localstudentphoto; use File::Copy; +use File::Find; 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.285 $'; #' stupid emacs +my $VERSION='$Revision: 1.318.2.4 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -86,6 +89,7 @@ my $ConnectionType; my %hostid; # ID's for hosts in cluster by ip. my %hostdom; # LonCAPA domain for hosts in cluster. +my %hostname; # DNSname -> ID's mapping. my %hostip; # IPs for hosts in cluster. my %hostdns; # ID's of hosts looked up by DNS name. @@ -112,20 +116,20 @@ my %Dispatcher; # my $lastpwderror = 13; # Largest error number from lcpasswd. my @passwderrors = ("ok", - "lcpasswd must be run as user 'www'", - "lcpasswd got incorrect number of arguments", - "lcpasswd did not get the right nubmer of input text lines", - "lcpasswd too many simultaneous pwd changes in progress", - "lcpasswd User does not exist.", - "lcpasswd Incorrect current passwd", - "lcpasswd Unable to su to root.", - "lcpasswd Cannot set new passwd.", - "lcpasswd Username has invalid characters", - "lcpasswd Invalid characters in password", - "lcpasswd User already exists", - "lcpasswd Something went wrong with user addition.", - "lcpasswd Password mismatch", - "lcpasswd Error filename is invalid"); + "pwchange_failure - lcpasswd must be run as user 'www'", + "pwchange_failure - lcpasswd got incorrect number of arguments", + "pwchange_failure - lcpasswd did not get the right nubmer of input text lines", + "pwchange_failure - lcpasswd too many simultaneous pwd changes in progress", + "pwchange_failure - lcpasswd User does not exist.", + "pwchange_failure - lcpasswd Incorrect current passwd", + "pwchange_failure - lcpasswd Unable to su to root.", + "pwchange_failure - lcpasswd Cannot set new passwd.", + "pwchange_failure - lcpasswd Username has invalid characters", + "pwchange_failure - lcpasswd Invalid characters in password", + "pwchange_failure - lcpasswd User already exists", + "pwchange_failure - lcpasswd Something went wrong with user addition.", + "pwchange_failure - lcpasswd Password mismatch", + "pwchange_failure - lcpasswd Error filename is invalid"); # The array below are lcuseradd error strings.: @@ -969,23 +973,13 @@ sub tie_domain_hash { my $user_top_dir = $perlvar{'lonUsersDir'}; my $domain_dir = $user_top_dir."/$domain"; - my $resource_file = $domain_dir."/$namespace.db"; - my %hash; - if(tie(%hash, 'GDBM_File', $resource_file, $how, 0640)) { - if (defined($loghead)) { # Need to log the operation. - my $logFh = IO::File->new(">>$domain_dir/$namespace.hist"); - if($logFh) { - my $timestamp = time; - print $logFh "$loghead:$timestamp:$logtail\n"; - } - $logFh->close; - } - return \%hash; # Return the tied hash. - } else { - return undef; # Tie failed. - } + 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 @@ -1011,18 +1005,27 @@ sub tie_user_hash { $namespace=~s/\//\_/g; # / -> _ $namespace=~s/\W//g; # whitespace eliminated. my $proname = propath($domain, $user); - - # Tie the database. - + + 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', "$proname/$namespace.db", - $how, 0640)) { + 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: $namespace $args"); - my $hfh = IO::File->new(">>$proname/$namespace.hist"); + 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"; @@ -1033,7 +1036,72 @@ sub tie_user_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 @@ -1066,7 +1134,7 @@ sub read_profile { $qresult.="$hashref->{$queries[$i]}&"; # Presumably failure gives empty string. } $qresult=~s/\&$//; # Remove trailing & from last lookup. - if (untie %$hashref) { + if (&untie_user_hash($hashref)) { return $qresult; } else { return "error: ".($!+0)." untie (GDBM) Failed"; @@ -1363,18 +1431,18 @@ sub du_handler { # etc. # if (-d $ududir) { - # And as Shakespeare would say to make - # assurance double sure, - # use execute_command to ensure that the command is not executed in - # a shell that can screw us up. - - my $duout = execute_command("du -ks $ududir"); - $duout=~s/[^\d]//g; #preserve only the numbers - &Reply($client,"$duout\n","$cmd:$ududir"); + my $total_size=0; + my $code=sub { + if ($_=~/\.\d+\./) { return;} + if ($_=~/\.meta$/) { return;} + $total_size+=(stat($_))[7]; + }; + chdir($ududir); + find($code,$ududir); + $total_size=int($total_size/1024); + &Reply($client,"$total_size\n","$cmd:$ududir"); } else { - &Failure($client, "bad_directory:$ududir\n","$cmd:$ududir"); - } return 1; } @@ -1413,14 +1481,15 @@ sub ls_handler { if(-d $ulsdir) { if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { - undef $obs, $rights; + undef($obs); + undef($rights); my @ulsstats=stat($ulsdir.'/'.$ulsfn); #We do some obsolete checking here if(-e $ulsdir.'/'.$ulsfn.".meta") { open(FILE, $ulsdir.'/'.$ulsfn.".meta"); my @obsolete=; foreach my $obsolete (@obsolete) { - if($obsolete =~ m|()(on)|) { $obs = 1; } + if($obsolete =~ m/()(on|1)/) { $obs = 1; } if($obsolete =~ m|()(default)|) { $rights = 1; } } } @@ -1480,14 +1549,15 @@ sub ls2_handler { if(-d $ulsdir) { if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { - undef $obs, $rights; + undef($obs); + undef($rights); my @ulsstats=stat($ulsdir.'/'.$ulsfn); #We do some obsolete checking here if(-e $ulsdir.'/'.$ulsfn.".meta") { open(FILE, $ulsdir.'/'.$ulsfn.".meta"); my @obsolete=; foreach my $obsolete (@obsolete) { - if($obsolete =~ m|()(on)|) { $obs = 1; } + if($obsolete =~ m/()(on|1)/) { $obs = 1; } if($obsolete =~ m|()(default)|) { $rights = 1; } @@ -1701,19 +1771,9 @@ sub change_password_handler { &Failure( $client, "non_authorized\n",$userinput); } } elsif ($howpwd eq 'unix') { - # Unix means we have to access /etc/password - &Debug("auth is unix"); - my $execdir=$perlvar{'lonDaemons'}; - &Debug("Opening lcpasswd pipeline"); - my $pf = IO::File->new("|$execdir/lcpasswd > " - ."$perlvar{'lonDaemons'}" - ."/logs/lcpasswd.log"); - print $pf "$uname\n$npass\n$npass\n"; - close $pf; - my $err = $?; - my $result = ($err>0 ? 'pwchange_failure' : 'ok'); + my $result = &change_unix_password($uname, $npass); &logthis("Result of password change for $uname: ". - &lcpasswdstrerror($?)); + $result); &Reply($client, "$result\n", $userinput); } else { # this just means that the current password mode is not @@ -1812,6 +1872,9 @@ sub add_user_handler { # Implicit inputs: # The authentication systems describe above have their own forms of implicit # input into the authentication process that are described above. +# NOTE: +# This is also used to change the authentication credential values (e.g. passwd). +# # sub change_authentication_handler { @@ -1831,24 +1894,41 @@ sub change_authentication_handler { my $oldauth = &get_auth_type($udom, $uname); # Get old auth info. my $passfilename = &password_path($udom, $uname); if ($passfilename) { # Not allowed to create a new user!! - my $result=&make_passwd_file($uname, $umode,$npass,$passfilename); - # - # If the current auth mode is internal, and the old auth mode was - # unix, or krb*, and the user is an author for this domain, - # re-run manage_permissions for that role in order to be able - # to take ownership of the construction space back to www:www - # - - if( (($oldauth =~ /^unix/) && ($umode eq "internal")) || - (($oldauth =~ /^internal/) && ($umode eq "unix")) ) { - if(&is_author($udom, $uname)) { - &Debug(" Need to manage author permissions..."); - &manage_permissions("/$udom/_au", $udom, $uname, "$umode:"); + # If just changing the unix passwd. need to arrange to run + # passwd since otherwise make_passwd_file will run + # lcuseradd which fails if an account already exists + # (to prevent an unscrupulous LONCAPA admin from stealing + # an existing account by overwriting it as a LonCAPA account). + + if(($oldauth =~/^unix/) && ($umode eq "unix")) { + my $result = &change_unix_password($uname, $npass); + &logthis("Result of password change for $uname: ".$result); + if ($result eq "ok") { + &Reply($client, "$result\n") + } else { + &Failure($client, "$result\n"); } + } else { + my $result=&make_passwd_file($uname, $umode,$npass,$passfilename); + # + # If the current auth mode is internal, and the old auth mode was + # unix, or krb*, and the user is an author for this domain, + # re-run manage_permissions for that role in order to be able + # to take ownership of the construction space back to www:www + # + + + if( (($oldauth =~ /^unix/) && ($umode eq "internal")) || + (($oldauth =~ /^internal/) && ($umode eq "unix")) ) { + if(&is_author($udom, $uname)) { + &Debug(" Need to manage author permissions..."); + &manage_permissions("/$udom/_au", $udom, $uname, "$umode:"); + } + } + &Reply($client, $result, $userinput); } - &Reply($client, $result, $userinput); } else { &Failure($client, "non_authorized\n", $userinput); # Fail the user now. } @@ -1930,6 +2010,7 @@ sub update_resource_handler { my $since=$now-$atime; if ($since>$perlvar{'lonExpire'}) { my $reply=&reply("unsub:$fname","$clientname"); + &devalidate_meta_cache($fname); unlink("$fname"); } else { my $transname="$fname.in.transfer"; @@ -1960,6 +2041,7 @@ sub update_resource_handler { alarm(0); } rename($transname,$fname); + &devalidate_meta_cache($fname); } } &Reply( $client, "ok\n", $userinput); @@ -1973,6 +2055,26 @@ sub update_resource_handler { } ®ister_handler("update", \&update_resource_handler, 0 ,1, 0); +sub devalidate_meta_cache { + my ($url) = @_; + use Cache::Memcached; + my $memcache = new Cache::Memcached({'servers'=>['127.0.0.1:11211']}); + $url = &declutter($url); + $url =~ s-\.meta$--; + my $id = &escape('meta:'.$url); + $memcache->delete($id); +} + +sub declutter { + my $thisfn=shift; + $thisfn=~s/^\Q$perlvar{'lonDocRoot'}\E//; + $thisfn=~s/^\///; + $thisfn=~s|^adm/wrapper/||; + $thisfn=~s|^adm/coursedocs/showdoc/||; + $thisfn=~s/^res\///; + $thisfn=~s/\?.+$//; + return $thisfn; +} # # Fetch a user file from a remote server to the user's home directory # userfiles subdir. @@ -2341,7 +2443,7 @@ sub put_user_profile_entry { my ($key,$value)=split(/=/,$pair); $hashref->{$key}=$value; } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { &Reply( $client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". @@ -2349,7 +2451,7 @@ sub put_user_profile_entry { $userinput); } } else { - &Failure( $client, "error: ".($!)." tie(GDBM) Failed ". + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". "while attempting put\n", $userinput); } } else { @@ -2385,7 +2487,7 @@ sub newput_user_profile_entry { my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_WRCREAT(),"N",$what); if(!$hashref) { - &Failure( $client, "error: ".($!)." tie(GDBM) Failed ". + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". "while attempting put\n", $userinput); return 1; } @@ -2404,7 +2506,7 @@ sub newput_user_profile_entry { $hashref->{$key}=$value; } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { &Reply( $client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". @@ -2457,7 +2559,7 @@ sub increment_user_value_handler { } } } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { &Reply( $client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". @@ -2524,7 +2626,7 @@ sub roles_put_handler { $auth_type); $hashref->{$key}=$value; } - if (untie($hashref)) { + if (&untie_user_hash($hashref)) { &Reply($client, "ok\n", $userinput); } else { &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". @@ -2575,7 +2677,7 @@ sub roles_delete_handler { foreach my $key (@rolekeys) { delete $hashref->{$key}; } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { &Reply($client, "ok\n", $userinput); } else { &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". @@ -2716,7 +2818,7 @@ sub delete_profile_entry { foreach my $key (@keys) { delete($hashref->{$key}); } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { &Reply($client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". @@ -2758,7 +2860,7 @@ sub get_profile_keys { foreach my $key (keys %$hashref) { $qresult.="$key&"; } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { $qresult=~s/\&$//; &Reply($client, "$qresult\n", $userinput); } else { @@ -2813,13 +2915,19 @@ sub dump_profile_database { while (my ($key,$value) = each(%$hashref)) { my ($v,$symb,$param) = split(/:/,$key); next if ($v eq 'version' || $symb eq 'keys'); - next if (exists($data{$symb}) && - exists($data{$symb}->{$param}) && - $data{$symb}->{'v.'.$param} > $v); - $data{$symb}->{$param}=$value; - $data{$symb}->{'v.'.$param}=$v; + # 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; + } } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { while (my ($symb,$param_hash) = each(%data)) { while(my ($param,$value) = each (%$param_hash)){ next if ($param =~ /^v\./); # Ignore versions... @@ -2874,27 +2982,44 @@ sub dump_with_regexp { my $userinput = "$cmd:$tail"; - my ($udom,$uname,$namespace,$regexp)=split(/:/,$tail); + my ($udom,$uname,$namespace,$regexp,$range)=split(/:/,$tail); if (defined($regexp)) { $regexp=&unescape($regexp); } else { $regexp='.'; } + my ($start,$end); + if (defined($range)) { + if ($range =~/^(\d+)\-(\d+)$/) { + ($start,$end) = ($1,$2); + } elsif ($range =~/^(\d+)$/) { + ($start,$end) = (0,$1); + } else { + undef($range); + } + } my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()); if ($hashref) { my $qresult=''; + my $count=0; while (my ($key,$value) = each(%$hashref)) { if ($regexp eq '.') { + $count++; + if (defined($range) && $count >= $end) { last; } + if (defined($range) && $count < $start) { next; } $qresult.=$key.'='.$value.'&'; } else { my $unescapeKey = &unescape($key); if (eval('$unescapeKey=~/$regexp/')) { + $count++; + if (defined($range) && $count >= $end) { last; } + if (defined($range) && $count < $start) { next; } $qresult.="$key=$value&"; } } } - if (untie(%$hashref)) { + if (&untie_user_hash($hashref)) { chop($qresult); &Reply($client, "$qresult\n", $userinput); } else { @@ -2949,14 +3074,13 @@ sub store_handler { my $version=$hashref->{"version:$rid"}; my $allkeys=''; foreach my $pair (@pairs) { - my ($key,$value)=split(/=/,$pair); + my ($key)=split(/=/,$pair); $allkeys.=$key.':'; - $hashref->{"$version:$rid:$key"}=$value; } - $hashref->{"$version:$rid:timestamp"}=$now; + $hashref->{"$version:$rid"}=$what."\×tamp=$now"; $allkeys.='timestamp'; $hashref->{"$version:keys:$rid"}=$allkeys; - if (untie($hashref)) { + if (&untie_user_hash($hashref)) { &Reply($client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". @@ -3008,24 +3132,30 @@ sub restore_handler { $namespace=~s/\//\_/g; $namespace=~s/\W//g; chomp($rid); - my $proname=&propath($udom,$uname); my $qresult=''; - my %hash; - if (tie(%hash,'GDBM_File',"$proname/$namespace.db", - &GDBM_READER(),0640)) { - my $version=$hash{"version:$rid"}; + my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()); + if ($hashref) { + my $version=$hashref->{"version:$rid"}; $qresult.="version=$version&"; my $scope; for ($scope=1;$scope<=$version;$scope++) { - my $vkeys=$hash{"$scope:keys:$rid"}; + my $vkeys=$hashref->{"$scope:keys:$rid"}; my @keys=split(/:/,$vkeys); my $key; $qresult.="$scope:keys=$vkeys&"; - foreach $key (@keys) { - $qresult.="$scope:$key=".$hash{"$scope:$rid:$key"}."&"; - } + 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"}."&"; + } + } } - if (untie(%hash)) { + if (&untie_user_hash($hashref)) { $qresult=~s/\&$//; &Reply( $client, "$qresult\n", $userinput); } else { @@ -3258,7 +3388,7 @@ sub put_course_id_handler { } $hashref->{$key}=$courseinfo.':'.$now; } - if (untie(%$hashref)) { + if (&untie_domain_hash($hashref)) { &Reply( $client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0) @@ -3374,7 +3504,7 @@ sub dump_course_id_handler { $qresult.=$key.'='.$descr.':'.$inst_code.':'.$owner.'&'; } } - if (untie(%$hashref)) { + if (&untie_domain_hash($hashref)) { chop($qresult); &Reply($client, "$qresult\n", $userinput); } else { @@ -3423,7 +3553,7 @@ sub put_id_handler { my ($key,$value)=split(/=/,$pair); $hashref->{$key}=$value; } - if (untie(%$hashref)) { + if (&untie_domain_hash($hashref)) { &Reply($client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". @@ -3472,7 +3602,7 @@ sub get_id_handler { for (my $i=0;$i<=$#queries;$i++) { $qresult.="$hashref->{$queries[$i]}&"; } - if (untie(%$hashref)) { + if (&untie_domain_hash($hashref)) { $qresult=~s/\&$//; &Reply($client, "$qresult\n", $userinput); } else { @@ -3489,6 +3619,258 @@ sub get_id_handler { ®ister_handler("idget", \&get_id_handler, 0, 1, 0); # +# Puts broadcast e-mail sent by Domain Coordinator in nohist_dcmail database +# +# Parameters +# $cmd - Command keyword that caused us to be dispatched. +# $tail - Tail of the command. Consists of a colon separated: +# domain - the domain whose dcmail we are recording +# email Consists of key=value pair +# where key is unique msgid +# and value is message (in XML) +# $client - Socket open on the client. +# +# Returns: +# 1 - indicating processing should continue. +# Side effects +# reply is written to $client. +# +sub put_dcmail_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + + my ($udom,$what)=split(/:/,$tail); + chomp($what); + my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT()); + if ($hashref) { + my ($key,$value)=split(/=/,$what); + $hashref->{$key}=$value; + } + if (&untie_domain_hash($hashref)) { + &Reply($client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting dcmailput\n", $userinput); + } + return 1; +} +®ister_handler("dcmailput", \&put_dcmail_handler, 0, 1, 0); + +# +# Retrieves broadcast e-mail from nohist_dcmail database +# Returns to client an & separated list of key=value pairs, +# where key is msgid and value is message information. +# +# Parameters +# $cmd - Command keyword that caused us to be dispatched. +# $tail - Tail of the command. Consists of a colon separated: +# domain - the domain whose dcmail table we dump +# startfilter - beginning of time window +# endfilter - end of time window +# sendersfilter - & separated list of username:domain +# for senders to search for. +# $client - Socket open on the client. +# +# Returns: +# 1 - indicating processing should continue. +# Side effects +# reply (& separated list of msgid=messageinfo pairs) is +# written to $client. +# +sub dump_dcmail_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + my ($udom,$startfilter,$endfilter,$sendersfilter) = split(/:/,$tail); + chomp($sendersfilter); + my @senders = (); + if (defined($startfilter)) { + $startfilter=&unescape($startfilter); + } else { + $startfilter='.'; + } + if (defined($endfilter)) { + $endfilter=&unescape($endfilter); + } else { + $endfilter='.'; + } + if (defined($sendersfilter)) { + $sendersfilter=&unescape($sendersfilter); + @senders = map { &unescape($_) } split(/\&/,$sendersfilter); + } + + my $qresult=''; + my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT()); + if ($hashref) { + while (my ($key,$value) = each(%$hashref)) { + my $match = 1; + my ($timestamp,$subj,$uname,$udom) = + split(/:/,&unescape(&unescape($key)),5); # yes, twice really + $subj = &unescape($subj); + unless ($startfilter eq '.' || !defined($startfilter)) { + if ($timestamp < $startfilter) { + $match = 0; + } + } + unless ($endfilter eq '.' || !defined($endfilter)) { + if ($timestamp > $endfilter) { + $match = 0; + } + } + unless (@senders < 1) { + unless (grep/^$uname:$udom$/,@senders) { + $match = 0; + } + } + if ($match == 1) { + $qresult.=$key.'='.$value.'&'; + } + } + if (&untie_domain_hash($hashref)) { + chop($qresult); + &Reply($client, "$qresult\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting dcmaildump\n", $userinput); + } + } else { + &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting dcmaildump\n", $userinput); + } + return 1; +} + +®ister_handler("dcmaildump", \&dump_dcmail_handler, 0, 1, 0); + +# +# Puts domain roles in nohist_domainroles database +# +# Parameters +# $cmd - Command keyword that caused us to be dispatched. +# $tail - Tail of the command. Consists of a colon separated: +# domain - the domain whose roles we are recording +# role - Consists of key=value pair +# where key is unique role +# and value is start/end date information +# $client - Socket open on the client. +# +# Returns: +# 1 - indicating processing should continue. +# Side effects +# reply is written to $client. +# + +sub put_domainroles_handler { + my ($cmd,$tail,$client) = @_; + + my $userinput = "$cmd:$tail"; + my ($udom,$what)=split(/:/,$tail); + chomp($what); + my @pairs=split(/\&/,$what); + my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT()); + 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 domroleput\n", $userinput); + } + } else { + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting domroleput\n", $userinput); + } + + return 1; +} + +®ister_handler("domroleput", \&put_domainroles_handler, 0, 1, 0); + +# +# Retrieves domain roles from nohist_domainroles database +# Returns to client an & separated list of key=value pairs, +# where key is role and value is start and end date information. +# +# Parameters +# $cmd - Command keyword that caused us to be dispatched. +# $tail - Tail of the command. Consists of a colon separated: +# domain - the domain whose domain roles table we dump +# $client - Socket open on the client. +# +# Returns: +# 1 - indicating processing should continue. +# Side effects +# reply (& separated list of role=start/end info pairs) is +# written to $client. +# +sub dump_domainroles_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + my ($udom,$startfilter,$endfilter,$rolesfilter) = split(/:/,$tail); + chomp($rolesfilter); + my @roles = (); + if (defined($startfilter)) { + $startfilter=&unescape($startfilter); + } else { + $startfilter='.'; + } + if (defined($endfilter)) { + $endfilter=&unescape($endfilter); + } else { + $endfilter='.'; + } + if (defined($rolesfilter)) { + $rolesfilter=&unescape($rolesfilter); + @roles = split(/\&/,$rolesfilter); + } + + my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT()); + if ($hashref) { + my $qresult = ''; + while (my ($key,$value) = each(%$hashref)) { + my $match = 1; + my ($start,$end) = split(/:/,&unescape($value)); + my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,&unescape($key)); + unless ($startfilter eq '.' || !defined($startfilter)) { + if ($start >= $startfilter) { + $match = 0; + } + } + unless ($endfilter eq '.' || !defined($endfilter)) { + if ($end <= $endfilter) { + $match = 0; + } + } + unless (@roles < 1) { + unless (grep/^$trole$/,@roles) { + $match = 0; + } + } + if ($match == 1) { + $qresult.=$key.'='.$value.'&'; + } + } + if (&untie_domain_hash($hashref)) { + chop($qresult); + &Reply($client, "$qresult\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting domrolesdump\n", $userinput); + } + } else { + &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting domrolesdump\n", $userinput); + } + return 1; +} + +®ister_handler("domrolesdump", \&dump_domainroles_handler, 0, 1, 0); + + # Process the tmpput command I'm not sure what this does.. Seems to # create a file in the lonDaemons/tmp directory of the form $id.tmp # where Id is the client's ip concatenated with a sequence number. @@ -3967,6 +4349,64 @@ sub get_institutional_code_format_handle ®ister_handler("autoinstcodeformat", \&get_institutional_code_format_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 = &localenroll::photo_permission($cdom,\$perm_reqd, + \$conditions); + &Reply($client, &escape($outcome.':'.$perm_reqd.':'. $conditions)."\n", + $userinput); +} +®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); +} +®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) = &localenroll::manager_photo_update($cdom); + &Reply($client,&escape($update).':'.&escape($comment)."\n",$userinput); +} +®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. @@ -3979,24 +4419,33 @@ 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') { + $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; } @@ -4392,18 +4841,26 @@ sub ReadHostTable { open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file"; my $myloncapaname = $perlvar{'lonHostID'}; Debug("My loncapa name is : $myloncapaname"); + my %name_to_ip; while (my $configline=) { if ($configline !~ /^\s*\#/ && $configline !~ /^\s*$/ ) { my ($id,$domain,$role,$name)=split(/:/,$configline); $name=~s/\s//g; - my $ip = gethostbyname($name); - if (length($ip) ne 4) { - &logthis("Skipping host $id name $name no IP $ip found\n"); - next; + my $ip; + if (!exists($name_to_ip{$name})) { + $ip = gethostbyname($name); + if (!$ip || length($ip) ne 4) { + &logthis("Skipping host $id name $name no IP found\n"); + next; + } + $ip=inet_ntoa($ip); + $name_to_ip{$name} = $ip; + } else { + $ip = $name_to_ip{$name}; } - $ip=inet_ntoa($ip); $hostid{$ip}=$id; # LonCAPA name of host by IP. $hostdom{$id}=$domain; # LonCAPA domain name of host. + $hostname{$id}=$name; # LonCAPA name -> DNS name $hostip{$id}=$ip; # IP address of host. $hostdns{$name} = $id; # LonCAPA name of host by DNS. @@ -4654,12 +5111,12 @@ sub reconlonc { sub subreply { my ($cmd,$server)=@_; - my $peerfile="$perlvar{'lonSockDir'}/$server"; + my $peerfile="$perlvar{'lonSockDir'}/".$hostname{$server}; my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile", Type => SOCK_STREAM, Timeout => 10) or return "con_lost"; - print $sclient "$cmd\n"; + print $sclient "sethost:$server:$cmd\n"; my $answer=<$sclient>; chomp($answer); if (!$answer) { $answer="con_lost"; } @@ -4675,7 +5132,7 @@ sub reply { $answer=subreply("ping",$server); if ($answer ne $server) { &logthis("sub reply: answer != server answer is $answer, server is $server"); - &reconlonc("$perlvar{'lonSockDir'}/$server"); + &reconlonc("$perlvar{'lonSockDir'}/".$hostname{$server}); } $answer=subreply($cmd,$server); } @@ -4768,6 +5225,8 @@ $SIG{USR2} = \&UpdateHosts; ReadHostTable; +my $dist=`$perlvar{'lonDaemons'}/distprobe`; + # -------------------------------------------------------------- # Accept connections. When a connection comes in, it is validated # and if good, a child process is created to process transactions @@ -4843,7 +5302,9 @@ sub make_new_child { # my $tmpsnum=0; # Now global #---------------------------------------------------- kerberos 5 initialization &Authen::Krb5::init_context(); - &Authen::Krb5::init_ets(); + unless (($dist eq 'fedora4') || ($dist eq 'suse9.3')) { + &Authen::Krb5::init_ets(); + } &status('Accepted connection'); # ============================================================================= @@ -4977,7 +5438,7 @@ sub make_new_child { # no need to try to do recon's to myself next; } - &reconlonc("$perlvar{'lonSockDir'}/$id"); + &reconlonc("$perlvar{'lonSockDir'}/".$hostname{$id}); } &logthis("Established connection: $clientname"); &status('Will listen to '.$clientname); @@ -5053,16 +5514,13 @@ sub is_author { # user - Name of the user for which the role is being put. # authtype - The authentication type associated with the user. # -sub manage_permissions -{ - - +sub manage_permissions { my ($request, $domain, $user, $authtype) = @_; &Debug("manage_permissions: $request $domain $user $authtype"); # See if the request is of the form /$domain/_au - if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput... + if($request =~ /^(\/\Q$domain\E\/_au)$/) { # It's an author rolesput... my $execdir = $perlvar{'lonDaemons'}; my $userhome= "/home/$user" ; &logthis("system $execdir/lchtmldir $userhome $user $authtype"); @@ -5311,38 +5769,38 @@ sub addline { sub get_chat { my ($cdom,$cname,$udom,$uname)=@_; - my %hash; - my $proname=&propath($cdom,$cname); + my @entries=(); - if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db", - &GDBM_READER(),0640)) { - @entries=map { $_.':'.$hash{$_} } sort keys %hash; - untie %hash; + my $hashref = &tie_user_hash($cdom, $cname, 'nohist_chatroom', + &GDBM_READER()); + if ($hashref) { + @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref)); + &untie_user_hash($hashref); } my @participants=(); my $cutoff=time-60; - if (tie(%hash,'GDBM_File',"$proname/nohist_inchatroom.db", - &GDBM_WRCREAT(),0640)) { - $hash{$uname.':'.$udom}=time; - foreach (sort keys %hash) { - if ($hash{$_}>$cutoff) { - $participants[$#participants+1]='active_participant:'.$_; + $hashref = &tie_user_hash($cdom, $cname, 'nohist_inchatroom', + &GDBM_WRCREAT()); + if ($hashref) { + $hashref->{$uname.':'.$udom}=time; + foreach my $user (sort(keys(%$hashref))) { + if ($hashref->{$user}>$cutoff) { + push(@participants, 'active_participant:'.$user); } } - untie %hash; + &untie_user_hash($hashref); } return (@participants,@entries); } sub chat_add { my ($cdom,$cname,$newchat)=@_; - my %hash; - my $proname=&propath($cdom,$cname); my @entries=(); my $time=time; - if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db", - &GDBM_WRCREAT(),0640)) { - @entries=map { $_.':'.$hash{$_} } sort keys %hash; + my $hashref = &tie_user_hash($cdom, $cname, 'nohist_chatroom', + &GDBM_WRCREAT()); + if ($hashref) { + @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref)); my ($lastid)=($entries[$#entries]=~/^(\w+)\:/); my ($thentime,$idnum)=split(/\_/,$lastid); my $newid=$time.'_000000'; @@ -5352,21 +5810,22 @@ sub chat_add { $idnum=substr('000000'.$idnum,-6,6); $newid=$time.'_'.$idnum; } - $hash{$newid}=$newchat; + $hashref->{$newid}=$newchat; my $expired=$time-3600; - foreach (keys %hash) { - my ($thistime)=($_=~/(\d+)\_/); + foreach my $comment (keys(%$hashref)) { + my ($thistime) = ($comment=~/(\d+)\_/); if ($thistime<$expired) { - delete $hash{$_}; + delete $hashref->{$comment}; } } - untie %hash; - } - { - my $hfh; - if ($hfh=IO::File->new(">>$proname/chatroom.log")) { - print $hfh "$time:".&unescape($newchat)."\n"; + { + my $proname=&propath($cdom,$cname); + if (open(CHATLOG,">>$proname/chatroom.log")) { + print CHATLOG ("$time:".&unescape($newchat)."\n"); + } + close(CHATLOG); } + &untie_user_hash($hashref); } } @@ -5455,7 +5914,7 @@ sub thisversion { sub subscribe { my ($userinput,$clientip)=@_; my $result; - my ($cmd,$fname)=split(/:/,$userinput); + my ($cmd,$fname)=split(/:/,$userinput,2); my $ownership=&ishome($fname); if ($ownership eq 'owner') { # explitly asking for the current version? @@ -5499,6 +5958,35 @@ sub subscribe { } return $result; } +# Change the passwd of a unix user. The caller must have +# first verified that the user is a loncapa user. +# +# Parameters: +# user - Unix user name to change. +# pass - New password for the user. +# Returns: +# ok - if success +# other - Some meaningfule error message string. +# NOTE: +# invokes a setuid script to change the passwd. +sub change_unix_password { + my ($user, $pass) = @_; + + &Debug("change_unix_password"); + my $execdir=$perlvar{'lonDaemons'}; + &Debug("Opening lcpasswd pipeline"); + my $pf = IO::File->new("|$execdir/lcpasswd > " + ."$perlvar{'lonDaemons'}" + ."/logs/lcpasswd.log"); + print $pf "$user\n$pass\n$pass\n"; + close $pf; + my $err = $?; + return ($err < @passwderrors) ? $passwderrors[$err] : + "pwchange_falure - unknown error"; + + +} + sub make_passwd_file { my ($uname, $umode,$npass,$passfilename)=@_;