#!/bin/ksh -p
#
# ident "@(#)utwall.ksh	1.35 09/03/18 SMI"
#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#
# Solaris 10 Trusted Extension guard
#
ORIGIN=`/usr/bin/dirname $0`
UTIL_LIB=${ORIGIN:-/opt/SUNWut/lib}/../lib/support_lib/util_lib
. $UTIL_LIB
FailExecInLocalZoneOnTx 
#
# Solaris 10 Trusted Extension guard 
#


umask 022
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11/bin:/usr/X11R6/bin:/usr/openwin/bin:/opt/gnome/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

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

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

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

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

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

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

  return 0
}

function LoadArrayIDLE {
  $TRACING

  typeset I

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

  return 0
}

function ListDpysForUID {
  $TRACING
  
  DPYS=`cd $SESSIONPROCDIR && grep -l "uid=$1\$" *`
  for DPY in $DPYS
  do
    # validate that it's all numeric just to avoid picking up cruft
    if [ -z "`echo $DPY | sed 's/[0-9]//g'`" ]; then
      ps -e -o user,pid,args |
      awk '
	$3 ~ /'${XPROC}'/ && \
	$0 ~ / :'${DPY}' / && \
	$0 ~ / -auth /
      ' -
    fi
  done
}

function ListDpysForDPY {
  $TRACING
  
  DPYS=`cd $SESSIONPROCDIR && grep -l "uid=$1\$" *`
  for DPY in $DPYS
  do
    # Make sure there's a user+dpy match to the specification
    if [ $DPY = ${2#*:} ]; then
      ps -e -o user,pid,args | 
      awk '
	  $3 ~ /'${XPROC}'/ && \
	  $0 ~ / :'${2#*:}' / && \
	  $0 ~ / -auth /
	' -
    fi
  done
}

function ListXsunProcs {
  $TRACING

  typeset USER=""
  typeset UID=""
  typeset PWENT=""

  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
      	PWENT=`getent passwd ${USER%:*} 2>/dev/null`
        case $? in
        2)
	  Error "unknown username '$USER'"
	  continue  # skip unknown usernames
	  ;;

        1|3)
	  Fatal "internal error, can not lookup username '$USER'"
	  ;;
        esac
	UID=`echo $PWENT | awk -F: '{print $3}'`
      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
	#
	# XXX When :DPY is permitted why support this too? Should deprecate
	ListDpysForDPY $UID $USER
	;;

      *)
	# ps with USER, use awk to find Xsun procs
	# having the opt -auth
	#
	ListDpysForUID $UID
	;;
      esac
    done |
    (export LC_ALL=C; sort)
    ;;
  esac

  return 0
}

function GetPsLine {
  $TRACING

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

  return 0
}

function 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
}

function GetXauthorityNameLinux {
  /bin/ps uww $1 |
  sed -n 's:.* -auth \(/[^ ]*\).*:\1:p'

  return 0
}

function GetXauthorityNameSolaris {
  pargs $1 | awk '
    BEGIN { printnextline=0; }
    $NF == "-auth" { printnextline=1; continue; }
    printnextline == 1 { print $NF; exit 0; }
  '
}

function 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="$($GETXAUTH $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
      THEDATE=`date`
      unset DBUS_SESSION_BUS_ADDRESS WINDOWID
      while (( $x < $SCREENS )); do
        DISPLAY="$XSUN_DISPLAY.$x"
	zenity --warning --title "utwall from $US @ $THEDATE" --width 500 --text "$TEXT" 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
}

function SetPlatformSpecs
{
  OS=`/bin/uname -s`

  case "$OS" in
    SunOS)
      XPROC="(\/usr\/openwin\/bin\/Xsun|${XNEWT_ESC})"
      AUDIOPLAY="audioplay -i"
      MAIL="mailx"
      GETXAUTH=GetXauthorityNameSolaris
      ;;
    Linux)
      XPROC="${XNEWT_ESC}"
      AUDIOPLAY="play"
      MAIL="mail"
      GETXAUTH=GetXauthorityNameLinux
      ;;
  esac
}

# main() {

STATUS=0
XDPY="xdpyinfo"

PRODINFO=/etc/opt/SUNWut/basedir/lib/utprodinfo
UTO_BASEDIR="$($PRODINFO -r SUNWuto)/SUNWut"
UTO_BASEDIR_ESC=`print $UTO_BASEDIR | sed -e 's%/%\\\\/%g'`
XNEWT_ESC="${UTO_BASEDIR_ESC}\/lib\/Xnewt"
SESSIONPROCDIR="/var/opt/SUNWut/session_proc"

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

US=`who am i | awk '{print $1}'`

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

# }
