File:  [LON-CAPA] / loncom / Attic / lonc
Revision 1.40: download - view: text, annotated - select for diffs
Sat May 11 21:17:39 2002 UTC (22 years ago) by harris41
Branches: MAIN
CVS tags: HEAD
using LONCAPA::Configuration::read_conf
BUG 129

    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.40 2002/05/11 21:17:39 harris41 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: # 3/07/02 Ron Fox 
   49: # based on nonforker from Perl Cookbook
   50: # - server who multiplexes without forking
   51: # 5/11/2002 Scott Harrison
   52: 
   53: use lib '/home/httpd/lib/perl/';
   54: use LONCAPA::Configuration;
   55: 
   56: use POSIX;
   57: use IO::Socket;
   58: use IO::Select;
   59: use IO::File;
   60: use Socket;
   61: use Fcntl;
   62: use Tie::RefHash;
   63: use Crypt::IDEA;
   64: #use Net::Ping;
   65: use LWP::UserAgent();
   66: 
   67: $status='';
   68: $lastlog='';
   69: $conserver='SHELL';
   70: $DEBUG = 0;			# Set to 1 for annoyingly complete logs.
   71: 
   72: # -------------------------------- Set signal handlers to record abnormal exits
   73: 
   74: &status("Init exception handlers");
   75: $SIG{QUIT}=\&catchexception;
   76: $SIG{__DIE__}=\&catchexception;
   77: 
   78: # ------------------------------------------- Read access.conf and loncapa.conf
   79: &status("Read access.conf and loncapa.conf");
   80: my $perlvarref=LONCAPA::Configuration::read_conf('access.conf','loncapa.conf');
   81: my %perlvar=%{$perlvarref};
   82: undef $perlvarref;
   83: 
   84: # ----------------------------- Make sure this process is running from user=www
   85: &status("Check user ID");
   86: my $wwwid=getpwnam('www');
   87: if ($wwwid!=$<) {
   88:    $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
   89:    $subj="LON: $perlvar{'lonHostID'} User ID mismatch";
   90:    system("echo 'User ID mismatch.  lonc must be run as user www.' |\
   91:  mailto $emailto -s '$subj' > /dev/null");
   92:    exit 1;
   93: }
   94: 
   95: # --------------------------------------------- Check if other instance running
   96: 
   97: my $pidfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
   98: 
   99: if (-e $pidfile) {
  100:    my $lfh=IO::File->new("$pidfile");
  101:    my $pide=<$lfh>;
  102:    chomp($pide);
  103:    if (kill 0 => $pide) { die "already running"; }
  104: }
  105: 
  106: # ------------------------------------------------------------- Read hosts file
  107: 
  108: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
  109: 
  110: while ($configline=<CONFIG>) {
  111:     my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
  112:     chomp($ip);
  113:     if ($ip) {
  114:      $hostip{$id}=$ip;
  115:      $hostname{$id}=$name;
  116:     }
  117: }
  118: 
  119: close(CONFIG);
  120: 
  121: # -------------------------------------------------------- Routines for forking
  122: 
  123: %children               = ();       # keys are current child process IDs,
  124:                                     # values are hosts
  125: %childpid               = ();       # the other way around
  126: 
  127: %childatt               = ();       # number of attempts to start server
  128:                                     # for ID
  129: 
  130: $childmaxattempts=5;
  131: 
  132: # ---------------------------------------------------- Fork once and dissociate
  133: &status("Fork and dissociate");
  134: $fpid=fork;
  135: exit if $fpid;
  136: die "Couldn't fork: $!" unless defined ($fpid);
  137: 
  138: POSIX::setsid() or die "Can't start new session: $!";
  139: 
  140: $conserver='PARENT';
  141: 
  142: # ------------------------------------------------------- Write our PID on disk
  143: &status("Write PID");
  144: $execdir=$perlvar{'lonDaemons'};
  145: open (PIDSAVE,">$execdir/logs/lonc.pid");
  146: print PIDSAVE "$$\n";
  147: close(PIDSAVE);
  148: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
  149: 
  150: # ----------------------------- Ignore signals generated during initial startup
  151: $SIG{HUP}=$SIG{USR1}='IGNORE';
  152: # ------------------------------------------------------- Now we are on our own
  153:     
  154: # Fork off our children, one for every server
  155: 
  156: &status("Forking ...");
  157: 
  158: foreach $thisserver (keys %hostip) {
  159:     #if (&online($hostname{$thisserver})) {
  160:        make_new_child($thisserver);
  161:     #}
  162: }
  163: 
  164: &logthis("Done starting initial servers");
  165: # ----------------------------------------------------- Install signal handlers
  166: 
  167: 
  168: $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
  169: $SIG{HUP}  = \&HUPSMAN;
  170: $SIG{USR1} = \&USRMAN;
  171: 
  172: # And maintain the population.
  173: while (1) {
  174:     my $deadpid = wait;		# Wait for the next child to die.
  175:                                 # See who died and start new one
  176:                                 # or a signal (e.g. USR1 for restart).
  177:                                 # if a signal, the wait will fail
  178:                                 # This is ordinarily detected by
  179:                                 # checking for the existence of the
  180:                                 # pid index inthe children hash since
  181:                                 # the return value from a failed wait is -1
  182:                                 # which is an impossible PID.
  183:     &status("Woke up");
  184:     my $skipping='';
  185: 
  186:     if(exists($children{$deadpid})) {
  187: 
  188: 	$thisserver = $children{$deadpid}; # Look name of dead guy's peer.
  189: 
  190: 	delete($children{$deadpid}); # Get rid of dead hash entry.
  191: 
  192: 	if($childatt{$thisserver} < $childmaxattempts) {
  193: 	    $childatt{$thisserver}++;
  194: 	    &logthis(
  195: 	       "<font color=yellow>INFO: Trying to reconnect for $thisserver "
  196:             ."($childatt{$thisserver} of $childmaxattempts attempts)</font>"); 
  197: 	    make_new_child($thisserver);
  198: 	
  199: 	}
  200: 	else {
  201: 	    $skipping .= $thisserver.' ';
  202: 	}
  203: 	if($skipping) {
  204: 	    &logthis("<font color=blue>WARNING: Skipped $skipping</font>");
  205:   
  206: 	}
  207:     }
  208: 
  209: }
  210: 
  211: 
  212: 
  213: sub make_new_child {
  214:    
  215:     $newserver=shift;
  216:     my $pid;
  217:     my $sigset;
  218:     &logthis("Attempting to start child for server $newserver");
  219:     # block signal for fork
  220:     $sigset = POSIX::SigSet->new(SIGINT);
  221:     sigprocmask(SIG_BLOCK, $sigset)
  222:         or die "Can't block SIGINT for fork: $!\n";
  223:     
  224:     die "fork: $!" unless defined ($pid = fork);
  225:     
  226:     if ($pid) {
  227:         # Parent records the child's birth and returns.
  228:         sigprocmask(SIG_UNBLOCK, $sigset)
  229:             or die "Can't unblock SIGINT for fork: $!\n";
  230:         $children{$pid} = $newserver;
  231:         $childpid{$newserver} = $pid;
  232:         return;
  233:     } else {
  234:         $conserver=$newserver;
  235:         # Child can *not* return from this subroutine.
  236:         $SIG{INT} = 'DEFAULT';      # make SIGINT kill us as it did before
  237:         $SIG{USR1}= \&logstatus;
  238:    
  239:         # unblock signals
  240:         sigprocmask(SIG_UNBLOCK, $sigset)
  241:             or die "Can't unblock SIGINT for fork: $!\n";
  242: 
  243: # ----------------------------- This is the modified main program of non-forker
  244: 
  245: $port = "$perlvar{'lonSockDir'}/$conserver";
  246: 
  247: unlink($port);
  248: 
  249: # -------------------------------------------------------------- Open other end
  250: 
  251: &openremote($conserver);
  252: 	&logthis("<font color=green> Connection to $conserver open </font>");
  253: # ----------------------------------------- We're online, send delayed messages
  254:     &status("Checking for delayed messages");
  255: 
  256:     my @allbuffered;
  257:     my $path="$perlvar{'lonSockDir'}/delayed";
  258:     opendir(DIRHANDLE,$path);
  259:     @allbuffered=grep /\.$conserver$/, readdir DIRHANDLE;
  260:     closedir(DIRHANDLE);
  261:     my $dfname;
  262:     foreach (@allbuffered) {
  263:         &status("Sending delayed: $_");
  264:         $dfname="$path/$_";
  265:         if($DEBUG) { &logthis('Sending '.$dfname); }
  266:         my $wcmd;
  267:         {
  268:          my $dfh=IO::File->new($dfname);
  269:          $cmd=<$dfh>;
  270:         }
  271:         chomp($cmd);
  272:         my $bcmd=$cmd;
  273:         if ($cmd =~ /^encrypt\:/) {
  274: 	    my $rcmd=$cmd;
  275:             $rcmd =~ s/^encrypt\://;
  276:             chomp($rcmd);
  277:             my $cmdlength=length($rcmd);
  278:             $rcmd.="         ";
  279:             my $encrequest='';
  280:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  281:                 $encrequest.=
  282:                     unpack("H16",$cipher->encrypt(substr($rcmd,$encidx,8)));
  283:             }
  284:             $cmd="enc:$cmdlength:$encrequest\n";
  285:         }
  286: 	$answer = londtransaction($remotesock, $cmd, 60);
  287: 	chomp($answer);
  288: 
  289:         if (($answer ne '') && ($@!~/timeout/)) {
  290: 	    unlink("$dfname");
  291:             &logthis("Delayed $cmd: >$answer<");
  292:             &logperm("S:$conserver:$bcmd");
  293:         }        
  294:     }
  295: 	if($DEBUG) { &logthis("<font color=green> Delayed transactions sent"); }
  296: 
  297: # ------------------------------------------------------- Listen to UNIX socket
  298: &status("Opening socket");
  299: unless (
  300:   $server = IO::Socket::UNIX->new(Local  => $port,
  301:                                   Type   => SOCK_STREAM,
  302:                                   Listen => 10 )
  303:    ) { 
  304:        my $st=120+int(rand(240));
  305:        &logthis(
  306:          "<font color=blue>WARNING: ".
  307:          "Can't make server socket ($st secs):  .. exiting</font>");
  308:        sleep($st);
  309:        exit; 
  310:      };
  311:    
  312: # -----------------------------------------------------------------------------
  313: 
  314: &logthis("<font color=green>$conserver online</font>");
  315: 
  316: # -----------------------------------------------------------------------------
  317: # begin with empty buffers
  318: %inbuffer  = ();
  319: %outbuffer = ();
  320: %ready     = ();
  321: %servers   = ();	# To be compatible with make filevector.  indexed by
  322: 			# File ids, values are sockets.
  323: 			# note that the accept socket is omitted.
  324: 
  325: tie %ready, 'Tie::RefHash';
  326: 
  327: # nonblock($server);
  328: # $select = IO::Select->new($server);
  329: 
  330: # Main loop: check reads/accepts, check writes, check ready to process
  331: 
  332: status("Main loop");
  333: while (1) {
  334:     my $client;
  335:     my $rv;
  336:     my $data;
  337: 
  338:     my $infdset;		# bit vec of fd's to select on input.
  339: 
  340:     my $outfdset;		# Bit vec of fd's to select on output.
  341: 
  342: 
  343:     $infdset = MakeFileVector(\%servers);
  344:     $outfdset= MakeFileVector(\%outbuffer);
  345:     vec($infdset, $server->fileno, 1) = 1;
  346:     if($DEBUG) {
  347: 	&logthis("Adding ".$server->fileno.
  348: 		 " to input select vector (listner)".
  349: 		 unpack("b*",$infdset)."\n");
  350:     }
  351:     DoSelect(\$infdset, \$outfdset); # Wait for input.
  352:     if($DEBUG) {
  353: 	&logthis("Doselect completed!");
  354: 	&logthis("ins = ".unpack("b*",$infdset)."\n");
  355: 	&logthis("outs= ".unpack("b*",$outfdset)."\n");
  356: 		 
  357:     }
  358: 
  359:     # Checkfor new connections:
  360:     if (vec($infdset, $server->fileno, 1)) {
  361: 	if($DEBUG) {
  362: 	    &logthis("New connection established");
  363: 	}
  364: 	# accept a new connection
  365: 	&status("Accept new connection: $conserver");
  366: 	$client = $server->accept();
  367: 	if($DEBUG) {
  368: 	    &logthis("New client fd = ".$client->fileno."\n");
  369: 	}
  370: 	$servers{$client->fileno} = $client;
  371: 	nonblock($client);
  372:     }
  373:     HandleInput($infdset, \%servers, \%inbuffer, \%outbuffer, \%ready);
  374:     HandleOutput($outfdset, \%servers, \%outbuffer, \%inbuffer,
  375: 		 \%ready);
  376: # -------------------------------------------------------- Wow, connection lost
  377: 
  378: }
  379:    
  380:     }
  381: }
  382: 
  383: # ------------------------------------------------------- End of make_new_child
  384: 
  385: 
  386: #
  387: #  Make a vector of file descriptors to wait for in a select.
  388: #  parameters:
  389: #     \%fdhash  -reference to a hash which has IO::Socket's as indices.  
  390: #                We only care about the indices, not the values.
  391: #  A select vector is created from all indices of the hash.
  392: 
  393: sub MakeFileVector
  394: {
  395:     my $fdhash = shift;
  396:     my $selvar = "";
  397: 
  398:     foreach $socket (keys %$fdhash) {
  399: 	if($DEBUG) {
  400: 	    &logthis("Adding  ".$socket.
  401: 		     "to select vector. (client)\n");
  402: 	}
  403: 	vec($selvar, $socket, 1) = 1;
  404:     }
  405:     return $selvar;
  406: }
  407: 
  408: 
  409: #
  410: #  HandleOutput:
  411: #    Processes output on a buffered set of file descriptors which are
  412: #    ready to be read.
  413: #  Parameters:
  414: #    $selvector - Vector of file descriptors which are writable.
  415: #    \%sockets  - Vector of socket references indexed by socket.
  416: #    \%buffers  - Reference to a hash containing output buffers.
  417: #                 Hashes are indexed by sockets.  The file descriptors of some
  418: #                 of those sockets will be present in $selvector.
  419: #                 For each one of those, we will attempt to write the output
  420: #                 buffer to the socket.  Note that we will assume that
  421: #                 the sockets are being run in non blocking mode.
  422: #   \%inbufs    - Reference to hash containing input buffers.
  423: #   \%readys    - Reference to hash containing flags for items with complete
  424: #                 requests.
  425: #
  426: sub HandleOutput
  427: {
  428:     my $selvector = shift;
  429:     my $sockets   = shift;
  430:     my $buffers   = shift;
  431:     my $inbufs    = shift;
  432:     my $readys    = shift;
  433:     my $sock;
  434: 
  435:     if($DEBUG) {
  436: 	&logthis("HandleOutput entered\n");
  437:     }
  438: 
  439:     foreach $sock (keys %$sockets) {
  440: 	my $socket = $sockets->{$sock};
  441: 	if(vec($selvector, $sock, 1)) { # $socket is writable.
  442: 	    if($DEBUG) {
  443: 		&logthis("Sending $buffers->{$sock} \n");
  444: 	    }
  445: 	    my $rv = $socket->send($buffers->{$sock}, 0);
  446: 	    $errno = $!;
  447: 	    unless ($buffers->{$sock} eq "con_lost\n") {
  448: 		unless (defined $rv) { # Write failed... could be EINTR
  449: 		    unless ($errno == POSIX::EINTR) {
  450: 			&logthis("Write failed on writable socket");
  451: 		    }		# EINTR is not an error .. just retry.
  452: 		    next;
  453: 		}
  454: 		if( ($rv == length $buffers->{$sock})    ||
  455: 		    ($errno == POSIX::EWOULDBLOCK)       ||
  456: 		    ($errno == POSIX::EAGAIN)            || # same as above.
  457: 		    ($errno == POSIX::EINTR)             || # signal during IO
  458: 		    ($errno == 0)) {
  459: 		    substr($buffers->{$sock}, 0, $rv)=""; # delete written part
  460: 		    delete $buffers->{$sock} unless length $buffers->{$sock};
  461: 		} else {
  462: 		    # For some reason the write failed with an error code
  463: 		    # we didn't look for.  Shutdown the socket.
  464: 		    &logthis("Unable to write data with ".$errno.": ".
  465: 			     "Dropping data: ".length($buffers->{$sock}).
  466: 			     ", $rv");
  467: 		    #
  468: 		    # kill off the buffers in the hash:
  469: 
  470: 		    delete $buffers->{$sock};
  471: 		    delete $inbufs->{$sock};
  472: 		    delete $readys->{$sock};
  473: 
  474: 		    close($socket); # Close the client socket.
  475: 		    next;
  476: 		}
  477: 	    } else {		# Kludgy way to mark lond connection lost.
  478: 		&logthis(
  479: 		 "<font color=red>CRITICAL lond connection lost</font>");
  480: 		status("Connection lost");
  481: 		$remotesock->shutdown(2);
  482: 		&logthis("Attempting to open a new connection");
  483: 		&openremote($conserver);
  484: 	    }
  485: 		   
  486: 	}
  487:     }
  488: 
  489: }
  490: #
  491: #   HandleInput - Deals with input on client sockets.
  492: #                 Each socket has an associated input buffer.
  493: #                 For each readable socket, the currently available
  494: #                 data is appended to this buffer.
  495: #                 If necessary, the buffer is created.
  496: #                 On various failures, we may shutdown the client.
  497: #  Parameters:
  498: #     $selvec   - Vector of readable sockets.
  499: #     \%sockets - Refers to the  Hash of sockets indexed by sockets.  
  500: #                 Each of these may or may not have it's fd bit set 
  501: #                 in the $selvec.
  502: #     \%ibufs   - Refers to the hash of input buffers indexed by socket.
  503: #     \%obufs   - Hash of output buffers indexed by socket. 
  504: #     \%ready   - Hash of ready flags indicating the existence of a completed
  505: #                 Request.
  506: sub HandleInput 
  507: {
  508: 
  509:     # Marshall the parameters.   Note that the hashes are actually
  510:     # references not values.
  511: 
  512:     my $selvec  = shift;
  513:     my $sockets = shift;
  514:     my $ibufs   = shift;
  515:     my $obufs   = shift;
  516:     my $ready   = shift;
  517:     my $sock;
  518: 
  519:     if($DEBUG) {
  520: 	&logthis("Entered HandleInput\n");
  521:     }
  522:     foreach $sock (keys %$sockets) {
  523: 	my $socket = $sockets->{$sock};
  524: 	if(vec($selvec, $sock, 1)) { # Socket which is readable.
  525: 
  526: 	    #  Attempt to read the data and do error management.
  527: 	    my $data = '';
  528: 	    my $rv = $socket->recv($data, POSIX::BUFSIZ, 0);
  529: 	    if($DEBUG) {
  530: 		&logthis("Received $data from socket");
  531: 	    }
  532: 	    unless (defined($rv) && length $data) {
  533: 
  534: 		# Read an end of file.. this is a disconnect from the peer.
  535: 
  536: 		delete $sockets->{$sock};
  537: 		delete $ibufs->{$sock};
  538: 		delete $obufs->{$sock};
  539: 		delete $ready->{$sock};
  540: 
  541: 		status("Idle");
  542: 		close $socket;
  543: 		next;
  544: 	    }
  545: 	    #  Append the read data to the input buffer. If the buffer
  546: 	    # now contains a \n the request is complete and we can 
  547: 	    # mark this in the $ready hash (one request for each \n.)
  548: 
  549: 	    $ibufs->{$sock} .= $data;
  550: 	    while($ibufs->{$sock} =~ s/(.*\n)//) {
  551: 		push(@{$ready->{$sock}}, $1);
  552: 	    }
  553: 	    
  554: 	}
  555:     }
  556:     #  Now handle any requests which are ready:
  557: 
  558:     foreach $client (keys %ready) {
  559: 	handle($client);
  560:     }
  561: }
  562: 
  563: # DoSelect:  does a select with no timeout.  On signal (errno == EINTR), 
  564: #            the select is retried until there are items in the returned
  565: #            vectors.  
  566: #
  567: # Parameters:
  568: #   \$readvec   - Reference to a vector of file descriptors to 
  569: #                 check for readability.
  570: #   \$writevec  - Reference to a vector of file descriptors to check for
  571: #                 writability.
  572: #  On exit, the referents are modified with vectors indicating which 
  573: #  file handles are readable/writable.
  574: #
  575: sub DoSelect {
  576:     my $readvec = shift;
  577:     my $writevec= shift;
  578:     my $outs;
  579:     my $ins;
  580: 
  581:     while (1) {
  582: 	my $nfds = select( $ins = $$readvec, $outs = $$writevec, undef, undef);
  583: 	if($nfds) {
  584: 	    if($DEBUG) {
  585: 		&logthis("select exited with ".$nfds." fds\n");
  586: 		&logthis("ins = ".unpack("b*",$ins).
  587: 			 " readvec = ".unpack("b*",$$readvec)."\n");
  588: 		&logthis("outs = ".unpack("b*",$outs).
  589: 			 " writevec = ".unpack("b*",$$writevec)."\n");
  590: 	    }
  591: 	    $$readvec  = $ins;
  592: 	    $$writevec = $outs;
  593: 	    return;
  594: 	} else {
  595: 	    if($DEBUG) {
  596: 		&logthis("Select exited with no bits set in mask\n");
  597: 	    }
  598: 	    die "Select failed" unless $! == EINTR;
  599: 	}
  600:     }
  601: }
  602: 
  603: # handle($socket) deals with all pending requests for $client
  604: #
  605: sub handle {
  606:     # requests are in $ready{$client}
  607:     # send output to $outbuffer{$client}
  608:     my $client = shift;
  609:     my $request;
  610:     foreach $request (@{$ready{$client}}) {
  611: # ============================================================= Process request
  612:         # $request is the text of the request
  613:         # put text of reply into $outbuffer{$client}
  614: # ------------------------------------------------------------ Is this the end?
  615: 	chomp($request);
  616: 	if($DEBUG) {
  617:      &logthis("<font color=green> Request $request processing starts</font>");
  618:         }
  619:         if ($request eq "close_connection_exit\n") {
  620: 	    &status("Request close connection");
  621:            &logthis(
  622:      "<font color=red>CRITICAL: Request Close Connection ... exiting</font>");
  623:            $remotesock->shutdown(2);
  624:            $server->close();
  625:            exit;
  626:         }
  627: # -----------------------------------------------------------------------------
  628:         if ($request =~ /^encrypt\:/) {
  629: 	    my $cmd=$request;
  630:             $cmd =~ s/^encrypt\://;
  631:             chomp($cmd);
  632:             my $cmdlength=length($cmd);
  633:             $cmd.="         ";
  634:             my $encrequest='';
  635:             for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
  636:                 $encrequest.=
  637:                     unpack("H16",$cipher->encrypt(substr($cmd,$encidx,8)));
  638:             }
  639:             $request="enc:$cmdlength:$encrequest";
  640:         }
  641: # --------------------------------------------------------------- Main exchange
  642: 	$answer = londtransaction($remotesock, $request, 300);
  643: 
  644: 	if($DEBUG) { 
  645: 	    &logthis("<font color=green> Request data exchange complete");
  646: 	}
  647: 	if ($@=~/timeout/) { 
  648: 	    $answer='';
  649: 	    &logthis(
  650: 		     "<font color=red>CRITICAL: Timeout: $request</font>");
  651: 	}  
  652: 
  653: 
  654:         if ($answer) {
  655: 	   if ($answer =~ /^enc/) {
  656:                my ($cmd,$cmdlength,$encinput)=split(/:/,$answer);
  657:                chomp($encinput);
  658: 	       $answer='';
  659:                for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
  660:                   $answer.=$cipher->decrypt(
  661:                    pack("H16",substr($encinput,$encidx,16))
  662:                   );
  663: 	       }
  664: 	      $answer=substr($answer,0,$cmdlength);
  665: 	      $answer.="\n";
  666: 	   }
  667: 	   if($DEBUG) {
  668: 	       &logthis("sending $answer to client\n");
  669: 	   }
  670:            $outbuffer{$client} .= $answer;
  671:         } else {
  672:            $outbuffer{$client} .= "con_lost\n";
  673:         }
  674: 
  675:      &status("Completed: $request");
  676: 	if($DEBUG) {
  677: 	    &logthis("<font color=green> Request processing complete</font>");
  678: 	}
  679: # ===================================================== Done processing request
  680:     }
  681:     delete $ready{$client};
  682: # -------------------------------------------------------------- End non-forker
  683:     if($DEBUG) {
  684: 	&logthis("<font color=green> requests for child handled</font>");
  685:     }
  686: }
  687: # ---------------------------------------------------------- End make_new_child
  688: 
  689: # nonblock($socket) puts socket into nonblocking mode
  690: sub nonblock {
  691:     my $socket = shift;
  692:     my $flags;
  693: 
  694:     
  695:     $flags = fcntl($socket, F_GETFL, 0)
  696:             or die "Can't get flags for socket: $!\n";
  697:     fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
  698:             or die "Can't make socket nonblocking: $!\n";
  699: }
  700: 
  701: 
  702: sub openremote {
  703: # ---------------------------------------------------- Client to network server
  704: 
  705:     my $conserver=shift;
  706: 
  707: &status("Opening TCP");
  708:     my $st=120+int(rand(240)); # Sleep before opening:
  709: 
  710: unless (
  711:   $remotesock = IO::Socket::INET->new(PeerAddr => $hostip{$conserver},
  712:                                       PeerPort => $perlvar{'londPort'},
  713:                                       Proto    => "tcp",
  714:                                       Type     => SOCK_STREAM)
  715:    ) { 
  716: 
  717:        &logthis(
  718: "<font color=blue>WARNING: Couldn't connect to $conserver ($st secs): </font>");
  719:        sleep($st);
  720:        exit; 
  721:      };
  722: # ----------------------------------------------------------------- Init dialog
  723: 
  724: &logthis("<font color=green>INFO Connected to $conserver, initing </font>");
  725: &status("Init dialogue: $conserver");
  726: 
  727:     $answer = londtransaction($remotesock, "init", 60);
  728:     chomp($answer);
  729:     $answer = londtransaction($remotesock, $answer, 60);
  730:     chomp($answer);
  731:  
  732:      if ($@=~/timeout/) {
  733: 	 &logthis("Timed out during init.. exiting");
  734:          exit;
  735:      }
  736: 
  737: if ($answer ne 'ok') {
  738:        &logthis("Init reply: >$answer<");
  739:        my $st=120+int(rand(240));
  740:        &logthis(
  741: "<font color=blue>WARNING: Init failed ($st secs)</font>");
  742:        sleep($st);
  743:        exit; 
  744: }
  745: 
  746: sleep 5;
  747: &status("Ponging");
  748: print $remotesock "pong\n";
  749: $answer=<$remotesock>;
  750: chomp($answer);
  751: if ($answer!~/^$conserver/) {
  752:    &logthis("Pong reply: >$answer<");
  753: }
  754: # ----------------------------------------------------------- Initialize cipher
  755: 
  756: &status("Initialize cipher");
  757: print $remotesock "ekey\n";
  758: my $buildkey=<$remotesock>;
  759: my $key=$conserver.$perlvar{'lonHostID'};
  760: $key=~tr/a-z/A-Z/;
  761: $key=~tr/G-P/0-9/;
  762: $key=~tr/Q-Z/0-9/;
  763: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
  764: $key=substr($key,0,32);
  765: my $cipherkey=pack("H32",$key);
  766: if ($cipher=new IDEA $cipherkey) {
  767:    &logthis("Secure connection initialized");
  768: } else {
  769:    my $st=120+int(rand(240));
  770:    &logthis(
  771:      "<font color=blue>WARNING: ".
  772:      "Could not establish secure connection ($st secs)!</font>");
  773:    sleep($st);
  774:    exit;
  775: }
  776:     &logthis("<font color=green> Remote open success </font>");
  777: }
  778: 
  779: 
  780: 
  781: # grabs exception and records it to log before exiting
  782: sub catchexception {
  783:     my ($signal)=@_;
  784:     $SIG{QUIT}='DEFAULT';
  785:     $SIG{__DIE__}='DEFAULT';
  786:     chomp($signal);
  787:     &logthis("<font color=red>CRITICAL: "
  788:      ."ABNORMAL EXIT. Child $$ for server [$wasserver] died through "
  789:      ."\"$signal\" with parameter </font>");
  790:     die("Signal abend");
  791: }
  792: 
  793: # -------------------------------------- Routines to see if other box available
  794: 
  795: #sub online {
  796: #    my $host=shift;
  797: #    &status("Pinging ".$host);
  798: #    my $p=Net::Ping->new("tcp",20);
  799: #    my $online=$p->ping("$host");
  800: #    $p->close();
  801: #    undef ($p);
  802: #    return $online;
  803: #}
  804: 
  805: sub connected {
  806:     my ($local,$remote)=@_;
  807:     &status("Checking connection $local to $remote");
  808:     $local=~s/\W//g;
  809:     $remote=~s/\W//g;
  810: 
  811:     unless ($hostname{$local}) { return 'local_unknown'; }
  812:     unless ($hostname{$remote}) { return 'remote_unknown'; }
  813: 
  814:     #unless (&online($hostname{$local})) { return 'local_offline'; }
  815: 
  816:     my $ua=new LWP::UserAgent;
  817:     
  818:     my $request=new HTTP::Request('GET',
  819:       "http://".$hostname{$local}.'/cgi-bin/ping.pl?'.$remote);
  820: 
  821:     my $response=$ua->request($request);
  822: 
  823:     unless ($response->is_success) { return 'local_error'; }
  824: 
  825:     my $reply=$response->content;
  826:     $reply=(split("\n",$reply))[0];
  827:     $reply=~s/\W//g;
  828:     if ($reply ne $remote) { return $reply; }
  829:     return 'ok';
  830: }
  831: 
  832: 
  833: 
  834: sub hangup {
  835:     foreach (keys %children) {
  836:         $wasserver=$children{$_};
  837:         &status("Closing $wasserver");
  838:         &logthis('Closing '.$wasserver.': '.&subreply('exit',$wasserver));
  839:         &status("Kill PID $_ for $wasserver");
  840: 	kill ('INT',$_);
  841:     }
  842: }
  843: 
  844: sub HUNTSMAN {                      # signal handler for SIGINT
  845:     local($SIG{CHLD}) = 'IGNORE';   # we're going to kill our children
  846:     &hangup();
  847:     my $execdir=$perlvar{'lonDaemons'};
  848:     unlink("$execdir/logs/lonc.pid");
  849:     &logthis("<font color=red>CRITICAL: Shutting down</font>");
  850:     exit;                           # clean up with dignity
  851: }
  852: 
  853: sub HUPSMAN {                      # signal handler for SIGHUP
  854:     local($SIG{CHLD}) = 'IGNORE';  # we're going to kill our children
  855:     &hangup();
  856:     &logthis("<font color=red>CRITICAL: Restarting</font>");
  857:     unlink("$execdir/logs/lonc.pid");
  858:     my $execdir=$perlvar{'lonDaemons'};
  859:     exec("$execdir/lonc");         # here we go again
  860: }
  861: 
  862: sub checkchildren {
  863:     &initnewstatus();
  864:     &logstatus();
  865:     &logthis('Going to check on the children');
  866:     foreach (sort keys %children) {
  867: 	sleep 1;
  868:         unless (kill 'USR1' => $_) {
  869: 	    &logthis ('<font color=red>CRITICAL: Child '.$_.' is dead</font>');
  870:             &logstatus($$.' is dead');
  871:         } 
  872:     }
  873: }
  874: 
  875: sub USRMAN {
  876:     &logthis("USR1: Trying to establish connections again");
  877:     #
  878:     #  It is really important not to just clear the childatt hash or we will
  879:     #  lose all memory of the children.  What we really want to do is this:
  880:     #  For each index where childatt is >= $childmaxattempts
  881:     #  Zero the associated counter and do a make_child for the host.
  882:     #  Regardles, the childatt entry is zeroed:
  883:     my $host;
  884:     foreach $host (keys %childatt) {
  885: 	if ($childatt{$host} >= $childmaxattempts) {
  886: 	    $childatt{$host} = 0;
  887: 	    &logthis("<font color=green>INFO: Restarting child for server: "
  888: 		     .$host."</font>\n");
  889: 	    make_new_child($host);
  890: 	}
  891: 	else {
  892: 	    $childatt{$host} = 0;
  893: 	}
  894:     }
  895:     &checkchildren();		# See if any children are still dead...
  896: }
  897: 
  898: # -------------------------------------------------- Non-critical communication
  899: sub subreply { 
  900:  my ($cmd,$server)=@_;
  901:  my $answer='';
  902:  if ($server ne $perlvar{'lonHostID'}) { 
  903:     my $peerfile="$perlvar{'lonSockDir'}/$server";
  904:     my $sclient=IO::Socket::UNIX->new(Peer    =>"$peerfile",
  905:                                       Type    => SOCK_STREAM,
  906:                                       Timeout => 10)
  907:        or return "con_lost";
  908: 
  909: 
  910:     $answer = londtransaction($sclient, $cmd, 10);
  911: 
  912:     if ((!$answer) || ($@=~/timeout/)) { $answer="con_lost"; }
  913:     $SIG{ALRM}='DEFAULT';
  914:     $SIG{__DIE__}=\&catchexception;
  915:  } else { $answer='self_reply'; }
  916:  return $answer;
  917: }
  918: 
  919: # --------------------------------------------------------------------- Logging
  920: 
  921: sub logthis {
  922:     my $message=shift;
  923:     my $execdir=$perlvar{'lonDaemons'};
  924:     my $fh=IO::File->new(">>$execdir/logs/lonc.log");
  925:     my $now=time;
  926:     my $local=localtime($now);
  927:     $lastlog=$local.': '.$message;
  928:     print $fh "$local ($$) [$conserver] [$status]: $message\n";
  929: }
  930: 
  931: #--------------------------------------  londtransaction:
  932: #  
  933: #  Performs a transaction with lond with timeout support.
  934: #    result = londtransaction(socket,request,timeout)
  935: #
  936: sub londtransaction {
  937:     my ($socket, $request, $tmo) = @_;
  938: 
  939:     if($DEBUG) {
  940: 	&logthis("londtransaction request: $request");
  941:     }
  942: 
  943:     # Set the signal handlers: ALRM for timeout and disble the others.
  944: 
  945:     $SIG{ALRM} = sub { die "timeout" };
  946:     $SIG{__DIE__} = 'DEFAULT';
  947:     
  948:     # Disable all but alarm so that only that can interupt the
  949:     # send /receive.
  950:     #
  951:     my $sigset = POSIX::SigSet->new(QUIT, USR1, HUP, INT, TERM);
  952:     my $priorsigs = POSIX::SigSet->new;
  953:     unless (defined sigprocmask(SIG_BLOCK, $sigset, $priorsigs)) {
  954: 	&logthis("<font color=red> CRITICAL -- londtransaction ".
  955: 		"failed to block signals </font>");
  956: 	die "could not block signals in londtransaction";
  957:     }
  958:     $answer = '';
  959:     #
  960:     #  Send request to lond.
  961:     #
  962:     eval { 
  963: 	alarm($tmo);
  964: 	print $socket "$request\n";
  965: 	alarm(0);
  966:     };
  967:     #  If request didn't timeout, try for the response.
  968:     #
  969: 
  970:     if ($@!~/timeout/) {
  971: 	eval {
  972: 	    alarm($tmo);
  973: 	    $answer = <$socket>;
  974: 	    if($DEBUG) {
  975: 		&logthis("Received $answer in londtransaction");
  976: 	    }
  977: 	    alarm(0);
  978: 	};
  979:     } else {
  980: 	if($DEBUG) {
  981: 	    &logthis("Timeout on send in londtransaction");
  982: 	}
  983:     }
  984:     if( ($@ =~ /timeout/)  && ($DEBUG)) {
  985: 	&logthis("Timeout on receive in londtransaction");
  986:     }
  987:     #
  988:     # Restore the initial sigmask set.
  989:     #
  990:     unless (defined sigprocmask(SIG_UNBLOCK, $priorsigs)) {
  991: 	&logthis("<font color=red> CRITICAL -- londtransaction ".
  992: 		"failed to re-enable signal processing. </font>");
  993: 	die "londtransaction failed to re-enable signals";
  994:     }
  995:     #
  996:     # go back to the prior handler set.
  997:     #
  998:     $SIG{ALRM} = 'DEFAULT';
  999:     $SIG{__DIE__} = \&cathcexception;
 1000: 
 1001:     #    chomp $answer;
 1002:     if ($DEBUG) {
 1003: 	&logthis("Returning $answer in londtransaction");
 1004:     }
 1005:     return $answer;
 1006: 
 1007: }
 1008: 
 1009: sub logperm {
 1010:     my $message=shift;
 1011:     my $execdir=$perlvar{'lonDaemons'};
 1012:     my $now=time;
 1013:     my $local=localtime($now);
 1014:     my $fh=IO::File->new(">>$execdir/logs/lonnet.perm.log");
 1015:     print $fh "$now:$message:$local\n";
 1016: }
 1017: # ------------------------------------------------------------------ Log status
 1018: 
 1019: sub logstatus {
 1020:     my $docdir=$perlvar{'lonDocRoot'};
 1021:     my $fh=IO::File->new(">>$docdir/lon-status/loncstatus.txt");
 1022:     print $fh $$."\t".$conserver."\t".$status."\t".$lastlog."\n";
 1023: }
 1024: 
 1025: sub initnewstatus {
 1026:     my $docdir=$perlvar{'lonDocRoot'};
 1027:     my $fh=IO::File->new(">$docdir/lon-status/loncstatus.txt");
 1028:     my $now=time;
 1029:     my $local=localtime($now);
 1030:     print $fh "LONC status $local - parent $$\n\n";
 1031: }
 1032: 
 1033: # -------------------------------------------------------------- Status setting
 1034: 
 1035: sub status {
 1036:     my $what=shift;
 1037:     my $now=time;
 1038:     my $local=localtime($now);
 1039:     $status=$local.': '.$what;
 1040: }
 1041: 
 1042: 
 1043: 
 1044: # ----------------------------------- POD (plain old documentation, CPAN style)
 1045: 
 1046: =head1 NAME
 1047: 
 1048: lonc - LON TCP-MySQL-Server Daemon for handling database requests.
 1049: 
 1050: =head1 SYNOPSIS
 1051: 
 1052: Usage: B<lonc>
 1053: 
 1054: Should only be run as user=www.  This is a command-line script which
 1055: is invoked by B<loncron>.  There is no expectation that a typical user
 1056: will manually start B<lonc> from the command-line.  (In other words,
 1057: DO NOT START B<lonc> YOURSELF.)
 1058: 
 1059: =head1 DESCRIPTION
 1060: 
 1061: Provides persistent TCP connections to the other servers in the network
 1062: through multiplexed domain sockets
 1063: 
 1064: B<lonc> forks off children processes that correspond to the other servers
 1065: in the network.  Management of these processes can be done at the
 1066: parent process level or the child process level.
 1067: 
 1068:   After forking off the children, B<lonc> the B<parent> 
 1069: executes a main loop which simply waits for processes to exit.
 1070: As a process exits, a new process managing a link to the same
 1071: peer as the exiting process is created.  
 1072: 
 1073: B<logs/lonc.log> is the location of log messages.
 1074: 
 1075: The process management is now explained in terms of linux shell commands,
 1076: subroutines internal to this code, and signal assignments:
 1077: 
 1078: =over 4
 1079: 
 1080: =item *
 1081: 
 1082: PID is stored in B<logs/lonc.pid>
 1083: 
 1084: This is the process id number of the parent B<lonc> process.
 1085: 
 1086: =item *
 1087: 
 1088: SIGTERM and SIGINT
 1089: 
 1090: Parent signal assignment:
 1091:  $SIG{INT}  = $SIG{TERM} = \&HUNTSMAN;
 1092: 
 1093: Child signal assignment:
 1094:  $SIG{INT}  = 'DEFAULT'; (and SIGTERM is DEFAULT also)
 1095: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
 1096:  to restart a new child.)
 1097: 
 1098: Command-line invocations:
 1099:  B<kill> B<-s> SIGTERM I<PID>
 1100:  B<kill> B<-s> SIGINT I<PID>
 1101: 
 1102: Subroutine B<HUNTSMAN>:
 1103:  This is only invoked for the B<lonc> parent I<PID>.
 1104: This kills all the children, and then the parent.
 1105: The B<lonc.pid> file is cleared.
 1106: 
 1107: =item *
 1108: 
 1109: SIGHUP
 1110: 
 1111: Current bug:
 1112:  This signal can only be processed the first time
 1113: on the parent process.  Subsequent SIGHUP signals
 1114: have no effect.
 1115: 
 1116: Parent signal assignment:
 1117:  $SIG{HUP}  = \&HUPSMAN;
 1118: 
 1119: Child signal assignment:
 1120:  none (nothing happens)
 1121: 
 1122: Command-line invocations:
 1123:  B<kill> B<-s> SIGHUP I<PID>
 1124: 
 1125: Subroutine B<HUPSMAN>:
 1126:  This is only invoked for the B<lonc> parent I<PID>,
 1127: This kills all the children, and then the parent.
 1128: The B<lonc.pid> file is cleared.
 1129: 
 1130: =item *
 1131: 
 1132: SIGUSR1
 1133: 
 1134: Parent signal assignment:
 1135:  $SIG{USR1} = \&USRMAN;
 1136: 
 1137: Child signal assignment:
 1138:  $SIG{USR1}= \&logstatus;
 1139: 
 1140: Command-line invocations:
 1141:  B<kill> B<-s> SIGUSR1 I<PID>
 1142: 
 1143: Subroutine B<USRMAN>:
 1144:  When invoked for the B<lonc> parent I<PID>,
 1145: SIGUSR1 is sent to all the children, and the status of
 1146: each connection is logged.
 1147: 
 1148: 
 1149: =back
 1150: 
 1151: =head1 PREREQUISITES
 1152: 
 1153: POSIX
 1154: IO::Socket
 1155: IO::Select
 1156: IO::File
 1157: Socket
 1158: Fcntl
 1159: Tie::RefHash
 1160: Crypt::IDEA
 1161: 
 1162: =head1 COREQUISITES
 1163: 
 1164: =head1 OSNAMES
 1165: 
 1166: linux
 1167: 
 1168: =head1 SCRIPT CATEGORIES
 1169: 
 1170: Server/Process
 1171: 
 1172: =cut

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