--- loncom/lond 2000/12/05 16:51:41 1.23 +++ loncom/lond 2002/08/09 10:05:00 1.87 @@ -1,6 +1,31 @@ #!/usr/bin/perl # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) +# +# $Id: lond,v 1.87 2002/08/09 10:05:00 www Exp $ +# +# Copyright Michigan State University Board of Trustees +# +# This file is part of the LearningOnline Network with CAPA (LON-CAPA). +# +# LON-CAPA is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# LON-CAPA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LON-CAPA; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# /home/httpd/html/adm/gpl.txt +# +# 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, @@ -8,13 +33,38 @@ # 03/07,05/31 Gerd Kortemeyer # 06/26 Scott Harrison # 06/29,06/30,07/14,07/15,07/17,07/20,07/25,09/18 Gerd Kortemeyer -# +# 12/05 Scott Harrison +# 12/05,12/13,12/29 Gerd Kortemeyer +# YEAR=2001 +# Jan 01 Scott Harrison +# 02/12 Gerd Kortemeyer +# 03/15 Scott Harrison +# 03/24 Gerd Kortemeyer +# 04/02 Scott Harrison +# 05/11,05/28,08/30 Gerd Kortemeyer +# 9/30,10/22,11/13,11/15,11/16 Scott Harrison +# 11/26,11/27 Gerd Kortemeyer +# 12/20 Scott Harrison +# 12/22 Gerd Kortemeyer +# YEAR=2002 +# 01/20/02,02/05 Gerd Kortemeyer +# 02/05 Guy Albertelli +# 02/07 Scott Harrison +# 02/12 Gerd Kortemeyer +# 02/19 Matthew Hall +# 02/25 Gerd Kortemeyer +# 05/11 Scott Harrison +### + # based on "Perl Cookbook" ISBN 1-56592-243-3 # preforker - server who forks first # runs as a daemon # HUPs # uses IDEA encryption +use lib '/home/httpd/lib/perl/'; +use LONCAPA::Configuration; + use IO::Socket; use IO::File; use Apache::File; @@ -24,46 +74,53 @@ use Crypt::IDEA; use LWP::UserAgent(); use GDBM_File; use Authen::Krb4; +use lib '/home/httpd/lib/perl/'; +use localauth; + +my $DEBUG = 0; # Non zero to enable debug log entries. + +my $status=''; +my $lastlog=''; # grabs exception and records it to log before exiting sub catchexception { - my ($signal)=@_; + my ($error)=@_; + $SIG{'QUIT'}='DEFAULT'; + $SIG{__DIE__}='DEFAULT'; &logthis("CRITICAL: " ."ABNORMAL EXIT. Child $$ for server $wasserver died through " - ."$signal with this parameter->[$@]"); - die($@); + ."a crash with this error msg->[$error]"); + &logthis('Famous last words: '.$status.' - '.$lastlog); + if ($client) { print $client "error: $error\n"; } + $server->close(); + die($error); } -# grabs exception and records it to log before exiting -# NOTE: we must NOT use the regular (non-overrided) die function in -# the code because a handler CANNOT be attached to it -# (despite what some of the documentation says about SIG{__DIE__}. -sub catchdie { - my ($message)=@_; - &logthis("CRITICAL: " - ."ABNORMAL EXIT. Child $$ for server $wasserver died through " - ."\_\_DIE\_\_ with this parameter->[$message]"); - die($message); +sub timeout { + &logthis("CRITICAL: TIME OUT ".$$.""); + &catchexception('Timeout'); } - # -------------------------------- Set signal handlers to record abnormal exits $SIG{'QUIT'}=\&catchexception; $SIG{__DIE__}=\&catchexception; -# ------------------------------------ Read httpd access.conf and get variables - -open (CONFIG,"/etc/httpd/conf/access.conf") - || catchdie "Can't read access.conf"; - -while ($configline=) { - if ($configline =~ /PerlSetVar/) { - my ($dummy,$varname,$varvalue)=split(/\s+/,$configline); - chomp($varvalue); - $perlvar{$varname}=$varvalue; - } +# ---------------------------------- Read loncapa_apache.conf and loncapa.conf +&status("Read loncapa_apache.conf and loncapa.conf"); +my $perlvarref=LONCAPA::Configuration::read_conf('loncapa_apache.conf', + 'loncapa.conf'); +my %perlvar=%{$perlvarref}; +undef $perlvarref; + +# ----------------------------- Make sure this process is running from user=www +my $wwwid=getpwnam('www'); +if ($wwwid!=$<) { + $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}"; + $subj="LON: $perlvar{'lonHostID'} User ID mismatch"; + system("echo 'User ID mismatch. lond must be run as user www.' |\ + mailto $emailto -s '$subj' > /dev/null"); + exit 1; } -close(CONFIG); # --------------------------------------------- Check if other instance running @@ -73,19 +130,18 @@ if (-e $pidfile) { my $lfh=IO::File->new("$pidfile"); my $pide=<$lfh>; chomp($pide); - if (kill 0 => $pide) { catchdie "already running"; } + if (kill 0 => $pide) { die "already running"; } } $PREFORK=4; # number of children to maintain, at least four spare # ------------------------------------------------------------- Read hosts file -open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") - || catchdie "Can't read host file"; +open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file"; while ($configline=) { my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); - chomp($ip); + chomp($ip); $ip=~s/\D+$//; $hostid{$ip}=$id; if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; } $PREFORK++; @@ -98,13 +154,13 @@ $server = IO::Socket::INET->new(LocalPor Proto => 'tcp', Reuse => 1, Listen => 10 ) - or catchdie "making socket: $@\n"; + or die "making socket: $@\n"; # --------------------------------------------------------- Do global variables # global variables -$MAX_CLIENTS_PER_CHILD = 5; # number of clients each child should +$MAX_CLIENTS_PER_CHILD = 50; # number of clients each child should # process %children = (); # keys are current child process IDs $children = 0; # current number of children @@ -112,14 +168,19 @@ $children = 0; # cu sub REAPER { # takes care of dead children $SIG{CHLD} = \&REAPER; my $pid = wait; - $children --; - &logthis("Child $pid died"); - delete $children{$pid}; + if (defined($children{$pid})) { + &logthis("Child $pid died"); + $children --; + delete $children{$pid}; + } else { + &logthis("Unknown Child $pid died"); + } } sub HUNTSMAN { # signal handler for SIGINT local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children kill 'INT' => keys %children; + &logthis("Free socket: ".shutdown($server,2)); # free up socket my $execdir=$perlvar{'lonDaemons'}; unlink("$execdir/logs/lond.pid"); &logthis("CRITICAL: Shutting down"); @@ -129,12 +190,39 @@ sub HUNTSMAN { # si sub HUPSMAN { # signal handler for SIGHUP local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children kill 'INT' => keys %children; - close($server); # free up socket + &logthis("Free socket: ".shutdown($server,2)); # free up socket &logthis("CRITICAL: Restarting"); + unlink("$execdir/logs/lond.pid"); my $execdir=$perlvar{'lonDaemons'}; exec("$execdir/lond"); # here we go again } +sub checkchildren { + &initnewstatus(); + &logstatus(); + &logthis('Going to check on the children'); + $docdir=$perlvar{'lonDocRoot'}; + foreach (sort keys %children) { + sleep 1; + unless (kill 'USR1' => $_) { + &logthis ('Child '.$_.' is dead'); + &logstatus($$.' is dead'); + } + } + sleep 5; + foreach (sort keys %children) { + unless (-e "$docdir/lon-status/londchld/$_.txt") { + &logthis('Child '.$_.' did not respond'); + kill 9 => $_; + $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}"; + $subj="LON: $perlvar{'lonHostID'} killed lond process $_"; + my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`; + $execdir=$perlvar{'lonDaemons'}; + $result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_` + } + } +} + # --------------------------------------------------------------------- Logging sub logthis { @@ -143,9 +231,54 @@ sub logthis { my $fh=IO::File->new(">>$execdir/logs/lond.log"); my $now=time; my $local=localtime($now); + $lastlog=$local.': '.$message; print $fh "$local ($$): $message\n"; } +# ------------------------- Conditional log if $DEBUG true. +sub Debug { + my $message = shift; + if($DEBUG) { + &logthis($message); + } +} +# ------------------------------------------------------------------ Log status + +sub logstatus { + my $docdir=$perlvar{'lonDocRoot'}; + { + my $fh=IO::File->new(">>$docdir/lon-status/londstatus.txt"); + print $fh $$."\t".$status."\t".$lastlog."\n"; + $fh->close(); + } + { + my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt"); + print $fh $status."\n".$lastlog."\n".time; + $fh->close(); + } +} + +sub initnewstatus { + my $docdir=$perlvar{'lonDocRoot'}; + my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt"); + my $now=time; + my $local=localtime($now); + print $fh "LOND status $local - parent $$\n\n"; + opendir(DIR,"$docdir/lon-status/londchld"); + while ($filename=readdir(DIR)) { + unlink("$docdir/lon-status/londchld/$filename"); + } + closedir(DIR); +} + +# -------------------------------------------------------------- Status setting + +sub status { + my $what=shift; + my $now=time; + my $local=localtime($now); + $status=$local.': '.$what; +} # -------------------------------------------------------- Escape Special Chars @@ -175,10 +308,10 @@ sub reconlonc { if (kill 0 => $loncpid) { &logthis("lonc at pid $loncpid responding, sending USR1"); kill USR1 => $loncpid; - sleep 1; + sleep 5; if (-e "$peerfile") { return; } &logthis("$peerfile still not there, give it another try"); - sleep 5; + sleep 10; if (-e "$peerfile") { return; } &logthis( "WARNING: $peerfile still not there, giving up"); @@ -216,6 +349,7 @@ sub reply { if ($answer eq 'con_lost') { $answer=subreply("ping",$server); if ($answer ne $server) { + &logthis("sub reply: answer != server"); &reconlonc("$perlvar{'lonSockDir'}/$server"); } $answer=subreply($cmd,$server); @@ -281,9 +415,9 @@ sub ishome { $fpid=fork; exit if $fpid; -catchdie "Couldn't fork: $!" unless defined ($fpid); +die "Couldn't fork: $!" unless defined ($fpid); -POSIX::setsid() or catchdie "Can't start new session: $!"; +POSIX::setsid() or die "Can't start new session: $!"; # ------------------------------------------------------- Write our PID on disk @@ -292,6 +426,7 @@ open (PIDSAVE,">$execdir/logs/lond.pid") print PIDSAVE "$$\n"; close(PIDSAVE); &logthis("CRITICAL: ---------- Starting ----------"); +&status('Starting'); # ------------------------------------------------------- Now we are on our own @@ -302,13 +437,19 @@ for (1 .. $PREFORK) { # ----------------------------------------------------- Install signal handlers +&status('Forked children'); + $SIG{CHLD} = \&REAPER; $SIG{INT} = $SIG{TERM} = \&HUNTSMAN; $SIG{HUP} = \&HUPSMAN; +$SIG{USR1} = \&checkchildren; # And maintain the population. while (1) { + &status('Sleeping'); sleep; # wait for a signal (i.e., child's death) + &logthis('Woke up'); + &status('Woke up'); for ($i = $children; $i < $PREFORK; $i++) { make_new_child(); # top up the child pool } @@ -322,31 +463,37 @@ sub make_new_child { # block signal for fork $sigset = POSIX::SigSet->new(SIGINT); sigprocmask(SIG_BLOCK, $sigset) - or catchdie "Can't block SIGINT for fork: $!\n"; + or die "Can't block SIGINT for fork: $!\n"; - catchdie "fork: $!" unless defined ($pid = fork); + die "fork: $!" unless defined ($pid = fork); if ($pid) { # Parent records the child's birth and returns. sigprocmask(SIG_UNBLOCK, $sigset) - or catchdie "Can't unblock SIGINT for fork: $!\n"; + or die "Can't unblock SIGINT for fork: $!\n"; $children{$pid} = 1; $children++; + &status('Started child '.$pid); return; } else { # Child can *not* return from this subroutine. $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before - + $SIG{USR1}= \&logstatus; + $SIG{ALRM}= \&timeout; + $lastlog='Forked '; + $status='Forked'; + # unblock signals sigprocmask(SIG_UNBLOCK, $sigset) - or catchdie "Can't unblock SIGINT for fork: $!\n"; + or die "Can't unblock SIGINT for fork: $!\n"; $tmpsnum=0; # handle connections until we've reached $MAX_CLIENTS_PER_CHILD for ($i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) { + &status('Idle, waiting for connection'); $client = $server->accept() or last; - + &status('Accepted connection'); # ============================================================================= # do something with the connection # ----------------------------------------------------------------------------- @@ -356,14 +503,19 @@ sub make_new_child { my $clientip=inet_ntoa($iaddr); my $clientrec=($hostid{$clientip} ne undef); &logthis( -"INFO: Connect from $clientip ($hostid{$clientip})"); +"INFO: Connection $i, $clientip ($hostid{$clientip})" + ); + &status("Connecting $clientip ($hostid{$clientip})"); my $clientok; if ($clientrec) { + &status("Waiting for init from $clientip ($hostid{$clientip})"); my $remotereq=<$client>; $remotereq=~s/\W//g; if ($remotereq eq 'init') { my $challenge="$$".time; print $client "$challenge\n"; + &status( + "Waiting for challenge reply from $clientip ($hostid{$clientip})"); $remotereq=<$client>; $remotereq=~s/\W//g; if ($challenge eq $remotereq) { @@ -372,28 +524,33 @@ sub make_new_child { } else { &logthis( "WARNING: $clientip did not reply challenge"); - print $client "bye\n"; + &status('No challenge reply '.$clientip); } } else { &logthis( "WARNING: " ."$clientip failed to initialize: >$remotereq< "); - print $client "bye\n"; + &status('No init '.$clientip); } } else { &logthis( "WARNING: Unknown client $clientip"); - print $client "bye\n"; + &status('Hung up on '.$clientip); } if ($clientok) { # ---------------- New known client connecting, could mean machine online again + &reconlonc("$perlvar{'lonSockDir'}/$hostid{$clientip}"); &logthis( "Established connection: $hostid{$clientip}"); + &status('Will listen to '.$hostid{$clientip}); # ------------------------------------------------------------ Process requests while (my $userinput=<$client>) { chomp($userinput); + Debug("Request = $userinput\n"); + &status('Processing '.$hostid{$clientip}.': '.$userinput); my $wasenc=0; + alarm(120); # ------------------------------------------------------------ See if encrypted if ($userinput =~ /^enc/) { if ($cipher) { @@ -407,8 +564,9 @@ sub make_new_child { } $userinput=substr($userinput,0,$cmdlength); $wasenc=1; - } } + } + # ------------------------------------------------------------- Normal commands # ------------------------------------------------------------------------ ping if ($userinput =~ /^ping/) { @@ -441,6 +599,20 @@ sub make_new_child { $loadavg =~ s/\s.*//g; my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'}; print $client "$loadpercent\n"; +# ----------------------------------------------------------------- currentauth + } elsif ($userinput =~ /^currentauth/) { + if ($wasenc==1) { + my ($cmd,$udom,$uname)=split(/:/,$userinput); + my $result = GetAuthType($udom, $uname); + if($result eq "nouser") { + print $client "unknown_user\n"; + } + else { + print $client "$result\n" + } + } else { + print $client "refused\n"; + } # ------------------------------------------------------------------------ auth } elsif ($userinput =~ /^auth/) { if ($wasenc==1) { @@ -460,14 +632,30 @@ sub make_new_child { (crypt($upass,$contentpwd) eq $contentpwd); } elsif ($howpwd eq 'unix') { $contentpwd=(getpwnam($uname))[1]; - $pwdcorrect= - (crypt($upass,$contentpwd) eq $contentpwd); + my $pwauth_path="/usr/local/sbin/pwauth"; + unless ($contentpwd eq 'x') { + $pwdcorrect= + (crypt($upass,$contentpwd) eq $contentpwd); + } + elsif (-e $pwauth_path) { + open PWAUTH, "|$pwauth_path" or + die "Cannot invoke authentication"; + print PWAUTH "$uname\n$upass\n"; + close PWAUTH; + $pwdcorrect=!$?; + } } elsif ($howpwd eq 'krb4') { + $null=pack("C",0); + unless ($upass=~/$null/) { $pwdcorrect=( Authen::Krb4::get_pw_in_tkt($uname,"", $contentpwd,'krbtgt',$contentpwd,1, $upass) == 0); - } + } else { $pwdcorrect=0; } + } elsif ($howpwd eq 'localauth') { + $pwdcorrect=&localauth::localauth($uname,$upass, + $contentpwd); + } if ($pwdcorrect) { print $client "authorized\n"; } else { @@ -485,7 +673,10 @@ sub make_new_child { my ($cmd,$udom,$uname,$upass,$npass)=split(/:/,$userinput); chomp($npass); - my $proname=propath($udom,$uname); + $upass=&unescape($upass); + $npass=&unescape($npass); + &logthis("Trying to change password for $uname"); + my $proname=propath($udom,$uname); my $passfilename="$proname/passwd"; if (-e $passfilename) { my $realpasswd; @@ -499,12 +690,43 @@ sub make_new_child { $salt=substr($salt,6,2); my $ncpass=crypt($npass,$salt); { my $pf = IO::File->new(">$passfilename"); - print $pf "internal:$ncpass\n";; } + print $pf "internal:$ncpass\n"; } + &logthis("Result of password change for $uname: pwchange_success"); print $client "ok\n"; } else { print $client "non_authorized\n"; } - } else { + } elsif ($howpwd eq 'unix') { + # Unix means we have to access /etc/password + # one way or another. + # First: Make sure the current password is + # correct + $contentpwd=(getpwnam($uname))[1]; + my $pwdcorrect = "0"; + my $pwauth_path="/usr/local/sbin/pwauth"; + unless ($contentpwd eq 'x') { + $pwdcorrect= + (crypt($upass,$contentpwd) eq $contentpwd); + } elsif (-e $pwauth_path) { + open PWAUTH, "|$pwauth_path" or + die "Cannot invoke authentication"; + print PWAUTH "$uname\n$upass\n"; + close PWAUTH; + $pwdcorrect=!$?; + } + if ($pwdcorrect) { + my $execdir=$perlvar{'lonDaemons'}; + my $pf = IO::File->new("|$execdir/lcpasswd"); + print $pf "$uname\n$npass\n$npass\n"; + close $pf; + my $result = ($?>0 ? 'pwchange_failure' + : 'ok'); + &logthis("Result of password change for $uname: $result"); + print $client "$result\n"; + } else { + print $client "non_authorized\n"; + } + } else { print $client "auth_mode_error\n"; } } else { @@ -513,6 +735,156 @@ sub make_new_child { } else { print $client "refused\n"; } +# -------------------------------------------------------------------- makeuser + } elsif ($userinput =~ /^makeuser/) { + Debug("Make user received"); + my $oldumask=umask(0077); + if ($wasenc==1) { + my + ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput); + &Debug("cmd =".$cmd." $udom =".$udom. + " uname=".$uname); + chomp($npass); + $npass=&unescape($npass); + my $proname=propath($udom,$uname); + my $passfilename="$proname/passwd"; + &Debug("Password file created will be:". + $passfilename); + if (-e $passfilename) { + print $client "already_exists\n"; + } elsif ($udom ne $perlvar{'lonDefDomain'}) { + print $client "not_right_domain\n"; + } else { + @fpparts=split(/\//,$proname); + $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2]; + $fperror=''; + for ($i=3;$i<=$#fpparts;$i++) { + $fpnow.='/'.$fpparts[$i]; + unless (-e $fpnow) { + unless (mkdir($fpnow,0777)) { + $fperror="error:$!"; + } + } + } + unless ($fperror) { + if ($umode eq 'krb4') { + { + my $pf = IO::File->new(">$passfilename"); + print $pf "krb4:$npass\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'internal') { + my $salt=time; + $salt=substr($salt,6,2); + my $ncpass=crypt($npass,$salt); + { + &Debug("Creating internal auth"); + my $pf = IO::File->new(">$passfilename"); + print $pf "internal:$ncpass\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'localauth') { + { + my $pf = IO::File->new(">$passfilename"); + print $pf "localauth:$npass\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'unix') { + { + my $execpath="$perlvar{'lonDaemons'}/". + "lcuseradd"; + { + &Debug("Executing external: ". + $execpath); + my $se = IO::File->new("|$execpath"); + print $se "$uname\n"; + print $se "$npass\n"; + print $se "$npass\n"; + } + my $pf = IO::File->new(">$passfilename"); + print $pf "unix:\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'none') { + { + my $pf = IO::File->new(">$passfilename"); + print $pf "none:\n"; + } + print $client "ok\n"; + } else { + print $client "auth_mode_error\n"; + } + } else { + print $client "$fperror\n"; + } + } + } else { + print $client "refused\n"; + } + umask($oldumask); +# -------------------------------------------------------------- changeuserauth + } elsif ($userinput =~ /^changeuserauth/) { + &Debug("Changing authorization"); + if ($wasenc==1) { + my + ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput); + chomp($npass); + &Debug("cmd = ".$cmd." domain= ".$udom. + "uname =".$uname." umode= ".$umode); + $npass=&unescape($npass); + my $proname=propath($udom,$uname); + my $passfilename="$proname/passwd"; + if ($udom ne $perlvar{'lonDefDomain'}) { + print $client "not_right_domain\n"; + } else { + if ($umode eq 'krb4') { + { + my $pf = IO::File->new(">$passfilename"); + print $pf "krb4:$npass\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'internal') { + my $salt=time; + $salt=substr($salt,6,2); + my $ncpass=crypt($npass,$salt); + { + my $pf = IO::File->new(">$passfilename"); + print $pf "internal:$ncpass\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'localauth') { + { + my $pf = IO::File->new(">$passfilename"); + print $pf "localauth:$npass\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'unix') { + { + my $execpath="$perlvar{'lonDaemons'}/". + "lcuseradd"; + { + my $se = IO::File->new("|$execpath"); + print $se "$uname\n"; + print $se "$npass\n"; + print $se "$npass\n"; + } + my $pf = IO::File->new(">$passfilename"); + print $pf "unix:\n"; + } + print $client "ok\n"; + } elsif ($umode eq 'none') { + { + my $pf = IO::File->new(">$passfilename"); + print $pf "none:\n"; + } + print $client "ok\n"; + } else { + print $client "auth_mode_error\n"; + } + } + } else { + print $client "refused\n"; + } # ------------------------------------------------------------------------ home } elsif ($userinput =~ /^home/) { my ($cmd,$udom,$uname)=split(/:/,$userinput); @@ -550,12 +922,13 @@ sub make_new_child { $response=$ua->request($request,$transname); } if ($response->is_error()) { - unline($transname); + unlink($transname); my $message=$response->status_line; &logthis( "LWP GET: $message for $fname ($remoteurl)"); } else { if ($remoteurl!~/\.meta$/) { + my $ua=new LWP::UserAgent; my $mrequest= new HTTP::Request('GET',$remoteurl.'.meta'); my $mresponse= @@ -574,42 +947,61 @@ sub make_new_child { } else { print $client "rejected\n"; } +# -------------------------------------- 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); } + 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"; + } +# ------------------------------------------ authenticate access to a user file + } elsif ($userinput =~ /^tokenauthuserfile/) { + my ($cmd,$fname,$session)=split(/:/,$userinput); + chomp($session); + $reply='non_auth'; + if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'. + $session.'.id')) { + while ($line=) { + if ($line=~/userfile\.$fname\=/) { $reply='ok'; } + } + close(ENVIN); + print $client $reply."\n"; + } else { + print $client "invalid_token\n"; + } # ----------------------------------------------------------------- unsubscribe } elsif ($userinput =~ /^unsub/) { my ($cmd,$fname)=split(/:/,$userinput); if (-e $fname) { - if (unlink("$fname.$hostid{$clientip}")) { - print $client "ok\n"; - } else { - print $client "not_subscribed\n"; - } + print $client &unsub($client,$fname,$clientip); } else { print $client "not_found\n"; } # ------------------------------------------------------------------- subscribe } elsif ($userinput =~ /^sub/) { - my ($cmd,$fname)=split(/:/,$userinput); - my $ownership=ishome($fname); - if ($ownership eq 'owner') { - if (-e $fname) { - if (-d $fname) { - print $client "directory\n"; - } else { - $now=time; - { - my $sh=IO::File->new(">$fname.$hostid{$clientip}"); - print $sh "$clientip:$now\n"; - } - $fname=~s/\/home\/httpd\/html\/res/raw/; - $fname="http://$thisserver/".$fname; - print $client "$fname\n"; - } - } else { - print $client "not_found\n"; - } - } else { - print $client "rejected\n"; - } + print $client &subscribe($userinput,$clientip); # ------------------------------------------------------------------------- log } elsif ($userinput =~ /^log/) { my ($cmd,$udom,$uname,$what)=split(/:/,$userinput); @@ -635,7 +1027,7 @@ sub make_new_child { chomp($what); my $proname=propath($udom,$uname); my $now=time; - { + unless ($namespace=~/^nohist\_/) { my $hfh; if ( $hfh=IO::File->new(">>$proname/$namespace.hist") @@ -660,9 +1052,13 @@ sub make_new_child { } # -------------------------------------------------------------------- rolesput } elsif ($userinput =~ /^rolesput/) { + &Debug("rolesput"); if ($wasenc==1) { my ($cmd,$exedom,$exeuser,$udom,$uname,$what) =split(/:/,$userinput); + &Debug("cmd = ".$cmd." exedom= ".$exedom. + "user = ".$exeuser." udom=".$udom. + "what = ".$what); my $namespace='roles'; chomp($what); my $proname=propath($udom,$uname); @@ -679,7 +1075,11 @@ sub make_new_child { if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT,0640)) { foreach $pair (@pairs) { ($key,$value)=split(/=/,$pair); + &ManagePermissions($key, $udom, $uname, + &GetAuthType( $udom, + $uname)); $hash{$key}=$value; + } if (untie(%hash)) { print $client "ok\n"; @@ -760,7 +1160,7 @@ sub make_new_child { chomp($what); my $proname=propath($udom,$uname); my $now=time; - { + unless ($namespace=~/^nohist\_/) { my $hfh; if ( $hfh=IO::File->new(">>$proname/$namespace.hist") @@ -802,15 +1202,22 @@ sub make_new_child { } # ------------------------------------------------------------------------ dump } elsif ($userinput =~ /^dump/) { - my ($cmd,$udom,$uname,$namespace) + my ($cmd,$udom,$uname,$namespace,$regexp) =split(/:/,$userinput); $namespace=~s/\//\_/g; $namespace=~s/\W//g; + if (defined($regexp)) { + $regexp=&unescape($regexp); + } else { + $regexp='.'; + } my $proname=propath($udom,$uname); my $qresult=''; if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { foreach $key (keys %hash) { - $qresult.="$key=$hash{$key}&"; + if (eval('$key=~/$regexp/')) { + $qresult.="$key=$hash{$key}&"; + } } if (untie(%hash)) { $qresult=~s/\&$//; @@ -831,7 +1238,7 @@ sub make_new_child { chomp($what); my $proname=propath($udom,$uname); my $now=time; - { + unless ($namespace=~/^nohist\_/) { my $hfh; if ( $hfh=IO::File->new(">>$proname/$namespace.hist") @@ -850,7 +1257,8 @@ sub make_new_child { $allkeys.=$key.':'; $hash{"$version:$rid:$key"}=$value; } - $allkeys=~s/:$//; + $hash{"$version:$rid:timestamp"}=$now; + $allkeys.='timestamp'; $hash{"$version:keys:$rid"}=$allkeys; if (untie(%hash)) { print $client "ok\n"; @@ -894,19 +1302,40 @@ sub make_new_child { } else { print $client "error:$!\n"; } +# -------------------------------------------------------------------- chatsend + } elsif ($userinput =~ /^chatsend/) { + my ($cmd,$cdom,$cnum,$newpost)=split(/\:/,$userinput); + &chatadd($cdom,$cnum,$newpost); + print $client "ok\n"; +# -------------------------------------------------------------------- chatretr + } elsif ($userinput =~ /^chatretr/) { + my ($cmd,$cdom,$cnum)=split(/\:/,$userinput); + my $reply=''; + foreach (&getchat($cdom,$cnum)) { + $reply.=&escape($_).':'; + } + $reply=~s/\:$//; + print $client $reply."\n"; # ------------------------------------------------------------------- querysend } elsif ($userinput =~ /^querysend/) { - my ($cmd,$query)=split(/:/,$userinput); + my ($cmd,$query, + $arg1,$arg2,$arg3)=split(/\:/,$userinput); $query=~s/\n*$//g; - print $client sqlreply("$hostid{$clientip}\&$query")."\n"; + print $client "". + sqlreply("$hostid{$clientip}\&$query". + "\&$arg1"."\&$arg2"."\&$arg3")."\n"; # ------------------------------------------------------------------ queryreply } elsif ($userinput =~ /^queryreply/) { my ($cmd,$id,$reply)=split(/:/,$userinput); my $store; my $execdir=$perlvar{'lonDaemons'}; if ($store=IO::File->new(">$execdir/tmp/$id")) { + $reply=~s/\&/\n/g; print $store $reply; close $store; + my $store2=IO::File->new(">$execdir/tmp/$id.end"); + print $store2 "done\n"; + close $store2; print $client "ok\n"; } else { @@ -1000,34 +1429,58 @@ sub make_new_child { my $ulsout=''; my $ulsfn; if (-e $ulsdir) { - while ($ulsfn=<$ulsdir/*>) { - my @ulsstats=stat($ulsfn); - $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; - } - } else { + if(-d $ulsdir) { + if (opendir(LSDIR,$ulsdir)) { + while ($ulsfn=readdir(LSDIR)) { + my @ulsstats=stat($ulsdir.'/'.$ulsfn); + $ulsout.=$ulsfn.'&'. + join('&',@ulsstats).':'; + } + closedir(LSDIR); + } + } else { + my @ulsstats=stat($ulsdir); + $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; + } + } else { $ulsout='no_such_dir'; } if ($ulsout eq '') { $ulsout='empty'; } print $client "$ulsout\n"; +# ------------------------------------------------------------------ Hanging up + } elsif (($userinput =~ /^exit/) || + ($userinput =~ /^init/)) { + &logthis( + "Client $clientip ($hostid{$clientip}) hanging up: $userinput"); + print $client "bye\n"; + $client->close(); + last; # ------------------------------------------------------------- unknown command } else { # unknown command print $client "unknown_cmd\n"; } -# ------------------------------------------------------ client unknown, refuse +# -------------------------------------------------------------------- complete + alarm(0); + &status('Listening to '.$hostid{$clientip}); } +# --------------------------------------------- client unknown or fishy, refuse } else { print $client "refused\n"; + $client->close(); &logthis("WARNING: " ."Rejected client $clientip, closing connection"); - } - &logthis("CRITICAL: " - ."Disconnect from $clientip ($hostid{$clientip})"); + } + } + # ============================================================================= - } - + + &logthis("CRITICAL: " + ."Disconnect from $clientip ($hostid{$clientip})"); # tidy up gracefully and finish + $server->close(); + # this exit is VERY important, otherwise the child will become # a producer of more and more children, forking yourself into # process death. @@ -1036,6 +1489,485 @@ sub make_new_child { } +# +# Checks to see if the input roleput request was to set +# an author role. If so, invokes the lchtmldir script to set +# up a correct public_html +# Parameters: +# request - The request sent to the rolesput subchunk. +# We're looking for /domain/_au +# domain - The domain in which the user is having roles doctored. +# user - Name of the user for which the role is being put. +# authtype - The authentication type associated with the user. +# +sub ManagePermissions +{ + my $request = shift; + my $domain = shift; + my $user = shift; + my $authtype= shift; + + # See if the request is of the form /$domain/_au + + if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput... + my $execdir = $perlvar{'lonDaemons'}; + my $userhome= "/home/$user" ; + Debug("system $execdir/lchtmldir $userhome $system $authtype"); + system("$execdir/lchtmldir $userhome $user $authtype"); + } +} +# +# GetAuthType - Determines the authorization type of a user in a domain. + +# Returns the authorization type or nouser if there is no such user. +# +sub GetAuthType +{ + my $domain = shift; + my $user = shift; + + Debug("GetAuthType( $domain, $user ) \n"); + my $proname = &propath($domain, $user); + my $passwdfile = "$proname/passwd"; + if( -e $passwdfile ) { + my $pf = IO::File->new($passwdfile); + my $realpassword = <$pf>; + chomp($realpassword); + Debug("Password info = $realpassword\n"); + my ($authtype, $contentpwd) = split(/:/, $realpassword); + Debug("Authtype = $authtype, content = $contentpwd\n"); + my $availinfo = ''; + if($authtype eq 'krb4') { + $availinfo = $contentpwd; + } + + return "$authtype:$availinfo"; + } + else { + Debug("Returning nouser"); + return "nouser"; + } +} + +sub addline { + my ($fname,$hostid,$ip,$newline)=@_; + my $contents; + my $found=0; + my $expr='^'.$hostid.':'.$ip.':'; + $expr =~ s/\./\\\./g; + if ($sh=IO::File->new("$fname.subscription")) { + while (my $subline=<$sh>) { + if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;} + } + $sh->close(); + } + $sh=IO::File->new(">$fname.subscription"); + if ($contents) { print $sh $contents; } + if ($newline) { print $sh $newline; } + $sh->close(); + return $found; +} + +sub getchat { + my ($cdom,$cname)=@_; + my %hash; + my $proname=&propath($cdom,$cname); + my @entries=(); + if + (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",&GDBM_READER(),0640)) + { + @entries=map { $_.':'.$hash{$_} } sort keys %hash; + untie %hash; + } + return @entries; +} + +sub chatadd { + my ($cdom,$cname,$newchat)=@_; + my %hash; + my $proname=&propath($cdom,$cname); + my @entries=(); + if + (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",&GDBM_WRCREAT(),0640)) + { + @entries=map { $_.':'.$hash{$_} } sort keys %hash; + my $time=time; + my ($lastid)=($entries[$#entries]=~/^(\w+)\:/); + my ($thentime,$idnum)=split(/\_/,$lastid); + my $newid=$time.'_000000'; + if ($thentime==$time) { + $idnum=~s/^0+//; + $idnum++; + $idnum=substr('000000'.$idnum,-6,6); + $newid=$time.'_'.$idnum; + } + $hash{$newid}=$newchat; + my $expired=$time-3600; + foreach (keys %hash) { + my ($thistime)=($_=~/(\d+)\_/); + if ($thistime<$expired) { + undef $hash{$_}; + } + } + untie %hash; + } +} + +sub unsub { + my ($fname,$clientip)=@_; + my $result; + if (unlink("$fname.$hostid{$clientip}")) { + $result="ok\n"; + } else { + $result="not_subscribed\n"; + } + if (-e "$fname.subscription") { + my $found=&addline($fname,$hostid{$clientip},$clientip,''); + if ($found) { $result="ok\n"; } + } else { + if ($result != "ok\n") { $result="not_subscribed\n"; } + } + return $result; +} + +sub subscribe { + my ($userinput,$clientip)=@_; + my $result; + my ($cmd,$fname)=split(/:/,$userinput); + my $ownership=&ishome($fname); + if ($ownership eq 'owner') { + if (-e $fname) { + if (-d $fname) { + $result="directory\n"; + } else { + if (-e "$fname.$hostid{$clientip}") {&unsub($fname,$clientip);} + $now=time; + my $found=&addline($fname,$hostid{$clientip},$clientip, + "$hostid{$clientip}:$clientip:$now\n"); + if ($found) { $result="$fname\n"; } + # if they were subscribed to only meta data, delete that + # subscription, when you subscribe to a file you also get + # the metadata + unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); } + $fname=~s/\/home\/httpd\/html\/res/raw/; + $fname="http://$thisserver/".$fname; + $result="$fname\n"; + } + } else { + $result="not_found\n"; + } + } else { + $result="rejected\n"; + } + return $result; +} +# ----------------------------------- POD (plain old documentation, CPAN style) + +=head1 NAME + +lond - "LON Daemon" Server (port "LOND" 5663) + +=head1 SYNOPSIS + +Usage: B + +Should only be run as user=www. This is a command-line script which +is invoked by B. There is no expectation that a typical user +will manually start B from the command-line. (In other words, +DO NOT START B YOURSELF.) + +=head1 DESCRIPTION + +There are two characteristics associated with the running of B, +PROCESS MANAGEMENT (starting, stopping, handling child processes) +and SERVER-SIDE ACTIVITIES (password authentication, user creation, +subscriptions, etc). These are described in two large +sections below. + +B + +Preforker - server who forks first. Runs as a daemon. HUPs. +Uses IDEA encryption + +B forks off children processes that correspond to the other servers +in the network. Management of these processes can be done at the +parent process level or the child process level. + +B is the location of log messages. + +The process management is now explained in terms of linux shell commands, +subroutines internal to this code, and signal assignments: + +=over 4 + +=item * + +PID is stored in B + +This is the process id number of the parent B process. + +=item * + +SIGTERM and SIGINT + +Parent signal assignment: + $SIG{INT} = $SIG{TERM} = \&HUNTSMAN; + +Child signal assignment: + $SIG{INT} = 'DEFAULT'; (and SIGTERM is DEFAULT also) +(The child dies and a SIGALRM is sent to parent, awaking parent from slumber + to restart a new child.) + +Command-line invocations: + B B<-s> SIGTERM I + B B<-s> SIGINT I + +Subroutine B: + This is only invoked for the B parent I. +This kills all the children, and then the parent. +The B file is cleared. + +=item * + +SIGHUP + +Current bug: + This signal can only be processed the first time +on the parent process. Subsequent SIGHUP signals +have no effect. + +Parent signal assignment: + $SIG{HUP} = \&HUPSMAN; + +Child signal assignment: + none (nothing happens) + +Command-line invocations: + B B<-s> SIGHUP I + +Subroutine B: + This is only invoked for the B parent I, +This kills all the children, and then the parent. +The B file is cleared. + +=item * + +SIGUSR1 + +Parent signal assignment: + $SIG{USR1} = \&USRMAN; + +Child signal assignment: + $SIG{USR1}= \&logstatus; + +Command-line invocations: + B B<-s> SIGUSR1 I + +Subroutine B: + When invoked for the B parent I, +SIGUSR1 is sent to all the children, and the status of +each connection is logged. + +=item * + +SIGCHLD + +Parent signal assignment: + $SIG{CHLD} = \&REAPER; + +Child signal assignment: + none + +Command-line invocations: + B B<-s> SIGCHLD I + +Subroutine B: + This is only invoked for the B parent I. +Information pertaining to the child is removed. +The socket port is cleaned up. + +=back + +B + +Server-side information can be accepted in an encrypted or non-encrypted +method. + +=over 4 + +=item ping + +Query a client in the hosts.tab table; "Are you there?" + +=item pong + +Respond to a ping query. + +=item ekey + +Read in encrypted key, make cipher. Respond with a buildkey. + +=item load + +Respond with CPU load based on a computation upon /proc/loadavg. + +=item currentauth + +Reply with current authentication information (only over an +encrypted channel). + +=item auth + +Only over an encrypted channel, reply as to whether a user's +authentication information can be validated. + +=item passwd + +Allow for a password to be set. + +=item makeuser + +Make a user. + +=item passwd + +Allow for authentication mechanism and password to be changed. + +=item home + +Respond to a question "are you the home for a given user?" + +=item update + +Update contents of a subscribed resource. + +=item unsubscribe + +The server is unsubscribing from a resource. + +=item subscribe + +The server is subscribing to a resource. + +=item log + +Place in B + +=item put + +stores hash in namespace + +=item rolesput + +put a role into a user's environment + +=item get + +returns hash with keys from array +reference filled in from namespace + +=item eget + +returns hash with keys from array +reference filled in from namesp (encrypts the return communication) + +=item rolesget + +get a role from a user's environment + +=item del + +deletes keys out of array from namespace + +=item keys + +returns namespace keys + +=item dump + +dumps the complete (or key matching regexp) namespace into a hash + +=item store + +stores hash permanently +for this url; hashref needs to be given and should be a \%hashname; the +remaining args aren't required and if they aren't passed or are '' they will +be derived from the ENV + +=item restore + +returns a hash for a given url + +=item querysend + +Tells client about the lonsql process that has been launched in response +to a sent query. + +=item queryreply + +Accept information from lonsql and make appropriate storage in temporary +file space. + +=item idput + +Defines usernames as corresponding to IDs. (These "IDs" are unique identifiers +for each student, defined perhaps by the institutional Registrar.) + +=item idget + +Returns usernames corresponding to IDs. (These "IDs" are unique identifiers +for each student, defined perhaps by the institutional Registrar.) + +=item tmpput + +Accept and store information in temporary space. + +=item tmpget + +Send along temporarily stored information. + +=item ls + +List part of a user's directory. + +=item Hanging up (exit or init) + +What to do when a client tells the server that they (the client) +are leaving the network. + +=item unknown command + +If B is sent an unknown command (not in the list above), +it replys to the client "unknown_cmd". + +=item UNKNOWN CLIENT + +If the anti-spoofing algorithm cannot verify the client, +the client is rejected (with a "refused" message sent +to the client, and the connection is closed. + +=back + +=head1 PREREQUISITES + +IO::Socket +IO::File +Apache::File +Symbol +POSIX +Crypt::IDEA +LWP::UserAgent() +GDBM_File +Authen::Krb4 + +=head1 COREQUISITES + +=head1 OSNAMES + +linux +=head1 SCRIPT CATEGORIES +Server/Process +=cut