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