--- loncom/LondConnection.pm 2004/03/02 11:14:38 1.27 +++ loncom/LondConnection.pm 2018/12/11 20:51:08 1.61 @@ -1,7 +1,7 @@ # This module defines and implements a class that represents # a connection to a lond daemon. # -# $Id: LondConnection.pm,v 1.27 2004/03/02 11:14:38 foxr Exp $ +# $Id: LondConnection.pm,v 1.61 2018/12/11 20:51:08 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -36,14 +36,17 @@ use IO::File; use Fcntl; use POSIX; use Crypt::IDEA; - - - +use LONCAPA::lonlocal; +use LONCAPA::lonssl; my $DebugLevel=0; -my %hostshash; my %perlvar; +my %secureconf; +my %badcerts; +my %hosttypes; +my %crlchecked; +my $InsecureOk; # # Set debugging level @@ -61,57 +64,37 @@ sub SetDebug { my $ConfigRead = 0; # Read the configuration file for apache to get the perl -# variable set. +# variables set. sub ReadConfig { + Debug(8, "ReadConfig called"); + my $perlvarref = read_conf('loncapa.conf'); %perlvar = %{$perlvarref}; - my $hoststab = read_hosts( - "$perlvar{lonTabDir}/hosts.tab") || - die "Can't read host table!!"; - %hostshash = %{$hoststab}; $ConfigRead = 1; - -} -# -# Read a foreign configuration. -# This sub is intended for the cases where the package -# will be read from outside the LonCAPA environment, in that case -# the client will need to explicitly provide: -# - A file in hosts.tab format. -# - Some idea of the 'lonCAPA' name of the local host (for building -# the encryption key). -# -# Parameters: -# MyHost - Name of this host as far as LonCAPA is concerned. -# Filename - Name of a hosts.tab formatted file that will be used -# to build up the hosts table. -# -sub ReadForeignConfig { - my $MyHost = shift; - my $Filename = shift; - - &Debug(4, "ReadForeignConfig $MyHost $Filename\n"); - - $perlvar{lonHostID} = $MyHost; # Rmember my host. - my $hosttab = read_hosts($Filename) || - die "Can't read hosts table!!"; - %hostshash = %{$hosttab}; - if($DebugLevel > 3) { - foreach my $host (keys %hostshash) { - print "host $host => $hostshash{$host}\n"; - } + $InsecureOk = $perlvar{loncAllowInsecure}; + + unless (lonssl::Read_Connect_Config(\%secureconf,\%perlvar) eq 'ok') { + Debug(1,"Failed to retrieve secureconf hash.\n"); } - $ConfigRead = 1; + unless (lonssl::Read_Host_Types(\%hosttypes,\%perlvar) eq 'ok') { + Debug(1,"Failed to retrieve hosttypes hash.\n"); + } + %badcerts = (); + %crlchecked = (); +} +sub ResetReadConfig { + $ConfigRead = 0; } sub Debug { - my $level = shift; - my $message = shift; + + my ($level, $message) = @_; + if ($level < $DebugLevel) { - print($message."\n"); + print STDERR ($message."\n"); } } @@ -125,9 +108,19 @@ Dump the internal state of the object: F sub Dump { my $self = shift; + my $level = shift; + my $now = time; + my $local = localtime($now); + + if ($level >= $DebugLevel) { + return; + } + + my $key; my $value; - print STDERR "Dumping LondConnectionObject:\n"; + print STDERR "[ $local ] Dumping LondConnectionObject:\n"; + print STDERR join(':',caller(1))."\n"; while(($key, $value) = each %$self) { print STDERR "$key -> $value\n"; } @@ -143,8 +136,9 @@ old state. =cut sub Transition { - my $self = shift; - my $newstate = shift; + + my ($self, $newstate) = @_; + my $oldstate = $self->{State}; $self->{State} = $newstate; $self->{TimeoutRemaining} = $self->{TimeoutValue}; @@ -171,18 +165,39 @@ host the remote lond is on. This host is port number the remote lond is listening on. +=item lonid + + lonid of the remote lond is listening on. + +=item deflonid + + default lonhostID of the remote lond is listening on. + =cut sub new { - my $class = shift; # class name. - my $Hostname = shift; # Name of host to connect to. - my $Port = shift; # Port to connect + my ($class, $DnsName, $Port, $lonid, $deflonid) = @_; if (!$ConfigRead) { ReadConfig(); $ConfigRead = 1; } - &Debug(4,$class."::new( ".$Hostname.",".$Port.")\n"); + &Debug(4,$class."::new( ".$DnsName.",".$Port.",".$lonid.",".$deflonid.")\n"); + + my ($conntype,$gotconninfo,$allowinsecure); + if ((ref($secureconf{'connto'}) eq 'HASH') && + (exists($hosttypes{$lonid}))) { + $conntype = $secureconf{'connto'}{$hosttypes{$lonid}}; + if ($conntype ne '') { + if ($conntype ne 'req') { + $allowinsecure = 1; + } + $gotconninfo = 1; + } + } + unless ($gotconninfo) { + $allowinsecure = $InsecureOk; + } # The host must map to an entry in the hosts table: # We connect to the dns host that corresponds to that @@ -190,20 +205,20 @@ sub new { # negotion. In the objec these become the Host and # LoncapaHim fields of the object respectively. # - if (!exists $hostshash{$Hostname}) { - &Debug(8, "No Such host $Hostname"); - return undef; # No such host!!! - } - my @ConfigLine = @{$hostshash{$Hostname}}; - my $DnsName = $ConfigLine[3]; # 4'th item is dns of host. - Debug(5, "Connecting to ".$DnsName); + # if it is me use loopback for connection + if ($DnsName eq &main::my_hostname()) { $DnsName="127.0.0.1"; } + Debug(9, "Connecting to $DnsName"); # Now create the object... my $self = { Host => $DnsName, - LoncapaHim => $Hostname, + LoncapaHim => $lonid, + LoncapaDefid => $deflonid, Port => $Port, State => "Initialized", + AuthenticationMode => "", + InsecureOK => $allowinsecure, TransactionRequest => "", TransactionReply => "", + NextRequest => "", InformReadable => 0, InformWritable => 0, TimeoutCallback => undef, @@ -211,41 +226,111 @@ sub new { Timeoutable => 0, TimeoutValue => 30, TimeoutRemaining => 0, + LocalKeyFile => "", CipherKey => "", LondVersion => "Unknown", - Cipher => undef}; + Cipher => undef, + ClientData => undef}; bless($self, $class); unless ($self->{Socket} = IO::Socket::INET->new(PeerHost => $self->{Host}, PeerPort => $self->{Port}, Type => SOCK_STREAM, Proto => "tcp", Timeout => 3)) { + Debug(8, "Error? \n$@ \n$!"); return undef; # Inidicates the socket could not be made. } + my $socket = $self->{Socket}; # For local use only. + $socket->sockopt(SO_KEEPALIVE, 1); # Turn on keepalive probes when idle. + # If we are local, we'll first try local auth mode, otherwise, we'll try + # the ssl auth mode: + + my $key; + my $keyfile; + if ($DnsName eq '127.0.0.1') { + $self->{AuthenticationMode} = "local"; + ($key, $keyfile) = lonlocal::CreateKeyFile(); + Debug(8, "Local key: $key, stored in $keyfile"); + + # If I can't make the key file fall back to insecure if + # allowed...else give up right away. + + if(!(defined $key) || !(defined $keyfile)) { + my $canconnect = 0; + if (ref($secureconf{'connto'}) eq 'HASH') { + unless ($secureconf{'connto'}->{'dom'} eq 'req') { + $canconnect = 1; + } + } else { + $canconnect = $InsecureOk; + } + if ($canconnect) { + $self->{AuthenticationMode} = "insecure"; + $self->{TransactionRequest} = "init\n"; + } + else { + $socket->close; + return undef; + } + } + $self->{TransactionRequest} = "init:local:$keyfile\n"; + Debug(9, "Init string is init:local:$keyfile"); + if(!$self->CreateCipher($key)) { # Nothing's going our way... + $socket->close; + return undef; + } + + } else { + # Remote peer: I'd like to do ssl, but if my host key or certificates + # are not all installed, my only choice is insecure, if that's + # allowed: + + my ($ca, $cert) = lonssl::CertificateFile; + my $sslkeyfile = lonssl::KeyFile; + my $badcertfile = lonssl::has_badcert_file($self->{LoncapaHim}); + my ($loncaparev) = ($perlvar{'lonVersion'} =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/); + + if (($conntype ne 'no') && (defined($ca)) && (defined($cert)) && (defined($sslkeyfile)) && + (!exists($badcerts{$self->{LoncapaHim}})) && !$badcertfile) { + $self->{AuthenticationMode} = "ssl"; + $self->{TransactionRequest} = "init:ssl:$loncaparev\n"; + } elsif ($self->{InsecureOK}) { + # Allowed to do insecure: + $self->{AuthenticationMode} = "insecure"; + $self->{TransactionRequest} = "init::$loncaparev\n"; + } else { + # Not allowed to do insecure... + $socket->close; + return undef; + } + } + # # We're connected. Set the state, and the events we'll accept: # $self->Transition("Connected"); $self->{InformWritable} = 1; # When socket is writable we send init $self->{Timeoutable} = 1; # Timeout allowed during startup negotiation. - $self->{TransactionRequest} = "init\n"; + # # Set socket to nonblocking I/O. # - my $socket = $self->{Socket}; - my $flags = fcntl($socket->fileno, F_GETFL,0); - if($flags == -1) { + my $flags = fcntl($socket, F_GETFL,0); + if(!$flags) { $socket->close; return undef; } - if(fcntl($socket, F_SETFL, $flags | O_NONBLOCK) == -1) { + if(!fcntl($socket, F_SETFL, $flags | O_NONBLOCK)) { $socket->close; return undef; } # return the object : + Debug(9, "Initial object state: "); + $self->Dump(9); + return $self; } @@ -280,6 +365,8 @@ sub Readable { my $socket = $self->{Socket}; my $data = ''; my $rv; + my $ConnectionMode = $self->{AuthenticationMode}; + if ($socket) { eval { $rv = $socket->recv($data, POSIX::BUFSIZ, 0); @@ -304,76 +391,168 @@ sub Readable { $self->Transition("Disconnected"); return -1; } + # If we actually got data, reset the timeout. + + if (length $data) { + $self->{TimeoutRemaining} = $self->{TimeoutValue}; # getting data resets the timeout period. + } # Append the data to the buffer. And figure out if the read is done: &Debug(9,"Received from host: ".$data); $self->{TransactionReply} .= $data; - if($self->{TransactionReply} =~ /(.*\n)/) { + if($self->{TransactionReply} =~ m/\n$/) { &Debug(8,"Readable End of line detected"); + + if ($self->{State} eq "Initialized") { # We received the challenge: - if($self->{TransactionReply} eq "refused\n") { # Remote doesn't have - - $self->Transition("Disconnected"); # in host tables. + # Our init was replied to. What happens next depends both on + # the actual init we sent (AuthenticationMode member data) + # and the response: + # AuthenticationMode == local: + # Response ok: The key has been exchanged and + # the key file destroyed. We can jump + # into setting the host and requesting the + # Later we'll also bypass key exchange. + # Response digits: + # Old style lond. Delete the keyfile. + # If allowed fall back to insecure mode. + # else close connection and fail. + # Response other: + # Failed local auth + # Close connection and fail. + # + # AuthenticationMode == ssl: + # Response ok:ssl + # Response digits: + # Response other: + # Authentication mode == insecure + # Response digits + # Response other: + + my $Response = $self->{TransactionReply}; + if($ConnectionMode eq "local") { + if($Response =~ /^ok:local/) { # Good local auth. + $self->ToVersionRequest(); + return 0; + } + elsif ($Response =~/^[0-9]+/) { # Old style lond. + return $self->CompleteInsecure(); + + } + else { # Complete flop + &Debug(3, "init:local : unrecognized reply"); + $self->Transition("Disconnected"); + $socket->close; + return -1; + } + } + elsif ($ConnectionMode eq "ssl") { + if($Response =~ /^ok:ssl/) { # Good ssl... + my $sslresult = $self->ExchangeKeysViaSSL(); + if ($sslresult == 1) { # Success skip to vsn stuff + # Need to reset to non blocking: + + my $flags = fcntl($socket, F_GETFL, 0); + fcntl($socket, F_SETFL, $flags | O_NONBLOCK); + $self->ToVersionRequest(); + return 0; + } + else { # Failed in ssl exchange. + if (($sslresult == -1) && (lonssl::LastError == -1) && ($self->{InsecureOK})) { + my $badcertdir = &lonssl::BadCertDir(); + if (($badcertdir) && $self->{LoncapaHim}) { + if (open(my $fh,'>',"$badcertdir/".$self->{LoncapaHim})) { + close($fh); + } + } + $badcerts{$self->{LoncapaHim}} = 1; + &Debug(3,"SSL verification failed: close socket and initiate insecure connection"); + $self->Transition("ReInitNoSSL"); + $socket->close; + return -1; + } + &Debug(3,"init:ssl failed key negotiation!"); + $self->Transition("Disconnected"); + $socket->close; + return -1; + } + } + elsif ($Response =~ /^[0-9]+/) { # Old style lond. + return $self->CompleteInsecure(); + } + else { # Complete flop + } + } + elsif ($ConnectionMode eq "insecure") { + if($self->{TransactionReply} eq "refused\n") { # Remote doesn't have + + $self->Transition("Disconnected"); # in host tables. + $socket->close(); + return -1; + + } + return $self->CompleteInsecure(); + } + else { + &Debug(1,"Authentication mode incorrect"); + die "BUG!!! LondConnection::Readable invalid authmode"; + } + + + } elsif ($self->{State} eq "ChallengeReplied") { + if($self->{TransactionReply} ne "ok\n") { + $self->Transition("Disconnected"); $socket->close(); return -1; } + $self->ToVersionRequest(); + return 0; - &Debug(8," Transition out of Initialized"); - $self->{TransactionRequest} = $self->{TransactionReply}; - $self->{InformWritable} = 1; - $self->{InformReadable} = 0; - $self->Transition("ChallengeReceived"); - $self->{TimeoutRemaining} = $self->{TimeoutValue}; + } elsif ($self->{State} eq "ReadingVersionString") { + chomp($self->{TransactionReply}); + $self->{LondVersion} = $self->{TransactionReply}; + $self->Transition("SetHost"); + $self->{InformReadable} = 0; + $self->{InformWritable} = 1; + my $peer = $self->{LoncapaHim}; + $self->{TransactionRequest}= "sethost:$peer\n"; return 0; - } elsif ($self->{State} eq "ChallengeReplied") { - if($self->{TransactionReply} ne "ok\n") { - $self->Transition("Disconnected"); - $socket->close(); - return -1; - } - $self->Transition("RequestingVersion"); - $self->{InformReadable} = 0; - $self->{InformWritable} = 1; - $self->{TransactionRequest} = "version\n"; - return 0; - } elsif ($self->{State} eq "ReadingVersionString") { - $self->{LondVersion} = chomp($self->{TransactionReply}); - $self->Transition("SetHost"); - $self->{InformReadable} = 0; - $self->{InformWritable} = 1; - my $peer = $self->{LoncapaHim}; - $self->{TransactionRequest}= "sethost:$peer\n"; - return 0; } elsif ($self->{State} eq "HostSet") { # should be ok. - if($self->{TransactionReply} ne "ok\n") { - $self->Transition("Disconnected"); - $socket->close(); - return -1; - } - $self->Transition("RequestingKey"); - $self->{InformReadable} = 0; - $self->{InformWritable} = 1; - $self->{TransactionRequest} = "ekey\n"; - return 0; + if($self->{TransactionReply} ne "ok\n") { + $self->Transition("Disconnected"); + $socket->close(); + return -1; + } + # If the auth mode is insecure we must still + # exchange session keys. Otherwise, + # we can just transition to idle. + + if($ConnectionMode eq "insecure") { + $self->Transition("RequestingKey"); + $self->{InformReadable} = 0; + $self->{InformWritable} = 1; + $self->{TransactionRequest} = "ekey\n"; + return 0; + } + else { + $self->ToIdle(); + return 0; + } } elsif ($self->{State} eq "ReceivingKey") { my $buildkey = $self->{TransactionReply}; + chomp($buildkey); my $key = $self->{LoncapaHim}.$perlvar{'lonHostID'}; $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); - $self->{Cipher} = new IDEA $cipherkey; - if($self->{Cipher} eq undef) { + $key =$key.$buildkey.$key.$buildkey.$key.$buildkey; + $key = substr($key,0,32); + if(!$self->CreateCipher($key)) { $self->Transition("Disconnected"); $socket->close(); return -1; } else { - $self->Transition("Idle"); - $self->{InformWritable} = 0; - $self->{InformReadable} = 0; - $self->{Timeoutable} = 0; + $self->ToIdle(); return 0; } } elsif ($self->{State} eq "ReceivingReply") { @@ -383,16 +562,24 @@ sub Readable { my $answer = $self->{TransactionReply}; if($answer =~ /^enc\:/) { $answer = $self->Decrypt($answer); - $self->{TransactionReply} = $answer; + $self->{TransactionReply} = "$answer\n"; } - + # if we have a NextRequest do it immeadiately + if ($self->{NextRequest}) { + $self->{TransactionRequest} = $self->{NextRequest}; + undef( $self->{NextRequest} ); + $self->{TransactionReply} = ""; + $self->{InformWritable} = 1; + $self->{InformReadable} = 0; + $self->{Timeoutable} = 1; + $self->Transition("SendingRequest"); + return 0; + } else { # finish the transaction - $self->{InformWritable} = 0; - $self->{InformReadable} = 0; - $self->{Timeoutable} = 0; - $self->Transition("Idle"); - return 0; + $self->ToIdle(); + return 0; + } } elsif ($self->{State} eq "Disconnected") { # No connection. return -1; } else { # Internal error: Invalid state. @@ -446,41 +633,42 @@ sub Writable { ($errno == POSIX::EAGAIN) || ($errno == POSIX::EINTR) || ($errno == 0)) { + $self->{TimeoutRemaining} = $self->{TimeoutValue}; substr($self->{TransactionRequest}, 0, $nwritten) = ""; # rmv written part - if(length $self->{TransactionRequest} == 0) { - $self->{InformWritable} = 0; - $self->{InformReadable} = 1; - $self->{TransactionReply} = ''; - # - # Figure out the next state: - # - if($self->{State} eq "Connected") { - $self->Transition("Initialized"); - } elsif($self->{State} eq "ChallengeReceived") { - $self->Transition("ChallengeReplied"); - } elsif($self->{State} eq "RequestingVersion") { - $self->Transition("ReadingVersionString"); - } elsif ($self->{State} eq "SetHost") { - $self->Transition("HostSet"); - } elsif($self->{State} eq "RequestingKey") { - $self->Transition("ReceivingKey"); + if(length $self->{TransactionRequest} == 0) { + $self->{InformWritable} = 0; + $self->{InformReadable} = 1; + $self->{TransactionReply} = ''; + # + # Figure out the next state: + # + if($self->{State} eq "Connected") { + $self->Transition("Initialized"); + } elsif($self->{State} eq "ChallengeReceived") { + $self->Transition("ChallengeReplied"); + } elsif($self->{State} eq "RequestingVersion") { + $self->Transition("ReadingVersionString"); + } elsif ($self->{State} eq "SetHost") { + $self->Transition("HostSet"); + } elsif($self->{State} eq "RequestingKey") { + $self->Transition("ReceivingKey"); # $self->{InformWritable} = 0; # $self->{InformReadable} = 1; # $self->{TransactionReply} = ''; - } elsif ($self->{State} eq "SendingRequest") { - $self->Transition("ReceivingReply"); - $self->{TimeoutRemaining} = $self->{TimeoutValue}; - } elsif ($self->{State} eq "Disconnected") { - return -1; - } - return 0; - } - } else { # The write failed (e.g. partner disconnected). - $self->Transition("Disconnected"); - $socket->close(); - return -1; - } - + } elsif ($self->{State} eq "SendingRequest") { + $self->Transition("ReceivingReply"); + $self->{TimeoutRemaining} = $self->{TimeoutValue}; + } elsif ($self->{State} eq "Disconnected") { + return -1; + } + return 0; + } + } else { # The write failed (e.g. partner disconnected). + $self->Transition("Disconnected"); + $socket->close(); + return -1; + } + } =pod @@ -535,8 +723,8 @@ timout, and to request writability notif =cut sub InitiateTransaction { - my $self = shift; - my $data = shift; + + my ($self, $data) = @_; Debug(1, "initiating transaction: ".$data); if($self->{State} ne "Idle") { @@ -544,14 +732,26 @@ sub InitiateTransaction { return -1; # Error indicator. } # if the transaction is to be encrypted encrypt the data: + (my $sethost, my $server,$data)=split(/:/,$data,3); if($data =~ /^encrypt\:/) { $data = $self->Encrypt($data); } # Setup the trasaction - - $self->{TransactionRequest} = $data; + # currently no version of lond supports inlining the sethost + if ($self->PeerVersion() <= 321) { + if ($server ne $self->{LoncapaHim}) { + $self->{NextRequest} = $data; + $self->{TransactionRequest} = "$sethost:$server\n"; + $self->{LoncapaHim} = $server; + } else { + $self->{TransactionRequest} = $data; + } + } else { + $self->{LoncapaHim} = $server; + $self->{TransactionRequest} = "$sethost:$server:$data"; + } $self->{TransactionReply} = ""; $self->{InformWritable} = 1; $self->{InformReadable} = 0; @@ -587,8 +787,9 @@ established callback or undef if there w =cut sub SetTimeoutCallback { - my $self = shift; - my $callback = shift; + + my ($self, $callback) = @_; + my $oldCallback = $self->{TimeoutCallback}; $self->{TimeoutCallback} = $callback; return $oldCallback; @@ -614,6 +815,7 @@ sub Shutdown { $socket->shutdown(2); } } + $self->{Timeoutable} = 0; # Shutdown sockets can't timeout. } =pod @@ -716,8 +918,8 @@ The output string can be directly sent t =cut sub Encrypt { - my $self = shift; # Reference to the object. - my $request = shift; # Text to send. + + my ($self, $request) = @_; # Split the encrypt: off the request and figure out it's length. @@ -759,8 +961,8 @@ Decrypt a response from the server. The =cut sub Decrypt { - my $self = shift; # Recover reference to object - my $encrypted = shift; # This is the encrypted data. + + my ($self, $encrypted) = @_; # Bust up the response into length, and encryptedstring: @@ -783,34 +985,169 @@ sub Decrypt { # $length tells us the actual length of the decrypted string: $decrypted = substr($decrypted, 0, $length); + Debug(9, "Decrypted $EncryptedString to $decrypted"); return $decrypted; } +# ToIdle +# Called to transition to idle... done enough it's worth subbing +# off to ensure it's always done right!! +# +sub ToIdle { + my $self = shift; -=pod + $self->Transition("Idle"); + $self->{InformWritiable} = 0; + $self->{InformReadable} = 0; + $self->{Timeoutable} = 0; +} + +# ToVersionRequest +# Called to transition to "RequestVersion" also done a few times +# so worth subbing out. +# +sub ToVersionRequest { + my $self = shift; + + $self->Transition("RequestingVersion"); + $self->{InformReadable} = 0; + $self->{InformWritable} = 1; + $self->{TransactionRequest} = "version\n"; + +} +# +# CreateCipher +# Given a cipher key stores the key in the object context, +# creates the cipher object, (stores that in object context), +# This is done a couple of places, so it's worth factoring it out. +# +# Parameters: +# (self) +# key - The Cipher key. +# +# Returns: +# 0 - Failure to create IDEA cipher. +# 1 - Success. +# +sub CreateCipher { + my ($self, $key) = @_; # According to coding std. + + $self->{CipherKey} = $key; # Save the text key... + my $packedkey = pack ("H32", $key); + my $cipher = new IDEA $packedkey; + if($cipher) { + $self->{Cipher} = $cipher; + Debug("Cipher created dumping socket: "); + $self->Dump(9); + return 1; + } + else { + return 0; + } +} +# ExchangeKeysViaSSL +# Called to do cipher key exchange via SSL. +# The socket is promoted to an SSL socket. If that's successful, +# we read out cipher key through the socket and create an IDEA +# cipher object. +# Parameters: +# (self) +# Returns: +# true - Success. +# false - Failure. +# +# Assumptions: +# 1. The ssl session setup has timeout logic built in so we don't +# have to worry about DOS attacks at that stage. +# 2. If the ssl session gets set up we are talking to a legitimate +# lond so again we don't have to worry about DOS attacks. +# All this allows us just to call +sub ExchangeKeysViaSSL { + my $self = shift; + my $socket = $self->{Socket}; + my $peer = $self->{LoncapaHim}; + my $peerdef = $self->{LoncapaDefid}; -=head2 GetHostIterator + # Get our signed certificate, the certificate authority's + # certificate and our private key file. All of these + # are needed to create the ssl connection. + + my ($SSLCACertificate, + $SSLCertificate) = lonssl::CertificateFile(); + my $SSLKey = lonssl::KeyFile(); + my $CRLFile; + unless ($crlchecked{$peerdef}) { + $CRLFile = lonssl::CRLFile(); + $crlchecked{$peerdef} = 1; + } + # Promote our connection to ssl and read the key from lond. + + my $SSLSocket = lonssl::PromoteClientSocket($socket, + $SSLCACertificate, + $SSLCertificate, + $SSLKey, + $peer, + $peerdef, + $CRLFile); + if(defined $SSLSocket) { + my $key = <$SSLSocket>; + lonssl::Close($SSLSocket); + if($key) { + chomp($key); # \n is not part of the key. + return $self->CreateCipher($key); + } + else { + Debug(3, "Failed to read ssl key"); + return 0; + } + } + else { + # Failed!! + Debug(3, "Failed to negotiate SSL connection!"); + return -1; + } + # should not get here + return 0; -Returns a hash iterator to the host information. Each get from -this iterator returns a reference to an array that contains -information read from the hosts configuration file. Array elements -are used as follows: - - [0] - LonCapa host name. - [1] - LonCapa domain name. - [2] - Loncapa role (e.g. library or access). - [3] - DNS name server hostname. - [4] - IP address (result of e.g. nslookup [3]). - [5] - Maximum connection count. - [6] - Idle timeout for reducing connection count. - [7] - Minimum connection count. +} -=cut -sub GetHostIterator { - return HashIterator->new(\%hostshash); +# +# CompleteInsecure: +# This function is called to initiate the completion of +# insecure challenge response negotiation. +# To do this, we copy the challenge string to the transaction +# request, flip to writability and state transition to +# ChallengeReceived.. +# All this is only possible if InsecureOk is true. +# Parameters: +# (self) - This object's context hash. +# Return: +# 0 - Ok to transition. +# -1 - Not ok to transition (InsecureOk not ok). +# +sub CompleteInsecure { + my $self = shift; + if ($self->{InsecureOK}) { + $self->{AuthenticationMode} = "insecure"; + &Debug(8," Transition out of Initialized:insecure"); + $self->{TransactionRequest} = $self->{TransactionReply}; + $self->{InformWritable} = 1; + $self->{InformReadable} = 0; + $self->Transition("ChallengeReceived"); + $self->{TimeoutRemaining} = $self->{TimeoutValue}; + return 0; + + + } + else { + &Debug(3, "Insecure key negotiation disabled!"); + my $socket = $self->{Socket}; + $socket->close; + return -1; + } } ########################################################### @@ -824,7 +1161,7 @@ sub GetHostIterator { # -my $confdir='/etc/httpd/conf/'; +my @confdirs=('/etc/httpd/conf/','/etc/apache2/'); # ------------------- Subroutine read_conf: read LON-CAPA server configuration. # This subroutine reads PerlSetVar values out of specified web server @@ -832,73 +1169,43 @@ my $confdir='/etc/httpd/conf/'; sub read_conf { my (@conf_files)=@_; - my %perlvar; - foreach my $filename (@conf_files,'loncapa_apache.conf') - { - if($DebugLevel > 3) { - print("Going to read $confdir.$filename\n"); - } - open(CONFIG,'<'.$confdir.$filename) or - die("Can't read $confdir$filename"); - while (my $configline=) - { - if ($configline =~ /^[^\#]*PerlSetVar/) - { - my ($unused,$varname,$varvalue)=split(/\s+/,$configline); + my (%perlvar,%configdirs); + foreach my $filename (@conf_files,'loncapa_apache.conf') { + my $configdir = ''; + $configdirs{$filename} = [@confdirs]; + while ($configdir eq '' && @{$configdirs{$filename}} > 0) { + my $testdir = shift(@{$configdirs{$filename}}); + if (-e $testdir.$filename) { + $configdir = $testdir; + } + } + if ($configdir eq '') { + die("Couldn't find a directory containing $filename"); + } + if($DebugLevel > 3) { + print STDERR ("Going to read $configdir.$filename\n"); + } + open(CONFIG,'<'.$configdir.$filename) or + die("Can't read $configdir$filename"); + while (my $configline=) { + if ($configline =~ /^[^\#]*PerlSetVar/) { + my ($unused,$varname,$varvalue)=split(/\s+/,$configline); chomp($varvalue); $perlvar{$varname}=$varvalue; - } - } + } + } close(CONFIG); - } + } if($DebugLevel > 3) { - print "Dumping perlvar:\n"; + print STDERR "Dumping perlvar:\n"; foreach my $var (keys %perlvar) { - print "$var = $perlvar{$var}\n"; + print STDERR "$var = $perlvar{$var}\n"; } } my $perlvarref=\%perlvar; return $perlvarref; } -#---------------------- Subroutine read_hosts: Read a LON-CAPA hosts.tab -# formatted configuration file. -# -my $RequiredCount = 5; # Required item count in hosts.tab. -my $DefaultMaxCon = 5; # Default value for maximum connections. -my $DefaultIdle = 1000; # Default connection idle time in seconds. -my $DefaultMinCon = 0; # Default value for minimum connections. - -sub read_hosts { - my $Filename = shift; - my %HostsTab; - - open(CONFIG,'<'.$Filename) or die("Can't read $Filename"); - while (my $line = ) { - if (!($line =~ /^\s*\#/)) { - my @items = split(/:/, $line); - if(scalar @items >= $RequiredCount) { - if (scalar @items == $RequiredCount) { # Only required items: - $items[$RequiredCount] = $DefaultMaxCon; - } - if(scalar @items == $RequiredCount + 1) { # up through maxcon. - $items[$RequiredCount+1] = $DefaultIdle; - } - if(scalar @items == $RequiredCount + 2) { # up through idle. - $items[$RequiredCount+2] = $DefaultMinCon; - } - { - my @list = @items; # probably not needed but I'm unsure of - # about the scope of item so... - $HostsTab{$list[0]} = \@list; - } - } - } - } - close(CONFIG); - my $hostref = \%HostsTab; - return ($hostref); -} # # Get the version of our peer. Note that this is only well # defined if the state machine has hit the idle state at least @@ -907,8 +1214,41 @@ sub read_hosts { # sub PeerVersion { my $self = shift; - - return $self->{LondVersion}; + my ($version) = ($self->{LondVersion} =~ /Revision: 1\.(\d+)/); + return $version; +} + +# +# Manipulate the client data field +# +sub SetClientData { + my ($self, $newData) = @_; + $self->{ClientData} = $newData; +} +# +# Get the current client data field. +# +sub GetClientData { + my $self = shift; + return $self->{ClientData}; +} + +# +# Get the HostID of our peer +# + +sub PeerLoncapaHim { + my $self = shift; + return $self->{LoncapaHim}; +} + +# +# Get the Authentication mode +# + +sub GetKeyMode { + my $self = shift; + return $self->{AuthenticationMode}; } 1; @@ -984,6 +1324,17 @@ Socket open on the connection. The current state. +=item AuthenticationMode + +How authentication is being done. This can be any of: + + o local - Authenticate via a key exchanged in a file. + o ssl - Authenticate via a key exchaned through a temporary ssl tunnel. + o insecure - Exchange keys in an insecure manner. + +insecure is only allowed if the configuration parameter loncAllowInsecure +is nonzero. + =item TransactionRequest The request being transmitted. @@ -1093,8 +1444,4 @@ true if the current state requires a wri true if the current state requires timeout support. -=item GetHostIterator: - -Returns an iterator into the host file hash. - =cut 500 Internal Server Error

Internal Server Error

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

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

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