--- loncom/lonnet/perl/lonnet.pm	2009/08/23 03:57:20	1.1022
+++ loncom/lonnet/perl/lonnet.pm	2009/11/28 19:03:36	1.1044
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1022 2009/08/23 03:57:20 raeburn Exp $
+# $Id: lonnet.pm,v 1.1044 2009/11/28 19:03:36 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -92,6 +92,7 @@ use Time::HiRes qw( gettimeofday tv_inte
 use Cache::Memcached;
 use Digest::MD5;
 use Math::Random;
+use File::MMagic;
 use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::Configuration;
 
@@ -784,7 +785,8 @@ sub changepass {
     my ($uname,$udom,$currentpass,$newpass,$server,$context)=@_;
     $currentpass = &escape($currentpass);
     $newpass     = &escape($newpass);
-    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context",
+    my $lonhost = $perlvar{'lonHostID'};
+    my $answer = reply("encrypt:passwd:$udom:$uname:$currentpass:$newpass:$context:$lonhost",
 		       $server);
     if (! $answer) {
 	&logthis("No reply on password change request to $server ".
@@ -809,6 +811,9 @@ sub changepass {
     } elsif ($answer =~ "^refused") {
 	&logthis("$server refused to change $uname in $udom password because ".
 		 "it was sent an unencrypted request to change the password.");
+    } elsif ($answer =~ "invalid_client") {
+        &logthis("$server refused to change $uname in $udom password because ".
+                 "it was a reset by e-mail originating from an invalid server.");
     }
     return $answer;
 }
@@ -958,44 +963,21 @@ sub idput {
     }
 }
 
-# ------------------------------------------------ dump from domain db files
-
+# ------------------------------dump from db file owned by domainconfig user
 sub dump_dom {
-    my ($namespace,$udom,$uhome,$regexp,$range)=@_;
+    my ($namespace,$udom,$regexp,$range)=@_;
     if (!$udom) {
         $udom=$env{'user.domain'};
-        if (defined(&domain($udom,'primary'))) {
-            $uhome=&domain($udom,'primary');
-        } else {
-            undef($uhome);
-        }
-    } else {
-        if (!$uhome) {
-            if (defined(&domain($udom,'primary'))) {
-                $uhome=&domain($udom,'primary');
-            }
-        }
     }
     my %returnhash;
-    if ($udom && $uhome && ($uhome ne 'no_host')) {
-        if ($regexp) {
-            $regexp=&escape($regexp);
-        } else {
-            $regexp='.';
-        }
-        my $rep=&reply("dumpdom:$udom:$namespace:$regexp:$range",$uhome);
-        my @pairs=split(/\&/,$rep);
-        foreach my $item (@pairs) {
-            my ($key,$value)=split(/=/,$item,2);
-            $key = &unescape($key);
-            next if ($key =~ /^error: 2 /);
-            $returnhash{$key}=&thaw_unescape($value);
-        }
+    if ($udom) {
+        my $uname = &get_domainconfiguser($udom);
+        %returnhash = &dump($namespace,$udom,$uname,$regexp,$range);
     }
     return %returnhash;
 }
 
-# ------------------------------------------- get items from domain db files   
+# ------------------------------------------ get items from domain db files   
 
 sub get_dom {
     my ($namespace,$storearr,$udom,$uhome)=@_;
@@ -1069,70 +1051,40 @@ sub put_dom {
     }
 }
 
-# -------------------------------------- newput for items in domain db files
-
+# --------------------- newput for items in db file owned by domainconfig user
 sub newput_dom {
-    my ($namespace,$storehash,$udom,$uhome) = @_;
+    my ($namespace,$storehash,$udom) = @_;
     my $result;
     if (!$udom) {
         $udom=$env{'user.domain'};
-        if (defined(&domain($udom,'primary'))) {
-            $uhome=&domain($udom,'primary');
-        } else {
-            undef($uhome);
-        }
-    } else {
-        if (!$uhome) {
-            if (defined(&domain($udom,'primary'))) {
-                $uhome=&domain($udom,'primary');
-            }
-        }
     }
-    if ($udom && $uhome && ($uhome ne 'no_host')) {
-        my $items='';
-        if (ref($storehash) eq 'HASH') {
-            foreach my $key (keys(%$storehash)) {
-                $items.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&';
-            }
-            $items=~s/\&$//;
-            $result = &reply("newputdom:$udom:$namespace:$items",$uhome);
-        }
-    } else {
-        &logthis("put_dom failed - no homeserver and/or domain");
+    if ($udom) {
+        my $uname = &get_domainconfiguser($udom);
+        $result = &newput($namespace,$storehash,$udom,$uname);
     }
     return $result;
 }
 
+# --------------------- delete for items in db file owned by domainconfig user
 sub del_dom {
-    my ($namespace,$storearr,$udom,$uhome)=@_;
+    my ($namespace,$storearr,$udom)=@_;
     if (ref($storearr) eq 'ARRAY') {
-        my $items='';
-        foreach my $item (@$storearr) {
-            $items.=&escape($item).'&';
-        }
-        $items=~s/\&$//;
         if (!$udom) {
             $udom=$env{'user.domain'};
-            if (defined(&domain($udom,'primary'))) {
-                $uhome=&domain($udom,'primary');
-            } else {
-                undef($uhome);
-            }
-        } else {
-            if (!$uhome) {
-                if (defined(&domain($udom,'primary'))) {
-                    $uhome=&domain($udom,'primary');
-                }
-            }
         }
-        if ($udom && $uhome && ($uhome ne 'no_host')) {
-            return &reply("deldom:$udom:$namespace:$items",$uhome);
-        } else {
-            &logthis("del_dom failed - no homeserver and/or domain");
+        if ($udom) {
+            my $uname = &get_domainconfiguser($udom); 
+            return &del($namespace,$storearr,$udom,$uname);
         }
     }
 }
 
+# ----------------------------------construct domainconfig user for a domain 
+sub get_domainconfiguser {
+    my ($udom) = @_;
+    return $udom.'-domainconfig';
+}
+
 sub retrieve_inst_usertypes {
     my ($udom) = @_;
     my (%returnhash,@order);
@@ -2112,9 +2064,13 @@ sub process_coursefile {
             print $fh $env{'form.'.$source};
             close($fh);
             if ($parser eq 'parse') {
-                my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase);
-                unless ($parse_result eq 'ok') {
-                    &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result);
+                my $mm = new File::MMagic;
+                my $mime_type = $mm->checktype_filename($filepath.'/'.$fname);
+                if ($mime_type eq 'text/html') {
+                    my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase);
+                    unless ($parse_result eq 'ok') {
+                        &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result);
+                    }
                 }
             }
             $fetchresult= &reply('fetchuserfile:'.$docudom.'/'.$docuname.'/'.$file,
@@ -2356,11 +2312,15 @@ sub finishuserfileupload {
 	}
     }
     if ($parser eq 'parse') {
-        my $parse_result = &extract_embedded_items($filepath.'/'.$file,$allfiles,
-						   $codebase);
-        unless ($parse_result eq 'ok') {
-            &logthis('Failed to parse '.$filepath.$file.
-		     ' for embedded media: '.$parse_result); 
+        my $mm = new File::MMagic;
+        my $mime_type = $mm->checktype_filename($filepath.'/'.$file);
+        if ($mime_type eq 'text/html') {
+            my $parse_result = &extract_embedded_items($filepath.'/'.$file,
+                                                       $allfiles,$codebase);
+            unless ($parse_result eq 'ok') {
+                &logthis('Failed to parse '.$filepath.$file.
+	   	         ' for embedded media: '.$parse_result); 
+            }
         }
     }
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
@@ -2769,7 +2729,7 @@ sub userrolelog {
     if (($trole=~/^ca/) || ($trole=~/^aa/) ||
         ($trole=~/^in/) || ($trole=~/^cc/) ||
         ($trole=~/^ep/) || ($trole=~/^cr/) ||
-        ($trole=~/^ta/)) {
+        ($trole=~/^ta/) || ($trole=~/^co/)) {
        my (undef,$rudom,$runame,$rsec)=split(/\//,$area);
        $userrolehash
          {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec}
@@ -2778,7 +2738,8 @@ sub userrolelog {
     if (($env{'request.role'} =~ /dc\./) &&
 	(($trole=~/^au/) || ($trole=~/^in/) ||
 	 ($trole=~/^cc/) || ($trole=~/^ep/) ||
-	 ($trole=~/^cr/) || ($trole=~/^ta/))) {
+	 ($trole=~/^cr/) || ($trole=~/^ta/) ||
+         ($trole=~/^co/))) {
        $userrolehash
          {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'}
                     =$tend.':'.$tstart;
@@ -2799,7 +2760,8 @@ sub courserolelog {
     if (($trole eq 'cc') || ($trole eq 'in') ||
         ($trole eq 'ep') || ($trole eq 'ad') ||
         ($trole eq 'ta') || ($trole eq 'st') ||
-        ($trole=~/^cr/) || ($trole eq 'gr')) {
+        ($trole=~/^cr/) || ($trole eq 'gr') ||
+        ($trole eq 'co')) {
         if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) {
             my $cdom = $1;
             my $cnum = $2;
@@ -3071,7 +3033,8 @@ sub courseidput {
 sub courseiddump {
     my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,
         $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok,
-        $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone,$cloneonly)=@_;
+        $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone,
+        $cloneonly,$createdbefore,$createdafter,$creationcontext)=@_;
     my $as_hash = 1;
     my %returnhash;
     if (!$domfilter) { $domfilter=''; }
@@ -3091,7 +3054,9 @@ sub courseiddump {
                          ':'.&escape($regexp_ok).':'.$as_hash.':'.
                          &escape($selfenrollonly).':'.&escape($catfilter).':'.
                          $showhidden.':'.$caller.':'.&escape($cloner).':'.
-                         &escape($cc_clone).':'.$cloneonly,$tryserver);
+                         &escape($cc_clone).':'.$cloneonly.':'.
+                         &escape($createdbefore).':'.&escape($createdafter).':'.
+                         &escape($creationcontext),$tryserver);
                 my @pairs=split(/\&/,$rep);
                 foreach my $item (@pairs) {
                     my ($key,$value)=split(/\=/,$item,2);
@@ -3839,7 +3804,10 @@ sub privileged {
     my ($username,$domain)=@_;
     my $rolesdump=&reply("dump:$domain:$username:roles",
 			&homeserver($username,$domain));
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return 0; }
+    if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
+        ($rolesdump =~ /^error:/)) {
+        return 0;
+    }
     my $now=time;
     if ($rolesdump ne '') {
         foreach my $entry (split(/&/,$rolesdump)) {
@@ -3867,13 +3835,15 @@ sub privileged {
 
 sub rolesinit {
     my ($domain,$username,$authhost)=@_;
-    my %userroles;
+    my $now=time;
+    my %userroles = ('user.login.time' => $now);
     my $rolesdump=reply("dump:$domain:$username:roles",$authhost);
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return \%userroles; }
+    if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
+        ($rolesdump =~ /^error:/)) { 
+        return \%userroles;
+    }
     my %allroles=();
     my %allgroups=();   
-    my $now=time;
-    %userroles = ('user.login.time' => $now);
     my $group_privs;
 
     if ($rolesdump ne '') {
@@ -3939,6 +3909,9 @@ sub custom_roleprivs {
         if (($rdummy ne 'con_lost') && ($roledef ne '')) {
             my ($syspriv,$dompriv,$coursepriv)=split(/\_/,$roledef);
             if (defined($syspriv)) {
+                if ($trest =~ /^$match_community$/) {
+                    $syspriv =~ s/bre\&S//; 
+                }
                 $$allroles{'cm./'}.=':'.$syspriv;
                 $$allroles{$spec.'./'}.=':'.$syspriv;
             }
@@ -4047,8 +4020,8 @@ sub role_status {
             $$tstatus='is';
             if ($$tstart && $$tstart>$then) {
                 $$tstatus='future';
-                if ($$tstart && $$tstart>$refresh) {
-                    if ($$tstart<$now) {
+                if ($$tstart<$now) {
+                    if ($$tstart && $$tstart>$refresh) {
                         if (($$where ne '') && ($$role ne '')) {
                             my (%allroles,%allgroups,$group_privs);
                             my %userroles = (
@@ -4078,9 +4051,9 @@ sub role_status {
                             my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups);
                             &appenv(\%userroles,[$$role,'cm']);
                             &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
-                            $$tstatus = 'is';
                         }
                     }
+                    $$tstatus = 'is';
                 }
             }
             if ($$tend) {
@@ -4772,7 +4745,7 @@ sub usertools_access {
         $toolstatus = $env{'environment.'.$context.'.'.$tool};
         $inststatus = $env{'environment.inststatus'};
     } else {
-        my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool);
+        my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus');
         $toolstatus = $userenv{$context.'.'.$tool};
         $inststatus = $userenv{'inststatus'};
     }
@@ -4873,6 +4846,55 @@ sub is_advanced_user {
     return $is_adv;
 }
 
+sub check_can_request {
+    my ($dom,$can_request,$request_domains) = @_;
+    my $canreq = 0;
+    my ($types,$typename) = &Apache::loncommon::course_types();
+    my @options = ('approval','validate','autolimit');
+    my $optregex = join('|',@options);
+    if ((ref($can_request) eq 'HASH') && (ref($types) eq 'ARRAY')) {
+        foreach my $type (@{$types}) {
+            if (&usertools_access($env{'user.name'},
+                                  $env{'user.domain'},
+                                  $type,undef,'requestcourses')) {
+                $canreq ++;
+                if (ref($request_domains) eq 'HASH') {
+                    push(@{$request_domains->{$type}},$env{'user.domain'});
+                }
+                if ($dom eq $env{'user.domain'}) {
+                    $can_request->{$type} = 1;
+                }
+            }
+            if ($env{'environment.reqcrsotherdom.'.$type} ne '') {
+                my @curr = split(',',$env{'environment.reqcrsotherdom.'.$type});
+                if (@curr > 0) {
+                    foreach my $item (@curr) {
+                        if (ref($request_domains) eq 'HASH') {
+                            my ($otherdom) = ($item =~ /^($match_domain):($optregex)(=?\d*)$/);
+                            if ($otherdom ne '') {
+                                if (ref($request_domains->{$type}) eq 'ARRAY') {
+                                    unless (grep(/^\Q$otherdom\E$/,@{$request_domains->{$type}})) {
+                                        push(@{$request_domains->{$type}},$otherdom);
+                                    }
+                                } else {
+                                    push(@{$request_domains->{$type}},$otherdom);
+                                }
+                            }
+                        }
+                    }
+                    unless($dom eq $env{'user.domain'}) {
+                        $canreq ++;
+                        if (grep(/^\Q$dom\E:($optregex)(=?\d*)$/,@curr)) {
+                            $can_request->{$type} = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return $canreq;
+}
+
 # ---------------------------------------------- Custom access rule evaluation
 
 sub customaccess {
@@ -5027,17 +5049,68 @@ sub allowed {
     my $statecond=0;
     my $courseprivid='';
 
+    my $ownaccess;
+    # Community Coordinator or Assistant Co-author browsing resource space.
+    if (($priv eq 'bro') && ($env{'user.author'})) {
+        if ($uri eq '') {
+            $ownaccess = 1;
+        } else {
+            if (($env{'user.domain'} ne '') && ($env{'user.name'} ne '')) {
+                my $udom = $env{'user.domain'};
+                my $uname = $env{'user.name'};
+                if ($uri =~ m{^\Q$udom\E/?$}) {
+                    $ownaccess = 1;
+                } elsif ($uri =~ m{^\Q$udom\E/\Q$uname\E/?}) {
+                    unless ($uri =~ m{\.\./}) {
+                        $ownaccess = 1;
+                    }
+                } elsif (($udom ne 'public') && ($uname ne 'public')) {
+                    my $now = time;
+                    if ($uri =~ m{^([^/]+)/?$}) {
+                        my $adom = $1;
+                        foreach my $key (keys(%env)) {
+                            if ($key =~ m{^user\.role\.(ca|aa)/\Q$adom\E}) {
+                                my ($start,$end) = split('.',$env{$key});
+                                if (($now >= $start) && (!$end || $end < $now)) {
+                                    $ownaccess = 1;
+                                    last;
+                                }
+                            }
+                        }
+                    } elsif ($uri =~ m{^([^/]+)/([^/]+)/?}) {
+                        my $adom = $1;
+                        my $aname = $2;
+                        foreach my $role ('ca','aa') { 
+                            if ($env{"user.role.$role./$adom/$aname"}) {
+                                my ($start,$end) =
+                                    split('.',$env{"user.role.$role./$adom/$aname"});
+                                if (($now >= $start) && (!$end || $end < $now)) {
+                                    $ownaccess = 1;
+                                    last;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
 # Course
 
     if ($env{'user.priv.'.$env{'request.role'}.'./'}=~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
+        unless (($priv eq 'bro') && (!$ownaccess)) {
+            $thisallowed.=$1;
+        }
     }
 
 # Domain
 
     if ($env{'user.priv.'.$env{'request.role'}.'./'.(split(/\//,$uri))[0].'/'}
        =~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
+        unless (($priv eq 'bro') && (!$ownaccess)) {
+            $thisallowed.=$1;
+        }
     }
 
 # Course: uri itself is a course
@@ -5047,7 +5120,9 @@ sub allowed {
 
     if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseuri}
        =~/\Q$priv\E\&([^\:]*)/) {
-       $thisallowed.=$1;
+        unless (($priv eq 'bro') && (!$ownaccess)) {
+            $thisallowed.=$1;
+        }
     }
 
 # URI is an uploaded document for this course, default permissions don't matter
@@ -5657,7 +5732,8 @@ sub auto_validate_instcode {
     }
     my $response=&unescape(&reply('autovalidateinstcode:'.$cdom.':'.
                            &escape($instcode).':'.&escape($owner),$homeserver));
-    return $response;
+    my ($outcome,$description) = map { &unescape($_); } split('&',$response,2);
+    return ($outcome,$description);
 }
 
 sub auto_create_password {
@@ -6072,24 +6148,31 @@ sub plaintext {
     if (!defined($cid)) {
         $cid = $env{'request.course.id'};
     }
-    if (defined($cid) && ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '')) {
-        unless ($forcedefault) {
-            my $roletext = $env{'course.'.$cid.'.'.$short.'.plaintext'}; 
-            &Apache::lonlocal::mt_escape(\$roletext);
-            return &Apache::lonlocal::mt($roletext);
-        }
-    }
     my %rolenames = (
                       Course    => 'std',
                       Community => 'alt1',
                     );
-    if (defined($type) && 
-         defined($rolenames{$type}) && 
-         defined($prp{$short}{$rolenames{$type}})) {
+    if ($cid ne '') {
+        if ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '') {
+            unless ($forcedefault) {
+                my $roletext = $env{'course.'.$cid.'.'.$short.'.plaintext'}; 
+                &Apache::lonlocal::mt_escape(\$roletext);
+                return &Apache::lonlocal::mt($roletext);
+            }
+        }
+    }
+    if ((defined($type)) && (defined($rolenames{$type})) &&
+        (defined($rolenames{$type})) && 
+        (defined($prp{$short}{$rolenames{$type}}))) {
         return &Apache::lonlocal::mt($prp{$short}{$rolenames{$type}});
-    } else {
-        return &Apache::lonlocal::mt($prp{$short}{'std'});
+    } elsif ($cid ne '') {
+        my $crstype = $env{'course.'.$cid.'.type'};
+        if (($crstype ne '') && (defined($rolenames{$crstype})) &&
+            (defined($prp{$short}{$rolenames{$crstype}}))) {
+            return &Apache::lonlocal::mt($prp{$short}{$rolenames{$crstype}});
+        }
     }
+    return &Apache::lonlocal::mt($prp{$short}{'std'});
 }
 
 # ----------------------------------------------------------------- Assign Role
@@ -6102,10 +6185,27 @@ sub assignrole {
         my $cwosec=$url;
         $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
 	unless (&allowed('ccr',$cwosec)) {
-           &logthis('Refused custom assignrole: '.
-             $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
-		    $env{'user.name'}.' at '.$env{'user.domain'});
-           return 'refused'; 
+           my $refused = 1;
+           if ($context eq 'requestcourses') {
+               if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
+                   if ($role =~ m{^cr/($match_domain)/($match_username)/([^/]+)$}) {
+                       if (($1 eq $env{'user.domain'}) && ($2 eq $env{'user.name'})) {
+                           my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
+                           my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                           if ($crsenv{'internal.courseowner'} eq
+                               $env{'user.name'}.':'.$env{'user.domain'}) {
+                               $refused = '';
+                           }
+                       }
+                   }
+               }
+           }
+           if ($refused) {
+               &logthis('Refused custom assignrole: '.
+                        $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.
+                        ' by '.$env{'user.name'}.' at '.$env{'user.domain'});
+               return 'refused';
+           }
         }
         $mrole='cr';
     } elsif ($role =~ /^gr\//) {
@@ -6134,12 +6234,21 @@ sub assignrole {
                 if (($selfenroll == 1) && ($role eq 'st') && ($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
                     $refused = '';
                 } elsif ($context eq 'requestcourses') {
-                    if (($role eq 'cc') && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) {
-                        my ($cdom,$cnum) = ($cwosec =~ m{^/($match_domain)/($match_courseid)$});
-                        my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
-                        if ($crsenv{'internal.courseowner'} eq 
-                             $env{'user.name'}.':'.$env{'user.domain'}) {
-                            $refused = '';
+                    my @possroles = ('st','ta','ep','in','cc','co');
+                    if ((grep(/^\Q$role\E$/,@possroles)) && ($env{'user.name'} ne '' && $env{'user.domain'} ne '')) {
+                        my ($cdom,$cnum) = ($cwosec =~ m{^/?($match_domain)/($match_courseid)$});
+                        my $wrongcc;
+                        if ($cnum =~ /^$match_community$/) {
+                            $wrongcc = 1 if ($role eq 'cc');
+                        } else {
+                            $wrongcc = 1 if ($role eq 'co');
+                        }
+                        unless ($wrongcc) {
+                            my %crsenv = &userenvironment($cdom,$cnum,('internal.courseowner'));
+                            if ($crsenv{'internal.courseowner'} eq 
+                                 $env{'user.name'}.':'.$env{'user.domain'}) {
+                                $refused = '';
+                            }
                         }
                     }
                 }
@@ -6470,14 +6579,39 @@ sub createcourse {
         $course_owner,$crstype,$cnum,$context,$category)=@_;
     $url=&declutter($url);
     my $cid='';
-    unless (&allowed('ccc',$udom)) {
-        if ($context eq 'requestcourses') {
-            unless (&usertools_access($course_owner,$udom,$category,undef,$context)) {
-                return 'refused';
+    if ($context eq 'requestcourses') {
+        my $can_create = 0;
+        my ($ownername,$ownerdom) = split(':',$course_owner);
+        if ($udom eq $ownerdom) {
+            if (&usertools_access($ownername,$ownerdom,$category,undef,
+                                  $context)) {
+                $can_create = 1;
+            }
+        } else {
+            my %userenv = &userenvironment($ownerdom,$ownername,'reqcrsotherdom.'.
+                                           $category);
+            if ($userenv{'reqcrsotherdom.'.$category} ne '') {
+                my @curr = split(',',$userenv{'reqcrsotherdom.'.$category});
+                if (@curr > 0) {
+                    my @options = qw(approval validate autolimit);
+                    my $optregex = join('|',@options);
+                    if (grep(/^\Q$udom\E:($optregex)(=?\d*)$/,@curr)) {
+                        $can_create = 1;
+                    }
+                }
+            }
+        }
+        if ($can_create) {
+            unless ($ownername eq $env{'user.name'} && $ownerdom eq $env{'user.domain'}) {
+                unless (&allowed('ccc',$udom)) {
+                    return 'refused'; 
+                }
             }
         } else {
             return 'refused';
         }
+    } elsif (!&allowed('ccc',$udom)) {
+        return 'refused';
     }
 # --------------------------------------------------------------- Get Unique ID
     my $uname;
@@ -6486,10 +6620,10 @@ sub createcourse {
         if (($chome eq '') || ($chome eq 'no_host')) {
             $uname = $cnum;
         } else {
-            $uname = &generate_coursenum($udom);
+            $uname = &generate_coursenum($udom,$crstype);
         }
     } else {
-        $uname = &generate_coursenum($udom);
+        $uname = &generate_coursenum($udom,$crstype);
     }
     return $uname if ($uname =~ /^error/);
 # -------------------------------------------------- Check supplied server name
@@ -6507,12 +6641,17 @@ sub createcourse {
     }
 # ----------------------------------------------------------------- Course made
 # log existence
+    my $now = time;
     my $newcourse = {
                     $udom.'_'.$uname => {
                                      description => $description,
                                      inst_code   => $inst_code,
                                      owner       => $course_owner,
                                      type        => $crstype,
+                                     creator     => $env{'user.name'}.':'.
+                                                    $env{'user.domain'},
+                                     created     => $now,
+                                     context     => $context,
                                                 },
                     };
     &courseidput($udom,$newcourse,$uhome,'notime');
@@ -6544,17 +6683,28 @@ ENDINITMAP
 
 # ------------------------------------------------------------------- Create ID
 sub generate_coursenum {
-    my ($udom) = @_;
+    my ($udom,$crstype) = @_;
     my $domdesc = &domain($udom);
     return 'error: invalid domain' if ($domdesc eq '');
-    my $uname=int(1+rand(9)).
+    my $first;
+    if ($crstype eq 'Community') {
+        $first = '0';
+    } else {
+        $first = int(1+rand(9)); 
+    } 
+    my $uname=$first.
         ('a'..'z','A'..'Z','0'..'9')[int(rand(62))].
         substr($$.time,0,5).unpack("H8",pack("I32",time)).
         unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'};
 # ----------------------------------------------- Make sure that does not exist
     my $uhome=&homeserver($uname,$udom,'true');
     unless (($uhome eq '') || ($uhome eq 'no_host')) {
-        $uname=int(1+rand(9)).
+        if ($crstype eq 'Community') {
+            $first = '0';
+        } else {
+            $first = int(1+rand(9));
+        }
+        $uname=$first.
                ('a'..'z','A'..'Z','0'..'9')[int(rand(62))].
                substr($$.time,0,5).unpack("H8",pack("I32",time)).
                unpack("H2",pack("I32",int(rand(255)))).$perlvar{'lonHostID'};
@@ -8206,6 +8356,9 @@ sub symbverify {
 
     if (tie(%bighash,'GDBM_File',$env{'request.course.fn'}.'.db',
                             &GDBM_READER(),0640)) {
+        if (($thisurl =~ m{^/adm/wrapper/ext/}) || ($thisurl =~ m{^ext/})) {
+            $thisurl =~ s/\?.+$//;
+        }
         my $ids=$bighash{'ids_'.&clutter($thisurl)};
         unless ($ids) { 
            $ids=$bighash{'ids_/'.$thisurl};
@@ -8214,6 +8367,9 @@ sub symbverify {
 # ------------------------------------------------------------------- Has ID(s)
 	    foreach my $id (split(/\,/,$ids)) {
 	       my ($mapid,$resid)=split(/\./,$id);
+               if ($thisfn =~ m{^/adm/wrapper/ext/}) {
+                   $symb =~ s/\?.+$//;
+               }
                if (
   &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn)
    eq $symb) { 
@@ -9090,7 +9246,9 @@ sub declutter {
     $thisfn=~s|^adm/wrapper/||;
     $thisfn=~s|^adm/coursedocs/showdoc/||;
     $thisfn=~s/^res\///;
-    $thisfn=~s/\?.+$//;
+    unless (($thisfn =~ /^ext/) || ($thisfn =~ /\.(page|sequence)___\d+___ext/)) {
+        $thisfn=~s/\?.+$//;
+    }
     return $thisfn;
 }
 
@@ -9102,8 +9260,8 @@ sub clutter {
 	|| $thisfn =~ m{^/adm/(includes|pages)} ) { 
        $thisfn='/res'.$thisfn; 
     }
-    if ($thisfn !~m|/adm|) {
-	if ($thisfn =~ m|/ext/|) {
+    if ($thisfn !~m|^/adm|) {
+	if ($thisfn =~ m|^/ext/|) {
 	    $thisfn='/adm/wrapper'.$thisfn;
 	} else {
 	    my ($ext) = ($thisfn =~ /\.(\w+)$/);
@@ -10130,7 +10288,7 @@ createcourse($udom,$description,$url,$co
 
 =item *
 
-generate_coursenum($udom) : get a unique (unused) course number in domain $udom
+generate_coursenum($udom,$crstype) : get a unique (unused) course number in domain $udom for course type $crstype (Course or Community).
 
 =back