#!/usr/bin/perl # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # # $Id: lond,v 1.84 2002/07/26 19:35:20 albertel 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, # 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 # 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; 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 $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 ($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 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; } # --------------------------------------------- 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 open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file"; while ($configline=) { my ($id,$domain,$role,$name,$ip)=split(/:/,$configline); chomp($ip); $ip=~s/\D+$//; $hostid{$ip}=$id; if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; } $PREFORK++; } close(CONFIG); # establish SERVER socket, bind and listen. $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'}, Type => SOCK_STREAM, Proto => 'tcp', Reuse => 1, Listen => 10 ) or die "making socket: $@\n"; # --------------------------------------------------------- Do global variables # global variables $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 sub REAPER { # takes care of dead children $SIG{CHLD} = \&REAPER; my $pid = wait; 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"); 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; &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 { my $message=shift; my $execdir=$perlvar{'lonDaemons'}; 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 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 { my $peerfile=shift; &logthis("Trying to reconnect for $peerfile"); my $loncfile="$perlvar{'lonDaemons'}/logs/lonc.pid"; if (my $fh=IO::File->new("$loncfile")) { my $loncpid=<$fh>; chomp($loncpid); if (kill 0 => $loncpid) { &logthis("lonc at pid $loncpid responding, sending USR1"); kill USR1 => $loncpid; sleep 5; if (-e "$peerfile") { return; } &logthis("$peerfile still not there, give it another try"); sleep 10; if (-e "$peerfile") { return; } &logthis( "WARNING: $peerfile still not there, giving up"); } else { &logthis( "CRITICAL: " ."lonc at pid $loncpid not responding, giving up"); } } else { &logthis('CRITICAL: lonc not running, giving up'); } } # -------------------------------------------------- Non-critical communication sub subreply { my ($cmd,$server)=@_; my $peerfile="$perlvar{'lonSockDir'}/$server"; 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; } sub reply { my ($cmd,$server)=@_; my $answer; if ($server ne $perlvar{'lonHostID'}) { $answer=subreply($cmd,$server); 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); } } else { $answer='self_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.'__'; $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/; my ($udom,$uname)=split(/\//,$author); my $proname=propath($udom,$uname); if (-e $proname) { return 'owner'; } else { return 'not_owner'; } } # ======================================================= Continue main program # ---------------------------------------------------- Fork once and dissociate $fpid=fork; exit if $fpid; die "Couldn't fork: $!" unless defined ($fpid); POSIX::setsid() or die "Can't start new session: $!"; # ------------------------------------------------------- Write our PID on disk $execdir=$perlvar{'lonDaemons'}; open (PIDSAVE,">$execdir/logs/lond.pid"); print PIDSAVE "$$\n"; close(PIDSAVE); &logthis("CRITICAL: ---------- Starting ----------"); &status('Starting'); # ------------------------------------------------------- Now we are on our own # Fork off our children. for (1 .. $PREFORK) { make_new_child(); } # ----------------------------------------------------- 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 } } sub make_new_child { my $pid; my $cipher; my $sigset; &logthis("Attempting to start child"); # block signal for fork $sigset = POSIX::SigSet->new(SIGINT); sigprocmask(SIG_BLOCK, $sigset) or die "Can't block SIGINT for fork: $!\n"; die "fork: $!" unless defined ($pid = fork); 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++; &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 # ----------------------------------------------------------------------------- # see if we know client and check for spoof IP by challenge my $caller=getpeername($client); my ($port,$iaddr)=unpack_sockaddr_in($caller); my $clientip=inet_ntoa($iaddr); my $clientrec=($hostid{$clientip} ne undef); &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( "WARNING: $clientip did not reply challenge"); &status('No challenge reply '.$clientip); } } else { &logthis( "WARNING: " ."$clientip failed to initialize: >$remotereq< "); &status('No init '.$clientip); } } else { &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); Debug("Request = $userinput\n"); &status('Processing '.$hostid{$clientip}.': '.$userinput); my $wasenc=0; alarm(120); # ------------------------------------------------------------ See if encrypted if ($userinput =~ /^enc/) { if ($cipher) { my ($cmd,$cmdlength,$encinput)=split(/:/,$userinput); $userinput=''; for (my $encidx=0;$encidxdecrypt( pack("H16",substr($encinput,$encidx,16)) ); } $userinput=substr($userinput,0,$cmdlength); $wasenc=1; } } # ------------------------------------------------------------- Normal commands # ------------------------------------------------------------------------ ping if ($userinput =~ /^ping/) { print $client "$perlvar{'lonHostID'}\n"; # ------------------------------------------------------------------------ pong } elsif ($userinput =~ /^pong/) { $reply=reply("ping",$hostid{$clientip}); print $client "$perlvar{'lonHostID'}:$reply\n"; # ------------------------------------------------------------------------ ekey } elsif ($userinput =~ /^ekey/) { my $buildkey=time.$$.int(rand 100000); $buildkey=~tr/1-6/A-F/; $buildkey=int(rand 100000).$buildkey.int(rand 100000); my $key=$perlvar{'lonHostID'}.$hostid{$clientip}; $key=~tr/a-z/A-Z/; $key=~tr/G-P/0-9/; $key=~tr/Q-Z/0-9/; $key=$key.$buildkey.$key.$buildkey.$key.$buildkey; $key=substr($key,0,32); my $cipherkey=pack("H32",$key); $cipher=new IDEA $cipherkey; print $client "$buildkey\n"; # ------------------------------------------------------------------------ load } elsif ($userinput =~ /^load/) { my $loadavg; { my $loadfile=IO::File->new('/proc/loadavg'); $loadavg=<$loadfile>; } $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) { my ($cmd,$udom,$uname,$upass)=split(/:/,$userinput); chomp($upass); $upass=unescape($upass); 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 $pwdcorrect=0; if ($howpwd eq 'internal') { $pwdcorrect= (crypt($upass,$contentpwd) eq $contentpwd); } elsif ($howpwd eq 'unix') { $contentpwd=(getpwnam($uname))[1]; 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 { print $client "non_authorized\n"; } } else { print $client "unknown_user\n"; } } else { print $client "refused\n"; } # ---------------------------------------------------------------------- passwd } elsif ($userinput =~ /^passwd/) { if ($wasenc==1) { my ($cmd,$udom,$uname,$upass,$npass)=split(/:/,$userinput); chomp($npass); $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; { my $pf = IO::File->new($passfilename); $realpasswd=<$pf>; } chomp($realpasswd); my ($howpwd,$contentpwd)=split(/:/,$realpasswd); if ($howpwd eq 'internal') { if (crypt($upass,$contentpwd) eq $contentpwd) { my $salt=time; $salt=substr($salt,6,2); my $ncpass=crypt($npass,$salt); { my $pf = IO::File->new(">$passfilename"); print $pf "internal:$ncpass\n"; } &logthis("Result of password change for $uname: pwchange_success"); print $client "ok\n"; } else { print $client "non_authorized\n"; } } 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 { print $client "unknown_user\n"; } } 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); chomp($uname); my $proname=propath($udom,$uname); if (-e $proname) { print $client "found\n"; } else { print $client "not_found\n"; } # ---------------------------------------------------------------------- update } elsif ($userinput =~ /^update/) { my ($cmd,$fname)=split(/:/,$userinput); my $ownership=ishome($fname); if ($ownership eq 'not_owner') { if (-e $fname) { my ($dev,$ino,$mode,$nlink, $uid,$gid,$rdev,$size, $atime,$mtime,$ctime, $blksize,$blocks)=stat($fname); $now=time; $since=$now-$atime; if ($since>$perlvar{'lonExpire'}) { $reply= reply("unsub:$fname","$hostid{$clientip}"); unlink("$fname"); } else { my $transname="$fname.in.transfer"; my $remoteurl= reply("sub:$fname","$hostid{$clientip}"); 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)"); } 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); } } print $client "ok\n"; } else { print $client "not_found\n"; } } else { print $client "rejected\n"; } # ----------------------------------------------------------------- unsubscribe } elsif ($userinput =~ /^unsub/) { my ($cmd,$fname)=split(/:/,$userinput); if (-e $fname) { print $client &unsub($client,$fname,$clientip); } else { print $client "not_found\n"; } # ------------------------------------------------------------------- subscribe } elsif ($userinput =~ /^sub/) { print $client &subscribe($userinput,$clientip); # ------------------------------------------------------------------------- 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) =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:$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 (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/) { &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); 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); &ManagePermissions($key, $udom, $uname, &GetAuthType( $udom, $uname)); $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"; } # ------------------------------------------------------------------------- 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 (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { for ($i=0;$i<=$#queries;$i++) { $qresult.="$hash{$queries[$i]}&"; } if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { print $client "error:$!\n"; } } else { print $client "error:$!\n"; } # ------------------------------------------------------------------------ eget } 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 (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { for ($i=0;$i<=$#queries;$i++) { $qresult.="$hash{$queries[$i]}&"; } if (untie(%hash)) { $qresult=~s/\&$//; if ($cipher) { my $cmdlength=length($qresult); $qresult.=" "; my $encqresult=''; for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { $encqresult.= unpack("H16", $cipher->encrypt(substr($qresult,$encidx,8))); } print $client "enc:$cmdlength:$encqresult\n"; } else { print $client "error:no_key\n"; } } else { print $client "error:$!\n"; } } else { print $client "error:$!\n"; } # ------------------------------------------------------------------------- del } 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 (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT,0640)) { foreach $key (@keys) { delete($hash{$key}); } if (untie(%hash)) { print $client "ok\n"; } else { print $client "error:$!\n"; } } else { print $client "error:$!\n"; } # ------------------------------------------------------------------------ keys } elsif ($userinput =~ /^keys/) { my ($cmd,$udom,$uname,$namespace) =split(/:/,$userinput); $namespace=~s/\//\_/g; $namespace=~s/\W//g; my $proname=propath($udom,$uname); my $qresult=''; if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER,0640)) { foreach $key (keys %hash) { $qresult.="$key&"; } if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { print $client "error:$!\n"; } } else { print $client "error:$!\n"; } # ------------------------------------------------------------------------ dump } elsif ($userinput =~ /^dump/) { 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) { if (eval('$key=~/$regexp/')) { $qresult.="$key=$hash{$key}&"; } } if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { print $client "error:$!\n"; } } 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, $arg1,$arg2,$arg3)=split(/\:/,$userinput); $query=~s/\n*$//g; 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 { print $client "error:$!\n"; } # ----------------------------------------------------------------------- idput } elsif ($userinput =~ /^idput/) { my ($cmd,$udom,$what)=split(/:/,$userinput); chomp($what); $udom=~s/\W//g; my $proname="$perlvar{'lonUsersDir'}/$udom/ids"; my $now=time; { my $hfh; if ( $hfh=IO::File->new(">>$proname.hist") ) { print $hfh "P:$now:$what\n"; } } my @pairs=split(/\&/,$what); if (tie(%hash,'GDBM_File',"$proname.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"; } # ----------------------------------------------------------------------- idget } elsif ($userinput =~ /^idget/) { my ($cmd,$udom,$what)=split(/:/,$userinput); chomp($what); $udom=~s/\W//g; my $proname="$perlvar{'lonUsersDir'}/$udom/ids"; my @queries=split(/\&/,$what); my $qresult=''; if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER,0640)) { for ($i=0;$i<=$#queries;$i++) { $qresult.="$hash{$queries[$i]}&"; } if (untie(%hash)) { $qresult=~s/\&$//; print $client "$qresult\n"; } else { print $client "error:$!\n"; } } 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(-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"; } # -------------------------------------------------------------------- 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})"); # 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. exit; } } # # 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 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