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