File:  [LON-CAPA] / loncom / LondConnection.pm
Revision 1.33: download - view: text, annotated - select for diffs
Thu Jun 17 11:02:25 2004 UTC (19 years, 9 months ago) by foxr
Branches: MAIN
CVS tags: version_1_2_X, version_1_2_1, version_1_2_0, version_1_1_99_5, version_1_1_99_4, version_1_1_99_3, version_1_1_99_2, version_1_1_99_1, HEAD
If the certificate files or host key are missing, than
switch to insecure authentication if allowed.  The idea
is this: After an upgrade to lond/lond ssl, you won't in general
have your signed host certificate yet.  We want to support uninterrupted
service until your certs are granted, so although the software is ssl
capable, it must, in the interim, do insecure authentication, if permitted
by the administrators.

One case not handled, and left not handled: if the lonCerts directory does not
exist the init:local key exchange cannot work.  This case can be ignored because
that diretory will be created as part of the update/upgrade.  If it does not exist
it's a sign of some other serious problem that ought to be fixed IMHO.

    1: #   This module defines and implements a class that represents
    2: #   a connection to a lond daemon.
    3: #
    4: # $Id: LondConnection.pm,v 1.33 2004/06/17 11:02:25 foxr Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: 
   29: package LondConnection;
   30: 
   31: use strict;
   32: use IO::Socket;
   33: use IO::Socket::INET;
   34: use IO::Handle;
   35: use IO::File;
   36: use Fcntl;
   37: use POSIX;
   38: use Crypt::IDEA;
   39: use LONCAPA::lonlocal;
   40: use LONCAPA::lonssl;
   41: 
   42: 
   43: 
   44: 
   45: my $DebugLevel=0;
   46: my %hostshash;
   47: my %perlvar;
   48: my $LocalDns = "";		# Need not be defined for managers.
   49: my $InsecureOk;
   50: 
   51: #
   52: #  Set debugging level
   53: #
   54: sub SetDebug {
   55:     $DebugLevel = shift;
   56: }
   57: 
   58: #
   59: #   The config read is done in this way to support the read of
   60: #   the non-default configuration file in the
   61: #   event we are being used outside of loncapa.
   62: #
   63: 
   64: my $ConfigRead = 0;
   65: 
   66: #   Read the configuration file for apache to get the perl
   67: #   variables set.
   68: 
   69: sub ReadConfig {
   70:     Debug(8, "ReadConfig called");
   71: 
   72:     my $perlvarref = read_conf('loncapa.conf');
   73:     %perlvar    = %{$perlvarref};
   74:     my $hoststab   = read_hosts(
   75: 				"$perlvar{lonTabDir}/hosts.tab") || 
   76: 				die "Can't read host table!!";
   77:     %hostshash  = %{$hoststab};
   78:     $ConfigRead = 1;
   79:     
   80:     my $myLonCapaName = $perlvar{lonHostID};
   81:     Debug(8, "My loncapa name is $myLonCapaName");
   82:     
   83:     if(defined $hostshash{$myLonCapaName}) {
   84: 	Debug(8, "My loncapa name is in hosthash");
   85: 	my @ConfigLine = @{$hostshash{$myLonCapaName}};
   86: 	$LocalDns = $ConfigLine[3];
   87: 	Debug(8, "Got local name $LocalDns");
   88:     }
   89:     $InsecureOk = $perlvar{loncAllowInsecure};
   90:     
   91:     Debug(3, "ReadConfig - LocalDNS = $LocalDns");
   92: }
   93: 
   94: #
   95: #  Read a foreign configuration.
   96: #  This sub is intended for the cases where the package
   97: #  will be read from outside the LonCAPA environment, in that case
   98: #  the client will need to explicitly provide:
   99: #   - A file in hosts.tab format.
  100: #   - Some idea of the 'lonCAPA' name of the local host (for building
  101: #     the encryption key).
  102: #
  103: #  Parameters:
  104: #      MyHost   - Name of this host as far as LonCAPA is concerned.
  105: #      Filename - Name of a hosts.tab formatted file that will be used
  106: #                 to build up the hosts table.
  107: #
  108: sub ReadForeignConfig {
  109: 
  110:     my ($MyHost, $Filename) = @_;
  111: 
  112:     &Debug(4, "ReadForeignConfig $MyHost $Filename\n");
  113: 
  114:     $perlvar{lonHostID} = $MyHost; # Rmember my host.
  115:     my $hosttab = read_hosts($Filename) ||
  116: 	die "Can't read hosts table!!";
  117:     %hostshash = %{$hosttab};
  118:     if($DebugLevel > 3) {
  119: 	foreach my $host (keys %hostshash) {
  120: 	    print STDERR "host $host => $hostshash{$host}\n";
  121: 	}
  122:     }
  123:     $ConfigRead = 1;
  124: 
  125:     my $myLonCapaName = $perlvar{lonHostID};
  126:     
  127:     if(defined $hostshash{$myLonCapaName}) {
  128: 	my @ConfigLine = @{$hostshash{$myLonCapaName}};
  129: 	$LocalDns = $ConfigLine[3];
  130:     }
  131:     $InsecureOk = $perlvar{loncAllowInsecure};
  132:     
  133:     Debug(3, "ReadForeignConfig  - LocalDNS = $LocalDns");
  134: 
  135: }
  136: 
  137: sub Debug {
  138: 
  139:     my ($level, $message) = @_;
  140: 
  141:     if ($level < $DebugLevel) {
  142: 	print STDERR ($message."\n");
  143:     }
  144: }
  145: 
  146: =pod
  147: 
  148: =head2 Dump
  149: 
  150: Dump the internal state of the object: For debugging purposes, to stderr.
  151: 
  152: =cut
  153: 
  154: sub Dump {
  155:     my $self   = shift;
  156:     my $level  = shift;
  157:     
  158:     if ($level <= $DebugLevel) {
  159: 	return;
  160:     }
  161: 
  162:     my $key;
  163:     my $value;
  164:     print STDERR "Dumping LondConnectionObject:\n";
  165:     while(($key, $value) = each %$self) {
  166: 	print STDERR "$key -> $value\n";
  167:     }
  168:     print STDERR "-------------------------------\n";
  169: }
  170: 
  171: =pod
  172: 
  173: Local function to do a state transition.  If the state transition
  174: callback is defined it is called with two parameters: the self and the
  175: old state.
  176: 
  177: =cut
  178: 
  179: sub Transition {
  180: 
  181:     my ($self, $newstate) = @_;
  182: 
  183:     my $oldstate = $self->{State};
  184:     $self->{State} = $newstate;
  185:     $self->{TimeoutRemaining} = $self->{TimeoutValue};
  186:     if($self->{TransitionCallback}) {
  187: 	($self->{TransitionCallback})->($self, $oldstate); 
  188:     }
  189: }
  190: 
  191: 
  192: 
  193: =pod
  194: 
  195: =head2 new
  196: 
  197: Construct a new lond connection.
  198: 
  199: Parameters (besides the class name) include:
  200: 
  201: =item hostname
  202: 
  203: host the remote lond is on. This host is a host in the hosts.tab file
  204: 
  205: =item port
  206: 
  207:  port number the remote lond is listening on.
  208: 
  209: =cut
  210: 
  211: sub new {
  212: 
  213:     my ($class, $Hostname, $Port) = @_;
  214: 
  215:     if (!$ConfigRead) {
  216: 	ReadConfig();
  217: 	$ConfigRead = 1;
  218:     }
  219:     &Debug(4,$class."::new( ".$Hostname.",".$Port.")\n");
  220: 
  221:     # The host must map to an entry in the hosts table:
  222:     #  We connect to the dns host that corresponds to that
  223:     #  system and use the hostname for the encryption key 
  224:     #  negotion.  In the objec these become the Host and
  225:     #  LoncapaHim fields of the object respectively.
  226:     #
  227:     if (!exists $hostshash{$Hostname}) {
  228: 	&Debug(8, "No Such host $Hostname");
  229: 	return undef;		# No such host!!!
  230:     }
  231:     my @ConfigLine = @{$hostshash{$Hostname}};
  232:     my $DnsName    = $ConfigLine[3]; # 4'th item is dns of host.
  233:     Debug(5, "Connecting to ".$DnsName);
  234:     # Now create the object...
  235:     my $self     = { Host               => $DnsName,
  236:                      LoncapaHim         => $Hostname,
  237:                      Port               => $Port,
  238:                      State              => "Initialized",
  239: 		     AuthenticationMode => "",
  240:                      TransactionRequest => "",
  241:                      TransactionReply   => "",
  242:                      InformReadable     => 0,
  243:                      InformWritable     => 0,
  244:                      TimeoutCallback    => undef,
  245:                      TransitionCallback => undef,
  246:                      Timeoutable        => 0,
  247:                      TimeoutValue       => 30,
  248:                      TimeoutRemaining   => 0,
  249: 		     LocalKeyFile       => "",
  250:                      CipherKey          => "",
  251:                      LondVersion        => "Unknown",
  252:                      Cipher             => undef};
  253:     bless($self, $class);
  254:     unless ($self->{Socket} = IO::Socket::INET->new(PeerHost => $self->{Host},
  255: 					       PeerPort => $self->{Port},
  256: 					       Type     => SOCK_STREAM,
  257: 					       Proto    => "tcp",
  258: 					       Timeout  => 3)) {
  259: 	return undef;		# Inidicates the socket could not be made.
  260:     }
  261:     my $socket = $self->{Socket}; # For local use only.
  262:     #  If we are local, we'll first try local auth mode, otherwise, we'll try
  263:     #  the ssl auth mode:
  264: 
  265:     Debug(8, "Connecting to $DnsName I am $LocalDns");
  266:     my $key;
  267:     my $keyfile;
  268:     if ($DnsName eq $LocalDns) {
  269: 	$self->{AuthenticationMode} = "local";
  270: 	($key, $keyfile)         = lonlocal::CreateKeyFile();
  271: 	Debug(8, "Local key: $key, stored in $keyfile");
  272: 	   
  273: 	#  If I can't make the key file fall back to insecure if 
  274: 	#  allowed...else give up right away.
  275: 
  276: 	if(!(defined $key) || !(defined $keyfile)) {
  277: 	    if($InsecureOk) {
  278: 		$self->{AuthenticationMode} = "insecure";
  279: 		$self->{TransactionRequest} = "init\n";
  280: 	    } 
  281: 	    else {
  282: 		$socket->close;
  283: 		return undef;
  284: 	    }
  285: 	}
  286: 	$self->{TransactionRequest} = "init:local:$keyfile\n";
  287: 	Debug(9, "Init string is init:local:$keyfile");
  288: 	if(!$self->CreateCipher($key)) { # Nothing's going our way...
  289: 	    $socket->close;
  290: 	    return undef;
  291: 	}
  292: 
  293:     }
  294:     else {
  295: 	#  Remote peer:  I'd like to do ssl, but if my host key or certificates
  296: 	#  are not all installed, my only choice is insecure, if that's 
  297: 	#  allowed:
  298: 
  299: 	my ($ca, $cert) = lonssl::CertificateFile;
  300: 	my $sslkeyfile  = lonssl::KeyFile;
  301: 
  302: 	if((defined $ca)  && (defined $cert) && (defined $sslkeyfile)) {
  303: 
  304: 	    $self->{AuthenticationMode} = "ssl";
  305: 	    $self->{TransactionRequest} = "init:ssl\n";
  306: 	} else {
  307: 	    if($InsecureOk) {		# Allowed to do insecure:
  308: 		$self->{AuthenticationMode} = "insecure";
  309: 		$self->{TransactionRequest} = "init\n";
  310: 	    }
  311: 	    else {		# Not allowed to do insecure...
  312: 		$socket->close;
  313: 		return undef;
  314: 	    }
  315: 	}
  316:     }
  317: 
  318:     #
  319:     # We're connected.  Set the state, and the events we'll accept:
  320:     #
  321:     $self->Transition("Connected");
  322:     $self->{InformWritable}     = 1;    # When  socket is writable we send init
  323:     $self->{Timeoutable}        = 1;    # Timeout allowed during startup negotiation. 
  324: 
  325:     
  326:     #
  327:     # Set socket to nonblocking I/O.
  328:     #
  329:     my $socket = $self->{Socket};
  330:     my $flags    = fcntl($socket, F_GETFL,0);
  331:     if(!$flags) {
  332: 	$socket->close;
  333: 	return undef;
  334:     }
  335:     if(!fcntl($socket, F_SETFL, $flags | O_NONBLOCK)) {
  336: 	$socket->close;
  337: 	return undef;
  338:     }
  339: 
  340:     # return the object :
  341: 
  342:     Debug(9, "Initial object state: ");
  343:     $self->Dump(9);
  344: 
  345:     return $self;
  346: }
  347: 
  348: =pod
  349: 
  350: =head2 Readable
  351: 
  352: This member should be called when the Socket becomes readable.  Until
  353: the read completes, action is state independet. Data are accepted into
  354: the TransactionReply until a newline character is received.  At that
  355: time actionis state dependent:
  356: 
  357: =item Connected
  358: 
  359: in this case we received challenge, the state changes to
  360: ChallengeReceived, and we initiate a send with the challenge response.
  361: 
  362: =item ReceivingReply
  363: 
  364: In this case a reply has been received for a transaction, the state
  365: goes to Idle and we disable write and read notification.
  366: 
  367: =item ChallengeReeived
  368: 
  369: we just got what should be an ok\n and the connection can now handle
  370: transactions.
  371: 
  372: =cut
  373: 
  374: sub Readable {
  375:     my $self    = shift;
  376:     my $socket  = $self->{Socket};
  377:     my $data    = '';
  378:     my $rv;
  379:     my $ConnectionMode = $self->{AuthenticationMode};
  380: 
  381:     if ($socket) {
  382: 	eval {
  383: 	    $rv = $socket->recv($data, POSIX::BUFSIZ, 0);
  384: 	}
  385:     } else {
  386: 	$self->Transition("Disconnected");
  387: 	return -1;
  388:     }
  389:     my $errno   = $! + 0;	             # Force numeric context.
  390: 
  391:     unless (defined($rv) && length $data) {# Read failed,
  392: 	if(($errno == POSIX::EWOULDBLOCK)   ||
  393: 	   ($errno == POSIX::EAGAIN)        ||
  394: 	   ($errno == POSIX::EINTR)) {
  395: 	    return 0;
  396: 	}
  397: 
  398: 	# Connection likely lost.
  399: 	&Debug(4, "Connection lost");
  400: 	$self->{TransactionRequest} = '';
  401: 	$socket->close();
  402: 	$self->Transition("Disconnected");
  403: 	return -1;
  404:     }
  405:     #  Append the data to the buffer.  And figure out if the read is done:
  406: 
  407:     &Debug(9,"Received from host: ".$data);
  408:     $self->{TransactionReply} .= $data;
  409:     if($self->{TransactionReply} =~ m/\n$/) {
  410: 	&Debug(8,"Readable End of line detected");
  411: 	
  412: 
  413: 	if ($self->{State}  eq "Initialized") { # We received the challenge:
  414: 	    #   Our init was replied to. What happens next depends both on
  415: 	    #  the actual init we sent (AuthenticationMode member data)
  416: 	    #  and the response:
  417: 	    #     AuthenticationMode == local:
  418: 	    #       Response ok:   The key has been exchanged and
  419: 	    #                      the key file destroyed. We can jump
  420: 	    #                      into setting the host and requesting the
  421: 	    #                      Later we'll also bypass key exchange.
  422: 	    #       Response digits: 
  423: 	    #                      Old style lond. Delete the keyfile.
  424: 	    #                      If allowed fall back to insecure mode.
  425: 	    #                      else close connection and fail.
  426: 	    #       Response other:
  427: 	    #                      Failed local auth 
  428: 	    #                      Close connection and fail.
  429: 	    #
  430: 	    #    AuthenticationMode == ssl:
  431: 	    #        Response ok:ssl
  432: 	    #        Response digits:
  433: 	    #        Response other:
  434: 	    #    Authentication mode == insecure
  435: 	    #        Response digits
  436: 	    #        Response other:
  437: 	    
  438: 	    my $Response = $self->{TransactionReply};
  439: 	    if($ConnectionMode eq "local") {
  440: 		if($Response =~ /^ok:local/) { #  Good local auth.
  441: 		    $self->ToVersionRequest();
  442: 		    return 0;
  443: 		}
  444: 		elsif ($Response =~/^[0-9]+/) {	# Old style lond.
  445: 		    return $self->CompleteInsecure();
  446: 
  447: 		}
  448: 		else {		                # Complete flop
  449: 		    &Debug(3, "init:local : unrecognized reply");
  450: 		    $self->Transition("Disconnected");
  451: 		    $socket->close;
  452: 		    return -1;
  453: 		}
  454: 	    }
  455: 	    elsif ($ConnectionMode eq "ssl") {
  456: 		if($Response =~ /^ok:ssl/) {     # Good ssl...
  457: 		    if($self->ExchangeKeysViaSSL()) { # Success skip to vsn stuff
  458: 			# Need to reset to non blocking:
  459: 
  460: 			my $flags = fcntl($socket, F_GETFL, 0);
  461: 			fcntl($socket, F_SETFL, $flags | O_NONBLOCK);
  462: 			$self->ToVersionRequest();
  463: 			return 0;
  464: 		    }
  465: 		    else {	         # Failed in ssl exchange.
  466: 			&Debug(3,"init:ssl failed key negotiation!");
  467: 			$self->Transition("Disconnected");
  468: 			$socket->close;
  469: 			return -1;
  470: 		    }
  471: 		} 
  472: 		elsif ($Response =~ /^[0-9]+/) { # Old style lond.
  473: 		    return $self->CompleteInsecure();
  474: 		}
  475: 		else {		                 # Complete flop
  476: 		}
  477: 	    }
  478: 	    elsif ($ConnectionMode eq "insecure") {
  479: 		if($self->{TransactionReply} eq "refused\n") {	# Remote doesn't have
  480: 		    
  481: 		    $self->Transition("Disconnected"); # in host tables.
  482: 		    $socket->close();
  483: 		    return -1;
  484: 
  485: 		}
  486: 		return $self->CompleteInsecure();
  487: 	    }
  488: 	    else {
  489: 		&Debug(1,"Authentication mode incorrect");
  490: 		die "BUG!!! LondConnection::Readable invalid authmode";
  491: 	    }
  492: 
  493: 
  494: 	}  elsif ($self->{State} eq "ChallengeReplied") {
  495: 	    if($self->{TransactionReply} ne "ok\n") {
  496: 		$self->Transition("Disconnected");
  497: 		$socket->close();
  498: 		return -1;
  499: 	    }
  500: 	    $self->ToVersionRequest();
  501: 	    return 0;
  502: 
  503: 	} elsif ($self->{State} eq "ReadingVersionString") {
  504: 	    $self->{LondVersion}       = chomp($self->{TransactionReply});
  505: 	    $self->Transition("SetHost");
  506: 	    $self->{InformReadable}    = 0;
  507: 	    $self->{InformWritable}    = 1;
  508: 	    my $peer = $self->{LoncapaHim};
  509: 	    $self->{TransactionRequest}= "sethost:$peer\n";
  510: 	    return 0;
  511: 	} elsif ($self->{State} eq "HostSet") { # should be ok.
  512: 	    if($self->{TransactionReply} ne "ok\n") {
  513: 		$self->Transition("Disconnected");
  514: 		$socket->close();
  515: 		return -1;
  516: 	    }
  517: 	    #  If the auth mode is insecure we must still
  518: 	    #  exchange session keys. Otherwise,
  519: 	    #  we can just transition to idle.
  520: 
  521: 	    if($ConnectionMode eq "insecure") {
  522: 		$self->Transition("RequestingKey");
  523: 		$self->{InformReadable}  = 0;
  524: 		$self->{InformWritable}  = 1;
  525: 		$self->{TransactionRequest} = "ekey\n";
  526: 		return 0;
  527: 	    }
  528: 	    else {
  529: 		$self->ToIdle();
  530: 		return 0;
  531: 	    }
  532: 	} elsif ($self->{State}  eq "ReceivingKey") {
  533: 	    my $buildkey = $self->{TransactionReply};
  534: 	    my $key = $self->{LoncapaHim}.$perlvar{'lonHostID'};
  535: 	    $key=~tr/a-z/A-Z/;
  536: 	    $key=~tr/G-P/0-9/;
  537: 	    $key=~tr/Q-Z/0-9/;
  538: 	    $key =$key.$buildkey.$key.$buildkey.$key.$buildkey;
  539: 	    $key               = substr($key,0,32);
  540: 	    if(!$self->CreateCipher($key)) {
  541: 		$self->Transition("Disconnected");
  542: 		$socket->close();
  543: 		return -1;
  544: 	    } else {
  545: 		$self->ToIdle();
  546: 		return 0;
  547: 	    }
  548: 	} elsif ($self->{State}  eq "ReceivingReply") {
  549: 
  550: 	    # If the data are encrypted, decrypt first.
  551: 
  552: 	    my $answer = $self->{TransactionReply};
  553: 	    if($answer =~ /^enc\:/) {
  554: 		$answer = $self->Decrypt($answer);
  555: 		$self->{TransactionReply} = $answer;
  556: 	    }
  557: 
  558: 	    # finish the transaction
  559: 
  560: 	    $self->ToIdle();
  561: 	    return 0;
  562: 	} elsif ($self->{State} eq "Disconnected") { # No connection.
  563: 	    return -1;
  564: 	} else {			# Internal error: Invalid state.
  565: 	    $self->Transition("Disconnected");
  566: 	    $socket->close();
  567: 	    return -1;
  568: 	}
  569:     }
  570: 
  571:     return 0;
  572:     
  573: }
  574: 
  575: 
  576: =pod
  577: 
  578: This member should be called when the Socket becomes writable.
  579: 
  580: The action is state independent. An attempt is made to drain the
  581: contents of the TransactionRequest member.  Once this is drained, we
  582: mark the object as waiting for readability.
  583: 
  584: Returns  0 if successful, or -1 if not.
  585: 
  586: =cut
  587: sub Writable {
  588:     my $self     = shift;		# Get reference to the object.
  589:     my $socket   = $self->{Socket};
  590:     my $nwritten;
  591:     if ($socket) {
  592: 	eval {
  593: 	    $nwritten = $socket->send($self->{TransactionRequest}, 0);
  594: 	}
  595:     } else {
  596: 	# For whatever reason, there's no longer a socket left.
  597: 
  598: 
  599: 	$self->Transition("Disconnected");
  600: 	return -1;
  601:     }
  602:     my $errno    = $! + 0;
  603:     unless (defined $nwritten) {
  604: 	if($errno != POSIX::EINTR) {
  605: 	    $self->Transition("Disconnected");
  606: 	    return -1;
  607: 	}
  608:       
  609:     }
  610:     if (($nwritten >= 0)                        ||
  611:         ($errno == POSIX::EWOULDBLOCK)    ||
  612: 	($errno == POSIX::EAGAIN)         ||
  613: 	($errno == POSIX::EINTR)          ||
  614: 	($errno ==  0)) {
  615: 	substr($self->{TransactionRequest}, 0, $nwritten) = ""; # rmv written part
  616:       if(length $self->{TransactionRequest} == 0) {
  617:          $self->{InformWritable} = 0;
  618:          $self->{InformReadable} = 1;
  619:          $self->{TransactionReply} = '';
  620:          #
  621:          # Figure out the next state:
  622:          #
  623:          if($self->{State} eq "Connected") {
  624:             $self->Transition("Initialized");
  625:          } elsif($self->{State} eq "ChallengeReceived") {
  626:             $self->Transition("ChallengeReplied");
  627:          } elsif($self->{State} eq "RequestingVersion") {
  628:             $self->Transition("ReadingVersionString");
  629:          } elsif ($self->{State} eq "SetHost") {
  630:             $self->Transition("HostSet");
  631:          } elsif($self->{State} eq "RequestingKey") {
  632:             $self->Transition("ReceivingKey");
  633: #            $self->{InformWritable} = 0;
  634: #            $self->{InformReadable} = 1;
  635: #            $self->{TransactionReply} = '';
  636:          } elsif ($self->{State} eq "SendingRequest") {
  637:             $self->Transition("ReceivingReply");
  638:             $self->{TimeoutRemaining} = $self->{TimeoutValue};
  639:          } elsif ($self->{State} eq "Disconnected") {
  640:             return -1;
  641:          }
  642:          return 0;
  643:       }
  644:    } else {			# The write failed (e.g. partner disconnected).
  645:       $self->Transition("Disconnected");
  646:       $socket->close();
  647:       return -1;
  648:    }
  649: 	
  650: }
  651: =pod
  652: 
  653: =head2 Tick
  654: 
  655:    Tick is called every time unit by the event framework.  It
  656: 
  657: =item 1 decrements the remaining timeout.
  658: 
  659: =item 2 If the timeout is zero, calls TimedOut indicating that the current operation timed out.
  660: 
  661: =cut
  662:     
  663: sub Tick {
  664:     my $self = shift;
  665:     $self->{TimeoutRemaining}--;
  666:     if ($self->{TimeoutRemaining} < 0) {
  667: 	$self->TimedOut();
  668:     }
  669: }
  670: 
  671: =pod
  672: 
  673: =head2 TimedOut
  674: 
  675: called on a timeout.  If the timeout callback is defined, it is called
  676: with $self as its parameters.
  677: 
  678: =cut
  679: 
  680: sub TimedOut  {
  681: 
  682:     my $self = shift;
  683:     if($self->{TimeoutCallback}) {
  684: 	my $callback = $self->{TimeoutCallback};
  685: 	my @args = ( $self);
  686: 	&$callback(@args);
  687:     }
  688: }
  689: 
  690: =pod
  691: 
  692: =head2 InitiateTransaction
  693: 
  694: Called to initiate a transaction.  A transaction can only be initiated
  695: when the object is idle... otherwise an error is returned.  A
  696: transaction consists of a request to the server that will have a
  697: reply.  This member sets the request data in the TransactionRequest
  698: member, makes the state SendingRequest and sets the data to allow a
  699: timout, and to request writability notification.
  700: 
  701: =cut
  702: 
  703: sub InitiateTransaction {
  704: 
  705:     my ($self, $data) = @_;
  706: 
  707:     Debug(1, "initiating transaction: ".$data);
  708:     if($self->{State} ne "Idle") {
  709: 	Debug(0," .. but not idle here\n");
  710: 	return -1;		# Error indicator.
  711:     }
  712:     # if the transaction is to be encrypted encrypt the data:
  713: 
  714:     if($data =~ /^encrypt\:/) {
  715: 	$data = $self->Encrypt($data);
  716:     }
  717: 
  718:     # Setup the trasaction
  719: 
  720:     $self->{TransactionRequest} = $data;
  721:     $self->{TransactionReply}   = "";
  722:     $self->{InformWritable}     = 1;
  723:     $self->{InformReadable}     = 0;
  724:     $self->{Timeoutable}        = 1;
  725:     $self->{TimeoutRemaining}   = $self->{TimeoutValue};
  726:     $self->Transition("SendingRequest");
  727: }
  728: 
  729: 
  730: =pod
  731: 
  732: =head2 SetStateTransitionCallback
  733: 
  734: Sets a callback for state transitions.  Returns a reference to any
  735: prior established callback, or undef if there was none:
  736: 
  737: =cut
  738: 
  739: sub SetStateTransitionCallback {
  740:     my $self        = shift;
  741:     my $oldCallback = $self->{TransitionCallback};
  742:     $self->{TransitionCallback} = shift;
  743:     return $oldCallback;
  744: }
  745: 
  746: =pod
  747: 
  748: =head2 SetTimeoutCallback
  749: 
  750: Sets the timeout callback.  Returns a reference to any prior
  751: established callback or undef if there was none.
  752: 
  753: =cut
  754: 
  755: sub SetTimeoutCallback {
  756: 
  757:     my ($self, $callback) = @_;
  758: 
  759:     my $oldCallback          = $self->{TimeoutCallback};
  760:     $self->{TimeoutCallback} = $callback;
  761:     return $oldCallback;
  762: }
  763: 
  764: =pod
  765: 
  766: =head2 Shutdown:
  767: 
  768: Shuts down the socket.
  769: 
  770: =cut
  771: 
  772: sub Shutdown {
  773:     my $self = shift;
  774:     my $socket = $self->GetSocket();
  775:     Debug(5,"socket is -$socket-");
  776:     if ($socket) {
  777: 	# Ask lond to exit too.  Non blocking so
  778: 	# there is no cost for failure.
  779: 	eval {
  780: 	    $socket->send("exit\n", 0);
  781: 	    $socket->shutdown(2);
  782: 	}
  783:     }
  784: }
  785: 
  786: =pod
  787: 
  788: =head2 GetState
  789: 
  790: selector for the object state.
  791: 
  792: =cut
  793: 
  794: sub GetState {
  795:     my $self = shift;
  796:     return $self->{State};
  797: }
  798: 
  799: =pod
  800: 
  801: =head2 GetSocket
  802: 
  803: selector for the object socket.
  804: 
  805: =cut
  806: 
  807: sub GetSocket {
  808:     my $self  = shift;
  809:     return $self->{Socket};
  810: }
  811: 
  812: 
  813: =pod
  814: 
  815: =head2 WantReadable
  816: 
  817: Return the state of the flag that indicates the object wants to be
  818: called when readable.
  819: 
  820: =cut
  821: 
  822: sub WantReadable {
  823:     my   $self = shift;
  824: 
  825:     return $self->{InformReadable};
  826: }
  827: 
  828: =pod
  829: 
  830: =head2 WantWritable
  831: 
  832: Return the state of the flag that indicates the object wants write
  833: notification.
  834: 
  835: =cut
  836: 
  837: sub WantWritable {
  838:     my $self = shift;
  839:     return $self->{InformWritable};
  840: }
  841: 
  842: =pod
  843: 
  844: =head2 WantTimeout
  845: 
  846: return the state of the flag that indicates the object wants to be
  847: informed of timeouts.
  848: 
  849: =cut
  850: 
  851: sub WantTimeout {
  852:     my $self = shift;
  853:     return $self->{Timeoutable};
  854: }
  855: 
  856: =pod
  857: 
  858: =head2 GetReply
  859: 
  860: Returns the reply from the last transaction.
  861: 
  862: =cut
  863: 
  864: sub GetReply {
  865:     my $self = shift;
  866:     return $self->{TransactionReply};
  867: }
  868: 
  869: =pod
  870: 
  871: =head2 Encrypt
  872: 
  873: Returns the encrypted version of the command string.
  874: 
  875: The command input string is of the form:
  876: 
  877:   encrypt:command
  878: 
  879: The output string can be directly sent to lond as it is of the form:
  880: 
  881:   enc:length:<encodedrequest>
  882: 
  883: =cut
  884: 
  885: sub Encrypt {
  886:     
  887:     my ($self, $request) = @_;
  888: 
  889:    
  890:     # Split the encrypt: off the request and figure out it's length.
  891:     # the cipher works in blocks of 8 bytes.
  892: 
  893:     my $cmd = $request;
  894:     $cmd    =~ s/^encrypt\://;	# strip off encrypt:
  895:     chomp($cmd);		# strip off trailing \n
  896:     my     $length=length($cmd);	# Get the string length.
  897:     $cmd .= "         ";	# Pad with blanks so we can fill out a block.
  898: 
  899:     # encrypt the request in 8 byte chunks to create the encrypted
  900:     # output request.
  901: 
  902:     my $Encoded = '';
  903:     for(my $index = 0; $index <= $length; $index += 8) {
  904: 	$Encoded .= 
  905: 	    unpack("H16", 
  906: 		   $self->{Cipher}->encrypt(substr($cmd, 
  907: 						   $index, 8)));
  908:     }
  909: 
  910:     # Build up the answer as enc:length:$encrequest.
  911: 
  912:     $request = "enc:$length:$Encoded\n";
  913:     return $request;
  914:     
  915:     
  916: }
  917: 
  918: =pod
  919: 
  920: =head2 Decrypt
  921: 
  922: Decrypt a response from the server.  The response is in the form:
  923: 
  924:  enc:<length>:<encrypted data>
  925: 
  926: =cut
  927: 
  928: sub Decrypt {
  929: 
  930:     my ($self, $encrypted) = @_;
  931: 
  932:     #  Bust up the response into length, and encryptedstring:
  933: 
  934:     my ($enc, $length, $EncryptedString) = split(/:/,$encrypted);
  935:     chomp($EncryptedString);
  936: 
  937:     # Decode the data in 8 byte blocks.  The string is encoded
  938:     # as hex digits so there are two characters per byte:
  939: 
  940:     my $decrypted = "";
  941:     for(my $index = 0; $index < length($EncryptedString);
  942: 	$index += 16) {
  943: 	$decrypted .= $self->{Cipher}->decrypt(
  944: 				    pack("H16",
  945: 					 substr($EncryptedString,
  946: 						$index, 
  947: 						16)));
  948:     }
  949:     #  the answer may have trailing pads to fill out a block.
  950:     #  $length tells us the actual length of the decrypted string:
  951: 
  952:     $decrypted = substr($decrypted, 0, $length);
  953: 
  954:     return $decrypted;
  955: 
  956: }
  957: # ToIdle
  958: #     Called to transition to idle... done enough it's worth subbing
  959: #     off to ensure it's always done right!!
  960: #
  961: sub ToIdle {
  962:     my $self   = shift;
  963: 
  964:     $self->Transition("Idle");
  965:     $self->{InformWritiable} = 0;
  966:     $self->{InformReadable}  = 0;
  967:     $self->{Timeoutable}     = 0;
  968: }
  969: 
  970: #  ToVersionRequest
  971: #    Called to transition to "RequestVersion"  also done a few times
  972: #    so worth subbing out.
  973: #
  974: sub ToVersionRequest {
  975:     my $self   = shift;
  976:     
  977:     $self->Transition("RequestingVersion");
  978:     $self->{InformReadable}   = 0;
  979:     $self->{InformWritable}   = 1;
  980:     $self->{TransactionRequest} = "version\n";
  981:     
  982: }
  983: #
  984: #  CreateCipher
  985: #    Given a cipher key stores the key in the object context,
  986: #    creates the cipher object, (stores that in object context),
  987: #    This is done a couple of places, so it's worth factoring it out.
  988: #
  989: # Parameters:
  990: #    (self)
  991: #    key - The Cipher key.
  992: #
  993: # Returns:
  994: #    0   - Failure to create IDEA cipher.
  995: #    1   - Success.
  996: #
  997: sub CreateCipher {
  998:     my ($self, $key)   = @_;	# According to coding std.
  999: 
 1000:     $self->{CipherKey} = $key; # Save the text key...
 1001:     my $packedkey = pack ("H32", $key);
 1002:     my $cipher            = new IDEA $packedkey;
 1003:     if($cipher) {
 1004: 	$self->{Cipher} = $cipher;
 1005: 	Debug("Cipher created  dumping socket: ");
 1006: 	$self->Dump(9);
 1007: 	return 1;
 1008:     }
 1009:     else {
 1010: 	return 0;
 1011:     }
 1012: }
 1013: # ExchangeKeysViaSSL
 1014: #     Called to do cipher key exchange via SSL.
 1015: #     The socket is promoted to an SSL socket. If that's successful,
 1016: #     we read out cipher key through the socket and create an IDEA
 1017: #     cipher object.
 1018: # Parameters:
 1019: #    (self)
 1020: # Returns:
 1021: #      true    - Success.
 1022: #      false   - Failure.
 1023: #
 1024: # Assumptions:
 1025: #  1.   The ssl session setup has timeout logic built in so we don't
 1026: #     have to worry about DOS attacks at that stage.
 1027: #  2.   If the ssl session gets set up we are talking to a legitimate
 1028: #     lond so again we don't have to worry about DOS attacks.
 1029: #  All this allows us just to call 
 1030: sub ExchangeKeysViaSSL {
 1031:     my $self   = shift;
 1032:     my $socket = $self->{Socket};
 1033: 
 1034:     #  Get our signed certificate, the certificate authority's 
 1035:     #  certificate and our private key file.  All of these
 1036:     #  are needed to create the ssl connection.
 1037: 
 1038:     my ($SSLCACertificate,
 1039: 	$SSLCertificate) = lonssl::CertificateFile();
 1040:     my $SSLKey             = lonssl::KeyFile();
 1041: 
 1042:     #  Promote our connection to ssl and read the key from lond.
 1043: 
 1044:     my $SSLSocket = lonssl::PromoteClientSocket($socket,
 1045: 						$SSLCACertificate,
 1046: 						$SSLCertificate,
 1047: 						$SSLKey);
 1048:     if(defined $SSLSocket) {
 1049: 	my $key  = <$SSLSocket>;
 1050: 	lonssl::Close($SSLSocket);
 1051: 	if($key) {
 1052: 	    chomp($key);	# \n is not part of the key.
 1053: 	    return $self->CreateCipher($key);
 1054: 	} 
 1055: 	else {
 1056: 	    Debug(3, "Failed to read ssl key");
 1057: 	    return 0;
 1058: 	}
 1059:     }
 1060:     else {
 1061: 	# Failed!!
 1062: 	Debug(3, "Failed to negotiate SSL connection!");
 1063: 	return 0;
 1064:     }
 1065:     # should not get here
 1066:     return 0;
 1067: 
 1068: }
 1069: 
 1070: 
 1071: 
 1072: #
 1073: #  CompleteInsecure:
 1074: #      This function is called to initiate the completion of
 1075: #      insecure challenge response negotiation.
 1076: #      To do this, we copy the challenge string to the transaction
 1077: #      request, flip to writability and state transition to 
 1078: #      ChallengeReceived..
 1079: #      All this is only possible if InsecureOk is true.
 1080: # Parameters:
 1081: #      (self)    - This object's context hash.
 1082: #  Return:
 1083: #      0   - Ok to transition.
 1084: #     -1   - Not ok to transition (InsecureOk not ok).
 1085: #
 1086: sub CompleteInsecure {
 1087:     my $self = shift;
 1088:     if($InsecureOk) {
 1089: 	$self->{AuthenticationMode} = "insecure";
 1090: 	&Debug(8," Transition out of Initialized:insecure");
 1091: 	$self->{TransactionRequest} = $self->{TransactionReply};
 1092: 	$self->{InformWritable}     = 1;
 1093: 	$self->{InformReadable}     = 0;
 1094: 	$self->Transition("ChallengeReceived");
 1095: 	$self->{TimeoutRemaining}   = $self->{TimeoutValue};
 1096: 	return 0;
 1097: 	
 1098: 	
 1099:     }
 1100:     else {
 1101: 	&Debug(3, "Insecure key negotiation disabled!");
 1102: 	my $socket = $self->{Socket};
 1103: 	$socket->close;
 1104: 	return -1;
 1105:     }
 1106: }
 1107: 
 1108: =pod
 1109: 
 1110: =head2 GetHostIterator
 1111: 
 1112: Returns a hash iterator to the host information.  Each get from 
 1113: this iterator returns a reference to an array that contains 
 1114: information read from the hosts configuration file.  Array elements
 1115: are used as follows:
 1116: 
 1117:  [0]   - LonCapa host name.
 1118:  [1]   - LonCapa domain name.
 1119:  [2]   - Loncapa role (e.g. library or access).
 1120:  [3]   - DNS name server hostname.
 1121:  [4]   - IP address (result of e.g. nslookup [3]).
 1122:  [5]   - Maximum connection count.
 1123:  [6]   - Idle timeout for reducing connection count.
 1124:  [7]   - Minimum connection count.
 1125: 
 1126: =cut
 1127: 
 1128: sub GetHostIterator {
 1129: 
 1130:     return HashIterator->new(\%hostshash);    
 1131: }
 1132: 
 1133: ###########################################################
 1134: #
 1135: #  The following is an unashamed kludge that is here to
 1136: # allow LondConnection to be used outside of the
 1137: # loncapa environment (e.g. by lonManage).
 1138: # 
 1139: #   This is a textual inclusion of pieces of the
 1140: #   Configuration.pm module.
 1141: #
 1142: 
 1143: 
 1144: my $confdir='/etc/httpd/conf/';
 1145: 
 1146: # ------------------- Subroutine read_conf: read LON-CAPA server configuration.
 1147: # This subroutine reads PerlSetVar values out of specified web server
 1148: # configuration files.
 1149: sub read_conf
 1150:   {
 1151:     my (@conf_files)=@_;
 1152:     my %perlvar;
 1153:     foreach my $filename (@conf_files,'loncapa_apache.conf')
 1154:       {
 1155: 	  if($DebugLevel > 3) {
 1156: 	      print STDERR ("Going to read $confdir.$filename\n");
 1157: 	  }
 1158: 	open(CONFIG,'<'.$confdir.$filename) or
 1159: 	    die("Can't read $confdir$filename");
 1160: 	while (my $configline=<CONFIG>)
 1161: 	  {
 1162: 	    if ($configline =~ /^[^\#]*PerlSetVar/)
 1163: 	      {
 1164: 		my ($unused,$varname,$varvalue)=split(/\s+/,$configline);
 1165: 		chomp($varvalue);
 1166: 		$perlvar{$varname}=$varvalue;
 1167: 	      }
 1168: 	  }
 1169: 	close(CONFIG);
 1170:       }
 1171:     if($DebugLevel > 3) {
 1172: 	print STDERR "Dumping perlvar:\n";
 1173: 	foreach my $var (keys %perlvar) {
 1174: 	    print STDERR "$var = $perlvar{$var}\n";
 1175: 	}
 1176:     }
 1177:     my $perlvarref=\%perlvar;
 1178:     return $perlvarref;
 1179: }
 1180: 
 1181: #---------------------- Subroutine read_hosts: Read a LON-CAPA hosts.tab
 1182: # formatted configuration file.
 1183: #
 1184: my $RequiredCount = 5;		# Required item count in hosts.tab.
 1185: my $DefaultMaxCon = 5;		# Default value for maximum connections.
 1186: my $DefaultIdle   = 1000;       # Default connection idle time in seconds.
 1187: my $DefaultMinCon = 0;          # Default value for minimum connections.
 1188: 
 1189: sub read_hosts {
 1190:     my $Filename = shift;
 1191:     my %HostsTab;
 1192:     
 1193:    open(CONFIG,'<'.$Filename) or die("Can't read $Filename");
 1194:     while (my $line = <CONFIG>) {
 1195: 	if (!($line =~ /^\s*\#/)) {
 1196: 	    my @items = split(/:/, $line);
 1197: 	    if(scalar @items >= $RequiredCount) {
 1198: 		if (scalar @items == $RequiredCount) { # Only required items:
 1199: 		    $items[$RequiredCount] = $DefaultMaxCon;
 1200: 		}
 1201: 		if(scalar @items == $RequiredCount + 1) { # up through maxcon.
 1202: 		    $items[$RequiredCount+1] = $DefaultIdle;
 1203: 		}
 1204: 		if(scalar @items == $RequiredCount + 2) { # up through idle.
 1205: 		    $items[$RequiredCount+2] = $DefaultMinCon;
 1206: 		}
 1207: 		{
 1208: 		    my @list = @items; # probably not needed but I'm unsure of 
 1209: 		    # about the scope of item so...
 1210: 		    $HostsTab{$list[0]} = \@list; 
 1211: 		}
 1212: 	    }
 1213: 	}
 1214:     }
 1215:     close(CONFIG);
 1216:     my $hostref = \%HostsTab;
 1217:     return ($hostref);
 1218: }
 1219: #
 1220: #   Get the version of our peer.  Note that this is only well
 1221: #   defined if the state machine has hit the idle state at least
 1222: #   once (well actually if it has transitioned out of 
 1223: #   ReadingVersionString   The member data LondVersion is returned.
 1224: #
 1225: sub PeerVersion {
 1226:    my $self = shift;
 1227:    
 1228:    return $self->{LondVersion};
 1229: }
 1230: 
 1231: 1;
 1232: 
 1233: =pod
 1234: 
 1235: =head1 Theory
 1236: 
 1237: The lond object is a state machine.  It lives through the following states:
 1238: 
 1239: =item Connected:
 1240: 
 1241: a TCP connection has been formed, but the passkey has not yet been
 1242: negotiated.
 1243: 
 1244: =item Initialized:
 1245: 
 1246: "init" sent.
 1247: 
 1248: =item ChallengeReceived:
 1249: 
 1250: lond sent its challenge to us.
 1251: 
 1252: =item ChallengeReplied:
 1253: 
 1254: We replied to lond's challenge waiting for lond's ok.
 1255: 
 1256: =item RequestingKey:
 1257: 
 1258: We are requesting an encryption key.
 1259: 
 1260: =item ReceivingKey:
 1261: 
 1262: We are receiving an encryption key.
 1263: 
 1264: =item Idle:
 1265: 
 1266: Connection was negotiated but no requests are active.
 1267: 
 1268: =item SendingRequest:
 1269: 
 1270: A request is being sent to the peer.
 1271: 
 1272: =item ReceivingReply:
 1273: 
 1274: Waiting for an entire reply from the peer.
 1275: 
 1276: =item Disconnected:
 1277: 
 1278: For whatever reason, the connection was dropped.
 1279: 
 1280: When we need to be writing data, we have a writable event. When we
 1281: need to be reading data, a readable event established.  Events
 1282: dispatch through the class functions Readable and Writable, and the
 1283: watcher contains a reference to the associated object to allow object
 1284: context to be reached.
 1285: 
 1286: =head2 Member data.
 1287: 
 1288: =item Host
 1289: 
 1290: Host socket is connected to.
 1291: 
 1292: =item Port
 1293: 
 1294: The port the remote lond is listening on.
 1295: 
 1296: =item Socket
 1297: 
 1298: Socket open on the connection.
 1299: 
 1300: =item State
 1301: 
 1302: The current state.
 1303: 
 1304: =item AuthenticationMode
 1305: 
 1306: How authentication is being done. This can be any of:
 1307: 
 1308:     o local - Authenticate via a key exchanged in a file.
 1309:     o ssl   - Authenticate via a key exchaned through a temporary ssl tunnel.
 1310:     o insecure - Exchange keys in an insecure manner.
 1311: 
 1312: insecure is only allowed if the configuration parameter loncAllowInsecure 
 1313: is nonzero.
 1314: 
 1315: =item TransactionRequest
 1316: 
 1317: The request being transmitted.
 1318: 
 1319: =item TransactionReply
 1320: 
 1321: The reply being received from the transaction.
 1322: 
 1323: =item InformReadable
 1324: 
 1325: True if we want to be called when socket is readable.
 1326: 
 1327: =item InformWritable
 1328: 
 1329: True if we want to be informed if the socket is writable.
 1330: 
 1331: =item Timeoutable
 1332: 
 1333: True if the current operation is allowed to timeout.
 1334: 
 1335: =item TimeoutValue
 1336: 
 1337: Number of seconds in the timeout.
 1338: 
 1339: =item TimeoutRemaining
 1340: 
 1341: Number of seconds left in the timeout.
 1342: 
 1343: =item CipherKey
 1344: 
 1345: The key that was negotiated with the peer.
 1346: 
 1347: =item Cipher
 1348: 
 1349: The cipher obtained via the key.
 1350: 
 1351: 
 1352: =head2 The following are callback like members:
 1353: 
 1354: =item Tick:
 1355: 
 1356: Called in response to a timer tick. Used to managed timeouts etc.
 1357: 
 1358: =item Readable:
 1359: 
 1360: Called when the socket becomes readable.
 1361: 
 1362: =item Writable:
 1363: 
 1364: Called when the socket becomes writable.
 1365: 
 1366: =item TimedOut:
 1367: 
 1368: Called when a timed operation timed out.
 1369: 
 1370: 
 1371: =head2 The following are operational member functions.
 1372: 
 1373: =item InitiateTransaction:
 1374: 
 1375: Called to initiate a new transaction
 1376: 
 1377: =item SetStateTransitionCallback:
 1378: 
 1379: Called to establish a function that is called whenever the object goes
 1380: through a state transition.  This is used by The client to manage the
 1381: work flow for the object.
 1382: 
 1383: =item SetTimeoutCallback:
 1384: 
 1385: Set a function to be called when a transaction times out.  The
 1386: function will be called with the object as its sole parameter.
 1387: 
 1388: =item Encrypt:
 1389: 
 1390: Encrypts a block of text according to the cipher negotiated with the
 1391: peer (assumes the text is a command).
 1392: 
 1393: =item Decrypt:
 1394: 
 1395: Decrypts a block of text according to the cipher negotiated with the
 1396: peer (assumes the block was a reply.
 1397: 
 1398: =item Shutdown:
 1399: 
 1400: Shuts off the socket.
 1401: 
 1402: =head2 The following are selector member functions:
 1403: 
 1404: =item GetState:
 1405: 
 1406: Returns the current state
 1407: 
 1408: =item GetSocket:
 1409: 
 1410: Gets the socekt open on the connection to lond.
 1411: 
 1412: =item WantReadable:
 1413: 
 1414: true if the current state requires a readable event.
 1415: 
 1416: =item WantWritable:
 1417: 
 1418: true if the current state requires a writable event.
 1419: 
 1420: =item WantTimeout:
 1421: 
 1422: true if the current state requires timeout support.
 1423: 
 1424: =item GetHostIterator:
 1425: 
 1426: Returns an iterator into the host file hash.
 1427: 
 1428: =cut

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