--- loncom/loncnew 2011/01/20 11:19:37 1.94 +++ loncom/loncnew 2018/12/10 17:34:22 1.107 @@ -2,7 +2,7 @@ # The LearningOnline Network with CAPA # lonc maintains the connections to remote computers # -# $Id: loncnew,v 1.94 2011/01/20 11:19:37 foxr Exp $ +# $Id: loncnew,v 1.107 2018/12/10 17:34:22 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,7 +26,7 @@ # http://www.lon-capa.org/ # # -# new lonc handles n request out bver m connections to londs. +# new lonc handles n request out over m connections to londs. # This module is based on the Event class. # Development iterations: # - Setup basic event loop. (done) @@ -74,6 +74,7 @@ my %perlvar = %{$perlvarref}; my %ChildPid; # by pid -> host. my %ChildHost; # by host. +my %ChildKeyMode; # by pid -> keymode my %listening_to; # Socket->host table for who the parent # is listening to. my %parent_dispatchers; # host-> listener watcher events. @@ -93,8 +94,9 @@ my $executable = $0; # Get the full # # The variables below are only used by the child processes. # -my $RemoteHost; # Name of host child is talking to. -my $RemoteHostId; # default lonid of host child is talking to. +my $RemoteHost; # Hostname of host child is talking to. +my $RemoteHostId; # lonid of host child is talking to. +my $RemoteDefHostId; # default lonhostID of host child is talking to. my @all_host_ids; my $UnixSocketDir= $perlvar{'lonSockDir'}; my $IdleConnections = Stack->new(); # Set of idle connections @@ -349,7 +351,7 @@ sub child_exit { =head2 Tick -Invoked each timer tick. +Invoked each timer tick. =cut @@ -488,8 +490,8 @@ sub ServerToIdle { Event callback for when a client socket is writable. -This callback is established when a transaction reponse is -avaiable from lond. The response is forwarded to the unix socket +This callback is established when a transaction response is +available from lond. The response is forwarded to the unix socket as it becomes writable in this sub. Parameters: @@ -559,7 +561,8 @@ sub ClientWritable { if($errno == POSIX::EWOULDBLOCK || $errno == POSIX::EAGAIN || $errno == POSIX::EINTR) { - # No action taken? + # No action taken...the socket will be writable firing the event again + # which will result in a retry of the write. } else { # Unanticipated errno. &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost); $Watcher->cancel; # Stop the watcher. @@ -588,8 +591,8 @@ Parameters: =item Socket -Socket on which the lond transaction occured. This is a -LondConnection. The data received is in the TransactionReply member. +Socket on which the lond transaction occurred. This is a +LondConnection. The data received are in the TransactionReply member. =item Transaction @@ -610,7 +613,7 @@ sub CompleteTransaction { StartClientReply($Transaction, $data); } else { # Delete deferred transaction file. Log("SUCCESS", "A delayed transaction was completed"); - LogPerm("S:".$Transaction->getClient().":".$Transaction->getRequest()); + LogPerm("S:".$Socket->PeerLoncapaHim().":".$Transaction->getRequest()); unlink($Transaction->getFile()); } } @@ -629,7 +632,7 @@ sub CompleteTransaction { =item data - The data to send to apached client. + The data to send to apache client. =cut @@ -740,14 +743,15 @@ Parameters: The socket to kill off. -=item Restart +=item restart -nonzero if we are allowed to create a new connection. +non-zero if we are allowed to create a new connection. =cut sub KillSocket { my $Socket = shift; + my $restart = shift; Log("WARNING", "Shutting down a socket"); $Socket->Shutdown(); @@ -764,15 +768,24 @@ sub KillSocket { if(exists($ActiveConnections{$Socket})) { $ActiveConnections{$Socket}->cancel; delete($ActiveConnections{$Socket}); - $ConnectionCount--; + # Decrement ConnectionCount unless we will immediately + # re-connect (i.e., $restart is true), because this was + # a connection where the SSL channel for exchange of the + # shared key failed, and we may use an insecure channel. + unless ($restart) { + $ConnectionCount--; + } if ($ConnectionCount < 0) { $ConnectionCount = 0; } } # If the connection count has gone to zero and there is work in the # work queue, the work all gets failed with con_lost. # + if($ConnectionCount == 0) { + $LondConnecting = 0; # No connections so also not connecting. EmptyQueue(); - CloseAllLondConnections; # Should all already be closed but... + CloseAllLondConnections(); # Should all already be closed but... + &clear_childpid($$); } UpdateStatus(); } @@ -786,7 +799,7 @@ is readable. The action is state depend =head3 State=Initialized -We''re waiting for the challenge, this is a no-op until the +We are waiting for the challenge, this is a no-op until the state changes. =head3 State=Challenged @@ -796,7 +809,7 @@ The connection must echo the challenge b =head3 State=ChallengeReplied -The challenge has been replied to. The we are receiveing the +The challenge has been replied to. Then we are receiving the 'ok' from the partner. =head3 State=ReadingVersionString @@ -822,9 +835,9 @@ The the key has been requested, now we a =head3 State=Idle The encryption key has been negotiated or we have finished -reading data from the a transaction. If the callback data has -a client as well as the socket iformation, then we are -doing a transaction and the data received is relayed to the client +reading data from the a transaction. If the callback data have +a client as well as the socket information, then we are +doing a transaction and the data received are relayed to the client before the socket is put on the idle list. =head3 State=SendingRequest @@ -841,7 +854,7 @@ to readable to receive the reply. The parameter to this function are: The event. Implicit in this is the watcher and its data. The data -contains at least the lond connection object and, if a +contain at least the lond connection object and, if a transaction is in progress, the socket attached to the local client. =cut @@ -869,17 +882,33 @@ sub LondReadable { Log("WARNING", "Lond connection lost."); + my $state_on_exit = $Socket->GetState(); if(exists($ActiveTransactions{$Socket})) { FailTransaction($ActiveTransactions{$Socket}); } else { # Socket is connecting and failed... need to mark # no longer connecting. - $LondConnecting = 0; } $Watcher->cancel(); - KillSocket($Socket); - $ConnectionRetriesLeft--; # Counts as connection failure + if ($state_on_exit eq 'ReInitNoSSL') { + # SSL certificate verification failed, and insecure connection + # allowed. Send restart arg to KillSocket(), so EmptyQueue() + # is not called, as we still hope to process queued request. + + KillSocket($Socket,1); + + # Re-initiate creation of Lond Connection for use with queued + # request. + + ShowStatus("Connected to ".$RemoteHost); + Log("WARNING","No SSL channel (verification failed), will try with insecure channel"); + &MakeLondConnection(1); + + } else { + KillSocket($Socket); + $ConnectionRetriesLeft--; # Counts as connection failure + } return; } SocketDump(6,$Socket); @@ -890,6 +919,8 @@ sub LondReadable { if($State eq "Initialized") { + } elsif ($State eq "ReInitNoSSL") { + } elsif ($State eq "ChallengeReceived") { # The challenge must be echoed back; The state machine # in the connection takes care of setting that up. Just @@ -926,7 +957,14 @@ sub LondReadable { } elsif ($State eq "ReceivingKey") { } elsif ($State eq "Idle") { - + + if ($ConnectionCount == 1) { + # Write child Pid file to keep track of ssl and insecure + # connections + + &record_childpid($Socket); + } + # This is as good a spot as any to get the peer version # string: @@ -946,7 +984,8 @@ sub LondReadable { CompleteTransaction($Socket, $ActiveTransactions{$Socket}); } else { - Log("SUCCESS", "Connection ".$ConnectionCount." to " + my $count = $Socket->GetClientData(); + Log("SUCCESS", "Connection ".$count." to " .$RemoteHost." now ready for action"); } ServerToIdle($Socket); # Next work unit or idle. @@ -983,9 +1022,9 @@ event. The action taken is very state d =head3 State = Connected The connection is in the process of sending the 'init' hailing to the -lond on the remote end. The connection object''s Writable member is -called. On error, ConnectionError is called to destroy the connection -and remove it from the ActiveConnections hash +lond on the remote end. The Writable member of the connection object +is called. On error, call ConnectionError to destroy the connection +and remove it from the ActiveConnections hash. =head3 Initialized @@ -1090,7 +1129,9 @@ sub LondWritable { $Watcher->cb(\&LondReadable); $Watcher->poll("r"); - + + } elsif ($State eq "ReInitNoSSL") { + } elsif ($State eq "ChallengeReceived") { # We received the challenge, now we # are echoing it back. This is a no-op, @@ -1194,27 +1235,28 @@ sub QueueDelayed { =head2 MakeLondConnection Create a new lond connection object, and start it towards its initial -idleness. Once idle, it becomes elligible to receive transactions +idleness. Once idle, it becomes eligible to receive transactions from the work queue. If the work queue is not empty when the connection is completed and becomes idle, it will dequeue an entry and start off on it. =cut -sub MakeLondConnection { +sub MakeLondConnection { + my ($restart) = @_; Debug(4,"MakeLondConnection to ".GetServerHost()." on port " .GetServerPort()); my $Connection = LondConnection->new(&GetServerHost(), &GetServerPort(), - &GetHostId()); + &GetHostId(), + &GetDefHostId()); - if($Connection eq undef) { + if($Connection eq undef) { Log("CRITICAL","Failed to make a connection with lond."); $ConnectionRetriesLeft--; return 0; # Failure. } else { - $LondConnecting = 1; # Connection in progress. # The connection needs to have writability # monitored in order to send the init sequence @@ -1239,12 +1281,15 @@ sub MakeLondConnection { if ($ConnectionCount == 0) { &SetupTimer; # Need to handle timeouts with connections... } - $ConnectionCount++; + unless ($restart) { + $ConnectionCount++; + } + $Connection->SetClientData($ConnectionCount); Debug(4, "Connection count = ".$ConnectionCount); if($ConnectionCount == 1) { # First Connection: QueueDelayed; } - Log("SUCESS", "Created connection ".$ConnectionCount + Log("SUCCESS", "Created connection ".$ConnectionCount ." to host ".GetServerHost()); return 1; # Return success. } @@ -1265,7 +1310,7 @@ reply. =item $Client -Connection to the client that is making this request We got the +Connection to the client that is making this request. We got the request from this socket, and when the request has been relayed to lond and we get a reply back from lond it will get sent to this socket. @@ -1351,11 +1396,11 @@ sub QueueTransaction { } } -#-------------------------- Lonc UNIX socket handling --------------------- - +#-------------------------- Lonc UNIX socket handling ------------------- =pod =head2 ClientRequest + Callback that is called when data can be read from the UNIX domain socket connecting us with an apache server process. @@ -1455,7 +1500,7 @@ sub accept_client { Callback that is called when a connection is received on the unix socket for a new client of lonc. The callback is parameterized by the event.. which is a-priori assumed to be an io event, and therefore has -an fd member that is the Listener socket. We Accept the connection +an fd member that is the Listener socket. We accept the connection and register a new event on the readability of that socket: =cut @@ -1506,7 +1551,7 @@ sub GetServerHost { =pod -=head2 GetServerId +=head2 GetHostId Returns the hostid whose lond we talk with. @@ -1518,6 +1563,18 @@ sub GetHostId { =pod +=head2 GetDefHostId + +Returns the default hostid for the node whose lond we talk with. + +=cut + +sub GetDefHostId { # Setup by the fork. + return $RemoteDefHostId; +} + +=pod + =head2 GetServerPort Returns the lond port number. @@ -1535,7 +1592,7 @@ sub GetServerPort { Setup a lonc listener event. The event is called when the socket becomes readable.. that corresponds to the receipt of a new connection. The event handler established will accept the connection -(creating a communcations channel), that int turn will establish +(creating a communications channel), that in turn will establish another event handler to subess requests. =head2 Parameters: @@ -1648,6 +1705,7 @@ sub SignalledToDeath { ."died through "."\"$signal\""); #LogPerm("F:lonc: $$ on $RemoteHost signalled to death: " # ."\"$signal\""); + &clear_childpid($$); exit 0; } @@ -1675,8 +1733,8 @@ sub ToggleDebug { This sub implements a child process for a single lonc daemon. Optional parameter: - $socket - if provided, this is a socket already open for listen - on the client socket. Otherwise, a new listen is set up. + $socket - if provided, this is a socket already open for listening + on the client socket. Otherwise, a new listener is set up. =cut @@ -1778,7 +1836,7 @@ sub ChildProcess { # Create a new child for host passed in: sub CreateChild { - my ($host, $hostid) = @_; + my ($host, $hostid, $defhostid) = @_; my $sigset = POSIX::SigSet->new(SIGINT); sigprocmask(SIG_BLOCK, $sigset); @@ -1793,6 +1851,7 @@ sub CreateChild { undef(@all_host_ids); } else { # child. $RemoteHostId = $hostid; + $RemoteDefHostId = $defhostid; ShowStatus("Connected to ".$RemoteHost); $SIG{INT} = 'DEFAULT'; sigprocmask(SIG_UNBLOCK, $sigset); @@ -1860,7 +1919,7 @@ sub get_remote_hostname { (my $hostname,my $lonid,@all_host_ids) = split(':',$data); $ChildHost{$hostname}++; if ($ChildHost{$hostname} == 1) { - &CreateChild($hostname,$lonid); + &CreateChild($hostname,$lonid,$all_host_ids[-1]); } else { &Log('WARNING',"Request for a second child on $hostname"); } @@ -1968,6 +2027,7 @@ sub server_died { my $host = $ChildPid{$pid}; if($host) { # It's for real... &Debug(9, "Caught sigchild for $host"); + &clear_childpid($pid); delete($ChildPid{$pid}); delete($ChildHost{$host}); &parent_clean_up($host); @@ -2076,7 +2136,7 @@ die "Main Event loop exited: $ret"; =head1 CheckKids Since kids do not die as easily in this implementation -as the previous one, there is no need to restart the +as the previous one, there is no need to restart the dead ones (all dead kids get restarted when they die!!) The only thing this function does is to pass USR1 to the kids so that they report their status. @@ -2138,6 +2198,8 @@ sub UpdateKids { # (lost unless they are critical). &KillThemAll(); + LondConnection->ResetReadConfig(); + ShowStatus('Parent keeping the flock'); } @@ -2146,13 +2208,14 @@ sub UpdateKids { =head1 Restart Signal handler for HUP... all children are killed and -we self restart. This is an el-cheapo way to re read +we self restart. This is an el-cheapo way to re-read the config file. =cut sub Restart { &KillThemAll; # First kill all the children. + LondConnection->ResetReadConfig(); Log("CRITICAL", "Restarting"); my $execdir = $perlvar{'lonDaemons'}; unlink("$execdir/logs/lonc.pid"); @@ -2181,6 +2244,7 @@ sub KillThemAll { ShowStatus("Nicely Killing lonc for $serving pid = $pid"); Log("CRITICAL", "Nicely Killing lonc for $serving pid = $pid"); kill 'QUIT' => $pid; + &clear_childpid($pid); } ShowStatus("Finished killing child processes off."); } @@ -2200,6 +2264,7 @@ sub really_kill_them_all_dammit Log("CRITICAL", "Nastily killing lonc for $serving pid = $pid"); kill 'KILL' => $pid; delete($ChildPid{$pid}); + delete($ChildKeyMode{$pid}); my $execdir = $perlvar{'lonDaemons'}; unlink("$execdir/logs/lonc.pid"); } @@ -2228,13 +2293,60 @@ sub Terminate { } +=pod + +=cut + sub my_hostname { - use Sys::Hostname; - my $name = &hostname(); + use Sys::Hostname::FQDN(); + my $name = Sys::Hostname::FQDN::fqdn(); &Debug(9,"Name is $name"); return $name; } +sub record_childpid { + my ($Socket) = @_; + my $docdir = $perlvar{'lonDocRoot'}; + my $authmode = $Socket->GetKeyMode(); + my $peer = $Socket->PeerLoncapaHim(); + if (($authmode eq 'ssl') || ($authmode eq 'insecure')) { + my $childpid = $$; + if ($childpid) { + unless (exists($ChildKeyMode{$childpid})) { + $ChildKeyMode{$childpid} = $authmode; + } + if (-d "$docdir/lon-status/loncchld") { + unless (-e "$docdir/lon-status/loncchld/$childpid") { + if (open (my $pidfh,'>',"$docdir/lon-status/loncchld/$childpid")) { + print $pidfh "$peer:$authmode\n"; + close($pidfh); + } + } + } + } + } + return; +} + +sub clear_childpid { + my ($childpid) = @_; + my $docdir = $perlvar{'lonDocRoot'}; + if (-d "$docdir/lon-status/loncchld") { + if ($childpid =~ /^\d+$/) { + if (($ChildKeyMode{$childpid} eq 'insecure') || + ($ChildKeyMode{$childpid} eq 'ssl')) { + if (-e "$docdir/lon-status/loncchld/$childpid") { + unlink("$docdir/lon-status/loncchld/$childpid"); + } + } + } + } + if (exists($ChildKeyMode{$childpid})) { + delete($ChildKeyMode{$childpid}); + } + return; +} + =pod =head1 Theory @@ -2292,7 +2404,7 @@ If there are pending transactions in the they are failed (saved if critical). If the connection retry count gets exceeded by this, the remote host is marked as dead. -Called when timeouts occured during the connection and +Called when timeouts occurred during the connection and connection dialog with a remote host. =item Critical Host makred DEAD @@ -2339,6 +2451,12 @@ connection or died. This should be foll "WARNING Failing transaction..." msgs for each in-flight or queued transaction. +=item WARNING No SSL channel (verification failed), will try with insecure channel. + +Called when promotion of a socket to SSL failed because SSL certificate verification failed. +Domain configuration must also permit insecure channel use for key exchange. Connection +negotiation will start again from the beginning, but with Authentication Mode not set to ssl. + =item INFO Connected to lond version: When connection negotiation is complete, the lond version is requested and logged here. @@ -2419,7 +2537,7 @@ the event processing loop is entered. =item INFO Updating connections via SIGUSR2 SIGUSR2 received. The original code would kill all clients, re-read the host file, -then restart children for each host. Now that childrean aree started on demand, this +then restart children for each host. Now that children are started on demand, this just kills all child processes and lets requests start them as needed again. @@ -2430,7 +2548,7 @@ SigHUP received. all the children are k =item CRITICAL Nicely killing lonc for host pid = Attempting to kill the child that is serving the specified host (pid given) cleanly via -SIGQUIT The child should handle that, clean up nicely and exit. +SIGQUIT. The child should handle that, clean up nicely and exit. =item CRITICAL Nastily killing lonc for host pid =