Diff for /loncom/lond between versions 1.190 and 1.209

version 1.190, 2004/05/11 19:51:49 version 1.209, 2004/07/23 14:10:47
Line 45  use Authen::Krb4; Line 45  use Authen::Krb4;
 use Authen::Krb5;  use Authen::Krb5;
 use lib '/home/httpd/lib/perl/';  use lib '/home/httpd/lib/perl/';
 use localauth;  use localauth;
   use localenroll;
 use File::Copy;  use File::Copy;
 use LONCAPA::ConfigFileEdit;  use LONCAPA::ConfigFileEdit;
   use LONCAPA::lonlocal;
   use LONCAPA::lonssl;
   
 my $DEBUG = 0;       # Non zero to enable debug log entries.  my $DEBUG = 0;       # Non zero to enable debug log entries.
   
Line 59  my $currenthostid; Line 62  my $currenthostid;
 my $currentdomainid;  my $currentdomainid;
   
 my $client;  my $client;
 my $clientip;  my $clientip; # IP address of client.
 my $clientname;  my $clientdns; # DNS name of client.
   my $clientname; # LonCAPA name of client.
   
 my $server;  my $server;
 my $thisserver;  my $thisserver; # DNS of us.
   
   my $keymode;
   
   my $cipher; # Cipher key negotiated with client
   my $tmpsnum = 0; # Id of tmpputs.
   
 #   # 
 #   Connection type is:  #   Connection type is:
Line 74  my $thisserver; Line 83  my $thisserver;
   
 my $ConnectionType;  my $ConnectionType;
   
 my %hostid;  my %hostid; # ID's for hosts in cluster by ip.
 my %hostdom;  my %hostdom; # LonCAPA domain for hosts in cluster.
 my %hostip;  my %hostip; # IPs for hosts in cluster.
   my %hostdns; # ID's of hosts looked up by DNS name.
   
 my %managers; # Ip -> manager names  my %managers; # Ip -> manager names
   
 my %perlvar; # Will have the apache conf defined perl vars.  my %perlvar; # Will have the apache conf defined perl vars.
   
 #  #
   #   The hash below is used for command dispatching, and is therefore keyed on the request keyword.
   #    Each element of the hash contains a reference to an array that contains:
   #          A reference to a sub that executes the request corresponding to the keyword.
   #          A flag that is true if the request must be encoded to be acceptable.
   #          A mask with bits as follows:
   #                      CLIENT_OK    - Set when the function is allowed by ordinary clients
   #                      MANAGER_OK   - Set when the function is allowed to manager clients.
   #
   my $CLIENT_OK  = 1;
   my $MANAGER_OK = 2;
   my %Dispatcher;
   
   
   #
 #  The array below are password error strings."  #  The array below are password error strings."
 #  #
 my $lastpwderror    = 13; # Largest error number from lcpasswd.  my $lastpwderror    = 13; # Largest error number from lcpasswd.
Line 120  my @adderrors    = ("ok", Line 144  my @adderrors    = ("ok",
     "lcuseradd Password mismatch");      "lcuseradd Password mismatch");
   
   
   
   #
   #   Statistics that are maintained and dislayed in the status line.
   #
   my $Transactions; # Number of attempted transactions.
   my $Failures; # Number of transcations failed.
   
   #   ResetStatistics: 
   #      Resets the statistics counters:
   #
   sub ResetStatistics {
       $Transactions = 0;
       $Failures     = 0;
   }
   
   
   
   #------------------------------------------------------------------------
   #
   #   LocalConnection
   #     Completes the formation of a locally authenticated connection.
   #     This function will ensure that the 'remote' client is really the
   #     local host.  If not, the connection is closed, and the function fails.
   #     If so, initcmd is parsed for the name of a file containing the
   #     IDEA session key.  The fie is opened, read, deleted and the session
   #     key returned to the caller.
   #
   # Parameters:
   #   $Socket      - Socket open on client.
   #   $initcmd     - The full text of the init command.
   #
   # Implicit inputs:
   #    $clientdns  - The DNS name of the remote client.
   #    $thisserver - Our DNS name.
   #
   # Returns:
   #     IDEA session key on success.
   #     undef on failure.
   #
   sub LocalConnection {
       my ($Socket, $initcmd) = @_;
       Debug("Attempting local connection: $initcmd client: $clientdns me: $thisserver");
       if($clientdns ne $thisserver) {
    &logthis('<font color="red"> LocalConnection rejecting non local: '
    ."$clientdns ne $thisserver </font>");
    close $Socket;
    return undef;
       } 
       else {
    chomp($initcmd); # Get rid of \n in filename.
    my ($init, $type, $name) = split(/:/, $initcmd);
    Debug(" Init command: $init $type $name ");
   
    # Require that $init = init, and $type = local:  Otherwise
    # the caller is insane:
   
    if(($init ne "init") && ($type ne "local")) {
       &logthis('<font color = "red"> LocalConnection: caller is insane! '
        ."init = $init, and type = $type </font>");
       close($Socket);;
       return undef;
   
    }
    #  Now get the key filename:
   
    my $IDEAKey = lonlocal::ReadKeyFile($name);
    return $IDEAKey;
       }
   }
   #------------------------------------------------------------------------------
   #
   #  SSLConnection
   #   Completes the formation of an ssh authenticated connection. The
   #   socket is promoted to an ssl socket.  If this promotion and the associated
   #   certificate exchange are successful, the IDEA key is generated and sent
   #   to the remote peer via the SSL tunnel. The IDEA key is also returned to
   #   the caller after the SSL tunnel is torn down.
   #
   # Parameters:
   #   Name              Type             Purpose
   #   $Socket          IO::Socket::INET  Plaintext socket.
   #
   # Returns:
   #    IDEA key on success.
   #    undef on failure.
   #
   sub SSLConnection {
       my $Socket   = shift;
   
       Debug("SSLConnection: ");
       my $KeyFile         = lonssl::KeyFile();
       if(!$KeyFile) {
    my $err = lonssl::LastError();
    &logthis("<font color=\"red\"> CRITICAL"
    ."Can't get key file $err </font>");
    return undef;
       }
       my ($CACertificate,
    $Certificate) = lonssl::CertificateFile();
   
   
       # If any of the key, certificate or certificate authority 
       # certificate filenames are not defined, this can't work.
   
       if((!$Certificate) || (!$CACertificate)) {
    my $err = lonssl::LastError();
    &logthis("<font color=\"red\"> CRITICAL"
    ."Can't get certificates: $err </font>");
   
    return undef;
       }
       Debug("Key: $KeyFile CA: $CACertificate Cert: $Certificate");
   
       # Indicate to our peer that we can procede with
       # a transition to ssl authentication:
   
       print $Socket "ok:ssl\n";
   
       Debug("Approving promotion -> ssl");
       #  And do so:
   
       my $SSLSocket = lonssl::PromoteServerSocket($Socket,
    $CACertificate,
    $Certificate,
    $KeyFile);
       if(! ($SSLSocket) ) { # SSL socket promotion failed.
    my $err = lonssl::LastError();
    &logthis("<font color=\"red\"> CRITICAL "
    ."SSL Socket promotion failed: $err </font>");
    return undef;
       }
       Debug("SSL Promotion successful");
   
       # 
       #  The only thing we'll use the socket for is to send the IDEA key
       #  to the peer:
   
       my $Key = lonlocal::CreateCipherKey();
       print $SSLSocket "$Key\n";
   
       lonssl::Close($SSLSocket); 
   
       Debug("Key exchange complete: $Key");
   
       return $Key;
   }
   #
   #     InsecureConnection: 
   #        If insecure connections are allowd,
   #        exchange a challenge with the client to 'validate' the
   #        client (not really, but that's the protocol):
   #        We produce a challenge string that's sent to the client.
   #        The client must then echo the challenge verbatim to us.
   #
   #  Parameter:
   #      Socket      - Socket open on the client.
   #  Returns:
   #      1           - success.
   #      0           - failure (e.g.mismatch or insecure not allowed).
   #
   sub InsecureConnection {
       my $Socket  =  shift;
   
       #   Don't even start if insecure connections are not allowed.
   
       if(! $perlvar{londAllowInsecure}) { # Insecure connections not allowed.
    return 0;
       }
   
       #   Fabricate a challenge string and send it..
   
       my $challenge = "$$".time; # pid + time.
       print $Socket "$challenge\n";
       &status("Waiting for challenge reply");
   
       my $answer = <$Socket>;
       $answer    =~s/\W//g;
       if($challenge eq $answer) {
    return 1;
       } 
       else {
    logthis("<font color='blue'>WARNING client did not respond to challenge</font>");
    &status("No challenge reqply");
    return 0;
       }
       
   
   }
   
 #  #
 #   GetCertificate: Given a transaction that requires a certificate,  #   GetCertificate: Given a transaction that requires a certificate,
 #   this function will extract the certificate from the transaction  #   this function will extract the certificate from the transaction
Line 175  sub ReadManagerTable { Line 388  sub ReadManagerTable {
    while(my $host = <MANAGERS>) {     while(my $host = <MANAGERS>) {
       chomp($host);        chomp($host);
       if ($host =~ "^#") {                  # Comment line.        if ($host =~ "^#") {                  # Comment line.
          logthis('<font color="green"> Skipping line: '. "$host</font>\n");  
          next;           next;
       }        }
       if (!defined $hostip{$host}) { # This is a non cluster member        if (!defined $hostip{$host}) { # This is a non cluster member
Line 225  sub ValidManager { Line 437  sub ValidManager {
 #     1   - Success.  #     1   - Success.
 #  #
 sub CopyFile {  sub CopyFile {
     my $oldfile = shift;  
     my $newfile = shift;      my ($oldfile, $newfile) = @_;
   
     #  The file must exist:      #  The file must exist:
   
Line 326  sub AdjustHostContents { Line 538  sub AdjustHostContents {
 #      0       - failure and $! has an errno.  #      0       - failure and $! has an errno.
 #  #
 sub InstallFile {  sub InstallFile {
     my $Filename = shift;  
     my $Contents = shift;      my ($Filename, $Contents) = @_;
     my $TempFile = $Filename.".tmp";      my $TempFile = $Filename.".tmp";
   
     #  Open the file for write:      #  Open the file for write:
Line 350  sub InstallFile { Line 562  sub InstallFile {
   
     return 1;      return 1;
 }  }
   
   
 #  #
 #   ConfigFileFromSelector: converts a configuration file selector  #   ConfigFileFromSelector: converts a configuration file selector
 #                 (one of host or domain at this point) into a   #                 (one of host or domain at this point) into a 
Line 564  sub isValidEditCommand { Line 778  sub isValidEditCommand {
 #                  file being edited.  #                  file being edited.
 #  #
 sub ApplyEdit {  sub ApplyEdit {
     my $directive   = shift;  
     my $editor      = shift;      my ($directive, $editor) = @_;
   
     # Break the directive down into its command and its parameters      # Break the directive down into its command and its parameters
     # (at most two at this point.  The meaning of the parameters, if in fact      # (at most two at this point.  The meaning of the parameters, if in fact
Line 649  sub AdjustOurHost { Line 863  sub AdjustOurHost {
 #        editor     - Editor containing the file.  #        editor     - Editor containing the file.
 #  #
 sub ReplaceConfigFile {  sub ReplaceConfigFile {
     my $filename  = shift;      
     my $editor    = shift;      my ($filename, $editor) = @_;
   
     CopyFile ($filename, $filename.".old");      CopyFile ($filename, $filename.".old");
   
Line 719  sub EditFile { Line 933  sub EditFile {
   
     return "ok\n";      return "ok\n";
 }  }
   
   #---------------------------------------------------------------
   #
   # Manipulation of hash based databases (factoring out common code
   # for later use as we refactor.
   #
   #  Ties a domain level resource file to a hash.
   #  If requested a history entry is created in the associated hist file.
   #
   #  Parameters:
   #     domain    - Name of the domain in which the resource file lives.
   #     namespace - Name of the hash within that domain.
   #     how       - How to tie the hash (e.g. GDBM_WRCREAT()).
   #     loghead   - Optional parameter, if present a log entry is created
   #                 in the associated history file and this is the first part
   #                  of that entry.
   #     logtail   - Goes along with loghead,  The actual logentry is of the
   #                 form $loghead:<timestamp>:logtail.
   # Returns:
   #    Reference to a hash bound to the db file or alternatively undef
   #    if the tie failed.
   #
   sub tie_domain_hash {
       my ($domain, 
    $namespace,
    $how)     = @_;
       
       # Filter out any whitespace in the domain name:
       
       $domain =~ s/\W//g;
       
       # We have enough to go on to tie the hash:
       
       my $user_top_dir   = $perlvar{'lonUsersDir'};
       my $domain_dir     = $user_top_dir."/$domain";
       my $resource_file  = $domain_dir."/$namespace.db";
       my %hash;
       if(tie(%hash, 'GDBM_File', $resource_file, $how, 0640)) {
    if (scalar @_) { # Need to log the operation.
       my $logFh = IO::File->new(">>domain_dir/$namespace.hist");
       if($logFh) {
    my $timestamp = time;
    my ($loghead, $logtail) = @_;
    print $logFh "$loghead:$timestamp:$logtail\n";
       }
    }
    return \%hash; # Return the tied hash.
       }
       else {
    return undef; # Tie failed.
       }
   }
   
   #
   #   Ties a user's resource file to a hash.  
   #   If necessary, an appropriate history
   #   log file entry is made as well.
   #   This sub factors out common code from the subs that manipulate
   #   the various gdbm files that keep keyword value pairs.
   # Parameters:
   #   domain       - Name of the domain the user is in.
   #   user         - Name of the 'current user'.
   #   namespace    - Namespace representing the file to tie.
   #   how          - What the tie is done to (e.g. GDBM_WRCREAT().
   #   loghead      - Optional first part of log entry if there may be a
   #                  history file.
   #   what         - Optional tail of log entry if there may be a history
   #                  file.
   # Returns:
   #   hash to which the database is tied.  It's up to the caller to untie.
   #   undef if the has could not be tied.
   #
   sub TieUserHash {
       my ($domain,
    $user,
    $namespace,
    $how)       = @_;
   
       
       $namespace=~s/\//\_/g; # / -> _
       $namespace=~s/\W//g; # whitespace eliminated.
       my $proname     = propath($domain, $user);
      
       #  Tie the database.
       
       my %hash;
       if(tie(%hash, 'GDBM_File', "$proname/$namespace.db",
      $how, 0640)) {
    # If this is a namespace for which a history is kept,
    # make the history log entry:    
    if (($namespace =~/^nohist\_/) && (scalar @_ > 0)) {
       my $args = scalar @_;
       Debug(" Opening history: $namespace $args");
       my $hfh = IO::File->new(">>$proname/$namespace.hist"); 
       if($hfh) {
    my $now = time;
    my $loghead  = shift;
    my $what    = shift;
    print $hfh "$loghead:$now:$what\n";
       }
    }
    return \%hash;
       } else {
    return undef;
       }
       
   }
   #---------------------------------------------------------------
   #
   #   Getting, decoding and dispatching requests:
   #
   
   #
   #   Get a Request:
   #   Gets a Request message from the client.  The transaction
   #   is defined as a 'line' of text.  We remove the new line
   #   from the text line.  
   #   
   sub GetRequest {
       my $input = <$client>;
       chomp($input);
   
       Debug("Request = $input\n");
   
       &status('Processing '.$clientname.':'.$input);
   
       return $input;
   }
   #
   #   Decipher encoded traffic
   #  Parameters:
   #     input      - Encoded data.
   #  Returns:
   #     Decoded data or undef if encryption key was not yet negotiated.
   #  Implicit input:
   #     cipher  - This global holds the negotiated encryption key.
   #
   sub Decipher {
       my ($input)  = @_;
       my $output = '';
      
      
       if($cipher) {
    my($enc, $enclength, $encinput) = split(/:/, $input);
    for(my $encidx = 0; $encidx < length($encinput); $encidx += 16) {
       $output .= 
    $cipher->decrypt(pack("H16", substr($encinput, $encidx, 16)));
    }
    return substr($output, 0, $enclength);
       } else {
    return undef;
       }
   }
   
   #
   #   Register a command processor.  This function is invoked to register a sub
   #   to process a request.  Once registered, the ProcessRequest sub can automatically
   #   dispatch requests to an appropriate sub, and do the top level validity checking
   #   as well:
   #    - Is the keyword recognized.
   #    - Is the proper client type attempting the request.
   #    - Is the request encrypted if it has to be.
   #   Parameters:
   #    $request_name         - Name of the request being registered.
   #                           This is the command request that will match
   #                           against the hash keywords to lookup the information
   #                           associated with the dispatch information.
   #    $procedure           - Reference to a sub to call to process the request.
   #                           All subs get called as follows:
   #                             Procedure($cmd, $tail, $replyfd, $key)
   #                             $cmd    - the actual keyword that invoked us.
   #                             $tail   - the tail of the request that invoked us.
   #                             $replyfd- File descriptor connected to the client
   #    $must_encode          - True if the request must be encoded to be good.
   #    $client_ok            - True if it's ok for a client to request this.
   #    $manager_ok           - True if it's ok for a manager to request this.
   # Side effects:
   #      - On success, the Dispatcher hash has an entry added for the key $RequestName
   #      - On failure, the program will die as it's a bad internal bug to try to 
   #        register a duplicate command handler.
   #
   sub RegisterHandler {
       my ($request_name,
    $procedure,
    $must_encode,
    $client_ok,
    $manager_ok)   = @_;
   
       #  Don't allow duplication#
      
       if (defined $Dispatcher{$request_name}) {
    die "Attempting to define a duplicate request handler for $request_name\n";
       }
       #   Build the client type mask:
       
       my $client_type_mask = 0;
       if($client_ok) {
    $client_type_mask  |= $CLIENT_OK;
       }
       if($manager_ok) {
    $client_type_mask  |= $MANAGER_OK;
       }
      
       #  Enter the hash:
         
       my @entry = ($procedure, $must_encode, $client_type_mask);
      
       $Dispatcher{$request_name} = \@entry;
      
      
   }
   
   
   #------------------------------------------------------------------
   
   
   
   
 #  #
 #  Convert an error return code from lcpasswd to a string value.  #  Convert an error return code from lcpasswd to a string value.
 #  #
Line 863  sub HUPSMAN {                      # sig Line 1295  sub HUPSMAN {                      # sig
 #  #
 #    Kill off hashes that describe the host table prior to re-reading it.  #    Kill off hashes that describe the host table prior to re-reading it.
 #    Hashes affected are:  #    Hashes affected are:
 #       %hostid, %hostdom %hostip  #       %hostid, %hostdom %hostip %hostdns.
 #  #
 sub KillHostHashes {  sub KillHostHashes {
     foreach my $key (keys %hostid) {      foreach my $key (keys %hostid) {
Line 875  sub KillHostHashes { Line 1307  sub KillHostHashes {
     foreach my $key (keys %hostip) {      foreach my $key (keys %hostip) {
  delete $hostip{$key};   delete $hostip{$key};
     }      }
       foreach my $key (keys %hostdns) {
    delete $hostdns{$key};
       }
 }  }
 #  #
 #   Read in the host table from file and distribute it into the various hashes:  #   Read in the host table from file and distribute it into the various hashes:
Line 885  sub KillHostHashes { Line 1320  sub KillHostHashes {
 sub ReadHostTable {  sub ReadHostTable {
   
     open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";      open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
           my $myloncapaname = $perlvar{'lonHostID'};
       Debug("My loncapa name is : $myloncapaname");
     while (my $configline=<CONFIG>) {      while (my $configline=<CONFIG>) {
  if (!($configline =~ /^\s*\#/)) {   if (!($configline =~ /^\s*\#/)) {
     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);      my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
     chomp($ip); $ip=~s/\D+$//;      chomp($ip); $ip=~s/\D+$//;
     $hostid{$ip}=$id;      $hostid{$ip}=$id;         # LonCAPA name of host by IP.
     $hostdom{$id}=$domain;      $hostdom{$id}=$domain;    # LonCAPA domain name of host. 
     $hostip{$id}=$ip;      $hostip{$id}=$ip;      # IP address of host.
     if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; }      $hostdns{$name} = $id;    # LonCAPA name of host by DNS.
   
       if ($id eq $perlvar{'lonHostID'}) { 
    Debug("Found me in the host table: $name");
    $thisserver=$name; 
       }
  }   }
     }      }
     close(CONFIG);      close(CONFIG);
Line 1015  sub Debug { Line 1456  sub Debug {
 #     request - Original request from client.  #     request - Original request from client.
 #  #
 sub Reply {  sub Reply {
     my $fd      = shift;  
     my $reply   = shift;      my ($fd, $reply, $request) = @_;
     my $request = shift;  
   
     print $fd $reply;      print $fd $reply;
     Debug("Request was $request  Reply was $reply");      Debug("Request was $request  Reply was $reply");
Line 1030  sub logstatus { Line 1470  sub logstatus {
     my $docdir=$perlvar{'lonDocRoot'};      my $docdir=$perlvar{'lonDocRoot'};
     {      {
     my $fh=IO::File->new(">>$docdir/lon-status/londstatus.txt");      my $fh=IO::File->new(">>$docdir/lon-status/londstatus.txt");
     print $fh $$."\t".$clientname."\t".$currenthostid."\t".$status."\t".$lastlog."\n";      print $fh $$."\t".$clientname."\t".$currenthostid."\t"
    .$status."\t".$lastlog."\t $keymode\n";
     $fh->close();      $fh->close();
     }      }
     &status("Finished londstatus.txt");      &status("Finished londstatus.txt");
     {      {
  my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");   my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");
         print $fh $status."\n".$lastlog."\n".time;          print $fh $status."\n".$lastlog."\n".time."\n$keymode";
         $fh->close();          $fh->close();
     }      }
     &status("Finished logging");      &status("Finished logging");
Line 1236  while (1) { Line 1677  while (1) {
   
 sub make_new_child {  sub make_new_child {
     my $pid;      my $pid;
     my $cipher;  #    my $cipher;     # Now global
     my $sigset;      my $sigset;
   
     $client = shift;      $client = shift;
Line 1265  sub make_new_child { Line 1706  sub make_new_child {
  &logthis("Unable to determine who caller was, getpeername returned nothing");   &logthis("Unable to determine who caller was, getpeername returned nothing");
     }      }
     if (defined($iaddr)) {      if (defined($iaddr)) {
  $clientip=inet_ntoa($iaddr);   $clientip  = inet_ntoa($iaddr);
    Debug("Connected with $clientip");
    $clientdns = gethostbyaddr($iaddr, AF_INET);
    Debug("Connected with $clientdns by name");
     } else {      } else {
  &logthis("Unable to determine clinetip");   &logthis("Unable to determine clientip");
  $clientip='Unavailable';   $clientip='Unavailable';
     }      }
           
Line 1292  sub make_new_child { Line 1736  sub make_new_child {
         sigprocmask(SIG_UNBLOCK, $sigset)          sigprocmask(SIG_UNBLOCK, $sigset)
             or die "Can't unblock SIGINT for fork: $!\n";              or die "Can't unblock SIGINT for fork: $!\n";
   
         my $tmpsnum=0;  #        my $tmpsnum=0;            # Now global
 #---------------------------------------------------- kerberos 5 initialization  #---------------------------------------------------- kerberos 5 initialization
         &Authen::Krb5::init_context();          &Authen::Krb5::init_context();
         &Authen::Krb5::init_ets();          &Authen::Krb5::init_ets();
Line 1301  sub make_new_child { Line 1745  sub make_new_child {
 # =============================================================================  # =============================================================================
             # do something with the connection              # do something with the connection
 # -----------------------------------------------------------------------------  # -----------------------------------------------------------------------------
  # see if we know client and check for spoof IP by challenge   # see if we know client and 'check' for spoof IP by ineffective challenge
   
  ReadManagerTable; # May also be a manager!!   ReadManagerTable; # May also be a manager!!
   
Line 1319  sub make_new_child { Line 1763  sub make_new_child {
     $clientname = $managers{$clientip};      $clientname = $managers{$clientip};
  }   }
  my $clientok;   my $clientok;
   
  if ($clientrec || $ismanager) {   if ($clientrec || $ismanager) {
     &status("Waiting for init from $clientip $clientname");      &status("Waiting for init from $clientip $clientname");
     &logthis('<font color="yellow">INFO: Connection, '.      &logthis('<font color="yellow">INFO: Connection, '.
Line 1326  sub make_new_child { Line 1771  sub make_new_child {
   " ($clientname) connection type = $ConnectionType </font>" );    " ($clientname) connection type = $ConnectionType </font>" );
     &status("Connecting $clientip  ($clientname))");       &status("Connecting $clientip  ($clientname))"); 
     my $remotereq=<$client>;      my $remotereq=<$client>;
     $remotereq=~s/[^\w:]//g;      chomp($remotereq);
       Debug("Got init: $remotereq");
       my $inikeyword = split(/:/, $remotereq);
     if ($remotereq =~ /^init/) {      if ($remotereq =~ /^init/) {
  &sethost("sethost:$perlvar{'lonHostID'}");   &sethost("sethost:$perlvar{'lonHostID'}");
  my $challenge="$$".time;   #
  print $client "$challenge\n";   #  If the remote is attempting a local init... give that a try:
  &status(   #
  "Waiting for challenge reply from $clientip ($clientname)");    my ($i, $inittype) = split(/:/, $remotereq);
  $remotereq=<$client>;  
  $remotereq=~s/\W//g;   # If the connection type is ssl, but I didn't get my
  if ($challenge eq $remotereq) {   # certificate files yet, then I'll drop  back to 
     $clientok=1;   # insecure (if allowed).
     print $client "ok\n";  
    if($inittype eq "ssl") {
       my ($ca, $cert) = lonssl::CertificateFile;
       my $kfile       = lonssl::KeyFile;
       if((!$ca)   || 
          (!$cert) || 
          (!$kfile)) {
    $inittype = ""; # This forces insecure attempt.
    &logthis("<font color=\"blue\"> Certificates not "
    ."installed -- trying insecure auth</font>");
       }
       else { # SSL certificates are in place so
       } # Leave the inittype alone.
    }
   
    if($inittype eq "local") {
       my $key = LocalConnection($client, $remotereq);
       if($key) {
    Debug("Got local key $key");
    $clientok     = 1;
    my $cipherkey = pack("H32", $key);
    $cipher       = new IDEA($cipherkey);
    print $client "ok:local\n";
    &logthis('<font color="green"'
    . "Successful local authentication </font>");
    $keymode = "local"
       } else {
    Debug("Failed to get local key");
    $clientok = 0;
    shutdown($client, 3);
    close $client;
       }
    } elsif ($inittype eq "ssl") {
       my $key = SSLConnection($client);
       if ($key) {
    $clientok = 1;
    my $cipherkey = pack("H32", $key);
    $cipher       = new IDEA($cipherkey);
    &logthis('<font color="green">'
    ."Successfull ssl authentication with $clientname </font>");
    $keymode = "ssl";
        
       } else {
    $clientok = 0;
    close $client;
       }
      
  } else {   } else {
     &logthis(      my $ok = InsecureConnection($client);
      "<font color='blue'>WARNING: $clientip did not reply challenge</font>");      if($ok) {
     &status('No challenge reply '.$clientip);   $clientok = 1;
    &logthis('<font color="green">'
    ."Successful insecure authentication with $clientname </font>");
    print $client "ok\n";
    $keymode = "insecure";
       } else {
    &logthis('<font color="yellow">'
     ."Attempted insecure connection disallowed </font>");
    close $client;
    $clientok = 0;
   
       }
  }   }
     } else {      } else {
  &logthis(   &logthis(
Line 1349  sub make_new_child { Line 1853  sub make_new_child {
  ."$clientip failed to initialize: >$remotereq< </font>");   ."$clientip failed to initialize: >$remotereq< </font>");
  &status('No init '.$clientip);   &status('No init '.$clientip);
     }      }
       
  } else {   } else {
     &logthis(      &logthis(
      "<font color='blue'>WARNING: Unknown client $clientip</font>");       "<font color='blue'>WARNING: Unknown client $clientip</font>");
     &status('Hung up on '.$clientip);      &status('Hung up on '.$clientip);
  }   }
    
  if ($clientok) {   if ($clientok) {
 # ---------------- New known client connecting, could mean machine online again  # ---------------- New known client connecting, could mean machine online again
           
Line 1562  sub make_new_child { Line 2068  sub make_new_child {
  $pwdcorrect=0;    $pwdcorrect=0; 
  # log error if it is not a bad password   # log error if it is not a bad password
  if ($krb4_error != 62) {   if ($krb4_error != 62) {
     &logthis('krb4:'.$uname.','.$contentpwd.','.      &logthis('krb4:'.$uname.','.
      &Authen::Krb4::get_err_txt($Authen::Krb4::error));       &Authen::Krb4::get_err_txt($Authen::Krb4::error));
  }   }
     }      }
Line 1979  sub make_new_child { Line 2485  sub make_new_child {
  } elsif ($userinput =~ /^put/) {   } elsif ($userinput =~ /^put/) {
     if(isClient) {      if(isClient) {
  my ($cmd,$udom,$uname,$namespace,$what)   my ($cmd,$udom,$uname,$namespace,$what)
     =split(/:/,$userinput);      =split(/:/,$userinput,5);
  $namespace=~s/\//\_/g;   $namespace=~s/\//\_/g;
  $namespace=~s/\W//g;   $namespace=~s/\W//g;
  if ($namespace ne 'roles') {   if ($namespace ne 'roles') {
     chomp($what);      chomp($what);
     my $proname=propath($udom,$uname);      my $proname=propath($udom,$uname);
     my $now=time;      my $now=time;
     unless ($namespace=~/^nohist\_/) {  
  my $hfh;  
  if (  
     $hfh=IO::File->new(">>$proname/$namespace.hist")  
     ) { print $hfh "P:$now:$what\n"; }  
     }  
     my @pairs=split(/\&/,$what);      my @pairs=split(/\&/,$what);
     my %hash;      my %hash;
     if (tie(%hash,'GDBM_File',      if (tie(%hash,'GDBM_File',
     "$proname/$namespace.db",      "$proname/$namespace.db",
     &GDBM_WRCREAT(),0640)) {      &GDBM_WRCREAT(),0640)) {
    unless ($namespace=~/^nohist\_/) {
       my $hfh;
       if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { print $hfh "P:$now:$what\n"; }
    }
   
  foreach my $pair (@pairs) {   foreach my $pair (@pairs) {
     my ($key,$value)=split(/=/,$pair);      my ($key,$value)=split(/=/,$pair);
     $hash{$key}=$value;      $hash{$key}=$value;
Line 2031  sub make_new_child { Line 2536  sub make_new_child {
     chomp($what);      chomp($what);
     my $proname=propath($udom,$uname);      my $proname=propath($udom,$uname);
     my $now=time;      my $now=time;
     unless ($namespace=~/^nohist\_/) {  
  my $hfh;  
  if (  
     $hfh=IO::File->new(">>$proname/$namespace.hist")  
     ) { print $hfh "P:$now:$what\n"; }  
     }  
     my @pairs=split(/\&/,$what);      my @pairs=split(/\&/,$what);
     my %hash;      my %hash;
     if (tie(%hash,'GDBM_File',      if (tie(%hash,'GDBM_File',
     "$proname/$namespace.db",      "$proname/$namespace.db",
     &GDBM_WRCREAT(),0640)) {      &GDBM_WRCREAT(),0640)) {
    unless ($namespace=~/^nohist\_/) {
       my $hfh;
       if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { print $hfh "P:$now:$what\n"; }
    }
  foreach my $pair (@pairs) {   foreach my $pair (@pairs) {
     my ($key,$value)=split(/=/,$pair);      my ($key,$value)=split(/=/,$pair);
                                     # We could check that we have a number...                                      # We could check that we have a number...
Line 2083  sub make_new_child { Line 2586  sub make_new_child {
     chomp($what);      chomp($what);
     my $proname=propath($udom,$uname);      my $proname=propath($udom,$uname);
     my $now=time;      my $now=time;
     {  
  my $hfh;  
  if (  
     $hfh=IO::File->new(">>$proname/$namespace.hist")  
     ) {   
     print $hfh "P:$now:$exedom:$exeuser:$what\n";  
  }  
     }  
     my @pairs=split(/\&/,$what);      my @pairs=split(/\&/,$what);
     my %hash;      my %hash;
     if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {      if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
    {
       my $hfh;
       if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { 
    print $hfh "P:$now:$exedom:$exeuser:$what\n";
       }
    }
   
  foreach my $pair (@pairs) {   foreach my $pair (@pairs) {
     my ($key,$value)=split(/=/,$pair);      my ($key,$value)=split(/=/,$pair);
     &ManagePermissions($key, $udom, $uname,      &ManagePermissions($key, $udom, $uname,
Line 2134  sub make_new_child { Line 2636  sub make_new_child {
     chomp($what);      chomp($what);
     my $proname=propath($udom,$uname);      my $proname=propath($udom,$uname);
     my $now=time;      my $now=time;
     {  
  my $hfh;  
  if (  
     $hfh=IO::File->new(">>$proname/$namespace.hist")  
     ) {   
     print $hfh "D:$now:$exedom:$exeuser:$what\n";  
  }  
     }  
     my @rolekeys=split(/\&/,$what);      my @rolekeys=split(/\&/,$what);
     my %hash;      my %hash;
     if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {      if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
    {
       my $hfh;
       if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { 
    print $hfh "D:$now:$exedom:$exeuser:$what\n";
       }
    }
  foreach my $key (@rolekeys) {   foreach my $key (@rolekeys) {
     delete $hash{$key};      delete $hash{$key};
  }   }
Line 2261  sub make_new_child { Line 2761  sub make_new_child {
  chomp($what);   chomp($what);
  my $proname=propath($udom,$uname);   my $proname=propath($udom,$uname);
  my $now=time;   my $now=time;
  unless ($namespace=~/^nohist\_/) {  
     my $hfh;  
     if (  
  $hfh=IO::File->new(">>$proname/$namespace.hist")  
  ) { print $hfh "D:$now:$what\n"; }  
  }  
  my @keys=split(/\&/,$what);   my @keys=split(/\&/,$what);
  my %hash;   my %hash;
  if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {   if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
       unless ($namespace=~/^nohist\_/) {
    my $hfh;
    if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { print $hfh "D:$now:$what\n"; }
       }
     foreach my $key (@keys) {      foreach my $key (@keys) {
  delete($hash{$key});   delete($hash{$key});
     }      }
Line 2424  sub make_new_child { Line 2922  sub make_new_child {
     chomp($what);      chomp($what);
     my $proname=propath($udom,$uname);      my $proname=propath($udom,$uname);
     my $now=time;      my $now=time;
     unless ($namespace=~/^nohist\_/) {  
  my $hfh;  
  if (  
     $hfh=IO::File->new(">>$proname/$namespace.hist")  
     ) { print $hfh "P:$now:$rid:$what\n"; }  
     }  
     my @pairs=split(/\&/,$what);      my @pairs=split(/\&/,$what);
     my %hash;      my %hash;
     if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {      if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
    unless ($namespace=~/^nohist\_/) {
       my $hfh;
       if ($hfh=IO::File->new(">>$proname/$namespace.hist")) {
    print $hfh "P:$now:$rid:$what\n";
       }
    }
  my @previouskeys=split(/&/,$hash{"keys:$rid"});   my @previouskeys=split(/&/,$hash{"keys:$rid"});
  my $key;   my $key;
  $hash{"version:$rid"}++;   $hash{"version:$rid"}++;
Line 2533  sub make_new_child { Line 3031  sub make_new_child {
     }      }
 # ------------------------------------------------------------------- querysend  # ------------------------------------------------------------------- querysend
  } elsif ($userinput =~ /^querysend/) {   } elsif ($userinput =~ /^querysend/) {
     if(isClient) {      if (isClient) {
  my ($cmd,$query,   my ($cmd,$query,
     $arg1,$arg2,$arg3)=split(/\:/,$userinput);      $arg1,$arg2,$arg3)=split(/\:/,$userinput);
  $query=~s/\n*$//g;   $query=~s/\n*$//g;
Line 2581  sub make_new_child { Line 3079  sub make_new_child {
  my %hash;   my %hash;
  if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {   if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
     foreach my $pair (@pairs) {      foreach my $pair (@pairs) {
  my ($key,$value)=split(/=/,$pair);   my ($key,$descr,$inst_code)=split(/=/,$pair);
  $hash{$key}=$value.':'.$now;   $hash{$key}=$descr.':'.$inst_code.':'.$now;
     }      }
     if (untie(%hash)) {      if (untie(%hash)) {
  print $client "ok\n";   print $client "ok\n";
Line 2617  sub make_new_child { Line 3115  sub make_new_child {
  my %hash;   my %hash;
  if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {   if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
     while (my ($key,$value) = each(%hash)) {      while (my ($key,$value) = each(%hash)) {
  my ($descr,$lasttime)=split(/\:/,$value);                                  my ($descr,$lasttime,$inst_code);
                                   if ($value =~ m/^([^\:]*):([^\:]*):(\d+)$/) {
       ($descr,$inst_code,$lasttime)=($1,$2,$3);
                                   } else {
                                       ($descr,$lasttime) = split(/\:/,$value);
                                   }
  if ($lasttime<$since) { next; }   if ($lasttime<$since) { next; }
  if ($description eq '.') {   if ($description eq '.') {
     $qresult.=$key.'='.$descr.'&';      $qresult.=$key.'='.$descr.':'.$inst_code.'&';
  } else {   } else {
     my $unescapeVal = &unescape($descr);      my $unescapeVal = &unescape($descr);
     if (eval('$unescapeVal=~/\Q$description\E/i')) {      if (eval('$unescapeVal=~/\Q$description\E/i')) {
  $qresult.="$key=$descr&";   $qresult.=$key.'='.$descr.':'.$inst_code.'&';
     }      }
  }   }
     }      }
Line 2653  sub make_new_child { Line 3156  sub make_new_child {
  $udom=~s/\W//g;   $udom=~s/\W//g;
  my $proname="$perlvar{'lonUsersDir'}/$udom/ids";   my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
  my $now=time;   my $now=time;
  {  
     my $hfh;  
     if (  
  $hfh=IO::File->new(">>$proname.hist")  
  ) { print $hfh "P:$now:$what\n"; }  
  }  
  my @pairs=split(/\&/,$what);   my @pairs=split(/\&/,$what);
  my %hash;   my %hash;
  if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {   if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
       {
    my $hfh;
    if ($hfh=IO::File->new(">>$proname.hist")) {
       print $hfh "P:$now:$what\n";
    }
       }
     foreach my $pair (@pairs) {      foreach my $pair (@pairs) {
  my ($key,$value)=split(/=/,$pair);   my ($key,$value)=split(/=/,$pair);
  $hash{$key}=$value;   $hash{$key}=$value;
Line 2778  sub make_new_child { Line 3281  sub make_new_child {
  Reply($client, "refused\n", $userinput);   Reply($client, "refused\n", $userinput);
             
     }      }
   # ----------------------------------------- portfolio directory list (portls)
                   } elsif ($userinput =~ /^portls/) {
                       if(isClient) {
                           my ($cmd,$uname,$udom)=split(/:/,$userinput);
                           my $udir=propath($udom,$uname).'/userfiles/portfolio';
                           my $dirLine='';
                           my $dirContents='';
                           if (opendir(LSDIR,$udir.'/')){
                               while ($dirLine = readdir(LSDIR)){
                                   $dirContents = $dirContents.$dirLine.'<br />';
                               }
                           } else {
                               $dirContents = "No directory found\n";
                           }
                           print $client $dirContents."\n";
                       } else {
                           Reply($client, "refused\n", $userinput);
                       }
 # -------------------------------------------------------------------------- ls  # -------------------------------------------------------------------------- ls
  } elsif ($userinput =~ /^ls/) {   } elsif ($userinput =~ /^ls/) {
     if(isClient) {      if(isClient) {
Line 2864  sub make_new_child { Line 3385  sub make_new_child {
     } else {      } else {
  print $client "refused\n";   print $client "refused\n";
     }      }
   #------------------------------- is auto-enrollment enabled?
                   } elsif ($userinput =~/^autorun:/) {
                       if (isClient) {
                           my ($cmd,$cdom) = split(/:/,$userinput);
                           my $outcome = &localenroll::run($cdom);
                           print $client "$outcome\n";
                       } else {
                           print $client "0\n";
                       }
   #------------------------------- get official sections (for auto-enrollment).
                   } elsif ($userinput =~/^autogetsections:/) {
                       if (isClient) {
                           my ($cmd,$coursecode,$cdom)=split(/:/,$userinput);
                           my @secs = &localenroll::get_sections($coursecode,$cdom);
                           my $seclist = &escape(join(':',@secs));
                           print $client "$seclist\n";
                       } else {
                           print $client "refused\n";
                       }
   #----------------------- validate owner of new course section (for auto-enrollment).
                   } elsif ($userinput =~/^autonewcourse:/) {
                       if (isClient) {
                           my ($cmd,$inst_course_id,$owner,$cdom)=split(/:/,$userinput);
                           my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom);
                           print $client "$outcome\n";
                       } else {
                           print $client "refused\n";
                       }
   #-------------- validate course section in schedule of classes (for auto-enrollment).
                   } elsif ($userinput =~/^autovalidatecourse:/) {
                       if (isClient) {
                           my ($cmd,$inst_course_id,$cdom)=split(/:/,$userinput);
                           my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom);
                           print $client "$outcome\n";
                       } else {
                           print $client "refused\n";
                       }
   #--------------------------- create password for new user (for auto-enrollment).
                   } elsif ($userinput =~/^autocreatepassword:/) {
                       if (isClient) {
                           my ($cmd,$authparam,$cdom)=split(/:/,$userinput);
                           my ($create_passwd,$authchk);
                           ($authparam,$create_passwd,$authchk) = &localenroll::create_password($authparam,$cdom);
                           print $client &escape($authparam.':'.$create_passwd.':'.$authchk)."\n";
                       } else {
                           print $client "refused\n";
                       }
   #---------------------------  read and remove temporary files (for auto-enrollment).
                   } elsif ($userinput =~/^autoretrieve:/) {
                       if (isClient) {
                           my ($cmd,$filename) = split(/:/,$userinput);
                           my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename;
                           if ( (-e $source) && ($filename ne '') ) {
                               my $reply = '';
                               if (open(my $fh,$source)) {
                                   while (<$fh>) {
                                       chomp($_);
                                       $_ =~ s/^\s+//g;
                                       $_ =~ s/\s+$//g;
                                       $reply .= $_;
                                   }
                                   close($fh);
                                   print $client &escape($reply)."\n";
   #                                unlink($source);
                               } else {
                                   print $client "error\n";
                               }
                           } else {
                               print $client "error\n";
                           }
                       } else {
                           print $client "refused\n";
                       }
   #---------------------  read and retrieve institutional code format (for support form).
                   } elsif ($userinput =~/^autoinstcodeformat:/) {
                       if (isClient) {
                           my $reply;
                           my($cmd,$cdom,$course) = split(/:/,$userinput);
                           my @pairs = split/\&/,$course;
                           my %instcodes = ();
                           my %codes = ();
                           my @codetitles = ();
                           my %cat_titles = ();
                           my %cat_order = ();
                           foreach (@pairs) {
                               my ($key,$value) = split/=/,$_;
                               $instcodes{&unescape($key)} = &unescape($value);
                           }
                           my $formatreply = &localenroll::instcode_format($cdom,\%instcodes,\%codes,\@codetitles,\%cat_titles,\%cat_order);
                           if ($formatreply eq 'ok') {
                               my $codes_str = &hash2str(%codes);
                               my $codetitles_str = &array2str(@codetitles);
                               my $cat_titles_str = &hash2str(%cat_titles);
                               my $cat_order_str = &hash2str(%cat_order);
                               print $client $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':'.$cat_order_str."\n";
                           }
                       } else {
                           print $client "refused\n";
                       }
 # ------------------------------------------------------------- unknown command  # ------------------------------------------------------------- unknown command
   
  } else {   } else {
Line 2872  sub make_new_child { Line 3492  sub make_new_child {
  }   }
 # -------------------------------------------------------------------- complete  # -------------------------------------------------------------------- complete
  alarm(0);   alarm(0);
  &status('Listening to '.$clientname);   &status('Listening to '.$clientname." ($keymode)");
     }      }
 # --------------------------------------------- client unknown or fishy, refuse  # --------------------------------------------- client unknown or fishy, refuse
  } else {   } else {
Line 2910  sub make_new_child { Line 3530  sub make_new_child {
 #  #
 sub ManagePermissions  sub ManagePermissions
 {  {
     my $request = shift;  
     my $domain  = shift;      my ($request, $domain, $user, $authtype) = @_;
     my $user    = shift;  
     my $authtype= shift;  
   
     # See if the request is of the form /$domain/_au      # See if the request is of the form /$domain/_au
     if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput...      if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput...
Line 2930  sub ManagePermissions Line 3548  sub ManagePermissions
 #  #
 sub GetAuthType   sub GetAuthType 
 {  {
     my $domain = shift;  
     my $user   = shift;      my ($domain, $user)  = @_;
   
     Debug("GetAuthType( $domain, $user ) \n");      Debug("GetAuthType( $domain, $user ) \n");
     my $proname    = &propath($domain, $user);       my $proname    = &propath($domain, $user); 
Line 3233  sub sethost { Line 3851  sub sethost {
     my (undef,$hostid)=split(/:/,$remotereq);      my (undef,$hostid)=split(/:/,$remotereq);
     if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }      if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }
     if ($hostip{$perlvar{'lonHostID'}} eq $hostip{$hostid}) {      if ($hostip{$perlvar{'lonHostID'}} eq $hostip{$hostid}) {
  $currenthostid=$hostid;   $currenthostid  =$hostid;
  $currentdomainid=$hostdom{$hostid};   $currentdomainid=$hostdom{$hostid};
  &logthis("Setting hostid to $hostid, and domain to $currentdomainid");   &logthis("Setting hostid to $hostid, and domain to $currentdomainid");
     } else {      } else {
Line 3273  sub userload { Line 3891  sub userload {
     return $userloadpercent;      return $userloadpercent;
 }  }
   
   # Routines for serializing arrays and hashes (copies from lonnet)
   
   sub array2str {
     my (@array) = @_;
     my $result=&arrayref2str(\@array);
     $result=~s/^__ARRAY_REF__//;
     $result=~s/__END_ARRAY_REF__$//;
     return $result;
   }
                                                                                    
   sub arrayref2str {
     my ($arrayref) = @_;
     my $result='__ARRAY_REF__';
     foreach my $elem (@$arrayref) {
       if(ref($elem) eq 'ARRAY') {
         $result.=&arrayref2str($elem).'&';
       } elsif(ref($elem) eq 'HASH') {
         $result.=&hashref2str($elem).'&';
       } elsif(ref($elem)) {
         #print("Got a ref of ".(ref($elem))." skipping.");
       } else {
         $result.=&escape($elem).'&';
       }
     }
     $result=~s/\&$//;
     $result .= '__END_ARRAY_REF__';
     return $result;
   }
                                                                                    
   sub hash2str {
     my (%hash) = @_;
     my $result=&hashref2str(\%hash);
     $result=~s/^__HASH_REF__//;
     $result=~s/__END_HASH_REF__$//;
     return $result;
   }
                                                                                    
   sub hashref2str {
     my ($hashref)=@_;
     my $result='__HASH_REF__';
     foreach (sort(keys(%$hashref))) {
       if (ref($_) eq 'ARRAY') {
         $result.=&arrayref2str($_).'=';
       } elsif (ref($_) eq 'HASH') {
         $result.=&hashref2str($_).'=';
       } elsif (ref($_)) {
         $result.='=';
         #print("Got a ref of ".(ref($_))." skipping.");
       } else {
           if ($_) {$result.=&escape($_).'=';} else { last; }
       }
   
       if(ref($hashref->{$_}) eq 'ARRAY') {
         $result.=&arrayref2str($hashref->{$_}).'&';
       } elsif(ref($hashref->{$_}) eq 'HASH') {
         $result.=&hashref2str($hashref->{$_}).'&';
       } elsif(ref($hashref->{$_})) {
          $result.='&';
         #print("Got a ref of ".(ref($hashref->{$_}))." skipping.");
       } else {
         $result.=&escape($hashref->{$_}).'&';
       }
     }
     $result=~s/\&$//;
     $result .= '__END_HASH_REF__';
     return $result;
   }
   
 # ----------------------------------- POD (plain old documentation, CPAN style)  # ----------------------------------- POD (plain old documentation, CPAN style)
   
 =head1 NAME  =head1 NAME

Removed from v.1.190  
changed lines
  Added in v.1.209


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>