--- loncom/lond 1999/10/26 20:24:47 1.2 +++ loncom/lond 2002/02/05 17:17:49 1.65 @@ -1,9 +1,55 @@ #!/usr/bin/perl # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) +# +# $Id: lond,v 1.65 2002/02/05 17:17:49 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 Gerd Kortemeyer +# 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/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 +### + # based on "Perl Cookbook" ISBN 1-56592-243-3 # preforker - server who forks first # runs as a daemon @@ -17,6 +63,36 @@ use Symbol; use POSIX; use Crypt::IDEA; use LWP::UserAgent(); +use GDBM_File; +use Authen::Krb4; +use lib '/home/httpd/lib/perl/'; +use localauth; + +my $status=''; +my $lastlog=''; + +# grabs exception and records it to log before exiting +sub catchexception { + my ($error)=@_; + $SIG{'QUIT'}='DEFAULT'; + $SIG{__DIE__}='DEFAULT'; + &logthis("CRITICAL: " + ."ABNORMAL EXIT. Child $$ for server $wasserver died through " + ."a crash with this error msg->[$error]"); + &logthis('Famous last words: '.$status.' - '.$lastlog); + if ($client) { print $client "error: $error\n"; } + $server->close(); + die($error); +} + +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 @@ -25,11 +101,33 @@ open (CONFIG,"/etc/httpd/conf/access.con while ($configline=) { if ($configline =~ /PerlSetVar/) { my ($dummy,$varname,$varvalue)=split(/\s+/,$configline); + chomp($varvalue); $perlvar{$varname}=$varvalue; } } close(CONFIG); +# ----------------------------- 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; +} + +# --------------------------------------------- Check if other instance running + +my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid"; + +if (-e $pidfile) { + my $lfh=IO::File->new("$pidfile"); + my $pide=<$lfh>; + chomp($pide); + if (kill 0 => $pide) { die "already running"; } +} + $PREFORK=4; # number of children to maintain, at least four spare # ------------------------------------------------------------- Read hosts file @@ -73,21 +171,44 @@ sub REAPER { # ta 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("Shutting down"); + &logthis("CRITICAL: Shutting down"); exit; # clean up with dignity } 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("Restarting"); + &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 => $_; + } + } +} + # --------------------------------------------------------------------- Logging sub logthis { @@ -96,9 +217,64 @@ 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"; } +# ------------------------------------------------------------------ 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 + +sub escape { + my $str=shift; + $str =~ s/(\W)/"%".unpack('H2',$1)/eg; + return $str; +} + +# ----------------------------------------------------- Un-Escape Special Chars + +sub unescape { + my $str=shift; + $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; + return $str; +} + # ----------------------------------------------------------- Send USR1 to lonc sub reconlonc { @@ -116,16 +292,20 @@ sub reconlonc { &logthis("$peerfile still not there, give it another try"); sleep 5; if (-e "$peerfile") { return; } - &logthis("$peerfile still not there, giving up"); + &logthis( + "WARNING: $peerfile still not there, giving up"); } else { - &logthis("lonc at pid $loncpid not responding, giving up"); + &logthis( + "CRITICAL: " + ."lonc at pid $loncpid not responding, giving up"); } } else { - &logthis('lonc not running, giving up'); + &logthis('CRITICAL: lonc not running, giving up'); } } # -------------------------------------------------- Non-critical communication + sub subreply { my ($cmd,$server)=@_; my $peerfile="$perlvar{'lonSockDir'}/$server"; @@ -158,18 +338,44 @@ sub reply { return $answer; } +# -------------------------------------------------------------- Talk to lonsql + +sub sqlreply { + my ($cmd)=@_; + my $answer=subsqlreply($cmd); + if ($answer eq 'con_lost') { $answer=subsqlreply($cmd); } + return $answer; +} + +sub subsqlreply { + my ($cmd)=@_; + my $unixsock="mysqlsock"; + my $peerfile="$perlvar{'lonSockDir'}/$unixsock"; + my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile", + Type => SOCK_STREAM, + Timeout => 10) + or return "con_lost"; + print $sclient "$cmd\n"; + my $answer=<$sclient>; + chomp($answer); + if (!$answer) { $answer="con_lost"; } + return $answer; +} + # -------------------------------------------- Return path to profile directory + sub propath { my ($udom,$uname)=@_; $udom=~s/\W//g; $uname=~s/\W//g; - my $subdir=$uname; + my $subdir=$uname.'__'; $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/; my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname"; return $proname; } # --------------------------------------- Is this the home server of an author? + sub ishome { my $author=shift; $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; @@ -197,7 +403,8 @@ $execdir=$perlvar{'lonDaemons'}; open (PIDSAVE,">$execdir/logs/lond.pid"); print PIDSAVE "$$\n"; close(PIDSAVE); -&logthis("Starting"); +&logthis("CRITICAL: ---------- Starting ----------"); +&status('Starting'); # ------------------------------------------------------- Now we are on our own @@ -208,13 +415,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 } @@ -238,19 +451,27 @@ sub make_new_child { 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 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 # ----------------------------------------------------------------------------- @@ -259,35 +480,53 @@ sub make_new_child { my ($port,$iaddr)=unpack_sockaddr_in($caller); my $clientip=inet_ntoa($iaddr); my $clientrec=($hostid{$clientip} ne undef); - &logthis("Connect from $clientip ($hostid{$clientip})"); + &logthis( +"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) { $clientok=1; print $client "ok\n"; } else { - &logthis("$clientip did not reply challenge"); + &logthis( + "WARNING: $clientip did not reply challenge"); + &status('No challenge reply '.$clientip); } } else { - &logthis("$clientip failed to initialize: >$remotereq<"); + &logthis( + "WARNING: " + ."$clientip failed to initialize: >$remotereq< "); + &status('No init '.$clientip); } } else { - &logthis("Unknown client $clientip"); + &logthis( + "WARNING: Unknown client $clientip"); + &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); + &status('Processing '.$hostid{$clientip}.': '.$userinput); my $wasenc=0; + alarm(120); # ------------------------------------------------------------ See if encrypted if ($userinput =~ /^enc/) { if ($cipher) { @@ -335,11 +574,34 @@ 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 $proname=propath($udom,$uname); + my $passfilename="$proname/passwd"; + if (-e $passfilename) { + my $pf = IO::File->new($passfilename); + my $realpasswd=<$pf>; + chomp($realpasswd); + my ($howpwd,$contentpwd)=split(/:/,$realpasswd); + my $availablecontent=''; + if ($howpwd eq 'krb4') { + $availablecontent=$contentpwd; + } + print $client "$howpwd:$availablecontent\n"; + } else { + print $client "unknown_user\n"; + } + } else { + print $client "refused\n"; + } # ------------------------------------------------------------------------ auth } elsif ($userinput =~ /^auth/) { if ($wasenc==1) { my ($cmd,$udom,$uname,$upass)=split(/:/,$userinput); chomp($upass); + $upass=unescape($upass); my $proname=propath($udom,$uname); my $passfilename="$proname/passwd"; if (-e $passfilename) { @@ -353,9 +615,27 @@ 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') { + $pwdcorrect=( + Authen::Krb4::get_pw_in_tkt($uname,"", + $contentpwd,'krbtgt',$contentpwd,1, + $upass) == 0); + } elsif ($howpwd eq 'localauth') { + $pwdcorrect=&localauth::localauth($uname,$upass, + $contentpwd); + } if ($pwdcorrect) { print $client "authorized\n"; } else { @@ -373,6 +653,8 @@ sub make_new_child { my ($cmd,$udom,$uname,$upass,$npass)=split(/:/,$userinput); chomp($npass); + $upass=&unescape($upass); + $npass=&unescape($npass); my $proname=propath($udom,$uname); my $passfilename="$proname/passwd"; if (-e $passfilename) { @@ -387,7 +669,7 @@ 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"; } print $client "ok\n"; } else { print $client "non_authorized\n"; @@ -401,6 +683,145 @@ sub make_new_child { } else { print $client "refused\n"; } +# -------------------------------------------------------------------- makeuser + } elsif ($userinput =~ /^makeuser/) { + my $oldumask=umask(0077); + if ($wasenc==1) { + my + ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput); + chomp($npass); + $npass=&unescape($npass); + my $proname=propath($udom,$uname); + my $passfilename="$proname/passwd"; + 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); + { + 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 "$fperror\n"; + } + } + } else { + print $client "refused\n"; + } + umask($oldumask); +# -------------------------------------------------------------- changeuserauth + } elsif ($userinput =~ /^changeuserauth/) { + if ($wasenc==1) { + my + ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput); + chomp($npass); + $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); @@ -438,11 +859,21 @@ 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= + $ua->request($mrequest,$fname.'.meta'); + if ($mresponse->is_error()) { + unlink($fname.'.meta'); + } + } rename($transname,$fname); } } @@ -471,41 +902,102 @@ sub make_new_child { 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"; + my $sh; + if ($sh= + IO::File->new(">$fname.$hostid{$clientip}")) { + print $sh "$clientip:$now\n"; + } } + unless ($fname=~/\.meta$/) { + unlink("$fname.meta.$hostid{$clientip}"); + } $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"; } +# ------------------------------------------------------------------------- log + } elsif ($userinput =~ /^log/) { + my ($cmd,$udom,$uname,$what)=split(/:/,$userinput); + chomp($what); + my $proname=propath($udom,$uname); + my $now=time; + { + my $hfh; + if ($hfh=IO::File->new(">>$proname/activity.log")) { + print $hfh "$now:$hostid{$clientip}:$what\n"; + print $client "ok\n"; + } else { + print $client "error:$!\n"; + } + } # ------------------------------------------------------------------------- put } elsif ($userinput =~ /^put/) { - my ($cmd,$udom,$uname,$namespace,$what) + my ($cmd,$udom,$uname,$namespace,$what) =split(/:/,$userinput); - $namespace=~s/\W//g; + $namespace=~s/\//\_/g; + $namespace=~s/\W//g; + if ($namespace ne 'roles') { chomp($what); my $proname=propath($udom,$uname); my $now=time; - { + unless ($namespace=~/^nohist\_/) { my $hfh; if ( $hfh=IO::File->new(">>$proname/$namespace.hist") ) { print $hfh "P:$now:$what\n"; } } my @pairs=split(/\&/,$what); - if (dbmopen(%hash,"$proname/$namespace.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT,0640)) { + foreach $pair (@pairs) { + ($key,$value)=split(/=/,$pair); + $hash{$key}=$value; + } + if (untie(%hash)) { + print $client "ok\n"; + } else { + print $client "error:$!\n"; + } + } else { + print $client "error:$!\n"; + } + } else { + print $client "refused\n"; + } +# -------------------------------------------------------------------- rolesput + } elsif ($userinput =~ /^rolesput/) { + if ($wasenc==1) { + my ($cmd,$exedom,$exeuser,$udom,$uname,$what) + =split(/:/,$userinput); + my $namespace='roles'; + chomp($what); + my $proname=propath($udom,$uname); + my $now=time; + { + my $hfh; + if ( + $hfh=IO::File->new(">>$proname/$namespace.hist") + ) { + print $hfh "P:$now:$exedom:$exeuser:$what\n"; + } + } + my @pairs=split(/\&/,$what); + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT,0640)) { foreach $pair (@pairs) { ($key,$value)=split(/=/,$pair); $hash{$key}=$value; } - if (dbmclose(%hash)) { + if (untie(%hash)) { print $client "ok\n"; } else { print $client "error:$!\n"; @@ -513,20 +1005,24 @@ sub make_new_child { } else { print $client "error:$!\n"; } + } else { + print $client "refused\n"; + } # ------------------------------------------------------------------------- get } elsif ($userinput =~ /^get/) { my ($cmd,$udom,$uname,$namespace,$what) =split(/:/,$userinput); + $namespace=~s/\//\_/g; $namespace=~s/\W//g; chomp($what); my @queries=split(/\&/,$what); my $proname=propath($udom,$uname); my $qresult=''; - if (dbmopen(%hash,"$proname/$namespace.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { for ($i=0;$i<=$#queries;$i++) { $qresult.="$hash{$queries[$i]}&"; } - if (dbmclose(%hash)) { + if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { @@ -539,16 +1035,17 @@ sub make_new_child { } elsif ($userinput =~ /^eget/) { my ($cmd,$udom,$uname,$namespace,$what) =split(/:/,$userinput); + $namespace=~s/\//\_/g; $namespace=~s/\W//g; chomp($what); my @queries=split(/\&/,$what); my $proname=propath($udom,$uname); my $qresult=''; - if (dbmopen(%hash,"$proname/$namespace.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { for ($i=0;$i<=$#queries;$i++) { $qresult.="$hash{$queries[$i]}&"; } - if (dbmclose(%hash)) { + if (untie(%hash)) { $qresult=~s/\&$//; if ($cipher) { my $cmdlength=length($qresult); @@ -574,22 +1071,23 @@ sub make_new_child { } elsif ($userinput =~ /^del/) { my ($cmd,$udom,$uname,$namespace,$what) =split(/:/,$userinput); + $namespace=~s/\//\_/g; $namespace=~s/\W//g; chomp($what); my $proname=propath($udom,$uname); my $now=time; - { + unless ($namespace=~/^nohist\_/) { my $hfh; if ( $hfh=IO::File->new(">>$proname/$namespace.hist") ) { print $hfh "D:$now:$what\n"; } } my @keys=split(/\&/,$what); - if (dbmopen(%hash,"$proname/$namespace.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT,0640)) { foreach $key (@keys) { delete($hash{$key}); } - if (dbmclose(%hash)) { + if (untie(%hash)) { print $client "ok\n"; } else { print $client "error:$!\n"; @@ -601,15 +1099,15 @@ sub make_new_child { } elsif ($userinput =~ /^keys/) { my ($cmd,$udom,$uname,$namespace) =split(/:/,$userinput); + $namespace=~s/\//\_/g; $namespace=~s/\W//g; - chomp($namespace); my $proname=propath($udom,$uname); my $qresult=''; - if (dbmopen(%hash,"$proname/$namespace.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { foreach $key (keys %hash) { $qresult.="$key&"; } - if (dbmclose(%hash)) { + if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { @@ -620,17 +1118,24 @@ 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; - chomp($namespace); + if (defined($regexp)) { + $regexp=&unescape($regexp); + } else { + $regexp='.'; + } my $proname=propath($udom,$uname); my $qresult=''; - if (dbmopen(%hash,"$proname/$namespace.db",0644)) { + 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 (dbmclose(%hash)) { + if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { @@ -639,6 +1144,111 @@ sub make_new_child { } else { print $client "error:$!\n"; } +# ----------------------------------------------------------------------- store + } elsif ($userinput =~ /^store/) { + my ($cmd,$udom,$uname,$namespace,$rid,$what) + =split(/:/,$userinput); + $namespace=~s/\//\_/g; + $namespace=~s/\W//g; + if ($namespace ne 'roles') { + chomp($what); + my $proname=propath($udom,$uname); + my $now=time; + unless ($namespace=~/^nohist\_/) { + my $hfh; + if ( + $hfh=IO::File->new(">>$proname/$namespace.hist") + ) { print $hfh "P:$now:$rid:$what\n"; } + } + my @pairs=split(/\&/,$what); + + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT,0640)) { + my @previouskeys=split(/&/,$hash{"keys:$rid"}); + my $key; + $hash{"version:$rid"}++; + my $version=$hash{"version:$rid"}; + my $allkeys=''; + foreach $pair (@pairs) { + ($key,$value)=split(/=/,$pair); + $allkeys.=$key.':'; + $hash{"$version:$rid:$key"}=$value; + } + $hash{"$version:$rid:timestamp"}=$now; + $allkeys.='timestamp'; + $hash{"$version:keys:$rid"}=$allkeys; + if (untie(%hash)) { + print $client "ok\n"; + } else { + print $client "error:$!\n"; + } + } else { + print $client "error:$!\n"; + } + } else { + print $client "refused\n"; + } +# --------------------------------------------------------------------- restore + } elsif ($userinput =~ /^restore/) { + my ($cmd,$udom,$uname,$namespace,$rid) + =split(/:/,$userinput); + $namespace=~s/\//\_/g; + $namespace=~s/\W//g; + chomp($rid); + my $proname=propath($udom,$uname); + my $qresult=''; + if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { + my $version=$hash{"version:$rid"}; + $qresult.="version=$version&"; + my $scope; + for ($scope=1;$scope<=$version;$scope++) { + my $vkeys=$hash{"$scope:keys:$rid"}; + my @keys=split(/:/,$vkeys); + my $key; + $qresult.="$scope:keys=$vkeys&"; + foreach $key (@keys) { + $qresult.="$scope:$key=".$hash{"$scope:$rid:$key"}."&"; + } + } + if (untie(%hash)) { + $qresult=~s/\&$//; + print $client "$qresult\n"; + } else { + print $client "error:$!\n"; + } + } else { + print $client "error:$!\n"; + } +# ------------------------------------------------------------------- querysend + } elsif ($userinput =~ /^querysend/) { + my ($cmd,$query, + $custom,$customshow)=split(/:/,$userinput); + $query=~s/\n*$//g; + unless ($custom or $customshow) { + print $client "". + sqlreply("$hostid{$clientip}\&$query")."\n"; + } + else { + print $client "". + sqlreply("$hostid{$clientip}\&$query". + "\&$custom"."\&$customshow")."\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 { + print $client "error:$!\n"; + } # ----------------------------------------------------------------------- idput } elsif ($userinput =~ /^idput/) { my ($cmd,$udom,$what)=split(/:/,$userinput); @@ -653,12 +1263,12 @@ sub make_new_child { ) { print $hfh "P:$now:$what\n"; } } my @pairs=split(/\&/,$what); - if (dbmopen(%hash,"$proname.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT,0640)) { foreach $pair (@pairs) { ($key,$value)=split(/=/,$pair); $hash{$key}=$value; } - if (dbmclose(%hash)) { + if (untie(%hash)) { print $client "ok\n"; } else { print $client "error:$!\n"; @@ -674,11 +1284,11 @@ sub make_new_child { my $proname="$perlvar{'lonUsersDir'}/$udom/ids"; my @queries=split(/\&/,$what); my $qresult=''; - if (dbmopen(%hash,"$proname.db",0644)) { + if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER,0640)) { for ($i=0;$i<=$#queries;$i++) { $qresult.="$hash{$queries[$i]}&"; } - if (dbmclose(%hash)) { + if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { @@ -687,23 +1297,92 @@ sub make_new_child { } else { print $client "error:$!\n"; } +# ---------------------------------------------------------------------- tmpput + } elsif ($userinput =~ /^tmpput/) { + my ($cmd,$what)=split(/:/,$userinput); + my $store; + $tmpsnum++; + my $id=$$.'_'.$clientip.'_'.$tmpsnum; + $id=~s/\W/\_/g; + $what=~s/\n//g; + my $execdir=$perlvar{'lonDaemons'}; + if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) { + print $store $what; + close $store; + print $client "$id\n"; + } + else { + print $client "error:$!\n"; + } + +# ---------------------------------------------------------------------- tmpget + } elsif ($userinput =~ /^tmpget/) { + my ($cmd,$id)=split(/:/,$userinput); + chomp($id); + $id=~s/\W/\_/g; + my $store; + my $execdir=$perlvar{'lonDaemons'}; + if ($store=IO::File->new("$execdir/tmp/$id.tmp")) { + my $reply=<$store>; + print $client "$reply\n"; + close $store; + } + else { + print $client "error:$!\n"; + } + +# -------------------------------------------------------------------------- ls + } elsif ($userinput =~ /^ls/) { + my ($cmd,$ulsdir)=split(/:/,$userinput); + my $ulsout=''; + my $ulsfn; + if (-e $ulsdir) { + if (opendir(LSDIR,$ulsdir)) { + while ($ulsfn=readdir(LSDIR)) { + my @ulsstats=stat($ulsdir.'/'.$ulsfn); + $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; + } + closedir(LSDIR); + } + } 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"; - &logthis("Rejected client $clientip, closing connection"); + $client->close(); + &logthis("WARNING: " + ."Rejected client $clientip, closing connection"); } - &logthis("Disconnect from $clientip ($hostid{$clientip})"); + &logthis("CRITICAL: " + ."Disconnect from $clientip ($hostid{$clientip})"); # ============================================================================= } # tidy up gracefully and finish + $client->close(); + $server->close(); + # this exit is VERY important, otherwise the child will become # a producer of more and more children, forking yourself into # process death. @@ -711,6 +1390,48 @@ sub make_new_child { } } +# ----------------------------------- POD (plain old documentation, CPAN style) + +=head1 NAME + +lond - "LON Daemon" Server (port "LOND" 5663) + +=head1 SYNOPSIS + +Should only be run as user=www. Invoked by loncron. + +=head1 DESCRIPTION + +Preforker - server who forks first. Runs as a daemon. HUPs. +Uses IDEA encryption + +=head1 README + +Not yet written. + +=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