--- loncom/lond 2021/06/20 16:24:09 1.489.2.41 +++ loncom/lond 2023/07/09 00:46:44 1.489.2.43.2.8 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.489.2.41 2021/06/20 16:24:09 raeburn Exp $ +# $Id: lond,v 1.489.2.43.2.8 2023/07/09 00:46:44 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -63,7 +63,7 @@ my $DEBUG = 0; # Non zero to ena my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.489.2.41 $'; #' stupid emacs +my $VERSION='$Revision: 1.489.2.43.2.8 $'; #' stupid emacs my $remoteVERSION; my $currenthostid="default"; my $currentdomainid; @@ -653,7 +653,7 @@ sub PushFile { if($filename eq "host") { $contents = AdjustHostContents($contents); - } elsif ($filename eq 'dns_host' || $filename eq 'dns_domain') { + } elsif (($filename eq 'dns_hosts') || ($filename eq 'dns_domain')) { if ($contents eq '') { &logthis(' Pushfile: unable to install ' .$tablefile." - no data received from push. "); @@ -1795,7 +1795,7 @@ sub read_lonnet_global { } if ($what eq 'perlvar') { if (!exists($packagevars{$what}{'lonBalancer'})) { - if ($dist =~ /^(centos|rhes|fedora|scientific|oracle)/) { + if ($dist =~ /^(centos|rhes|fedora|scientific|oracle|rocky|alma)/) { my $othervarref=LONCAPA::Configuration::read_conf('httpd.conf'); if (ref($othervarref) eq 'HASH') { $items->{'lonBalancer'} = $othervarref->{'lonBalancer'}; @@ -2407,6 +2407,36 @@ sub update_passwd_history { return; } +sub inst_unamemap_check { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my %rulecheck; + my $outcome; + my ($udom,$uname,@rules) = split(/:/,$tail); + $udom = &unescape($udom); + $uname = &unescape($uname); + @rules = map {&unescape($_);} (@rules); + eval { + local($SIG{__DIE__})='DEFAULT'; + $outcome = &localenroll::unamemap_check($udom,$uname,\@rules,\%rulecheck); + }; + if (!$@) { + if ($outcome eq 'ok') { + my $result=''; + foreach my $key (keys(%rulecheck)) { + $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&'; + } + &Reply($client,\$result,$userinput); + } else { + &Reply($client,"error\n", $userinput); + } + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } +} +®ister_handler("instunamemapcheck",\&inst_unamemap_check,0,1,0); + + # # 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 @@ -2804,6 +2834,54 @@ sub user_has_session_handler { } ®ister_handler("userhassession", \&user_has_session_handler, 0,1,0); +sub del_usersession_handler { + my ($cmd, $tail, $client) = @_; + + my $result; + my ($udom, $uname) = map { &unescape($_) } (split(/:/, $tail)); + if (($udom =~ /^$LONCAPA::match_domain$/) && ($uname =~ /^$LONCAPA::match_username$/)) { + my $lonidsdir = $perlvar{'lonIDsDir'}; + if (-d $lonidsdir) { + if (opendir(DIR,$lonidsdir)) { + my $filename; + while ($filename=readdir(DIR)) { + if ($filename=~/^\Q$uname\E_\d+_\Q$udom\E_/) { + if (tie(my %oldenv,'GDBM_File',"$lonidsdir/$filename", + &GDBM_READER(),0640)) { + my $linkedfile; + if (exists($oldenv{'user.linkedenv'})) { + $linkedfile = $oldenv{'user.linkedenv'}; + } + untie(%oldenv); + $result = unlink("$lonidsdir/$filename"); + if ($result) { + if ($linkedfile =~ /^[a-f0-9]+_linked$/) { + if (-l "$lonidsdir/$linkedfile.id") { + unlink("$lonidsdir/$linkedfile.id"); + } + } + } + } else { + $result = unlink("$lonidsdir/$filename"); + } + last; + } + } + } + } + if ($result == 1) { + &Reply($client, "$result\n", "$cmd:$tail"); + } else { + &Reply($client, "not_found\n", "$cmd:$tail"); + } + } else { + &Failure($client, "invalid_user\n", "$cmd:$tail"); + } + return 1; +} + +®ister_handler("delusersession", \&del_usersession_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 @@ -3536,6 +3614,47 @@ sub dump_with_regexp { } ®ister_handler("dump", \&dump_with_regexp, 0, 1, 0); +# +# Process the encrypted dump request. Original call should +# be from lonnet::dump() with seventh arg ($encrypt) set to +# 1, to ensure that both request and response are encrypted. +# +# Parameters: +# $cmd - Command keyword of request (edump). +# $tail - Tail of the command. +# See &dump_with_regexp for more +# information about this. +# $client - File open on the client. +# Returns: +# 1 - Continue processing +# 0 - server should exit. +# + +sub encrypted_dump_with_regexp { + my ($cmd, $tail, $client) = @_; + my $res = LONCAPA::Lond::dump_with_regexp($tail, $clientversion); + + if ($res =~ /^error:/) { + Failure($client, \$res, "$cmd:$tail"); + } else { + if ($cipher) { + my $cmdlength=length($res); + $res.=" "; + my $encres=''; + for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encres.= unpack("H16", + $cipher->encrypt(substr($res, + $encidx, + 8))); + } + &Reply( $client,"enc:$cmdlength:$encres\n","$cmd:$tail"); + } else { + &Failure( $client, "error:no_key\n","$cmd:$tail"); + } + } +} +®ister_handler("edump", \&encrypted_dump_with_regexp, 0, 1, 0); + # Store a set of key=value pairs associated with a versioned name. # # Parameters: @@ -4682,16 +4801,193 @@ sub get_domain_handler { my $userinput = "$cmd:$tail"; my ($udom,$namespace,$what)=split(/:/,$tail,3); - my $res = LONCAPA::Lond::get_dom($userinput); + if (($namespace =~ /^enc/) || ($namespace eq 'private')) { + &Failure( $client, "refused\n", $userinput); + } else { + my $res = LONCAPA::Lond::get_dom($userinput); + if ($res =~ /^error:/) { + &Failure($client, \$res, $userinput); + } else { + &Reply($client, \$res, $userinput); + } + } + + return 1; +} +®ister_handler("getdom", \&get_domain_handler, 0, 1, 0); + +sub encrypted_get_domain_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($udom,$namespace,$what) = split(/:/,$tail,3); + if ($namespace eq 'private') { + &Failure( $client, "refused\n", $userinput); + } else { + my $res = LONCAPA::Lond::get_dom($userinput); + if ($res =~ /^error:/) { + &Failure($client, \$res, $userinput); + } else { + if ($cipher) { + my $cmdlength=length($res); + $res.=" "; + my $encres=''; + for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encres.= unpack("H16", + $cipher->encrypt(substr($res, + $encidx, + 8))); + } + &Reply( $client,"enc:$cmdlength:$encres\n",$userinput); + } else { + &Failure( $client, "error:no_key\n",$userinput); + } + } + } + return 1; +} +®ister_handler("egetdom", \&encrypted_get_domain_handler, 1, 1, 0); + +# +# Encrypted get from the namespace database file at the domain level. +# This function retrieves a keyed item from a specific named database in the +# domain directory. +# +# Parameters: +# $cmd - Command request keyword (lti). +# $tail - Tail of the command. This is a colon-separated list +# consisting of the domain, coursenum, if for LTI- +# enabled deep-linking to course content using +# link protection configured within a course, +# context (=deeplink) if for LTI-enabled deep-linking +# to course content using LTI Provider settings +# configured within a course's domain, the (escaped) +# launch URL, the (escaped) method (typically POST), +# and a frozen hash of the LTI launch parameters +# from the LTI payload. +# $client - File descriptor open on the client. +# Returns: +# 1 - Continue processing. +# 0 - Exit. +# Side effects: +# The reply will contain an LTI itemID, if the signed LTI payload +# could be verified using the consumer key and the shared secret +# available for that key (for the itemID) for either the course or domain, +# depending on values for cnum and context. The reply is encrypted before +# being written to $client. +# +sub lti_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($cdom,$cnum,$context,$escurl,$escmethod,$items) = split(/:/,$tail); + my $url = &unescape($escurl); + my $method = &unescape($escmethod); + my $params = &Apache::lonnet::thaw_unescape($items); + my $res; + if ($cnum ne '') { + $res = &LONCAPA::Lond::crslti_itemid($cdom,$cnum,$url,$method,$params,$perlvar{'lonVersion'}); + } else { + $res = &LONCAPA::Lond::domlti_itemid($cdom,$context,$url,$method,$params,$perlvar{'lonVersion'}); + } if ($res =~ /^error:/) { &Failure($client, \$res, $userinput); } else { - &Reply($client, \$res, $userinput); + if ($cipher) { + my $cmdlength=length($res); + $res.=" "; + my $encres=''; + for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encres.= unpack("H16", + $cipher->encrypt(substr($res, + $encidx, + 8))); + } + &Reply( $client,"enc:$cmdlength:$encres\n",$userinput); + } else { + &Failure( $client, "error:no_key\n",$userinput); + } } + return 1; +} +®ister_handler("lti", \<i_handler, 1, 1, 0); +# +# Data for LTI payload (received encrypted) are unencrypted and +# then signed with the appropriate key and secret, before re-encrypting +# the signed payload which is sent to the client for unencryption by +# the caller: lonnet::sign_lti()) before dispatch either to a web browser +# (launch) or to a remote web service (roster, logout, or grade). +# +# Parameters: +# $cmd - Command request keyword (signlti). +# $tail - Tail of the command. This is a colon-separated list +# consisting of the domain, coursenum (if for an External +# Tool defined in a course), crsdef (true if defined in +# a course), type (linkprot or lti), +# context (launch, roster, logout, or grade), +# escaped launch URL, numeric ID of external tool, +# version number for encryption key (if tool's LTI secret was +# encrypted before storing), a frozen hash of LTI launch +# parameters, and a frozen hash of LTI information, +# (e.g., method => 'HMAC-SHA1', +# respfmt => 'to_authorization_header'). +# $client - File descriptor open on the client. +# Returns: +# 1 - Continue processing. +# 0 - Exit. +# Side effects: +# The reply will contain the LTI payload, as & separated key=value pairs, +# where value is itself a frozen hash, if the required key and secret +# for the apecific tool ID are available. The payload data are retrieved from +# a call to Lond::sign_lti_payload(), and the reply is encrypted before being +# written to $client. +# +sub sign_lti_handler { + my ($cmd, $tail, $client) = @_; + + my $userinput = "$cmd:$tail"; + + my ($cdom,$cnum,$crsdef,$type,$context,$escurl, + $ltinum,$keynum,$paramsref,$inforef) = split(/:/,$tail); + my $url = &unescape($escurl); + my $params = &Apache::lonnet::thaw_unescape($paramsref); + my $info = &Apache::lonnet::thaw_unescape($inforef); + my $res = + &LONCAPA::Lond::sign_lti_payload($cdom,$cnum,$crsdef,$type,$context,$url,$ltinum, + $keynum,$perlvar{'lonVersion'},$params,$info); + my $result; + if (ref($res) eq 'HASH') { + foreach my $key (keys(%{$res})) { + $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($res->{$key}).'&'; + } + $result =~ s/\&$//; + } else { + $result = $res; + } + if ($result =~ /^error:/) { + &Failure($client, \$result, $userinput); + } else { + if ($cipher) { + my $cmdlength=length($result); + $result.=" "; + my $encres=''; + for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { + $encres.= unpack("H16", + $cipher->encrypt(substr($result, + $encidx, + 8))); + } + &Reply( $client,"enc:$cmdlength:$encres\n",$userinput); + } else { + &Failure( $client, "error:no_key\n",$userinput); + } + } return 1; } -®ister_handler("getdom", \&get_domain_handler, 0, 1, 0); +®ister_handler("signlti", \&sign_lti_handler, 1, 1, 0); # # Puts an id to a domains id database. @@ -5118,15 +5414,23 @@ sub tmp_put_handler { } my ($id,$store); $tmpsnum++; - if (($context eq 'resetpw') || ($context eq 'createaccount')) { - $id = &md5_hex(&md5_hex(time.{}.rand().$$)); + my $numtries = 0; + my $execdir=$perlvar{'lonDaemons'}; + if (($context eq 'resetpw') || ($context eq 'createaccount') || + ($context eq 'sso') || ($context eq 'link') || ($context eq 'retry')) { + $id = &md5_hex(&md5_hex(time.{}.rand().$$.$tmpsnum)); + while ((-e "$execdir/tmp/$id.tmp") && ($numtries <10)) { + undef($id); + $id = &md5_hex(&md5_hex(time.{}.rand().$$.$tmpsnum)); + $numtries ++; + } } else { $id = $$.'_'.$clientip.'_'.$tmpsnum; } $id=~s/\W/\_/g; $record=~s/\n//g; - my $execdir=$perlvar{'lonDaemons'}; - if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) { + if (($id ne '') && + ($store=IO::File->new(">$execdir/tmp/$id.tmp"))) { print $store $record; close $store; &Reply($client, \$id, $userinput); @@ -6217,6 +6521,39 @@ sub get_institutional_selfcreate_rules { } ®ister_handler("instemailrules",\&get_institutional_selfcreate_rules,0,1,0); +sub get_unamemap_rules { + my ($cmd, $tail, $client) = @_; + my $userinput = "$cmd:$tail"; + my $dom = &unescape($tail); + my (%rules_hash,@rules_order); + my $outcome; + eval { + local($SIG{__DIE__})='DEFAULT'; + $outcome = &localenroll::unamemap_rules($dom,\%rules_hash,\@rules_order); + }; + if (!$@) { + if ($outcome eq 'ok') { + my $result; + foreach my $key (keys(%rules_hash)) { + $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&'; + } + $result =~ s/\&$//; + $result .= ':'; + if (@rules_order > 0) { + foreach my $item (@rules_order) { + $result .= &escape($item).'&'; + } + } + $result =~ s/\&$//; + &Reply($client,\$result,$userinput); + } else { + &Reply($client,"error\n", $userinput); + } + } else { + &Failure($client,"unknown_cmd\n",$userinput); + } +} +®ister_handler("unamemaprules",\&get_unamemap_rules,0,1,0); sub institutional_username_check { my ($cmd, $tail, $client) = @_; @@ -7192,7 +7529,7 @@ sub make_new_child { &Authen::Krb5::init_context(); my $no_ets; - if ($dist =~ /^(?:centos|rhes|scientific|oracle)(\d+)$/) { + if ($dist =~ /^(?:centos|rhes|scientific|oracle|rocky|alma)(\d+)/) { if ($1 >= 7) { $no_ets = 1; } @@ -7358,7 +7695,7 @@ sub make_new_child { Debug("Main: Got $user_input\n"); $keep_going = &process_request($user_input); alarm(0); - &status('Listening to '.$clientname." ($keymode)"); + &status('Listening to '.$clientname." ($keymode)"); } # --------------------------------------------- client unknown or fishy, refuse @@ -7374,8 +7711,8 @@ sub make_new_child { &logthis("CRITICAL: " ."Disconnect from $clientip ($clientname)"); - - + + # this exit is VERY important, otherwise the child will become # a producer of more and more children, forking yourself into # process death. @@ -7595,8 +7932,15 @@ sub validate_user { } elsif ((($domdefaults{'auth_def'} eq 'krb4') || ($domdefaults{'auth_def'} eq 'krb5')) && ($domdefaults{'auth_arg_def'} ne '')) { - $howpwd = $domdefaults{'auth_def'}; - $contentpwd = $domdefaults{'auth_arg_def'}; + # + # Don't attempt authentication for username and password supplied + # for user without an account if username contains @ to avoid + # call to &Authen::Krb5::parse_name() which will result in con_lost + # + unless ($user =~ /\@/) { + $howpwd = $domdefaults{'auth_def'}; + $contentpwd = $domdefaults{'auth_arg_def'}; + } } } }