--- loncom/lond 2009/08/22 19:52:08 1.424 +++ loncom/lond 2017/02/07 18:14:13 1.531 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.424 2009/08/22 19:52:08 raeburn Exp $ +# $Id: lond,v 1.531 2017/02/07 18:14:13 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -33,16 +33,16 @@ use strict; use lib '/home/httpd/lib/perl/'; use LONCAPA; use LONCAPA::Configuration; +use LONCAPA::Lond; use IO::Socket; use IO::File; #use Apache::File; use POSIX; use Crypt::IDEA; -use LWP::UserAgent(); +use HTTP::Request; use Digest::MD5 qw(md5_hex); use GDBM_File; -use Authen::Krb4; use Authen::Krb5; use localauth; use localenroll; @@ -53,13 +53,18 @@ use LONCAPA::lonlocal; use LONCAPA::lonssl; use Fcntl qw(:flock); use Apache::lonnet; +use Mail::Send; +use Crypt::Eksblowfish::Bcrypt; +use Digest::SHA; +use Encode; +use LONCAPA::LWPReq; my $DEBUG = 0; # Non zero to enable debug log entries. my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.424 $'; #' stupid emacs +my $VERSION='$Revision: 1.531 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -67,7 +72,16 @@ my $currentdomainid; my $client; my $clientip; # IP address of client. my $clientname; # LonCAPA name of client. - +my $clientversion; # LonCAPA version running on client. +my $clienthomedom; # LonCAPA domain of homeID for client. +my $clientintdom; # LonCAPA "internet domain" for client. +my $clientsameinst; # LonCAPA "internet domain" same for + # this host and client. +my $clientremoteok; # Client allowed to host domain's users. + # (version constraints ignored), not set + # if this host and client share "internet domain". +my %clientprohibited; # Actions prohibited on client; + my $server; my $keymode; @@ -88,6 +102,8 @@ my %managers; # Ip -> manager names my %perlvar; # Will have the apache conf defined perl vars. +my $dist; + # # The hash below is used for command dispatching, and is therefore keyed on the request keyword. # Each element of the hash contains a reference to an array that contains: @@ -123,37 +139,157 @@ my @passwderrors = ("ok", "pwchange_failure - lcpasswd Error filename is invalid"); -# The array below are lcuseradd error strings.: - -my $lastadderror = 13; -my @adderrors = ("ok", - "User ID mismatch, lcuseradd must run as user www", - "lcuseradd Incorrect number of command line parameters must be 3", - "lcuseradd Incorrect number of stdinput lines, must be 3", - "lcuseradd Too many other simultaneous pwd changes in progress", - "lcuseradd User does not exist", - "lcuseradd Unable to make www member of users's group", - "lcuseradd Unable to su to root", - "lcuseradd Unable to set password", - "lcuseradd Username has invalid characters", - "lcuseradd Password has an invalid character", - "lcuseradd User already exists", - "lcuseradd Could not add user.", - "lcuseradd Password mismatch"); - - # This array are the errors from lcinstallfile: my @installerrors = ("ok", "Initial user id of client not that of www", "Usage error, not enough command line arguments", - "Source file name does not exist", - "Destination file name does not exist", + "Source filename does not exist", + "Destination filename does not exist", "Some file operation failed", "Invalid table filename." ); # +# The %trust hash classifies commands according to type of trust +# required for execution of the command. +# +# When clients from a different institution request execution of a +# particular command, the trust settings for that institution set +# for this domain (or default domain for a multi-domain server) will +# be checked to see if running the command is allowed. +# +# Trust types which depend on the "Trust" domain configuration +# for the machine's default domain are: +# +# content ("Access to this domain's content by others") +# shared ("Access to other domain's content by this domain") +# enroll ("Enrollment in this domain's courses by others") +# coaurem ("Co-author roles for this domain's users elsewhere") +# domroles ("Domain roles in this domain assignable to others") +# catalog ("Course Catalog for this domain displayed elsewhere") +# reqcrs ("Requests for creation of courses in this domain by others") +# msg ("Users in other domains can send messages to this domain") +# +# Trust type which depends on the User Session Hosting (remote) +# domain configuration for machine's default domain is: "remote". +# +# Trust types which depend on contents of manager.tab in +# /home/httpd/lonTabs is: "manageronly". +# +# Trust type which requires client to share the same LON-CAPA +# "internet domain" (i.e., same institution as this server) is: +# "institutiononly". +# + +my %trust = ( + auth => {remote => 1}, + autocreatepassword => {remote => 1}, + autocrsreqchecks => {remote => 1, reqcrs => 1}, + autocrsrequpdate => {remote => 1}, + autocrsreqvalidation => {remote => 1}, + autogetsections => {remote => 1}, + autoinstcodedefaults => {remote => 1, catalog => 1}, + autoinstcodeformat => {remote => 1, catalog => 1}, + autonewcourse => {remote => 1, reqcrs => 1}, + autophotocheck => {remote => 1, enroll => 1}, + autophotochoice => {remote => 1}, + autophotopermission => {remote => 1, enroll => 1}, + autopossibleinstcodes => {remote => 1, reqcrs => 1}, + autoretrieve => {remote => 1, enroll => 1, catalog => 1}, + autorun => {remote => 1, enroll => 1, reqcrs => 1}, + autovalidateclass_sec => {catalog => 1}, + autovalidatecourse => {remote => 1, enroll => 1}, + autovalidateinstcode => {domroles => 1, remote => 1, enroll => 1}, + changeuserauth => {remote => 1, domroles => 1}, + chatretr => {remote => 1, enroll => 1}, + chatsend => {remote => 1, enroll => 1}, + courseiddump => {remote => 1, domroles => 1, enroll => 1}, + courseidput => {remote => 1, domroles => 1, enroll => 1}, + courseidputhash => {remote => 1, domroles => 1, enroll => 1}, + courselastaccess => {remote => 1, domroles => 1, enroll => 1}, + currentauth => {remote => 1, domroles => 1, enroll => 1}, + currentdump => {remote => 1, enroll => 1}, + currentversion => {remote=> 1, content => 1}, + dcmaildump => {remote => 1, domroles => 1}, + dcmailput => {remote => 1, domroles => 1}, + del => {remote => 1, domroles => 1, enroll => 1, content => 1}, + deldom => {remote => 1, domroles => 1}, # not currently used + devalidatecache => {institutiononly => 1}, + domroleput => {remote => 1, enroll => 1}, + domrolesdump => {remote => 1, catalog => 1}, + du => {remote => 1, enroll => 1}, + du2 => {remote => 1, enroll => 1}, + dump => {remote => 1, enroll => 1, domroles => 1}, + edit => {institutiononly => 1}, #not used currently + eget => {remote => 1, domroles => 1, enroll => 1}, #not used currently + ekey => {}, #not used currently + exit => {anywhere => 1}, + fetchuserfile => {remote => 1, enroll => 1}, + get => {remote => 1, domroles => 1, enroll => 1}, + getdom => {anywhere => 1}, + home => {anywhere => 1}, + iddel => {remote => 1, enroll => 1}, + idget => {remote => 1, enroll => 1}, + idput => {remote => 1, domroles => 1, enroll => 1}, + inc => {remote => 1, enroll => 1}, + init => {anywhere => 1}, + inst_usertypes => {remote => 1, domroles => 1, enroll => 1}, + instemailrules => {remote => 1, domroles => 1}, + instidrulecheck => {remote => 1, domroles => 1,}, + instidrules => {remote => 1, domroles => 1,}, + instrulecheck => {remote => 1, enroll => 1, reqcrs => 1, domroles => 1}, + instselfcreatecheck => {institutiononly => 1}, + instuserrules => {remote => 1, enroll => 1, reqcrs => 1, domroles => 1}, + keys => {remote => 1,}, + load => {anywhere => 1}, + log => {anywhere => 1}, + ls => {remote => 1, enroll => 1, content => 1,}, + ls2 => {remote => 1, enroll => 1, content => 1,}, + ls3 => {remote => 1, enroll => 1, content => 1,}, + makeuser => {remote => 1, enroll => 1, domroles => 1,}, + mkdiruserfile => {remote => 1, enroll => 1,}, + newput => {remote => 1, enroll => 1, reqcrs => 1, domroles => 1,}, + passwd => {remote => 1}, + ping => {anywhere => 1}, + pong => {anywhere => 1}, + pushfile => {manageronly => 1}, + put => {remote => 1, enroll => 1, domroles => 1, msg => 1, content => 1, shared => 1}, + putdom => {remote => 1, domroles => 1,}, + putstore => {remote => 1, enroll => 1}, + queryreply => {anywhere => 1}, + querysend => {anywhere => 1}, + quit => {anywhere => 1}, + readlonnetglobal => {institutiononly => 1}, + reinit => {manageronly => 1}, #not used currently + removeuserfile => {remote => 1, enroll => 1}, + renameuserfile => {remote => 1,}, + restore => {remote => 1, enroll => 1, reqcrs => 1,}, + rolesdel => {remote => 1, enroll => 1, domroles => 1, coaurem => 1}, + rolesput => {remote => 1, enroll => 1, domroles => 1, coaurem => 1}, + servercerts => {institutiononly => 1}, + serverdistarch => {anywhere => 1}, + serverhomeID => {anywhere => 1}, + serverloncaparev => {anywhere => 1}, + servertimezone => {remote => 1, enroll => 1}, + setannounce => {remote => 1, domroles => 1}, + sethost => {anywhere => 1}, + store => {remote => 1, enroll => 1, reqcrs => 1,}, + studentphoto => {remote => 1, enroll => 1}, + sub => {content => 1,}, + tmpdel => {anywhere => 1}, + tmpget => {anywhere => 1}, + tmpput => {anywhere => 1}, + tokenauthuserfile => {anywhere => 1}, + unsub => {content => 1,}, + update => {shared => 1}, + updateclickers => {remote => 1}, + userhassession => {anywhere => 1}, + userload => {anywhere => 1}, + version => {anywhere => 1}, #not used + ); + +# # Statistics that are maintained and dislayed in the status line. # my $Transactions = 0; # Number of attempted transactions. @@ -417,8 +553,11 @@ sub ReadManagerTable { my $tablename = $perlvar{'lonTabDir'}."/managers.tab"; if (!open (MANAGERS, $tablename)) { - logthis('<font color="red">No manager table. Nobody can manage!!</font>'); - return; + my $hostname = &Apache::lonnet::hostname($perlvar{'lonHostID'}); + if (&Apache::lonnet::is_LC_dns($hostname)) { + &logthis('<font color="red">No manager table. Nobody can manage!!</font>'); + } + return; } while(my $host = <MANAGERS>) { chomp($host); @@ -443,7 +582,7 @@ sub ReadManagerTable { } } else { logthis('<font color="green"> existing host'." $host</font>\n"); - $managers{&Apache::lonnet::get_host_ip($host)} = $host; # Use info from cluster tab if clumemeber + $managers{&Apache::lonnet::get_host_ip($host)} = $host; # Use info from cluster tab if cluster memeber } } } @@ -505,7 +644,8 @@ sub AdjustHostContents { my $me = $perlvar{'lonHostID'}; foreach my $line (split(/\n/,$contents)) { - if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) { + if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/) || + ($line =~ /^\s*\^/))) { chomp($line); my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line); if ($id eq $me) { @@ -593,11 +733,8 @@ sub InstallFile { # # ConfigFileFromSelector: converts a configuration file selector # into a configuration file pathname. -# It's probably no longer necessary to preserve -# special handling of hosts or domain as those -# files have been superceded by dns_hosts, dns_domain. -# The default action is just to prepend the directory -# and append .tab +# Supports the following file selectors: +# hosts, domain, dns_hosts, dns_domain # # # Parameters: @@ -610,15 +747,11 @@ sub ConfigFileFromSelector { my $tablefile; my $tabledir = $perlvar{'lonTabDir'}.'/'; - if ($selector eq "hosts") { - $tablefile = $tabledir."hosts.tab"; - } elsif ($selector eq "domain") { - $tablefile = $tabledir."domain.tab"; - } else { + if (($selector eq "hosts") || ($selector eq "domain") || + ($selector eq "dns_hosts") || ($selector eq "dns_domain")) { $tablefile = $tabledir.$selector.'.tab'; } return $tablefile; - } # # PushFile: Called to do an administrative push of a file. @@ -636,7 +769,7 @@ sub ConfigFileFromSelector { # String to send to client ("ok" or "refused" if bad file). # sub PushFile { - my $request = shift; + my $request = shift; my ($command, $filename, $contents) = split(":", $request, 3); &Debug("PushFile"); @@ -644,6 +777,8 @@ sub PushFile { # supported: # hosts.tab ($filename eq host). # domain.tab ($filename eq domain). + # dns_hosts.tab ($filename eq dns_host). + # dns_domain.tab ($filename eq dns_domain). # Construct the destination filename or reject the request. # # lonManage is supposed to ensure this, however this session could be @@ -664,6 +799,42 @@ sub PushFile { if($filename eq "host") { $contents = AdjustHostContents($contents); + } elsif ($filename eq 'dns_host' || $filename eq 'dns_domain') { + if ($contents eq '') { + &logthis('<font color="red"> Pushfile: unable to install ' + .$tablefile." - no data received from push. </font>"); + return 'error: push had no data'; + } + if (&Apache::lonnet::get_host_ip($clientname)) { + my $clienthost = &Apache::lonnet::hostname($clientname); + if ($managers{$clientip} eq $clientname) { + my $clientprotocol = $Apache::lonnet::protocol{$clientname}; + $clientprotocol = 'http' if ($clientprotocol ne 'https'); + my $url = '/adm/'.$filename; + $url =~ s{_}{/}; + my $request=new HTTP::Request('GET',"$clientprotocol://$clienthost$url"); + my $response = LONCAPA::LWPReq::makerequest($clientname,$request,'',\%perlvar,60,0); + if ($response->is_error()) { + &logthis('<font color="red"> Pushfile: unable to install ' + .$tablefile." - error attempting to pull data. </font>"); + return 'error: pull failed'; + } else { + my $result = $response->content; + chomp($result); + unless ($result eq $contents) { + &logthis('<font color="red"> Pushfile: unable to install ' + .$tablefile." - pushed data and pulled data differ. </font>"); + my $pushleng = length($contents); + my $pullleng = length($result); + if ($pushleng != $pullleng) { + return "error: $pushleng vs $pullleng bytes"; + } else { + return "error: mismatch push and pull"; + } + } + } + } + } } # Install the new file: @@ -675,11 +846,31 @@ sub PushFile { return "error:$!"; } else { &logthis('<font color="green"> Installed new '.$tablefile - ."</font>"); - + ." - transaction by: $clientname ($clientip)</font>"); + my $adminmail = $perlvar{'lonAdmEMail'}; + my $admindom = &Apache::lonnet::host_domain($perlvar{'lonHostID'}); + if ($admindom ne '') { + my %domconfig = + &Apache::lonnet::get_dom('configuration',['contacts'],$admindom); + if (ref($domconfig{'contacts'}) eq 'HASH') { + if ($domconfig{'contacts'}{'adminemail'} ne '') { + $adminmail = $domconfig{'contacts'}{'adminemail'}; + } + } + } + if ($adminmail =~ /^[^\@]+\@[^\@]+$/) { + my $msg = new Mail::Send; + $msg->to($adminmail); + $msg->subject('LON-CAPA DNS update on '.$perlvar{'lonHostID'}); + $msg->add('Content-type','text/plain; charset=UTF-8'); + if (my $fh = $msg->open()) { + print $fh 'Update to '.$tablefile.' from Cluster Manager '. + "$clientname ($clientip)\n"; + $fh->close; + } + } } - # Indicate success: return "ok"; @@ -975,6 +1166,9 @@ sub read_profile { &GDBM_READER()); if ($hashref) { my @queries=split(/\&/,$what); + if ($namespace eq 'roles') { + @queries = map { &unescape($_); } @queries; + } my $qresult=''; for (my $i=0;$i<=$#queries;$i++) { @@ -1068,7 +1262,7 @@ sub pong_handler { # Implicit Inputs: # $currenthostid - Global variable that carries the name of the host # known as. -# $clientname - Global variable that carries the name of the hsot we're connected to. +# $clientname - Global variable that carries the name of the host we're connected to. # Returns: # 1 - Ok to continue processing. # 0 - Program should exit. @@ -1107,7 +1301,7 @@ sub establish_key_handler { # Implicit Inputs: # $currenthostid - Global variable that carries the name of the host # known as. -# $clientname - Global variable that carries the name of the hsot we're connected to. +# $clientname - Global variable that carries the name of the host we're connected to. # Returns: # 1 - Ok to continue processing. # 0 - Program should exit. @@ -1116,6 +1310,8 @@ sub establish_key_handler { sub load_handler { my ($cmd, $tail, $replyfd) = @_; + + # Get the load average from /proc/loadavg and calculate it as a percentage of # the allowed load limit as set by the perl global variable lonLoadLim @@ -1144,7 +1340,7 @@ sub load_handler { # Implicit Inputs: # $currenthostid - Global variable that carries the name of the host # known as. -# $clientname - Global variable that carries the name of the hsot we're connected to. +# $clientname - Global variable that carries the name of the host we're connected to. # Returns: # 1 - Ok to continue processing. # 0 - Program should exit @@ -1372,6 +1568,20 @@ sub du2_handler { # 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. +# +# If the requested path contains /../ or is: +# +# 1. for a directory, and the path does not begin with one of: +# (a) /home/httpd/html/res/<domain> +# (b) /home/httpd/html/res/userfiles/ +# (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles +# or is: +# +# 2. for a file, and the path (after prepending) does not begin with: +# /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/ +# +# the response will be "refused". +# # Parameters: # $cmd - The command that dispatched us (ls). # $ulsdir - The directory path to list... I'm not sure what this @@ -1393,8 +1603,17 @@ sub ls_handler { my $rights; my $ulsout=''; my $ulsfn; + if ($ulsdir =~m{/\.\./}) { + &Failure($client,"refused\n",$userinput); + return 1; + } if (-e $ulsdir) { if(-d $ulsdir) { + unless (($ulsdir =~ m{/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) || + ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/userfiles/})) { + &Failure($client,"refused\n",$userinput); + return 1; + } if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { undef($obs); @@ -1418,6 +1637,10 @@ sub ls_handler { closedir(LSDIR); } } else { + unless ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/}) { + &Failure($client,"refused\n",$userinput); + return 1; + } my @ulsstats=stat($ulsdir); $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; } @@ -1442,6 +1665,20 @@ sub ls_handler { # 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. +# +# If the requested path contains /../ or is: +# +# 1. for a directory, and the path does not begin with one of: +# (a) /home/httpd/html/res/<domain> +# (b) /home/httpd/html/res/userfiles/ +# (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles +# or is: +# +# 2. for a file, and the path (after prepending) does not begin with: +# /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/ +# +# the response will be "refused". +# # Parameters: # $cmd - The command that dispatched us (ls). # $ulsdir - The directory path to list... I'm not sure what this @@ -1462,8 +1699,17 @@ sub ls2_handler { my $rights; my $ulsout=''; my $ulsfn; + if ($ulsdir =~m{/\.\./}) { + &Failure($client,"refused\n",$userinput); + return 1; + } if (-e $ulsdir) { if(-d $ulsdir) { + unless (($ulsdir =~ m{/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) || + ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/userfiles/})) { + &Failure($client,"refused\n","$userinput"); + return 1; + } if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { undef($obs); @@ -1488,6 +1734,10 @@ sub ls2_handler { closedir(LSDIR); } } else { + unless ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/}) { + &Failure($client,"refused\n",$userinput); + return 1; + } my @ulsstats=stat($ulsdir); $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; } @@ -1504,6 +1754,22 @@ sub ls2_handler { # 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. +# +# If the requested path (after prepending) contains /../ or is: +# +# 1. for a directory, and the path does not begin with one of: +# (a) /home/httpd/html/res/<domain> +# (b) /home/httpd/html/res/userfiles/ +# (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles +# (d) /home/httpd/html/priv/<domain>/ and client is the homeserver +# +# or is: +# +# 2. for a file, and the path (after prepending) does not begin with: +# /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/ +# +# the response will be "refused". +# # Parameters: # $cmd - The command that dispatched us (ls). # $tail - The tail of the request that invoked us. @@ -1543,22 +1809,12 @@ sub ls3_handler { } my $dir_root = $perlvar{'lonDocRoot'}; - if ($getpropath) { + if (($getpropath) || ($getuserdir)) { if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { $dir_root = &propath($udom,$uname); $dir_root =~ s/\/$//; } else { - &Failure($client,"refused\n","$cmd:$tail"); - return 1; - } - } elsif ($getuserdir) { - if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { - my $subdir=$uname.'__'; - $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/; - $dir_root = $Apache::lonnet::perlvar{'lonUsersDir'} - ."/$udom/$subdir/$uname"; - } else { - &Failure($client,"refused\n","$cmd:$tail"); + &Failure($client,"refused\n",$userinput); return 1; } } elsif ($alternate_root ne '') { @@ -1571,12 +1827,28 @@ sub ls3_handler { $ulsdir = $dir_root.'/'.$ulsdir; } } + if ($ulsdir =~m{/\.\./}) { + &Failure($client,"refused\n",$userinput); + return 1; + } + my $islocal; + my @machine_ids = &Apache::lonnet::current_machine_ids(); + if (grep(/^\Q$clientname\E$/,@machine_ids)) { + $islocal = 1; + } my $obs; my $rights; my $ulsout=''; my $ulsfn; if (-e $ulsdir) { if(-d $ulsdir) { + unless (($getpropath) || ($getuserdir) || + ($ulsdir =~ m{/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) || + ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/userfiles/}) || + (($ulsdir =~ m{/home/httpd/html/priv/$LONCAPA::match_domain/}) && ($islocal))) { + &Failure($client,"refused\n",$userinput); + return 1; + } if (opendir(LSDIR,$ulsdir)) { while ($ulsfn=readdir(LSDIR)) { undef($obs); @@ -1601,6 +1873,11 @@ sub ls3_handler { closedir(LSDIR); } } else { + unless (($getpropath) || ($getuserdir) || + ($ulsdir =~ m{/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_username/})) { + &Failure($client,"refused\n",$userinput); + return 1; + } my @ulsstats=stat($ulsdir); $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; } @@ -1613,6 +1890,82 @@ sub ls3_handler { } ®ister_handler("ls3", \&ls3_handler, 0, 1, 0); +sub read_lonnet_global { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + my $requested = &Apache::lonnet::thaw_unescape($tail); + my $result; + my %packagevars = ( + spareid => \%Apache::lonnet::spareid, + perlvar => \%Apache::lonnet::perlvar, + ); + my %limit_to = ( + perlvar => { + lonOtherAuthen => 1, + lonBalancer => 1, + lonVersion => 1, + lonAdmEMail => 1, + lonSupportEMail => 1, + lonSysEMail => 1, + lonHostID => 1, + lonRole => 1, + lonDefDomain => 1, + lonLoadLim => 1, + lonUserLoadLim => 1, + } + ); + if (ref($requested) eq 'HASH') { + foreach my $what (keys(%{$requested})) { + my $response; + my $items = {}; + if (exists($packagevars{$what})) { + if (ref($limit_to{$what}) eq 'HASH') { + foreach my $varname (keys(%{$packagevars{$what}})) { + if ($limit_to{$what}{$varname}) { + $items->{$varname} = $packagevars{$what}{$varname}; + } + } + } else { + $items = $packagevars{$what}; + } + if ($what eq 'perlvar') { + if (!exists($packagevars{$what}{'lonBalancer'})) { + if ($dist =~ /^(centos|rhes|fedora|scientific)/) { + my $othervarref=LONCAPA::Configuration::read_conf('httpd.conf'); + if (ref($othervarref) eq 'HASH') { + $items->{'lonBalancer'} = $othervarref->{'lonBalancer'}; + } + } + } + } + $response = &Apache::lonnet::freeze_escape($items); + } + $result .= &escape($what).'='.$response.'&'; + } + } + $result =~ s/\&$//; + &Reply($client,\$result,$userinput); + return 1; +} +®ister_handler("readlonnetglobal", \&read_lonnet_global, 0, 1, 0); + +sub server_devalidatecache_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + my $items = &unescape($tail); + my @cached = split(/\&/,$items); + foreach my $key (@cached) { + if ($key =~ /:/) { + my ($name,$id) = map { &unescape($_); } split(/:/,$key); + &Apache::lonnet::devalidate_cache_new($name,$id); + } + } + my $result = 'ok'; + &Reply($client,\$result,$userinput); + return 1; +} +®ister_handler("devalidatecache", \&server_devalidatecache_handler, 0, 1, 0); + sub server_timezone_handler { my ($cmd,$tail,$client) = @_; my $userinput = "$cmd:$tail"; @@ -1653,6 +2006,33 @@ sub server_loncaparev_handler { } ®ister_handler("serverloncaparev", \&server_loncaparev_handler, 0, 1, 0); +sub server_homeID_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + &Reply($client,\$perlvar{'lonHostID'},$userinput); + return 1; +} +®ister_handler("serverhomeID", \&server_homeID_handler, 0, 1, 0); + +sub server_distarch_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + my $reply = &distro_and_arch(); + &Reply($client,\$reply,$userinput); + return 1; +} +®ister_handler("serverdistarch", \&server_distarch_handler, 0, 1, 0); + +sub server_certs_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; + my $result; + my $result = &LONCAPA::Lond::server_certs(\%perlvar); + &Reply($client,\$result,$userinput); + return; +} +®ister_handler("servercerts", \&server_certs_handler, 0, 1, 0); + # Process a reinit request. Reinit requests that either # lonc or lond be reinitialized so that an updated # host.tab or domain.tab can be processed. @@ -1762,15 +2142,45 @@ sub authenticate_handler { # upass - User's password. # checkdefauth - Pass to validate_user() to try authentication # with default auth type(s) if no user account. + # clientcancheckhost - Passed by clients with functionality in lonauth.pm + # to check if session can be hosted. - my ($udom, $uname, $upass, $checkdefauth)=split(/:/,$tail); + my ($udom, $uname, $upass, $checkdefauth, $clientcancheckhost)=split(/:/,$tail); &Debug(" Authenticate domain = $udom, user = $uname, password = $upass, checkdefauth = $checkdefauth"); chomp($upass); $upass=&unescape($upass); my $pwdcorrect = &validate_user($udom,$uname,$upass,$checkdefauth); if($pwdcorrect) { - &Reply( $client, "authorized\n", $userinput); + my $canhost = 1; + unless ($clientcancheckhost) { + my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); + my $uint_dom = &Apache::lonnet::internet_dom($uprimary_id); + my @intdoms; + my $internet_names = &Apache::lonnet::get_internet_names($clientname); + if (ref($internet_names) eq 'ARRAY') { + @intdoms = @{$internet_names}; + } + unless ($uint_dom ne '' && grep(/^\Q$uint_dom\E$/,@intdoms)) { + my ($remote,$hosted); + my $remotesession = &get_usersession_config($udom,'remotesession'); + if (ref($remotesession) eq 'HASH') { + $remote = $remotesession->{'remote'}; + } + my $hostedsession = &get_usersession_config($clienthomedom,'hostedsession'); + if (ref($hostedsession) eq 'HASH') { + $hosted = $hostedsession->{'hosted'}; + } + $canhost = &Apache::lonnet::can_host_session($udom,$clientname, + $clientversion, + $remote,$hosted); + } + } + if ($canhost) { + &Reply( $client, "authorized\n", $userinput); + } else { + &Reply( $client, "not_allowed_to_host\n", $userinput); + } # # Bad credentials: Failed to authorize # @@ -1815,8 +2225,9 @@ sub change_password_handler { # npass - New password. # context - Context in which this was called # (preferences or reset_by_email). + # lonhost - HostID of server where request originated - my ($udom,$uname,$upass,$npass,$context)=split(/:/,$tail); + my ($udom,$uname,$upass,$npass,$context,$lonhost)=split(/:/,$tail); $upass=&unescape($upass); $npass=&unescape($npass); @@ -1825,9 +2236,13 @@ sub change_password_handler { # First require that the user can be authenticated with their # old password unless context was 'reset_by_email': - my $validated; + my ($validated,$failure); if ($context eq 'reset_by_email') { - $validated = 1; + if ($lonhost eq '') { + $failure = 'invalid_client'; + } else { + $validated = 1; + } } else { $validated = &validate_user($udom, $uname, $upass); } @@ -1837,12 +2252,14 @@ sub change_password_handler { my ($howpwd,$contentpwd)=split(/:/,$realpasswd); if ($howpwd eq 'internal') { &Debug("internal auth"); - my $salt=time; - $salt=substr($salt,6,2); - my $ncpass=crypt($npass,$salt); + my $ncpass = &hash_passwd($udom,$npass); if(&rewrite_password_file($udom, $uname, "internal:$ncpass")) { - &logthis("Result of password change for " - ."$uname: pwchange_success"); + my $msg="Result of password change for $uname: pwchange_success"; + if ($lonhost) { + $msg .= " - request originated from: $lonhost"; + } + &logthis($msg); + &update_passwd_history($uname,$udom,$howpwd,$context); &Reply($client, "ok\n", $userinput); } else { &logthis("Unable to open $uname passwd " @@ -1851,6 +2268,9 @@ sub change_password_handler { } } elsif ($howpwd eq 'unix' && $context ne 'reset_by_email') { my $result = &change_unix_password($uname, $npass); + if ($result eq 'ok') { + &update_passwd_history($uname,$udom,$howpwd,$context); + } &logthis("Result of password change for $uname: ". $result); &Reply($client, \$result, $userinput); @@ -1863,13 +2283,52 @@ sub change_password_handler { } } else { - &Failure( $client, "non_authorized\n", $userinput); + if ($failure eq '') { + $failure = 'non_authorized'; + } + &Failure( $client, "$failure\n", $userinput); } return 1; } ®ister_handler("passwd", \&change_password_handler, 1, 1, 0); +sub hash_passwd { + my ($domain,$plainpass,@rest) = @_; + my ($salt,$cost); + if (@rest) { + $cost = $rest[0]; + # salt is first 22 characters, base-64 encoded by bcrypt + my $plainsalt = substr($rest[1],0,22); + $salt = Crypt::Eksblowfish::Bcrypt::de_base64($plainsalt); + } else { + my $defaultcost; + my %domconfig = + &Apache::lonnet::get_dom('configuration',['password'],$domain); + if (ref($domconfig{'password'}) eq 'HASH') { + $defaultcost = $domconfig{'password'}{'cost'}; + } + if (($defaultcost eq '') || ($defaultcost =~ /D/)) { + $cost = 10; + } else { + $cost = $defaultcost; + } + # Generate random 16-octet base64 salt + $salt = ""; + $salt .= pack("C", int rand(256)) for 1..16; + } + my $hash = &Crypt::Eksblowfish::Bcrypt::bcrypt_hash({ + key_nul => 1, + cost => $cost, + salt => $salt, + }, Digest::SHA::sha512(Encode::encode('UTF-8',$plainpass))); + + my $result = join("!", "", "bcrypt", sprintf("%02d",$cost), + &Crypt::Eksblowfish::Bcrypt::en_base64($salt). + &Crypt::Eksblowfish::Bcrypt::en_base64($hash)); + return $result; +} + # # Create a new user. User in this case means a lon-capa user. # The user must either already exist in some authentication realm @@ -1913,7 +2372,8 @@ sub add_user_handler { ."makeuser"; } unless ($fperror) { - my $result=&make_passwd_file($uname, $umode,$npass, $passfilename); + my $result=&make_passwd_file($uname,$udom,$umode,$npass, + $passfilename,'makeuser'); &Reply($client,\$result, $userinput); #BUGBUG - could be fail } else { &Failure($client, \$fperror, $userinput); @@ -1974,36 +2434,30 @@ sub change_authentication_handler { my $passfilename = &password_path($udom, $uname); if ($passfilename) { # Not allowed to create a new user!! # 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). + # passwd since otherwise make_passwd_file will fail as + # creation of unix authenticated users is no longer supported + # except from the command line, when running make_domain_coordinator.pl 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") { + &update_passwd_history($uname,$udom,$umode,'changeuserauth'); &Reply($client, \$result); } else { &Failure($client, \$result); } } else { - my $result=&make_passwd_file($uname, $umode,$npass,$passfilename); + my $result=&make_passwd_file($uname,$udom,$umode,$npass, + $passfilename,'changeuserauth'); # # 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); } @@ -2016,6 +2470,17 @@ sub change_authentication_handler { } ®ister_handler("changeuserauth", \&change_authentication_handler, 1,1, 0); +sub update_passwd_history { + my ($uname,$udom,$umode,$context) = @_; + my $proname=&propath($udom,$uname); + my $now = time; + if (open(my $fh,">>$proname/passwd.log")) { + print $fh "$now:$umode:$context\n"; + close($fh); + } + return; +} + # # Determines if this is the home server for a user. The home server # for a user will have his/her lon-capa passwd file. Therefore all we need @@ -2050,16 +2515,10 @@ sub is_home_handler { ®ister_handler("home", \&is_home_handler, 0,1,0); # -# Process an update request for a resource?? I think what's going on here is -# that a resource has been modified that we hold a subscription to. +# Process an update request for a resource. +# A resource has been modified that we hold a subscription to. # If the resource is not local, then we must update, or at least invalidate our # cached copy of the resource. -# FUTURE WORK: -# I need to look at this logic carefully. My druthers would be to follow -# typical caching logic, and simple invalidate the cache, drop any subscription -# an let the next fetch start the ball rolling again... however that may -# actually be more difficult than it looks given the complex web of -# proxy servers. # Parameters: # $cmd - The command that got us here. # $tail - Tail of the command (remaining parameters). @@ -2083,43 +2542,54 @@ sub update_resource_handler { my $ownership=ishome($fname); if ($ownership eq 'not_owner') { if (-e $fname) { + # Delete preview file, if exists + unlink("$fname.tmp"); + # Get usage stats my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks)=stat($fname); my $now=time; my $since=$now-$atime; + # If the file has not been used within lonExpire seconds, + # unsubscribe from it and delete local copy if ($since>$perlvar{'lonExpire'}) { my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname"); &devalidate_meta_cache($fname); unlink("$fname"); unlink("$fname.meta"); } else { + # Yes, this is in active use. Get a fresh copy. Since it might be in + # very active use and huge (like a movie), copy it to "in.transfer" filename first. my $transname="$fname.in.transfer"; my $remoteurl=&Apache::lonnet::reply("sub:$fname","$clientname"); my $response; - alarm(120); +# FIXME: cannot replicate files that take more than two minutes to transfer? +# alarm(120); +# FIXME: this should use the LWP mechanism, not internal alarms. + alarm(1200); { - my $ua=new LWP::UserAgent; my $request=new HTTP::Request('GET',"$remoteurl"); - $response=$ua->request($request,$transname); + $response=&LONCAPA::LWPReq::makerequest($clientname,$request,$transname,\%perlvar,1200,0,1); } alarm(0); if ($response->is_error()) { +# FIXME: we should probably clean up here instead of just whine unlink($transname); my $message=$response->status_line; &logthis("LWP GET: $message for $fname ($remoteurl)"); } else { if ($remoteurl!~/\.meta$/) { +# FIXME: isn't there an internal LWP mechanism for this? alarm(120); { - my $ua=new LWP::UserAgent; my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta'); - my $mresponse=$ua->request($mrequest,$fname.'.meta'); + my $mresponse = &LONCAPA::LWPReq::makerequest($clientname,$mrequest,$fname.'.meta',\%perlvar,120,0,1); if ($mresponse->is_error()) { unlink($fname.'.meta'); } } alarm(0); } + # we successfully transfered, copy file over to real name rename($transname,$fname); &devalidate_meta_cache($fname); } @@ -2181,14 +2651,21 @@ sub fetch_user_file_handler { my $destname=$udir.'/'.$ufile; my $transname=$udir.'/'.$ufile.'.in.transit'; - my $remoteurl='http://'.$clientip.'/userfiles/'.$fname; + my $clientprotocol=$Apache::lonnet::protocol{$clientname}; + $clientprotocol = 'http' if ($clientprotocol ne 'https'); + my $clienthost = &Apache::lonnet::hostname($clientname); + my $remoteurl=$clientprotocol.'://'.$clienthost.'/userfiles/'.$fname; my $response; Debug("Remote URL : $remoteurl Transfername $transname Destname: $destname"); - alarm(120); + alarm(1200); { - my $ua=new LWP::UserAgent; my $request=new HTTP::Request('GET',"$remoteurl"); - $response=$ua->request($request,$transname); + my $verifycert = 1; + my @machine_ids = &Apache::lonnet::current_machine_ids(); + if (grep(/^\Q$clientname\E$/,@machine_ids)) { + $verifycert = 0; + } + $response = &LONCAPA::LWPReq::makerequest($clientname,$request,$transname,\%perlvar,1200,$verifycert); } alarm(0); if ($response->is_error()) { @@ -2203,6 +2680,24 @@ sub fetch_user_file_handler { unlink($transname); &Failure($client, "failed\n", $userinput); } else { + if ($fname =~ /^default.+\.(page|sequence)$/) { + my ($major,$minor) = split(/\./,$clientversion); + if (($major < 2) || ($major == 2 && $minor < 11)) { + my $now = time; + &Apache::lonnet::do_cache_new('crschange',$udom.'_'.$uname,$now,600); + my $key = &escape('internal.contentchange'); + my $what = "$key=$now"; + my $hashref = &tie_user_hash($udom,$uname,'environment', + &GDBM_WRCREAT(),"P",$what); + if ($hashref) { + $hashref->{$key}=$now; + if (!&untie_user_hash($hashref)) { + &logthis("error: ".($!+0)." untie (GDBM) failed ". + "when updating internal.contentchange"); + } + } + } + } &Reply($client, "ok\n", $userinput); } } @@ -2239,11 +2734,20 @@ sub remove_user_file_handler { if (-e $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: + # However it's possible the client wants a dir + # removed, in which case rmdir is more appropriate. + # Note: rmdir will only remove an empty directory. # if (-f $file){ unlink($file); + # for html files remove the associated .bak file + # which may have been created by the editor. + if ($ufile =~ m{^((docs|supplemental)/(?:\d+|default)/\d+(?:|/.+)/)[^/]+\.x?html?$}i) { + my $path = $1; + if (-e $file.'.bak') { + unlink($file.'.bak'); + } + } } elsif(-d $file) { rmdir($file); } @@ -2357,7 +2861,6 @@ sub user_has_session_handler { my ($udom, $uname) = map { &unescape($_) } (split(/:/, $tail)); - &logthis("Looking for $udom $uname"); opendir(DIR,$perlvar{'lonIDsDir'}); my $filename; while ($filename=readdir(DIR)) { @@ -2607,8 +3110,12 @@ sub newput_user_profile_entry { foreach my $pair (@pairs) { my ($key,$value)=split(/=/,$pair); if (exists($hashref->{$key})) { - &Failure($client, "key_exists: ".$key."\n",$userinput); - return 1; + if (!&untie_user_hash($hashref)) { + &logthis("error: ".($!+0)." untie (GDBM) failed ". + "while attempting newput - early out as key exists"); + } + &Failure($client, "key_exists: ".$key."\n",$userinput); + return 1; } } @@ -3010,6 +3517,17 @@ sub get_profile_keys { sub dump_profile_database { my ($cmd, $tail, $client) = @_; + my $res = LONCAPA::Lond::dump_profile_database($tail); + + if ($res =~ /^error:/) { + Failure($client, \$res, "$cmd:$tail"); + } else { + Reply($client, \$res, "$cmd:$tail"); + } + + return 1; + + #TODO remove my $userinput = "$cmd:$tail"; my ($udom,$uname,$namespace) = split(/:/,$tail); @@ -3076,6 +3594,9 @@ sub dump_profile_database { # that is matched against # database keywords to do # selective dumps. +# range - optional range of entries +# e.g., 10-20 would return the +# 10th to 19th items, etc. # $client - Channel open on the client. # Returns: # 1 - Continue processing. @@ -3085,56 +3606,12 @@ sub dump_profile_database { sub dump_with_regexp { my ($cmd, $tail, $client) = @_; - - my $userinput = "$cmd:$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_user_hash($hashref)) { - chop($qresult); - &Reply($client, \$qresult, $userinput); - } else { - &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". - "while attempting dump\n", $userinput); - } + my $res = LONCAPA::Lond::dump_with_regexp($tail, $clientversion); + + if ($res =~ /^error:/) { + Failure($client, \$res, "$cmd:$tail"); } else { - &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". - "while attempting dump\n", $userinput); + Reply($client, \$res, "$cmd:$tail"); } return 1; @@ -3151,6 +3628,9 @@ sub dump_with_regexp { # namespace - Name of the database being modified # rid - Resource keyword to modify. # what - new value associated with rid. +# laststore - (optional) version=timestamp +# for most recent transaction for rid +# in namespace, when cstore was called # # $client - Socket open on the client. # @@ -3159,23 +3639,45 @@ sub dump_with_regexp { # 1 (keep on processing). # Side-Effects: # Writes to the client +# Successful storage will cause either 'ok', or, if $laststore was included +# in the tail of the request, and the version number for the last transaction +# is larger than the version in $laststore, delay:$numtrans , where $numtrans +# is the number of store evevnts recorded for rid in namespace since +# lonnet::store() was called by the client. +# sub store_handler { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; - - my ($udom,$uname,$namespace,$rid,$what) =split(/:/,$tail); + chomp($tail); + my ($udom,$uname,$namespace,$rid,$what,$laststore) =split(/:/,$tail); if ($namespace ne 'roles') { - chomp($what); my @pairs=split(/\&/,$what); my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_WRCREAT(), "S", "$rid:$what"); if ($hashref) { my $now = time; - my @previouskeys=split(/&/,$hashref->{"keys:$rid"}); - my $key; + my $numtrans; + if ($laststore) { + my ($previousversion,$previoustime) = split(/\=/,$laststore); + my ($lastversion,$lasttime) = (0,0); + $lastversion = $hashref->{"version:$rid"}; + if ($lastversion) { + $lasttime = $hashref->{"$lastversion:$rid:timestamp"}; + } + if (($previousversion) && ($previousversion !~ /\D/)) { + if (($lastversion > $previousversion) && ($lasttime >= $previoustime)) { + $numtrans = $lastversion - $previousversion; + } + } elsif ($lastversion) { + $numtrans = $lastversion; + } + if ($numtrans) { + $numtrans =~ s/D//g; + } + } $hashref->{"version:$rid"}++; my $version=$hashref->{"version:$rid"}; my $allkeys=''; @@ -3188,7 +3690,11 @@ sub store_handler { $allkeys.='timestamp'; $hashref->{"$version:keys:$rid"}=$allkeys; if (&untie_user_hash($hashref)) { - &Reply($client, "ok\n", $userinput); + my $msg = 'ok'; + if ($numtrans) { + $msg = 'delay:'.$numtrans; + } + &Reply($client, "$msg\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". "while attempting store\n", $userinput); @@ -3693,12 +4199,20 @@ sub put_course_id_hash_handler { # caller - if set to 'coursecatalog', courses set to be hidden # from course catalog will be excluded from results (unless # overridden by "showhidden". -# cloner - escaped username:domain of course cloner (if picking course to# +# cloner - escaped username:domain of course cloner (if picking course to # clone). # cc_clone_list - escaped comma separated list of courses for which # course cloner has active CC role (and so can clone # automatically). -# cloneonly - filter by courses for which cloner has rights to clone. +# cloneonly - filter by courses for which cloner has rights to clone. +# createdbefore - include courses for which creation date preceeded this date. +# createdafter - include courses for which creation date followed this date. +# creationcontext - include courses created in specified context +# +# domcloner - flag to indicate if user can create CCs in course's domain. +# If so, ability to clone course is automatic. +# hasuniquecode - filter by courses for which a six character unique code has +# been set. # # $client - The socket open on the client. # Returns: @@ -3707,11 +4221,23 @@ sub put_course_id_hash_handler { # a reply is written to $client. sub dump_course_id_handler { my ($cmd, $tail, $client) = @_; + + my $res = LONCAPA::Lond::dump_course_id_handler($tail); + if ($res =~ /^error:/) { + Failure($client, \$res, "$cmd:$tail"); + } else { + Reply($client, \$res, "$cmd:$tail"); + } + + return 1; + + #TODO remove my $userinput = "$cmd:$tail"; my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter, $typefilter,$regexp_ok,$rtn_as_hash,$selfenrollonly,$catfilter,$showhidden, - $caller,$cloner,$cc_clone_list,$cloneonly) =split(/:/,$tail); + $caller,$cloner,$cc_clone_list,$cloneonly,$createdbefore,$createdafter, + $creationcontext,$domcloner,$hasuniquecode) =split(/:/,$tail); my $now = time; my ($cloneruname,$clonerudom,%cc_clone); if (defined($description)) { @@ -3769,9 +4295,26 @@ sub dump_course_id_handler { $cc_clone{$clonedom.'_'.$clonenum} = 1; } } - + if ($createdbefore ne '') { + $createdbefore = &unescape($createdbefore); + } else { + $createdbefore = 0; + } + if ($createdafter ne '') { + $createdafter = &unescape($createdafter); + } else { + $createdafter = 0; + } + if ($creationcontext ne '') { + $creationcontext = &unescape($creationcontext); + } else { + $creationcontext = '.'; + } + unless ($hasuniquecode) { + $hasuniquecode = '.'; + } my $unpack = 1; - if ($description eq '.' && $instcodefilter eq '.' && $coursefilter eq '.' && + if ($description eq '.' && $instcodefilter eq '.' && $ownerfilter eq '.' && $typefilter eq '.') { $unpack = 0; } @@ -3781,7 +4324,8 @@ sub dump_course_id_handler { if ($hashref) { while (my ($key,$value) = each(%$hashref)) { my ($unesc_key,$lasttime_key,$lasttime,$is_hash,%val, - %unesc_val,$selfenroll_end,$selfenroll_types); + %unesc_val,$selfenroll_end,$selfenroll_types,$created, + $context); $unesc_key = &unescape($key); if ($unesc_key =~ /^lasttime:/) { next; @@ -3795,8 +4339,13 @@ sub dump_course_id_handler { my ($canclone,$valchange); my $items = &Apache::lonnet::thaw_unescape($value); if (ref($items) eq 'HASH') { + if ($hashref->{$lasttime_key} eq '') { + next if ($since > 1); + } $is_hash = 1; - if (defined($clonerudom)) { + if ($domcloner) { + $canclone = 1; + } elsif (defined($clonerudom)) { if ($items->{'cloners'}) { my @cloneable = split(',',$items->{'cloners'}); if (@cloneable) { @@ -3824,6 +4373,19 @@ sub dump_course_id_handler { $items->{'cloners'} = $cloneruname.':'.$clonerudom; $valchange = 1; } + unless ($canclone) { + if ($items->{'owner'} =~ /:/) { + if ($items->{'owner'} eq $cloner) { + $canclone = 1; + } + } elsif ($cloner eq $items->{'owner'}.':'.$udom) { + $canclone = 1; + } + if ($canclone) { + $items->{'cloners'} = $cloneruname.':'.$clonerudom; + $valchange = 1; + } + } } } if ($unpack || !$rtn_as_hash) { @@ -3832,15 +4394,31 @@ sub dump_course_id_handler { $unesc_val{'owner'} = $items->{'owner'}; $unesc_val{'type'} = $items->{'type'}; $unesc_val{'cloners'} = $items->{'cloners'}; + $unesc_val{'created'} = $items->{'created'}; + $unesc_val{'context'} = $items->{'context'}; } $selfenroll_types = $items->{'selfenroll_types'}; $selfenroll_end = $items->{'selfenroll_end_date'}; + $created = $items->{'created'}; + $context = $items->{'context'}; + if ($hasuniquecode ne '.') { + next unless ($items->{'uniquecode'}); + } if ($selfenrollonly) { next if (!$selfenroll_types); if (($selfenroll_end > 0) && ($selfenroll_end <= $now)) { next; } } + if ($creationcontext ne '.') { + next if (($context ne '') && ($context ne $creationcontext)); + } + if ($createdbefore > 0) { + next if (($created eq '') || ($created > $createdbefore)); + } + if ($createdafter > 0) { + next if (($created eq '') || ($created <= $createdafter)); + } if ($catfilter ne '') { next if ($items->{'categories'} eq ''); my @categories = split('&',$items->{'categories'}); @@ -3863,6 +4441,8 @@ sub dump_course_id_handler { } else { next if ($catfilter ne ''); next if ($selfenrollonly); + next if ($createdbefore || $createdafter); + next if ($creationcontext ne '.'); if ((defined($clonerudom)) && (defined($cloneruname))) { if ($cc_clone{$unesc_key}) { $canclone = 1; @@ -4009,6 +4589,53 @@ sub dump_course_id_handler { } ®ister_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0); +sub course_lastaccess_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($cdom,$cnum) = split(':',$tail); + my (%lastaccess,$qresult); + my $hashref = &tie_domain_hash($cdom, "nohist_courseids", &GDBM_WRCREAT()); + if ($hashref) { + while (my ($key,$value) = each(%$hashref)) { + my ($unesc_key,$lasttime); + $unesc_key = &unescape($key); + if ($cnum) { + next unless ($unesc_key =~ /\Q$cdom\E_\Q$cnum\E$/); + } + if ($unesc_key =~ /^lasttime:($LONCAPA::match_domain\_$LONCAPA::match_courseid)/) { + $lastaccess{$1} = $value; + } else { + my $items = &Apache::lonnet::thaw_unescape($value); + if (ref($items) eq 'HASH') { + unless ($lastaccess{$unesc_key}) { + $lastaccess{$unesc_key} = ''; + } + } else { + my @courseitems = split(':',$value); + $lastaccess{$unesc_key} = pop(@courseitems); + } + } + } + foreach my $cid (sort(keys(%lastaccess))) { + $qresult.=&escape($cid).'='.$lastaccess{$cid}.'&'; + } + if (&untie_domain_hash($hashref)) { + if ($qresult) { + chop($qresult); + } + &Reply($client, \$qresult, $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting lastacourseaccess\n", $userinput); + } + } else { + &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting lastcourseaccess\n", $userinput); + } + return 1; +} +®ister_handler("courselastaccess",\&course_lastaccess_handler, 0, 1, 0); + # # Puts an unencrypted entry in a namespace db file at the domain level # @@ -4052,59 +4679,121 @@ sub put_domain_handler { } ®ister_handler("putdom", \&put_domain_handler, 0, 1, 0); -# -# Puts a piece of new data in a namespace db file at the domain level -# returns error if key already exists +# Updates one or more entries in clickers.db file at the domain level # # Parameters: # $cmd - The command that got us here. # $tail - Tail of the command (remaining parameters). +# In this case a colon separated list containing: +# (a) the domain for which we are updating the entries, +# (b) the action required -- add or del -- and +# (c) a &-separated list of entries to add or delete. # $client - File descriptor connected to client. # Returns -# 0 - Requested to exit, caller should shut down. # 1 - Continue processing. +# 0 - Requested to exit, caller should shut down. # Side effects: # reply is written to $client. # -sub newput_domain_handler { + + +sub update_clickers { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; - - my ($udom,$namespace,$what) =split(/:/,$tail,3); + my ($udom,$action,$what) =split(/:/,$tail,3); chomp($what); - my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_WRCREAT(), - "N", $what); - if(!$hashref) { + + my $hashref = &tie_domain_hash($udom, "clickers", &GDBM_WRCREAT(), + "U","$action:$what"); + + if (!$hashref) { &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". - "while attempting newputdom\n", $userinput); + "while attempting updateclickers\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; + if ($action eq 'add') { + if (exists($hashref->{$key})) { + my @newvals = split(/,/,&unescape($value)); + my @currvals = split(/,/,&unescape($hashref->{$key})); + my @merged = sort(keys(%{{map { $_ => 1 } (@newvals,@currvals)}})); + $hashref->{$key}=&escape(join(',',@merged)); + } else { + $hashref->{$key}=$value; + } + } elsif ($action eq 'del') { + if (exists($hashref->{$key})) { + my %current; + map { $current{$_} = 1; } split(/,/,&unescape($hashref->{$key})); + map { delete($current{$_}); } split(/,/,&unescape($value)); + if (keys(%current)) { + $hashref->{$key}=&escape(join(',',sort(keys(%current)))); + } else { + delete($hashref->{$key}); + } + } } } - - foreach my $pair (@pairs) { - my ($key,$value)=split(/=/,$pair); - $hashref->{$key}=$value; - } - - if (&untie_domain_hash($hashref)) { + if (&untie_user_hash($hashref)) { &Reply( $client, "ok\n", $userinput); } else { &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". - "while attempting newputdom\n", + "while attempting put\n", $userinput); } return 1; } -®ister_handler("newputdom", \&newput_domain_handler, 0, 1, 0); +®ister_handler("updateclickers", \&update_clickers, 0, 1, 0); + + +# Deletes one or more entries in a namespace db file at the domain level +# +# Parameters: +# $cmd - The command that got us here. +# $tail - Tail of the command (remaining parameters). +# In this case a colon separated list containing: +# (a) the domain for which we are deleting the entries, +# (b) &-separated list of keys to delete. +# $client - File descriptor connected to client. +# Returns +# 1 - Continue processing. +# 0 - Requested to exit, caller should shut down. +# Side effects: +# reply is written to $client. +# + +sub del_domain_handler { + my ($cmd,$tail,$client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($udom,$namespace,$what)=split(/:/,$tail,3); + chomp($what); + my $hashref = &tie_domain_hash($udom,$namespace,&GDBM_WRCREAT(), + "D", $what); + if ($hashref) { + my @keys=split(/\&/,$what); + foreach my $key (@keys) { + delete($hashref->{$key}); + } + if (&untie_user_hash($hashref)) { + &Reply($client, "ok\n", $userinput); + } else { + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting deldom\n", $userinput); + } + } else { + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting deldom\n", $userinput); + } + return 1; +} +®ister_handler("deldom", \&del_domain_handler, 0, 1, 0); + # Unencrypted get from the namespace database file at the domain level. # This function retrieves a keyed item from a specific named database in the @@ -4128,6 +4817,7 @@ sub newput_domain_handler { sub get_domain_handler { my ($cmd, $tail, $client) = @_; + my $userinput = "$client:$tail"; my ($udom,$namespace,$what)=split(/:/,$tail,3); @@ -4156,50 +4846,6 @@ sub get_domain_handler { ®ister_handler("getdom", \&get_domain_handler, 0, 1, 0); # -# Deletes a key in a user profile database. -# -# Parameters: -# $cmd - Command keyword (deldom). -# $tail - Command tail. IN this case a colon -# separated list containing: -# the domain to which the database file belongs; -# the namespace (name of the database file); -# & separated list of keys to delete. -# $client - File open on client socket. -# Returns: -# 1 - Continue processing -# 0 - Exit server. -# -# -sub delete_domain_entry { - my ($cmd, $tail, $client) = @_; - - my $userinput = "cmd:$tail"; - - my ($udom,$namespace,$what) = split(/:/,$tail); - chomp($what); - my $hashref = &tie_domain_hash($udom, $namespace, &GDBM_WRCREAT(), - "D",$what); - if ($hashref) { - my @keys=split(/\&/,$what); - foreach my $key (@keys) { - delete($hashref->{$key}); - } - if (&untie_user_hash($hashref)) { - &Reply($client, "ok\n", $userinput); - } else { - &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". - "while attempting deldom\n", $userinput); - } - } else { - &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". - "while attempting deldom\n", $userinput); - } - return 1; -} -®ister_handler("deldom", \&delete_domain_entry, 0, 1, 0); - -# # Puts an id to a domains id database. # # Parameters: @@ -4296,59 +4942,48 @@ sub get_id_handler { } ®ister_handler("idget", \&get_id_handler, 0, 1, 0); -sub dump_dom_with_regexp { - my ($cmd, $tail, $client) = @_; +# Deletes one or more ids in a domain's id database. +# +# Parameters: +# $cmd - Command keyword (iddel). +# $tail - Command tail. In this case a colon +# separated list containing: +# The domain for which we are deleting the id(s). +# &-separated list of id(s) to delete. +# $client - File open on client socket. +# Returns: +# 1 - Continue processing +# 0 - Exit server. +# +# + +sub del_id_handler { + my ($cmd,$tail,$client) = @_; + my $userinput = "$cmd:$tail"; - my ($udom,$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_domain_hash($udom, $namespace, &GDBM_READER()); + + my ($udom,$what)=split(/:/,$tail); + chomp($what); + my $hashref = &tie_domain_hash($udom, "ids", &GDBM_WRCREAT(), + "D", $what); 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&"; - } - } + my @keys=split(/\&/,$what); + foreach my $key (@keys) { + delete($hashref->{$key}); } if (&untie_user_hash($hashref)) { - chop($qresult); - &Reply($client, \$qresult, $userinput); + &Reply($client, "ok\n", $userinput); } else { - &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". - "while attempting dump\n", $userinput); + &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". + "while attempting iddel\n", $userinput); } } else { - &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". - "while attempting dump\n", $userinput); + &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". + "while attempting iddel\n", $userinput); } return 1; } -®ister_handler("dumpdom", \&dump_dom_with_regexp, 0, 1, 0); +®ister_handler("iddel", \&del_id_handler, 0, 1, 0); # # Puts broadcast e-mail sent by Domain Coordinator in nohist_dcmail database @@ -4370,7 +5005,8 @@ sub dump_dom_with_regexp { 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()); @@ -4634,7 +5270,7 @@ sub tmp_put_handler { } my ($id,$store); $tmpsnum++; - if ($context eq 'resetpw') { + if (($context eq 'resetpw') || ($context eq 'createaccount')) { $id = &md5_hex(&md5_hex(time.{}.rand().$$)); } else { $id = $$.'_'.$clientip.'_'.$tmpsnum; @@ -4897,8 +5533,11 @@ sub validate_instcode_handler { my ($dom,$instcode,$owner) = split(/:/, $tail); $instcode = &unescape($instcode); $owner = &unescape($owner); - my $outcome=&localenroll::validate_instcode($dom,$instcode,$owner); - &Reply($client, \$outcome, $userinput); + my ($outcome,$description,$credits) = + &localenroll::validate_instcode($dom,$instcode,$owner); + my $result = &escape($outcome).'&'.&escape($description).'&'. + &escape($credits); + &Reply($client, \$result, $userinput); return 1; } @@ -4950,10 +5589,11 @@ sub get_sections_handler { sub validate_course_owner_handler { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; - my ($inst_course_id, $owner, $cdom) = split(/:/, $tail); - + my ($inst_course_id, $owner, $cdom, $coowners) = split(/:/, $tail); + $owner = &unescape($owner); - my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom); + $coowners = &unescape($coowners); + my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom,$coowners); &Reply($client, \$outcome, $userinput); @@ -5060,13 +5700,58 @@ sub create_auto_enroll_password_handler ®ister_handler("autocreatepassword", \&create_auto_enroll_password_handler, 0, 1, 0); +sub auto_export_grades_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($cdom,$cnum,$info,$data) = split(/:/,$tail); + my $inforef = &Apache::lonnet::thaw_unescape($info); + my $dataref = &Apache::lonnet::thaw_unescape($data); + my ($outcome,$result);; + eval { + local($SIG{__DIE__})='DEFAULT'; + my %rtnhash; + $outcome=&localenroll::export_grades($cdom,$cnum,$inforef,$dataref,\%rtnhash); + if ($outcome eq 'ok') { + foreach my $key (keys(%rtnhash)) { + $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rtnhash{$key}).'&'; + } + $result =~ s/\&$//; + } + }; + if (!$@) { + if ($outcome eq 'ok') { + if ($cipher) { + my $cmdlength=length($result); + $result.=" "; + my $encresult=''; + for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encresult.= unpack("H16", + $cipher->encrypt(substr($result, + $encidx, + 8))); + } + &Reply( $client, "enc:$cmdlength:$encresult\n", $userinput); + } else { + &Failure( $client, "error:no_key\n", $userinput); + } + } else { + &Reply($client, "$outcome\n", $userinput); + } + } else { + &Failure($client,"export_error\n",$userinput); + } + return 1; +} +®ister_handler("autoexportgrades", \&auto_export_grades_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. +# $filename - The name of the file to retrieve. # The filename is given as a path relative to # the LonCAPA temp file directory. # $client - Socket open on the client. @@ -5080,7 +5765,12 @@ sub retrieve_auto_file_handler { my ($filename) = split(/:/, $tail); my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename; - if ( (-e $source) && ($filename ne '') ) { + + if ($filename =~m{/\.\./}) { + &Failure($client, "refused\n", $userinput); + } elsif ($filename !~ /^$LONCAPA::match_domain\_$LONCAPA::match_courseid\_.+_classlist\.xml$/) { + &Failure($client, "refused\n", $userinput); + } elsif ( (-e $source) && ($filename ne '') ) { my $reply = ''; if (open(my $fh,$source)) { while (<$fh>) { @@ -5112,7 +5802,7 @@ sub crsreq_checks_handler { my $userinput = "$cmd:$tail"; my $dom = $tail; my $result; - my @reqtypes = ('official','unofficial','community'); + my @reqtypes = ('official','unofficial','community','textbook','placement'); eval { local($SIG{__DIE__})='DEFAULT'; my %validations; @@ -5139,19 +5829,20 @@ sub crsreq_checks_handler { sub validate_crsreq_handler { my ($cmd, $tail, $client) = @_; my $userinput = "$cmd:$tail"; - my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = split(/:/, $tail); + my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist,$customdata) = split(/:/, $tail); $instcode = &unescape($instcode); $owner = &unescape($owner); $crstype = &unescape($crstype); $inststatuslist = &unescape($inststatuslist); $instcode = &unescape($instcode); $instseclist = &unescape($instseclist); + my $custominfo = &Apache::lonnet::thaw_unescape($customdata); my $outcome; eval { local($SIG{__DIE__})='DEFAULT'; $outcome = &localenroll::validate_crsreq($dom,$owner,$crstype, $inststatuslist,$instcode, - $instseclist); + $instseclist,$custominfo); }; if (!$@) { &Reply($client, \$outcome, $userinput); @@ -5162,6 +5853,53 @@ sub validate_crsreq_handler { } ®ister_handler("autocrsreqvalidation", \&validate_crsreq_handler, 0, 1, 0); +sub crsreq_update_handler { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my ($cdom,$cnum,$crstype,$action,$ownername,$ownerdomain,$fullname,$title,$code, + $accessstart,$accessend,$infohashref) = + split(/:/, $tail); + $crstype = &unescape($crstype); + $action = &unescape($action); + $ownername = &unescape($ownername); + $ownerdomain = &unescape($ownerdomain); + $fullname = &unescape($fullname); + $title = &unescape($title); + $code = &unescape($code); + $accessstart = &unescape($accessstart); + $accessend = &unescape($accessend); + my $incoming = &Apache::lonnet::thaw_unescape($infohashref); + my ($result,$outcome); + eval { + local($SIG{__DIE__})='DEFAULT'; + my %rtnhash; + $outcome = &localenroll::crsreq_updates($cdom,$cnum,$crstype,$action, + $ownername,$ownerdomain,$fullname, + $title,$code,$accessstart,$accessend, + $incoming,\%rtnhash); + if ($outcome eq 'ok') { + my @posskeys = qw(createdweb createdmsg createdcustomized createdactions queuedweb queuedmsg formitems reviewweb validationjs onload javascript); + foreach my $key (keys(%rtnhash)) { + if (grep(/^\Q$key\E/,@posskeys)) { + $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rtnhash{$key}).'&'; + } + } + $result =~ s/\&$//; + } + }; + if (!$@) { + if ($outcome eq 'ok') { + &Reply($client, \$result, $userinput); + } else { + &Reply($client, "format_error\n", $userinput); + } + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } + return 1; +} +®ister_handler("autocrsrequpdate", \&crsreq_update_handler, 0, 1, 0); + # # Read and retrieve institutional code format (for support form). # Formal Parameters: @@ -5669,12 +6407,13 @@ sub get_request { # # Parameters: # user_input - The request received from the client (lonc). +# # Returns: # true to keep processing, false if caller should exit. # sub process_request { - my ($userinput) = @_; # Easier for now to break style than to - # fix all the userinput -> user_input. + my ($userinput) = @_; # Easier for now to break style than to + # fix all the userinput -> user_input. my $wasenc = 0; # True if request was encrypted. # ------------------------------------------------------------ See if encrypted # for command @@ -5754,6 +6493,49 @@ sub process_request { Debug("Client not privileged to do this operation"); $ok = 0; } + if ($ok) { + if (ref($trust{$command}) eq 'HASH') { + my $donechecks; + if ($trust{$command}{'anywhere'}) { + $donechecks = 1; + } elsif ($trust{$command}{'manageronly'}) { + unless (&isManager()) { + $ok = 0; + } + $donechecks = 1; + } elsif ($trust{$command}{'institutiononly'}) { + unless ($clientsameinst) { + $ok = 0; + } + $donechecks = 1; + } elsif ($clientsameinst) { + $donechecks = 1; + } + unless ($donechecks) { + foreach my $rule (keys(%{$trust{$command}})) { + next if ($rule eq 'remote'); + if ($trust{$command}{$rule}) { + if ($clientprohibited{$rule}) { + $ok = 0; + } else { + $ok = 1; + $donechecks = 1; + last; + } + } + } + } + unless ($donechecks) { + if ($trust{$command}{'remote'}) { + if ($clientremoteok) { + $ok = 1; + } else { + $ok = 0; + } + } + } + } + } if($ok) { Debug("Dispatching to handler $command $tail"); @@ -5764,8 +6546,7 @@ sub process_request { Failure($client, "refused\n", $userinput); return 1; } - - } + } print $client "unknown_cmd\n"; # -------------------------------------------------------------------- complete @@ -5869,18 +6650,6 @@ sub lcpasswdstrerror { } } -# -# Convert an error return code from lcuseradd to a string value: -# -sub lcuseraddstrerror { - my $ErrorCode = shift; - if(($ErrorCode < 0) || ($ErrorCode > $lastadderror)) { - return "lcuseradd - Unrecognized error code: ".$ErrorCode; - } else { - return $adderrors[$ErrorCode]; - } -} - # grabs exception and records it to log before exiting sub catchexception { my ($error)=@_; @@ -5941,7 +6710,7 @@ if (-e $pidfile) { $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'}, Type => SOCK_STREAM, Proto => 'tcp', - Reuse => 1, + ReuseAddr => 1, Listen => 10 ) or die "making socket: $@\n"; @@ -6004,9 +6773,13 @@ sub HUPSMAN { # sig # a setuid perl script that can be root for us to do this job. # sub ReloadApache { - my $execdir = $perlvar{'lonDaemons'}; - my $script = $execdir."/apachereload"; - system($script); +# --------------------------- Handle case of another apachereload process (locking) + if (&LONCAPA::try_to_lock('/tmp/lock_apachereload')) { + my $execdir = $perlvar{'lonDaemons'}; + my $script = $execdir."/apachereload"; + system($script); + unlink('/tmp/lock_apachereload'); # Remove the lock file. + } } # @@ -6117,6 +6890,9 @@ sub Debug { # reply - Text to send to client. # request - Original request from client. # +#NOTE $reply must be terminated by exactly *one* \n. If $reply is a reference +#this is done automatically ($$reply must not contain any \n in this case). +#If $reply is a string the caller has to ensure this. sub Reply { my ($fd, $reply, $request) = @_; if (ref($reply)) { @@ -6179,7 +6955,7 @@ sub logstatus { sub initnewstatus { my $docdir=$perlvar{'lonDocRoot'}; my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt"); - my $now=time; + my $now=time(); my $local=localtime($now); print $fh "LOND status $local - parent $$\n\n"; opendir(DIR,"$docdir/lon-status/londchld"); @@ -6268,8 +7044,16 @@ $SIG{USR2} = \&UpdateHosts; # Read the host hashes: &Apache::lonnet::load_hosts_tab(); +my %iphost = &Apache::lonnet::get_iphost(1); -my $dist=`$perlvar{'lonDaemons'}/distprobe`; +$dist=`$perlvar{'lonDaemons'}/distprobe`; + +my $arch = `uname -i`; +chomp($arch); +if ($arch eq 'unknown') { + $arch = `uname -m`; + chomp($arch); +} # -------------------------------------------------------------- # Accept connections. When a connection comes in, it is validated @@ -6328,6 +7112,7 @@ sub make_new_child { or die "Can't unblock SIGINT for fork: $!\n"; $children{$pid} = $clientip; &status('Started child '.$pid); + close($client); return; } else { # Child can *not* return from this subroutine. @@ -6336,6 +7121,13 @@ sub make_new_child { #don't get intercepted $SIG{USR1}= \&logstatus; $SIG{ALRM}= \&timeout; + # + # Block sigpipe as it gets thrownon socket disconnect and we want to + # deal with that as a read faiure instead. + # + my $blockset = POSIX::SigSet->new(SIGPIPE); + sigprocmask(SIG_BLOCK, $blockset); + $lastlog='Forked '; $status='Forked'; @@ -6346,8 +7138,26 @@ sub make_new_child { # my $tmpsnum=0; # Now global #---------------------------------------------------- kerberos 5 initialization &Authen::Krb5::init_context(); - unless (($dist eq 'fedora5') || ($dist eq 'fedora4') || - ($dist eq 'fedora6') || ($dist eq 'suse9.3')) { + + my $no_ets; + if ($dist =~ /^(?:centos|rhes|scientific)(\d+)$/) { + if ($1 >= 7) { + $no_ets = 1; + } + } elsif ($dist =~ /^suse(\d+\.\d+)$/) { + if (($1 eq '9.3') || ($1 >= 12.2)) { + $no_ets = 1; + } + } elsif ($dist =~ /^sles(\d+)$/) { + if ($1 > 11) { + $no_ets = 1; + } + } elsif ($dist =~ /^fedora(\d+)$/) { + if ($1 < 7) { + $no_ets = 1; + } + } + unless ($no_ets) { &Authen::Krb5::init_ets(); } @@ -6364,7 +7174,7 @@ sub make_new_child { &ReadManagerTable(); my $clientrec=defined(&Apache::lonnet::get_hosts_from_ip($outsideip)); my $ismanager=($managers{$outsideip} ne undef); - $clientname = "[unknonwn]"; + $clientname = "[unknown]"; if($clientrec) { # Establish client type. $ConnectionType = "client"; $clientname = (&Apache::lonnet::get_hosts_from_ip($outsideip))[-1]; @@ -6392,7 +7202,13 @@ sub make_new_child { # # If the remote is attempting a local init... give that a try: # - my ($i, $inittype) = split(/:/, $remotereq); + (my $i, my $inittype, $clientversion) = split(/:/, $remotereq); + # For LON-CAPA 2.9, the client session will have sent its LON-CAPA + # version when initiating the connection. For LON-CAPA 2.8 and older, + # the version is retrieved from the global %loncaparevs in lonnet.pm. + # $clientversion contains path to keyfile if $inittype eq 'local' + # it's overridden below in this case + $clientversion ||= $Apache::lonnet::loncaparevs{$clientname}; # If the connection type is ssl, but I didn't get my # certificate files yet, then I'll drop back to @@ -6412,6 +7228,7 @@ sub make_new_child { } if($inittype eq "local") { + $clientversion = $perlvar{'lonVersion'}; my $key = LocalConnection($client, $remotereq); if($key) { Debug("Got local key $key"); @@ -6419,7 +7236,7 @@ sub make_new_child { my $cipherkey = pack("H32", $key); $cipher = new IDEA($cipherkey); print $client "ok:local\n"; - &logthis('<font color="green"' + &logthis('<font color="green">' . "Successful local authentication </font>"); $keymode = "local" } else { @@ -6483,6 +7300,44 @@ sub make_new_child { # ------------------------------------------------------------ Process requests my $keep_going = 1; my $user_input; + my $clienthost = &Apache::lonnet::hostname($clientname); + my $clientserverhomeID = &Apache::lonnet::get_server_homeID($clienthost); + $clienthomedom = &Apache::lonnet::host_domain($clientserverhomeID); + $clientintdom = &Apache::lonnet::internet_dom($clientserverhomeID); + $clientsameinst = 0; + if ($clientintdom ne '') { + my $internet_names = &Apache::lonnet::get_internet_names($currenthostid); + if (ref($internet_names) eq 'ARRAY') { + if (grep(/^\Q$clientintdom\E$/,@{$internet_names})) { + $clientsameinst = 1; + } + } + } + $clientremoteok = 0; + unless ($clientsameinst) { + $clientremoteok = 1; + my $defdom = &Apache::lonnet::host_domain($perlvar{'lonHostID'}); + %clientprohibited = &get_prohibited($defdom); + if ($clientintdom) { + my $remsessconf = &get_usersession_config($defdom,'remotesession'); + if (ref($remsessconf) eq 'HASH') { + if (ref($remsessconf->{'remote'}) eq 'HASH') { + if (ref($remsessconf->{'remote'}->{'excludedomain'}) eq 'ARRAY') { + if (grep(/^\Q$clientintdom\E$/,@{$remsessconf->{'remote'}->{'excludedomain'}})) { + $clientremoteok = 0; + } + } + if (ref($remsessconf->{'remote'}->{'includedomain'}) eq 'ARRAY') { + if (grep(/^\Q$clientintdom\E$/,@{$remsessconf->{'remote'}->{'includedomain'}})) { + $clientremoteok = 1; + } else { + $clientremoteok = 0; + } + } + } + } + } + } while(($user_input = get_request) && $keep_going) { alarm(120); Debug("Main: Got $user_input\n"); @@ -6498,7 +7353,7 @@ sub make_new_child { &logthis("<font color='blue'>WARNING: " ."Rejected client $clientip, closing connection</font>"); } - } + } # ============================================================================= @@ -6532,22 +7387,29 @@ sub is_author { # Author role should show up as a key /domain/_au - my $key = "/$domain/_au"; my $value; - if (defined($hashref)) { - $value = $hashref->{$key}; - } + if ($hashref) { - if(defined($value)) { - &Debug("$user @ $domain is an author"); + my $key = "/$domain/_au"; + if (defined($hashref)) { + $value = $hashref->{$key}; + if(!untie_user_hash($hashref)) { + return 'error: ' . ($!+0)." untie (GDBM) Failed"; + } + } + + if(defined($value)) { + &Debug("$user @ $domain is an author"); + } + } else { + return 'error: '.($!+0)." tie (GDBM) Failed"; } 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 -# up a correct public_html +# an author role. If so, creates construction space # Parameters: # request - The request sent to the rolesput subchunk. # We're looking for /domain/_au @@ -6557,16 +7419,15 @@ sub is_author { # 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 =~ /^(\/\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"); + my $path=$perlvar{'lonDocRoot'}."/priv/$domain"; + unless (-e $path) { + mkdir($path); + } + unless (-e $path.'/'.$user) { + mkdir($path.'/'.$user); + } } } @@ -6641,9 +7502,7 @@ sub rewrite_password_file { # Returns the authorization type or nouser if there is no such user. # -sub get_auth_type -{ - +sub get_auth_type { my ($domain, $user) = @_; Debug("get_auth_type( $domain, $user ) \n"); @@ -6718,7 +7577,18 @@ sub validate_user { } if ($howpwd ne 'nouser') { if($howpwd eq "internal") { # Encrypted is in local password file. - $validated = (crypt($password, $contentpwd) eq $contentpwd); + if (length($contentpwd) == 13) { + $validated = (crypt($password,$contentpwd) eq $contentpwd); + if ($validated) { + my $ncpass = &hash_passwd($domain,$password); + if (&rewrite_password_file($domain,$user,"$howpwd:$ncpass")) { + &update_passwd_history($user,$domain,$howpwd,'conversion'); + &logthis("Validated password hashed with bcrypt for $user:$domain"); + } + } + } else { + $validated = &check_internal_passwd($password,$contentpwd,$domain); + } } elsif ($howpwd eq "unix") { # User is a normal unix user. $contentpwd = (getpwnam($user))[1]; @@ -6738,54 +7608,24 @@ sub validate_user { } else { $validated = 0; } - } - elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain. - if(! ($password =~ /$null/) ) { - my $k4error = &Authen::Krb4::get_pw_in_tkt($user, - "", - $contentpwd,, - 'krbtgt', - $contentpwd, - 1, - $password); - if(!$k4error) { - $validated = 1; - } else { - $validated = 0; - &logthis('krb4: '.$user.', '.$contentpwd.', '. - &Authen::Krb4::get_err_txt($Authen::Krb4::error)); - } - } else { - $validated = 0; # Password has a match with null. - } + } elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain. + my $checkwithkrb5 = 0; + if ($dist =~/^fedora(\d+)$/) { + if ($1 > 11) { + $checkwithkrb5 = 1; + } + } elsif ($dist =~ /^suse([\d.]+)$/) { + if ($1 > 11.1) { + $checkwithkrb5 = 1; + } + } + if ($checkwithkrb5) { + $validated = &krb5_authen($password,$null,$user,$contentpwd); + } else { + $validated = &krb4_authen($password,$null,$user,$contentpwd); + } } elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain. - if(!($password =~ /$null/)) { # Null password not allowed. - my $krbclient = &Authen::Krb5::parse_name($user.'@' - .$contentpwd); - my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd; - my $krbserver = &Authen::Krb5::parse_name($krbservice); - my $credentials= &Authen::Krb5::cc_default(); - $credentials->initialize(&Authen::Krb5::parse_name($user.'@' - .$contentpwd)); - my $krbreturn; - if (exists(&Authen::Krb5::get_init_creds_password)) { - $krbreturn = - &Authen::Krb5::get_init_creds_password($krbclient,$password, - $krbservice); - $validated = (ref($krbreturn) eq 'Authen::Krb5::Creds'); - } else { - $krbreturn = - &Authen::Krb5::get_in_tkt_with_password($krbclient,$krbserver, - $password,$credentials); - $validated = ($krbreturn == 1); - } - if (!$validated) { - &logthis('krb5: '.$user.', '.$contentpwd.', '. - &Authen::Krb5::error()); - } - } else { - $validated = 0; - } + $validated = &krb5_authen($password,$null,$user,$contentpwd); } elsif ($howpwd eq "localauth") { # Authenticate via installation specific authentcation method: $validated = &localauth::localauth($user, @@ -6816,6 +7656,98 @@ sub validate_user { return $validated; } +sub check_internal_passwd { + my ($plainpass,$stored,$domain) = @_; + my (undef,$method,@rest) = split(/!/,$stored); + if ($method eq "bcrypt") { + my $result = &hash_passwd($domain,$plainpass,@rest); + if ($result ne $stored) { + return 0; + } + # Upgrade to a larger number of rounds if necessary + my $defaultcost; + my %domconfig = + &Apache::lonnet::get_dom('configuration',['password'],$domain); + if (ref($domconfig{'password'}) eq 'HASH') { + $defaultcost = $domconfig{'password'}{'cost'}; + } + if (($defaultcost eq '') || ($defaultcost =~ /D/)) { + $defaultcost = 10; + } + return 1 unless($rest[0]<$defaultcost); + } + return 0; +} + +sub get_last_authchg { + my ($domain,$user) = @_; + my $lastmod; + my $logname = &propath($domain,$user).'/passwd.log'; + if (-e "$logname") { + $lastmod = (stat("$logname"))[9]; + } + return $lastmod; +} + +sub krb4_authen { + my ($password,$null,$user,$contentpwd) = @_; + my $validated = 0; + if (!($password =~ /$null/) ) { # Null password not allowed. + eval { + require Authen::Krb4; + }; + if (!$@) { + my $k4error = &Authen::Krb4::get_pw_in_tkt($user, + "", + $contentpwd,, + 'krbtgt', + $contentpwd, + 1, + $password); + if(!$k4error) { + $validated = 1; + } else { + $validated = 0; + &logthis('krb4: '.$user.', '.$contentpwd.', '. + &Authen::Krb4::get_err_txt($Authen::Krb4::error)); + } + } else { + $validated = krb5_authen($password,$null,$user,$contentpwd); + } + } + return $validated; +} + +sub krb5_authen { + my ($password,$null,$user,$contentpwd) = @_; + my $validated = 0; + if(!($password =~ /$null/)) { # Null password not allowed. + my $krbclient = &Authen::Krb5::parse_name($user.'@' + .$contentpwd); + my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd; + my $krbserver = &Authen::Krb5::parse_name($krbservice); + my $credentials= &Authen::Krb5::cc_default(); + $credentials->initialize(&Authen::Krb5::parse_name($user.'@' + .$contentpwd)); + my $krbreturn; + if (exists(&Authen::Krb5::get_init_creds_password)) { + $krbreturn = + &Authen::Krb5::get_init_creds_password($krbclient,$password, + $krbservice); + $validated = (ref($krbreturn) eq 'Authen::Krb5::Creds'); + } else { + $krbreturn = + &Authen::Krb5::get_in_tkt_with_password($krbclient,$krbserver, + $password,$credentials); + $validated = ($krbreturn == 1); + } + if (!$validated) { + &logthis('krb5: '.$user.', '.$contentpwd.', '. + &Authen::Krb5::error()); + } + } + return $validated; +} sub addline { my ($fname,$hostid,$ip,$newline)=@_; @@ -7028,7 +7960,9 @@ sub subscribe { # the metadata unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); } $fname=~s/\/home\/httpd\/html\/res/raw/; - $fname="http://".&Apache::lonnet::hostname($perlvar{'lonHostID'})."/".$fname; + my $protocol = $Apache::lonnet::protocol{$perlvar{'lonHostID'}}; + $protocol = 'http' if ($protocol ne 'https'); + $fname=$protocol.'://'.&Apache::lonnet::hostname($perlvar{'lonHostID'})."/".$fname; $result="$fname\n"; } } else { @@ -7070,26 +8004,26 @@ sub change_unix_password { sub make_passwd_file { - my ($uname, $umode,$npass,$passfilename)=@_; + my ($uname,$udom,$umode,$npass,$passfilename,$action)=@_; my $result="ok"; if ($umode eq 'krb4' or $umode eq 'krb5') { { my $pf = IO::File->new(">$passfilename"); if ($pf) { print $pf "$umode:$npass\n"; + &update_passwd_history($uname,$udom,$umode,$action); } else { $result = "pass_file_failed_error"; } } } elsif ($umode eq 'internal') { - my $salt=time; - $salt=substr($salt,6,2); - my $ncpass=crypt($npass,$salt); + my $ncpass = &hash_passwd($udom,$npass); { &Debug("Creating internal auth"); my $pf = IO::File->new(">$passfilename"); if($pf) { - print $pf "internal:$ncpass\n"; + print $pf "internal:$ncpass\n"; + &update_passwd_history($uname,$udom,$umode,$action); } else { $result = "pass_file_failed_error"; } @@ -7099,60 +8033,14 @@ sub make_passwd_file { my $pf = IO::File->new(">$passfilename"); if($pf) { print $pf "localauth:$npass\n"; + &update_passwd_history($uname,$udom,$umode,$action); } else { $result = "pass_file_failed_error"; } } } elsif ($umode eq 'unix') { - { - # - # Don't allow the creation of privileged accounts!!! that would - # be real bad!!! - # - my $uid = getpwnam($uname); - if((defined $uid) && ($uid == 0)) { - &logthis(">>>Attempted to create privilged account blocked"); - return "no_priv_account_error\n"; - } - - my $execpath ="$perlvar{'lonDaemons'}/"."lcuseradd"; - - my $lc_error_file = $execdir."/tmp/lcuseradd".$$.".status"; - { - &Debug("Executing external: ".$execpath); - &Debug("user = ".$uname.", Password =". $npass); - my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log"); - print $se "$uname\n"; - print $se "$npass\n"; - print $se "$npass\n"; - print $se "$lc_error_file\n"; # Status -> unique file. - } - 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"; - } else { - my $pf = IO::File->new(">$passfilename"); - if($pf) { - print $pf "unix:\n"; - } else { - $result = "pass_file_failed_error"; - } - } - } else { - &Debug("Could not locate lcuseradd error: $lc_error_file"); - $result="bug_lcuseradd_no_output_file"; - } - } + &logthis(">>>Attempt to create unix account blocked -- unix auth not available for new users."); + $result="no_new_unix_accounts"; } elsif ($umode eq 'none') { { my $pf = IO::File->new("> $passfilename"); @@ -7186,7 +8074,7 @@ sub sethost { eq &Apache::lonnet::get_host_ip($hostid)) { $currenthostid =$hostid; $currentdomainid=&Apache::lonnet::host_domain($hostid); - &logthis("Setting hostid to $hostid, and domain to $currentdomainid"); +# &logthis("Setting hostid to $hostid, and domain to $currentdomainid"); } else { &logthis("Requested host id $hostid not an alias of ". $perlvar{'lonHostID'}." refusing connection"); @@ -7201,6 +8089,57 @@ sub version { return "version:$VERSION"; } +sub get_usersession_config { + my ($dom,$name) = @_; + my ($usersessionconf,$cached)=&Apache::lonnet::is_cached_new($name,$dom); + if (defined($cached)) { + return $usersessionconf; + } else { + my %domconfig = &Apache::lonnet::get_dom('configuration',['usersessions'],$dom); + &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'usersessions'},3600); + return $domconfig{'usersessions'}; + } + return; +} + +sub get_prohibited { + my ($dom) = @_; + my $name = 'trust'; + my ($trustconfig,$cached)=&Apache::lonnet::is_cached_new($name,$dom); + unless (defined($cached)) { + my %domconfig = &Apache::lonnet::get_dom('configuration',['trust'],$dom); + &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'trust'},3600); + $trustconfig = $domconfig{'trust'}; + } + my %prohibited; + if (ref($trustconfig)) { + foreach my $prefix (keys(%{$trustconfig})) { + if (ref($trustconfig->{$prefix}) eq 'HASH') { + my $reject; + if (ref($trustconfig->{$prefix}->{'exc'}) eq 'ARRAY') { + if (grep(/^\Q$clientintdom\E$/,@{$trustconfig->{$prefix}->{'exc'}})) { + $reject = 1; + } + } + if (ref($trustconfig->{$prefix}->{'inc'}) eq 'ARRAY') { + if (grep(/^\Q$clientintdom\E$/,@{$trustconfig->{$prefix}->{'inc'}})) { + $reject = 0; + } else { + $reject = 1; + } + } + if ($reject) { + $prohibited{$prefix} = 1; + } + } + } + } + return %prohibited; +} + +sub distro_and_arch { + return $dist.':'.$arch; +} # ----------------------------------- POD (plain old documentation, CPAN style) @@ -7381,7 +8320,7 @@ Allow for a password to be set. Make a user. -=item passwd +=item changeuserauth Allow for authentication mechanism and password to be changed. @@ -7409,7 +8348,7 @@ Place in B<logs/lond.log> stores hash in namespace -=item rolesputy +=item rolesput put a role into a user's environment @@ -7470,6 +8409,10 @@ for each student, defined perhaps by the Returns usernames corresponding to IDs. (These "IDs" are unique identifiers for each student, defined perhaps by the institutional Registrar.) +=item iddel + +Deletes one or more ids in a domain's id database. + =item tmpput Accept and store information in temporary space. @@ -7526,6 +8469,8 @@ Authen::Krb5 =head1 COREQUISITES +none + =head1 OSNAMES linux @@ -7613,9 +8558,9 @@ or the CA's certificate in the call to l <error> is the textual reason this failed. Usual reasons: =over 2 - + =item Apache config file for loncapa incorrect: - + one of the variables lonCertificateDirectory, lonnetCertificateAuthority, or lonnetCertificate undefined or incorrect @@ -7734,7 +8679,7 @@ Could not rewrite the internal password file for a user =item Result of password change for <user> : <result> - + A unix password change for <user> was attempted and the pipe returned <result> @@ -7763,7 +8708,7 @@ lond has been asked to exit by its clien client systemand <input> is the full exit command sent to the server. =item Red CRITICAL: ABNORMAL EXIT. child <pid> for server <hostname> died through a crass with this error->[<message>]. - + A lond child terminated. NOte that this termination can also occur when the child receives the QUIT or DIE signals. <pid> is the process id of the child, <hostname> the host lond is working for, and <message> the reason the child died @@ -7847,7 +8792,7 @@ file when sent it's USR1 signal. That p assumed to be hung in some un-fixable way. =item Finished checking children - + Master processs's USR1 processing is cojmplete. =item (Red) CRITICAL: ------- Starting ------ @@ -7861,7 +8806,7 @@ Started a new child process for <client> connected to the child. This was as a result of a TCP/IP connection from a client. =item Unable to determine who caller was, getpeername returned nothing - + In child process initialization. either getpeername returned undef or a zero sized object was returned. Processing continues, but in my opinion, this should be cause for the child to exit. @@ -7872,7 +8817,7 @@ In child process initialization. The pe The client address is stored as "Unavailable" and processing continues. =item (Yellow) INFO: Connection <ip> <name> connection type = <type> - + In child initialization. A good connectionw as received from <ip>. =over 2 @@ -7922,7 +8867,7 @@ The client (<client> is the peer's name negotiated an SSL connection with this child process. =item (Green) Successful insecure authentication with <client> - + The client has successfully negotiated an insecure connection withthe child process. @@ -7936,5 +8881,7 @@ string. =back +=back + =cut