#!/bin/tcsh -f
# JLdL 01Sep13.
#
# Copyright (C) 2008-2013 by Jorge L. deLyra <delyra@fma.if.usp.br>.
# This program may be copied and/or distributed freely. See the
# _ terms and conditions in /usr/share/doc/<package>/copyright.
#
# A script to be executed from a CGI form and allow users to
# _ register their home machines with dynamical addresses in
# _ the NIS hosts database of the master NIS server, which
# _ must be the local host.
#
# Since this script is to be called from a CGI script,
# _ set the font size for the error messages.
echo '<body bgcolor="#e0d4c0" text="#000000"'
echo ' link="0000ff" vlink="0000aa" alink="ff0000">'
echo '<font size=4>'
#
# Wait for a random time between 0.1 sec and 1.0 sec,
# _ to ensure that the locking works even if the user
# _ submits several requests almost at the same time.
sleep 0.`rani`
#
# Record the name this script was called with.
set name = `basename $0`
#
# Record the short name of this host.
set shost = `hostname -s`
#
# Get the domain in which to act, from the configuration file;
# _ this is needed in case the host has two network interfaces
# _ to different domains, typically the internet and a private
# _ network, and we need to choose in which domain this remote
# _ authorization system will be active.
set dhost = `cat /etc/dynahost/defaultdomain`
#
# Record the full name of this host in that domain.
set fhost = $shost.$dhost
#
# Define also the localhost.
set lhost = "localhost"
#
# There may be more than one entry in the /etc/hosts file with
# _ the hostname and the same IP, so prevent any trouble with
# _ that here, simply using only the first entry.
set ihost = `hostname --all-ip-addresses | cut -d' ' -f1`
#
# Check for a lock file.
set lockfile = /var/lock/$name
if ( -f $lockfile ) then
    echo "${name}: ERROR: this script is already running"
    echo '</font>'
    echo '</body>'
    exit 1
endif
#
# Make a lock file.
touch $lockfile
#
# Make sure the log file is there.
set logfile = /var/log/$name.log
touch $logfile
#
# Alias defining a macro for exiting cleanly.
alias cleanexit \
    "\rm -f $lockfile >& /dev/null ; echo '</font>\n</body>'; exit \!*"
#
# Define a variable with the TAB character.
set tab = "`echo '\t'`"
#
# Get the remote username from the command line.
set ruser = "$1"
#
# Get the remote hostname from the command line.
set rhost = "$2"
#
# Get the remote IP address from the command line;
# _ filter out a possible "+" left when the user
# _ puts a trailing space in the CGI-script form
# _ entry for the address, which can happen when
# _ one uses cut-and-paste with the mouse.
set raddr = "`echo $3 | cut -d+ -f1`"
#
# Fix the variables to get fixed columns on the log file.
set nchar = `echo -n $ruser | wc -c`
if ( $nchar < 5 ) then
    set ru = "$ruser$tab"
else
    set ru = "$ruser"
endif
set nchar = `echo -n $rhost | wc -c`
if ( $nchar < 5 ) then
    set rh = "$rhost$tab"
else
    set rh = "$rhost"
endif
set nchar = `echo -n $raddr | wc -c`
if ( $nchar < 5 ) then
    set ra = "$raddr$tab$tab"
else if ( $nchar < 13 ) then
    set ra = "$raddr$tab"
else
    set ra = "$raddr"
endif
#
# Make sure the configuration file is there;
# _ first define the configuration file.
set conffile = /etc/dynahost/$name.conf
#
# If it is not, then exit in error.
if ( ! -f $conffile ) then
    echo "${name}: ERROR:" >> $logfile
    echo -n "UN=$ru${tab}HN=NONE${tab}${tab}IP=$ra$tab" >> $logfile
    date >> $logfile
    echo \
    "${name}: INTERNAL ERROR: missing configuration file" | \
    tee -a $logfile
    cleanexit 1
endif
#
# Now check the configuration file.
#
# Get the first record of the configuration file for this username.
set record = `grep "^[ $tab]*${ruser}[ $tab]" $conffile | head -n 1`
#
# If there is no such record, then exit in error.
if ( "$record" == "" ) then
    echo "${name}: ERROR:" >> $logfile
    echo -n "UN=$ru${tab}HN=NONE${tab}${tab}IP=$ra$tab" >> $logfile
    date >> $logfile
    echo \
    "${name}: ERROR: user $ruser not registered for this service" | \
    tee -a $logfile
    cleanexit 1
endif
#
# If the record is incomplete, then exit in error.
if ( $#record < 2 ) then
    echo "${name}: ERROR:" >> $logfile
    echo -n "UN=$ru${tab}HN=NONE${tab}${tab}IP=$ra$tab" >> $logfile
    date >> $logfile
    echo \
    "${name}: ERROR: first record for user $ruser is incomplete" | \
    tee -a $logfile
    cleanexit 1
endif
#
# If the rhost variable is the empty string, then use the default
# _ hostname, which is the one listed in the first entry for this
# _ user in the configuration file, which was extracted above;
# _ otherwise, look for a record with the given hostname.
if ( "$rhost" == "" ) then
    #
    # Extract the remote hostname from the first record.
    set rhost = $record[2]
    #
    # Fix the variable to get fixed columns on the log file.
    set nchar = `echo -n $rhost | wc -c`
    if ( $nchar < 5 ) then
	set rh = "$rhost$tab"
    else
	set rh = "$rhost"
    endif
    #
else
    #
    # Get the record of the configuration file for both
    # _ this username and this hostname.
    set record = `grep "^[ $tab]*${ruser}[ $tab]*${rhost}[ $tab]*"'$' $conffile`
    #
    # If there is no such record, then exit in error.
    if ( "$record" == "" ) then
	echo "${name}: ERROR:" >> $logfile
	echo -n "UN=$ru${tab}HN=$rh${tab}${tab}IP=$ra$tab" >> $logfile
	date >> $logfile
	echo \
	"${name}: ERROR: host $rhost not registered for user $ruser" | \
	tee -a $logfile
	cleanexit 1
    endif
    #
endif
#
# Define the fully qualified remote domain name.
set rfqdn = "$rhost.$dhost"
#
# Check whether the remote host exists.
grep -q "$tab$rfqdn$tab" /etc/hosts
set error = $status
#
# If it does not, then exit in error.
if ( $error != 0 ) then
    echo "${name}: ERROR:" >> $logfile
    echo -n "UN=$ru${tab}HN=$rh${tab}IP=$ra$tab" >> $logfile
    date >> $logfile
    echo "${name}: ERROR: unknown host $rhost" | \
	tee -a $logfile
    cleanexit 1
endif
#
# Check whether the remote host is registered.
grep -q "($rfqdn,-,`nisdomainname`)" /etc/netgroup
set error = $status
#
# If it is not, then exit in error.
if ( $error != 0 ) then
    echo "${name}: ERROR:" >> $logfile
    echo -n "UN=$ru${tab}HN=$rh${tab}IP=$ra$tab" >> $logfile
    date >> $logfile
    echo \
	"${name}: ERROR: host $rhost not registered for this service" | \
	tee -a $logfile
    cleanexit 1
endif
#
# Prevent the shell from interpreting any special
# _ characters contained within the grep targets.
set noglob
#
# Define a grep target to match a generic numerical IP address.
set iptrg = '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}'
#
# Check for the correctness of the IP address.
echo "$raddr" | grep -q "^$iptrg"'$'
set error = $status
#
# If it is wrong, then exit in error.
if ( $error != 0 ) then
    echo "${name}: ERROR:" >> $logfile
    echo -n "UN=$ru${tab}HN=$rh${tab}IP=$ra$tab" >> $logfile
    date >> $logfile
    echo "${name}: ERROR: error in address $raddr" | \
	tee -a $logfile
    cleanexit 1
endif
#
# We must block the registration of addresses which belong to
# _ any local network that this machine is attached to, since
# _ otherwise duplicated entries for some such addresses may
# _ be created and chaos will then ensue within that local
# _ network; therefore, we must check that the IP address
# _ submitted does not belong to any local networks.
#
# In order to do this, get a list of the existing routes to
# _ local networks, that is, those accessed directly and not
# _ through a gateway; however, we must exclude the default
# _ routes for PPP connections, because these do not list a
# _ gateway and match every IP address; from this list cut
# _ out the network addresses and the netmasks.
set alladdrs = `route -n | \
		grep "^$iptrg *0.0.0.0 *$iptrg" | \
		grep -v "^0.0.0.0 *0.0.0.0 *0.0.0.0" | \
		tr -s ' ' | \
		cut -d ' ' -f 1,3`
#
# Collect the local network addresses and netmasks into
# _ two separate variables.
set ic = 0
#
# Start with the standard loopback network data.
set laddrs = "127.0.0.0"
set lmasks = "255.0.0.0"
#
# We should also block the registration from the address
# _ ranges reserved for private networks, since one does
# _ not expect wide-area remote connections originating
# _ from such addresses.
#
# Add the standard private network data.
#
#   10.0.0.0 / 255.0.0.0
set laddrs = ( $laddrs 10.0.0.0 )
set lmasks = ( $lmasks 255.0.0.0 )
#   172.16.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.16.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.17.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.17.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.18.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.18.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.19.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.19.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.20.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.20.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.21.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.21.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.22.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.22.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.23.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.23.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.24.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.24.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.25.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.25.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.26.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.26.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.27.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.27.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.28.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.28.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.29.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.29.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.30.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.30.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   172.31.0.0 / 255.255.0.0
set laddrs = ( $laddrs 172.31.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#   192.168.0.0 / 255.255.0.0
set laddrs = ( $laddrs 192.168.0.0 )
set lmasks = ( $lmasks 255.255.0.0 )
#
# Add the local networks data.
while ( $ic < $#alladdrs )
    @ ic = $ic + 1
    set laddrs = ( $laddrs $alladdrs[$ic] )
    @ ic = $ic + 1
    set lmasks = ( $lmasks $alladdrs[$ic] )
end
#
# Add any arbitrary networks the system manager wishes to
# _ configure as forbidden.
if ( -f /etc/dynahost/forbidnetsnis.conf ) then
    source /etc/dynahost/forbidnetsnis.conf
endif
#
# Based on the corresponding netmask, identify and get the
# _ network part of the network IP address of every local
# _ network which this system is attached to, and add to
# _ them grep targets to match the host parts.
#
# Initalize a variable for the network-parts targets; start
# _ with a generic address with 0 in the first byte.
set lnptrgs = "0\{1,2\}.[0-9]\{1,3\}.[0-9]\{1,3\}.[0-9]\{1,3\}"
#
# Loop over the local networks.
set ic = 0
while ( $ic < $#laddrs )
    @ ic = $ic + 1
    #
    # Initialize a flag for the network part.
    set kc = 4
    #
    # Loop over the 4 numbers of the IP addresses.
    set jc = 0
    while ( $jc < $kc )
	@ jc = $jc + 1
	#
	# Get the current number of the mask.
	@ nn = `echo $lmasks[$ic] | cut -d . -f $jc`
	#
	# If the number is not 255, set the flag;
	# _ this will quit the inner loop.
	if ( $nn != 255 ) then 
	    @ kc = $jc - 1
	endif
	#
    end
    #
    # Get the network part of the network address.
    set lnptrg = `echo $laddrs[$ic] | cut -d . -f 1-$kc`
    #
    # Add the appropriate grep targets at the end.
    if ( $kc == 0 ) then
	set lnptrg = "$iptrg"
    else if ( $kc == 1 ) then
	set lnptrg = "$lnptrg.[0-9]\{1,3\}.[0-9]\{1,3\}.[0-9]\{1,3\}"
    else if ( $kc == 2 ) then
	set lnptrg = "$lnptrg.[0-9]\{1,3\}.[0-9]\{1,3\}"
    else if ( $kc == 3 ) then
	set lnptrg = "$lnptrg.[0-9]\{1,3\}"
    endif
    #
    # Collect the network parts into a single variable; this
    # _ is where the noglob variable must be set, because we
    # _ cannot use double quotes here, since this operation
    # _ will not work correctly if we do.
    set lnptrgs = ( $lnptrgs $lnptrg )
    #
end
#
# Loop over the network-part grep targets.
set ic = 0
while ( $ic < $#lnptrgs )
    @ ic = $ic + 1
    #
    # Check for the an illegal IP address.
    echo "$raddr" | grep -q "^$lnptrgs[$ic]"'$'
    set error = $status
    #
    # If it is not allowed, then exit in error.
    if ( $error == 0 ) then
	echo "${name}: ERROR:" >> $logfile
	echo -n "UN=$ru${tab}HN=$rh${tab}IP=$ra$tab" >> $logfile
	date >> $logfile
	echo "${name}: ERROR: address $raddr NOT ALLOWED" | \
	    tee -a $logfile
	echo '<p>'
	echo -n "NOTE: make sure that your browser is NOT"
	echo    " configured to use a proxy server"
	cleanexit 1
    endif
    #
end
#
# Restore the interpretation of special characters by the shell.
unset noglob
#
# Start the dynahostnisd daemon in the background, in order
# _ to monitor the status of the remote client about to be
# _ registered; however, only do this of this run is _not_
# _ itself part of an unregistration action.
if ( "$raddr" != 000.000.000.000 ) then
    #
    # First stop any instances of the daemon which may already
    # _ be running for this remote user; note that pgrep does
    # _ not produce any output on error, in case there are no
    # _ instances of the daemon, but simply exits in error.
    #
    # We must kill the children processes of the daemon as
    # _ well, and it must be done before the daemon dies.
    #
    # Get the PID of the daemon process(es) for this user and host.
    set pids = `pgrep -f \
		"/bin/tcsh -f /usr/lib/dynahost/dynahostnisd $ruser $rhost"`
    #
    # Only act if there is a daemon process running; but note
    # _ that there could be more than one process running.
    if ( "$pids" != "" ) then
	#
	# Loop over the daemon processes, as there may be
	# _ more than one due to some previous error.
	foreach pid ( $pids )
	    #
	    # Kill all the children processes of the daemon.
	    pkill -P $pid >& /dev/null
	    set error = $status
	    #
	    # Normally the deamon should die when the children do;
	    # _ if there were children processes, then give the
	    # _ daemon a moment to finish off on its own.
	    if ( $error == 0 ) then
		sleep 0.5
	    endif
	    #
	    # Kill the daemon anyway, just to make sure.
	    /bin/kill $pid >& /dev/null
	    #
	end
	#
    endif
    #
    # Now start the daemon on the background; doing it in a subshell
    # _ prevents the shell from printing the report of the job being
    # _ backgrounded; on the other hand, this has the side-effect of
    # _ making it impossible to capture any eventual exit error code
    # _ of the daemon; therefore, the daemon is to be used according
    # _ to a fire-and-forget strategy; note the redirection of stdin
    # _ and stdout to /dev/null, which is needed so that the calling
    # _ program will not go into the zombie state.
    ( /usr/lib/dynahost/dynahostnisd "$ruser" "$rhost" "$raddr" \
	>& /dev/null < /dev/null & )
    #
endif
#
# Update the /etc/hosts file.
echo '</font>'
echo '<font size=3>'
echo '<pre>'
echo Updating the $rfqdn registration...
/usr/lib/dynahost/update-nishost "$rfqdn" "$raddr"
#
# The mail relay part of the update is only done if the remote
# _ host is listed in the mail-relay configuration file.
#
# Only do this if the configuration file exists.
set mailfile = /etc/dynahost/mailrelaynis.conf
if ( -f $mailfile ) then
    #
    # Get the record of the configuration file for this hostname.
    set record = `grep "^[ $tab]*${rhost}[ $tab]" $mailfile`
    #
    # Only do this if the record exists.
    if ( "$record" != "" ) then
	#
	# Extract the hostname of the mail relay server
	# _ from the record, if it is listed there.
	if ( $#record > 1 ) then
	    set msrvr = $record[2]
	    #
	    # Raise the mail-server flag if needed.
	    if ( "$msrvr" != "$shost" && \
		 "$msrvr" != "$fhost" && \
		 "$msrvr" != "$lhost" && \
		 "$msrvr" != "$ihost" ) then
		set rmflg = 1
	    else
		set rmflg = 0
	    endif
	#
	# By default the mail relay server is the local host.
	else
	    set msrvr = "$shost"
	    #
	    # Lower the mail-server flag.
	    set rmflg = 0
	    #
	endif
	#
	# Update the mail client relay authorization.
	echo Updating the mail client relay authorization...
	if ( $rmflg ) then
	    ssh $msrvr /usr/lib/dynahost/update-nismailrelay "$rhost" "$raddr"
	else
	    /usr/lib/dynahost/update-nismailrelay "$rhost" "$raddr"
	endif
	#
    endif
    #
endif
echo '</pre>'
echo '</font>'
#
# Log the event.
echo -n "UN=$ru${tab}HN=$rh${tab}IP=$ra$tab" >> $logfile
date >> $logfile
#
# Done.
echo '<font size=4>'
echo "${name}: done with address $raddr for host $rhost"
cleanexit 0
