File:  [LON-CAPA] / loncom / Attic / lonc
Revision 1.29: download - view: text, annotated - select for diffs
Mon Feb 25 15:48:11 2002 UTC (22 years, 2 months ago) by www
Branches: MAIN
CVS tags: HEAD
Attempt to be able to close connections from inside of lonnet UNTESTED

    1: #!/usr/bin/perl
    2: 
    3: # The LearningOnline Network
    4: # lonc - LON TCP-Client Domain-Socket-Server
    5: # provides persistent TCP connections to the other servers in the network
    6: # through multiplexed domain sockets
    7: #
    8: # $Id: lonc,v 1.29 2002/02/25 15:48:11 www Exp $
    9: #
   10: # Copyright Michigan State University Board of Trustees
   11: #
   12: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   13: #
   14: # LON-CAPA is free software; you can redistribute it and/or modify
   15: # it under the terms of the GNU General Public License as published by
   16: # the Free Software Foundation; either version 2 of the License, or
   17: # (at your option) any later version.
   18: #
   19: # LON-CAPA is distributed in the hope that it will be useful,
   20: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   21: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   22: # GNU General Public License for more details.
   23: #
   24: # You should have received a copy of the GNU General Public License
   25: # along with LON-CAPA; if not, write to the Free Software
   26: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   27: #
   28: # /home/httpd/html/adm/gpl.txt
   29: #
   30: # http://www.lon-capa.org/
   31: #
   32: # PID in subdir logs/lonc.pid
   33: # kill kills
   34: # HUP restarts
   35: # USR1 tries to open connections again
   36: 
   37: # 6/4/99,6/5,6/7,6/8,6/9,6/10,6/11,6/12,7/14,7/19,
   38: # 10/8,10/9,10/15,11/18,12/22,
   39: # 2/8,7/25 Gerd Kortemeyer
   40: # 12/05 Scott Harrison
   41: # 12/05 Gerd Kortemeyer
   42: # YEAR=2001
   43: # 01/10/01 Scott Harrison
   44: # 03/14/01,03/15,06/12,11/26,11/27,11/28 Gerd Kortemeyer
   45: # 12/20 Scott Harrison
   46: # YEAR=2002
   47: # 2/19/02,02/22/02,02/25/02 Gerd Kortemeyer
   48: # 
   49: # based on nonforker from Perl Cookbook
   50: # - server who multiplexes without forking
   51: 
   52: use POSIX;
   53: use IO::Socket;
   54: use IO::Select;
   55: use IO::File;
   56: use Socket;
   57: use Fcntl;
   58: use Tie::RefHash;
   59: use Crypt::IDEA;
   60: use Net::Ping;
   61: use LWP::UserAgent();
   62: 
   63: my $status='';
   64: my $lastlog='';
   65: 
   66: # grabs exception and records it to log before exiting
   67: sub catchexception {
   68:     my ($signal)=@_;
   69:     $SIG{QUIT}='DEFAULT';
   70:     $SIG{__DIE__}='DEFAULT';
   71:     chomp($signal);
   72:     &logthis("<font color=red>CRITICAL: "
   73:      ."ABNORMAL EXIT. Child $$ for server [$wasserver] died through "
   74:      ."\"$signal\" with parameter [$@]</font>");
   75:     die($@);
   76: }
   77: 
   78: $childmaxattempts=5;
   79: 
   80: # -------------------------------------- Routines to see if other box available
   81: 
   82: sub online {
   83:     my $host=shift;
   84:     &status("Pinging ".$host);
   85:     my $p=Net::Ping->new("tcp",20);
   86:     my $online=$p->ping("$host");
   87:     $p->close();
   88:     undef ($p);
   89:     return $online;
   90: }
   91: 
   92: sub connected {
   93:     my ($local,$remote)=@_;
   94:     &status("Checking connection $local to $remote");
   95:     $local=~s/\W//g;
   96:     $remote=~s/\W//g;
   97: 
   98:     unless ($hostname{$local}) { return 'local_unknown'; }
   99:     unless ($hostname{$remote}) { return 'remote_unknown'; }
  100: 
  101:     unless (&online($hostname{$local})) { return 'local_offline'; }
  102: 
  103:     my $ua=new LWP::UserAgent;
  104:     
  105:     my $request=new HTTP::Request('GET',
  106:       "http://".$hostname{$local}.'/cgi-bin/ping.pl?'.$remote);
  107: 
  108:     my $response=$ua->request($request);
  109: 
  110:     unless ($response->is_success) { return 'local_error'; }
  111: 
  112:     my $reply=$response->content;
  113:     $reply=(split("\n",$reply))[0];
  114:     $reply=~s/\W//g;
  115:     if ($reply ne $remote) { return $reply; }
  116:     return 'ok';
  117: }
  118: 
  119: 
  120: # -------------------------------- Set signal handlers to record abnormal exits
  121: 
  122: &status("Init exception handlers");
  123: $SIG{QUIT}=\&catchexception;
  124: $SIG{__DIE__}=\&catchexception;
  125: 
  126: # ------------------------------------ Read httpd access.conf and get variables
  127: &status("Read access.conf");
  128: open (CONFIG,"/etc/httpd/conf/access.conf") || die "Can't read access.conf";
  129: 
  130: while ($configline=<CONFIG>) {
  131:     if ($configline =~ /PerlSetVar/) {
  132: 	my ($dummy,$varname,$varvalue)=split(/\s+/,$configline);
  133:         chomp($varvalue);
  134:         $perlvar{$varname}=$varvalue;
  135:     }
  136: }
  137: close(CONFIG);
  138: 
  139: # ----------------------------- Make sure this process is running from user=www
  140: &status("Check user ID");
  141: my $wwwid=getpwnam('www');
  142: if ($wwwid!=$<) {
  143:    $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
  144:    $subj="LON: $perlvar{'lonHostID'} User ID mismatch";
  145:    system("echo 'User ID mismatch.  lonc must be run as user www.' |\
  146:  mailto $emailto -s '$subj' > /dev/null");
  147:    exit 1;
  148: }
  149: 
  150: # --------------------------------------------- Check if other instance running
  151: 
  152: my $pidfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
  153: 
  154: if (-e $pidfile) {
  155:    my $lfh=IO::File->new("$pidfile");
  156:    my $pide=<$lfh>;
  157:    chomp($pide);
  158:    if (kill 0 => $pide) { die "already running"; }
  159: }
  160: 
  161: # ------------------------------------------------------------- Read hosts file
  162: 
  163: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
  164: 
  165: while ($configline=<CONFIG>) {
  166:     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
  167:     chomp($ip);
  168:     if ($ip) {
  169:      $hostip{$id}=$ip;
  170:      $hostname{$id}=$name;
  171:     }
  172: }
  173: 
  174: close(CONFIG);
  175: 
  176: # -------------------------------------------------------- Routines for forking
  177: 
  178: %children               = ();       # keys are current child process IDs,
  179:                                     # values are hosts
  180: %childpid               = ();       # the other way around
  181: 
  182: %childatt               = ();       # number of attempts to start server
  183:                                     # for ID
  184: 
  185: sub REAPER {                        # takes care of dead children
  186:     $SIG{CHLD} = \&REAPER;
  187:     my $pid = wait;
  188:     my $wasserver=$children{$pid};
  189:     &logthis("<font color=red>CRITICAL: "
  190:      ."Child $pid for server $wasserver died ($childatt{$wasserver})</font>");
  191:     delete $children{$pid};
  192:     delete $childpid{$wasserver};
  193:     my $port = "$perlvar{'lonSockDir'}/$wasserver";
  194:     unlink($port);
  195: }
  196: 
  197: sub hangup {
  198:     foreach (keys %children) {
  199:         $wasserver=$children{$_};
  200:         &status("Closing $wasserver");
  201:         &logthis('Closing '.$wasserver.': '.&subreply('exit',$wasserver));
  202:         &status("Kill PID $_ for $wasserver");
  203: 	kill ('INT',$_);
  204:     }
  205: }
  206: 
  207: sub HUNTSMAN {                      # signal handler for SIGINT
  208:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
  209:     &hangup();
  210:     my $execdir=$perlvar{'lonDaemons'};
  211:     unlink("$execdir/logs/lonc.pid");
  212:     &logthis("<font color=red>CRITICAL: Shutting down</font>");
  213:     exit;                           # clean up with dignity
  214: }
  215: 
  216: sub HUPSMAN {                      # signal handler for SIGHUP
  217:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
  218:     &hangup();
  219:     &logthis("<font color=red>CRITICAL: Restarting</font>");
  220:     unlink("$execdir/logs/lonc.pid");
  221:     my $execdir=$perlvar{'lonDaemons'};
  222:     exec("$execdir/lonc");         # here we go again
  223: }
  224: 
  225: sub checkchildren {
  226:     &initnewstatus();
  227:     &logstatus();
  228:     &logthis('Going to check on the children');
  229:     foreach (sort keys %children) {
  230: 	sleep 1;
  231:         unless (kill 'USR1' => $_) {
  232: 	    &logthis ('<font color=red>CRITICAL: Child '.$_.' is dead</font>');
  233:             &logstatus($$.' is dead');
  234:         } 
  235:     }
  236: }
  237: 
  238: sub USRMAN {
  239:     &logthis("USR1: Trying to establish connections again");
  240:     %childatt=();
  241:     &checkchildren();
  242: }
  243: 
  244: # -------------------------------------------------- Non-critical communication
  245: sub subreply { 
  246:  my ($cmd,$server)=@_;
  247:  my $answer='';
  248:  if ($server ne $perlvar{'lonHostID'}) { 
  249:     my $peerfile="$perlvar{'lonSockDir'}/$server";
  250:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
  251:                                       Type    => SOCK_STREAM,
  252:                                       Timeout => 10)
  253:        or return "con_lost";
  254: 
  255: 
  256:     $SIG{ALRM}=sub { die "timeout" };
  257:     $SIG{__DIE__}='DEFAULT';
  258:     eval {
  259:      alarm(10);
  260:      print $sclient "$cmd\n";
  261:      $answer=<$sclient>;
  262:      chomp($answer);
  263:      alarm(0);
  264:     };
  265:     if ((!$answer) || ($@=~/timeout/)) { $answer="con_lost"; }
  266:     $SIG{ALRM}='DEFAULT';
  267:     $SIG{__DIE__}=\&catchexception;
  268:  } else { $answer='self_reply'; }
  269:  return $answer;
  270: }
  271: 
  272: # --------------------------------------------------------------------- Logging
  273: 
  274: sub logthis {
  275:     my $message=shift;
  276:     my $execdir=$perlvar{'lonDaemons'};
  277:     my $fh=IO::File->new(">>$execdir/logs/lonc.log");
  278:     my $now=time;
  279:     my $local=localtime($now);
  280:     $lastlog=$local.': '.$message;
  281:     print $fh "$local ($$) [$status]: $message\n";
  282: }
  283: 
  284: 
  285: sub logperm {
  286:     my $message=shift;
  287:     my $execdir=$perlvar{'lonDaemons'};
  288:     my $now=time;
  289:     my $local=localtime($now);
  290:     my $fh=IO::File->new(">>$execdir/logs/lonnet.perm.log");
  291:     print $fh "$now:$message:$local\n";
  292: }
  293: # ------------------------------------------------------------------ Log status
  294: 
  295: sub logstatus {
  296:     my $docdir=$perlvar{'lonDocRoot'};
  297:     my $fh=IO::File->new(">>$docdir/lon-status/loncstatus.txt");
  298:     print $fh $$."\t".$status."\t".$lastlog."\n";
  299: }
  300: 
  301: sub initnewstatus {
  302:     my $docdir=$perlvar{'lonDocRoot'};
  303:     my $fh=IO::File->new(">$docdir/lon-status/loncstatus.txt");
  304:     my $now=time;
  305:     my $local=localtime($now);
  306:     print $fh "LONC status $local - parent $$\n\n";
  307: }
  308: 
  309: # -------------------------------------------------------------- Status setting
  310: 
  311: sub status {
  312:     my $what=shift;
  313:     my $now=time;
  314:     my $local=localtime($now);
  315:     $status=$local.': '.$what;
  316: }
  317: 
  318: 
  319: # ---------------------------------------------------- Fork once and dissociate
  320: &status("Fork and dissociate");
  321: $fpid=fork;
  322: exit if $fpid;
  323: die "Couldn't fork: $!" unless defined ($fpid);
  324: 
  325: POSIX::setsid() or die "Can't start new session: $!";
  326: 
  327: # ------------------------------------------------------- Write our PID on disk
  328: &status("Write PID");
  329: $execdir=$perlvar{'lonDaemons'};
  330: open (PIDSAVE,">$execdir/logs/lonc.pid");
  331: print PIDSAVE "$$\n";
  332: close(PIDSAVE);
  333: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
  334: 
  335: # ----------------------------- Ignore signals generated during initial startup
  336: $SIG{HUP}=$SIG{USR1}='IGNORE';
  337: # ------------------------------------------------------- Now we are on our own
  338:     
  339: # Fork off our children, one for every server
  340: 
  341: &status("Forking ...");
  342: 
  343: foreach $thisserver (keys %hostip) {
  344:     if (&online($hostname{$thisserver})) {
  345:        make_new_child($thisserver);
  346:     }
  347: }
  348: 
  349: &logthis("Done starting initial servers");
  350: # ----------------------------------------------------- Install signal handlers
  351: 
  352: $SIG{CHLD} = \&REAPER;
  353: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
  354: $SIG{HUP}  = \&HUPSMAN;
  355: $SIG{USR1} = \&USRMAN;
  356: 
  357: # And maintain the population.
  358: while (1) {
  359:     &status("Sleeping");
  360:     sleep;                          # wait for a signal (i.e., child's death)
  361:                                     # See who died and start new one
  362:     &status("Woke up");
  363:     foreach $thisserver (keys %hostip) {
  364:         if (!$childpid{$thisserver}) {
  365: 	    if (($childatt{$thisserver}<$childmaxattempts) &&
  366:                 (&online($hostname{$thisserver}))) {
  367: 	       $childatt{$thisserver}++;
  368:                &logthis(
  369:    "<font color=yellow>INFO: Trying to reconnect for $thisserver "
  370:   ."(".($childatt{$thisserver}?$childatt{$thisserver}:'none').
  371:    " of $childmaxattempts attempts)</font>"); 
  372:                make_new_child($thisserver);
  373: 	   } else {
  374:                &logthis(
  375:    "<font color=yellow>INFO: Skipping $thisserver "
  376:   ."($childatt{$thisserver} of $childmaxattempts attempts)</font>");
  377:            } 
  378:                
  379:         }       
  380:     }
  381: }
  382: 
  383: 
  384: sub make_new_child {
  385:    
  386:     my $conserver=shift;
  387:     my $pid;
  388:     my $sigset;
  389:     &logthis("Attempting to start child for server $conserver");
  390:     # block signal for fork
  391:     $sigset = POSIX::SigSet->new(SIGINT);
  392:     sigprocmask(SIG_BLOCK, $sigset)
  393:         or die "Can't block SIGINT for fork: $!\n";
  394:     
  395:     die "fork: $!" unless defined ($pid = fork);
  396:     
  397:     if ($pid) {
  398:         # Parent records the child's birth and returns.
  399:         sigprocmask(SIG_UNBLOCK, $sigset)
  400:             or die "Can't unblock SIGINT for fork: $!\n";
  401:         $children{$pid} = $conserver;
  402:         $childpid{$conserver} = $pid;
  403:         return;
  404:     } else {
  405:         # Child can *not* return from this subroutine.
  406:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
  407:         $SIG{USR1}= \&logstatus;
  408:    
  409:         # unblock signals
  410:         sigprocmask(SIG_UNBLOCK, $sigset)
  411:             or die "Can't unblock SIGINT for fork: $!\n";
  412: 
  413: # ----------------------------- This is the modified main program of non-forker
  414: 
  415: $port = "$perlvar{'lonSockDir'}/$conserver";
  416: 
  417: unlink($port);
  418: 
  419: # -------------------------------------------------------------- Open other end
  420: 
  421: &openremote($conserver);
  422: 
  423: # ----------------------------------------- We're online, send delayed messages
  424:     &status("Checking for delayed messages");
  425:     my @allbuffered;
  426:     my $path="$perlvar{'lonSockDir'}/delayed";
  427:     opendir(DIRHANDLE,$path);
  428:     @allbuffered=grep /\.$conserver$/, readdir DIRHANDLE;
  429:     closedir(DIRHANDLE);
  430:     my $dfname;
  431:     foreach (@allbuffered) {
  432:         &status("Sending delayed $conserver $_");
  433:         $dfname="$path/$_";
  434:         &logthis('Sending '.$dfname);
  435:         my $wcmd;
  436:         {
  437:          my $dfh=IO::File->new($dfname);
  438:          $cmd=<$dfh>;
  439:         }
  440:         chomp($cmd);
  441:         my $bcmd=$cmd;
  442:         if ($cmd =~ /^encrypt\:/) {
  443: 	    my $rcmd=$cmd;
  444:             $rcmd =~ s/^encrypt\://;
  445:             chomp($rcmd);
  446:             my $cmdlength=length($rcmd);
  447:             $rcmd.="         ";
  448:             my $encrequest='';
  449:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  450:                 $encrequest.=
  451:                     unpack("H16",$cipher->encrypt(substr($rcmd,$encidx,8)));
  452:             }
  453:             $cmd="enc:$cmdlength:$encrequest\n";
  454:         }
  455:     $SIG{ALRM}=sub { die "timeout" };
  456:     $SIG{__DIE__}='DEFAULT';
  457:     eval {
  458:         alarm(60);
  459:         print $remotesock "$cmd\n";
  460:         $answer=<$remotesock>;
  461: 	chomp($answer);
  462:         alarm(0);
  463:     };
  464:     $SIG{ALRM}='DEFAULT';
  465:     $SIG{__DIE__}=\&catchexception;
  466: 
  467:         if (($answer ne '') && ($@!~/timeout/)) {
  468: 	    unlink("$dfname");
  469:             &logthis("Delayed $cmd to $conserver: >$answer<");
  470:             &logperm("S:$conserver:$bcmd");
  471:         }        
  472:     }
  473: 
  474: # ------------------------------------------------------- Listen to UNIX socket
  475: &status("Opening socket $conserver");
  476: unless (
  477:   $server = IO::Socket::UNIX->new(Local  => $port,
  478:                                   Type   => SOCK_STREAM,
  479:                                   Listen => 10 )
  480:    ) { 
  481:        my $st=120+int(rand(240));
  482:        &logthis(
  483:          "<font color=blue>WARNING: ".
  484:          "Can't make server socket $conserver ($st secs): $@</font>");
  485:        sleep($st);
  486:        exit; 
  487:      };
  488: 
  489: # -----------------------------------------------------------------------------
  490: 
  491: &logthis("<font color=green>$conserver online</font>");
  492: 
  493: # -----------------------------------------------------------------------------
  494: # begin with empty buffers
  495: %inbuffer  = ();
  496: %outbuffer = ();
  497: %ready     = ();
  498: 
  499: tie %ready, 'Tie::RefHash';
  500: 
  501: nonblock($server);
  502: $select = IO::Select->new($server);
  503: 
  504: # Main loop: check reads/accepts, check writes, check ready to process
  505: while (1) {
  506:     my $client;
  507:     my $rv;
  508:     my $data;
  509: 
  510:     # check for new information on the connections we have
  511: 
  512:     # anything to read or accept?
  513:     foreach $client ($select->can_read(0.1)) {
  514: 
  515:         if ($client == $server) {
  516:             # accept a new connection
  517:             &status("Accept new connection: $conserver");
  518:             $client = $server->accept();
  519:             $select->add($client);
  520:             nonblock($client);
  521:         } else {
  522:             # read data
  523:             $data = '';
  524:             $rv   = $client->recv($data, POSIX::BUFSIZ, 0);
  525: 
  526:             unless (defined($rv) && length $data) {
  527:                 # This would be the end of file, so close the client
  528:                 delete $inbuffer{$client};
  529:                 delete $outbuffer{$client};
  530:                 delete $ready{$client};
  531: 
  532:                 &status("Idle $conserver");
  533:                 $select->remove($client);
  534:                 close $client;
  535:                 next;
  536:             }
  537: 
  538:             $inbuffer{$client} .= $data;
  539: 
  540:             # test whether the data in the buffer or the data we
  541:             # just read means there is a complete request waiting
  542:             # to be fulfilled.  If there is, set $ready{$client}
  543:             # to the requests waiting to be fulfilled.
  544:             while ($inbuffer{$client} =~ s/(.*\n)//) {
  545:                 push( @{$ready{$client}}, $1 );
  546:             }
  547:         }
  548:     }
  549: 
  550:     # Any complete requests to process?
  551:     foreach $client (keys %ready) {
  552:         handle($client);
  553:     }
  554: 
  555:     # Buffers to flush?
  556:     foreach $client ($select->can_write(1)) {
  557:         # Skip this client if we have nothing to say
  558:         next unless exists $outbuffer{$client};
  559: 
  560:         $rv = $client->send($outbuffer{$client}, 0);
  561: 
  562:       unless ($outbuffer{$client}=~/con_lost\n$/) {
  563:         unless (defined $rv) {
  564:             # Whine, but move on.
  565:             &logthis("I was told I could write, but I can't.\n");
  566:             next;
  567:         }
  568:         $errno=$!;
  569:         if (($rv == length $outbuffer{$client}) ||
  570:             ($errno == POSIX::EWOULDBLOCK) || ($errno == 0)) {
  571:             substr($outbuffer{$client}, 0, $rv) = '';
  572:             delete $outbuffer{$client} unless length $outbuffer{$client};
  573:         } else {
  574:             # Couldn't write all the data, and it wasn't because
  575:             # it would have blocked.  Shutdown and move on.
  576: 
  577: 	    &logthis("Dropping data with ".$errno.": ".
  578:                      length($outbuffer{$client}).", $rv");
  579: 
  580:             delete $inbuffer{$client};
  581:             delete $outbuffer{$client};
  582:             delete $ready{$client};
  583: 
  584:             $select->remove($client);
  585:             close($client);
  586:             next;
  587:         }
  588:       } else {
  589: # -------------------------------------------------------- Wow, connection lost
  590:          &logthis(
  591:      "<font color=red>CRITICAL: Closing connection $conserver</font>");
  592: 	 &status("Connection lost $conserver");
  593:          $remotesock->shutdown(2);
  594:          &logthis("Attempting to open new connection");
  595:          &openremote($conserver);          
  596:       }
  597:     }
  598:    
  599: }
  600: }
  601: 
  602: # ------------------------------------------------------- End of make_new_child
  603: 
  604: # handle($socket) deals with all pending requests for $client
  605: sub handle {
  606:     # requests are in $ready{$client}
  607:     # send output to $outbuffer{$client}
  608:     my $client = shift;
  609:     my $request;
  610: 
  611:     foreach $request (@{$ready{$client}}) {
  612: # ============================================================= Process request
  613:         # $request is the text of the request
  614:         # put text of reply into $outbuffer{$client}
  615: # ------------------------------------------------------------ Is this the end?
  616:         if ($request eq "close_connection_exit\n") {
  617: 	    &status("Request close connection: $conserver");
  618:            &logthis(
  619:      "<font color=red>CRITICAL: Request Close Connection $conserver</font>");
  620:            $remotesock->shutdown(2);
  621:            $server->close();
  622:            exit;
  623:         }
  624: # -----------------------------------------------------------------------------
  625:         if ($request =~ /^encrypt\:/) {
  626: 	    my $cmd=$request;
  627:             $cmd =~ s/^encrypt\://;
  628:             chomp($cmd);
  629:             my $cmdlength=length($cmd);
  630:             $cmd.="         ";
  631:             my $encrequest='';
  632:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  633:                 $encrequest.=
  634:                     unpack("H16",$cipher->encrypt(substr($cmd,$encidx,8)));
  635:             }
  636:             $request="enc:$cmdlength:$encrequest\n";
  637:         }
  638: # --------------------------------------------------------------- Main exchange
  639:     $SIG{ALRM}=sub { die "timeout" };
  640:     $SIG{__DIE__}='DEFAULT';
  641:     eval {
  642:         alarm(300);
  643:         &status("Sending $conserver: $request");
  644:         print $remotesock "$request";
  645:         &status("Waiting for reply from $conserver: $request");
  646:         $answer=<$remotesock>;
  647:         &status("Received reply: $request");
  648:         alarm(0);
  649:     };
  650:     if ($@=~/timeout/) { 
  651:        $answer='';
  652:        &logthis(
  653:         "<font color=red>CRITICAL: Timeout $conserver: $request</font>");
  654:     }  
  655:     $SIG{ALRM}='DEFAULT';
  656:     $SIG{__DIE__}=\&catchexception;
  657: 
  658: 
  659:         if ($answer) {
  660: 	   if ($answer =~ /^enc/) {
  661:                my ($cmd,$cmdlength,$encinput)=split(/:/,$answer);
  662:                chomp($encinput);
  663: 	       $answer='';
  664:                for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
  665:                   $answer.=$cipher->decrypt(
  666:                    pack("H16",substr($encinput,$encidx,16))
  667:                   );
  668: 	       }
  669: 	      $answer=substr($answer,0,$cmdlength);
  670: 	      $answer.="\n";
  671: 	   }
  672:            $outbuffer{$client} .= $answer;
  673:         } else {
  674:            $outbuffer{$client} .= "con_lost\n";
  675:         }
  676: 
  677: # ===================================================== Done processing request
  678:     }
  679:     delete $ready{$client};
  680:     &status("Completed $conserver: $request");
  681: # -------------------------------------------------------------- End non-forker
  682: }
  683: # ---------------------------------------------------------- End make_new_child
  684: }
  685: 
  686: # nonblock($socket) puts socket into nonblocking mode
  687: sub nonblock {
  688:     my $socket = shift;
  689:     my $flags;
  690: 
  691:     
  692:     $flags = fcntl($socket, F_GETFL, 0)
  693:             or die "Can't get flags for socket: $!\n";
  694:     fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
  695:             or die "Can't make socket nonblocking: $!\n";
  696: }
  697: 
  698: 
  699: sub openremote {
  700: # ---------------------------------------------------- Client to network server
  701: 
  702:     my $conserver=shift;
  703: 
  704: &status("Opening TCP: $conserver");
  705: 
  706: unless (
  707:   $remotesock = IO::Socket::INET->new(PeerAddr => $hostip{$conserver},
  708:                                       PeerPort => $perlvar{'londPort'},
  709:                                       Proto    => "tcp",
  710:                                       Type     => SOCK_STREAM)
  711:    ) { 
  712:        my $st=120+int(rand(240));
  713:        &logthis(
  714: "<font color=blue>WARNING: Couldn't connect $conserver ($st secs): $@</font>");
  715:        sleep($st);
  716:        exit; 
  717:      };
  718: # ----------------------------------------------------------------- Init dialog
  719: 
  720: &status("Init dialogue: $conserver");
  721: 
  722:      $SIG{ALRM}=sub { die "timeout" };
  723:      $SIG{__DIE__}='DEFAULT';
  724:      eval {
  725:          alarm(60);
  726: print $remotesock "init\n";
  727: $answer=<$remotesock>;
  728: print $remotesock "$answer";
  729: $answer=<$remotesock>;
  730: chomp($answer);
  731:           alarm(0);
  732:      };
  733:      $SIG{ALRM}='DEFAULT';
  734:      $SIG{__DIE__}=\&catchexception;
  735:  
  736:      if ($@=~/timeout/) {
  737: 	 &logthis("Timed out during init: $conserver");
  738:          exit;
  739:      }
  740: 
  741: if ($answer ne 'ok') {
  742:        &logthis("Init reply for $conserver: >$answer<");
  743:        my $st=120+int(rand(240));
  744:        &logthis(
  745: "<font color=blue>WARNING: Init failed $conserver ($st secs)</font>");
  746:        sleep($st);
  747:        exit; 
  748: }
  749: 
  750: sleep 5;
  751: &status("Ponging $conserver");
  752: print $remotesock "pong\n";
  753: $answer=<$remotesock>;
  754: chomp($answer);
  755: if ($answer!~/^$converver/) {
  756:    &logthis("Pong reply for $conserver: >$answer<");
  757: }
  758: # ----------------------------------------------------------- Initialize cipher
  759: 
  760: &status("Initialize cipher: $conserver");
  761: print $remotesock "ekey\n";
  762: my $buildkey=<$remotesock>;
  763: my $key=$conserver.$perlvar{'lonHostID'};
  764: $key=~tr/a-z/A-Z/;
  765: $key=~tr/G-P/0-9/;
  766: $key=~tr/Q-Z/0-9/;
  767: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
  768: $key=substr($key,0,32);
  769: my $cipherkey=pack("H32",$key);
  770: if ($cipher=new IDEA $cipherkey) {
  771:    &logthis("Secure connection initialized: $conserver");
  772: } else {
  773:    my $st=120+int(rand(240));
  774:    &logthis(
  775:      "<font color=blue>WARNING: ".
  776:      "Could not establish secure connection, $conserver ($st secs)!</font>");
  777:    sleep($st);
  778:    exit;
  779: }
  780: 
  781: }
  782: 
  783: # ----------------------------------- POD (plain old documentation, CPAN style)
  784: 
  785: =head1 NAME
  786: 
  787: lonc - LON TCP-MySQL-Server Daemon for handling database requests.
  788: 
  789: =head1 SYNOPSIS
  790: 
  791: Should only be run as user=www.  This is a command-line script which
  792: is invoked by loncron.
  793: 
  794: =head1 DESCRIPTION
  795: 
  796: Provides persistent TCP connections to the other servers in the network
  797: through multiplexed domain sockets
  798: 
  799:  PID in subdir logs/lonc.pid
  800:  kill kills
  801:  HUP restarts
  802:  USR1 tries to open connections again
  803: 
  804: =head1 README
  805: 
  806: Not yet written.
  807: 
  808: =head1 PREREQUISITES
  809: 
  810: POSIX
  811: IO::Socket
  812: IO::Select
  813: IO::File
  814: Socket
  815: Fcntl
  816: Tie::RefHash
  817: Crypt::IDEA
  818: 
  819: =head1 COREQUISITES
  820: 
  821: =head1 OSNAMES
  822: 
  823: linux
  824: 
  825: =head1 SCRIPT CATEGORIES
  826: 
  827: Server/Process
  828: 
  829: =cut

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