Annotation of loncom/lond, revision 1.489.2.35.2.3

1.1       albertel    1: #!/usr/bin/perl
                      2: # The LearningOnline Network
                      3: # lond "LON Daemon" Server (port "LOND" 5663)
1.60      www         4: #
1.489.2.35.2.  (raeburn    5:): # $Id: lond,v 1.489.2.35.2.2 2020/07/19 19:02:39 raeburn Exp $
1.60      www         6: #
                      7: # Copyright Michigan State University Board of Trustees
                      8: #
                      9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                     10: #
                     11: # LON-CAPA is free software; you can redistribute it and/or modify
                     12: # it under the terms of the GNU General Public License as published by
1.167     foxr       13: # the Free Software Foundation; either version 2 of the License, or 
1.60      www        14: # (at your option) any later version.
                     15: #
                     16: # LON-CAPA is distributed in the hope that it will be useful,
                     17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     19: # GNU General Public License for more details.
                     20: #
                     21: # You should have received a copy of the GNU General Public License
                     22: # along with LON-CAPA; if not, write to the Free Software
1.178     foxr       23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
1.60      www        24: #
                     25: # /home/httpd/html/adm/gpl.txt
                     26: #
1.161     foxr       27: 
                     28: 
1.60      www        29: # http://www.lon-capa.org/
                     30: #
1.54      harris41   31: 
1.134     albertel   32: use strict;
1.80      harris41   33: use lib '/home/httpd/lib/perl/';
1.325     albertel   34: use LONCAPA;
1.80      harris41   35: use LONCAPA::Configuration;
1.489.2.4  raeburn    36: use LONCAPA::Lond;
1.80      harris41   37: 
1.1       albertel   38: use IO::Socket;
                     39: use IO::File;
1.126     albertel   40: #use Apache::File;
1.1       albertel   41: use POSIX;
                     42: use Crypt::IDEA;
                     43: use LWP::UserAgent();
1.347     raeburn    44: use Digest::MD5 qw(md5_hex);
1.3       www        45: use GDBM_File;
1.91      albertel   46: use Authen::Krb5;
1.49      albertel   47: use localauth;
1.193     raeburn    48: use localenroll;
1.265     albertel   49: use localstudentphoto;
1.143     foxr       50: use File::Copy;
1.292     albertel   51: use File::Find;
1.200     matthew    52: use LONCAPA::lonlocal;
                     53: use LONCAPA::lonssl;
1.221     albertel   54: use Fcntl qw(:flock);
1.383     raeburn    55: use Apache::lonnet;
1.472     raeburn    56: use Mail::Send;
1.489.2.21  raeburn    57: use Crypt::Eksblowfish::Bcrypt;
                     58: use Digest::SHA;
                     59: use Encode;
1.1       albertel   60: 
1.463     foxr       61: my $DEBUG = 0;		       # Non zero to enable debug log entries.
1.77      foxr       62: 
1.57      www        63: my $status='';
                     64: my $lastlog='';
                     65: 
1.489.2.35.2.  (raeburn   66:): my $VERSION='$Revision: 1.489.2.35.2.2 $'; #' stupid emacs
1.121     albertel   67: my $remoteVERSION;
1.214     foxr       68: my $currenthostid="default";
1.115     albertel   69: my $currentdomainid;
1.134     albertel   70: 
                     71: my $client;
1.200     matthew    72: my $clientip;			# IP address of client.
                     73: my $clientname;			# LonCAPA name of client.
1.448     raeburn    74: my $clientversion;              # LonCAPA version running on client.
                     75: my $clienthomedom;              # LonCAPA domain of homeID for client. 
                     76:                                 # primary library server. 
1.140     foxr       77: 
1.134     albertel   78: my $server;
1.200     matthew    79: 
                     80: my $keymode;
1.198     foxr       81: 
1.207     foxr       82: my $cipher;			# Cipher key negotiated with client
                     83: my $tmpsnum = 0;		# Id of tmpputs.
                     84: 
1.178     foxr       85: # 
                     86: #   Connection type is:
                     87: #      client                   - All client actions are allowed
                     88: #      manager                  - only management functions allowed.
                     89: #      both                     - Both management and client actions are allowed
                     90: #
1.161     foxr       91: 
1.178     foxr       92: my $ConnectionType;
1.161     foxr       93: 
1.178     foxr       94: my %managers;			# Ip -> manager names
1.161     foxr       95: 
1.178     foxr       96: my %perlvar;			# Will have the apache conf defined perl vars.
1.134     albertel   97: 
1.480     raeburn    98: my $dist;
                     99: 
1.178     foxr      100: #
1.207     foxr      101: #   The hash below is used for command dispatching, and is therefore keyed on the request keyword.
                    102: #    Each element of the hash contains a reference to an array that contains:
                    103: #          A reference to a sub that executes the request corresponding to the keyword.
                    104: #          A flag that is true if the request must be encoded to be acceptable.
                    105: #          A mask with bits as follows:
                    106: #                      CLIENT_OK    - Set when the function is allowed by ordinary clients
                    107: #                      MANAGER_OK   - Set when the function is allowed to manager clients.
                    108: #
                    109: my $CLIENT_OK  = 1;
                    110: my $MANAGER_OK = 2;
                    111: my %Dispatcher;
                    112: 
                    113: 
                    114: #
1.178     foxr      115: #  The array below are password error strings."
                    116: #
                    117: my $lastpwderror    = 13;		# Largest error number from lcpasswd.
                    118: my @passwderrors = ("ok",
1.287     foxr      119: 		   "pwchange_failure - lcpasswd must be run as user 'www'",
                    120: 		   "pwchange_failure - lcpasswd got incorrect number of arguments",
                    121: 		   "pwchange_failure - lcpasswd did not get the right nubmer of input text lines",
                    122: 		   "pwchange_failure - lcpasswd too many simultaneous pwd changes in progress",
                    123: 		   "pwchange_failure - lcpasswd User does not exist.",
                    124: 		   "pwchange_failure - lcpasswd Incorrect current passwd",
                    125: 		   "pwchange_failure - lcpasswd Unable to su to root.",
                    126: 		   "pwchange_failure - lcpasswd Cannot set new passwd.",
                    127: 		   "pwchange_failure - lcpasswd Username has invalid characters",
                    128: 		   "pwchange_failure - lcpasswd Invalid characters in password",
                    129: 		   "pwchange_failure - lcpasswd User already exists", 
                    130:                    "pwchange_failure - lcpasswd Something went wrong with user addition.",
                    131: 		   "pwchange_failure - lcpasswd Password mismatch",
                    132: 		   "pwchange_failure - lcpasswd Error filename is invalid");
1.97      foxr      133: 
                    134: 
1.412     foxr      135: # This array are the errors from lcinstallfile:
                    136: 
                    137: my @installerrors = ("ok",
                    138: 		     "Initial user id of client not that of www",
                    139: 		     "Usage error, not enough command line arguments",
1.489.2.5  raeburn   140: 		     "Source filename does not exist",
                    141: 		     "Destination filename does not exist",
1.412     foxr      142: 		     "Some file operation failed",
                    143: 		     "Invalid table filename."
                    144: 		     );
1.207     foxr      145: 
                    146: #
                    147: #   Statistics that are maintained and dislayed in the status line.
                    148: #
1.212     foxr      149: my $Transactions = 0;		# Number of attempted transactions.
                    150: my $Failures     = 0;		# Number of transcations failed.
1.207     foxr      151: 
                    152: #   ResetStatistics: 
                    153: #      Resets the statistics counters:
                    154: #
                    155: sub ResetStatistics {
                    156:     $Transactions = 0;
                    157:     $Failures     = 0;
                    158: }
                    159: 
1.200     matthew   160: #------------------------------------------------------------------------
                    161: #
                    162: #   LocalConnection
                    163: #     Completes the formation of a locally authenticated connection.
                    164: #     This function will ensure that the 'remote' client is really the
                    165: #     local host.  If not, the connection is closed, and the function fails.
                    166: #     If so, initcmd is parsed for the name of a file containing the
                    167: #     IDEA session key.  The fie is opened, read, deleted and the session
                    168: #     key returned to the caller.
                    169: #
                    170: # Parameters:
                    171: #   $Socket      - Socket open on client.
                    172: #   $initcmd     - The full text of the init command.
                    173: #
                    174: # Returns:
                    175: #     IDEA session key on success.
                    176: #     undef on failure.
                    177: #
                    178: sub LocalConnection {
                    179:     my ($Socket, $initcmd) = @_;
1.373     albertel  180:     Debug("Attempting local connection: $initcmd client: $clientip");
1.277     albertel  181:     if($clientip ne "127.0.0.1") {
1.200     matthew   182: 	&logthis('<font color="red"> LocalConnection rejecting non local: '
1.373     albertel  183: 		 ."$clientip ne 127.0.0.1 </font>");
1.200     matthew   184: 	close $Socket;
                    185: 	return undef;
1.224     foxr      186:     }  else {
1.200     matthew   187: 	chomp($initcmd);	# Get rid of \n in filename.
                    188: 	my ($init, $type, $name) = split(/:/, $initcmd);
                    189: 	Debug(" Init command: $init $type $name ");
                    190: 
                    191: 	# Require that $init = init, and $type = local:  Otherwise
                    192: 	# the caller is insane:
                    193: 
                    194: 	if(($init ne "init") && ($type ne "local")) {
                    195: 	    &logthis('<font color = "red"> LocalConnection: caller is insane! '
                    196: 		     ."init = $init, and type = $type </font>");
                    197: 	    close($Socket);;
                    198: 	    return undef;
                    199: 		
                    200: 	}
                    201: 	#  Now get the key filename:
                    202: 
                    203: 	my $IDEAKey = lonlocal::ReadKeyFile($name);
                    204: 	return $IDEAKey;
                    205:     }
                    206: }
                    207: #------------------------------------------------------------------------------
                    208: #
                    209: #  SSLConnection
                    210: #   Completes the formation of an ssh authenticated connection. The
                    211: #   socket is promoted to an ssl socket.  If this promotion and the associated
                    212: #   certificate exchange are successful, the IDEA key is generated and sent
                    213: #   to the remote peer via the SSL tunnel. The IDEA key is also returned to
                    214: #   the caller after the SSL tunnel is torn down.
                    215: #
                    216: # Parameters:
                    217: #   Name              Type             Purpose
                    218: #   $Socket          IO::Socket::INET  Plaintext socket.
                    219: #
                    220: # Returns:
                    221: #    IDEA key on success.
                    222: #    undef on failure.
                    223: #
                    224: sub SSLConnection {
                    225:     my $Socket   = shift;
                    226: 
                    227:     Debug("SSLConnection: ");
                    228:     my $KeyFile         = lonssl::KeyFile();
                    229:     if(!$KeyFile) {
                    230: 	my $err = lonssl::LastError();
                    231: 	&logthis("<font color=\"red\"> CRITICAL"
                    232: 		 ."Can't get key file $err </font>");
                    233: 	return undef;
                    234:     }
                    235:     my ($CACertificate,
                    236: 	$Certificate) = lonssl::CertificateFile();
                    237: 
                    238: 
                    239:     # If any of the key, certificate or certificate authority 
                    240:     # certificate filenames are not defined, this can't work.
                    241: 
                    242:     if((!$Certificate) || (!$CACertificate)) {
                    243: 	my $err = lonssl::LastError();
                    244: 	&logthis("<font color=\"red\"> CRITICAL"
                    245: 		 ."Can't get certificates: $err </font>");
                    246: 
                    247: 	return undef;
                    248:     }
                    249:     Debug("Key: $KeyFile CA: $CACertificate Cert: $Certificate");
                    250: 
                    251:     # Indicate to our peer that we can procede with
                    252:     # a transition to ssl authentication:
                    253: 
                    254:     print $Socket "ok:ssl\n";
                    255: 
                    256:     Debug("Approving promotion -> ssl");
                    257:     #  And do so:
                    258: 
                    259:     my $SSLSocket = lonssl::PromoteServerSocket($Socket,
                    260: 						$CACertificate,
                    261: 						$Certificate,
                    262: 						$KeyFile);
                    263:     if(! ($SSLSocket) ) {	# SSL socket promotion failed.
                    264: 	my $err = lonssl::LastError();
                    265: 	&logthis("<font color=\"red\"> CRITICAL "
                    266: 		 ."SSL Socket promotion failed: $err </font>");
                    267: 	return undef;
                    268:     }
                    269:     Debug("SSL Promotion successful");
                    270: 
                    271:     # 
                    272:     #  The only thing we'll use the socket for is to send the IDEA key
                    273:     #  to the peer:
                    274: 
                    275:     my $Key = lonlocal::CreateCipherKey();
                    276:     print $SSLSocket "$Key\n";
                    277: 
                    278:     lonssl::Close($SSLSocket); 
                    279: 
                    280:     Debug("Key exchange complete: $Key");
                    281: 
                    282:     return $Key;
                    283: }
                    284: #
                    285: #     InsecureConnection: 
                    286: #        If insecure connections are allowd,
                    287: #        exchange a challenge with the client to 'validate' the
                    288: #        client (not really, but that's the protocol):
                    289: #        We produce a challenge string that's sent to the client.
                    290: #        The client must then echo the challenge verbatim to us.
                    291: #
                    292: #  Parameter:
                    293: #      Socket      - Socket open on the client.
                    294: #  Returns:
                    295: #      1           - success.
                    296: #      0           - failure (e.g.mismatch or insecure not allowed).
                    297: #
                    298: sub InsecureConnection {
                    299:     my $Socket  =  shift;
                    300: 
                    301:     #   Don't even start if insecure connections are not allowed.
                    302: 
                    303:     if(! $perlvar{londAllowInsecure}) {	# Insecure connections not allowed.
                    304: 	return 0;
                    305:     }
                    306: 
                    307:     #   Fabricate a challenge string and send it..
                    308: 
                    309:     my $challenge = "$$".time;	# pid + time.
                    310:     print $Socket "$challenge\n";
                    311:     &status("Waiting for challenge reply");
                    312: 
                    313:     my $answer = <$Socket>;
                    314:     $answer    =~s/\W//g;
                    315:     if($challenge eq $answer) {
                    316: 	return 1;
1.224     foxr      317:     } else {
1.200     matthew   318: 	logthis("<font color='blue'>WARNING client did not respond to challenge</font>");
                    319: 	&status("No challenge reqply");
                    320: 	return 0;
                    321:     }
                    322:     
                    323: 
                    324: }
1.251     foxr      325: #
                    326: #   Safely execute a command (as long as it's not a shel command and doesn
                    327: #   not require/rely on shell escapes.   The function operates by doing a
                    328: #   a pipe based fork and capturing stdout and stderr  from the pipe.
                    329: #
                    330: # Formal Parameters:
                    331: #     $line                    - A line of text to be executed as a command.
                    332: # Returns:
                    333: #     The output from that command.  If the output is multiline the caller
                    334: #     must know how to split up the output.
                    335: #
                    336: #
                    337: sub execute_command {
                    338:     my ($line)    = @_;
                    339:     my @words     = split(/\s/, $line);	# Bust the command up into words.
                    340:     my $output    = "";
                    341: 
                    342:     my $pid = open(CHILD, "-|");
                    343:     
                    344:     if($pid) {			# Parent process
                    345: 	Debug("In parent process for execute_command");
                    346: 	my @data = <CHILD>;	# Read the child's outupt...
                    347: 	close CHILD;
                    348: 	foreach my $output_line (@data) {
                    349: 	    Debug("Adding $output_line");
                    350: 	    $output .= $output_line; # Presumably has a \n on it.
                    351: 	}
                    352: 
                    353:     } else {			# Child process
                    354: 	close (STDERR);
                    355: 	open  (STDERR, ">&STDOUT");# Combine stderr, and stdout...
                    356: 	exec(@words);		# won't return.
                    357:     }
                    358:     return $output;
                    359: }
                    360: 
1.200     matthew   361: 
1.140     foxr      362: #   GetCertificate: Given a transaction that requires a certificate,
                    363: #   this function will extract the certificate from the transaction
                    364: #   request.  Note that at this point, the only concept of a certificate
                    365: #   is the hostname to which we are connected.
                    366: #
                    367: #   Parameter:
                    368: #      request   - The request sent by our client (this parameterization may
                    369: #                  need to change when we really use a certificate granting
                    370: #                  authority.
                    371: #
                    372: sub GetCertificate {
                    373:     my $request = shift;
                    374: 
                    375:     return $clientip;
                    376: }
1.161     foxr      377: 
1.178     foxr      378: #
                    379: #   Return true if client is a manager.
                    380: #
                    381: sub isManager {
                    382:     return (($ConnectionType eq "manager") || ($ConnectionType eq "both"));
                    383: }
                    384: #
                    385: #   Return tru if client can do client functions
                    386: #
                    387: sub isClient {
                    388:     return (($ConnectionType eq "client") || ($ConnectionType eq "both"));
                    389: }
1.161     foxr      390: 
                    391: 
1.156     foxr      392: #
                    393: #   ReadManagerTable: Reads in the current manager table. For now this is
                    394: #                     done on each manager authentication because:
                    395: #                     - These authentications are not frequent
                    396: #                     - This allows dynamic changes to the manager table
                    397: #                       without the need to signal to the lond.
                    398: #
                    399: sub ReadManagerTable {
                    400: 
1.412     foxr      401:     &Debug("Reading manager table");
1.156     foxr      402:     #   Clean out the old table first..
                    403: 
1.166     foxr      404:    foreach my $key (keys %managers) {
                    405:       delete $managers{$key};
                    406:    }
                    407: 
                    408:    my $tablename = $perlvar{'lonTabDir'}."/managers.tab";
                    409:    if (!open (MANAGERS, $tablename)) {
1.473     raeburn   410:        my $hostname = &Apache::lonnet::hostname($perlvar{'lonHostID'});
                    411:        if (&Apache::lonnet::is_LC_dns($hostname)) {
1.472     raeburn   412:            &logthis('<font color="red">No manager table.  Nobody can manage!!</font>');
                    413:        }
                    414:        return;
1.166     foxr      415:    }
                    416:    while(my $host = <MANAGERS>) {
                    417:       chomp($host);
                    418:       if ($host =~ "^#") {                  # Comment line.
                    419:          next;
                    420:       }
1.368     albertel  421:       if (!defined &Apache::lonnet::get_host_ip($host)) { # This is a non cluster member
1.161     foxr      422: 	    #  The entry is of the form:
                    423: 	    #    cluname:hostname
                    424: 	    #  cluname - A 'cluster hostname' is needed in order to negotiate
                    425: 	    #            the host key.
                    426: 	    #  hostname- The dns name of the host.
                    427: 	    #
1.166     foxr      428:           my($cluname, $dnsname) = split(/:/, $host);
                    429:           
                    430:           my $ip = gethostbyname($dnsname);
                    431:           if(defined($ip)) {                 # bad names don't deserve entry.
                    432:             my $hostip = inet_ntoa($ip);
                    433:             $managers{$hostip} = $cluname;
                    434:             logthis('<font color="green"> registering manager '.
                    435:                     "$dnsname as $cluname with $hostip </font>\n");
                    436:          }
                    437:       } else {
                    438:          logthis('<font color="green"> existing host'." $host</font>\n");
1.472     raeburn   439:          $managers{&Apache::lonnet::get_host_ip($host)} = $host;  # Use info from cluster tab if cluster memeber
1.166     foxr      440:       }
                    441:    }
1.156     foxr      442: }
1.140     foxr      443: 
                    444: #
                    445: #  ValidManager: Determines if a given certificate represents a valid manager.
                    446: #                in this primitive implementation, the 'certificate' is
                    447: #                just the connecting loncapa client name.  This is checked
                    448: #                against a valid client list in the configuration.
                    449: #
                    450: #                  
                    451: sub ValidManager {
                    452:     my $certificate = shift; 
                    453: 
1.163     foxr      454:     return isManager;
1.140     foxr      455: }
                    456: #
1.143     foxr      457: #  CopyFile:  Called as part of the process of installing a 
                    458: #             new configuration file.  This function copies an existing
                    459: #             file to a backup file.
                    460: # Parameters:
                    461: #     oldfile  - Name of the file to backup.
                    462: #     newfile  - Name of the backup file.
                    463: # Return:
                    464: #     0   - Failure (errno has failure reason).
                    465: #     1   - Success.
                    466: #
                    467: sub CopyFile {
1.192     foxr      468: 
                    469:     my ($oldfile, $newfile) = @_;
1.143     foxr      470: 
1.281     matthew   471:     if (! copy($oldfile,$newfile)) {
                    472:         return 0;
1.143     foxr      473:     }
1.281     matthew   474:     chmod(0660, $newfile);
                    475:     return 1;
1.143     foxr      476: }
1.157     foxr      477: #
                    478: #  Host files are passed out with externally visible host IPs.
                    479: #  If, for example, we are behind a fire-wall or NAT host, our 
                    480: #  internally visible IP may be different than the externally
                    481: #  visible IP.  Therefore, we always adjust the contents of the
                    482: #  host file so that the entry for ME is the IP that we believe
                    483: #  we have.  At present, this is defined as the entry that
                    484: #  DNS has for us.  If by some chance we are not able to get a
                    485: #  DNS translation for us, then we assume that the host.tab file
                    486: #  is correct.  
                    487: #    BUGBUGBUG - in the future, we really should see if we can
                    488: #       easily query the interface(s) instead.
                    489: # Parameter(s):
                    490: #     contents    - The contents of the host.tab to check.
                    491: # Returns:
                    492: #     newcontents - The adjusted contents.
                    493: #
                    494: #
                    495: sub AdjustHostContents {
                    496:     my $contents  = shift;
                    497:     my $adjusted;
                    498:     my $me        = $perlvar{'lonHostID'};
                    499: 
1.354     albertel  500:     foreach my $line (split(/\n/,$contents)) {
1.472     raeburn   501: 	if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/) ||
                    502:              ($line =~ /^\s*\^/))) {
1.157     foxr      503: 	    chomp($line);
                    504: 	    my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line);
                    505: 	    if ($id eq $me) {
1.354     albertel  506: 		my $ip = gethostbyname($name);
                    507: 		my $ipnew = inet_ntoa($ip);
                    508: 		$ip = $ipnew;
1.157     foxr      509: 		#  Reconstruct the host line and append to adjusted:
                    510: 		
1.354     albertel  511: 		my $newline = "$id:$domain:$role:$name:$ip";
                    512: 		if($maxcon ne "") { # Not all hosts have loncnew tuning params
                    513: 		    $newline .= ":$maxcon:$idleto:$mincon";
                    514: 		}
                    515: 		$adjusted .= $newline."\n";
1.157     foxr      516: 		
1.354     albertel  517: 	    } else {		# Not me, pass unmodified.
                    518: 		$adjusted .= $line."\n";
                    519: 	    }
1.157     foxr      520: 	} else {                  # Blank or comment never re-written.
                    521: 	    $adjusted .= $line."\n";	# Pass blanks and comments as is.
                    522: 	}
1.354     albertel  523:     }
                    524:     return $adjusted;
1.157     foxr      525: }
1.143     foxr      526: #
                    527: #   InstallFile: Called to install an administrative file:
1.412     foxr      528: #       - The file is created int a temp directory called <name>.tmp
                    529: #       - lcinstall file is called to install the file.
                    530: #         since the web app has no direct write access to the table directory
1.143     foxr      531: #
                    532: #  Parameters:
                    533: #       Name of the file
                    534: #       File Contents.
                    535: #  Return:
                    536: #      nonzero - success.
                    537: #      0       - failure and $! has an errno.
1.412     foxr      538: # Assumptions:
                    539: #    File installtion is a relatively infrequent
1.143     foxr      540: #
                    541: sub InstallFile {
1.192     foxr      542: 
                    543:     my ($Filename, $Contents) = @_;
1.412     foxr      544: #     my $TempFile = $Filename.".tmp";
                    545:     my $exedir = $perlvar{'lonDaemons'};
                    546:     my $tmpdir = $exedir.'/tmp/';
                    547:     my $TempFile = $tmpdir."TempTableFile.tmp";
1.143     foxr      548: 
                    549:     #  Open the file for write:
                    550: 
                    551:     my $fh = IO::File->new("> $TempFile"); # Write to temp.
                    552:     if(!(defined $fh)) {
                    553: 	&logthis('<font color="red"> Unable to create '.$TempFile."</font>");
                    554: 	return 0;
                    555:     }
                    556:     #  write the contents of the file:
                    557: 
                    558:     print $fh ($Contents); 
                    559:     $fh->close;			# In case we ever have a filesystem w. locking
                    560: 
1.412     foxr      561:     chmod(0664, $TempFile);	# Everyone can write it.
                    562: 
                    563:     # Use lcinstall file to put the file in the table directory...
                    564: 
                    565:     &Debug("Opening pipe to $exedir/lcinstallfile $TempFile $Filename");
                    566:     my $pf = IO::File->new("| $exedir/lcinstallfile   $TempFile $Filename > $exedir/logs/lcinstallfile.log");
                    567:     close $pf;
                    568:     my $err = $?;
                    569:     &Debug("Status is $err");
                    570:     if ($err != 0) {
                    571: 	my $msg = $err;
                    572: 	if ($err < @installerrors) {
                    573: 	    $msg = $installerrors[$err];
                    574: 	}
                    575: 	&logthis("Install failed for table file $Filename : $msg");
                    576: 	return 0;
                    577:     }
                    578: 
                    579:     # Remove the temp file:
1.143     foxr      580: 
1.412     foxr      581:     unlink($TempFile);
1.143     foxr      582: 
                    583:     return 1;
                    584: }
1.200     matthew   585: 
                    586: 
1.169     foxr      587: #
                    588: #   ConfigFileFromSelector: converts a configuration file selector
1.411     foxr      589: #                 into a configuration file pathname.
1.472     raeburn   590: #                 Supports the following file selectors: 
                    591: #                 hosts, domain, dns_hosts, dns_domain  
1.411     foxr      592: #
1.169     foxr      593: #
                    594: #  Parameters:
                    595: #      selector  - Configuration file selector.
                    596: #  Returns:
                    597: #      Full path to the file or undef if the selector is invalid.
                    598: #
                    599: sub ConfigFileFromSelector {
                    600:     my $selector   = shift;
                    601:     my $tablefile;
                    602: 
                    603:     my $tabledir = $perlvar{'lonTabDir'}.'/';
1.472     raeburn   604:     if (($selector eq "hosts") || ($selector eq "domain") || 
                    605:         ($selector eq "dns_hosts") || ($selector eq "dns_domain")) {
1.411     foxr      606: 	$tablefile =  $tabledir.$selector.'.tab';
1.169     foxr      607:     }
                    608:     return $tablefile;
                    609: }
1.143     foxr      610: #
1.141     foxr      611: #   PushFile:  Called to do an administrative push of a file.
                    612: #              - Ensure the file being pushed is one we support.
                    613: #              - Backup the old file to <filename.saved>
                    614: #              - Separate the contents of the new file out from the
                    615: #                rest of the request.
                    616: #              - Write the new file.
                    617: #  Parameter:
                    618: #     Request - The entire user request.  This consists of a : separated
                    619: #               string pushfile:tablename:contents.
                    620: #     NOTE:  The contents may have :'s in it as well making things a bit
                    621: #            more interesting... but not much.
                    622: #  Returns:
                    623: #     String to send to client ("ok" or "refused" if bad file).
                    624: #
                    625: sub PushFile {
1.489.2.15  raeburn   626:     my $request = shift;
1.141     foxr      627:     my ($command, $filename, $contents) = split(":", $request, 3);
1.412     foxr      628:     &Debug("PushFile");
1.141     foxr      629:     
                    630:     #  At this point in time, pushes for only the following tables are
                    631:     #  supported:
                    632:     #   hosts.tab  ($filename eq host).
                    633:     #   domain.tab ($filename eq domain).
1.472     raeburn   634:     #   dns_hosts.tab ($filename eq dns_host).
                    635:     #   dns_domain.tab ($filename eq dns_domain). 
1.141     foxr      636:     # Construct the destination filename or reject the request.
                    637:     #
                    638:     # lonManage is supposed to ensure this, however this session could be
                    639:     # part of some elaborate spoof that managed somehow to authenticate.
                    640:     #
                    641: 
1.169     foxr      642: 
                    643:     my $tablefile = ConfigFileFromSelector($filename);
                    644:     if(! (defined $tablefile)) {
1.141     foxr      645: 	return "refused";
                    646:     }
1.412     foxr      647: 
1.157     foxr      648:     #  If the file being pushed is the host file, we adjust the entry for ourself so that the
                    649:     #  IP will be our current IP as looked up in dns.  Note this is only 99% good as it's possible
                    650:     #  to conceive of conditions where we don't have a DNS entry locally.  This is possible in a 
                    651:     #  network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now)
                    652:     #  that possibilty.
                    653: 
                    654:     if($filename eq "host") {
                    655: 	$contents = AdjustHostContents($contents);
1.489.2.15  raeburn   656:     } elsif ($filename eq 'dns_host' || $filename eq 'dns_domain') {
                    657:         if ($contents eq '') {
                    658:             &logthis('<font color="red"> Pushfile: unable to install '
                    659:                     .$tablefile." - no data received from push. </font>");
                    660:             return 'error: push had no data';
                    661:         }
                    662:         if (&Apache::lonnet::get_host_ip($clientname)) {
                    663:             my $clienthost = &Apache::lonnet::hostname($clientname);
                    664:             if ($managers{$clientip} eq $clientname) {
                    665:                 my $clientprotocol = $Apache::lonnet::protocol{$clientname};
                    666:                 $clientprotocol = 'http' if ($clientprotocol ne 'https');
                    667:                 my $url = '/adm/'.$filename;
                    668:                 $url =~ s{_}{/};
                    669:                 my $ua=new LWP::UserAgent;
                    670:                 $ua->timeout(60);
                    671:                 my $request=new HTTP::Request('GET',"$clientprotocol://$clienthost$url");
                    672:                 my $response=$ua->request($request);
                    673:                 if ($response->is_error()) {
                    674:                     &logthis('<font color="red"> Pushfile: unable to install '
                    675:                             .$tablefile." - error attempting to pull data. </font>");
                    676:                     return 'error: pull failed';
                    677:                 } else {
                    678:                     my $result = $response->content;
                    679:                     chomp($result);
                    680:                     unless ($result eq $contents) {
                    681:                         &logthis('<font color="red"> Pushfile: unable to install '
                    682:                                 .$tablefile." - pushed data and pulled data differ. </font>");
                    683:                         my $pushleng = length($contents);
                    684:                         my $pullleng = length($result);
                    685:                         if ($pushleng != $pullleng) {
                    686:                             return "error: $pushleng vs $pullleng bytes";
                    687:                         } else {
                    688:                             return "error: mismatch push and pull";
                    689:                         }
                    690:                     }
                    691:                 }
                    692:             }
                    693:         }
1.157     foxr      694:     }
                    695: 
1.141     foxr      696:     #  Install the new file:
                    697: 
1.412     foxr      698:     &logthis("Installing new $tablefile contents:\n$contents");
1.143     foxr      699:     if(!InstallFile($tablefile, $contents)) {
                    700: 	&logthis('<font color="red"> Pushfile: unable to install '
1.145     foxr      701: 	 .$tablefile." $! </font>");
1.143     foxr      702: 	return "error:$!";
1.224     foxr      703:     } else {
1.143     foxr      704: 	&logthis('<font color="green"> Installed new '.$tablefile
1.473     raeburn   705: 		 ." - transaction by: $clientname ($clientip)</font>");
1.472     raeburn   706:         my $adminmail = $perlvar{'lonAdmEMail'};
                    707:         my $admindom = &Apache::lonnet::host_domain($perlvar{'lonHostID'});
                    708:         if ($admindom ne '') {
                    709:             my %domconfig =
                    710:                 &Apache::lonnet::get_dom('configuration',['contacts'],$admindom);
                    711:             if (ref($domconfig{'contacts'}) eq 'HASH') {
                    712:                 if ($domconfig{'contacts'}{'adminemail'} ne '') {
                    713:                     $adminmail = $domconfig{'contacts'}{'adminemail'};
                    714:                 }
                    715:             }
                    716:         }
                    717:         if ($adminmail =~ /^[^\@]+\@[^\@]+$/) {
                    718:             my $msg = new Mail::Send;
                    719:             $msg->to($adminmail);
                    720:             $msg->subject('LON-CAPA DNS update on '.$perlvar{'lonHostID'});
                    721:             $msg->add('Content-type','text/plain; charset=UTF-8');
                    722:             if (my $fh = $msg->open()) {
                    723:                 print $fh 'Update to '.$tablefile.' from Cluster Manager '.
1.473     raeburn   724:                           "$clientname ($clientip)\n";
1.472     raeburn   725:                 $fh->close;
                    726:             }
                    727:         }
1.143     foxr      728:     }
                    729: 
1.141     foxr      730:     #  Indicate success:
                    731:  
                    732:     return "ok";
                    733: 
                    734: }
1.145     foxr      735: 
                    736: #
                    737: #  Called to re-init either lonc or lond.
                    738: #
                    739: #  Parameters:
                    740: #    request   - The full request by the client.  This is of the form
                    741: #                reinit:<process>  
                    742: #                where <process> is allowed to be either of 
                    743: #                lonc or lond
                    744: #
                    745: #  Returns:
                    746: #     The string to be sent back to the client either:
                    747: #   ok         - Everything worked just fine.
                    748: #   error:why  - There was a failure and why describes the reason.
                    749: #
                    750: #
                    751: sub ReinitProcess {
                    752:     my $request = shift;
                    753: 
1.146     foxr      754: 
                    755:     # separate the request (reinit) from the process identifier and
                    756:     # validate it producing the name of the .pid file for the process.
                    757:     #
                    758:     #
                    759:     my ($junk, $process) = split(":", $request);
1.147     foxr      760:     my $processpidfile = $perlvar{'lonDaemons'}.'/logs/';
1.146     foxr      761:     if($process eq 'lonc') {
                    762: 	$processpidfile = $processpidfile."lonc.pid";
1.147     foxr      763: 	if (!open(PIDFILE, "< $processpidfile")) {
                    764: 	    return "error:Open failed for $processpidfile";
                    765: 	}
                    766: 	my $loncpid = <PIDFILE>;
                    767: 	close(PIDFILE);
                    768: 	logthis('<font color="red"> Reinitializing lonc pid='.$loncpid
                    769: 		."</font>");
                    770: 	kill("USR2", $loncpid);
1.146     foxr      771:     } elsif ($process eq 'lond') {
1.147     foxr      772: 	logthis('<font color="red"> Reinitializing self (lond) </font>');
                    773: 	&UpdateHosts;			# Lond is us!!
1.146     foxr      774:     } else {
                    775: 	&logthis('<font color="yellow" Invalid reinit request for '.$process
                    776: 		 ."</font>");
                    777: 	return "error:Invalid process identifier $process";
                    778:     }
1.145     foxr      779:     return 'ok';
                    780: }
1.168     foxr      781: #   Validate a line in a configuration file edit script:
                    782: #   Validation includes:
                    783: #     - Ensuring the command is valid.
                    784: #     - Ensuring the command has sufficient parameters
                    785: #   Parameters:
                    786: #     scriptline - A line to validate (\n has been stripped for what it's worth).
1.167     foxr      787: #
1.168     foxr      788: #   Return:
                    789: #      0     - Invalid scriptline.
                    790: #      1     - Valid scriptline
                    791: #  NOTE:
                    792: #     Only the command syntax is checked, not the executability of the
                    793: #     command.
                    794: #
                    795: sub isValidEditCommand {
                    796:     my $scriptline = shift;
                    797: 
                    798:     #   Line elements are pipe separated:
                    799: 
                    800:     my ($command, $key, $newline)  = split(/\|/, $scriptline);
                    801:     &logthis('<font color="green"> isValideditCommand checking: '.
                    802: 	     "Command = '$command', Key = '$key', Newline = '$newline' </font>\n");
                    803:     
                    804:     if ($command eq "delete") {
                    805: 	#
                    806: 	#   key with no newline.
                    807: 	#
                    808: 	if( ($key eq "") || ($newline ne "")) {
                    809: 	    return 0;		# Must have key but no newline.
                    810: 	} else {
                    811: 	    return 1;		# Valid syntax.
                    812: 	}
1.169     foxr      813:     } elsif ($command eq "replace") {
1.168     foxr      814: 	#
                    815: 	#   key and newline:
                    816: 	#
                    817: 	if (($key eq "") || ($newline eq "")) {
                    818: 	    return 0;
                    819: 	} else {
                    820: 	    return 1;
                    821: 	}
1.169     foxr      822:     } elsif ($command eq "append") {
                    823: 	if (($key ne "") && ($newline eq "")) {
                    824: 	    return 1;
                    825: 	} else {
                    826: 	    return 0;
                    827: 	}
1.168     foxr      828:     } else {
                    829: 	return 0;		# Invalid command.
                    830:     }
                    831:     return 0;			# Should not get here!!!
                    832: }
1.169     foxr      833: #
                    834: #   ApplyEdit - Applies an edit command to a line in a configuration 
                    835: #               file.  It is the caller's responsiblity to validate the
                    836: #               edit line.
                    837: #   Parameters:
                    838: #      $directive - A single edit directive to apply.  
                    839: #                   Edit directives are of the form:
                    840: #                  append|newline      - Appends a new line to the file.
                    841: #                  replace|key|newline - Replaces the line with key value 'key'
                    842: #                  delete|key          - Deletes the line with key value 'key'.
                    843: #      $editor   - A config file editor object that contains the
                    844: #                  file being edited.
                    845: #
                    846: sub ApplyEdit {
1.192     foxr      847: 
                    848:     my ($directive, $editor) = @_;
1.169     foxr      849: 
                    850:     # Break the directive down into its command and its parameters
                    851:     # (at most two at this point.  The meaning of the parameters, if in fact
                    852:     #  they exist depends on the command).
                    853: 
                    854:     my ($command, $p1, $p2) = split(/\|/, $directive);
                    855: 
                    856:     if($command eq "append") {
                    857: 	$editor->Append($p1);	          # p1 - key p2 null.
                    858:     } elsif ($command eq "replace") {
                    859: 	$editor->ReplaceLine($p1, $p2);   # p1 - key p2 = newline.
                    860:     } elsif ($command eq "delete") {
                    861: 	$editor->DeleteLine($p1);         # p1 - key p2 null.
                    862:     } else {			          # Should not get here!!!
                    863: 	die "Invalid command given to ApplyEdit $command"
                    864:     }
                    865: }
                    866: #
                    867: # AdjustOurHost:
                    868: #           Adjusts a host file stored in a configuration file editor object
                    869: #           for the true IP address of this host. This is necessary for hosts
                    870: #           that live behind a firewall.
                    871: #           Those hosts have a publicly distributed IP of the firewall, but
                    872: #           internally must use their actual IP.  We assume that a given
                    873: #           host only has a single IP interface for now.
                    874: # Formal Parameters:
                    875: #     editor   - The configuration file editor to adjust.  This
                    876: #                editor is assumed to contain a hosts.tab file.
                    877: # Strategy:
                    878: #    - Figure out our hostname.
                    879: #    - Lookup the entry for this host.
                    880: #    - Modify the line to contain our IP
                    881: #    - Do a replace for this host.
                    882: sub AdjustOurHost {
                    883:     my $editor        = shift;
                    884: 
                    885:     # figure out who I am.
                    886: 
                    887:     my $myHostName    = $perlvar{'lonHostID'}; # LonCAPA hostname.
                    888: 
                    889:     #  Get my host file entry.
                    890: 
                    891:     my $ConfigLine    = $editor->Find($myHostName);
                    892:     if(! (defined $ConfigLine)) {
                    893: 	die "AdjustOurHost - no entry for me in hosts file $myHostName";
                    894:     }
                    895:     # figure out my IP:
                    896:     #   Use the config line to get my hostname.
                    897:     #   Use gethostbyname to translate that into an IP address.
                    898:     #
1.338     albertel  899:     my ($id,$domain,$role,$name,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine);
1.169     foxr      900:     #
                    901:     #  Reassemble the config line from the elements in the list.
                    902:     #  Note that if the loncnew items were not present before, they will
                    903:     #  be now even if they would be empty
                    904:     #
                    905:     my $newConfigLine = $id;
1.338     albertel  906:     foreach my $item ($domain, $role, $name, $maxcon, $idleto, $mincon) {
1.169     foxr      907: 	$newConfigLine .= ":".$item;
                    908:     }
                    909:     #  Replace the line:
                    910: 
                    911:     $editor->ReplaceLine($id, $newConfigLine);
                    912:     
                    913: }
                    914: #
                    915: #   ReplaceConfigFile:
                    916: #              Replaces a configuration file with the contents of a
                    917: #              configuration file editor object.
                    918: #              This is done by:
                    919: #              - Copying the target file to <filename>.old
                    920: #              - Writing the new file to <filename>.tmp
                    921: #              - Moving <filename.tmp>  -> <filename>
                    922: #              This laborious process ensures that the system is never without
                    923: #              a configuration file that's at least valid (even if the contents
                    924: #              may be dated).
                    925: #   Parameters:
                    926: #        filename   - Name of the file to modify... this is a full path.
                    927: #        editor     - Editor containing the file.
                    928: #
                    929: sub ReplaceConfigFile {
1.192     foxr      930:     
                    931:     my ($filename, $editor) = @_;
1.168     foxr      932: 
1.169     foxr      933:     CopyFile ($filename, $filename.".old");
                    934: 
                    935:     my $contents  = $editor->Get(); # Get the contents of the file.
                    936: 
                    937:     InstallFile($filename, $contents);
                    938: }
1.168     foxr      939: #   
                    940: #
                    941: #   Called to edit a configuration table  file
1.167     foxr      942: #   Parameters:
                    943: #      request           - The entire command/request sent by lonc or lonManage
                    944: #   Return:
                    945: #      The reply to send to the client.
1.168     foxr      946: #
1.167     foxr      947: sub EditFile {
                    948:     my $request = shift;
                    949: 
                    950:     #  Split the command into it's pieces:  edit:filetype:script
                    951: 
1.339     albertel  952:     my ($cmd, $filetype, $script) = split(/:/, $request,3);	# : in script
1.167     foxr      953: 
                    954:     #  Check the pre-coditions for success:
                    955: 
1.339     albertel  956:     if($cmd != "edit") {	# Something is amiss afoot alack.
1.167     foxr      957: 	return "error:edit request detected, but request != 'edit'\n";
                    958:     }
                    959:     if( ($filetype ne "hosts")  &&
                    960: 	($filetype ne "domain")) {
                    961: 	return "error:edit requested with invalid file specifier: $filetype \n";
                    962:     }
                    963: 
                    964:     #   Split the edit script and check it's validity.
1.168     foxr      965: 
                    966:     my @scriptlines = split(/\n/, $script);  # one line per element.
                    967:     my $linecount   = scalar(@scriptlines);
                    968:     for(my $i = 0; $i < $linecount; $i++) {
                    969: 	chomp($scriptlines[$i]);
                    970: 	if(!isValidEditCommand($scriptlines[$i])) {
                    971: 	    return "error:edit with bad script line: '$scriptlines[$i]' \n";
                    972: 	}
                    973:     }
1.145     foxr      974: 
1.167     foxr      975:     #   Execute the edit operation.
1.169     foxr      976:     #   - Create a config file editor for the appropriate file and 
                    977:     #   - execute each command in the script:
                    978:     #
                    979:     my $configfile = ConfigFileFromSelector($filetype);
                    980:     if (!(defined $configfile)) {
                    981: 	return "refused\n";
                    982:     }
                    983:     my $editor = ConfigFileEdit->new($configfile);
1.167     foxr      984: 
1.169     foxr      985:     for (my $i = 0; $i < $linecount; $i++) {
                    986: 	ApplyEdit($scriptlines[$i], $editor);
                    987:     }
                    988:     # If the file is the host file, ensure that our host is
                    989:     # adjusted to have our ip:
                    990:     #
                    991:     if($filetype eq "host") {
                    992: 	AdjustOurHost($editor);
                    993:     }
                    994:     #  Finally replace the current file with our file.
                    995:     #
                    996:     ReplaceConfigFile($configfile, $editor);
1.167     foxr      997: 
                    998:     return "ok\n";
                    999: }
1.207     foxr     1000: 
1.255     foxr     1001: #   read_profile
                   1002: #
                   1003: #   Returns a set of specific entries from a user's profile file.
                   1004: #   this is a utility function that is used by both get_profile_entry and
                   1005: #   get_profile_entry_encrypted.
                   1006: #
                   1007: # Parameters:
                   1008: #    udom       - Domain in which the user exists.
                   1009: #    uname      - User's account name (loncapa account)
                   1010: #    namespace  - The profile namespace to open.
                   1011: #    what       - A set of & separated queries.
                   1012: # Returns:
                   1013: #    If all ok: - The string that needs to be shipped back to the user.
                   1014: #    If failure - A string that starts with error: followed by the failure
                   1015: #                 reason.. note that this probabyl gets shipped back to the
                   1016: #                 user as well.
                   1017: #
                   1018: sub read_profile {
                   1019:     my ($udom, $uname, $namespace, $what) = @_;
                   1020:     
                   1021:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   1022: 				 &GDBM_READER());
                   1023:     if ($hashref) {
                   1024:         my @queries=split(/\&/,$what);
1.440     raeburn  1025:         if ($namespace eq 'roles') {
                   1026:             @queries = map { &unescape($_); } @queries; 
                   1027:         }
1.255     foxr     1028:         my $qresult='';
                   1029: 	
                   1030: 	for (my $i=0;$i<=$#queries;$i++) {
                   1031: 	    $qresult.="$hashref->{$queries[$i]}&";    # Presumably failure gives empty string.
                   1032: 	}
                   1033: 	$qresult=~s/\&$//;              # Remove trailing & from last lookup.
1.311     albertel 1034: 	if (&untie_user_hash($hashref)) {
1.255     foxr     1035: 	    return $qresult;
                   1036: 	} else {
                   1037: 	    return "error: ".($!+0)." untie (GDBM) Failed";
                   1038: 	}
                   1039:     } else {
                   1040: 	if ($!+0 == 2) {
                   1041: 	    return "error:No such file or GDBM reported bad block error";
                   1042: 	} else {
                   1043: 	    return "error: ".($!+0)." tie (GDBM) Failed";
                   1044: 	}
                   1045:     }
                   1046: 
                   1047: }
1.214     foxr     1048: #--------------------- Request Handlers --------------------------------------------
                   1049: #
1.215     foxr     1050: #   By convention each request handler registers itself prior to the sub 
                   1051: #   declaration:
1.214     foxr     1052: #
                   1053: 
1.216     foxr     1054: #++
                   1055: #
1.214     foxr     1056: #  Handles ping requests.
                   1057: #  Parameters:
                   1058: #      $cmd    - the actual keyword that invoked us.
                   1059: #      $tail   - the tail of the request that invoked us.
                   1060: #      $replyfd- File descriptor connected to the client
                   1061: #  Implicit Inputs:
                   1062: #      $currenthostid - Global variable that carries the name of the host we are
                   1063: #                       known as.
                   1064: #  Returns:
                   1065: #      1       - Ok to continue processing.
                   1066: #      0       - Program should exit.
                   1067: #  Side effects:
                   1068: #      Reply information is sent to the client.
                   1069: sub ping_handler {
                   1070:     my ($cmd, $tail, $client) = @_;
                   1071:     Debug("$cmd $tail $client .. $currenthostid:");
                   1072:    
1.387     albertel 1073:     Reply( $client,\$currenthostid,"$cmd:$tail");
1.214     foxr     1074:    
                   1075:     return 1;
                   1076: }
                   1077: &register_handler("ping", \&ping_handler, 0, 1, 1);       # Ping unencoded, client or manager.
                   1078: 
1.216     foxr     1079: #++
1.215     foxr     1080: #
                   1081: # Handles pong requests.  Pong replies with our current host id, and
                   1082: #                         the results of a ping sent to us via our lonc.
                   1083: #
                   1084: # Parameters:
                   1085: #      $cmd    - the actual keyword that invoked us.
                   1086: #      $tail   - the tail of the request that invoked us.
                   1087: #      $replyfd- File descriptor connected to the client
                   1088: #  Implicit Inputs:
                   1089: #      $currenthostid - Global variable that carries the name of the host we are
                   1090: #                       connected to.
                   1091: #  Returns:
                   1092: #      1       - Ok to continue processing.
                   1093: #      0       - Program should exit.
                   1094: #  Side effects:
                   1095: #      Reply information is sent to the client.
                   1096: sub pong_handler {
                   1097:     my ($cmd, $tail, $replyfd) = @_;
                   1098: 
1.365     albertel 1099:     my $reply=&Apache::lonnet::reply("ping",$clientname);
1.215     foxr     1100:     &Reply( $replyfd, "$currenthostid:$reply\n", "$cmd:$tail"); 
                   1101:     return 1;
                   1102: }
                   1103: &register_handler("pong", \&pong_handler, 0, 1, 1);       # Pong unencoded, client or manager
                   1104: 
1.216     foxr     1105: #++
                   1106: #      Called to establish an encrypted session key with the remote client.
                   1107: #      Note that with secure lond, in most cases this function is never
                   1108: #      invoked.  Instead, the secure session key is established either
                   1109: #      via a local file that's locked down tight and only lives for a short
                   1110: #      time, or via an ssl tunnel...and is generated from a bunch-o-random
                   1111: #      bits from /dev/urandom, rather than the predictable pattern used by
                   1112: #      by this sub.  This sub is only used in the old-style insecure
                   1113: #      key negotiation.
                   1114: # Parameters:
                   1115: #      $cmd    - the actual keyword that invoked us.
                   1116: #      $tail   - the tail of the request that invoked us.
                   1117: #      $replyfd- File descriptor connected to the client
                   1118: #  Implicit Inputs:
                   1119: #      $currenthostid - Global variable that carries the name of the host
                   1120: #                       known as.
1.448     raeburn  1121: #      $clientname    - Global variable that carries the name of the host we're connected to.
1.216     foxr     1122: #  Returns:
                   1123: #      1       - Ok to continue processing.
                   1124: #      0       - Program should exit.
                   1125: #  Implicit Outputs:
                   1126: #      Reply information is sent to the client.
                   1127: #      $cipher is set with a reference to a new IDEA encryption object.
                   1128: #
                   1129: sub establish_key_handler {
                   1130:     my ($cmd, $tail, $replyfd) = @_;
                   1131: 
                   1132:     my $buildkey=time.$$.int(rand 100000);
                   1133:     $buildkey=~tr/1-6/A-F/;
                   1134:     $buildkey=int(rand 100000).$buildkey.int(rand 100000);
                   1135:     my $key=$currenthostid.$clientname;
                   1136:     $key=~tr/a-z/A-Z/;
                   1137:     $key=~tr/G-P/0-9/;
                   1138:     $key=~tr/Q-Z/0-9/;
                   1139:     $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
                   1140:     $key=substr($key,0,32);
                   1141:     my $cipherkey=pack("H32",$key);
                   1142:     $cipher=new IDEA $cipherkey;
1.387     albertel 1143:     &Reply($replyfd, \$buildkey, "$cmd:$tail"); 
1.216     foxr     1144:    
                   1145:     return 1;
                   1146: 
                   1147: }
                   1148: &register_handler("ekey", \&establish_key_handler, 0, 1,1);
                   1149: 
1.217     foxr     1150: #     Handler for the load command.  Returns the current system load average
                   1151: #     to the requestor.
                   1152: #
                   1153: # Parameters:
                   1154: #      $cmd    - the actual keyword that invoked us.
                   1155: #      $tail   - the tail of the request that invoked us.
                   1156: #      $replyfd- File descriptor connected to the client
                   1157: #  Implicit Inputs:
                   1158: #      $currenthostid - Global variable that carries the name of the host
                   1159: #                       known as.
1.448     raeburn  1160: #      $clientname    - Global variable that carries the name of the host we're connected to.
1.217     foxr     1161: #  Returns:
                   1162: #      1       - Ok to continue processing.
                   1163: #      0       - Program should exit.
                   1164: #  Side effects:
                   1165: #      Reply information is sent to the client.
                   1166: sub load_handler {
                   1167:     my ($cmd, $tail, $replyfd) = @_;
                   1168: 
1.463     foxr     1169: 
                   1170: 
1.217     foxr     1171:    # Get the load average from /proc/loadavg and calculate it as a percentage of
                   1172:    # the allowed load limit as set by the perl global variable lonLoadLim
                   1173: 
                   1174:     my $loadavg;
                   1175:     my $loadfile=IO::File->new('/proc/loadavg');
                   1176:    
                   1177:     $loadavg=<$loadfile>;
                   1178:     $loadavg =~ s/\s.*//g;                      # Extract the first field only.
                   1179:    
                   1180:     my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};
                   1181: 
1.387     albertel 1182:     &Reply( $replyfd, \$loadpercent, "$cmd:$tail");
1.217     foxr     1183:    
                   1184:     return 1;
                   1185: }
1.263     albertel 1186: &register_handler("load", \&load_handler, 0, 1, 0);
1.217     foxr     1187: 
                   1188: #
                   1189: #   Process the userload request.  This sub returns to the client the current
                   1190: #  user load average.  It can be invoked either by clients or managers.
                   1191: #
                   1192: # Parameters:
                   1193: #      $cmd    - the actual keyword that invoked us.
                   1194: #      $tail   - the tail of the request that invoked us.
                   1195: #      $replyfd- File descriptor connected to the client
                   1196: #  Implicit Inputs:
                   1197: #      $currenthostid - Global variable that carries the name of the host
                   1198: #                       known as.
1.448     raeburn  1199: #      $clientname    - Global variable that carries the name of the host we're connected to.
1.217     foxr     1200: #  Returns:
                   1201: #      1       - Ok to continue processing.
                   1202: #      0       - Program should exit
                   1203: # Implicit inputs:
                   1204: #     whatever the userload() function requires.
                   1205: #  Implicit outputs:
                   1206: #     the reply is written to the client.
                   1207: #
                   1208: sub user_load_handler {
                   1209:     my ($cmd, $tail, $replyfd) = @_;
                   1210: 
1.365     albertel 1211:     my $userloadpercent=&Apache::lonnet::userload();
1.387     albertel 1212:     &Reply($replyfd, \$userloadpercent, "$cmd:$tail");
1.217     foxr     1213:     
                   1214:     return 1;
                   1215: }
1.263     albertel 1216: &register_handler("userload", \&user_load_handler, 0, 1, 0);
1.217     foxr     1217: 
1.218     foxr     1218: #   Process a request for the authorization type of a user:
                   1219: #   (userauth).
                   1220: #
                   1221: # Parameters:
                   1222: #      $cmd    - the actual keyword that invoked us.
                   1223: #      $tail   - the tail of the request that invoked us.
                   1224: #      $replyfd- File descriptor connected to the client
                   1225: #  Returns:
                   1226: #      1       - Ok to continue processing.
                   1227: #      0       - Program should exit
                   1228: # Implicit outputs:
                   1229: #    The user authorization type is written to the client.
                   1230: #
                   1231: sub user_authorization_type {
                   1232:     my ($cmd, $tail, $replyfd) = @_;
                   1233:    
                   1234:     my $userinput = "$cmd:$tail";
                   1235:    
                   1236:     #  Pull the domain and username out of the command tail.
1.222     foxr     1237:     # and call get_auth_type to determine the authentication type.
1.218     foxr     1238:    
                   1239:     my ($udom,$uname)=split(/:/,$tail);
1.222     foxr     1240:     my $result = &get_auth_type($udom, $uname);
1.218     foxr     1241:     if($result eq "nouser") {
                   1242: 	&Failure( $replyfd, "unknown_user\n", $userinput);
                   1243:     } else {
                   1244: 	#
1.222     foxr     1245: 	# We only want to pass the second field from get_auth_type
1.218     foxr     1246: 	# for ^krb.. otherwise we'll be handing out the encrypted
                   1247: 	# password for internals e.g.
                   1248: 	#
                   1249: 	my ($type,$otherinfo) = split(/:/,$result);
                   1250: 	if($type =~ /^krb/) {
                   1251: 	    $type = $result;
1.269     raeburn  1252: 	} else {
                   1253:             $type .= ':';
                   1254:         }
1.387     albertel 1255: 	&Reply( $replyfd, \$type, $userinput);
1.218     foxr     1256:     }
                   1257:   
                   1258:     return 1;
                   1259: }
                   1260: &register_handler("currentauth", \&user_authorization_type, 1, 1, 0);
                   1261: 
                   1262: #   Process a request by a manager to push a hosts or domain table 
                   1263: #   to us.  We pick apart the command and pass it on to the subs
                   1264: #   that already exist to do this.
                   1265: #
                   1266: # Parameters:
                   1267: #      $cmd    - the actual keyword that invoked us.
                   1268: #      $tail   - the tail of the request that invoked us.
                   1269: #      $client - File descriptor connected to the client
                   1270: #  Returns:
                   1271: #      1       - Ok to continue processing.
                   1272: #      0       - Program should exit
                   1273: # Implicit Output:
                   1274: #    a reply is written to the client.
                   1275: sub push_file_handler {
                   1276:     my ($cmd, $tail, $client) = @_;
1.412     foxr     1277:     &Debug("In push file handler");
1.218     foxr     1278:     my $userinput = "$cmd:$tail";
                   1279: 
                   1280:     # At this time we only know that the IP of our partner is a valid manager
                   1281:     # the code below is a hook to do further authentication (e.g. to resolve
                   1282:     # spoofing).
                   1283: 
                   1284:     my $cert = &GetCertificate($userinput);
1.412     foxr     1285:     if(&ValidManager($cert)) {
                   1286: 	&Debug("Valid manager: $client");
1.218     foxr     1287: 
                   1288: 	# Now presumably we have the bona fides of both the peer host and the
                   1289: 	# process making the request.
                   1290:       
                   1291: 	my $reply = &PushFile($userinput);
1.387     albertel 1292: 	&Reply($client, \$reply, $userinput);
1.218     foxr     1293: 
                   1294:     } else {
1.412     foxr     1295: 	&logthis("push_file_handler $client is not valid");
1.218     foxr     1296: 	&Failure( $client, "refused\n", $userinput);
                   1297:     } 
1.219     foxr     1298:     return 1;
1.218     foxr     1299: }
                   1300: &register_handler("pushfile", \&push_file_handler, 1, 0, 1);
                   1301: 
1.399     raeburn  1302: # The du_handler routine should be considered obsolete and is retained
                   1303: # for communication with legacy servers.  Please see the du2_handler.
1.243     banghart 1304: #
1.399     raeburn  1305: #   du  - list the disk usage of a directory recursively. 
1.243     banghart 1306: #    
                   1307: #   note: stolen code from the ls file handler
                   1308: #   under construction by Rick Banghart 
                   1309: #    .
                   1310: # Parameters:
                   1311: #    $cmd        - The command that dispatched us (du).
                   1312: #    $ududir     - The directory path to list... I'm not sure what this
                   1313: #                  is relative as things like ls:. return e.g.
                   1314: #                  no_such_dir.
                   1315: #    $client     - Socket open on the client.
                   1316: # Returns:
                   1317: #     1 - indicating that the daemon should not disconnect.
                   1318: # Side Effects:
                   1319: #   The reply is written to  $client.
                   1320: #
                   1321: sub du_handler {
                   1322:     my ($cmd, $ududir, $client) = @_;
1.339     albertel 1323:     ($ududir) = split(/:/,$ududir); # Make 'telnet' testing easier.
1.251     foxr     1324:     my $userinput = "$cmd:$ududir";
                   1325: 
1.245     albertel 1326:     if ($ududir=~/\.\./ || $ududir!~m|^/home/httpd/|) {
                   1327: 	&Failure($client,"refused\n","$cmd:$ududir");
                   1328: 	return 1;
                   1329:     }
1.249     foxr     1330:     #  Since $ududir could have some nasties in it,
                   1331:     #  we will require that ududir is a valid
                   1332:     #  directory.  Just in case someone tries to
                   1333:     #  slip us a  line like .;(cd /home/httpd rm -rf*)
                   1334:     #  etc.
                   1335:     #
                   1336:     if (-d $ududir) {
1.292     albertel 1337: 	my $total_size=0;
                   1338: 	my $code=sub { 
                   1339: 	    if ($_=~/\.\d+\./) { return;} 
                   1340: 	    if ($_=~/\.meta$/) { return;}
1.362     albertel 1341: 	    if (-d $_)         { return;}
1.292     albertel 1342: 	    $total_size+=(stat($_))[7];
                   1343: 	};
1.295     raeburn  1344: 	chdir($ududir);
1.292     albertel 1345: 	find($code,$ududir);
                   1346: 	$total_size=int($total_size/1024);
1.387     albertel 1347: 	&Reply($client,\$total_size,"$cmd:$ududir");
1.249     foxr     1348:     } else {
1.251     foxr     1349: 	&Failure($client, "bad_directory:$ududir\n","$cmd:$ududir"); 
1.249     foxr     1350:     }
1.243     banghart 1351:     return 1;
                   1352: }
                   1353: &register_handler("du", \&du_handler, 0, 1, 0);
1.218     foxr     1354: 
1.399     raeburn  1355: # Please also see the du_handler, which is obsoleted by du2. 
                   1356: # du2_handler differs from du_handler in that required path to directory
                   1357: # provided by &propath() is prepended in the handler instead of on the 
                   1358: # client side.
1.239     foxr     1359: #
1.399     raeburn  1360: #   du2  - list the disk usage of a directory recursively.
                   1361: #
                   1362: # Parameters:
                   1363: #    $cmd        - The command that dispatched us (du).
                   1364: #    $tail       - The tail of the request that invoked us.
                   1365: #                  $tail is a : separated list of the following:
                   1366: #                   - $ududir - directory path to list (before prepending)
                   1367: #                   - $getpropath = 1 if &propath() should prepend
                   1368: #                   - $uname - username to use for &propath or user dir
                   1369: #                   - $udom - domain to use for &propath or user dir
                   1370: #                   All are escaped.
                   1371: #    $client     - Socket open on the client.
                   1372: # Returns:
                   1373: #     1 - indicating that the daemon should not disconnect.
                   1374: # Side Effects:
                   1375: #   The reply is written to $client.
                   1376: #
                   1377: 
                   1378: sub du2_handler {
                   1379:     my ($cmd, $tail, $client) = @_;
                   1380:     my ($ududir,$getpropath,$uname,$udom) = map { &unescape($_) } (split(/:/, $tail));
                   1381:     my $userinput = "$cmd:$tail";
                   1382:     if (($ududir=~/\.\./) || (($ududir!~m|^/home/httpd/|) && (!$getpropath))) {
                   1383:         &Failure($client,"refused\n","$cmd:$tail");
                   1384:         return 1;
                   1385:     }
                   1386:     if ($getpropath) {
                   1387:         if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) {
                   1388:             $ududir = &propath($udom,$uname).'/'.$ududir;
                   1389:         } else {
                   1390:             &Failure($client,"refused\n","$cmd:$tail");
                   1391:             return 1;
                   1392:         }
                   1393:     }
                   1394:     #  Since $ududir could have some nasties in it,
                   1395:     #  we will require that ududir is a valid
                   1396:     #  directory.  Just in case someone tries to
                   1397:     #  slip us a  line like .;(cd /home/httpd rm -rf*)
                   1398:     #  etc.
                   1399:     #
                   1400:     if (-d $ududir) {
                   1401:         my $total_size=0;
                   1402:         my $code=sub {
                   1403:             if ($_=~/\.\d+\./) { return;}
                   1404:             if ($_=~/\.meta$/) { return;}
                   1405:             if (-d $_)         { return;}
                   1406:             $total_size+=(stat($_))[7];
                   1407:         };
                   1408:         chdir($ududir);
                   1409:         find($code,$ududir);
                   1410:         $total_size=int($total_size/1024);
                   1411:         &Reply($client,\$total_size,"$cmd:$ududir");
                   1412:     } else {
                   1413:         &Failure($client, "bad_directory:$ududir\n","$cmd:$tail");
                   1414:     }
                   1415:     return 1;
                   1416: }
                   1417: &register_handler("du2", \&du2_handler, 0, 1, 0);
                   1418: 
                   1419: #
                   1420: # The ls_handler routine should be considered obsolete and is retained
                   1421: # for communication with legacy servers.  Please see the ls3_handler.
1.280     matthew  1422: #
1.239     foxr     1423: #   ls  - list the contents of a directory.  For each file in the
                   1424: #    selected directory the filename followed by the full output of
                   1425: #    the stat function is returned.  The returned info for each
                   1426: #    file are separated by ':'.  The stat fields are separated by &'s.
1.489.2.23  raeburn  1427: #
                   1428: #    If the requested path contains /../ or is:
                   1429: #
                   1430: #    1. for a directory, and the path does not begin with one of:
1.489.2.25  raeburn  1431: #        (a) /home/httpd/html/res/<domain>
1.489.2.28  raeburn  1432: #        (b) /home/httpd/html/userfiles/
1.489.2.23  raeburn  1433: #        (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles
                   1434: #    or is:
                   1435: #
1.489.2.28  raeburn  1436: #    2. for a file, and the path (after prepending) does not begin with one of:
                   1437: #        (a) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
                   1438: #        (b) /home/httpd/html/res/<domain>/<username>/
                   1439: #        (c) /home/httpd/html/userfiles/<domain>/<username>/
1.489.2.23  raeburn  1440: #
                   1441: #    the response will be "refused".
                   1442: #
1.239     foxr     1443: # Parameters:
                   1444: #    $cmd        - The command that dispatched us (ls).
                   1445: #    $ulsdir     - The directory path to list... I'm not sure what this
                   1446: #                  is relative as things like ls:. return e.g.
                   1447: #                  no_such_dir.
                   1448: #    $client     - Socket open on the client.
                   1449: # Returns:
                   1450: #     1 - indicating that the daemon should not disconnect.
                   1451: # Side Effects:
                   1452: #   The reply is written to  $client.
                   1453: #
                   1454: sub ls_handler {
1.280     matthew  1455:     # obsoleted by ls2_handler
1.239     foxr     1456:     my ($cmd, $ulsdir, $client) = @_;
                   1457: 
                   1458:     my $userinput = "$cmd:$ulsdir";
                   1459: 
                   1460:     my $obs;
                   1461:     my $rights;
                   1462:     my $ulsout='';
                   1463:     my $ulsfn;
1.489.2.23  raeburn  1464:     if ($ulsdir =~m{/\.\./}) {
                   1465:         &Failure($client,"refused\n",$userinput);
                   1466:         return 1;
                   1467:     }
1.239     foxr     1468:     if (-e $ulsdir) {
                   1469: 	if(-d $ulsdir) {
1.489.2.28  raeburn  1470:             unless (($ulsdir =~ m{^/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) ||
                   1471:                     ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/userfiles})) {
1.489.2.23  raeburn  1472:                 &Failure($client,"refused\n",$userinput);
                   1473:                 return 1;
                   1474:             }
1.239     foxr     1475: 	    if (opendir(LSDIR,$ulsdir)) {
                   1476: 		while ($ulsfn=readdir(LSDIR)) {
1.291     albertel 1477: 		    undef($obs);
                   1478: 		    undef($rights); 
1.239     foxr     1479: 		    my @ulsstats=stat($ulsdir.'/'.$ulsfn);
                   1480: 		    #We do some obsolete checking here
                   1481: 		    if(-e $ulsdir.'/'.$ulsfn.".meta") { 
                   1482: 			open(FILE, $ulsdir.'/'.$ulsfn.".meta");
                   1483: 			my @obsolete=<FILE>;
                   1484: 			foreach my $obsolete (@obsolete) {
1.301     www      1485: 			    if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; } 
1.239     foxr     1486: 			    if($obsolete =~ m|(<copyright>)(default)|) { $rights = 1; }
                   1487: 			}
                   1488: 		    }
                   1489: 		    $ulsout.=$ulsfn.'&'.join('&',@ulsstats);
                   1490: 		    if($obs eq '1') { $ulsout.="&1"; }
                   1491: 		    else { $ulsout.="&0"; }
                   1492: 		    if($rights eq '1') { $ulsout.="&1:"; }
                   1493: 		    else { $ulsout.="&0:"; }
                   1494: 		}
                   1495: 		closedir(LSDIR);
                   1496: 	    }
                   1497: 	} else {
1.489.2.28  raeburn  1498:             unless (($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/}) ||
                   1499:                     ($ulsdir =~ m{^/home/httpd/html/(?:res|userfiles)/$LONCAPA::match_domain/$LONCAPA::match_name/})) { 
1.489.2.23  raeburn  1500:                 &Failure($client,"refused\n",$userinput);
                   1501:                 return 1;
                   1502:             }
1.239     foxr     1503: 	    my @ulsstats=stat($ulsdir);
                   1504: 	    $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
                   1505: 	}
                   1506:     } else {
                   1507: 	$ulsout='no_such_dir';
                   1508:     }
                   1509:     if ($ulsout eq '') { $ulsout='empty'; }
1.387     albertel 1510:     &Reply($client, \$ulsout, $userinput); # This supports debug logging.
1.239     foxr     1511:     
                   1512:     return 1;
                   1513: 
                   1514: }
                   1515: &register_handler("ls", \&ls_handler, 0, 1, 0);
                   1516: 
1.399     raeburn  1517: # The ls2_handler routine should be considered obsolete and is retained
                   1518: # for communication with legacy servers.  Please see the ls3_handler.
                   1519: # Please also see the ls_handler, which was itself obsoleted by ls2.
1.280     matthew  1520: # ls2_handler differs from ls_handler in that it escapes its return 
                   1521: # values before concatenating them together with ':'s.
                   1522: #
                   1523: #   ls2  - list the contents of a directory.  For each file in the
                   1524: #    selected directory the filename followed by the full output of
                   1525: #    the stat function is returned.  The returned info for each
                   1526: #    file are separated by ':'.  The stat fields are separated by &'s.
1.489.2.23  raeburn  1527: #
                   1528: #    If the requested path contains /../ or is:
                   1529: #
                   1530: #    1. for a directory, and the path does not begin with one of:
1.489.2.25  raeburn  1531: #        (a) /home/httpd/html/res/<domain>
1.489.2.28  raeburn  1532: #        (b) /home/httpd/html/userfiles/
1.489.2.23  raeburn  1533: #        (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles
                   1534: #    or is:
                   1535: #
1.489.2.28  raeburn  1536: #    2. for a file, and the path (after prepending) does not begin with one of:
                   1537: #        (a) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
                   1538: #        (b) /home/httpd/html/res/<domain>/<username>/
                   1539: #        (c) /home/httpd/html/userfiles/<domain>/<username>/
1.489.2.23  raeburn  1540: #
                   1541: #    the response will be "refused".
                   1542: #
1.280     matthew  1543: # Parameters:
                   1544: #    $cmd        - The command that dispatched us (ls).
                   1545: #    $ulsdir     - The directory path to list... I'm not sure what this
                   1546: #                  is relative as things like ls:. return e.g.
                   1547: #                  no_such_dir.
                   1548: #    $client     - Socket open on the client.
                   1549: # Returns:
                   1550: #     1 - indicating that the daemon should not disconnect.
                   1551: # Side Effects:
                   1552: #   The reply is written to  $client.
                   1553: #
                   1554: sub ls2_handler {
                   1555:     my ($cmd, $ulsdir, $client) = @_;
                   1556: 
                   1557:     my $userinput = "$cmd:$ulsdir";
                   1558: 
                   1559:     my $obs;
                   1560:     my $rights;
                   1561:     my $ulsout='';
                   1562:     my $ulsfn;
1.489.2.23  raeburn  1563:     if ($ulsdir =~m{/\.\./}) {
                   1564:         &Failure($client,"refused\n",$userinput);
                   1565:         return 1;
                   1566:     }
1.280     matthew  1567:     if (-e $ulsdir) {
                   1568:         if(-d $ulsdir) {
1.489.2.28  raeburn  1569:             unless (($ulsdir =~ m{^/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) ||
                   1570:                     ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/userfiles})) {
1.489.2.23  raeburn  1571:                 &Failure($client,"refused\n","$userinput");
                   1572:                 return 1;
                   1573:             }
1.280     matthew  1574:             if (opendir(LSDIR,$ulsdir)) {
                   1575:                 while ($ulsfn=readdir(LSDIR)) {
1.291     albertel 1576:                     undef($obs);
                   1577: 		    undef($rights); 
1.280     matthew  1578:                     my @ulsstats=stat($ulsdir.'/'.$ulsfn);
                   1579:                     #We do some obsolete checking here
                   1580:                     if(-e $ulsdir.'/'.$ulsfn.".meta") { 
                   1581:                         open(FILE, $ulsdir.'/'.$ulsfn.".meta");
                   1582:                         my @obsolete=<FILE>;
                   1583:                         foreach my $obsolete (@obsolete) {
1.301     www      1584:                             if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; } 
1.280     matthew  1585:                             if($obsolete =~ m|(<copyright>)(default)|) {
                   1586:                                 $rights = 1;
                   1587:                             }
                   1588:                         }
                   1589:                     }
                   1590:                     my $tmp = $ulsfn.'&'.join('&',@ulsstats);
                   1591:                     if ($obs    eq '1') { $tmp.="&1"; } else { $tmp.="&0"; }
                   1592:                     if ($rights eq '1') { $tmp.="&1"; } else { $tmp.="&0"; }
                   1593:                     $ulsout.= &escape($tmp).':';
                   1594:                 }
                   1595:                 closedir(LSDIR);
                   1596:             }
                   1597:         } else {
1.489.2.28  raeburn  1598:             unless (($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/}) ||
                   1599:                     ($ulsdir =~ m{^/home/httpd/html/(?:res|userfiles)/$LONCAPA::match_domain/$LONCAPA::match_name/})) {
1.489.2.23  raeburn  1600:                 &Failure($client,"refused\n",$userinput);
                   1601:                 return 1;
                   1602:             }
1.280     matthew  1603:             my @ulsstats=stat($ulsdir);
                   1604:             $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
                   1605:         }
                   1606:     } else {
                   1607:         $ulsout='no_such_dir';
                   1608:    }
                   1609:    if ($ulsout eq '') { $ulsout='empty'; }
1.387     albertel 1610:    &Reply($client, \$ulsout, $userinput); # This supports debug logging.
1.280     matthew  1611:    return 1;
                   1612: }
                   1613: &register_handler("ls2", \&ls2_handler, 0, 1, 0);
1.399     raeburn  1614: #
                   1615: #   ls3  - list the contents of a directory.  For each file in the
                   1616: #    selected directory the filename followed by the full output of
                   1617: #    the stat function is returned.  The returned info for each
                   1618: #    file are separated by ':'.  The stat fields are separated by &'s.
1.489.2.23  raeburn  1619: #
                   1620: #    If the requested path (after prepending) contains /../ or is:
                   1621: #
                   1622: #    1. for a directory, and the path does not begin with one of:
1.489.2.25  raeburn  1623: #        (a) /home/httpd/html/res/<domain>
1.489.2.28  raeburn  1624: #        (b) /home/httpd/html/userfiles/
1.489.2.23  raeburn  1625: #        (c) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/userfiles
1.489.2.28  raeburn  1626: #        (d) /home/httpd/html/priv/<domain> and client is the homeserver
1.489.2.23  raeburn  1627: #
                   1628: #    or is:
                   1629: #
1.489.2.28  raeburn  1630: #    2. for a file, and the path (after prepending) does not begin with one of:
                   1631: #        (a) /home/httpd/lonUsers/<domain>/<1>/<2>/<3>/<username>/
                   1632: #        (b) /home/httpd/html/res/<domain>/<username>/
                   1633: #        (c) /home/httpd/html/userfiles/<domain>/<username>/
                   1634: #        (d) /home/httpd/html/priv/<domain>/<username>/ and client is the homeserver
1.489.2.23  raeburn  1635: #
                   1636: #    the response will be "refused".
                   1637: #
1.399     raeburn  1638: # Parameters:
                   1639: #    $cmd        - The command that dispatched us (ls).
                   1640: #    $tail       - The tail of the request that invoked us.
                   1641: #                  $tail is a : separated list of the following:
                   1642: #                   - $ulsdir - directory path to list (before prepending)
                   1643: #                   - $getpropath = 1 if &propath() should prepend
                   1644: #                   - $getuserdir = 1 if path to user dir in lonUsers should
                   1645: #                                     prepend
                   1646: #                   - $alternate_root - path to prepend
                   1647: #                   - $uname - username to use for &propath or user dir
                   1648: #                   - $udom - domain to use for &propath or user dir
                   1649: #            All of these except $getpropath and &getuserdir are escaped.    
                   1650: #                  no_such_dir.
                   1651: #    $client     - Socket open on the client.
                   1652: # Returns:
                   1653: #     1 - indicating that the daemon should not disconnect.
                   1654: # Side Effects:
                   1655: #   The reply is written to $client.
                   1656: #
                   1657: 
                   1658: sub ls3_handler {
                   1659:     my ($cmd, $tail, $client) = @_;
                   1660:     my $userinput = "$cmd:$tail";
                   1661:     my ($ulsdir,$getpropath,$getuserdir,$alternate_root,$uname,$udom) =
                   1662:         split(/:/,$tail);
                   1663:     if (defined($ulsdir)) {
                   1664:         $ulsdir = &unescape($ulsdir);
                   1665:     }
                   1666:     if (defined($alternate_root)) {
                   1667:         $alternate_root = &unescape($alternate_root);
                   1668:     }
                   1669:     if (defined($uname)) {
                   1670:         $uname = &unescape($uname);
                   1671:     }
                   1672:     if (defined($udom)) {
                   1673:         $udom = &unescape($udom);
                   1674:     }
                   1675: 
                   1676:     my $dir_root = $perlvar{'lonDocRoot'};
1.489.2.23  raeburn  1677:     if (($getpropath) || ($getuserdir)) {
1.399     raeburn  1678:         if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) {
                   1679:             $dir_root = &propath($udom,$uname);
                   1680:             $dir_root =~ s/\/$//;
                   1681:         } else {
1.489.2.23  raeburn  1682:             &Failure($client,"refused\n",$userinput);
1.399     raeburn  1683:             return 1;
                   1684:         }
1.400     raeburn  1685:     } elsif ($alternate_root ne '') {
1.399     raeburn  1686:         $dir_root = $alternate_root;
                   1687:     }
1.408     raeburn  1688:     if (($dir_root ne '') && ($dir_root ne '/')) {
1.400     raeburn  1689:         if ($ulsdir =~ /^\//) {
                   1690:             $ulsdir = $dir_root.$ulsdir;
                   1691:         } else {
                   1692:             $ulsdir = $dir_root.'/'.$ulsdir;
                   1693:         }
1.399     raeburn  1694:     }
1.489.2.23  raeburn  1695:     if ($ulsdir =~m{/\.\./}) {
                   1696:         &Failure($client,"refused\n",$userinput);
                   1697:         return 1;
                   1698:     }
                   1699:     my $islocal;
                   1700:     my @machine_ids = &Apache::lonnet::current_machine_ids();
                   1701:     if (grep(/^\Q$clientname\E$/,@machine_ids)) {
                   1702:         $islocal = 1;
                   1703:     }
1.399     raeburn  1704:     my $obs;
                   1705:     my $rights;
                   1706:     my $ulsout='';
                   1707:     my $ulsfn;
                   1708:     if (-e $ulsdir) {
                   1709:         if(-d $ulsdir) {
1.489.2.23  raeburn  1710:             unless (($getpropath) || ($getuserdir) ||
1.489.2.28  raeburn  1711:                     ($ulsdir =~ m{^/home/httpd/html/(res/$LONCAPA::match_domain|userfiles/)}) ||
                   1712:                     ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/userfiles}) ||
                   1713:                     (($ulsdir =~ m{^/home/httpd/html/priv/$LONCAPA::match_domain}) && ($islocal))) {
1.489.2.23  raeburn  1714:                 &Failure($client,"refused\n",$userinput);
                   1715:                 return 1;
                   1716:             }
1.399     raeburn  1717:             if (opendir(LSDIR,$ulsdir)) {
                   1718:                 while ($ulsfn=readdir(LSDIR)) {
                   1719:                     undef($obs);
                   1720:                     undef($rights);
                   1721:                     my @ulsstats=stat($ulsdir.'/'.$ulsfn);
                   1722:                     #We do some obsolete checking here
                   1723:                     if(-e $ulsdir.'/'.$ulsfn.".meta") {
                   1724:                         open(FILE, $ulsdir.'/'.$ulsfn.".meta");
                   1725:                         my @obsolete=<FILE>;
                   1726:                         foreach my $obsolete (@obsolete) {
                   1727:                             if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; }
                   1728:                             if($obsolete =~ m|(<copyright>)(default)|) {
                   1729:                                 $rights = 1;
                   1730:                             }
                   1731:                         }
                   1732:                     }
                   1733:                     my $tmp = $ulsfn.'&'.join('&',@ulsstats);
                   1734:                     if ($obs    eq '1') { $tmp.="&1"; } else { $tmp.="&0"; }
                   1735:                     if ($rights eq '1') { $tmp.="&1"; } else { $tmp.="&0"; }
                   1736:                     $ulsout.= &escape($tmp).':';
                   1737:                 }
                   1738:                 closedir(LSDIR);
                   1739:             }
                   1740:         } else {
1.489.2.23  raeburn  1741:             unless (($getpropath) || ($getuserdir) ||
1.489.2.28  raeburn  1742:                     ($ulsdir =~ m{^/home/httpd/lonUsers/$LONCAPA::match_domain(?:/[\w\-.@]){3}/$LONCAPA::match_name/}) ||
                   1743:                     ($ulsdir =~ m{^/home/httpd/html/(?:res|userfiles)/$LONCAPA::match_domain/$LONCAPA::match_name/}) ||
                   1744:                     (($ulsdir =~ m{^/home/httpd/html/priv/$LONCAPA::match_domain/$LONCAPA::match_name/}) && ($islocal))) {
1.489.2.23  raeburn  1745:                 &Failure($client,"refused\n",$userinput);
                   1746:                 return 1;
                   1747:             }
1.399     raeburn  1748:             my @ulsstats=stat($ulsdir);
                   1749:             $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
                   1750:         }
                   1751:     } else {
                   1752:         $ulsout='no_such_dir';
1.400     raeburn  1753:     }
                   1754:     if ($ulsout eq '') { $ulsout='empty'; }
                   1755:     &Reply($client, \$ulsout, $userinput); # This supports debug logging.
                   1756:     return 1;
1.399     raeburn  1757: }
                   1758: &register_handler("ls3", \&ls3_handler, 0, 1, 0);
1.280     matthew  1759: 
1.477     raeburn  1760: sub read_lonnet_global {
                   1761:     my ($cmd,$tail,$client) = @_;
                   1762:     my $userinput = "$cmd:$tail";
                   1763:     my $requested = &Apache::lonnet::thaw_unescape($tail);
                   1764:     my $result;
1.480     raeburn  1765:     my %packagevars = (
                   1766:                         spareid => \%Apache::lonnet::spareid,
                   1767:                         perlvar => \%Apache::lonnet::perlvar,
                   1768:                       );
                   1769:     my %limit_to = (
                   1770:                     perlvar => {
                   1771:                                  lonOtherAuthen => 1,
                   1772:                                  lonBalancer    => 1,
                   1773:                                  lonVersion     => 1,
                   1774:                                  lonSysEMail    => 1,
                   1775:                                  lonHostID      => 1,
                   1776:                                  lonRole        => 1,
                   1777:                                  lonDefDomain   => 1,
                   1778:                                  lonLoadLim     => 1,
                   1779:                                  lonUserLoadLim => 1,
                   1780:                                }
                   1781:                   );
1.477     raeburn  1782:     if (ref($requested) eq 'HASH') {
                   1783:         foreach my $what (keys(%{$requested})) {
                   1784:             my $response;
1.480     raeburn  1785:             my $items = {};
                   1786:             if (exists($packagevars{$what})) {
                   1787:                 if (ref($limit_to{$what}) eq 'HASH') {
                   1788:                     foreach my $varname (keys(%{$packagevars{$what}})) {
                   1789:                         if ($limit_to{$what}{$varname}) {
                   1790:                             $items->{$varname} = $packagevars{$what}{$varname};
                   1791:                         }
                   1792:                     }
                   1793:                 } else {
                   1794:                     $items = $packagevars{$what};
1.477     raeburn  1795:                 }
1.480     raeburn  1796:                 if ($what eq 'perlvar') {
                   1797:                     if (!exists($packagevars{$what}{'lonBalancer'})) {
1.489.2.31  raeburn  1798:                         if ($dist =~ /^(centos|rhes|fedora|scientific|oracle)/) {
1.480     raeburn  1799:                             my $othervarref=LONCAPA::Configuration::read_conf('httpd.conf');
                   1800:                             if (ref($othervarref) eq 'HASH') {
                   1801:                                 $items->{'lonBalancer'} = $othervarref->{'lonBalancer'};
                   1802:                             }
                   1803:                         }
                   1804:                     }
1.477     raeburn  1805:                 }
1.480     raeburn  1806:                 $response = &Apache::lonnet::freeze_escape($items);
1.477     raeburn  1807:             }
1.478     raeburn  1808:             $result .= &escape($what).'='.$response.'&';
1.477     raeburn  1809:         }
                   1810:     }
                   1811:     $result =~ s/\&$//;
                   1812:     &Reply($client,\$result,$userinput);
                   1813:     return 1;
                   1814: }
                   1815: &register_handler("readlonnetglobal", \&read_lonnet_global, 0, 1, 0);
                   1816: 
1.479     raeburn  1817: sub server_devalidatecache_handler {
                   1818:     my ($cmd,$tail,$client) = @_;
                   1819:     my $userinput = "$cmd:$tail";
1.489.2.9  raeburn  1820:     my $items = &unescape($tail);
                   1821:     my @cached = split(/\&/,$items);
                   1822:     foreach my $key (@cached) {
                   1823:         if ($key =~ /:/) {
                   1824:             my ($name,$id) = map { &unescape($_); } split(/:/,$key);
                   1825:             &Apache::lonnet::devalidate_cache_new($name,$id);
                   1826:         }
                   1827:     }
1.479     raeburn  1828:     my $result = 'ok';
                   1829:     &Reply($client,\$result,$userinput);
                   1830:     return 1;
                   1831: }
1.481     raeburn  1832: &register_handler("devalidatecache", \&server_devalidatecache_handler, 0, 1, 0);
1.479     raeburn  1833: 
1.410     raeburn  1834: sub server_timezone_handler {
                   1835:     my ($cmd,$tail,$client) = @_;
                   1836:     my $userinput = "$cmd:$tail";
                   1837:     my $timezone;
                   1838:     my $clockfile = '/etc/sysconfig/clock'; # Fedora/CentOS/SuSE
                   1839:     my $tzfile = '/etc/timezone'; # Debian/Ubuntu
                   1840:     if (-e $clockfile) {
                   1841:         if (open(my $fh,"<$clockfile")) {
                   1842:             while (<$fh>) {
                   1843:                 next if (/^[\#\s]/);
                   1844:                 if (/^(?:TIME)?ZONE\s*=\s*['"]?\s*([\w\/]+)/) {
                   1845:                     $timezone = $1;
                   1846:                     last;
                   1847:                 }
                   1848:             }
                   1849:             close($fh);
                   1850:         }
                   1851:     } elsif (-e $tzfile) {
                   1852:         if (open(my $fh,"<$tzfile")) {
                   1853:             $timezone = <$fh>;
                   1854:             close($fh);
                   1855:             chomp($timezone);
                   1856:             if ($timezone =~ m{^Etc/(\w+)$}) {
                   1857:                 $timezone = $1;
                   1858:             }
                   1859:         }
                   1860:     }
                   1861:     &Reply($client,\$timezone,$userinput); # This supports debug logging.
                   1862:     return 1;
                   1863: }
                   1864: &register_handler("servertimezone", \&server_timezone_handler, 0, 1, 0);
                   1865: 
1.413     raeburn  1866: sub server_loncaparev_handler {
                   1867:     my ($cmd,$tail,$client) = @_;
                   1868:     my $userinput = "$cmd:$tail";
                   1869:     &Reply($client,\$perlvar{'lonVersion'},$userinput);
                   1870:     return 1;
                   1871: }
                   1872: &register_handler("serverloncaparev", \&server_loncaparev_handler, 0, 1, 0);
                   1873: 
1.448     raeburn  1874: sub server_homeID_handler {
                   1875:     my ($cmd,$tail,$client) = @_;
                   1876:     my $userinput = "$cmd:$tail";
                   1877:     &Reply($client,\$perlvar{'lonHostID'},$userinput);
                   1878:     return 1;
                   1879: }
                   1880: &register_handler("serverhomeID", \&server_homeID_handler, 0, 1, 0);
                   1881: 
1.471     raeburn  1882: sub server_distarch_handler {
                   1883:     my ($cmd,$tail,$client) = @_;
                   1884:     my $userinput = "$cmd:$tail";
                   1885:     my $reply = &distro_and_arch();
                   1886:     &Reply($client,\$reply,$userinput);
                   1887:     return 1;
                   1888: }
                   1889: &register_handler("serverdistarch", \&server_distarch_handler, 0, 1, 0);
                   1890: 
1.218     foxr     1891: #   Process a reinit request.  Reinit requests that either
                   1892: #   lonc or lond be reinitialized so that an updated 
                   1893: #   host.tab or domain.tab can be processed.
                   1894: #
                   1895: # Parameters:
                   1896: #      $cmd    - the actual keyword that invoked us.
                   1897: #      $tail   - the tail of the request that invoked us.
                   1898: #      $client - File descriptor connected to the client
                   1899: #  Returns:
                   1900: #      1       - Ok to continue processing.
                   1901: #      0       - Program should exit
                   1902: #  Implicit output:
                   1903: #     a reply is sent to the client.
                   1904: #
                   1905: sub reinit_process_handler {
                   1906:     my ($cmd, $tail, $client) = @_;
                   1907:    
                   1908:     my $userinput = "$cmd:$tail";
                   1909:    
                   1910:     my $cert = &GetCertificate($userinput);
                   1911:     if(&ValidManager($cert)) {
                   1912: 	chomp($userinput);
                   1913: 	my $reply = &ReinitProcess($userinput);
1.387     albertel 1914: 	&Reply( $client,  \$reply, $userinput);
1.218     foxr     1915:     } else {
                   1916: 	&Failure( $client, "refused\n", $userinput);
                   1917:     }
                   1918:     return 1;
                   1919: }
                   1920: &register_handler("reinit", \&reinit_process_handler, 1, 0, 1);
                   1921: 
                   1922: #  Process the editing script for a table edit operation.
                   1923: #  the editing operation must be encrypted and requested by
                   1924: #  a manager host.
                   1925: #
                   1926: # Parameters:
                   1927: #      $cmd    - the actual keyword that invoked us.
                   1928: #      $tail   - the tail of the request that invoked us.
                   1929: #      $client - File descriptor connected to the client
                   1930: #  Returns:
                   1931: #      1       - Ok to continue processing.
                   1932: #      0       - Program should exit
                   1933: #  Implicit output:
                   1934: #     a reply is sent to the client.
                   1935: #
                   1936: sub edit_table_handler {
                   1937:     my ($command, $tail, $client) = @_;
                   1938:    
                   1939:     my $userinput = "$command:$tail";
                   1940: 
                   1941:     my $cert = &GetCertificate($userinput);
                   1942:     if(&ValidManager($cert)) {
                   1943: 	my($filetype, $script) = split(/:/, $tail);
                   1944: 	if (($filetype eq "hosts") || 
                   1945: 	    ($filetype eq "domain")) {
                   1946: 	    if($script ne "") {
                   1947: 		&Reply($client,              # BUGBUG - EditFile
                   1948: 		      &EditFile($userinput), #   could fail.
                   1949: 		      $userinput);
                   1950: 	    } else {
                   1951: 		&Failure($client,"refused\n",$userinput);
                   1952: 	    }
                   1953: 	} else {
                   1954: 	    &Failure($client,"refused\n",$userinput);
                   1955: 	}
                   1956:     } else {
                   1957: 	&Failure($client,"refused\n",$userinput);
                   1958:     }
                   1959:     return 1;
                   1960: }
1.263     albertel 1961: &register_handler("edit", \&edit_table_handler, 1, 0, 1);
1.218     foxr     1962: 
1.220     foxr     1963: #
                   1964: #   Authenticate a user against the LonCAPA authentication
                   1965: #   database.  Note that there are several authentication
                   1966: #   possibilities:
                   1967: #   - unix     - The user can be authenticated against the unix
                   1968: #                password file.
                   1969: #   - internal - The user can be authenticated against a purely 
                   1970: #                internal per user password file.
                   1971: #   - kerberos - The user can be authenticated against either a kerb4 or kerb5
                   1972: #                ticket granting authority.
                   1973: #   - user     - The person tailoring LonCAPA can supply a user authentication
                   1974: #                mechanism that is per system.
                   1975: #
                   1976: # Parameters:
                   1977: #    $cmd      - The command that got us here.
                   1978: #    $tail     - Tail of the command (remaining parameters).
                   1979: #    $client   - File descriptor connected to client.
                   1980: # Returns
                   1981: #     0        - Requested to exit, caller should shut down.
                   1982: #     1        - Continue processing.
                   1983: # Implicit inputs:
                   1984: #    The authentication systems describe above have their own forms of implicit
                   1985: #    input into the authentication process that are described above.
                   1986: #
                   1987: sub authenticate_handler {
                   1988:     my ($cmd, $tail, $client) = @_;
                   1989: 
                   1990:     
                   1991:     #  Regenerate the full input line 
                   1992:     
                   1993:     my $userinput  = $cmd.":".$tail;
                   1994:     
                   1995:     #  udom    - User's domain.
                   1996:     #  uname   - Username.
                   1997:     #  upass   - User's password.
1.396     raeburn  1998:     #  checkdefauth - Pass to validate_user() to try authentication
                   1999:     #                 with default auth type(s) if no user account.
1.447     raeburn  2000:     #  clientcancheckhost - Passed by clients with functionality in lonauth.pm
                   2001:     #                       to check if session can be hosted.
1.220     foxr     2002:     
1.447     raeburn  2003:     my ($udom, $uname, $upass, $checkdefauth, $clientcancheckhost)=split(/:/,$tail);
1.399     raeburn  2004:     &Debug(" Authenticate domain = $udom, user = $uname, password = $upass,  checkdefauth = $checkdefauth");
1.220     foxr     2005:     chomp($upass);
                   2006:     $upass=&unescape($upass);
                   2007: 
1.396     raeburn  2008:     my $pwdcorrect = &validate_user($udom,$uname,$upass,$checkdefauth);
1.220     foxr     2009:     if($pwdcorrect) {
1.447     raeburn  2010:         my $canhost = 1;
                   2011:         unless ($clientcancheckhost) {
1.448     raeburn  2012:             my $uprimary_id = &Apache::lonnet::domain($udom,'primary');
                   2013:             my $uint_dom = &Apache::lonnet::internet_dom($uprimary_id);
1.452     raeburn  2014:             my @intdoms;
                   2015:             my $internet_names = &Apache::lonnet::get_internet_names($clientname);
                   2016:             if (ref($internet_names) eq 'ARRAY') {
                   2017:                 @intdoms = @{$internet_names};
                   2018:             }
1.448     raeburn  2019:             unless ($uint_dom ne '' && grep(/^\Q$uint_dom\E$/,@intdoms)) {
1.447     raeburn  2020:                 my ($remote,$hosted);
                   2021:                 my $remotesession = &get_usersession_config($udom,'remotesession');
                   2022:                 if (ref($remotesession) eq 'HASH') {
1.489.2.32  raeburn  2023:                     $remote = $remotesession->{'remote'};
1.447     raeburn  2024:                 }
1.448     raeburn  2025:                 my $hostedsession = &get_usersession_config($clienthomedom,'hostedsession');
1.447     raeburn  2026:                 if (ref($hostedsession) eq 'HASH') {
                   2027:                     $hosted = $hostedsession->{'hosted'};
                   2028:                 }
1.449     raeburn  2029:                 my $loncaparev = $clientversion;
                   2030:                 if ($loncaparev eq '') {
                   2031:                     $loncaparev = $Apache::lonnet::loncaparevs{$clientname};
                   2032:                 }
1.448     raeburn  2033:                 $canhost = &Apache::lonnet::can_host_session($udom,$clientname,
1.449     raeburn  2034:                                                              $loncaparev,
1.447     raeburn  2035:                                                              $remote,$hosted);
                   2036:             }
                   2037:         }
                   2038:         if ($canhost) {               
                   2039:             &Reply( $client, "authorized\n", $userinput);
                   2040:         } else {
                   2041:             &Reply( $client, "not_allowed_to_host\n", $userinput);
                   2042:         }
1.220     foxr     2043: 	#
                   2044: 	#  Bad credentials: Failed to authorize
                   2045: 	#
                   2046:     } else {
                   2047: 	&Failure( $client, "non_authorized\n", $userinput);
                   2048:     }
                   2049: 
                   2050:     return 1;
                   2051: }
1.263     albertel 2052: &register_handler("auth", \&authenticate_handler, 1, 1, 0);
1.214     foxr     2053: 
1.222     foxr     2054: #
                   2055: #   Change a user's password.  Note that this function is complicated by
                   2056: #   the fact that a user may be authenticated in more than one way:
                   2057: #   At present, we are not able to change the password for all types of
                   2058: #   authentication methods.  Only for:
                   2059: #      unix    - unix password or shadow passoword style authentication.
                   2060: #      local   - Locally written authentication mechanism.
                   2061: #   For now, kerb4 and kerb5 password changes are not supported and result
                   2062: #   in an error.
                   2063: # FUTURE WORK:
                   2064: #    Support kerberos passwd changes?
                   2065: # Parameters:
                   2066: #    $cmd      - The command that got us here.
                   2067: #    $tail     - Tail of the command (remaining parameters).
                   2068: #    $client   - File descriptor connected to client.
                   2069: # Returns
                   2070: #     0        - Requested to exit, caller should shut down.
                   2071: #     1        - Continue processing.
                   2072: # Implicit inputs:
                   2073: #    The authentication systems describe above have their own forms of implicit
                   2074: #    input into the authentication process that are described above.
                   2075: sub change_password_handler {
                   2076:     my ($cmd, $tail, $client) = @_;
                   2077: 
                   2078:     my $userinput = $cmd.":".$tail;           # Reconstruct client's string.
                   2079: 
                   2080:     #
                   2081:     #  udom  - user's domain.
                   2082:     #  uname - Username.
                   2083:     #  upass - Current password.
                   2084:     #  npass - New password.
1.346     raeburn  2085:     #  context - Context in which this was called 
                   2086:     #            (preferences or reset_by_email).
1.428     raeburn  2087:     #  lonhost - HostID of server where request originated 
1.222     foxr     2088:    
1.428     raeburn  2089:     my ($udom,$uname,$upass,$npass,$context,$lonhost)=split(/:/,$tail);
1.222     foxr     2090: 
                   2091:     $upass=&unescape($upass);
                   2092:     $npass=&unescape($npass);
                   2093:     &Debug("Trying to change password for $uname");
                   2094: 
                   2095:     # First require that the user can be authenticated with their
1.346     raeburn  2096:     # old password unless context was 'reset_by_email':
                   2097:     
1.428     raeburn  2098:     my ($validated,$failure);
1.346     raeburn  2099:     if ($context eq 'reset_by_email') {
1.428     raeburn  2100:         if ($lonhost eq '') {
                   2101:             $failure = 'invalid_client';
                   2102:         } else {
                   2103:             $validated = 1;
                   2104:         }
1.346     raeburn  2105:     } else {
                   2106:         $validated = &validate_user($udom, $uname, $upass);
                   2107:     }
1.222     foxr     2108:     if($validated) {
                   2109: 	my $realpasswd  = &get_auth_type($udom, $uname); # Defined since authd.
                   2110: 	my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
1.489.2.33  raeburn  2111:         my $notunique;
1.222     foxr     2112: 	if ($howpwd eq 'internal') {
                   2113: 	    &Debug("internal auth");
1.489.2.21  raeburn  2114:             my $ncpass = &hash_passwd($udom,$npass);
1.489.2.33  raeburn  2115:             my (undef,$method,@rest) = split(/!/,$contentpwd);
                   2116:             if ($method eq 'bcrypt') {
                   2117:                 my %passwdconf = &Apache::lonnet::get_passwdconf($udom);
                   2118:                 if (($passwdconf{'numsaved'}) && ($passwdconf{'numsaved'} =~ /^\d+$/)) {
                   2119:                     my @oldpasswds;
                   2120:                     my $userpath = &propath($udom,$uname);
                   2121:                     my $fullpath = $userpath.'/oldpasswds';
                   2122:                     if (-d $userpath) {
                   2123:                         my @oldfiles;
                   2124:                         if (-e $fullpath) {
                   2125:                             if (opendir(my $dir,$fullpath)) {
                   2126:                                 (@oldfiles) = grep(/^\d+$/,readdir($dir));
                   2127:                                 closedir($dir);
                   2128:                             }
                   2129:                             if (@oldfiles) {
                   2130:                                 @oldfiles = sort { $b <=> $a } (@oldfiles);
                   2131:                                 my $numremoved = 0;
                   2132:                                 for (my $i=0; $i<@oldfiles; $i++) {
                   2133:                                     if ($i>=$passwdconf{'numsaved'}) {
                   2134:                                         if (-f "$fullpath/$oldfiles[$i]") {
                   2135:                                             if (unlink("$fullpath/$oldfiles[$i]")) {
                   2136:                                                 $numremoved ++;
                   2137:                                             }
                   2138:                                         }
                   2139:                                     } elsif (open(my $fh,'<',"$fullpath/$oldfiles[$i]")) {
                   2140:                                         while (my $line = <$fh>) {
                   2141:                                             push(@oldpasswds,$line);
                   2142:                                         }
                   2143:                                         close($fh);
                   2144:                                     }
                   2145:                                 }
                   2146:                                 if ($numremoved) {
                   2147:                                     &logthis("unlinked $numremoved old password files for $uname:$udom");
                   2148:                                 }
                   2149:                             }
                   2150:                         }
                   2151:                         push(@oldpasswds,$contentpwd);
                   2152:                         foreach my $item (@oldpasswds) {
                   2153:                             my (undef,$method,@rest) = split(/!/,$item);
                   2154:                             if ($method eq 'bcrypt') {
                   2155:                                 my $result = &hash_passwd($udom,$npass,@rest);
                   2156:                                 if ($result eq $item) {
                   2157:                                     $notunique = 1;
                   2158:                                     last;
                   2159:                                 }
                   2160:                             }
                   2161:                         }
                   2162:                         unless ($notunique) {
                   2163:                             unless (-e $fullpath) {
                   2164:                                 if (&mkpath("$fullpath/")) {
                   2165:                                     chmod(0700,$fullpath);
                   2166:                                 }
                   2167:                             }
                   2168:                             if (-d $fullpath) {
                   2169:                                 my $now = time;
                   2170:                                 if (open(my $fh,'>',"$fullpath/$now")) {
                   2171:                                     print $fh $contentpwd;
                   2172:                                     close($fh);
                   2173:                                     chmod(0400,"$fullpath/$now");
                   2174:                                 }
                   2175:                             }
                   2176:                         }
                   2177:                     }
                   2178:                 }
                   2179:             }
                   2180:             if ($notunique) {
                   2181:                 my $msg="Result of password change for $uname:$udom - password matches one used before";
                   2182:                 if ($lonhost) {
                   2183:                     $msg .= " - request originated from: $lonhost";
                   2184:                 }
                   2185:                 &logthis($msg);
                   2186:                 &Reply($client, "prioruse\n", $userinput);
                   2187: 	    } elsif (&rewrite_password_file($udom, $uname, "internal:$ncpass")) {
1.428     raeburn  2188: 		my $msg="Result of password change for $uname: pwchange_success";
                   2189:                 if ($lonhost) {
                   2190:                     $msg .= " - request originated from: $lonhost";
                   2191:                 }
                   2192:                 &logthis($msg);
1.489.2.21  raeburn  2193:                 &update_passwd_history($uname,$udom,$howpwd,$context);
1.222     foxr     2194: 		&Reply($client, "ok\n", $userinput);
                   2195: 	    } else {
                   2196: 		&logthis("Unable to open $uname passwd "               
                   2197: 			 ."to change password");
                   2198: 		&Failure( $client, "non_authorized\n",$userinput);
                   2199: 	    }
1.346     raeburn  2200: 	} elsif ($howpwd eq 'unix' && $context ne 'reset_by_email') {
1.287     foxr     2201: 	    my $result = &change_unix_password($uname, $npass);
1.489.2.21  raeburn  2202:             if ($result eq 'ok') {
                   2203:                 &update_passwd_history($uname,$udom,$howpwd,$context);
                   2204:             }
1.222     foxr     2205: 	    &logthis("Result of password change for $uname: ".
1.287     foxr     2206: 		     $result);
1.387     albertel 2207: 	    &Reply($client, \$result, $userinput);
1.222     foxr     2208: 	} else {
                   2209: 	    # this just means that the current password mode is not
                   2210: 	    # one we know how to change (e.g the kerberos auth modes or
                   2211: 	    # locally written auth handler).
                   2212: 	    #
                   2213: 	    &Failure( $client, "auth_mode_error\n", $userinput);
                   2214: 	}  
1.224     foxr     2215:     } else {
1.428     raeburn  2216: 	if ($failure eq '') {
                   2217: 	    $failure = 'non_authorized';
                   2218: 	}
                   2219: 	&Failure( $client, "$failure\n", $userinput);
1.222     foxr     2220:     }
                   2221: 
                   2222:     return 1;
                   2223: }
1.263     albertel 2224: &register_handler("passwd", \&change_password_handler, 1, 1, 0);
1.222     foxr     2225: 
1.489.2.21  raeburn  2226: sub hash_passwd {
                   2227:     my ($domain,$plainpass,@rest) = @_;
                   2228:     my ($salt,$cost);
                   2229:     if (@rest) {
                   2230:         $cost = $rest[0];
                   2231:         # salt is first 22 characters, base-64 encoded by bcrypt
                   2232:         my $plainsalt = substr($rest[1],0,22);
                   2233:         $salt = Crypt::Eksblowfish::Bcrypt::de_base64($plainsalt);
                   2234:     } else {
1.489.2.26  raeburn  2235:         my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
                   2236:         my $defaultcost = $domdefaults{'intauth_cost'};
1.489.2.21  raeburn  2237:         if (($defaultcost eq '') || ($defaultcost =~ /D/)) {
                   2238:             $cost = 10;
                   2239:         } else {
                   2240:             $cost = $defaultcost;
                   2241:         }
                   2242:         # Generate random 16-octet base64 salt
                   2243:         $salt = "";
                   2244:         $salt .= pack("C", int rand(256)) for 1..16;
                   2245:     }
                   2246:     my $hash = &Crypt::Eksblowfish::Bcrypt::bcrypt_hash({
                   2247:         key_nul => 1,
                   2248:         cost    => $cost,
                   2249:         salt    => $salt,
                   2250:     }, Digest::SHA::sha512(Encode::encode('UTF-8',$plainpass)));
                   2251: 
                   2252:     my $result = join("!", "", "bcrypt", sprintf("%02d",$cost),
                   2253:                 &Crypt::Eksblowfish::Bcrypt::en_base64($salt).
                   2254:                 &Crypt::Eksblowfish::Bcrypt::en_base64($hash));
                   2255:     return $result;
                   2256: }
                   2257: 
1.225     foxr     2258: #
                   2259: #   Create a new user.  User in this case means a lon-capa user.
                   2260: #   The user must either already exist in some authentication realm
                   2261: #   like kerberos or the /etc/passwd.  If not, a user completely local to
                   2262: #   this loncapa system is created.
                   2263: #
                   2264: # Parameters:
                   2265: #    $cmd      - The command that got us here.
                   2266: #    $tail     - Tail of the command (remaining parameters).
                   2267: #    $client   - File descriptor connected to client.
                   2268: # Returns
                   2269: #     0        - Requested to exit, caller should shut down.
                   2270: #     1        - Continue processing.
                   2271: # Implicit inputs:
                   2272: #    The authentication systems describe above have their own forms of implicit
                   2273: #    input into the authentication process that are described above.
                   2274: sub add_user_handler {
                   2275: 
                   2276:     my ($cmd, $tail, $client) = @_;
                   2277: 
                   2278: 
                   2279:     my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
                   2280:     my $userinput = $cmd.":".$tail; # Reconstruct the full request line.
                   2281: 
                   2282:     &Debug("cmd =".$cmd." $udom =".$udom." uname=".$uname);
                   2283: 
                   2284: 
                   2285:     if($udom eq $currentdomainid) { # Reject new users for other domains...
                   2286: 	
                   2287: 	my $oldumask=umask(0077);
                   2288: 	chomp($npass);
                   2289: 	$npass=&unescape($npass);
                   2290: 	my $passfilename  = &password_path($udom, $uname);
                   2291: 	&Debug("Password file created will be:".$passfilename);
                   2292: 	if (-e $passfilename) {
                   2293: 	    &Failure( $client, "already_exists\n", $userinput);
                   2294: 	} else {
                   2295: 	    my $fperror='';
1.264     albertel 2296: 	    if (!&mkpath($passfilename)) {
                   2297: 		$fperror="error: ".($!+0)." mkdir failed while attempting "
                   2298: 		    ."makeuser";
1.225     foxr     2299: 	    }
                   2300: 	    unless ($fperror) {
1.489.2.21  raeburn  2301: 		my $result=&make_passwd_file($uname,$udom,$umode,$npass,
                   2302:                                              $passfilename,'makeuser');
1.390     raeburn  2303: 		&Reply($client,\$result, $userinput);     #BUGBUG - could be fail
1.225     foxr     2304: 	    } else {
1.387     albertel 2305: 		&Failure($client, \$fperror, $userinput);
1.225     foxr     2306: 	    }
                   2307: 	}
                   2308: 	umask($oldumask);
                   2309:     }  else {
                   2310: 	&Failure($client, "not_right_domain\n",
                   2311: 		$userinput);	# Even if we are multihomed.
                   2312:     
                   2313:     }
                   2314:     return 1;
                   2315: 
                   2316: }
                   2317: &register_handler("makeuser", \&add_user_handler, 1, 1, 0);
                   2318: 
                   2319: #
                   2320: #   Change the authentication method of a user.  Note that this may
                   2321: #   also implicitly change the user's password if, for example, the user is
                   2322: #   joining an existing authentication realm.  Known authentication realms at
                   2323: #   this time are:
                   2324: #    internal   - Purely internal password file (only loncapa knows this user)
                   2325: #    local      - Institutionally written authentication module.
                   2326: #    unix       - Unix user (/etc/passwd with or without /etc/shadow).
                   2327: #    kerb4      - kerberos version 4
                   2328: #    kerb5      - kerberos version 5
                   2329: #
                   2330: # Parameters:
                   2331: #    $cmd      - The command that got us here.
                   2332: #    $tail     - Tail of the command (remaining parameters).
                   2333: #    $client   - File descriptor connected to client.
                   2334: # Returns
                   2335: #     0        - Requested to exit, caller should shut down.
                   2336: #     1        - Continue processing.
                   2337: # Implicit inputs:
                   2338: #    The authentication systems describe above have their own forms of implicit
                   2339: #    input into the authentication process that are described above.
1.287     foxr     2340: # NOTE:
                   2341: #   This is also used to change the authentication credential values (e.g. passwd).
                   2342: #   
1.225     foxr     2343: #
                   2344: sub change_authentication_handler {
                   2345: 
                   2346:     my ($cmd, $tail, $client) = @_;
                   2347:    
                   2348:     my $userinput  = "$cmd:$tail";              # Reconstruct user input.
                   2349: 
                   2350:     my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
                   2351:     &Debug("cmd = ".$cmd." domain= ".$udom."uname =".$uname." umode= ".$umode);
                   2352:     if ($udom ne $currentdomainid) {
                   2353: 	&Failure( $client, "not_right_domain\n", $client);
                   2354:     } else {
                   2355: 	
                   2356: 	chomp($npass);
                   2357: 	
                   2358: 	$npass=&unescape($npass);
1.261     foxr     2359: 	my $oldauth = &get_auth_type($udom, $uname); # Get old auth info.
1.225     foxr     2360: 	my $passfilename = &password_path($udom, $uname);
                   2361: 	if ($passfilename) {	# Not allowed to create a new user!!
1.287     foxr     2362: 	    # If just changing the unix passwd. need to arrange to run
1.489.2.8  raeburn  2363: 	    # passwd since otherwise make_passwd_file will fail as 
                   2364: 	    # creation of unix authenticated users is no longer supported
                   2365:             # except from the command line, when running make_domain_coordinator.pl
1.287     foxr     2366: 
                   2367: 	    if(($oldauth =~/^unix/) && ($umode eq "unix")) {
                   2368: 		my $result = &change_unix_password($uname, $npass);
                   2369: 		&logthis("Result of password change for $uname: ".$result);
                   2370: 		if ($result eq "ok") {
1.489.2.21  raeburn  2371:                     &update_passwd_history($uname,$udom,$umode,'changeuserauth');
1.390     raeburn  2372: 		    &Reply($client, \$result);
1.288     albertel 2373: 		} else {
1.387     albertel 2374: 		    &Failure($client, \$result);
1.287     foxr     2375: 		}
1.288     albertel 2376: 	    } else {
1.489.2.21  raeburn  2377: 		my $result=&make_passwd_file($uname,$udom,$umode,$npass,
                   2378:                                              $passfilename,'changeuserauth');
1.287     foxr     2379: 		#
                   2380: 		#  If the current auth mode is internal, and the old auth mode was
                   2381: 		#  unix, or krb*,  and the user is an author for this domain,
                   2382: 		#  re-run manage_permissions for that role in order to be able
                   2383: 		#  to take ownership of the construction space back to www:www
                   2384: 		#
1.489.2.8  raeburn  2385: 
                   2386: 
1.387     albertel 2387: 		&Reply($client, \$result, $userinput);
1.261     foxr     2388: 	    }
                   2389: 	       
                   2390: 
1.225     foxr     2391: 	} else {	       
1.251     foxr     2392: 	    &Failure($client, "non_authorized\n", $userinput); # Fail the user now.
1.225     foxr     2393: 	}
                   2394:     }
                   2395:     return 1;
                   2396: }
                   2397: &register_handler("changeuserauth", \&change_authentication_handler, 1,1, 0);
                   2398: 
1.489.2.21  raeburn  2399: sub update_passwd_history {
                   2400:     my ($uname,$udom,$umode,$context) = @_;
                   2401:     my $proname=&propath($udom,$uname);
                   2402:     my $now = time;
                   2403:     if (open(my $fh,">>$proname/passwd.log")) {
                   2404:         print $fh "$now:$umode:$context\n";
                   2405:         close($fh);
                   2406:     }
                   2407:     return;
                   2408: }
                   2409: 
1.225     foxr     2410: #
                   2411: #   Determines if this is the home server for a user.  The home server
                   2412: #   for a user will have his/her lon-capa passwd file.  Therefore all we need
                   2413: #   to do is determine if this file exists.
                   2414: #
                   2415: # Parameters:
                   2416: #    $cmd      - The command that got us here.
                   2417: #    $tail     - Tail of the command (remaining parameters).
                   2418: #    $client   - File descriptor connected to client.
                   2419: # Returns
                   2420: #     0        - Requested to exit, caller should shut down.
                   2421: #     1        - Continue processing.
                   2422: # Implicit inputs:
                   2423: #    The authentication systems describe above have their own forms of implicit
                   2424: #    input into the authentication process that are described above.
                   2425: #
                   2426: sub is_home_handler {
                   2427:     my ($cmd, $tail, $client) = @_;
                   2428:    
                   2429:     my $userinput  = "$cmd:$tail";
                   2430:    
                   2431:     my ($udom,$uname)=split(/:/,$tail);
                   2432:     chomp($uname);
                   2433:     my $passfile = &password_filename($udom, $uname);
                   2434:     if($passfile) {
                   2435: 	&Reply( $client, "found\n", $userinput);
                   2436:     } else {
                   2437: 	&Failure($client, "not_found\n", $userinput);
                   2438:     }
                   2439:     return 1;
                   2440: }
                   2441: &register_handler("home", \&is_home_handler, 0,1,0);
                   2442: 
                   2443: #
1.434     www      2444: #   Process an update request for a resource.
                   2445: #   A resource has been modified that we hold a subscription to.
1.225     foxr     2446: #   If the resource is not local, then we must update, or at least invalidate our
                   2447: #   cached copy of the resource. 
                   2448: # Parameters:
                   2449: #    $cmd      - The command that got us here.
                   2450: #    $tail     - Tail of the command (remaining parameters).
                   2451: #    $client   - File descriptor connected to client.
                   2452: # Returns
                   2453: #     0        - Requested to exit, caller should shut down.
                   2454: #     1        - Continue processing.
                   2455: # Implicit inputs:
                   2456: #    The authentication systems describe above have their own forms of implicit
                   2457: #    input into the authentication process that are described above.
                   2458: #
                   2459: sub update_resource_handler {
                   2460: 
                   2461:     my ($cmd, $tail, $client) = @_;
                   2462:    
                   2463:     my $userinput = "$cmd:$tail";
                   2464:    
                   2465:     my $fname= $tail;		# This allows interactive testing
                   2466: 
                   2467: 
                   2468:     my $ownership=ishome($fname);
                   2469:     if ($ownership eq 'not_owner') {
                   2470: 	if (-e $fname) {
1.434     www      2471:             # Delete preview file, if exists
                   2472:             unlink("$fname.tmp");
                   2473:             # Get usage stats
1.225     foxr     2474: 	    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
                   2475: 		$atime,$mtime,$ctime,$blksize,$blocks)=stat($fname);
                   2476: 	    my $now=time;
                   2477: 	    my $since=$now-$atime;
1.434     www      2478:             # If the file has not been used within lonExpire seconds,
                   2479:             # unsubscribe from it and delete local copy
1.225     foxr     2480: 	    if ($since>$perlvar{'lonExpire'}) {
1.365     albertel 2481: 		my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname");
1.308     albertel 2482: 		&devalidate_meta_cache($fname);
1.225     foxr     2483: 		unlink("$fname");
1.334     albertel 2484: 		unlink("$fname.meta");
1.225     foxr     2485: 	    } else {
1.434     www      2486:             # Yes, this is in active use. Get a fresh copy. Since it might be in
                   2487:             # very active use and huge (like a movie), copy it to "in.transfer" filename first.
1.225     foxr     2488: 		my $transname="$fname.in.transfer";
1.365     albertel 2489: 		my $remoteurl=&Apache::lonnet::reply("sub:$fname","$clientname");
1.225     foxr     2490: 		my $response;
1.455     www      2491: # FIXME: cannot replicate files that take more than two minutes to transfer?
                   2492: #		alarm(120);
                   2493: # FIXME: this should use the LWP mechanism, not internal alarms.
                   2494:                 alarm(1200);
1.225     foxr     2495: 		{
                   2496: 		    my $ua=new LWP::UserAgent;
                   2497: 		    my $request=new HTTP::Request('GET',"$remoteurl");
                   2498: 		    $response=$ua->request($request,$transname);
                   2499: 		}
                   2500: 		alarm(0);
                   2501: 		if ($response->is_error()) {
1.489.2.30  raeburn  2502:                     my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname");
                   2503:                     &devalidate_meta_cache($fname);
                   2504:                     if (-e $transname) {
                   2505:                         unlink($transname);
                   2506:                     }
                   2507:                     unlink($fname);
1.225     foxr     2508: 		    my $message=$response->status_line;
                   2509: 		    &logthis("LWP GET: $message for $fname ($remoteurl)");
                   2510: 		} else {
                   2511: 		    if ($remoteurl!~/\.meta$/) {
1.455     www      2512: # FIXME: isn't there an internal LWP mechanism for this?
1.225     foxr     2513: 			alarm(120);
                   2514: 			{
                   2515: 			    my $ua=new LWP::UserAgent;
                   2516: 			    my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta');
                   2517: 			    my $mresponse=$ua->request($mrequest,$fname.'.meta');
                   2518: 			    if ($mresponse->is_error()) {
                   2519: 				unlink($fname.'.meta');
                   2520: 			    }
                   2521: 			}
                   2522: 			alarm(0);
                   2523: 		    }
1.434     www      2524:                     # we successfully transfered, copy file over to real name
1.225     foxr     2525: 		    rename($transname,$fname);
1.308     albertel 2526: 		    &devalidate_meta_cache($fname);
1.225     foxr     2527: 		}
                   2528: 	    }
                   2529: 	    &Reply( $client, "ok\n", $userinput);
                   2530: 	} else {
                   2531: 	    &Failure($client, "not_found\n", $userinput);
                   2532: 	}
                   2533:     } else {
                   2534: 	&Failure($client, "rejected\n", $userinput);
                   2535:     }
                   2536:     return 1;
                   2537: }
                   2538: &register_handler("update", \&update_resource_handler, 0 ,1, 0);
                   2539: 
1.308     albertel 2540: sub devalidate_meta_cache {
                   2541:     my ($url) = @_;
                   2542:     use Cache::Memcached;
                   2543:     my $memcache = new Cache::Memcached({'servers'=>['127.0.0.1:11211']});
1.365     albertel 2544:     $url = &Apache::lonnet::declutter($url);
1.308     albertel 2545:     $url =~ s-\.meta$--;
                   2546:     my $id = &escape('meta:'.$url);
                   2547:     $memcache->delete($id);
                   2548: }
                   2549: 
1.225     foxr     2550: #
1.226     foxr     2551: #   Fetch a user file from a remote server to the user's home directory
                   2552: #   userfiles subdir.
1.225     foxr     2553: # Parameters:
                   2554: #    $cmd      - The command that got us here.
                   2555: #    $tail     - Tail of the command (remaining parameters).
                   2556: #    $client   - File descriptor connected to client.
                   2557: # Returns
                   2558: #     0        - Requested to exit, caller should shut down.
                   2559: #     1        - Continue processing.
                   2560: #
                   2561: sub fetch_user_file_handler {
                   2562: 
                   2563:     my ($cmd, $tail, $client) = @_;
                   2564: 
                   2565:     my $userinput = "$cmd:$tail";
                   2566:     my $fname           = $tail;
1.232     foxr     2567:     my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
1.225     foxr     2568:     my $udir=&propath($udom,$uname).'/userfiles';
                   2569:     unless (-e $udir) {
                   2570: 	mkdir($udir,0770); 
                   2571:     }
1.232     foxr     2572:     Debug("fetch user file for $fname");
1.225     foxr     2573:     if (-e $udir) {
                   2574: 	$ufile=~s/^[\.\~]+//;
1.232     foxr     2575: 
                   2576: 	# IF necessary, create the path right down to the file.
                   2577: 	# Note that any regular files in the way of this path are
                   2578: 	# wiped out to deal with some earlier folly of mine.
                   2579: 
1.267     raeburn  2580: 	if (!&mkpath($udir.'/'.$ufile)) {
1.264     albertel 2581: 	    &Failure($client, "unable_to_create\n", $userinput);	    
1.232     foxr     2582: 	}
                   2583: 
1.225     foxr     2584: 	my $destname=$udir.'/'.$ufile;
                   2585: 	my $transname=$udir.'/'.$ufile.'.in.transit';
1.476     raeburn  2586:         my $clientprotocol=$Apache::lonnet::protocol{$clientname};
                   2587:         $clientprotocol = 'http' if ($clientprotocol ne 'https');
1.486     raeburn  2588: 	my $clienthost = &Apache::lonnet::hostname($clientname);
                   2589: 	my $remoteurl=$clientprotocol.'://'.$clienthost.'/userfiles/'.$fname;
1.225     foxr     2590: 	my $response;
1.232     foxr     2591: 	Debug("Remote URL : $remoteurl Transfername $transname Destname: $destname");
1.225     foxr     2592: 	alarm(120);
                   2593: 	{
                   2594: 	    my $ua=new LWP::UserAgent;
                   2595: 	    my $request=new HTTP::Request('GET',"$remoteurl");
                   2596: 	    $response=$ua->request($request,$transname);
                   2597: 	}
                   2598: 	alarm(0);
                   2599: 	if ($response->is_error()) {
                   2600: 	    unlink($transname);
                   2601: 	    my $message=$response->status_line;
                   2602: 	    &logthis("LWP GET: $message for $fname ($remoteurl)");
                   2603: 	    &Failure($client, "failed\n", $userinput);
                   2604: 	} else {
1.232     foxr     2605: 	    Debug("Renaming $transname to $destname");
1.225     foxr     2606: 	    if (!rename($transname,$destname)) {
                   2607: 		&logthis("Unable to move $transname to $destname");
                   2608: 		unlink($transname);
                   2609: 		&Failure($client, "failed\n", $userinput);
                   2610: 	    } else {
1.489.2.2  raeburn  2611:                 if ($fname =~ /^default.+\.(page|sequence)$/) {
                   2612:                     my ($major,$minor) = split(/\./,$clientversion);
                   2613:                     if (($major < 2) || ($major == 2 && $minor < 11)) {
                   2614:                         my $now = time;
                   2615:                         &Apache::lonnet::do_cache_new('crschange',$udom.'_'.$uname,$now,600);
                   2616:                         my $key = &escape('internal.contentchange');
                   2617:                         my $what = "$key=$now";
                   2618:                         my $hashref = &tie_user_hash($udom,$uname,'environment',
                   2619:                                                      &GDBM_WRCREAT(),"P",$what);
                   2620:                         if ($hashref) {
                   2621:                             $hashref->{$key}=$now;
                   2622:                             if (!&untie_user_hash($hashref)) {
                   2623:                                 &logthis("error: ".($!+0)." untie (GDBM) failed ".
                   2624:                                          "when updating internal.contentchange");
                   2625:                             }
                   2626:                         }
                   2627:                     }
                   2628:                 }
1.225     foxr     2629: 		&Reply($client, "ok\n", $userinput);
                   2630: 	    }
                   2631: 	}   
                   2632:     } else {
                   2633: 	&Failure($client, "not_home\n", $userinput);
                   2634:     }
                   2635:     return 1;
                   2636: }
                   2637: &register_handler("fetchuserfile", \&fetch_user_file_handler, 0, 1, 0);
                   2638: 
1.226     foxr     2639: #
                   2640: #   Remove a file from a user's home directory userfiles subdirectory.
                   2641: # Parameters:
                   2642: #    cmd   - the Lond request keyword that got us here.
                   2643: #    tail  - the part of the command past the keyword.
                   2644: #    client- File descriptor connected with the client.
                   2645: #
                   2646: # Returns:
                   2647: #    1    - Continue processing.
                   2648: sub remove_user_file_handler {
                   2649:     my ($cmd, $tail, $client) = @_;
                   2650: 
                   2651:     my ($fname) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent.
                   2652: 
                   2653:     my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
                   2654:     if ($ufile =~m|/\.\./|) {
                   2655: 	# any files paths with /../ in them refuse 
                   2656: 	# to deal with
                   2657: 	&Failure($client, "refused\n", "$cmd:$tail");
                   2658:     } else {
                   2659: 	my $udir = &propath($udom,$uname);
                   2660: 	if (-e $udir) {
                   2661: 	    my $file=$udir.'/userfiles/'.$ufile;
                   2662: 	    if (-e $file) {
1.253     foxr     2663: 		#
                   2664: 		#   If the file is a regular file unlink is fine...
1.489.2.19  raeburn  2665: 		#   However it's possible the client wants a dir
                   2666: 		#   removed, in which case rmdir is more appropriate
                   2667: 	        #   Note: rmdir will only remove an empty directory.
1.253     foxr     2668: 		#
1.240     banghart 2669: 	        if (-f $file){
1.241     albertel 2670: 		    unlink($file);
1.489.2.19  raeburn  2671:                     # for html files remove the associated .bak file
                   2672:                     # which may have been created by the editor.
                   2673:                     if ($ufile =~ m{^((docs|supplemental)/(?:\d+|default)/\d+(?:|/.+)/)[^/]+\.x?html?$}i) {
                   2674:                         my $path = $1;
                   2675:                         if (-e $file.'.bak') {
                   2676:                             unlink($file.'.bak');
                   2677:                         }
                   2678:                     }
1.241     albertel 2679: 		} elsif(-d $file) {
                   2680: 		    rmdir($file);
1.240     banghart 2681: 		}
1.226     foxr     2682: 		if (-e $file) {
1.253     foxr     2683: 		    #  File is still there after we deleted it ?!?
                   2684: 
1.226     foxr     2685: 		    &Failure($client, "failed\n", "$cmd:$tail");
                   2686: 		} else {
                   2687: 		    &Reply($client, "ok\n", "$cmd:$tail");
                   2688: 		}
                   2689: 	    } else {
                   2690: 		&Failure($client, "not_found\n", "$cmd:$tail");
                   2691: 	    }
                   2692: 	} else {
                   2693: 	    &Failure($client, "not_home\n", "$cmd:$tail");
                   2694: 	}
                   2695:     }
                   2696:     return 1;
                   2697: }
                   2698: &register_handler("removeuserfile", \&remove_user_file_handler, 0,1,0);
                   2699: 
1.236     albertel 2700: #
                   2701: #   make a directory in a user's home directory userfiles subdirectory.
                   2702: # Parameters:
                   2703: #    cmd   - the Lond request keyword that got us here.
                   2704: #    tail  - the part of the command past the keyword.
                   2705: #    client- File descriptor connected with the client.
                   2706: #
                   2707: # Returns:
                   2708: #    1    - Continue processing.
                   2709: sub mkdir_user_file_handler {
                   2710:     my ($cmd, $tail, $client) = @_;
                   2711: 
                   2712:     my ($dir) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent.
                   2713:     $dir=&unescape($dir);
                   2714:     my ($udom,$uname,$ufile) = ($dir =~ m|^([^/]+)/([^/]+)/(.+)$|);
                   2715:     if ($ufile =~m|/\.\./|) {
                   2716: 	# any files paths with /../ in them refuse 
                   2717: 	# to deal with
                   2718: 	&Failure($client, "refused\n", "$cmd:$tail");
                   2719:     } else {
                   2720: 	my $udir = &propath($udom,$uname);
                   2721: 	if (-e $udir) {
1.264     albertel 2722: 	    my $newdir=$udir.'/userfiles/'.$ufile.'/';
                   2723: 	    if (!&mkpath($newdir)) {
                   2724: 		&Failure($client, "failed\n", "$cmd:$tail");
1.236     albertel 2725: 	    }
1.264     albertel 2726: 	    &Reply($client, "ok\n", "$cmd:$tail");
1.236     albertel 2727: 	} else {
                   2728: 	    &Failure($client, "not_home\n", "$cmd:$tail");
                   2729: 	}
                   2730:     }
                   2731:     return 1;
                   2732: }
                   2733: &register_handler("mkdiruserfile", \&mkdir_user_file_handler, 0,1,0);
                   2734: 
1.237     albertel 2735: #
                   2736: #   rename a file in a user's home directory userfiles subdirectory.
                   2737: # Parameters:
                   2738: #    cmd   - the Lond request keyword that got us here.
                   2739: #    tail  - the part of the command past the keyword.
                   2740: #    client- File descriptor connected with the client.
                   2741: #
                   2742: # Returns:
                   2743: #    1    - Continue processing.
                   2744: sub rename_user_file_handler {
                   2745:     my ($cmd, $tail, $client) = @_;
                   2746: 
                   2747:     my ($udom,$uname,$old,$new) = split(/:/, $tail);
                   2748:     $old=&unescape($old);
                   2749:     $new=&unescape($new);
                   2750:     if ($new =~m|/\.\./| || $old =~m|/\.\./|) {
                   2751: 	# any files paths with /../ in them refuse to deal with
                   2752: 	&Failure($client, "refused\n", "$cmd:$tail");
                   2753:     } else {
                   2754: 	my $udir = &propath($udom,$uname);
                   2755: 	if (-e $udir) {
                   2756: 	    my $oldfile=$udir.'/userfiles/'.$old;
                   2757: 	    my $newfile=$udir.'/userfiles/'.$new;
                   2758: 	    if (-e $newfile) {
                   2759: 		&Failure($client, "exists\n", "$cmd:$tail");
                   2760: 	    } elsif (! -e $oldfile) {
                   2761: 		&Failure($client, "not_found\n", "$cmd:$tail");
                   2762: 	    } else {
                   2763: 		if (!rename($oldfile,$newfile)) {
                   2764: 		    &Failure($client, "failed\n", "$cmd:$tail");
                   2765: 		} else {
                   2766: 		    &Reply($client, "ok\n", "$cmd:$tail");
                   2767: 		}
                   2768: 	    }
                   2769: 	} else {
                   2770: 	    &Failure($client, "not_home\n", "$cmd:$tail");
                   2771: 	}
                   2772:     }
                   2773:     return 1;
                   2774: }
                   2775: &register_handler("renameuserfile", \&rename_user_file_handler, 0,1,0);
                   2776: 
1.227     foxr     2777: #
1.382     albertel 2778: #  Checks if the specified user has an active session on the server
                   2779: #  return ok if so, not_found if not
                   2780: #
                   2781: # Parameters:
                   2782: #   cmd      - The request keyword that dispatched to tus.
                   2783: #   tail     - The tail of the request (colon separated parameters).
                   2784: #   client   - Filehandle open on the client.
                   2785: # Return:
                   2786: #    1.
                   2787: sub user_has_session_handler {
                   2788:     my ($cmd, $tail, $client) = @_;
                   2789: 
                   2790:     my ($udom, $uname) = map { &unescape($_) } (split(/:/, $tail));
                   2791:     
                   2792:     opendir(DIR,$perlvar{'lonIDsDir'});
                   2793:     my $filename;
                   2794:     while ($filename=readdir(DIR)) {
                   2795: 	last if ($filename=~/^\Q$uname\E_\d+_\Q$udom\E_/);
                   2796:     }
                   2797:     if ($filename) {
                   2798: 	&Reply($client, "ok\n", "$cmd:$tail");
                   2799:     } else {
                   2800: 	&Failure($client, "not_found\n", "$cmd:$tail");
                   2801:     }
                   2802:     return 1;
                   2803: 
                   2804: }
                   2805: &register_handler("userhassession", \&user_has_session_handler, 0,1,0);
                   2806: 
                   2807: #
1.263     albertel 2808: #  Authenticate access to a user file by checking that the token the user's 
                   2809: #  passed also exists in their session file
1.227     foxr     2810: #
                   2811: # Parameters:
                   2812: #   cmd      - The request keyword that dispatched to tus.
                   2813: #   tail     - The tail of the request (colon separated parameters).
                   2814: #   client   - Filehandle open on the client.
                   2815: # Return:
                   2816: #    1.
                   2817: sub token_auth_user_file_handler {
                   2818:     my ($cmd, $tail, $client) = @_;
                   2819: 
                   2820:     my ($fname, $session) = split(/:/, $tail);
                   2821:     
                   2822:     chomp($session);
1.393     raeburn  2823:     my $reply="non_auth";
1.343     albertel 2824:     my $file = $perlvar{'lonIDsDir'}.'/'.$session.'.id';
                   2825:     if (open(ENVIN,"$file")) {
1.332     albertel 2826: 	flock(ENVIN,LOCK_SH);
1.343     albertel 2827: 	tie(my %disk_env,'GDBM_File',"$file",&GDBM_READER(),0640);
                   2828: 	if (exists($disk_env{"userfile.$fname"})) {
1.393     raeburn  2829: 	    $reply="ok";
1.343     albertel 2830: 	} else {
                   2831: 	    foreach my $envname (keys(%disk_env)) {
                   2832: 		if ($envname=~ m|^userfile\.\Q$fname\E|) {
1.393     raeburn  2833: 		    $reply="ok";
1.343     albertel 2834: 		    last;
                   2835: 		}
                   2836: 	    }
1.227     foxr     2837: 	}
1.343     albertel 2838: 	untie(%disk_env);
1.227     foxr     2839: 	close(ENVIN);
1.387     albertel 2840: 	&Reply($client, \$reply, "$cmd:$tail");
1.227     foxr     2841:     } else {
                   2842: 	&Failure($client, "invalid_token\n", "$cmd:$tail");
                   2843:     }
                   2844:     return 1;
                   2845: 
                   2846: }
                   2847: &register_handler("tokenauthuserfile", \&token_auth_user_file_handler, 0,1,0);
1.229     foxr     2848: 
                   2849: #
                   2850: #   Unsubscribe from a resource.
                   2851: #
                   2852: # Parameters:
                   2853: #    $cmd      - The command that got us here.
                   2854: #    $tail     - Tail of the command (remaining parameters).
                   2855: #    $client   - File descriptor connected to client.
                   2856: # Returns
                   2857: #     0        - Requested to exit, caller should shut down.
                   2858: #     1        - Continue processing.
                   2859: #
                   2860: sub unsubscribe_handler {
                   2861:     my ($cmd, $tail, $client) = @_;
                   2862: 
                   2863:     my $userinput= "$cmd:$tail";
                   2864:     
                   2865:     my ($fname) = split(/:/,$tail); # Split in case there's extrs.
                   2866: 
                   2867:     &Debug("Unsubscribing $fname");
                   2868:     if (-e $fname) {
                   2869: 	&Debug("Exists");
                   2870: 	&Reply($client, &unsub($fname,$clientip), $userinput);
                   2871:     } else {
                   2872: 	&Failure($client, "not_found\n", $userinput);
                   2873:     }
                   2874:     return 1;
                   2875: }
                   2876: &register_handler("unsub", \&unsubscribe_handler, 0, 1, 0);
1.263     albertel 2877: 
1.230     foxr     2878: #   Subscribe to a resource
                   2879: #
                   2880: # Parameters:
                   2881: #    $cmd      - The command that got us here.
                   2882: #    $tail     - Tail of the command (remaining parameters).
                   2883: #    $client   - File descriptor connected to client.
                   2884: # Returns
                   2885: #     0        - Requested to exit, caller should shut down.
                   2886: #     1        - Continue processing.
                   2887: #
                   2888: sub subscribe_handler {
                   2889:     my ($cmd, $tail, $client)= @_;
                   2890: 
                   2891:     my $userinput  = "$cmd:$tail";
                   2892: 
                   2893:     &Reply( $client, &subscribe($userinput,$clientip), $userinput);
                   2894: 
                   2895:     return 1;
                   2896: }
                   2897: &register_handler("sub", \&subscribe_handler, 0, 1, 0);
                   2898: 
                   2899: #
1.379     albertel 2900: #   Determine the latest version of a resource (it looks for the highest
                   2901: #   past version and then returns that +1)
1.230     foxr     2902: #
                   2903: # Parameters:
                   2904: #    $cmd      - The command that got us here.
                   2905: #    $tail     - Tail of the command (remaining parameters).
1.379     albertel 2906: #                 (Should consist of an absolute path to a file)
1.230     foxr     2907: #    $client   - File descriptor connected to client.
                   2908: # Returns
                   2909: #     0        - Requested to exit, caller should shut down.
                   2910: #     1        - Continue processing.
                   2911: #
                   2912: sub current_version_handler {
                   2913:     my ($cmd, $tail, $client) = @_;
                   2914: 
                   2915:     my $userinput= "$cmd:$tail";
                   2916:    
                   2917:     my $fname   = $tail;
                   2918:     &Reply( $client, &currentversion($fname)."\n", $userinput);
                   2919:     return 1;
                   2920: 
                   2921: }
                   2922: &register_handler("currentversion", \&current_version_handler, 0, 1, 0);
                   2923: 
                   2924: #  Make an entry in a user's activity log.
                   2925: #
                   2926: # Parameters:
                   2927: #    $cmd      - The command that got us here.
                   2928: #    $tail     - Tail of the command (remaining parameters).
                   2929: #    $client   - File descriptor connected to client.
                   2930: # Returns
                   2931: #     0        - Requested to exit, caller should shut down.
                   2932: #     1        - Continue processing.
                   2933: #
                   2934: sub activity_log_handler {
                   2935:     my ($cmd, $tail, $client) = @_;
                   2936: 
                   2937: 
                   2938:     my $userinput= "$cmd:$tail";
                   2939: 
                   2940:     my ($udom,$uname,$what)=split(/:/,$tail);
                   2941:     chomp($what);
                   2942:     my $proname=&propath($udom,$uname);
                   2943:     my $now=time;
                   2944:     my $hfh;
                   2945:     if ($hfh=IO::File->new(">>$proname/activity.log")) { 
                   2946: 	print $hfh "$now:$clientname:$what\n";
                   2947: 	&Reply( $client, "ok\n", $userinput); 
                   2948:     } else {
                   2949: 	&Failure($client, "error: ".($!+0)." IO::File->new Failed "
                   2950: 		 ."while attempting log\n", 
                   2951: 		 $userinput);
                   2952:     }
                   2953: 
                   2954:     return 1;
                   2955: }
1.263     albertel 2956: &register_handler("log", \&activity_log_handler, 0, 1, 0);
1.230     foxr     2957: 
                   2958: #
                   2959: #   Put a namespace entry in a user profile hash.
                   2960: #   My druthers would be for this to be an encrypted interaction too.
                   2961: #   anything that might be an inadvertent covert channel about either
                   2962: #   user authentication or user personal information....
                   2963: #
                   2964: # Parameters:
                   2965: #    $cmd      - The command that got us here.
                   2966: #    $tail     - Tail of the command (remaining parameters).
                   2967: #    $client   - File descriptor connected to client.
                   2968: # Returns
                   2969: #     0        - Requested to exit, caller should shut down.
                   2970: #     1        - Continue processing.
                   2971: #
                   2972: sub put_user_profile_entry {
                   2973:     my ($cmd, $tail, $client)  = @_;
1.229     foxr     2974: 
1.230     foxr     2975:     my $userinput = "$cmd:$tail";
                   2976:     
1.242     raeburn  2977:     my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4);
1.230     foxr     2978:     if ($namespace ne 'roles') {
                   2979: 	chomp($what);
                   2980: 	my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   2981: 				  &GDBM_WRCREAT(),"P",$what);
                   2982: 	if($hashref) {
                   2983: 	    my @pairs=split(/\&/,$what);
                   2984: 	    foreach my $pair (@pairs) {
                   2985: 		my ($key,$value)=split(/=/,$pair);
                   2986: 		$hashref->{$key}=$value;
                   2987: 	    }
1.311     albertel 2988: 	    if (&untie_user_hash($hashref)) {
1.230     foxr     2989: 		&Reply( $client, "ok\n", $userinput);
                   2990: 	    } else {
                   2991: 		&Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
                   2992: 			"while attempting put\n", 
                   2993: 			$userinput);
                   2994: 	    }
                   2995: 	} else {
1.316     albertel 2996: 	    &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
1.230     foxr     2997: 		     "while attempting put\n", $userinput);
                   2998: 	}
                   2999:     } else {
                   3000:         &Failure( $client, "refused\n", $userinput);
                   3001:     }
                   3002:     
                   3003:     return 1;
                   3004: }
                   3005: &register_handler("put", \&put_user_profile_entry, 0, 1, 0);
                   3006: 
1.283     albertel 3007: #   Put a piece of new data in hash, returns error if entry already exists
                   3008: # Parameters:
                   3009: #    $cmd      - The command that got us here.
                   3010: #    $tail     - Tail of the command (remaining parameters).
                   3011: #    $client   - File descriptor connected to client.
                   3012: # Returns
                   3013: #     0        - Requested to exit, caller should shut down.
                   3014: #     1        - Continue processing.
                   3015: #
                   3016: sub newput_user_profile_entry {
                   3017:     my ($cmd, $tail, $client)  = @_;
                   3018: 
                   3019:     my $userinput = "$cmd:$tail";
                   3020: 
                   3021:     my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4);
                   3022:     if ($namespace eq 'roles') {
                   3023:         &Failure( $client, "refused\n", $userinput);
                   3024: 	return 1;
                   3025:     }
                   3026: 
                   3027:     chomp($what);
                   3028: 
                   3029:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   3030: 				 &GDBM_WRCREAT(),"N",$what);
                   3031:     if(!$hashref) {
1.316     albertel 3032: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
1.283     albertel 3033: 		  "while attempting put\n", $userinput);
                   3034: 	return 1;
                   3035:     }
                   3036: 
                   3037:     my @pairs=split(/\&/,$what);
                   3038:     foreach my $pair (@pairs) {
                   3039: 	my ($key,$value)=split(/=/,$pair);
                   3040: 	if (exists($hashref->{$key})) {
1.489.2.17  raeburn  3041:             if (!&untie_user_hash($hashref)) {
                   3042:                 &logthis("error: ".($!+0)." untie (GDBM) failed ".
                   3043:                          "while attempting newput - early out as key exists");
                   3044:             }
1.283     albertel 3045: 	    &Failure($client, "key_exists: ".$key."\n",$userinput);
                   3046: 	    return 1;
                   3047: 	}
                   3048:     }
                   3049: 
                   3050:     foreach my $pair (@pairs) {
                   3051: 	my ($key,$value)=split(/=/,$pair);
                   3052: 	$hashref->{$key}=$value;
                   3053:     }
                   3054: 
1.311     albertel 3055:     if (&untie_user_hash($hashref)) {
1.283     albertel 3056: 	&Reply( $client, "ok\n", $userinput);
                   3057:     } else {
                   3058: 	&Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
                   3059: 		 "while attempting put\n", 
                   3060: 		 $userinput);
                   3061:     }
                   3062:     return 1;
                   3063: }
                   3064: &register_handler("newput", \&newput_user_profile_entry, 0, 1, 0);
                   3065: 
1.230     foxr     3066: # 
                   3067: #   Increment a profile entry in the user history file.
                   3068: #   The history contains keyword value pairs.  In this case,
                   3069: #   The value itself is a pair of numbers.  The first, the current value
                   3070: #   the second an increment that this function applies to the current
                   3071: #   value.
                   3072: #
                   3073: # Parameters:
                   3074: #    $cmd      - The command that got us here.
                   3075: #    $tail     - Tail of the command (remaining parameters).
                   3076: #    $client   - File descriptor connected to client.
                   3077: # Returns
                   3078: #     0        - Requested to exit, caller should shut down.
                   3079: #     1        - Continue processing.
                   3080: #
                   3081: sub increment_user_value_handler {
                   3082:     my ($cmd, $tail, $client) = @_;
                   3083:     
                   3084:     my $userinput   = "$cmd:$tail";
                   3085:     
                   3086:     my ($udom,$uname,$namespace,$what) =split(/:/,$tail);
                   3087:     if ($namespace ne 'roles') {
                   3088:         chomp($what);
                   3089: 	my $hashref = &tie_user_hash($udom, $uname,
                   3090: 				     $namespace, &GDBM_WRCREAT(),
                   3091: 				     "P",$what);
                   3092: 	if ($hashref) {
                   3093: 	    my @pairs=split(/\&/,$what);
                   3094: 	    foreach my $pair (@pairs) {
                   3095: 		my ($key,$value)=split(/=/,$pair);
1.284     raeburn  3096:                 $value = &unescape($value);
1.230     foxr     3097: 		# We could check that we have a number...
                   3098: 		if (! defined($value) || $value eq '') {
                   3099: 		    $value = 1;
                   3100: 		}
                   3101: 		$hashref->{$key}+=$value;
1.284     raeburn  3102:                 if ($namespace eq 'nohist_resourcetracker') {
                   3103:                     if ($hashref->{$key} < 0) {
                   3104:                         $hashref->{$key} = 0;
                   3105:                     }
                   3106:                 }
1.230     foxr     3107: 	    }
1.311     albertel 3108: 	    if (&untie_user_hash($hashref)) {
1.230     foxr     3109: 		&Reply( $client, "ok\n", $userinput);
                   3110: 	    } else {
                   3111: 		&Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
                   3112: 			 "while attempting inc\n", $userinput);
                   3113: 	    }
                   3114: 	} else {
                   3115: 	    &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3116: 		     "while attempting inc\n", $userinput);
                   3117: 	}
                   3118:     } else {
                   3119: 	&Failure($client, "refused\n", $userinput);
                   3120:     }
                   3121:     
                   3122:     return 1;
                   3123: }
                   3124: &register_handler("inc", \&increment_user_value_handler, 0, 1, 0);
                   3125: 
                   3126: #
                   3127: #   Put a new role for a user.  Roles are LonCAPA's packaging of permissions.
                   3128: #   Each 'role' a user has implies a set of permissions.  Adding a new role
                   3129: #   for a person grants the permissions packaged with that role
                   3130: #   to that user when the role is selected.
                   3131: #
                   3132: # Parameters:
                   3133: #    $cmd       - The command string (rolesput).
                   3134: #    $tail      - The remainder of the request line.  For rolesput this
                   3135: #                 consists of a colon separated list that contains:
                   3136: #                 The domain and user that is granting the role (logged).
                   3137: #                 The domain and user that is getting the role.
                   3138: #                 The roles being granted as a set of & separated pairs.
                   3139: #                 each pair a key value pair.
                   3140: #    $client    - File descriptor connected to the client.
                   3141: # Returns:
                   3142: #     0         - If the daemon should exit
                   3143: #     1         - To continue processing.
                   3144: #
                   3145: #
                   3146: sub roles_put_handler {
                   3147:     my ($cmd, $tail, $client) = @_;
                   3148: 
                   3149:     my $userinput  = "$cmd:$tail";
                   3150: 
                   3151:     my ( $exedom, $exeuser, $udom, $uname,  $what) = split(/:/,$tail);
                   3152:     
                   3153: 
                   3154:     my $namespace='roles';
                   3155:     chomp($what);
                   3156:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   3157: 				 &GDBM_WRCREAT(), "P",
                   3158: 				 "$exedom:$exeuser:$what");
                   3159:     #
                   3160:     #  Log the attempt to set a role.  The {}'s here ensure that the file 
                   3161:     #  handle is open for the minimal amount of time.  Since the flush
                   3162:     #  is done on close this improves the chances the log will be an un-
                   3163:     #  corrupted ordered thing.
                   3164:     if ($hashref) {
1.261     foxr     3165: 	my $pass_entry = &get_auth_type($udom, $uname);
                   3166: 	my ($auth_type,$pwd)  = split(/:/, $pass_entry);
                   3167: 	$auth_type = $auth_type.":";
1.230     foxr     3168: 	my @pairs=split(/\&/,$what);
                   3169: 	foreach my $pair (@pairs) {
                   3170: 	    my ($key,$value)=split(/=/,$pair);
                   3171: 	    &manage_permissions($key, $udom, $uname,
1.260     foxr     3172: 			       $auth_type);
1.230     foxr     3173: 	    $hashref->{$key}=$value;
                   3174: 	}
1.311     albertel 3175: 	if (&untie_user_hash($hashref)) {
1.230     foxr     3176: 	    &Reply($client, "ok\n", $userinput);
                   3177: 	} else {
                   3178: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3179: 		     "while attempting rolesput\n", $userinput);
                   3180: 	}
                   3181:     } else {
                   3182: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3183: 		 "while attempting rolesput\n", $userinput);
                   3184:     }
                   3185:     return 1;
                   3186: }
                   3187: &register_handler("rolesput", \&roles_put_handler, 1,1,0);  # Encoded client only.
                   3188: 
                   3189: #
1.231     foxr     3190: #   Deletes (removes) a role for a user.   This is equivalent to removing
                   3191: #  a permissions package associated with the role from the user's profile.
                   3192: #
                   3193: # Parameters:
                   3194: #     $cmd                 - The command (rolesdel)
                   3195: #     $tail                - The remainder of the request line. This consists
                   3196: #                             of:
                   3197: #                             The domain and user requesting the change (logged)
                   3198: #                             The domain and user being changed.
                   3199: #                             The roles being revoked.  These are shipped to us
                   3200: #                             as a bunch of & separated role name keywords.
                   3201: #     $client              - The file handle open on the client.
                   3202: # Returns:
                   3203: #     1                    - Continue processing
                   3204: #     0                    - Exit.
                   3205: #
                   3206: sub roles_delete_handler {
                   3207:     my ($cmd, $tail, $client)  = @_;
                   3208: 
                   3209:     my $userinput    = "$cmd:$tail";
                   3210:    
                   3211:     my ($exedom,$exeuser,$udom,$uname,$what)=split(/:/,$tail);
                   3212:     &Debug("cmd = ".$cmd." exedom= ".$exedom."user = ".$exeuser." udom=".$udom.
                   3213: 	   "what = ".$what);
                   3214:     my $namespace='roles';
                   3215:     chomp($what);
                   3216:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   3217: 				 &GDBM_WRCREAT(), "D",
                   3218: 				 "$exedom:$exeuser:$what");
                   3219:     
                   3220:     if ($hashref) {
                   3221: 	my @rolekeys=split(/\&/,$what);
                   3222: 	
                   3223: 	foreach my $key (@rolekeys) {
                   3224: 	    delete $hashref->{$key};
                   3225: 	}
1.315     albertel 3226: 	if (&untie_user_hash($hashref)) {
1.231     foxr     3227: 	    &Reply($client, "ok\n", $userinput);
                   3228: 	} else {
                   3229: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3230: 		     "while attempting rolesdel\n", $userinput);
                   3231: 	}
                   3232:     } else {
                   3233:         &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3234: 		 "while attempting rolesdel\n", $userinput);
                   3235:     }
                   3236:     
                   3237:     return 1;
                   3238: }
                   3239: &register_handler("rolesdel", \&roles_delete_handler, 1,1, 0); # Encoded client only
                   3240: 
                   3241: # Unencrypted get from a user's profile database.  See 
                   3242: # GetProfileEntryEncrypted for a version that does end-to-end encryption.
                   3243: # This function retrieves a keyed item from a specific named database in the
                   3244: # user's directory.
                   3245: #
                   3246: # Parameters:
                   3247: #   $cmd             - Command request keyword (get).
                   3248: #   $tail            - Tail of the command.  This is a colon separated list
                   3249: #                      consisting of the domain and username that uniquely
                   3250: #                      identifies the profile,
                   3251: #                      The 'namespace' which selects the gdbm file to 
                   3252: #                      do the lookup in, 
                   3253: #                      & separated list of keys to lookup.  Note that
                   3254: #                      the values are returned as an & separated list too.
                   3255: #   $client          - File descriptor open on the client.
                   3256: # Returns:
                   3257: #   1       - Continue processing.
                   3258: #   0       - Exit.
                   3259: #
                   3260: sub get_profile_entry {
                   3261:     my ($cmd, $tail, $client) = @_;
                   3262: 
                   3263:     my $userinput= "$cmd:$tail";
                   3264:    
                   3265:     my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
                   3266:     chomp($what);
1.255     foxr     3267: 
1.390     raeburn  3268: 
1.255     foxr     3269:     my $replystring = read_profile($udom, $uname, $namespace, $what);
                   3270:     my ($first) = split(/:/,$replystring);
                   3271:     if($first ne "error") {
1.387     albertel 3272: 	&Reply($client, \$replystring, $userinput);
1.231     foxr     3273:     } else {
1.255     foxr     3274: 	&Failure($client, $replystring." while attempting get\n", $userinput);
1.231     foxr     3275:     }
                   3276:     return 1;
1.255     foxr     3277: 
                   3278: 
1.231     foxr     3279: }
                   3280: &register_handler("get", \&get_profile_entry, 0,1,0);
                   3281: 
                   3282: #
                   3283: #  Process the encrypted get request.  Note that the request is sent
                   3284: #  in clear, but the reply is encrypted.  This is a small covert channel:
                   3285: #  information about the sensitive keys is given to the snooper.  Just not
                   3286: #  information about the values of the sensitive key.  Hmm if I wanted to
                   3287: #  know these I'd snoop for the egets. Get the profile item names from them
                   3288: #  and then issue a get for them since there's no enforcement of the
                   3289: #  requirement of an encrypted get for particular profile items.  If I
                   3290: #  were re-doing this, I'd force the request to be encrypted as well as the
                   3291: #  reply.  I'd also just enforce encrypted transactions for all gets since
                   3292: #  that would prevent any covert channel snooping.
                   3293: #
                   3294: #  Parameters:
                   3295: #     $cmd               - Command keyword of request (eget).
1.489.2.32  raeburn  3296: #     $tail              - Tail of the command.  See GetProfileEntry
                   3297: #                          for more information about this.
1.231     foxr     3298: #     $client            - File open on the client.
                   3299: #  Returns:
                   3300: #     1      - Continue processing
                   3301: #     0      - server should exit.
                   3302: sub get_profile_entry_encrypted {
                   3303:     my ($cmd, $tail, $client) = @_;
                   3304: 
                   3305:     my $userinput = "$cmd:$tail";
                   3306:    
1.339     albertel 3307:     my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
1.231     foxr     3308:     chomp($what);
1.255     foxr     3309:     my $qresult = read_profile($udom, $uname, $namespace, $what);
                   3310:     my ($first) = split(/:/, $qresult);
                   3311:     if($first ne "error") {
                   3312: 	
                   3313: 	if ($cipher) {
                   3314: 	    my $cmdlength=length($qresult);
                   3315: 	    $qresult.="         ";
                   3316: 	    my $encqresult='';
                   3317: 	    for(my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
                   3318: 		$encqresult.= unpack("H16", 
                   3319: 				     $cipher->encrypt(substr($qresult,
                   3320: 							     $encidx,
                   3321: 							     8)));
                   3322: 	    }
                   3323: 	    &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput);
                   3324: 	} else {
1.231     foxr     3325: 		&Failure( $client, "error:no_key\n", $userinput);
                   3326: 	    }
                   3327:     } else {
1.255     foxr     3328: 	&Failure($client, "$qresult while attempting eget\n", $userinput);
                   3329: 
1.231     foxr     3330:     }
                   3331:     
                   3332:     return 1;
                   3333: }
1.255     foxr     3334: &register_handler("eget", \&get_profile_entry_encrypted, 0, 1, 0);
1.263     albertel 3335: 
1.231     foxr     3336: #
                   3337: #   Deletes a key in a user profile database.
                   3338: #   
                   3339: #   Parameters:
                   3340: #       $cmd                  - Command keyword (del).
                   3341: #       $tail                 - Command tail.  IN this case a colon
                   3342: #                               separated list containing:
                   3343: #                               The domain and user that identifies uniquely
                   3344: #                               the identity of the user.
                   3345: #                               The profile namespace (name of the profile
                   3346: #                               database file).
                   3347: #                               & separated list of keywords to delete.
                   3348: #       $client              - File open on client socket.
                   3349: # Returns:
                   3350: #     1   - Continue processing
                   3351: #     0   - Exit server.
                   3352: #
                   3353: #
                   3354: sub delete_profile_entry {
                   3355:     my ($cmd, $tail, $client) = @_;
                   3356: 
                   3357:     my $userinput = "cmd:$tail";
                   3358: 
                   3359:     my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
                   3360:     chomp($what);
                   3361:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   3362: 				 &GDBM_WRCREAT(),
                   3363: 				 "D",$what);
                   3364:     if ($hashref) {
                   3365:         my @keys=split(/\&/,$what);
                   3366: 	foreach my $key (@keys) {
                   3367: 	    delete($hashref->{$key});
                   3368: 	}
1.315     albertel 3369: 	if (&untie_user_hash($hashref)) {
1.231     foxr     3370: 	    &Reply($client, "ok\n", $userinput);
                   3371: 	} else {
                   3372: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3373: 		    "while attempting del\n", $userinput);
                   3374: 	}
                   3375:     } else {
                   3376: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3377: 		 "while attempting del\n", $userinput);
                   3378:     }
                   3379:     return 1;
                   3380: }
                   3381: &register_handler("del", \&delete_profile_entry, 0, 1, 0);
1.263     albertel 3382: 
1.231     foxr     3383: #
                   3384: #  List the set of keys that are defined in a profile database file.
                   3385: #  A successful reply from this will contain an & separated list of
                   3386: #  the keys. 
                   3387: # Parameters:
                   3388: #     $cmd              - Command request (keys).
                   3389: #     $tail             - Remainder of the request, a colon separated
                   3390: #                         list containing domain/user that identifies the
                   3391: #                         user being queried, and the database namespace
                   3392: #                         (database filename essentially).
                   3393: #     $client           - File open on the client.
                   3394: #  Returns:
                   3395: #    1    - Continue processing.
                   3396: #    0    - Exit the server.
                   3397: #
                   3398: sub get_profile_keys {
                   3399:     my ($cmd, $tail, $client) = @_;
                   3400: 
                   3401:     my $userinput = "$cmd:$tail";
                   3402: 
                   3403:     my ($udom,$uname,$namespace)=split(/:/,$tail);
                   3404:     my $qresult='';
                   3405:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   3406: 				  &GDBM_READER());
                   3407:     if ($hashref) {
                   3408: 	foreach my $key (keys %$hashref) {
                   3409: 	    $qresult.="$key&";
                   3410: 	}
1.315     albertel 3411: 	if (&untie_user_hash($hashref)) {
1.231     foxr     3412: 	    $qresult=~s/\&$//;
1.387     albertel 3413: 	    &Reply($client, \$qresult, $userinput);
1.231     foxr     3414: 	} else {
                   3415: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3416: 		    "while attempting keys\n", $userinput);
                   3417: 	}
                   3418:     } else {
                   3419: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3420: 		 "while attempting keys\n", $userinput);
                   3421:     }
                   3422:    
                   3423:     return 1;
                   3424: }
                   3425: &register_handler("keys", \&get_profile_keys, 0, 1, 0);
                   3426: 
                   3427: #
                   3428: #   Dump the contents of a user profile database.
                   3429: #   Note that this constitutes a very large covert channel too since
                   3430: #   the dump will return sensitive information that is not encrypted.
                   3431: #   The naive security assumption is that the session negotiation ensures
                   3432: #   our client is trusted and I don't believe that's assured at present.
                   3433: #   Sure want badly to go to ssl or tls.  Of course if my peer isn't really
                   3434: #   a LonCAPA node they could have negotiated an encryption key too so >sigh<.
                   3435: # 
                   3436: #  Parameters:
                   3437: #     $cmd           - The command request keyword (currentdump).
                   3438: #     $tail          - Remainder of the request, consisting of a colon
                   3439: #                      separated list that has the domain/username and
                   3440: #                      the namespace to dump (database file).
                   3441: #     $client        - file open on the remote client.
                   3442: # Returns:
                   3443: #     1    - Continue processing.
                   3444: #     0    - Exit the server.
                   3445: #
                   3446: sub dump_profile_database {
                   3447:     my ($cmd, $tail, $client) = @_;
                   3448: 
                   3449:     my $userinput = "$cmd:$tail";
                   3450:    
                   3451:     my ($udom,$uname,$namespace) = split(/:/,$tail);
                   3452:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   3453: 				 &GDBM_READER());
                   3454:     if ($hashref) {
                   3455: 	# Structure of %data:
                   3456: 	# $data{$symb}->{$parameter}=$value;
                   3457: 	# $data{$symb}->{'v.'.$parameter}=$version;
                   3458: 	# since $parameter will be unescaped, we do not
                   3459:  	# have to worry about silly parameter names...
                   3460: 	
                   3461:         my $qresult='';
                   3462: 	my %data = ();                     # A hash of anonymous hashes..
                   3463: 	while (my ($key,$value) = each(%$hashref)) {
                   3464: 	    my ($v,$symb,$param) = split(/:/,$key);
                   3465: 	    next if ($v eq 'version' || $symb eq 'keys');
                   3466: 	    next if (exists($data{$symb}) && 
                   3467: 		     exists($data{$symb}->{$param}) &&
                   3468: 		     $data{$symb}->{'v.'.$param} > $v);
                   3469: 	    $data{$symb}->{$param}=$value;
                   3470: 	    $data{$symb}->{'v.'.$param}=$v;
                   3471: 	}
1.311     albertel 3472: 	if (&untie_user_hash($hashref)) {
1.231     foxr     3473: 	    while (my ($symb,$param_hash) = each(%data)) {
                   3474: 		while(my ($param,$value) = each (%$param_hash)){
                   3475: 		    next if ($param =~ /^v\./);       # Ignore versions...
                   3476: 		    #
                   3477: 		    #   Just dump the symb=value pairs separated by &
                   3478: 		    #
                   3479: 		    $qresult.=$symb.':'.$param.'='.$value.'&';
                   3480: 		}
                   3481: 	    }
                   3482: 	    chop($qresult);
1.387     albertel 3483: 	    &Reply($client , \$qresult, $userinput);
1.231     foxr     3484: 	} else {
                   3485: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3486: 		     "while attempting currentdump\n", $userinput);
                   3487: 	}
                   3488:     } else {
                   3489: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3490: 		"while attempting currentdump\n", $userinput);
                   3491:     }
                   3492: 
                   3493:     return 1;
                   3494: }
                   3495: &register_handler("currentdump", \&dump_profile_database, 0, 1, 0);
                   3496: 
                   3497: #
                   3498: #   Dump a profile database with an optional regular expression
                   3499: #   to match against the keys.  In this dump, no effort is made
                   3500: #   to separate symb from version information. Presumably the
                   3501: #   databases that are dumped by this command are of a different
                   3502: #   structure.  Need to look at this and improve the documentation of
                   3503: #   both this and the currentdump handler.
                   3504: # Parameters:
                   3505: #    $cmd                     - The command keyword.
                   3506: #    $tail                    - All of the characters after the $cmd:
                   3507: #                               These are expected to be a colon
                   3508: #                               separated list containing:
                   3509: #                               domain/user - identifying the user.
                   3510: #                               namespace   - identifying the database.
                   3511: #                               regexp      - optional regular expression
                   3512: #                                             that is matched against
                   3513: #                                             database keywords to do
                   3514: #                                             selective dumps.
1.488     raeburn  3515: #                               range       - optional range of entries
                   3516: #                                             e.g., 10-20 would return the
                   3517: #                                             10th to 19th items, etc.  
1.231     foxr     3518: #   $client                   - Channel open on the client.
                   3519: # Returns:
                   3520: #    1    - Continue processing.
                   3521: # Side effects:
                   3522: #    response is written to $client.
                   3523: #
                   3524: sub dump_with_regexp {
                   3525:     my ($cmd, $tail, $client) = @_;
                   3526: 
1.489.2.4  raeburn  3527:     my $res = LONCAPA::Lond::dump_with_regexp($tail, $clientversion);
1.231     foxr     3528: 
1.489.2.4  raeburn  3529:     if ($res =~ /^error:/) {
                   3530:         &Failure($client, \$res, "$cmd:$tail");
1.231     foxr     3531:     } else {
1.489.2.4  raeburn  3532:         &Reply($client, \$res, "$cmd:$tail");
1.231     foxr     3533:     }
                   3534: 
                   3535:     return 1;
                   3536: }
                   3537: &register_handler("dump", \&dump_with_regexp, 0, 1, 0);
                   3538: 
                   3539: #  Store a set of key=value pairs associated with a versioned name.
                   3540: #
                   3541: #  Parameters:
                   3542: #    $cmd                - Request command keyword.
                   3543: #    $tail               - Tail of the request.  This is a colon
                   3544: #                          separated list containing:
                   3545: #                          domain/user - User and authentication domain.
                   3546: #                          namespace   - Name of the database being modified
                   3547: #                          rid         - Resource keyword to modify.
                   3548: #                          what        - new value associated with rid.
1.489.2.17  raeburn  3549: #                          laststore   - (optional) version=timestamp
                   3550: #                                        for most recent transaction for rid
                   3551: #                                        in namespace, when cstore was called
1.231     foxr     3552: #
                   3553: #    $client             - Socket open on the client.
                   3554: #
                   3555: #
                   3556: #  Returns:
                   3557: #      1 (keep on processing).
                   3558: #  Side-Effects:
                   3559: #    Writes to the client
1.489.2.17  raeburn  3560: #    Successful storage will cause either 'ok', or, if $laststore was included
                   3561: #    in the tail of the request, and the version number for the last transaction
                   3562: #    is larger than the version in $laststore, delay:$numtrans , where $numtrans
                   3563: #    is the number of store evevnts recorded for rid in namespace since
                   3564: #    lonnet::store() was called by the client.
                   3565: #
1.231     foxr     3566: sub store_handler {
                   3567:     my ($cmd, $tail, $client) = @_;
                   3568:  
                   3569:     my $userinput = "$cmd:$tail";
                   3570: 
1.489.2.17  raeburn  3571:     chomp($tail);
                   3572:     my ($udom,$uname,$namespace,$rid,$what,$laststore) =split(/:/,$tail);
1.231     foxr     3573:     if ($namespace ne 'roles') {
                   3574: 
                   3575: 	my @pairs=split(/\&/,$what);
                   3576: 	my $hashref  = &tie_user_hash($udom, $uname, $namespace,
1.268     albertel 3577: 				       &GDBM_WRCREAT(), "S",
1.231     foxr     3578: 				       "$rid:$what");
                   3579: 	if ($hashref) {
                   3580: 	    my $now = time;
1.489.2.17  raeburn  3581:             my $numtrans;
                   3582:             if ($laststore) {
                   3583:                 my ($previousversion,$previoustime) = split(/\=/,$laststore);
                   3584:                 my ($lastversion,$lasttime) = (0,0);
                   3585:                 $lastversion = $hashref->{"version:$rid"};
                   3586:                 if ($lastversion) {
                   3587:                     $lasttime = $hashref->{"$lastversion:$rid:timestamp"};
                   3588:                 }
                   3589:                 if (($previousversion) && ($previousversion !~ /\D/)) {
                   3590:                     if (($lastversion > $previousversion) && ($lasttime >= $previoustime)) {
                   3591:                         $numtrans = $lastversion - $previousversion;
                   3592:                     }
                   3593:                 } elsif ($lastversion) {
                   3594:                     $numtrans = $lastversion;
                   3595:                 }
                   3596:                 if ($numtrans) {
                   3597:                     $numtrans =~ s/D//g;
                   3598:                 }
                   3599:             }
                   3600: 
1.231     foxr     3601: 	    $hashref->{"version:$rid"}++;
                   3602: 	    my $version=$hashref->{"version:$rid"};
                   3603: 	    my $allkeys=''; 
                   3604: 	    foreach my $pair (@pairs) {
                   3605: 		my ($key,$value)=split(/=/,$pair);
                   3606: 		$allkeys.=$key.':';
                   3607: 		$hashref->{"$version:$rid:$key"}=$value;
                   3608: 	    }
                   3609: 	    $hashref->{"$version:$rid:timestamp"}=$now;
                   3610: 	    $allkeys.='timestamp';
                   3611: 	    $hashref->{"$version:keys:$rid"}=$allkeys;
1.311     albertel 3612: 	    if (&untie_user_hash($hashref)) {
1.489.2.17  raeburn  3613:                 my $msg = 'ok';
                   3614:                 if ($numtrans) {
                   3615:                     $msg = 'delay:'.$numtrans;
                   3616:                 }
                   3617:                 &Reply($client, "$msg\n", $userinput);
1.231     foxr     3618: 	    } else {
                   3619: 		&Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3620: 			"while attempting store\n", $userinput);
                   3621: 	    }
                   3622: 	} else {
                   3623: 	    &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3624: 		     "while attempting store\n", $userinput);
                   3625: 	}
                   3626:     } else {
                   3627: 	&Failure($client, "refused\n", $userinput);
                   3628:     }
                   3629: 
                   3630:     return 1;
                   3631: }
                   3632: &register_handler("store", \&store_handler, 0, 1, 0);
1.263     albertel 3633: 
1.323     albertel 3634: #  Modify a set of key=value pairs associated with a versioned name.
                   3635: #
                   3636: #  Parameters:
                   3637: #    $cmd                - Request command keyword.
                   3638: #    $tail               - Tail of the request.  This is a colon
                   3639: #                          separated list containing:
                   3640: #                          domain/user - User and authentication domain.
                   3641: #                          namespace   - Name of the database being modified
                   3642: #                          rid         - Resource keyword to modify.
                   3643: #                          v           - Version item to modify
                   3644: #                          what        - new value associated with rid.
                   3645: #
                   3646: #    $client             - Socket open on the client.
                   3647: #
                   3648: #
                   3649: #  Returns:
                   3650: #      1 (keep on processing).
                   3651: #  Side-Effects:
                   3652: #    Writes to the client
                   3653: sub putstore_handler {
                   3654:     my ($cmd, $tail, $client) = @_;
                   3655:  
                   3656:     my $userinput = "$cmd:$tail";
                   3657: 
                   3658:     my ($udom,$uname,$namespace,$rid,$v,$what) =split(/:/,$tail);
                   3659:     if ($namespace ne 'roles') {
                   3660: 
                   3661: 	chomp($what);
                   3662: 	my $hashref  = &tie_user_hash($udom, $uname, $namespace,
                   3663: 				       &GDBM_WRCREAT(), "M",
                   3664: 				       "$rid:$v:$what");
                   3665: 	if ($hashref) {
                   3666: 	    my $now = time;
                   3667: 	    my %data = &hash_extract($what);
                   3668: 	    my @allkeys;
                   3669: 	    while (my($key,$value) = each(%data)) {
                   3670: 		push(@allkeys,$key);
                   3671: 		$hashref->{"$v:$rid:$key"} = $value;
                   3672: 	    }
                   3673: 	    my $allkeys = join(':',@allkeys);
                   3674: 	    $hashref->{"$v:keys:$rid"}=$allkeys;
                   3675: 
                   3676: 	    if (&untie_user_hash($hashref)) {
                   3677: 		&Reply($client, "ok\n", $userinput);
                   3678: 	    } else {
                   3679: 		&Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3680: 			"while attempting store\n", $userinput);
                   3681: 	    }
                   3682: 	} else {
                   3683: 	    &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3684: 		     "while attempting store\n", $userinput);
                   3685: 	}
                   3686:     } else {
                   3687: 	&Failure($client, "refused\n", $userinput);
                   3688:     }
                   3689: 
                   3690:     return 1;
                   3691: }
                   3692: &register_handler("putstore", \&putstore_handler, 0, 1, 0);
                   3693: 
                   3694: sub hash_extract {
                   3695:     my ($str)=@_;
                   3696:     my %hash;
                   3697:     foreach my $pair (split(/\&/,$str)) {
                   3698: 	my ($key,$value)=split(/=/,$pair);
                   3699: 	$hash{$key}=$value;
                   3700:     }
                   3701:     return (%hash);
                   3702: }
                   3703: sub hash_to_str {
                   3704:     my ($hash_ref)=@_;
                   3705:     my $str;
                   3706:     foreach my $key (keys(%$hash_ref)) {
                   3707: 	$str.=$key.'='.$hash_ref->{$key}.'&';
                   3708:     }
                   3709:     $str=~s/\&$//;
                   3710:     return $str;
                   3711: }
                   3712: 
1.231     foxr     3713: #
                   3714: #  Dump out all versions of a resource that has key=value pairs associated
                   3715: # with it for each version.  These resources are built up via the store
                   3716: # command.
                   3717: #
                   3718: #  Parameters:
                   3719: #     $cmd               - Command keyword.
                   3720: #     $tail              - Remainder of the request which consists of:
                   3721: #                          domain/user   - User and auth. domain.
                   3722: #                          namespace     - name of resource database.
                   3723: #                          rid           - Resource id.
                   3724: #    $client             - socket open on the client.
                   3725: #
                   3726: # Returns:
                   3727: #      1  indicating the caller should not yet exit.
                   3728: # Side-effects:
                   3729: #   Writes a reply to the client.
                   3730: #   The reply is a string of the following shape:
                   3731: #   version=current&version:keys=k1:k2...&1:k1=v1&1:k2=v2...
                   3732: #    Where the 1 above represents version 1.
                   3733: #    this continues for all pairs of keys in all versions.
                   3734: #
                   3735: #
                   3736: #    
                   3737: #
                   3738: sub restore_handler {
                   3739:     my ($cmd, $tail, $client) = @_;
                   3740: 
                   3741:     my $userinput = "$cmd:$tail";	# Only used for logging purposes.
1.351     banghart 3742:     my ($udom,$uname,$namespace,$rid) = split(/:/,$tail);
1.352     albertel 3743:     $namespace=~s/\//\_/g;
1.350     albertel 3744:     $namespace = &LONCAPA::clean_username($namespace);
1.349     albertel 3745: 
1.231     foxr     3746:     chomp($rid);
                   3747:     my $qresult='';
1.309     albertel 3748:     my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER());
                   3749:     if ($hashref) {
                   3750: 	my $version=$hashref->{"version:$rid"};
1.231     foxr     3751: 	$qresult.="version=$version&";
                   3752: 	my $scope;
                   3753: 	for ($scope=1;$scope<=$version;$scope++) {
1.309     albertel 3754: 	    my $vkeys=$hashref->{"$scope:keys:$rid"};
1.231     foxr     3755: 	    my @keys=split(/:/,$vkeys);
                   3756: 	    my $key;
                   3757: 	    $qresult.="$scope:keys=$vkeys&";
                   3758: 	    foreach $key (@keys) {
1.309     albertel 3759: 		$qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&";
1.231     foxr     3760: 	    }                                  
                   3761: 	}
1.311     albertel 3762: 	if (&untie_user_hash($hashref)) {
1.231     foxr     3763: 	    $qresult=~s/\&$//;
1.387     albertel 3764: 	    &Reply( $client, \$qresult, $userinput);
1.231     foxr     3765: 	} else {
                   3766: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   3767: 		    "while attempting restore\n", $userinput);
                   3768: 	}
                   3769:     } else {
                   3770: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   3771: 		"while attempting restore\n", $userinput);
                   3772:     }
                   3773:   
                   3774:     return 1;
                   3775: 
                   3776: 
                   3777: }
                   3778: &register_handler("restore", \&restore_handler, 0,1,0);
1.234     foxr     3779: 
                   3780: #
1.324     raeburn  3781: #   Add a chat message to a synchronous discussion board.
1.234     foxr     3782: #
                   3783: # Parameters:
                   3784: #    $cmd                - Request keyword.
                   3785: #    $tail               - Tail of the command. A colon separated list
                   3786: #                          containing:
                   3787: #                          cdom    - Domain on which the chat board lives
1.324     raeburn  3788: #                          cnum    - Course containing the chat board.
                   3789: #                          newpost - Body of the posting.
                   3790: #                          group   - Optional group, if chat board is only 
                   3791: #                                    accessible in a group within the course 
1.234     foxr     3792: #   $client              - Socket open on the client.
                   3793: # Returns:
                   3794: #   1    - Indicating caller should keep on processing.
                   3795: #
                   3796: # Side-effects:
                   3797: #   writes a reply to the client.
                   3798: #
                   3799: #
                   3800: sub send_chat_handler {
                   3801:     my ($cmd, $tail, $client) = @_;
                   3802: 
                   3803:     
                   3804:     my $userinput = "$cmd:$tail";
                   3805: 
1.324     raeburn  3806:     my ($cdom,$cnum,$newpost,$group)=split(/\:/,$tail);
                   3807:     &chat_add($cdom,$cnum,$newpost,$group);
1.234     foxr     3808:     &Reply($client, "ok\n", $userinput);
                   3809: 
                   3810:     return 1;
                   3811: }
                   3812: &register_handler("chatsend", \&send_chat_handler, 0, 1, 0);
1.263     albertel 3813: 
1.234     foxr     3814: #
1.324     raeburn  3815: #   Retrieve the set of chat messages from a discussion board.
1.234     foxr     3816: #
                   3817: #  Parameters:
                   3818: #    $cmd             - Command keyword that initiated the request.
                   3819: #    $tail            - Remainder of the request after the command
                   3820: #                       keyword.  In this case a colon separated list of
                   3821: #                       chat domain    - Which discussion board.
                   3822: #                       chat id        - Discussion thread(?)
                   3823: #                       domain/user    - Authentication domain and username
                   3824: #                                        of the requesting person.
1.324     raeburn  3825: #                       group          - Optional course group containing
                   3826: #                                        the board.      
1.234     foxr     3827: #   $client           - Socket open on the client program.
                   3828: # Returns:
                   3829: #    1     - continue processing
                   3830: # Side effects:
                   3831: #    Response is written to the client.
                   3832: #
                   3833: sub retrieve_chat_handler {
                   3834:     my ($cmd, $tail, $client) = @_;
                   3835: 
                   3836: 
                   3837:     my $userinput = "$cmd:$tail";
                   3838: 
1.324     raeburn  3839:     my ($cdom,$cnum,$udom,$uname,$group)=split(/\:/,$tail);
1.234     foxr     3840:     my $reply='';
1.324     raeburn  3841:     foreach (&get_chat($cdom,$cnum,$udom,$uname,$group)) {
1.234     foxr     3842: 	$reply.=&escape($_).':';
                   3843:     }
                   3844:     $reply=~s/\:$//;
1.387     albertel 3845:     &Reply($client, \$reply, $userinput);
1.234     foxr     3846: 
                   3847: 
                   3848:     return 1;
                   3849: }
                   3850: &register_handler("chatretr", \&retrieve_chat_handler, 0, 1, 0);
                   3851: 
                   3852: #
                   3853: #  Initiate a query of an sql database.  SQL query repsonses get put in
                   3854: #  a file for later retrieval.  This prevents sql query results from
                   3855: #  bottlenecking the system.  Note that with loncnew, perhaps this is
                   3856: #  less of an issue since multiple outstanding requests can be concurrently
                   3857: #  serviced.
                   3858: #
                   3859: #  Parameters:
                   3860: #     $cmd       - COmmand keyword that initiated the request.
                   3861: #     $tail      - Remainder of the command after the keyword.
                   3862: #                  For this function, this consists of a query and
                   3863: #                  3 arguments that are self-documentingly labelled
                   3864: #                  in the original arg1, arg2, arg3.
                   3865: #     $client    - Socket open on the client.
                   3866: # Return:
                   3867: #    1   - Indicating processing should continue.
                   3868: # Side-effects:
                   3869: #    a reply is written to $client.
                   3870: #
                   3871: sub send_query_handler {
                   3872:     my ($cmd, $tail, $client) = @_;
                   3873: 
                   3874: 
                   3875:     my $userinput = "$cmd:$tail";
                   3876: 
                   3877:     my ($query,$arg1,$arg2,$arg3)=split(/\:/,$tail);
                   3878:     $query=~s/\n*$//g;
1.489.2.27  raeburn  3879:     if (($query eq 'usersearch') || ($query eq 'instdirsearch')) {
                   3880:         my $usersearchconf = &get_usersearch_config($currentdomainid,'directorysrch');
                   3881:         my $earlyout;
                   3882:         if (ref($usersearchconf) eq 'HASH') {
                   3883:             if ($currentdomainid eq $clienthomedom) {
                   3884:                 if ($query eq 'usersearch') {
                   3885:                     if ($usersearchconf->{'lcavailable'} eq '0') {
                   3886:                         $earlyout = 1;
                   3887:                     }
                   3888:                 } else {
                   3889:                     if ($usersearchconf->{'available'} eq '0') {
                   3890:                         $earlyout = 1;
                   3891:                     }
                   3892:                 }
                   3893:             } else {
                   3894:                 if ($query eq 'usersearch') {
                   3895:                     if ($usersearchconf->{'lclocalonly'}) {
                   3896:                         $earlyout = 1;
                   3897:                     }
                   3898:                 } else {
                   3899:                     if ($usersearchconf->{'localonly'}) {
                   3900:                         $earlyout = 1;
                   3901:                     }
                   3902:                 }
                   3903:             }
                   3904:         }
                   3905:         if ($earlyout) {
                   3906:             &Reply($client, "query_not_authorized\n");
                   3907:             return 1;
                   3908:         }
                   3909:     }
1.234     foxr     3910:     &Reply($client, "". &sql_reply("$clientname\&$query".
                   3911: 				"\&$arg1"."\&$arg2"."\&$arg3")."\n",
                   3912: 	  $userinput);
                   3913:     
                   3914:     return 1;
                   3915: }
                   3916: &register_handler("querysend", \&send_query_handler, 0, 1, 0);
                   3917: 
                   3918: #
                   3919: #   Add a reply to an sql query.  SQL queries are done asyncrhonously.
                   3920: #   The query is submitted via a "querysend" transaction.
                   3921: #   There it is passed on to the lonsql daemon, queued and issued to
                   3922: #   mysql.
                   3923: #     This transaction is invoked when the sql transaction is complete
                   3924: #   it stores the query results in flie and indicates query completion.
                   3925: #   presumably local software then fetches this response... I'm guessing
                   3926: #   the sequence is: lonc does a querysend, we ask lonsql to do it.
                   3927: #   lonsql on completion of the query interacts with the lond of our
                   3928: #   client to do a query reply storing two files:
                   3929: #    - id     - The results of the query.
                   3930: #    - id.end - Indicating the transaction completed. 
                   3931: #    NOTE: id is a unique id assigned to the query and querysend time.
                   3932: # Parameters:
                   3933: #    $cmd        - Command keyword that initiated this request.
                   3934: #    $tail       - Remainder of the tail.  In this case that's a colon
                   3935: #                  separated list containing the query Id and the 
                   3936: #                  results of the query.
                   3937: #    $client     - Socket open on the client.
                   3938: # Return:
                   3939: #    1           - Indicating that we should continue processing.
                   3940: # Side effects:
                   3941: #    ok written to the client.
                   3942: #
                   3943: sub reply_query_handler {
                   3944:     my ($cmd, $tail, $client) = @_;
                   3945: 
                   3946: 
                   3947:     my $userinput = "$cmd:$tail";
                   3948: 
1.339     albertel 3949:     my ($id,$reply)=split(/:/,$tail); 
1.234     foxr     3950:     my $store;
                   3951:     my $execdir=$perlvar{'lonDaemons'};
                   3952:     if ($store=IO::File->new(">$execdir/tmp/$id")) {
                   3953: 	$reply=~s/\&/\n/g;
                   3954: 	print $store $reply;
                   3955: 	close $store;
                   3956: 	my $store2=IO::File->new(">$execdir/tmp/$id.end");
                   3957: 	print $store2 "done\n";
                   3958: 	close $store2;
                   3959: 	&Reply($client, "ok\n", $userinput);
                   3960:     } else {
                   3961: 	&Failure($client, "error: ".($!+0)
                   3962: 		." IO::File->new Failed ".
                   3963: 		"while attempting queryreply\n", $userinput);
                   3964:     }
                   3965:  
                   3966: 
                   3967:     return 1;
                   3968: }
                   3969: &register_handler("queryreply", \&reply_query_handler, 0, 1, 0);
                   3970: 
                   3971: #
                   3972: #  Process the courseidput request.  Not quite sure what this means
                   3973: #  at the system level sense.  It appears a gdbm file in the 
                   3974: #  /home/httpd/lonUsers/$domain/nohist_courseids is tied and
                   3975: #  a set of entries made in that database.
                   3976: #
                   3977: # Parameters:
                   3978: #   $cmd      - The command keyword that initiated this request.
                   3979: #   $tail     - Tail of the command.  In this case consists of a colon
                   3980: #               separated list contaning the domain to apply this to and
                   3981: #               an ampersand separated list of keyword=value pairs.
1.272     raeburn  3982: #               Each value is a colon separated list that includes:  
                   3983: #               description, institutional code and course owner.
                   3984: #               For backward compatibility with versions included
                   3985: #               in LON-CAPA 1.1.X (and earlier) and 1.2.X, institutional
                   3986: #               code and/or course owner are preserved from the existing 
                   3987: #               record when writing a new record in response to 1.1 or 
                   3988: #               1.2 implementations of lonnet::flushcourselogs().   
                   3989: #                      
1.234     foxr     3990: #   $client   - Socket open on the client.
                   3991: # Returns:
                   3992: #   1    - indicating that processing should continue
                   3993: #
                   3994: # Side effects:
                   3995: #   reply is written to the client.
                   3996: #
                   3997: sub put_course_id_handler {
                   3998:     my ($cmd, $tail, $client) = @_;
                   3999: 
                   4000: 
                   4001:     my $userinput = "$cmd:$tail";
                   4002: 
1.266     raeburn  4003:     my ($udom, $what) = split(/:/, $tail,2);
1.234     foxr     4004:     chomp($what);
                   4005:     my $now=time;
                   4006:     my @pairs=split(/\&/,$what);
                   4007: 
                   4008:     my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT());
                   4009:     if ($hashref) {
                   4010: 	foreach my $pair (@pairs) {
1.271     raeburn  4011:             my ($key,$courseinfo) = split(/=/,$pair,2);
                   4012:             $courseinfo =~ s/=/:/g;
1.384     raeburn  4013:             if (defined($hashref->{$key})) {
                   4014:                 my $value = &Apache::lonnet::thaw_unescape($hashref->{$key});
                   4015:                 if (ref($value) eq 'HASH') {
                   4016:                     my @items = ('description','inst_code','owner','type');
                   4017:                     my @new_items = split(/:/,$courseinfo,-1);
                   4018:                     my %storehash; 
                   4019:                     for (my $i=0; $i<@new_items; $i++) {
1.391     raeburn  4020:                         $storehash{$items[$i]} = &unescape($new_items[$i]);
1.384     raeburn  4021:                     }
                   4022:                     $hashref->{$key} = 
                   4023:                         &Apache::lonnet::freeze_escape(\%storehash);
                   4024:                     my $unesc_key = &unescape($key);
                   4025:                     $hashref->{&escape('lasttime:'.$unesc_key)} = $now;
                   4026:                     next;
1.383     raeburn  4027:                 }
1.384     raeburn  4028:             }
                   4029:             my @current_items = split(/:/,$hashref->{$key},-1);
                   4030:             shift(@current_items); # remove description
                   4031:             pop(@current_items);   # remove last access
                   4032:             my $numcurrent = scalar(@current_items);
                   4033:             if ($numcurrent > 3) {
                   4034:                 $numcurrent = 3;
                   4035:             }
                   4036:             my @new_items = split(/:/,$courseinfo,-1);
                   4037:             my $numnew = scalar(@new_items);
                   4038:             if ($numcurrent > 0) {
                   4039:                 if ($numnew <= $numcurrent) { # flushcourselogs() from pre 2.2 
                   4040:                     for (my $j=$numcurrent-$numnew; $j>=0; $j--) {
                   4041:                         $courseinfo .= ':'.$current_items[$numcurrent-$j-1];
1.333     raeburn  4042:                     }
1.272     raeburn  4043:                 }
                   4044:             }
1.384     raeburn  4045:             $hashref->{$key}=$courseinfo.':'.$now;
1.234     foxr     4046: 	}
1.311     albertel 4047: 	if (&untie_domain_hash($hashref)) {
1.253     foxr     4048: 	    &Reply( $client, "ok\n", $userinput);
1.234     foxr     4049: 	} else {
1.253     foxr     4050: 	    &Failure($client, "error: ".($!+0)
1.234     foxr     4051: 		     ." untie(GDBM) Failed ".
                   4052: 		     "while attempting courseidput\n", $userinput);
                   4053: 	}
                   4054:     } else {
1.253     foxr     4055: 	&Failure($client, "error: ".($!+0)
1.234     foxr     4056: 		 ." tie(GDBM) Failed ".
                   4057: 		 "while attempting courseidput\n", $userinput);
                   4058:     }
                   4059: 
                   4060:     return 1;
                   4061: }
                   4062: &register_handler("courseidput", \&put_course_id_handler, 0, 1, 0);
                   4063: 
1.383     raeburn  4064: sub put_course_id_hash_handler {
                   4065:     my ($cmd, $tail, $client) = @_;
                   4066:     my $userinput = "$cmd:$tail";
1.384     raeburn  4067:     my ($udom,$mode,$what) = split(/:/, $tail,3);
1.383     raeburn  4068:     chomp($what);
                   4069:     my $now=time;
                   4070:     my @pairs=split(/\&/,$what);
1.384     raeburn  4071:     my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT());
1.383     raeburn  4072:     if ($hashref) {
                   4073:         foreach my $pair (@pairs) {
                   4074:             my ($key,$value)=split(/=/,$pair);
1.384     raeburn  4075:             my $unesc_key = &unescape($key);
                   4076:             if ($mode ne 'timeonly') {
                   4077:                 if (!defined($hashref->{&escape('lasttime:'.$unesc_key)})) {
                   4078:                     my $curritems = &Apache::lonnet::thaw_unescape($key); 
                   4079:                     if (ref($curritems) ne 'HASH') {
                   4080:                         my @current_items = split(/:/,$hashref->{$key},-1);
                   4081:                         my $lasttime = pop(@current_items);
                   4082:                         $hashref->{&escape('lasttime:'.$unesc_key)} = $lasttime;
                   4083:                     } else {
                   4084:                         $hashref->{&escape('lasttime:'.$unesc_key)} = '';
                   4085:                     }
                   4086:                 } 
                   4087:                 $hashref->{$key} = $value;
                   4088:             }
                   4089:             if ($mode ne 'notime') {
                   4090:                 $hashref->{&escape('lasttime:'.$unesc_key)} = $now;
                   4091:             }
1.383     raeburn  4092:         }
                   4093:         if (&untie_domain_hash($hashref)) {
                   4094:             &Reply($client, "ok\n", $userinput);
                   4095:         } else {
                   4096:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4097:                      "while attempting courseidputhash\n", $userinput);
                   4098:         }
                   4099:     } else {
                   4100:         &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4101:                   "while attempting courseidputhash\n", $userinput);
                   4102:     }
                   4103:     return 1;
                   4104: }
                   4105: &register_handler("courseidputhash", \&put_course_id_hash_handler, 0, 1, 0);
                   4106: 
1.234     foxr     4107: #  Retrieves the value of a course id resource keyword pattern
                   4108: #  defined since a starting date.  Both the starting date and the
                   4109: #  keyword pattern are optional.  If the starting date is not supplied it
                   4110: #  is treated as the beginning of time.  If the pattern is not found,
                   4111: #  it is treatred as "." matching everything.
                   4112: #
                   4113: #  Parameters:
                   4114: #     $cmd     - Command keyword that resulted in us being dispatched.
                   4115: #     $tail    - The remainder of the command that, in this case, consists
                   4116: #                of a colon separated list of:
                   4117: #                 domain   - The domain in which the course database is 
                   4118: #                            defined.
                   4119: #                 since    - Optional parameter describing the minimum
                   4120: #                            time of definition(?) of the resources that
                   4121: #                            will match the dump.
                   4122: #                 description - regular expression that is used to filter
                   4123: #                            the dump.  Only keywords matching this regexp
                   4124: #                            will be used.
1.272     raeburn  4125: #                 institutional code - optional supplied code to filter 
                   4126: #                            the dump. Only courses with an institutional code 
                   4127: #                            that match the supplied code will be returned.
1.336     raeburn  4128: #                 owner    - optional supplied username and domain of owner to
                   4129: #                            filter the dump.  Only courses for which the course
                   4130: #                            owner matches the supplied username and/or domain
                   4131: #                            will be returned. Pre-2.2.0 legacy entries from 
                   4132: #                            nohist_courseiddump will only contain usernames.
1.384     raeburn  4133: #                 type     - optional parameter for selection 
1.418     raeburn  4134: #                 regexp_ok - if 1 or -1 allow the supplied institutional code
                   4135: #                            filter to behave as a regular expression:
                   4136: #	                      1 will not exclude the course if the instcode matches the RE 
                   4137: #                            -1 will exclude the course if the instcode matches the RE
1.384     raeburn  4138: #                 rtn_as_hash - whether to return the information available for
                   4139: #                            each matched item as a frozen hash of all 
                   4140: #                            key, value pairs in the item's hash, or as a 
                   4141: #                            colon-separated list of (in order) description,
                   4142: #                            institutional code, and course owner.
1.404     raeburn  4143: #                 selfenrollonly - filter by courses allowing self-enrollment  
                   4144: #                                  now or in the future (selfenrollonly = 1).
                   4145: #                 catfilter - filter by course category, assigned to a course 
                   4146: #                             using manually defined categories (i.e., not
1.407     raeburn  4147: #                             self-cataloging based on on institutional code).   
1.404     raeburn  4148: #                 showhidden - include course in results even if course  
1.407     raeburn  4149: #                              was set to be excluded from course catalog (DC only).
1.404     raeburn  4150: #                 caller -  if set to 'coursecatalog', courses set to be hidden
                   4151: #                           from course catalog will be excluded from results (unless
                   4152: #                           overridden by "showhidden".
1.427     raeburn  4153: #                 cloner - escaped username:domain of course cloner (if picking course to
1.419     raeburn  4154: #                          clone).
                   4155: #                 cc_clone_list - escaped comma separated list of courses for which 
                   4156: #                                 course cloner has active CC role (and so can clone
                   4157: #                                 automatically).
1.427     raeburn  4158: #                 cloneonly - filter by courses for which cloner has rights to clone.
                   4159: #                 createdbefore - include courses for which creation date preceeded this date.
                   4160: #                 createdafter - include courses for which creation date followed this date.
                   4161: #                 creationcontext - include courses created in specified context 
1.404     raeburn  4162: #
1.445     raeburn  4163: #                 domcloner - flag to indicate if user can create CCs in course's domain.
1.489.2.11  raeburn  4164: #                             If so, ability to clone course is automatic.
                   4165: #                 hasuniquecode - filter by courses for which a six character unique code has
                   4166: #                                 been set.
1.445     raeburn  4167: #
1.234     foxr     4168: #     $client  - The socket open on the client.
                   4169: # Returns:
                   4170: #    1     - Continue processing.
                   4171: # Side Effects:
                   4172: #   a reply is written to $client.
                   4173: sub dump_course_id_handler {
                   4174:     my ($cmd, $tail, $client) = @_;
                   4175:     my $userinput = "$cmd:$tail";
                   4176: 
1.333     raeburn  4177:     my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter,
1.404     raeburn  4178:         $typefilter,$regexp_ok,$rtn_as_hash,$selfenrollonly,$catfilter,$showhidden,
1.427     raeburn  4179:         $caller,$cloner,$cc_clone_list,$cloneonly,$createdbefore,$createdafter,
1.489.2.11  raeburn  4180:         $creationcontext,$domcloner,$hasuniquecode) =split(/:/,$tail);
1.397     raeburn  4181:     my $now = time;
1.419     raeburn  4182:     my ($cloneruname,$clonerudom,%cc_clone);
1.234     foxr     4183:     if (defined($description)) {
                   4184: 	$description=&unescape($description);
                   4185:     } else {
                   4186: 	$description='.';
                   4187:     }
1.266     raeburn  4188:     if (defined($instcodefilter)) {
                   4189:         $instcodefilter=&unescape($instcodefilter);
                   4190:     } else {
                   4191:         $instcodefilter='.';
                   4192:     }
1.336     raeburn  4193:     my ($ownerunamefilter,$ownerdomfilter);
1.266     raeburn  4194:     if (defined($ownerfilter)) {
                   4195:         $ownerfilter=&unescape($ownerfilter);
1.336     raeburn  4196:         if ($ownerfilter ne '.' && defined($ownerfilter)) {
                   4197:             if ($ownerfilter =~ /^([^:]*):([^:]*)$/) {
                   4198:                  $ownerunamefilter = $1;
                   4199:                  $ownerdomfilter = $2;
                   4200:             } else {
                   4201:                 $ownerunamefilter = $ownerfilter;
                   4202:                 $ownerdomfilter = '';
                   4203:             }
                   4204:         }
1.266     raeburn  4205:     } else {
                   4206:         $ownerfilter='.';
                   4207:     }
1.336     raeburn  4208: 
1.282     raeburn  4209:     if (defined($coursefilter)) {
                   4210:         $coursefilter=&unescape($coursefilter);
                   4211:     } else {
                   4212:         $coursefilter='.';
                   4213:     }
1.333     raeburn  4214:     if (defined($typefilter)) {
                   4215:         $typefilter=&unescape($typefilter);
                   4216:     } else {
                   4217:         $typefilter='.';
                   4218:     }
1.344     raeburn  4219:     if (defined($regexp_ok)) {
                   4220:         $regexp_ok=&unescape($regexp_ok);
                   4221:     }
1.401     raeburn  4222:     if (defined($catfilter)) {
                   4223:         $catfilter=&unescape($catfilter);
                   4224:     }
1.419     raeburn  4225:     if (defined($cloner)) {
                   4226:         $cloner = &unescape($cloner);
                   4227:         ($cloneruname,$clonerudom) = ($cloner =~ /^($LONCAPA::match_username):($LONCAPA::match_domain)$/); 
                   4228:     }
                   4229:     if (defined($cc_clone_list)) {
                   4230:         $cc_clone_list = &unescape($cc_clone_list);
                   4231:         my @cc_cloners = split('&',$cc_clone_list);
                   4232:         foreach my $cid (@cc_cloners) {
                   4233:             my ($clonedom,$clonenum) = split(':',$cid);
                   4234:             next if ($clonedom ne $udom); 
                   4235:             $cc_clone{$clonedom.'_'.$clonenum} = 1;
                   4236:         } 
                   4237:     }
1.431     raeburn  4238:     if ($createdbefore ne '') {
1.427     raeburn  4239:         $createdbefore = &unescape($createdbefore);
                   4240:     } else {
                   4241:        $createdbefore = 0;
                   4242:     }
1.431     raeburn  4243:     if ($createdafter ne '') {
1.427     raeburn  4244:         $createdafter = &unescape($createdafter);
                   4245:     } else {
                   4246:         $createdafter = 0;
                   4247:     }
1.431     raeburn  4248:     if ($creationcontext ne '') {
1.427     raeburn  4249:         $creationcontext = &unescape($creationcontext);
                   4250:     } else {
                   4251:         $creationcontext = '.';
                   4252:     }
1.489.2.11  raeburn  4253:     unless ($hasuniquecode) {
                   4254:         $hasuniquecode = '.';
                   4255:     }
1.384     raeburn  4256:     my $unpack = 1;
1.485     raeburn  4257:     if ($description eq '.' && $instcodefilter eq '.' && $ownerfilter eq '.' && 
1.384     raeburn  4258:         $typefilter eq '.') {
                   4259:         $unpack = 0;
                   4260:     }
                   4261:     if (!defined($since)) { $since=0; }
1.234     foxr     4262:     my $qresult='';
                   4263:     my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT());
                   4264:     if ($hashref) {
1.384     raeburn  4265: 	while (my ($key,$value) = each(%$hashref)) {
1.397     raeburn  4266:             my ($unesc_key,$lasttime_key,$lasttime,$is_hash,%val,
1.427     raeburn  4267:                 %unesc_val,$selfenroll_end,$selfenroll_types,$created,
                   4268:                 $context);
1.384     raeburn  4269:             $unesc_key = &unescape($key);
                   4270:             if ($unesc_key =~ /^lasttime:/) {
                   4271:                 next;
                   4272:             } else {
                   4273:                 $lasttime_key = &escape('lasttime:'.$unesc_key);
                   4274:             }
                   4275:             if ($hashref->{$lasttime_key} ne '') {
                   4276:                 $lasttime = $hashref->{$lasttime_key};
                   4277:                 next if ($lasttime<$since);
                   4278:             }
1.419     raeburn  4279:             my ($canclone,$valchange);
1.384     raeburn  4280:             my $items = &Apache::lonnet::thaw_unescape($value);
                   4281:             if (ref($items) eq 'HASH') {
1.429     raeburn  4282:                 if ($hashref->{$lasttime_key} eq '') {
1.430     raeburn  4283:                     next if ($since > 1);
1.429     raeburn  4284:                 }
1.384     raeburn  4285:                 $is_hash =  1;
1.445     raeburn  4286:                 if ($domcloner) {
                   4287:                     $canclone = 1;
                   4288:                 } elsif (defined($clonerudom)) {
1.419     raeburn  4289:                     if ($items->{'cloners'}) {
                   4290:                         my @cloneable = split(',',$items->{'cloners'});
                   4291:                         if (@cloneable) {
                   4292:                             if (grep(/^\*$/,@cloneable))  {
                   4293:                                 $canclone = 1;
                   4294:                             } elsif (grep(/^\*:\Q$clonerudom\E$/,@cloneable)) {
                   4295:                                 $canclone = 1;
                   4296:                             } elsif (grep(/^\Q$cloneruname\E:\Q$clonerudom\E$/,@cloneable)) {
                   4297:                                 $canclone = 1;
                   4298:                             }
                   4299:                         }
                   4300:                         unless ($canclone) {
                   4301:                             if ($cloneruname ne '' && $clonerudom ne '') {
                   4302:                                 if ($cc_clone{$unesc_key}) {
                   4303:                                     $canclone = 1;
                   4304:                                     $items->{'cloners'} .= ','.$cloneruname.':'.
                   4305:                                                            $clonerudom;
                   4306:                                     $valchange = 1;
                   4307:                                 }
                   4308:                             }
                   4309:                         }
                   4310:                     } elsif (defined($cloneruname)) {
                   4311:                         if ($cc_clone{$unesc_key}) {
                   4312:                             $canclone = 1;
                   4313:                             $items->{'cloners'} = $cloneruname.':'.$clonerudom;
                   4314:                             $valchange = 1;
                   4315:                         }
1.437     raeburn  4316:                         unless ($canclone) {
                   4317:                             if ($items->{'owner'} =~ /:/) {
                   4318:                                 if ($items->{'owner'} eq $cloner) {
                   4319:                                     $canclone = 1;
                   4320:                                 }
1.444     raeburn  4321:                             } elsif ($cloner eq $items->{'owner'}.':'.$udom) {
1.437     raeburn  4322:                                 $canclone = 1;
                   4323:                             }
                   4324:                             if ($canclone) {
                   4325:                                 $items->{'cloners'} = $cloneruname.':'.$clonerudom;
                   4326:                                 $valchange = 1;
                   4327:                             }
                   4328:                         }
1.419     raeburn  4329:                     }
                   4330:                 }
1.384     raeburn  4331:                 if ($unpack || !$rtn_as_hash) {
                   4332:                     $unesc_val{'descr'} = $items->{'description'};
                   4333:                     $unesc_val{'inst_code'} = $items->{'inst_code'};
                   4334:                     $unesc_val{'owner'} = $items->{'owner'};
                   4335:                     $unesc_val{'type'} = $items->{'type'};
1.419     raeburn  4336:                     $unesc_val{'cloners'} = $items->{'cloners'};
1.427     raeburn  4337:                     $unesc_val{'created'} = $items->{'created'};
                   4338:                     $unesc_val{'context'} = $items->{'context'};
1.404     raeburn  4339:                 }
                   4340:                 $selfenroll_types = $items->{'selfenroll_types'};
                   4341:                 $selfenroll_end = $items->{'selfenroll_end_date'};
1.427     raeburn  4342:                 $created = $items->{'created'};
                   4343:                 $context = $items->{'context'};
1.489.2.11  raeburn  4344:                 if ($hasuniquecode ne '.') {
                   4345:                     next unless ($items->{'uniquecode'});
                   4346:                 }
1.404     raeburn  4347:                 if ($selfenrollonly) {
                   4348:                     next if (!$selfenroll_types);
                   4349:                     if (($selfenroll_end > 0) && ($selfenroll_end <= $now)) {
                   4350:                         next;
1.397     raeburn  4351:                     }
1.404     raeburn  4352:                 }
1.427     raeburn  4353:                 if ($creationcontext ne '.') {
                   4354:                     next if (($context ne '') && ($context ne $creationcontext));  
                   4355:                 }
                   4356:                 if ($createdbefore > 0) {
                   4357:                     next if (($created eq '') || ($created > $createdbefore));   
                   4358:                 }
                   4359:                 if ($createdafter > 0) {
                   4360:                     next if (($created eq '') || ($created <= $createdafter)); 
                   4361:                 }
1.404     raeburn  4362:                 if ($catfilter ne '') {
1.406     raeburn  4363:                     next if ($items->{'categories'} eq '');
                   4364:                     my @categories = split('&',$items->{'categories'}); 
1.407     raeburn  4365:                     next if (@categories == 0);
                   4366:                     my @subcats = split('&',$catfilter);
                   4367:                     my $matchcat = 0;
                   4368:                     foreach my $cat (@categories) {
                   4369:                         if (grep(/^\Q$cat\E$/,@subcats)) {
                   4370:                             $matchcat = 1;
                   4371:                             last;
                   4372:                         }
                   4373:                     }
                   4374:                     next if (!$matchcat);
1.404     raeburn  4375:                 }
                   4376:                 if ($caller eq 'coursecatalog') {
1.405     raeburn  4377:                     if ($items->{'hidefromcat'} eq 'yes') {
                   4378:                         next if !$showhidden;
1.401     raeburn  4379:                     }
1.384     raeburn  4380:                 }
1.383     raeburn  4381:             } else {
1.401     raeburn  4382:                 next if ($catfilter ne '');
1.419     raeburn  4383:                 next if ($selfenrollonly);
1.427     raeburn  4384:                 next if ($createdbefore || $createdafter);
                   4385:                 next if ($creationcontext ne '.');
1.419     raeburn  4386:                 if ((defined($clonerudom)) && (defined($cloneruname)))  {
                   4387:                     if ($cc_clone{$unesc_key}) {
                   4388:                         $canclone = 1;
                   4389:                         $val{'cloners'} = &escape($cloneruname.':'.$clonerudom);
                   4390:                     }
                   4391:                 }
1.384     raeburn  4392:                 $is_hash =  0;
1.388     raeburn  4393:                 my @courseitems = split(/:/,$value);
1.403     raeburn  4394:                 $lasttime = pop(@courseitems);
1.402     raeburn  4395:                 if ($hashref->{$lasttime_key} eq '') {
                   4396:                     next if ($lasttime<$since);
                   4397:                 }
1.384     raeburn  4398: 	        ($val{'descr'},$val{'inst_code'},$val{'owner'},$val{'type'}) = @courseitems;
1.383     raeburn  4399:             }
1.419     raeburn  4400:             if ($cloneonly) {
                   4401:                next unless ($canclone);
                   4402:             }
1.266     raeburn  4403:             my $match = 1;
1.384     raeburn  4404: 	    if ($description ne '.') {
                   4405:                 if (!$is_hash) {
                   4406:                     $unesc_val{'descr'} = &unescape($val{'descr'});
                   4407:                 }
                   4408:                 if (eval{$unesc_val{'descr'} !~ /\Q$description\E/i}) {
1.266     raeburn  4409:                     $match = 0;
1.384     raeburn  4410:                 }
1.266     raeburn  4411:             }
1.384     raeburn  4412:             if ($instcodefilter ne '.') {
                   4413:                 if (!$is_hash) {
                   4414:                     $unesc_val{'inst_code'} = &unescape($val{'inst_code'});
                   4415:                 }
1.418     raeburn  4416:                 if ($regexp_ok == 1) {
1.384     raeburn  4417:                     if (eval{$unesc_val{'inst_code'} !~ /$instcodefilter/}) {
1.344     raeburn  4418:                         $match = 0;
                   4419:                     }
1.418     raeburn  4420:                 } elsif ($regexp_ok == -1) {
                   4421:                     if (eval{$unesc_val{'inst_code'} =~ /$instcodefilter/}) {
                   4422:                         $match = 0;
                   4423:                     }
1.344     raeburn  4424:                 } else {
1.384     raeburn  4425:                     if (eval{$unesc_val{'inst_code'} !~ /\Q$instcodefilter\E/i}) {
1.344     raeburn  4426:                         $match = 0;
                   4427:                     }
1.266     raeburn  4428:                 }
1.234     foxr     4429: 	    }
1.384     raeburn  4430:             if ($ownerfilter ne '.') {
                   4431:                 if (!$is_hash) {
                   4432:                     $unesc_val{'owner'} = &unescape($val{'owner'});
                   4433:                 }
1.336     raeburn  4434:                 if (($ownerunamefilter ne '') && ($ownerdomfilter ne '')) {
1.384     raeburn  4435:                     if ($unesc_val{'owner'} =~ /:/) {
                   4436:                         if (eval{$unesc_val{'owner'} !~ 
                   4437:                              /\Q$ownerunamefilter\E:\Q$ownerdomfilter\E$/i}) {
1.336     raeburn  4438:                             $match = 0;
                   4439:                         } 
                   4440:                     } else {
1.384     raeburn  4441:                         if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) {
1.336     raeburn  4442:                             $match = 0;
                   4443:                         }
                   4444:                     }
                   4445:                 } elsif ($ownerunamefilter ne '') {
1.384     raeburn  4446:                     if ($unesc_val{'owner'} =~ /:/) {
                   4447:                         if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E:[^:]+$/i}) {
1.336     raeburn  4448:                              $match = 0;
                   4449:                         }
                   4450:                     } else {
1.384     raeburn  4451:                         if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) {
1.336     raeburn  4452:                             $match = 0;
                   4453:                         }
                   4454:                     }
                   4455:                 } elsif ($ownerdomfilter ne '') {
1.384     raeburn  4456:                     if ($unesc_val{'owner'} =~ /:/) {
                   4457:                         if (eval{$unesc_val{'owner'} !~ /^[^:]+:\Q$ownerdomfilter\E/}) {
1.336     raeburn  4458:                              $match = 0;
                   4459:                         }
                   4460:                     } else {
                   4461:                         if ($ownerdomfilter ne $udom) {
                   4462:                             $match = 0;
                   4463:                         }
                   4464:                     }
1.266     raeburn  4465:                 }
                   4466:             }
1.384     raeburn  4467:             if ($coursefilter ne '.') {
                   4468:                 if (eval{$unesc_key !~ /^$udom(_)\Q$coursefilter\E$/}) {
1.282     raeburn  4469:                     $match = 0;
                   4470:                 }
                   4471:             }
1.384     raeburn  4472:             if ($typefilter ne '.') {
                   4473:                 if (!$is_hash) {
                   4474:                     $unesc_val{'type'} = &unescape($val{'type'});
                   4475:                 }
                   4476:                 if ($unesc_val{'type'} eq '') {
1.333     raeburn  4477:                     if ($typefilter ne 'Course') {
                   4478:                         $match = 0;
                   4479:                     }
1.383     raeburn  4480:                 } else {
1.384     raeburn  4481:                     if (eval{$unesc_val{'type'} !~ /^\Q$typefilter\E$/}) {
1.333     raeburn  4482:                         $match = 0;
                   4483:                     }
                   4484:                 }
                   4485:             }
1.266     raeburn  4486:             if ($match == 1) {
1.384     raeburn  4487:                 if ($rtn_as_hash) {
                   4488:                     if ($is_hash) {
1.419     raeburn  4489:                         if ($valchange) {
                   4490:                             my $newvalue = &Apache::lonnet::freeze_escape($items);
                   4491:                             $qresult.=$key.'='.$newvalue.'&';
                   4492:                         } else {
                   4493:                             $qresult.=$key.'='.$value.'&';
                   4494:                         }
1.384     raeburn  4495:                     } else {
1.388     raeburn  4496:                         my %rtnhash = ( 'description' => &unescape($val{'descr'}),
                   4497:                                         'inst_code' => &unescape($val{'inst_code'}),
                   4498:                                         'owner'     => &unescape($val{'owner'}),
                   4499:                                         'type'      => &unescape($val{'type'}),
1.419     raeburn  4500:                                         'cloners'   => &unescape($val{'cloners'}),
1.384     raeburn  4501:                                       );
                   4502:                         my $items = &Apache::lonnet::freeze_escape(\%rtnhash);
                   4503:                         $qresult.=$key.'='.$items.'&';
                   4504:                     }
1.383     raeburn  4505:                 } else {
1.384     raeburn  4506:                     if ($is_hash) {
                   4507:                         $qresult .= $key.'='.&escape($unesc_val{'descr'}).':'.
                   4508:                                     &escape($unesc_val{'inst_code'}).':'.
                   4509:                                     &escape($unesc_val{'owner'}).'&';
                   4510:                     } else {
                   4511:                         $qresult .= $key.'='.$val{'descr'}.':'.$val{'inst_code'}.
                   4512:                                     ':'.$val{'owner'}.'&';
                   4513:                     }
1.383     raeburn  4514:                 }
1.266     raeburn  4515:             }
1.234     foxr     4516: 	}
1.311     albertel 4517: 	if (&untie_domain_hash($hashref)) {
1.234     foxr     4518: 	    chop($qresult);
1.387     albertel 4519: 	    &Reply($client, \$qresult, $userinput);
1.234     foxr     4520: 	} else {
                   4521: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4522: 		    "while attempting courseiddump\n", $userinput);
                   4523: 	}
                   4524:     } else {
                   4525: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4526: 		"while attempting courseiddump\n", $userinput);
                   4527:     }
                   4528:     return 1;
                   4529: }
                   4530: &register_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0);
1.238     foxr     4531: 
1.438     raeburn  4532: sub course_lastaccess_handler {
                   4533:     my ($cmd, $tail, $client) = @_;
                   4534:     my $userinput = "$cmd:$tail";
                   4535:     my ($cdom,$cnum) = split(':',$tail); 
                   4536:     my (%lastaccess,$qresult);
                   4537:     my $hashref = &tie_domain_hash($cdom, "nohist_courseids", &GDBM_WRCREAT());
                   4538:     if ($hashref) {
                   4539:         while (my ($key,$value) = each(%$hashref)) {
                   4540:             my ($unesc_key,$lasttime);
                   4541:             $unesc_key = &unescape($key);
                   4542:             if ($cnum) {
                   4543:                 next unless ($unesc_key =~ /\Q$cdom\E_\Q$cnum\E$/);
                   4544:             }
                   4545:             if ($unesc_key =~ /^lasttime:($LONCAPA::match_domain\_$LONCAPA::match_courseid)/) {
                   4546:                 $lastaccess{$1} = $value;
                   4547:             } else {
                   4548:                 my $items = &Apache::lonnet::thaw_unescape($value);
                   4549:                 if (ref($items) eq 'HASH') {
                   4550:                     unless ($lastaccess{$unesc_key}) {
                   4551:                         $lastaccess{$unesc_key} = '';
                   4552:                     }
                   4553:                 } else {
                   4554:                     my @courseitems = split(':',$value);
                   4555:                     $lastaccess{$unesc_key} = pop(@courseitems);
                   4556:                 }
                   4557:             }
                   4558:         }
                   4559:         foreach my $cid (sort(keys(%lastaccess))) {
                   4560:             $qresult.=&escape($cid).'='.$lastaccess{$cid}.'&'; 
                   4561:         }
                   4562:         if (&untie_domain_hash($hashref)) {
                   4563:             if ($qresult) {
                   4564:                 chop($qresult);
                   4565:             }
                   4566:             &Reply($client, \$qresult, $userinput);
                   4567:         } else {
                   4568:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4569:                     "while attempting lastacourseaccess\n", $userinput);
                   4570:         }
                   4571:     } else {
                   4572:         &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4573:                 "while attempting lastcourseaccess\n", $userinput);
                   4574:     }
                   4575:     return 1;
                   4576: }
                   4577: &register_handler("courselastaccess",\&course_lastaccess_handler, 0, 1, 0);
                   4578: 
1.489.2.35.2.  (raeburn 4579:): sub course_sessions_handler {
                   4580:):     my ($cmd, $tail, $client) = @_;
                   4581:):     my $userinput = "$cmd:$tail";
                   4582:):     my ($cdom,$cnum,$lastactivity) = split(':',$tail);
                   4583:):     my $dbsuffix = '_'.$cdom.'_'.$cnum.'.db';
                   4584:):     my (%sessions,$qresult);
                   4585:):     my $now=time;
                   4586:):     if (opendir(DIR,$perlvar{'lonIDsDir'})) {
                   4587:):         my $filename;
                   4588:):         while ($filename=readdir(DIR)) {
                   4589:):             next if ($filename=~/^\./);
                   4590:):             next if ($filename=~/^publicuser_/);
                   4591:):             next if ($filename=~/^[a-f0-9]+_(linked|lti_\d+)\.id$/);
                   4592:):             if ($filename =~ /^($LONCAPA::match_username)_\d+_($LONCAPA::match_domain)_/) {
                   4593:):                 my ($uname,$udom) = ($1,$2);
                   4594:):                 next unless (-e "$perlvar{'lonDaemons'}/tmp/$uname$dbsuffix");
                   4595:):                 my $mtime = (stat("$perlvar{'lonIDsDir'}/$filename"))[9];
                   4596:):                 if ($lastactivity < 0) {
                   4597:):                     next if ($mtime-$now > $lastactivity);
                   4598:):                 } else {
                   4599:):                     next if ($now-$mtime > $lastactivity);
                   4600:):                 }
                   4601:):                 $sessions{$uname.':'.$udom} = $mtime;
                   4602:):             }
                   4603:):         }
                   4604:):         closedir(DIR);
                   4605:):     }
                   4606:):     foreach my $user (keys(%sessions)) {
                   4607:):         $qresult.=&escape($user).'='.$sessions{$user}.'&';
                   4608:):     }
                   4609:):     if ($qresult) {
                   4610:):         chop($qresult);
                   4611:):     }
                   4612:):     &Reply($client, \$qresult, $userinput);
                   4613:):     return 1;
                   4614:): }
                   4615:): &register_handler("coursesessions",\&course_sessions_handler, 0, 1, 0);
                   4616:): 
1.238     foxr     4617: #
1.348     raeburn  4618: # Puts an unencrypted entry in a namespace db file at the domain level 
                   4619: #
                   4620: # Parameters:
                   4621: #    $cmd      - The command that got us here.
                   4622: #    $tail     - Tail of the command (remaining parameters).
                   4623: #    $client   - File descriptor connected to client.
                   4624: # Returns
                   4625: #     0        - Requested to exit, caller should shut down.
                   4626: #     1        - Continue processing.
                   4627: #  Side effects:
                   4628: #     reply is written to $client.
                   4629: #
                   4630: sub put_domain_handler {
                   4631:     my ($cmd,$tail,$client) = @_;
                   4632: 
                   4633:     my $userinput = "$cmd:$tail";
                   4634: 
                   4635:     my ($udom,$namespace,$what) =split(/:/,$tail,3);
                   4636:     chomp($what);
                   4637:     my @pairs=split(/\&/,$what);
                   4638:     my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_WRCREAT(),
                   4639:                                    "P", $what);
                   4640:     if ($hashref) {
                   4641:         foreach my $pair (@pairs) {
                   4642:             my ($key,$value)=split(/=/,$pair);
                   4643:             $hashref->{$key}=$value;
                   4644:         }
                   4645:         if (&untie_domain_hash($hashref)) {
                   4646:             &Reply($client, "ok\n", $userinput);
                   4647:         } else {
                   4648:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4649:                      "while attempting putdom\n", $userinput);
                   4650:         }
                   4651:     } else {
                   4652:         &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4653:                   "while attempting putdom\n", $userinput);
                   4654:     }
                   4655: 
                   4656:     return 1;
                   4657: }
                   4658: &register_handler("putdom", \&put_domain_handler, 0, 1, 0);
                   4659: 
                   4660: # Unencrypted get from the namespace database file at the domain level.
                   4661: # This function retrieves a keyed item from a specific named database in the
                   4662: # domain directory.
                   4663: #
                   4664: # Parameters:
                   4665: #   $cmd             - Command request keyword (get).
                   4666: #   $tail            - Tail of the command.  This is a colon separated list
                   4667: #                      consisting of the domain and the 'namespace' 
                   4668: #                      which selects the gdbm file to do the lookup in,
                   4669: #                      & separated list of keys to lookup.  Note that
                   4670: #                      the values are returned as an & separated list too.
                   4671: #   $client          - File descriptor open on the client.
                   4672: # Returns:
                   4673: #   1       - Continue processing.
                   4674: #   0       - Exit.
                   4675: #  Side effects:
                   4676: #     reply is written to $client.
                   4677: #
                   4678: 
                   4679: sub get_domain_handler {
                   4680:     my ($cmd, $tail, $client) = @_;
                   4681: 
1.461     foxr     4682: 
1.489.2.34  raeburn  4683:     my $userinput = "$cmd:$tail";
1.348     raeburn  4684: 
                   4685:     my ($udom,$namespace,$what)=split(/:/,$tail,3);
                   4686:     chomp($what);
1.489.2.35.2.  (raeburn 4687:):     if ($namespace =~ /^enc/) {
                   4688:):         &Failure( $client, "refused\n", $userinput);
                   4689:):     } else {
                   4690:):         my @queries=split(/\&/,$what);
                   4691:):         my $qresult='';
                   4692:):         my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_READER());
                   4693:):         if ($hashref) {
                   4694:):             for (my $i=0;$i<=$#queries;$i++) {
                   4695:):                 $qresult.="$hashref->{$queries[$i]}&";
                   4696:):             }
                   4697:):             if (&untie_domain_hash($hashref)) {
                   4698:):                 $qresult=~s/\&$//;
                   4699:):                 &Reply($client, \$qresult, $userinput);
                   4700:):             } else {
                   4701:):                 &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4702:):                           "while attempting getdom\n",$userinput);
                   4703:):             }
                   4704:):         } else {
                   4705:):             &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4706:):                      "while attempting getdom\n",$userinput);
                   4707:):         }
                   4708:):     }
                   4709:): 
                   4710:):     return 1;
                   4711:): }
                   4712:): &register_handler("getdom", \&get_domain_handler, 0, 1, 0);
                   4713:): 
                   4714:): sub encrypted_get_domain_handler {
                   4715:):     my ($cmd, $tail, $client) = @_;
                   4716:): 
                   4717:):     my $userinput = "$cmd:$tail";
                   4718:): 
                   4719:):     my ($udom,$namespace,$what)=split(/:/,$tail,3);
                   4720:):     chomp($what);
1.348     raeburn  4721:     my @queries=split(/\&/,$what);
                   4722:     my $qresult='';
                   4723:     my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_READER());
                   4724:     if ($hashref) {
                   4725:         for (my $i=0;$i<=$#queries;$i++) {
                   4726:             $qresult.="$hashref->{$queries[$i]}&";
                   4727:         }
                   4728:         if (&untie_domain_hash($hashref)) {
                   4729:             $qresult=~s/\&$//;
1.489.2.35.2.  (raeburn 4730:):             if ($cipher) {
                   4731:):                 my $cmdlength=length($qresult);
                   4732:):                 $qresult.="         ";
                   4733:):                 my $encqresult='';
                   4734:):                 for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
                   4735:):                     $encqresult.= unpack("H16",
                   4736:):                                          $cipher->encrypt(substr($qresult,
                   4737:):                                                                  $encidx,
                   4738:):                                                                  8)));
                   4739:):                 }
                   4740:):                 &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput);
                   4741:):             } else {
                   4742:):                 &Failure( $client, "error:no_key\n", $userinput);
                   4743:):             }
1.348     raeburn  4744:         } else {
                   4745:             &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
1.489.2.35.2.  (raeburn 4746:):                       "while attempting egetdom\n",$userinput);
1.348     raeburn  4747:         }
                   4748:     } else {
                   4749:         &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
1.489.2.35.2.  (raeburn 4750:):                  "while attempting egetdom\n",$userinput);
1.348     raeburn  4751:     }
                   4752:     return 1;
                   4753: }
1.489.2.35.2.  (raeburn 4754:): &register_handler("egetdom", \&encrypted_get_domain_handler, 1, 1, 0);
1.348     raeburn  4755: 
1.420     raeburn  4756: #
1.238     foxr     4757: #  Puts an id to a domains id database. 
                   4758: #
                   4759: #  Parameters:
                   4760: #   $cmd     - The command that triggered us.
                   4761: #   $tail    - Remainder of the request other than the command. This is a 
                   4762: #              colon separated list containing:
                   4763: #              $domain  - The domain for which we are writing the id.
                   4764: #              $pairs  - The id info to write... this is and & separated list
                   4765: #                        of keyword=value.
                   4766: #   $client  - Socket open on the client.
                   4767: #  Returns:
                   4768: #    1   - Continue processing.
                   4769: #  Side effects:
                   4770: #     reply is written to $client.
                   4771: #
                   4772: sub put_id_handler {
                   4773:     my ($cmd,$tail,$client) = @_;
                   4774: 
                   4775: 
                   4776:     my $userinput = "$cmd:$tail";
                   4777: 
                   4778:     my ($udom,$what)=split(/:/,$tail);
                   4779:     chomp($what);
                   4780:     my @pairs=split(/\&/,$what);
                   4781:     my $hashref = &tie_domain_hash($udom, "ids", &GDBM_WRCREAT(),
                   4782: 				   "P", $what);
                   4783:     if ($hashref) {
                   4784: 	foreach my $pair (@pairs) {
                   4785: 	    my ($key,$value)=split(/=/,$pair);
                   4786: 	    $hashref->{$key}=$value;
                   4787: 	}
1.311     albertel 4788: 	if (&untie_domain_hash($hashref)) {
1.238     foxr     4789: 	    &Reply($client, "ok\n", $userinput);
                   4790: 	} else {
                   4791: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4792: 		     "while attempting idput\n", $userinput);
                   4793: 	}
                   4794:     } else {
                   4795: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4796: 		  "while attempting idput\n", $userinput);
                   4797:     }
                   4798: 
                   4799:     return 1;
                   4800: }
1.263     albertel 4801: &register_handler("idput", \&put_id_handler, 0, 1, 0);
1.238     foxr     4802: 
                   4803: #
                   4804: #  Retrieves a set of id values from the id database.
                   4805: #  Returns an & separated list of results, one for each requested id to the
                   4806: #  client.
                   4807: #
                   4808: # Parameters:
                   4809: #   $cmd       - Command keyword that caused us to be dispatched.
                   4810: #   $tail      - Tail of the command.  Consists of a colon separated:
                   4811: #               domain - the domain whose id table we dump
                   4812: #               ids      Consists of an & separated list of
                   4813: #                        id keywords whose values will be fetched.
                   4814: #                        nonexisting keywords will have an empty value.
                   4815: #   $client    - Socket open on the client.
                   4816: #
                   4817: # Returns:
                   4818: #    1 - indicating processing should continue.
                   4819: # Side effects:
                   4820: #   An & separated list of results is written to $client.
                   4821: #
                   4822: sub get_id_handler {
                   4823:     my ($cmd, $tail, $client) = @_;
                   4824: 
                   4825:     
                   4826:     my $userinput = "$client:$tail";
                   4827:     
                   4828:     my ($udom,$what)=split(/:/,$tail);
                   4829:     chomp($what);
                   4830:     my @queries=split(/\&/,$what);
                   4831:     my $qresult='';
                   4832:     my $hashref = &tie_domain_hash($udom, "ids", &GDBM_READER());
                   4833:     if ($hashref) {
                   4834: 	for (my $i=0;$i<=$#queries;$i++) {
                   4835: 	    $qresult.="$hashref->{$queries[$i]}&";
                   4836: 	}
1.311     albertel 4837: 	if (&untie_domain_hash($hashref)) {
1.238     foxr     4838: 	    $qresult=~s/\&$//;
1.387     albertel 4839: 	    &Reply($client, \$qresult, $userinput);
1.238     foxr     4840: 	} else {
                   4841: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4842: 		      "while attempting idget\n",$userinput);
                   4843: 	}
                   4844:     } else {
                   4845: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4846: 		 "while attempting idget\n",$userinput);
                   4847:     }
                   4848:     
                   4849:     return 1;
                   4850: }
1.263     albertel 4851: &register_handler("idget", \&get_id_handler, 0, 1, 0);
1.238     foxr     4852: 
1.489.2.7  raeburn  4853: #   Deletes one or more ids in a domain's id database.
1.489.2.6  raeburn  4854: #
                   4855: #   Parameters:
                   4856: #       $cmd                  - Command keyword (iddel).
                   4857: #       $tail                 - Command tail.  In this case a colon
                   4858: #                               separated list containing:
                   4859: #                               The domain for which we are deleting the id(s).
                   4860: #                               &-separated list of id(s) to delete.
                   4861: #       $client               - File open on client socket.
                   4862: # Returns:
                   4863: #     1   - Continue processing
                   4864: #     0   - Exit server.
                   4865: #
                   4866: #
                   4867: 
                   4868: sub del_id_handler {
                   4869:     my ($cmd,$tail,$client) = @_;
                   4870: 
                   4871:     my $userinput = "$cmd:$tail";
                   4872: 
                   4873:     my ($udom,$what)=split(/:/,$tail);
                   4874:     chomp($what);
                   4875:     my $hashref = &tie_domain_hash($udom, "ids", &GDBM_WRCREAT(),
                   4876:                                    "D", $what);
                   4877:     if ($hashref) {
                   4878:         my @keys=split(/\&/,$what);
                   4879:         foreach my $key (@keys) {
                   4880:             delete($hashref->{$key});
                   4881:         }
                   4882:         if (&untie_user_hash($hashref)) {
                   4883:             &Reply($client, "ok\n", $userinput);
                   4884:         } else {
                   4885:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4886:                     "while attempting iddel\n", $userinput);
                   4887:         }
                   4888:     } else {
                   4889:         &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   4890:                  "while attempting iddel\n", $userinput);
                   4891:     }
                   4892:     return 1;
                   4893: }
                   4894: &register_handler("iddel", \&del_id_handler, 0, 1, 0);
                   4895: 
1.238     foxr     4896: #
1.299     raeburn  4897: # Puts broadcast e-mail sent by Domain Coordinator in nohist_dcmail database 
                   4898: #
                   4899: # Parameters
                   4900: #   $cmd       - Command keyword that caused us to be dispatched.
                   4901: #   $tail      - Tail of the command.  Consists of a colon separated:
                   4902: #               domain - the domain whose dcmail we are recording
                   4903: #               email    Consists of key=value pair 
                   4904: #                        where key is unique msgid
                   4905: #                        and value is message (in XML)
                   4906: #   $client    - Socket open on the client.
                   4907: #
                   4908: # Returns:
                   4909: #    1 - indicating processing should continue.
                   4910: # Side effects
                   4911: #     reply is written to $client.
                   4912: #
                   4913: sub put_dcmail_handler {
                   4914:     my ($cmd,$tail,$client) = @_;
                   4915:     my $userinput = "$cmd:$tail";
1.463     foxr     4916: 
                   4917: 
1.299     raeburn  4918:     my ($udom,$what)=split(/:/,$tail);
                   4919:     chomp($what);
                   4920:     my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT());
                   4921:     if ($hashref) {
                   4922:         my ($key,$value)=split(/=/,$what);
                   4923:         $hashref->{$key}=$value;
                   4924:     }
1.311     albertel 4925:     if (&untie_domain_hash($hashref)) {
1.299     raeburn  4926:         &Reply($client, "ok\n", $userinput);
                   4927:     } else {
                   4928:         &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   4929:                  "while attempting dcmailput\n", $userinput);
                   4930:     }
                   4931:     return 1;
                   4932: }
                   4933: &register_handler("dcmailput", \&put_dcmail_handler, 0, 1, 0);
                   4934: 
                   4935: #
                   4936: # Retrieves broadcast e-mail from nohist_dcmail database
                   4937: # Returns to client an & separated list of key=value pairs,
                   4938: # where key is msgid and value is message information.
                   4939: #
                   4940: # Parameters
                   4941: #   $cmd       - Command keyword that caused us to be dispatched.
                   4942: #   $tail      - Tail of the command.  Consists of a colon separated:
                   4943: #               domain - the domain whose dcmail table we dump
                   4944: #               startfilter - beginning of time window 
                   4945: #               endfilter - end of time window
                   4946: #               sendersfilter - & separated list of username:domain 
                   4947: #                 for senders to search for.
                   4948: #   $client    - Socket open on the client.
                   4949: #
                   4950: # Returns:
                   4951: #    1 - indicating processing should continue.
                   4952: # Side effects
                   4953: #     reply (& separated list of msgid=messageinfo pairs) is 
                   4954: #     written to $client.
                   4955: #
                   4956: sub dump_dcmail_handler {
                   4957:     my ($cmd, $tail, $client) = @_;
                   4958:                                                                                 
                   4959:     my $userinput = "$cmd:$tail";
                   4960:     my ($udom,$startfilter,$endfilter,$sendersfilter) = split(/:/,$tail);
                   4961:     chomp($sendersfilter);
                   4962:     my @senders = ();
                   4963:     if (defined($startfilter)) {
                   4964:         $startfilter=&unescape($startfilter);
                   4965:     } else {
                   4966:         $startfilter='.';
                   4967:     }
                   4968:     if (defined($endfilter)) {
                   4969:         $endfilter=&unescape($endfilter);
                   4970:     } else {
                   4971:         $endfilter='.';
                   4972:     }
                   4973:     if (defined($sendersfilter)) {
                   4974:         $sendersfilter=&unescape($sendersfilter);
1.300     albertel 4975: 	@senders = map { &unescape($_) } split(/\&/,$sendersfilter);
1.299     raeburn  4976:     }
                   4977: 
                   4978:     my $qresult='';
                   4979:     my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT());
                   4980:     if ($hashref) {
                   4981:         while (my ($key,$value) = each(%$hashref)) {
                   4982:             my $match = 1;
1.303     albertel 4983:             my ($timestamp,$subj,$uname,$udom) = 
                   4984: 		split(/:/,&unescape(&unescape($key)),5); # yes, twice really
1.299     raeburn  4985:             $subj = &unescape($subj);
                   4986:             unless ($startfilter eq '.' || !defined($startfilter)) {
                   4987:                 if ($timestamp < $startfilter) {
                   4988:                     $match = 0;
                   4989:                 }
                   4990:             }
                   4991:             unless ($endfilter eq '.' || !defined($endfilter)) {
                   4992:                 if ($timestamp > $endfilter) {
                   4993:                     $match = 0;
                   4994:                 }
                   4995:             }
                   4996:             unless (@senders < 1) {
                   4997:                 unless (grep/^$uname:$udom$/,@senders) {
                   4998:                     $match = 0;
                   4999:                 }
                   5000:             }
                   5001:             if ($match == 1) {
                   5002:                 $qresult.=$key.'='.$value.'&';
                   5003:             }
                   5004:         }
1.311     albertel 5005:         if (&untie_domain_hash($hashref)) {
1.299     raeburn  5006:             chop($qresult);
1.387     albertel 5007:             &Reply($client, \$qresult, $userinput);
1.299     raeburn  5008:         } else {
                   5009:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   5010:                     "while attempting dcmaildump\n", $userinput);
                   5011:         }
                   5012:     } else {
                   5013:         &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   5014:                 "while attempting dcmaildump\n", $userinput);
                   5015:     }
                   5016:     return 1;
                   5017: }
                   5018: 
                   5019: &register_handler("dcmaildump", \&dump_dcmail_handler, 0, 1, 0);
                   5020: 
                   5021: #
                   5022: # Puts domain roles in nohist_domainroles database
                   5023: #
                   5024: # Parameters
                   5025: #   $cmd       - Command keyword that caused us to be dispatched.
                   5026: #   $tail      - Tail of the command.  Consists of a colon separated:
                   5027: #               domain - the domain whose roles we are recording  
                   5028: #               role -   Consists of key=value pair
                   5029: #                        where key is unique role
                   5030: #                        and value is start/end date information
                   5031: #   $client    - Socket open on the client.
                   5032: #
                   5033: # Returns:
                   5034: #    1 - indicating processing should continue.
                   5035: # Side effects
                   5036: #     reply is written to $client.
                   5037: #
                   5038: 
                   5039: sub put_domainroles_handler {
                   5040:     my ($cmd,$tail,$client) = @_;
                   5041: 
                   5042:     my $userinput = "$cmd:$tail";
                   5043:     my ($udom,$what)=split(/:/,$tail);
                   5044:     chomp($what);
                   5045:     my @pairs=split(/\&/,$what);
                   5046:     my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT());
                   5047:     if ($hashref) {
                   5048:         foreach my $pair (@pairs) {
                   5049:             my ($key,$value)=split(/=/,$pair);
                   5050:             $hashref->{$key}=$value;
                   5051:         }
1.311     albertel 5052:         if (&untie_domain_hash($hashref)) {
1.299     raeburn  5053:             &Reply($client, "ok\n", $userinput);
                   5054:         } else {
                   5055:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   5056:                      "while attempting domroleput\n", $userinput);
                   5057:         }
                   5058:     } else {
                   5059:         &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   5060:                   "while attempting domroleput\n", $userinput);
                   5061:     }
                   5062:                                                                                   
                   5063:     return 1;
                   5064: }
                   5065: 
                   5066: &register_handler("domroleput", \&put_domainroles_handler, 0, 1, 0);
                   5067: 
                   5068: #
                   5069: # Retrieves domain roles from nohist_domainroles database
                   5070: # Returns to client an & separated list of key=value pairs,
                   5071: # where key is role and value is start and end date information.
                   5072: #
                   5073: # Parameters
                   5074: #   $cmd       - Command keyword that caused us to be dispatched.
                   5075: #   $tail      - Tail of the command.  Consists of a colon separated:
                   5076: #               domain - the domain whose domain roles table we dump
                   5077: #   $client    - Socket open on the client.
                   5078: #
                   5079: # Returns:
                   5080: #    1 - indicating processing should continue.
                   5081: # Side effects
                   5082: #     reply (& separated list of role=start/end info pairs) is
                   5083: #     written to $client.
                   5084: #
                   5085: sub dump_domainroles_handler {
                   5086:     my ($cmd, $tail, $client) = @_;
                   5087:                                                                                            
                   5088:     my $userinput = "$cmd:$tail";
                   5089:     my ($udom,$startfilter,$endfilter,$rolesfilter) = split(/:/,$tail);
                   5090:     chomp($rolesfilter);
                   5091:     my @roles = ();
                   5092:     if (defined($startfilter)) {
                   5093:         $startfilter=&unescape($startfilter);
                   5094:     } else {
                   5095:         $startfilter='.';
                   5096:     }
                   5097:     if (defined($endfilter)) {
                   5098:         $endfilter=&unescape($endfilter);
                   5099:     } else {
                   5100:         $endfilter='.';
                   5101:     }
                   5102:     if (defined($rolesfilter)) {
                   5103:         $rolesfilter=&unescape($rolesfilter);
1.300     albertel 5104: 	@roles = split(/\&/,$rolesfilter);
1.299     raeburn  5105:     }
1.421     raeburn  5106: 
1.299     raeburn  5107:     my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT());
                   5108:     if ($hashref) {
                   5109:         my $qresult = '';
                   5110:         while (my ($key,$value) = each(%$hashref)) {
                   5111:             my $match = 1;
1.421     raeburn  5112:             my ($end,$start) = split(/:/,&unescape($value));
1.299     raeburn  5113:             my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,&unescape($key));
1.421     raeburn  5114:             unless (@roles < 1) {
                   5115:                 unless (grep/^\Q$trole\E$/,@roles) {
                   5116:                     $match = 0;
                   5117:                     next;
                   5118:                 }
                   5119:             }
1.299     raeburn  5120:             unless ($startfilter eq '.' || !defined($startfilter)) {
1.415     raeburn  5121:                 if ((defined($start)) && ($start >= $startfilter)) {
1.299     raeburn  5122:                     $match = 0;
1.421     raeburn  5123:                     next;
1.299     raeburn  5124:                 }
                   5125:             }
                   5126:             unless ($endfilter eq '.' || !defined($endfilter)) {
1.421     raeburn  5127:                 if ((defined($end)) && (($end > 0) && ($end <= $endfilter))) {
1.299     raeburn  5128:                     $match = 0;
1.421     raeburn  5129:                     next;
1.299     raeburn  5130:                 }
                   5131:             }
                   5132:             if ($match == 1) {
                   5133:                 $qresult.=$key.'='.$value.'&';
                   5134:             }
                   5135:         }
1.311     albertel 5136:         if (&untie_domain_hash($hashref)) {
1.299     raeburn  5137:             chop($qresult);
1.387     albertel 5138:             &Reply($client, \$qresult, $userinput);
1.299     raeburn  5139:         } else {
                   5140:             &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
                   5141:                     "while attempting domrolesdump\n", $userinput);
                   5142:         }
                   5143:     } else {
                   5144:         &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   5145:                 "while attempting domrolesdump\n", $userinput);
                   5146:     }
                   5147:     return 1;
                   5148: }
                   5149: 
                   5150: &register_handler("domrolesdump", \&dump_domainroles_handler, 0, 1, 0);
                   5151: 
                   5152: 
1.238     foxr     5153: #  Process the tmpput command I'm not sure what this does.. Seems to
                   5154: #  create a file in the lonDaemons/tmp directory of the form $id.tmp
                   5155: # where Id is the client's ip concatenated with a sequence number.
                   5156: # The file will contain some value that is passed in.  Is this e.g.
                   5157: # a login token?
                   5158: #
                   5159: # Parameters:
                   5160: #    $cmd     - The command that got us dispatched.
                   5161: #    $tail    - The remainder of the request following $cmd:
                   5162: #               In this case this will be the contents of the file.
                   5163: #    $client  - Socket connected to the client.
                   5164: # Returns:
                   5165: #    1 indicating processing can continue.
                   5166: # Side effects:
                   5167: #   A file is created in the local filesystem.
                   5168: #   A reply is sent to the client.
                   5169: sub tmp_put_handler {
                   5170:     my ($cmd, $what, $client) = @_;
                   5171: 
                   5172:     my $userinput = "$cmd:$what";	# Reconstruct for logging.
                   5173: 
1.347     raeburn  5174:     my ($record,$context) = split(/:/,$what);
                   5175:     if ($context ne '') {
                   5176:         chomp($context);
                   5177:         $context = &unescape($context);
                   5178:     }
                   5179:     my ($id,$store);
1.238     foxr     5180:     $tmpsnum++;
1.454     raeburn  5181:     if (($context eq 'resetpw') || ($context eq 'createaccount')) {
1.347     raeburn  5182:         $id = &md5_hex(&md5_hex(time.{}.rand().$$));
                   5183:     } else {
                   5184:         $id = $$.'_'.$clientip.'_'.$tmpsnum;
                   5185:     }
1.238     foxr     5186:     $id=~s/\W/\_/g;
1.347     raeburn  5187:     $record=~s/\n//g;
1.238     foxr     5188:     my $execdir=$perlvar{'lonDaemons'};
                   5189:     if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) {
1.347     raeburn  5190: 	print $store $record;
1.238     foxr     5191: 	close $store;
1.387     albertel 5192: 	&Reply($client, \$id, $userinput);
1.238     foxr     5193:     } else {
                   5194: 	&Failure( $client, "error: ".($!+0)."IO::File->new Failed ".
                   5195: 		  "while attempting tmpput\n", $userinput);
                   5196:     }
                   5197:     return 1;
                   5198:   
                   5199: }
                   5200: &register_handler("tmpput", \&tmp_put_handler, 0, 1, 0);
1.263     albertel 5201: 
1.238     foxr     5202: #   Processes the tmpget command.  This command returns the contents
                   5203: #  of a temporary resource file(?) created via tmpput.
                   5204: #
                   5205: # Paramters:
                   5206: #    $cmd      - Command that got us dispatched.
                   5207: #    $id       - Tail of the command, contain the id of the resource
                   5208: #                we want to fetch.
                   5209: #    $client   - socket open on the client.
                   5210: # Return:
                   5211: #    1         - Inidcating processing can continue.
                   5212: # Side effects:
                   5213: #   A reply is sent to the client.
                   5214: #
                   5215: sub tmp_get_handler {
                   5216:     my ($cmd, $id, $client) = @_;
                   5217: 
                   5218:     my $userinput = "$cmd:$id"; 
                   5219:     
                   5220: 
                   5221:     $id=~s/\W/\_/g;
                   5222:     my $store;
                   5223:     my $execdir=$perlvar{'lonDaemons'};
                   5224:     if ($store=IO::File->new("$execdir/tmp/$id.tmp")) {
                   5225: 	my $reply=<$store>;
1.387     albertel 5226: 	&Reply( $client, \$reply, $userinput);
1.238     foxr     5227: 	close $store;
                   5228:     } else {
                   5229: 	&Failure( $client, "error: ".($!+0)."IO::File->new Failed ".
                   5230: 		  "while attempting tmpget\n", $userinput);
                   5231:     }
                   5232: 
                   5233:     return 1;
                   5234: }
                   5235: &register_handler("tmpget", \&tmp_get_handler, 0, 1, 0);
1.263     albertel 5236: 
1.238     foxr     5237: #
                   5238: #  Process the tmpdel command.  This command deletes a temp resource
                   5239: #  created by the tmpput command.
                   5240: #
                   5241: # Parameters:
                   5242: #   $cmd      - Command that got us here.
                   5243: #   $id       - Id of the temporary resource created.
                   5244: #   $client   - socket open on the client process.
                   5245: #
                   5246: # Returns:
                   5247: #   1     - Indicating processing should continue.
                   5248: # Side Effects:
                   5249: #   A file is deleted
                   5250: #   A reply is sent to the client.
                   5251: sub tmp_del_handler {
                   5252:     my ($cmd, $id, $client) = @_;
                   5253:     
                   5254:     my $userinput= "$cmd:$id";
                   5255:     
                   5256:     chomp($id);
                   5257:     $id=~s/\W/\_/g;
                   5258:     my $execdir=$perlvar{'lonDaemons'};
                   5259:     if (unlink("$execdir/tmp/$id.tmp")) {
                   5260: 	&Reply($client, "ok\n", $userinput);
                   5261:     } else {
                   5262: 	&Failure( $client, "error: ".($!+0)."Unlink tmp Failed ".
                   5263: 		  "while attempting tmpdel\n", $userinput);
                   5264:     }
                   5265:     
                   5266:     return 1;
                   5267: 
                   5268: }
                   5269: &register_handler("tmpdel", \&tmp_del_handler, 0, 1, 0);
1.263     albertel 5270: 
1.238     foxr     5271: #
1.489.2.35.2.  (raeburn 5272:): #  Process the updatebalcookie command.  This command updates a
                   5273:): #  cookie in the lonBalancedir directory on a load balancer node.
                   5274:): #
                   5275:): # Parameters:
                   5276:): #   $cmd      - Command that got us here.
                   5277:): #   $tail     - Tail of the request (escaped cookie: escaped current entry)
                   5278:): #
                   5279:): #   $client   - socket open on the client process.
                   5280:): #
                   5281:): # Returns:
                   5282:): #   1     - Indicating processing should continue.
                   5283:): # Side Effects:
                   5284:): #   A cookie file is updated from the lonBalancedir directory
                   5285:): #   A reply is sent to the client.
                   5286:): #
                   5287:): sub update_balcookie_handler {
                   5288:):     my ($cmd, $tail, $client) = @_;
                   5289:): 
                   5290:):     my $userinput= "$cmd:$tail";
                   5291:):     chomp($tail);
                   5292:):     my ($cookie,$lastentry) = map { &unescape($_) } (split(/:/,$tail));
                   5293:): 
                   5294:):     my $updatedone;
                   5295:):     if ($cookie =~ /^$LONCAPA::match_domain\_$LONCAPA::match_username\_[a-f0-9]{32}$/) {
                   5296:):         my $execdir=$perlvar{'lonBalanceDir'};
                   5297:):         if (-e "$execdir/$cookie.id") {
                   5298:):             my $doupdate;
                   5299:):             if (open(my $fh,'<',"$execdir/$cookie.id")) {
                   5300:):                 while (my $line = <$fh>) {
                   5301:):                     chomp($line);
                   5302:):                     if ($line eq $lastentry) {
                   5303:):                         $doupdate = 1;
                   5304:):                         last;
                   5305:):                     }
                   5306:):                 }
                   5307:):                 close($fh);
                   5308:):             }
                   5309:):             if ($doupdate) {
                   5310:):                 if (open(my $fh,'>',"$execdir/$cookie.id")) {
                   5311:):                     print $fh $clientname;
                   5312:):                     close($fh);
                   5313:):                     $updatedone = 1;
                   5314:):                 }
                   5315:):             }
                   5316:):         }
                   5317:):     }
                   5318:):     if ($updatedone) {
                   5319:):         &Reply($client, "ok\n", $userinput);
                   5320:):     } else {
                   5321:):         &Failure( $client, "error: ".($!+0)."file update failed ".
                   5322:):                   "while attempting updatebalcookie\n", $userinput);
                   5323:):     }
                   5324:):     return 1;
                   5325:): }
                   5326:): &register_handler("updatebalcookie", \&update_balcookie_handler, 0, 1, 0);
                   5327:): 
                   5328:): #
1.489.2.32  raeburn  5329: #  Process the delbalcookie command. This command deletes a balancer
1.489.2.35.2.  (raeburn 5330:): #  cookie in the lonBalancedir directory on a load balancer node.
1.489.2.32  raeburn  5331: #
                   5332: # Parameters:
                   5333: #   $cmd      - Command that got us here.
                   5334: #   $cookie   - Cookie to be deleted.
                   5335: #   $client   - socket open on the client process.
                   5336: #
                   5337: # Returns:
                   5338: #   1     - Indicating processing should continue.
                   5339: # Side Effects:
                   5340: #   A cookie file is deleted from the lonBalancedir directory
                   5341: #   A reply is sent to the client.
                   5342: sub del_balcookie_handler {
                   5343:     my ($cmd, $cookie, $client) = @_;
                   5344: 
                   5345:     my $userinput= "$cmd:$cookie";
                   5346: 
                   5347:     chomp($cookie);
1.489.2.35.2.  (raeburn 5348:):     $cookie = &unescape($cookie);
1.489.2.32  raeburn  5349:     my $deleted = '';
                   5350:     if ($cookie =~ /^$LONCAPA::match_domain\_$LONCAPA::match_username\_[a-f0-9]{32}$/) {
                   5351:         my $execdir=$perlvar{'lonBalanceDir'};
                   5352:         if (-e "$execdir/$cookie.id") {
                   5353:             if (open(my $fh,'<',"$execdir/$cookie.id")) {
                   5354:                 my $dodelete;
                   5355:                 while (my $line = <$fh>) {
                   5356:                     chomp($line);
                   5357:                     if ($line eq $clientname) {
                   5358:                         $dodelete = 1;
                   5359:                         last;
                   5360:                     }
                   5361:                 }
                   5362:                 close($fh);
                   5363:                 if ($dodelete) {
                   5364:                     if (unlink("$execdir/$cookie.id")) {
                   5365:                         $deleted = 1;
                   5366:                     }
                   5367:                 }
                   5368:             }
                   5369:         }
                   5370:     }
                   5371:     if ($deleted) {
                   5372:         &Reply($client, "ok\n", $userinput);
                   5373:     } else {
                   5374:         &Failure( $client, "error: ".($!+0)."Unlinking cookie file Failed ".
                   5375:                   "while attempting delbalcookie\n", $userinput);
                   5376:     }
                   5377:     return 1;
                   5378: }
                   5379: &register_handler("delbalcookie", \&del_balcookie_handler, 0, 1, 0);
                   5380: 
                   5381: #
1.246     foxr     5382: #   Processes the setannounce command.  This command
                   5383: #   creates a file named announce.txt in the top directory of
                   5384: #   the documentn root and sets its contents.  The announce.txt file is
                   5385: #   printed in its entirety at the LonCAPA login page.  Note:
                   5386: #   once the announcement.txt fileis created it cannot be deleted.
                   5387: #   However, setting the contents of the file to empty removes the
                   5388: #   announcement from the login page of loncapa so who cares.
                   5389: #
                   5390: # Parameters:
                   5391: #    $cmd          - The command that got us dispatched.
                   5392: #    $announcement - The text of the announcement.
                   5393: #    $client       - Socket open on the client process.
                   5394: # Retunrns:
                   5395: #   1             - Indicating request processing should continue
                   5396: # Side Effects:
                   5397: #   The file {DocRoot}/announcement.txt is created.
                   5398: #   A reply is sent to $client.
                   5399: #
                   5400: sub set_announce_handler {
                   5401:     my ($cmd, $announcement, $client) = @_;
                   5402:   
                   5403:     my $userinput    = "$cmd:$announcement";
                   5404: 
                   5405:     chomp($announcement);
                   5406:     $announcement=&unescape($announcement);
                   5407:     if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}.
                   5408: 				'/announcement.txt')) {
                   5409: 	print $store $announcement;
                   5410: 	close $store;
                   5411: 	&Reply($client, "ok\n", $userinput);
                   5412:     } else {
                   5413: 	&Failure($client, "error: ".($!+0)."\n", $userinput);
                   5414:     }
                   5415: 
                   5416:     return 1;
                   5417: }
                   5418: &register_handler("setannounce", \&set_announce_handler, 0, 1, 0);
1.263     albertel 5419: 
1.246     foxr     5420: #
                   5421: #  Return the version of the daemon.  This can be used to determine
                   5422: #  the compatibility of cross version installations or, alternatively to
                   5423: #  simply know who's out of date and who isn't.  Note that the version
                   5424: #  is returned concatenated with the tail.
                   5425: # Parameters:
                   5426: #   $cmd        - the request that dispatched to us.
                   5427: #   $tail       - Tail of the request (client's version?).
                   5428: #   $client     - Socket open on the client.
                   5429: #Returns:
                   5430: #   1 - continue processing requests.
                   5431: # Side Effects:
                   5432: #   Replies with version to $client.
                   5433: sub get_version_handler {
                   5434:     my ($cmd, $tail, $client) = @_;
                   5435: 
                   5436:     my $userinput  = $cmd.$tail;
                   5437:     
                   5438:     &Reply($client, &version($userinput)."\n", $userinput);
                   5439: 
                   5440: 
                   5441:     return 1;
                   5442: }
                   5443: &register_handler("version", \&get_version_handler, 0, 1, 0);
1.263     albertel 5444: 
1.246     foxr     5445: #  Set the current host and domain.  This is used to support
                   5446: #  multihomed systems.  Each IP of the system, or even separate daemons
                   5447: #  on the same IP can be treated as handling a separate lonCAPA virtual
                   5448: #  machine.  This command selects the virtual lonCAPA.  The client always
                   5449: #  knows the right one since it is lonc and it is selecting the domain/system
                   5450: #  from the hosts.tab file.
                   5451: # Parameters:
                   5452: #    $cmd      - Command that dispatched us.
                   5453: #    $tail     - Tail of the command (domain/host requested).
                   5454: #    $socket   - Socket open on the client.
                   5455: #
                   5456: # Returns:
                   5457: #     1   - Indicates the program should continue to process requests.
                   5458: # Side-effects:
                   5459: #     The default domain/system context is modified for this daemon.
                   5460: #     a reply is sent to the client.
                   5461: #
                   5462: sub set_virtual_host_handler {
                   5463:     my ($cmd, $tail, $socket) = @_;
                   5464:   
                   5465:     my $userinput  ="$cmd:$tail";
                   5466: 
                   5467:     &Reply($client, &sethost($userinput)."\n", $userinput);
                   5468: 
                   5469: 
                   5470:     return 1;
                   5471: }
1.247     albertel 5472: &register_handler("sethost", \&set_virtual_host_handler, 0, 1, 0);
1.246     foxr     5473: 
                   5474: #  Process a request to exit:
                   5475: #   - "bye" is sent to the client.
                   5476: #   - The client socket is shutdown and closed.
                   5477: #   - We indicate to the caller that we should exit.
                   5478: # Formal Parameters:
                   5479: #   $cmd                - The command that got us here.
                   5480: #   $tail               - Tail of the command (empty).
                   5481: #   $client             - Socket open on the tail.
                   5482: # Returns:
                   5483: #   0      - Indicating the program should exit!!
                   5484: #
                   5485: sub exit_handler {
                   5486:     my ($cmd, $tail, $client) = @_;
                   5487: 
                   5488:     my $userinput = "$cmd:$tail";
                   5489: 
                   5490:     &logthis("Client $clientip ($clientname) hanging up: $userinput");
                   5491:     &Reply($client, "bye\n", $userinput);
                   5492:     $client->shutdown(2);        # shutdown the socket forcibly.
                   5493:     $client->close();
                   5494: 
                   5495:     return 0;
                   5496: }
1.248     foxr     5497: &register_handler("exit", \&exit_handler, 0,1,1);
                   5498: &register_handler("init", \&exit_handler, 0,1,1);
                   5499: &register_handler("quit", \&exit_handler, 0,1,1);
                   5500: 
                   5501: #  Determine if auto-enrollment is enabled.
                   5502: #  Note that the original had what I believe to be a defect.
                   5503: #  The original returned 0 if the requestor was not a registerd client.
                   5504: #  It should return "refused".
                   5505: # Formal Parameters:
                   5506: #   $cmd       - The command that invoked us.
                   5507: #   $tail      - The tail of the command (Extra command parameters.
                   5508: #   $client    - The socket open on the client that issued the request.
                   5509: # Returns:
                   5510: #    1         - Indicating processing should continue.
                   5511: #
                   5512: sub enrollment_enabled_handler {
                   5513:     my ($cmd, $tail, $client) = @_;
                   5514:     my $userinput = $cmd.":".$tail; # For logging purposes.
                   5515: 
                   5516:     
1.337     albertel 5517:     my ($cdom) = split(/:/, $tail, 2);   # Domain we're asking about.
                   5518: 
1.248     foxr     5519:     my $outcome  = &localenroll::run($cdom);
1.387     albertel 5520:     &Reply($client, \$outcome, $userinput);
1.248     foxr     5521: 
                   5522:     return 1;
                   5523: }
                   5524: &register_handler("autorun", \&enrollment_enabled_handler, 0, 1, 0);
                   5525: 
1.417     raeburn  5526: #
1.423     raeburn  5527: #   Validate an institutional code used for a LON-CAPA course.          
1.417     raeburn  5528: #
                   5529: # Formal Parameters:
                   5530: #   $cmd          - The command request that got us dispatched.
                   5531: #   $tail         - The tail of the command.  In this case,
                   5532: #                   this is a colon separated set of words that will be split
                   5533: #                   into:
1.424     raeburn  5534: #                        $dom      - The domain for which the check of 
                   5535: #                                    institutional course code will occur.
                   5536: #
                   5537: #                        $instcode - The institutional code for the course
                   5538: #                                    being requested, or validated for rights
                   5539: #                                    to request.
                   5540: #
                   5541: #                        $owner    - The course requestor (who will be the
                   5542: #                                    course owner, in the form username:domain
                   5543: #
1.417     raeburn  5544: #   $client       - Socket open on the client.
                   5545: # Returns:
                   5546: #    1           - Indicating processing should continue.
                   5547: #
                   5548: sub validate_instcode_handler {
                   5549:     my ($cmd, $tail, $client) = @_;
                   5550:     my $userinput = "$cmd:$tail";
1.423     raeburn  5551:     my ($dom,$instcode,$owner) = split(/:/, $tail);
1.422     raeburn  5552:     $instcode = &unescape($instcode);
                   5553:     $owner = &unescape($owner);
1.489.2.3  raeburn  5554:     my ($outcome,$description,$credits) = 
1.426     raeburn  5555:         &localenroll::validate_instcode($dom,$instcode,$owner);
1.489.2.3  raeburn  5556:     my $result = &escape($outcome).'&'.&escape($description).'&'.
                   5557:                  &escape($credits);
1.426     raeburn  5558:     &Reply($client, \$result, $userinput);
1.417     raeburn  5559: 
                   5560:     return 1;
                   5561: }
                   5562: &register_handler("autovalidateinstcode", \&validate_instcode_handler, 0, 1, 0);
                   5563: 
1.248     foxr     5564: #   Get the official sections for which auto-enrollment is possible.
                   5565: #   Since the admin people won't know about 'unofficial sections' 
                   5566: #   we cannot auto-enroll on them.
                   5567: # Formal Parameters:
                   5568: #    $cmd     - The command request that got us dispatched here.
                   5569: #    $tail    - The remainder of the request.  In our case this
                   5570: #               will be split into:
                   5571: #               $coursecode   - The course name from the admin point of view.
                   5572: #               $cdom         - The course's domain(?).
                   5573: #    $client  - Socket open on the client.
                   5574: # Returns:
                   5575: #    1    - Indiciting processing should continue.
                   5576: #
                   5577: sub get_sections_handler {
                   5578:     my ($cmd, $tail, $client) = @_;
                   5579:     my $userinput = "$cmd:$tail";
                   5580: 
                   5581:     my ($coursecode, $cdom) = split(/:/, $tail);
                   5582:     my @secs = &localenroll::get_sections($coursecode,$cdom);
                   5583:     my $seclist = &escape(join(':',@secs));
                   5584: 
1.387     albertel 5585:     &Reply($client, \$seclist, $userinput);
1.248     foxr     5586:     
                   5587: 
                   5588:     return 1;
                   5589: }
                   5590: &register_handler("autogetsections", \&get_sections_handler, 0, 1, 0);
                   5591: 
                   5592: #   Validate the owner of a new course section.  
                   5593: #
                   5594: # Formal Parameters:
                   5595: #   $cmd      - Command that got us dispatched.
                   5596: #   $tail     - the remainder of the command.  For us this consists of a
                   5597: #               colon separated string containing:
                   5598: #                  $inst    - Course Id from the institutions point of view.
                   5599: #                  $owner   - Proposed owner of the course.
                   5600: #                  $cdom    - Domain of the course (from the institutions
                   5601: #                             point of view?)..
                   5602: #   $client   - Socket open on the client.
                   5603: #
                   5604: # Returns:
                   5605: #   1        - Processing should continue.
                   5606: #
                   5607: sub validate_course_owner_handler {
                   5608:     my ($cmd, $tail, $client)  = @_;
                   5609:     my $userinput = "$cmd:$tail";
1.470     raeburn  5610:     my ($inst_course_id, $owner, $cdom, $coowners) = split(/:/, $tail);
                   5611:     
1.336     raeburn  5612:     $owner = &unescape($owner);
1.470     raeburn  5613:     $coowners = &unescape($coowners);
                   5614:     my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom,$coowners);
1.387     albertel 5615:     &Reply($client, \$outcome, $userinput);
1.248     foxr     5616: 
                   5617: 
                   5618: 
                   5619:     return 1;
                   5620: }
                   5621: &register_handler("autonewcourse", \&validate_course_owner_handler, 0, 1, 0);
1.263     albertel 5622: 
1.248     foxr     5623: #
                   5624: #   Validate a course section in the official schedule of classes
                   5625: #   from the institutions point of view (part of autoenrollment).
                   5626: #
                   5627: # Formal Parameters:
                   5628: #   $cmd          - The command request that got us dispatched.
                   5629: #   $tail         - The tail of the command.  In this case,
                   5630: #                   this is a colon separated set of words that will be split
                   5631: #                   into:
                   5632: #                        $inst_course_id - The course/section id from the
                   5633: #                                          institutions point of view.
                   5634: #                        $cdom           - The domain from the institutions
                   5635: #                                          point of view.
                   5636: #   $client       - Socket open on the client.
                   5637: # Returns:
                   5638: #    1           - Indicating processing should continue.
                   5639: #
                   5640: sub validate_course_section_handler {
                   5641:     my ($cmd, $tail, $client) = @_;
                   5642:     my $userinput = "$cmd:$tail";
                   5643:     my ($inst_course_id, $cdom) = split(/:/, $tail);
                   5644: 
                   5645:     my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom);
1.387     albertel 5646:     &Reply($client, \$outcome, $userinput);
1.248     foxr     5647: 
                   5648: 
                   5649:     return 1;
                   5650: }
                   5651: &register_handler("autovalidatecourse", \&validate_course_section_handler, 0, 1, 0);
                   5652: 
                   5653: #
1.340     raeburn  5654: #   Validate course owner's access to enrollment data for specific class section. 
                   5655: #   
                   5656: #
                   5657: # Formal Parameters:
                   5658: #    $cmd     - The command request that got us dispatched.
                   5659: #    $tail    - The tail of the command.   In this case this is a colon separated
1.489.2.29  raeburn  5660: #               set of values that will be split into:
1.340     raeburn  5661: #               $inst_class  - Institutional code for the specific class section   
1.489.2.29  raeburn  5662: #               $ownerlist   - An escaped comma-separated list of username:domain
                   5663: #                              of the course owner, and co-owner(s).
1.340     raeburn  5664: #               $cdom        - The domain of the course from the institution's
                   5665: #                              point of view.
                   5666: #    $client  - The socket open on the client.
                   5667: # Returns:
                   5668: #    1 - continue processing.
                   5669: #
                   5670: 
                   5671: sub validate_class_access_handler {
                   5672:     my ($cmd, $tail, $client) = @_;
                   5673:     my $userinput = "$cmd:$tail";
1.383     raeburn  5674:     my ($inst_class,$ownerlist,$cdom) = split(/:/, $tail);
1.392     raeburn  5675:     my $owners = &unescape($ownerlist);
1.341     albertel 5676:     my $outcome;
                   5677:     eval {
                   5678: 	local($SIG{__DIE__})='DEFAULT';
1.392     raeburn  5679: 	$outcome=&localenroll::check_section($inst_class,$owners,$cdom);
1.341     albertel 5680:     };
1.387     albertel 5681:     &Reply($client,\$outcome, $userinput);
1.340     raeburn  5682: 
                   5683:     return 1;
                   5684: }
                   5685: &register_handler("autovalidateclass_sec", \&validate_class_access_handler, 0, 1, 0);
                   5686: 
                   5687: #
1.489.2.29  raeburn  5688: #   Validate course owner or co-owners(s) access to enrollment data for all sections
                   5689: #   and crosslistings for a particular course.
                   5690: #
                   5691: #
                   5692: # Formal Parameters:
                   5693: #    $cmd     - The command request that got us dispatched.
                   5694: #    $tail    - The tail of the command.   In this case this is a colon separated
                   5695: #               set of values that will be split into:
                   5696: #               $ownerlist   - An escaped comma-separated list of username:domain
                   5697: #                              of the course owner, and co-owner(s).
                   5698: #               $cdom        - The domain of the course from the institution's
                   5699: #                              point of view.
                   5700: #               $classes     - Frozen hash of institutional course sections and
                   5701: #                              crosslistings.
                   5702: #    $client  - The socket open on the client.
                   5703: # Returns:
                   5704: #    1 - continue processing.
                   5705: #
                   5706: 
                   5707: sub validate_classes_handler {
                   5708:     my ($cmd, $tail, $client) = @_;
                   5709:     my $userinput = "$cmd:$tail";
                   5710:     my ($ownerlist,$cdom,$classes) = split(/:/, $tail);
                   5711:     my $classesref = &Apache::lonnet::thaw_unescape($classes);
                   5712:     my $owners = &unescape($ownerlist);
                   5713:     my $result;
                   5714:     eval {
                   5715:         local($SIG{__DIE__})='DEFAULT';
                   5716:         my %validations;
                   5717:         my $response = &localenroll::check_instclasses($owners,$cdom,$classesref,
                   5718:                                                        \%validations);
                   5719:         if ($response eq 'ok') {
                   5720:             foreach my $key (keys(%validations)) {
                   5721:                 $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($validations{$key}).'&';
                   5722:             }
                   5723:             $result =~ s/\&$//;
                   5724:         } else {
                   5725:             $result = 'error';
                   5726:         }
                   5727:     };
                   5728:     if (!$@) {
                   5729:         &Reply($client, \$result, $userinput);
                   5730:     } else {
                   5731:         &Failure($client,"unknown_cmd\n",$userinput);
                   5732:     }
                   5733:     return 1;
                   5734: }
                   5735: &register_handler("autovalidateinstclasses", \&validate_classes_handler, 0, 1, 0);
                   5736: 
                   5737: #
1.340     raeburn  5738: #   Create a password for a new LON-CAPA user added by auto-enrollment.
                   5739: #   Only used for case where authentication method for new user is localauth
1.248     foxr     5740: #
                   5741: # Formal Parameters:
                   5742: #    $cmd     - The command request that got us dispatched.
                   5743: #    $tail    - The tail of the command.   In this case this is a colon separated
                   5744: #               set of words that will be split into:
1.340     raeburn  5745: #               $authparam - An authentication parameter (localauth parameter).
1.248     foxr     5746: #               $cdom      - The domain of the course from the institution's
                   5747: #                            point of view.
                   5748: #    $client  - The socket open on the client.
                   5749: # Returns:
                   5750: #    1 - continue processing.
                   5751: #
                   5752: sub create_auto_enroll_password_handler {
                   5753:     my ($cmd, $tail, $client) = @_;
                   5754:     my $userinput = "$cmd:$tail";
                   5755: 
                   5756:     my ($authparam, $cdom) = split(/:/, $userinput);
                   5757: 
                   5758:     my ($create_passwd,$authchk);
                   5759:     ($authparam,
                   5760:      $create_passwd,
                   5761:      $authchk) = &localenroll::create_password($authparam,$cdom);
                   5762: 
                   5763:     &Reply($client, &escape($authparam.':'.$create_passwd.':'.$authchk)."\n",
                   5764: 	   $userinput);
                   5765: 
                   5766: 
                   5767:     return 1;
                   5768: }
                   5769: &register_handler("autocreatepassword", \&create_auto_enroll_password_handler, 
                   5770: 		  0, 1, 0);
                   5771: 
1.489.2.22  raeburn  5772: sub auto_export_grades_handler {
                   5773:     my ($cmd, $tail, $client) = @_;
                   5774:     my $userinput = "$cmd:$tail";
                   5775:     my ($cdom,$cnum,$info,$data) = split(/:/,$tail);
                   5776:     my $inforef = &Apache::lonnet::thaw_unescape($info);
                   5777:     my $dataref = &Apache::lonnet::thaw_unescape($data);
                   5778:     my ($outcome,$result);;
                   5779:     eval {
                   5780:         local($SIG{__DIE__})='DEFAULT';
                   5781:         my %rtnhash;
                   5782:         $outcome=&localenroll::export_grades($cdom,$cnum,$inforef,$dataref,\%rtnhash);
                   5783:         if ($outcome eq 'ok') {
                   5784:             foreach my $key (keys(%rtnhash)) {
                   5785:                 $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rtnhash{$key}).'&';
                   5786:             }
                   5787:             $result =~ s/\&$//;
                   5788:         }
                   5789:     };
                   5790:     if (!$@) {
                   5791:         if ($outcome eq 'ok') {
                   5792:             if ($cipher) {
                   5793:                 my $cmdlength=length($result);
                   5794:                 $result.="         ";
                   5795:                 my $encresult='';
                   5796:                 for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
                   5797:                     $encresult.= unpack("H16",
                   5798:                                         $cipher->encrypt(substr($result,
                   5799:                                                                 $encidx,
                   5800:                                                                 8)));
                   5801:                 }
                   5802:                 &Reply( $client, "enc:$cmdlength:$encresult\n", $userinput);
                   5803:             } else {
                   5804:                 &Failure( $client, "error:no_key\n", $userinput);
                   5805:             }
                   5806:         } else {
                   5807:             &Reply($client, "$outcome\n", $userinput);
                   5808:         }
                   5809:     } else {
                   5810:         &Failure($client,"export_error\n",$userinput);
                   5811:     }
                   5812:     return 1;
                   5813: }
                   5814: &register_handler("autoexportgrades", \&auto_export_grades_handler,
1.489.2.35  raeburn  5815:                   1, 1, 0);
1.489.2.22  raeburn  5816: 
                   5817: 
1.248     foxr     5818: #   Retrieve and remove temporary files created by/during autoenrollment.
                   5819: #
                   5820: # Formal Parameters:
                   5821: #    $cmd      - The command that got us dispatched.
                   5822: #    $tail     - The tail of the command.  In our case this is a colon 
                   5823: #                separated list that will be split into:
1.489.2.24  raeburn  5824: #                $filename - The name of the file to retrieve.
1.248     foxr     5825: #                            The filename is given as a path relative to
                   5826: #                            the LonCAPA temp file directory.
                   5827: #    $client   - Socket open on the client.
                   5828: #
                   5829: # Returns:
                   5830: #   1     - Continue processing.
                   5831: sub retrieve_auto_file_handler {
                   5832:     my ($cmd, $tail, $client)    = @_;
                   5833:     my $userinput                = "cmd:$tail";
                   5834: 
                   5835:     my ($filename)   = split(/:/, $tail);
                   5836: 
                   5837:     my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename;
1.489.2.20  raeburn  5838:     if ($filename =~m{/\.\./}) {
                   5839:         &Failure($client, "refused\n", $userinput);
1.489.2.24  raeburn  5840:     } elsif ($filename !~ /^$LONCAPA::match_domain\_$LONCAPA::match_courseid\_.+_classlist\.xml$/) {
                   5841:         &Failure($client, "refused\n", $userinput);
1.489.2.20  raeburn  5842:     } elsif ( (-e $source) && ($filename ne '') ) {
1.248     foxr     5843: 	my $reply = '';
                   5844: 	if (open(my $fh,$source)) {
                   5845: 	    while (<$fh>) {
                   5846: 		chomp($_);
                   5847: 		$_ =~ s/^\s+//g;
                   5848: 		$_ =~ s/\s+$//g;
                   5849: 		$reply .= $_;
                   5850: 	    }
                   5851: 	    close($fh);
                   5852: 	    &Reply($client, &escape($reply)."\n", $userinput);
                   5853: 
                   5854: #   Does this have to be uncommented??!?  (RF).
                   5855: #
                   5856: #                                unlink($source);
                   5857: 	} else {
                   5858: 	    &Failure($client, "error\n", $userinput);
                   5859: 	}
                   5860:     } else {
                   5861: 	&Failure($client, "error\n", $userinput);
                   5862:     }
                   5863:     
                   5864: 
                   5865:     return 1;
                   5866: }
                   5867: &register_handler("autoretrieve", \&retrieve_auto_file_handler, 0,1,0);
                   5868: 
1.423     raeburn  5869: sub crsreq_checks_handler {
                   5870:     my ($cmd, $tail, $client) = @_;
                   5871:     my $userinput = "$cmd:$tail";
                   5872:     my $dom = $tail;
                   5873:     my $result;
1.489.2.12  raeburn  5874:     my @reqtypes = ('official','unofficial','community','textbook');
1.423     raeburn  5875:     eval {
                   5876:         local($SIG{__DIE__})='DEFAULT';
                   5877:         my %validations;
1.424     raeburn  5878:         my $response = &localenroll::crsreq_checks($dom,\@reqtypes,
                   5879:                                                    \%validations);
1.423     raeburn  5880:         if ($response eq 'ok') { 
                   5881:             foreach my $key (keys(%validations)) {
                   5882:                 $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($validations{$key}).'&';
                   5883:             }
                   5884:             $result =~ s/\&$//;
                   5885:         } else {
                   5886:             $result = 'error';
                   5887:         }
                   5888:     };
                   5889:     if (!$@) {
                   5890:         &Reply($client, \$result, $userinput);
                   5891:     } else {
                   5892:         &Failure($client,"unknown_cmd\n",$userinput);
                   5893:     }
                   5894:     return 1;
                   5895: }
                   5896: &register_handler("autocrsreqchecks", \&crsreq_checks_handler, 0, 1, 0);
                   5897: 
                   5898: sub validate_crsreq_handler {
                   5899:     my ($cmd, $tail, $client) = @_;
                   5900:     my $userinput = "$cmd:$tail";
1.489.2.13  raeburn  5901:     my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist,$customdata) = split(/:/, $tail);
1.423     raeburn  5902:     $instcode = &unescape($instcode);
                   5903:     $owner = &unescape($owner);
                   5904:     $crstype = &unescape($crstype);
                   5905:     $inststatuslist = &unescape($inststatuslist);
                   5906:     $instcode = &unescape($instcode);
                   5907:     $instseclist = &unescape($instseclist);
1.489.2.13  raeburn  5908:     my $custominfo = &Apache::lonnet::thaw_unescape($customdata);
1.423     raeburn  5909:     my $outcome;
                   5910:     eval {
                   5911:         local($SIG{__DIE__})='DEFAULT';
                   5912:         $outcome = &localenroll::validate_crsreq($dom,$owner,$crstype,
                   5913:                                                  $inststatuslist,$instcode,
1.489.2.13  raeburn  5914:                                                  $instseclist,$custominfo);
1.423     raeburn  5915:     };
                   5916:     if (!$@) {
                   5917:         &Reply($client, \$outcome, $userinput);
                   5918:     } else {
                   5919:         &Failure($client,"unknown_cmd\n",$userinput);
                   5920:     }
                   5921:     return 1;
                   5922: }
                   5923: &register_handler("autocrsreqvalidation", \&validate_crsreq_handler, 0, 1, 0);
                   5924: 
1.489.2.11  raeburn  5925: sub crsreq_update_handler {
                   5926:     my ($cmd, $tail, $client) = @_;
                   5927:     my $userinput = "$cmd:$tail";
1.489.2.14  raeburn  5928:     my ($cdom,$cnum,$crstype,$action,$ownername,$ownerdomain,$fullname,$title,$code,
                   5929:         $accessstart,$accessend,$infohashref) =
1.489.2.11  raeburn  5930:         split(/:/, $tail);
                   5931:     $crstype = &unescape($crstype);
                   5932:     $action = &unescape($action);
                   5933:     $ownername = &unescape($ownername);
                   5934:     $ownerdomain = &unescape($ownerdomain);
                   5935:     $fullname = &unescape($fullname);
                   5936:     $title = &unescape($title);
                   5937:     $code = &unescape($code);
1.489.2.14  raeburn  5938:     $accessstart = &unescape($accessstart);
                   5939:     $accessend = &unescape($accessend);
1.489.2.11  raeburn  5940:     my $incoming = &Apache::lonnet::thaw_unescape($infohashref);
                   5941:     my ($result,$outcome);
                   5942:     eval {
                   5943:         local($SIG{__DIE__})='DEFAULT';
                   5944:         my %rtnhash;
                   5945:         $outcome = &localenroll::crsreq_updates($cdom,$cnum,$crstype,$action,
                   5946:                                                 $ownername,$ownerdomain,$fullname,
1.489.2.14  raeburn  5947:                                                 $title,$code,$accessstart,$accessend,
                   5948:                                                 $incoming,\%rtnhash);
1.489.2.11  raeburn  5949:         if ($outcome eq 'ok') {
1.489.2.18  raeburn  5950:             my @posskeys = qw(createdweb createdmsg createdcustomized createdactions queuedweb queuedmsg formitems reviewweb validationjs onload javascript);
1.489.2.11  raeburn  5951:             foreach my $key (keys(%rtnhash)) {
                   5952:                 if (grep(/^\Q$key\E/,@posskeys)) {
                   5953:                     $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rtnhash{$key}).'&';
                   5954:                 }
                   5955:             }
                   5956:             $result =~ s/\&$//;
                   5957:         }
                   5958:     };
                   5959:     if (!$@) {
                   5960:         if ($outcome eq 'ok') {
                   5961:             &Reply($client, \$result, $userinput);
                   5962:         } else {
                   5963:             &Reply($client, "format_error\n", $userinput);
                   5964:         }
                   5965:     } else {
                   5966:         &Failure($client,"unknown_cmd\n",$userinput);
                   5967:     }
                   5968:     return 1;
                   5969: }
                   5970: &register_handler("autocrsrequpdate", \&crsreq_update_handler, 0, 1, 0);
                   5971: 
1.248     foxr     5972: #
                   5973: #   Read and retrieve institutional code format (for support form).
                   5974: # Formal Parameters:
                   5975: #    $cmd        - Command that dispatched us.
                   5976: #    $tail       - Tail of the command.  In this case it conatins 
                   5977: #                  the course domain and the coursename.
                   5978: #    $client     - Socket open on the client.
                   5979: # Returns:
                   5980: #    1     - Continue processing.
                   5981: #
                   5982: sub get_institutional_code_format_handler {
                   5983:     my ($cmd, $tail, $client)   = @_;
                   5984:     my $userinput               = "$cmd:$tail";
                   5985: 
                   5986:     my $reply;
                   5987:     my($cdom,$course) = split(/:/,$tail);
                   5988:     my @pairs = split/\&/,$course;
                   5989:     my %instcodes = ();
                   5990:     my %codes = ();
                   5991:     my @codetitles = ();
                   5992:     my %cat_titles = ();
                   5993:     my %cat_order = ();
                   5994:     foreach (@pairs) {
                   5995: 	my ($key,$value) = split/=/,$_;
                   5996: 	$instcodes{&unescape($key)} = &unescape($value);
                   5997:     }
                   5998:     my $formatreply = &localenroll::instcode_format($cdom,
                   5999: 						    \%instcodes,
                   6000: 						    \%codes,
                   6001: 						    \@codetitles,
                   6002: 						    \%cat_titles,
                   6003: 						    \%cat_order);
                   6004:     if ($formatreply eq 'ok') {
1.365     albertel 6005: 	my $codes_str = &Apache::lonnet::hash2str(%codes);
                   6006: 	my $codetitles_str = &Apache::lonnet::array2str(@codetitles);
                   6007: 	my $cat_titles_str = &Apache::lonnet::hash2str(%cat_titles);
                   6008: 	my $cat_order_str = &Apache::lonnet::hash2str(%cat_order);
1.248     foxr     6009: 	&Reply($client,
                   6010: 	       $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':'
                   6011: 	       .$cat_order_str."\n",
                   6012: 	       $userinput);
                   6013:     } else {
                   6014: 	# this else branch added by RF since if not ok, lonc will
                   6015: 	# hang waiting on reply until timeout.
                   6016: 	#
                   6017: 	&Reply($client, "format_error\n", $userinput);
                   6018:     }
                   6019:     
                   6020:     return 1;
                   6021: }
1.265     albertel 6022: &register_handler("autoinstcodeformat",
                   6023: 		  \&get_institutional_code_format_handler,0,1,0);
1.246     foxr     6024: 
1.345     raeburn  6025: sub get_institutional_defaults_handler {
                   6026:     my ($cmd, $tail, $client)   = @_;
                   6027:     my $userinput               = "$cmd:$tail";
                   6028: 
                   6029:     my $dom = $tail;
                   6030:     my %defaults_hash;
                   6031:     my @code_order;
                   6032:     my $outcome;
                   6033:     eval {
                   6034:         local($SIG{__DIE__})='DEFAULT';
                   6035:         $outcome = &localenroll::instcode_defaults($dom,\%defaults_hash,
                   6036:                                                    \@code_order);
                   6037:     };
                   6038:     if (!$@) {
                   6039:         if ($outcome eq 'ok') {
                   6040:             my $result='';
                   6041:             while (my ($key,$value) = each(%defaults_hash)) {
                   6042:                 $result.=&escape($key).'='.&escape($value).'&';
                   6043:             }
                   6044:             $result .= 'code_order='.&escape(join('&',@code_order));
1.387     albertel 6045:             &Reply($client,\$result,$userinput);
1.345     raeburn  6046:         } else {
                   6047:             &Reply($client,"error\n", $userinput);
                   6048:         }
                   6049:     } else {
                   6050:         &Failure($client,"unknown_cmd\n",$userinput);
                   6051:     }
                   6052: }
                   6053: &register_handler("autoinstcodedefaults",
                   6054:                   \&get_institutional_defaults_handler,0,1,0);
                   6055: 
1.416     raeburn  6056: sub get_possible_instcodes_handler {
                   6057:     my ($cmd, $tail, $client)   = @_;
                   6058:     my $userinput               = "$cmd:$tail";
                   6059: 
                   6060:     my $reply;
                   6061:     my $cdom = $tail;
1.417     raeburn  6062:     my (@codetitles,%cat_titles,%cat_order,@code_order);
1.416     raeburn  6063:     my $formatreply = &localenroll::possible_instcodes($cdom,
                   6064:                                                        \@codetitles,
                   6065:                                                        \%cat_titles,
1.417     raeburn  6066:                                                        \%cat_order,
                   6067:                                                        \@code_order);
1.416     raeburn  6068:     if ($formatreply eq 'ok') {
                   6069:         my $result = join('&',map {&escape($_);} (@codetitles)).':';
1.417     raeburn  6070:         $result .= join('&',map {&escape($_);} (@code_order)).':';
1.416     raeburn  6071:         foreach my $key (keys(%cat_titles)) {
                   6072:             $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($cat_titles{$key}).'&';
                   6073:         }
                   6074:         $result =~ s/\&$//;
                   6075:         $result .= ':';
                   6076:         foreach my $key (keys(%cat_order)) {
                   6077:             $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($cat_order{$key}).'&';
                   6078:         }
                   6079:         $result =~ s/\&$//;
                   6080:         &Reply($client,\$result,$userinput);
                   6081:     } else {
                   6082:         &Reply($client, "format_error\n", $userinput);
                   6083:     }
                   6084:     return 1;
                   6085: }
                   6086: &register_handler("autopossibleinstcodes",
                   6087:                   \&get_possible_instcodes_handler,0,1,0);
                   6088: 
1.381     raeburn  6089: sub get_institutional_user_rules {
                   6090:     my ($cmd, $tail, $client)   = @_;
                   6091:     my $userinput               = "$cmd:$tail";
                   6092:     my $dom = &unescape($tail);
                   6093:     my (%rules_hash,@rules_order);
                   6094:     my $outcome;
                   6095:     eval {
                   6096:         local($SIG{__DIE__})='DEFAULT';
                   6097:         $outcome = &localenroll::username_rules($dom,\%rules_hash,\@rules_order);
                   6098:     };
                   6099:     if (!$@) {
                   6100:         if ($outcome eq 'ok') {
                   6101:             my $result;
                   6102:             foreach my $key (keys(%rules_hash)) {
                   6103:                 $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&';
                   6104:             }
                   6105:             $result =~ s/\&$//;
                   6106:             $result .= ':';
                   6107:             if (@rules_order > 0) {
                   6108:                 foreach my $item (@rules_order) {
                   6109:                     $result .= &escape($item).'&';
                   6110:                 }
                   6111:             }
                   6112:             $result =~ s/\&$//;
1.387     albertel 6113:             &Reply($client,\$result,$userinput);
1.381     raeburn  6114:         } else {
                   6115:             &Reply($client,"error\n", $userinput);
                   6116:         }
                   6117:     } else {
                   6118:         &Failure($client,"unknown_cmd\n",$userinput);
                   6119:     }
                   6120: }
                   6121: &register_handler("instuserrules",\&get_institutional_user_rules,0,1,0);
                   6122: 
1.389     raeburn  6123: sub get_institutional_id_rules {
                   6124:     my ($cmd, $tail, $client)   = @_;
                   6125:     my $userinput               = "$cmd:$tail";
                   6126:     my $dom = &unescape($tail);
                   6127:     my (%rules_hash,@rules_order);
                   6128:     my $outcome;
                   6129:     eval {
                   6130:         local($SIG{__DIE__})='DEFAULT';
                   6131:         $outcome = &localenroll::id_rules($dom,\%rules_hash,\@rules_order);
                   6132:     };
                   6133:     if (!$@) {
                   6134:         if ($outcome eq 'ok') {
                   6135:             my $result;
                   6136:             foreach my $key (keys(%rules_hash)) {
                   6137:                 $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&';
                   6138:             }
                   6139:             $result =~ s/\&$//;
                   6140:             $result .= ':';
                   6141:             if (@rules_order > 0) {
                   6142:                 foreach my $item (@rules_order) {
                   6143:                     $result .= &escape($item).'&';
                   6144:                 }
                   6145:             }
                   6146:             $result =~ s/\&$//;
                   6147:             &Reply($client,\$result,$userinput);
                   6148:         } else {
                   6149:             &Reply($client,"error\n", $userinput);
                   6150:         }
                   6151:     } else {
                   6152:         &Failure($client,"unknown_cmd\n",$userinput);
                   6153:     }
                   6154: }
                   6155: &register_handler("instidrules",\&get_institutional_id_rules,0,1,0);
                   6156: 
1.397     raeburn  6157: sub get_institutional_selfcreate_rules {
1.396     raeburn  6158:     my ($cmd, $tail, $client)   = @_;
                   6159:     my $userinput               = "$cmd:$tail";
                   6160:     my $dom = &unescape($tail);
                   6161:     my (%rules_hash,@rules_order);
                   6162:     my $outcome;
                   6163:     eval {
                   6164:         local($SIG{__DIE__})='DEFAULT';
1.397     raeburn  6165:         $outcome = &localenroll::selfcreate_rules($dom,\%rules_hash,\@rules_order);
1.396     raeburn  6166:     };
                   6167:     if (!$@) {
                   6168:         if ($outcome eq 'ok') {
                   6169:             my $result;
                   6170:             foreach my $key (keys(%rules_hash)) {
                   6171:                 $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&';
                   6172:             }
                   6173:             $result =~ s/\&$//;
                   6174:             $result .= ':';
                   6175:             if (@rules_order > 0) {
                   6176:                 foreach my $item (@rules_order) {
                   6177:                     $result .= &escape($item).'&';
                   6178:                 }
                   6179:             }
                   6180:             $result =~ s/\&$//;
                   6181:             &Reply($client,\$result,$userinput);
                   6182:         } else {
                   6183:             &Reply($client,"error\n", $userinput);
                   6184:         }
                   6185:     } else {
                   6186:         &Failure($client,"unknown_cmd\n",$userinput);
                   6187:     }
                   6188: }
1.397     raeburn  6189: &register_handler("instemailrules",\&get_institutional_selfcreate_rules,0,1,0);
1.396     raeburn  6190: 
1.381     raeburn  6191: 
                   6192: sub institutional_username_check {
                   6193:     my ($cmd, $tail, $client)   = @_;
                   6194:     my $userinput               = "$cmd:$tail";
                   6195:     my %rulecheck;
                   6196:     my $outcome;
                   6197:     my ($udom,$uname,@rules) = split(/:/,$tail);
                   6198:     $udom = &unescape($udom);
                   6199:     $uname = &unescape($uname);
                   6200:     @rules = map {&unescape($_);} (@rules);
                   6201:     eval {
                   6202:         local($SIG{__DIE__})='DEFAULT';
                   6203:         $outcome = &localenroll::username_check($udom,$uname,\@rules,\%rulecheck);
                   6204:     };
                   6205:     if (!$@) {
                   6206:         if ($outcome eq 'ok') {
                   6207:             my $result='';
                   6208:             foreach my $key (keys(%rulecheck)) {
                   6209:                 $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&';
                   6210:             }
1.387     albertel 6211:             &Reply($client,\$result,$userinput);
1.381     raeburn  6212:         } else {
                   6213:             &Reply($client,"error\n", $userinput);
                   6214:         }
                   6215:     } else {
                   6216:         &Failure($client,"unknown_cmd\n",$userinput);
                   6217:     }
                   6218: }
                   6219: &register_handler("instrulecheck",\&institutional_username_check,0,1,0);
                   6220: 
1.389     raeburn  6221: sub institutional_id_check {
                   6222:     my ($cmd, $tail, $client)   = @_;
                   6223:     my $userinput               = "$cmd:$tail";
                   6224:     my %rulecheck;
                   6225:     my $outcome;
                   6226:     my ($udom,$id,@rules) = split(/:/,$tail);
                   6227:     $udom = &unescape($udom);
                   6228:     $id = &unescape($id);
                   6229:     @rules = map {&unescape($_);} (@rules);
                   6230:     eval {
                   6231:         local($SIG{__DIE__})='DEFAULT';
                   6232:         $outcome = &localenroll::id_check($udom,$id,\@rules,\%rulecheck);
                   6233:     };
                   6234:     if (!$@) {
                   6235:         if ($outcome eq 'ok') {
                   6236:             my $result='';
                   6237:             foreach my $key (keys(%rulecheck)) {
                   6238:                 $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&';
                   6239:             }
                   6240:             &Reply($client,\$result,$userinput);
                   6241:         } else {
                   6242:             &Reply($client,"error\n", $userinput);
                   6243:         }
                   6244:     } else {
                   6245:         &Failure($client,"unknown_cmd\n",$userinput);
                   6246:     }
                   6247: }
                   6248: &register_handler("instidrulecheck",\&institutional_id_check,0,1,0);
1.345     raeburn  6249: 
1.397     raeburn  6250: sub institutional_selfcreate_check {
1.396     raeburn  6251:     my ($cmd, $tail, $client)   = @_;
                   6252:     my $userinput               = "$cmd:$tail";
                   6253:     my %rulecheck;
                   6254:     my $outcome;
                   6255:     my ($udom,$email,@rules) = split(/:/,$tail);
                   6256:     $udom = &unescape($udom);
                   6257:     $email = &unescape($email);
                   6258:     @rules = map {&unescape($_);} (@rules);
                   6259:     eval {
                   6260:         local($SIG{__DIE__})='DEFAULT';
1.397     raeburn  6261:         $outcome = &localenroll::selfcreate_check($udom,$email,\@rules,\%rulecheck);
1.396     raeburn  6262:     };
                   6263:     if (!$@) {
                   6264:         if ($outcome eq 'ok') {
                   6265:             my $result='';
                   6266:             foreach my $key (keys(%rulecheck)) {
                   6267:                 $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&';
                   6268:             }
                   6269:             &Reply($client,\$result,$userinput);
                   6270:         } else {
                   6271:             &Reply($client,"error\n", $userinput);
                   6272:         }
                   6273:     } else {
                   6274:         &Failure($client,"unknown_cmd\n",$userinput);
                   6275:     }
                   6276: }
1.397     raeburn  6277: &register_handler("instselfcreatecheck",\&institutional_selfcreate_check,0,1,0);
1.396     raeburn  6278: 
1.317     raeburn  6279: # Get domain specific conditions for import of student photographs to a course
                   6280: #
                   6281: # Retrieves information from photo_permission subroutine in localenroll.
                   6282: # Returns outcome (ok) if no processing errors, and whether course owner is 
                   6283: # required to accept conditions of use (yes/no).
                   6284: #
                   6285: #    
                   6286: sub photo_permission_handler {
                   6287:     my ($cmd, $tail, $client)   = @_;
                   6288:     my $userinput               = "$cmd:$tail";
                   6289:     my $cdom = $tail;
                   6290:     my ($perm_reqd,$conditions);
1.320     albertel 6291:     my $outcome;
                   6292:     eval {
                   6293: 	local($SIG{__DIE__})='DEFAULT';
                   6294: 	$outcome = &localenroll::photo_permission($cdom,\$perm_reqd,
                   6295: 						  \$conditions);
                   6296:     };
                   6297:     if (!$@) {
                   6298: 	&Reply($client, &escape($outcome.':'.$perm_reqd.':'. $conditions)."\n",
                   6299: 	       $userinput);
                   6300:     } else {
                   6301: 	&Failure($client,"unknown_cmd\n",$userinput);
                   6302:     }
                   6303:     return 1;
1.317     raeburn  6304: }
                   6305: &register_handler("autophotopermission",\&photo_permission_handler,0,1,0);
                   6306: 
                   6307: #
                   6308: # Checks if student photo is available for a user in the domain, in the user's
                   6309: # directory (in /userfiles/internal/studentphoto.jpg).
                   6310: # Uses localstudentphoto:fetch() to ensure there is an up to date copy of
                   6311: # the student's photo.   
                   6312: 
                   6313: sub photo_check_handler {
                   6314:     my ($cmd, $tail, $client)   = @_;
                   6315:     my $userinput               = "$cmd:$tail";
                   6316:     my ($udom,$uname,$pid) = split(/:/,$tail);
                   6317:     $udom = &unescape($udom);
                   6318:     $uname = &unescape($uname);
                   6319:     $pid = &unescape($pid);
                   6320:     my $path=&propath($udom,$uname).'/userfiles/internal/';
                   6321:     if (!-e $path) {
                   6322:         &mkpath($path);
                   6323:     }
                   6324:     my $response;
                   6325:     my $result = &localstudentphoto::fetch($udom,$uname,$pid,\$response);
                   6326:     $result .= ':'.$response;
                   6327:     &Reply($client, &escape($result)."\n",$userinput);
1.320     albertel 6328:     return 1;
1.317     raeburn  6329: }
                   6330: &register_handler("autophotocheck",\&photo_check_handler,0,1,0);
                   6331: 
                   6332: #
                   6333: # Retrieve information from localenroll about whether to provide a button     
                   6334: # for users who have enbled import of student photos to initiate an 
                   6335: # update of photo files for registered students. Also include 
                   6336: # comment to display alongside button.  
                   6337: 
                   6338: sub photo_choice_handler {
                   6339:     my ($cmd, $tail, $client) = @_;
                   6340:     my $userinput             = "$cmd:$tail";
                   6341:     my $cdom                  = &unescape($tail);
1.320     albertel 6342:     my ($update,$comment);
                   6343:     eval {
                   6344: 	local($SIG{__DIE__})='DEFAULT';
                   6345: 	($update,$comment)    = &localenroll::manager_photo_update($cdom);
                   6346:     };
                   6347:     if (!$@) {
                   6348: 	&Reply($client,&escape($update).':'.&escape($comment)."\n",$userinput);
                   6349:     } else {
                   6350: 	&Failure($client,"unknown_cmd\n",$userinput);
                   6351:     }
                   6352:     return 1;
1.317     raeburn  6353: }
                   6354: &register_handler("autophotochoice",\&photo_choice_handler,0,1,0);
                   6355: 
1.265     albertel 6356: #
                   6357: # Gets a student's photo to exist (in the correct image type) in the user's 
                   6358: # directory.
                   6359: # Formal Parameters:
                   6360: #    $cmd     - The command request that got us dispatched.
                   6361: #    $tail    - A colon separated set of words that will be split into:
                   6362: #               $domain - student's domain
                   6363: #               $uname  - student username
                   6364: #               $type   - image type desired
                   6365: #    $client  - The socket open on the client.
                   6366: # Returns:
                   6367: #    1 - continue processing.
1.317     raeburn  6368: 
1.265     albertel 6369: sub student_photo_handler {
                   6370:     my ($cmd, $tail, $client) = @_;
1.317     raeburn  6371:     my ($domain,$uname,$ext,$type) = split(/:/, $tail);
1.265     albertel 6372: 
1.317     raeburn  6373:     my $path=&propath($domain,$uname). '/userfiles/internal/';
                   6374:     my $filename = 'studentphoto.'.$ext;
                   6375:     if ($type eq 'thumbnail') {
                   6376:         $filename = 'studentphoto_tn.'.$ext;
                   6377:     }
                   6378:     if (-e $path.$filename) {
1.265     albertel 6379: 	&Reply($client,"ok\n","$cmd:$tail");
                   6380: 	return 1;
                   6381:     }
                   6382:     &mkpath($path);
1.317     raeburn  6383:     my $file;
                   6384:     if ($type eq 'thumbnail') {
1.320     albertel 6385: 	eval {
                   6386: 	    local($SIG{__DIE__})='DEFAULT';
                   6387: 	    $file=&localstudentphoto::fetch_thumbnail($domain,$uname);
                   6388: 	};
1.317     raeburn  6389:     } else {
                   6390:         $file=&localstudentphoto::fetch($domain,$uname);
                   6391:     }
1.265     albertel 6392:     if (!$file) {
                   6393: 	&Failure($client,"unavailable\n","$cmd:$tail");
                   6394: 	return 1;
                   6395:     }
1.317     raeburn  6396:     if (!-e $path.$filename) { &convert_photo($file,$path.$filename); }
                   6397:     if (-e $path.$filename) {
1.265     albertel 6398: 	&Reply($client,"ok\n","$cmd:$tail");
                   6399: 	return 1;
                   6400:     }
                   6401:     &Failure($client,"unable_to_convert\n","$cmd:$tail");
                   6402:     return 1;
                   6403: }
                   6404: &register_handler("studentphoto", \&student_photo_handler, 0, 1, 0);
1.246     foxr     6405: 
1.361     raeburn  6406: sub inst_usertypes_handler {
                   6407:     my ($cmd, $domain, $client) = @_;
                   6408:     my $res;
                   6409:     my $userinput = $cmd.":".$domain; # For logging purposes.
1.370     albertel 6410:     my (%typeshash,@order,$result);
                   6411:     eval {
                   6412: 	local($SIG{__DIE__})='DEFAULT';
                   6413: 	$result=&localenroll::inst_usertypes($domain,\%typeshash,\@order);
                   6414:     };
                   6415:     if ($result eq 'ok') {
1.361     raeburn  6416:         if (keys(%typeshash) > 0) {
                   6417:             foreach my $key (keys(%typeshash)) {
                   6418:                 $res.=&escape($key).'='.&escape($typeshash{$key}).'&';
                   6419:             }
                   6420:         }
                   6421:         $res=~s/\&$//;
                   6422:         $res .= ':';
                   6423:         if (@order > 0) {
                   6424:             foreach my $item (@order) {
                   6425:                 $res .= &escape($item).'&';
                   6426:             }
                   6427:         }
                   6428:         $res=~s/\&$//;
                   6429:     }
1.387     albertel 6430:     &Reply($client, \$res, $userinput);
1.361     raeburn  6431:     return 1;
                   6432: }
                   6433: &register_handler("inst_usertypes", \&inst_usertypes_handler, 0, 1, 0);
                   6434: 
1.264     albertel 6435: # mkpath makes all directories for a file, expects an absolute path with a
                   6436: # file or a trailing / if just a dir is passed
                   6437: # returns 1 on success 0 on failure
                   6438: sub mkpath {
                   6439:     my ($file)=@_;
                   6440:     my @parts=split(/\//,$file,-1);
                   6441:     my $now=$parts[0].'/'.$parts[1].'/'.$parts[2];
                   6442:     for (my $i=3;$i<= ($#parts-1);$i++) {
1.265     albertel 6443: 	$now.='/'.$parts[$i]; 
1.264     albertel 6444: 	if (!-e $now) {
                   6445: 	    if  (!mkdir($now,0770)) { return 0; }
                   6446: 	}
                   6447:     }
                   6448:     return 1;
                   6449: }
                   6450: 
1.207     foxr     6451: #---------------------------------------------------------------
                   6452: #
                   6453: #   Getting, decoding and dispatching requests:
                   6454: #
                   6455: #
                   6456: #   Get a Request:
                   6457: #   Gets a Request message from the client.  The transaction
                   6458: #   is defined as a 'line' of text.  We remove the new line
                   6459: #   from the text line.  
1.226     foxr     6460: #
1.211     albertel 6461: sub get_request {
1.207     foxr     6462:     my $input = <$client>;
                   6463:     chomp($input);
1.226     foxr     6464: 
1.234     foxr     6465:     &Debug("get_request: Request = $input\n");
1.207     foxr     6466: 
                   6467:     &status('Processing '.$clientname.':'.$input);
                   6468: 
                   6469:     return $input;
                   6470: }
1.212     foxr     6471: #---------------------------------------------------------------
                   6472: #
                   6473: #  Process a request.  This sub should shrink as each action
                   6474: #  gets farmed out into a separat sub that is registered 
                   6475: #  with the dispatch hash.  
                   6476: #
                   6477: # Parameters:
                   6478: #    user_input   - The request received from the client (lonc).
                   6479: # Returns:
                   6480: #    true to keep processing, false if caller should exit.
                   6481: #
                   6482: sub process_request {
                   6483:     my ($userinput) = @_;      # Easier for now to break style than to
                   6484:                                 # fix all the userinput -> user_input.
                   6485:     my $wasenc    = 0;		# True if request was encrypted.
                   6486: # ------------------------------------------------------------ See if encrypted
1.322     albertel 6487:     # for command
                   6488:     # sethost:<server>
                   6489:     # <command>:<args>
                   6490:     #   we just send it to the processor
                   6491:     # for
                   6492:     # sethost:<server>:<command>:<args>
                   6493:     #  we do the implict set host and then do the command
                   6494:     if ($userinput =~ /^sethost:/) {
                   6495: 	(my $cmd,my $newid,$userinput) = split(':',$userinput,3);
                   6496: 	if (defined($userinput)) {
                   6497: 	    &sethost("$cmd:$newid");
                   6498: 	} else {
                   6499: 	    $userinput = "$cmd:$newid";
                   6500: 	}
                   6501:     }
                   6502: 
1.212     foxr     6503:     if ($userinput =~ /^enc/) {
                   6504: 	$userinput = decipher($userinput);
                   6505: 	$wasenc=1;
                   6506: 	if(!$userinput) {	# Cipher not defined.
1.251     foxr     6507: 	    &Failure($client, "error: Encrypted data without negotated key\n");
1.212     foxr     6508: 	    return 0;
                   6509: 	}
                   6510:     }
                   6511:     Debug("process_request: $userinput\n");
                   6512:     
1.213     foxr     6513:     #  
                   6514:     #   The 'correct way' to add a command to lond is now to
                   6515:     #   write a sub to execute it and Add it to the command dispatch
                   6516:     #   hash via a call to register_handler..  The comments to that
                   6517:     #   sub should give you enough to go on to show how to do this
                   6518:     #   along with the examples that are building up as this code
                   6519:     #   is getting refactored.   Until all branches of the
                   6520:     #   if/elseif monster below have been factored out into
                   6521:     #   separate procesor subs, if the dispatch hash is missing
                   6522:     #   the command keyword, we will fall through to the remainder
                   6523:     #   of the if/else chain below in order to keep this thing in 
                   6524:     #   working order throughout the transmogrification.
                   6525: 
                   6526:     my ($command, $tail) = split(/:/, $userinput, 2);
                   6527:     chomp($command);
                   6528:     chomp($tail);
                   6529:     $tail =~ s/(\r)//;		# This helps people debugging with e.g. telnet.
1.214     foxr     6530:     $command =~ s/(\r)//;	# And this too for parameterless commands.
                   6531:     if(!$tail) {
                   6532: 	$tail ="";		# defined but blank.
                   6533:     }
1.213     foxr     6534: 
                   6535:     &Debug("Command received: $command, encoded = $wasenc");
                   6536: 
                   6537:     if(defined $Dispatcher{$command}) {
                   6538: 
                   6539: 	my $dispatch_info = $Dispatcher{$command};
                   6540: 	my $handler       = $$dispatch_info[0];
                   6541: 	my $need_encode   = $$dispatch_info[1];
                   6542: 	my $client_types  = $$dispatch_info[2];
                   6543: 	Debug("Matched dispatch hash: mustencode: $need_encode "
                   6544: 	      ."ClientType $client_types");
                   6545:       
                   6546: 	#  Validate the request:
                   6547:       
                   6548: 	my $ok = 1;
                   6549: 	my $requesterprivs = 0;
                   6550: 	if(&isClient()) {
                   6551: 	    $requesterprivs |= $CLIENT_OK;
                   6552: 	}
                   6553: 	if(&isManager()) {
                   6554: 	    $requesterprivs |= $MANAGER_OK;
                   6555: 	}
                   6556: 	if($need_encode && (!$wasenc)) {
                   6557: 	    Debug("Must encode but wasn't: $need_encode $wasenc");
                   6558: 	    $ok = 0;
                   6559: 	}
                   6560: 	if(($client_types & $requesterprivs) == 0) {
                   6561: 	    Debug("Client not privileged to do this operation");
                   6562: 	    $ok = 0;
                   6563: 	}
                   6564: 
                   6565: 	if($ok) {
                   6566: 	    Debug("Dispatching to handler $command $tail");
                   6567: 	    my $keep_going = &$handler($command, $tail, $client);
                   6568: 	    return $keep_going;
                   6569: 	} else {
                   6570: 	    Debug("Refusing to dispatch because client did not match requirements");
                   6571: 	    Failure($client, "refused\n", $userinput);
                   6572: 	    return 1;
                   6573: 	}
                   6574: 
                   6575:     }    
                   6576: 
1.262     foxr     6577:     print $client "unknown_cmd\n";
1.212     foxr     6578: # -------------------------------------------------------------------- complete
                   6579:     Debug("process_request - returning 1");
                   6580:     return 1;
                   6581: }
1.207     foxr     6582: #
                   6583: #   Decipher encoded traffic
                   6584: #  Parameters:
                   6585: #     input      - Encoded data.
                   6586: #  Returns:
                   6587: #     Decoded data or undef if encryption key was not yet negotiated.
                   6588: #  Implicit input:
                   6589: #     cipher  - This global holds the negotiated encryption key.
                   6590: #
1.211     albertel 6591: sub decipher {
1.207     foxr     6592:     my ($input)  = @_;
                   6593:     my $output = '';
1.212     foxr     6594:     
                   6595:     
1.207     foxr     6596:     if($cipher) {
                   6597: 	my($enc, $enclength, $encinput) = split(/:/, $input);
                   6598: 	for(my $encidx = 0; $encidx < length($encinput); $encidx += 16) {
                   6599: 	    $output .= 
                   6600: 		$cipher->decrypt(pack("H16", substr($encinput, $encidx, 16)));
                   6601: 	}
                   6602: 	return substr($output, 0, $enclength);
                   6603:     } else {
                   6604: 	return undef;
                   6605:     }
                   6606: }
                   6607: 
                   6608: #
                   6609: #   Register a command processor.  This function is invoked to register a sub
                   6610: #   to process a request.  Once registered, the ProcessRequest sub can automatically
                   6611: #   dispatch requests to an appropriate sub, and do the top level validity checking
                   6612: #   as well:
                   6613: #    - Is the keyword recognized.
                   6614: #    - Is the proper client type attempting the request.
                   6615: #    - Is the request encrypted if it has to be.
                   6616: #   Parameters:
                   6617: #    $request_name         - Name of the request being registered.
                   6618: #                           This is the command request that will match
                   6619: #                           against the hash keywords to lookup the information
                   6620: #                           associated with the dispatch information.
                   6621: #    $procedure           - Reference to a sub to call to process the request.
                   6622: #                           All subs get called as follows:
                   6623: #                             Procedure($cmd, $tail, $replyfd, $key)
                   6624: #                             $cmd    - the actual keyword that invoked us.
                   6625: #                             $tail   - the tail of the request that invoked us.
                   6626: #                             $replyfd- File descriptor connected to the client
                   6627: #    $must_encode          - True if the request must be encoded to be good.
                   6628: #    $client_ok            - True if it's ok for a client to request this.
                   6629: #    $manager_ok           - True if it's ok for a manager to request this.
                   6630: # Side effects:
                   6631: #      - On success, the Dispatcher hash has an entry added for the key $RequestName
                   6632: #      - On failure, the program will die as it's a bad internal bug to try to 
                   6633: #        register a duplicate command handler.
                   6634: #
1.211     albertel 6635: sub register_handler {
1.212     foxr     6636:     my ($request_name,$procedure,$must_encode,	$client_ok,$manager_ok)   = @_;
1.207     foxr     6637: 
                   6638:     #  Don't allow duplication#
                   6639:    
                   6640:     if (defined $Dispatcher{$request_name}) {
                   6641: 	die "Attempting to define a duplicate request handler for $request_name\n";
                   6642:     }
                   6643:     #   Build the client type mask:
                   6644:     
                   6645:     my $client_type_mask = 0;
                   6646:     if($client_ok) {
                   6647: 	$client_type_mask  |= $CLIENT_OK;
                   6648:     }
                   6649:     if($manager_ok) {
                   6650: 	$client_type_mask  |= $MANAGER_OK;
                   6651:     }
                   6652:    
                   6653:     #  Enter the hash:
                   6654:       
                   6655:     my @entry = ($procedure, $must_encode, $client_type_mask);
                   6656:    
                   6657:     $Dispatcher{$request_name} = \@entry;
                   6658:    
                   6659: }
                   6660: 
                   6661: 
                   6662: #------------------------------------------------------------------
                   6663: 
                   6664: 
                   6665: 
                   6666: 
1.141     foxr     6667: #
1.96      foxr     6668: #  Convert an error return code from lcpasswd to a string value.
                   6669: #
                   6670: sub lcpasswdstrerror {
                   6671:     my $ErrorCode = shift;
1.97      foxr     6672:     if(($ErrorCode < 0) || ($ErrorCode > $lastpwderror)) {
1.96      foxr     6673: 	return "lcpasswd Unrecognized error return value ".$ErrorCode;
                   6674:     } else {
1.98      foxr     6675: 	return $passwderrors[$ErrorCode];
1.96      foxr     6676:     }
                   6677: }
                   6678: 
1.23      harris41 6679: # grabs exception and records it to log before exiting
                   6680: sub catchexception {
1.27      albertel 6681:     my ($error)=@_;
1.25      www      6682:     $SIG{'QUIT'}='DEFAULT';
                   6683:     $SIG{__DIE__}='DEFAULT';
1.165     albertel 6684:     &status("Catching exception");
1.190     albertel 6685:     &logthis("<font color='red'>CRITICAL: "
1.373     albertel 6686:      ."ABNORMAL EXIT. Child $$ for server ".$perlvar{'lonHostID'}." died through "
1.27      albertel 6687:      ."a crash with this error msg->[$error]</font>");
1.57      www      6688:     &logthis('Famous last words: '.$status.' - '.$lastlog);
1.27      albertel 6689:     if ($client) { print $client "error: $error\n"; }
1.59      www      6690:     $server->close();
1.27      albertel 6691:     die($error);
1.23      harris41 6692: }
1.63      www      6693: sub timeout {
1.165     albertel 6694:     &status("Handling Timeout");
1.190     albertel 6695:     &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>");
1.63      www      6696:     &catchexception('Timeout');
                   6697: }
1.22      harris41 6698: # -------------------------------- Set signal handlers to record abnormal exits
                   6699: 
1.226     foxr     6700: 
1.22      harris41 6701: $SIG{'QUIT'}=\&catchexception;
                   6702: $SIG{__DIE__}=\&catchexception;
                   6703: 
1.81      matthew  6704: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
1.95      harris41 6705: &status("Read loncapa.conf and loncapa_apache.conf");
                   6706: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
1.141     foxr     6707: %perlvar=%{$perlvarref};
1.80      harris41 6708: undef $perlvarref;
1.19      www      6709: 
1.35      harris41 6710: # ----------------------------- Make sure this process is running from user=www
                   6711: my $wwwid=getpwnam('www');
                   6712: if ($wwwid!=$<) {
1.134     albertel 6713:    my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                   6714:    my $subj="LON: $currenthostid User ID mismatch";
1.489.2.31  raeburn  6715:    system("echo 'User ID mismatch.  lond must be run as user www.' |".
                   6716:           " mail -s '$subj' $emailto > /dev/null");
1.35      harris41 6717:    exit 1;
                   6718: }
                   6719: 
1.19      www      6720: # --------------------------------------------- Check if other instance running
                   6721: 
                   6722: my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid";
                   6723: 
                   6724: if (-e $pidfile) {
                   6725:    my $lfh=IO::File->new("$pidfile");
                   6726:    my $pide=<$lfh>;
                   6727:    chomp($pide);
1.29      harris41 6728:    if (kill 0 => $pide) { die "already running"; }
1.19      www      6729: }
1.1       albertel 6730: 
                   6731: # ------------------------------------------------------------- Read hosts file
                   6732: 
                   6733: 
                   6734: 
                   6735: # establish SERVER socket, bind and listen.
                   6736: $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'},
                   6737:                                 Type      => SOCK_STREAM,
                   6738:                                 Proto     => 'tcp',
1.469     foxr     6739:                                 ReuseAddr     => 1,
1.1       albertel 6740:                                 Listen    => 10 )
1.29      harris41 6741:   or die "making socket: $@\n";
1.1       albertel 6742: 
                   6743: # --------------------------------------------------------- Do global variables
                   6744: 
                   6745: # global variables
                   6746: 
1.134     albertel 6747: my %children               = ();       # keys are current child process IDs
1.1       albertel 6748: 
                   6749: sub REAPER {                        # takes care of dead children
                   6750:     $SIG{CHLD} = \&REAPER;
1.165     albertel 6751:     &status("Handling child death");
1.178     foxr     6752:     my $pid;
                   6753:     do {
                   6754: 	$pid = waitpid(-1,&WNOHANG());
                   6755: 	if (defined($children{$pid})) {
                   6756: 	    &logthis("Child $pid died");
                   6757: 	    delete($children{$pid});
1.183     albertel 6758: 	} elsif ($pid > 0) {
1.178     foxr     6759: 	    &logthis("Unknown Child $pid died");
                   6760: 	}
                   6761:     } while ( $pid > 0 );
                   6762:     foreach my $child (keys(%children)) {
                   6763: 	$pid = waitpid($child,&WNOHANG());
                   6764: 	if ($pid > 0) {
                   6765: 	    &logthis("Child $child - $pid looks like we missed it's death");
                   6766: 	    delete($children{$pid});
                   6767: 	}
1.176     albertel 6768:     }
1.165     albertel 6769:     &status("Finished Handling child death");
1.1       albertel 6770: }
                   6771: 
                   6772: sub HUNTSMAN {                      # signal handler for SIGINT
1.165     albertel 6773:     &status("Killing children (INT)");
1.1       albertel 6774:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
                   6775:     kill 'INT' => keys %children;
1.59      www      6776:     &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.1       albertel 6777:     my $execdir=$perlvar{'lonDaemons'};
                   6778:     unlink("$execdir/logs/lond.pid");
1.190     albertel 6779:     &logthis("<font color='red'>CRITICAL: Shutting down</font>");
1.165     albertel 6780:     &status("Done killing children");
1.1       albertel 6781:     exit;                           # clean up with dignity
                   6782: }
                   6783: 
                   6784: sub HUPSMAN {                      # signal handler for SIGHUP
                   6785:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
1.165     albertel 6786:     &status("Killing children for restart (HUP)");
1.1       albertel 6787:     kill 'INT' => keys %children;
1.59      www      6788:     &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.190     albertel 6789:     &logthis("<font color='red'>CRITICAL: Restarting</font>");
1.134     albertel 6790:     my $execdir=$perlvar{'lonDaemons'};
1.30      harris41 6791:     unlink("$execdir/logs/lond.pid");
1.165     albertel 6792:     &status("Restarting self (HUP)");
1.1       albertel 6793:     exec("$execdir/lond");         # here we go again
                   6794: }
                   6795: 
1.144     foxr     6796: #
1.148     foxr     6797: #  Reload the Apache daemon's state.
1.150     foxr     6798: #  This is done by invoking /home/httpd/perl/apachereload
                   6799: #  a setuid perl script that can be root for us to do this job.
1.148     foxr     6800: #
                   6801: sub ReloadApache {
1.473     raeburn  6802: # --------------------------- Handle case of another apachereload process (locking)
1.474     raeburn  6803:     if (&LONCAPA::try_to_lock('/tmp/lock_apachereload')) {
                   6804:         my $execdir = $perlvar{'lonDaemons'};
                   6805:         my $script  = $execdir."/apachereload";
                   6806:         system($script);
                   6807:         unlink('/tmp/lock_apachereload'); #  Remove the lock file.
                   6808:     }
1.148     foxr     6809: }
                   6810: 
                   6811: #
1.144     foxr     6812: #   Called in response to a USR2 signal.
                   6813: #   - Reread hosts.tab
                   6814: #   - All children connected to hosts that were removed from hosts.tab
                   6815: #     are killed via SIGINT
                   6816: #   - All children connected to previously existing hosts are sent SIGUSR1
                   6817: #   - Our internal hosts hash is updated to reflect the new contents of
                   6818: #     hosts.tab causing connections from hosts added to hosts.tab to
                   6819: #     now be honored.
                   6820: #
                   6821: sub UpdateHosts {
1.165     albertel 6822:     &status("Reload hosts.tab");
1.147     foxr     6823:     logthis('<font color="blue"> Updating connections </font>');
1.148     foxr     6824:     #
                   6825:     #  The %children hash has the set of IP's we currently have children
                   6826:     #  on.  These need to be matched against records in the hosts.tab
                   6827:     #  Any ip's no longer in the table get killed off they correspond to
                   6828:     #  either dropped or changed hosts.  Note that the re-read of the table
                   6829:     #  will take care of new and changed hosts as connections come into being.
                   6830: 
1.371     albertel 6831:     &Apache::lonnet::reset_hosts_info();
1.148     foxr     6832: 
1.368     albertel 6833:     foreach my $child (keys(%children)) {
1.148     foxr     6834: 	my $childip = $children{$child};
1.374     albertel 6835: 	if ($childip ne '127.0.0.1'
                   6836: 	    && !defined(&Apache::lonnet::get_hosts_from_ip($childip))) {
1.149     foxr     6837: 	    logthis('<font color="blue"> UpdateHosts killing child '
                   6838: 		    ." $child for ip $childip </font>");
1.148     foxr     6839: 	    kill('INT', $child);
1.149     foxr     6840: 	} else {
                   6841: 	    logthis('<font color="green"> keeping child for ip '
                   6842: 		    ." $childip (pid=$child) </font>");
1.148     foxr     6843: 	}
                   6844:     }
                   6845:     ReloadApache;
1.165     albertel 6846:     &status("Finished reloading hosts.tab");
1.144     foxr     6847: }
                   6848: 
1.148     foxr     6849: 
1.57      www      6850: sub checkchildren {
1.165     albertel 6851:     &status("Checking on the children (sending signals)");
1.57      www      6852:     &initnewstatus();
                   6853:     &logstatus();
                   6854:     &logthis('Going to check on the children');
1.134     albertel 6855:     my $docdir=$perlvar{'lonDocRoot'};
1.61      harris41 6856:     foreach (sort keys %children) {
1.221     albertel 6857: 	#sleep 1;
1.57      www      6858:         unless (kill 'USR1' => $_) {
                   6859: 	    &logthis ('Child '.$_.' is dead');
                   6860:             &logstatus($$.' is dead');
1.221     albertel 6861: 	    delete($children{$_});
1.57      www      6862:         } 
1.61      harris41 6863:     }
1.63      www      6864:     sleep 5;
1.212     foxr     6865:     $SIG{ALRM} = sub { Debug("timeout"); 
                   6866: 		       die "timeout";  };
1.113     albertel 6867:     $SIG{__DIE__} = 'DEFAULT';
1.165     albertel 6868:     &status("Checking on the children (waiting for reports)");
1.63      www      6869:     foreach (sort keys %children) {
                   6870:         unless (-e "$docdir/lon-status/londchld/$_.txt") {
1.113     albertel 6871:           eval {
                   6872:             alarm(300);
1.63      www      6873: 	    &logthis('Child '.$_.' did not respond');
1.67      albertel 6874: 	    kill 9 => $_;
1.131     albertel 6875: 	    #$emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                   6876: 	    #$subj="LON: $currenthostid killed lond process $_";
                   6877: 	    #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;
                   6878: 	    #$execdir=$perlvar{'lonDaemons'};
                   6879: 	    #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;
1.221     albertel 6880: 	    delete($children{$_});
1.113     albertel 6881: 	    alarm(0);
                   6882: 	  }
1.63      www      6883:         }
                   6884:     }
1.113     albertel 6885:     $SIG{ALRM} = 'DEFAULT';
1.155     albertel 6886:     $SIG{__DIE__} = \&catchexception;
1.165     albertel 6887:     &status("Finished checking children");
1.221     albertel 6888:     &logthis('Finished Checking children');
1.57      www      6889: }
                   6890: 
1.1       albertel 6891: # --------------------------------------------------------------------- Logging
                   6892: 
                   6893: sub logthis {
                   6894:     my $message=shift;
                   6895:     my $execdir=$perlvar{'lonDaemons'};
                   6896:     my $fh=IO::File->new(">>$execdir/logs/lond.log");
                   6897:     my $now=time;
                   6898:     my $local=localtime($now);
1.58      www      6899:     $lastlog=$local.': '.$message;
1.1       albertel 6900:     print $fh "$local ($$): $message\n";
                   6901: }
                   6902: 
1.77      foxr     6903: # ------------------------- Conditional log if $DEBUG true.
                   6904: sub Debug {
                   6905:     my $message = shift;
                   6906:     if($DEBUG) {
                   6907: 	&logthis($message);
                   6908:     }
                   6909: }
1.161     foxr     6910: 
                   6911: #
                   6912: #   Sub to do replies to client.. this gives a hook for some
                   6913: #   debug tracing too:
                   6914: #  Parameters:
                   6915: #     fd      - File open on client.
                   6916: #     reply   - Text to send to client.
                   6917: #     request - Original request from client.
                   6918: #
                   6919: sub Reply {
1.192     foxr     6920:     my ($fd, $reply, $request) = @_;
1.387     albertel 6921:     if (ref($reply)) {
                   6922: 	print $fd $$reply;
                   6923: 	print $fd "\n";
                   6924: 	if ($DEBUG) { Debug("Request was $request  Reply was $$reply"); }
                   6925:     } else {
                   6926: 	print $fd $reply;
                   6927: 	if ($DEBUG) { Debug("Request was $request  Reply was $reply"); }
                   6928:     }
1.212     foxr     6929:     $Transactions++;
                   6930: }
                   6931: 
                   6932: 
                   6933: #
                   6934: #    Sub to report a failure.
                   6935: #    This function:
                   6936: #     -   Increments the failure statistic counters.
                   6937: #     -   Invokes Reply to send the error message to the client.
                   6938: # Parameters:
                   6939: #    fd       - File descriptor open on the client
                   6940: #    reply    - Reply text to emit.
                   6941: #    request  - The original request message (used by Reply
                   6942: #               to debug if that's enabled.
                   6943: # Implicit outputs:
                   6944: #    $Failures- The number of failures is incremented.
                   6945: #    Reply (invoked here) sends a message to the 
                   6946: #    client:
                   6947: #
                   6948: sub Failure {
                   6949:     my $fd      = shift;
                   6950:     my $reply   = shift;
                   6951:     my $request = shift;
                   6952:    
                   6953:     $Failures++;
                   6954:     Reply($fd, $reply, $request);      # That's simple eh?
1.161     foxr     6955: }
1.57      www      6956: # ------------------------------------------------------------------ Log status
                   6957: 
                   6958: sub logstatus {
1.178     foxr     6959:     &status("Doing logging");
                   6960:     my $docdir=$perlvar{'lonDocRoot'};
                   6961:     {
                   6962: 	my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");
1.200     matthew  6963:         print $fh $status."\n".$lastlog."\n".time."\n$keymode";
1.178     foxr     6964:         $fh->close();
                   6965:     }
1.221     albertel 6966:     &status("Finished $$.txt");
                   6967:     {
                   6968: 	open(LOG,">>$docdir/lon-status/londstatus.txt");
                   6969: 	flock(LOG,LOCK_EX);
                   6970: 	print LOG $$."\t".$clientname."\t".$currenthostid."\t"
                   6971: 	    .$status."\t".$lastlog."\t $keymode\n";
1.275     albertel 6972: 	flock(LOG,LOCK_UN);
1.221     albertel 6973: 	close(LOG);
                   6974:     }
1.178     foxr     6975:     &status("Finished logging");
1.57      www      6976: }
                   6977: 
                   6978: sub initnewstatus {
                   6979:     my $docdir=$perlvar{'lonDocRoot'};
                   6980:     my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt");
1.460     foxr     6981:     my $now=time();
1.57      www      6982:     my $local=localtime($now);
                   6983:     print $fh "LOND status $local - parent $$\n\n";
1.64      www      6984:     opendir(DIR,"$docdir/lon-status/londchld");
1.134     albertel 6985:     while (my $filename=readdir(DIR)) {
1.64      www      6986:         unlink("$docdir/lon-status/londchld/$filename");
                   6987:     }
                   6988:     closedir(DIR);
1.57      www      6989: }
                   6990: 
                   6991: # -------------------------------------------------------------- Status setting
                   6992: 
                   6993: sub status {
                   6994:     my $what=shift;
                   6995:     my $now=time;
                   6996:     my $local=localtime($now);
1.178     foxr     6997:     $status=$local.': '.$what;
                   6998:     $0='lond: '.$what.' '.$local;
1.57      www      6999: }
1.11      www      7000: 
1.13      www      7001: # -------------------------------------------------------------- Talk to lonsql
                   7002: 
1.234     foxr     7003: sub sql_reply {
1.12      harris41 7004:     my ($cmd)=@_;
1.234     foxr     7005:     my $answer=&sub_sql_reply($cmd);
                   7006:     if ($answer eq 'con_lost') { $answer=&sub_sql_reply($cmd); }
1.12      harris41 7007:     return $answer;
                   7008: }
                   7009: 
1.234     foxr     7010: sub sub_sql_reply {
1.12      harris41 7011:     my ($cmd)=@_;
                   7012:     my $unixsock="mysqlsock";
                   7013:     my $peerfile="$perlvar{'lonSockDir'}/$unixsock";
                   7014:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
                   7015:                                       Type    => SOCK_STREAM,
                   7016:                                       Timeout => 10)
                   7017:        or return "con_lost";
1.319     www      7018:     print $sclient "$cmd:$currentdomainid\n";
1.12      harris41 7019:     my $answer=<$sclient>;
                   7020:     chomp($answer);
                   7021:     if (!$answer) { $answer="con_lost"; }
                   7022:     return $answer;
                   7023: }
                   7024: 
1.1       albertel 7025: # --------------------------------------- Is this the home server of an author?
1.11      www      7026: 
1.1       albertel 7027: sub ishome {
                   7028:     my $author=shift;
                   7029:     $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
                   7030:     my ($udom,$uname)=split(/\//,$author);
                   7031:     my $proname=propath($udom,$uname);
                   7032:     if (-e $proname) {
                   7033: 	return 'owner';
                   7034:     } else {
                   7035:         return 'not_owner';
                   7036:     }
                   7037: }
                   7038: 
                   7039: # ======================================================= Continue main program
                   7040: # ---------------------------------------------------- Fork once and dissociate
                   7041: 
1.134     albertel 7042: my $fpid=fork;
1.1       albertel 7043: exit if $fpid;
1.29      harris41 7044: die "Couldn't fork: $!" unless defined ($fpid);
1.1       albertel 7045: 
1.29      harris41 7046: POSIX::setsid() or die "Can't start new session: $!";
1.1       albertel 7047: 
                   7048: # ------------------------------------------------------- Write our PID on disk
                   7049: 
1.134     albertel 7050: my $execdir=$perlvar{'lonDaemons'};
1.1       albertel 7051: open (PIDSAVE,">$execdir/logs/lond.pid");
                   7052: print PIDSAVE "$$\n";
                   7053: close(PIDSAVE);
1.190     albertel 7054: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
1.57      www      7055: &status('Starting');
1.1       albertel 7056: 
1.106     foxr     7057: 
1.1       albertel 7058: 
                   7059: # ----------------------------------------------------- Install signal handlers
                   7060: 
1.57      www      7061: 
1.1       albertel 7062: $SIG{CHLD} = \&REAPER;
                   7063: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
                   7064: $SIG{HUP}  = \&HUPSMAN;
1.57      www      7065: $SIG{USR1} = \&checkchildren;
1.144     foxr     7066: $SIG{USR2} = \&UpdateHosts;
1.106     foxr     7067: 
1.148     foxr     7068: #  Read the host hashes:
1.368     albertel 7069: &Apache::lonnet::load_hosts_tab();
1.447     raeburn  7070: my %iphost = &Apache::lonnet::get_iphost(1);
1.106     foxr     7071: 
1.480     raeburn  7072: $dist=`$perlvar{'lonDaemons'}/distprobe`;
1.286     albertel 7073: 
1.471     raeburn  7074: my $arch = `uname -i`;
1.475     raeburn  7075: chomp($arch);
1.471     raeburn  7076: if ($arch eq 'unknown') {
                   7077:     $arch = `uname -m`;
1.475     raeburn  7078:     chomp($arch);
1.471     raeburn  7079: }
                   7080: 
1.106     foxr     7081: # --------------------------------------------------------------
                   7082: #   Accept connections.  When a connection comes in, it is validated
                   7083: #   and if good, a child process is created to process transactions
                   7084: #   along the connection.
                   7085: 
1.1       albertel 7086: while (1) {
1.165     albertel 7087:     &status('Starting accept');
1.106     foxr     7088:     $client = $server->accept() or next;
1.165     albertel 7089:     &status('Accepted '.$client.' off to spawn');
1.386     albertel 7090:     make_new_child($client);
1.165     albertel 7091:     &status('Finished spawning');
1.1       albertel 7092: }
                   7093: 
1.212     foxr     7094: sub make_new_child {
                   7095:     my $pid;
                   7096: #    my $cipher;     # Now global
                   7097:     my $sigset;
1.178     foxr     7098: 
1.212     foxr     7099:     $client = shift;
                   7100:     &status('Starting new child '.$client);
                   7101:     &logthis('<font color="green"> Attempting to start child ('.$client.
                   7102: 	     ")</font>");    
                   7103:     # block signal for fork
                   7104:     $sigset = POSIX::SigSet->new(SIGINT);
                   7105:     sigprocmask(SIG_BLOCK, $sigset)
                   7106:         or die "Can't block SIGINT for fork: $!\n";
1.178     foxr     7107: 
1.212     foxr     7108:     die "fork: $!" unless defined ($pid = fork);
1.178     foxr     7109: 
1.212     foxr     7110:     $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of
                   7111: 	                               # connection liveness.
1.178     foxr     7112: 
1.212     foxr     7113:     #
                   7114:     #  Figure out who we're talking to so we can record the peer in 
                   7115:     #  the pid hash.
                   7116:     #
                   7117:     my $caller = getpeername($client);
                   7118:     my ($port,$iaddr);
                   7119:     if (defined($caller) && length($caller) > 0) {
                   7120: 	($port,$iaddr)=unpack_sockaddr_in($caller);
                   7121:     } else {
                   7122: 	&logthis("Unable to determine who caller was, getpeername returned nothing");
                   7123:     }
                   7124:     if (defined($iaddr)) {
                   7125: 	$clientip  = inet_ntoa($iaddr);
                   7126: 	Debug("Connected with $clientip");
                   7127:     } else {
                   7128: 	&logthis("Unable to determine clientip");
                   7129: 	$clientip='Unavailable';
                   7130:     }
                   7131:     
                   7132:     if ($pid) {
                   7133:         # Parent records the child's birth and returns.
                   7134:         sigprocmask(SIG_UNBLOCK, $sigset)
                   7135:             or die "Can't unblock SIGINT for fork: $!\n";
                   7136:         $children{$pid} = $clientip;
                   7137:         &status('Started child '.$pid);
1.462     foxr     7138: 	close($client);
1.212     foxr     7139:         return;
                   7140:     } else {
                   7141:         # Child can *not* return from this subroutine.
                   7142:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
                   7143:         $SIG{CHLD} = 'DEFAULT'; #make this default so that pwauth returns 
                   7144:                                 #don't get intercepted
                   7145:         $SIG{USR1}= \&logstatus;
                   7146:         $SIG{ALRM}= \&timeout;
1.468     foxr     7147: 	#
                   7148: 	# Block sigpipe as it gets thrownon socket disconnect and we want to 
                   7149: 	# deal with that as a read faiure instead.
                   7150: 	#
                   7151: 	my $blockset = POSIX::SigSet->new(SIGPIPE);
                   7152: 	sigprocmask(SIG_BLOCK, $blockset);
                   7153: 
1.212     foxr     7154:         $lastlog='Forked ';
                   7155:         $status='Forked';
1.178     foxr     7156: 
1.212     foxr     7157:         # unblock signals
                   7158:         sigprocmask(SIG_UNBLOCK, $sigset)
                   7159:             or die "Can't unblock SIGINT for fork: $!\n";
1.178     foxr     7160: 
1.212     foxr     7161: #        my $tmpsnum=0;            # Now global
                   7162: #---------------------------------------------------- kerberos 5 initialization
                   7163:         &Authen::Krb5::init_context();
1.489.2.16  raeburn  7164: 
                   7165:         my $no_ets;
1.489.2.31  raeburn  7166:         if ($dist =~ /^(?:centos|rhes|scientific|oracle)(\d+)$/) {
1.489.2.16  raeburn  7167:             if ($1 >= 7) {
                   7168:                 $no_ets = 1;
                   7169:             }
                   7170:         } elsif ($dist =~ /^suse(\d+\.\d+)$/) {
                   7171:             if (($1 eq '9.3') || ($1 >= 12.2)) {
                   7172:                 $no_ets = 1;
                   7173:             }
1.489.2.17  raeburn  7174:         } elsif ($dist =~ /^sles(\d+)$/) {
                   7175:             if ($1 > 11) {
                   7176:                 $no_ets = 1;
                   7177:             }
1.489.2.16  raeburn  7178:         } elsif ($dist =~ /^fedora(\d+)$/) {
                   7179:             if ($1 < 7) {
                   7180:                 $no_ets = 1;
                   7181:             }
                   7182:         }
                   7183:         unless ($no_ets) {
                   7184:             &Authen::Krb5::init_ets();
                   7185:         }
1.209     albertel 7186: 
1.212     foxr     7187: 	&status('Accepted connection');
                   7188: # =============================================================================
                   7189:             # do something with the connection
                   7190: # -----------------------------------------------------------------------------
                   7191: 	# see if we know client and 'check' for spoof IP by ineffective challenge
1.178     foxr     7192: 
1.278     albertel 7193: 	my $outsideip=$clientip;
                   7194: 	if ($clientip eq '127.0.0.1') {
1.368     albertel 7195: 	    $outsideip=&Apache::lonnet::get_host_ip($perlvar{'lonHostID'});
1.278     albertel 7196: 	}
1.412     foxr     7197: 	&ReadManagerTable();
1.368     albertel 7198: 	my $clientrec=defined(&Apache::lonnet::get_hosts_from_ip($outsideip));
1.278     albertel 7199: 	my $ismanager=($managers{$outsideip}    ne undef);
1.432     raeburn  7200: 	$clientname  = "[unknown]";
1.212     foxr     7201: 	if($clientrec) {	# Establish client type.
                   7202: 	    $ConnectionType = "client";
1.368     albertel 7203: 	    $clientname = (&Apache::lonnet::get_hosts_from_ip($outsideip))[-1];
1.212     foxr     7204: 	    if($ismanager) {
                   7205: 		$ConnectionType = "both";
                   7206: 	    }
                   7207: 	} else {
                   7208: 	    $ConnectionType = "manager";
1.278     albertel 7209: 	    $clientname = $managers{$outsideip};
1.212     foxr     7210: 	}
                   7211: 	my $clientok;
1.178     foxr     7212: 
1.212     foxr     7213: 	if ($clientrec || $ismanager) {
                   7214: 	    &status("Waiting for init from $clientip $clientname");
                   7215: 	    &logthis('<font color="yellow">INFO: Connection, '.
                   7216: 		     $clientip.
                   7217: 		  " ($clientname) connection type = $ConnectionType </font>" );
                   7218: 	    &status("Connecting $clientip  ($clientname))"); 
                   7219: 	    my $remotereq=<$client>;
                   7220: 	    chomp($remotereq);
                   7221: 	    Debug("Got init: $remotereq");
1.337     albertel 7222: 
1.212     foxr     7223: 	    if ($remotereq =~ /^init/) {
                   7224: 		&sethost("sethost:$perlvar{'lonHostID'}");
                   7225: 		#
                   7226: 		#  If the remote is attempting a local init... give that a try:
                   7227: 		#
1.432     raeburn  7228: 		(my $i, my $inittype, $clientversion) = split(/:/, $remotereq);
1.489.2.4  raeburn  7229:                 # For LON-CAPA 2.9, the  client session will have sent its LON-CAPA
                   7230:                 # version when initiating the connection. For LON-CAPA 2.8 and older,
                   7231:                 # the version is retrieved from the global %loncaparevs in lonnet.pm.
                   7232:                 # $clientversion contains path to keyfile if $inittype eq 'local'
                   7233:                 # it's overridden below in this case
                   7234:                 $clientversion ||= $Apache::lonnet::loncaparevs{$clientname};
1.209     albertel 7235: 
1.212     foxr     7236: 		# If the connection type is ssl, but I didn't get my
                   7237: 		# certificate files yet, then I'll drop  back to 
                   7238: 		# insecure (if allowed).
                   7239: 		
                   7240: 		if($inittype eq "ssl") {
                   7241: 		    my ($ca, $cert) = lonssl::CertificateFile;
                   7242: 		    my $kfile       = lonssl::KeyFile;
                   7243: 		    if((!$ca)   || 
                   7244: 		       (!$cert) || 
                   7245: 		       (!$kfile)) {
                   7246: 			$inittype = ""; # This forces insecure attempt.
                   7247: 			&logthis("<font color=\"blue\"> Certificates not "
                   7248: 				 ."installed -- trying insecure auth</font>");
1.224     foxr     7249: 		    } else {	# SSL certificates are in place so
1.212     foxr     7250: 		    }		# Leave the inittype alone.
                   7251: 		}
                   7252: 
                   7253: 		if($inittype eq "local") {
1.432     raeburn  7254:                     $clientversion = $perlvar{'lonVersion'};
1.212     foxr     7255: 		    my $key = LocalConnection($client, $remotereq);
                   7256: 		    if($key) {
                   7257: 			Debug("Got local key $key");
                   7258: 			$clientok     = 1;
                   7259: 			my $cipherkey = pack("H32", $key);
                   7260: 			$cipher       = new IDEA($cipherkey);
                   7261: 			print $client "ok:local\n";
1.442     www      7262: 			&logthis('<font color="green">'
1.212     foxr     7263: 				 . "Successful local authentication </font>");
                   7264: 			$keymode = "local"
1.178     foxr     7265: 		    } else {
1.212     foxr     7266: 			Debug("Failed to get local key");
                   7267: 			$clientok = 0;
                   7268: 			shutdown($client, 3);
                   7269: 			close $client;
1.178     foxr     7270: 		    }
1.212     foxr     7271: 		} elsif ($inittype eq "ssl") {
                   7272: 		    my $key = SSLConnection($client);
                   7273: 		    if ($key) {
                   7274: 			$clientok = 1;
                   7275: 			my $cipherkey = pack("H32", $key);
                   7276: 			$cipher       = new IDEA($cipherkey);
                   7277: 			&logthis('<font color="green">'
                   7278: 				 ."Successfull ssl authentication with $clientname </font>");
                   7279: 			$keymode = "ssl";
                   7280: 	     
1.178     foxr     7281: 		    } else {
1.212     foxr     7282: 			$clientok = 0;
                   7283: 			close $client;
1.178     foxr     7284: 		    }
1.212     foxr     7285: 	   
                   7286: 		} else {
                   7287: 		    my $ok = InsecureConnection($client);
                   7288: 		    if($ok) {
                   7289: 			$clientok = 1;
                   7290: 			&logthis('<font color="green">'
                   7291: 				 ."Successful insecure authentication with $clientname </font>");
                   7292: 			print $client "ok\n";
                   7293: 			$keymode = "insecure";
1.178     foxr     7294: 		    } else {
1.212     foxr     7295: 			&logthis('<font color="yellow">'
                   7296: 				  ."Attempted insecure connection disallowed </font>");
                   7297: 			close $client;
                   7298: 			$clientok = 0;
1.178     foxr     7299: 		    }
                   7300: 		}
1.212     foxr     7301: 	    } else {
                   7302: 		&logthis(
                   7303: 			 "<font color='blue'>WARNING: "
                   7304: 			 ."$clientip failed to initialize: >$remotereq< </font>");
                   7305: 		&status('No init '.$clientip);
                   7306: 	    }
                   7307: 	} else {
                   7308: 	    &logthis(
                   7309: 		     "<font color='blue'>WARNING: Unknown client $clientip</font>");
                   7310: 	    &status('Hung up on '.$clientip);
                   7311: 	}
                   7312:  
                   7313: 	if ($clientok) {
                   7314: # ---------------- New known client connecting, could mean machine online again
1.368     albertel 7315: 	    if (&Apache::lonnet::get_host_ip($currenthostid) ne $clientip 
1.367     albertel 7316: 		&& $clientip ne '127.0.0.1') {
1.375     albertel 7317: 		&Apache::lonnet::reconlonc($clientname);
1.212     foxr     7318: 	    }
                   7319: 	    &logthis("<font color='green'>Established connection: $clientname</font>");
                   7320: 	    &status('Will listen to '.$clientname);
                   7321: # ------------------------------------------------------------ Process requests
                   7322: 	    my $keep_going = 1;
                   7323: 	    my $user_input;
1.448     raeburn  7324:             my $clienthost = &Apache::lonnet::hostname($clientname);
                   7325:             my $clientserverhomeID = &Apache::lonnet::get_server_homeID($clienthost);
                   7326:             $clienthomedom = &Apache::lonnet::host_domain($clientserverhomeID);
1.212     foxr     7327: 	    while(($user_input = get_request) && $keep_going) {
                   7328: 		alarm(120);
                   7329: 		Debug("Main: Got $user_input\n");
                   7330: 		$keep_going = &process_request($user_input);
1.178     foxr     7331: 		alarm(0);
1.212     foxr     7332: 		&status('Listening to '.$clientname." ($keymode)");	   
1.161     foxr     7333: 	    }
1.212     foxr     7334: 
1.59      www      7335: # --------------------------------------------- client unknown or fishy, refuse
1.212     foxr     7336: 	}  else {
1.161     foxr     7337: 	    print $client "refused\n";
                   7338: 	    $client->close();
1.190     albertel 7339: 	    &logthis("<font color='blue'>WARNING: "
1.161     foxr     7340: 		     ."Rejected client $clientip, closing connection</font>");
                   7341: 	}
1.212     foxr     7342:     }            
1.161     foxr     7343:     
1.1       albertel 7344: # =============================================================================
1.161     foxr     7345:     
1.190     albertel 7346:     &logthis("<font color='red'>CRITICAL: "
1.161     foxr     7347: 	     ."Disconnect from $clientip ($clientname)</font>");    
                   7348:     
                   7349:     
                   7350:     # this exit is VERY important, otherwise the child will become
                   7351:     # a producer of more and more children, forking yourself into
                   7352:     # process death.
                   7353:     exit;
1.106     foxr     7354:     
1.78      foxr     7355: }
1.261     foxr     7356: #
                   7357: #   Determine if a user is an author for the indicated domain.
                   7358: #
                   7359: # Parameters:
                   7360: #    domain          - domain to check in .
                   7361: #    user            - Name of user to check.
                   7362: #
                   7363: # Return:
                   7364: #     1             - User is an author for domain.
                   7365: #     0             - User is not an author for domain.
                   7366: sub is_author {
                   7367:     my ($domain, $user) = @_;
                   7368: 
                   7369:     &Debug("is_author: $user @ $domain");
                   7370: 
                   7371:     my $hashref = &tie_user_hash($domain, $user, "roles",
                   7372: 				 &GDBM_READER());
                   7373: 
                   7374:     #  Author role should show up as a key /domain/_au
1.78      foxr     7375: 
1.321     albertel 7376:     my $value;
1.487     foxr     7377:     if ($hashref) {
1.78      foxr     7378: 
1.487     foxr     7379: 	my $key    = "/$domain/_au";
                   7380: 	if (defined($hashref)) {
                   7381: 	    $value = $hashref->{$key};
                   7382: 	    if(!untie_user_hash($hashref)) {
                   7383: 		return 'error: ' .  ($!+0)." untie (GDBM) Failed";
                   7384: 	    }
                   7385: 	}
                   7386: 	
                   7387: 	if(defined($value)) {
                   7388: 	    &Debug("$user @ $domain is an author");
                   7389: 	}
                   7390:     } else {
                   7391: 	return 'error: '.($!+0)." tie (GDBM) Failed";
1.261     foxr     7392:     }
                   7393: 
                   7394:     return defined($value);
                   7395: }
1.78      foxr     7396: #
                   7397: #   Checks to see if the input roleput request was to set
1.482     www      7398: # an author role.  If so, creates construction space 
1.78      foxr     7399: # Parameters:
                   7400: #    request   - The request sent to the rolesput subchunk.
                   7401: #                We're looking for  /domain/_au
                   7402: #    domain    - The domain in which the user is having roles doctored.
                   7403: #    user      - Name of the user for which the role is being put.
                   7404: #    authtype  - The authentication type associated with the user.
                   7405: #
1.289     albertel 7406: sub manage_permissions {
1.192     foxr     7407:     my ($request, $domain, $user, $authtype) = @_;
1.78      foxr     7408:     # See if the request is of the form /$domain/_au
1.289     albertel 7409:     if($request =~ /^(\/\Q$domain\E\/_au)$/) { # It's an author rolesput...
1.484     raeburn  7410:         my $path=$perlvar{'lonDocRoot'}."/priv/$domain";
1.482     www      7411:         unless (-e $path) {        
                   7412:            mkdir($path);
                   7413:         }
                   7414:         unless (-e $path.'/'.$user) {
                   7415:            mkdir($path.'/'.$user);
                   7416:         }
1.78      foxr     7417:     }
                   7418: }
1.222     foxr     7419: 
                   7420: 
                   7421: #
                   7422: #  Return the full path of a user password file, whether it exists or not.
                   7423: # Parameters:
                   7424: #   domain     - Domain in which the password file lives.
                   7425: #   user       - name of the user.
                   7426: # Returns:
                   7427: #    Full passwd path:
                   7428: #
                   7429: sub password_path {
                   7430:     my ($domain, $user) = @_;
1.264     albertel 7431:     return &propath($domain, $user).'/passwd';
1.222     foxr     7432: }
                   7433: 
                   7434: #   Password Filename
                   7435: #   Returns the path to a passwd file given domain and user... only if
                   7436: #  it exists.
                   7437: # Parameters:
                   7438: #   domain    - Domain in which to search.
                   7439: #   user      - username.
                   7440: # Returns:
                   7441: #   - If the password file exists returns its path.
                   7442: #   - If the password file does not exist, returns undefined.
                   7443: #
                   7444: sub password_filename {
                   7445:     my ($domain, $user) = @_;
                   7446: 
                   7447:     Debug ("PasswordFilename called: dom = $domain user = $user");
                   7448: 
                   7449:     my $path  = &password_path($domain, $user);
                   7450:     Debug("PasswordFilename got path: $path");
                   7451:     if(-e $path) {
                   7452: 	return $path;
                   7453:     } else {
                   7454: 	return undef;
                   7455:     }
                   7456: }
                   7457: 
                   7458: #
                   7459: #   Rewrite the contents of the user's passwd file.
                   7460: #  Parameters:
                   7461: #    domain    - domain of the user.
                   7462: #    name      - User's name.
                   7463: #    contents  - New contents of the file.
1.489.2.26  raeburn  7464: #    saveold   - (optional). If true save old file in a passwd.bak file.
1.222     foxr     7465: # Returns:
                   7466: #   0    - Failed.
                   7467: #   1    - Success.
                   7468: #
                   7469: sub rewrite_password_file {
1.489.2.26  raeburn  7470:     my ($domain, $user, $contents, $saveold) = @_;
1.222     foxr     7471: 
                   7472:     my $file = &password_filename($domain, $user);
                   7473:     if (defined $file) {
1.489.2.26  raeburn  7474:         if ($saveold) {
                   7475:             my $bakfile = $file.'.bak';
                   7476:             if (CopyFile($file,$bakfile)) {
                   7477:                 chmod(0400,$bakfile);
                   7478:                 &logthis("Old password saved in passwd.bak for internally authenticated user: $user:$domain");
                   7479:             } else {
                   7480:                 &logthis("Failed to save old password in passwd.bak for internally authenticated user: $user:$domain");
                   7481:             }
                   7482:         }
1.222     foxr     7483: 	my $pf = IO::File->new(">$file");
                   7484: 	if($pf) {
                   7485: 	    print $pf "$contents\n";
                   7486: 	    return 1;
                   7487: 	} else {
                   7488: 	    return 0;
                   7489: 	}
                   7490:     } else {
                   7491: 	return 0;
                   7492:     }
                   7493: 
                   7494: }
                   7495: 
1.78      foxr     7496: #
1.222     foxr     7497: #   get_auth_type - Determines the authorization type of a user in a domain.
1.78      foxr     7498: 
                   7499: #     Returns the authorization type or nouser if there is no such user.
                   7500: #
1.436     raeburn  7501: sub get_auth_type {
1.192     foxr     7502:     my ($domain, $user)  = @_;
1.78      foxr     7503: 
1.222     foxr     7504:     Debug("get_auth_type( $domain, $user ) \n");
1.78      foxr     7505:     my $proname    = &propath($domain, $user); 
                   7506:     my $passwdfile = "$proname/passwd";
                   7507:     if( -e $passwdfile ) {
                   7508: 	my $pf = IO::File->new($passwdfile);
                   7509: 	my $realpassword = <$pf>;
                   7510: 	chomp($realpassword);
1.79      foxr     7511: 	Debug("Password info = $realpassword\n");
1.78      foxr     7512: 	my ($authtype, $contentpwd) = split(/:/, $realpassword);
1.79      foxr     7513: 	Debug("Authtype = $authtype, content = $contentpwd\n");
1.259     raeburn  7514: 	return "$authtype:$contentpwd";     
1.224     foxr     7515:     } else {
1.79      foxr     7516: 	Debug("Returning nouser");
1.78      foxr     7517: 	return "nouser";
                   7518:     }
1.1       albertel 7519: }
                   7520: 
1.220     foxr     7521: #
                   7522: #  Validate a user given their domain, name and password.  This utility
                   7523: #  function is used by both  AuthenticateHandler and ChangePasswordHandler
                   7524: #  to validate the login credentials of a user.
                   7525: # Parameters:
                   7526: #    $domain    - The domain being logged into (this is required due to
                   7527: #                 the capability for multihomed systems.
                   7528: #    $user      - The name of the user being validated.
                   7529: #    $password  - The user's propoposed password.
                   7530: #
                   7531: # Returns:
                   7532: #     1        - The domain,user,pasword triplet corresponds to a valid
                   7533: #                user.
                   7534: #     0        - The domain,user,password triplet is not a valid user.
                   7535: #
                   7536: sub validate_user {
1.396     raeburn  7537:     my ($domain, $user, $password, $checkdefauth) = @_;
1.220     foxr     7538: 
                   7539:     # Why negative ~pi you may well ask?  Well this function is about
                   7540:     # authentication, and therefore very important to get right.
                   7541:     # I've initialized the flag that determines whether or not I've 
                   7542:     # validated correctly to a value it's not supposed to get.
                   7543:     # At the end of this function. I'll ensure that it's not still that
                   7544:     # value so we don't just wind up returning some accidental value
                   7545:     # as a result of executing an unforseen code path that
1.249     foxr     7546:     # did not set $validated.  At the end of valid execution paths,
                   7547:     # validated shoule be 1 for success or 0 for failuer.
1.220     foxr     7548: 
                   7549:     my $validated = -3.14159;
                   7550: 
                   7551:     #  How we authenticate is determined by the type of authentication
                   7552:     #  the user has been assigned.  If the authentication type is
                   7553:     #  "nouser", the user does not exist so we will return 0.
                   7554: 
1.222     foxr     7555:     my $contents = &get_auth_type($domain, $user);
1.220     foxr     7556:     my ($howpwd, $contentpwd) = split(/:/, $contents);
                   7557: 
                   7558:     my $null = pack("C",0);	# Used by kerberos auth types.
                   7559: 
1.395     raeburn  7560:     if ($howpwd eq 'nouser') {
1.396     raeburn  7561:         if ($checkdefauth) {
                   7562:             my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
                   7563:             if ($domdefaults{'auth_def'} eq 'localauth') {
                   7564:                 $howpwd = $domdefaults{'auth_def'};
                   7565:                 $contentpwd = $domdefaults{'auth_arg_def'};
                   7566:             } elsif ((($domdefaults{'auth_def'} eq 'krb4') || 
                   7567:                       ($domdefaults{'auth_def'} eq 'krb5')) &&
                   7568:                      ($domdefaults{'auth_arg_def'} ne '')) {
                   7569:                 $howpwd = $domdefaults{'auth_def'};
                   7570:                 $contentpwd = $domdefaults{'auth_arg_def'}; 
                   7571:             }
1.395     raeburn  7572:         }
1.489.2.26  raeburn  7573:     }
1.220     foxr     7574:     if ($howpwd ne 'nouser') {
                   7575: 	if($howpwd eq "internal") { # Encrypted is in local password file.
1.489.2.21  raeburn  7576:             if (length($contentpwd) == 13) {
                   7577:                 $validated = (crypt($password,$contentpwd) eq $contentpwd);
                   7578:                 if ($validated) {
1.489.2.26  raeburn  7579:                     my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
                   7580:                     if ($domdefaults{'intauth_switch'}) {
                   7581:                         my $ncpass = &hash_passwd($domain,$password);
                   7582:                         my $saveold;
                   7583:                         if ($domdefaults{'intauth_switch'} == 2) {
                   7584:                             $saveold = 1;
                   7585:                         }
                   7586:                         if (&rewrite_password_file($domain,$user,"$howpwd:$ncpass",$saveold)) {
                   7587:                             &update_passwd_history($user,$domain,$howpwd,'conversion');
                   7588:                             &logthis("Validated password hashed with bcrypt for $user:$domain");
                   7589:                         }
1.489.2.21  raeburn  7590:                     }
                   7591:                 }
                   7592:             } else {
1.489.2.26  raeburn  7593:                 $validated = &check_internal_passwd($password,$contentpwd,$domain,$user);
1.489.2.21  raeburn  7594:             }
1.220     foxr     7595: 	}
                   7596: 	elsif ($howpwd eq "unix") { # User is a normal unix user.
                   7597: 	    $contentpwd = (getpwnam($user))[1];
                   7598: 	    if($contentpwd) {
                   7599: 		if($contentpwd eq 'x') { # Shadow password file...
                   7600: 		    my $pwauth_path = "/usr/local/sbin/pwauth";
                   7601: 		    open PWAUTH,  "|$pwauth_path" or
                   7602: 			die "Cannot invoke authentication";
                   7603: 		    print PWAUTH "$user\n$password\n";
                   7604: 		    close PWAUTH;
                   7605: 		    $validated = ! $?;
                   7606: 
                   7607: 		} else { 	         # Passwords in /etc/passwd. 
                   7608: 		    $validated = (crypt($password,
                   7609: 					$contentpwd) eq $contentpwd);
                   7610: 		}
                   7611: 	    } else {
                   7612: 		$validated = 0;
                   7613: 	    }
1.439     raeburn  7614: 	} elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain.
                   7615:             my $checkwithkrb5 = 0;
                   7616:             if ($dist =~/^fedora(\d+)$/) {
                   7617:                 if ($1 > 11) {
                   7618:                     $checkwithkrb5 = 1;
                   7619:                 }
                   7620:             } elsif ($dist =~ /^suse([\d.]+)$/) {
                   7621:                 if ($1 > 11.1) {
                   7622:                     $checkwithkrb5 = 1; 
                   7623:                 }
                   7624:             }
                   7625:             if ($checkwithkrb5) {
                   7626:                 $validated = &krb5_authen($password,$null,$user,$contentpwd);
                   7627:             } else {
                   7628:                 $validated = &krb4_authen($password,$null,$user,$contentpwd);
                   7629:             }
1.224     foxr     7630: 	} elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain.
1.439     raeburn  7631:             $validated = &krb5_authen($password,$null,$user,$contentpwd);
1.224     foxr     7632: 	} elsif ($howpwd eq "localauth") { 
1.220     foxr     7633: 	    #  Authenticate via installation specific authentcation method:
                   7634: 	    $validated = &localauth::localauth($user, 
                   7635: 					       $password, 
1.353     albertel 7636: 					       $contentpwd,
                   7637: 					       $domain);
1.358     albertel 7638: 	    if ($validated < 0) {
1.357     albertel 7639: 		&logthis("localauth for $contentpwd $user:$domain returned a $validated");
                   7640: 		$validated = 0;
                   7641: 	    }
1.224     foxr     7642: 	} else {			# Unrecognized auth is also bad.
1.220     foxr     7643: 	    $validated = 0;
                   7644: 	}
                   7645:     } else {
                   7646: 	$validated = 0;
                   7647:     }
                   7648:     #
                   7649:     #  $validated has the correct stat of the authentication:
                   7650:     #
                   7651: 
                   7652:     unless ($validated != -3.14159) {
1.249     foxr     7653: 	#  I >really really< want to know if this happens.
                   7654: 	#  since it indicates that user authentication is badly
                   7655: 	#  broken in some code path.
                   7656:         #
                   7657: 	die "ValidateUser - failed to set the value of validated $domain, $user $password";
1.220     foxr     7658:     }
                   7659:     return $validated;
                   7660: }
                   7661: 
1.489.2.21  raeburn  7662: sub check_internal_passwd {
1.489.2.26  raeburn  7663:     my ($plainpass,$stored,$domain,$user) = @_;
1.489.2.21  raeburn  7664:     my (undef,$method,@rest) = split(/!/,$stored);
1.489.2.26  raeburn  7665:     if ($method eq 'bcrypt') {
1.489.2.21  raeburn  7666:         my $result = &hash_passwd($domain,$plainpass,@rest);
                   7667:         if ($result ne $stored) {
                   7668:             return 0;
                   7669:         }
1.489.2.26  raeburn  7670:         my %domdefaults = &Apache::lonnet::get_domain_defaults($domain);
                   7671:         if ($domdefaults{'intauth_check'}) {
                   7672:             # Upgrade to a larger number of rounds if necessary
                   7673:             my $defaultcost = $domdefaults{'intauth_cost'};
                   7674:             if (($defaultcost eq '') || ($defaultcost =~ /D/)) {
                   7675:                 $defaultcost = 10;
                   7676:             }
                   7677:             if (int($rest[0])<int($defaultcost)) {
                   7678:                 if ($domdefaults{'intauth_check'} == 1) {
                   7679:                     my $ncpass = &hash_passwd($domain,$plainpass);
                   7680:                     if (&rewrite_password_file($domain,$user,"internal:$ncpass")) {
                   7681:                         &update_passwd_history($user,$domain,'internal','update cost');
                   7682:                         &logthis("Validated password hashed with bcrypt for $user:$domain");
                   7683:                     }
                   7684:                     return 1;
                   7685:                 } elsif ($domdefaults{'intauth_check'} == 2) {
                   7686:                     return 0;
                   7687:                 }
                   7688:             }
                   7689:         } else {
                   7690:             return 1;
1.489.2.21  raeburn  7691:         }
                   7692:     }
                   7693:     return 0;
                   7694: }
                   7695: 
                   7696: sub get_last_authchg {
                   7697:     my ($domain,$user) = @_;
                   7698:     my $lastmod;
                   7699:     my $logname = &propath($domain,$user).'/passwd.log';
                   7700:     if (-e "$logname") {
                   7701:         $lastmod = (stat("$logname"))[9];
                   7702:     }
                   7703:     return $lastmod;
                   7704: }
                   7705: 
1.439     raeburn  7706: sub krb4_authen {
                   7707:     my ($password,$null,$user,$contentpwd) = @_;
                   7708:     my $validated = 0;
                   7709:     if (!($password =~ /$null/) ) {  # Null password not allowed.
                   7710:         eval {
                   7711:             require Authen::Krb4;
                   7712:         };
                   7713:         if (!$@) {
                   7714:             my $k4error = &Authen::Krb4::get_pw_in_tkt($user,
                   7715:                                                        "",
                   7716:                                                        $contentpwd,,
                   7717:                                                        'krbtgt',
                   7718:                                                        $contentpwd,
                   7719:                                                        1,
                   7720:                                                        $password);
                   7721:             if(!$k4error) {
                   7722:                 $validated = 1;
                   7723:             } else {
                   7724:                 $validated = 0;
                   7725:                 &logthis('krb4: '.$user.', '.$contentpwd.', '.
                   7726:                           &Authen::Krb4::get_err_txt($Authen::Krb4::error));
                   7727:             }
                   7728:         } else {
                   7729:             $validated = krb5_authen($password,$null,$user,$contentpwd);
                   7730:         }
                   7731:     }
                   7732:     return $validated;
                   7733: }
                   7734: 
                   7735: sub krb5_authen {
                   7736:     my ($password,$null,$user,$contentpwd) = @_;
                   7737:     my $validated = 0;
                   7738:     if(!($password =~ /$null/)) { # Null password not allowed.
                   7739:         my $krbclient = &Authen::Krb5::parse_name($user.'@'
                   7740:                                                   .$contentpwd);
                   7741:         my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd;
                   7742:         my $krbserver  = &Authen::Krb5::parse_name($krbservice);
                   7743:         my $credentials= &Authen::Krb5::cc_default();
                   7744:         $credentials->initialize(&Authen::Krb5::parse_name($user.'@'
                   7745:                                                             .$contentpwd));
                   7746:         my $krbreturn;
                   7747:         if (exists(&Authen::Krb5::get_init_creds_password)) {
                   7748:             $krbreturn =
                   7749:                 &Authen::Krb5::get_init_creds_password($krbclient,$password,
                   7750:                                                           $krbservice);
                   7751:             $validated = (ref($krbreturn) eq 'Authen::Krb5::Creds');
                   7752:         } else {
                   7753:             $krbreturn  =
                   7754:                 &Authen::Krb5::get_in_tkt_with_password($krbclient,$krbserver,
                   7755:                                                          $password,$credentials);
                   7756:             $validated = ($krbreturn == 1);
                   7757:         }
                   7758:         if (!$validated) {
                   7759:             &logthis('krb5: '.$user.', '.$contentpwd.', '.
                   7760:                      &Authen::Krb5::error());
                   7761:         }
                   7762:     }
                   7763:     return $validated;
                   7764: }
1.220     foxr     7765: 
1.84      albertel 7766: sub addline {
                   7767:     my ($fname,$hostid,$ip,$newline)=@_;
                   7768:     my $contents;
                   7769:     my $found=0;
1.355     albertel 7770:     my $expr='^'.quotemeta($hostid).':'.quotemeta($ip).':';
1.134     albertel 7771:     my $sh;
1.84      albertel 7772:     if ($sh=IO::File->new("$fname.subscription")) {
                   7773: 	while (my $subline=<$sh>) {
                   7774: 	    if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;}
                   7775: 	}
                   7776: 	$sh->close();
                   7777:     }
                   7778:     $sh=IO::File->new(">$fname.subscription");
                   7779:     if ($contents) { print $sh $contents; }
                   7780:     if ($newline) { print $sh $newline; }
                   7781:     $sh->close();
                   7782:     return $found;
1.86      www      7783: }
                   7784: 
1.234     foxr     7785: sub get_chat {
1.324     raeburn  7786:     my ($cdom,$cname,$udom,$uname,$group)=@_;
1.310     albertel 7787: 
1.87      www      7788:     my @entries=();
1.324     raeburn  7789:     my $namespace = 'nohist_chatroom';
                   7790:     my $namespace_inroom = 'nohist_inchatroom';
1.335     albertel 7791:     if ($group ne '') {
1.324     raeburn  7792:         $namespace .= '_'.$group;
                   7793:         $namespace_inroom .= '_'.$group;
                   7794:     }
                   7795:     my $hashref = &tie_user_hash($cdom, $cname, $namespace,
1.310     albertel 7796: 				 &GDBM_READER());
                   7797:     if ($hashref) {
                   7798: 	@entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref));
1.311     albertel 7799: 	&untie_user_hash($hashref);
1.123     www      7800:     }
1.124     www      7801:     my @participants=();
1.134     albertel 7802:     my $cutoff=time-60;
1.324     raeburn  7803:     $hashref = &tie_user_hash($cdom, $cname, $namespace_inroom,
1.310     albertel 7804: 			      &GDBM_WRCREAT());
                   7805:     if ($hashref) {
                   7806:         $hashref->{$uname.':'.$udom}=time;
                   7807:         foreach my $user (sort(keys(%$hashref))) {
                   7808: 	    if ($hashref->{$user}>$cutoff) {
                   7809: 		push(@participants, 'active_participant:'.$user);
1.123     www      7810:             }
                   7811:         }
1.311     albertel 7812:         &untie_user_hash($hashref);
1.86      www      7813:     }
1.124     www      7814:     return (@participants,@entries);
1.86      www      7815: }
                   7816: 
1.234     foxr     7817: sub chat_add {
1.324     raeburn  7818:     my ($cdom,$cname,$newchat,$group)=@_;
1.88      albertel 7819:     my @entries=();
1.142     www      7820:     my $time=time;
1.324     raeburn  7821:     my $namespace = 'nohist_chatroom';
                   7822:     my $logfile = 'chatroom.log';
1.335     albertel 7823:     if ($group ne '') {
1.324     raeburn  7824:         $namespace .= '_'.$group;
                   7825:         $logfile = 'chatroom_'.$group.'.log';
                   7826:     }
                   7827:     my $hashref = &tie_user_hash($cdom, $cname, $namespace,
1.310     albertel 7828: 				 &GDBM_WRCREAT());
                   7829:     if ($hashref) {
                   7830: 	@entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref));
1.88      albertel 7831: 	my ($lastid)=($entries[$#entries]=~/^(\w+)\:/);
                   7832: 	my ($thentime,$idnum)=split(/\_/,$lastid);
                   7833: 	my $newid=$time.'_000000';
                   7834: 	if ($thentime==$time) {
                   7835: 	    $idnum=~s/^0+//;
                   7836: 	    $idnum++;
                   7837: 	    $idnum=substr('000000'.$idnum,-6,6);
                   7838: 	    $newid=$time.'_'.$idnum;
                   7839: 	}
1.310     albertel 7840: 	$hashref->{$newid}=$newchat;
1.88      albertel 7841: 	my $expired=$time-3600;
1.310     albertel 7842: 	foreach my $comment (keys(%$hashref)) {
                   7843: 	    my ($thistime) = ($comment=~/(\d+)\_/);
1.88      albertel 7844: 	    if ($thistime<$expired) {
1.310     albertel 7845: 		delete $hashref->{$comment};
1.88      albertel 7846: 	    }
                   7847: 	}
1.310     albertel 7848: 	{
                   7849: 	    my $proname=&propath($cdom,$cname);
1.324     raeburn  7850: 	    if (open(CHATLOG,">>$proname/$logfile")) { 
1.310     albertel 7851: 		print CHATLOG ("$time:".&unescape($newchat)."\n");
                   7852: 	    }
                   7853: 	    close(CHATLOG);
1.142     www      7854: 	}
1.311     albertel 7855: 	&untie_user_hash($hashref);
1.86      www      7856:     }
1.84      albertel 7857: }
                   7858: 
                   7859: sub unsub {
                   7860:     my ($fname,$clientip)=@_;
                   7861:     my $result;
1.188     foxr     7862:     my $unsubs = 0;		# Number of successful unsubscribes:
                   7863: 
                   7864: 
                   7865:     # An old way subscriptions were handled was to have a 
                   7866:     # subscription marker file:
                   7867: 
                   7868:     Debug("Attempting unlink of $fname.$clientname");
1.161     foxr     7869:     if (unlink("$fname.$clientname")) {
1.188     foxr     7870: 	$unsubs++;		# Successful unsub via marker file.
                   7871:     } 
                   7872: 
                   7873:     # The more modern way to do it is to have a subscription list
                   7874:     # file:
                   7875: 
1.84      albertel 7876:     if (-e "$fname.subscription") {
1.161     foxr     7877: 	my $found=&addline($fname,$clientname,$clientip,'');
1.188     foxr     7878: 	if ($found) { 
                   7879: 	    $unsubs++;
                   7880: 	}
                   7881:     } 
                   7882: 
                   7883:     #  If either or both of these mechanisms succeeded in unsubscribing a 
                   7884:     #  resource we can return ok:
                   7885: 
                   7886:     if($unsubs) {
                   7887: 	$result = "ok\n";
1.84      albertel 7888:     } else {
1.188     foxr     7889: 	$result = "not_subscribed\n";
1.84      albertel 7890:     }
1.188     foxr     7891: 
1.84      albertel 7892:     return $result;
                   7893: }
                   7894: 
1.101     www      7895: sub currentversion {
                   7896:     my $fname=shift;
                   7897:     my $version=-1;
                   7898:     my $ulsdir='';
                   7899:     if ($fname=~/^(.+)\/[^\/]+$/) {
                   7900:        $ulsdir=$1;
                   7901:     }
1.114     albertel 7902:     my ($fnamere1,$fnamere2);
                   7903:     # remove version if already specified
1.101     www      7904:     $fname=~s/\.\d+\.(\w+(?:\.meta)*)$/\.$1/;
1.114     albertel 7905:     # get the bits that go before and after the version number
                   7906:     if ( $fname=~/^(.*\.)(\w+(?:\.meta)*)$/ ) {
                   7907: 	$fnamere1=$1;
                   7908: 	$fnamere2='.'.$2;
                   7909:     }
1.101     www      7910:     if (-e $fname) { $version=1; }
                   7911:     if (-e $ulsdir) {
1.134     albertel 7912: 	if(-d $ulsdir) {
                   7913: 	    if (opendir(LSDIR,$ulsdir)) {
                   7914: 		my $ulsfn;
                   7915: 		while ($ulsfn=readdir(LSDIR)) {
1.101     www      7916: # see if this is a regular file (ignore links produced earlier)
1.134     albertel 7917: 		    my $thisfile=$ulsdir.'/'.$ulsfn;
                   7918: 		    unless (-l $thisfile) {
1.160     www      7919: 			if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) {
1.134     albertel 7920: 			    if ($1>$version) { $version=$1; }
                   7921: 			}
                   7922: 		    }
                   7923: 		}
                   7924: 		closedir(LSDIR);
                   7925: 		$version++;
                   7926: 	    }
                   7927: 	}
                   7928:     }
                   7929:     return $version;
1.101     www      7930: }
                   7931: 
                   7932: sub thisversion {
                   7933:     my $fname=shift;
                   7934:     my $version=-1;
                   7935:     if ($fname=~/\.(\d+)\.\w+(?:\.meta)*$/) {
                   7936: 	$version=$1;
                   7937:     }
                   7938:     return $version;
                   7939: }
                   7940: 
1.84      albertel 7941: sub subscribe {
                   7942:     my ($userinput,$clientip)=@_;
                   7943:     my $result;
1.293     albertel 7944:     my ($cmd,$fname)=split(/:/,$userinput,2);
1.84      albertel 7945:     my $ownership=&ishome($fname);
                   7946:     if ($ownership eq 'owner') {
1.101     www      7947: # explitly asking for the current version?
                   7948:         unless (-e $fname) {
                   7949:             my $currentversion=&currentversion($fname);
                   7950: 	    if (&thisversion($fname)==$currentversion) {
                   7951:                 if ($fname=~/^(.+)\.\d+\.(\w+(?:\.meta)*)$/) {
                   7952: 		    my $root=$1;
                   7953:                     my $extension=$2;
                   7954:                     symlink($root.'.'.$extension,
                   7955:                             $root.'.'.$currentversion.'.'.$extension);
1.102     www      7956:                     unless ($extension=~/\.meta$/) {
                   7957:                        symlink($root.'.'.$extension.'.meta',
                   7958:                             $root.'.'.$currentversion.'.'.$extension.'.meta');
                   7959: 		    }
1.101     www      7960:                 }
                   7961:             }
                   7962:         }
1.84      albertel 7963: 	if (-e $fname) {
                   7964: 	    if (-d $fname) {
                   7965: 		$result="directory\n";
                   7966: 	    } else {
1.161     foxr     7967: 		if (-e "$fname.$clientname") {&unsub($fname,$clientip);}
1.134     albertel 7968: 		my $now=time;
1.161     foxr     7969: 		my $found=&addline($fname,$clientname,$clientip,
                   7970: 				   "$clientname:$clientip:$now\n");
1.84      albertel 7971: 		if ($found) { $result="$fname\n"; }
                   7972: 		# if they were subscribed to only meta data, delete that
                   7973:                 # subscription, when you subscribe to a file you also get
                   7974:                 # the metadata
                   7975: 		unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); }
                   7976: 		$fname=~s/\/home\/httpd\/html\/res/raw/;
1.476     raeburn  7977:                 my $protocol = $Apache::lonnet::protocol{$perlvar{'lonHostID'}};
                   7978:                 $protocol = 'http' if ($protocol ne 'https');
                   7979: 		$fname=$protocol.'://'.&Apache::lonnet::hostname($perlvar{'lonHostID'})."/".$fname;
1.84      albertel 7980: 		$result="$fname\n";
                   7981: 	    }
                   7982: 	} else {
                   7983: 	    $result="not_found\n";
                   7984: 	}
                   7985:     } else {
                   7986: 	$result="rejected\n";
                   7987:     }
                   7988:     return $result;
                   7989: }
1.287     foxr     7990: #  Change the passwd of a unix user.  The caller must have
                   7991: #  first verified that the user is a loncapa user.
                   7992: #
                   7993: # Parameters:
                   7994: #    user      - Unix user name to change.
                   7995: #    pass      - New password for the user.
                   7996: # Returns:
                   7997: #    ok    - if success
                   7998: #    other - Some meaningfule error message string.
                   7999: # NOTE:
                   8000: #    invokes a setuid script to change the passwd.
                   8001: sub change_unix_password {
                   8002:     my ($user, $pass) = @_;
                   8003: 
                   8004:     &Debug("change_unix_password");
                   8005:     my $execdir=$perlvar{'lonDaemons'};
                   8006:     &Debug("Opening lcpasswd pipeline");
                   8007:     my $pf = IO::File->new("|$execdir/lcpasswd > "
                   8008: 			   ."$perlvar{'lonDaemons'}"
                   8009: 			   ."/logs/lcpasswd.log");
                   8010:     print $pf "$user\n$pass\n$pass\n";
                   8011:     close $pf;
                   8012:     my $err = $?;
                   8013:     return ($err < @passwderrors) ? $passwderrors[$err] : 
                   8014: 	"pwchange_falure - unknown error";
                   8015: 
                   8016:     
                   8017: }
                   8018: 
1.91      albertel 8019: 
                   8020: sub make_passwd_file {
1.489.2.21  raeburn  8021:     my ($uname,$udom,$umode,$npass,$passfilename,$action)=@_;
1.390     raeburn  8022:     my $result="ok";
1.91      albertel 8023:     if ($umode eq 'krb4' or $umode eq 'krb5') {
                   8024: 	{
                   8025: 	    my $pf = IO::File->new(">$passfilename");
1.261     foxr     8026: 	    if ($pf) {
                   8027: 		print $pf "$umode:$npass\n";
1.489.2.21  raeburn  8028:                 &update_passwd_history($uname,$udom,$umode,$action);
1.261     foxr     8029: 	    } else {
                   8030: 		$result = "pass_file_failed_error";
                   8031: 	    }
1.91      albertel 8032: 	}
                   8033:     } elsif ($umode eq 'internal') {
1.489.2.21  raeburn  8034:         my $ncpass = &hash_passwd($udom,$npass);
1.91      albertel 8035: 	{
                   8036: 	    &Debug("Creating internal auth");
                   8037: 	    my $pf = IO::File->new(">$passfilename");
1.261     foxr     8038: 	    if($pf) {
                   8039: 		print $pf "internal:$ncpass\n"; 
1.489.2.21  raeburn  8040:                 &update_passwd_history($uname,$udom,$umode,$action);
1.261     foxr     8041: 	    } else {
                   8042: 		$result = "pass_file_failed_error";
                   8043: 	    }
1.91      albertel 8044: 	}
                   8045:     } elsif ($umode eq 'localauth') {
                   8046: 	{
                   8047: 	    my $pf = IO::File->new(">$passfilename");
1.261     foxr     8048: 	    if($pf) {
                   8049: 		print $pf "localauth:$npass\n";
1.489.2.21  raeburn  8050:                 &update_passwd_history($uname,$udom,$umode,$action);
1.261     foxr     8051: 	    } else {
                   8052: 		$result = "pass_file_failed_error";
                   8053: 	    }
1.91      albertel 8054: 	}
                   8055:     } elsif ($umode eq 'unix') {
1.489.2.8  raeburn  8056: 	&logthis(">>>Attempt to create unix account blocked -- unix auth not available for new users.");
                   8057: 	$result="no_new_unix_accounts";
1.91      albertel 8058:     } elsif ($umode eq 'none') {
                   8059: 	{
1.223     foxr     8060: 	    my $pf = IO::File->new("> $passfilename");
1.261     foxr     8061: 	    if($pf) {
                   8062: 		print $pf "none:\n";
                   8063: 	    } else {
                   8064: 		$result = "pass_file_failed_error";
                   8065: 	    }
1.91      albertel 8066: 	}
                   8067:     } else {
1.390     raeburn  8068: 	$result="auth_mode_error";
1.91      albertel 8069:     }
                   8070:     return $result;
1.121     albertel 8071: }
                   8072: 
1.265     albertel 8073: sub convert_photo {
                   8074:     my ($start,$dest)=@_;
                   8075:     system("convert $start $dest");
                   8076: }
                   8077: 
1.121     albertel 8078: sub sethost {
                   8079:     my ($remotereq) = @_;
                   8080:     my (undef,$hostid)=split(/:/,$remotereq);
1.322     albertel 8081:     # ignore sethost if we are already correct
                   8082:     if ($hostid eq $currenthostid) {
                   8083: 	return 'ok';
                   8084:     }
                   8085: 
1.121     albertel 8086:     if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }
1.368     albertel 8087:     if (&Apache::lonnet::get_host_ip($perlvar{'lonHostID'}) 
                   8088: 	eq &Apache::lonnet::get_host_ip($hostid)) {
1.200     matthew  8089: 	$currenthostid  =$hostid;
1.369     albertel 8090: 	$currentdomainid=&Apache::lonnet::host_domain($hostid);
1.443     www      8091: #	&logthis("Setting hostid to $hostid, and domain to $currentdomainid");
1.121     albertel 8092:     } else {
                   8093: 	&logthis("Requested host id $hostid not an alias of ".
                   8094: 		 $perlvar{'lonHostID'}." refusing connection");
                   8095: 	return 'unable_to_set';
                   8096:     }
                   8097:     return 'ok';
                   8098: }
                   8099: 
                   8100: sub version {
                   8101:     my ($userinput)=@_;
                   8102:     $remoteVERSION=(split(/:/,$userinput))[1];
                   8103:     return "version:$VERSION";
1.127     albertel 8104: }
1.178     foxr     8105: 
1.447     raeburn  8106: sub get_usersession_config {
                   8107:     my ($dom,$name) = @_;
                   8108:     my ($usersessionconf,$cached)=&Apache::lonnet::is_cached_new($name,$dom);
                   8109:     if (defined($cached)) {
                   8110:         return $usersessionconf;
                   8111:     } else {
                   8112:         my %domconfig = &Apache::lonnet::get_dom('configuration',['usersessions'],$dom);
                   8113:         if (ref($domconfig{'usersessions'}) eq 'HASH') {
                   8114:             &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'usersessions'},3600);
                   8115:             return $domconfig{'usersessions'};
                   8116:         }
                   8117:     }
                   8118:     return;
                   8119: }
1.200     matthew  8120: 
1.489.2.27  raeburn  8121: sub get_usersearch_config {
                   8122:     my ($dom,$name) = @_;
                   8123:     my ($usersearchconf,$cached)=&Apache::lonnet::is_cached_new($name,$dom);
                   8124:     if (defined($cached)) {
                   8125:         return $usersearchconf;
                   8126:     } else {
                   8127:         my %domconfig = &Apache::lonnet::get_dom('configuration',['directorysrch'],$dom);
                   8128:         &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'directorysrch'},3600);
                   8129:         return $domconfig{'directorysrch'};
                   8130:     }
                   8131:     return;
                   8132: }
1.450     raeburn  8133: 
1.471     raeburn  8134: sub distro_and_arch {
                   8135:     return $dist.':'.$arch;
                   8136: }
                   8137: 
1.61      harris41 8138: # ----------------------------------- POD (plain old documentation, CPAN style)
                   8139: 
                   8140: =head1 NAME
                   8141: 
                   8142: lond - "LON Daemon" Server (port "LOND" 5663)
                   8143: 
                   8144: =head1 SYNOPSIS
                   8145: 
1.74      harris41 8146: Usage: B<lond>
                   8147: 
                   8148: Should only be run as user=www.  This is a command-line script which
                   8149: is invoked by B<loncron>.  There is no expectation that a typical user
                   8150: will manually start B<lond> from the command-line.  (In other words,
                   8151: DO NOT START B<lond> YOURSELF.)
1.61      harris41 8152: 
                   8153: =head1 DESCRIPTION
                   8154: 
1.74      harris41 8155: There are two characteristics associated with the running of B<lond>,
                   8156: PROCESS MANAGEMENT (starting, stopping, handling child processes)
                   8157: and SERVER-SIDE ACTIVITIES (password authentication, user creation,
                   8158: subscriptions, etc).  These are described in two large
                   8159: sections below.
                   8160: 
                   8161: B<PROCESS MANAGEMENT>
                   8162: 
1.61      harris41 8163: Preforker - server who forks first. Runs as a daemon. HUPs.
                   8164: Uses IDEA encryption
                   8165: 
1.74      harris41 8166: B<lond> forks off children processes that correspond to the other servers
                   8167: in the network.  Management of these processes can be done at the
                   8168: parent process level or the child process level.
                   8169: 
                   8170: B<logs/lond.log> is the location of log messages.
                   8171: 
                   8172: The process management is now explained in terms of linux shell commands,
                   8173: subroutines internal to this code, and signal assignments:
                   8174: 
                   8175: =over 4
                   8176: 
                   8177: =item *
                   8178: 
                   8179: PID is stored in B<logs/lond.pid>
                   8180: 
                   8181: This is the process id number of the parent B<lond> process.
                   8182: 
                   8183: =item *
                   8184: 
                   8185: SIGTERM and SIGINT
                   8186: 
                   8187: Parent signal assignment:
                   8188:  $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
                   8189: 
                   8190: Child signal assignment:
                   8191:  $SIG{INT}  = 'DEFAULT'; (and SIGTERM is DEFAULT also)
                   8192: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
                   8193:  to restart a new child.)
                   8194: 
                   8195: Command-line invocations:
                   8196:  B<kill> B<-s> SIGTERM I<PID>
                   8197:  B<kill> B<-s> SIGINT I<PID>
                   8198: 
                   8199: Subroutine B<HUNTSMAN>:
                   8200:  This is only invoked for the B<lond> parent I<PID>.
                   8201: This kills all the children, and then the parent.
                   8202: The B<lonc.pid> file is cleared.
                   8203: 
                   8204: =item *
                   8205: 
                   8206: SIGHUP
                   8207: 
                   8208: Current bug:
                   8209:  This signal can only be processed the first time
                   8210: on the parent process.  Subsequent SIGHUP signals
                   8211: have no effect.
                   8212: 
                   8213: Parent signal assignment:
                   8214:  $SIG{HUP}  = \&HUPSMAN;
                   8215: 
                   8216: Child signal assignment:
                   8217:  none (nothing happens)
                   8218: 
                   8219: Command-line invocations:
                   8220:  B<kill> B<-s> SIGHUP I<PID>
                   8221: 
                   8222: Subroutine B<HUPSMAN>:
                   8223:  This is only invoked for the B<lond> parent I<PID>,
                   8224: This kills all the children, and then the parent.
                   8225: The B<lond.pid> file is cleared.
                   8226: 
                   8227: =item *
                   8228: 
                   8229: SIGUSR1
                   8230: 
                   8231: Parent signal assignment:
                   8232:  $SIG{USR1} = \&USRMAN;
                   8233: 
                   8234: Child signal assignment:
                   8235:  $SIG{USR1}= \&logstatus;
                   8236: 
                   8237: Command-line invocations:
                   8238:  B<kill> B<-s> SIGUSR1 I<PID>
                   8239: 
                   8240: Subroutine B<USRMAN>:
                   8241:  When invoked for the B<lond> parent I<PID>,
                   8242: SIGUSR1 is sent to all the children, and the status of
                   8243: each connection is logged.
1.144     foxr     8244: 
                   8245: =item *
                   8246: 
                   8247: SIGUSR2
                   8248: 
                   8249: Parent Signal assignment:
                   8250:     $SIG{USR2} = \&UpdateHosts
                   8251: 
                   8252: Child signal assignment:
                   8253:     NONE
                   8254: 
1.74      harris41 8255: 
                   8256: =item *
                   8257: 
                   8258: SIGCHLD
                   8259: 
                   8260: Parent signal assignment:
                   8261:  $SIG{CHLD} = \&REAPER;
                   8262: 
                   8263: Child signal assignment:
                   8264:  none
                   8265: 
                   8266: Command-line invocations:
                   8267:  B<kill> B<-s> SIGCHLD I<PID>
                   8268: 
                   8269: Subroutine B<REAPER>:
                   8270:  This is only invoked for the B<lond> parent I<PID>.
                   8271: Information pertaining to the child is removed.
                   8272: The socket port is cleaned up.
                   8273: 
                   8274: =back
                   8275: 
                   8276: B<SERVER-SIDE ACTIVITIES>
                   8277: 
                   8278: Server-side information can be accepted in an encrypted or non-encrypted
                   8279: method.
                   8280: 
                   8281: =over 4
                   8282: 
                   8283: =item ping
                   8284: 
                   8285: Query a client in the hosts.tab table; "Are you there?"
                   8286: 
                   8287: =item pong
                   8288: 
                   8289: Respond to a ping query.
                   8290: 
                   8291: =item ekey
                   8292: 
                   8293: Read in encrypted key, make cipher.  Respond with a buildkey.
                   8294: 
                   8295: =item load
                   8296: 
                   8297: Respond with CPU load based on a computation upon /proc/loadavg.
                   8298: 
                   8299: =item currentauth
                   8300: 
                   8301: Reply with current authentication information (only over an
                   8302: encrypted channel).
                   8303: 
                   8304: =item auth
                   8305: 
                   8306: Only over an encrypted channel, reply as to whether a user's
                   8307: authentication information can be validated.
                   8308: 
                   8309: =item passwd
                   8310: 
                   8311: Allow for a password to be set.
                   8312: 
                   8313: =item makeuser
                   8314: 
                   8315: Make a user.
                   8316: 
                   8317: =item passwd
                   8318: 
                   8319: Allow for authentication mechanism and password to be changed.
                   8320: 
                   8321: =item home
1.61      harris41 8322: 
1.74      harris41 8323: Respond to a question "are you the home for a given user?"
                   8324: 
                   8325: =item update
                   8326: 
                   8327: Update contents of a subscribed resource.
                   8328: 
                   8329: =item unsubscribe
                   8330: 
                   8331: The server is unsubscribing from a resource.
                   8332: 
                   8333: =item subscribe
                   8334: 
                   8335: The server is subscribing to a resource.
                   8336: 
                   8337: =item log
                   8338: 
                   8339: Place in B<logs/lond.log>
                   8340: 
                   8341: =item put
                   8342: 
                   8343: stores hash in namespace
                   8344: 
1.489.2.2  raeburn  8345: =item rolesput
1.74      harris41 8346: 
                   8347: put a role into a user's environment
                   8348: 
                   8349: =item get
                   8350: 
                   8351: returns hash with keys from array
                   8352: reference filled in from namespace
                   8353: 
                   8354: =item eget
                   8355: 
                   8356: returns hash with keys from array
                   8357: reference filled in from namesp (encrypts the return communication)
                   8358: 
                   8359: =item rolesget
                   8360: 
                   8361: get a role from a user's environment
                   8362: 
                   8363: =item del
                   8364: 
                   8365: deletes keys out of array from namespace
                   8366: 
                   8367: =item keys
                   8368: 
                   8369: returns namespace keys
                   8370: 
                   8371: =item dump
                   8372: 
                   8373: dumps the complete (or key matching regexp) namespace into a hash
                   8374: 
                   8375: =item store
                   8376: 
                   8377: stores hash permanently
                   8378: for this url; hashref needs to be given and should be a \%hashname; the
                   8379: remaining args aren't required and if they aren't passed or are '' they will
                   8380: be derived from the ENV
                   8381: 
                   8382: =item restore
                   8383: 
                   8384: returns a hash for a given url
                   8385: 
                   8386: =item querysend
                   8387: 
                   8388: Tells client about the lonsql process that has been launched in response
                   8389: to a sent query.
                   8390: 
                   8391: =item queryreply
                   8392: 
                   8393: Accept information from lonsql and make appropriate storage in temporary
                   8394: file space.
                   8395: 
                   8396: =item idput
                   8397: 
                   8398: Defines usernames as corresponding to IDs.  (These "IDs" are unique identifiers
                   8399: for each student, defined perhaps by the institutional Registrar.)
                   8400: 
                   8401: =item idget
                   8402: 
                   8403: Returns usernames corresponding to IDs.  (These "IDs" are unique identifiers
                   8404: for each student, defined perhaps by the institutional Registrar.)
                   8405: 
                   8406: =item tmpput
                   8407: 
                   8408: Accept and store information in temporary space.
                   8409: 
                   8410: =item tmpget
                   8411: 
                   8412: Send along temporarily stored information.
                   8413: 
                   8414: =item ls
                   8415: 
                   8416: List part of a user's directory.
                   8417: 
1.135     foxr     8418: =item pushtable
                   8419: 
                   8420: Pushes a file in /home/httpd/lonTab directory.  Currently limited to:
                   8421: hosts.tab and domain.tab. The old file is copied to  *.tab.backup but
                   8422: must be restored manually in case of a problem with the new table file.
                   8423: pushtable requires that the request be encrypted and validated via
                   8424: ValidateManager.  The form of the command is:
                   8425: enc:pushtable tablename <tablecontents> \n
                   8426: where pushtable, tablename and <tablecontents> will be encrypted, but \n is a 
                   8427: cleartext newline.
                   8428: 
1.74      harris41 8429: =item Hanging up (exit or init)
                   8430: 
                   8431: What to do when a client tells the server that they (the client)
                   8432: are leaving the network.
                   8433: 
                   8434: =item unknown command
                   8435: 
                   8436: If B<lond> is sent an unknown command (not in the list above),
                   8437: it replys to the client "unknown_cmd".
1.135     foxr     8438: 
1.74      harris41 8439: 
                   8440: =item UNKNOWN CLIENT
                   8441: 
                   8442: If the anti-spoofing algorithm cannot verify the client,
                   8443: the client is rejected (with a "refused" message sent
                   8444: to the client, and the connection is closed.
                   8445: 
                   8446: =back
1.61      harris41 8447: 
                   8448: =head1 PREREQUISITES
                   8449: 
                   8450: IO::Socket
                   8451: IO::File
                   8452: Apache::File
                   8453: POSIX
                   8454: Crypt::IDEA
                   8455: LWP::UserAgent()
                   8456: GDBM_File
                   8457: Authen::Krb4
1.91      albertel 8458: Authen::Krb5
1.61      harris41 8459: 
                   8460: =head1 COREQUISITES
                   8461: 
                   8462: =head1 OSNAMES
                   8463: 
                   8464: linux
                   8465: 
                   8466: =head1 SCRIPT CATEGORIES
                   8467: 
                   8468: Server/Process
                   8469: 
                   8470: =cut
1.409     foxr     8471: 
                   8472: 
                   8473: =pod
                   8474: 
                   8475: =head1 LOG MESSAGES
                   8476: 
                   8477: The messages below can be emitted in the lond log.  This log is located
                   8478: in ~httpd/perl/logs/lond.log  Many log messages have HTML encapsulation
                   8479: to provide coloring if examined from inside a web page. Some do not.
                   8480: Where color is used, the colors are; Red for sometihhng to get excited
                   8481: about and to follow up on. Yellow for something to keep an eye on to
                   8482: be sure it does not get worse, Green,and Blue for informational items.
                   8483: 
                   8484: In the discussions below, sometimes reference is made to ~httpd
                   8485: when describing file locations.  There isn't really an httpd 
                   8486: user, however there is an httpd directory that gets installed in the
                   8487: place that user home directories go.  On linux, this is usually
                   8488: (always?) /home/httpd.
                   8489: 
                   8490: 
                   8491: Some messages are colorless.  These are usually (not always)
                   8492: Green/Blue color level messages.
                   8493: 
                   8494: =over 2
                   8495: 
                   8496: =item (Red)  LocalConnection rejecting non local: <ip> ne 127.0.0.1
                   8497: 
                   8498: A local connection negotiation was attempted by
                   8499: a host whose IP address was not 127.0.0.1.
                   8500: The socket is closed and the child will exit.
                   8501: lond has three ways to establish an encyrption
                   8502: key with a client:
                   8503: 
                   8504: =over 2
                   8505: 
                   8506: =item local 
                   8507: 
                   8508: The key is written and read from a file.
                   8509: This is only valid for connections from localhost.
                   8510: 
                   8511: =item insecure 
                   8512: 
                   8513: The key is generated by the server and
                   8514: transmitted to the client.
                   8515: 
                   8516: =item  ssl (secure)
                   8517: 
                   8518: An ssl connection is negotiated with the client,
                   8519: the key is generated by the server and sent to the 
                   8520: client across this ssl connection before the
                   8521: ssl connectionis terminated and clear text
                   8522: transmission resumes.
                   8523: 
                   8524: =back
                   8525: 
                   8526: =item (Red) LocalConnection: caller is insane! init = <init> and type = <type>
                   8527: 
                   8528: The client is local but has not sent an initialization
                   8529: string that is the literal "init:local"  The connection
                   8530: is closed and the child exits.
                   8531: 
                   8532: =item Red CRITICAL Can't get key file <error>        
                   8533: 
                   8534: SSL key negotiation is being attempted but the call to
                   8535: lonssl::KeyFile  failed.  This usually means that the
                   8536: configuration file is not correctly defining or protecting
                   8537: the directories/files lonCertificateDirectory or
                   8538: lonnetPrivateKey
                   8539: <error> is a string that describes the reason that
                   8540: the key file could not be located.
                   8541: 
                   8542: =item (Red) CRITICAL  Can't get certificates <error>  
                   8543: 
                   8544: SSL key negotiation failed because we were not able to retrives our certificate
                   8545: or the CA's certificate in the call to lonssl::CertificateFile
                   8546: <error> is the textual reason this failed.  Usual reasons:
                   8547: 
                   8548: =over 2
                   8549:        
                   8550: =item Apache config file for loncapa  incorrect:
                   8551:  
                   8552: one of the variables 
                   8553: lonCertificateDirectory, lonnetCertificateAuthority, or lonnetCertificate
                   8554: undefined or incorrect
                   8555: 
                   8556: =item Permission error:
                   8557: 
                   8558: The directory pointed to by lonCertificateDirectory is not readable by lond
                   8559: 
                   8560: =item Permission error:
                   8561: 
                   8562: Files in the directory pointed to by lonCertificateDirectory are not readable by lond.
                   8563: 
                   8564: =item Installation error:                         
                   8565: 
                   8566: Either the certificate authority file or the certificate have not
                   8567: been installed in lonCertificateDirectory.
                   8568: 
                   8569: =item (Red) CRITICAL SSL Socket promotion failed:  <err> 
                   8570: 
                   8571: The promotion of the connection from plaintext to SSL failed
                   8572: <err> is the reason for the failure.  There are two
                   8573: system calls involved in the promotion (one of which failed), 
                   8574: a dup to produce
                   8575: a second fd on the raw socket over which the encrypted data
                   8576: will flow and IO::SOcket::SSL->new_from_fd which creates
                   8577: the SSL connection on the duped fd.
                   8578: 
                   8579: =item (Blue)   WARNING client did not respond to challenge 
                   8580: 
                   8581: This occurs on an insecure (non SSL) connection negotiation request.
                   8582: lond generates some number from the time, the PID and sends it to
                   8583: the client.  The client must respond by echoing this information back.
                   8584: If the client does not do so, that's a violation of the challenge
                   8585: protocols and the connection will be failed.
                   8586: 
                   8587: =item (Red) No manager table. Nobody can manage!!    
                   8588: 
                   8589: lond has the concept of privileged hosts that
                   8590: can perform remote management function such
                   8591: as update the hosts.tab.   The manager hosts
                   8592: are described in the 
                   8593: ~httpd/lonTabs/managers.tab file.
                   8594: this message is logged if this file is missing.
                   8595: 
                   8596: 
                   8597: =item (Green) Registering manager <dnsname> as <cluster_name> with <ipaddress>
                   8598: 
                   8599: Reports the successful parse and registration
                   8600: of a specific manager. 
                   8601: 
                   8602: =item Green existing host <clustername:dnsname>  
                   8603: 
                   8604: The manager host is already defined in the hosts.tab
                   8605: the information in that table, rather than the info in the
                   8606: manager table will be used to determine the manager's ip.
                   8607: 
                   8608: =item (Red) Unable to craete <filename>                 
                   8609: 
                   8610: lond has been asked to create new versions of an administrative
                   8611: file (by a manager).  When this is done, the new file is created
                   8612: in a temp file and then renamed into place so that there are always
                   8613: usable administrative files, even if the update fails.  This failure
                   8614: message means that the temp file could not be created.
                   8615: The update is abandoned, and the old file is available for use.
                   8616: 
                   8617: =item (Green) CopyFile from <oldname> to <newname> failed
                   8618: 
                   8619: In an update of administrative files, the copy of the existing file to a
                   8620: backup file failed.  The installation of the new file may still succeed,
                   8621: but there will not be a back up file to rever to (this should probably
                   8622: be yellow).
                   8623: 
                   8624: =item (Green) Pushfile: backed up <oldname> to <newname>
                   8625: 
                   8626: See above, the backup of the old administrative file succeeded.
                   8627: 
                   8628: =item (Red)  Pushfile: Unable to install <filename> <reason>
                   8629: 
                   8630: The new administrative file could not be installed.  In this case,
                   8631: the old administrative file is still in use.
                   8632: 
                   8633: =item (Green) Installed new < filename>.                      
                   8634: 
                   8635: The new administrative file was successfullly installed.                                               
                   8636: 
                   8637: =item (Red) Reinitializing lond pid=<pid>                    
                   8638: 
                   8639: The lonc child process <pid> will be sent a USR2 
                   8640: signal.
                   8641: 
                   8642: =item (Red) Reinitializing self                                    
                   8643: 
                   8644: We've been asked to re-read our administrative files,and
                   8645: are doing so.
                   8646: 
                   8647: =item (Yellow) error:Invalid process identifier <ident>  
                   8648: 
                   8649: A reinit command was received, but the target part of the 
                   8650: command was not valid.  It must be either
                   8651: 'lond' or 'lonc' but was <ident>
                   8652: 
                   8653: =item (Green) isValideditCommand checking: Command = <command> Key = <key> newline = <newline>
                   8654: 
                   8655: Checking to see if lond has been handed a valid edit
                   8656: command.  It is possible the edit command is not valid
                   8657: in that case there are no log messages to indicate that.
                   8658: 
                   8659: =item Result of password change for  <username> pwchange_success
                   8660: 
                   8661: The password for <username> was
                   8662: successfully changed.
                   8663: 
                   8664: =item Unable to open <user> passwd to change password
                   8665: 
                   8666: Could not rewrite the 
                   8667: internal password file for a user
                   8668: 
                   8669: =item Result of password change for <user> : <result>
                   8670:                                                                      
                   8671: A unix password change for <user> was attempted 
                   8672: and the pipe returned <result>  
                   8673: 
                   8674: =item LWP GET: <message> for <fname> (<remoteurl>)
                   8675: 
                   8676: The lightweight process fetch for a resource failed
                   8677: with <message> the local filename that should
                   8678: have existed/been created was  <fname> the
                   8679: corresponding URI: <remoteurl>  This is emitted in several
                   8680: places.
                   8681: 
                   8682: =item Unable to move <transname> to <destname>     
                   8683: 
                   8684: From fetch_user_file_handler - the user file was replicated but could not
                   8685: be mv'd to its final location.
                   8686: 
                   8687: =item Looking for <domain> <username>              
                   8688: 
                   8689: From user_has_session_handler - This should be a Debug call instead
                   8690: it indicates lond is about to check whether the specified user has a 
                   8691: session active on the specified domain on the local host.
                   8692: 
                   8693: =item Client <ip> (<name>) hanging up: <input>     
                   8694: 
                   8695: lond has been asked to exit by its client.  The <ip> and <name> identify the
                   8696: client systemand <input> is the full exit command sent to the server.
                   8697: 
                   8698: =item Red CRITICAL: ABNORMAL EXIT. child <pid> for server <hostname> died through a crass with this error->[<message>].
                   8699:                                                  
                   8700: A lond child terminated.  NOte that this termination can also occur when the
                   8701: child receives the QUIT or DIE signals.  <pid> is the process id of the child,
                   8702: <hostname> the host lond is working for, and <message> the reason the child died
                   8703: to the best of our ability to get it (I would guess that any numeric value
                   8704: represents and errno value).  This is immediately followed by
                   8705: 
                   8706: =item  Famous last words: Catching exception - <log> 
                   8707: 
                   8708: Where log is some recent information about the state of the child.
                   8709: 
                   8710: =item Red CRITICAL: TIME OUT <pid>                     
                   8711: 
                   8712: Some timeout occured for server <pid>.  THis is normally a timeout on an LWP
                   8713: doing an HTTP::GET.
                   8714: 
                   8715: =item child <pid> died                              
                   8716: 
                   8717: The reaper caught a SIGCHILD for the lond child process <pid>
                   8718: This should be modified to also display the IP of the dying child
                   8719: $children{$pid}
                   8720: 
                   8721: =item Unknown child 0 died                           
                   8722: A child died but the wait for it returned a pid of zero which really should not
                   8723: ever happen. 
                   8724: 
                   8725: =item Child <which> - <pid> looks like we missed it's death 
                   8726: 
                   8727: When a sigchild is received, the reaper process checks all children to see if they are
                   8728: alive.  If children are dying quite quickly, the lack of signal queuing can mean
                   8729: that a signal hearalds the death of more than one child.  If so this message indicates
                   8730: which other one died. <which> is the ip of a dead child
                   8731: 
                   8732: =item Free socket: <shutdownretval>                
                   8733: 
                   8734: The HUNTSMAN sub was called due to a SIGINT in a child process.  The socket is being shutdown.
                   8735: for whatever reason, <shutdownretval> is printed but in fact shutdown() is not documented
                   8736: to return anything. This is followed by: 
                   8737: 
                   8738: =item Red CRITICAL: Shutting down                       
                   8739: 
                   8740: Just prior to exit.
                   8741: 
                   8742: =item Free socket: <shutdownretval>                 
                   8743: 
                   8744: The HUPSMAN sub was called due to a SIGHUP.  all children get killsed, and lond execs itself.
                   8745: This is followed by:
                   8746: 
                   8747: =item (Red) CRITICAL: Restarting                         
                   8748: 
                   8749: lond is about to exec itself to restart.
                   8750: 
                   8751: =item (Blue) Updating connections                        
                   8752: 
                   8753: (In response to a USR2).  All the children (except the one for localhost)
                   8754: are about to be killed, the hosts tab reread, and Apache reloaded via apachereload.
                   8755: 
                   8756: =item (Blue) UpdateHosts killing child <pid> for ip <ip>   
                   8757: 
                   8758: Due to USR2 as above.
                   8759: 
                   8760: =item (Green) keeping child for ip <ip> (pid = <pid>)    
                   8761: 
                   8762: In response to USR2 as above, the child indicated is not being restarted because
                   8763: it's assumed that we'll always need a child for the localhost.
                   8764: 
                   8765: 
                   8766: =item Going to check on the children                
                   8767: 
                   8768: Parent is about to check on the health of the child processes.
                   8769: Note that this is in response to a USR1 sent to the parent lond.
                   8770: there may be one or more of the next two messages:
                   8771: 
                   8772: =item <pid> is dead                                 
                   8773: 
                   8774: A child that we have in our child hash as alive has evidently died.
                   8775: 
                   8776: =item  Child <pid> did not respond                   
                   8777: 
                   8778: In the health check the child <pid> did not update/produce a pid_.txt
                   8779: file when sent it's USR1 signal.  That process is killed with a 9 signal, as it's
                   8780: assumed to be hung in some un-fixable way.
                   8781: 
                   8782: =item Finished checking children                   
                   8783:  
                   8784: Master processs's USR1 processing is cojmplete.
                   8785: 
                   8786: =item (Red) CRITICAL: ------- Starting ------            
                   8787: 
                   8788: (There are more '-'s on either side).  Lond has forked itself off to 
                   8789: form a new session and is about to start actual initialization.
                   8790: 
                   8791: =item (Green) Attempting to start child (<client>)       
                   8792: 
                   8793: Started a new child process for <client>.  Client is IO::Socket object
                   8794: connected to the child.  This was as a result of a TCP/IP connection from a client.
                   8795: 
                   8796: =item Unable to determine who caller was, getpeername returned nothing
                   8797:                                                   
                   8798: In child process initialization.  either getpeername returned undef or
                   8799: a zero sized object was returned.  Processing continues, but in my opinion,
                   8800: this should be cause for the child to exit.
                   8801: 
                   8802: =item Unable to determine clientip                  
                   8803: 
                   8804: In child process initialization.  The peer address from getpeername was not defined.
                   8805: The client address is stored as "Unavailable" and processing continues.
                   8806: 
                   8807: =item (Yellow) INFO: Connection <ip> <name> connection type = <type>
                   8808:                                                   
                   8809: In child initialization.  A good connectionw as received from <ip>.
                   8810: 
                   8811: =over 2
                   8812: 
                   8813: =item <name> 
                   8814: 
                   8815: is the name of the client from hosts.tab.
                   8816: 
                   8817: =item <type> 
                   8818: 
                   8819: Is the connection type which is either 
                   8820: 
                   8821: =over 2
                   8822: 
                   8823: =item manager 
                   8824: 
                   8825: The connection is from a manager node, not in hosts.tab
                   8826: 
                   8827: =item client  
                   8828: 
                   8829: the connection is from a non-manager in the hosts.tab
                   8830: 
                   8831: =item both
                   8832: 
                   8833: The connection is from a manager in the hosts.tab.
                   8834: 
                   8835: =back
                   8836: 
                   8837: =back
                   8838: 
                   8839: =item (Blue) Certificates not installed -- trying insecure auth
                   8840: 
                   8841: One of the certificate file, key file or
                   8842: certificate authority file could not be found for a client attempting
                   8843: SSL connection intiation.  COnnection will be attemptied in in-secure mode.
                   8844: (this would be a system with an up to date lond that has not gotten a 
                   8845: certificate from us).
                   8846: 
                   8847: =item (Green)  Successful local authentication            
                   8848: 
                   8849: A local connection successfully negotiated the encryption key. 
                   8850: In this case the IDEA key is in a file (that is hopefully well protected).
                   8851: 
                   8852: =item (Green) Successful ssl authentication with <client>  
                   8853: 
                   8854: The client (<client> is the peer's name in hosts.tab), has successfully
                   8855: negotiated an SSL connection with this child process.
                   8856: 
                   8857: =item (Green) Successful insecure authentication with <client>
                   8858:                                                    
                   8859: 
                   8860: The client has successfully negotiated an  insecure connection withthe child process.
                   8861: 
                   8862: =item (Yellow) Attempted insecure connection disallowed    
                   8863: 
                   8864: The client attempted and failed to successfully negotiate a successful insecure
                   8865: connection.  This can happen either because the variable londAllowInsecure is false
                   8866: or undefined, or becuse the child did not successfully echo back the challenge
                   8867: string.
                   8868: 
                   8869: 
                   8870: =back
                   8871: 
1.441     raeburn  8872: =back
                   8873: 
1.409     foxr     8874: 
                   8875: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.