Annotation of loncom/lonc, revision 1.13
1.1 albertel 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: # PID in subdir logs/lonc.pid
9: # kill kills
10: # HUP restarts
11: # USR1 tries to open connections again
12:
1.2 www 13: # 6/4/99,6/5,6/7,6/8,6/9,6/10,6/11,6/12,7/14,7/19,
1.5 www 14: # 10/8,10/9,10/15,11/18,12/22,
1.10 www 15: # 2/8,7/25 Gerd Kortemeyer
16: # 12/05 Scott Harrison
17: # 12/05 Gerd Kortemeyer
18: #
1.1 albertel 19: # based on nonforker from Perl Cookbook
20: # - server who multiplexes without forking
21:
22: use POSIX;
23: use IO::Socket;
24: use IO::Select;
25: use IO::File;
26: use Socket;
27: use Fcntl;
28: use Tie::RefHash;
29: use Crypt::IDEA;
30:
1.9 harris41 31: # grabs exception and records it to log before exiting
32: sub catchexception {
33: my ($signal)=@_;
1.10 www 34: $SIG{'QUIT'}='DEFAULT';
35: $SIG{__DIE__}='DEFAULT';
1.9 harris41 36: &logthis("<font color=red>CRITICAL: "
37: ."ABNORMAL EXIT. Child $$ for server $wasserver died through "
1.11 harris41 38: ."\"$signal\" with this parameter->[$@]</font>");
1.9 harris41 39: die($@);
40: }
41:
1.5 www 42: $childmaxattempts=10;
43:
1.8 harris41 44: # -------------------------------- Set signal handlers to record abnormal exits
45:
46: $SIG{'QUIT'}=\&catchexception;
47: $SIG{__DIE__}=\&catchexception;
48:
1.1 albertel 49: # ------------------------------------ Read httpd access.conf and get variables
50:
1.11 harris41 51: open (CONFIG,"/etc/httpd/conf/access.conf") || die "Can't read access.conf";
1.1 albertel 52:
53: while ($configline=<CONFIG>) {
54: if ($configline =~ /PerlSetVar/) {
55: my ($dummy,$varname,$varvalue)=split(/\s+/,$configline);
1.4 www 56: chomp($varvalue);
1.1 albertel 57: $perlvar{$varname}=$varvalue;
58: }
59: }
60: close(CONFIG);
1.7 www 61:
1.13 ! harris41 62: # ----------------------------- Make sure this process is running from user=www
! 63: my $wwwid=getpwnam('www');
! 64: if ($wwwid!=$<) {
! 65: $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
! 66: $subj="LON: $perlvar{'lonHostID'} User ID mismatch";
! 67: system("echo 'User ID mismatch. loncron must be run as user www.' |\
! 68: mailto $emailto -s '$subj' > /dev/null");
! 69: exit 1;
! 70: }
! 71:
1.7 www 72: # --------------------------------------------- Check if other instance running
73:
74: my $pidfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
75:
76: if (-e $pidfile) {
77: my $lfh=IO::File->new("$pidfile");
78: my $pide=<$lfh>;
79: chomp($pide);
1.11 harris41 80: if (kill 0 => $pide) { die "already running"; }
1.7 www 81: }
1.1 albertel 82:
83: # ------------------------------------------------------------- Read hosts file
84:
1.11 harris41 85: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
1.1 albertel 86:
87: while ($configline=<CONFIG>) {
88: my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
89: chomp($ip);
90: $hostip{$id}=$ip;
91: }
92: close(CONFIG);
93:
94: # -------------------------------------------------------- Routines for forking
95:
96: %children = (); # keys are current child process IDs,
97: # values are hosts
98: %childpid = (); # the other way around
99:
100: %childatt = (); # number of attempts to start server
101: # for ID
102:
103: sub REAPER { # takes care of dead children
104: $SIG{CHLD} = \&REAPER;
105: my $pid = wait;
106: my $wasserver=$children{$pid};
1.6 www 107: &logthis("<font color=red>CRITICAL: "
108: ."Child $pid for server $wasserver died ($childatt{$wasserver})</font>");
1.1 albertel 109: delete $children{$pid};
110: delete $childpid{$wasserver};
111: my $port = "$perlvar{'lonSockDir'}/$wasserver";
112: unlink($port);
113: }
114:
115: sub HUNTSMAN { # signal handler for SIGINT
116: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
117: kill 'INT' => keys %children;
118: my $execdir=$perlvar{'lonDaemons'};
119: unlink("$execdir/logs/lonc.pid");
1.5 www 120: &logthis("<font color=red>CRITICAL: Shutting down</font>");
1.1 albertel 121: exit; # clean up with dignity
122: }
123:
124: sub HUPSMAN { # signal handler for SIGHUP
125: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
126: kill 'INT' => keys %children;
1.5 www 127: &logthis("<font color=red>CRITICAL: Restarting</font>");
1.12 harris41 128: unlink("$execdir/logs/lonc.pid");
1.1 albertel 129: my $execdir=$perlvar{'lonDaemons'};
130: exec("$execdir/lonc"); # here we go again
131: }
132:
133: sub USRMAN {
134: &logthis("USR1: Trying to establish connections again");
135: foreach $thisserver (keys %hostip) {
136: $answer=subreply("ping",$thisserver);
1.6 www 137: &logthis("USR1: Ping $thisserver "
138: ."(pid >$childpid{$thisserver}<, $childatt{thisserver} attempts): "
139: ." >$answer<");
1.1 albertel 140: }
1.6 www 141: %childatt=();
1.1 albertel 142: }
143:
144: # -------------------------------------------------- Non-critical communication
145: sub subreply {
146: my ($cmd,$server)=@_;
1.5 www 147: my $answer='';
1.1 albertel 148: if ($server ne $perlvar{'lonHostID'}) {
149: my $peerfile="$perlvar{'lonSockDir'}/$server";
150: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
151: Type => SOCK_STREAM,
152: Timeout => 10)
153: or return "con_lost";
154: print $sclient "$cmd\n";
155: my $answer=<$sclient>;
156: chomp($answer);
157: if (!$answer) { $answer="con_lost"; }
158: } else { $answer='self_reply'; }
159: return $answer;
160: }
161:
162: # --------------------------------------------------------------------- Logging
163:
164: sub logthis {
165: my $message=shift;
166: my $execdir=$perlvar{'lonDaemons'};
167: my $fh=IO::File->new(">>$execdir/logs/lonc.log");
168: my $now=time;
169: my $local=localtime($now);
170: print $fh "$local ($$): $message\n";
171: }
172:
1.3 www 173:
174: sub logperm {
175: my $message=shift;
176: my $execdir=$perlvar{'lonDaemons'};
177: my $now=time;
178: my $local=localtime($now);
179: my $fh=IO::File->new(">>$execdir/logs/lonnet.perm.log");
180: print $fh "$now:$message:$local\n";
181: }
182:
1.1 albertel 183: # ---------------------------------------------------- Fork once and dissociate
184:
185: $fpid=fork;
186: exit if $fpid;
1.11 harris41 187: die "Couldn't fork: $!" unless defined ($fpid);
1.1 albertel 188:
1.11 harris41 189: POSIX::setsid() or die "Can't start new session: $!";
1.1 albertel 190:
191: # ------------------------------------------------------- Write our PID on disk
192:
193: $execdir=$perlvar{'lonDaemons'};
194: open (PIDSAVE,">$execdir/logs/lonc.pid");
195: print PIDSAVE "$$\n";
196: close(PIDSAVE);
1.5 www 197: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
1.1 albertel 198:
199: # ----------------------------- Ignore signals generated during initial startup
200: $SIG{HUP}=$SIG{USR1}='IGNORE';
201: # ------------------------------------------------------- Now we are on our own
202:
203: # Fork off our children, one for every server
204:
205: foreach $thisserver (keys %hostip) {
206: make_new_child($thisserver);
207: }
208:
209: &logthis("Done starting initial servers");
210: # ----------------------------------------------------- Install signal handlers
211:
212: $SIG{CHLD} = \&REAPER;
213: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
214: $SIG{HUP} = \&HUPSMAN;
215: $SIG{USR1} = \&USRMAN;
216:
217: # And maintain the population.
218: while (1) {
219: sleep; # wait for a signal (i.e., child's death)
220: # See who died and start new one
221: foreach $thisserver (keys %hostip) {
222: if (!$childpid{$thisserver}) {
1.6 www 223: if ($childatt{$thisserver}<=$childmaxattempts) {
224: $childatt{$thisserver}++;
1.5 www 225: &logthis(
226: "<font color=yellow>INFO: Trying to reconnect for $thisserver "
1.6 www 227: ."($childatt{$thisserver} of $childmaxattempts attempts)</font>");
1.1 albertel 228: make_new_child($thisserver);
229: }
230: }
231: }
232: }
233:
234:
235: sub make_new_child {
236:
237: my $conserver=shift;
238: my $pid;
239: my $sigset;
240: &logthis("Attempting to start child for server $conserver");
241: # block signal for fork
242: $sigset = POSIX::SigSet->new(SIGINT);
243: sigprocmask(SIG_BLOCK, $sigset)
1.11 harris41 244: or die "Can't block SIGINT for fork: $!\n";
1.1 albertel 245:
1.11 harris41 246: die "fork: $!" unless defined ($pid = fork);
1.1 albertel 247:
248: if ($pid) {
249: # Parent records the child's birth and returns.
250: sigprocmask(SIG_UNBLOCK, $sigset)
1.11 harris41 251: or die "Can't unblock SIGINT for fork: $!\n";
1.1 albertel 252: $children{$pid} = $conserver;
253: $childpid{$conserver} = $pid;
254: return;
255: } else {
256: # Child can *not* return from this subroutine.
257: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
258:
259: # unblock signals
260: sigprocmask(SIG_UNBLOCK, $sigset)
1.11 harris41 261: or die "Can't unblock SIGINT for fork: $!\n";
1.1 albertel 262:
263: # ----------------------------- This is the modified main program of non-forker
264:
265: $port = "$perlvar{'lonSockDir'}/$conserver";
266:
267: unlink($port);
268: # ---------------------------------------------------- Client to network server
269: unless (
270: $remotesock = IO::Socket::INET->new(PeerAddr => $hostip{$conserver},
271: PeerPort => $perlvar{'londPort'},
272: Proto => "tcp",
273: Type => SOCK_STREAM)
1.5 www 274: ) {
275: my $st=120+int(rand(240));
276: &logthis(
277: "<font color=blue>WARNING: Couldn't connect $conserver ($st secs): $@</font>");
278: sleep($st);
1.1 albertel 279: exit;
280: };
281: # --------------------------------------- Send a ping to make other end do USR1
1.2 www 282: print $remotesock "init\n";
283: $answer=<$remotesock>;
284: print $remotesock "$answer";
1.1 albertel 285: $answer=<$remotesock>;
286: chomp($answer);
1.2 www 287: &logthis("Init reply for $conserver: >$answer<");
1.1 albertel 288: sleep 5;
289: print $remotesock "pong\n";
290: $answer=<$remotesock>;
291: chomp($answer);
292: &logthis("Pong reply for $conserver: >$answer<");
293: # ----------------------------------------------------------- Initialize cipher
294:
295: print $remotesock "ekey\n";
296: my $buildkey=<$remotesock>;
297: my $key=$conserver.$perlvar{'lonHostID'};
298: $key=~tr/a-z/A-Z/;
299: $key=~tr/G-P/0-9/;
300: $key=~tr/Q-Z/0-9/;
301: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
302: $key=substr($key,0,32);
303: my $cipherkey=pack("H32",$key);
304: if ($cipher=new IDEA $cipherkey) {
1.12 harris41 305: &logthis("Secure connection initialized: $conserver");
1.1 albertel 306: } else {
1.5 www 307: my $st=120+int(rand(240));
308: &logthis(
309: "<font color=blue>WARNING: ".
310: "Could not establish secure connection, $conserver ($st secs)!</font>");
311: sleep($st);
312: exit;
1.1 albertel 313: }
314:
1.3 www 315: # ----------------------------------------- We're online, send delayed messages
316:
1.4 www 317: my @allbuffered;
1.3 www 318: my $path="$perlvar{'lonSockDir'}/delayed";
1.4 www 319: opendir(DIRHANDLE,$path);
320: @allbuffered=grep /\.$conserver$/, readdir DIRHANDLE;
321: closedir(DIRHANDLE);
1.3 www 322: my $dfname;
1.4 www 323: map {
324: $dfname="$path/$_";
325: &logthis($dfname);
1.3 www 326: my $wcmd;
327: {
328: my $dfh=IO::File->new($dfname);
1.4 www 329: $cmd=<$dfh>;
1.3 www 330: }
331: chomp($cmd);
332: my $bcmd=$cmd;
333: if ($cmd =~ /^encrypt\:/) {
334: my $rcmd=$cmd;
335: $rcmd =~ s/^encrypt\://;
336: chomp($rcmd);
337: my $cmdlength=length($rcmd);
338: $rcmd.=" ";
339: my $encrequest='';
340: for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
341: $encrequest.=
342: unpack("H16",$cipher->encrypt(substr($rcmd,$encidx,8)));
343: }
344: $cmd="enc:$cmdlength:$encrequest\n";
345: }
346:
347: print $remotesock "$cmd\n";
348: $answer=<$remotesock>;
349: chomp($answer);
350: if ($answer ne '') {
351: unlink("$dfname");
1.4 www 352: &logthis("Delayed $cmd to $conserver: >$answer<");
1.3 www 353: &logperm("S:$conserver:$bcmd");
354: }
1.4 www 355: } @allbuffered;
1.1 albertel 356:
357: # ------------------------------------------------------- Listen to UNIX socket
358: unless (
359: $server = IO::Socket::UNIX->new(Local => $port,
360: Type => SOCK_STREAM,
361: Listen => 10 )
1.5 www 362: ) {
363: my $st=120+int(rand(240));
364: &logthis(
365: "<font color=blue>WARNING: ".
366: "Can't make server socket $conserver ($st secs): $@</font>");
367: sleep($st);
1.1 albertel 368: exit;
369: };
370:
371: # -----------------------------------------------------------------------------
372:
1.5 www 373: &logthis("<font color=green>$conserver online</font>");
374:
375: # -----------------------------------------------------------------------------
1.1 albertel 376: # begin with empty buffers
377: %inbuffer = ();
378: %outbuffer = ();
379: %ready = ();
380:
381: tie %ready, 'Tie::RefHash';
382:
383: nonblock($server);
384: $select = IO::Select->new($server);
385:
386: # Main loop: check reads/accepts, check writes, check ready to process
387: while (1) {
388: my $client;
389: my $rv;
390: my $data;
391:
392: # check for new information on the connections we have
393:
394: # anything to read or accept?
395: foreach $client ($select->can_read(1)) {
396:
397: if ($client == $server) {
398: # accept a new connection
399:
400: $client = $server->accept();
401: $select->add($client);
402: nonblock($client);
403: } else {
404: # read data
405: $data = '';
406: $rv = $client->recv($data, POSIX::BUFSIZ, 0);
407:
408: unless (defined($rv) && length $data) {
409: # This would be the end of file, so close the client
410: delete $inbuffer{$client};
411: delete $outbuffer{$client};
412: delete $ready{$client};
413:
414: $select->remove($client);
415: close $client;
416: next;
417: }
418:
419: $inbuffer{$client} .= $data;
420:
421: # test whether the data in the buffer or the data we
422: # just read means there is a complete request waiting
423: # to be fulfilled. If there is, set $ready{$client}
424: # to the requests waiting to be fulfilled.
425: while ($inbuffer{$client} =~ s/(.*\n)//) {
426: push( @{$ready{$client}}, $1 );
427: }
428: }
429: }
430:
431: # Any complete requests to process?
432: foreach $client (keys %ready) {
433: handle($client);
434: }
435:
436: # Buffers to flush?
437: foreach $client ($select->can_write(1)) {
438: # Skip this client if we have nothing to say
439: next unless exists $outbuffer{$client};
440:
441: $rv = $client->send($outbuffer{$client}, 0);
442: unless (defined $rv) {
443: # Whine, but move on.
444: warn "I was told I could write, but I can't.\n";
445: next;
446: }
447: if (($rv == length $outbuffer{$client}) ||
448: ($! == POSIX::EWOULDBLOCK)) {
449: substr($outbuffer{$client}, 0, $rv) = '';
450: delete $outbuffer{$client} unless length $outbuffer{$client};
451: } else {
452: # Couldn't write all the data, and it wasn't because
453: # it would have blocked. Shutdown and move on.
454: delete $inbuffer{$client};
455: delete $outbuffer{$client};
456: delete $ready{$client};
457:
458: $select->remove($client);
459: close($client);
460: next;
461: }
462: }
463: }
464: }
465:
466: # ------------------------------------------------------- End of make_new_child
467:
468: # handle($socket) deals with all pending requests for $client
469: sub handle {
470: # requests are in $ready{$client}
471: # send output to $outbuffer{$client}
472: my $client = shift;
473: my $request;
474:
475: foreach $request (@{$ready{$client}}) {
476: # ============================================================= Process request
477: # $request is the text of the request
478: # put text of reply into $outbuffer{$client}
479: # -----------------------------------------------------------------------------
480: if ($request =~ /^encrypt\:/) {
481: my $cmd=$request;
482: $cmd =~ s/^encrypt\://;
483: chomp($cmd);
484: my $cmdlength=length($cmd);
485: $cmd.=" ";
486: my $encrequest='';
487: for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
488: $encrequest.=
489: unpack("H16",$cipher->encrypt(substr($cmd,$encidx,8)));
490: }
491: $request="enc:$cmdlength:$encrequest\n";
492: }
493: print $remotesock "$request";
494: $answer=<$remotesock>;
495: if ($answer) {
496: if ($answer =~ /^enc/) {
497: my ($cmd,$cmdlength,$encinput)=split(/:/,$answer);
498: chomp($encinput);
499: $answer='';
500: for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
501: $answer.=$cipher->decrypt(
502: pack("H16",substr($encinput,$encidx,16))
503: );
504: }
505: $answer=substr($answer,0,$cmdlength);
506: $answer.="\n";
507: }
508: $outbuffer{$client} .= $answer;
509: } else {
510: $outbuffer{$client} .= "con_lost\n";
511: }
512:
513: # ===================================================== Done processing request
514: }
515: delete $ready{$client};
516: # -------------------------------------------------------------- End non-forker
517: }
518: # ---------------------------------------------------------- End make_new_child
519: }
520:
521: # nonblock($socket) puts socket into nonblocking mode
522: sub nonblock {
523: my $socket = shift;
524: my $flags;
525:
526:
527: $flags = fcntl($socket, F_GETFL, 0)
1.11 harris41 528: or die "Can't get flags for socket: $!\n";
1.1 albertel 529: fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
1.11 harris41 530: or die "Can't make socket nonblocking: $!\n";
1.8 harris41 531: }
1.1 albertel 532:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>