Annotation of loncom/lond, revision 1.231

1.1       albertel    1: #!/usr/bin/perl
                      2: # The LearningOnline Network
                      3: # lond "LON Daemon" Server (port "LOND" 5663)
1.60      www         4: #
1.231   ! foxr        5: # $Id: lond,v 1.230 2004/08/16 11:44:10 foxr 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/';
                     34: use LONCAPA::Configuration;
                     35: 
1.1       albertel   36: use IO::Socket;
                     37: use IO::File;
1.126     albertel   38: #use Apache::File;
1.1       albertel   39: use Symbol;
                     40: use POSIX;
                     41: use Crypt::IDEA;
                     42: use LWP::UserAgent();
1.3       www        43: use GDBM_File;
                     44: use Authen::Krb4;
1.91      albertel   45: use Authen::Krb5;
1.49      albertel   46: use lib '/home/httpd/lib/perl/';
                     47: use localauth;
1.193     raeburn    48: use localenroll;
1.143     foxr       49: use File::Copy;
1.169     foxr       50: use LONCAPA::ConfigFileEdit;
1.200     matthew    51: use LONCAPA::lonlocal;
                     52: use LONCAPA::lonssl;
1.221     albertel   53: use Fcntl qw(:flock);
1.1       albertel   54: 
1.225     foxr       55: my $DEBUG = 0;		       # Non zero to enable debug log entries.
1.77      foxr       56: 
1.57      www        57: my $status='';
                     58: my $lastlog='';
                     59: 
1.231   ! foxr       60: my $VERSION='$Revision: 1.230 $'; #' stupid emacs
1.121     albertel   61: my $remoteVERSION;
1.214     foxr       62: my $currenthostid="default";
1.115     albertel   63: my $currentdomainid;
1.134     albertel   64: 
                     65: my $client;
1.200     matthew    66: my $clientip;			# IP address of client.
                     67: my $clientdns;			# DNS name of client.
                     68: my $clientname;			# LonCAPA name of client.
1.140     foxr       69: 
1.134     albertel   70: my $server;
1.200     matthew    71: my $thisserver;			# DNS of us.
                     72: 
                     73: my $keymode;
1.198     foxr       74: 
1.207     foxr       75: my $cipher;			# Cipher key negotiated with client
                     76: my $tmpsnum = 0;		# Id of tmpputs.
                     77: 
1.178     foxr       78: # 
                     79: #   Connection type is:
                     80: #      client                   - All client actions are allowed
                     81: #      manager                  - only management functions allowed.
                     82: #      both                     - Both management and client actions are allowed
                     83: #
1.161     foxr       84: 
1.178     foxr       85: my $ConnectionType;
1.161     foxr       86: 
1.200     matthew    87: my %hostid;			# ID's for hosts in cluster by ip.
                     88: my %hostdom;			# LonCAPA domain for hosts in cluster.
                     89: my %hostip;			# IPs for hosts in cluster.
                     90: my %hostdns;			# ID's of hosts looked up by DNS name.
1.161     foxr       91: 
1.178     foxr       92: my %managers;			# Ip -> manager names
1.161     foxr       93: 
1.178     foxr       94: my %perlvar;			# Will have the apache conf defined perl vars.
1.134     albertel   95: 
1.178     foxr       96: #
1.207     foxr       97: #   The hash below is used for command dispatching, and is therefore keyed on the request keyword.
                     98: #    Each element of the hash contains a reference to an array that contains:
                     99: #          A reference to a sub that executes the request corresponding to the keyword.
                    100: #          A flag that is true if the request must be encoded to be acceptable.
                    101: #          A mask with bits as follows:
                    102: #                      CLIENT_OK    - Set when the function is allowed by ordinary clients
                    103: #                      MANAGER_OK   - Set when the function is allowed to manager clients.
                    104: #
                    105: my $CLIENT_OK  = 1;
                    106: my $MANAGER_OK = 2;
                    107: my %Dispatcher;
                    108: 
                    109: 
                    110: #
1.178     foxr      111: #  The array below are password error strings."
                    112: #
                    113: my $lastpwderror    = 13;		# Largest error number from lcpasswd.
                    114: my @passwderrors = ("ok",
                    115: 		   "lcpasswd must be run as user 'www'",
                    116: 		   "lcpasswd got incorrect number of arguments",
                    117: 		   "lcpasswd did not get the right nubmer of input text lines",
                    118: 		   "lcpasswd too many simultaneous pwd changes in progress",
                    119: 		   "lcpasswd User does not exist.",
                    120: 		   "lcpasswd Incorrect current passwd",
                    121: 		   "lcpasswd Unable to su to root.",
                    122: 		   "lcpasswd Cannot set new passwd.",
                    123: 		   "lcpasswd Username has invalid characters",
                    124: 		   "lcpasswd Invalid characters in password",
1.223     foxr      125: 		   "lcpasswd User already exists", 
                    126:                    "lcpasswd Something went wrong with user addition.",
                    127: 		    "lcpasswd Password mismatch",
                    128: 		    "lcpasswd Error filename is invalid");
1.97      foxr      129: 
                    130: 
1.178     foxr      131: #  The array below are lcuseradd error strings.:
1.97      foxr      132: 
1.178     foxr      133: my $lastadderror = 13;
                    134: my @adderrors    = ("ok",
                    135: 		    "User ID mismatch, lcuseradd must run as user www",
                    136: 		    "lcuseradd Incorrect number of command line parameters must be 3",
                    137: 		    "lcuseradd Incorrect number of stdinput lines, must be 3",
                    138: 		    "lcuseradd Too many other simultaneous pwd changes in progress",
                    139: 		    "lcuseradd User does not exist",
                    140: 		    "lcuseradd Unable to make www member of users's group",
                    141: 		    "lcuseradd Unable to su to root",
                    142: 		    "lcuseradd Unable to set password",
                    143: 		    "lcuseradd Usrname has invalid characters",
                    144: 		    "lcuseradd Password has an invalid character",
                    145: 		    "lcuseradd User already exists",
                    146: 		    "lcuseradd Could not add user.",
                    147: 		    "lcuseradd Password mismatch");
1.97      foxr      148: 
1.96      foxr      149: 
1.207     foxr      150: 
                    151: #
                    152: #   Statistics that are maintained and dislayed in the status line.
                    153: #
1.212     foxr      154: my $Transactions = 0;		# Number of attempted transactions.
                    155: my $Failures     = 0;		# Number of transcations failed.
1.207     foxr      156: 
                    157: #   ResetStatistics: 
                    158: #      Resets the statistics counters:
                    159: #
                    160: sub ResetStatistics {
                    161:     $Transactions = 0;
                    162:     $Failures     = 0;
                    163: }
                    164: 
1.200     matthew   165: #------------------------------------------------------------------------
                    166: #
                    167: #   LocalConnection
                    168: #     Completes the formation of a locally authenticated connection.
                    169: #     This function will ensure that the 'remote' client is really the
                    170: #     local host.  If not, the connection is closed, and the function fails.
                    171: #     If so, initcmd is parsed for the name of a file containing the
                    172: #     IDEA session key.  The fie is opened, read, deleted and the session
                    173: #     key returned to the caller.
                    174: #
                    175: # Parameters:
                    176: #   $Socket      - Socket open on client.
                    177: #   $initcmd     - The full text of the init command.
                    178: #
                    179: # Implicit inputs:
                    180: #    $clientdns  - The DNS name of the remote client.
                    181: #    $thisserver - Our DNS name.
                    182: #
                    183: # Returns:
                    184: #     IDEA session key on success.
                    185: #     undef on failure.
                    186: #
                    187: sub LocalConnection {
                    188:     my ($Socket, $initcmd) = @_;
                    189:     Debug("Attempting local connection: $initcmd client: $clientdns me: $thisserver");
                    190:     if($clientdns ne $thisserver) {
                    191: 	&logthis('<font color="red"> LocalConnection rejecting non local: '
                    192: 		 ."$clientdns ne $thisserver </font>");
                    193: 	close $Socket;
                    194: 	return undef;
1.224     foxr      195:     }  else {
1.200     matthew   196: 	chomp($initcmd);	# Get rid of \n in filename.
                    197: 	my ($init, $type, $name) = split(/:/, $initcmd);
                    198: 	Debug(" Init command: $init $type $name ");
                    199: 
                    200: 	# Require that $init = init, and $type = local:  Otherwise
                    201: 	# the caller is insane:
                    202: 
                    203: 	if(($init ne "init") && ($type ne "local")) {
                    204: 	    &logthis('<font color = "red"> LocalConnection: caller is insane! '
                    205: 		     ."init = $init, and type = $type </font>");
                    206: 	    close($Socket);;
                    207: 	    return undef;
                    208: 		
                    209: 	}
                    210: 	#  Now get the key filename:
                    211: 
                    212: 	my $IDEAKey = lonlocal::ReadKeyFile($name);
                    213: 	return $IDEAKey;
                    214:     }
                    215: }
                    216: #------------------------------------------------------------------------------
                    217: #
                    218: #  SSLConnection
                    219: #   Completes the formation of an ssh authenticated connection. The
                    220: #   socket is promoted to an ssl socket.  If this promotion and the associated
                    221: #   certificate exchange are successful, the IDEA key is generated and sent
                    222: #   to the remote peer via the SSL tunnel. The IDEA key is also returned to
                    223: #   the caller after the SSL tunnel is torn down.
                    224: #
                    225: # Parameters:
                    226: #   Name              Type             Purpose
                    227: #   $Socket          IO::Socket::INET  Plaintext socket.
                    228: #
                    229: # Returns:
                    230: #    IDEA key on success.
                    231: #    undef on failure.
                    232: #
                    233: sub SSLConnection {
                    234:     my $Socket   = shift;
                    235: 
                    236:     Debug("SSLConnection: ");
                    237:     my $KeyFile         = lonssl::KeyFile();
                    238:     if(!$KeyFile) {
                    239: 	my $err = lonssl::LastError();
                    240: 	&logthis("<font color=\"red\"> CRITICAL"
                    241: 		 ."Can't get key file $err </font>");
                    242: 	return undef;
                    243:     }
                    244:     my ($CACertificate,
                    245: 	$Certificate) = lonssl::CertificateFile();
                    246: 
                    247: 
                    248:     # If any of the key, certificate or certificate authority 
                    249:     # certificate filenames are not defined, this can't work.
                    250: 
                    251:     if((!$Certificate) || (!$CACertificate)) {
                    252: 	my $err = lonssl::LastError();
                    253: 	&logthis("<font color=\"red\"> CRITICAL"
                    254: 		 ."Can't get certificates: $err </font>");
                    255: 
                    256: 	return undef;
                    257:     }
                    258:     Debug("Key: $KeyFile CA: $CACertificate Cert: $Certificate");
                    259: 
                    260:     # Indicate to our peer that we can procede with
                    261:     # a transition to ssl authentication:
                    262: 
                    263:     print $Socket "ok:ssl\n";
                    264: 
                    265:     Debug("Approving promotion -> ssl");
                    266:     #  And do so:
                    267: 
                    268:     my $SSLSocket = lonssl::PromoteServerSocket($Socket,
                    269: 						$CACertificate,
                    270: 						$Certificate,
                    271: 						$KeyFile);
                    272:     if(! ($SSLSocket) ) {	# SSL socket promotion failed.
                    273: 	my $err = lonssl::LastError();
                    274: 	&logthis("<font color=\"red\"> CRITICAL "
                    275: 		 ."SSL Socket promotion failed: $err </font>");
                    276: 	return undef;
                    277:     }
                    278:     Debug("SSL Promotion successful");
                    279: 
                    280:     # 
                    281:     #  The only thing we'll use the socket for is to send the IDEA key
                    282:     #  to the peer:
                    283: 
                    284:     my $Key = lonlocal::CreateCipherKey();
                    285:     print $SSLSocket "$Key\n";
                    286: 
                    287:     lonssl::Close($SSLSocket); 
                    288: 
                    289:     Debug("Key exchange complete: $Key");
                    290: 
                    291:     return $Key;
                    292: }
                    293: #
                    294: #     InsecureConnection: 
                    295: #        If insecure connections are allowd,
                    296: #        exchange a challenge with the client to 'validate' the
                    297: #        client (not really, but that's the protocol):
                    298: #        We produce a challenge string that's sent to the client.
                    299: #        The client must then echo the challenge verbatim to us.
                    300: #
                    301: #  Parameter:
                    302: #      Socket      - Socket open on the client.
                    303: #  Returns:
                    304: #      1           - success.
                    305: #      0           - failure (e.g.mismatch or insecure not allowed).
                    306: #
                    307: sub InsecureConnection {
                    308:     my $Socket  =  shift;
                    309: 
                    310:     #   Don't even start if insecure connections are not allowed.
                    311: 
                    312:     if(! $perlvar{londAllowInsecure}) {	# Insecure connections not allowed.
                    313: 	return 0;
                    314:     }
                    315: 
                    316:     #   Fabricate a challenge string and send it..
                    317: 
                    318:     my $challenge = "$$".time;	# pid + time.
                    319:     print $Socket "$challenge\n";
                    320:     &status("Waiting for challenge reply");
                    321: 
                    322:     my $answer = <$Socket>;
                    323:     $answer    =~s/\W//g;
                    324:     if($challenge eq $answer) {
                    325: 	return 1;
1.224     foxr      326:     } else {
1.200     matthew   327: 	logthis("<font color='blue'>WARNING client did not respond to challenge</font>");
                    328: 	&status("No challenge reqply");
                    329: 	return 0;
                    330:     }
                    331:     
                    332: 
                    333: }
                    334: 
1.96      foxr      335: #
1.140     foxr      336: #   GetCertificate: Given a transaction that requires a certificate,
                    337: #   this function will extract the certificate from the transaction
                    338: #   request.  Note that at this point, the only concept of a certificate
                    339: #   is the hostname to which we are connected.
                    340: #
                    341: #   Parameter:
                    342: #      request   - The request sent by our client (this parameterization may
                    343: #                  need to change when we really use a certificate granting
                    344: #                  authority.
                    345: #
                    346: sub GetCertificate {
                    347:     my $request = shift;
                    348: 
                    349:     return $clientip;
                    350: }
1.161     foxr      351: 
1.178     foxr      352: #
                    353: #   Return true if client is a manager.
                    354: #
                    355: sub isManager {
                    356:     return (($ConnectionType eq "manager") || ($ConnectionType eq "both"));
                    357: }
                    358: #
                    359: #   Return tru if client can do client functions
                    360: #
                    361: sub isClient {
                    362:     return (($ConnectionType eq "client") || ($ConnectionType eq "both"));
                    363: }
1.161     foxr      364: 
                    365: 
1.156     foxr      366: #
                    367: #   ReadManagerTable: Reads in the current manager table. For now this is
                    368: #                     done on each manager authentication because:
                    369: #                     - These authentications are not frequent
                    370: #                     - This allows dynamic changes to the manager table
                    371: #                       without the need to signal to the lond.
                    372: #
                    373: sub ReadManagerTable {
                    374: 
                    375:     #   Clean out the old table first..
                    376: 
1.166     foxr      377:    foreach my $key (keys %managers) {
                    378:       delete $managers{$key};
                    379:    }
                    380: 
                    381:    my $tablename = $perlvar{'lonTabDir'}."/managers.tab";
                    382:    if (!open (MANAGERS, $tablename)) {
                    383:       logthis('<font color="red">No manager table.  Nobody can manage!!</font>');
                    384:       return;
                    385:    }
                    386:    while(my $host = <MANAGERS>) {
                    387:       chomp($host);
                    388:       if ($host =~ "^#") {                  # Comment line.
                    389:          next;
                    390:       }
                    391:       if (!defined $hostip{$host}) { # This is a non cluster member
1.161     foxr      392: 	    #  The entry is of the form:
                    393: 	    #    cluname:hostname
                    394: 	    #  cluname - A 'cluster hostname' is needed in order to negotiate
                    395: 	    #            the host key.
                    396: 	    #  hostname- The dns name of the host.
                    397: 	    #
1.166     foxr      398:           my($cluname, $dnsname) = split(/:/, $host);
                    399:           
                    400:           my $ip = gethostbyname($dnsname);
                    401:           if(defined($ip)) {                 # bad names don't deserve entry.
                    402:             my $hostip = inet_ntoa($ip);
                    403:             $managers{$hostip} = $cluname;
                    404:             logthis('<font color="green"> registering manager '.
                    405:                     "$dnsname as $cluname with $hostip </font>\n");
                    406:          }
                    407:       } else {
                    408:          logthis('<font color="green"> existing host'." $host</font>\n");
                    409:          $managers{$hostip{$host}} = $host;  # Use info from cluster tab if clumemeber
                    410:       }
                    411:    }
1.156     foxr      412: }
1.140     foxr      413: 
                    414: #
                    415: #  ValidManager: Determines if a given certificate represents a valid manager.
                    416: #                in this primitive implementation, the 'certificate' is
                    417: #                just the connecting loncapa client name.  This is checked
                    418: #                against a valid client list in the configuration.
                    419: #
                    420: #                  
                    421: sub ValidManager {
                    422:     my $certificate = shift; 
                    423: 
1.163     foxr      424:     return isManager;
1.140     foxr      425: }
                    426: #
1.143     foxr      427: #  CopyFile:  Called as part of the process of installing a 
                    428: #             new configuration file.  This function copies an existing
                    429: #             file to a backup file.
                    430: # Parameters:
                    431: #     oldfile  - Name of the file to backup.
                    432: #     newfile  - Name of the backup file.
                    433: # Return:
                    434: #     0   - Failure (errno has failure reason).
                    435: #     1   - Success.
                    436: #
                    437: sub CopyFile {
1.192     foxr      438: 
                    439:     my ($oldfile, $newfile) = @_;
1.143     foxr      440: 
                    441:     #  The file must exist:
                    442: 
                    443:     if(-e $oldfile) {
                    444: 
                    445: 	 # Read the old file.
                    446: 
                    447: 	my $oldfh = IO::File->new("< $oldfile");
                    448: 	if(!$oldfh) {
                    449: 	    return 0;
                    450: 	}
                    451: 	my @contents = <$oldfh>;  # Suck in the entire file.
                    452: 
                    453: 	# write the backup file:
                    454: 
                    455: 	my $newfh = IO::File->new("> $newfile");
                    456: 	if(!(defined $newfh)){
                    457: 	    return 0;
                    458: 	}
                    459: 	my $lines = scalar @contents;
                    460: 	for (my $i =0; $i < $lines; $i++) {
                    461: 	    print $newfh ($contents[$i]);
                    462: 	}
                    463: 
                    464: 	$oldfh->close;
                    465: 	$newfh->close;
                    466: 
                    467: 	chmod(0660, $newfile);
                    468: 
                    469: 	return 1;
                    470: 	    
                    471:     } else {
                    472: 	return 0;
                    473:     }
                    474: }
1.157     foxr      475: #
                    476: #  Host files are passed out with externally visible host IPs.
                    477: #  If, for example, we are behind a fire-wall or NAT host, our 
                    478: #  internally visible IP may be different than the externally
                    479: #  visible IP.  Therefore, we always adjust the contents of the
                    480: #  host file so that the entry for ME is the IP that we believe
                    481: #  we have.  At present, this is defined as the entry that
                    482: #  DNS has for us.  If by some chance we are not able to get a
                    483: #  DNS translation for us, then we assume that the host.tab file
                    484: #  is correct.  
                    485: #    BUGBUGBUG - in the future, we really should see if we can
                    486: #       easily query the interface(s) instead.
                    487: # Parameter(s):
                    488: #     contents    - The contents of the host.tab to check.
                    489: # Returns:
                    490: #     newcontents - The adjusted contents.
                    491: #
                    492: #
                    493: sub AdjustHostContents {
                    494:     my $contents  = shift;
                    495:     my $adjusted;
                    496:     my $me        = $perlvar{'lonHostID'};
                    497: 
1.166     foxr      498:  foreach my $line (split(/\n/,$contents)) {
1.157     foxr      499: 	if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) {
                    500: 	    chomp($line);
                    501: 	    my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line);
                    502: 	    if ($id eq $me) {
1.166     foxr      503:           my $ip = gethostbyname($name);
                    504:           my $ipnew = inet_ntoa($ip);
                    505:          $ip = $ipnew;
1.157     foxr      506: 		#  Reconstruct the host line and append to adjusted:
                    507: 		
1.166     foxr      508: 		   my $newline = "$id:$domain:$role:$name:$ip";
                    509: 		   if($maxcon ne "") { # Not all hosts have loncnew tuning params
                    510: 		     $newline .= ":$maxcon:$idleto:$mincon";
                    511: 		   }
                    512: 		   $adjusted .= $newline."\n";
1.157     foxr      513: 		
1.166     foxr      514:       } else {		# Not me, pass unmodified.
                    515: 		   $adjusted .= $line."\n";
                    516:       }
1.157     foxr      517: 	} else {                  # Blank or comment never re-written.
                    518: 	    $adjusted .= $line."\n";	# Pass blanks and comments as is.
                    519: 	}
1.166     foxr      520:  }
                    521:  return $adjusted;
1.157     foxr      522: }
1.143     foxr      523: #
                    524: #   InstallFile: Called to install an administrative file:
                    525: #       - The file is created with <name>.tmp
                    526: #       - The <name>.tmp file is then mv'd to <name>
                    527: #   This lugubrious procedure is done to ensure that we are never without
                    528: #   a valid, even if dated, version of the file regardless of who crashes
                    529: #   and when the crash occurs.
                    530: #
                    531: #  Parameters:
                    532: #       Name of the file
                    533: #       File Contents.
                    534: #  Return:
                    535: #      nonzero - success.
                    536: #      0       - failure and $! has an errno.
                    537: #
                    538: sub InstallFile {
1.192     foxr      539: 
                    540:     my ($Filename, $Contents) = @_;
1.143     foxr      541:     my $TempFile = $Filename.".tmp";
                    542: 
                    543:     #  Open the file for write:
                    544: 
                    545:     my $fh = IO::File->new("> $TempFile"); # Write to temp.
                    546:     if(!(defined $fh)) {
                    547: 	&logthis('<font color="red"> Unable to create '.$TempFile."</font>");
                    548: 	return 0;
                    549:     }
                    550:     #  write the contents of the file:
                    551: 
                    552:     print $fh ($Contents); 
                    553:     $fh->close;			# In case we ever have a filesystem w. locking
                    554: 
                    555:     chmod(0660, $TempFile);
                    556: 
                    557:     # Now we can move install the file in position.
                    558:     
                    559:     move($TempFile, $Filename);
                    560: 
                    561:     return 1;
                    562: }
1.200     matthew   563: 
                    564: 
1.169     foxr      565: #
                    566: #   ConfigFileFromSelector: converts a configuration file selector
                    567: #                 (one of host or domain at this point) into a 
                    568: #                 configuration file pathname.
                    569: #
                    570: #  Parameters:
                    571: #      selector  - Configuration file selector.
                    572: #  Returns:
                    573: #      Full path to the file or undef if the selector is invalid.
                    574: #
                    575: sub ConfigFileFromSelector {
                    576:     my $selector   = shift;
                    577:     my $tablefile;
                    578: 
                    579:     my $tabledir = $perlvar{'lonTabDir'}.'/';
                    580:     if ($selector eq "hosts") {
                    581: 	$tablefile = $tabledir."hosts.tab";
                    582:     } elsif ($selector eq "domain") {
                    583: 	$tablefile = $tabledir."domain.tab";
                    584:     } else {
                    585: 	return undef;
                    586:     }
                    587:     return $tablefile;
1.143     foxr      588: 
1.169     foxr      589: }
1.143     foxr      590: #
1.141     foxr      591: #   PushFile:  Called to do an administrative push of a file.
                    592: #              - Ensure the file being pushed is one we support.
                    593: #              - Backup the old file to <filename.saved>
                    594: #              - Separate the contents of the new file out from the
                    595: #                rest of the request.
                    596: #              - Write the new file.
                    597: #  Parameter:
                    598: #     Request - The entire user request.  This consists of a : separated
                    599: #               string pushfile:tablename:contents.
                    600: #     NOTE:  The contents may have :'s in it as well making things a bit
                    601: #            more interesting... but not much.
                    602: #  Returns:
                    603: #     String to send to client ("ok" or "refused" if bad file).
                    604: #
                    605: sub PushFile {
                    606:     my $request = shift;    
                    607:     my ($command, $filename, $contents) = split(":", $request, 3);
                    608:     
                    609:     #  At this point in time, pushes for only the following tables are
                    610:     #  supported:
                    611:     #   hosts.tab  ($filename eq host).
                    612:     #   domain.tab ($filename eq domain).
                    613:     # Construct the destination filename or reject the request.
                    614:     #
                    615:     # lonManage is supposed to ensure this, however this session could be
                    616:     # part of some elaborate spoof that managed somehow to authenticate.
                    617:     #
                    618: 
1.169     foxr      619: 
                    620:     my $tablefile = ConfigFileFromSelector($filename);
                    621:     if(! (defined $tablefile)) {
1.141     foxr      622: 	return "refused";
                    623:     }
                    624:     #
                    625:     # >copy< the old table to the backup table
                    626:     #        don't rename in case system crashes/reboots etc. in the time
                    627:     #        window between a rename and write.
                    628:     #
                    629:     my $backupfile = $tablefile;
                    630:     $backupfile    =~ s/\.tab$/.old/;
1.143     foxr      631:     if(!CopyFile($tablefile, $backupfile)) {
                    632: 	&logthis('<font color="green"> CopyFile from '.$tablefile." to ".$backupfile." failed </font>");
                    633: 	return "error:$!";
                    634:     }
1.141     foxr      635:     &logthis('<font color="green"> Pushfile: backed up '
                    636: 	    .$tablefile." to $backupfile</font>");
                    637:     
1.157     foxr      638:     #  If the file being pushed is the host file, we adjust the entry for ourself so that the
                    639:     #  IP will be our current IP as looked up in dns.  Note this is only 99% good as it's possible
                    640:     #  to conceive of conditions where we don't have a DNS entry locally.  This is possible in a 
                    641:     #  network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now)
                    642:     #  that possibilty.
                    643: 
                    644:     if($filename eq "host") {
                    645: 	$contents = AdjustHostContents($contents);
                    646:     }
                    647: 
1.141     foxr      648:     #  Install the new file:
                    649: 
1.143     foxr      650:     if(!InstallFile($tablefile, $contents)) {
                    651: 	&logthis('<font color="red"> Pushfile: unable to install '
1.145     foxr      652: 	 .$tablefile." $! </font>");
1.143     foxr      653: 	return "error:$!";
1.224     foxr      654:     } else {
1.143     foxr      655: 	&logthis('<font color="green"> Installed new '.$tablefile
                    656: 		 ."</font>");
                    657: 
                    658:     }
                    659: 
1.141     foxr      660: 
                    661:     #  Indicate success:
                    662:  
                    663:     return "ok";
                    664: 
                    665: }
1.145     foxr      666: 
                    667: #
                    668: #  Called to re-init either lonc or lond.
                    669: #
                    670: #  Parameters:
                    671: #    request   - The full request by the client.  This is of the form
                    672: #                reinit:<process>  
                    673: #                where <process> is allowed to be either of 
                    674: #                lonc or lond
                    675: #
                    676: #  Returns:
                    677: #     The string to be sent back to the client either:
                    678: #   ok         - Everything worked just fine.
                    679: #   error:why  - There was a failure and why describes the reason.
                    680: #
                    681: #
                    682: sub ReinitProcess {
                    683:     my $request = shift;
                    684: 
1.146     foxr      685: 
                    686:     # separate the request (reinit) from the process identifier and
                    687:     # validate it producing the name of the .pid file for the process.
                    688:     #
                    689:     #
                    690:     my ($junk, $process) = split(":", $request);
1.147     foxr      691:     my $processpidfile = $perlvar{'lonDaemons'}.'/logs/';
1.146     foxr      692:     if($process eq 'lonc') {
                    693: 	$processpidfile = $processpidfile."lonc.pid";
1.147     foxr      694: 	if (!open(PIDFILE, "< $processpidfile")) {
                    695: 	    return "error:Open failed for $processpidfile";
                    696: 	}
                    697: 	my $loncpid = <PIDFILE>;
                    698: 	close(PIDFILE);
                    699: 	logthis('<font color="red"> Reinitializing lonc pid='.$loncpid
                    700: 		."</font>");
                    701: 	kill("USR2", $loncpid);
1.146     foxr      702:     } elsif ($process eq 'lond') {
1.147     foxr      703: 	logthis('<font color="red"> Reinitializing self (lond) </font>');
                    704: 	&UpdateHosts;			# Lond is us!!
1.146     foxr      705:     } else {
                    706: 	&logthis('<font color="yellow" Invalid reinit request for '.$process
                    707: 		 ."</font>");
                    708: 	return "error:Invalid process identifier $process";
                    709:     }
1.145     foxr      710:     return 'ok';
                    711: }
1.168     foxr      712: #   Validate a line in a configuration file edit script:
                    713: #   Validation includes:
                    714: #     - Ensuring the command is valid.
                    715: #     - Ensuring the command has sufficient parameters
                    716: #   Parameters:
                    717: #     scriptline - A line to validate (\n has been stripped for what it's worth).
1.167     foxr      718: #
1.168     foxr      719: #   Return:
                    720: #      0     - Invalid scriptline.
                    721: #      1     - Valid scriptline
                    722: #  NOTE:
                    723: #     Only the command syntax is checked, not the executability of the
                    724: #     command.
                    725: #
                    726: sub isValidEditCommand {
                    727:     my $scriptline = shift;
                    728: 
                    729:     #   Line elements are pipe separated:
                    730: 
                    731:     my ($command, $key, $newline)  = split(/\|/, $scriptline);
                    732:     &logthis('<font color="green"> isValideditCommand checking: '.
                    733: 	     "Command = '$command', Key = '$key', Newline = '$newline' </font>\n");
                    734:     
                    735:     if ($command eq "delete") {
                    736: 	#
                    737: 	#   key with no newline.
                    738: 	#
                    739: 	if( ($key eq "") || ($newline ne "")) {
                    740: 	    return 0;		# Must have key but no newline.
                    741: 	} else {
                    742: 	    return 1;		# Valid syntax.
                    743: 	}
1.169     foxr      744:     } elsif ($command eq "replace") {
1.168     foxr      745: 	#
                    746: 	#   key and newline:
                    747: 	#
                    748: 	if (($key eq "") || ($newline eq "")) {
                    749: 	    return 0;
                    750: 	} else {
                    751: 	    return 1;
                    752: 	}
1.169     foxr      753:     } elsif ($command eq "append") {
                    754: 	if (($key ne "") && ($newline eq "")) {
                    755: 	    return 1;
                    756: 	} else {
                    757: 	    return 0;
                    758: 	}
1.168     foxr      759:     } else {
                    760: 	return 0;		# Invalid command.
                    761:     }
                    762:     return 0;			# Should not get here!!!
                    763: }
1.169     foxr      764: #
                    765: #   ApplyEdit - Applies an edit command to a line in a configuration 
                    766: #               file.  It is the caller's responsiblity to validate the
                    767: #               edit line.
                    768: #   Parameters:
                    769: #      $directive - A single edit directive to apply.  
                    770: #                   Edit directives are of the form:
                    771: #                  append|newline      - Appends a new line to the file.
                    772: #                  replace|key|newline - Replaces the line with key value 'key'
                    773: #                  delete|key          - Deletes the line with key value 'key'.
                    774: #      $editor   - A config file editor object that contains the
                    775: #                  file being edited.
                    776: #
                    777: sub ApplyEdit {
1.192     foxr      778: 
                    779:     my ($directive, $editor) = @_;
1.169     foxr      780: 
                    781:     # Break the directive down into its command and its parameters
                    782:     # (at most two at this point.  The meaning of the parameters, if in fact
                    783:     #  they exist depends on the command).
                    784: 
                    785:     my ($command, $p1, $p2) = split(/\|/, $directive);
                    786: 
                    787:     if($command eq "append") {
                    788: 	$editor->Append($p1);	          # p1 - key p2 null.
                    789:     } elsif ($command eq "replace") {
                    790: 	$editor->ReplaceLine($p1, $p2);   # p1 - key p2 = newline.
                    791:     } elsif ($command eq "delete") {
                    792: 	$editor->DeleteLine($p1);         # p1 - key p2 null.
                    793:     } else {			          # Should not get here!!!
                    794: 	die "Invalid command given to ApplyEdit $command"
                    795:     }
                    796: }
                    797: #
                    798: # AdjustOurHost:
                    799: #           Adjusts a host file stored in a configuration file editor object
                    800: #           for the true IP address of this host. This is necessary for hosts
                    801: #           that live behind a firewall.
                    802: #           Those hosts have a publicly distributed IP of the firewall, but
                    803: #           internally must use their actual IP.  We assume that a given
                    804: #           host only has a single IP interface for now.
                    805: # Formal Parameters:
                    806: #     editor   - The configuration file editor to adjust.  This
                    807: #                editor is assumed to contain a hosts.tab file.
                    808: # Strategy:
                    809: #    - Figure out our hostname.
                    810: #    - Lookup the entry for this host.
                    811: #    - Modify the line to contain our IP
                    812: #    - Do a replace for this host.
                    813: sub AdjustOurHost {
                    814:     my $editor        = shift;
                    815: 
                    816:     # figure out who I am.
                    817: 
                    818:     my $myHostName    = $perlvar{'lonHostID'}; # LonCAPA hostname.
                    819: 
                    820:     #  Get my host file entry.
                    821: 
                    822:     my $ConfigLine    = $editor->Find($myHostName);
                    823:     if(! (defined $ConfigLine)) {
                    824: 	die "AdjustOurHost - no entry for me in hosts file $myHostName";
                    825:     }
                    826:     # figure out my IP:
                    827:     #   Use the config line to get my hostname.
                    828:     #   Use gethostbyname to translate that into an IP address.
                    829:     #
                    830:     my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine);
                    831:     my $BinaryIp = gethostbyname($name);
                    832:     my $ip       = inet_ntoa($ip);
                    833:     #
                    834:     #  Reassemble the config line from the elements in the list.
                    835:     #  Note that if the loncnew items were not present before, they will
                    836:     #  be now even if they would be empty
                    837:     #
                    838:     my $newConfigLine = $id;
                    839:     foreach my $item ($domain, $role, $name, $ip, $maxcon, $idleto, $mincon) {
                    840: 	$newConfigLine .= ":".$item;
                    841:     }
                    842:     #  Replace the line:
                    843: 
                    844:     $editor->ReplaceLine($id, $newConfigLine);
                    845:     
                    846: }
                    847: #
                    848: #   ReplaceConfigFile:
                    849: #              Replaces a configuration file with the contents of a
                    850: #              configuration file editor object.
                    851: #              This is done by:
                    852: #              - Copying the target file to <filename>.old
                    853: #              - Writing the new file to <filename>.tmp
                    854: #              - Moving <filename.tmp>  -> <filename>
                    855: #              This laborious process ensures that the system is never without
                    856: #              a configuration file that's at least valid (even if the contents
                    857: #              may be dated).
                    858: #   Parameters:
                    859: #        filename   - Name of the file to modify... this is a full path.
                    860: #        editor     - Editor containing the file.
                    861: #
                    862: sub ReplaceConfigFile {
1.192     foxr      863:     
                    864:     my ($filename, $editor) = @_;
1.168     foxr      865: 
1.169     foxr      866:     CopyFile ($filename, $filename.".old");
                    867: 
                    868:     my $contents  = $editor->Get(); # Get the contents of the file.
                    869: 
                    870:     InstallFile($filename, $contents);
                    871: }
1.168     foxr      872: #   
                    873: #
                    874: #   Called to edit a configuration table  file
1.167     foxr      875: #   Parameters:
                    876: #      request           - The entire command/request sent by lonc or lonManage
                    877: #   Return:
                    878: #      The reply to send to the client.
1.168     foxr      879: #
1.167     foxr      880: sub EditFile {
                    881:     my $request = shift;
                    882: 
                    883:     #  Split the command into it's pieces:  edit:filetype:script
                    884: 
1.168     foxr      885:     my ($request, $filetype, $script) = split(/:/, $request,3);	# : in script
1.167     foxr      886: 
                    887:     #  Check the pre-coditions for success:
                    888: 
                    889:     if($request != "edit") {	# Something is amiss afoot alack.
                    890: 	return "error:edit request detected, but request != 'edit'\n";
                    891:     }
                    892:     if( ($filetype ne "hosts")  &&
                    893: 	($filetype ne "domain")) {
                    894: 	return "error:edit requested with invalid file specifier: $filetype \n";
                    895:     }
                    896: 
                    897:     #   Split the edit script and check it's validity.
1.168     foxr      898: 
                    899:     my @scriptlines = split(/\n/, $script);  # one line per element.
                    900:     my $linecount   = scalar(@scriptlines);
                    901:     for(my $i = 0; $i < $linecount; $i++) {
                    902: 	chomp($scriptlines[$i]);
                    903: 	if(!isValidEditCommand($scriptlines[$i])) {
                    904: 	    return "error:edit with bad script line: '$scriptlines[$i]' \n";
                    905: 	}
                    906:     }
1.145     foxr      907: 
1.167     foxr      908:     #   Execute the edit operation.
1.169     foxr      909:     #   - Create a config file editor for the appropriate file and 
                    910:     #   - execute each command in the script:
                    911:     #
                    912:     my $configfile = ConfigFileFromSelector($filetype);
                    913:     if (!(defined $configfile)) {
                    914: 	return "refused\n";
                    915:     }
                    916:     my $editor = ConfigFileEdit->new($configfile);
1.167     foxr      917: 
1.169     foxr      918:     for (my $i = 0; $i < $linecount; $i++) {
                    919: 	ApplyEdit($scriptlines[$i], $editor);
                    920:     }
                    921:     # If the file is the host file, ensure that our host is
                    922:     # adjusted to have our ip:
                    923:     #
                    924:     if($filetype eq "host") {
                    925: 	AdjustOurHost($editor);
                    926:     }
                    927:     #  Finally replace the current file with our file.
                    928:     #
                    929:     ReplaceConfigFile($configfile, $editor);
1.167     foxr      930: 
                    931:     return "ok\n";
                    932: }
1.207     foxr      933: 
                    934: #---------------------------------------------------------------
                    935: #
                    936: # Manipulation of hash based databases (factoring out common code
                    937: # for later use as we refactor.
                    938: #
                    939: #  Ties a domain level resource file to a hash.
                    940: #  If requested a history entry is created in the associated hist file.
                    941: #
                    942: #  Parameters:
                    943: #     domain    - Name of the domain in which the resource file lives.
                    944: #     namespace - Name of the hash within that domain.
                    945: #     how       - How to tie the hash (e.g. GDBM_WRCREAT()).
                    946: #     loghead   - Optional parameter, if present a log entry is created
                    947: #                 in the associated history file and this is the first part
                    948: #                  of that entry.
                    949: #     logtail   - Goes along with loghead,  The actual logentry is of the
                    950: #                 form $loghead:<timestamp>:logtail.
                    951: # Returns:
                    952: #    Reference to a hash bound to the db file or alternatively undef
                    953: #    if the tie failed.
                    954: #
1.209     albertel  955: sub tie_domain_hash {
1.210     albertel  956:     my ($domain,$namespace,$how,$loghead,$logtail) = @_;
1.207     foxr      957:     
                    958:     # Filter out any whitespace in the domain name:
                    959:     
                    960:     $domain =~ s/\W//g;
                    961:     
                    962:     # We have enough to go on to tie the hash:
                    963:     
                    964:     my $user_top_dir   = $perlvar{'lonUsersDir'};
                    965:     my $domain_dir     = $user_top_dir."/$domain";
                    966:     my $resource_file  = $domain_dir."/$namespace.db";
                    967:     my %hash;
                    968:     if(tie(%hash, 'GDBM_File', $resource_file, $how, 0640)) {
1.211     albertel  969: 	if (defined($loghead)) {	# Need to log the operation.
1.210     albertel  970: 	    my $logFh = IO::File->new(">>$domain_dir/$namespace.hist");
1.207     foxr      971: 	    if($logFh) {
                    972: 		my $timestamp = time;
                    973: 		print $logFh "$loghead:$timestamp:$logtail\n";
                    974: 	    }
1.210     albertel  975: 	    $logFh->close;
1.207     foxr      976: 	}
                    977: 	return \%hash;		# Return the tied hash.
1.210     albertel  978:     } else {
1.207     foxr      979: 	return undef;		# Tie failed.
                    980:     }
                    981: }
                    982: 
                    983: #
                    984: #   Ties a user's resource file to a hash.  
                    985: #   If necessary, an appropriate history
                    986: #   log file entry is made as well.
                    987: #   This sub factors out common code from the subs that manipulate
                    988: #   the various gdbm files that keep keyword value pairs.
                    989: # Parameters:
                    990: #   domain       - Name of the domain the user is in.
                    991: #   user         - Name of the 'current user'.
                    992: #   namespace    - Namespace representing the file to tie.
                    993: #   how          - What the tie is done to (e.g. GDBM_WRCREAT().
                    994: #   loghead      - Optional first part of log entry if there may be a
                    995: #                  history file.
                    996: #   what         - Optional tail of log entry if there may be a history
                    997: #                  file.
                    998: # Returns:
                    999: #   hash to which the database is tied.  It's up to the caller to untie.
                   1000: #   undef if the has could not be tied.
                   1001: #
1.210     albertel 1002: sub tie_user_hash {
                   1003:     my ($domain,$user,$namespace,$how,$loghead,$what) = @_;
1.207     foxr     1004: 
                   1005:     $namespace=~s/\//\_/g;	# / -> _
                   1006:     $namespace=~s/\W//g;		# whitespace eliminated.
                   1007:     my $proname     = propath($domain, $user);
                   1008:    
                   1009:     #  Tie the database.
                   1010:     
                   1011:     my %hash;
                   1012:     if(tie(%hash, 'GDBM_File', "$proname/$namespace.db",
                   1013: 	   $how, 0640)) {
1.209     albertel 1014: 	# If this is a namespace for which a history is kept,
                   1015: 	# make the history log entry:    
1.211     albertel 1016: 	if (($namespace =~/^nohist\_/) && (defined($loghead))) {
1.209     albertel 1017: 	    my $args = scalar @_;
                   1018: 	    Debug(" Opening history: $namespace $args");
                   1019: 	    my $hfh = IO::File->new(">>$proname/$namespace.hist"); 
                   1020: 	    if($hfh) {
                   1021: 		my $now = time;
                   1022: 		print $hfh "$loghead:$now:$what\n";
                   1023: 	    }
1.210     albertel 1024: 	    $hfh->close;
1.209     albertel 1025: 	}
1.207     foxr     1026: 	return \%hash;
1.209     albertel 1027:     } else {
1.207     foxr     1028: 	return undef;
                   1029:     }
                   1030:     
                   1031: }
1.214     foxr     1032: 
                   1033: #--------------------- Request Handlers --------------------------------------------
                   1034: #
1.215     foxr     1035: #   By convention each request handler registers itself prior to the sub 
                   1036: #   declaration:
1.214     foxr     1037: #
                   1038: 
1.216     foxr     1039: #++
                   1040: #
1.214     foxr     1041: #  Handles ping requests.
                   1042: #  Parameters:
                   1043: #      $cmd    - the actual keyword that invoked us.
                   1044: #      $tail   - the tail of the request that invoked us.
                   1045: #      $replyfd- File descriptor connected to the client
                   1046: #  Implicit Inputs:
                   1047: #      $currenthostid - Global variable that carries the name of the host we are
                   1048: #                       known as.
                   1049: #  Returns:
                   1050: #      1       - Ok to continue processing.
                   1051: #      0       - Program should exit.
                   1052: #  Side effects:
                   1053: #      Reply information is sent to the client.
                   1054: 
                   1055: sub ping_handler {
                   1056:     my ($cmd, $tail, $client) = @_;
                   1057:     Debug("$cmd $tail $client .. $currenthostid:");
                   1058:    
                   1059:     Reply( $client,"$currenthostid\n","$cmd:$tail");
                   1060:    
                   1061:     return 1;
                   1062: }
                   1063: &register_handler("ping", \&ping_handler, 0, 1, 1);       # Ping unencoded, client or manager.
                   1064: 
1.216     foxr     1065: #++
1.215     foxr     1066: #
                   1067: # Handles pong requests.  Pong replies with our current host id, and
                   1068: #                         the results of a ping sent to us via our lonc.
                   1069: #
                   1070: # Parameters:
                   1071: #      $cmd    - the actual keyword that invoked us.
                   1072: #      $tail   - the tail of the request that invoked us.
                   1073: #      $replyfd- File descriptor connected to the client
                   1074: #  Implicit Inputs:
                   1075: #      $currenthostid - Global variable that carries the name of the host we are
                   1076: #                       connected to.
                   1077: #  Returns:
                   1078: #      1       - Ok to continue processing.
                   1079: #      0       - Program should exit.
                   1080: #  Side effects:
                   1081: #      Reply information is sent to the client.
                   1082: 
                   1083: sub pong_handler {
                   1084:     my ($cmd, $tail, $replyfd) = @_;
                   1085: 
                   1086:     my $reply=&reply("ping",$clientname);
                   1087:     &Reply( $replyfd, "$currenthostid:$reply\n", "$cmd:$tail"); 
                   1088:     return 1;
                   1089: }
                   1090: &register_handler("pong", \&pong_handler, 0, 1, 1);       # Pong unencoded, client or manager
                   1091: 
1.216     foxr     1092: #++
                   1093: #      Called to establish an encrypted session key with the remote client.
                   1094: #      Note that with secure lond, in most cases this function is never
                   1095: #      invoked.  Instead, the secure session key is established either
                   1096: #      via a local file that's locked down tight and only lives for a short
                   1097: #      time, or via an ssl tunnel...and is generated from a bunch-o-random
                   1098: #      bits from /dev/urandom, rather than the predictable pattern used by
                   1099: #      by this sub.  This sub is only used in the old-style insecure
                   1100: #      key negotiation.
                   1101: # Parameters:
                   1102: #      $cmd    - the actual keyword that invoked us.
                   1103: #      $tail   - the tail of the request that invoked us.
                   1104: #      $replyfd- File descriptor connected to the client
                   1105: #  Implicit Inputs:
                   1106: #      $currenthostid - Global variable that carries the name of the host
                   1107: #                       known as.
                   1108: #      $clientname    - Global variable that carries the name of the hsot we're connected to.
                   1109: #  Returns:
                   1110: #      1       - Ok to continue processing.
                   1111: #      0       - Program should exit.
                   1112: #  Implicit Outputs:
                   1113: #      Reply information is sent to the client.
                   1114: #      $cipher is set with a reference to a new IDEA encryption object.
                   1115: #
                   1116: sub establish_key_handler {
                   1117:     my ($cmd, $tail, $replyfd) = @_;
                   1118: 
                   1119:     my $buildkey=time.$$.int(rand 100000);
                   1120:     $buildkey=~tr/1-6/A-F/;
                   1121:     $buildkey=int(rand 100000).$buildkey.int(rand 100000);
                   1122:     my $key=$currenthostid.$clientname;
                   1123:     $key=~tr/a-z/A-Z/;
                   1124:     $key=~tr/G-P/0-9/;
                   1125:     $key=~tr/Q-Z/0-9/;
                   1126:     $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
                   1127:     $key=substr($key,0,32);
                   1128:     my $cipherkey=pack("H32",$key);
                   1129:     $cipher=new IDEA $cipherkey;
                   1130:     &Reply($replyfd, "$buildkey\n", "$cmd:$tail"); 
                   1131:    
                   1132:     return 1;
                   1133: 
                   1134: }
                   1135: &register_handler("ekey", \&establish_key_handler, 0, 1,1);
                   1136: 
1.215     foxr     1137: 
1.217     foxr     1138: #     Handler for the load command.  Returns the current system load average
                   1139: #     to the requestor.
                   1140: #
                   1141: # Parameters:
                   1142: #      $cmd    - the actual keyword that invoked us.
                   1143: #      $tail   - the tail of the request that invoked us.
                   1144: #      $replyfd- File descriptor connected to the client
                   1145: #  Implicit Inputs:
                   1146: #      $currenthostid - Global variable that carries the name of the host
                   1147: #                       known as.
                   1148: #      $clientname    - Global variable that carries the name of the hsot we're connected to.
                   1149: #  Returns:
                   1150: #      1       - Ok to continue processing.
                   1151: #      0       - Program should exit.
                   1152: #  Side effects:
                   1153: #      Reply information is sent to the client.
                   1154: sub load_handler {
                   1155:     my ($cmd, $tail, $replyfd) = @_;
                   1156: 
                   1157:    # Get the load average from /proc/loadavg and calculate it as a percentage of
                   1158:    # the allowed load limit as set by the perl global variable lonLoadLim
                   1159: 
                   1160:     my $loadavg;
                   1161:     my $loadfile=IO::File->new('/proc/loadavg');
                   1162:    
                   1163:     $loadavg=<$loadfile>;
                   1164:     $loadavg =~ s/\s.*//g;                      # Extract the first field only.
                   1165:    
                   1166:     my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};
                   1167: 
                   1168:     &Reply( $replyfd, "$loadpercent\n", "$cmd:$tail");
                   1169:    
                   1170:     return 1;
                   1171: }
                   1172: register_handler("load", \&load_handler, 0, 1, 0);
                   1173: 
                   1174: #
                   1175: #   Process the userload request.  This sub returns to the client the current
                   1176: #  user load average.  It can be invoked either by clients or managers.
                   1177: #
                   1178: # Parameters:
                   1179: #      $cmd    - the actual keyword that invoked us.
                   1180: #      $tail   - the tail of the request that invoked us.
                   1181: #      $replyfd- File descriptor connected to the client
                   1182: #  Implicit Inputs:
                   1183: #      $currenthostid - Global variable that carries the name of the host
                   1184: #                       known as.
                   1185: #      $clientname    - Global variable that carries the name of the hsot we're connected to.
                   1186: #  Returns:
                   1187: #      1       - Ok to continue processing.
                   1188: #      0       - Program should exit
                   1189: # Implicit inputs:
                   1190: #     whatever the userload() function requires.
                   1191: #  Implicit outputs:
                   1192: #     the reply is written to the client.
                   1193: #
                   1194: sub user_load_handler {
                   1195:     my ($cmd, $tail, $replyfd) = @_;
                   1196: 
                   1197:     my $userloadpercent=&userload();
                   1198:     &Reply($replyfd, "$userloadpercent\n", "$cmd:$tail");
                   1199:     
                   1200:     return 1;
                   1201: }
                   1202: register_handler("userload", \&user_load_handler, 0, 1, 0);
                   1203: 
1.218     foxr     1204: #   Process a request for the authorization type of a user:
                   1205: #   (userauth).
                   1206: #
                   1207: # Parameters:
                   1208: #      $cmd    - the actual keyword that invoked us.
                   1209: #      $tail   - the tail of the request that invoked us.
                   1210: #      $replyfd- File descriptor connected to the client
                   1211: #  Returns:
                   1212: #      1       - Ok to continue processing.
                   1213: #      0       - Program should exit
                   1214: # Implicit outputs:
                   1215: #    The user authorization type is written to the client.
                   1216: #
                   1217: sub user_authorization_type {
                   1218:     my ($cmd, $tail, $replyfd) = @_;
                   1219:    
                   1220:     my $userinput = "$cmd:$tail";
                   1221:    
                   1222:     #  Pull the domain and username out of the command tail.
1.222     foxr     1223:     # and call get_auth_type to determine the authentication type.
1.218     foxr     1224:    
                   1225:     my ($udom,$uname)=split(/:/,$tail);
1.222     foxr     1226:     my $result = &get_auth_type($udom, $uname);
1.218     foxr     1227:     if($result eq "nouser") {
                   1228: 	&Failure( $replyfd, "unknown_user\n", $userinput);
                   1229:     } else {
                   1230: 	#
1.222     foxr     1231: 	# We only want to pass the second field from get_auth_type
1.218     foxr     1232: 	# for ^krb.. otherwise we'll be handing out the encrypted
                   1233: 	# password for internals e.g.
                   1234: 	#
                   1235: 	my ($type,$otherinfo) = split(/:/,$result);
                   1236: 	if($type =~ /^krb/) {
                   1237: 	    $type = $result;
                   1238: 	}
1.222     foxr     1239: 	&Reply( $replyfd, "$type:\n", $userinput);
1.218     foxr     1240:     }
                   1241:   
                   1242:     return 1;
                   1243: }
                   1244: &register_handler("currentauth", \&user_authorization_type, 1, 1, 0);
                   1245: 
                   1246: #   Process a request by a manager to push a hosts or domain table 
                   1247: #   to us.  We pick apart the command and pass it on to the subs
                   1248: #   that already exist to do this.
                   1249: #
                   1250: # Parameters:
                   1251: #      $cmd    - the actual keyword that invoked us.
                   1252: #      $tail   - the tail of the request that invoked us.
                   1253: #      $client - File descriptor connected to the client
                   1254: #  Returns:
                   1255: #      1       - Ok to continue processing.
                   1256: #      0       - Program should exit
                   1257: # Implicit Output:
                   1258: #    a reply is written to the client.
                   1259: 
                   1260: sub push_file_handler {
                   1261:     my ($cmd, $tail, $client) = @_;
                   1262: 
                   1263:     my $userinput = "$cmd:$tail";
                   1264: 
                   1265:     # At this time we only know that the IP of our partner is a valid manager
                   1266:     # the code below is a hook to do further authentication (e.g. to resolve
                   1267:     # spoofing).
                   1268: 
                   1269:     my $cert = &GetCertificate($userinput);
                   1270:     if(&ValidManager($cert)) { 
                   1271: 
                   1272: 	# Now presumably we have the bona fides of both the peer host and the
                   1273: 	# process making the request.
                   1274:       
                   1275: 	my $reply = &PushFile($userinput);
                   1276: 	&Reply($client, "$reply\n", $userinput);
                   1277: 
                   1278:     } else {
                   1279: 	&Failure( $client, "refused\n", $userinput);
                   1280:     } 
1.219     foxr     1281:     return 1;
1.218     foxr     1282: }
                   1283: &register_handler("pushfile", \&push_file_handler, 1, 0, 1);
                   1284: 
                   1285: 
                   1286: 
                   1287: #   Process a reinit request.  Reinit requests that either
                   1288: #   lonc or lond be reinitialized so that an updated 
                   1289: #   host.tab or domain.tab can be processed.
                   1290: #
                   1291: # Parameters:
                   1292: #      $cmd    - the actual keyword that invoked us.
                   1293: #      $tail   - the tail of the request that invoked us.
                   1294: #      $client - File descriptor connected to the client
                   1295: #  Returns:
                   1296: #      1       - Ok to continue processing.
                   1297: #      0       - Program should exit
                   1298: #  Implicit output:
                   1299: #     a reply is sent to the client.
                   1300: #
                   1301: sub reinit_process_handler {
                   1302:     my ($cmd, $tail, $client) = @_;
                   1303:    
                   1304:     my $userinput = "$cmd:$tail";
                   1305:    
                   1306:     my $cert = &GetCertificate($userinput);
                   1307:     if(&ValidManager($cert)) {
                   1308: 	chomp($userinput);
                   1309: 	my $reply = &ReinitProcess($userinput);
                   1310: 	&Reply( $client,  "$reply\n", $userinput);
                   1311:     } else {
                   1312: 	&Failure( $client, "refused\n", $userinput);
                   1313:     }
                   1314:     return 1;
                   1315: }
                   1316: 
                   1317: &register_handler("reinit", \&reinit_process_handler, 1, 0, 1);
                   1318: 
                   1319: #  Process the editing script for a table edit operation.
                   1320: #  the editing operation must be encrypted and requested by
                   1321: #  a manager host.
                   1322: #
                   1323: # Parameters:
                   1324: #      $cmd    - the actual keyword that invoked us.
                   1325: #      $tail   - the tail of the request that invoked us.
                   1326: #      $client - File descriptor connected to the client
                   1327: #  Returns:
                   1328: #      1       - Ok to continue processing.
                   1329: #      0       - Program should exit
                   1330: #  Implicit output:
                   1331: #     a reply is sent to the client.
                   1332: #
                   1333: sub edit_table_handler {
                   1334:     my ($command, $tail, $client) = @_;
                   1335:    
                   1336:     my $userinput = "$command:$tail";
                   1337: 
                   1338:     my $cert = &GetCertificate($userinput);
                   1339:     if(&ValidManager($cert)) {
                   1340: 	my($filetype, $script) = split(/:/, $tail);
                   1341: 	if (($filetype eq "hosts") || 
                   1342: 	    ($filetype eq "domain")) {
                   1343: 	    if($script ne "") {
                   1344: 		&Reply($client,              # BUGBUG - EditFile
                   1345: 		      &EditFile($userinput), #   could fail.
                   1346: 		      $userinput);
                   1347: 	    } else {
                   1348: 		&Failure($client,"refused\n",$userinput);
                   1349: 	    }
                   1350: 	} else {
                   1351: 	    &Failure($client,"refused\n",$userinput);
                   1352: 	}
                   1353:     } else {
                   1354: 	&Failure($client,"refused\n",$userinput);
                   1355:     }
                   1356:     return 1;
                   1357: }
                   1358: register_handler("edit", \&edit_table_handler, 1, 0, 1);
                   1359: 
                   1360: 
1.220     foxr     1361: #
                   1362: #   Authenticate a user against the LonCAPA authentication
                   1363: #   database.  Note that there are several authentication
                   1364: #   possibilities:
                   1365: #   - unix     - The user can be authenticated against the unix
                   1366: #                password file.
                   1367: #   - internal - The user can be authenticated against a purely 
                   1368: #                internal per user password file.
                   1369: #   - kerberos - The user can be authenticated against either a kerb4 or kerb5
                   1370: #                ticket granting authority.
                   1371: #   - user     - The person tailoring LonCAPA can supply a user authentication
                   1372: #                mechanism that is per system.
                   1373: #
                   1374: # Parameters:
                   1375: #    $cmd      - The command that got us here.
                   1376: #    $tail     - Tail of the command (remaining parameters).
                   1377: #    $client   - File descriptor connected to client.
                   1378: # Returns
                   1379: #     0        - Requested to exit, caller should shut down.
                   1380: #     1        - Continue processing.
                   1381: # Implicit inputs:
                   1382: #    The authentication systems describe above have their own forms of implicit
                   1383: #    input into the authentication process that are described above.
                   1384: #
                   1385: sub authenticate_handler {
                   1386:     my ($cmd, $tail, $client) = @_;
                   1387: 
                   1388:     
                   1389:     #  Regenerate the full input line 
                   1390:     
                   1391:     my $userinput  = $cmd.":".$tail;
                   1392:     
                   1393:     #  udom    - User's domain.
                   1394:     #  uname   - Username.
                   1395:     #  upass   - User's password.
                   1396:     
                   1397:     my ($udom,$uname,$upass)=split(/:/,$tail);
                   1398:     &Debug(" Authenticate domain = $udom, user = $uname, password = $upass");
                   1399:     chomp($upass);
                   1400:     $upass=&unescape($upass);
                   1401: 
                   1402:     my $pwdcorrect = &validate_user($udom, $uname, $upass);
                   1403:     if($pwdcorrect) {
                   1404: 	&Reply( $client, "authorized\n", $userinput);
                   1405: 	#
                   1406: 	#  Bad credentials: Failed to authorize
                   1407: 	#
                   1408:     } else {
                   1409: 	&Failure( $client, "non_authorized\n", $userinput);
                   1410:     }
                   1411: 
                   1412:     return 1;
                   1413: }
1.218     foxr     1414: 
1.220     foxr     1415: register_handler("auth", \&authenticate_handler, 1, 1, 0);
1.214     foxr     1416: 
1.222     foxr     1417: #
                   1418: #   Change a user's password.  Note that this function is complicated by
                   1419: #   the fact that a user may be authenticated in more than one way:
                   1420: #   At present, we are not able to change the password for all types of
                   1421: #   authentication methods.  Only for:
                   1422: #      unix    - unix password or shadow passoword style authentication.
                   1423: #      local   - Locally written authentication mechanism.
                   1424: #   For now, kerb4 and kerb5 password changes are not supported and result
                   1425: #   in an error.
                   1426: # FUTURE WORK:
                   1427: #    Support kerberos passwd changes?
                   1428: # Parameters:
                   1429: #    $cmd      - The command that got us here.
                   1430: #    $tail     - Tail of the command (remaining parameters).
                   1431: #    $client   - File descriptor connected to client.
                   1432: # Returns
                   1433: #     0        - Requested to exit, caller should shut down.
                   1434: #     1        - Continue processing.
                   1435: # Implicit inputs:
                   1436: #    The authentication systems describe above have their own forms of implicit
                   1437: #    input into the authentication process that are described above.
                   1438: sub change_password_handler {
                   1439:     my ($cmd, $tail, $client) = @_;
                   1440: 
                   1441:     my $userinput = $cmd.":".$tail;           # Reconstruct client's string.
                   1442: 
                   1443:     #
                   1444:     #  udom  - user's domain.
                   1445:     #  uname - Username.
                   1446:     #  upass - Current password.
                   1447:     #  npass - New password.
                   1448:    
                   1449:     my ($udom,$uname,$upass,$npass)=split(/:/,$tail);
                   1450: 
                   1451:     $upass=&unescape($upass);
                   1452:     $npass=&unescape($npass);
                   1453:     &Debug("Trying to change password for $uname");
                   1454: 
                   1455:     # First require that the user can be authenticated with their
                   1456:     # old password:
                   1457: 
                   1458:     my $validated = &validate_user($udom, $uname, $upass);
                   1459:     if($validated) {
                   1460: 	my $realpasswd  = &get_auth_type($udom, $uname); # Defined since authd.
                   1461: 	
                   1462: 	my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
                   1463: 	if ($howpwd eq 'internal') {
                   1464: 	    &Debug("internal auth");
                   1465: 	    my $salt=time;
                   1466: 	    $salt=substr($salt,6,2);
                   1467: 	    my $ncpass=crypt($npass,$salt);
                   1468: 	    if(&rewrite_password_file($udom, $uname, "internal:$ncpass")) {
                   1469: 		&logthis("Result of password change for "
                   1470: 			 ."$uname: pwchange_success");
                   1471: 		&Reply($client, "ok\n", $userinput);
                   1472: 	    } else {
                   1473: 		&logthis("Unable to open $uname passwd "               
                   1474: 			 ."to change password");
                   1475: 		&Failure( $client, "non_authorized\n",$userinput);
                   1476: 	    }
                   1477: 	} elsif ($howpwd eq 'unix') {
                   1478: 	    # Unix means we have to access /etc/password
                   1479: 	    &Debug("auth is unix");
                   1480: 	    my $execdir=$perlvar{'lonDaemons'};
                   1481: 	    &Debug("Opening lcpasswd pipeline");
                   1482: 	    my $pf = IO::File->new("|$execdir/lcpasswd > "
                   1483: 				   ."$perlvar{'lonDaemons'}"
                   1484: 				   ."/logs/lcpasswd.log");
                   1485: 	    print $pf "$uname\n$npass\n$npass\n";
                   1486: 	    close $pf;
                   1487: 	    my $err = $?;
                   1488: 	    my $result = ($err>0 ? 'pwchange_failure' : 'ok');
                   1489: 	    &logthis("Result of password change for $uname: ".
                   1490: 		     &lcpasswdstrerror($?));
                   1491: 	    &Reply($client, "$result\n", $userinput);
                   1492: 	} else {
                   1493: 	    # this just means that the current password mode is not
                   1494: 	    # one we know how to change (e.g the kerberos auth modes or
                   1495: 	    # locally written auth handler).
                   1496: 	    #
                   1497: 	    &Failure( $client, "auth_mode_error\n", $userinput);
                   1498: 	}  
                   1499: 	
1.224     foxr     1500:     } else {
1.222     foxr     1501: 	&Failure( $client, "non_authorized\n", $userinput);
                   1502:     }
                   1503: 
                   1504:     return 1;
                   1505: }
                   1506: register_handler("passwd", \&change_password_handler, 1, 1, 0);
                   1507: 
                   1508: 
1.225     foxr     1509: #
                   1510: #   Create a new user.  User in this case means a lon-capa user.
                   1511: #   The user must either already exist in some authentication realm
                   1512: #   like kerberos or the /etc/passwd.  If not, a user completely local to
                   1513: #   this loncapa system is created.
                   1514: #
                   1515: # Parameters:
                   1516: #    $cmd      - The command that got us here.
                   1517: #    $tail     - Tail of the command (remaining parameters).
                   1518: #    $client   - File descriptor connected to client.
                   1519: # Returns
                   1520: #     0        - Requested to exit, caller should shut down.
                   1521: #     1        - Continue processing.
                   1522: # Implicit inputs:
                   1523: #    The authentication systems describe above have their own forms of implicit
                   1524: #    input into the authentication process that are described above.
                   1525: sub add_user_handler {
                   1526: 
                   1527:     my ($cmd, $tail, $client) = @_;
                   1528: 
                   1529: 
                   1530:     my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
                   1531:     my $userinput = $cmd.":".$tail; # Reconstruct the full request line.
                   1532: 
                   1533:     &Debug("cmd =".$cmd." $udom =".$udom." uname=".$uname);
                   1534: 
                   1535: 
                   1536:     if($udom eq $currentdomainid) { # Reject new users for other domains...
                   1537: 	
                   1538: 	my $oldumask=umask(0077);
                   1539: 	chomp($npass);
                   1540: 	$npass=&unescape($npass);
                   1541: 	my $passfilename  = &password_path($udom, $uname);
                   1542: 	&Debug("Password file created will be:".$passfilename);
                   1543: 	if (-e $passfilename) {
                   1544: 	    &Failure( $client, "already_exists\n", $userinput);
                   1545: 	} else {
                   1546: 	    my @fpparts=split(/\//,$passfilename);
                   1547: 	    my $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2];
                   1548: 	    my $fperror='';
                   1549: 	    for (my $i=3;$i<= ($#fpparts-1);$i++) {
                   1550: 		$fpnow.='/'.$fpparts[$i]; 
                   1551: 		unless (-e $fpnow) {
                   1552: 		    &logthis("mkdir $fpnow");
                   1553: 		    unless (mkdir($fpnow,0777)) {
                   1554: 			$fperror="error: ".($!+0)." mkdir failed while attempting "
                   1555: 			    ."makeuser";
                   1556: 		    }
                   1557: 		}
                   1558: 	    }
                   1559: 	    unless ($fperror) {
                   1560: 		my $result=&make_passwd_file($uname, $umode,$npass, $passfilename);
                   1561: 		&Reply($client, $result, $userinput);     #BUGBUG - could be fail
                   1562: 	    } else {
                   1563: 		&Failure($client, "$fperror\n", $userinput);
                   1564: 	    }
                   1565: 	}
                   1566: 	umask($oldumask);
                   1567:     }  else {
                   1568: 	&Failure($client, "not_right_domain\n",
                   1569: 		$userinput);	# Even if we are multihomed.
                   1570:     
                   1571:     }
                   1572:     return 1;
                   1573: 
                   1574: }
                   1575: &register_handler("makeuser", \&add_user_handler, 1, 1, 0);
                   1576: 
                   1577: #
                   1578: #   Change the authentication method of a user.  Note that this may
                   1579: #   also implicitly change the user's password if, for example, the user is
                   1580: #   joining an existing authentication realm.  Known authentication realms at
                   1581: #   this time are:
                   1582: #    internal   - Purely internal password file (only loncapa knows this user)
                   1583: #    local      - Institutionally written authentication module.
                   1584: #    unix       - Unix user (/etc/passwd with or without /etc/shadow).
                   1585: #    kerb4      - kerberos version 4
                   1586: #    kerb5      - kerberos version 5
                   1587: #
                   1588: # Parameters:
                   1589: #    $cmd      - The command that got us here.
                   1590: #    $tail     - Tail of the command (remaining parameters).
                   1591: #    $client   - File descriptor connected to client.
                   1592: # Returns
                   1593: #     0        - Requested to exit, caller should shut down.
                   1594: #     1        - Continue processing.
                   1595: # Implicit inputs:
                   1596: #    The authentication systems describe above have their own forms of implicit
                   1597: #    input into the authentication process that are described above.
                   1598: #
                   1599: sub change_authentication_handler {
                   1600: 
                   1601:     my ($cmd, $tail, $client) = @_;
                   1602:    
                   1603:     my $userinput  = "$cmd:$tail";              # Reconstruct user input.
                   1604: 
                   1605:     my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
                   1606:     &Debug("cmd = ".$cmd." domain= ".$udom."uname =".$uname." umode= ".$umode);
                   1607:     if ($udom ne $currentdomainid) {
                   1608: 	&Failure( $client, "not_right_domain\n", $client);
                   1609:     } else {
                   1610: 	
                   1611: 	chomp($npass);
                   1612: 	
                   1613: 	$npass=&unescape($npass);
                   1614: 	my $passfilename = &password_path($udom, $uname);
                   1615: 	if ($passfilename) {	# Not allowed to create a new user!!
                   1616: 	    my $result=&make_passwd_file($uname, $umode,$npass,$passfilename);
                   1617: 	    &Reply($client, $result, $userinput);
                   1618: 	} else {	       
                   1619: 	    &Failure($client, "non_authorized", $userinput); # Fail the user now.
                   1620: 	}
                   1621:     }
                   1622:     return 1;
                   1623: }
                   1624: &register_handler("changeuserauth", \&change_authentication_handler, 1,1, 0);
                   1625: 
                   1626: #
                   1627: #   Determines if this is the home server for a user.  The home server
                   1628: #   for a user will have his/her lon-capa passwd file.  Therefore all we need
                   1629: #   to do is determine if this file exists.
                   1630: #
                   1631: # Parameters:
                   1632: #    $cmd      - The command that got us here.
                   1633: #    $tail     - Tail of the command (remaining parameters).
                   1634: #    $client   - File descriptor connected to client.
                   1635: # Returns
                   1636: #     0        - Requested to exit, caller should shut down.
                   1637: #     1        - Continue processing.
                   1638: # Implicit inputs:
                   1639: #    The authentication systems describe above have their own forms of implicit
                   1640: #    input into the authentication process that are described above.
                   1641: #
                   1642: sub is_home_handler {
                   1643:     my ($cmd, $tail, $client) = @_;
                   1644:    
                   1645:     my $userinput  = "$cmd:$tail";
                   1646:    
                   1647:     my ($udom,$uname)=split(/:/,$tail);
                   1648:     chomp($uname);
                   1649:     my $passfile = &password_filename($udom, $uname);
                   1650:     if($passfile) {
                   1651: 	&Reply( $client, "found\n", $userinput);
                   1652:     } else {
                   1653: 	&Failure($client, "not_found\n", $userinput);
                   1654:     }
                   1655:     return 1;
                   1656: }
                   1657: &register_handler("home", \&is_home_handler, 0,1,0);
                   1658: 
                   1659: #
                   1660: #   Process an update request for a resource?? I think what's going on here is
                   1661: #   that a resource has been modified that we hold a subscription to.
                   1662: #   If the resource is not local, then we must update, or at least invalidate our
                   1663: #   cached copy of the resource. 
                   1664: #   FUTURE WORK:
                   1665: #      I need to look at this logic carefully.  My druthers would be to follow
                   1666: #      typical caching logic, and simple invalidate the cache, drop any subscription
                   1667: #      an let the next fetch start the ball rolling again... however that may
                   1668: #      actually be more difficult than it looks given the complex web of
                   1669: #      proxy servers.
                   1670: # Parameters:
                   1671: #    $cmd      - The command that got us here.
                   1672: #    $tail     - Tail of the command (remaining parameters).
                   1673: #    $client   - File descriptor connected to client.
                   1674: # Returns
                   1675: #     0        - Requested to exit, caller should shut down.
                   1676: #     1        - Continue processing.
                   1677: # Implicit inputs:
                   1678: #    The authentication systems describe above have their own forms of implicit
                   1679: #    input into the authentication process that are described above.
                   1680: #
                   1681: sub update_resource_handler {
                   1682: 
                   1683:     my ($cmd, $tail, $client) = @_;
                   1684:    
                   1685:     my $userinput = "$cmd:$tail";
                   1686:    
                   1687:     my $fname= $tail;		# This allows interactive testing
                   1688: 
                   1689: 
                   1690:     my $ownership=ishome($fname);
                   1691:     if ($ownership eq 'not_owner') {
                   1692: 	if (-e $fname) {
                   1693: 	    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
                   1694: 		$atime,$mtime,$ctime,$blksize,$blocks)=stat($fname);
                   1695: 	    my $now=time;
                   1696: 	    my $since=$now-$atime;
                   1697: 	    if ($since>$perlvar{'lonExpire'}) {
                   1698: 		my $reply=&reply("unsub:$fname","$clientname");
                   1699: 		unlink("$fname");
                   1700: 	    } else {
                   1701: 		my $transname="$fname.in.transfer";
                   1702: 		my $remoteurl=&reply("sub:$fname","$clientname");
                   1703: 		my $response;
                   1704: 		alarm(120);
                   1705: 		{
                   1706: 		    my $ua=new LWP::UserAgent;
                   1707: 		    my $request=new HTTP::Request('GET',"$remoteurl");
                   1708: 		    $response=$ua->request($request,$transname);
                   1709: 		}
                   1710: 		alarm(0);
                   1711: 		if ($response->is_error()) {
                   1712: 		    unlink($transname);
                   1713: 		    my $message=$response->status_line;
                   1714: 		    &logthis("LWP GET: $message for $fname ($remoteurl)");
                   1715: 		} else {
                   1716: 		    if ($remoteurl!~/\.meta$/) {
                   1717: 			alarm(120);
                   1718: 			{
                   1719: 			    my $ua=new LWP::UserAgent;
                   1720: 			    my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta');
                   1721: 			    my $mresponse=$ua->request($mrequest,$fname.'.meta');
                   1722: 			    if ($mresponse->is_error()) {
                   1723: 				unlink($fname.'.meta');
                   1724: 			    }
                   1725: 			}
                   1726: 			alarm(0);
                   1727: 		    }
                   1728: 		    rename($transname,$fname);
                   1729: 		}
                   1730: 	    }
                   1731: 	    &Reply( $client, "ok\n", $userinput);
                   1732: 	} else {
                   1733: 	    &Failure($client, "not_found\n", $userinput);
                   1734: 	}
                   1735:     } else {
                   1736: 	&Failure($client, "rejected\n", $userinput);
                   1737:     }
                   1738:     return 1;
                   1739: }
                   1740: &register_handler("update", \&update_resource_handler, 0 ,1, 0);
                   1741: 
                   1742: #
1.226     foxr     1743: #   Fetch a user file from a remote server to the user's home directory
                   1744: #   userfiles subdir.
1.225     foxr     1745: # Parameters:
                   1746: #    $cmd      - The command that got us here.
                   1747: #    $tail     - Tail of the command (remaining parameters).
                   1748: #    $client   - File descriptor connected to client.
                   1749: # Returns
                   1750: #     0        - Requested to exit, caller should shut down.
                   1751: #     1        - Continue processing.
                   1752: #
                   1753: sub fetch_user_file_handler {
                   1754: 
                   1755:     my ($cmd, $tail, $client) = @_;
                   1756: 
                   1757:     my $userinput = "$cmd:$tail";
                   1758:     my $fname           = $tail;
                   1759:     my ($udom,$uname,$ufile)=split(/\//,$fname);
                   1760:     my $udir=&propath($udom,$uname).'/userfiles';
                   1761:     unless (-e $udir) {
                   1762: 	mkdir($udir,0770); 
                   1763:     }
                   1764:     if (-e $udir) {
                   1765: 	$ufile=~s/^[\.\~]+//;
                   1766: 	$ufile=~s/\///g;
                   1767: 	my $destname=$udir.'/'.$ufile;
                   1768: 	my $transname=$udir.'/'.$ufile.'.in.transit';
                   1769: 	my $remoteurl='http://'.$clientip.'/userfiles/'.$fname;
                   1770: 	my $response;
                   1771: 	alarm(120);
                   1772: 	{
                   1773: 	    my $ua=new LWP::UserAgent;
                   1774: 	    my $request=new HTTP::Request('GET',"$remoteurl");
                   1775: 	    $response=$ua->request($request,$transname);
                   1776: 	}
                   1777: 	alarm(0);
                   1778: 	if ($response->is_error()) {
                   1779: 	    unlink($transname);
                   1780: 	    my $message=$response->status_line;
                   1781: 	    &logthis("LWP GET: $message for $fname ($remoteurl)");
                   1782: 	    &Failure($client, "failed\n", $userinput);
                   1783: 	} else {
                   1784: 	    if (!rename($transname,$destname)) {
                   1785: 		&logthis("Unable to move $transname to $destname");
                   1786: 		unlink($transname);
                   1787: 		&Failure($client, "failed\n", $userinput);
                   1788: 	    } else {
                   1789: 		&Reply($client, "ok\n", $userinput);
                   1790: 	    }
                   1791: 	}   
                   1792:     } else {
                   1793: 	&Failure($client, "not_home\n", $userinput);
                   1794:     }
                   1795:     return 1;
                   1796: }
                   1797: &register_handler("fetchuserfile", \&fetch_user_file_handler, 0, 1, 0);
                   1798: 
1.226     foxr     1799: #
                   1800: #   Remove a file from a user's home directory userfiles subdirectory.
                   1801: # Parameters:
                   1802: #    cmd   - the Lond request keyword that got us here.
                   1803: #    tail  - the part of the command past the keyword.
                   1804: #    client- File descriptor connected with the client.
                   1805: #
                   1806: # Returns:
                   1807: #    1    - Continue processing.
                   1808: 
                   1809: sub remove_user_file_handler {
                   1810:     my ($cmd, $tail, $client) = @_;
                   1811: 
                   1812:     my ($fname) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent.
                   1813: 
                   1814:     my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
                   1815:     if ($ufile =~m|/\.\./|) {
                   1816: 	# any files paths with /../ in them refuse 
                   1817: 	# to deal with
                   1818: 	&Failure($client, "refused\n", "$cmd:$tail");
                   1819:     } else {
                   1820: 	my $udir = &propath($udom,$uname);
                   1821: 	if (-e $udir) {
                   1822: 	    my $file=$udir.'/userfiles/'.$ufile;
                   1823: 	    if (-e $file) {
                   1824: 		unlink($file);
                   1825: 		if (-e $file) {
                   1826: 		    &Failure($client, "failed\n", "$cmd:$tail");
                   1827: 		} else {
                   1828: 		    &Reply($client, "ok\n", "$cmd:$tail");
                   1829: 		}
                   1830: 	    } else {
                   1831: 		&Failure($client, "not_found\n", "$cmd:$tail");
                   1832: 	    }
                   1833: 	} else {
                   1834: 	    &Failure($client, "not_home\n", "$cmd:$tail");
                   1835: 	}
                   1836:     }
                   1837:     return 1;
                   1838: }
                   1839: &register_handler("removeuserfile", \&remove_user_file_handler, 0,1,0);
                   1840: 
1.227     foxr     1841: 
                   1842: #
                   1843: #  Authenticate access to a user file by checking the user's 
                   1844: #  session token(?)
                   1845: #
                   1846: # Parameters:
                   1847: #   cmd      - The request keyword that dispatched to tus.
                   1848: #   tail     - The tail of the request (colon separated parameters).
                   1849: #   client   - Filehandle open on the client.
                   1850: # Return:
                   1851: #    1.
                   1852: 
                   1853: sub token_auth_user_file_handler {
                   1854:     my ($cmd, $tail, $client) = @_;
                   1855: 
                   1856:     my ($fname, $session) = split(/:/, $tail);
                   1857:     
                   1858:     chomp($session);
                   1859:     my $reply='non_auth';
                   1860:     if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'.
                   1861: 	     $session.'.id')) {
                   1862: 	while (my $line=<ENVIN>) {
                   1863: 	    if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply='ok'; }
                   1864: 	}
                   1865: 	close(ENVIN);
                   1866: 	&Reply($client, $reply);
                   1867:     } else {
                   1868: 	&Failure($client, "invalid_token\n", "$cmd:$tail");
                   1869:     }
                   1870:     return 1;
                   1871: 
                   1872: }
                   1873: 
                   1874: &register_handler("tokenauthuserfile", \&token_auth_user_file_handler, 0,1,0);
1.229     foxr     1875: 
                   1876: 
                   1877: #
                   1878: #   Unsubscribe from a resource.
                   1879: #
                   1880: # Parameters:
                   1881: #    $cmd      - The command that got us here.
                   1882: #    $tail     - Tail of the command (remaining parameters).
                   1883: #    $client   - File descriptor connected to client.
                   1884: # Returns
                   1885: #     0        - Requested to exit, caller should shut down.
                   1886: #     1        - Continue processing.
                   1887: #
                   1888: sub unsubscribe_handler {
                   1889:     my ($cmd, $tail, $client) = @_;
                   1890: 
                   1891:     my $userinput= "$cmd:$tail";
                   1892:     
                   1893:     my ($fname) = split(/:/,$tail); # Split in case there's extrs.
                   1894: 
                   1895:     &Debug("Unsubscribing $fname");
                   1896:     if (-e $fname) {
                   1897: 	&Debug("Exists");
                   1898: 	&Reply($client, &unsub($fname,$clientip), $userinput);
                   1899:     } else {
                   1900: 	&Failure($client, "not_found\n", $userinput);
                   1901:     }
                   1902:     return 1;
                   1903: }
                   1904: &register_handler("unsub", \&unsubscribe_handler, 0, 1, 0);
1.230     foxr     1905: #   Subscribe to a resource
                   1906: #
                   1907: # Parameters:
                   1908: #    $cmd      - The command that got us here.
                   1909: #    $tail     - Tail of the command (remaining parameters).
                   1910: #    $client   - File descriptor connected to client.
                   1911: # Returns
                   1912: #     0        - Requested to exit, caller should shut down.
                   1913: #     1        - Continue processing.
                   1914: #
                   1915: sub subscribe_handler {
                   1916:     my ($cmd, $tail, $client)= @_;
                   1917: 
                   1918:     my $userinput  = "$cmd:$tail";
                   1919: 
                   1920:     &Reply( $client, &subscribe($userinput,$clientip), $userinput);
                   1921: 
                   1922:     return 1;
                   1923: }
                   1924: &register_handler("sub", \&subscribe_handler, 0, 1, 0);
                   1925: 
                   1926: #
                   1927: #   Determine the version of a resource (?) Or is it return
                   1928: #   the top version of the resource?  Not yet clear from the
                   1929: #   code in currentversion.
                   1930: #
                   1931: # Parameters:
                   1932: #    $cmd      - The command that got us here.
                   1933: #    $tail     - Tail of the command (remaining parameters).
                   1934: #    $client   - File descriptor connected to client.
                   1935: # Returns
                   1936: #     0        - Requested to exit, caller should shut down.
                   1937: #     1        - Continue processing.
                   1938: #
                   1939: sub current_version_handler {
                   1940:     my ($cmd, $tail, $client) = @_;
                   1941: 
                   1942:     my $userinput= "$cmd:$tail";
                   1943:    
                   1944:     my $fname   = $tail;
                   1945:     &Reply( $client, &currentversion($fname)."\n", $userinput);
                   1946:     return 1;
                   1947: 
                   1948: }
                   1949: &register_handler("currentversion", \&current_version_handler, 0, 1, 0);
                   1950: 
                   1951: #  Make an entry in a user's activity log.
                   1952: #
                   1953: # Parameters:
                   1954: #    $cmd      - The command that got us here.
                   1955: #    $tail     - Tail of the command (remaining parameters).
                   1956: #    $client   - File descriptor connected to client.
                   1957: # Returns
                   1958: #     0        - Requested to exit, caller should shut down.
                   1959: #     1        - Continue processing.
                   1960: #
                   1961: sub activity_log_handler {
                   1962:     my ($cmd, $tail, $client) = @_;
                   1963: 
                   1964: 
                   1965:     my $userinput= "$cmd:$tail";
                   1966: 
                   1967:     my ($udom,$uname,$what)=split(/:/,$tail);
                   1968:     chomp($what);
                   1969:     my $proname=&propath($udom,$uname);
                   1970:     my $now=time;
                   1971:     my $hfh;
                   1972:     if ($hfh=IO::File->new(">>$proname/activity.log")) { 
                   1973: 	print $hfh "$now:$clientname:$what\n";
                   1974: 	&Reply( $client, "ok\n", $userinput); 
                   1975:     } else {
                   1976: 	&Failure($client, "error: ".($!+0)." IO::File->new Failed "
                   1977: 		 ."while attempting log\n", 
                   1978: 		 $userinput);
                   1979:     }
                   1980: 
                   1981:     return 1;
                   1982: }
                   1983: register_handler("log", \&activity_log_handler, 0, 1, 0);
                   1984: 
                   1985: #
                   1986: #   Put a namespace entry in a user profile hash.
                   1987: #   My druthers would be for this to be an encrypted interaction too.
                   1988: #   anything that might be an inadvertent covert channel about either
                   1989: #   user authentication or user personal information....
                   1990: #
                   1991: # Parameters:
                   1992: #    $cmd      - The command that got us here.
                   1993: #    $tail     - Tail of the command (remaining parameters).
                   1994: #    $client   - File descriptor connected to client.
                   1995: # Returns
                   1996: #     0        - Requested to exit, caller should shut down.
                   1997: #     1        - Continue processing.
                   1998: #
                   1999: sub put_user_profile_entry {
                   2000:     my ($cmd, $tail, $client)  = @_;
1.229     foxr     2001: 
1.230     foxr     2002:     my $userinput = "$cmd:$tail";
                   2003:     
                   2004:     my ($udom,$uname,$namespace,$what) =split(/:/,$tail);
                   2005:     if ($namespace ne 'roles') {
                   2006: 	chomp($what);
                   2007: 	my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   2008: 				  &GDBM_WRCREAT(),"P",$what);
                   2009: 	if($hashref) {
                   2010: 	    my @pairs=split(/\&/,$what);
                   2011: 	    foreach my $pair (@pairs) {
                   2012: 		my ($key,$value)=split(/=/,$pair);
                   2013: 		$hashref->{$key}=$value;
                   2014: 	    }
                   2015: 	    if (untie(%$hashref)) {
                   2016: 		&Reply( $client, "ok\n", $userinput);
                   2017: 	    } else {
                   2018: 		&Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
                   2019: 			"while attempting put\n", 
                   2020: 			$userinput);
                   2021: 	    }
                   2022: 	} else {
                   2023: 	    &Failure( $client, "error: ".($!)." tie(GDBM) Failed ".
                   2024: 		     "while attempting put\n", $userinput);
                   2025: 	}
                   2026:     } else {
                   2027:         &Failure( $client, "refused\n", $userinput);
                   2028:     }
                   2029:     
                   2030:     return 1;
                   2031: }
                   2032: &register_handler("put", \&put_user_profile_entry, 0, 1, 0);
                   2033: 
                   2034: # 
                   2035: #   Increment a profile entry in the user history file.
                   2036: #   The history contains keyword value pairs.  In this case,
                   2037: #   The value itself is a pair of numbers.  The first, the current value
                   2038: #   the second an increment that this function applies to the current
                   2039: #   value.
                   2040: #
                   2041: # Parameters:
                   2042: #    $cmd      - The command that got us here.
                   2043: #    $tail     - Tail of the command (remaining parameters).
                   2044: #    $client   - File descriptor connected to client.
                   2045: # Returns
                   2046: #     0        - Requested to exit, caller should shut down.
                   2047: #     1        - Continue processing.
                   2048: #
                   2049: sub increment_user_value_handler {
                   2050:     my ($cmd, $tail, $client) = @_;
                   2051:     
                   2052:     my $userinput   = "$cmd:$tail";
                   2053:     
                   2054:     my ($udom,$uname,$namespace,$what) =split(/:/,$tail);
                   2055:     if ($namespace ne 'roles') {
                   2056:         chomp($what);
                   2057: 	my $hashref = &tie_user_hash($udom, $uname,
                   2058: 				     $namespace, &GDBM_WRCREAT(),
                   2059: 				     "P",$what);
                   2060: 	if ($hashref) {
                   2061: 	    my @pairs=split(/\&/,$what);
                   2062: 	    foreach my $pair (@pairs) {
                   2063: 		my ($key,$value)=split(/=/,$pair);
                   2064: 		# We could check that we have a number...
                   2065: 		if (! defined($value) || $value eq '') {
                   2066: 		    $value = 1;
                   2067: 		}
                   2068: 		$hashref->{$key}+=$value;
                   2069: 	    }
                   2070: 	    if (untie(%$hashref)) {
                   2071: 		&Reply( $client, "ok\n", $userinput);
                   2072: 	    } else {
                   2073: 		&Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
                   2074: 			 "while attempting inc\n", $userinput);
                   2075: 	    }
                   2076: 	} else {
                   2077: 	    &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
                   2078: 		     "while attempting inc\n", $userinput);
                   2079: 	}
                   2080:     } else {
                   2081: 	&Failure($client, "refused\n", $userinput);
                   2082:     }
                   2083:     
                   2084:     return 1;
                   2085: }
                   2086: &register_handler("inc", \&increment_user_value_handler, 0, 1, 0);
                   2087: 
                   2088: 
                   2089: #
                   2090: #   Put a new role for a user.  Roles are LonCAPA's packaging of permissions.
                   2091: #   Each 'role' a user has implies a set of permissions.  Adding a new role
                   2092: #   for a person grants the permissions packaged with that role
                   2093: #   to that user when the role is selected.
                   2094: #
                   2095: # Parameters:
                   2096: #    $cmd       - The command string (rolesput).
                   2097: #    $tail      - The remainder of the request line.  For rolesput this
                   2098: #                 consists of a colon separated list that contains:
                   2099: #                 The domain and user that is granting the role (logged).
                   2100: #                 The domain and user that is getting the role.
                   2101: #                 The roles being granted as a set of & separated pairs.
                   2102: #                 each pair a key value pair.
                   2103: #    $client    - File descriptor connected to the client.
                   2104: # Returns:
                   2105: #     0         - If the daemon should exit
                   2106: #     1         - To continue processing.
                   2107: #
                   2108: #
                   2109: sub roles_put_handler {
                   2110:     my ($cmd, $tail, $client) = @_;
                   2111: 
                   2112:     my $userinput  = "$cmd:$tail";
                   2113: 
                   2114:     my ( $exedom, $exeuser, $udom, $uname,  $what) = split(/:/,$tail);
                   2115:     
                   2116: 
                   2117:     my $namespace='roles';
                   2118:     chomp($what);
                   2119:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
                   2120: 				 &GDBM_WRCREAT(), "P",
                   2121: 				 "$exedom:$exeuser:$what");
                   2122:     #
                   2123:     #  Log the attempt to set a role.  The {}'s here ensure that the file 
                   2124:     #  handle is open for the minimal amount of time.  Since the flush
                   2125:     #  is done on close this improves the chances the log will be an un-
                   2126:     #  corrupted ordered thing.
                   2127:     if ($hashref) {
                   2128: 	my @pairs=split(/\&/,$what);
                   2129: 	foreach my $pair (@pairs) {
                   2130: 	    my ($key,$value)=split(/=/,$pair);
                   2131: 	    &manage_permissions($key, $udom, $uname,
                   2132: 			       &get_auth_type( $udom, $uname));
                   2133: 	    $hashref->{$key}=$value;
                   2134: 	}
                   2135: 	if (untie($hashref)) {
                   2136: 	    &Reply($client, "ok\n", $userinput);
                   2137: 	} else {
                   2138: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
                   2139: 		     "while attempting rolesput\n", $userinput);
                   2140: 	}
                   2141:     } else {
                   2142: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
                   2143: 		 "while attempting rolesput\n", $userinput);
                   2144:     }
                   2145:     return 1;
                   2146: }
                   2147: &register_handler("rolesput", \&roles_put_handler, 1,1,0);  # Encoded client only.
                   2148: 
                   2149: #
1.231   ! foxr     2150: #   Deletes (removes) a role for a user.   This is equivalent to removing
        !          2151: #  a permissions package associated with the role from the user's profile.
        !          2152: #
        !          2153: # Parameters:
        !          2154: #     $cmd                 - The command (rolesdel)
        !          2155: #     $tail                - The remainder of the request line. This consists
        !          2156: #                             of:
        !          2157: #                             The domain and user requesting the change (logged)
        !          2158: #                             The domain and user being changed.
        !          2159: #                             The roles being revoked.  These are shipped to us
        !          2160: #                             as a bunch of & separated role name keywords.
        !          2161: #     $client              - The file handle open on the client.
        !          2162: # Returns:
        !          2163: #     1                    - Continue processing
        !          2164: #     0                    - Exit.
        !          2165: #
        !          2166: sub roles_delete_handler {
        !          2167:     my ($cmd, $tail, $client)  = @_;
        !          2168: 
        !          2169:     my $userinput    = "$cmd:$tail";
        !          2170:    
        !          2171:     my ($exedom,$exeuser,$udom,$uname,$what)=split(/:/,$tail);
        !          2172:     &Debug("cmd = ".$cmd." exedom= ".$exedom."user = ".$exeuser." udom=".$udom.
        !          2173: 	   "what = ".$what);
        !          2174:     my $namespace='roles';
        !          2175:     chomp($what);
        !          2176:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2177: 				 &GDBM_WRCREAT(), "D",
        !          2178: 				 "$exedom:$exeuser:$what");
        !          2179:     
        !          2180:     if ($hashref) {
        !          2181: 	my @rolekeys=split(/\&/,$what);
        !          2182: 	
        !          2183: 	foreach my $key (@rolekeys) {
        !          2184: 	    delete $hashref->{$key};
        !          2185: 	}
        !          2186: 	if (untie(%$hashref)) {
        !          2187: 	    &Reply($client, "ok\n", $userinput);
        !          2188: 	} else {
        !          2189: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2190: 		     "while attempting rolesdel\n", $userinput);
        !          2191: 	}
        !          2192:     } else {
        !          2193:         &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2194: 		 "while attempting rolesdel\n", $userinput);
        !          2195:     }
        !          2196:     
        !          2197:     return 1;
        !          2198: }
        !          2199: &register_handler("rolesdel", \&roles_delete_handler, 1,1, 0); # Encoded client only
        !          2200: 
        !          2201: # Unencrypted get from a user's profile database.  See 
        !          2202: # GetProfileEntryEncrypted for a version that does end-to-end encryption.
        !          2203: # This function retrieves a keyed item from a specific named database in the
        !          2204: # user's directory.
        !          2205: #
        !          2206: # Parameters:
        !          2207: #   $cmd             - Command request keyword (get).
        !          2208: #   $tail            - Tail of the command.  This is a colon separated list
        !          2209: #                      consisting of the domain and username that uniquely
        !          2210: #                      identifies the profile,
        !          2211: #                      The 'namespace' which selects the gdbm file to 
        !          2212: #                      do the lookup in, 
        !          2213: #                      & separated list of keys to lookup.  Note that
        !          2214: #                      the values are returned as an & separated list too.
        !          2215: #   $client          - File descriptor open on the client.
        !          2216: # Returns:
        !          2217: #   1       - Continue processing.
        !          2218: #   0       - Exit.
        !          2219: #
        !          2220: sub get_profile_entry {
        !          2221:     my ($cmd, $tail, $client) = @_;
        !          2222: 
        !          2223:     my $userinput= "$cmd:$tail";
        !          2224:    
        !          2225:     my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
        !          2226:     chomp($what);
        !          2227:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2228: 				 &GDBM_READER());
        !          2229:     if ($hashref) {
        !          2230:         my @queries=split(/\&/,$what);
        !          2231:         my $qresult='';
        !          2232: 	
        !          2233: 	for (my $i=0;$i<=$#queries;$i++) {
        !          2234: 	    $qresult.="$hashref->{$queries[$i]}&";    # Presumably failure gives empty string.
        !          2235: 	}
        !          2236: 	$qresult=~s/\&$//;              # Remove trailing & from last lookup.
        !          2237: 	if (untie(%$hashref)) {
        !          2238: 	    &Reply($client, "$qresult\n", $userinput);
        !          2239: 	} else {
        !          2240: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2241: 		    "while attempting get\n", $userinput);
        !          2242: 	}
        !          2243:     } else {
        !          2244: 	if ($!+0 == 2) {               # +0 coerces errno -> number 2 is ENOENT
        !          2245: 	    &Failure($client, "error:No such file or ".
        !          2246: 		    "GDBM reported bad block error\n", $userinput);
        !          2247: 	} else {                        # Some other undifferentiated err.
        !          2248: 	    &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2249: 		    "while attempting get\n", $userinput);
        !          2250: 	}
        !          2251:     }
        !          2252:     return 1;
        !          2253: }
        !          2254: &register_handler("get", \&get_profile_entry, 0,1,0);
        !          2255: 
        !          2256: #
        !          2257: #  Process the encrypted get request.  Note that the request is sent
        !          2258: #  in clear, but the reply is encrypted.  This is a small covert channel:
        !          2259: #  information about the sensitive keys is given to the snooper.  Just not
        !          2260: #  information about the values of the sensitive key.  Hmm if I wanted to
        !          2261: #  know these I'd snoop for the egets. Get the profile item names from them
        !          2262: #  and then issue a get for them since there's no enforcement of the
        !          2263: #  requirement of an encrypted get for particular profile items.  If I
        !          2264: #  were re-doing this, I'd force the request to be encrypted as well as the
        !          2265: #  reply.  I'd also just enforce encrypted transactions for all gets since
        !          2266: #  that would prevent any covert channel snooping.
        !          2267: #
        !          2268: #  Parameters:
        !          2269: #     $cmd               - Command keyword of request (eget).
        !          2270: #     $tail              - Tail of the command.  See GetProfileEntry
#                          for more information about this.
        !          2271: #     $client            - File open on the client.
        !          2272: #  Returns:
        !          2273: #     1      - Continue processing
        !          2274: #     0      - server should exit.
        !          2275: sub get_profile_entry_encrypted {
        !          2276:     my ($cmd, $tail, $client) = @_;
        !          2277: 
        !          2278:     my $userinput = "$cmd:$tail";
        !          2279:    
        !          2280:     my ($cmd,$udom,$uname,$namespace,$what) = split(/:/,$userinput);
        !          2281:     chomp($what);
        !          2282:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2283: 				 &GDBM_READER());
        !          2284:     if ($hashref) {
        !          2285:         my @queries=split(/\&/,$what);
        !          2286:         my $qresult='';
        !          2287: 	for (my $i=0;$i<=$#queries;$i++) {
        !          2288: 	    $qresult.="$hashref->{$queries[$i]}&";
        !          2289: 	}
        !          2290: 	if (untie(%$hashref)) {
        !          2291: 	    $qresult=~s/\&$//;
        !          2292: 	    if ($cipher) {
        !          2293: 		my $cmdlength=length($qresult);
        !          2294: 		$qresult.="         ";
        !          2295: 		my $encqresult='';
        !          2296: 		for(my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
        !          2297: 		    $encqresult.= unpack("H16", 
        !          2298: 					 $cipher->encrypt(substr($qresult,
        !          2299: 								 $encidx,
        !          2300: 								 8)));
        !          2301: 		}
        !          2302: 		&Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput);
        !          2303: 	    } else {
        !          2304: 		&Failure( $client, "error:no_key\n", $userinput);
        !          2305: 	    }
        !          2306: 	} else {
        !          2307: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2308: 		    "while attempting eget\n", $userinput);
        !          2309: 	}
        !          2310:     } else {
        !          2311: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2312: 		"while attempting eget\n", $userinput);
        !          2313:     }
        !          2314:     
        !          2315:     return 1;
        !          2316: }
        !          2317: &register_handler("eget", \&GetProfileEntryEncrypted, 0, 1, 0);
        !          2318: #
        !          2319: #   Deletes a key in a user profile database.
        !          2320: #   
        !          2321: #   Parameters:
        !          2322: #       $cmd                  - Command keyword (del).
        !          2323: #       $tail                 - Command tail.  IN this case a colon
        !          2324: #                               separated list containing:
        !          2325: #                               The domain and user that identifies uniquely
        !          2326: #                               the identity of the user.
        !          2327: #                               The profile namespace (name of the profile
        !          2328: #                               database file).
        !          2329: #                               & separated list of keywords to delete.
        !          2330: #       $client              - File open on client socket.
        !          2331: # Returns:
        !          2332: #     1   - Continue processing
        !          2333: #     0   - Exit server.
        !          2334: #
        !          2335: #
        !          2336: 
        !          2337: sub delete_profile_entry {
        !          2338:     my ($cmd, $tail, $client) = @_;
        !          2339: 
        !          2340:     my $userinput = "cmd:$tail";
        !          2341: 
        !          2342:     my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
        !          2343:     chomp($what);
        !          2344:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2345: 				 &GDBM_WRCREAT(),
        !          2346: 				 "D",$what);
        !          2347:     if ($hashref) {
        !          2348:         my @keys=split(/\&/,$what);
        !          2349: 	foreach my $key (@keys) {
        !          2350: 	    delete($hashref->{$key});
        !          2351: 	}
        !          2352: 	if (untie(%$hashref)) {
        !          2353: 	    &Reply($client, "ok\n", $userinput);
        !          2354: 	} else {
        !          2355: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2356: 		    "while attempting del\n", $userinput);
        !          2357: 	}
        !          2358:     } else {
        !          2359: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2360: 		 "while attempting del\n", $userinput);
        !          2361:     }
        !          2362:     return 1;
        !          2363: }
        !          2364: &register_handler("del", \&delete_profile_entry, 0, 1, 0);
        !          2365: #
        !          2366: #  List the set of keys that are defined in a profile database file.
        !          2367: #  A successful reply from this will contain an & separated list of
        !          2368: #  the keys. 
        !          2369: # Parameters:
        !          2370: #     $cmd              - Command request (keys).
        !          2371: #     $tail             - Remainder of the request, a colon separated
        !          2372: #                         list containing domain/user that identifies the
        !          2373: #                         user being queried, and the database namespace
        !          2374: #                         (database filename essentially).
        !          2375: #     $client           - File open on the client.
        !          2376: #  Returns:
        !          2377: #    1    - Continue processing.
        !          2378: #    0    - Exit the server.
        !          2379: #
        !          2380: sub get_profile_keys {
        !          2381:     my ($cmd, $tail, $client) = @_;
        !          2382: 
        !          2383:     my $userinput = "$cmd:$tail";
        !          2384: 
        !          2385:     my ($udom,$uname,$namespace)=split(/:/,$tail);
        !          2386:     my $qresult='';
        !          2387:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2388: 				  &GDBM_READER());
        !          2389:     if ($hashref) {
        !          2390: 	foreach my $key (keys %$hashref) {
        !          2391: 	    $qresult.="$key&";
        !          2392: 	}
        !          2393: 	if (untie(%$hashref)) {
        !          2394: 	    $qresult=~s/\&$//;
        !          2395: 	    &Reply($client, "$qresult\n", $userinput);
        !          2396: 	} else {
        !          2397: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2398: 		    "while attempting keys\n", $userinput);
        !          2399: 	}
        !          2400:     } else {
        !          2401: 	&Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2402: 		 "while attempting keys\n", $userinput);
        !          2403:     }
        !          2404:    
        !          2405:     return 1;
        !          2406: }
        !          2407: &register_handler("keys", \&get_profile_keys, 0, 1, 0);
        !          2408: 
        !          2409: #
        !          2410: #   Dump the contents of a user profile database.
        !          2411: #   Note that this constitutes a very large covert channel too since
        !          2412: #   the dump will return sensitive information that is not encrypted.
        !          2413: #   The naive security assumption is that the session negotiation ensures
        !          2414: #   our client is trusted and I don't believe that's assured at present.
        !          2415: #   Sure want badly to go to ssl or tls.  Of course if my peer isn't really
        !          2416: #   a LonCAPA node they could have negotiated an encryption key too so >sigh<.
        !          2417: # 
        !          2418: #  Parameters:
        !          2419: #     $cmd           - The command request keyword (currentdump).
        !          2420: #     $tail          - Remainder of the request, consisting of a colon
        !          2421: #                      separated list that has the domain/username and
        !          2422: #                      the namespace to dump (database file).
        !          2423: #     $client        - file open on the remote client.
        !          2424: # Returns:
        !          2425: #     1    - Continue processing.
        !          2426: #     0    - Exit the server.
        !          2427: #
        !          2428: sub dump_profile_database {
        !          2429:     my ($cmd, $tail, $client) = @_;
        !          2430: 
        !          2431:     my $userinput = "$cmd:$tail";
        !          2432:    
        !          2433:     my ($udom,$uname,$namespace) = split(/:/,$tail);
        !          2434:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2435: 				 &GDBM_READER());
        !          2436:     if ($hashref) {
        !          2437: 	# Structure of %data:
        !          2438: 	# $data{$symb}->{$parameter}=$value;
        !          2439: 	# $data{$symb}->{'v.'.$parameter}=$version;
        !          2440: 	# since $parameter will be unescaped, we do not
        !          2441:  	# have to worry about silly parameter names...
        !          2442: 	
        !          2443:         my $qresult='';
        !          2444: 	my %data = ();                     # A hash of anonymous hashes..
        !          2445: 	while (my ($key,$value) = each(%$hashref)) {
        !          2446: 	    my ($v,$symb,$param) = split(/:/,$key);
        !          2447: 	    next if ($v eq 'version' || $symb eq 'keys');
        !          2448: 	    next if (exists($data{$symb}) && 
        !          2449: 		     exists($data{$symb}->{$param}) &&
        !          2450: 		     $data{$symb}->{'v.'.$param} > $v);
        !          2451: 	    $data{$symb}->{$param}=$value;
        !          2452: 	    $data{$symb}->{'v.'.$param}=$v;
        !          2453: 	}
        !          2454: 	if (untie(%$hashref)) {
        !          2455: 	    while (my ($symb,$param_hash) = each(%data)) {
        !          2456: 		while(my ($param,$value) = each (%$param_hash)){
        !          2457: 		    next if ($param =~ /^v\./);       # Ignore versions...
        !          2458: 		    #
        !          2459: 		    #   Just dump the symb=value pairs separated by &
        !          2460: 		    #
        !          2461: 		    $qresult.=$symb.':'.$param.'='.$value.'&';
        !          2462: 		}
        !          2463: 	    }
        !          2464: 	    chop($qresult);
        !          2465: 	    &Reply($client , "$qresult\n", $userinput);
        !          2466: 	} else {
        !          2467: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2468: 		     "while attempting currentdump\n", $userinput);
        !          2469: 	}
        !          2470:     } else {
        !          2471: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2472: 		"while attempting currentdump\n", $userinput);
        !          2473:     }
        !          2474: 
        !          2475:     return 1;
        !          2476: }
        !          2477: &register_handler("currentdump", \&dump_profile_database, 0, 1, 0);
        !          2478: 
        !          2479: #
        !          2480: #   Dump a profile database with an optional regular expression
        !          2481: #   to match against the keys.  In this dump, no effort is made
        !          2482: #   to separate symb from version information. Presumably the
        !          2483: #   databases that are dumped by this command are of a different
        !          2484: #   structure.  Need to look at this and improve the documentation of
        !          2485: #   both this and the currentdump handler.
        !          2486: # Parameters:
        !          2487: #    $cmd                     - The command keyword.
        !          2488: #    $tail                    - All of the characters after the $cmd:
        !          2489: #                               These are expected to be a colon
        !          2490: #                               separated list containing:
        !          2491: #                               domain/user - identifying the user.
        !          2492: #                               namespace   - identifying the database.
        !          2493: #                               regexp      - optional regular expression
        !          2494: #                                             that is matched against
        !          2495: #                                             database keywords to do
        !          2496: #                                             selective dumps.
        !          2497: #   $client                   - Channel open on the client.
        !          2498: # Returns:
        !          2499: #    1    - Continue processing.
        !          2500: # Side effects:
        !          2501: #    response is written to $client.
        !          2502: #
        !          2503: sub dump_with_regexp {
        !          2504:     my ($cmd, $tail, $client) = @_;
        !          2505: 
        !          2506: 
        !          2507:     my $userinput = "$cmd:$tail";
        !          2508: 
        !          2509:     my ($udom,$uname,$namespace,$regexp)=split(/:/,$tail);
        !          2510:     if (defined($regexp)) {
        !          2511: 	$regexp=&unescape($regexp);
        !          2512:     } else {
        !          2513: 	$regexp='.';
        !          2514:     }
        !          2515:     my $hashref = &tie_user_hash($udom, $uname, $namespace,
        !          2516: 				 &GDBM_READER());
        !          2517:     if ($hashref) {
        !          2518:         my $qresult='';
        !          2519: 	while (my ($key,$value) = each(%$hashref)) {
        !          2520: 	    if ($regexp eq '.') {
        !          2521: 		$qresult.=$key.'='.$value.'&';
        !          2522: 	    } else {
        !          2523: 		my $unescapeKey = &unescape($key);
        !          2524: 		if (eval('$unescapeKey=~/$regexp/')) {
        !          2525: 		    $qresult.="$key=$value&";
        !          2526: 		}
        !          2527: 	    }
        !          2528: 	}
        !          2529: 	if (untie(%$hashref)) {
        !          2530: 	    chop($qresult);
        !          2531: 	    &Reply($client, "$qresult\n", $userinput);
        !          2532: 	} else {
        !          2533: 	    &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2534: 		     "while attempting dump\n", $userinput);
        !          2535: 	}
        !          2536:     } else {
        !          2537: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2538: 		"while attempting dump\n", $userinput);
        !          2539:     }
        !          2540: 
        !          2541:     return 1;
        !          2542: }
        !          2543: 
        !          2544: &register_handler("dump", \&dump_with_regexp, 0, 1, 0);
        !          2545: 
        !          2546: #  Store a set of key=value pairs associated with a versioned name.
        !          2547: #
        !          2548: #  Parameters:
        !          2549: #    $cmd                - Request command keyword.
        !          2550: #    $tail               - Tail of the request.  This is a colon
        !          2551: #                          separated list containing:
        !          2552: #                          domain/user - User and authentication domain.
        !          2553: #                          namespace   - Name of the database being modified
        !          2554: #                          rid         - Resource keyword to modify.
        !          2555: #                          what        - new value associated with rid.
        !          2556: #
        !          2557: #    $client             - Socket open on the client.
        !          2558: #
        !          2559: #
        !          2560: #  Returns:
        !          2561: #      1 (keep on processing).
        !          2562: #  Side-Effects:
        !          2563: #    Writes to the client
        !          2564: sub store_handler {
        !          2565:     my ($cmd, $tail, $client) = @_;
        !          2566:  
        !          2567:     my $userinput = "$cmd:$tail";
        !          2568: 
        !          2569:     my ($udom,$uname,$namespace,$rid,$what) =split(/:/,$tail);
        !          2570:     if ($namespace ne 'roles') {
        !          2571: 
        !          2572: 	chomp($what);
        !          2573: 	my @pairs=split(/\&/,$what);
        !          2574: 	my $hashref  = &tie_user_hash($udom, $uname, $namespace,
        !          2575: 				       &GDBM_WRCREAT(), "P",
        !          2576: 				       "$rid:$what");
        !          2577: 	if ($hashref) {
        !          2578: 	    my $now = time;
        !          2579: 	    my @previouskeys=split(/&/,$hashref->{"keys:$rid"});
        !          2580: 	    my $key;
        !          2581: 	    $hashref->{"version:$rid"}++;
        !          2582: 	    my $version=$hashref->{"version:$rid"};
        !          2583: 	    my $allkeys=''; 
        !          2584: 	    foreach my $pair (@pairs) {
        !          2585: 		my ($key,$value)=split(/=/,$pair);
        !          2586: 		$allkeys.=$key.':';
        !          2587: 		$hashref->{"$version:$rid:$key"}=$value;
        !          2588: 	    }
        !          2589: 	    $hashref->{"$version:$rid:timestamp"}=$now;
        !          2590: 	    $allkeys.='timestamp';
        !          2591: 	    $hashref->{"$version:keys:$rid"}=$allkeys;
        !          2592: 	    if (untie($hashref)) {
        !          2593: 		&Reply($client, "ok\n", $userinput);
        !          2594: 	    } else {
        !          2595: 		&Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2596: 			"while attempting store\n", $userinput);
        !          2597: 	    }
        !          2598: 	} else {
        !          2599: 	    &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2600: 		     "while attempting store\n", $userinput);
        !          2601: 	}
        !          2602:     } else {
        !          2603: 	&Failure($client, "refused\n", $userinput);
        !          2604:     }
        !          2605: 
        !          2606:     return 1;
        !          2607: }
        !          2608: &register_handler("store", \&store_handler, 0, 1, 0);
        !          2609: #
        !          2610: #  Dump out all versions of a resource that has key=value pairs associated
        !          2611: # with it for each version.  These resources are built up via the store
        !          2612: # command.
        !          2613: #
        !          2614: #  Parameters:
        !          2615: #     $cmd               - Command keyword.
        !          2616: #     $tail              - Remainder of the request which consists of:
        !          2617: #                          domain/user   - User and auth. domain.
        !          2618: #                          namespace     - name of resource database.
        !          2619: #                          rid           - Resource id.
        !          2620: #    $client             - socket open on the client.
        !          2621: #
        !          2622: # Returns:
        !          2623: #      1  indicating the caller should not yet exit.
        !          2624: # Side-effects:
        !          2625: #   Writes a reply to the client.
        !          2626: #   The reply is a string of the following shape:
        !          2627: #   version=current&version:keys=k1:k2...&1:k1=v1&1:k2=v2...
        !          2628: #    Where the 1 above represents version 1.
        !          2629: #    this continues for all pairs of keys in all versions.
        !          2630: #
        !          2631: #
        !          2632: #    
        !          2633: #
        !          2634: sub restore_handler {
        !          2635:     my ($cmd, $tail, $client) = @_;
        !          2636: 
        !          2637:     my $userinput = "$cmd:$tail";	# Only used for logging purposes.
        !          2638: 
        !          2639:     my ($cmd,$udom,$uname,$namespace,$rid) = split(/:/,$userinput);
        !          2640:     $namespace=~s/\//\_/g;
        !          2641:     $namespace=~s/\W//g;
        !          2642:     chomp($rid);
        !          2643:     my $proname=&propath($udom,$uname);
        !          2644:     my $qresult='';
        !          2645:     my %hash;
        !          2646:     if (tie(%hash,'GDBM_File',"$proname/$namespace.db",
        !          2647: 	    &GDBM_READER(),0640)) {
        !          2648: 	my $version=$hash{"version:$rid"};
        !          2649: 	$qresult.="version=$version&";
        !          2650: 	my $scope;
        !          2651: 	for ($scope=1;$scope<=$version;$scope++) {
        !          2652: 	    my $vkeys=$hash{"$scope:keys:$rid"};
        !          2653: 	    my @keys=split(/:/,$vkeys);
        !          2654: 	    my $key;
        !          2655: 	    $qresult.="$scope:keys=$vkeys&";
        !          2656: 	    foreach $key (@keys) {
        !          2657: 		$qresult.="$scope:$key=".$hash{"$scope:$rid:$key"}."&";
        !          2658: 	    }                                  
        !          2659: 	}
        !          2660: 	if (untie(%hash)) {
        !          2661: 	    $qresult=~s/\&$//;
        !          2662: 	    &Reply( $client, "$qresult\n", $userinput);
        !          2663: 	} else {
        !          2664: 	    &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
        !          2665: 		    "while attempting restore\n", $userinput);
        !          2666: 	}
        !          2667:     } else {
        !          2668: 	&Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
        !          2669: 		"while attempting restore\n", $userinput);
        !          2670:     }
        !          2671:   
        !          2672:     return 1;
        !          2673: 
        !          2674: 
        !          2675: }
        !          2676: &register_handler("restore", \&restore_handler, 0,1,0);
        !          2677: #
        !          2678: #
1.207     foxr     2679: #---------------------------------------------------------------
                   2680: #
                   2681: #   Getting, decoding and dispatching requests:
                   2682: #
                   2683: 
                   2684: #
                   2685: #   Get a Request:
                   2686: #   Gets a Request message from the client.  The transaction
                   2687: #   is defined as a 'line' of text.  We remove the new line
                   2688: #   from the text line.  
1.226     foxr     2689: #
1.211     albertel 2690: sub get_request {
1.207     foxr     2691:     my $input = <$client>;
                   2692:     chomp($input);
1.226     foxr     2693: 
1.212     foxr     2694:     Debug("get_request: Request = $input\n");
1.207     foxr     2695: 
                   2696:     &status('Processing '.$clientname.':'.$input);
                   2697: 
                   2698:     return $input;
                   2699: }
1.212     foxr     2700: #---------------------------------------------------------------
                   2701: #
                   2702: #  Process a request.  This sub should shrink as each action
                   2703: #  gets farmed out into a separat sub that is registered 
                   2704: #  with the dispatch hash.  
                   2705: #
                   2706: # Parameters:
                   2707: #    user_input   - The request received from the client (lonc).
                   2708: # Returns:
                   2709: #    true to keep processing, false if caller should exit.
                   2710: #
                   2711: sub process_request {
                   2712:     my ($userinput) = @_;      # Easier for now to break style than to
                   2713:                                 # fix all the userinput -> user_input.
                   2714:     my $wasenc    = 0;		# True if request was encrypted.
                   2715: # ------------------------------------------------------------ See if encrypted
                   2716:     if ($userinput =~ /^enc/) {
                   2717: 	$userinput = decipher($userinput);
                   2718: 	$wasenc=1;
                   2719: 	if(!$userinput) {	# Cipher not defined.
                   2720: 	    &Failure($client, "error: Encrypted data without negotated key");
                   2721: 	    return 0;
                   2722: 	}
                   2723:     }
                   2724:     Debug("process_request: $userinput\n");
                   2725:     
1.213     foxr     2726:     #  
                   2727:     #   The 'correct way' to add a command to lond is now to
                   2728:     #   write a sub to execute it and Add it to the command dispatch
                   2729:     #   hash via a call to register_handler..  The comments to that
                   2730:     #   sub should give you enough to go on to show how to do this
                   2731:     #   along with the examples that are building up as this code
                   2732:     #   is getting refactored.   Until all branches of the
                   2733:     #   if/elseif monster below have been factored out into
                   2734:     #   separate procesor subs, if the dispatch hash is missing
                   2735:     #   the command keyword, we will fall through to the remainder
                   2736:     #   of the if/else chain below in order to keep this thing in 
                   2737:     #   working order throughout the transmogrification.
                   2738: 
                   2739:     my ($command, $tail) = split(/:/, $userinput, 2);
                   2740:     chomp($command);
                   2741:     chomp($tail);
                   2742:     $tail =~ s/(\r)//;		# This helps people debugging with e.g. telnet.
1.214     foxr     2743:     $command =~ s/(\r)//;	# And this too for parameterless commands.
                   2744:     if(!$tail) {
                   2745: 	$tail ="";		# defined but blank.
                   2746:     }
1.213     foxr     2747: 
                   2748:     &Debug("Command received: $command, encoded = $wasenc");
                   2749: 
                   2750:     if(defined $Dispatcher{$command}) {
                   2751: 
                   2752: 	my $dispatch_info = $Dispatcher{$command};
                   2753: 	my $handler       = $$dispatch_info[0];
                   2754: 	my $need_encode   = $$dispatch_info[1];
                   2755: 	my $client_types  = $$dispatch_info[2];
                   2756: 	Debug("Matched dispatch hash: mustencode: $need_encode "
                   2757: 	      ."ClientType $client_types");
                   2758:       
                   2759: 	#  Validate the request:
                   2760:       
                   2761: 	my $ok = 1;
                   2762: 	my $requesterprivs = 0;
                   2763: 	if(&isClient()) {
                   2764: 	    $requesterprivs |= $CLIENT_OK;
                   2765: 	}
                   2766: 	if(&isManager()) {
                   2767: 	    $requesterprivs |= $MANAGER_OK;
                   2768: 	}
                   2769: 	if($need_encode && (!$wasenc)) {
                   2770: 	    Debug("Must encode but wasn't: $need_encode $wasenc");
                   2771: 	    $ok = 0;
                   2772: 	}
                   2773: 	if(($client_types & $requesterprivs) == 0) {
                   2774: 	    Debug("Client not privileged to do this operation");
                   2775: 	    $ok = 0;
                   2776: 	}
                   2777: 
                   2778: 	if($ok) {
                   2779: 	    Debug("Dispatching to handler $command $tail");
                   2780: 	    my $keep_going = &$handler($command, $tail, $client);
                   2781: 	    return $keep_going;
                   2782: 	} else {
                   2783: 	    Debug("Refusing to dispatch because client did not match requirements");
                   2784: 	    Failure($client, "refused\n", $userinput);
                   2785: 	    return 1;
                   2786: 	}
                   2787: 
                   2788:     }    
                   2789: 
1.215     foxr     2790: #------------------- Commands not yet in spearate handlers. --------------
                   2791: 
1.218     foxr     2792: 
1.231   ! foxr     2793: 
1.212     foxr     2794: # -------------------------------------------------------------------- chatsend
1.231   ! foxr     2795:    if ($userinput =~ /^chatsend/) {
1.212     foxr     2796: 	if(isClient) {
                   2797: 	    my ($cmd,$cdom,$cnum,$newpost)=split(/\:/,$userinput);
                   2798: 	    &chatadd($cdom,$cnum,$newpost);
                   2799: 	    print $client "ok\n";
                   2800: 	} else {
                   2801: 	    Reply($client, "refused\n", $userinput);
                   2802: 	    
                   2803: 	}
                   2804: # -------------------------------------------------------------------- chatretr
                   2805:     } elsif ($userinput =~ /^chatretr/) {
                   2806: 	if(isClient) {
                   2807: 	    my 
                   2808: 		($cmd,$cdom,$cnum,$udom,$uname)=split(/\:/,$userinput);
                   2809: 	    my $reply='';
                   2810: 	    foreach (&getchat($cdom,$cnum,$udom,$uname)) {
                   2811: 		$reply.=&escape($_).':';
                   2812: 	    }
                   2813: 	    $reply=~s/\:$//;
                   2814: 	    print $client $reply."\n";
                   2815: 	} else {
                   2816: 	    Reply($client, "refused\n", $userinput);
                   2817: 	    
                   2818: 	}
                   2819: # ------------------------------------------------------------------- querysend
                   2820:     } elsif ($userinput =~ /^querysend/) {
                   2821: 	if (isClient) {
                   2822: 	    my ($cmd,$query,
                   2823: 		$arg1,$arg2,$arg3)=split(/\:/,$userinput);
                   2824: 	    $query=~s/\n*$//g;
                   2825: 	    print $client "".
                   2826: 		sqlreply("$clientname\&$query".
                   2827: 			 "\&$arg1"."\&$arg2"."\&$arg3")."\n";
                   2828: 	} else {
                   2829: 	    Reply($client, "refused\n", $userinput);
                   2830: 	    
                   2831: 	}
                   2832: # ------------------------------------------------------------------ queryreply
                   2833:     } elsif ($userinput =~ /^queryreply/) {
                   2834: 	if(isClient) {
                   2835: 	    my ($cmd,$id,$reply)=split(/:/,$userinput); 
                   2836: 	    my $store;
                   2837: 	    my $execdir=$perlvar{'lonDaemons'};
                   2838: 	    if ($store=IO::File->new(">$execdir/tmp/$id")) {
                   2839: 		$reply=~s/\&/\n/g;
                   2840: 		print $store $reply;
                   2841: 		close $store;
                   2842: 		my $store2=IO::File->new(">$execdir/tmp/$id.end");
                   2843: 		print $store2 "done\n";
                   2844: 		close $store2;
                   2845: 		print $client "ok\n";
1.224     foxr     2846: 	    } else {
1.212     foxr     2847: 		print $client "error: ".($!+0)
                   2848: 		    ." IO::File->new Failed ".
                   2849: 		    "while attempting queryreply\n";
                   2850: 	    }
                   2851: 	} else {
                   2852: 	    Reply($client, "refused\n", $userinput);
                   2853: 	    
                   2854: 	}
                   2855: # ----------------------------------------------------------------- courseidput
                   2856:     } elsif ($userinput =~ /^courseidput/) {
                   2857: 	if(isClient) {
                   2858: 	    my ($cmd,$udom,$what)=split(/:/,$userinput);
                   2859: 	    chomp($what);
                   2860: 			$udom=~s/\W//g;
                   2861: 	    my $proname=
                   2862: 		"$perlvar{'lonUsersDir'}/$udom/nohist_courseids";
                   2863: 	    my $now=time;
                   2864: 	    my @pairs=split(/\&/,$what);
                   2865: 	    my %hash;
                   2866: 	    if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
                   2867: 		foreach my $pair (@pairs) {
                   2868: 		    my ($key,$descr,$inst_code)=split(/=/,$pair);
                   2869: 		    $hash{$key}=$descr.':'.$inst_code.':'.$now;
                   2870: 		}
                   2871: 		if (untie(%hash)) {
                   2872: 		    print $client "ok\n";
                   2873: 		} else {
                   2874: 		    print $client "error: ".($!+0)
                   2875: 			." untie(GDBM) Failed ".
                   2876: 			"while attempting courseidput\n";
                   2877: 		}
                   2878: 	    } else {
                   2879: 		print $client "error: ".($!+0)
                   2880: 		    ." tie(GDBM) Failed ".
                   2881: 		    "while attempting courseidput\n";
                   2882: 	    }
                   2883: 	} else {
                   2884: 	    Reply($client, "refused\n", $userinput);
                   2885: 	    
                   2886: 	}
                   2887: # ---------------------------------------------------------------- courseiddump
                   2888:     } elsif ($userinput =~ /^courseiddump/) {
                   2889: 	if(isClient) {
                   2890: 	    my ($cmd,$udom,$since,$description)
                   2891: 		=split(/:/,$userinput);
                   2892: 	    if (defined($description)) {
                   2893: 		$description=&unescape($description);
                   2894: 	    } else {
                   2895: 		$description='.';
                   2896: 	    }
                   2897: 	    unless (defined($since)) { $since=0; }
                   2898: 	    my $qresult='';
                   2899: 	    my $proname=
                   2900: 		"$perlvar{'lonUsersDir'}/$udom/nohist_courseids";
                   2901: 	    my %hash;
                   2902: 	    if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
                   2903: 		while (my ($key,$value) = each(%hash)) {
                   2904: 		    my ($descr,$lasttime,$inst_code);
                   2905: 		    if ($value =~ m/^([^\:]*):([^\:]*):(\d+)$/) {
                   2906: 			($descr,$inst_code,$lasttime)=($1,$2,$3);
                   2907: 		    } else {
                   2908: 			($descr,$lasttime) = split(/\:/,$value);
                   2909: 		    }
                   2910: 		    if ($lasttime<$since) { next; }
                   2911: 		    if ($description eq '.') {
                   2912: 			$qresult.=$key.'='.$descr.':'.$inst_code.'&';
                   2913: 		    } else {
                   2914: 			my $unescapeVal = &unescape($descr);
                   2915: 			if (eval('$unescapeVal=~/\Q$description\E/i')) {
                   2916: 			    $qresult.=$key.'='.$descr.':'.$inst_code.'&';
                   2917: 			}
                   2918: 		    }
                   2919: 		}
                   2920: 		if (untie(%hash)) {
                   2921: 		    chop($qresult);
                   2922: 		    print $client "$qresult\n";
                   2923: 		} else {
                   2924: 		    print $client "error: ".($!+0)
                   2925: 			." untie(GDBM) Failed ".
                   2926: 			"while attempting courseiddump\n";
                   2927: 		}
                   2928: 	    } else {
                   2929: 		print $client "error: ".($!+0)
                   2930: 		    ." tie(GDBM) Failed ".
                   2931: 		    "while attempting courseiddump\n";
                   2932: 	    }
                   2933: 	} else {
                   2934: 	    Reply($client, "refused\n", $userinput);
                   2935: 	    
                   2936: 	}
                   2937: # ----------------------------------------------------------------------- idput
                   2938:     } elsif ($userinput =~ /^idput/) {
                   2939: 	if(isClient) {
                   2940: 	    my ($cmd,$udom,$what)=split(/:/,$userinput);
                   2941: 	    chomp($what);
                   2942: 	    $udom=~s/\W//g;
                   2943: 	    my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
                   2944: 	    my $now=time;
                   2945: 	    my @pairs=split(/\&/,$what);
                   2946: 	    my %hash;
                   2947: 	    if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
                   2948: 		{
                   2949: 		    my $hfh;
                   2950: 		    if ($hfh=IO::File->new(">>$proname.hist")) {
                   2951: 			print $hfh "P:$now:$what\n";
                   2952: 		    }
                   2953: 		}
                   2954: 		foreach my $pair (@pairs) {
                   2955: 		    my ($key,$value)=split(/=/,$pair);
                   2956: 		    $hash{$key}=$value;
                   2957: 		}
                   2958: 		if (untie(%hash)) {
                   2959: 		    print $client "ok\n";
                   2960: 		} else {
                   2961: 		    print $client "error: ".($!+0)
                   2962: 			." untie(GDBM) Failed ".
                   2963: 			"while attempting idput\n";
                   2964: 		}
                   2965: 	    } else {
                   2966: 		print $client "error: ".($!+0)
                   2967: 		    ." tie(GDBM) Failed ".
                   2968: 		    "while attempting idput\n";
                   2969: 	    }
                   2970: 	} else {
                   2971: 	    Reply($client, "refused\n", $userinput);
                   2972: 	    
                   2973: 	}
                   2974: # ----------------------------------------------------------------------- idget
                   2975:     } elsif ($userinput =~ /^idget/) {
                   2976: 	if(isClient) {
                   2977: 	    my ($cmd,$udom,$what)=split(/:/,$userinput);
                   2978: 	    chomp($what);
                   2979: 	    $udom=~s/\W//g;
                   2980: 	    my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
                   2981: 	    my @queries=split(/\&/,$what);
                   2982: 	    my $qresult='';
                   2983: 	    my %hash;
                   2984: 	    if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
                   2985: 		for (my $i=0;$i<=$#queries;$i++) {
                   2986: 		    $qresult.="$hash{$queries[$i]}&";
                   2987: 		}
                   2988: 		if (untie(%hash)) {
                   2989: 		    $qresult=~s/\&$//;
                   2990: 		    print $client "$qresult\n";
                   2991: 		} else {
                   2992: 		    print $client "error: ".($!+0)
                   2993: 			." untie(GDBM) Failed ".
                   2994: 			"while attempting idget\n";
                   2995: 		}
                   2996: 	    } else {
                   2997: 		print $client "error: ".($!+0)
                   2998: 		    ." tie(GDBM) Failed ".
                   2999: 		    "while attempting idget\n";
                   3000: 	    }
                   3001: 	} else {
                   3002: 	    Reply($client, "refused\n", $userinput);
                   3003: 	    
                   3004: 	}
                   3005: # ---------------------------------------------------------------------- tmpput
                   3006:     } elsif ($userinput =~ /^tmpput/) {
                   3007: 	if(isClient) {
                   3008: 	    my ($cmd,$what)=split(/:/,$userinput);
                   3009: 	    my $store;
                   3010: 	    $tmpsnum++;
                   3011: 	    my $id=$$.'_'.$clientip.'_'.$tmpsnum;
                   3012: 	    $id=~s/\W/\_/g;
                   3013: 	    $what=~s/\n//g;
                   3014: 	    my $execdir=$perlvar{'lonDaemons'};
                   3015: 	    if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) {
                   3016: 		print $store $what;
                   3017: 		close $store;
                   3018: 		print $client "$id\n";
                   3019: 	    }
                   3020: 	    else {
                   3021: 		print $client "error: ".($!+0)
                   3022: 		    ."IO::File->new Failed ".
                   3023: 		    "while attempting tmpput\n";
                   3024: 	    }
                   3025: 	} else {
                   3026: 	    Reply($client, "refused\n", $userinput);
                   3027: 	    
                   3028: 	}
                   3029: 	
                   3030: # ---------------------------------------------------------------------- tmpget
                   3031:     } elsif ($userinput =~ /^tmpget/) {
                   3032: 	if(isClient) {
                   3033: 	    my ($cmd,$id)=split(/:/,$userinput);
                   3034: 	    chomp($id);
                   3035: 	    $id=~s/\W/\_/g;
                   3036: 	    my $store;
                   3037: 	    my $execdir=$perlvar{'lonDaemons'};
                   3038: 	    if ($store=IO::File->new("$execdir/tmp/$id.tmp")) {
                   3039: 		my $reply=<$store>;
                   3040: 			    print $client "$reply\n";
                   3041: 		close $store;
                   3042: 	    }
                   3043: 	    else {
                   3044: 		print $client "error: ".($!+0)
                   3045: 		    ."IO::File->new Failed ".
                   3046: 		    "while attempting tmpget\n";
                   3047: 	    }
                   3048: 	} else {
                   3049: 	    Reply($client, "refused\n", $userinput);
                   3050: 	    
                   3051: 	}
                   3052: # ---------------------------------------------------------------------- tmpdel
                   3053:     } elsif ($userinput =~ /^tmpdel/) {
                   3054: 	if(isClient) {
                   3055: 	    my ($cmd,$id)=split(/:/,$userinput);
                   3056: 	    chomp($id);
                   3057: 	    $id=~s/\W/\_/g;
                   3058: 	    my $execdir=$perlvar{'lonDaemons'};
                   3059: 	    if (unlink("$execdir/tmp/$id.tmp")) {
                   3060: 		print $client "ok\n";
                   3061: 	    } else {
                   3062: 		print $client "error: ".($!+0)
                   3063: 		    ."Unlink tmp Failed ".
                   3064: 		    "while attempting tmpdel\n";
                   3065: 	    }
                   3066: 	} else {
                   3067: 	    Reply($client, "refused\n", $userinput);
                   3068: 	    
                   3069: 	}
                   3070: # ----------------------------------------- portfolio directory list (portls)
                   3071:     } elsif ($userinput =~ /^portls/) {
                   3072: 	if(isClient) {
                   3073: 	    my ($cmd,$uname,$udom)=split(/:/,$userinput);
                   3074: 	    my $udir=propath($udom,$uname).'/userfiles/portfolio';
                   3075: 	    my $dirLine='';
                   3076: 	    my $dirContents='';
                   3077: 	    if (opendir(LSDIR,$udir.'/')){
                   3078: 		while ($dirLine = readdir(LSDIR)){
                   3079: 		    $dirContents = $dirContents.$dirLine.'<br />';
                   3080: 		}
                   3081: 	    } else {
                   3082: 		$dirContents = "No directory found\n";
                   3083: 	    }
                   3084: 	    print $client $dirContents."\n";
                   3085: 	} else {
                   3086: 	    Reply($client, "refused\n", $userinput);
                   3087: 	}
                   3088: # -------------------------------------------------------------------------- ls
                   3089:     } elsif ($userinput =~ /^ls/) {
                   3090: 	if(isClient) {
                   3091: 	    my $obs;
                   3092: 	    my $rights;
                   3093: 	    my ($cmd,$ulsdir)=split(/:/,$userinput);
                   3094: 	    my $ulsout='';
                   3095: 	    my $ulsfn;
                   3096: 	    if (-e $ulsdir) {
                   3097: 		if(-d $ulsdir) {
                   3098: 		    if (opendir(LSDIR,$ulsdir)) {
                   3099: 			while ($ulsfn=readdir(LSDIR)) {
                   3100: 			    undef $obs, $rights; 
                   3101: 			    my @ulsstats=stat($ulsdir.'/'.$ulsfn);
                   3102: 			    #We do some obsolete checking here
                   3103: 			    if(-e $ulsdir.'/'.$ulsfn.".meta") { 
                   3104: 				open(FILE, $ulsdir.'/'.$ulsfn.".meta");
                   3105: 				my @obsolete=<FILE>;
                   3106: 				foreach my $obsolete (@obsolete) {
                   3107: 				    if($obsolete =~ m|(<obsolete>)(on)|) { $obs = 1; } 
                   3108: 				    if($obsolete =~ m|(<copyright>)(default)|) { $rights = 1; }
                   3109: 				}
                   3110: 			    }
                   3111: 			    $ulsout.=$ulsfn.'&'.join('&',@ulsstats);
                   3112: 			    if($obs eq '1') { $ulsout.="&1"; }
                   3113: 			    else { $ulsout.="&0"; }
                   3114: 			    if($rights eq '1') { $ulsout.="&1:"; }
                   3115: 			    else { $ulsout.="&0:"; }
                   3116: 			}
                   3117: 			closedir(LSDIR);
                   3118: 		    }
                   3119: 		} else {
                   3120: 		    my @ulsstats=stat($ulsdir);
                   3121: 		    $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
                   3122: 		}
                   3123: 	    } else {
                   3124: 		$ulsout='no_such_dir';
                   3125: 	    }
                   3126: 	    if ($ulsout eq '') { $ulsout='empty'; }
                   3127: 	    print $client "$ulsout\n";
                   3128: 	} else {
                   3129: 	    Reply($client, "refused\n", $userinput);
                   3130: 	    
                   3131: 	}
                   3132: # ----------------------------------------------------------------- setannounce
                   3133:     } elsif ($userinput =~ /^setannounce/) {
                   3134: 	if (isClient) {
                   3135: 	    my ($cmd,$announcement)=split(/:/,$userinput);
                   3136: 	    chomp($announcement);
                   3137: 	    $announcement=&unescape($announcement);
                   3138: 	    if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}.
                   3139: 					'/announcement.txt')) {
                   3140: 		print $store $announcement;
                   3141: 		close $store;
                   3142: 		print $client "ok\n";
                   3143: 	    } else {
                   3144: 		print $client "error: ".($!+0)."\n";
                   3145: 	    }
                   3146: 	} else {
                   3147: 	    Reply($client, "refused\n", $userinput);
                   3148: 	    
                   3149: 	}
                   3150: # ------------------------------------------------------------------ Hanging up
                   3151:     } elsif (($userinput =~ /^exit/) ||
                   3152: 	     ($userinput =~ /^init/)) { # no restrictions.
                   3153: 	&logthis(
                   3154: 		 "Client $clientip ($clientname) hanging up: $userinput");
                   3155: 	print $client "bye\n";
                   3156: 	$client->shutdown(2);        # shutdown the socket forcibly.
                   3157: 	$client->close();
                   3158: 	return 0;
                   3159: 	
                   3160: # ---------------------------------- set current host/domain
1.231   ! foxr     3161:     } elsif ($userinput =~ /^sethost/) {
1.212     foxr     3162: 	if (isClient) {
                   3163: 	    print $client &sethost($userinput)."\n";
                   3164: 	} else {
                   3165: 	    print $client "refused\n";
                   3166: 	}
                   3167: #---------------------------------- request file (?) version.
1.231   ! foxr     3168:     } elsif ($userinput =~/^version/) {
1.212     foxr     3169: 	if (isClient) {
                   3170: 	    print $client &version($userinput)."\n";
                   3171: 	} else {
                   3172: 	    print $client "refused\n";
                   3173: 	}
                   3174: #------------------------------- is auto-enrollment enabled?
1.231   ! foxr     3175:     } elsif ($userinput =~/^autorun/) {
1.212     foxr     3176: 	if (isClient) {
                   3177: 	    my ($cmd,$cdom) = split(/:/,$userinput);
                   3178: 	    my $outcome = &localenroll::run($cdom);
                   3179: 	    print $client "$outcome\n";
                   3180: 	} else {
                   3181: 	    print $client "0\n";
                   3182: 	}
                   3183: #------------------------------- get official sections (for auto-enrollment).
1.231   ! foxr     3184:     } elsif ($userinput =~/^autogetsections/) {
1.212     foxr     3185: 	if (isClient) {
                   3186: 	    my ($cmd,$coursecode,$cdom)=split(/:/,$userinput);
                   3187: 	    my @secs = &localenroll::get_sections($coursecode,$cdom);
                   3188: 	    my $seclist = &escape(join(':',@secs));
                   3189: 	    print $client "$seclist\n";
                   3190: 	} else {
                   3191: 	    print $client "refused\n";
                   3192: 	}
                   3193: #----------------------- validate owner of new course section (for auto-enrollment).
1.231   ! foxr     3194:     } elsif ($userinput =~/^autonewcourse/) {
1.212     foxr     3195: 	if (isClient) {
                   3196: 	    my ($cmd,$inst_course_id,$owner,$cdom)=split(/:/,$userinput);
                   3197: 	    my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom);
                   3198: 	    print $client "$outcome\n";
                   3199: 	} else {
                   3200: 	    print $client "refused\n";
                   3201: 	}
                   3202: #-------------- validate course section in schedule of classes (for auto-enrollment).
1.231   ! foxr     3203:     } elsif ($userinput =~/^autovalidatecourse/) {
1.212     foxr     3204: 	if (isClient) {
                   3205: 	    my ($cmd,$inst_course_id,$cdom)=split(/:/,$userinput);
                   3206: 	    my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom);
                   3207: 	    print $client "$outcome\n";
                   3208: 	} else {
                   3209: 	    print $client "refused\n";
                   3210: 	}
                   3211: #--------------------------- create password for new user (for auto-enrollment).
1.231   ! foxr     3212:     } elsif ($userinput =~/^autocreatepassword/) {
1.212     foxr     3213: 	if (isClient) {
                   3214: 	    my ($cmd,$authparam,$cdom)=split(/:/,$userinput);
                   3215: 	    my ($create_passwd,$authchk);
                   3216: 	    ($authparam,$create_passwd,$authchk) = &localenroll::create_password($authparam,$cdom);
                   3217: 	    print $client &escape($authparam.':'.$create_passwd.':'.$authchk)."\n";
                   3218: 	} else {
                   3219: 	    print $client "refused\n";
                   3220: 	}
                   3221: #---------------------------  read and remove temporary files (for auto-enrollment).
1.231   ! foxr     3222:     } elsif ($userinput =~/^autoretrieve/) {
1.212     foxr     3223: 	if (isClient) {
                   3224: 	    my ($cmd,$filename) = split(/:/,$userinput);
                   3225: 	    my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename;
                   3226: 	    if ( (-e $source) && ($filename ne '') ) {
                   3227: 		my $reply = '';
                   3228: 		if (open(my $fh,$source)) {
                   3229: 		    while (<$fh>) {
                   3230: 			chomp($_);
                   3231: 			$_ =~ s/^\s+//g;
                   3232: 			$_ =~ s/\s+$//g;
                   3233: 			$reply .= $_;
                   3234: 		    }
                   3235: 		    close($fh);
                   3236: 		    print $client &escape($reply)."\n";
                   3237: #                                unlink($source);
                   3238: 		} else {
                   3239: 		    print $client "error\n";
                   3240: 		}
                   3241: 	    } else {
                   3242: 		print $client "error\n";
                   3243: 	    }
                   3244: 	} else {
                   3245: 	    print $client "refused\n";
                   3246: 	}
                   3247: #---------------------  read and retrieve institutional code format (for support form).
1.231   ! foxr     3248:     } elsif ($userinput =~/^autoinstcodeformat/) {
1.212     foxr     3249: 	if (isClient) {
                   3250: 	    my $reply;
                   3251: 	    my($cmd,$cdom,$course) = split(/:/,$userinput);
                   3252: 	    my @pairs = split/\&/,$course;
                   3253: 	    my %instcodes = ();
                   3254: 	    my %codes = ();
                   3255: 	    my @codetitles = ();
                   3256: 	    my %cat_titles = ();
                   3257: 	    my %cat_order = ();
                   3258: 	    foreach (@pairs) {
                   3259: 		my ($key,$value) = split/=/,$_;
                   3260: 		$instcodes{&unescape($key)} = &unescape($value);
                   3261: 	    }
                   3262: 	    my $formatreply = &localenroll::instcode_format($cdom,\%instcodes,\%codes,\@codetitles,\%cat_titles,\%cat_order);
                   3263: 	    if ($formatreply eq 'ok') {
                   3264: 		my $codes_str = &hash2str(%codes);
                   3265: 		my $codetitles_str = &array2str(@codetitles);
                   3266: 		my $cat_titles_str = &hash2str(%cat_titles);
                   3267: 		my $cat_order_str = &hash2str(%cat_order);
                   3268: 		print $client $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':'.$cat_order_str."\n";
                   3269: 	    }
                   3270: 	} else {
                   3271: 	    print $client "refused\n";
                   3272: 	}
                   3273: # ------------------------------------------------------------- unknown command
                   3274: 	
                   3275:     } else {
                   3276: 	# unknown command
                   3277: 	print $client "unknown_cmd\n";
                   3278:     }
                   3279: # -------------------------------------------------------------------- complete
                   3280:     Debug("process_request - returning 1");
                   3281:     return 1;
                   3282: }
1.207     foxr     3283: #
                   3284: #   Decipher encoded traffic
                   3285: #  Parameters:
                   3286: #     input      - Encoded data.
                   3287: #  Returns:
                   3288: #     Decoded data or undef if encryption key was not yet negotiated.
                   3289: #  Implicit input:
                   3290: #     cipher  - This global holds the negotiated encryption key.
                   3291: #
1.211     albertel 3292: sub decipher {
1.207     foxr     3293:     my ($input)  = @_;
                   3294:     my $output = '';
1.212     foxr     3295:     
                   3296:     
1.207     foxr     3297:     if($cipher) {
                   3298: 	my($enc, $enclength, $encinput) = split(/:/, $input);
                   3299: 	for(my $encidx = 0; $encidx < length($encinput); $encidx += 16) {
                   3300: 	    $output .= 
                   3301: 		$cipher->decrypt(pack("H16", substr($encinput, $encidx, 16)));
                   3302: 	}
                   3303: 	return substr($output, 0, $enclength);
                   3304:     } else {
                   3305: 	return undef;
                   3306:     }
                   3307: }
                   3308: 
                   3309: #
                   3310: #   Register a command processor.  This function is invoked to register a sub
                   3311: #   to process a request.  Once registered, the ProcessRequest sub can automatically
                   3312: #   dispatch requests to an appropriate sub, and do the top level validity checking
                   3313: #   as well:
                   3314: #    - Is the keyword recognized.
                   3315: #    - Is the proper client type attempting the request.
                   3316: #    - Is the request encrypted if it has to be.
                   3317: #   Parameters:
                   3318: #    $request_name         - Name of the request being registered.
                   3319: #                           This is the command request that will match
                   3320: #                           against the hash keywords to lookup the information
                   3321: #                           associated with the dispatch information.
                   3322: #    $procedure           - Reference to a sub to call to process the request.
                   3323: #                           All subs get called as follows:
                   3324: #                             Procedure($cmd, $tail, $replyfd, $key)
                   3325: #                             $cmd    - the actual keyword that invoked us.
                   3326: #                             $tail   - the tail of the request that invoked us.
                   3327: #                             $replyfd- File descriptor connected to the client
                   3328: #    $must_encode          - True if the request must be encoded to be good.
                   3329: #    $client_ok            - True if it's ok for a client to request this.
                   3330: #    $manager_ok           - True if it's ok for a manager to request this.
                   3331: # Side effects:
                   3332: #      - On success, the Dispatcher hash has an entry added for the key $RequestName
                   3333: #      - On failure, the program will die as it's a bad internal bug to try to 
                   3334: #        register a duplicate command handler.
                   3335: #
1.211     albertel 3336: sub register_handler {
1.212     foxr     3337:     my ($request_name,$procedure,$must_encode,	$client_ok,$manager_ok)   = @_;
1.207     foxr     3338: 
                   3339:     #  Don't allow duplication#
                   3340:    
                   3341:     if (defined $Dispatcher{$request_name}) {
                   3342: 	die "Attempting to define a duplicate request handler for $request_name\n";
                   3343:     }
                   3344:     #   Build the client type mask:
                   3345:     
                   3346:     my $client_type_mask = 0;
                   3347:     if($client_ok) {
                   3348: 	$client_type_mask  |= $CLIENT_OK;
                   3349:     }
                   3350:     if($manager_ok) {
                   3351: 	$client_type_mask  |= $MANAGER_OK;
                   3352:     }
                   3353:    
                   3354:     #  Enter the hash:
                   3355:       
                   3356:     my @entry = ($procedure, $must_encode, $client_type_mask);
                   3357:    
                   3358:     $Dispatcher{$request_name} = \@entry;
                   3359:    
                   3360: }
                   3361: 
                   3362: 
                   3363: #------------------------------------------------------------------
                   3364: 
                   3365: 
                   3366: 
                   3367: 
1.141     foxr     3368: #
1.96      foxr     3369: #  Convert an error return code from lcpasswd to a string value.
                   3370: #
                   3371: sub lcpasswdstrerror {
                   3372:     my $ErrorCode = shift;
1.97      foxr     3373:     if(($ErrorCode < 0) || ($ErrorCode > $lastpwderror)) {
1.96      foxr     3374: 	return "lcpasswd Unrecognized error return value ".$ErrorCode;
                   3375:     } else {
1.98      foxr     3376: 	return $passwderrors[$ErrorCode];
1.96      foxr     3377:     }
                   3378: }
                   3379: 
1.97      foxr     3380: #
                   3381: # Convert an error return code from lcuseradd to a string value:
                   3382: #
                   3383: sub lcuseraddstrerror {
                   3384:     my $ErrorCode = shift;
                   3385:     if(($ErrorCode < 0) || ($ErrorCode > $lastadderror)) {
                   3386: 	return "lcuseradd - Unrecognized error code: ".$ErrorCode;
                   3387:     } else {
1.98      foxr     3388: 	return $adderrors[$ErrorCode];
1.97      foxr     3389:     }
                   3390: }
                   3391: 
1.23      harris41 3392: # grabs exception and records it to log before exiting
                   3393: sub catchexception {
1.27      albertel 3394:     my ($error)=@_;
1.25      www      3395:     $SIG{'QUIT'}='DEFAULT';
                   3396:     $SIG{__DIE__}='DEFAULT';
1.165     albertel 3397:     &status("Catching exception");
1.190     albertel 3398:     &logthis("<font color='red'>CRITICAL: "
1.134     albertel 3399:      ."ABNORMAL EXIT. Child $$ for server $thisserver died through "
1.27      albertel 3400:      ."a crash with this error msg->[$error]</font>");
1.57      www      3401:     &logthis('Famous last words: '.$status.' - '.$lastlog);
1.27      albertel 3402:     if ($client) { print $client "error: $error\n"; }
1.59      www      3403:     $server->close();
1.27      albertel 3404:     die($error);
1.23      harris41 3405: }
1.63      www      3406: sub timeout {
1.165     albertel 3407:     &status("Handling Timeout");
1.190     albertel 3408:     &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>");
1.63      www      3409:     &catchexception('Timeout');
                   3410: }
1.22      harris41 3411: # -------------------------------- Set signal handlers to record abnormal exits
                   3412: 
1.226     foxr     3413: 
1.22      harris41 3414: $SIG{'QUIT'}=\&catchexception;
                   3415: $SIG{__DIE__}=\&catchexception;
                   3416: 
1.81      matthew  3417: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
1.95      harris41 3418: &status("Read loncapa.conf and loncapa_apache.conf");
                   3419: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
1.141     foxr     3420: %perlvar=%{$perlvarref};
1.80      harris41 3421: undef $perlvarref;
1.19      www      3422: 
1.35      harris41 3423: # ----------------------------- Make sure this process is running from user=www
                   3424: my $wwwid=getpwnam('www');
                   3425: if ($wwwid!=$<) {
1.134     albertel 3426:    my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                   3427:    my $subj="LON: $currenthostid User ID mismatch";
1.37      harris41 3428:    system("echo 'User ID mismatch.  lond must be run as user www.' |\
1.35      harris41 3429:  mailto $emailto -s '$subj' > /dev/null");
                   3430:    exit 1;
                   3431: }
                   3432: 
1.19      www      3433: # --------------------------------------------- Check if other instance running
                   3434: 
                   3435: my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid";
                   3436: 
                   3437: if (-e $pidfile) {
                   3438:    my $lfh=IO::File->new("$pidfile");
                   3439:    my $pide=<$lfh>;
                   3440:    chomp($pide);
1.29      harris41 3441:    if (kill 0 => $pide) { die "already running"; }
1.19      www      3442: }
1.1       albertel 3443: 
                   3444: # ------------------------------------------------------------- Read hosts file
                   3445: 
                   3446: 
                   3447: 
                   3448: # establish SERVER socket, bind and listen.
                   3449: $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'},
                   3450:                                 Type      => SOCK_STREAM,
                   3451:                                 Proto     => 'tcp',
                   3452:                                 Reuse     => 1,
                   3453:                                 Listen    => 10 )
1.29      harris41 3454:   or die "making socket: $@\n";
1.1       albertel 3455: 
                   3456: # --------------------------------------------------------- Do global variables
                   3457: 
                   3458: # global variables
                   3459: 
1.134     albertel 3460: my %children               = ();       # keys are current child process IDs
1.1       albertel 3461: 
                   3462: sub REAPER {                        # takes care of dead children
                   3463:     $SIG{CHLD} = \&REAPER;
1.165     albertel 3464:     &status("Handling child death");
1.178     foxr     3465:     my $pid;
                   3466:     do {
                   3467: 	$pid = waitpid(-1,&WNOHANG());
                   3468: 	if (defined($children{$pid})) {
                   3469: 	    &logthis("Child $pid died");
                   3470: 	    delete($children{$pid});
1.183     albertel 3471: 	} elsif ($pid > 0) {
1.178     foxr     3472: 	    &logthis("Unknown Child $pid died");
                   3473: 	}
                   3474:     } while ( $pid > 0 );
                   3475:     foreach my $child (keys(%children)) {
                   3476: 	$pid = waitpid($child,&WNOHANG());
                   3477: 	if ($pid > 0) {
                   3478: 	    &logthis("Child $child - $pid looks like we missed it's death");
                   3479: 	    delete($children{$pid});
                   3480: 	}
1.176     albertel 3481:     }
1.165     albertel 3482:     &status("Finished Handling child death");
1.1       albertel 3483: }
                   3484: 
                   3485: sub HUNTSMAN {                      # signal handler for SIGINT
1.165     albertel 3486:     &status("Killing children (INT)");
1.1       albertel 3487:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
                   3488:     kill 'INT' => keys %children;
1.59      www      3489:     &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.1       albertel 3490:     my $execdir=$perlvar{'lonDaemons'};
                   3491:     unlink("$execdir/logs/lond.pid");
1.190     albertel 3492:     &logthis("<font color='red'>CRITICAL: Shutting down</font>");
1.165     albertel 3493:     &status("Done killing children");
1.1       albertel 3494:     exit;                           # clean up with dignity
                   3495: }
                   3496: 
                   3497: sub HUPSMAN {                      # signal handler for SIGHUP
                   3498:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
1.165     albertel 3499:     &status("Killing children for restart (HUP)");
1.1       albertel 3500:     kill 'INT' => keys %children;
1.59      www      3501:     &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.190     albertel 3502:     &logthis("<font color='red'>CRITICAL: Restarting</font>");
1.134     albertel 3503:     my $execdir=$perlvar{'lonDaemons'};
1.30      harris41 3504:     unlink("$execdir/logs/lond.pid");
1.165     albertel 3505:     &status("Restarting self (HUP)");
1.1       albertel 3506:     exec("$execdir/lond");         # here we go again
                   3507: }
                   3508: 
1.144     foxr     3509: #
1.148     foxr     3510: #    Kill off hashes that describe the host table prior to re-reading it.
                   3511: #    Hashes affected are:
1.200     matthew  3512: #       %hostid, %hostdom %hostip %hostdns.
1.148     foxr     3513: #
                   3514: sub KillHostHashes {
                   3515:     foreach my $key (keys %hostid) {
                   3516: 	delete $hostid{$key};
                   3517:     }
                   3518:     foreach my $key (keys %hostdom) {
                   3519: 	delete $hostdom{$key};
                   3520:     }
                   3521:     foreach my $key (keys %hostip) {
                   3522: 	delete $hostip{$key};
                   3523:     }
1.200     matthew  3524:     foreach my $key (keys %hostdns) {
                   3525: 	delete $hostdns{$key};
                   3526:     }
1.148     foxr     3527: }
                   3528: #
                   3529: #   Read in the host table from file and distribute it into the various hashes:
                   3530: #
                   3531: #    - %hostid  -  Indexed by IP, the loncapa hostname.
                   3532: #    - %hostdom -  Indexed by  loncapa hostname, the domain.
                   3533: #    - %hostip  -  Indexed by hostid, the Ip address of the host.
                   3534: sub ReadHostTable {
                   3535: 
                   3536:     open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
1.200     matthew  3537:     my $myloncapaname = $perlvar{'lonHostID'};
                   3538:     Debug("My loncapa name is : $myloncapaname");
1.148     foxr     3539:     while (my $configline=<CONFIG>) {
1.178     foxr     3540: 	if (!($configline =~ /^\s*\#/)) {
                   3541: 	    my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
                   3542: 	    chomp($ip); $ip=~s/\D+$//;
1.200     matthew  3543: 	    $hostid{$ip}=$id;         # LonCAPA name of host by IP.
                   3544: 	    $hostdom{$id}=$domain;    # LonCAPA domain name of host. 
                   3545: 	    $hostip{$id}=$ip;	      # IP address of host.
                   3546: 	    $hostdns{$name} = $id;    # LonCAPA name of host by DNS.
                   3547: 
                   3548: 	    if ($id eq $perlvar{'lonHostID'}) { 
                   3549: 		Debug("Found me in the host table: $name");
                   3550: 		$thisserver=$name; 
                   3551: 	    }
1.178     foxr     3552: 	}
1.148     foxr     3553:     }
                   3554:     close(CONFIG);
                   3555: }
                   3556: #
                   3557: #  Reload the Apache daemon's state.
1.150     foxr     3558: #  This is done by invoking /home/httpd/perl/apachereload
                   3559: #  a setuid perl script that can be root for us to do this job.
1.148     foxr     3560: #
                   3561: sub ReloadApache {
1.150     foxr     3562:     my $execdir = $perlvar{'lonDaemons'};
                   3563:     my $script  = $execdir."/apachereload";
                   3564:     system($script);
1.148     foxr     3565: }
                   3566: 
                   3567: #
1.144     foxr     3568: #   Called in response to a USR2 signal.
                   3569: #   - Reread hosts.tab
                   3570: #   - All children connected to hosts that were removed from hosts.tab
                   3571: #     are killed via SIGINT
                   3572: #   - All children connected to previously existing hosts are sent SIGUSR1
                   3573: #   - Our internal hosts hash is updated to reflect the new contents of
                   3574: #     hosts.tab causing connections from hosts added to hosts.tab to
                   3575: #     now be honored.
                   3576: #
                   3577: sub UpdateHosts {
1.165     albertel 3578:     &status("Reload hosts.tab");
1.147     foxr     3579:     logthis('<font color="blue"> Updating connections </font>');
1.148     foxr     3580:     #
                   3581:     #  The %children hash has the set of IP's we currently have children
                   3582:     #  on.  These need to be matched against records in the hosts.tab
                   3583:     #  Any ip's no longer in the table get killed off they correspond to
                   3584:     #  either dropped or changed hosts.  Note that the re-read of the table
                   3585:     #  will take care of new and changed hosts as connections come into being.
                   3586: 
                   3587: 
                   3588:     KillHostHashes;
                   3589:     ReadHostTable;
                   3590: 
                   3591:     foreach my $child (keys %children) {
                   3592: 	my $childip = $children{$child};
                   3593: 	if(!$hostid{$childip}) {
1.149     foxr     3594: 	    logthis('<font color="blue"> UpdateHosts killing child '
                   3595: 		    ." $child for ip $childip </font>");
1.148     foxr     3596: 	    kill('INT', $child);
1.149     foxr     3597: 	} else {
                   3598: 	    logthis('<font color="green"> keeping child for ip '
                   3599: 		    ." $childip (pid=$child) </font>");
1.148     foxr     3600: 	}
                   3601:     }
                   3602:     ReloadApache;
1.165     albertel 3603:     &status("Finished reloading hosts.tab");
1.144     foxr     3604: }
                   3605: 
1.148     foxr     3606: 
1.57      www      3607: sub checkchildren {
1.165     albertel 3608:     &status("Checking on the children (sending signals)");
1.57      www      3609:     &initnewstatus();
                   3610:     &logstatus();
                   3611:     &logthis('Going to check on the children');
1.134     albertel 3612:     my $docdir=$perlvar{'lonDocRoot'};
1.61      harris41 3613:     foreach (sort keys %children) {
1.221     albertel 3614: 	#sleep 1;
1.57      www      3615:         unless (kill 'USR1' => $_) {
                   3616: 	    &logthis ('Child '.$_.' is dead');
                   3617:             &logstatus($$.' is dead');
1.221     albertel 3618: 	    delete($children{$_});
1.57      www      3619:         } 
1.61      harris41 3620:     }
1.63      www      3621:     sleep 5;
1.212     foxr     3622:     $SIG{ALRM} = sub { Debug("timeout"); 
                   3623: 		       die "timeout";  };
1.113     albertel 3624:     $SIG{__DIE__} = 'DEFAULT';
1.165     albertel 3625:     &status("Checking on the children (waiting for reports)");
1.63      www      3626:     foreach (sort keys %children) {
                   3627:         unless (-e "$docdir/lon-status/londchld/$_.txt") {
1.113     albertel 3628:           eval {
                   3629:             alarm(300);
1.63      www      3630: 	    &logthis('Child '.$_.' did not respond');
1.67      albertel 3631: 	    kill 9 => $_;
1.131     albertel 3632: 	    #$emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
                   3633: 	    #$subj="LON: $currenthostid killed lond process $_";
                   3634: 	    #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;
                   3635: 	    #$execdir=$perlvar{'lonDaemons'};
                   3636: 	    #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;
1.221     albertel 3637: 	    delete($children{$_});
1.113     albertel 3638: 	    alarm(0);
                   3639: 	  }
1.63      www      3640:         }
                   3641:     }
1.113     albertel 3642:     $SIG{ALRM} = 'DEFAULT';
1.155     albertel 3643:     $SIG{__DIE__} = \&catchexception;
1.165     albertel 3644:     &status("Finished checking children");
1.221     albertel 3645:     &logthis('Finished Checking children');
1.57      www      3646: }
                   3647: 
1.1       albertel 3648: # --------------------------------------------------------------------- Logging
                   3649: 
                   3650: sub logthis {
                   3651:     my $message=shift;
                   3652:     my $execdir=$perlvar{'lonDaemons'};
                   3653:     my $fh=IO::File->new(">>$execdir/logs/lond.log");
                   3654:     my $now=time;
                   3655:     my $local=localtime($now);
1.58      www      3656:     $lastlog=$local.': '.$message;
1.1       albertel 3657:     print $fh "$local ($$): $message\n";
                   3658: }
                   3659: 
1.77      foxr     3660: # ------------------------- Conditional log if $DEBUG true.
                   3661: sub Debug {
                   3662:     my $message = shift;
                   3663:     if($DEBUG) {
                   3664: 	&logthis($message);
                   3665:     }
                   3666: }
1.161     foxr     3667: 
                   3668: #
                   3669: #   Sub to do replies to client.. this gives a hook for some
                   3670: #   debug tracing too:
                   3671: #  Parameters:
                   3672: #     fd      - File open on client.
                   3673: #     reply   - Text to send to client.
                   3674: #     request - Original request from client.
                   3675: #
                   3676: sub Reply {
1.192     foxr     3677:     my ($fd, $reply, $request) = @_;
1.161     foxr     3678:     print $fd $reply;
                   3679:     Debug("Request was $request  Reply was $reply");
                   3680: 
1.212     foxr     3681:     $Transactions++;
                   3682: 
                   3683: 
                   3684: }
                   3685: 
                   3686: 
                   3687: #
                   3688: #    Sub to report a failure.
                   3689: #    This function:
                   3690: #     -   Increments the failure statistic counters.
                   3691: #     -   Invokes Reply to send the error message to the client.
                   3692: # Parameters:
                   3693: #    fd       - File descriptor open on the client
                   3694: #    reply    - Reply text to emit.
                   3695: #    request  - The original request message (used by Reply
                   3696: #               to debug if that's enabled.
                   3697: # Implicit outputs:
                   3698: #    $Failures- The number of failures is incremented.
                   3699: #    Reply (invoked here) sends a message to the 
                   3700: #    client:
                   3701: #
                   3702: sub Failure {
                   3703:     my $fd      = shift;
                   3704:     my $reply   = shift;
                   3705:     my $request = shift;
                   3706:    
                   3707:     $Failures++;
                   3708:     Reply($fd, $reply, $request);      # That's simple eh?
1.161     foxr     3709: }
1.57      www      3710: # ------------------------------------------------------------------ Log status
                   3711: 
                   3712: sub logstatus {
1.178     foxr     3713:     &status("Doing logging");
                   3714:     my $docdir=$perlvar{'lonDocRoot'};
                   3715:     {
                   3716: 	my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");
1.200     matthew  3717:         print $fh $status."\n".$lastlog."\n".time."\n$keymode";
1.178     foxr     3718:         $fh->close();
                   3719:     }
1.221     albertel 3720:     &status("Finished $$.txt");
                   3721:     {
                   3722: 	open(LOG,">>$docdir/lon-status/londstatus.txt");
                   3723: 	flock(LOG,LOCK_EX);
                   3724: 	print LOG $$."\t".$clientname."\t".$currenthostid."\t"
                   3725: 	    .$status."\t".$lastlog."\t $keymode\n";
                   3726: 	flock(DB,LOCK_UN);
                   3727: 	close(LOG);
                   3728:     }
1.178     foxr     3729:     &status("Finished logging");
1.57      www      3730: }
                   3731: 
                   3732: sub initnewstatus {
                   3733:     my $docdir=$perlvar{'lonDocRoot'};
                   3734:     my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt");
                   3735:     my $now=time;
                   3736:     my $local=localtime($now);
                   3737:     print $fh "LOND status $local - parent $$\n\n";
1.64      www      3738:     opendir(DIR,"$docdir/lon-status/londchld");
1.134     albertel 3739:     while (my $filename=readdir(DIR)) {
1.64      www      3740:         unlink("$docdir/lon-status/londchld/$filename");
                   3741:     }
                   3742:     closedir(DIR);
1.57      www      3743: }
                   3744: 
                   3745: # -------------------------------------------------------------- Status setting
                   3746: 
                   3747: sub status {
                   3748:     my $what=shift;
                   3749:     my $now=time;
                   3750:     my $local=localtime($now);
1.178     foxr     3751:     $status=$local.': '.$what;
                   3752:     $0='lond: '.$what.' '.$local;
1.57      www      3753: }
1.11      www      3754: 
                   3755: # -------------------------------------------------------- Escape Special Chars
                   3756: 
                   3757: sub escape {
                   3758:     my $str=shift;
                   3759:     $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
                   3760:     return $str;
                   3761: }
                   3762: 
                   3763: # ----------------------------------------------------- Un-Escape Special Chars
                   3764: 
                   3765: sub unescape {
                   3766:     my $str=shift;
                   3767:     $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
                   3768:     return $str;
                   3769: }
                   3770: 
1.1       albertel 3771: # ----------------------------------------------------------- Send USR1 to lonc
                   3772: 
                   3773: sub reconlonc {
                   3774:     my $peerfile=shift;
                   3775:     &logthis("Trying to reconnect for $peerfile");
                   3776:     my $loncfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
                   3777:     if (my $fh=IO::File->new("$loncfile")) {
                   3778: 	my $loncpid=<$fh>;
                   3779:         chomp($loncpid);
                   3780:         if (kill 0 => $loncpid) {
                   3781: 	    &logthis("lonc at pid $loncpid responding, sending USR1");
                   3782:             kill USR1 => $loncpid;
                   3783:         } else {
1.9       www      3784: 	    &logthis(
1.190     albertel 3785:               "<font color='red'>CRITICAL: "
1.9       www      3786:              ."lonc at pid $loncpid not responding, giving up</font>");
1.1       albertel 3787:         }
                   3788:     } else {
1.190     albertel 3789:       &logthis('<font color="red">CRITICAL: lonc not running, giving up</font>');
1.1       albertel 3790:     }
                   3791: }
                   3792: 
                   3793: # -------------------------------------------------- Non-critical communication
1.11      www      3794: 
1.1       albertel 3795: sub subreply {
                   3796:     my ($cmd,$server)=@_;
                   3797:     my $peerfile="$perlvar{'lonSockDir'}/$server";
                   3798:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
                   3799:                                       Type    => SOCK_STREAM,
                   3800:                                       Timeout => 10)
                   3801:        or return "con_lost";
                   3802:     print $sclient "$cmd\n";
                   3803:     my $answer=<$sclient>;
                   3804:     chomp($answer);
                   3805:     if (!$answer) { $answer="con_lost"; }
                   3806:     return $answer;
                   3807: }
                   3808: 
                   3809: sub reply {
                   3810:   my ($cmd,$server)=@_;
                   3811:   my $answer;
1.115     albertel 3812:   if ($server ne $currenthostid) { 
1.1       albertel 3813:     $answer=subreply($cmd,$server);
                   3814:     if ($answer eq 'con_lost') {
                   3815: 	$answer=subreply("ping",$server);
                   3816:         if ($answer ne $server) {
1.115     albertel 3817: 	    &logthis("sub reply: answer != server answer is $answer, server is $server");
1.1       albertel 3818:            &reconlonc("$perlvar{'lonSockDir'}/$server");
                   3819:         }
                   3820:         $answer=subreply($cmd,$server);
                   3821:     }
                   3822:   } else {
                   3823:     $answer='self_reply';
                   3824:   } 
                   3825:   return $answer;
                   3826: }
                   3827: 
1.13      www      3828: # -------------------------------------------------------------- Talk to lonsql
                   3829: 
1.12      harris41 3830: sub sqlreply {
                   3831:     my ($cmd)=@_;
                   3832:     my $answer=subsqlreply($cmd);
                   3833:     if ($answer eq 'con_lost') { $answer=subsqlreply($cmd); }
                   3834:     return $answer;
                   3835: }
                   3836: 
                   3837: sub subsqlreply {
                   3838:     my ($cmd)=@_;
                   3839:     my $unixsock="mysqlsock";
                   3840:     my $peerfile="$perlvar{'lonSockDir'}/$unixsock";
                   3841:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
                   3842:                                       Type    => SOCK_STREAM,
                   3843:                                       Timeout => 10)
                   3844:        or return "con_lost";
                   3845:     print $sclient "$cmd\n";
                   3846:     my $answer=<$sclient>;
                   3847:     chomp($answer);
                   3848:     if (!$answer) { $answer="con_lost"; }
                   3849:     return $answer;
                   3850: }
                   3851: 
1.1       albertel 3852: # -------------------------------------------- Return path to profile directory
1.11      www      3853: 
1.1       albertel 3854: sub propath {
                   3855:     my ($udom,$uname)=@_;
                   3856:     $udom=~s/\W//g;
                   3857:     $uname=~s/\W//g;
1.16      www      3858:     my $subdir=$uname.'__';
1.1       albertel 3859:     $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
                   3860:     my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
                   3861:     return $proname;
                   3862: } 
                   3863: 
                   3864: # --------------------------------------- Is this the home server of an author?
1.11      www      3865: 
1.1       albertel 3866: sub ishome {
                   3867:     my $author=shift;
                   3868:     $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
                   3869:     my ($udom,$uname)=split(/\//,$author);
                   3870:     my $proname=propath($udom,$uname);
                   3871:     if (-e $proname) {
                   3872: 	return 'owner';
                   3873:     } else {
                   3874:         return 'not_owner';
                   3875:     }
                   3876: }
                   3877: 
                   3878: # ======================================================= Continue main program
                   3879: # ---------------------------------------------------- Fork once and dissociate
                   3880: 
1.134     albertel 3881: my $fpid=fork;
1.1       albertel 3882: exit if $fpid;
1.29      harris41 3883: die "Couldn't fork: $!" unless defined ($fpid);
1.1       albertel 3884: 
1.29      harris41 3885: POSIX::setsid() or die "Can't start new session: $!";
1.1       albertel 3886: 
                   3887: # ------------------------------------------------------- Write our PID on disk
                   3888: 
1.134     albertel 3889: my $execdir=$perlvar{'lonDaemons'};
1.1       albertel 3890: open (PIDSAVE,">$execdir/logs/lond.pid");
                   3891: print PIDSAVE "$$\n";
                   3892: close(PIDSAVE);
1.190     albertel 3893: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
1.57      www      3894: &status('Starting');
1.1       albertel 3895: 
1.106     foxr     3896: 
1.1       albertel 3897: 
                   3898: # ----------------------------------------------------- Install signal handlers
                   3899: 
1.57      www      3900: 
1.1       albertel 3901: $SIG{CHLD} = \&REAPER;
                   3902: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
                   3903: $SIG{HUP}  = \&HUPSMAN;
1.57      www      3904: $SIG{USR1} = \&checkchildren;
1.144     foxr     3905: $SIG{USR2} = \&UpdateHosts;
1.106     foxr     3906: 
1.148     foxr     3907: #  Read the host hashes:
                   3908: 
                   3909: ReadHostTable;
1.106     foxr     3910: 
                   3911: # --------------------------------------------------------------
                   3912: #   Accept connections.  When a connection comes in, it is validated
                   3913: #   and if good, a child process is created to process transactions
                   3914: #   along the connection.
                   3915: 
1.1       albertel 3916: while (1) {
1.165     albertel 3917:     &status('Starting accept');
1.106     foxr     3918:     $client = $server->accept() or next;
1.165     albertel 3919:     &status('Accepted '.$client.' off to spawn');
1.106     foxr     3920:     make_new_child($client);
1.165     albertel 3921:     &status('Finished spawning');
1.1       albertel 3922: }
                   3923: 
1.212     foxr     3924: sub make_new_child {
                   3925:     my $pid;
                   3926: #    my $cipher;     # Now global
                   3927:     my $sigset;
1.178     foxr     3928: 
1.212     foxr     3929:     $client = shift;
                   3930:     &status('Starting new child '.$client);
                   3931:     &logthis('<font color="green"> Attempting to start child ('.$client.
                   3932: 	     ")</font>");    
                   3933:     # block signal for fork
                   3934:     $sigset = POSIX::SigSet->new(SIGINT);
                   3935:     sigprocmask(SIG_BLOCK, $sigset)
                   3936:         or die "Can't block SIGINT for fork: $!\n";
1.178     foxr     3937: 
1.212     foxr     3938:     die "fork: $!" unless defined ($pid = fork);
1.178     foxr     3939: 
1.212     foxr     3940:     $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of
                   3941: 	                               # connection liveness.
1.178     foxr     3942: 
1.212     foxr     3943:     #
                   3944:     #  Figure out who we're talking to so we can record the peer in 
                   3945:     #  the pid hash.
                   3946:     #
                   3947:     my $caller = getpeername($client);
                   3948:     my ($port,$iaddr);
                   3949:     if (defined($caller) && length($caller) > 0) {
                   3950: 	($port,$iaddr)=unpack_sockaddr_in($caller);
                   3951:     } else {
                   3952: 	&logthis("Unable to determine who caller was, getpeername returned nothing");
                   3953:     }
                   3954:     if (defined($iaddr)) {
                   3955: 	$clientip  = inet_ntoa($iaddr);
                   3956: 	Debug("Connected with $clientip");
                   3957: 	$clientdns = gethostbyaddr($iaddr, AF_INET);
                   3958: 	Debug("Connected with $clientdns by name");
                   3959:     } else {
                   3960: 	&logthis("Unable to determine clientip");
                   3961: 	$clientip='Unavailable';
                   3962:     }
                   3963:     
                   3964:     if ($pid) {
                   3965:         # Parent records the child's birth and returns.
                   3966:         sigprocmask(SIG_UNBLOCK, $sigset)
                   3967:             or die "Can't unblock SIGINT for fork: $!\n";
                   3968:         $children{$pid} = $clientip;
                   3969:         &status('Started child '.$pid);
                   3970:         return;
                   3971:     } else {
                   3972:         # Child can *not* return from this subroutine.
                   3973:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
                   3974:         $SIG{CHLD} = 'DEFAULT'; #make this default so that pwauth returns 
                   3975:                                 #don't get intercepted
                   3976:         $SIG{USR1}= \&logstatus;
                   3977:         $SIG{ALRM}= \&timeout;
                   3978:         $lastlog='Forked ';
                   3979:         $status='Forked';
1.178     foxr     3980: 
1.212     foxr     3981:         # unblock signals
                   3982:         sigprocmask(SIG_UNBLOCK, $sigset)
                   3983:             or die "Can't unblock SIGINT for fork: $!\n";
1.178     foxr     3984: 
1.212     foxr     3985: #        my $tmpsnum=0;            # Now global
                   3986: #---------------------------------------------------- kerberos 5 initialization
                   3987:         &Authen::Krb5::init_context();
                   3988:         &Authen::Krb5::init_ets();
1.209     albertel 3989: 
1.212     foxr     3990: 	&status('Accepted connection');
                   3991: # =============================================================================
                   3992:             # do something with the connection
                   3993: # -----------------------------------------------------------------------------
                   3994: 	# see if we know client and 'check' for spoof IP by ineffective challenge
1.178     foxr     3995: 
1.212     foxr     3996: 	ReadManagerTable;	# May also be a manager!!
                   3997: 	
                   3998: 	my $clientrec=($hostid{$clientip}     ne undef);
                   3999: 	my $ismanager=($managers{$clientip}    ne undef);
                   4000: 	$clientname  = "[unknonwn]";
                   4001: 	if($clientrec) {	# Establish client type.
                   4002: 	    $ConnectionType = "client";
                   4003: 	    $clientname = $hostid{$clientip};
                   4004: 	    if($ismanager) {
                   4005: 		$ConnectionType = "both";
                   4006: 	    }
                   4007: 	} else {
                   4008: 	    $ConnectionType = "manager";
                   4009: 	    $clientname = $managers{$clientip};
                   4010: 	}
                   4011: 	my $clientok;
1.178     foxr     4012: 
1.212     foxr     4013: 	if ($clientrec || $ismanager) {
                   4014: 	    &status("Waiting for init from $clientip $clientname");
                   4015: 	    &logthis('<font color="yellow">INFO: Connection, '.
                   4016: 		     $clientip.
                   4017: 		  " ($clientname) connection type = $ConnectionType </font>" );
                   4018: 	    &status("Connecting $clientip  ($clientname))"); 
                   4019: 	    my $remotereq=<$client>;
                   4020: 	    chomp($remotereq);
                   4021: 	    Debug("Got init: $remotereq");
                   4022: 	    my $inikeyword = split(/:/, $remotereq);
                   4023: 	    if ($remotereq =~ /^init/) {
                   4024: 		&sethost("sethost:$perlvar{'lonHostID'}");
                   4025: 		#
                   4026: 		#  If the remote is attempting a local init... give that a try:
                   4027: 		#
                   4028: 		my ($i, $inittype) = split(/:/, $remotereq);
1.209     albertel 4029: 
1.212     foxr     4030: 		# If the connection type is ssl, but I didn't get my
                   4031: 		# certificate files yet, then I'll drop  back to 
                   4032: 		# insecure (if allowed).
                   4033: 		
                   4034: 		if($inittype eq "ssl") {
                   4035: 		    my ($ca, $cert) = lonssl::CertificateFile;
                   4036: 		    my $kfile       = lonssl::KeyFile;
                   4037: 		    if((!$ca)   || 
                   4038: 		       (!$cert) || 
                   4039: 		       (!$kfile)) {
                   4040: 			$inittype = ""; # This forces insecure attempt.
                   4041: 			&logthis("<font color=\"blue\"> Certificates not "
                   4042: 				 ."installed -- trying insecure auth</font>");
1.224     foxr     4043: 		    } else {	# SSL certificates are in place so
1.212     foxr     4044: 		    }		# Leave the inittype alone.
                   4045: 		}
                   4046: 
                   4047: 		if($inittype eq "local") {
                   4048: 		    my $key = LocalConnection($client, $remotereq);
                   4049: 		    if($key) {
                   4050: 			Debug("Got local key $key");
                   4051: 			$clientok     = 1;
                   4052: 			my $cipherkey = pack("H32", $key);
                   4053: 			$cipher       = new IDEA($cipherkey);
                   4054: 			print $client "ok:local\n";
                   4055: 			&logthis('<font color="green"'
                   4056: 				 . "Successful local authentication </font>");
                   4057: 			$keymode = "local"
1.178     foxr     4058: 		    } else {
1.212     foxr     4059: 			Debug("Failed to get local key");
                   4060: 			$clientok = 0;
                   4061: 			shutdown($client, 3);
                   4062: 			close $client;
1.178     foxr     4063: 		    }
1.212     foxr     4064: 		} elsif ($inittype eq "ssl") {
                   4065: 		    my $key = SSLConnection($client);
                   4066: 		    if ($key) {
                   4067: 			$clientok = 1;
                   4068: 			my $cipherkey = pack("H32", $key);
                   4069: 			$cipher       = new IDEA($cipherkey);
                   4070: 			&logthis('<font color="green">'
                   4071: 				 ."Successfull ssl authentication with $clientname </font>");
                   4072: 			$keymode = "ssl";
                   4073: 	     
1.178     foxr     4074: 		    } else {
1.212     foxr     4075: 			$clientok = 0;
                   4076: 			close $client;
1.178     foxr     4077: 		    }
1.212     foxr     4078: 	   
                   4079: 		} else {
                   4080: 		    my $ok = InsecureConnection($client);
                   4081: 		    if($ok) {
                   4082: 			$clientok = 1;
                   4083: 			&logthis('<font color="green">'
                   4084: 				 ."Successful insecure authentication with $clientname </font>");
                   4085: 			print $client "ok\n";
                   4086: 			$keymode = "insecure";
1.178     foxr     4087: 		    } else {
1.212     foxr     4088: 			&logthis('<font color="yellow">'
                   4089: 				  ."Attempted insecure connection disallowed </font>");
                   4090: 			close $client;
                   4091: 			$clientok = 0;
1.178     foxr     4092: 			
                   4093: 		    }
                   4094: 		}
1.212     foxr     4095: 	    } else {
                   4096: 		&logthis(
                   4097: 			 "<font color='blue'>WARNING: "
                   4098: 			 ."$clientip failed to initialize: >$remotereq< </font>");
                   4099: 		&status('No init '.$clientip);
                   4100: 	    }
                   4101: 	    
                   4102: 	} else {
                   4103: 	    &logthis(
                   4104: 		     "<font color='blue'>WARNING: Unknown client $clientip</font>");
                   4105: 	    &status('Hung up on '.$clientip);
                   4106: 	}
                   4107:  
                   4108: 	if ($clientok) {
                   4109: # ---------------- New known client connecting, could mean machine online again
                   4110: 	    
                   4111: 	    foreach my $id (keys(%hostip)) {
                   4112: 		if ($hostip{$id} ne $clientip ||
                   4113: 		    $hostip{$currenthostid} eq $clientip) {
                   4114: 		    # no need to try to do recon's to myself
                   4115: 		    next;
                   4116: 		}
                   4117: 		&reconlonc("$perlvar{'lonSockDir'}/$id");
                   4118: 	    }
                   4119: 	    &logthis("<font color='green'>Established connection: $clientname</font>");
                   4120: 	    &status('Will listen to '.$clientname);
                   4121: # ------------------------------------------------------------ Process requests
                   4122: 	    my $keep_going = 1;
                   4123: 	    my $user_input;
                   4124: 	    while(($user_input = get_request) && $keep_going) {
                   4125: 		alarm(120);
                   4126: 		Debug("Main: Got $user_input\n");
                   4127: 		$keep_going = &process_request($user_input);
1.178     foxr     4128: 		alarm(0);
1.212     foxr     4129: 		&status('Listening to '.$clientname." ($keymode)");	   
1.161     foxr     4130: 	    }
1.212     foxr     4131: 
1.59      www      4132: # --------------------------------------------- client unknown or fishy, refuse
1.212     foxr     4133: 	}  else {
1.161     foxr     4134: 	    print $client "refused\n";
                   4135: 	    $client->close();
1.190     albertel 4136: 	    &logthis("<font color='blue'>WARNING: "
1.161     foxr     4137: 		     ."Rejected client $clientip, closing connection</font>");
                   4138: 	}
1.212     foxr     4139:     }            
1.161     foxr     4140:     
1.1       albertel 4141: # =============================================================================
1.161     foxr     4142:     
1.190     albertel 4143:     &logthis("<font color='red'>CRITICAL: "
1.161     foxr     4144: 	     ."Disconnect from $clientip ($clientname)</font>");    
                   4145:     
                   4146:     
                   4147:     # this exit is VERY important, otherwise the child will become
                   4148:     # a producer of more and more children, forking yourself into
                   4149:     # process death.
                   4150:     exit;
1.106     foxr     4151:     
1.78      foxr     4152: }
                   4153: 
                   4154: 
                   4155: #
                   4156: #   Checks to see if the input roleput request was to set
                   4157: # an author role.  If so, invokes the lchtmldir script to set
                   4158: # up a correct public_html 
                   4159: # Parameters:
                   4160: #    request   - The request sent to the rolesput subchunk.
                   4161: #                We're looking for  /domain/_au
                   4162: #    domain    - The domain in which the user is having roles doctored.
                   4163: #    user      - Name of the user for which the role is being put.
                   4164: #    authtype  - The authentication type associated with the user.
                   4165: #
1.230     foxr     4166: sub manage_permissions
1.78      foxr     4167: {
1.192     foxr     4168: 
                   4169:     my ($request, $domain, $user, $authtype) = @_;
1.78      foxr     4170: 
                   4171:     # See if the request is of the form /$domain/_au
                   4172:     if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput...
                   4173: 	my $execdir = $perlvar{'lonDaemons'};
                   4174: 	my $userhome= "/home/$user" ;
1.134     albertel 4175: 	&logthis("system $execdir/lchtmldir $userhome $user $authtype");
1.78      foxr     4176: 	system("$execdir/lchtmldir $userhome $user $authtype");
                   4177:     }
                   4178: }
1.222     foxr     4179: 
                   4180: 
                   4181: #
                   4182: #  Return the full path of a user password file, whether it exists or not.
                   4183: # Parameters:
                   4184: #   domain     - Domain in which the password file lives.
                   4185: #   user       - name of the user.
                   4186: # Returns:
                   4187: #    Full passwd path:
                   4188: #
                   4189: sub password_path {
                   4190:     my ($domain, $user) = @_;
                   4191: 
                   4192: 
                   4193:     my $path   = &propath($domain, $user);
                   4194:     $path  .= "/passwd";
                   4195: 
                   4196:     return $path;
                   4197: }
                   4198: 
                   4199: #   Password Filename
                   4200: #   Returns the path to a passwd file given domain and user... only if
                   4201: #  it exists.
                   4202: # Parameters:
                   4203: #   domain    - Domain in which to search.
                   4204: #   user      - username.
                   4205: # Returns:
                   4206: #   - If the password file exists returns its path.
                   4207: #   - If the password file does not exist, returns undefined.
                   4208: #
                   4209: sub password_filename {
                   4210:     my ($domain, $user) = @_;
                   4211: 
                   4212:     Debug ("PasswordFilename called: dom = $domain user = $user");
                   4213: 
                   4214:     my $path  = &password_path($domain, $user);
                   4215:     Debug("PasswordFilename got path: $path");
                   4216:     if(-e $path) {
                   4217: 	return $path;
                   4218:     } else {
                   4219: 	return undef;
                   4220:     }
                   4221: }
                   4222: 
                   4223: #
                   4224: #   Rewrite the contents of the user's passwd file.
                   4225: #  Parameters:
                   4226: #    domain    - domain of the user.
                   4227: #    name      - User's name.
                   4228: #    contents  - New contents of the file.
                   4229: # Returns:
                   4230: #   0    - Failed.
                   4231: #   1    - Success.
                   4232: #
                   4233: sub rewrite_password_file {
                   4234:     my ($domain, $user, $contents) = @_;
                   4235: 
                   4236:     my $file = &password_filename($domain, $user);
                   4237:     if (defined $file) {
                   4238: 	my $pf = IO::File->new(">$file");
                   4239: 	if($pf) {
                   4240: 	    print $pf "$contents\n";
                   4241: 	    return 1;
                   4242: 	} else {
                   4243: 	    return 0;
                   4244: 	}
                   4245:     } else {
                   4246: 	return 0;
                   4247:     }
                   4248: 
                   4249: }
                   4250: 
1.78      foxr     4251: #
1.222     foxr     4252: #   get_auth_type - Determines the authorization type of a user in a domain.
1.78      foxr     4253: 
                   4254: #     Returns the authorization type or nouser if there is no such user.
                   4255: #
1.222     foxr     4256: sub get_auth_type 
1.78      foxr     4257: {
1.192     foxr     4258: 
                   4259:     my ($domain, $user)  = @_;
1.78      foxr     4260: 
1.222     foxr     4261:     Debug("get_auth_type( $domain, $user ) \n");
1.78      foxr     4262:     my $proname    = &propath($domain, $user); 
                   4263:     my $passwdfile = "$proname/passwd";
                   4264:     if( -e $passwdfile ) {
                   4265: 	my $pf = IO::File->new($passwdfile);
                   4266: 	my $realpassword = <$pf>;
                   4267: 	chomp($realpassword);
1.79      foxr     4268: 	Debug("Password info = $realpassword\n");
1.78      foxr     4269: 	my ($authtype, $contentpwd) = split(/:/, $realpassword);
1.79      foxr     4270: 	Debug("Authtype = $authtype, content = $contentpwd\n");
1.78      foxr     4271: 	my $availinfo = '';
1.91      albertel 4272: 	if($authtype eq 'krb4' or $authtype eq 'krb5') {
1.78      foxr     4273: 	    $availinfo = $contentpwd;
                   4274: 	}
1.79      foxr     4275: 
1.78      foxr     4276: 	return "$authtype:$availinfo";
1.224     foxr     4277:     } else {
1.79      foxr     4278: 	Debug("Returning nouser");
1.78      foxr     4279: 	return "nouser";
                   4280:     }
1.1       albertel 4281: }
                   4282: 
1.220     foxr     4283: #
                   4284: #  Validate a user given their domain, name and password.  This utility
                   4285: #  function is used by both  AuthenticateHandler and ChangePasswordHandler
                   4286: #  to validate the login credentials of a user.
                   4287: # Parameters:
                   4288: #    $domain    - The domain being logged into (this is required due to
                   4289: #                 the capability for multihomed systems.
                   4290: #    $user      - The name of the user being validated.
                   4291: #    $password  - The user's propoposed password.
                   4292: #
                   4293: # Returns:
                   4294: #     1        - The domain,user,pasword triplet corresponds to a valid
                   4295: #                user.
                   4296: #     0        - The domain,user,password triplet is not a valid user.
                   4297: #
                   4298: sub validate_user {
                   4299:     my ($domain, $user, $password) = @_;
                   4300: 
                   4301: 
                   4302:     # Why negative ~pi you may well ask?  Well this function is about
                   4303:     # authentication, and therefore very important to get right.
                   4304:     # I've initialized the flag that determines whether or not I've 
                   4305:     # validated correctly to a value it's not supposed to get.
                   4306:     # At the end of this function. I'll ensure that it's not still that
                   4307:     # value so we don't just wind up returning some accidental value
                   4308:     # as a result of executing an unforseen code path that
                   4309:     # did not set $validated.
                   4310: 
                   4311:     my $validated = -3.14159;
                   4312: 
                   4313:     #  How we authenticate is determined by the type of authentication
                   4314:     #  the user has been assigned.  If the authentication type is
                   4315:     #  "nouser", the user does not exist so we will return 0.
                   4316: 
1.222     foxr     4317:     my $contents = &get_auth_type($domain, $user);
1.220     foxr     4318:     my ($howpwd, $contentpwd) = split(/:/, $contents);
                   4319: 
                   4320:     my $null = pack("C",0);	# Used by kerberos auth types.
                   4321: 
                   4322:     if ($howpwd ne 'nouser') {
                   4323: 
                   4324: 	if($howpwd eq "internal") { # Encrypted is in local password file.
                   4325: 	    $validated = (crypt($password, $contentpwd) eq $contentpwd);
                   4326: 	}
                   4327: 	elsif ($howpwd eq "unix") { # User is a normal unix user.
                   4328: 	    $contentpwd = (getpwnam($user))[1];
                   4329: 	    if($contentpwd) {
                   4330: 		if($contentpwd eq 'x') { # Shadow password file...
                   4331: 		    my $pwauth_path = "/usr/local/sbin/pwauth";
                   4332: 		    open PWAUTH,  "|$pwauth_path" or
                   4333: 			die "Cannot invoke authentication";
                   4334: 		    print PWAUTH "$user\n$password\n";
                   4335: 		    close PWAUTH;
                   4336: 		    $validated = ! $?;
                   4337: 
                   4338: 		} else { 	         # Passwords in /etc/passwd. 
                   4339: 		    $validated = (crypt($password,
                   4340: 					$contentpwd) eq $contentpwd);
                   4341: 		}
                   4342: 	    } else {
                   4343: 		$validated = 0;
                   4344: 	    }
                   4345: 	}
                   4346: 	elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain.
                   4347: 	    if(! ($password =~ /$null/) ) {
                   4348: 		my $k4error = &Authen::Krb4::get_pw_in_tkt($user,
                   4349: 							   "",
                   4350: 							   $contentpwd,,
                   4351: 							   'krbtgt',
                   4352: 							   $contentpwd,
                   4353: 							   1,
                   4354: 							   $password);
                   4355: 		if(!$k4error) {
                   4356: 		    $validated = 1;
1.224     foxr     4357: 		} else {
1.220     foxr     4358: 		    $validated = 0;
                   4359: 		    &logthis('krb4: '.$user.', '.$contentpwd.', '.
                   4360: 			     &Authen::Krb4::get_err_txt($Authen::Krb4::error));
                   4361: 		}
1.224     foxr     4362: 	    } else {
1.220     foxr     4363: 		$validated = 0; # Password has a match with null.
                   4364: 	    }
1.224     foxr     4365: 	} elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain.
1.220     foxr     4366: 	    if(!($password =~ /$null/)) { # Null password not allowed.
                   4367: 		my $krbclient = &Authen::Krb5::parse_name($user.'@'
                   4368: 							  .$contentpwd);
                   4369: 		my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd;
                   4370: 		my $krbserver  = &Authen::Krb5::parse_name($krbservice);
                   4371: 		my $credentials= &Authen::Krb5::cc_default();
                   4372: 		$credentials->initialize($krbclient);
                   4373: 		my $krbreturn  = &Authen::KRb5::get_in_tkt_with_password($krbclient,
                   4374: 									 $krbserver,
                   4375: 									 $password,
                   4376: 									 $credentials);
                   4377: 		$validated = ($krbreturn == 1);
1.224     foxr     4378: 	    } else {
1.220     foxr     4379: 		$validated = 0;
                   4380: 	    }
1.224     foxr     4381: 	} elsif ($howpwd eq "localauth") { 
1.220     foxr     4382: 	    #  Authenticate via installation specific authentcation method:
                   4383: 	    $validated = &localauth::localauth($user, 
                   4384: 					       $password, 
                   4385: 					       $contentpwd);
1.224     foxr     4386: 	} else {			# Unrecognized auth is also bad.
1.220     foxr     4387: 	    $validated = 0;
                   4388: 	}
                   4389:     } else {
                   4390: 	$validated = 0;
                   4391:     }
                   4392:     #
                   4393:     #  $validated has the correct stat of the authentication:
                   4394:     #
                   4395: 
                   4396:     unless ($validated != -3.14159) {
                   4397: 	die "ValidateUser - failed to set the value of validated";
                   4398:     }
                   4399:     return $validated;
                   4400: }
                   4401: 
                   4402: 
1.84      albertel 4403: sub addline {
                   4404:     my ($fname,$hostid,$ip,$newline)=@_;
                   4405:     my $contents;
                   4406:     my $found=0;
                   4407:     my $expr='^'.$hostid.':'.$ip.':';
                   4408:     $expr =~ s/\./\\\./g;
1.134     albertel 4409:     my $sh;
1.84      albertel 4410:     if ($sh=IO::File->new("$fname.subscription")) {
                   4411: 	while (my $subline=<$sh>) {
                   4412: 	    if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;}
                   4413: 	}
                   4414: 	$sh->close();
                   4415:     }
                   4416:     $sh=IO::File->new(">$fname.subscription");
                   4417:     if ($contents) { print $sh $contents; }
                   4418:     if ($newline) { print $sh $newline; }
                   4419:     $sh->close();
                   4420:     return $found;
1.86      www      4421: }
                   4422: 
                   4423: sub getchat {
1.122     www      4424:     my ($cdom,$cname,$udom,$uname)=@_;
1.87      www      4425:     my %hash;
                   4426:     my $proname=&propath($cdom,$cname);
                   4427:     my @entries=();
1.88      albertel 4428:     if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",
                   4429: 	    &GDBM_READER(),0640)) {
                   4430: 	@entries=map { $_.':'.$hash{$_} } sort keys %hash;
                   4431: 	untie %hash;
1.123     www      4432:     }
1.124     www      4433:     my @participants=();
1.134     albertel 4434:     my $cutoff=time-60;
1.123     www      4435:     if (tie(%hash,'GDBM_File',"$proname/nohist_inchatroom.db",
1.124     www      4436: 	    &GDBM_WRCREAT(),0640)) {
                   4437:         $hash{$uname.':'.$udom}=time;
1.123     www      4438:         foreach (sort keys %hash) {
                   4439: 	    if ($hash{$_}>$cutoff) {
1.124     www      4440: 		$participants[$#participants+1]='active_participant:'.$_;
1.123     www      4441:             }
                   4442:         }
                   4443:         untie %hash;
1.86      www      4444:     }
1.124     www      4445:     return (@participants,@entries);
1.86      www      4446: }
                   4447: 
                   4448: sub chatadd {
1.88      albertel 4449:     my ($cdom,$cname,$newchat)=@_;
                   4450:     my %hash;
                   4451:     my $proname=&propath($cdom,$cname);
                   4452:     my @entries=();
1.142     www      4453:     my $time=time;
1.88      albertel 4454:     if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",
                   4455: 	    &GDBM_WRCREAT(),0640)) {
                   4456: 	@entries=map { $_.':'.$hash{$_} } sort keys %hash;
                   4457: 	my ($lastid)=($entries[$#entries]=~/^(\w+)\:/);
                   4458: 	my ($thentime,$idnum)=split(/\_/,$lastid);
                   4459: 	my $newid=$time.'_000000';
                   4460: 	if ($thentime==$time) {
                   4461: 	    $idnum=~s/^0+//;
                   4462: 	    $idnum++;
                   4463: 	    $idnum=substr('000000'.$idnum,-6,6);
                   4464: 	    $newid=$time.'_'.$idnum;
                   4465: 	}
                   4466: 	$hash{$newid}=$newchat;
                   4467: 	my $expired=$time-3600;
                   4468: 	foreach (keys %hash) {
                   4469: 	    my ($thistime)=($_=~/(\d+)\_/);
                   4470: 	    if ($thistime<$expired) {
1.89      www      4471: 		delete $hash{$_};
1.88      albertel 4472: 	    }
                   4473: 	}
                   4474: 	untie %hash;
1.142     www      4475:     }
                   4476:     {
                   4477: 	my $hfh;
                   4478: 	if ($hfh=IO::File->new(">>$proname/chatroom.log")) { 
                   4479: 	    print $hfh "$time:".&unescape($newchat)."\n";
                   4480: 	}
1.86      www      4481:     }
1.84      albertel 4482: }
                   4483: 
                   4484: sub unsub {
                   4485:     my ($fname,$clientip)=@_;
                   4486:     my $result;
1.188     foxr     4487:     my $unsubs = 0;		# Number of successful unsubscribes:
                   4488: 
                   4489: 
                   4490:     # An old way subscriptions were handled was to have a 
                   4491:     # subscription marker file:
                   4492: 
                   4493:     Debug("Attempting unlink of $fname.$clientname");
1.161     foxr     4494:     if (unlink("$fname.$clientname")) {
1.188     foxr     4495: 	$unsubs++;		# Successful unsub via marker file.
                   4496:     } 
                   4497: 
                   4498:     # The more modern way to do it is to have a subscription list
                   4499:     # file:
                   4500: 
1.84      albertel 4501:     if (-e "$fname.subscription") {
1.161     foxr     4502: 	my $found=&addline($fname,$clientname,$clientip,'');
1.188     foxr     4503: 	if ($found) { 
                   4504: 	    $unsubs++;
                   4505: 	}
                   4506:     } 
                   4507: 
                   4508:     #  If either or both of these mechanisms succeeded in unsubscribing a 
                   4509:     #  resource we can return ok:
                   4510: 
                   4511:     if($unsubs) {
                   4512: 	$result = "ok\n";
1.84      albertel 4513:     } else {
1.188     foxr     4514: 	$result = "not_subscribed\n";
1.84      albertel 4515:     }
1.188     foxr     4516: 
1.84      albertel 4517:     return $result;
                   4518: }
                   4519: 
1.101     www      4520: sub currentversion {
                   4521:     my $fname=shift;
                   4522:     my $version=-1;
                   4523:     my $ulsdir='';
                   4524:     if ($fname=~/^(.+)\/[^\/]+$/) {
                   4525:        $ulsdir=$1;
                   4526:     }
1.114     albertel 4527:     my ($fnamere1,$fnamere2);
                   4528:     # remove version if already specified
1.101     www      4529:     $fname=~s/\.\d+\.(\w+(?:\.meta)*)$/\.$1/;
1.114     albertel 4530:     # get the bits that go before and after the version number
                   4531:     if ( $fname=~/^(.*\.)(\w+(?:\.meta)*)$/ ) {
                   4532: 	$fnamere1=$1;
                   4533: 	$fnamere2='.'.$2;
                   4534:     }
1.101     www      4535:     if (-e $fname) { $version=1; }
                   4536:     if (-e $ulsdir) {
1.134     albertel 4537: 	if(-d $ulsdir) {
                   4538: 	    if (opendir(LSDIR,$ulsdir)) {
                   4539: 		my $ulsfn;
                   4540: 		while ($ulsfn=readdir(LSDIR)) {
1.101     www      4541: # see if this is a regular file (ignore links produced earlier)
1.134     albertel 4542: 		    my $thisfile=$ulsdir.'/'.$ulsfn;
                   4543: 		    unless (-l $thisfile) {
1.160     www      4544: 			if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) {
1.134     albertel 4545: 			    if ($1>$version) { $version=$1; }
                   4546: 			}
                   4547: 		    }
                   4548: 		}
                   4549: 		closedir(LSDIR);
                   4550: 		$version++;
                   4551: 	    }
                   4552: 	}
                   4553:     }
                   4554:     return $version;
1.101     www      4555: }
                   4556: 
                   4557: sub thisversion {
                   4558:     my $fname=shift;
                   4559:     my $version=-1;
                   4560:     if ($fname=~/\.(\d+)\.\w+(?:\.meta)*$/) {
                   4561: 	$version=$1;
                   4562:     }
                   4563:     return $version;
                   4564: }
                   4565: 
1.84      albertel 4566: sub subscribe {
                   4567:     my ($userinput,$clientip)=@_;
                   4568:     my $result;
                   4569:     my ($cmd,$fname)=split(/:/,$userinput);
                   4570:     my $ownership=&ishome($fname);
                   4571:     if ($ownership eq 'owner') {
1.101     www      4572: # explitly asking for the current version?
                   4573:         unless (-e $fname) {
                   4574:             my $currentversion=&currentversion($fname);
                   4575: 	    if (&thisversion($fname)==$currentversion) {
                   4576:                 if ($fname=~/^(.+)\.\d+\.(\w+(?:\.meta)*)$/) {
                   4577: 		    my $root=$1;
                   4578:                     my $extension=$2;
                   4579:                     symlink($root.'.'.$extension,
                   4580:                             $root.'.'.$currentversion.'.'.$extension);
1.102     www      4581:                     unless ($extension=~/\.meta$/) {
                   4582:                        symlink($root.'.'.$extension.'.meta',
                   4583:                             $root.'.'.$currentversion.'.'.$extension.'.meta');
                   4584: 		    }
1.101     www      4585:                 }
                   4586:             }
                   4587:         }
1.84      albertel 4588: 	if (-e $fname) {
                   4589: 	    if (-d $fname) {
                   4590: 		$result="directory\n";
                   4591: 	    } else {
1.161     foxr     4592: 		if (-e "$fname.$clientname") {&unsub($fname,$clientip);}
1.134     albertel 4593: 		my $now=time;
1.161     foxr     4594: 		my $found=&addline($fname,$clientname,$clientip,
                   4595: 				   "$clientname:$clientip:$now\n");
1.84      albertel 4596: 		if ($found) { $result="$fname\n"; }
                   4597: 		# if they were subscribed to only meta data, delete that
                   4598:                 # subscription, when you subscribe to a file you also get
                   4599:                 # the metadata
                   4600: 		unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); }
                   4601: 		$fname=~s/\/home\/httpd\/html\/res/raw/;
                   4602: 		$fname="http://$thisserver/".$fname;
                   4603: 		$result="$fname\n";
                   4604: 	    }
                   4605: 	} else {
                   4606: 	    $result="not_found\n";
                   4607: 	}
                   4608:     } else {
                   4609: 	$result="rejected\n";
                   4610:     }
                   4611:     return $result;
                   4612: }
1.91      albertel 4613: 
                   4614: sub make_passwd_file {
1.98      foxr     4615:     my ($uname, $umode,$npass,$passfilename)=@_;
1.91      albertel 4616:     my $result="ok\n";
                   4617:     if ($umode eq 'krb4' or $umode eq 'krb5') {
                   4618: 	{
                   4619: 	    my $pf = IO::File->new(">$passfilename");
                   4620: 	    print $pf "$umode:$npass\n";
                   4621: 	}
                   4622:     } elsif ($umode eq 'internal') {
                   4623: 	my $salt=time;
                   4624: 	$salt=substr($salt,6,2);
                   4625: 	my $ncpass=crypt($npass,$salt);
                   4626: 	{
                   4627: 	    &Debug("Creating internal auth");
                   4628: 	    my $pf = IO::File->new(">$passfilename");
                   4629: 	    print $pf "internal:$ncpass\n"; 
                   4630: 	}
                   4631:     } elsif ($umode eq 'localauth') {
                   4632: 	{
                   4633: 	    my $pf = IO::File->new(">$passfilename");
                   4634: 	    print $pf "localauth:$npass\n";
                   4635: 	}
                   4636:     } elsif ($umode eq 'unix') {
                   4637: 	{
1.186     foxr     4638: 	    #
                   4639: 	    #  Don't allow the creation of privileged accounts!!! that would
                   4640: 	    #  be real bad!!!
                   4641: 	    #
                   4642: 	    my $uid = getpwnam($uname);
                   4643: 	    if((defined $uid) && ($uid == 0)) {
                   4644: 		&logthis(">>>Attempted to create privilged account blocked");
                   4645: 		return "no_priv_account_error\n";
                   4646: 	    }
                   4647: 
1.223     foxr     4648: 	    my $execpath       ="$perlvar{'lonDaemons'}/"."lcuseradd";
1.224     foxr     4649: 
                   4650: 	    my $lc_error_file  = $execdir."/tmp/lcuseradd".$$.".status";
1.91      albertel 4651: 	    {
                   4652: 		&Debug("Executing external: ".$execpath);
1.98      foxr     4653: 		&Debug("user  = ".$uname.", Password =". $npass);
1.132     matthew  4654: 		my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log");
1.91      albertel 4655: 		print $se "$uname\n";
                   4656: 		print $se "$npass\n";
                   4657: 		print $se "$npass\n";
1.223     foxr     4658: 		print $se "$lc_error_file\n"; # Status -> unique file.
1.97      foxr     4659: 	    }
1.223     foxr     4660: 	    my $error = IO::File->new("< $lc_error_file");
                   4661: 	    my $useraddok = <$error>;
                   4662: 	    $error->close;
                   4663: 	    unlink($lc_error_file);
                   4664: 
                   4665: 	    chomp $useraddok;
                   4666: 
1.97      foxr     4667: 	    if($useraddok > 0) {
1.223     foxr     4668: 		my $error_text = &lcuseraddstrerror($useraddok);
                   4669: 		&logthis("Failed lcuseradd: $error_text");
                   4670: 		$result = "lcuseradd_failed:$error_text\n";
1.224     foxr     4671: 	    }  else {
1.223     foxr     4672: 		my $pf = IO::File->new(">$passfilename");
                   4673: 		print $pf "unix:\n";
1.91      albertel 4674: 	    }
                   4675: 	}
                   4676:     } elsif ($umode eq 'none') {
                   4677: 	{
1.223     foxr     4678: 	    my $pf = IO::File->new("> $passfilename");
1.91      albertel 4679: 	    print $pf "none:\n";
                   4680: 	}
                   4681:     } else {
                   4682: 	$result="auth_mode_error\n";
                   4683:     }
                   4684:     return $result;
1.121     albertel 4685: }
                   4686: 
                   4687: sub sethost {
                   4688:     my ($remotereq) = @_;
                   4689:     my (undef,$hostid)=split(/:/,$remotereq);
                   4690:     if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }
                   4691:     if ($hostip{$perlvar{'lonHostID'}} eq $hostip{$hostid}) {
1.200     matthew  4692: 	$currenthostid  =$hostid;
1.121     albertel 4693: 	$currentdomainid=$hostdom{$hostid};
                   4694: 	&logthis("Setting hostid to $hostid, and domain to $currentdomainid");
                   4695:     } else {
                   4696: 	&logthis("Requested host id $hostid not an alias of ".
                   4697: 		 $perlvar{'lonHostID'}." refusing connection");
                   4698: 	return 'unable_to_set';
                   4699:     }
                   4700:     return 'ok';
                   4701: }
                   4702: 
                   4703: sub version {
                   4704:     my ($userinput)=@_;
                   4705:     $remoteVERSION=(split(/:/,$userinput))[1];
                   4706:     return "version:$VERSION";
1.127     albertel 4707: }
1.178     foxr     4708: 
1.128     albertel 4709: #There is a copy of this in lonnet.pm
1.127     albertel 4710: sub userload {
                   4711:     my $numusers=0;
                   4712:     {
                   4713: 	opendir(LONIDS,$perlvar{'lonIDsDir'});
                   4714: 	my $filename;
                   4715: 	my $curtime=time;
                   4716: 	while ($filename=readdir(LONIDS)) {
                   4717: 	    if ($filename eq '.' || $filename eq '..') {next;}
1.138     albertel 4718: 	    my ($mtime)=(stat($perlvar{'lonIDsDir'}.'/'.$filename))[9];
1.159     albertel 4719: 	    if ($curtime-$mtime < 1800) { $numusers++; }
1.127     albertel 4720: 	}
                   4721: 	closedir(LONIDS);
                   4722:     }
                   4723:     my $userloadpercent=0;
                   4724:     my $maxuserload=$perlvar{'lonUserLoadLim'};
                   4725:     if ($maxuserload) {
1.129     albertel 4726: 	$userloadpercent=100*$numusers/$maxuserload;
1.127     albertel 4727:     }
1.130     albertel 4728:     $userloadpercent=sprintf("%.2f",$userloadpercent);
1.127     albertel 4729:     return $userloadpercent;
1.91      albertel 4730: }
                   4731: 
1.205     raeburn  4732: # Routines for serializing arrays and hashes (copies from lonnet)
                   4733: 
                   4734: sub array2str {
                   4735:   my (@array) = @_;
                   4736:   my $result=&arrayref2str(\@array);
                   4737:   $result=~s/^__ARRAY_REF__//;
                   4738:   $result=~s/__END_ARRAY_REF__$//;
                   4739:   return $result;
                   4740: }
                   4741:                                                                                  
                   4742: sub arrayref2str {
                   4743:   my ($arrayref) = @_;
                   4744:   my $result='__ARRAY_REF__';
                   4745:   foreach my $elem (@$arrayref) {
                   4746:     if(ref($elem) eq 'ARRAY') {
                   4747:       $result.=&arrayref2str($elem).'&';
                   4748:     } elsif(ref($elem) eq 'HASH') {
                   4749:       $result.=&hashref2str($elem).'&';
                   4750:     } elsif(ref($elem)) {
                   4751:       #print("Got a ref of ".(ref($elem))." skipping.");
                   4752:     } else {
                   4753:       $result.=&escape($elem).'&';
                   4754:     }
                   4755:   }
                   4756:   $result=~s/\&$//;
                   4757:   $result .= '__END_ARRAY_REF__';
                   4758:   return $result;
                   4759: }
                   4760:                                                                                  
                   4761: sub hash2str {
                   4762:   my (%hash) = @_;
                   4763:   my $result=&hashref2str(\%hash);
                   4764:   $result=~s/^__HASH_REF__//;
                   4765:   $result=~s/__END_HASH_REF__$//;
                   4766:   return $result;
                   4767: }
                   4768:                                                                                  
                   4769: sub hashref2str {
                   4770:   my ($hashref)=@_;
                   4771:   my $result='__HASH_REF__';
                   4772:   foreach (sort(keys(%$hashref))) {
                   4773:     if (ref($_) eq 'ARRAY') {
                   4774:       $result.=&arrayref2str($_).'=';
                   4775:     } elsif (ref($_) eq 'HASH') {
                   4776:       $result.=&hashref2str($_).'=';
                   4777:     } elsif (ref($_)) {
                   4778:       $result.='=';
                   4779:       #print("Got a ref of ".(ref($_))." skipping.");
                   4780:     } else {
                   4781:         if ($_) {$result.=&escape($_).'=';} else { last; }
                   4782:     }
                   4783: 
                   4784:     if(ref($hashref->{$_}) eq 'ARRAY') {
                   4785:       $result.=&arrayref2str($hashref->{$_}).'&';
                   4786:     } elsif(ref($hashref->{$_}) eq 'HASH') {
                   4787:       $result.=&hashref2str($hashref->{$_}).'&';
                   4788:     } elsif(ref($hashref->{$_})) {
                   4789:        $result.='&';
                   4790:       #print("Got a ref of ".(ref($hashref->{$_}))." skipping.");
                   4791:     } else {
                   4792:       $result.=&escape($hashref->{$_}).'&';
                   4793:     }
                   4794:   }
                   4795:   $result=~s/\&$//;
                   4796:   $result .= '__END_HASH_REF__';
                   4797:   return $result;
                   4798: }
1.200     matthew  4799: 
1.61      harris41 4800: # ----------------------------------- POD (plain old documentation, CPAN style)
                   4801: 
                   4802: =head1 NAME
                   4803: 
                   4804: lond - "LON Daemon" Server (port "LOND" 5663)
                   4805: 
                   4806: =head1 SYNOPSIS
                   4807: 
1.74      harris41 4808: Usage: B<lond>
                   4809: 
                   4810: Should only be run as user=www.  This is a command-line script which
                   4811: is invoked by B<loncron>.  There is no expectation that a typical user
                   4812: will manually start B<lond> from the command-line.  (In other words,
                   4813: DO NOT START B<lond> YOURSELF.)
1.61      harris41 4814: 
                   4815: =head1 DESCRIPTION
                   4816: 
1.74      harris41 4817: There are two characteristics associated with the running of B<lond>,
                   4818: PROCESS MANAGEMENT (starting, stopping, handling child processes)
                   4819: and SERVER-SIDE ACTIVITIES (password authentication, user creation,
                   4820: subscriptions, etc).  These are described in two large
                   4821: sections below.
                   4822: 
                   4823: B<PROCESS MANAGEMENT>
                   4824: 
1.61      harris41 4825: Preforker - server who forks first. Runs as a daemon. HUPs.
                   4826: Uses IDEA encryption
                   4827: 
1.74      harris41 4828: B<lond> forks off children processes that correspond to the other servers
                   4829: in the network.  Management of these processes can be done at the
                   4830: parent process level or the child process level.
                   4831: 
                   4832: B<logs/lond.log> is the location of log messages.
                   4833: 
                   4834: The process management is now explained in terms of linux shell commands,
                   4835: subroutines internal to this code, and signal assignments:
                   4836: 
                   4837: =over 4
                   4838: 
                   4839: =item *
                   4840: 
                   4841: PID is stored in B<logs/lond.pid>
                   4842: 
                   4843: This is the process id number of the parent B<lond> process.
                   4844: 
                   4845: =item *
                   4846: 
                   4847: SIGTERM and SIGINT
                   4848: 
                   4849: Parent signal assignment:
                   4850:  $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
                   4851: 
                   4852: Child signal assignment:
                   4853:  $SIG{INT}  = 'DEFAULT'; (and SIGTERM is DEFAULT also)
                   4854: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
                   4855:  to restart a new child.)
                   4856: 
                   4857: Command-line invocations:
                   4858:  B<kill> B<-s> SIGTERM I<PID>
                   4859:  B<kill> B<-s> SIGINT I<PID>
                   4860: 
                   4861: Subroutine B<HUNTSMAN>:
                   4862:  This is only invoked for the B<lond> parent I<PID>.
                   4863: This kills all the children, and then the parent.
                   4864: The B<lonc.pid> file is cleared.
                   4865: 
                   4866: =item *
                   4867: 
                   4868: SIGHUP
                   4869: 
                   4870: Current bug:
                   4871:  This signal can only be processed the first time
                   4872: on the parent process.  Subsequent SIGHUP signals
                   4873: have no effect.
                   4874: 
                   4875: Parent signal assignment:
                   4876:  $SIG{HUP}  = \&HUPSMAN;
                   4877: 
                   4878: Child signal assignment:
                   4879:  none (nothing happens)
                   4880: 
                   4881: Command-line invocations:
                   4882:  B<kill> B<-s> SIGHUP I<PID>
                   4883: 
                   4884: Subroutine B<HUPSMAN>:
                   4885:  This is only invoked for the B<lond> parent I<PID>,
                   4886: This kills all the children, and then the parent.
                   4887: The B<lond.pid> file is cleared.
                   4888: 
                   4889: =item *
                   4890: 
                   4891: SIGUSR1
                   4892: 
                   4893: Parent signal assignment:
                   4894:  $SIG{USR1} = \&USRMAN;
                   4895: 
                   4896: Child signal assignment:
                   4897:  $SIG{USR1}= \&logstatus;
                   4898: 
                   4899: Command-line invocations:
                   4900:  B<kill> B<-s> SIGUSR1 I<PID>
                   4901: 
                   4902: Subroutine B<USRMAN>:
                   4903:  When invoked for the B<lond> parent I<PID>,
                   4904: SIGUSR1 is sent to all the children, and the status of
                   4905: each connection is logged.
1.144     foxr     4906: 
                   4907: =item *
                   4908: 
                   4909: SIGUSR2
                   4910: 
                   4911: Parent Signal assignment:
                   4912:     $SIG{USR2} = \&UpdateHosts
                   4913: 
                   4914: Child signal assignment:
                   4915:     NONE
                   4916: 
1.74      harris41 4917: 
                   4918: =item *
                   4919: 
                   4920: SIGCHLD
                   4921: 
                   4922: Parent signal assignment:
                   4923:  $SIG{CHLD} = \&REAPER;
                   4924: 
                   4925: Child signal assignment:
                   4926:  none
                   4927: 
                   4928: Command-line invocations:
                   4929:  B<kill> B<-s> SIGCHLD I<PID>
                   4930: 
                   4931: Subroutine B<REAPER>:
                   4932:  This is only invoked for the B<lond> parent I<PID>.
                   4933: Information pertaining to the child is removed.
                   4934: The socket port is cleaned up.
                   4935: 
                   4936: =back
                   4937: 
                   4938: B<SERVER-SIDE ACTIVITIES>
                   4939: 
                   4940: Server-side information can be accepted in an encrypted or non-encrypted
                   4941: method.
                   4942: 
                   4943: =over 4
                   4944: 
                   4945: =item ping
                   4946: 
                   4947: Query a client in the hosts.tab table; "Are you there?"
                   4948: 
                   4949: =item pong
                   4950: 
                   4951: Respond to a ping query.
                   4952: 
                   4953: =item ekey
                   4954: 
                   4955: Read in encrypted key, make cipher.  Respond with a buildkey.
                   4956: 
                   4957: =item load
                   4958: 
                   4959: Respond with CPU load based on a computation upon /proc/loadavg.
                   4960: 
                   4961: =item currentauth
                   4962: 
                   4963: Reply with current authentication information (only over an
                   4964: encrypted channel).
                   4965: 
                   4966: =item auth
                   4967: 
                   4968: Only over an encrypted channel, reply as to whether a user's
                   4969: authentication information can be validated.
                   4970: 
                   4971: =item passwd
                   4972: 
                   4973: Allow for a password to be set.
                   4974: 
                   4975: =item makeuser
                   4976: 
                   4977: Make a user.
                   4978: 
                   4979: =item passwd
                   4980: 
                   4981: Allow for authentication mechanism and password to be changed.
                   4982: 
                   4983: =item home
1.61      harris41 4984: 
1.74      harris41 4985: Respond to a question "are you the home for a given user?"
                   4986: 
                   4987: =item update
                   4988: 
                   4989: Update contents of a subscribed resource.
                   4990: 
                   4991: =item unsubscribe
                   4992: 
                   4993: The server is unsubscribing from a resource.
                   4994: 
                   4995: =item subscribe
                   4996: 
                   4997: The server is subscribing to a resource.
                   4998: 
                   4999: =item log
                   5000: 
                   5001: Place in B<logs/lond.log>
                   5002: 
                   5003: =item put
                   5004: 
                   5005: stores hash in namespace
                   5006: 
1.230     foxr     5007: =item rolesputy
1.74      harris41 5008: 
                   5009: put a role into a user's environment
                   5010: 
                   5011: =item get
                   5012: 
                   5013: returns hash with keys from array
                   5014: reference filled in from namespace
                   5015: 
                   5016: =item eget
                   5017: 
                   5018: returns hash with keys from array
                   5019: reference filled in from namesp (encrypts the return communication)
                   5020: 
                   5021: =item rolesget
                   5022: 
                   5023: get a role from a user's environment
                   5024: 
                   5025: =item del
                   5026: 
                   5027: deletes keys out of array from namespace
                   5028: 
                   5029: =item keys
                   5030: 
                   5031: returns namespace keys
                   5032: 
                   5033: =item dump
                   5034: 
                   5035: dumps the complete (or key matching regexp) namespace into a hash
                   5036: 
                   5037: =item store
                   5038: 
                   5039: stores hash permanently
                   5040: for this url; hashref needs to be given and should be a \%hashname; the
                   5041: remaining args aren't required and if they aren't passed or are '' they will
                   5042: be derived from the ENV
                   5043: 
                   5044: =item restore
                   5045: 
                   5046: returns a hash for a given url
                   5047: 
                   5048: =item querysend
                   5049: 
                   5050: Tells client about the lonsql process that has been launched in response
                   5051: to a sent query.
                   5052: 
                   5053: =item queryreply
                   5054: 
                   5055: Accept information from lonsql and make appropriate storage in temporary
                   5056: file space.
                   5057: 
                   5058: =item idput
                   5059: 
                   5060: Defines usernames as corresponding to IDs.  (These "IDs" are unique identifiers
                   5061: for each student, defined perhaps by the institutional Registrar.)
                   5062: 
                   5063: =item idget
                   5064: 
                   5065: Returns usernames corresponding to IDs.  (These "IDs" are unique identifiers
                   5066: for each student, defined perhaps by the institutional Registrar.)
                   5067: 
                   5068: =item tmpput
                   5069: 
                   5070: Accept and store information in temporary space.
                   5071: 
                   5072: =item tmpget
                   5073: 
                   5074: Send along temporarily stored information.
                   5075: 
                   5076: =item ls
                   5077: 
                   5078: List part of a user's directory.
                   5079: 
1.135     foxr     5080: =item pushtable
                   5081: 
                   5082: Pushes a file in /home/httpd/lonTab directory.  Currently limited to:
                   5083: hosts.tab and domain.tab. The old file is copied to  *.tab.backup but
                   5084: must be restored manually in case of a problem with the new table file.
                   5085: pushtable requires that the request be encrypted and validated via
                   5086: ValidateManager.  The form of the command is:
                   5087: enc:pushtable tablename <tablecontents> \n
                   5088: where pushtable, tablename and <tablecontents> will be encrypted, but \n is a 
                   5089: cleartext newline.
                   5090: 
1.74      harris41 5091: =item Hanging up (exit or init)
                   5092: 
                   5093: What to do when a client tells the server that they (the client)
                   5094: are leaving the network.
                   5095: 
                   5096: =item unknown command
                   5097: 
                   5098: If B<lond> is sent an unknown command (not in the list above),
                   5099: it replys to the client "unknown_cmd".
1.135     foxr     5100: 
1.74      harris41 5101: 
                   5102: =item UNKNOWN CLIENT
                   5103: 
                   5104: If the anti-spoofing algorithm cannot verify the client,
                   5105: the client is rejected (with a "refused" message sent
                   5106: to the client, and the connection is closed.
                   5107: 
                   5108: =back
1.61      harris41 5109: 
                   5110: =head1 PREREQUISITES
                   5111: 
                   5112: IO::Socket
                   5113: IO::File
                   5114: Apache::File
                   5115: Symbol
                   5116: POSIX
                   5117: Crypt::IDEA
                   5118: LWP::UserAgent()
                   5119: GDBM_File
                   5120: Authen::Krb4
1.91      albertel 5121: Authen::Krb5
1.61      harris41 5122: 
                   5123: =head1 COREQUISITES
                   5124: 
                   5125: =head1 OSNAMES
                   5126: 
                   5127: linux
                   5128: 
                   5129: =head1 SCRIPT CATEGORIES
                   5130: 
                   5131: Server/Process
                   5132: 
                   5133: =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.