--- loncom/Lond.pm 2018/12/22 17:06:02 1.14 +++ loncom/Lond.pm 2022/02/14 02:48:49 1.19 @@ -1,6 +1,6 @@ # The LearningOnline Network # -# $Id: Lond.pm,v 1.14 2018/12/22 17:06:02 raeburn Exp $ +# $Id: Lond.pm,v 1.19 2022/02/14 02:48:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -37,8 +37,11 @@ use lib '/home/httpd/lib/perl/'; use LONCAPA; use Apache::lonnet; use GDBM_File; +use MIME::Base64; use Crypt::OpenSSL::X509; +use Crypt::X509::CRL; use Crypt::PKCS10; +use Net::OAuth; sub dump_with_regexp { my ( $tail, $clientversion ) = @_; @@ -240,10 +243,10 @@ sub check_homecourses { } } unless (&untie_domain_hash($hashref)) { - &logthis("Failed to untie tied hash for nohist_courseids.db for $domain"); + &Apache::lonnet::logthis("Failed to untie tied hash for nohist_courseids.db for $domain"); } } else { - &logthis("Failed to tie hash for nohist_courseids.db for $domain"); + &Apache::lonnet::logthis("Failed to tie hash for nohist_courseids.db for $domain"); } } foreach my $hashid (keys(%recent)) { @@ -315,9 +318,9 @@ sub get_courseinfo_hash { }; if ($@) { if ($@ eq "timeout\n") { - &logthis("WARNING courseiddump for $cnum:$cdom from $home timedout"); + &Apache::lonnet::logthis("WARNING courseiddump for $cnum:$cdom from $home timedout"); } else { - &logthis("WARNING unexpected error during eval of call for courseiddump from $home"); + &Apache::lonnet::logthis("WARNING unexpected error during eval of call for courseiddump from $home"); } } else { if (ref($info{$cdom.'_'.$cnum}) eq 'HASH') { @@ -803,10 +806,10 @@ sub is_course { } &Apache::lonnet::do_cache_new('iscourse',$hashid,$iscourse,3600); unless (&untie_domain_hash($hashref)) { - &logthis("Failed to untie tied hash for nohist_courseids.db for $cdom"); + &Apache::lonnet::logthis("Failed to untie tied hash for nohist_courseids.db for $cdom"); } } else { - &logthis("Failed to tie hash for nohist_courseids.db for $cdom"); + &Apache::lonnet::logthis("Failed to tie hash for nohist_courseids.db for $cdom"); } } return $iscourse; @@ -819,25 +822,77 @@ sub server_certs { host => 'lonnetCertificate', hostname => 'lonnetHostnameCertificate', ca => 'lonnetCertificateAuthority', + crl => 'lonnetCertRevocationList', ); - my (%md5hash,%expected_cn,%expired,%revoked,%wrongcn,%info,$crlfile); + my (%md5hash,%expected_cn,%expired,%revoked,%wrongcn,%info,$crlfile,$cafile, + %rvkcerts,$numrvk); %info = ( key => {}, ca => {}, host => {}, hostname => {}, - ); + crl => {}, + ); + my @ordered = ('crl','key','ca','host','hostname'); if (ref($perlvar) eq 'HASH') { $expected_cn{'host'} = $Apache::lonnet::serverhomeIDs{$hostname}; $expected_cn{'hostname'} = 'internal-'.$hostname; my $certsdir = $perlvar->{'lonCertificateDirectory'}; if (-d $certsdir) { - $crlfile = $certsdir.'/'.$perlvar->{'lonnetCertRevocationList'}; - foreach my $key (keys(%pemfiles)) { + $crlfile = $certsdir.'/'.$perlvar->{$pemfiles{'crl'}}; + $cafile = $certsdir.'/'.$perlvar->{$pemfiles{'ca'}}; + foreach my $key (@ordered) { if ($perlvar->{$pemfiles{$key}}) { my $file = $certsdir.'/'.$perlvar->{$pemfiles{$key}}; if (-e $file) { - if ($key eq 'key') { + if ($key eq 'crl') { + if ((-e $crlfile) && (-e $cafile)) { + if (open(PIPE,"openssl crl -in $crlfile -inform pem -CAfile $cafile -noout 2>&1 |")) { + my $crlstatus = ; + close(PIPE); + chomp($crlstatus); + if ($crlstatus =~ /OK/) { + $info{$key}{'status'} = 'ok'; + $info{$key}{'details'} = 'CRL valid for CA'; + } + } + } + if (open(my $fh,'<',$crlfile)) { + my $pem_crl = ''; + while (my $line=<$fh>) { + chomp($line); + next if ($line eq '-----BEGIN X509 CRL-----'); + next if ($line eq '-----END X509 CRL-----'); + $pem_crl .= $line; + } + close($fh); + my $der_crl = MIME::Base64::decode_base64($pem_crl); + if ($der_crl ne '') { + my $decoded = Crypt::X509::CRL->new( crl => $der_crl ); + if ($decoded->error) { + $info{$key}{'status'} = 'error'; + } elsif (ref($decoded)) { + $info{$key}{'start'} = $decoded->this_update; + $info{$key}{'end'} = $decoded->next_update; + $info{$key}{'alg'} = $decoded->SigEncAlg.' '.$decoded->SigHashAlg; + $info{$key}{'cn'} = $decoded->issuer_cn; + $info{$key}{'email'} = $decoded->issuer_email; + $info{$key}{'size'} = $decoded->signature_length; + my $rlref = $decoded->revocation_list; + if (ref($rlref) eq 'HASH') { + foreach my $key (keys(%{$rlref})) { + my $hkey = sprintf("%X",$key); + $rvkcerts{$hkey} = 1; + } + $numrvk = scalar(keys(%{$rlref})); + if ($numrvk) { + $info{$key}{'details'} .= " ($numrvk revoked)"; + } + } + } + } + } + } elsif ($key eq 'key') { if (open(PIPE,"openssl rsa -noout -in $file -check |")) { my $check = ; close(PIPE); @@ -882,7 +937,7 @@ sub server_certs { $info{$key}{'alg'} = $x509->sig_alg_name(); $info{$key}{'size'} = $x509->bit_length(); $info{$key}{'email'} = $x509->email(); - $info{$key}{'serial'} = $x509->serial(); + $info{$key}{'serial'} = uc($x509->serial()); $info{$key}{'issuerhash'} = $x509->issuer_hash(); if ($x509->checkend(0)) { $expired{$key} = 1; @@ -891,15 +946,9 @@ sub server_certs { if ($info{$key}{'cn'} ne $expected_cn{$key}) { $wrongcn{$key} = 1; } - if ((-e $crlfile) && ($info{$key}{'serial'} =~ /^\w+$/)) { - my $serial = $info{$key}{'serial'}; - if (open(PIPE,"openssl crl -inform PEM -text -in $crlfile | grep $serial |")) { - my $result = ; - close(PIPE); - chomp($result); - if ($result ne '') { - $revoked{$key} = 1; - } + if (($numrvk) && ($info{$key}{'serial'})) { + if ($rvkcerts{$info{$key}{'serial'}}) { + $revoked{$key} = 1; } } } @@ -976,6 +1025,228 @@ sub server_certs { return $result; } +sub get_dom { + my ($userinput) = @_; + my ($cmd,$udom,$namespace,$what) =split(/:/,$userinput,4); + my $hashref = &tie_domain_hash($udom,$namespace,&GDBM_READER()) or + return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd"; + my $qresult=''; + if (ref($hashref)) { + chomp($what); + my @queries=split(/\&/,$what); + for (my $i=0;$i<=$#queries;$i++) { + $qresult.="$hashref->{$queries[$i]}&"; + } + $qresult=~s/\&$//; + } + &untie_user_hash($hashref) or + return "error: ".($!+0)." untie(GDBM) Failed while attempting $cmd"; + return $qresult; +} + +sub store_dom { + my ($userinput) = @_; + my ($cmd,$dom,$namespace,$rid,$what) =split(/:/,$userinput); + my $hashref = &tie_domain_hash($dom,$namespace,&GDBM_WRCREAT(),"S","$rid:$what") or + return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd"; + $hashref->{"version:$rid"}++; + my $version=$hashref->{"version:$rid"}; + my $allkeys=''; + my @pairs=split(/\&/,$what); + foreach my $pair (@pairs) { + my ($key,$value)=split(/=/,$pair); + $allkeys.=$key.':'; + $hashref->{"$version:$rid:$key"}=$value; + } + my $now = time; + $hashref->{"$version:$rid:timestamp"}=$now; + $allkeys.='timestamp'; + $hashref->{"$version:keys:$rid"}=$allkeys; + &untie_user_hash($hashref) or + return "error: ".($!+0)." untie(GDBM) Failed while attempting $cmd"; + return 'ok'; +} + +sub restore_dom { + my ($userinput) = @_; + my ($cmd,$dom,$namespace,$rid) = split(/:/,$userinput); + my $hashref = &tie_domain_hash($dom,$namespace,&GDBM_READER()) or + return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd"; + my $qresult=''; + if (ref($hashref)) { + chomp($rid); + my $version=$hashref->{"version:$rid"}; + $qresult.="version=$version&"; + my $scope; + for ($scope=1;$scope<=$version;$scope++) { + my $vkeys=$hashref->{"$scope:keys:$rid"}; + my @keys=split(/:/,$vkeys); + my $key; + $qresult.="$scope:keys=$vkeys&"; + foreach $key (@keys) { + $qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&"; + } + } + $qresult=~s/\&$//; + } + &untie_user_hash($hashref) or + return "error: ".($!+0)." untie(GDBM) Failed while attempting $cmd"; + return $qresult; +} + +sub crslti_itemid { + my ($cdom,$cnum,$url,$method,$params,$loncaparev) = @_; + unless (ref($params) eq 'HASH') { + return; + } + if (($cdom eq '') || ($cnum eq '')) { + return; + } + my ($itemid,$consumer_key,$secret); + + if (exists($params->{'oauth_callback'})) { + $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A; + } else { + $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0; + } + + my $consumer_key = $params->{'oauth_consumer_key'}; + return if ($consumer_key eq ''); + + my (%crslti,%crslti_by_key); + my $hashid=$cdom.'_'.$cnum; + my ($result,$cached)=&Apache::lonnet::is_cached_new('courseltienc',$hashid); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + %crslti = %{$result}; + } + } else { + my $reply = &dump_with_regexp(join(":",($cdom,$cnum,'nohist_ltienc','','')),$loncaparev); + %crslti = %{&Apache::lonnet::unserialize($reply)}; + my $cachetime = 24*60*60; + &Apache::lonnet::do_cache_new('courseltienc',$hashid,\%crslti,$cachetime); + } + + return if (!keys(%crslti)); + + foreach my $id (keys(%crslti)) { + if (ref($crslti{$id}) eq 'HASH') { + my $key = $crslti{$id}{'key'}; + if (($key ne '') && ($crslti{$id}{'secret'} ne '')) { + push(@{$crslti_by_key{$key}},$id); + } + } + } + + return if (!keys(%crslti_by_key)); + + if (ref($crslti_by_key{$consumer_key}) eq 'ARRAY') { + foreach my $id (@{$crslti_by_key{$consumer_key}}) { + my $secret = $crslti{$id}{'secret'}; + my $request = Net::OAuth->request('request token')->from_hash($params, + request_url => $url, + request_method => $method, + consumer_secret => $secret,); + if ($request->verify()) { + $itemid = $id; + last; + } + } + } + return $itemid; +} + +sub domlti_itemid { + my ($dom,$context,$url,$method,$params,$loncaparev) = @_; + unless (ref($params) eq 'HASH') { + return; + } + if ($dom eq '') { + return; + } + my ($itemid,$consumer_key,$secret); + + if (exists($params->{'oauth_callback'})) { + $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A; + } else { + $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0; + } + + my $consumer_key = $params->{'oauth_consumer_key'}; + return if ($consumer_key eq ''); + + my %ltienc; + my ($encresult,$enccached)=&Apache::lonnet::is_cached_new('ltienc',$dom); + if (defined($enccached)) { + if (ref($encresult) eq 'HASH') { + %ltienc = %{$encresult}; + } + } else { + my $reply = &get_dom("getdom:$dom:encconfig:lti"); + my $ltiencref = &Apache::lonnet::thaw_unescape($reply); + if (ref($ltiencref) eq 'HASH') { + %ltienc = %{$ltiencref}; + } + my $cachetime = 24*60*60; + &Apache::lonnet::do_cache_new('ltienc',$dom,\%ltienc,$cachetime); + } + + return if (!keys(%ltienc)); + + my %lti; + if ($context eq 'deeplink') { + my ($result,$cached)=&Apache::lonnet::is_cached_new('lti',$dom); + if (defined($cached)) { + if (ref($result) eq 'HASH') { + %lti = %{$result}; + } + } else { + my $reply = &get_dom("getdom:$dom:configuration:lti"); + my $ltiref = &Apache::lonnet::thaw_unescape($reply); + if (ref($ltiref) eq 'HASH') { + %lti = %{$ltiref}; + } + my $cachetime = 24*60*60; + &Apache::lonnet::do_cache_new('lti',$dom,\%lti,$cachetime); + } + } + return if (!keys(%lti)); + + my %lti_by_key; + foreach my $id (keys(%ltienc)) { + if (ref($ltienc{$id}) eq 'HASH') { + my $key = $ltienc{$id}{'key'}; + if (($key ne '') && ($ltienc{$id}{'secret'} ne '')) { + if ($context eq 'deeplink') { + if (ref($lti{$id}) eq 'HASH') { + if (!$lti{$id}{'requser'}) { + push(@{$lti_by_key{$key}},$id); + } + } + } else { + push(@{$lti_by_key{$key}},$id); + } + } + } + } + return if (!keys(%lti_by_key)); + + if (ref($lti_by_key{$consumer_key}) eq 'ARRAY') { + foreach my $id (@{$lti_by_key{$consumer_key}}) { + my $secret = $ltienc{$id}{'secret'}; + my $request = Net::OAuth->request('request token')->from_hash($params, + request_url => $url, + request_method => $method, + consumer_secret => $secret,); + if ($request->verify()) { + $itemid = $id; + last; + } + } + } + return $itemid; +} + 1; __END__ @@ -1098,7 +1369,18 @@ courseID -- for the course for which the The contents of the inner hash, for that single item in the outer hash are returned (and cached in memcache for 10 minutes). +=item get_dom ( $userinput ) +get_dom() will retrieve domain configuration information from a GDBM file +in /home/httpd/lonUsers/$dom on the primary library server in a domain. +The single argument passed is the string: $cmd:$udom:$namespace:$what +where $cmd is the command historically passed to lond - i.e., getdom +or egetdom, $udom is the domain, $namespace is the name of the GDBM file +(encconfig or configuration), and $what is a string containing names of +items to retrieve from the db file (each item name is escaped and separated +from the next item name with an ampersand). The return value is either: +error: followed by an error message, or a string containing the value (escaped) +for each item, again separated from the next item with an ampersand. =back