--- loncom/Lond.pm 2012/04/11 21:32:28 1.1 +++ loncom/Lond.pm 2022/02/14 02:48:49 1.19 @@ -1,6 +1,6 @@ # The LearningOnline Network # -# $Id: Lond.pm,v 1.1 2012/04/11 21:32:28 droeschl Exp $ +# $Id: Lond.pm,v 1.19 2022/02/14 02:48:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -27,6 +27,7 @@ ### #NOTE perldoc at the end of file +#TODO move remaining lond functions into this package LONCAPA::Lond; @@ -36,76 +37,67 @@ 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 { - #TODO encapsulate $clientname and $clientversion in a object. - my ( $cmd, $tail, $clientname, $clientversion ) = @_; + my ( $tail, $clientversion ) = @_; + my ( $udom, $uname, $namespace, $regexp, $range ) = + split /:/, $tail; - my $userinput = "$cmd:$tail"; + $regexp = $regexp ? unescape($regexp) : '.'; - my ($udom,$uname,$namespace,$regexp,$range,$extra)=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); - } + if ($range =~ /^(\d+)\-(\d+)$/) { + ($start,$end) = ($1,$2); + } elsif ($range =~/^(\d+)$/) { + ($start,$end) = (0,$1); + } else { + undef($range); + } } - Apache::lonnet::logthis("Lond.pm: udom:[$udom] uname:[$uname] namespace:[$namespace]"); - my $hashref = &tie_user_hash($udom, $uname, $namespace, - &GDBM_READER()); - my $skipcheck; - if ($hashref) { - my $qresult=''; - my $count=0; + + my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()) or + return "error: ".($!+0)." tie(GDBM) Failed while attempting dump"; + + my $qresult = ''; + my $count = 0; # # When dump is for roles.db, determine if LON-CAPA version checking is needed. -# Sessions on 2.10 and later will include skipcheck => 1 in extra args ref, -# to indicate no version checking is needed (in this case, checking occurs +# Sessions on 2.10 and later do not require version checking, as that occurs # on the server hosting the user session, when constructing the roles/courses # screen). # - if ($extra ne '') { - $extra = &Apache::lonnet::thaw_unescape($extra); - $skipcheck = $extra->{'skipcheck'}; - } - my @ids = &Apache::lonnet::current_machine_ids(); - my (%homecourses,$major,$minor,$now); + my $skipcheck; + my @ids = &Apache::lonnet::current_machine_ids(); + my (%homecourses, $major, $minor, $now); # # If dump is for roles.db from a pre-2.10 server, determine the LON-CAPA -# version on the server which requested the data. 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. +# version on the server which requested the data. # - if (($namespace eq 'roles') && (!$skipcheck)) { - my $loncaparev = $clientversion; - if ($loncaparev eq '') { - $loncaparev = $Apache::lonnet::loncaparevs{$clientname}; - } - if ($loncaparev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?/) { - $major = $1; - $minor = $2; - } - $now = time; + if ($namespace eq 'roles') { + if ($clientversion =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?/) { + $major = $1; + $minor = $2; + } - while (my ($key,$value) = each(%$hashref)) { - if ($namespace eq 'roles') { + if (($major > 2) || (($major == 2) && ($minor > 9))) { + $skipcheck = 1; + } + $now = time; + } + while (my ($key,$value) = each(%$hashref)) { + if ($namespace eq 'roles' && (!$skipcheck)) { if ($key =~ m{^/($LONCAPA::match_domain)/($LONCAPA::match_courseid)(/?[^_]*)_(cc|co|in|ta|ep|ad|st|cr)$}) { my $cdom = $1; my $cnum = $2; - unless ($skipcheck) { - my ($role,$roleend,$rolestart) = split(/\_/,$value); - if (!$roleend || $roleend > $now) { + my ($role,$roleend,$rolestart) = split(/\_/,$value); + if (!$roleend || $roleend > $now) { # # For active course roles, check that requesting server is running a LON-CAPA # version which meets any version requirements for the course. Do not include @@ -116,28 +108,29 @@ sub dump_with_regexp { # homeserver is the current server, or whether it is a different server. # In both cases, the course's version requirement needs to be retrieved. # - next unless (&releasereqd_check($cnum,$cdom,$key,$value,$major, - $minor,\%homecourses,\@ids)); - } + next unless (&releasereqd_check($cnum,$cdom,$key,$value,$major, + $minor,\%homecourses,\@ids)); } } } - 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)) { + 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&"; + } + } + } + + &untie_user_hash($hashref) or + return "error: ".($!+0)." untie(GDBM) Failed while attempting dump"; # # If dump is for roles.db from a pre-2.10 server, check if the LON-CAPA # version requirements for courses for which the current server is the home @@ -145,30 +138,1113 @@ sub dump_with_regexp { # user's session. If so, include those role results in the data returned to # the client server. # - if (($namespace eq 'roles') && (!$skipcheck)) { - if (keys(%homecourses) > 0) { - $qresult .= &check_homecourses(\%homecourses,$regexp,$count, - $range,$start,$end,$major,$minor); + if (($namespace eq 'roles') && (!$skipcheck)) { + if (keys(%homecourses) > 0) { + $qresult .= &check_homecourses(\%homecourses,$regexp,$count, + $range,$start,$end,$major,$minor); + } + } + chop($qresult); + return $qresult; +} + + +sub releasereqd_check { + my ($cnum,$cdom,$key,$value,$major,$minor,$homecourses,$ids) = @_; + my $home = &Apache::lonnet::homeserver($cnum,$cdom); + return if ($home eq 'no_host'); + my ($reqdmajor,$reqdminor,$displayrole); + if ($cnum =~ /$LONCAPA::match_community/) { + if ($major eq '' && $minor eq '') { + return unless ((ref($ids) eq 'ARRAY') && + (grep(/^\Q$home\E$/,@{$ids}))); + } else { + $reqdmajor = 2; + $reqdminor = 9; + return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor)); + } + } + my $hashid = $cdom.':'.$cnum; + my ($courseinfo,$cached) = + &Apache::lonnet::is_cached_new('courseinfo',$hashid); + if (defined($cached)) { + if (ref($courseinfo) eq 'HASH') { + if (exists($courseinfo->{'releaserequired'})) { + my ($reqdmajor,$reqdminor) = split(/\./,$courseinfo->{'releaserequired'}); + return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor)); + } + } + } else { + if (ref($ids) eq 'ARRAY') { + if (grep(/^\Q$home\E$/,@{$ids})) { + if (ref($homecourses) eq 'HASH') { + if (ref($homecourses->{$cdom}) eq 'HASH') { + if (ref($homecourses->{$cdom}{$cnum}) eq 'HASH') { + if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') { + push(@{$homecourses->{$cdom}{$cnum}},{$key=>$value}); + } else { + $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; + } + } else { + $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; + } + } else { + $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; + } + } + return; + } + } + my $courseinfo = &get_courseinfo_hash($cnum,$cdom,$home); + if (ref($courseinfo) eq 'HASH') { + if (exists($courseinfo->{'releaserequired'})) { + my ($reqdmajor,$reqdminor) = split(/\./,$courseinfo->{'releaserequired'}); + return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor)); + } + } else { + return; + } + } + return 1; +} + + +sub check_homecourses { + my ($homecourses,$regexp,$count,$range,$start,$end,$major,$minor) = @_; + my ($result,%addtocache); + my $yesterday = time - 24*3600; + if (ref($homecourses) eq 'HASH') { + my (%okcourses,%courseinfo,%recent); + foreach my $domain (keys(%{$homecourses})) { + my $hashref = + &tie_domain_hash($domain, "nohist_courseids", &GDBM_WRCREAT()); + if (ref($hashref) eq 'HASH') { + while (my ($key,$value) = each(%$hashref)) { + my $unesc_key = &unescape($key); + if ($unesc_key =~ /^lasttime:(\w+)$/) { + my $cid = $1; + $cid =~ s/_/:/; + if ($value > $yesterday ) { + $recent{$cid} = 1; + } + next; + } + my $items = &Apache::lonnet::thaw_unescape($value); + if (ref($items) eq 'HASH') { + my ($cdom,$cnum) = split(/_/,$unesc_key); + my $hashid = $cdom.':'.$cnum; + $courseinfo{$hashid} = $items; + if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') { + my ($reqdmajor,$reqdminor) = split(/\./,$items->{'releaserequired'}); + if (&useable_role($reqdmajor,$reqdminor,$major,$minor)) { + $okcourses{$hashid} = 1; + } + } + } + } + unless (&untie_domain_hash($hashref)) { + &Apache::lonnet::logthis("Failed to untie tied hash for nohist_courseids.db for $domain"); + } + } else { + &Apache::lonnet::logthis("Failed to tie hash for nohist_courseids.db for $domain"); + } + } + foreach my $hashid (keys(%recent)) { + my ($result,$cached)=&Apache::lonnet::is_cached_new('courseinfo',$hashid); + unless ($cached) { + &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); + } + } + foreach my $cdom (keys(%{$homecourses})) { + if (ref($homecourses->{$cdom}) eq 'HASH') { + foreach my $cnum (keys(%{$homecourses->{$cdom}})) { + my $hashid = $cdom.':'.$cnum; + next if ($recent{$hashid}); + &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); } } - chop($qresult); - Apache::lonnet::logthis("Lond.pm: qresult:[$qresult]"); - return $qresult; - #&Reply($client, \$qresult, $userinput); - } else { - return "error: ".($!+0)." untie(GDBM) Failed while attempting dump"; - #&Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". - # "while attempting dump\n", $userinput); + } + foreach my $hashid (keys(%okcourses)) { + my ($cdom,$cnum) = split(/:/,$hashid); + if ((ref($homecourses->{$cdom}) eq 'HASH') && + (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY')) { + foreach my $role (@{$homecourses->{$cdom}{$cnum}}) { + if (ref($role) eq 'HASH') { + while (my ($key,$value) = each(%{$role})) { + if ($regexp eq '.') { + $count++; + if (defined($range) && $count >= $end) { last; } + if (defined($range) && $count < $start) { next; } + $result.=$key.'='.$value.'&'; + } else { + my $unescapeKey = &unescape($key); + if (eval('$unescapeKey=~/$regexp/')) { + $count++; + if (defined($range) && $count >= $end) { last; } + if (defined($range) && $count < $start) { next; } + $result.="$key=$value&"; + } + } + } + } + } + } + } + } + return $result; +} + + +sub useable_role { + my ($reqdmajor,$reqdminor,$major,$minor) = @_; + if ($reqdmajor ne '' && $reqdminor ne '') { + return if (($major eq '' && $minor eq '') || + ($major < $reqdmajor) || + (($major == $reqdmajor) && ($minor < $reqdminor))); + } + return 1; +} + + +sub get_courseinfo_hash { + my ($cnum,$cdom,$home) = @_; + my %info; + eval { + local($SIG{ALRM}) = sub { die "timeout\n"; }; + local($SIG{__DIE__})='DEFAULT'; + alarm(3); + %info = &Apache::lonnet::courseiddump($cdom,'.',1,'.','.',$cnum,1,[$home],'.'); + alarm(0); + }; + if ($@) { + if ($@ eq "timeout\n") { + &Apache::lonnet::logthis("WARNING courseiddump for $cnum:$cdom from $home timedout"); + } else { + &Apache::lonnet::logthis("WARNING unexpected error during eval of call for courseiddump from $home"); + } + } else { + if (ref($info{$cdom.'_'.$cnum}) eq 'HASH') { + my $hashid = $cdom.':'.$cnum; + return &Apache::lonnet::do_cache_new('courseinfo',$hashid,$info{$cdom.'_'.$cnum},600); + } + } + return; +} + +sub dump_course_id_handler { + my ($tail) = @_; + + my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter, + $typefilter,$regexp_ok,$rtn_as_hash,$selfenrollonly,$catfilter,$showhidden, + $caller,$cloner,$cc_clone_list,$cloneonly,$createdbefore,$createdafter, + $creationcontext,$domcloner,$hasuniquecode,$reqcrsdom,$reqinstcode) = split(/:/,$tail); + my $now = time; + my ($cloneruname,$clonerudom,%cc_clone); + if (defined($description)) { + $description=&unescape($description); + } else { + $description='.'; + } + if (defined($instcodefilter)) { + $instcodefilter=&unescape($instcodefilter); + } else { + $instcodefilter='.'; + } + my ($ownerunamefilter,$ownerdomfilter); + if (defined($ownerfilter)) { + $ownerfilter=&unescape($ownerfilter); + if ($ownerfilter ne '.' && defined($ownerfilter)) { + if ($ownerfilter =~ /^([^:]*):([^:]*)$/) { + $ownerunamefilter = $1; + $ownerdomfilter = $2; + } else { + $ownerunamefilter = $ownerfilter; + $ownerdomfilter = ''; + } + } + } else { + $ownerfilter='.'; + } + + if (defined($coursefilter)) { + $coursefilter=&unescape($coursefilter); + } else { + $coursefilter='.'; + } + if (defined($typefilter)) { + $typefilter=&unescape($typefilter); + } else { + $typefilter='.'; + } + if (defined($regexp_ok)) { + $regexp_ok=&unescape($regexp_ok); + } + if (defined($catfilter)) { + $catfilter=&unescape($catfilter); + } + if (defined($cloner)) { + $cloner = &unescape($cloner); + ($cloneruname,$clonerudom) = ($cloner =~ /^($LONCAPA::match_username):($LONCAPA::match_domain)$/); + } + if (defined($cc_clone_list)) { + $cc_clone_list = &unescape($cc_clone_list); + my @cc_cloners = split('&',$cc_clone_list); + foreach my $cid (@cc_cloners) { + my ($clonedom,$clonenum) = split(':',$cid); + next if ($clonedom ne $udom); + $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 = '.'; + } + if ($reqinstcode ne '') { + $reqinstcode = &unescape($reqinstcode); + } + my $unpack = 1; + if ($description eq '.' && $instcodefilter eq '.' && $ownerfilter eq '.' && + $typefilter eq '.') { + $unpack = 0; + } + if (!defined($since)) { $since=0; } + my (%gotcodedefaults,%otcodedefaults); + my $qresult=''; + + my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()) + or return "error: ".($!+0)." tie(GDBM) Failed while attempting courseiddump"; + + while (my ($key,$value) = each(%$hashref)) { + my ($unesc_key,$lasttime_key,$lasttime,$is_hash,%val, + %unesc_val,$selfenroll_end,$selfenroll_types,$created, + $context); + $unesc_key = &unescape($key); + if ($unesc_key =~ /^lasttime:/) { + next; + } else { + $lasttime_key = &escape('lasttime:'.$unesc_key); + } + if ($hashref->{$lasttime_key} ne '') { + $lasttime = $hashref->{$lasttime_key}; + next if ($lasttime<$since); + } + my ($canclone,$valchange,$clonefromcode); + my $items = &Apache::lonnet::thaw_unescape($value); + if (ref($items) eq 'HASH') { + if ($hashref->{$lasttime_key} eq '') { + next if ($since > 1); + } + if ($items->{'inst_code'}) { + $clonefromcode = $items->{'inst_code'}; + } + $is_hash = 1; + if ($domcloner) { + $canclone = 1; + } elsif (defined($clonerudom)) { + if ($items->{'cloners'}) { + my @cloneable = split(',',$items->{'cloners'}); + if (@cloneable) { + if (grep(/^\*$/,@cloneable)) { + $canclone = 1; + } elsif (grep(/^\*:\Q$clonerudom\E$/,@cloneable)) { + $canclone = 1; + } elsif (grep(/^\Q$cloneruname\E:\Q$clonerudom\E$/,@cloneable)) { + $canclone = 1; + } + } + unless ($canclone) { + if ($cloneruname ne '' && $clonerudom ne '') { + if ($cc_clone{$unesc_key}) { + $canclone = 1; + $items->{'cloners'} .= ','.$cloneruname.':'. + $clonerudom; + $valchange = 1; + } + } + } + unless ($canclone) { + if (($reqcrsdom eq $udom) && ($reqinstcode) && ($clonefromcode)) { + if (grep(/\=/,@cloneable)) { + foreach my $cloner (@cloneable) { + if (($cloner ne '*') && ($cloner !~ /^\*\:$LONCAPA::match_domain$/) && + ($cloner !~ /^$LONCAPA::match_username\:$LONCAPA::match_domain$/) && ($cloner ne '')) { + if ($cloner =~ /=/) { + my (%codedefaults,@code_order); + if (ref($gotcodedefaults{$udom}) eq 'HASH') { + if (ref($gotcodedefaults{$udom}{'defaults'}) eq 'HASH') { + %codedefaults = %{$gotcodedefaults{$udom}{'defaults'}}; + } + if (ref($gotcodedefaults{$udom}{'order'}) eq 'ARRAY') { + @code_order = @{$gotcodedefaults{$udom}{'order'}}; + } + } else { + &Apache::lonnet::auto_instcode_defaults($udom, + \%codedefaults, + \@code_order); + $gotcodedefaults{$udom}{'defaults'} = \%codedefaults; + $gotcodedefaults{$udom}{'order'} = \@code_order; + } + if (@code_order > 0) { + if (&Apache::lonnet::check_instcode_cloning(\%codedefaults,\@code_order, + $cloner,$clonefromcode,$reqinstcode)) { + $canclone = 1; + last; + } + } + } + } + } + } + } + } + } elsif (defined($cloneruname)) { + if ($cc_clone{$unesc_key}) { + $canclone = 1; + $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; + } + } + } + unless (($canclone) || ($items->{'cloners'})) { + my %domdefs = &Apache::lonnet::get_domain_defaults($udom); + if ($domdefs{'canclone'}) { + unless ($domdefs{'canclone'} eq 'none') { + if ($domdefs{'canclone'} eq 'domain') { + if ($clonerudom eq $udom) { + $canclone = 1; + } + } elsif (($clonefromcode) && ($reqinstcode) && + ($udom eq $reqcrsdom)) { + if (&Apache::lonnet::default_instcode_cloning($udom,$domdefs{'canclone'}, + $clonefromcode,$reqinstcode)) { + $canclone = 1; + } + } + } + } + } + } + if ($unpack || !$rtn_as_hash) { + $unesc_val{'descr'} = $items->{'description'}; + $unesc_val{'inst_code'} = $items->{'inst_code'}; + $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 ($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'}); + next if (@categories == 0); + my @subcats = split('&',$catfilter); + my $matchcat = 0; + foreach my $cat (@categories) { + if (grep(/^\Q$cat\E$/,@subcats)) { + $matchcat = 1; + last; + } + } + next if (!$matchcat); + } + if ($caller eq 'coursecatalog') { + if ($items->{'hidefromcat'} eq 'yes') { + next if !$showhidden; + } + } + if ($hasuniquecode ne '.') { + next unless ($items->{'uniquecode'}); + } + } 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; + $val{'cloners'} = &escape($cloneruname.':'.$clonerudom); + } + } + $is_hash = 0; + my @courseitems = split(/:/,$value); + $lasttime = pop(@courseitems); + if ($hashref->{$lasttime_key} eq '') { + next if ($lasttime<$since); + } + ($val{'descr'},$val{'inst_code'},$val{'owner'},$val{'type'}) = @courseitems; + } + if ($cloneonly) { + next unless ($canclone); + } + my $match = 1; + if ($description ne '.') { + if (!$is_hash) { + $unesc_val{'descr'} = &unescape($val{'descr'}); + } + if (eval{$unesc_val{'descr'} !~ /\Q$description\E/i}) { + $match = 0; + } + } + if ($instcodefilter ne '.') { + if (!$is_hash) { + $unesc_val{'inst_code'} = &unescape($val{'inst_code'}); + } + if ($regexp_ok == 1) { + if (eval{$unesc_val{'inst_code'} !~ /$instcodefilter/}) { + $match = 0; + } + } elsif ($regexp_ok == -1) { + if (eval{$unesc_val{'inst_code'} =~ /$instcodefilter/}) { + $match = 0; + } + } else { + if (eval{$unesc_val{'inst_code'} !~ /\Q$instcodefilter\E/i}) { + $match = 0; + } + } + } + if ($ownerfilter ne '.') { + if (!$is_hash) { + $unesc_val{'owner'} = &unescape($val{'owner'}); + } + if (($ownerunamefilter ne '') && ($ownerdomfilter ne '')) { + if ($unesc_val{'owner'} =~ /:/) { + if (eval{$unesc_val{'owner'} !~ + /\Q$ownerunamefilter\E:\Q$ownerdomfilter\E$/i}) { + $match = 0; + } + } else { + if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) { + $match = 0; + } + } + } elsif ($ownerunamefilter ne '') { + if ($unesc_val{'owner'} =~ /:/) { + if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E:[^:]+$/i}) { + $match = 0; + } + } else { + if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) { + $match = 0; + } + } + } elsif ($ownerdomfilter ne '') { + if ($unesc_val{'owner'} =~ /:/) { + if (eval{$unesc_val{'owner'} !~ /^[^:]+:\Q$ownerdomfilter\E/}) { + $match = 0; + } + } else { + if ($ownerdomfilter ne $udom) { + $match = 0; + } + } + } + } + if ($coursefilter ne '.') { + if (eval{$unesc_key !~ /^$udom(_)\Q$coursefilter\E$/}) { + $match = 0; + } + } + if ($typefilter ne '.') { + if (!$is_hash) { + $unesc_val{'type'} = &unescape($val{'type'}); + } + if ($unesc_val{'type'} eq '') { + if ($typefilter ne 'Course') { + $match = 0; + } + } else { + if (eval{$unesc_val{'type'} !~ /^\Q$typefilter\E$/}) { + $match = 0; + } + } + } + if ($match == 1) { + if ($rtn_as_hash) { + if ($is_hash) { + if ($valchange) { + my $newvalue = &Apache::lonnet::freeze_escape($items); + $qresult.=$key.'='.$newvalue.'&'; + } else { + $qresult.=$key.'='.$value.'&'; + } + } else { + my %rtnhash = ( 'description' => &unescape($val{'descr'}), + 'inst_code' => &unescape($val{'inst_code'}), + 'owner' => &unescape($val{'owner'}), + 'type' => &unescape($val{'type'}), + 'cloners' => &unescape($val{'cloners'}), + ); + my $items = &Apache::lonnet::freeze_escape(\%rtnhash); + $qresult.=$key.'='.$items.'&'; + } + } else { + if ($is_hash) { + $qresult .= $key.'='.&escape($unesc_val{'descr'}).':'. + &escape($unesc_val{'inst_code'}).':'. + &escape($unesc_val{'owner'}).'&'; + } else { + $qresult .= $key.'='.$val{'descr'}.':'.$val{'inst_code'}. + ':'.$val{'owner'}.'&'; + } + } + } + } + &untie_domain_hash($hashref) or + return "error: ".($!+0)." untie(GDBM) Failed while attempting courseiddump"; + + chop($qresult); + return $qresult; +} + +sub dump_profile_database { + my ($tail) = @_; + + my ($udom,$uname,$namespace) = split(/:/,$tail); + + my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()) or + return "error: ".($!+0)." tie(GDBM) Failed while attempting currentdump"; + + # Structure of %data: + # $data{$symb}->{$parameter}=$value; + # $data{$symb}->{'v.'.$parameter}=$version; + # since $parameter will be unescaped, we do not + # have to worry about silly parameter names... + + my $qresult=''; + my %data = (); # A hash of anonymous hashes.. + while (my ($key,$value) = each(%$hashref)) { + my ($v,$symb,$param) = split(/:/,$key); + next if ($v eq 'version' || $symb eq 'keys'); + next if (exists($data{$symb}) && + exists($data{$symb}->{$param}) && + $data{$symb}->{'v.'.$param} > $v); + $data{$symb}->{$param}=$value; + $data{$symb}->{'v.'.$param}=$v; } + + &untie_user_hash($hashref) or + return "error: ".($!+0)." untie(GDBM) Failed while attempting currentdump"; + + while (my ($symb,$param_hash) = each(%data)) { + while(my ($param,$value) = each (%$param_hash)){ + next if ($param =~ /^v\./); # Ignore versions... + # + # Just dump the symb=value pairs separated by & + # + $qresult.=$symb.':'.$param.'='.$value.'&'; + } + } + + chop($qresult); + return $qresult; +} + +sub is_course { + my ($cdom,$cnum) = @_; + + return unless (($cdom =~ /^$LONCAPA::match_domain$/) && + ($cnum =~ /^$LONCAPA::match_courseid$/)); + my $hashid = $cdom.':'.$cnum; + my ($iscourse,$cached) = + &Apache::lonnet::is_cached_new('iscourse',$hashid); + unless (defined($cached)) { + my $hashref = + &tie_domain_hash($cdom, "nohist_courseids", &GDBM_WRCREAT()); + if (ref($hashref) eq 'HASH') { + my $esc_key = &escape($cdom.'_'.$cnum); + if (exists($hashref->{$esc_key})) { + $iscourse = 1; + } else { + $iscourse = 0; + } + &Apache::lonnet::do_cache_new('iscourse',$hashid,$iscourse,3600); + unless (&untie_domain_hash($hashref)) { + &Apache::lonnet::logthis("Failed to untie tied hash for nohist_courseids.db for $cdom"); + } + } else { + &Apache::lonnet::logthis("Failed to tie hash for nohist_courseids.db for $cdom"); + } + } + 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 = ; + 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); + chomp($check); + $info{$key}{'status'} = $check; + } + if (open(PIPE,"openssl rsa -noout -modulus -in $file | openssl md5 |")) { + $md5hash{$key} = ; + close(PIPE); + chomp($md5hash{$key}); + } + } else { + if ($key eq 'ca') { + if (open(PIPE,"openssl verify -CAfile $file $file |")) { + my $check = ; + 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} = ; + 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 = ; + 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 { + 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 { - return "error: ".($!+0)." tie(GDBM) Failed while attempting dump"; - #&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". - # "while attempting dump\n", $userinput); + $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0; } - #never get here - die("SHOULD NOT HAPPEN!"); - return 1; + 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; @@ -191,7 +1267,7 @@ LONCAPA::Lond.pm =over 4 -=item dump_with_regexp( $cmd, $tail, $client ) +=item dump_with_regexp( $tail, $client ) Dump a profile database with an optional regular expression to match against the keys. In this dump, no effort is made to separate symb from version @@ -199,8 +1275,6 @@ information. Presumably the databases th different structure. Need to look at this and improve the documentation of both this and the currentdump handler. -$cmd is the command keyword. - $tail a colon separated list containing =over @@ -224,10 +1298,6 @@ selective dumps. optional range of entries e.g., 10-20 would return the 10th to 19th items, etc. -=item extra - -optional ref to hash of additional args. currently skipcheck is only key used. - =back $client is the channel open on the client. @@ -236,6 +1306,82 @@ Returns: 1 (Continue processing). Side effects: response is written to $client. +=item dump_course_id_handler + +#TODO copy from lond + +=item dump_profile_database + +#TODO copy from lond + +=item releasereqd_check( $cnum, $cdom, $key, $value, $major, $minor, + $homecourses, $ids ) + +releasereqd_check() will determine if a LON-CAPA version (defined in the +$major,$minor args passed) is not too old to allow use of a role in a +course ($cnum,$cdom args passed), if at least one of the following applies: +(a) the course is a Community, (b) the course's home server is *not* the +current server, or (c) cached course information is not stale. + +For the case where none of these apply, the course is added to the +$homecourse hash ref (keys = courseIDs, values = array of a hash of roles). +The $homecourse hash ref is for courses for which the current server is the +home server. LON-CAPA version requirements are checked elsewhere for the +items in $homecourse. + + +=item check_homecourses( $homecourses, $regexp, $count, $range, $start, $end, + $major, $minor ) + +check_homecourses() will retrieve course information for those courses which +are keys of the $homecourses hash ref (first arg). The nohist_courseids.db +GDBM file is tied and course information for each course retrieved. Last +visit (lasttime key) is also retrieved for each, and cached values updated +for any courses last visited less than 24 hours ago. Cached values are also +updated for any courses included in the $homecourses hash ref. + +The reason for the 24 hours constraint is that the cron entry in +/etc/cron.d/loncapa for /home/httpd/perl/refresh_courseids_db.pl causes +cached course information to be updated nightly for courses with activity +within the past 24 hours. + +Role information for the user (included in a ref to an array of hashes as the +value for each key in $homecourses) is appended to the result returned by the +routine, which will in turn be appended to the string returned to the client +hosting the user's session. + + +=item useable_role( $reqdmajor, $reqdminor, $major, $minor ) + +useable_role() will compare the LON-CAPA version required by a course with +the version available on the client server. If the client server's version +is compatible, 1 will be returned. + + +=item get_courseinfo_hash( $cnum, $cdom, $home ) + +get_courseinfo_hash() is used to retrieve course information from the db +file: nohist_courseids.db for a course for which the current server is *not* +the home server. + +A hash of a hash will be retrieved. The outer hash contains a single key -- +courseID -- for the course for which the data are being requested. +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 =head1 BUGS