--- loncom/lond 2003/09/23 11:23:31 1.147 +++ loncom/lond 2003/11/01 16:32:32 1.160 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.147 2003/09/23 11:23:31 foxr Exp $ +# $Id: lond,v 1.160 2003/11/01 16:32:32 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,72 +26,6 @@ # # http://www.lon-capa.org/ # -# 5/26/99,6/4,6/10,6/11,6/14,6/15,6/26,6/28,6/30, -# 7/8,7/9,7/10,7/12,7/17,7/19,9/21, -# 10/7,10/8,10/9,10/11,10/13,10/15,11/4,11/16, -# 12/7,12/15,01/06,01/11,01/12,01/14,2/8, -# 03/07,05/31 Gerd Kortemeyer -# 06/29,06/30,07/14,07/15,07/17,07/20,07/25,09/18 Gerd Kortemeyer -# 12/05,12/13,12/29 Gerd Kortemeyer -# YEAR=2001 -# 02/12 Gerd Kortemeyer -# 03/24 Gerd Kortemeyer -# 05/11,05/28,08/30 Gerd Kortemeyer -# 11/26,11/27 Gerd Kortemeyer -# 12/22 Gerd Kortemeyer -# YEAR=2002 -# 01/20/02,02/05 Gerd Kortemeyer -# 02/05 Guy Albertelli -# 02/12 Gerd Kortemeyer -# 02/19 Matthew Hall -# 02/25 Gerd Kortemeyer -# 01/xx/2003 Ron Fox.. Remove preforking. This makes the general daemon -# logic simpler (and there were problems maintaining the preforked -# population). Since the time averaged connection rate is close to zero -# because lonc's purpose is to maintain near continuous connnections, -# preforking is not really needed. -# 08/xx/2003 Ron Fox: Add management requests. Management requests -# will be validated via a call to ValidateManager. At present, this -# is done by simple host verification. In the future we can modify -# this function to do a certificate check. -# Management functions supported include: -# - pushing /home/httpd/lonTabs/hosts.tab -# - pushing /home/httpd/lonTabs/domain.tab -# 09/08/2003 Ron Fox: Told lond to take care of change logging so we -# don't have to remember it: -# $Log: lond,v $ -# Revision 1.147 2003/09/23 11:23:31 foxr -# Comlplete implementation of reinit functionality. Must still implement -# the actual initialization functionality, but the process can now -# receive the request and either invoke the appropriate internal function or -# signal the correct lonc. -# -# Revision 1.146 2003/09/16 10:28:14 foxr -# ReinitProcess - decode the process selector and produce the associated pid -# filename. Note: While it is possible to test that valid process selectors are -# handled properly I am not able to test that invalid process selectors produce -# the appropriate error as lonManage also blocks the use of invalid process selectors. -# -# Revision 1.145 2003/09/16 10:13:20 foxr -# Added ReinitProcess function to oversee the parsing and processing of the -# reinit: client request. -# -# Revision 1.144 2003/09/16 09:47:01 foxr -# Added skeletal support for SIGUSR2 (update hosts.tab) -# -# Revision 1.143 2003/09/15 10:03:52 foxr -# Completed and tested code for pushfile. -# -# Revision 1.142 2003/09/09 20:47:46 www -# Permanently store chatroom entries in chatroom.log -# -# Revision 1.141 2003/09/08 10:32:07 foxr -# Added PushFile sub This sub oversees the push of a new configuration table file -# Currently supported files are: -# - hosts.tab (transaction pushfile:hosts:contents) -# - domain.tab (transaction pushfile:domain:contents) -# - use strict; use lib '/home/httpd/lib/perl/'; @@ -116,7 +50,7 @@ my $DEBUG = 0; # Non zero to ena my $status=''; my $lastlog=''; -my $VERSION='$Revision: 1.147 $'; #' stupid emacs +my $VERSION='$Revision: 1.160 $'; #' stupid emacs my $remoteVERSION; my $currenthostid; my $currentdomainid; @@ -130,6 +64,7 @@ my $thisserver; my %hostid; my %hostdom; my %hostip; +my %managers; # If defined $managers{hostname} is a manager my %perlvar; # Will have the apache conf defined perl vars. # @@ -160,10 +95,10 @@ my @adderrors = ("ok", "lcuseradd Incorrect number of stdinput lines, must be 3", "lcuseradd Too many other simultaneous pwd changes in progress", "lcuseradd User does not exist", - "lcuseradd Unabel to mak ewww member of users's group", + "lcuseradd Unable to make www member of users's group", "lcuseradd Unable to su to root", "lcuseradd Unable to set password", - "lcuseradd Usrname has invbalid charcters", + "lcuseradd Usrname has invalid characters", "lcuseradd Password has an invalid character", "lcuseradd User already exists", "lcuseradd Could not add user.", @@ -186,7 +121,37 @@ sub GetCertificate { return $clientip; } +# +# ReadManagerTable: Reads in the current manager table. For now this is +# done on each manager authentication because: +# - These authentications are not frequent +# - This allows dynamic changes to the manager table +# without the need to signal to the lond. +# + +sub ReadManagerTable { + # Clean out the old table first.. + + foreach my $key (keys %managers) { + delete $managers{$key}; + } + + my $tablename = $perlvar{'lonTabDir'}."/managers.tab"; + if (!open (MANAGERS, $tablename)) { + logthis('No manager table. Nobody can manage!!'); + return; + } + while(my $host = ) { + chomp($host); + if (!defined $hostip{$host}) { + logthis(' manager '.$host. + " not in hosts.tab, rejected as manager"); + } else { + $managers{$host} = $hostip{$host}; # Whatever for now. + } + } +} # # ValidManager: Determines if a given certificate represents a valid manager. @@ -198,14 +163,25 @@ sub GetCertificate { sub ValidManager { my $certificate = shift; - my $hostentry = $hostid{$certificate}; - if ($hostentry ne undef) { - &logthis('Authenticating manager'. - " $hostentry"); - return 1; + ReadManagerTable; + + my $hostname = $hostid{$certificate}; + + + if ($hostname ne undef) { + if($managers{$hostname} ne undef) { + &logthis('Authenticating manager'. + " $hostname"); + return 1; + } else { + &logthis('"); + return 0; + } } else { &logthis(' Failed manager authentication '. "$certificate "); + return 0; } } # @@ -257,7 +233,66 @@ sub CopyFile { return 0; } } - +# +# Host files are passed out with externally visible host IPs. +# If, for example, we are behind a fire-wall or NAT host, our +# internally visible IP may be different than the externally +# visible IP. Therefore, we always adjust the contents of the +# host file so that the entry for ME is the IP that we believe +# we have. At present, this is defined as the entry that +# DNS has for us. If by some chance we are not able to get a +# DNS translation for us, then we assume that the host.tab file +# is correct. +# BUGBUGBUG - in the future, we really should see if we can +# easily query the interface(s) instead. +# Parameter(s): +# contents - The contents of the host.tab to check. +# Returns: +# newcontents - The adjusted contents. +# +# +sub AdjustHostContents { + my $contents = shift; + my $adjusted; + my $me = $perlvar{'lonHostID'}; + + foreach my $line (split(/\n/,$contents)) { + if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) { + chomp($line); + my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line); + if ($id eq $me) { + open(PIPE, " /usr/bin/host $name |") || die "Cant' make host pipeline"; + my $hostinfo = ; + close PIPE; + + my ($hostname, $has, $address, $ipnew) = split(/ /,$hostinfo); + &logthis(''. + "hostname = $hostname me = $me, name = $name actual ip = $ipnew "); + + if ($hostname eq $name) { # Lookup succeeded.. + &logthis(' look up ok '); + $ip = $ipnew; + } else { + &logthis(' Lookup failed: ' + .$hostname." ne $name "); + } + # Reconstruct the host line and append to adjusted: + + my $newline = "$id:$domain:$role:$name:$ip"; + if($maxcon ne "") { # Not all hosts have loncnew tuning params + $newline .= ":$maxcon:$idleto:$mincon"; + } + $adjusted .= $newline."\n"; + + } else { # Not me, pass unmodified. + $adjusted .= $line."\n"; + } + } else { # Blank or comment never re-written. + $adjusted .= $line."\n"; # Pass blanks and comments as is. + } + } + return $adjusted; +} # # InstallFile: Called to install an administrative file: # - The file is created with .tmp @@ -350,6 +385,16 @@ sub PushFile { &logthis(' Pushfile: backed up ' .$tablefile." to $backupfile"); + # If the file being pushed is the host file, we adjust the entry for ourself so that the + # IP will be our current IP as looked up in dns. Note this is only 99% good as it's possible + # to conceive of conditions where we don't have a DNS entry locally. This is possible in a + # network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now) + # that possibilty. + + if($filename eq "host") { + $contents = AdjustHostContents($contents); + } + # Install the new file: if(!InstallFile($tablefile, $contents)) { @@ -492,17 +537,7 @@ if (-e $pidfile) { # ------------------------------------------------------------- Read hosts file -open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file"; -while (my $configline=) { - my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); - chomp($ip); $ip=~s/\D+$//; - $hostid{$ip}=$id; - $hostdom{$id}=$domain; - $hostip{$id}=$ip; - if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; } -} -close(CONFIG); # establish SERVER socket, bind and listen. $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'}, @@ -552,6 +587,53 @@ sub HUPSMAN { # sig } # +# Kill off hashes that describe the host table prior to re-reading it. +# Hashes affected are: +# %hostid, %hostdom %hostip +# +sub KillHostHashes { + foreach my $key (keys %hostid) { + delete $hostid{$key}; + } + foreach my $key (keys %hostdom) { + delete $hostdom{$key}; + } + foreach my $key (keys %hostip) { + delete $hostip{$key}; + } +} +# +# Read in the host table from file and distribute it into the various hashes: +# +# - %hostid - Indexed by IP, the loncapa hostname. +# - %hostdom - Indexed by loncapa hostname, the domain. +# - %hostip - Indexed by hostid, the Ip address of the host. +sub ReadHostTable { + + open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file"; + + while (my $configline=) { + my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); + chomp($ip); $ip=~s/\D+$//; + $hostid{$ip}=$id; + $hostdom{$id}=$domain; + $hostip{$id}=$ip; + if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; } + } + close(CONFIG); +} +# +# Reload the Apache daemon's state. +# This is done by invoking /home/httpd/perl/apachereload +# a setuid perl script that can be root for us to do this job. +# +sub ReloadApache { + my $execdir = $perlvar{'lonDaemons'}; + my $script = $execdir."/apachereload"; + system($script); +} + +# # Called in response to a USR2 signal. # - Reread hosts.tab # - All children connected to hosts that were removed from hosts.tab @@ -563,8 +645,32 @@ sub HUPSMAN { # sig # sub UpdateHosts { logthis(' Updating connections '); + # + # The %children hash has the set of IP's we currently have children + # on. These need to be matched against records in the hosts.tab + # Any ip's no longer in the table get killed off they correspond to + # either dropped or changed hosts. Note that the re-read of the table + # will take care of new and changed hosts as connections come into being. + + + KillHostHashes; + ReadHostTable; + + foreach my $child (keys %children) { + my $childip = $children{$child}; + if(!$hostid{$childip}) { + logthis(' UpdateHosts killing child ' + ." $child for ip $childip "); + kill('INT', $child); + } else { + logthis(' keeping child for ip ' + ." $childip (pid=$child) "); + } + } + ReloadApache; } + sub checkchildren { &initnewstatus(); &logstatus(); @@ -596,7 +702,7 @@ sub checkchildren { } } $SIG{ALRM} = 'DEFAULT'; - $SIG{__DIE__} = \&cathcexception; + $SIG{__DIE__} = \&catchexception; } # --------------------------------------------------------------------- Logging @@ -809,6 +915,9 @@ $SIG{HUP} = \&HUPSMAN; $SIG{USR1} = \&checkchildren; $SIG{USR2} = \&UpdateHosts; +# Read the host hashes: + +ReadHostTable; # -------------------------------------------------------------- # Accept connections. When a connection comes in, it is validated @@ -833,12 +942,23 @@ sub make_new_child { or die "Can't block SIGINT for fork: $!\n"; die "fork: $!" unless defined ($pid = fork); + + $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of + # connection liveness. + + # + # Figure out who we're talking to so we can record the peer in + # the pid hash. + # + my $caller = getpeername($client); + my ($port,$iaddr)=unpack_sockaddr_in($caller); + $clientip=inet_ntoa($iaddr); if ($pid) { # Parent records the child's birth and returns. sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock SIGINT for fork: $!\n"; - $children{$pid} = 1; + $children{$pid} = $clientip; $children++; &status('Started child '.$pid); return; @@ -865,12 +985,8 @@ sub make_new_child { # ============================================================================= # do something with the connection # ----------------------------------------------------------------------------- - $client->sockopt(SO_KEEPALIVE, 1);# Enable monitoring of - # connection liveness. - # see if we know client and check for spoof IP by challenge - my $caller = getpeername($client); - my ($port,$iaddr)=unpack_sockaddr_in($caller); - $clientip=inet_ntoa($iaddr); + # see if we know client and check for spoof IP by challenge + my $clientrec=($hostid{$clientip} ne undef); &logthis( "INFO: Connection, $clientip ($hostid{$clientip})" @@ -1326,33 +1442,39 @@ sub make_new_child { } # -------------------------------------- fetch a user file from a remote server } elsif ($userinput =~ /^fetchuserfile/) { - my ($cmd,$fname)=split(/:/,$userinput); - my ($udom,$uname,$ufile)=split(/\//,$fname); - my $udir=propath($udom,$uname).'/userfiles'; - unless (-e $udir) { mkdir($udir,0770); } + my ($cmd,$fname)=split(/:/,$userinput); + my ($udom,$uname,$ufile)=split(/\//,$fname); + my $udir=propath($udom,$uname).'/userfiles'; + unless (-e $udir) { mkdir($udir,0770); } if (-e $udir) { - $ufile=~s/^[\.\~]+//; - $ufile=~s/\///g; - my $transname=$udir.'/'.$ufile; - my $remoteurl='http://'.$clientip.'/userfiles/'.$fname; - my $response; - { - my $ua=new LWP::UserAgent; - my $request=new HTTP::Request('GET',"$remoteurl"); - $response=$ua->request($request,$transname); - } - if ($response->is_error()) { - unlink($transname); - my $message=$response->status_line; - &logthis( - "LWP GET: $message for $fname ($remoteurl)"); - print $client "failed\n"; - } else { - print $client "ok\n"; - } - } else { - print $client "not_home\n"; - } + $ufile=~s/^[\.\~]+//; + $ufile=~s/\///g; + my $destname=$udir.'/'.$ufile; + my $transname=$udir.'/'.$ufile.'.in.transit'; + my $remoteurl='http://'.$clientip.'/userfiles/'.$fname; + my $response; + { + my $ua=new LWP::UserAgent; + my $request=new HTTP::Request('GET',"$remoteurl"); + $response=$ua->request($request,$transname); + } + if ($response->is_error()) { + unlink($transname); + my $message=$response->status_line; + &logthis("LWP GET: $message for $fname ($remoteurl)"); + print $client "failed\n"; + } else { + if (!rename($transname,$destname)) { + &logthis("Unable to move $transname to $destname"); + unlink($transname); + print $client "failed\n"; + } else { + print $client "ok\n"; + } + } + } else { + print $client "not_home\n"; + } # ------------------------------------------ authenticate access to a user file } elsif ($userinput =~ /^tokenauthuserfile/) { my ($cmd,$fname,$session)=split(/:/,$userinput); @@ -2308,7 +2430,7 @@ sub currentversion { # see if this is a regular file (ignore links produced earlier) my $thisfile=$ulsdir.'/'.$ulsfn; unless (-l $thisfile) { - if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E/) { + if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) { if ($1>$version) { $version=$1; } } } @@ -2461,7 +2583,7 @@ sub userload { while ($filename=readdir(LONIDS)) { if ($filename eq '.' || $filename eq '..') {next;} my ($mtime)=(stat($perlvar{'lonIDsDir'}.'/'.$filename))[9]; - if ($curtime-$mtime < 3600) { $numusers++; } + if ($curtime-$mtime < 1800) { $numusers++; } } closedir(LONIDS); }