#!/bin/sh
# xon --- run remote X application

# Copyright (C) 1993, 1995, 1999 Noah S. Friedman

# Author: Noah Friedman <friedman@splode.com>
# Created: 1993-03-08

# $Id: xon,v 1.10 1999/10/12 08:11:17 friedman Exp $

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can either send email to this
# program's maintainer or write to: The Free Software Foundation,
# Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.

# Commentary:
# Code:

# Name by which this script was invoked.
fullprogname=$0
progname=`echo "$0" | sed -e 's/[^\/]*\///g'`

bq='`'
eq="'"

usage="Usage: $progname {options} [rhost] [cmd] {args...}

Options are:
-D, --debug                  Turn on shell debugging (${bq}set -x$eq).
-d, --display      DISP      Set DISPLAY environment variable to DISP.
-e, --export       VAR       Export current definition of environment
                   V,V1,...  variable VAR on remote host.  If multiple,
                             comma-separated variables are specified,
                             export the value of each one.
                             This option can also be specified multiple
                             times, once for each variable.
-h, --help                   You're looking at it.
-l, --login-as     USER      Log in as USER on remote host.
-r, --rsh          RSHCMD    Use RSHCMD to connect to remote host.
                             Defaults to remsh or rsh depending on system.
-X, --xauth                  Transmit xauth authentication information.
-x, --xhost-enable XHOST     Use ${bq}xhost$eq to enable X server access
                             for host XHOST.
"

# Make sure stdout and stderr are readable; if not
# on a terminal, then in a file.
for fd in 1 2; do
  if test ! -t $fd ; then
    # Redirect in append mode, in case the file is already there with
    # previous output.
    eval exec "$fd>>" ${XONERRORFILE-$HOME/.xon-errors}
  fi
done

# Usage: eval "$getopt"; value=$optarg
# or     optarg_optional=t; eval "$getopt"; value=$optarg
#
# This function automatically shifts the positional args as appropriate.
# The argument to an option is optional if the variable `optarg_optional'
# is non-empty.  Otherwise, the argument is required and getopt will cause
# the program to exit on an error.  optarg_optional is reset to be empty
# after every call to getopt.  The argument (if any) is stored in the
# variable `optarg'.
#
# Long option syntax is `--foo=bar' or `--foo bar'.  2nd argument
# won't get used if first long option syntax was used.
#
# Note: because of broken bourne shells, using --foo=bar syntax can
# actually screw the quoting of args that end with trailing newlines.
# Specifically, most shells strip trailing newlines from substituted
# output, regardless of quoting.
getopt='
  {
    optarg=
    case "$1" in
      --*=* )
        optarg=`echo "$1" | sed -e "1s/^[^=]*=//"`
        shift
       ;;
      * )
        case ${2+set} in
          set )
            optarg="$2"
            shift
            shift
           ;;
          * )
            case "$optarg_optional" in
              "" )
                case "$1" in
                  --*=* ) option=`echo "$1" | sed -e "1s/=.*//;q"` ;;
                  * ) option="$1" ;;
                esac
                exec 1>&2
                echo "$progname: option $bq$option$eq requires argument."
                echo "$progname: use $bq--help$eq to list option syntax."
                exit 1
               ;;
           esac
         ;;
        esac
     ;;
    esac
    optarg_optional=
  }'


# Initialize variables.
# Don't use `unset' since old bourne shells don't have this command.
# Instead, assign them an empty value.
debug=
close_stdfds=t
explicit_display=
export_vars=
rshcmd=
xauthinfo=
xhost_enable_host=

# Parse command line arguments.
# Make sure that all wildcarded options are long enough to be unambiguous.
# It's a good idea to document the full long option name in each case.
# Long options which take arguments will need a `*' appended to the
# canonical name to match the value appended after the `=' character.
while : ; do
  case $# in 0) break ;; esac
  case "$1" in
    -D | --debug | --de* )
      set -x
      debug=-x
      shift
     ;;
    -d | --display* | --di* )
      explicit_display=t
      eval "$getopt"
      DISPLAY=$optarg
     ;;
    -e | --export* | --e* )
      eval "$getopt"
      var=$optarg

      case "$var" in
        *,* ) var=`echo "$var" | sed -e 's/,/ /g'` ;;
      esac

      for v in $var ; do
        case " $export_vars " in
          *" $v "* ) : ;;
          * )        export_vars="$export_vars $v" ;;
        esac
      done
     ;;
    -l | --login-as* | --l* )
      eval "$getopt"
      USER=$optarg
     ;;
    -h | --help | --h )
      echo "$usage" 1>&2
      exit 1
     ;;
    -r | --rsh* | --r* )
      eval "$getopt"
      rshcmd="$optarg"
     ;;
    -X | --xauth | --xa* )
      shift
      xauthinfo="`xauth -q extract - $DISPLAY 2> /dev/null`"
     ;;
    -x | --xhost-enable* | --xh* )
      eval "$getopt"
      xhost_enable_host=$optarg
     ;;
    -- )     # Stop option processing
      shift
      break
     ;;
    -? | --* )
      case "$1" in
        --*=* ) arg=`echo "$1" | sed -e 's/=.*//'` ;;
        * )     arg="$1" ;;
      esac
      exec 1>&2
      echo "$progname: unknown or ambiguous option $bq$arg$eq"
      echo "$progname: Use $bq--help$eq for a list of options."
      exit 1
     ;;
    -??* )
      # Split grouped single options into separate args and try again
      optarg="$1"
      shift
      set fnord `echo "x$optarg" | sed -e 's/^x-//;s/\(.\)/-\1 /g'` ${1+"$@"}
      shift
     ;;
    * )
      break
     ;;
  esac
done

case $# in 0 | 1 )
  echo "$usage" 1>&2
  exit 1
 ;;
esac

USER=${USER-${LOGNAME-`{ id | sed -ne 's/.*uid=[0-9]*(//
                                       s/).*//
                                       p'
                       } \
                       || { (whoami) 2> /dev/null; }`}}
export USER

rhost=$1
shift

{
  hostname=`hostname 2> /dev/null`
  case "$hostname" in
    *.* ) : ;;
    * )
      # Try to use DNS to resolve the hostname.
      if (perl -v > /dev/null) 2> /dev/null; then
        name=`perl -e '($name) = gethostbyname ($ARGV[0]);
                       print "$name\n";' "$hostname"`
        case "$name" in
          *.* ) hostname="$name" ;;
        esac
      else
        # Note that if this is done the result could be incorrect.
        # "domainname" is actually supposed to return the NIS domain rather
        # than the domain of the host, but many system administrators
        # configure systems incorrectly.  If `hostname' doesn't return the
        # fully-qualified domain name, gripe at your sysadmin today!
        domainname=`(dnsdomainname) 2> /dev/null || domainname`
        hostname="$hostname.$domainname"
      fi
     ;;
  esac

  hostname_unqualified=`echo "$hostname" | sed -e 's/\..*//'`
}

# canonicalize DISPLAY if not explicitly declared on the command line
{
  case "$explicit_display" in
    t ) : ;;
    * )
      # If DISPLAY isn't already set, then we really don't know the display.
      # Assume :0.0 on current host.
      case "$DISPLAY" in
        '' )
          DISPLAY=$hostname:0.0
          export DISPLAY
         ;;
        * )
          display_number=`echo "$DISPLAY" | sed -e 's/.*://'`
          display_host=`echo "$DISPLAY" | sed -e 's/:[^:]*//'`

          # This will only happen if there's no display number in the first
          # place.
          case "$display_number" in "$display_host" )
            display_number=0.0 ;;
          esac

          # Try to guess intelligently whether display host should be reset
          # (e.g., if it's a hostname that's not local, then it's probably
          # accurate and we shouldn't clobber it by setting it to the local
          # host)
          case "$display_host" in
            unix | loopback | localhost | '' ) display_host=$hostname ;;
            $hostname_unqualified )            display_host=$hostname ;;
            *.* ) : ;;
            * )
              # Try to use DNS to resolve the FQDN.
              if (perl -v > /dev/null) 2> /dev/null; then
                name=`perl -e '($name) = gethostbyname ($ARGV[0]);
                               print "$name\n";' "$display_host"`
                case "$name" in
                  *.* ) display_host="$name" ;;
                esac
              fi
             ;;
          esac

          DISPLAY=$display_host:$display_number
          export DISPLAY
         ;;
      esac
     ;;
  esac
}

# Enable access via xhost, if necessary.
# The xhost in some later versions of X11 (X11R6) refuse to accept
# connections to any display other than the local display number
# (e.g. ":0.0" or ":1.0", but not "foo:0.0" even if xhost is run on foo).
# So munge the display appropriately.
#
# This calls this script recursively (sans the --xhost-enable option) to
# do the whole path searching and exporting frobs implemented below.
{
  case "$xhost_enable_host" in
    '' ) : ;;
    * )
      # usage: xhost_args="arguments..."; eval "$do_xhost"
      # Then inspect value of $xhost_msg.
      do_xhost='
        {
          xhost_msg=`
            xhost $xhost_args 2>&1 \
             | sed -ne '\''
                          /access control disabled/{s/.*/win/p;q;}
                          /^'\''"$xhost_enable_host"'\''$/{s/.*/win/p;q;}
                          /connection to .* refused by server/{s/.*/refused/p;q;}
                          /must be on local machine/{s/.*/refused/p;q;}
                          /added to access control list/{s/.*/added/p;q;}
                          /removed from access control list/{s/.*/removed/p;q;}
                          $s/.*/unauthorized/p
                       '\''
          `
         }'

      xhost_args=; eval "$do_xhost"
      case "$xhost_msg" in
        unauthorized )
          xhost_args=$xhost_enable_host
          eval "$do_xhost"
         ;;
      esac

      case "$xhost_msg" in
        win | added ) : ;;
        refused )
          display_host=`echo "$DISPLAY" | sed -e 's/:[^:]*//'`
          display_number=`echo "$DISPLAY" | sed -e 's/.*:/:/'`
          "$fullprogname" --display "$display_number" \
                          -- \
                          "$display_host" xhost "$xhost_enable_host"
         ;;
      esac
     ;;
  esac
}

{
  # Construct the export variable string that gets evaluated in the rsh
  # subshell.
  export_var_string=
  case "$export_vars" in
    '' ) export_var_string= ;;
    * )
      for var in $export_vars; do
        # You are not expected to understand this.
        str=$var=\'`eval echo \"\\$$var\"`\'';'
        export_var_string="$export_var_string $str"
      done
      export_var_string="$export_var_string export $export_vars"
     ;;
  esac

  # Standard global places that ought to go in path
  pdirs='
    /opt/lude/bin
    /opt/local/bin
    /usr/local/gnu/bin
    /usr/local/gnubin
    /usr/local/gnu
    /usr/local/bin
    /gnu/bin
    /lusr/gnu/bin
    /lusr/bin/gnu
    /usr/site
    /bin
    /usr/bin
    /usr/ucb
    /usr/local/etc
    /usr/etc
    /etc
    /usr/local/sbin
    /usr/sbin
    /sbin
    /usr/libexec
    /usr/lib
'

  # Places where X binaries might reside
  # These will appear in PATH *after* those listed in $pdirs.
  xdirs='
    /usr/X11R6/bin
    /usr/X11R5/bin
    /usr/X11R4/bin
    /usr/bin/X11R6
    /usr/bin/X11R5
    /usr/bin/X11R4
    /usr/local/X11R6/bin
    /usr/local/X11R5/bin
    /usr/local/X11R4/bin
    /usr/local/bin/X11R6
    /usr/local/bin/X11R5
    /usr/local/bin/X11R4
    /usr/X11/bin
    /usr/bin/X11
    /usr/local/X11/bin
    /usr/local/bin/X11
    /usr/openwin/bin
    /usr/openwin/share/bin
    /usr/dt/bin
    /usr/X386/bin
    /usr/x386/bin
    /usr/XFree86/bin/X11
    /usr/bin
    /usr/local/bin
    /usr/unsupported/bin
    /usr/athena/bin
    /usr/local/x11r5/bin
    /usr/lpp/Xamples/bin
  '

  case "$rshcmd" in
    '' )
      # Use rsh instead of remsh if the former exists
      rsh=remsh
      for d in `echo $PATH | sed -e 's/^:/.:/;s/::/:.:/g;s/:$/:./;s/:/ /g'`
      do
        if test -f "$d/rsh" ; then
          rsh=rsh
          break
        fi
      done
     ;;
    * ) rsh="$rshcmd" ;;
  esac

  remote_display=\${DISPLAY-\"$DISPLAY\"}

  # Be sure to close various file descripters so that lusing rshds wont
  # inherit them.  (6, 7, 8, and 9 show up under various operating
  # systems where rshd hasn't been fixed).
  #
  # The "xauth remove" is theoretically unnecessary below, but due to a bug
  # in the initial X11R5 (through at least fix-03) xauth, entries for
  # MIT-MAGIC-COOKIE-1 get lost if you do merge twice without the remove.
  {
    # This must be in a separate test because the test below runs in a
    # subshell, and changes in that environment won't affect this process.
    case "$rshcmd" in
      ssh-x11 ) close_stdfds= ;;
    esac

    {
      case "$rshcmd" in
        ssh-x11 ) exec ssh -o 'ForwardX11 yes' -l "$USER" "$rhost" sh $debug ;;
        ssh | */ssh ) exec $rsh -x -l "$USER" "$rhost" sh $debug ;;
        * )           exec $rsh    -l "$USER" "$rhost" sh $debug ;;
      esac
    } <<__EOF__
      $export_var_string

      case "$1" in
        /* | ./* ) : ;;
        * )
          for dir in `echo $pdirs $xdirs` ; do
            if test -f "\$dir/$1" ; then
              PATH="\$PATH:\$dir"
              break
            fi
          done
          export PATH
         ;;
      esac

      DISPLAY=$remote_display
      export DISPLAY

      case '$XUSERFILESEARCHPATH' in
        '' ) : ;;
        * )
          XUSERFILESEARCHPATH='$XUSERFILESEARCHPATH'
          export XUSERFILESEARCHPATH
         ;;
      esac

      case '$XAUTHORITY' in
        '' ) : ;;
        * )
          if test -f '$XAUTHORITY'; then
            XAUTHORITY='$XAUTHORITY'
            export XAUTHORITY
          fi
         ;;
      esac

      case '$xauthinfo' in
        t )
          echo '$xauthinfo' |
           {
             xauth -q remove $remote_display
             xauth -q merge -
           }
         ;;
      esac

      set fnord `for arg in ${1+"$@"} ; do
                   echo ":$arg"
                 done \
                 | sed -ne 's/^://
                            s/\\\\/\\\\\\\/g
                            s/'\''/'\''\\\\\\'\'\''/g
                            s/^/'\''/
                            s/$/'\''/
                            H
                            ${x
                              s/\n/ /g
                              p
                             }'`

      shift
      case "$close_stdfds" in
        t ) exec > /dev/null 2>&1 ;;
      esac
      \${1+"\$@"} 9>&- 8>&- 7>&- 6>&- < /dev/null &
__EOF__
    }
}

# xon ends here
