--- loncom/lond 2002/02/07 10:22:44 1.70 +++ loncom/lond 2002/05/08 02:31:04 1.79 @@ -2,7 +2,7 @@ # The LearningOnline Network # lond "LON Daemon" Server (port "LOND" 5663) # -# $Id: lond,v 1.70 2002/02/07 10:22:44 harris41 Exp $ +# $Id: lond,v 1.79 2002/05/08 02:31:04 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -48,6 +48,11 @@ # 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 ### # based on "Perl Cookbook" ISBN 1-56592-243-3 @@ -68,6 +73,8 @@ 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=''; @@ -155,7 +162,7 @@ $server = IO::Socket::INET->new(LocalPor # 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 @@ -230,6 +237,13 @@ sub logthis { print $fh "$local ($$): $message\n"; } +# ------------------------- Conditional log if $DEBUG true. +sub Debug { + my $message = shift; + if($DEBUG) { + &logthis($message); + } +} # ------------------------------------------------------------------ Log status sub logstatus { @@ -296,10 +310,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"); @@ -337,6 +351,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); @@ -526,6 +541,7 @@ sub make_new_child { } if ($clientok) { # ---------------- New known client connecting, could mean machine online again + &reconlonc("$perlvar{'lonSockDir'}/$hostid{$clientip}"); &logthis( "Established connection: $hostid{$clientip}"); @@ -533,6 +549,7 @@ sub make_new_child { # ------------------------------------------------------------ Process requests while (my $userinput=<$client>) { chomp($userinput); + Debug("Request = $userinput\n"); &status('Processing '.$hostid{$clientip}.': '.$userinput); my $wasenc=0; alarm(120); @@ -549,8 +566,9 @@ sub make_new_child { } $userinput=substr($userinput,0,$cmdlength); $wasenc=1; - } } + } + # ------------------------------------------------------------- Normal commands # ------------------------------------------------------------------------ ping if ($userinput =~ /^ping/) { @@ -587,21 +605,13 @@ sub make_new_child { } 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"; - } + my $result = GetAuthType($udom, $uname); + if($result eq "nouser") { + print $client "unknown_user\n"; + } + else { + print $client "$result\n" + } } else { print $client "refused\n"; } @@ -637,10 +647,13 @@ sub make_new_child { $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); @@ -664,7 +677,8 @@ sub make_new_child { chomp($npass); $upass=&unescape($upass); $npass=&unescape($npass); - my $proname=propath($udom,$uname); + &logthis("Trying to change password for $uname"); + my $proname=propath($udom,$uname); my $passfilename="$proname/passwd"; if (-e $passfilename) { my $realpasswd; @@ -679,11 +693,42 @@ sub make_new_child { 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"; } - } 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 { @@ -694,14 +739,19 @@ sub make_new_child { } # -------------------------------------------------------------------- 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'}) { @@ -730,7 +780,8 @@ sub make_new_child { $salt=substr($salt,6,2); my $ncpass=crypt($npass,$salt); { - my $pf = IO::File->new(">$passfilename"); + &Debug("Creating internal auth"); + my $pf = IO::File->new(">$passfilename"); print $pf "internal:$ncpass\n"; } print $client "ok\n"; @@ -745,6 +796,8 @@ sub make_new_child { my $execpath="$perlvar{'lonDaemons'}/". "lcuseradd"; { + &Debug("Executing external: ". + $execpath); my $se = IO::File->new("|$execpath"); print $se "$uname\n"; print $se "$npass\n"; @@ -773,10 +826,13 @@ sub make_new_child { umask($oldumask); # -------------------------------------------------------------- changeuserauth } elsif ($userinput =~ /^changeuserauth/) { - if ($wasenc==1) { + &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"; @@ -985,9 +1041,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); @@ -1004,7 +1064,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"; @@ -1381,15 +1445,15 @@ sub make_new_child { $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 - $client->close(); $server->close(); # this exit is VERY important, otherwise the child will become @@ -1399,6 +1463,68 @@ 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"; + } + +} + # ----------------------------------- POD (plain old documentation, CPAN style) =head1 NAME @@ -1407,16 +1533,284 @@ lond - "LON Daemon" Server (port "LOND" =head1 SYNOPSIS -Should only be run as user=www. Invoked by loncron. +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 -=head1 README +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 * -Not yet written. +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 @@ -1441,7 +1835,3 @@ linux Server/Process =cut - - - -