#!/bin/ksh -p
#
# ident "@(#)utwall.ksh	1.29 04/10/19 SMI"
#
# Copyright 2002,2004 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

umask 022
PATH="/usr/sbin:/usr/bin:/bin"

PROGRAM_ID="utwall"

TRACING=":"
case "$-" in
*x*)
  TRACING="set -x"
  typeset -ftx ListXsunProcs
  typeset -ftx NotifyUsers
  typeset -ftx GetPsLine
  typeset -ftx KillProc
  typeset -ftx LoadArrayIDLE
  ;;
esac

trap "CleanupAndExit 1" INT QUIT TERM

CleanupAndExit() {
  rm -rf /tmp/$PROGRAM_ID.$$
  exit $1
}

Usage() {
  print -u2 "Usage: $PROGRAM_ID $PROGRAM_OPTS"
  CleanupAndExit 1
}

Error() {
  print -u2 "$PROGRAM_ID: error, $1"
  STATUS=1
  return 0
}

Fatal() {
  print -u2 "$PROGRAM_ID: fatal, $1"
  CleanupAndExit 1
}

CheckUidIsZero() {
  case "$(id)" in
    'uid=0('*) return 0;;  # uid is zero
    *)         Fatal "must be run as uid 0 (root)";;
  esac
}

ListIdleDisplays() {
  ls -1 /var/opt/SUNWut/idle/*.pid 2>&- |
  sed 's:.*/\([0-9]*\).*:\1:'

  return 0
}

LoadArrayIDLE() {
  $TRACING

  typeset I

  for I in $(ListIdleDisplays); do
    IDLE[$I]=1
  done

  return 0
}

ListXsunProcs() {
  $TRACING

  typeset USER=""

  case "$1" in
  ALL)
    #
    # ps(1) all users, use awk to find Xsun procs having the
    # opt -auth # note: awk done in this way because we may
    # not know the position of the args for the Xsun proc
    #
    ps -e -o user,pid,args |
    awk '
      $3 ~ /'${XPROC}'/ && \
      $0 ~ / -auth /
    ' - |
    (export LC_ALL=C; sort)
    ;;

  *)
    for USER in $*; do
      if [[ -n "${USER%:*}" ]]; then
      	getent passwd ${USER%:*} 1>&- 2>&-
        case $? in
        2)
	  Error "unknown username '$USER'"
	  continue  # skip unknown usernames
	  ;;

        1|3)
	  Fatal "internal error, can not lookup username '$USER'"
	  ;;
        esac
      fi

      case "$USER" in
      :[0-9]*)
	# USER only contains the display number, ps using awk to find Xsun
	# with the display number portion and having opt -auth
	#
        ps -e -o user,pid,args |
        awk '
	  $3 ~ /'${XPROC}'/  && \
	  $0 ~ / '${USER}' / && \
	  $0 ~ / -auth /
	' -
	;;

      *:*)
	# USER has a ":", ps with the username portion of USER,
	# use awk to find Xsun procs with the display number
	# portion of USER and having the opt -auth
	#
	if [[ $OS == "Linux" ]]; then
		WHO=`${UTO_BASEDIR}/bin/utwho -c | awk '
                	$3 ~ /'${USER%:*}'/ {print $1}'`
		for ITEM in $WHO
		do
		if [[ ${ITEM#*\.} -eq 0 ]]; then
			ps -e -o user,pid,args |
        		awk '
	  	  	  $3 ~ /'${XPROC}'/ && \
		  	  $0 ~ / :'${ITEM%\.*}' / && \
	  	  	  $0 ~ / -auth /
		  	' -
		fi
		done
	else
		ps -u "${USER%:*}" -o user,pid,args | 
		awk '
		  $3 ~ /'${XPROC}'/ && \
		  $0 ~ / :'${USER#*:}' / && \
		  $0 ~ / -auth /
		' -
	fi
	;;

      *)
	# ps with USER, use awk to find Xsun procs
	# having the opt -auth
	#
	if [[ $OS == "Linux" ]]; then
		WHO=`${UTO_BASEDIR}/bin/utwho -c | awk '
			$3 ~ /'${USER}'/ {print $1}'`
		for ITEM in $WHO
		do
		if [[ ${ITEM#*\.} -eq 0 ]]; then
			ps -e -o user,pid,args |
        		awk '
		  	  $3 ~ /'${XPROC}'/ && \
		  	  $0 ~ / ':${ITEM%\.*}' / && \
		  	  $0 ~ / -auth /
			' -
		fi
		done
	else
		ps -u "$USER" -o user,pid,args | 
		awk '
		  $3 ~ /'${XPROC}'/ && \
		  $0 ~ / -auth /
		' -
	fi
	;;
      esac
    done |
    (export LC_ALL=C; sort)
    ;;
  esac

  return 0
}

GetPsLine() {
  $TRACING

  ps -e -o pid,args | grep "[0-9] $1"

  return 0
}

KillProc() {
  $TRACING

  typeset CMD_AND_ARGS="$1"

  typeset PID

  set -- $(GetPsLine "$CMD_AND_ARGS")
  PID="$1"
  if expr "$PID" : '[0-9]\{1,\}$' 1>/dev/null; then
    kill $PID
  else
    Error "internal, could not get PID of '$CMD_AND_ARGS'"
  fi

  return 0
}

GetXauthorityName() {
  $PSWW $1 |
  sed -n 's:.* -auth \(/[^ ]*\).*:\1:p'

  return 0
}

NotifyUsers() {
  $TRACING

  typeset    PREV_USER=""
  typeset -i ANNUNCIATE_TOTAL=0
  typeset -i DTERROR_TOTAL=0
  typeset -i MAIL_TOTAL=0

  typeset    UTAUDIO="$UTO_BASEDIR/bin/utaudio"
  
  typeset    USER ARGS ARG XSUN_DISPLAY XSUN_XAUTHORITY I

  typeset    FRMT="%-10s %s\n"

  typeset    UTWALL_ARGS=""

  typeset    WAIT_PIDS

  typeset -i N=0

  if $VERBOSE; then
    printf "$FRMT" "USER" "ACTION"
  fi

  while read USER PID ARGS; do # {
    XSUN_DISPLAY="$(expr "$ARGS" : '.* \(:[0-9][0-9]*\) .*')"
    XSUN_XAUTHORITY="$(GetXauthorityName $PID)"

    I="${XSUN_DISPLAY#:}"
    if [[ "${IDLE[$I]}" == "1" ]]; then
     continue
    fi

    if $DO_ANNUNCIATE; then # {
      N=$REPEAT_CNT
      (
        while (( $N > 0 )); do
	  UTWALL_ARGS="started by utwall for $USER$XSUN_DISPLAY"

          export DISPLAY="$XSUN_DISPLAY"
	  export XAUTHORITY="$XSUN_XAUTHORITY"

          export AUDIODEV=$($UTAUDIO $UTWALL_ARGS)
	  #
	  # startup a "owned" instance of utaudio service

	  $AUDIOPLAY -d $AUDIODEV $AUDIO_FILE
	  #
	  # play the audio file

	  KillProc "$UTAUDIO $UTWALL_ARGS"
	  #
	  # kill the "owned" utaudio instance

          N=$(($N - 1))
        done
      ) &

      if $VERBOSE; then
        printf "$FRMT" "$USER" "annunciate $REPEAT_CNT times on $XSUN_DISPLAY"
      fi

      ANNUNCIATE_TOTAL=$(($ANNUNCIATE_TOTAL + 1))
    fi # }

    if $DO_DTERROR; then # {
      #
      # DISPLAY and XAUTHORITY must be set to Xsun server values
      # in the shell that runs dterror.ds/xmessage
      #
      export DISPLAY="$XSUN_DISPLAY"
      export XAUTHORITY="$XSUN_XAUTHORITY"
      SCREENS=$($XDPY 2>/dev/null | sed -n -e "s/^number of screens: *//p")
      if [[ -z "$SCREENS" ]]; then
	SCREENS=1
      fi

      WAIT_PIDS=""
      x=0
      while (( $x < $SCREENS )); do
        DISPLAY="$XSUN_DISPLAY.$x"
	$MESSAGECMD $ARGTEXT "$ARG1" $ARGTITLE "$ARG2" $ARGBUTTON "$ARG3" 1>/dev/null 2>&1 &
	WAIT_PIDS="$WAIT_PIDS $!"
	x=$(($x+1))
      done

      if (( $SCREENS > 1 )); then
	# use waitany to monitor if more than 1 message process
      	(
	  ${UTO_BASEDIR}/lib/waitany $WAIT_PIDS
	  kill $WAIT_PIDS
	) 1>/dev/null 2>&1 &
      fi

      if $VERBOSE; then
        printf "$FRMT" "$USER" "message on $XSUN_DISPLAY"
      fi

      DTERROR_TOTAL=$(($DTERROR_TOTAL + 1))
    fi # }

    if $DO_MAIL; then # {
      if [ "$USER" != "$PREV_USER" ]; then # {
        PREV_USER="$USER"

        $MAIL -s "$SUBJECT" $USER <$MSGFILE

        if $VERBOSE; then
          printf "$FRMT" "$USER" "sent mail"
        fi

        MAIL_TOTAL=$(($MAIL_TOTAL + 1))
      fi # }
    fi # }
  done # }

  if $VERBOSE; then
    if $DO_DTERROR; then
      printf "%-10s Total: %4d\n" "message" $DTERROR_TOTAL
    fi

    if $DO_MAIL; then
      printf "%-10s Total: %4d\n" "mail" $MAIL_TOTAL
    fi

    if $DO_ANNUNCIATE; then
      printf "%-10s Total: %4d\n" "annunciate" $ANNUNCIATE_TOTAL
    fi
  fi

  return 0
}

SetPlatformSpecs()
{
  OS=`/bin/uname -s`

  case "$OS" in
    SunOS)
      MESSAGECMD=/usr/dt/bin/dterror.ds
      ARGTEXT=""
      ARG1="$TEXT"
      ARGTITLE=""
      ARG2="$TITLE"
      ARGBUTTON=""
      ARG3="$BUTTON"
      XDPY="/usr/openwin/bin/xdpyinfo"
      XPROC="\/usr\/openwin\/bin\/Xsun"
      PSWW="/usr/ucb/ps uxww"
      AUDIOPLAY="audioplay -i"
      MAIL="mailx"
      ;;
    Linux)
      MESSAGECMD="${UTO_BASEDIR}/lib/utxmessage"
      ARGTEXT=" -message "
      ARG1="$TEXT"
      ARGTITLE=" -title "
      ARG2="Notice"
      ARGBUTTON=" -buttons "
      ARG3="$BUTTON:0"
      XDPY="/usr/X11R6/bin/xdpyinfo"
      XPROC="\/usr\/X11R6\/bin\/Xnewt"
      PSWW="/bin/ps -uww"
      AUDIOPLAY="play"
      MAIL="mail"
      ;;
  esac
}

# main() {

STATUS=0

PRODINFO=/etc/opt/SUNWut/basedir/lib/utprodinfo
UTO_BASEDIR="$($PRODINFO -r SUNWuto)/SUNWut"

export TEXTDOMAIN="utwall"
export TEXTDOMAINDIR="$UTO_BASEDIR/lib/locale"

TITLE="$(gettext 'Notice')"	# message header label
BUTTON="$(gettext 'Dismiss')"	# message button label

OPTSTR="a:r:dm:t:v"
PROGRAM_OPTS="[-a aufile] [-r n] [-d] [-m 'subject'] [-t 'msg_text'] [-v] user[:DN] ..."

DO_ANNUNCIATE=false
DO_DTERROR=false
DO_MAIL=false
SUBJECT=""
REPEAT_CNT=1
TEXT=""
VERBOSE=false

while getopts $OPTSTR OPT 2>&-; do
  case "$OPT" in
    a) DO_ANNUNCIATE=true; AUDIO_FILE="$OPTARG";;
    d) DO_DTERROR=true;;
    m) DO_MAIL=true; SUBJECT="$OPTARG";;
    r) REPEAT_CNT="$OPTARG";;
    t) TEXT="$OPTARG";;
    v) VERBOSE=true;;
   \?) Usage;;
  esac
done
shift $(($OPTIND - 1))

if (( $# == 0 )); then
  Usage
fi

if ! $DO_ANNUNCIATE && ! $DO_DTERROR && ! $DO_MAIL; then
  Fatal "must provide one (or more) of the options: -a, -d, -m"
fi

if $DO_ANNUNCIATE || $DO_DTERROR; then
  CheckUidIsZero
fi

if $DO_DTERROR || $DO_MAIL; then
  if (( $REPEAT_CNT != 1 )); then
    Fatal "repeat count can not be greater than 1 with options: -d, -m"
  fi
fi

MSGDIR="/tmp/$PROGRAM_ID.$$"
rm -rf $MSGDIR
mkdir $MSGDIR
#
# the security police in their analysis of this script determined that
# it was best to remove all possibility that the msg dir could not ever
# be created by anyone else.  so umask is set, the dir is removed before
# it is again created anew. wow.

if (( $? != 0 )); then
  Fatal "internal, could not successfully create temporary directory '$MSGDIR'"
fi
# again the security police have new concerns: "to prevent a malicious
# user from tricking this program into damaging the system you MUST check
# the return status from the 'mkdir' invocation".  i guess it eliminates
# the risk that some user could guess the PID, create the tmp msg dir and
# link it to a system file.  all in time between the dir is removed and
# again created.

MSGFILE="$MSGDIR/msg.txt"

if [[ -n "$TEXT" ]]; then
  print -- "$TEXT" >$MSGFILE
elif $DO_DTERROR || $DO_MAIL; then
  cat - >$MSGFILE
  TEXT="$(awk '{printf("%s\\n", $0)}' $MSGFILE)"
fi

if $DO_ANNUNCIATE; then
  if [[ ! -r $AUDIO_FILE ]]; then
    Fatal "can not open for read '$AUDIO_FILE'"
  fi
fi

SetPlatformSpecs

LoadArrayIDLE

ListXsunProcs $* |
NotifyUsers

CleanupAndExit $STATUS

# }
