#!/bin/sh
#
# Copyright (C) 1995, 1996  Lars Berntzon
#
##################################################################
#		M K M A C H I N E S
#		-------------------
# Description:
#	Execute the hostinfo script on a set of machines and fill
#	the machines database with information about each host.
#
Usage="
#	mkmachines [option ...] [host...]
#
#	Options:
#
#	-sahome			- Set the SAHOME enviroment as an argument.
#
#	-sadbdir		- Set the SADBDIR enviroment as an argument.
#
#	-db <database>		- Specify another database than 'machines'
#				  which is default.
#
#	-l <remote-user>	- Run on remote machine as remote-user.
#	
#	-c <remote-command>	- Specify remote command to execute, default
#				  is ./hostinfo.
#
#	-i <input-file>		- Don't examine machines, read data from this
#				  file instead and feed into the database.
#
#	-o <output-file>	- Don't feed the data into DBM-files, only
#				  produce the output file which is suitable
#				  for giving as a -i file. This can be used
#				  for instance to mail text databases cross
#				  networks.
#
#	-n <num-procs>		- Number of parallell processes, default is
#				  10 processes.
#
#	-r			- Removes the old database before examining
#				  the hosts. This will produce a database with
#				  data only for hosts that respond. This option
#				  will be removed in the future.
#
#	-x			- Remove all machines in the database that
#				  was not to be examined by mkmachines.
#				  This is a simple way to keep the machines
#				  databased clean from old hosts that no
#				  longer exist. Just remove them from the
#				  /etc/hosts file or what ever file is being
#				  as an input list of hosts to mkmachines.
#
#	-f <file>		- Read machine names from file instead of
#				  arguments. Use - for stdin.
#
#	-t <seconds>		- Number of seconds to wait for response from
#				  one machine.
#	-hosts <file>		- Run on all hosts in the file on local host.
#				  The file is in /etc/hosts format.
#				  May be specified multiple times.
#
#	-rhosts <host>:<file>	- Run on all hosts in the file on remote host.
#				  The file is in /etc/hosts format.
#				  May be specified multiple times.
#
#	-nis			- Run on all hosts in the NIS map hosts.byname
#				  as seen by local host.
#				  May be specified multiple times.
#
#	-rnis <host>		- Run on all hosts in the NIS map hosts.byname
#				  as seen by remote host.
#				  May be specified multiple times.
#
#	-nisplus		- Run on all hosts in the NIS+ hosts table as
#				  seen by local host.
#				  May be specified multiple times.
#
#	-rnisplus <host>	- Run on all hosts in the NIS+ hosts table as
#				  seen by remote host.
#				  May be specified multiple times.
#
#	-dns <domain>		- Specify all machines in a domain.
#
#	-kill <kill-file>	- Specify a hosts killfile.
#
# 	Arguments:
#	host...			- The machine(s) to get information about.
#
"
##################################################################
DB=machines		# Default database name.
RCMD=./hostinfo		# Command to execute on remote machine.
RUNAS=""		# Default is to run as current user.
NPROC=10		# Default number of parallell processes.
TIMEOUT=300		# Default timeout per machine.
SADBMK=sadbmk		# Name of the database creator command.
KILLFILE=""		# Default is no kill file.
OPT_R=-r		# Use -r option for sadbmk (replace mode by default).

date="`date +%y%m%d`"	# Current date.
hour="`date +%H:%M:%S`"	# Current hour.
outputFile=""		# Output file to use.
inputFile=""		# Input file to use.
hostCmd=""		# Command to generate host names.

prog=`basename $0`	# Name of this program.

tmpdir=/tmp/mkma$$	# Directory for temporary files.
errorFile=$tmpdir/err	# Error output from rsh.
tmpout=$tmpdir/out	# Output from rsh.
hostList=$tmpdir/hosts	# List of hosts to examine.
lockfile=/tmp/mkma.lck	# Lockfile for mkmachines.

rnis_opt=""
rnisplus_opt=""
rhosts_opt=""

#
# Since this command greps on output from other commands,
# the grep must be in english.
#
unset LANG LC_MESSAGES

#
##################################################################
#		M A I N
#		-------
# Description:
#	This is the main routine for mkmachines.
#
##################################################################
main()
{
    #
    # Setup before execution.
    #
    setup

    #
    # Adjust the script so it will work on multiple platforms.
    #
    autoconfig

    #
    # Lock execution of mkmachines.
    #
    lock 

    #
    # Get arguments.
    #
    get_arguments "$@"

    #
    # Generate list of hosts to examine.
    #
    generate_host_list

    #
    # Kill hosts as specified in the kill file.
    #
    kill_host_list

    #
    # Procude data, either get data from an external text file or by running hostinfo
    # on all the machines.
    # Then store it, either in a database or an external text file.
    #
    produce_data | store_data

    #
    # Unlock execution of mkmachines.
    #
    unlock

    #
    # Cleanup temporary files.
    #
    cleanup
}

#
##################################################################
#		S E T U P
#		---------
# Description:
#	Setup everything before execution. Like creating
#	directory for temporary files.
#
##################################################################
setup()
{
    #
    # Create temporary directory.
    #
    if [ -d $tmpdir ]; then
	echo "$prog: $tmpdir: directory already exist" >&2
     	abort
    fi
    mkdir $tmpdir

    #
    # Make empty hostList
    #
    cp /dev/null $hostList

    trap "cleanup; exit 1" 1 2 3
}

#
##################################################################
#		L O C K
#		-------
# Description:
#	Lock current execution of mkmachines.
#
##################################################################
lock()
{
    if [ -s $lockfile ]; then
        pid=`cat $lockfile`
	if check_process $pid; then
	    echo "$prog: lock failed: $prog allready running" >&2
	    exit 1
	fi
    fi
    echo $$ > $lockfile
    chmod 666 $lockfile
}

#
##################################################################
#		U N L O C K
#		-----------
# Description:
#	Unlock mkmachines.
#
##################################################################
unlock()
{
    cp /dev/null $lockfile
    rm -f $lockfile
}

#
##################################################################
#		A U T O C O N F I G
#		-------------------
# Description:
#	This routine configures the mkmachines script so it will 
#	run on multiple platforms. Like SunOS, Solaris, HP/UX e.t.c.
#
##################################################################
autoconfig()
{
    #
    # Setup a universal PATH.
    #
    PATH=$PATH:/usr/ucb:/usr/etc:/usr/sbin:/sbin:/etc
    export PATH

    #
    # Append path for satools directory. Most probaly where
    # mkmachines resides itself.
    #
    MYDIR=`dirname $0`
    PATH=$MYDIR:$PATH

    #
    # Save my hostname.
    #
    HOSTNAME=`uname -n`

    #
    # Check which rsh to use, first try rsh, if that does not work try remsh (hpux).
    #
    if [ "`rsh $HOSTNAME uname -n 2> /dev/null < /dev/null`" = "$HOSTNAME" ]; then
	rsh="rsh"
    elif [ "`remsh $HOSTNAME uname -n 2> /dev/null < /dev/null`" = "$HOSTNAME" ]; then
	rsh="remsh"
    else
        echo "$prog: failed to remote shell to 'localhost'" >&2
        abort
    fi

    #
    # Check what kind of ping to use.
    #
    case "`uname -s`" in
    SunOS)
        pinghost() {
	    satimeout -3 ping "$1" 2 > /dev/null 2>&1
	}
	;;
    Linux)
        pinghost() {
	    satimeout -3 ping -c 1 "$1" > /dev/null 2>&1
	}
	;;
    HP-UX)
        pinghost() {
	    satimeout -3 /usr/sbin/ping "$1" -n 1 2>&1 | grep "1 packets received" > /dev/null && return 0
	}
	;;
    *)
        pinghost() {
	    satimeout -3 ping "$1" 2 > /dev/null 2>&1 && return 0
	}
	;;
    esac

    #
    # Check how processes can be checked.
    #
    if kill -0 28398 2>&1 | grep -i "no such" > /dev/null; then
    	check_process() {
	    kill -0 $1 2>&1 | grep -i "no such" > /dev/null || return 0
	    return 1
    	}
    else
    	echo "$prog: warning: don't know how to check processes" >&2
    	check_process() {
    	    return 1
    	}
    fi
}

#
##################################################################
#		G E T _ A R G U M E N T S
#		-------------------------
# Description:
#	Examine all command line arguments.
#
##################################################################
get_arguments()
{
    while [ $# != 0 ]
    do
	case $1 in
	-sahome)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    SAHOME=$1
	    export SAHOME
	    ;;

	-sadbdir)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    SADBDIR=$1
	    export SADBDIR
	    ;;

	-db)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    DB=$1
	    ;;
	-l)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    RUNAS="-l $1"
	    ;;

	-r)
	    echo "WARNING: the -r option will be removed in the future" >&2
	    OPT_R=""
	    ;;

	-x)
	    OPT_X=-x
	    ;;


	-i)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    inputFile=$1
	    ;;

	-o)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    outputFile=$1
	    ;;

	-c)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    RCMD=$1
	    ;;

	-n)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    NPROC=$1
	    ;;

	-f)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    if [ "$1" = "-" ]; then
		cat $1 >> $hostList
	    elif [ ! -r "$1" ]; then
		echo "$prog: $1: can not open file" >&2
		abort
	    else
		cat $1 >> $hostList
	    fi
	    ;;

	-dns)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    echo "ls -t a $1" |
		nslookup |
		sed -e '/=/d' -e '/[]:]/d' -e '/^[ 	]*$/d' -e '/>/d' -e '/localhost/d' |
		awk "{print \$1 \".$1\"}" >> $hostList
	    ;;

	-nis)
	    ypcat hosts | awk '{print $2}' >> $hostList
	    ;;

	-rnis)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    rnis_opt="$rnis_opt $1"
	    ;;

	-nisplus)
	    niscat hosts.org_dir | awk '{print $1}' >> $hostList
	    ;;

	-rnisplus)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    rnisplus_opt="$rnisplus_opt $1"
	    ;;

	-hosts)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    sed -e '/^[ 	]*#/d' -e 's/#.*//' -e '/^[ 	]*$/d' $1 |
	        awk '{print $2}' >> $hostList
	    ;;

	-rhosts)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    rhosts_opt="$rhosts_opt $1"
	    ;;

	-t)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    TIMEOUT=$1
	    ;;
	-kill)
	    if [ $# -le 1 ]; then
	    	usage
	    fi
	    shift
	    KILLFILE=$1
	    if [ ! -r "$KILLFILE" ]; then
	    	echo "$prog: $KILLFILE: file not readable" >&2
	    	exit 1
	    fi
	    ;;

	-*)
	    usage
	    ;;
	*)
	    break
	    ;;
	esac
	shift
    done

    #
    # The remaining hosts are specified as command line arguments.
    #
    for h in $*
    do
    	echo $h
    done >> $hostList

    #
    # Check environment. If database is to be written, make sure
    # enough environmental variables has been set.
    #
    if [ -z "$outputFile" ]; then
	if [ -z "$SADBDIR" ]; then
	    if [ -z "$SAHOME" ]; then
		echo "$prog: you must define either the SAHOME or SADBDIR environment" >&2
		abort
	    fi
	    SADBDIR=$SAHOME/sadb
	fi
	if [ ! -d "$SADBDIR" ]; then
	    echo "$prog: $SADBDIR: directory does not exist" >&2
	    abort
	fi
    fi
}

#
##################################################################
#		G E N E R A T E _ H O S T _ L I S T
#		-----------------------------------
# Description:
#	Generate the list of hosts to examine. The options and
#	arguments are used to make up this list.
#
##################################################################
generate_host_list()
{
    #
    # Examine all NIS servers.
    #
    for h in $rnis_opt
    do
        satimeout -$TIMEOUT $rsh $RUNAS $h sh >> $hostList << 'EOF'
	    if [ -f /etc/resolv.conf ]; then
		domain=".`awk '/^domain /{print $2}' /etc/resolv.conf`"
	    fi
	    ypcat hosts | awk '{print $2}' | sed "/^[^\.]*\$/s/\$/$domain/"
EOF
    done

    #
    # Examine all NIS+-servers.
    #
    for h in $rnisplus_opt
    do
        satimeout -$TIMEOUT $rsh $RUNAS $h sh >> $hostList << 'EOF'
	    if [ -f /etc/resolv.conf ]; then
		domain=".`awk '/^domain /{print $2}' /etc/resolv.conf`"
	    fi
	    niscat hosts.org_dir | awk '{print $1}' | sed "/^[^\.]*\$/s/\$/$domain/"
EOF
    done

    #
    # Examine all remote hosts-files.
    #
    for h in $rhosts_opt
    do
	host=`IFS=" 	:"; set - $h; echo $1`
	file=`IFS=" 	:"; set - $h; echo $2`

        satimeout -$TIMEOUT $rsh $RUNAS $host sh >> $hostList << EOF
	    if [ -f /etc/resolv.conf ]; then
		domain=".\`awk '/^domain /{print \$2}' /etc/resolv.conf\`"
	    fi
	    sed -e '/^[ 	]*#/d' -e 's/#.*//' -e '/^[ 	]*\$/d' $file |
	    awk '{print \$2}' | sed "/^[^\\.]*\\\$/s/\\\$/\$domain/"
EOF
    done

    #
    # Make host list unique.
    #
    sort -o $hostList -u $hostList

    #
    # Check that there is something to do.
    #
    if [ ! -s $hostList -a -z "$inputFile" ]; then
    	echo "$prog: no hosts nor input data was specified" >&2;
    	abort
    fi
}

#
##################################################################
#		K I L L _ H O S T _ L I S T
#		---------------------------
# Description:
#	Remove all hosts in the hosts file that is
#	matched by kill expressions in the kill file.
#
##################################################################
kill_host_list()
{
    #
    # Only process if kill files specified.
    #
    if [ -z "$KILLFILE" ]; then
    	return 0
    fi

    #
    # First make a temporary ed script from the kill file.
    #
    sed -e 's%^%g@%' -e 's%$%@d%' $KILLFILE > /tmp/kill$$

    status=`(cat /tmp/kill$$; echo w; echo q) |
            ed $hostList > /dev/null 2>&1`
    if [ $? != 0 ]; then
    	echo "$prog: $status: problems with killfile" >&2
    fi
    rm -f /tmp/kill$$
}

#
##################################################################
#		P R O D U C E _ D A T A
#		-----------------------
# Description:
#	Produce the data to be stored. This can also be data
#	directly from a input file in the satools external format.
#
##################################################################
produce_data()
{
    #
    # Check if input data is specified.
    #
    if [ ! -z "$inputFile" ]
    then
	if [ $inputFile = - ]; then
	    inputFile=""
	elif [ ! -r $inputFile ]; then
	    echo "$prog: $inputFile: can't read file" >&2
	    abort
	fi
	cat $inputFile
    fi


    #
    # Turn host list into two columns, first with full domain
    # name and the second with only host name. The output list
    # must be to a file because Sun:s has problem reading from
    # such a big pipe.
    #
    count=0
    cat $hostList |
    sed 's/^\([^\.][^\.]*\)\.\(.*\)/\1.\2 \1/' > $hostList.new
    while read m shortName
    do
	#
	# If only a short name, use that.
	#
	if [ -z "$shortName" ]; then
	    shortName=$m
	fi

	(
	    # Make sure files are clean.
	    rm -f $errorFile.$count $tmpout.$count

	    # This is only for trace printout.
	    errorText="ok"

	    if pinghost $m
	    then
		echo ping_day $shortName $date
		echo ping_hour $shortName $hour
		satimeout -$TIMEOUT $rsh $RUNAS $m $RCMD 2>$errorFile.$count > $tmpout.$count < /dev/null
		rc=$?

		if [ -s $errorFile.$count ]; then
		    echo key $shortName $shortName
		    echo error $shortName true
		    echo error_text $shortName `cat $errorFile.$count`
		    errorText="failed: `cat $errorFile.$count`"
		elif [ $rc != 0 ]; then
		    echo key $shortName $shortName
		    echo error_text $shortName timeout
		    echo error $shortName true
		    errorText="failed: timeout"
		fi

		if grep "^sAhOsTiNfO key " $tmpout.$count >/dev/null; then
		    sed -e '/^sAhOsTiNfO/!d' -e 's/^sAhOsTiNfO //' $tmpout.$count 
		    echo error $shortName false
		else 
		    errorText="`cat $errorFile.$count`"
		    echo key $shortName $shortName
		    echo error $shortName true
		    echo error_text $shortName $errorText
		fi
		rm -f $errorFile.$count $tmpout.$count
	    else
		errorText="failed: no answer from ping "
		echo key $shortName $shortName
		echo error $shortName true
		echo error_text $shortName $errorText
	    fi

	    echo "$m: $errorText" >&2
	) &
	count=`expr $count + 1`
	if [ $count -ge $NPROC ]; then
	    count=0
	    wait
	fi
    done < $hostList.new

    #
    # Also get a description of the columns, but only
    # if hosts has been examined, i.e. the -i option is not
    # only used.
    #
    #
    if [ -s "$hostList" ]; then
	$rsh $RUNAS $HOSTNAME $RCMD -d /dev/null | sed -e '/^sAhOsTiNfO/!d' -e 's/^sAhOsTiNfO //'
    fi

    wait
}    

#
##################################################################
#		S T O R E _ D A T A
#		-------------------
# Description:
#	Store the data. Either into the database by using the
#	sadbcmd command or to a text file. This text file will have
#	the standard satools external format.
#
##################################################################
store_data()
{
    (
	#
	# Either make the database or write to a output file.
	#
	if [ ! -z "$outputFile" ]; then
	    if [ $outputFile = - ]; then
		cat
	    else
		cat > $outputFile || {
		    echo "$prog: $outputFile: can't write to file" >&2
		    abort
		}
	    fi
	else
	    $SADBMK $OPT_R $OPT_X $SADBDIR/$DB.db || {
		echo "$prog: failed to write to database (column $c)"
	    }
	fi
    )
}


#
# V a r i o u s   s u p p o r t   r o u t i n e s .
# - - - - - - - - - - - - - - - - - - - - - - - - - 
#

#
##################################################################
#		U S A G E
#		---------
# Description:
#	Print a short usage message.
#
##################################################################
usage()
{
    echo "usage: $Usage" | sed 's/^#//' >&2
    abort
}

#
##################################################################
#		A B O R T
#		---------
# Description:
#	Abort execution, cleanup and return error exist status.
#
##################################################################
abort()
{
    cleanup
    exit 1
}

#
##################################################################
#		C L E A N U P
#		-------------
# Description:
#	Cleanup before exiting.
#
##################################################################
cleanup()
{
    rm -rf $tmpdir
}

#
# Execute the main routine (if NOEXEC is set to :, this file can be used as
# a shell library).
#
$NOEXEC main "$@"

#
# History of changes:
# mkmachines,v
# Revision 1.32  1999/04/20 21:05:08  lasse
# The columns description didn't work with the new sAhOsTiNfO mark string
#
# Revision 1.31  1997/09/27 18:39:04  lasse
# Added hostinfo tagging
#
# Revision 1.30  1997/09/15 18:17:32  lasse
# Added -kill option
#
# Revision 1.29  1996/11/21 20:18:58  lasse
# Now reads the column data from `uname -n` instead of local host.
#
# Revision 1.28  1996/11/12 20:25:28  lasse
# Fixed problems with Solaris and SunOS
#
# Revision 1.27  1996/10/22 21:21:51  lasse
# Added /usr/ucb to path and don't run rsh localhost hostinfo if no
# hostlist has been supplied.
#
# Revision 1.26  1996/10/21 20:57:06  lasse
# Added changes from SunOS and Solaris
#
# Revision 1.25  1996/10/20 21:52:15  lasse
# To be 1.2 maybe
#
# Revision 1.24  1996/10/20 21:27:14  lasse
# Added the -x option.
#
# Revision 1.23  1996/10/14 22:19:05  lasse
# Added -dns option
#
# Revision 1.22  1996/10/08 23:48:44  lasse
# Now has option for -dns but it does not work.
#
# Revision 1.21  1996/10/08 23:41:08  lasse
# Added options -rnis, -rnisplus and -rhosts.
#
# Revision 1.20  1996/09/21 23:25:11  lasse
# Use ping_day instead of ping_date which is supposed to be an internal column.
#
# Revision 1.19  1996/09/21 22:48:55  lasse
# Added timeout on ping.
#
# Revision 1.18  1996/09/14 18:33:25  lasse
# Added some things to the TODO and added pargs
#
# Revision 1.17  1996/09/14 18:11:19  lasse
# Added pingdate
#
# Revision 1.16  1996/07/30 21:54:52  lasse
# New version of sadbmk
#
# Revision 1.15  1996/07/13 19:08:24  lasse
# Various corrections
#
# Revision 1.14  1996/06/08 22:00:15  lasse
# Don't exit if one column can't be written.
#
# Revision 1.13  1996/05/01 21:30:33  lasse
# Corrected error with faulty argument checking.
#
# Revision 1.12  1996/04/30 19:37:58  lasse
# Corrected cleaningup.
#
# Revision 1.11  1996/04/30 19:14:25  lasse
# Added options -nis -nisplus and -hosts
#
# Revision 1.10  1996/03/12 19:42:26  lasse
# Checking in from remote.
#
# Revision 1.8  1995/09/24  16:11:04  lasse
# backup
#
# Revision 1.7  1995/09/23  13:46:36  lasse
# Imported
#
# Revision 1.1.1.1  1995/09/11  09:23:13  qdtlarb
# THis is version 0.6
#
# Revision 1.6  1995/09/10  20:42:09  lasse
# Added copyright to all
#
# Revision 1.5  1995/09/10  19:04:54  lasse
# Added log keyword
#
#
