Diff for /loncom/Lond.pm between versions 1.8.2.3.2.5 and 1.23

version 1.8.2.3.2.5, 2024/08/18 01:37:26 version 1.23, 2023/06/02 01:20:28
Line 37  use lib '/home/httpd/lib/perl/'; Line 37  use lib '/home/httpd/lib/perl/';
 use LONCAPA;  use LONCAPA;
 use Apache::lonnet;  use Apache::lonnet;
 use GDBM_File;  use GDBM_File;
   use MIME::Base64;
   use Crypt::OpenSSL::X509;
   use Crypt::X509::CRL;
   use Crypt::PKCS10;
 use Net::OAuth;  use Net::OAuth;
 use Crypt::CBC;  use Crypt::CBC;
   use Net::OAuth;
 use Digest::SHA;  use Digest::SHA;
 use Digest::MD5 qw(md5_hex);  use Digest::MD5 qw(md5_hex);
   
Line 61  sub dump_with_regexp { Line 66  sub dump_with_regexp {
         }          }
     }      }
   
 #  
 # If dump is for file_permissions.db from a pre-2.12 server and  
 # $uname:$udom is not a course, determine if value of portaccess  
 # in effect for $uname:$udom allows portfolio files to be shared.  
 # If sharing is not allowed, records returned for accesscontrol  
 # are restricted to those based on ip (i.e., for externalresponse).  
 #  
 # Note: for 2.12 or later session-hosting server, determination  
 # of portaccess value in effect occurs client-side.  
 #  
     my ($check_portaccess,$access,$now,$major,$minor,%by_ip);  
     if ($namespace eq 'file_permissions') {  
         if ($clientversion =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?/) {  
             $major = $1;  
             $minor = $2;  
         }  
         unless ((($major > 2) || (($major == 2) && ($minor > 11))) ||  
                 &is_course($udom,$uname)) {  
             $check_portaccess = 1;  
             $access = &portfolio_is_shareable($udom,$uname);  
         }  
         $now = time;  
     }  
   
     my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()) or       my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()) or 
         return "error: ".($!+0)." tie(GDBM) Failed while attempting dump";          return "error: ".($!+0)." tie(GDBM) Failed while attempting dump";
   
Line 98  sub dump_with_regexp { Line 79  sub dump_with_regexp {
 #   # 
     my $skipcheck;      my $skipcheck;
     my @ids = &Apache::lonnet::current_machine_ids();      my @ids = &Apache::lonnet::current_machine_ids();
     my %homecourses;      my (%homecourses, $major, $minor, $now);
 #   # 
 # If dump is for roles.db from a pre-2.10 server, determine the LON-CAPA     # If dump is for roles.db from a pre-2.10 server, determine the LON-CAPA   
 # version on the server which requested the data.   # version on the server which requested the data. 
Line 136  sub dump_with_regexp { Line 117  sub dump_with_regexp {
                     }                      }
                 }                  }
             }              }
             if ($namespace eq 'file_permissions') {  
                 if ($check_portaccess) {  
                     unless ($access) {  
                         my $unesckey = &unescape($key);  
                         if ($unesckey =~ m{\0((\d+)_\d+_\d+:([a-z]+)_(\d+)_(\d+))$}) {  
                             my ($acl,$timestamp,$scope,$end,$start) = ($1,$2,$3,$4);  
                             if ($scope eq 'ip') {  
                                 unless (($start > $now) &&  
                                         ($end && $end<$now)) {  
                                     my ($path) = split(/\0/,$unesckey);  
                                     push(@{$by_ip{$path}},{$acl => $timestamp});  
                                 }  
                             }  
                             next;  
                         } elsif ($unesckey =~ m{\0accesscontrol$}) {  
                             next;  
                         }  
                     }  
                 }  
             }  
         if ($regexp eq '.') {          if ($regexp eq '.') {
             $count++;              $count++;
             if (defined($range) && $count >= $end)   { last; }              if (defined($range) && $count >= $end)   { last; }
Line 171  sub dump_with_regexp { Line 132  sub dump_with_regexp {
             }              }
         }          }
     }      }
     if (($namespace eq 'file_permissions') && ($check_portaccess) && (!$access)) {  
         if (keys(%by_ip)) {  
             my %accesscontrol;  
             foreach my $path (keys(%by_ip)) {  
                 if (ref($by_ip{$path}) eq 'ARRAY') {  
                     foreach my $item (@{$by_ip{$path}}) {  
                         if (ref($item) eq 'HASH') {  
                             my ($acl,$timestamp) = each(%$item);  
                             my $key = &escape("$path\0$acl");  
                             my $value = $hashref->{$key};  
                             $qresult.= "$key=$value&";  
                             $accesscontrol{"$path\0accesscontrol"}{$acl} = $timestamp;  
                         }  
                     }  
                 }  
             }  
             if (keys(%accesscontrol)) {  
                 while (my ($key,$value) = each(%accesscontrol)) {  
                     $qresult.= &escape($key).'='.&Apache::lonnet::freeze_escape($value).'&';  
                 }  
             }  
         }  
     }  
     &untie_user_hash($hashref) or       &untie_user_hash($hashref) or 
         return "error: ".($!+0)." untie(GDBM) Failed while attempting dump";          return "error: ".($!+0)." untie(GDBM) Failed while attempting dump";
 #  #
Line 396  sub get_courseinfo_hash { Line 335  sub get_courseinfo_hash {
     return;      return;
 }  }
   
 sub portfolio_is_shareable {  
     my ($udom,$uname) = @_;  
     my $check_portaccess = 1;  
     my ($userportaccess,$inststatus,$access);  
     my $hashref = &tie_user_hash($udom, $uname, 'environment', &GDBM_READER());  
     if (ref($hashref) eq 'HASH') {  
         my $accesskey = &escape('tools.portaccess');  
         $userportaccess = $hashref->{$accesskey};  
         $inststatus = $hashref->{'inststatus'};  
         &untie_user_hash($hashref);  
     }  
     if ($userportaccess ne '') {  
         $access = $userportaccess;  
     } else {  
         my %domdefs = &Apache::lonnet::get_domain_defaults($udom);  
         if (ref($domdefs{'portaccess'}) eq 'HASH') {  
             if (($domdefs{'portaccess'}{'_LC_adv'} ne '') &&  
                 (&Apache::lonnet::is_advanced_user($udom,$uname))) {  
                 if ($domdefs{'portaccess'}{'_LC_adv'}) {  
                     $access = 1;  
                 } else {  
                     $access = 0;  
                 }  
             } elsif ($inststatus ne '') {  
                 my ($hasaccess,$hasnoaccess);  
                 foreach my $affiliation (split(/:/,$inststatus)) {  
                     if ($domdefs{'portaccess'}{$affiliation} ne '') {  
                         if ($domdefs{'portaccess'}{$affiliation}) {  
                             $hasaccess = 1;  
                         } else {  
                             $hasnoaccess = 1;  
                         }  
                     }  
                 }  
                 if ($hasaccess || $hasnoaccess) {  
                     if ($hasaccess) {  
                         $access = 1;  
                     } elsif ($hasnoaccess) {  
                         $access = 0;  
                     }  
                 }  
             } else {  
                 if ($domdefs{'portaccess'}{'default'} ne '') {  
                     if ($domdefs{'portaccess'}{'default'}) {  
                         $access = 1;  
                     } elsif ($domdefs{'portaccess'}{'default'} == 0) {  
                         $access = 0;  
                     }  
                 }  
             }  
         } else {  
             $access = 1;  
         }  
     }  
     return $access;  
 }  
   
 sub dump_course_id_handler {  sub dump_course_id_handler {
     my ($tail) = @_;      my ($tail) = @_;
   
Line 937  sub is_course { Line 819  sub is_course {
     return $iscourse;      return $iscourse;
 }  }
   
   sub server_certs {
       my ($perlvar,$lonhost,$hostname) = @_;
       my %pemfiles = (
                        key      => 'lonnetPrivateKey',
                        host     => 'lonnetCertificate',
                        hostname => 'lonnetHostnameCertificate',
                        ca       => 'lonnetCertificateAuthority',
                        crl      => 'lonnetCertRevocationList',
                      );
       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->{$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 'crl') {
                                if ((-e $crlfile) && (-e $cafile)) {
                                    if (open(PIPE,"openssl crl -in $crlfile -inform pem -CAfile $cafile -noout 2>&1 |")) {
                                        my $crlstatus = <PIPE>;
                                        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 = <PIPE>;
                                   close(PIPE);
                                   chomp($check);
                                   $info{$key}{'status'} = $check;
                               }
                               if (open(PIPE,"openssl rsa -noout -modulus -in $file | openssl md5 |")) {
                                   $md5hash{$key} = <PIPE>;
                                   close(PIPE);
                                   chomp($md5hash{$key});
                               }
                           } else {
                               if ($key eq 'ca') {
                                   if (open(PIPE,"openssl verify -CAfile $file $file |")) {
                                       my $check = <PIPE>;
                                       close(PIPE);
                                       chomp($check);
                                       if ($check eq "$file: OK") {
                                           $info{$key}{'status'} = 'ok';
                                       } else {
                                           $check =~ s/^\Q$file\E\:?\s*//;
                                           $info{$key}{'status'} = $check;
                                       }
                                   }
                               } else {
                                   if (open(PIPE,"openssl x509 -noout -modulus -in $file | openssl md5 |")) {
                                       $md5hash{$key} = <PIPE>;
                                       close(PIPE);
                                       chomp($md5hash{$key});
                                   }
                               }
                               my $x509 = Crypt::OpenSSL::X509->new_from_file($file);
                               my @items = split(/,\s+/,$x509->subject());
                               foreach my $item (@items) {
                                   my ($name,$value) = split(/=/,$item);
                                   if ($name eq 'CN') {
                                       $info{$key}{'cn'} = $value;
                                   }
                               }
                               $info{$key}{'start'} = $x509->notBefore();
                               $info{$key}{'end'} = $x509->notAfter();
                               $info{$key}{'alg'} = $x509->sig_alg_name();
                               $info{$key}{'size'} = $x509->bit_length();
                               $info{$key}{'email'} = $x509->email();
                               $info{$key}{'serial'} = uc($x509->serial());
                               $info{$key}{'issuerhash'} = $x509->issuer_hash();
                               if ($x509->checkend(0)) {
                                   $expired{$key} = 1;
                               }
                               if (($key eq 'host') || ($key eq 'hostname')) {
                                   if ($info{$key}{'cn'} ne $expected_cn{$key}) {
                                       $wrongcn{$key} = 1;
                                   }
                                   if (($numrvk) && ($info{$key}{'serial'})) {
                                       if ($rvkcerts{$info{$key}{'serial'}}) {
                                           $revoked{$key} = 1;
                                       }
                                   }
                               }
                           }
                       }
                       if (($key eq 'host') || ($key eq 'hostname')) {
                           my $csrfile = $file;
                           $csrfile =~ s/\.pem$/.csr/;
                           if (-e $csrfile) {
                               if (open(PIPE,"openssl req -noout -modulus -in $csrfile |openssl md5 |")) {
                                   my $csrhash = <PIPE>;
                                   close(PIPE);
                                   chomp($csrhash);
                                   if ((!-e $file) || ($csrhash ne $md5hash{$key}) || ($expired{$key}) ||
                                       ($wrongcn{$key}) || ($revoked{$key})) {
                                       Crypt::PKCS10->setAPIversion(1);
                                       my $decoded = Crypt::PKCS10->new( $csrfile,(PEMonly => 1, readFile => 1));
                                       if (ref($decoded)) {
                                           if ($decoded->commonName() eq $expected_cn{$key}) {
                                               $info{$key.'-csr'}{'cn'} = $decoded->commonName();
                                               $info{$key.'-csr'}{'alg'} = $decoded->pkAlgorithm();
                                               $info{$key.'-csr'}{'email'} = $decoded->emailAddress();
                                               my $params = $decoded->subjectPublicKeyParams();
                                               if (ref($params) eq 'HASH') {
                                                   $info{$key.'-csr'}{'size'} = $params->{keylen};
                                               }
                                               $md5hash{$key.'-csr'} = $csrhash;
                                           }
                                       }
                                   }
                               }
                           }
                       }
                   }
               }
           }
       }
       foreach my $key ('host','hostname') {
           if ($md5hash{$key}) {
               if ($md5hash{$key} eq $md5hash{'key'}) {
                   if ($revoked{$key}) {
                       $info{$key}{'status'} = 'revoked';
                   } elsif ($expired{$key}) {
                       $info{$key}{'status'} = 'expired';
                   } elsif ($wrongcn{$key}) {
                       $info{$key}{'status'} = 'wrongcn';
                   } elsif ((exists($info{'ca'}{'issuerhash'})) &&
                            ($info{'ca'}{'issuerhash'} ne $info{$key}{'issuerhash'})) {
                       $info{$key}{'status'} = 'mismatch';
                   } else {
                       $info{$key}{'status'} = 'ok';
                   }
               } elsif ($info{'key'}{'status'} =~ /ok/) {
                   $info{$key}{'status'} = 'otherkey';
               } else {
                   $info{$key}{'status'} = 'nokey';
               }
           }
           if ($md5hash{$key.'-csr'}) {
               if ($md5hash{$key.'-csr'} eq $md5hash{'key'}) {
                   $info{$key.'-csr'}{'status'} = 'ok';
               } elsif ($info{'key'}{'status'} =~ /ok/) {
                   $info{$key.'-csr'}{'status'} = 'otherkey';
               } else {
                   $info{$key.'-csr'}{'status'} = 'nokey';
               }
           }
       }
       my $result;
       foreach my $key (keys(%info)) {
           $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($info{$key}).'&';
       }
       $result =~ s/\&$//;
       return $result;
   }
   
 sub get_dom {  sub get_dom {
     my ($userinput) = @_;      my ($userinput) = @_;
     my ($cmd,$udom,$namespace,$what) =split(/:/,$userinput,4);      my ($cmd,$udom,$namespace,$what) =split(/:/,$userinput,4); 
     my $hashref = &tie_domain_hash($udom,$namespace,&GDBM_READER()) or      my $hashref = &tie_domain_hash($udom,$namespace,&GDBM_READER()) or
         return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd";          return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd";
     my $qresult='';      my $qresult='';
Line 1209  sub sign_lti_payload { Line 1301  sub sign_lti_payload {
     srand( time() ^ ($$ + ($$ << 15))  ); # Seed rand.      srand( time() ^ ($$ + ($$ << 15))  ); # Seed rand.
     my $nonce = Digest::SHA::sha1_hex(sprintf("%06x%06x",rand(0xfffff0),rand(0xfffff0)));      my $nonce = Digest::SHA::sha1_hex(sprintf("%06x%06x",rand(0xfffff0),rand(0xfffff0)));
     my $request;      my $request;
     if (($context eq 'grade') && ($reqtype eq 'consumer') && ($bodyhash ne '')) {      if (($context eq 'grade') && ($reqtype eq 'consumer') && ($bodyhash ne '')) { 
         $request = Net::OAuth->request($reqtype)->new(          $request = Net::OAuth->request($reqtype)->new(
                            consumer_key => $key,                             consumer_key => $key,
                            consumer_secret => $secret,                             consumer_secret => $secret,
Line 1473  in /home/httpd/lonUsers/$dom on the prim Line 1565  in /home/httpd/lonUsers/$dom on the prim
 The single argument passed is the string: $cmd:$udom:$namespace:$what  The single argument passed is the string: $cmd:$udom:$namespace:$what
 where $cmd is the command historically passed to lond - i.e., getdom  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  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  (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  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:  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)  error: followed by an error message, or a string containing the value (escaped)

Removed from v.1.8.2.3.2.5  
changed lines
  Added in v.1.23


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>