--- loncom/lond	2009/05/08 12:02:39	1.415
+++ loncom/lond	2009/10/29 03:23:52	1.432
@@ -2,7 +2,7 @@
 # The LearningOnline Network
 # lond "LON Daemon" Server (port "LOND" 5663)
 #
-# $Id: lond,v 1.415 2009/05/08 12:02:39 raeburn Exp $
+# $Id: lond,v 1.432 2009/10/29 03:23:52 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -59,7 +59,7 @@ my $DEBUG = 0;		       # Non zero to ena
 my $status='';
 my $lastlog='';
 
-my $VERSION='$Revision: 1.415 $'; #' stupid emacs
+my $VERSION='$Revision: 1.432 $'; #' stupid emacs
 my $remoteVERSION;
 my $currenthostid="default";
 my $currentdomainid;
@@ -67,6 +67,7 @@ my $currentdomainid;
 my $client;
 my $clientip;			# IP address of client.
 my $clientname;			# LonCAPA name of client.
+my $clientversion;              # LonCAPA version running on client
 
 my $server;
 
@@ -1815,8 +1816,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 +1827,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);
     }
@@ -1841,8 +1847,11 @@ sub change_password_handler {
 	    $salt=substr($salt,6,2);
 	    my $ncpass=crypt($npass,$salt);
 	    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);
 		&Reply($client, "ok\n", $userinput);
 	    } else {
 		&logthis("Unable to open $uname passwd "               
@@ -1863,7 +1872,10 @@ sub change_password_handler {
 	}  
 	
     } else {
-	&Failure( $client, "non_authorized\n", $userinput);
+	if ($failure eq '') {
+	    $failure = 'non_authorized';
+	}
+	&Failure( $client, "$failure\n", $userinput);
     }
 
     return 1;
@@ -3110,6 +3122,13 @@ sub dump_with_regexp {
         my $qresult='';
 	my $count=0;
 	while (my ($key,$value) = each(%$hashref)) {
+            if ($namespace eq 'roles') {
+                if ($key =~ /^($LONCAPA::match_domain)_($LONCAPA::match_community)_cc$/) {
+                    if ($clientversion =~ /^(\d+\.\d+)$/) {
+                        next if ($1 <= 2.9);
+                    }
+                }
+            }
 	    if ($regexp eq '.') {
 		$count++;
 		if (defined($range) && $count >= $end)   { last; }
@@ -3674,8 +3693,10 @@ sub put_course_id_hash_handler {
 #                            will be returned. Pre-2.2.0 legacy entries from 
 #                            nohist_courseiddump will only contain usernames.
 #                 type     - optional parameter for selection 
-#                 regexp_ok - if true, allow the supplied institutional code
-#                            filter to behave as a regular expression.  
+#                 regexp_ok - if 1 or -1 allow the supplied institutional code
+#                            filter to behave as a regular expression:
+#	                      1 will not exclude the course if the instcode matches the RE 
+#                            -1 will exclude the course if the instcode matches the RE
 #                 rtn_as_hash - whether to return the information available for
 #                            each matched item as a frozen hash of all 
 #                            key, value pairs in the item's hash, or as a 
@@ -3691,6 +3712,15 @@ 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
+#                          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.
+#                 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 
 #
 #     $client  - The socket open on the client.
 # Returns:
@@ -3703,8 +3733,10 @@ sub dump_course_id_handler {
 
     my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter,
         $typefilter,$regexp_ok,$rtn_as_hash,$selfenrollonly,$catfilter,$showhidden,
-        $caller) =split(/:/,$tail);
+        $caller,$cloner,$cc_clone_list,$cloneonly,$createdbefore,$createdafter,
+        $creationcontext) =split(/:/,$tail);
     my $now = time;
+    my ($cloneruname,$clonerudom,%cc_clone);
     if (defined($description)) {
 	$description=&unescape($description);
     } else {
@@ -3747,6 +3779,35 @@ sub dump_course_id_handler {
     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 = '.';
+    }
+    
     my $unpack = 1;
     if ($description eq '.' && $instcodefilter eq '.' && $coursefilter eq '.' && 
         $typefilter eq '.') {
@@ -3758,7 +3819,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;
@@ -3769,23 +3831,71 @@ sub dump_course_id_handler {
                 $lasttime = $hashref->{$lasttime_key};
                 next if ($lasttime<$since);
             }
+            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 ($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;
+                                }
+                            }
+                        }
+                    } elsif (defined($cloneruname)) {
+                        if ($cc_clone{$unesc_key}) {
+                            $canclone = 1;
+                            $items->{'cloners'} = $cloneruname.':'.$clonerudom;
+                            $valchange = 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'}); 
@@ -3807,7 +3917,15 @@ sub dump_course_id_handler {
                 }
             } else {
                 next if ($catfilter ne '');
-                next if ($selfenrollonly); 
+                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);
@@ -3816,6 +3934,9 @@ sub dump_course_id_handler {
                 }
 	        ($val{'descr'},$val{'inst_code'},$val{'owner'},$val{'type'}) = @courseitems;
             }
+            if ($cloneonly) {
+               next unless ($canclone);
+            }
             my $match = 1;
 	    if ($description ne '.') {
                 if (!$is_hash) {
@@ -3829,10 +3950,14 @@ sub dump_course_id_handler {
                 if (!$is_hash) {
                     $unesc_val{'inst_code'} = &unescape($val{'inst_code'});
                 }
-                if ($regexp_ok) {
+                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;
@@ -3898,12 +4023,18 @@ sub dump_course_id_handler {
             if ($match == 1) {
                 if ($rtn_as_hash) {
                     if ($is_hash) {
-                        $qresult.=$key.'='.$value.'&';
+                        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.'&';
@@ -4027,7 +4158,6 @@ sub get_domain_handler {
 }
 &register_handler("getdom", \&get_domain_handler, 0, 1, 0);
 
-
 #
 #  Puts an id to a domains id database. 
 #
@@ -4334,27 +4464,30 @@ sub dump_domainroles_handler {
         $rolesfilter=&unescape($rolesfilter);
 	@roles = split(/\&/,$rolesfilter);
     }
-                                                                                           
+
     my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT());
     if ($hashref) {
         my $qresult = '';
         while (my ($key,$value) = each(%$hashref)) {
             my $match = 1;
-            my ($start,$end) = split(/:/,&unescape($value));
+            my ($end,$start) = split(/:/,&unescape($value));
             my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,&unescape($key));
-            unless ($startfilter eq '.' || !defined($startfilter)) {
-                if ((defined($start)) && ($start >= $startfilter)) {
+            unless (@roles < 1) {
+                unless (grep/^\Q$trole\E$/,@roles) {
                     $match = 0;
+                    next;
                 }
             }
-            unless ($endfilter eq '.' || !defined($endfilter)) {
-                if ((defined($end)) && ($end <= $endfilter)) {
+            unless ($startfilter eq '.' || !defined($startfilter)) {
+                if ((defined($start)) && ($start >= $startfilter)) {
                     $match = 0;
+                    next;
                 }
             }
-            unless (@roles < 1) {
-                unless (grep/^\Q$trole\E$/,@roles) {
+            unless ($endfilter eq '.' || !defined($endfilter)) {
+                if ((defined($end)) && (($end > 0) && ($end <= $endfilter))) {
                     $match = 0;
+                    next;
                 }
             }
             if ($match == 1) {
@@ -4641,6 +4774,43 @@ sub enrollment_enabled_handler {
 }
 &register_handler("autorun", \&enrollment_enabled_handler, 0, 1, 0);
 
+#
+#   Validate an institutional code used for a LON-CAPA course.          
+#
+# Formal Parameters:
+#   $cmd          - The command request that got us dispatched.
+#   $tail         - The tail of the command.  In this case,
+#                   this is a colon separated set of words that will be split
+#                   into:
+#                        $dom      - The domain for which the check of 
+#                                    institutional course code will occur.
+#
+#                        $instcode - The institutional code for the course
+#                                    being requested, or validated for rights
+#                                    to request.
+#
+#                        $owner    - The course requestor (who will be the
+#                                    course owner, in the form username:domain
+#
+#   $client       - Socket open on the client.
+# Returns:
+#    1           - Indicating processing should continue.
+#
+sub validate_instcode_handler {
+    my ($cmd, $tail, $client) = @_;
+    my $userinput = "$cmd:$tail";
+    my ($dom,$instcode,$owner) = split(/:/, $tail);
+    $instcode = &unescape($instcode);
+    $owner = &unescape($owner);
+    my ($outcome,$description) = 
+        &localenroll::validate_instcode($dom,$instcode,$owner);
+    my $result = &escape($outcome).'&'.&escape($description);
+    &Reply($client, \$result, $userinput);
+
+    return 1;
+}
+&register_handler("autovalidateinstcode", \&validate_instcode_handler, 0, 1, 0);
+
 #   Get the official sections for which auto-enrollment is possible.
 #   Since the admin people won't know about 'unofficial sections' 
 #   we cannot auto-enroll on them.
@@ -4844,6 +5014,61 @@ sub retrieve_auto_file_handler {
 }
 &register_handler("autoretrieve", \&retrieve_auto_file_handler, 0,1,0);
 
+sub crsreq_checks_handler {
+    my ($cmd, $tail, $client) = @_;
+    my $userinput = "$cmd:$tail";
+    my $dom = $tail;
+    my $result;
+    my @reqtypes = ('official','unofficial','community');
+    eval {
+        local($SIG{__DIE__})='DEFAULT';
+        my %validations;
+        my $response = &localenroll::crsreq_checks($dom,\@reqtypes,
+                                                   \%validations);
+        if ($response eq 'ok') { 
+            foreach my $key (keys(%validations)) {
+                $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($validations{$key}).'&';
+            }
+            $result =~ s/\&$//;
+        } else {
+            $result = 'error';
+        }
+    };
+    if (!$@) {
+        &Reply($client, \$result, $userinput);
+    } else {
+        &Failure($client,"unknown_cmd\n",$userinput);
+    }
+    return 1;
+}
+&register_handler("autocrsreqchecks", \&crsreq_checks_handler, 0, 1, 0);
+
+sub validate_crsreq_handler {
+    my ($cmd, $tail, $client) = @_;
+    my $userinput = "$cmd:$tail";
+    my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = split(/:/, $tail);
+    $instcode = &unescape($instcode);
+    $owner = &unescape($owner);
+    $crstype = &unescape($crstype);
+    $inststatuslist = &unescape($inststatuslist);
+    $instcode = &unescape($instcode);
+    $instseclist = &unescape($instseclist);
+    my $outcome;
+    eval {
+        local($SIG{__DIE__})='DEFAULT';
+        $outcome = &localenroll::validate_crsreq($dom,$owner,$crstype,
+                                                 $inststatuslist,$instcode,
+                                                 $instseclist);
+    };
+    if (!$@) {
+        &Reply($client, \$outcome, $userinput);
+    } else {
+        &Failure($client,"unknown_cmd\n",$userinput);
+    }
+    return 1;
+}
+&register_handler("autocrsreqvalidation", \&validate_crsreq_handler, 0, 1, 0);
+
 #
 #   Read and retrieve institutional code format (for support form).
 # Formal Parameters:
@@ -4928,6 +5153,39 @@ sub get_institutional_defaults_handler {
 &register_handler("autoinstcodedefaults",
                   \&get_institutional_defaults_handler,0,1,0);
 
+sub get_possible_instcodes_handler {
+    my ($cmd, $tail, $client)   = @_;
+    my $userinput               = "$cmd:$tail";
+
+    my $reply;
+    my $cdom = $tail;
+    my (@codetitles,%cat_titles,%cat_order,@code_order);
+    my $formatreply = &localenroll::possible_instcodes($cdom,
+                                                       \@codetitles,
+                                                       \%cat_titles,
+                                                       \%cat_order,
+                                                       \@code_order);
+    if ($formatreply eq 'ok') {
+        my $result = join('&',map {&escape($_);} (@codetitles)).':';
+        $result .= join('&',map {&escape($_);} (@code_order)).':';
+        foreach my $key (keys(%cat_titles)) {
+            $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($cat_titles{$key}).'&';
+        }
+        $result =~ s/\&$//;
+        $result .= ':';
+        foreach my $key (keys(%cat_order)) {
+            $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($cat_order{$key}).'&';
+        }
+        $result =~ s/\&$//;
+        &Reply($client,\$result,$userinput);
+    } else {
+        &Reply($client, "format_error\n", $userinput);
+    }
+    return 1;
+}
+&register_handler("autopossibleinstcodes",
+                  \&get_possible_instcodes_handler,0,1,0);
+
 sub get_institutional_user_rules {
     my ($cmd, $tail, $client)   = @_;
     my $userinput               = "$cmd:$tail";
@@ -6013,7 +6271,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];
@@ -6041,7 +6299,7 @@ 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);
 
 		# If the connection type is ssl, but I didn't get my
 		# certificate files yet, then I'll drop  back to 
@@ -6061,6 +6319,7 @@ sub make_new_child {
 		}
 
 		if($inittype eq "local") {
+                    $clientversion = $perlvar{'lonVersion'};
 		    my $key = LocalConnection($client, $remotereq);
 		    if($key) {
 			Debug("Got local key $key");