Diff for /loncom/lond between versions 1.212 and 1.229

version 1.212, 2004/07/27 10:25:07 version 1.229, 2004/08/16 10:54:19
Line 50  use File::Copy; Line 50  use File::Copy;
 use LONCAPA::ConfigFileEdit;  use LONCAPA::ConfigFileEdit;
 use LONCAPA::lonlocal;  use LONCAPA::lonlocal;
 use LONCAPA::lonssl;  use LONCAPA::lonssl;
   use Fcntl qw(:flock);
   
 my $DEBUG = 0;       # Non zero to enable debug log entries.  my $DEBUG = 0;       # Non zero to enable debug log entries.
   
Line 58  my $lastlog=''; Line 59  my $lastlog='';
   
 my $VERSION='$Revision$'; #' stupid emacs  my $VERSION='$Revision$'; #' stupid emacs
 my $remoteVERSION;  my $remoteVERSION;
 my $currenthostid;  my $currenthostid="default";
 my $currentdomainid;  my $currentdomainid;
   
 my $client;  my $client;
Line 121  my @passwderrors = ("ok", Line 122  my @passwderrors = ("ok",
    "lcpasswd Cannot set new passwd.",     "lcpasswd Cannot set new passwd.",
    "lcpasswd Username has invalid characters",     "lcpasswd Username has invalid characters",
    "lcpasswd Invalid characters in password",     "lcpasswd Invalid characters in password",
     "11", "12",     "lcpasswd User already exists", 
     "lcpasswd Password mismatch");                     "lcpasswd Something went wrong with user addition.",
       "lcpasswd Password mismatch",
       "lcpasswd Error filename is invalid");
   
   
 #  The array below are lcuseradd error strings.:  #  The array below are lcuseradd error strings.:
Line 159  sub ResetStatistics { Line 162  sub ResetStatistics {
     $Failures     = 0;      $Failures     = 0;
 }  }
   
   
   
 #------------------------------------------------------------------------  #------------------------------------------------------------------------
 #  #
 #   LocalConnection  #   LocalConnection
Line 191  sub LocalConnection { Line 192  sub LocalConnection {
  ."$clientdns ne $thisserver </font>");   ."$clientdns ne $thisserver </font>");
  close $Socket;   close $Socket;
  return undef;   return undef;
     }       }  else {
     else {  
  chomp($initcmd); # Get rid of \n in filename.   chomp($initcmd); # Get rid of \n in filename.
  my ($init, $type, $name) = split(/:/, $initcmd);   my ($init, $type, $name) = split(/:/, $initcmd);
  Debug(" Init command: $init $type $name ");   Debug(" Init command: $init $type $name ");
Line 323  sub InsecureConnection { Line 323  sub InsecureConnection {
     $answer    =~s/\W//g;      $answer    =~s/\W//g;
     if($challenge eq $answer) {      if($challenge eq $answer) {
  return 1;   return 1;
     }       } else {
     else {  
  logthis("<font color='blue'>WARNING client did not respond to challenge</font>");   logthis("<font color='blue'>WARNING client did not respond to challenge</font>");
  &status("No challenge reqply");   &status("No challenge reqply");
  return 0;   return 0;
Line 371  sub isClient { Line 370  sub isClient {
 #                     - This allows dynamic changes to the manager table  #                     - This allows dynamic changes to the manager table
 #                       without the need to signal to the lond.  #                       without the need to signal to the lond.
 #  #
   
 sub ReadManagerTable {  sub ReadManagerTable {
   
     #   Clean out the old table first..      #   Clean out the old table first..
Line 653  sub PushFile { Line 651  sub PushFile {
  &logthis('<font color="red"> Pushfile: unable to install '   &logthis('<font color="red"> Pushfile: unable to install '
  .$tablefile." $! </font>");   .$tablefile." $! </font>");
  return "error:$!";   return "error:$!";
     }      } else {
     else {  
  &logthis('<font color="green"> Installed new '.$tablefile   &logthis('<font color="green"> Installed new '.$tablefile
  ."</font>");   ."</font>");
   
Line 1032  sub tie_user_hash { Line 1029  sub tie_user_hash {
     }      }
           
 }  }
 #---------------------------------------------------------------  
   #--------------------- Request Handlers --------------------------------------------
 #  #
 #   Getting, decoding and dispatching requests:  #   By convention each request handler registers itself prior to the sub 
   #   declaration:
 #  #
   
   #++
 #  #
 #   Get a Request:  #  Handles ping requests.
 #   Gets a Request message from the client.  The transaction  #  Parameters:
 #   is defined as a 'line' of text.  We remove the new line  #      $cmd    - the actual keyword that invoked us.
 #   from the text line.    #      $tail   - the tail of the request that invoked us.
 #     #      $replyfd- File descriptor connected to the client
 sub get_request {  #  Implicit Inputs:
     my $input = <$client>;  #      $currenthostid - Global variable that carries the name of the host we are
     chomp($input);  #                       known as.
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit.
   #  Side effects:
   #      Reply information is sent to the client.
   
   sub ping_handler {
       my ($cmd, $tail, $client) = @_;
       Debug("$cmd $tail $client .. $currenthostid:");
      
       Reply( $client,"$currenthostid\n","$cmd:$tail");
      
       return 1;
   }
   &register_handler("ping", \&ping_handler, 0, 1, 1);       # Ping unencoded, client or manager.
   
     Debug("get_request: Request = $input\n");  #++
   #
   # Handles pong requests.  Pong replies with our current host id, and
   #                         the results of a ping sent to us via our lonc.
   #
   # Parameters:
   #      $cmd    - the actual keyword that invoked us.
   #      $tail   - the tail of the request that invoked us.
   #      $replyfd- File descriptor connected to the client
   #  Implicit Inputs:
   #      $currenthostid - Global variable that carries the name of the host we are
   #                       connected to.
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit.
   #  Side effects:
   #      Reply information is sent to the client.
   
     &status('Processing '.$clientname.':'.$input);  sub pong_handler {
       my ($cmd, $tail, $replyfd) = @_;
   
     return $input;      my $reply=&reply("ping",$clientname);
       &Reply( $replyfd, "$currenthostid:$reply\n", "$cmd:$tail"); 
       return 1;
 }  }
 #---------------------------------------------------------------  &register_handler("pong", \&pong_handler, 0, 1, 1);       # Pong unencoded, client or manager
   
   #++
   #      Called to establish an encrypted session key with the remote client.
   #      Note that with secure lond, in most cases this function is never
   #      invoked.  Instead, the secure session key is established either
   #      via a local file that's locked down tight and only lives for a short
   #      time, or via an ssl tunnel...and is generated from a bunch-o-random
   #      bits from /dev/urandom, rather than the predictable pattern used by
   #      by this sub.  This sub is only used in the old-style insecure
   #      key negotiation.
   # Parameters:
   #      $cmd    - the actual keyword that invoked us.
   #      $tail   - the tail of the request that invoked us.
   #      $replyfd- File descriptor connected to the client
   #  Implicit Inputs:
   #      $currenthostid - Global variable that carries the name of the host
   #                       known as.
   #      $clientname    - Global variable that carries the name of the hsot we're connected to.
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit.
   #  Implicit Outputs:
   #      Reply information is sent to the client.
   #      $cipher is set with a reference to a new IDEA encryption object.
   #
   sub establish_key_handler {
       my ($cmd, $tail, $replyfd) = @_;
   
       my $buildkey=time.$$.int(rand 100000);
       $buildkey=~tr/1-6/A-F/;
       $buildkey=int(rand 100000).$buildkey.int(rand 100000);
       my $key=$currenthostid.$clientname;
       $key=~tr/a-z/A-Z/;
       $key=~tr/G-P/0-9/;
       $key=~tr/Q-Z/0-9/;
       $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
       $key=substr($key,0,32);
       my $cipherkey=pack("H32",$key);
       $cipher=new IDEA $cipherkey;
       &Reply($replyfd, "$buildkey\n", "$cmd:$tail"); 
      
       return 1;
   
   }
   &register_handler("ekey", \&establish_key_handler, 0, 1,1);
   
   
   #     Handler for the load command.  Returns the current system load average
   #     to the requestor.
 #  #
 #  Process a request.  This sub should shrink as each action  # Parameters:
 #  gets farmed out into a separat sub that is registered   #      $cmd    - the actual keyword that invoked us.
 #  with the dispatch hash.    #      $tail   - the tail of the request that invoked us.
   #      $replyfd- File descriptor connected to the client
   #  Implicit Inputs:
   #      $currenthostid - Global variable that carries the name of the host
   #                       known as.
   #      $clientname    - Global variable that carries the name of the hsot we're connected to.
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit.
   #  Side effects:
   #      Reply information is sent to the client.
   sub load_handler {
       my ($cmd, $tail, $replyfd) = @_;
   
      # Get the load average from /proc/loadavg and calculate it as a percentage of
      # the allowed load limit as set by the perl global variable lonLoadLim
   
       my $loadavg;
       my $loadfile=IO::File->new('/proc/loadavg');
      
       $loadavg=<$loadfile>;
       $loadavg =~ s/\s.*//g;                      # Extract the first field only.
      
       my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};
   
       &Reply( $replyfd, "$loadpercent\n", "$cmd:$tail");
      
       return 1;
   }
   register_handler("load", \&load_handler, 0, 1, 0);
   
   #
   #   Process the userload request.  This sub returns to the client the current
   #  user load average.  It can be invoked either by clients or managers.
 #  #
 # Parameters:  # Parameters:
 #    user_input   - The request received from the client (lonc).  #      $cmd    - the actual keyword that invoked us.
 # Returns:  #      $tail   - the tail of the request that invoked us.
 #    true to keep processing, false if caller should exit.  #      $replyfd- File descriptor connected to the client
   #  Implicit Inputs:
   #      $currenthostid - Global variable that carries the name of the host
   #                       known as.
   #      $clientname    - Global variable that carries the name of the hsot we're connected to.
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit
   # Implicit inputs:
   #     whatever the userload() function requires.
   #  Implicit outputs:
   #     the reply is written to the client.
 #  #
 sub process_request {  sub user_load_handler {
     my ($userinput) = @_;      # Easier for now to break style than to      my ($cmd, $tail, $replyfd) = @_;
                                 # fix all the userinput -> user_input.  
     my $wasenc    = 0; # True if request was encrypted.      my $userloadpercent=&userload();
 # ------------------------------------------------------------ See if encrypted      &Reply($replyfd, "$userloadpercent\n", "$cmd:$tail");
     if ($userinput =~ /^enc/) {  
  $userinput = decipher($userinput);  
  $wasenc=1;  
  if(!$userinput) { # Cipher not defined.  
     &Failure($client, "error: Encrypted data without negotated key");  
     return 0;  
  }  
     }  
     Debug("process_request: $userinput\n");  
           
 # ------------------------------------------------------------- Normal commands      return 1;
 # ------------------------------------------------------------------------ ping  }
     if ($userinput =~ /^ping/) { # client only  register_handler("userload", \&user_load_handler, 0, 1, 0);
  if(isClient) {  
     print $client "$currenthostid\n";  #   Process a request for the authorization type of a user:
  } else {  #   (userauth).
     Reply($client, "refused\n", $userinput);  
  }  
 # ------------------------------------------------------------------------ pong  
     }elsif ($userinput =~ /^pong/) { # client only  
  if(isClient) {  
     my $reply=&reply("ping",$clientname);  
     print $client "$currenthostid:$reply\n";   
  } else {  
     Reply($client, "refused\n", $userinput);  
  }  
 # ------------------------------------------------------------------------ ekey  
     } elsif ($userinput =~ /^ekey/) { # ok for both clients & mgrs  
  my $buildkey=time.$$.int(rand 100000);  
  $buildkey=~tr/1-6/A-F/;  
  $buildkey=int(rand 100000).$buildkey.int(rand 100000);  
  my $key=$currenthostid.$clientname;  
  $key=~tr/a-z/A-Z/;  
  $key=~tr/G-P/0-9/;  
  $key=~tr/Q-Z/0-9/;  
  $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;  
  $key=substr($key,0,32);  
  my $cipherkey=pack("H32",$key);  
  $cipher=new IDEA $cipherkey;  
  print $client "$buildkey\n";   
 # ------------------------------------------------------------------------ load  
     } elsif ($userinput =~ /^load/) { # client only  
  if (isClient) {  
     my $loadavg;  
     {  
  my $loadfile=IO::File->new('/proc/loadavg');  
  $loadavg=<$loadfile>;  
     }  
     $loadavg =~ s/\s.*//g;  
     my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};  
     print $client "$loadpercent\n";  
  } else {  
     Reply($client, "refused\n", $userinput);  
       
  }  
 # -------------------------------------------------------------------- userload  
     } elsif ($userinput =~ /^userload/) { # client only  
  if(isClient) {  
     my $userloadpercent=&userload();  
     print $client "$userloadpercent\n";  
  } else {  
     Reply($client, "refused\n", $userinput);  
       
  }  
 #  #
 #        Transactions requiring encryption:  # Parameters:
   #      $cmd    - the actual keyword that invoked us.
   #      $tail   - the tail of the request that invoked us.
   #      $replyfd- File descriptor connected to the client
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit
   # Implicit outputs:
   #    The user authorization type is written to the client.
 #  #
 # ----------------------------------------------------------------- currentauth  sub user_authorization_type {
     } elsif ($userinput =~ /^currentauth/) {      my ($cmd, $tail, $replyfd) = @_;
  if (($wasenc==1)  && isClient) { # Encoded & client only.     
     my ($cmd,$udom,$uname)=split(/:/,$userinput);      my $userinput = "$cmd:$tail";
     my $result = GetAuthType($udom, $uname);     
     if($result eq "nouser") {      #  Pull the domain and username out of the command tail.
  print $client "unknown_user\n";      # and call get_auth_type to determine the authentication type.
     }     
     else {      my ($udom,$uname)=split(/:/,$tail);
  print $client "$result\n";      my $result = &get_auth_type($udom, $uname);
     }      if($result eq "nouser") {
  } else {   &Failure( $replyfd, "unknown_user\n", $userinput);
     Reply($client, "refused\n", $userinput);      } else {
        #
  }   # We only want to pass the second field from get_auth_type
 #--------------------------------------------------------------------- pushfile   # for ^krb.. otherwise we'll be handing out the encrypted
     } elsif($userinput =~ /^pushfile/) { # encoded & manager.   # password for internals e.g.
  if(($wasenc == 1) && isManager) {   #
     my $cert = GetCertificate($userinput);   my ($type,$otherinfo) = split(/:/,$result);
     if(ValidManager($cert)) {   if($type =~ /^krb/) {
  my $reply = PushFile($userinput);      $type = $result;
  print $client "$reply\n";  
     } else {  
  print $client "refused\n";  
     }   
  } else {  
     Reply($client, "refused\n", $userinput);  
       
  }   }
 #--------------------------------------------------------------------- reinit   &Reply( $replyfd, "$type:\n", $userinput);
     } elsif($userinput =~ /^reinit/) { # Encoded and manager      }
  if (($wasenc == 1) && isManager) {    
     my $cert = GetCertificate($userinput);      return 1;
     if(ValidManager($cert)) {  }
  chomp($userinput);  &register_handler("currentauth", \&user_authorization_type, 1, 1, 0);
  my $reply = ReinitProcess($userinput);  
  print $client  "$reply\n";  #   Process a request by a manager to push a hosts or domain table 
   #   to us.  We pick apart the command and pass it on to the subs
   #   that already exist to do this.
   #
   # Parameters:
   #      $cmd    - the actual keyword that invoked us.
   #      $tail   - the tail of the request that invoked us.
   #      $client - File descriptor connected to the client
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit
   # Implicit Output:
   #    a reply is written to the client.
   
   sub push_file_handler {
       my ($cmd, $tail, $client) = @_;
   
       my $userinput = "$cmd:$tail";
   
       # At this time we only know that the IP of our partner is a valid manager
       # the code below is a hook to do further authentication (e.g. to resolve
       # spoofing).
   
       my $cert = &GetCertificate($userinput);
       if(&ValidManager($cert)) { 
   
    # Now presumably we have the bona fides of both the peer host and the
    # process making the request.
         
    my $reply = &PushFile($userinput);
    &Reply($client, "$reply\n", $userinput);
   
       } else {
    &Failure( $client, "refused\n", $userinput);
       } 
       return 1;
   }
   &register_handler("pushfile", \&push_file_handler, 1, 0, 1);
   
   
   
   #   Process a reinit request.  Reinit requests that either
   #   lonc or lond be reinitialized so that an updated 
   #   host.tab or domain.tab can be processed.
   #
   # Parameters:
   #      $cmd    - the actual keyword that invoked us.
   #      $tail   - the tail of the request that invoked us.
   #      $client - File descriptor connected to the client
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit
   #  Implicit output:
   #     a reply is sent to the client.
   #
   sub reinit_process_handler {
       my ($cmd, $tail, $client) = @_;
      
       my $userinput = "$cmd:$tail";
      
       my $cert = &GetCertificate($userinput);
       if(&ValidManager($cert)) {
    chomp($userinput);
    my $reply = &ReinitProcess($userinput);
    &Reply( $client,  "$reply\n", $userinput);
       } else {
    &Failure( $client, "refused\n", $userinput);
       }
       return 1;
   }
   
   &register_handler("reinit", \&reinit_process_handler, 1, 0, 1);
   
   #  Process the editing script for a table edit operation.
   #  the editing operation must be encrypted and requested by
   #  a manager host.
   #
   # Parameters:
   #      $cmd    - the actual keyword that invoked us.
   #      $tail   - the tail of the request that invoked us.
   #      $client - File descriptor connected to the client
   #  Returns:
   #      1       - Ok to continue processing.
   #      0       - Program should exit
   #  Implicit output:
   #     a reply is sent to the client.
   #
   sub edit_table_handler {
       my ($command, $tail, $client) = @_;
      
       my $userinput = "$command:$tail";
   
       my $cert = &GetCertificate($userinput);
       if(&ValidManager($cert)) {
    my($filetype, $script) = split(/:/, $tail);
    if (($filetype eq "hosts") || 
       ($filetype eq "domain")) {
       if($script ne "") {
    &Reply($client,              # BUGBUG - EditFile
         &EditFile($userinput), #   could fail.
         $userinput);
     } else {      } else {
  print $client "refused\n";   &Failure($client,"refused\n",$userinput);
     }      }
  } else {   } else {
     Reply($client, "refused\n", $userinput);      &Failure($client,"refused\n",$userinput);
  }  
 #------------------------------------------------------------------------- edit  
     } elsif ($userinput =~ /^edit/) {    # encoded and manager:  
  if(($wasenc ==1) && (isManager)) {  
     my $cert = GetCertificate($userinput);  
     if(ValidManager($cert)) {  
  my($command, $filetype, $script) = split(/:/, $userinput);  
  if (($filetype eq "hosts") || ($filetype eq "domain")) {  
     if($script ne "") {  
  Reply($client, EditFile($userinput));  
     } else {  
  Reply($client,"refused\n",$userinput);  
     }  
  } else {  
     Reply($client,"refused\n",$userinput);  
  }  
             } else {  
  Reply($client,"refused\n",$userinput);  
             }  
  } else {  
     Reply($client,"refused\n",$userinput);  
  }   }
 # ------------------------------------------------------------------------ auth      } else {
     } elsif ($userinput =~ /^auth/) { # Encoded and client only.   &Failure($client,"refused\n",$userinput);
  if (($wasenc==1) && isClient) {      }
     my ($cmd,$udom,$uname,$upass)=split(/:/,$userinput);      return 1;
     chomp($upass);  }
     $upass=unescape($upass);  register_handler("edit", \&edit_table_handler, 1, 0, 1);
     my $proname=propath($udom,$uname);  
     my $passfilename="$proname/passwd";  
     if (-e $passfilename) {  #
  my $pf = IO::File->new($passfilename);  #   Authenticate a user against the LonCAPA authentication
  my $realpasswd=<$pf>;  #   database.  Note that there are several authentication
  chomp($realpasswd);  #   possibilities:
  my ($howpwd,$contentpwd)=split(/:/,$realpasswd);  #   - unix     - The user can be authenticated against the unix
  my $pwdcorrect=0;  #                password file.
  if ($howpwd eq 'internal') {  #   - internal - The user can be authenticated against a purely 
     &Debug("Internal auth");  #                internal per user password file.
     $pwdcorrect=  #   - kerberos - The user can be authenticated against either a kerb4 or kerb5
  (crypt($upass,$contentpwd) eq $contentpwd);  #                ticket granting authority.
  } elsif ($howpwd eq 'unix') {  #   - user     - The person tailoring LonCAPA can supply a user authentication
     &Debug("Unix auth");  #                mechanism that is per system.
     if((getpwnam($uname))[1] eq "") { #no such user!  #
  $pwdcorrect = 0;  # Parameters:
     } else {  #    $cmd      - The command that got us here.
  $contentpwd=(getpwnam($uname))[1];  #    $tail     - Tail of the command (remaining parameters).
  my $pwauth_path="/usr/local/sbin/pwauth";  #    $client   - File descriptor connected to client.
  unless ($contentpwd eq 'x') {  # Returns
     $pwdcorrect=  #     0        - Requested to exit, caller should shut down.
  (crypt($upass,$contentpwd) eq   #     1        - Continue processing.
  $contentpwd);  # Implicit inputs:
  }  #    The authentication systems describe above have their own forms of implicit
   #    input into the authentication process that are described above.
  elsif (-e $pwauth_path) {  #
     open PWAUTH, "|$pwauth_path" or  sub authenticate_handler {
  die "Cannot invoke authentication";      my ($cmd, $tail, $client) = @_;
     print PWAUTH "$uname\n$upass\n";  
     close PWAUTH;      
     $pwdcorrect=!$?;      #  Regenerate the full input line 
  }      
     }      my $userinput  = $cmd.":".$tail;
  } elsif ($howpwd eq 'krb4') {      
     my $null=pack("C",0);      #  udom    - User's domain.
     unless ($upass=~/$null/) {      #  uname   - Username.
  my $krb4_error = &Authen::Krb4::get_pw_in_tkt      #  upass   - User's password.
     ($uname,"",$contentpwd,'krbtgt',      
      $contentpwd,1,$upass);      my ($udom,$uname,$upass)=split(/:/,$tail);
  if (!$krb4_error) {      &Debug(" Authenticate domain = $udom, user = $uname, password = $upass");
     $pwdcorrect = 1;      chomp($upass);
  } else {       $upass=&unescape($upass);
     $pwdcorrect=0;   
     # log error if it is not a bad password      my $pwdcorrect = &validate_user($udom, $uname, $upass);
     if ($krb4_error != 62) {      if($pwdcorrect) {
  &logthis('krb4:'.$uname.','.   &Reply( $client, "authorized\n", $userinput);
  &Authen::Krb4::get_err_txt($Authen::Krb4::error));   #
     }   #  Bad credentials: Failed to authorize
  }   #
     }      } else {
  } elsif ($howpwd eq 'krb5') {   &Failure( $client, "non_authorized\n", $userinput);
     my $null=pack("C",0);      }
     unless ($upass=~/$null/) {  
  my $krbclient=&Authen::Krb5::parse_name($uname.'@'.$contentpwd);      return 1;
  my $krbservice="krbtgt/".$contentpwd."\@".$contentpwd;  }
  my $krbserver=&Authen::Krb5::parse_name($krbservice);  
  my $credentials=&Authen::Krb5::cc_default();  register_handler("auth", \&authenticate_handler, 1, 1, 0);
  $credentials->initialize($krbclient);  
  my $krbreturn =   #
     &Authen::Krb5::get_in_tkt_with_password(  #   Change a user's password.  Note that this function is complicated by
     $krbclient,$krbserver,$upass,$credentials);  #   the fact that a user may be authenticated in more than one way:
 #  unless ($krbreturn) {  #   At present, we are not able to change the password for all types of
 #      &logthis("Krb5 Error: ".  #   authentication methods.  Only for:
 #       &Authen::Krb5::error());  #      unix    - unix password or shadow passoword style authentication.
 #  }  #      local   - Locally written authentication mechanism.
  $pwdcorrect = ($krbreturn == 1);  #   For now, kerb4 and kerb5 password changes are not supported and result
     } else { $pwdcorrect=0; }  #   in an error.
  } elsif ($howpwd eq 'localauth') {  # FUTURE WORK:
     $pwdcorrect=&localauth::localauth($uname,$upass,  #    Support kerberos passwd changes?
       $contentpwd);  # Parameters:
  }  #    $cmd      - The command that got us here.
  if ($pwdcorrect) {  #    $tail     - Tail of the command (remaining parameters).
     print $client "authorized\n";  #    $client   - File descriptor connected to client.
  } else {  # Returns
     print $client "non_authorized\n";  #     0        - Requested to exit, caller should shut down.
  }    #     1        - Continue processing.
     } else {  # Implicit inputs:
  print $client "unknown_user\n";  #    The authentication systems describe above have their own forms of implicit
     }  #    input into the authentication process that are described above.
  } else {  sub change_password_handler {
     Reply($client, "refused\n", $userinput);      my ($cmd, $tail, $client) = @_;
       
  }      my $userinput = $cmd.":".$tail;           # Reconstruct client's string.
 # ---------------------------------------------------------------------- passwd  
     } elsif ($userinput =~ /^passwd/) { # encoded and client      #
  if (($wasenc==1) && isClient) {      #  udom  - user's domain.
     my       #  uname - Username.
  ($cmd,$udom,$uname,$upass,$npass)=split(/:/,$userinput);      #  upass - Current password.
     chomp($npass);      #  npass - New password.
     $upass=&unescape($upass);     
     $npass=&unescape($npass);      my ($udom,$uname,$upass,$npass)=split(/:/,$tail);
     &Debug("Trying to change password for $uname");  
     my $proname=propath($udom,$uname);      $upass=&unescape($upass);
     my $passfilename="$proname/passwd";      $npass=&unescape($npass);
     if (-e $passfilename) {      &Debug("Trying to change password for $uname");
  my $realpasswd;  
  { my $pf = IO::File->new($passfilename);      # First require that the user can be authenticated with their
   $realpasswd=<$pf>; }      # old password:
  chomp($realpasswd);  
  my ($howpwd,$contentpwd)=split(/:/,$realpasswd);      my $validated = &validate_user($udom, $uname, $upass);
  if ($howpwd eq 'internal') {      if($validated) {
     &Debug("internal auth");   my $realpasswd  = &get_auth_type($udom, $uname); # Defined since authd.
     if (crypt($upass,$contentpwd) eq $contentpwd) {  
  my $salt=time;   my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
  $salt=substr($salt,6,2);   if ($howpwd eq 'internal') {
  my $ncpass=crypt($npass,$salt);      &Debug("internal auth");
  {      my $salt=time;
     my $pf;      $salt=substr($salt,6,2);
     if ($pf = IO::File->new(">$passfilename")) {      my $ncpass=crypt($npass,$salt);
  print $pf "internal:$ncpass\n";      if(&rewrite_password_file($udom, $uname, "internal:$ncpass")) {
  &logthis("Result of password change for $uname: pwchange_success");   &logthis("Result of password change for "
  print $client "ok\n";   ."$uname: pwchange_success");
     } else {   &Reply($client, "ok\n", $userinput);
  &logthis("Unable to open $uname passwd to change password");  
  print $client "non_authorized\n";  
     }  
  }               
   
     } else {  
  print $client "non_authorized\n";  
     }  
  } elsif ($howpwd eq 'unix') {  
     # Unix means we have to access /etc/password  
     # one way or another.  
     # First: Make sure the current password is  
     #        correct  
     &Debug("auth is unix");  
     $contentpwd=(getpwnam($uname))[1];  
     my $pwdcorrect = "0";  
     my $pwauth_path="/usr/local/sbin/pwauth";  
     unless ($contentpwd eq 'x') {  
  $pwdcorrect=  
     (crypt($upass,$contentpwd) eq $contentpwd);  
     } elsif (-e $pwauth_path) {  
  open PWAUTH, "|$pwauth_path" or  
     die "Cannot invoke authentication";  
  print PWAUTH "$uname\n$upass\n";  
  close PWAUTH;  
  &Debug("exited pwauth with $? ($uname,$upass) ");  
  $pwdcorrect=($? == 0);  
     }  
     if ($pwdcorrect) {  
  my $execdir=$perlvar{'lonDaemons'};  
  &Debug("Opening lcpasswd pipeline");  
  my $pf = IO::File->new("|$execdir/lcpasswd > $perlvar{'lonDaemons'}/logs/lcpasswd.log");  
  print $pf "$uname\n$npass\n$npass\n";  
  close $pf;  
  my $err = $?;  
  my $result = ($err>0 ? 'pwchange_failure'   
       : 'ok');  
  &logthis("Result of password change for $uname: ".  
  &lcpasswdstrerror($?));  
  print $client "$result\n";  
     } else {  
  print $client "non_authorized\n";  
     }  
  } else {  
     print $client "auth_mode_error\n";  
  }    
     } else {      } else {
  print $client "unknown_user\n";   &logthis("Unable to open $uname passwd "               
     }   ."to change password");
  } else {   &Failure( $client, "non_authorized\n",$userinput);
     Reply($client, "refused\n", $userinput);      }
        } elsif ($howpwd eq 'unix') {
  }      # Unix means we have to access /etc/password
 # -------------------------------------------------------------------- makeuser      &Debug("auth is unix");
     } elsif ($userinput =~ /^makeuser/) { # encoded and client.      my $execdir=$perlvar{'lonDaemons'};
  &Debug("Make user received");      &Debug("Opening lcpasswd pipeline");
       my $pf = IO::File->new("|$execdir/lcpasswd > "
      ."$perlvar{'lonDaemons'}"
      ."/logs/lcpasswd.log");
       print $pf "$uname\n$npass\n$npass\n";
       close $pf;
       my $err = $?;
       my $result = ($err>0 ? 'pwchange_failure' : 'ok');
       &logthis("Result of password change for $uname: ".
        &lcpasswdstrerror($?));
       &Reply($client, "$result\n", $userinput);
    } else {
       # this just means that the current password mode is not
       # one we know how to change (e.g the kerberos auth modes or
       # locally written auth handler).
       #
       &Failure( $client, "auth_mode_error\n", $userinput);
    }  
   
       } else {
    &Failure( $client, "non_authorized\n", $userinput);
       }
   
       return 1;
   }
   register_handler("passwd", \&change_password_handler, 1, 1, 0);
   
   
   #
   #   Create a new user.  User in this case means a lon-capa user.
   #   The user must either already exist in some authentication realm
   #   like kerberos or the /etc/passwd.  If not, a user completely local to
   #   this loncapa system is created.
   #
   # Parameters:
   #    $cmd      - The command that got us here.
   #    $tail     - Tail of the command (remaining parameters).
   #    $client   - File descriptor connected to client.
   # Returns
   #     0        - Requested to exit, caller should shut down.
   #     1        - Continue processing.
   # Implicit inputs:
   #    The authentication systems describe above have their own forms of implicit
   #    input into the authentication process that are described above.
   sub add_user_handler {
   
       my ($cmd, $tail, $client) = @_;
   
   
       my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
       my $userinput = $cmd.":".$tail; # Reconstruct the full request line.
   
       &Debug("cmd =".$cmd." $udom =".$udom." uname=".$uname);
   
   
       if($udom eq $currentdomainid) { # Reject new users for other domains...
   
  my $oldumask=umask(0077);   my $oldumask=umask(0077);
  if (($wasenc==1) && isClient) {   chomp($npass);
     my    $npass=&unescape($npass);
  ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput);   my $passfilename  = &password_path($udom, $uname);
     &Debug("cmd =".$cmd." $udom =".$udom.   &Debug("Password file created will be:".$passfilename);
    " uname=".$uname);   if (-e $passfilename) {
     chomp($npass);      &Failure( $client, "already_exists\n", $userinput);
     $npass=&unescape($npass);   } else {
     my $proname=propath($udom,$uname);      my @fpparts=split(/\//,$passfilename);
     my $passfilename="$proname/passwd";      my $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2];
     &Debug("Password file created will be:".      my $fperror='';
    $passfilename);      for (my $i=3;$i<= ($#fpparts-1);$i++) {
     if (-e $passfilename) {   $fpnow.='/'.$fpparts[$i]; 
  print $client "already_exists\n";   unless (-e $fpnow) {
     } elsif ($udom ne $currentdomainid) {      &logthis("mkdir $fpnow");
  print $client "not_right_domain\n";      unless (mkdir($fpnow,0777)) {
     } else {   $fperror="error: ".($!+0)." mkdir failed while attempting "
  my @fpparts=split(/\//,$proname);      ."makeuser";
  my $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2];  
  my $fperror='';  
  for (my $i=3;$i<=$#fpparts;$i++) {  
     $fpnow.='/'.$fpparts[$i];   
     unless (-e $fpnow) {  
  unless (mkdir($fpnow,0777)) {  
     $fperror="error: ".($!+0)  
  ." mkdir failed while attempting "  
  ."makeuser";  
  }  
     }      }
  }   }
  unless ($fperror) {  
     my $result=&make_passwd_file($uname, $umode,$npass,  
  $passfilename);  
     print $client $result;  
  } else {  
     print $client "$fperror\n";  
  }  
     }      }
  } else {      unless ($fperror) {
     Reply($client, "refused\n", $userinput);   my $result=&make_passwd_file($uname, $umode,$npass, $passfilename);
        &Reply($client, $result, $userinput);     #BUGBUG - could be fail
  }  
  umask($oldumask);  
 # -------------------------------------------------------------- changeuserauth  
     } elsif ($userinput =~ /^changeuserauth/) { # encoded & client  
  &Debug("Changing authorization");  
  if (($wasenc==1) && isClient) {  
     my   
  ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput);  
     chomp($npass);  
     &Debug("cmd = ".$cmd." domain= ".$udom.  
    "uname =".$uname." umode= ".$umode);  
     $npass=&unescape($npass);  
     my $proname=&propath($udom,$uname);  
     my $passfilename="$proname/passwd";  
     if ($udom ne $currentdomainid) {  
  print $client "not_right_domain\n";  
     } else {      } else {
  my $result=&make_passwd_file($uname, $umode,$npass,   &Failure($client, "$fperror\n", $userinput);
      $passfilename);  
  print $client $result;  
     }      }
  } else {  
     Reply($client, "refused\n", $userinput);  
       
  }   }
 # ------------------------------------------------------------------------ home   umask($oldumask);
     } elsif ($userinput =~ /^home/) { # client clear or encoded      }  else {
  if(isClient) {   &Failure($client, "not_right_domain\n",
     my ($cmd,$udom,$uname)=split(/:/,$userinput);   $userinput); # Even if we are multihomed.
     chomp($uname);      
     my $proname=propath($udom,$uname);      }
     if (-e $proname) {      return 1;
  print $client "found\n";  
     } else {  }
  print $client "not_found\n";  &register_handler("makeuser", \&add_user_handler, 1, 1, 0);
     }  
  } else {  #
     Reply($client, "refused\n", $userinput);  #   Change the authentication method of a user.  Note that this may
       #   also implicitly change the user's password if, for example, the user is
   #   joining an existing authentication realm.  Known authentication realms at
   #   this time are:
   #    internal   - Purely internal password file (only loncapa knows this user)
   #    local      - Institutionally written authentication module.
   #    unix       - Unix user (/etc/passwd with or without /etc/shadow).
   #    kerb4      - kerberos version 4
   #    kerb5      - kerberos version 5
   #
   # Parameters:
   #    $cmd      - The command that got us here.
   #    $tail     - Tail of the command (remaining parameters).
   #    $client   - File descriptor connected to client.
   # Returns
   #     0        - Requested to exit, caller should shut down.
   #     1        - Continue processing.
   # Implicit inputs:
   #    The authentication systems describe above have their own forms of implicit
   #    input into the authentication process that are described above.
   #
   sub change_authentication_handler {
   
       my ($cmd, $tail, $client) = @_;
      
       my $userinput  = "$cmd:$tail";              # Reconstruct user input.
   
       my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
       &Debug("cmd = ".$cmd." domain= ".$udom."uname =".$uname." umode= ".$umode);
       if ($udom ne $currentdomainid) {
    &Failure( $client, "not_right_domain\n", $client);
       } else {
   
    chomp($npass);
   
    $npass=&unescape($npass);
    my $passfilename = &password_path($udom, $uname);
    if ($passfilename) { # Not allowed to create a new user!!
       my $result=&make_passwd_file($uname, $umode,$npass,$passfilename);
       &Reply($client, $result, $userinput);
    } else {       
       &Failure($client, "non_authorized", $userinput); # Fail the user now.
  }   }
 # ---------------------------------------------------------------------- update      }
     } elsif ($userinput =~ /^update/) { # client clear or encoded.      return 1;
  if(isClient) {  }
     my ($cmd,$fname)=split(/:/,$userinput);  &register_handler("changeuserauth", \&change_authentication_handler, 1,1, 0);
     my $ownership=ishome($fname);  
     if ($ownership eq 'not_owner') {  #
  if (-e $fname) {  #   Determines if this is the home server for a user.  The home server
     my ($dev,$ino,$mode,$nlink,  #   for a user will have his/her lon-capa passwd file.  Therefore all we need
  $uid,$gid,$rdev,$size,  #   to do is determine if this file exists.
  $atime,$mtime,$ctime,  #
  $blksize,$blocks)=stat($fname);  # Parameters:
     my $now=time;  #    $cmd      - The command that got us here.
     my $since=$now-$atime;  #    $tail     - Tail of the command (remaining parameters).
     if ($since>$perlvar{'lonExpire'}) {  #    $client   - File descriptor connected to client.
  my $reply=  # Returns
     &reply("unsub:$fname","$clientname");  #     0        - Requested to exit, caller should shut down.
     unlink("$fname");  #     1        - Continue processing.
     } else {  # Implicit inputs:
  my $transname="$fname.in.transfer";  #    The authentication systems describe above have their own forms of implicit
  my $remoteurl=  #    input into the authentication process that are described above.
     &reply("sub:$fname","$clientname");  #
  my $response;  sub is_home_handler {
  {      my ($cmd, $tail, $client) = @_;
     my $ua=new LWP::UserAgent;     
     my $request=new HTTP::Request('GET',"$remoteurl");      my $userinput  = "$cmd:$tail";
     $response=$ua->request($request,$transname);     
  }      my ($udom,$uname)=split(/:/,$tail);
  if ($response->is_error()) {      chomp($uname);
     unlink($transname);      my $passfile = &password_filename($udom, $uname);
     my $message=$response->status_line;      if($passfile) {
     &logthis(   &Reply( $client, "found\n", $userinput);
      "LWP GET: $message for $fname ($remoteurl)");      } else {
  } else {   &Failure($client, "not_found\n", $userinput);
     if ($remoteurl!~/\.meta$/) {      }
  my $ua=new LWP::UserAgent;      return 1;
  my $mrequest=  }
     new HTTP::Request('GET',$remoteurl.'.meta');  &register_handler("home", \&is_home_handler, 0,1,0);
  my $mresponse=  
     $ua->request($mrequest,$fname.'.meta');  #
  if ($mresponse->is_error()) {  #   Process an update request for a resource?? I think what's going on here is
     unlink($fname.'.meta');  #   that a resource has been modified that we hold a subscription to.
  }  #   If the resource is not local, then we must update, or at least invalidate our
     }  #   cached copy of the resource. 
     rename($transname,$fname);  #   FUTURE WORK:
  }  #      I need to look at this logic carefully.  My druthers would be to follow
     }  #      typical caching logic, and simple invalidate the cache, drop any subscription
     print $client "ok\n";  #      an let the next fetch start the ball rolling again... however that may
  } else {  #      actually be more difficult than it looks given the complex web of
     print $client "not_found\n";  #      proxy servers.
  }  # Parameters:
   #    $cmd      - The command that got us here.
   #    $tail     - Tail of the command (remaining parameters).
   #    $client   - File descriptor connected to client.
   # Returns
   #     0        - Requested to exit, caller should shut down.
   #     1        - Continue processing.
   # Implicit inputs:
   #    The authentication systems describe above have their own forms of implicit
   #    input into the authentication process that are described above.
   #
   sub update_resource_handler {
   
       my ($cmd, $tail, $client) = @_;
      
       my $userinput = "$cmd:$tail";
      
       my $fname= $tail; # This allows interactive testing
   
   
       my $ownership=ishome($fname);
       if ($ownership eq 'not_owner') {
    if (-e $fname) {
       my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
    $atime,$mtime,$ctime,$blksize,$blocks)=stat($fname);
       my $now=time;
       my $since=$now-$atime;
       if ($since>$perlvar{'lonExpire'}) {
    my $reply=&reply("unsub:$fname","$clientname");
    unlink("$fname");
     } else {      } else {
  print $client "rejected\n";   my $transname="$fname.in.transfer";
     }   my $remoteurl=&reply("sub:$fname","$clientname");
  } else {  
     Reply($client, "refused\n", $userinput);  
       
  }  
 # -------------------------------------- fetch a user file from a remote server  
     } elsif ($userinput =~ /^fetchuserfile/) { # Client clear or enc.  
  if(isClient) {  
     my ($cmd,$fname)=split(/:/,$userinput);  
     my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);  
     my $udir=propath($udom,$uname).'/userfiles';  
     unless (-e $udir) { mkdir($udir,0770); }  
     if (-e $udir) {  
  $ufile=~s/^[\.\~]+//;  
  my $path = $udir;  
  if ($ufile =~m|(.+)/([^/]+)$|) {  
     my @parts=split('/',$1);  
     foreach my $part (@parts) {  
  $path .= '/'.$part;  
  if ((-e $path)!=1) {  
     mkdir($path,0770);  
  }  
     }  
  }  
  my $destname=$udir.'/'.$ufile;  
  my $transname=$udir.'/'.$ufile.'.in.transit';  
  my $remoteurl='http://'.$clientip.'/userfiles/'.$fname;  
  my $response;   my $response;
    alarm(120);
  {   {
     my $ua=new LWP::UserAgent;      my $ua=new LWP::UserAgent;
     my $request=new HTTP::Request('GET',"$remoteurl");      my $request=new HTTP::Request('GET',"$remoteurl");
     $response=$ua->request($request,$transname);      $response=$ua->request($request,$transname);
  }   }
    alarm(0);
  if ($response->is_error()) {   if ($response->is_error()) {
     unlink($transname);      unlink($transname);
     my $message=$response->status_line;      my $message=$response->status_line;
     &logthis("LWP GET: $message for $fname ($remoteurl)");      &logthis("LWP GET: $message for $fname ($remoteurl)");
     print $client "failed\n";  
  } else {   } else {
     if (!rename($transname,$destname)) {      if ($remoteurl!~/\.meta$/) {
  &logthis("Unable to move $transname to $destname");   alarm(120);
  unlink($transname);   {
  print $client "failed\n";      my $ua=new LWP::UserAgent;
     } else {      my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta');
  print $client "ok\n";      my $mresponse=$ua->request($mrequest,$fname.'.meta');
       if ($mresponse->is_error()) {
    unlink($fname.'.meta');
       }
    }
    alarm(0);
     }      }
       rename($transname,$fname);
  }   }
     } else {  
  print $client "not_home\n";  
     }      }
       &Reply( $client, "ok\n", $userinput);
  } else {   } else {
     Reply($client, "refused\n", $userinput);      &Failure($client, "not_found\n", $userinput);
  }   }
 # --------------------------------------------------------- remove a user file       } else {
     } elsif ($userinput =~ /^removeuserfile/) { # Client clear or enc.   &Failure($client, "rejected\n", $userinput);
  if(isClient) {      }
     my ($cmd,$fname)=split(/:/,$userinput);      return 1;
     my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);  }
     &logthis("$udom - $uname - $ufile");  &register_handler("update", \&update_resource_handler, 0 ,1, 0);
     if ($ufile =~m|/\.\./|) {  
  # any files paths with /../ in them refuse   #
  # to deal with  #   Fetch a user file from a remote server to the user's home directory
  print $client "refused\n";  #   userfiles subdir.
   # Parameters:
   #    $cmd      - The command that got us here.
   #    $tail     - Tail of the command (remaining parameters).
   #    $client   - File descriptor connected to client.
   # Returns
   #     0        - Requested to exit, caller should shut down.
   #     1        - Continue processing.
   #
   sub fetch_user_file_handler {
   
       my ($cmd, $tail, $client) = @_;
   
       my $userinput = "$cmd:$tail";
       my $fname           = $tail;
       my ($udom,$uname,$ufile)=split(/\//,$fname);
       my $udir=&propath($udom,$uname).'/userfiles';
       unless (-e $udir) {
    mkdir($udir,0770); 
       }
       if (-e $udir) {
    $ufile=~s/^[\.\~]+//;
    $ufile=~s/\///g;
    my $destname=$udir.'/'.$ufile;
    my $transname=$udir.'/'.$ufile.'.in.transit';
    my $remoteurl='http://'.$clientip.'/userfiles/'.$fname;
    my $response;
    alarm(120);
    {
       my $ua=new LWP::UserAgent;
       my $request=new HTTP::Request('GET',"$remoteurl");
       $response=$ua->request($request,$transname);
    }
    alarm(0);
    if ($response->is_error()) {
       unlink($transname);
       my $message=$response->status_line;
       &logthis("LWP GET: $message for $fname ($remoteurl)");
       &Failure($client, "failed\n", $userinput);
    } else {
       if (!rename($transname,$destname)) {
    &logthis("Unable to move $transname to $destname");
    unlink($transname);
    &Failure($client, "failed\n", $userinput);
     } else {      } else {
  my $udir=propath($udom,$uname);   &Reply($client, "ok\n", $userinput);
  if (-e $udir) {      }
     my $file=$udir.'/userfiles/'.$ufile;   }   
     if (-e $file) {      } else {
  unlink($file);   &Failure($client, "not_home\n", $userinput);
  if (-e $file) {      }
     print $client "failed\n";      return 1;
  } else {  }
     print $client "ok\n";  &register_handler("fetchuserfile", \&fetch_user_file_handler, 0, 1, 0);
  }  
     } else {  #
  print $client "not_found\n";  #   Remove a file from a user's home directory userfiles subdirectory.
     }  # Parameters:
   #    cmd   - the Lond request keyword that got us here.
   #    tail  - the part of the command past the keyword.
   #    client- File descriptor connected with the client.
   #
   # Returns:
   #    1    - Continue processing.
   
   sub remove_user_file_handler {
       my ($cmd, $tail, $client) = @_;
   
       my ($fname) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent.
   
       my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
       if ($ufile =~m|/\.\./|) {
    # any files paths with /../ in them refuse 
    # to deal with
    &Failure($client, "refused\n", "$cmd:$tail");
       } else {
    my $udir = &propath($udom,$uname);
    if (-e $udir) {
       my $file=$udir.'/userfiles/'.$ufile;
       if (-e $file) {
    unlink($file);
    if (-e $file) {
       &Failure($client, "failed\n", "$cmd:$tail");
  } else {   } else {
     print $client "not_home\n";      &Reply($client, "ok\n", "$cmd:$tail");
  }   }
     }  
  } else {  
     Reply($client, "refused\n", $userinput);  
  }  
 # ------------------------------------------ authenticate access to a user file  
     } elsif ($userinput =~ /^tokenauthuserfile/) { # Client only  
  if(isClient) {  
     my ($cmd,$fname,$session)=split(/:/,$userinput);  
     chomp($session);  
     my $reply='non_auth';  
     if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'.  
      $session.'.id')) {  
  while (my $line=<ENVIN>) {  
     if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply='ok'; }  
     }  
  close(ENVIN);  
  print $client $reply."\n";  
     } else {      } else {
  print $client "invalid_token\n";   &Failure($client, "not_found\n", "$cmd:$tail");
     }      }
  } else {   } else {
     Reply($client, "refused\n", $userinput);      &Failure($client, "not_home\n", "$cmd:$tail");
       
  }   }
 # ----------------------------------------------------------------- unsubscribe      }
     } elsif ($userinput =~ /^unsub/) {      return 1;
  if(isClient) {  }
     my ($cmd,$fname)=split(/:/,$userinput);  &register_handler("removeuserfile", \&remove_user_file_handler, 0,1,0);
     if (-e $fname) {  
  print $client &unsub($fname,$clientip);  
     } else {  #
  print $client "not_found\n";  #  Authenticate access to a user file by checking the user's 
     }  #  session token(?)
   #
   # Parameters:
   #   cmd      - The request keyword that dispatched to tus.
   #   tail     - The tail of the request (colon separated parameters).
   #   client   - Filehandle open on the client.
   # Return:
   #    1.
   
   sub token_auth_user_file_handler {
       my ($cmd, $tail, $client) = @_;
   
       my ($fname, $session) = split(/:/, $tail);
       
       chomp($session);
       my $reply='non_auth';
       if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'.
        $session.'.id')) {
    while (my $line=<ENVIN>) {
       if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply='ok'; }
    }
    close(ENVIN);
    &Reply($client, $reply);
       } else {
    &Failure($client, "invalid_token\n", "$cmd:$tail");
       }
       return 1;
   
   }
   
   &register_handler("tokenauthuserfile", \&token_auth_user_file_handler, 0,1,0);
   
   
   #
   #   Unsubscribe from a resource.
   #
   # Parameters:
   #    $cmd      - The command that got us here.
   #    $tail     - Tail of the command (remaining parameters).
   #    $client   - File descriptor connected to client.
   # Returns
   #     0        - Requested to exit, caller should shut down.
   #     1        - Continue processing.
   #
   sub unsubscribe_handler {
       my ($cmd, $tail, $client) = @_;
   
       my $userinput= "$cmd:$tail";
       
       my ($fname) = split(/:/,$tail); # Split in case there's extrs.
   
       &Debug("Unsubscribing $fname");
       if (-e $fname) {
    &Debug("Exists");
    &Reply($client, &unsub($fname,$clientip), $userinput);
       } else {
    &Failure($client, "not_found\n", $userinput);
       }
       return 1;
   }
   &register_handler("unsub", \&unsubscribe_handler, 0, 1, 0);
   
   #---------------------------------------------------------------
   #
   #   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 get_request {
       my $input = <$client>;
       chomp($input);
   
       Debug("get_request: Request = $input\n");
   
       &status('Processing '.$clientname.':'.$input);
   
       return $input;
   }
   #---------------------------------------------------------------
   #
   #  Process a request.  This sub should shrink as each action
   #  gets farmed out into a separat sub that is registered 
   #  with the dispatch hash.  
   #
   # Parameters:
   #    user_input   - The request received from the client (lonc).
   # Returns:
   #    true to keep processing, false if caller should exit.
   #
   sub process_request {
       my ($userinput) = @_;      # Easier for now to break style than to
                                   # fix all the userinput -> user_input.
       my $wasenc    = 0; # True if request was encrypted.
   # ------------------------------------------------------------ See if encrypted
       if ($userinput =~ /^enc/) {
    $userinput = decipher($userinput);
    $wasenc=1;
    if(!$userinput) { # Cipher not defined.
       &Failure($client, "error: Encrypted data without negotated key");
       return 0;
    }
       }
       Debug("process_request: $userinput\n");
       
       #  
       #   The 'correct way' to add a command to lond is now to
       #   write a sub to execute it and Add it to the command dispatch
       #   hash via a call to register_handler..  The comments to that
       #   sub should give you enough to go on to show how to do this
       #   along with the examples that are building up as this code
       #   is getting refactored.   Until all branches of the
       #   if/elseif monster below have been factored out into
       #   separate procesor subs, if the dispatch hash is missing
       #   the command keyword, we will fall through to the remainder
       #   of the if/else chain below in order to keep this thing in 
       #   working order throughout the transmogrification.
   
       my ($command, $tail) = split(/:/, $userinput, 2);
       chomp($command);
       chomp($tail);
       $tail =~ s/(\r)//; # This helps people debugging with e.g. telnet.
       $command =~ s/(\r)//; # And this too for parameterless commands.
       if(!$tail) {
    $tail =""; # defined but blank.
       }
   
       &Debug("Command received: $command, encoded = $wasenc");
   
       if(defined $Dispatcher{$command}) {
   
    my $dispatch_info = $Dispatcher{$command};
    my $handler       = $$dispatch_info[0];
    my $need_encode   = $$dispatch_info[1];
    my $client_types  = $$dispatch_info[2];
    Debug("Matched dispatch hash: mustencode: $need_encode "
         ."ClientType $client_types");
         
    #  Validate the request:
         
    my $ok = 1;
    my $requesterprivs = 0;
    if(&isClient()) {
       $requesterprivs |= $CLIENT_OK;
    }
    if(&isManager()) {
       $requesterprivs |= $MANAGER_OK;
    }
    if($need_encode && (!$wasenc)) {
       Debug("Must encode but wasn't: $need_encode $wasenc");
       $ok = 0;
    }
    if(($client_types & $requesterprivs) == 0) {
       Debug("Client not privileged to do this operation");
       $ok = 0;
    }
   
    if($ok) {
       Debug("Dispatching to handler $command $tail");
       my $keep_going = &$handler($command, $tail, $client);
       return $keep_going;
  } else {   } else {
     Reply($client, "refused\n", $userinput);      Debug("Refusing to dispatch because client did not match requirements");
           Failure($client, "refused\n", $userinput);
       return 1;
  }   }
   
       }    
   
   #------------------- Commands not yet in spearate handlers. --------------
   
   
   
   
 # ------------------------------------------------------------------- subscribe  # ------------------------------------------------------------------- subscribe
     } elsif ($userinput =~ /^sub/) {     if ($userinput =~ /^sub/) {
  if(isClient) {   if(isClient) {
     print $client &subscribe($userinput,$clientip);      print $client &subscribe($userinput,$clientip);
  } else {   } else {
Line 1782  sub process_request { Line 2176  sub process_request {
     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,
    &GetAuthType( $udom,      &get_auth_type( $udom, 
  $uname));   $uname));
  $hash{$key}=$value;   $hash{$key}=$value;
     }      }
Line 2239  sub process_request { Line 2633  sub process_request {
  print $store2 "done\n";   print $store2 "done\n";
  close $store2;   close $store2;
  print $client "ok\n";   print $client "ok\n";
     }      } else {
     else {  
  print $client "error: ".($!+0)   print $client "error: ".($!+0)
     ." IO::File->new Failed ".      ." IO::File->new Failed ".
     "while attempting queryreply\n";      "while attempting queryreply\n";
Line 2754  sub register_handler { Line 3147  sub register_handler {
         
     $Dispatcher{$request_name} = \@entry;      $Dispatcher{$request_name} = \@entry;
         
      
 }  }
   
   
Line 2801  sub catchexception { Line 3193  sub catchexception {
     $server->close();      $server->close();
     die($error);      die($error);
 }  }
   
 sub timeout {  sub timeout {
     &status("Handling Timeout");      &status("Handling Timeout");
     &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>");      &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>");
Line 2809  sub timeout { Line 3200  sub timeout {
 }  }
 # -------------------------------- Set signal handlers to record abnormal exits  # -------------------------------- Set signal handlers to record abnormal exits
   
   
 $SIG{'QUIT'}=\&catchexception;  $SIG{'QUIT'}=\&catchexception;
 $SIG{__DIE__}=\&catchexception;  $SIG{__DIE__}=\&catchexception;
   
Line 3009  sub checkchildren { Line 3401  sub checkchildren {
     &logthis('Going to check on the children');      &logthis('Going to check on the children');
     my $docdir=$perlvar{'lonDocRoot'};      my $docdir=$perlvar{'lonDocRoot'};
     foreach (sort keys %children) {      foreach (sort keys %children) {
  sleep 1;   #sleep 1;
         unless (kill 'USR1' => $_) {          unless (kill 'USR1' => $_) {
     &logthis ('Child '.$_.' is dead');      &logthis ('Child '.$_.' is dead');
             &logstatus($$.' is dead');              &logstatus($$.' is dead');
       delete($children{$_});
         }           } 
     }      }
     sleep 5;      sleep 5;
Line 3031  sub checkchildren { Line 3424  sub checkchildren {
     #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;      #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;
     #$execdir=$perlvar{'lonDaemons'};      #$execdir=$perlvar{'lonDaemons'};
     #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;      #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;
       delete($children{$_});
     alarm(0);      alarm(0);
   }    }
         }          }
Line 3038  sub checkchildren { Line 3432  sub checkchildren {
     $SIG{ALRM} = 'DEFAULT';      $SIG{ALRM} = 'DEFAULT';
     $SIG{__DIE__} = \&catchexception;      $SIG{__DIE__} = \&catchexception;
     &status("Finished checking children");      &status("Finished checking children");
       &logthis('Finished Checking children');
 }  }
   
 # --------------------------------------------------------------------- Logging  # --------------------------------------------------------------------- Logging
Line 3069  sub Debug { Line 3464  sub Debug {
 #     request - Original request from client.  #     request - Original request from client.
 #  #
 sub Reply {  sub Reply {
     alarm(120);  
     my $fd      = shift;  
     my $reply   = shift;  
     my $request = shift;  
   
     my ($fd, $reply, $request) = @_;      my ($fd, $reply, $request) = @_;
     print $fd $reply;      print $fd $reply;
     Debug("Request was $request  Reply was $reply");      Debug("Request was $request  Reply was $reply");
   
     $Transactions++;      $Transactions++;
     alarm(0);  
   
   
 }  }
Line 3114  sub logstatus { Line 3503  sub logstatus {
     &status("Doing logging");      &status("Doing logging");
     my $docdir=$perlvar{'lonDocRoot'};      my $docdir=$perlvar{'lonDocRoot'};
     {      {
     my $fh=IO::File->new(">>$docdir/lon-status/londstatus.txt");  
     print $fh $$."\t".$clientname."\t".$currenthostid."\t"  
  .$status."\t".$lastlog."\t $keymode\n";  
     $fh->close();  
     }  
     &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."\n$keymode";          print $fh $status."\n".$lastlog."\n".time."\n$keymode";
         $fh->close();          $fh->close();
     }      }
       &status("Finished $$.txt");
       {
    open(LOG,">>$docdir/lon-status/londstatus.txt");
    flock(LOG,LOCK_EX);
    print LOG $$."\t".$clientname."\t".$currenthostid."\t"
       .$status."\t".$lastlog."\t $keymode\n";
    flock(DB,LOCK_UN);
    close(LOG);
       }
     &status("Finished logging");      &status("Finished logging");
 }  }
   
Line 3439  sub make_new_child { Line 3830  sub make_new_child {
  $inittype = ""; # This forces insecure attempt.   $inittype = ""; # This forces insecure attempt.
  &logthis("<font color=\"blue\"> Certificates not "   &logthis("<font color=\"blue\"> Certificates not "
  ."installed -- trying insecure auth</font>");   ."installed -- trying insecure auth</font>");
     }      } else { # SSL certificates are in place so
     else { # SSL certificates are in place so  
     } # Leave the inittype alone.      } # Leave the inittype alone.
  }   }
   
Line 3576  sub ManagePermissions Line 3966  sub ManagePermissions
  system("$execdir/lchtmldir $userhome $user $authtype");   system("$execdir/lchtmldir $userhome $user $authtype");
     }      }
 }  }
   
   
   #
   #  Return the full path of a user password file, whether it exists or not.
   # Parameters:
   #   domain     - Domain in which the password file lives.
   #   user       - name of the user.
   # Returns:
   #    Full passwd path:
   #
   sub password_path {
       my ($domain, $user) = @_;
   
   
       my $path   = &propath($domain, $user);
       $path  .= "/passwd";
   
       return $path;
   }
   
   #   Password Filename
   #   Returns the path to a passwd file given domain and user... only if
   #  it exists.
   # Parameters:
   #   domain    - Domain in which to search.
   #   user      - username.
   # Returns:
   #   - If the password file exists returns its path.
   #   - If the password file does not exist, returns undefined.
   #
   sub password_filename {
       my ($domain, $user) = @_;
   
       Debug ("PasswordFilename called: dom = $domain user = $user");
   
       my $path  = &password_path($domain, $user);
       Debug("PasswordFilename got path: $path");
       if(-e $path) {
    return $path;
       } else {
    return undef;
       }
   }
   
   #
   #   Rewrite the contents of the user's passwd file.
   #  Parameters:
   #    domain    - domain of the user.
   #    name      - User's name.
   #    contents  - New contents of the file.
   # Returns:
   #   0    - Failed.
   #   1    - Success.
   #
   sub rewrite_password_file {
       my ($domain, $user, $contents) = @_;
   
       my $file = &password_filename($domain, $user);
       if (defined $file) {
    my $pf = IO::File->new(">$file");
    if($pf) {
       print $pf "$contents\n";
       return 1;
    } else {
       return 0;
    }
       } else {
    return 0;
       }
   
   }
   
 #  #
 #   GetAuthType - Determines the authorization type of a user in a domain.  #   get_auth_type - Determines the authorization type of a user in a domain.
   
 #     Returns the authorization type or nouser if there is no such user.  #     Returns the authorization type or nouser if there is no such user.
 #  #
 sub GetAuthType   sub get_auth_type 
 {  {
   
     my ($domain, $user)  = @_;      my ($domain, $user)  = @_;
   
     Debug("GetAuthType( $domain, $user ) \n");      Debug("get_auth_type( $domain, $user ) \n");
     my $proname    = &propath($domain, $user);       my $proname    = &propath($domain, $user); 
     my $passwdfile = "$proname/passwd";      my $passwdfile = "$proname/passwd";
     if( -e $passwdfile ) {      if( -e $passwdfile ) {
Line 3602  sub GetAuthType Line 4064  sub GetAuthType
  }   }
   
  return "$authtype:$availinfo";   return "$authtype:$availinfo";
     }      } else {
     else {  
  Debug("Returning nouser");   Debug("Returning nouser");
  return "nouser";   return "nouser";
     }      }
 }  }
   
   #
   #  Validate a user given their domain, name and password.  This utility
   #  function is used by both  AuthenticateHandler and ChangePasswordHandler
   #  to validate the login credentials of a user.
   # Parameters:
   #    $domain    - The domain being logged into (this is required due to
   #                 the capability for multihomed systems.
   #    $user      - The name of the user being validated.
   #    $password  - The user's propoposed password.
   #
   # Returns:
   #     1        - The domain,user,pasword triplet corresponds to a valid
   #                user.
   #     0        - The domain,user,password triplet is not a valid user.
   #
   sub validate_user {
       my ($domain, $user, $password) = @_;
   
   
       # Why negative ~pi you may well ask?  Well this function is about
       # authentication, and therefore very important to get right.
       # I've initialized the flag that determines whether or not I've 
       # validated correctly to a value it's not supposed to get.
       # At the end of this function. I'll ensure that it's not still that
       # value so we don't just wind up returning some accidental value
       # as a result of executing an unforseen code path that
       # did not set $validated.
   
       my $validated = -3.14159;
   
       #  How we authenticate is determined by the type of authentication
       #  the user has been assigned.  If the authentication type is
       #  "nouser", the user does not exist so we will return 0.
   
       my $contents = &get_auth_type($domain, $user);
       my ($howpwd, $contentpwd) = split(/:/, $contents);
   
       my $null = pack("C",0); # Used by kerberos auth types.
   
       if ($howpwd ne 'nouser') {
   
    if($howpwd eq "internal") { # Encrypted is in local password file.
       $validated = (crypt($password, $contentpwd) eq $contentpwd);
    }
    elsif ($howpwd eq "unix") { # User is a normal unix user.
       $contentpwd = (getpwnam($user))[1];
       if($contentpwd) {
    if($contentpwd eq 'x') { # Shadow password file...
       my $pwauth_path = "/usr/local/sbin/pwauth";
       open PWAUTH,  "|$pwauth_path" or
    die "Cannot invoke authentication";
       print PWAUTH "$user\n$password\n";
       close PWAUTH;
       $validated = ! $?;
   
    } else {         # Passwords in /etc/passwd. 
       $validated = (crypt($password,
    $contentpwd) eq $contentpwd);
    }
       } else {
    $validated = 0;
       }
    }
    elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain.
       if(! ($password =~ /$null/) ) {
    my $k4error = &Authen::Krb4::get_pw_in_tkt($user,
      "",
      $contentpwd,,
      'krbtgt',
      $contentpwd,
      1,
      $password);
    if(!$k4error) {
       $validated = 1;
    } else {
       $validated = 0;
       &logthis('krb4: '.$user.', '.$contentpwd.', '.
        &Authen::Krb4::get_err_txt($Authen::Krb4::error));
    }
       } else {
    $validated = 0; # Password has a match with null.
       }
    } elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain.
       if(!($password =~ /$null/)) { # Null password not allowed.
    my $krbclient = &Authen::Krb5::parse_name($user.'@'
     .$contentpwd);
    my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd;
    my $krbserver  = &Authen::Krb5::parse_name($krbservice);
    my $credentials= &Authen::Krb5::cc_default();
    $credentials->initialize($krbclient);
    my $krbreturn  = &Authen::KRb5::get_in_tkt_with_password($krbclient,
    $krbserver,
    $password,
    $credentials);
    $validated = ($krbreturn == 1);
       } else {
    $validated = 0;
       }
    } elsif ($howpwd eq "localauth") { 
       #  Authenticate via installation specific authentcation method:
       $validated = &localauth::localauth($user, 
          $password, 
          $contentpwd);
    } else { # Unrecognized auth is also bad.
       $validated = 0;
    }
       } else {
    $validated = 0;
       }
       #
       #  $validated has the correct stat of the authentication:
       #
   
       unless ($validated != -3.14159) {
    die "ValidateUser - failed to set the value of validated";
       }
       return $validated;
   }
   
   
 sub addline {  sub addline {
     my ($fname,$hostid,$ip,$newline)=@_;      my ($fname,$hostid,$ip,$newline)=@_;
     my $contents;      my $contents;
Line 3854  sub make_passwd_file { Line 4435  sub make_passwd_file {
  return "no_priv_account_error\n";   return "no_priv_account_error\n";
     }      }
   
     my $execpath="$perlvar{'lonDaemons'}/"."lcuseradd";      my $execpath       ="$perlvar{'lonDaemons'}/"."lcuseradd";
   
       my $lc_error_file  = $execdir."/tmp/lcuseradd".$$.".status";
     {      {
  &Debug("Executing external: ".$execpath);   &Debug("Executing external: ".$execpath);
  &Debug("user  = ".$uname.", Password =". $npass);   &Debug("user  = ".$uname.", Password =". $npass);
Line 3862  sub make_passwd_file { Line 4445  sub make_passwd_file {
  print $se "$uname\n";   print $se "$uname\n";
  print $se "$npass\n";   print $se "$npass\n";
  print $se "$npass\n";   print $se "$npass\n";
    print $se "$lc_error_file\n"; # Status -> unique file.
     }      }
     my $useraddok = $?;      my $error = IO::File->new("< $lc_error_file");
       my $useraddok = <$error>;
       $error->close;
       unlink($lc_error_file);
   
       chomp $useraddok;
   
     if($useraddok > 0) {      if($useraddok > 0) {
  &logthis("Failed lcuseradd: ".&lcuseraddstrerror($useraddok));   my $error_text = &lcuseraddstrerror($useraddok);
    &logthis("Failed lcuseradd: $error_text");
    $result = "lcuseradd_failed:$error_text\n";
       }  else {
    my $pf = IO::File->new(">$passfilename");
    print $pf "unix:\n";
     }      }
     my $pf = IO::File->new(">$passfilename");  
     print $pf "unix:\n";  
  }   }
     } elsif ($umode eq 'none') {      } elsif ($umode eq 'none') {
  {   {
     my $pf = IO::File->new(">$passfilename");      my $pf = IO::File->new("> $passfilename");
     print $pf "none:\n";      print $pf "none:\n";
  }   }
     } else {      } else {

Removed from v.1.212  
changed lines
  Added in v.1.229


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