--- loncom/lond 2004/08/23 11:24:45 1.234 +++ loncom/lond 2006/01/21 08:26:52 1.306 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.234 2004/08/23 11:24:45 foxr Exp $ +# $Id: lond,v 1.306 2006/01/21 08:26:52 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -46,7 +46,9 @@ use Authen::Krb5; use lib '/home/httpd/lib/perl/'; use localauth; use localenroll; +use localstudentphoto; use File::Copy; +use File::Find; use LONCAPA::ConfigFileEdit; use LONCAPA::lonlocal; use LONCAPA::lonssl; @@ -57,14 +59,13 @@ my $DEBUG = 0; # Non zero to ena my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.234 $'; #' stupid emacs +my $VERSION='$Revision: 1.306 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; my $client; my $clientip; # IP address of client. -my $clientdns; # DNS name of client. my $clientname; # LonCAPA name of client. my $server; @@ -112,20 +113,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.: @@ -177,7 +178,6 @@ sub ResetStatistics { # $initcmd - The full text of the init command. # # Implicit inputs: -# $clientdns - The DNS name of the remote client. # $thisserver - Our DNS name. # # Returns: @@ -186,10 +186,10 @@ sub ResetStatistics { # sub LocalConnection { my ($Socket, $initcmd) = @_; - Debug("Attempting local connection: $initcmd client: $clientdns me: $thisserver"); - if($clientdns ne $thisserver) { + Debug("Attempting local connection: $initcmd client: $clientip me: $thisserver"); + if($clientip ne "127.0.0.1") { &logthis(' LocalConnection rejecting non local: ' - ."$clientdns ne $thisserver "); + ."$clientip ne $thisserver "); close $Socket; return undef; } else { @@ -331,8 +331,43 @@ sub InsecureConnection { } - # +# Safely execute a command (as long as it's not a shel command and doesn +# not require/rely on shell escapes. The function operates by doing a +# a pipe based fork and capturing stdout and stderr from the pipe. +# +# Formal Parameters: +# $line - A line of text to be executed as a command. +# Returns: +# The output from that command. If the output is multiline the caller +# must know how to split up the output. +# +# +sub execute_command { + my ($line) = @_; + my @words = split(/\s/, $line); # Bust the command up into words. + my $output = ""; + + my $pid = open(CHILD, "-|"); + + if($pid) { # Parent process + Debug("In parent process for execute_command"); + my @data = ; # Read the child's outupt... + close CHILD; + foreach my $output_line (@data) { + Debug("Adding $output_line"); + $output .= $output_line; # Presumably has a \n on it. + } + + } else { # Child process + close (STDERR); + open (STDERR, ">&STDOUT");# Combine stderr, and stdout... + exec(@words); # won't return. + } + return $output; +} + + # GetCertificate: Given a transaction that requires a certificate, # this function will extract the certificate from the transaction # request. Note that at this point, the only concept of a certificate @@ -438,39 +473,11 @@ sub CopyFile { my ($oldfile, $newfile) = @_; - # The file must exist: - - if(-e $oldfile) { - - # Read the old file. - - my $oldfh = IO::File->new("< $oldfile"); - if(!$oldfh) { - return 0; - } - my @contents = <$oldfh>; # Suck in the entire file. - - # write the backup file: - - my $newfh = IO::File->new("> $newfile"); - if(!(defined $newfh)){ - return 0; - } - my $lines = scalar @contents; - for (my $i =0; $i < $lines; $i++) { - print $newfh ($contents[$i]); - } - - $oldfh->close; - $newfh->close; - - chmod(0660, $newfile); - - return 1; - - } else { - return 0; + if (! copy($oldfile,$newfile)) { + return 0; } + chmod(0660, $newfile); + return 1; } # # Host files are passed out with externally visible host IPs. @@ -1013,7 +1020,7 @@ sub tie_user_hash { $how, 0640)) { # If this is a namespace for which a history is kept, # make the history log entry: - if (($namespace =~/^nohist\_/) && (defined($loghead))) { + if (($namespace !~/^nohist\_/) && (defined($loghead))) { my $args = scalar @_; Debug(" Opening history: $namespace $args"); my $hfh = IO::File->new(">>$proname/$namespace.hist"); @@ -1030,6 +1037,50 @@ sub tie_user_hash { } +# read_profile +# +# Returns a set of specific entries from a user's profile file. +# this is a utility function that is used by both get_profile_entry and +# get_profile_entry_encrypted. +# +# Parameters: +# udom - Domain in which the user exists. +# uname - User's account name (loncapa account) +# namespace - The profile namespace to open. +# what - A set of & separated queries. +# Returns: +# If all ok: - The string that needs to be shipped back to the user. +# If failure - A string that starts with error: followed by the failure +# reason.. note that this probabyl gets shipped back to the +# user as well. +# +sub read_profile { + my ($udom, $uname, $namespace, $what) = @_; + + my $hashref = &tie_user_hash($udom, $uname, $namespace, + &GDBM_READER()); + if ($hashref) { + my @queries=split(/\&/,$what); + my $qresult=''; + + for (my $i=0;$i<=$#queries;$i++) { + $qresult.="$hashref->{$queries[$i]}&"; # Presumably failure gives empty string. + } + $qresult=~s/\&$//; # Remove trailing & from last lookup. + if (untie %$hashref) { + return $qresult; + } else { + return "error: ".($!+0)." untie (GDBM) Failed"; + } + } else { + if ($!+0 == 2) { + return "error:No such file or GDBM reported bad block error"; + } else { + return "error: ".($!+0)." tie (GDBM) Failed"; + } + } + +} #--------------------- Request Handlers -------------------------------------------- # # By convention each request handler registers itself prior to the sub @@ -1051,7 +1102,6 @@ sub tie_user_hash { # 0 - Program should exit. # Side effects: # Reply information is sent to the client. - sub ping_handler { my ($cmd, $tail, $client) = @_; Debug("$cmd $tail $client .. $currenthostid:"); @@ -1079,7 +1129,6 @@ sub ping_handler { # 0 - Program should exit. # Side effects: # Reply information is sent to the client. - sub pong_handler { my ($cmd, $tail, $replyfd) = @_; @@ -1134,7 +1183,6 @@ sub establish_key_handler { } ®ister_handler("ekey", \&establish_key_handler, 0, 1,1); - # Handler for the load command. Returns the current system load average # to the requestor. # @@ -1169,7 +1217,7 @@ sub load_handler { return 1; } -register_handler("load", \&load_handler, 0, 1, 0); +®ister_handler("load", \&load_handler, 0, 1, 0); # # Process the userload request. This sub returns to the client the current @@ -1199,7 +1247,7 @@ sub user_load_handler { return 1; } -register_handler("userload", \&user_load_handler, 0, 1, 0); +®ister_handler("userload", \&user_load_handler, 0, 1, 0); # Process a request for the authorization type of a user: # (userauth). @@ -1235,8 +1283,10 @@ sub user_authorization_type { my ($type,$otherinfo) = split(/:/,$result); if($type =~ /^krb/) { $type = $result; - } - &Reply( $replyfd, "$type:\n", $userinput); + } else { + $type .= ':'; + } + &Reply( $replyfd, "$type\n", $userinput); } return 1; @@ -1256,7 +1306,6 @@ sub user_authorization_type { # 0 - Program should exit # Implicit Output: # a reply is written to the client. - sub push_file_handler { my ($cmd, $tail, $client) = @_; @@ -1282,7 +1331,190 @@ sub push_file_handler { } ®ister_handler("pushfile", \&push_file_handler, 1, 0, 1); +# +# du - list the disk usuage of a directory recursively. +# +# note: stolen code from the ls file handler +# under construction by Rick Banghart +# . +# Parameters: +# $cmd - The command that dispatched us (du). +# $ududir - The directory path to list... I'm not sure what this +# is relative as things like ls:. return e.g. +# no_such_dir. +# $client - Socket open on the client. +# Returns: +# 1 - indicating that the daemon should not disconnect. +# Side Effects: +# The reply is written to $client. +# +sub du_handler { + my ($cmd, $ududir, $client) = @_; + my ($ududir) = split(/:/,$ududir); # Make 'telnet' testing easier. + my $userinput = "$cmd:$ududir"; + + if ($ududir=~/\.\./ || $ududir!~m|^/home/httpd/|) { + &Failure($client,"refused\n","$cmd:$ududir"); + return 1; + } + # Since $ududir could have some nasties in it, + # we will require that ududir is a valid + # directory. Just in case someone tries to + # slip us a line like .;(cd /home/httpd rm -rf*) + # etc. + # + if (-d $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; +} +®ister_handler("du", \&du_handler, 0, 1, 0); + +# +# The ls_handler routine should be considered obosolete and is retained +# for communication with legacy servers. Please see the ls2_handler. +# +# ls - list the contents of a directory. For each file in the +# selected directory the filename followed by the full output of +# the stat function is returned. The returned info for each +# file are separated by ':'. The stat fields are separated by &'s. +# Parameters: +# $cmd - The command that dispatched us (ls). +# $ulsdir - The directory path to list... I'm not sure what this +# is relative as things like ls:. return e.g. +# no_such_dir. +# $client - Socket open on the client. +# Returns: +# 1 - indicating that the daemon should not disconnect. +# Side Effects: +# The reply is written to $client. +# +sub ls_handler { + # obsoleted by ls2_handler + my ($cmd, $ulsdir, $client) = @_; + + my $userinput = "$cmd:$ulsdir"; + + my $obs; + my $rights; + my $ulsout=''; + my $ulsfn; + if (-e $ulsdir) { + if(-d $ulsdir) { + if (opendir(LSDIR,$ulsdir)) { + while ($ulsfn=readdir(LSDIR)) { + 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|1)/) { $obs = 1; } + if($obsolete =~ m|()(default)|) { $rights = 1; } + } + } + $ulsout.=$ulsfn.'&'.join('&',@ulsstats); + if($obs eq '1') { $ulsout.="&1"; } + else { $ulsout.="&0"; } + if($rights eq '1') { $ulsout.="&1:"; } + else { $ulsout.="&0:"; } + } + closedir(LSDIR); + } + } else { + my @ulsstats=stat($ulsdir); + $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; + } + } else { + $ulsout='no_such_dir'; + } + if ($ulsout eq '') { $ulsout='empty'; } + &Reply($client, "$ulsout\n", $userinput); # This supports debug logging. + + return 1; + +} +®ister_handler("ls", \&ls_handler, 0, 1, 0); + +# +# Please also see the ls_handler, which this routine obosolets. +# ls2_handler differs from ls_handler in that it escapes its return +# values before concatenating them together with ':'s. +# +# ls2 - list the contents of a directory. For each file in the +# selected directory the filename followed by the full output of +# the stat function is returned. The returned info for each +# file are separated by ':'. The stat fields are separated by &'s. +# Parameters: +# $cmd - The command that dispatched us (ls). +# $ulsdir - The directory path to list... I'm not sure what this +# is relative as things like ls:. return e.g. +# no_such_dir. +# $client - Socket open on the client. +# Returns: +# 1 - indicating that the daemon should not disconnect. +# Side Effects: +# The reply is written to $client. +# +sub ls2_handler { + my ($cmd, $ulsdir, $client) = @_; + my $userinput = "$cmd:$ulsdir"; + + my $obs; + my $rights; + my $ulsout=''; + my $ulsfn; + if (-e $ulsdir) { + if(-d $ulsdir) { + if (opendir(LSDIR,$ulsdir)) { + while ($ulsfn=readdir(LSDIR)) { + 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|1)/) { $obs = 1; } + if($obsolete =~ m|()(default)|) { + $rights = 1; + } + } + } + my $tmp = $ulsfn.'&'.join('&',@ulsstats); + if ($obs eq '1') { $tmp.="&1"; } else { $tmp.="&0"; } + if ($rights eq '1') { $tmp.="&1"; } else { $tmp.="&0"; } + $ulsout.= &escape($tmp).':'; + } + closedir(LSDIR); + } + } else { + my @ulsstats=stat($ulsdir); + $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; + } + } else { + $ulsout='no_such_dir'; + } + if ($ulsout eq '') { $ulsout='empty'; } + &Reply($client, "$ulsout\n", $userinput); # This supports debug logging. + return 1; +} +®ister_handler("ls2", \&ls2_handler, 0, 1, 0); # Process a reinit request. Reinit requests that either # lonc or lond be reinitialized so that an updated @@ -1313,7 +1545,6 @@ sub reinit_process_handler { } return 1; } - ®ister_handler("reinit", \&reinit_process_handler, 1, 0, 1); # Process the editing script for a table edit operation. @@ -1355,8 +1586,7 @@ sub edit_table_handler { } return 1; } -register_handler("edit", \&edit_table_handler, 1, 0, 1); - +®ister_handler("edit", \&edit_table_handler, 1, 0, 1); # # Authenticate a user against the LonCAPA authentication @@ -1411,8 +1641,7 @@ sub authenticate_handler { return 1; } - -register_handler("auth", \&authenticate_handler, 1, 1, 0); +®ister_handler("auth", \&authenticate_handler, 1, 1, 0); # # Change a user's password. Note that this function is complicated by @@ -1475,19 +1704,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 @@ -1503,8 +1722,7 @@ sub change_password_handler { return 1; } -register_handler("passwd", \&change_password_handler, 1, 1, 0); - +®ister_handler("passwd", \&change_password_handler, 1, 1, 0); # # Create a new user. User in this case means a lon-capa user. @@ -1543,18 +1761,10 @@ sub add_user_handler { if (-e $passfilename) { &Failure( $client, "already_exists\n", $userinput); } else { - my @fpparts=split(/\//,$passfilename); - my $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2]; my $fperror=''; - for (my $i=3;$i<= ($#fpparts-1);$i++) { - $fpnow.='/'.$fpparts[$i]; - unless (-e $fpnow) { - &logthis("mkdir $fpnow"); - unless (mkdir($fpnow,0777)) { - $fperror="error: ".($!+0)." mkdir failed while attempting " - ."makeuser"; - } - } + if (!&mkpath($passfilename)) { + $fperror="error: ".($!+0)." mkdir failed while attempting " + ."makeuser"; } unless ($fperror) { my $result=&make_passwd_file($uname, $umode,$npass, $passfilename); @@ -1595,6 +1805,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 { @@ -1611,12 +1824,46 @@ sub change_authentication_handler { chomp($npass); $npass=&unescape($npass); + 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); - &Reply($client, $result, $userinput); + # 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); + } + + } else { - &Failure($client, "non_authorized", $userinput); # Fail the user now. + &Failure($client, "non_authorized\n", $userinput); # Fail the user now. } } return 1; @@ -1726,6 +1973,14 @@ sub update_resource_handler { alarm(0); } rename($transname,$fname); + use Cache::Memcached; + my $memcache= + new Cache::Memcached({'servers'=>['127.0.0.1:11211']}); + my $url=$fname; + $url=~s-^/home/httpd/html--; + $url=~s-\.meta$--; + my $id=&escape('meta:'.$url); + $memcache->delete($id); } } &Reply( $client, "ok\n", $userinput); @@ -1769,21 +2024,10 @@ sub fetch_user_file_handler { # Note that any regular files in the way of this path are # wiped out to deal with some earlier folly of mine. - my $path = $udir; - if ($ufile =~m|(.+)/([^/]+)$|) { - my @parts=split('/',$1); - foreach my $part (@parts) { - $path .= '/'.$part; - if( -f $path) { - unlink($path); - } - if ((-e $path)!=1) { - mkdir($path,0770); - } - } + if (!&mkpath($udir.'/'.$ufile)) { + &Failure($client, "unable_to_create\n", $userinput); } - my $destname=$udir.'/'.$ufile; my $transname=$udir.'/'.$ufile.'.in.transit'; my $remoteurl='http://'.$clientip.'/userfiles/'.$fname; @@ -1827,7 +2071,6 @@ sub fetch_user_file_handler { # # Returns: # 1 - Continue processing. - sub remove_user_file_handler { my ($cmd, $tail, $client) = @_; @@ -1843,8 +2086,19 @@ sub remove_user_file_handler { if (-e $udir) { my $file=$udir.'/userfiles/'.$ufile; if (-e $file) { - unlink($file); + # + # If the file is a regular file unlink is fine... + # However it's possible the client wants a dir. + # removed, in which case rmdir is more approprate: + # + if (-f $file){ + unlink($file); + } elsif(-d $file) { + rmdir($file); + } if (-e $file) { + # File is still there after we deleted it ?!? + &Failure($client, "failed\n", "$cmd:$tail"); } else { &Reply($client, "ok\n", "$cmd:$tail"); @@ -1860,10 +2114,86 @@ sub remove_user_file_handler { } ®ister_handler("removeuserfile", \&remove_user_file_handler, 0,1,0); +# +# make a directory in a user's home directory userfiles subdirectory. +# Parameters: +# cmd - the Lond request keyword that got us here. +# tail - the part of the command past the keyword. +# client- File descriptor connected with the client. +# +# Returns: +# 1 - Continue processing. +sub mkdir_user_file_handler { + my ($cmd, $tail, $client) = @_; + + my ($dir) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent. + $dir=&unescape($dir); + my ($udom,$uname,$ufile) = ($dir =~ m|^([^/]+)/([^/]+)/(.+)$|); + if ($ufile =~m|/\.\./|) { + # any files paths with /../ in them refuse + # to deal with + &Failure($client, "refused\n", "$cmd:$tail"); + } else { + my $udir = &propath($udom,$uname); + if (-e $udir) { + my $newdir=$udir.'/userfiles/'.$ufile.'/'; + if (!&mkpath($newdir)) { + &Failure($client, "failed\n", "$cmd:$tail"); + } + &Reply($client, "ok\n", "$cmd:$tail"); + } else { + &Failure($client, "not_home\n", "$cmd:$tail"); + } + } + return 1; +} +®ister_handler("mkdiruserfile", \&mkdir_user_file_handler, 0,1,0); # -# Authenticate access to a user file by checking the user's -# session token(?) +# rename a file in a user's home directory userfiles subdirectory. +# Parameters: +# cmd - the Lond request keyword that got us here. +# tail - the part of the command past the keyword. +# client- File descriptor connected with the client. +# +# Returns: +# 1 - Continue processing. +sub rename_user_file_handler { + my ($cmd, $tail, $client) = @_; + + my ($udom,$uname,$old,$new) = split(/:/, $tail); + $old=&unescape($old); + $new=&unescape($new); + if ($new =~m|/\.\./| || $old =~m|/\.\./|) { + # any files paths with /../ in them refuse to deal with + &Failure($client, "refused\n", "$cmd:$tail"); + } else { + my $udir = &propath($udom,$uname); + if (-e $udir) { + my $oldfile=$udir.'/userfiles/'.$old; + my $newfile=$udir.'/userfiles/'.$new; + if (-e $newfile) { + &Failure($client, "exists\n", "$cmd:$tail"); + } elsif (! -e $oldfile) { + &Failure($client, "not_found\n", "$cmd:$tail"); + } else { + if (!rename($oldfile,$newfile)) { + &Failure($client, "failed\n", "$cmd:$tail"); + } else { + &Reply($client, "ok\n", "$cmd:$tail"); + } + } + } else { + &Failure($client, "not_home\n", "$cmd:$tail"); + } + } + return 1; +} +®ister_handler("renameuserfile", \&rename_user_file_handler, 0,1,0); + +# +# Authenticate access to a user file by checking that the token the user's +# passed also exists in their session file # # Parameters: # cmd - The request keyword that dispatched to tus. @@ -1871,31 +2201,28 @@ sub remove_user_file_handler { # client - Filehandle open on the client. # Return: # 1. - sub token_auth_user_file_handler { my ($cmd, $tail, $client) = @_; my ($fname, $session) = split(/:/, $tail); chomp($session); - my $reply='non_auth'; + my $reply="non_auth\n"; if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'. $session.'.id')) { while (my $line=) { - if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply='ok'; } + if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply="ok\n"; } } close(ENVIN); - &Reply($client, $reply); + &Reply($client, $reply, "$cmd:$tail"); } else { &Failure($client, "invalid_token\n", "$cmd:$tail"); } return 1; } - ®ister_handler("tokenauthuserfile", \&token_auth_user_file_handler, 0,1,0); - # # Unsubscribe from a resource. # @@ -1924,6 +2251,7 @@ sub unsubscribe_handler { return 1; } ®ister_handler("unsub", \&unsubscribe_handler, 0, 1, 0); + # Subscribe to a resource # # Parameters: @@ -2002,7 +2330,7 @@ sub activity_log_handler { return 1; } -register_handler("log", \&activity_log_handler, 0, 1, 0); +®ister_handler("log", \&activity_log_handler, 0, 1, 0); # # Put a namespace entry in a user profile hash. @@ -2023,7 +2351,7 @@ sub put_user_profile_entry { my $userinput = "$cmd:$tail"; - my ($udom,$uname,$namespace,$what) =split(/:/,$tail); + my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4); if ($namespace ne 'roles') { chomp($what); my $hashref = &tie_user_hash($udom, $uname, $namespace, @@ -2053,6 +2381,61 @@ sub put_user_profile_entry { } ®ister_handler("put", \&put_user_profile_entry, 0, 1, 0); +# Put a piece of new data in hash, returns error if entry already exists +# 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. +# +sub newput_user_profile_entry { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4); + if ($namespace eq 'roles') { + &Failure( $client, "refused\n", $userinput); + return 1; + } + + chomp($what); + + my $hashref = &tie_user_hash($udom, $uname, $namespace, + &GDBM_WRCREAT(),"N",$what); + if(!$hashref) { + &Failure( $client, "error: ".($!)." tie(GDBM) Failed ". + "while attempting put\n", $userinput); + return 1; + } + + my @pairs=split(/\&/,$what); + foreach my $pair (@pairs) { + my ($key,$value)=split(/=/,$pair); + if (exists($hashref->{$key})) { + &Failure($client, "key_exists: ".$key."\n",$userinput); + return 1; + } + } + + foreach my $pair (@pairs) { + my ($key,$value)=split(/=/,$pair); + $hashref->{$key}=$value; + } + + if (untie(%$hashref)) { + &Reply( $client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". + "while attempting put\n", + $userinput); + } + return 1; +} +®ister_handler("newput", \&newput_user_profile_entry, 0, 1, 0); + # # Increment a profile entry in the user history file. # The history contains keyword value pairs. In this case, @@ -2083,11 +2466,17 @@ sub increment_user_value_handler { my @pairs=split(/\&/,$what); foreach my $pair (@pairs) { my ($key,$value)=split(/=/,$pair); + $value = &unescape($value); # We could check that we have a number... if (! defined($value) || $value eq '') { $value = 1; } $hashref->{$key}+=$value; + if ($namespace eq 'nohist_resourcetracker') { + if ($hashref->{$key} < 0) { + $hashref->{$key} = 0; + } + } } if (untie(%$hashref)) { &Reply( $client, "ok\n", $userinput); @@ -2107,7 +2496,6 @@ sub increment_user_value_handler { } ®ister_handler("inc", \&increment_user_value_handler, 0, 1, 0); - # # Put a new role for a user. Roles are LonCAPA's packaging of permissions. # Each 'role' a user has implies a set of permissions. Adding a new role @@ -2147,11 +2535,14 @@ sub roles_put_handler { # is done on close this improves the chances the log will be an un- # corrupted ordered thing. if ($hashref) { + my $pass_entry = &get_auth_type($udom, $uname); + my ($auth_type,$pwd) = split(/:/, $pass_entry); + $auth_type = $auth_type.":"; my @pairs=split(/\&/,$what); foreach my $pair (@pairs) { my ($key,$value)=split(/=/,$pair); &manage_permissions($key, $udom, $uname, - &get_auth_type( $udom, $uname)); + $auth_type); $hashref->{$key}=$value; } if (untie($hashref)) { @@ -2246,32 +2637,17 @@ sub get_profile_entry { my ($udom,$uname,$namespace,$what) = split(/:/,$tail); chomp($what); - my $hashref = &tie_user_hash($udom, $uname, $namespace, - &GDBM_READER()); - if ($hashref) { - my @queries=split(/\&/,$what); - my $qresult=''; - - for (my $i=0;$i<=$#queries;$i++) { - $qresult.="$hashref->{$queries[$i]}&"; # Presumably failure gives empty string. - } - $qresult=~s/\&$//; # Remove trailing & from last lookup. - if (untie(%$hashref)) { - &Reply($client, "$qresult\n", $userinput); - } else { - &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". - "while attempting get\n", $userinput); - } + + my $replystring = read_profile($udom, $uname, $namespace, $what); + my ($first) = split(/:/,$replystring); + if($first ne "error") { + &Reply($client, "$replystring\n", $userinput); } else { - if ($!+0 == 2) { # +0 coerces errno -> number 2 is ENOENT - &Failure($client, "error:No such file or ". - "GDBM reported bad block error\n", $userinput); - } else { # Some other undifferentiated err. - &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". - "while attempting get\n", $userinput); - } + &Failure($client, $replystring." while attempting get\n", $userinput); } return 1; + + } ®ister_handler("get", \&get_profile_entry, 0,1,0); @@ -2301,42 +2677,33 @@ sub get_profile_entry_encrypted { my ($cmd,$udom,$uname,$namespace,$what) = split(/:/,$userinput); chomp($what); - my $hashref = &tie_user_hash($udom, $uname, $namespace, - &GDBM_READER()); - if ($hashref) { - my @queries=split(/\&/,$what); - my $qresult=''; - for (my $i=0;$i<=$#queries;$i++) { - $qresult.="$hashref->{$queries[$i]}&"; - } - if (untie(%$hashref)) { - $qresult=~s/\&$//; - if ($cipher) { - my $cmdlength=length($qresult); - $qresult.=" "; - my $encqresult=''; - for(my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { - $encqresult.= unpack("H16", - $cipher->encrypt(substr($qresult, - $encidx, - 8))); - } - &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput); - } else { - &Failure( $client, "error:no_key\n", $userinput); + my $qresult = read_profile($udom, $uname, $namespace, $what); + my ($first) = split(/:/, $qresult); + if($first ne "error") { + + if ($cipher) { + my $cmdlength=length($qresult); + $qresult.=" "; + my $encqresult=''; + for(my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encqresult.= unpack("H16", + $cipher->encrypt(substr($qresult, + $encidx, + 8))); } + &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput); } else { - &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". - "while attempting eget\n", $userinput); - } + &Failure( $client, "error:no_key\n", $userinput); + } } else { - &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". - "while attempting eget\n", $userinput); + &Failure($client, "$qresult while attempting eget\n", $userinput); + } return 1; } -®ister_handler("eget", \&GetProfileEntryEncrypted, 0, 1, 0); +®ister_handler("eget", \&get_profile_entry_encrypted, 0, 1, 0); + # # Deletes a key in a user profile database. # @@ -2355,7 +2722,6 @@ sub get_profile_entry_encrypted { # 0 - Exit server. # # - sub delete_profile_entry { my ($cmd, $tail, $client) = @_; @@ -2384,6 +2750,7 @@ sub delete_profile_entry { return 1; } ®ister_handler("del", \&delete_profile_entry, 0, 1, 0); + # # List the set of keys that are defined in a profile database file. # A successful reply from this will contain an & separated list of @@ -2528,22 +2895,39 @@ 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&"; } } @@ -2562,7 +2946,6 @@ sub dump_with_regexp { return 1; } - ®ister_handler("dump", \&dump_with_regexp, 0, 1, 0); # Store a set of key=value pairs associated with a versioned name. @@ -2594,7 +2977,7 @@ sub store_handler { chomp($what); my @pairs=split(/\&/,$what); my $hashref = &tie_user_hash($udom, $uname, $namespace, - &GDBM_WRCREAT(), "P", + &GDBM_WRCREAT(), "S", "$rid:$what"); if ($hashref) { my $now = time; @@ -2628,6 +3011,7 @@ sub store_handler { return 1; } ®ister_handler("store", \&store_handler, 0, 1, 0); + # # 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 @@ -2728,6 +3112,7 @@ sub send_chat_handler { return 1; } ®ister_handler("chatsend", \&send_chat_handler, 0, 1, 0); + # # Retrieve the set of chat messagss from a discussion board. # @@ -2863,6 +3248,14 @@ sub reply_query_handler { # $tail - Tail of the command. In this case consists of a colon # separated list contaning the domain to apply this to and # an ampersand separated list of keyword=value pairs. +# Each value is a colon separated list that includes: +# description, institutional code and course owner. +# For backward compatibility with versions included +# in LON-CAPA 1.1.X (and earlier) and 1.2.X, institutional +# code and/or course owner are preserved from the existing +# record when writing a new record in response to 1.1 or +# 1.2 implementations of lonnet::flushcourselogs(). +# # $client - Socket open on the client. # Returns: # 1 - indicating that processing should continue @@ -2876,7 +3269,7 @@ sub put_course_id_handler { my $userinput = "$cmd:$tail"; - my ($udom, $what) = split(/:/, $tail); + my ($udom, $what) = split(/:/, $tail,2); chomp($what); my $now=time; my @pairs=split(/\&/,$what); @@ -2884,21 +3277,38 @@ sub put_course_id_handler { my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()); if ($hashref) { foreach my $pair (@pairs) { - my ($key,$value)=split(/=/,$pair); - $hashref->{$key}=$value.':'.$now; + my ($key,$courseinfo) = split(/=/,$pair,2); + $courseinfo =~ s/=/:/g; + + my @current_items = split(/:/,$hashref->{$key}); + shift(@current_items); # remove description + pop(@current_items); # remove last access + my $numcurrent = scalar(@current_items); + + 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]; + } + } + $hashref->{$key}=$courseinfo.':'.$now; } if (untie(%$hashref)) { - &Reply($client, "ok\n", $userinput); + &Reply( $client, "ok\n", $userinput); } else { - &Failure( $client, "error: ".($!+0) + &Failure($client, "error: ".($!+0) ." untie(GDBM) Failed ". "while attempting courseidput\n", $userinput); } } else { - &Failure( $client, "error: ".($!+0) + &Failure($client, "error: ".($!+0) ." tie(GDBM) Failed ". "while attempting courseidput\n", $userinput); } + return 1; } @@ -2922,6 +3332,15 @@ sub put_course_id_handler { # description - regular expression that is used to filter # the dump. Only keywords matching this regexp # will be used. +# 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. # $client - The socket open on the client. # Returns: # 1 - Continue processing. @@ -2932,32 +3351,66 @@ sub dump_course_id_handler { my $userinput = "$cmd:$tail"; - my ($udom,$since,$description) =split(/:/,$tail); + my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter) =split(/:/,$tail); if (defined($description)) { $description=&unescape($description); } else { $description='.'; } + if (defined($instcodefilter)) { + $instcodefilter=&unescape($instcodefilter); + } else { + $instcodefilter='.'; + } + if (defined($ownerfilter)) { + $ownerfilter=&unescape($ownerfilter); + } else { + $ownerfilter='.'; + } + if (defined($coursefilter)) { + $coursefilter=&unescape($coursefilter); + } else { + $coursefilter='.'; + } + 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); - if ($value =~ m/^([^\:]*):([^\:]*):(\d+)$/) { - ($descr,$inst_code,$lasttime)=($1,$2,$3); - } else { - ($descr,$lasttime) = split(/\:/,$value); - } + my ($descr,$lasttime,$inst_code,$owner); + my @courseitems = split(/:/,$value); + $lasttime = pop(@courseitems); + ($descr,$inst_code,$owner)=@courseitems; if ($lasttime<$since) { next; } - if ($description eq '.') { - $qresult.=$key.'='.$descr.':'.$inst_code.'&'; - } else { - my $unescapeVal = &unescape($descr); - if (eval('$unescapeVal=~/\Q$description\E/i')) { - $qresult.=$key.'='.$descr.':'.$inst_code.'&'; + my $match = 1; + unless ($description eq '.') { + my $unescapeDescr = &unescape($descr); + unless (eval('$unescapeDescr=~/\Q$description\E/i')) { + $match = 0; } + } + unless ($instcodefilter eq '.' || !defined($instcodefilter)) { + my $unescapeInstcode = &unescape($inst_code); + 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; + } + } + unless ($coursefilter eq '.' || !defined($coursefilter)) { + my $unescapeCourse = &unescape($key); + unless (eval('$unescapeCourse=~/^$udom(_)\Q$coursefilter\E$/')) { + $match = 0; + } + } + if ($match == 1) { + $qresult.=$key.'='.$descr.':'.$inst_code.':'.$owner.'&'; + } } if (untie(%$hashref)) { chop($qresult); @@ -2975,14 +3428,893 @@ sub dump_course_id_handler { return 1; } ®ister_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0); + +# +# Puts an id to a domains id database. +# +# Parameters: +# $cmd - The command that triggered us. +# $tail - Remainder of the request other than the command. This is a +# colon separated list containing: +# $domain - The domain for which we are writing the id. +# $pairs - The id info to write... this is and & separated list +# of keyword=value. +# $client - Socket open on the client. +# Returns: +# 1 - Continue processing. +# Side effects: +# reply is written to $client. +# +sub put_id_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, "ids", &GDBM_WRCREAT(), + "P", $what); + if ($hashref) { + foreach my $pair (@pairs) { + my ($key,$value)=split(/=/,$pair); + $hashref->{$key}=$value; + } + if (untie(%$hashref)) { + &Reply($client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting idput\n", $userinput); + } + } else { + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting idput\n", $userinput); + } + + return 1; +} +®ister_handler("idput", \&put_id_handler, 0, 1, 0); + +# +# Retrieves a set of id values from the id database. +# Returns an & separated list of results, one for each requested id to the +# client. # +# Parameters: +# $cmd - Command keyword that caused us to be dispatched. +# $tail - Tail of the command. Consists of a colon separated: +# domain - the domain whose id table we dump +# ids Consists of an & separated list of +# id keywords whose values will be fetched. +# nonexisting keywords will have an empty value. +# $client - Socket open on the client. # +# Returns: +# 1 - indicating processing should continue. +# Side effects: +# An & separated list of results is written to $client. # +sub get_id_handler { + my ($cmd, $tail, $client) = @_; + + + my $userinput = "$client:$tail"; + + my ($udom,$what)=split(/:/,$tail); + chomp($what); + my @queries=split(/\&/,$what); + my $qresult=''; + my $hashref = &tie_domain_hash($udom, "ids", &GDBM_READER()); + if ($hashref) { + for (my $i=0;$i<=$#queries;$i++) { + $qresult.="$hashref->{$queries[$i]}&"; + } + if (untie(%$hashref)) { + $qresult=~s/\&$//; + &Reply($client, "$qresult\n", $userinput); + } else { + &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting idget\n",$userinput); + } + } else { + &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting idget\n",$userinput); + } + + return 1; +} +®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(%$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(%$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(%$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(%$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. +# The file will contain some value that is passed in. Is this e.g. +# a login token? +# +# Parameters: +# $cmd - The command that got us dispatched. +# $tail - The remainder of the request following $cmd: +# In this case this will be the contents of the file. +# $client - Socket connected to the client. +# Returns: +# 1 indicating processing can continue. +# Side effects: +# A file is created in the local filesystem. +# A reply is sent to the client. +sub tmp_put_handler { + my ($cmd, $what, $client) = @_; + + my $userinput = "$cmd:$what"; # Reconstruct for logging. + + + my $store; + $tmpsnum++; + my $id=$$.'_'.$clientip.'_'.$tmpsnum; + $id=~s/\W/\_/g; + $what=~s/\n//g; + my $execdir=$perlvar{'lonDaemons'}; + if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) { + print $store $what; + close $store; + &Reply($client, "$id\n", $userinput); + } else { + &Failure( $client, "error: ".($!+0)."IO::File->new Failed ". + "while attempting tmpput\n", $userinput); + } + return 1; + +} +®ister_handler("tmpput", \&tmp_put_handler, 0, 1, 0); + +# Processes the tmpget command. This command returns the contents +# of a temporary resource file(?) created via tmpput. +# +# Paramters: +# $cmd - Command that got us dispatched. +# $id - Tail of the command, contain the id of the resource +# we want to fetch. +# $client - socket open on the client. +# Return: +# 1 - Inidcating processing can continue. +# Side effects: +# A reply is sent to the client. +# +sub tmp_get_handler { + my ($cmd, $id, $client) = @_; + + my $userinput = "$cmd:$id"; + + + $id=~s/\W/\_/g; + my $store; + my $execdir=$perlvar{'lonDaemons'}; + if ($store=IO::File->new("$execdir/tmp/$id.tmp")) { + my $reply=<$store>; + &Reply( $client, "$reply\n", $userinput); + close $store; + } else { + &Failure( $client, "error: ".($!+0)."IO::File->new Failed ". + "while attempting tmpget\n", $userinput); + } + + return 1; +} +®ister_handler("tmpget", \&tmp_get_handler, 0, 1, 0); + +# +# Process the tmpdel command. This command deletes a temp resource +# created by the tmpput command. +# +# Parameters: +# $cmd - Command that got us here. +# $id - Id of the temporary resource created. +# $client - socket open on the client process. +# +# Returns: +# 1 - Indicating processing should continue. +# Side Effects: +# A file is deleted +# A reply is sent to the client. +sub tmp_del_handler { + my ($cmd, $id, $client) = @_; + + my $userinput= "$cmd:$id"; + + chomp($id); + $id=~s/\W/\_/g; + my $execdir=$perlvar{'lonDaemons'}; + if (unlink("$execdir/tmp/$id.tmp")) { + &Reply($client, "ok\n", $userinput); + } else { + &Failure( $client, "error: ".($!+0)."Unlink tmp Failed ". + "while attempting tmpdel\n", $userinput); + } + + return 1; + +} +®ister_handler("tmpdel", \&tmp_del_handler, 0, 1, 0); + +# +# Processes the setannounce command. This command +# creates a file named announce.txt in the top directory of +# the documentn root and sets its contents. The announce.txt file is +# printed in its entirety at the LonCAPA login page. Note: +# once the announcement.txt fileis created it cannot be deleted. +# However, setting the contents of the file to empty removes the +# announcement from the login page of loncapa so who cares. +# +# Parameters: +# $cmd - The command that got us dispatched. +# $announcement - The text of the announcement. +# $client - Socket open on the client process. +# Retunrns: +# 1 - Indicating request processing should continue +# Side Effects: +# The file {DocRoot}/announcement.txt is created. +# A reply is sent to $client. +# +sub set_announce_handler { + my ($cmd, $announcement, $client) = @_; + + my $userinput = "$cmd:$announcement"; + + chomp($announcement); + $announcement=&unescape($announcement); + if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}. + '/announcement.txt')) { + print $store $announcement; + close $store; + &Reply($client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)."\n", $userinput); + } + + return 1; +} +®ister_handler("setannounce", \&set_announce_handler, 0, 1, 0); + +# +# Return the version of the daemon. This can be used to determine +# the compatibility of cross version installations or, alternatively to +# simply know who's out of date and who isn't. Note that the version +# is returned concatenated with the tail. +# Parameters: +# $cmd - the request that dispatched to us. +# $tail - Tail of the request (client's version?). +# $client - Socket open on the client. +#Returns: +# 1 - continue processing requests. +# Side Effects: +# Replies with version to $client. +sub get_version_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = $cmd.$tail; + + &Reply($client, &version($userinput)."\n", $userinput); + + + return 1; +} +®ister_handler("version", \&get_version_handler, 0, 1, 0); + +# Set the current host and domain. This is used to support +# multihomed systems. Each IP of the system, or even separate daemons +# on the same IP can be treated as handling a separate lonCAPA virtual +# machine. This command selects the virtual lonCAPA. The client always +# knows the right one since it is lonc and it is selecting the domain/system +# from the hosts.tab file. +# Parameters: +# $cmd - Command that dispatched us. +# $tail - Tail of the command (domain/host requested). +# $socket - Socket open on the client. +# +# Returns: +# 1 - Indicates the program should continue to process requests. +# Side-effects: +# The default domain/system context is modified for this daemon. +# a reply is sent to the client. +# +sub set_virtual_host_handler { + my ($cmd, $tail, $socket) = @_; + + my $userinput ="$cmd:$tail"; + + &Reply($client, &sethost($userinput)."\n", $userinput); + + + return 1; +} +®ister_handler("sethost", \&set_virtual_host_handler, 0, 1, 0); + +# Process a request to exit: +# - "bye" is sent to the client. +# - The client socket is shutdown and closed. +# - We indicate to the caller that we should exit. +# Formal Parameters: +# $cmd - The command that got us here. +# $tail - Tail of the command (empty). +# $client - Socket open on the tail. +# Returns: +# 0 - Indicating the program should exit!! +# +sub exit_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + + &logthis("Client $clientip ($clientname) hanging up: $userinput"); + &Reply($client, "bye\n", $userinput); + $client->shutdown(2); # shutdown the socket forcibly. + $client->close(); + + return 0; +} +®ister_handler("exit", \&exit_handler, 0,1,1); +®ister_handler("init", \&exit_handler, 0,1,1); +®ister_handler("quit", \&exit_handler, 0,1,1); + +# Determine if auto-enrollment is enabled. +# Note that the original had what I believe to be a defect. +# The original returned 0 if the requestor was not a registerd client. +# It should return "refused". +# Formal Parameters: +# $cmd - The command that invoked us. +# $tail - The tail of the command (Extra command parameters. +# $client - The socket open on the client that issued the request. +# Returns: +# 1 - Indicating processing should continue. +# +sub enrollment_enabled_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = $cmd.":".$tail; # For logging purposes. + + + my $cdom = split(/:/, $tail); # Domain we're asking about. + my $outcome = &localenroll::run($cdom); + &Reply($client, "$outcome\n", $userinput); + + return 1; +} +®ister_handler("autorun", \&enrollment_enabled_handler, 0, 1, 0); + +# Get the official sections for which auto-enrollment is possible. +# Since the admin people won't know about 'unofficial sections' +# we cannot auto-enroll on them. +# Formal Parameters: +# $cmd - The command request that got us dispatched here. +# $tail - The remainder of the request. In our case this +# will be split into: +# $coursecode - The course name from the admin point of view. +# $cdom - The course's domain(?). +# $client - Socket open on the client. +# Returns: +# 1 - Indiciting processing should continue. +# +sub get_sections_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + + my ($coursecode, $cdom) = split(/:/, $tail); + my @secs = &localenroll::get_sections($coursecode,$cdom); + my $seclist = &escape(join(':',@secs)); + + &Reply($client, "$seclist\n", $userinput); + + + return 1; +} +®ister_handler("autogetsections", \&get_sections_handler, 0, 1, 0); + +# Validate the owner of a new course section. +# +# Formal Parameters: +# $cmd - Command that got us dispatched. +# $tail - the remainder of the command. For us this consists of a +# colon separated string containing: +# $inst - Course Id from the institutions point of view. +# $owner - Proposed owner of the course. +# $cdom - Domain of the course (from the institutions +# point of view?).. +# $client - Socket open on the client. +# +# Returns: +# 1 - Processing should continue. +# +sub validate_course_owner_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($inst_course_id, $owner, $cdom) = split(/:/, $tail); + + my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom); + &Reply($client, "$outcome\n", $userinput); + + + + return 1; +} +®ister_handler("autonewcourse", \&validate_course_owner_handler, 0, 1, 0); + +# +# Validate a course section in the official schedule of classes +# from the institutions point of view (part of autoenrollment). +# +# 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_course_id - The course/section id from the +# institutions point of view. +# $cdom - The domain from the institutions +# point of view. +# $client - Socket open on the client. +# Returns: +# 1 - Indicating processing should continue. +# +sub validate_course_section_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($inst_course_id, $cdom) = split(/:/, $tail); + + my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom); + &Reply($client, "$outcome\n", $userinput); + + + return 1; +} +®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. +# +# 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??). +# $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 create_auto_enroll_password_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + + my ($authparam, $cdom) = split(/:/, $userinput); + + my ($create_passwd,$authchk); + ($authparam, + $create_passwd, + $authchk) = &localenroll::create_password($authparam,$cdom); + + &Reply($client, &escape($authparam.':'.$create_passwd.':'.$authchk)."\n", + $userinput); + + + return 1; +} +®ister_handler("autocreatepassword", \&create_auto_enroll_password_handler, + 0, 1, 0); + +# Retrieve and remove temporary files created by/during autoenrollment. +# +# Formal Parameters: +# $cmd - The command that got us dispatched. +# $tail - The tail of the command. In our case this is a colon +# separated list that will be split into: +# $filename - The name of the file to remove. +# The filename is given as a path relative to +# the LonCAPA temp file directory. +# $client - Socket open on the client. +# +# Returns: +# 1 - Continue processing. +sub retrieve_auto_file_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "cmd:$tail"; + + my ($filename) = split(/:/, $tail); + + my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename; + if ( (-e $source) && ($filename ne '') ) { + my $reply = ''; + if (open(my $fh,$source)) { + while (<$fh>) { + chomp($_); + $_ =~ s/^\s+//g; + $_ =~ s/\s+$//g; + $reply .= $_; + } + close($fh); + &Reply($client, &escape($reply)."\n", $userinput); + +# Does this have to be uncommented??!? (RF). +# +# unlink($source); + } else { + &Failure($client, "error\n", $userinput); + } + } else { + &Failure($client, "error\n", $userinput); + } + + + return 1; +} +®ister_handler("autoretrieve", \&retrieve_auto_file_handler, 0,1,0); + +# +# Read and retrieve institutional code format (for support form). +# Formal Parameters: +# $cmd - Command that dispatched us. +# $tail - Tail of the command. In this case it conatins +# the course domain and the coursename. +# $client - Socket open on the client. +# Returns: +# 1 - Continue processing. +# +sub get_institutional_code_format_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + + my $reply; + my($cdom,$course) = split(/:/,$tail); + my @pairs = split/\&/,$course; + my %instcodes = (); + my %codes = (); + my @codetitles = (); + my %cat_titles = (); + my %cat_order = (); + foreach (@pairs) { + my ($key,$value) = split/=/,$_; + $instcodes{&unescape($key)} = &unescape($value); + } + my $formatreply = &localenroll::instcode_format($cdom, + \%instcodes, + \%codes, + \@codetitles, + \%cat_titles, + \%cat_order); + if ($formatreply eq 'ok') { + my $codes_str = &hash2str(%codes); + my $codetitles_str = &array2str(@codetitles); + my $cat_titles_str = &hash2str(%cat_titles); + my $cat_order_str = &hash2str(%cat_order); + &Reply($client, + $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':' + .$cat_order_str."\n", + $userinput); + } else { + # this else branch added by RF since if not ok, lonc will + # hang waiting on reply until timeout. + # + &Reply($client, "format_error\n", $userinput); + } + + return 1; +} +®ister_handler("autoinstcodeformat", + \&get_institutional_code_format_handler,0,1,0); + +# +# Gets a student's photo to exist (in the correct image type) in the user's +# directory. +# Formal Parameters: +# $cmd - The command request that got us dispatched. +# $tail - A colon separated set of words that will be split into: +# $domain - student's domain +# $uname - student username +# $type - image type desired +# $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 $path=&propath($domain,$uname). + '/userfiles/internal/studentphoto.'.$type; + if (-e $path) { + &Reply($client,"ok\n","$cmd:$tail"); + return 1; + } + &mkpath($path); + my $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) { + &Reply($client,"ok\n","$cmd:$tail"); + return 1; + } + &Failure($client,"unable_to_convert\n","$cmd:$tail"); + return 1; +} +®ister_handler("studentphoto", \&student_photo_handler, 0, 1, 0); + +# mkpath makes all directories for a file, expects an absolute path with a +# file or a trailing / if just a dir is passed +# returns 1 on success 0 on failure +sub mkpath { + my ($file)=@_; + my @parts=split(/\//,$file,-1); + my $now=$parts[0].'/'.$parts[1].'/'.$parts[2]; + for (my $i=3;$i<= ($#parts-1);$i++) { + $now.='/'.$parts[$i]; + if (!-e $now) { + if (!mkdir($now,0770)) { return 0; } + } + } + return 1; +} + #--------------------------------------------------------------- # # Getting, decoding and dispatching requests: # - # # Get a Request: # Gets a Request message from the client. The transaction @@ -3019,7 +4351,7 @@ sub process_request { $userinput = decipher($userinput); $wasenc=1; if(!$userinput) { # Cipher not defined. - &Failure($client, "error: Encrypted data without negotated key"); + &Failure($client, "error: Encrypted data without negotated key\n"); return 0; } } @@ -3089,351 +4421,7 @@ sub process_request { } -#------------------- Commands not yet in spearate handlers. -------------- - - -# ----------------------------------------------------------------------- idput - if ($userinput =~ /^idput/) { - if(isClient) { - my ($cmd,$udom,$what)=split(/:/,$userinput); - chomp($what); - $udom=~s/\W//g; - my $proname="$perlvar{'lonUsersDir'}/$udom/ids"; - my $now=time; - my @pairs=split(/\&/,$what); - my %hash; - if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) { - { - my $hfh; - if ($hfh=IO::File->new(">>$proname.hist")) { - print $hfh "P:$now:$what\n"; - } - } - foreach my $pair (@pairs) { - my ($key,$value)=split(/=/,$pair); - $hash{$key}=$value; - } - if (untie(%hash)) { - print $client "ok\n"; - } else { - print $client "error: ".($!+0) - ." untie(GDBM) Failed ". - "while attempting idput\n"; - } - } else { - print $client "error: ".($!+0) - ." tie(GDBM) Failed ". - "while attempting idput\n"; - } - } else { - Reply($client, "refused\n", $userinput); - - } -# ----------------------------------------------------------------------- idget - } elsif ($userinput =~ /^idget/) { - if(isClient) { - my ($cmd,$udom,$what)=split(/:/,$userinput); - chomp($what); - $udom=~s/\W//g; - my $proname="$perlvar{'lonUsersDir'}/$udom/ids"; - my @queries=split(/\&/,$what); - my $qresult=''; - my %hash; - if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) { - for (my $i=0;$i<=$#queries;$i++) { - $qresult.="$hash{$queries[$i]}&"; - } - if (untie(%hash)) { - $qresult=~s/\&$//; - print $client "$qresult\n"; - } else { - print $client "error: ".($!+0) - ." untie(GDBM) Failed ". - "while attempting idget\n"; - } - } else { - print $client "error: ".($!+0) - ." tie(GDBM) Failed ". - "while attempting idget\n"; - } - } else { - Reply($client, "refused\n", $userinput); - - } -# ---------------------------------------------------------------------- tmpput - } elsif ($userinput =~ /^tmpput/) { - if(isClient) { - my ($cmd,$what)=split(/:/,$userinput); - my $store; - $tmpsnum++; - my $id=$$.'_'.$clientip.'_'.$tmpsnum; - $id=~s/\W/\_/g; - $what=~s/\n//g; - my $execdir=$perlvar{'lonDaemons'}; - if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) { - print $store $what; - close $store; - print $client "$id\n"; - } - else { - print $client "error: ".($!+0) - ."IO::File->new Failed ". - "while attempting tmpput\n"; - } - } else { - Reply($client, "refused\n", $userinput); - - } - -# ---------------------------------------------------------------------- tmpget - } elsif ($userinput =~ /^tmpget/) { - if(isClient) { - my ($cmd,$id)=split(/:/,$userinput); - chomp($id); - $id=~s/\W/\_/g; - my $store; - my $execdir=$perlvar{'lonDaemons'}; - if ($store=IO::File->new("$execdir/tmp/$id.tmp")) { - my $reply=<$store>; - print $client "$reply\n"; - close $store; - } - else { - print $client "error: ".($!+0) - ."IO::File->new Failed ". - "while attempting tmpget\n"; - } - } else { - Reply($client, "refused\n", $userinput); - - } -# ---------------------------------------------------------------------- tmpdel - } elsif ($userinput =~ /^tmpdel/) { - if(isClient) { - my ($cmd,$id)=split(/:/,$userinput); - chomp($id); - $id=~s/\W/\_/g; - my $execdir=$perlvar{'lonDaemons'}; - if (unlink("$execdir/tmp/$id.tmp")) { - print $client "ok\n"; - } else { - print $client "error: ".($!+0) - ."Unlink tmp Failed ". - "while attempting tmpdel\n"; - } - } else { - Reply($client, "refused\n", $userinput); - - } -# ----------------------------------------- portfolio directory list (portls) - } elsif ($userinput =~ /^portls/) { - if(isClient) { - my ($cmd,$uname,$udom)=split(/:/,$userinput); - my $udir=propath($udom,$uname).'/userfiles/portfolio'; - my $dirLine=''; - my $dirContents=''; - if (opendir(LSDIR,$udir.'/')){ - while ($dirLine = readdir(LSDIR)){ - $dirContents = $dirContents.$dirLine.'
'; - } - } else { - $dirContents = "No directory found\n"; - } - print $client $dirContents."\n"; - } else { - Reply($client, "refused\n", $userinput); - } -# -------------------------------------------------------------------------- ls - } elsif ($userinput =~ /^ls/) { - if(isClient) { - my $obs; - my $rights; - my ($cmd,$ulsdir)=split(/:/,$userinput); - my $ulsout=''; - my $ulsfn; - if (-e $ulsdir) { - if(-d $ulsdir) { - if (opendir(LSDIR,$ulsdir)) { - while ($ulsfn=readdir(LSDIR)) { - undef $obs, $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|()(default)|) { $rights = 1; } - } - } - $ulsout.=$ulsfn.'&'.join('&',@ulsstats); - if($obs eq '1') { $ulsout.="&1"; } - else { $ulsout.="&0"; } - if($rights eq '1') { $ulsout.="&1:"; } - else { $ulsout.="&0:"; } - } - closedir(LSDIR); - } - } else { - my @ulsstats=stat($ulsdir); - $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; - } - } else { - $ulsout='no_such_dir'; - } - if ($ulsout eq '') { $ulsout='empty'; } - print $client "$ulsout\n"; - } else { - Reply($client, "refused\n", $userinput); - - } -# ----------------------------------------------------------------- setannounce - } elsif ($userinput =~ /^setannounce/) { - if (isClient) { - my ($cmd,$announcement)=split(/:/,$userinput); - chomp($announcement); - $announcement=&unescape($announcement); - if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}. - '/announcement.txt')) { - print $store $announcement; - close $store; - print $client "ok\n"; - } else { - print $client "error: ".($!+0)."\n"; - } - } else { - Reply($client, "refused\n", $userinput); - - } -# ------------------------------------------------------------------ Hanging up - } elsif (($userinput =~ /^exit/) || - ($userinput =~ /^init/)) { # no restrictions. - &logthis( - "Client $clientip ($clientname) hanging up: $userinput"); - print $client "bye\n"; - $client->shutdown(2); # shutdown the socket forcibly. - $client->close(); - return 0; - -# ---------------------------------- set current host/domain - } elsif ($userinput =~ /^sethost/) { - if (isClient) { - print $client &sethost($userinput)."\n"; - } else { - print $client "refused\n"; - } -#---------------------------------- request file (?) version. - } elsif ($userinput =~/^version/) { - if (isClient) { - print $client &version($userinput)."\n"; - } else { - print $client "refused\n"; - } -#------------------------------- is auto-enrollment enabled? - } elsif ($userinput =~/^autorun/) { - if (isClient) { - my ($cmd,$cdom) = split(/:/,$userinput); - my $outcome = &localenroll::run($cdom); - print $client "$outcome\n"; - } else { - print $client "0\n"; - } -#------------------------------- get official sections (for auto-enrollment). - } elsif ($userinput =~/^autogetsections/) { - if (isClient) { - my ($cmd,$coursecode,$cdom)=split(/:/,$userinput); - my @secs = &localenroll::get_sections($coursecode,$cdom); - my $seclist = &escape(join(':',@secs)); - print $client "$seclist\n"; - } else { - print $client "refused\n"; - } -#----------------------- validate owner of new course section (for auto-enrollment). - } elsif ($userinput =~/^autonewcourse/) { - if (isClient) { - my ($cmd,$inst_course_id,$owner,$cdom)=split(/:/,$userinput); - my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom); - print $client "$outcome\n"; - } else { - print $client "refused\n"; - } -#-------------- validate course section in schedule of classes (for auto-enrollment). - } elsif ($userinput =~/^autovalidatecourse/) { - if (isClient) { - my ($cmd,$inst_course_id,$cdom)=split(/:/,$userinput); - my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom); - print $client "$outcome\n"; - } else { - print $client "refused\n"; - } -#--------------------------- create password for new user (for auto-enrollment). - } elsif ($userinput =~/^autocreatepassword/) { - if (isClient) { - my ($cmd,$authparam,$cdom)=split(/:/,$userinput); - my ($create_passwd,$authchk); - ($authparam,$create_passwd,$authchk) = &localenroll::create_password($authparam,$cdom); - print $client &escape($authparam.':'.$create_passwd.':'.$authchk)."\n"; - } else { - print $client "refused\n"; - } -#--------------------------- read and remove temporary files (for auto-enrollment). - } elsif ($userinput =~/^autoretrieve/) { - if (isClient) { - my ($cmd,$filename) = split(/:/,$userinput); - my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename; - if ( (-e $source) && ($filename ne '') ) { - my $reply = ''; - if (open(my $fh,$source)) { - while (<$fh>) { - chomp($_); - $_ =~ s/^\s+//g; - $_ =~ s/\s+$//g; - $reply .= $_; - } - close($fh); - print $client &escape($reply)."\n"; -# unlink($source); - } else { - print $client "error\n"; - } - } else { - print $client "error\n"; - } - } else { - print $client "refused\n"; - } -#--------------------- read and retrieve institutional code format (for support form). - } elsif ($userinput =~/^autoinstcodeformat/) { - if (isClient) { - my $reply; - my($cmd,$cdom,$course) = split(/:/,$userinput); - my @pairs = split/\&/,$course; - my %instcodes = (); - my %codes = (); - my @codetitles = (); - my %cat_titles = (); - my %cat_order = (); - foreach (@pairs) { - my ($key,$value) = split/=/,$_; - $instcodes{&unescape($key)} = &unescape($value); - } - my $formatreply = &localenroll::instcode_format($cdom,\%instcodes,\%codes,\@codetitles,\%cat_titles,\%cat_order); - if ($formatreply eq 'ok') { - my $codes_str = &hash2str(%codes); - my $codetitles_str = &array2str(@codetitles); - my $cat_titles_str = &hash2str(%cat_titles); - my $cat_order_str = &hash2str(%cat_order); - print $client $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':'.$cat_order_str."\n"; - } - } else { - print $client "refused\n"; - } -# ------------------------------------------------------------- unknown command - - } else { - # unknown command - print $client "unknown_cmd\n"; - } + print $client "unknown_cmd\n"; # -------------------------------------------------------------------- complete Debug("process_request - returning 1"); return 1; @@ -3694,13 +4682,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*\#/)) { - my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); - chomp($ip); $ip=~s/\D+$//; + if ($configline !~ /^\s*\#/ && $configline !~ /^\s*$/ ) { + my ($id,$domain,$role,$name)=split(/:/,$configline); + $name=~s/\s//g; + 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}; + } $hostid{$ip}=$id; # LonCAPA name of host by IP. $hostdom{$id}=$domain; # LonCAPA domain name of host. - $hostip{$id}=$ip; # IP address of host. + $hostip{$id}=$ip; # IP address of host. $hostdns{$name} = $id; # LonCAPA name of host by DNS. if ($id eq $perlvar{'lonHostID'}) { @@ -3837,8 +4838,6 @@ sub Reply { Debug("Request was $request Reply was $reply"); $Transactions++; - - } @@ -3881,7 +4880,7 @@ sub logstatus { flock(LOG,LOCK_EX); print LOG $$."\t".$clientname."\t".$currenthostid."\t" .$status."\t".$lastlog."\t $keymode\n"; - flock(DB,LOCK_UN); + flock(LOG,LOCK_UN); close(LOG); } &status("Finished logging"); @@ -4066,6 +5065,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 @@ -4112,8 +5113,6 @@ sub make_new_child { if (defined($iaddr)) { $clientip = inet_ntoa($iaddr); Debug("Connected with $clientip"); - $clientdns = gethostbyaddr($iaddr, AF_INET); - Debug("Connected with $clientdns by name"); } else { &logthis("Unable to determine clientip"); $clientip='Unavailable'; @@ -4143,7 +5142,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'); # ============================================================================= @@ -4153,18 +5154,23 @@ sub make_new_child { ReadManagerTable; # May also be a manager!! - my $clientrec=($hostid{$clientip} ne undef); - my $ismanager=($managers{$clientip} ne undef); + my $outsideip=$clientip; + if ($clientip eq '127.0.0.1') { + $outsideip=$hostip{$perlvar{'lonHostID'}}; + } + + my $clientrec=($hostid{$outsideip} ne undef); + my $ismanager=($managers{$outsideip} ne undef); $clientname = "[unknonwn]"; if($clientrec) { # Establish client type. $ConnectionType = "client"; - $clientname = $hostid{$clientip}; + $clientname = $hostid{$outsideip}; if($ismanager) { $ConnectionType = "both"; } } else { $ConnectionType = "manager"; - $clientname = $managers{$clientip}; + $clientname = $managers{$outsideip}; } my $clientok; @@ -4308,8 +5314,35 @@ sub make_new_child { exit; } +# +# Determine if a user is an author for the indicated domain. +# +# Parameters: +# domain - domain to check in . +# user - Name of user to check. +# +# Return: +# 1 - User is an author for domain. +# 0 - User is not an author for domain. +sub is_author { + my ($domain, $user) = @_; + + &Debug("is_author: $user @ $domain"); + my $hashref = &tie_user_hash($domain, $user, "roles", + &GDBM_READER()); + + # Author role should show up as a key /domain/_au + my $key = "/$domain/_au"; + my $value = $hashref->{$key}; + + if(defined($value)) { + &Debug("$user @ $domain is an author"); + } + + return defined($value); +} # # Checks to see if the input roleput request was to set # an author role. If so, invokes the lchtmldir script to set @@ -4321,16 +5354,17 @@ sub make_new_child { # 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"); + &Debug("Setting homedir permissions for $userhome"); system("$execdir/lchtmldir $userhome $user $authtype"); } } @@ -4346,12 +5380,7 @@ sub manage_permissions # sub password_path { my ($domain, $user) = @_; - - - my $path = &propath($domain, $user); - $path .= "/passwd"; - - return $path; + return &propath($domain, $user).'/passwd'; } # Password Filename @@ -4426,12 +5455,7 @@ sub get_auth_type Debug("Password info = $realpassword\n"); my ($authtype, $contentpwd) = split(/:/, $realpassword); Debug("Authtype = $authtype, content = $contentpwd\n"); - my $availinfo = ''; - if($authtype eq 'krb4' or $authtype eq 'krb5') { - $availinfo = $contentpwd; - } - - return "$authtype:$availinfo"; + return "$authtype:$contentpwd"; } else { Debug("Returning nouser"); return "nouser"; @@ -4464,7 +5488,8 @@ sub validate_user { # At the end of this function. I'll ensure that it's not still that # value so we don't just wind up returning some accidental value # as a result of executing an unforseen code path that - # did not set $validated. + # did not set $validated. At the end of valid execution paths, + # validated shoule be 1 for success or 0 for failuer. my $validated = -3.14159; @@ -4528,7 +5553,7 @@ sub validate_user { my $krbserver = &Authen::Krb5::parse_name($krbservice); my $credentials= &Authen::Krb5::cc_default(); $credentials->initialize($krbclient); - my $krbreturn = &Authen::KRb5::get_in_tkt_with_password($krbclient, + my $krbreturn = &Authen::Krb5::get_in_tkt_with_password($krbclient, $krbserver, $password, $credentials); @@ -4552,7 +5577,11 @@ sub validate_user { # unless ($validated != -3.14159) { - die "ValidateUser - failed to set the value of validated"; + # I >really really< want to know if this happens. + # since it indicates that user authentication is badly + # broken in some code path. + # + die "ValidateUser - failed to set the value of validated $domain, $user $password"; } return $validated; } @@ -4724,7 +5753,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? @@ -4768,6 +5797,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)=@_; @@ -4775,7 +5833,11 @@ sub make_passwd_file { if ($umode eq 'krb4' or $umode eq 'krb5') { { my $pf = IO::File->new(">$passfilename"); - print $pf "$umode:$npass\n"; + if ($pf) { + print $pf "$umode:$npass\n"; + } else { + $result = "pass_file_failed_error"; + } } } elsif ($umode eq 'internal') { my $salt=time; @@ -4784,12 +5846,20 @@ sub make_passwd_file { { &Debug("Creating internal auth"); my $pf = IO::File->new(">$passfilename"); - print $pf "internal:$ncpass\n"; + if($pf) { + print $pf "internal:$ncpass\n"; + } else { + $result = "pass_file_failed_error"; + } } } elsif ($umode eq 'localauth') { { my $pf = IO::File->new(">$passfilename"); - print $pf "localauth:$npass\n"; + if($pf) { + print $pf "localauth:$npass\n"; + } else { + $result = "pass_file_failed_error"; + } } } elsif ($umode eq 'unix') { { @@ -4815,26 +5885,40 @@ sub make_passwd_file { print $se "$npass\n"; print $se "$lc_error_file\n"; # Status -> unique file. } - my $error = IO::File->new("< $lc_error_file"); - my $useraddok = <$error>; - $error->close; - unlink($lc_error_file); - - chomp $useraddok; - - if($useraddok > 0) { - my $error_text = &lcuseraddstrerror($useraddok); - &logthis("Failed lcuseradd: $error_text"); - $result = "lcuseradd_failed:$error_text\n"; + if (-r $lc_error_file) { + &Debug("Opening error file: $lc_error_file"); + my $error = IO::File->new("< $lc_error_file"); + my $useraddok = <$error>; + $error->close; + unlink($lc_error_file); + + chomp $useraddok; + + if($useraddok > 0) { + my $error_text = &lcuseraddstrerror($useraddok); + &logthis("Failed lcuseradd: $error_text"); + $result = "lcuseradd_failed:$error_text\n"; + } else { + my $pf = IO::File->new(">$passfilename"); + if($pf) { + print $pf "unix:\n"; + } else { + $result = "pass_file_failed_error"; + } + } } else { - my $pf = IO::File->new(">$passfilename"); - print $pf "unix:\n"; + &Debug("Could not locate lcuseradd error: $lc_error_file"); + $result="bug_lcuseradd_no_output_file"; } } } elsif ($umode eq 'none') { { my $pf = IO::File->new("> $passfilename"); - print $pf "none:\n"; + if($pf) { + print $pf "none:\n"; + } else { + $result = "pass_file_failed_error"; + } } } else { $result="auth_mode_error\n"; @@ -4842,6 +5926,11 @@ sub make_passwd_file { return $result; } +sub convert_photo { + my ($start,$dest)=@_; + system("convert $start $dest"); +} + sub sethost { my ($remotereq) = @_; my (undef,$hostid)=split(/:/,$remotereq);