From: Rainer Wilcke <wilcke@esrf.fr>
To: libes@cme.nist.gov
Subject: Update for expect (part 3 of 3)
Date: Tue, 25 Jul 95 19:16:48 METDST

This is the script and manual page for our automatic remote login "arlog".

--------------------------------------------------------------------------------
Begin of the script
--------------------------------------------------------------------------------

#!/usr/local/bin/expect --

# Version 18/07/95
#
# Author: R. Wilcke, ESRF (wilcke@esrf.fr)
# Update 23/01/95: added "]" to list of possible prompts
# Update 18/07/95: match binary nulls explicitely during login sequence
#
# This script performs a remote login, getting the password from an encrypted
# file.
#
# Usage: arlog hostname [username]
#
# The hostname and optionally the username used to perform the login are given
# on the command line. The password is read from an encrypted file, the script
# asks the user interactively for the decryption key.
#
# The encrypted file (called "name file" from now) contains one line for each
# hostname/username combination. Each line contains three fields, separated by
# blanks and/or tabs, the first field must start in column 1. The fields are:
# - hostname
# - username
# - password
# Comments can be put in the name file after the third field, or on separate
# lines starting with a "#" (hashmark). Blank lines in the file are ignored.
#
# There is a default name file, defined in the script. If the environment
# variable ARLOGFILE is defined, it will be interpreted as a file path to use
# instead of the default name file.
#
# The script tries to obtain an unique match between the requested hostname/
# username and the ones in the name file; if no username was given on the
# command line, anything matches for the username. The matching process is done
# in several steps:
# - first a pattern matching is tried; i.e. if "host1" is requested, "host1"
#   will match, but also "host1a" and "myhost1";
# - if this does not yield an unique match, an exact matching is tried; i.e.
#   if "host1" is requested, "host1a" or "myhost1" will not match;
# - if this still does not yield an unique match, the possible choices are
#   listed on the terminal and the user is asked to select.
#
# Once an unique match has been obtained, a "rlogin" is performed, using the
# hostname, username and password found in the name file. When at the end of
# the login the system prompt is received, control is handed to the user for
# interactive work.
#
# There is a default match defined in the script for the system prompt; if
# this is not appropriate, the prompt can be defined with the environment
# variable EXPECT_PROMPT. See below for details.
#-------------------------------------------------------------------------------

# Test for correct use, exit with error if no argument or more than two.

if {$argc == 0 || $argc > 2} {
   send_user "Usage: [file tail $argv0] hostname \[username\]\n"
   exit
}

# Try to guess how the system prompt looks like - this determines later when
# the remote login process has succeeded and control can be handed to the user.
# The choices are:
# - anything ending in "% "     (percent sign followed by a blank);
# - anything ending in "# "     (hashmark followed by a blank);
# - anything ending in "$ "     (dollar sign followed by a blank);
# - anything ending in ": "     (colon followed by a blank);
# - anything ending in ">"      (angle bracket not followed by a blank).
# - anything ending in "]"      (square bracket not followed by a blank).

set prompt "((%|#|\\$|:) |>|])$"        ;# default prompt

# If the environment variable "EXPECT_PROMPT" exists, it is taken as a regular
# expression which matches the end of your login prompt (but does not other-
# wise occur while logging in).

if [info exists env(EXPECT_PROMPT)] {
   set prompt $env(EXPECT_PROMPT)
}

# The input name file. Get its file path from the environment variable ARLOGFILE
# if this is defined. If not, take default file "$HOME/.arlogdat". Exit with
# error if the name file cannot be read.

if {[catch {set infile $env(ARLOGFILE)}] != 0} {
   set infile "$env(HOME)/.arlogdat"
}
if {[file readable $infile] == 0} {
   send_user "*** error: cannot read name file \"$infile\", exit\n"
   exit
}

# Fill "host" and "user" variables from the input arguments. If only one input
# argument is given, anything is supposed to match for "user".

set host [lindex $argv 0]
if {$argc == 2} {
   set user [lindex $argv 1]
} else {
   set user ".*"
}

# "awk" commands for pattern matching and exact matching. Matched are the fields
# 1 and 2 of each input line, corresponding to "host" and "user". 

set awkcmd "(\$1 ~ /$host/) && (\$2 ~ /$user/) {print \$1,\$2,\$3}"
set awkcmd2 "(\$1 ~ /^$host$/) && (\$2 ~ /^$user$/) {print \$1,\$2,\$3}"

# Prompt user for the decryption key. Terminal echoing is turned off during
# this time, to prevent the key from being visible at the terminal.

set timeout 30
system stty -echo
send_user "Enter key: "
expect_user {
   -re "(.*)\n" {send_user "\n"; set key $expect_out(1,string)}
   timeout {send_user "*** $timeout sec timeout expired, exit\n"; exit}
}
system stty echo

# Decrypt the name file, then select the desired host/user combination with
# "awk". The selection here is with pattern matching, i.e. if "host1" is
# requested, "host1" would be a match, but also "host1a" or "myhost1". If none
# is found, the script exits with an error message.

catch {exec crypt $key <$infile | awk $awkcmd } filelm
set namlst [split $filelm]
set namlen [llength $namlst]

if {$namlen == 0} {
   send_user "*** error: host \"$host\""
   if {$user != ".*"} {
      send_user " user \"$user\""
   }
   send_user " not found, exit\n"
   exit

# If the list resulting from the "awk" matching has exactly 3 elements, the
# matching is unique (the elements are: host, user, password).

} elseif {$namlen == 3} {
   set namind 0

# If the matching was not unique, try whether a unique match will be found by
# requesting an exact match (i.e., in the above example, for "host1" requested,
# only "host1" will match, not "host1a" or "myhost1").

} else {
   catch {exec awk $awkcmd2 << "$filelm"} filelm2
   set namlst2 [split $filelm2]
   set namlen2 [llength $namlst2]

# If the result has exactly three elements, there is an unique match.

   if {$namlen2 == 3} {
      set namind 0
      set namlen $namlen2
      set namlst $namlst2

# If the result has more than three elements AND both host and user were given
# as request, the content of the name file is ambiguous. Exit with error.

   } elseif {($namlen2 > 3) && ($user != ".*")} {
      send_user "*** error: host \"$host\" user \"$user\""
      send_user " is multiply defined in input file, exit\n"
      exit

# If the exact matching did not resolve the ambiguity, list the choices at the
# terminal and ask user which one to select. Loop until the requested one is
# found, or the user quits.

   } else {
      set namind -1
      send_user "ambiguous selection: hostname \"$host\""
      if {$user != ".*"} {
	 send_user " username \"$user\""
      }
      send_user "\n"

      for {set i 0} {$i < $namlen} {incr i 3} {
	 send_user "[lrange $namlst $i [expr $i + 1]]\n"
      }
      while {1} {
	 send_user "enter hostname \[username\], or q to quit: "
	 gets stdin input
	 if {$input == "q"} {
	    exit
	 }
	 set incnt [scan $input "%s%s" host user]

	 for {set i 0} {$i < $namlen} {incr i 3} {
	    if {$host == [lindex $namlst $i]} {
	       if {($incnt == 1) || ($user == [lindex $namlst [expr $i + 1]])} {
		  set namind $i
		  break
	       }
	    }
	 }
	 if {$namind == -1} {
	    send_user "*** error: hostname \"$host\""
	    if {$incnt == 2} {
	       send_user " username \"$user\""
	    } 
	 send_user " not found\n"
	 } else {
            break
	 }
      }
   }
}
   
# The requested and unambiguous host/user combination has been found. Do a
# remote login.

set hostname [lindex $namlst $namind]
set username [lindex $namlst [expr $namind + 1]]
set password [lindex $namlst [expr $namind + 2]]

send_user "rlogin $hostname -l $username\n"
spawn -noecho rlogin $hostname -l $username

# During the login process,
# - send password if asked for it;
# - get TERM environment variable from the user if asked for it;
# - hand over to the user for interactive mode when the system prompt arrives;
# - exit with error message if "eof" received or timeout occurs.
#
# The main problem in this context is the recognition of the system prompt, as
# other character sequences might arrive that resemble a prompt. To minimize
# this, everything ending in a <NL> (i.e., "\n") is already matched before
# testing for a prompt. Furthermore, once something like a prompt is matched,
# we wait for one second to see if more arrives - if so, it was not a prompt
# either. Thus we finally accept a prompt only if it matches the pattern for
# a prompt, does not end in a <NL> and nothing arrives after the prompt for at
# least one second.
#
# Some systems send binary nulls during this process. They need to be 
# explicitely matched and are then thrown away.

remove_nulls 1
set termset 0
interact {
   -o
   -nobuffer -re ".*assword:" {exp_send "$password\r"}
   -re ".*incorrect" {
      send_user " invalid password or account\n"
      exit
   }
   -nobuffer -re ".*TERM = .*\\) " {set termset 1}
   timeout 30 {
      send_user "connection to $host timed out\n"
      exit
   }
   -nobuffer -re "eof|.*nknown host|.*o route" {
      send_user " connection to $host failed\n"
      exit
   }
   -nobuffer -re ".*\n" {}
   null {}
   -re ".*$prompt" {
      set timeout 1
      expect {
	 -notransfer -re "..*" {send_user $interact_out(0,string)}
	 timeout {set timeout 10; return}
      }
   }
}

# Find out what shell is used on the remote computer, and define commands
# to be used accordingly.

log_user 0
exp_send "echo \$SHELL\r"
expect {
   -re ".*/.*csh\r\n" {
      set discmd {setenv DISPLAY $env(DISPLAY)\r}
      set termcmd {setenv TERM $env(TERM)\r}
   }
   -re ".*/.*sh\r\n" {
      set discmd {DISPLAY=$env(DISPLAY); export DISPLAY\r}
      set termcmd {TERM=$env(TERM); export TERM\r}
   }
}

# Set DISPLAY variable on the remote computer to the value on the local one,
# if it is defined.

if [info exists env(DISPLAY)] {
   eval set discmd1 \"$discmd\"
   exp_send "$discmd1"
   expect {
      -re ".*$discmd1\r*\n" {send_user "$discmd1\n"}
   }
}


# Set TERM variable on the remote computer to the value on the local one,
# providing that
# - TERM has not been set during the login process (then termset == 1);
# - the local TERM variable is known on the remote computer.

if {$termset == 0} {
   set tsetcmd "tset - -Q -I $env(TERM)\r"
   exp_send $tsetcmd
   expect {
      -re ".*(tset| -Q| -I)\[^\n]*\r*\n" {exp_continue}
      -re ".*unknown\r*\n" {}
      -re "(\[0-9A-Za-z_+@\.-]*)\r*\n" {
	 set termtype $expect_out(1,string)
	 eval set termcmd1 \"$termcmd\"
	 exp_send "$termcmd1"
	 expect {
	    -re ".*$termcmd1\r*\n" {send_user "$termcmd1\n"}
	 }
      }
   }
}

exp_send "\r"
interact

--------------------------------------------------------------------------------
End of the script
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Begin of the manual page
--------------------------------------------------------------------------------

.TH ARLOG 1 "07 December 1994"
.hy
.ad b
.SH NAME
arlog \- automatic remote login, obtaining the password from an encrypted file
.SH SYNOPSIS
.B arlog
.I host
[
.I user
]
.SH INTRODUCTION
.B arlog
is an
.B expect(1)
script that performs an automatic login 
to any of a number of remote computers listed in an
encrypted (and therefore secret) file. It is mainly intended for users who have
accounts with different passwords (and possibly different usernames) on several
computers: instead of keeping track of a (large) number of hostname / username /
password combinations, all one needs to remember is the encryption key for one
file (called the "name file" further on). This is both more convenient and more
secure than having a list with this information hanging in the office ...
.SH USAGE
To perform a remote login, \fBarlog\fR needs the following pieces of 
information: hostname, username and password. The hostname \fIhost\fR must be
given on the command line, the username \fIuser\fR can also be given on the
command line, and the password is always read from the name file.

When \fBarlog\fR is started, it asks the user interactively for the decryption
key of the name file and then reads the file, using \fBcrypt(1)\fR. If a wrong
decryption key is given, \fBarlog\fR will exit (probably with an error message
indicating that the hostname was not found), but it will not damage the name 
file.

The name file can contain any number of lines, each with three fields 
corresponding to hostname, username and password. See NAME FILE below for more
details.

\fBarlog\fR then tries to make an unambiguous match between the \fIhost\fR (and
\fIuser\fR, as the case may be) values and the information in the name file; if
no \fIuser\fR was given on the command line, anything matches for the username.
The matching process is done in several steps:
.RS
.TP 3
-
first regular pattern matching is tried, where the command line arguments are
the pattern; i.e. if "host1" was given on the command line for \fIhost\fR, then
e.g. "host1a" and "myhost1" will also match, but not "hostcomp1";
.TP
-
if this does not yield an unique match, exact matching is tried; i.e.
if "host1" is requested, "host1a" or "myhost1" will not match;
.TP
-
if this still does not yield an unique match, the possible choices are
listed on the terminal and the user is asked to select.
.RE
.PP
Once an unique match has been obtained, a \fBrlogin\fR is performed, using the
hostname, username and password found in the name file. When at the end of
the login the system prompt is received, control is handed to the user for
interactive work.
.SH NAME FILE
The encrypted name file contains one line for each
hostname/username combination. Each line contains three fields, separated by
blanks and/or tabs. The first field must start in column 1. The fields are:

.nf
- hostname
- username
- password
.fi

The hostname has to be given in the same form as required for a \fBrlogin(1)\fR,
i.e. with or without domain name.

Comments can be put in the name file after the third field, or on separate
lines starting with a "#" (hashmark). Blank lines in the file are ignored.

Example:

.RS 3
.nf
compu.unidomain.edu \h'3' smith \h'4' Let'sGo! 
ordinat1 \h'14' dupont \h'3' 4meije38  # For IFTI project

# Need access to this computer, but should only use it during
# the night.
rechna.unibo.d400.de \h'2' mueller \h'2' WiR4hIeR
.RE
.fi

An encrypted file can be created (and edited) using e.g. the \fBvi(1)\fR editor
with the \fB-x\fR option, or by converting between encrypted and non-encrypted 
versions of a file with \fBcrypt(1)\fR.

The script uses \fB$HOME/.arlogdat\fR as default for the name file, this can be
overridden with the environment variable ARLOGFILE (see ENVIRONMENT below).
.SH CAVEATS
The remote login is performed with a call to \fBrlogin(1)\fR, thus \fBarlog\fR
will only succeed to those computers where the user can do a \fBrlogin(1)\fB.

.B arlog
hands control to the user for active work once the system prompt is received.
The script sets

.nf
   .*((%|#|\\$|:) |>)$
.fi

as default pattern for a (\fBtcl\fR style) regular match with the system prompt,
i.e.:
.nf

- anything ending in "% "     (percent sign followed by a blank);
- anything ending in "# "     (hashmark followed by a blank); 
- anything ending in "$ "     (dollar sign followed by a blank);
- anything ending in ": "     (colon followed by a blank);
- anything ending in ">"      (angle bracket not followed by a blank).
.fi

If your system prompt is different, it will not be recognized. This manifests
itself in a rather subtle way, as it will seem that you are able to work anyway;
however, the environment variables TERM and DISPLAY are not set as described in
ENVIRONMENT below, and the connection will time out after being inactive for 30
seconds. To resolve the problem, use the environment variable EXPECT_PROMPT 
(see ENVIRONMENT below).

\fBarlog\fR will exit with an appropriate error message
.RS
.TP 3
-
if the environment variable ARLOGFILE (see ENVIRONMENT below) exists but does 
not point to a readable file; or
.TP
-
if ARLOGFILE does not exist and the default name file is not readable.
.RE
.PP
\fBarlog\fR sets the TERM environment variable if possible (see ENVIRONMENT
below), but it is up to the setup of the login process (as determined by
e.g. the files \fI.profile\fR or \fI.login\fR\) to do the correct initialization
for the terminal (e.g. determine the size of an \fIxterm\fR window). 

.B arlog
requires
.BR expect (1).
.SH ENVIRONMENT
If the environment variable ARLOGFILE exists, it is taken as the file name of
the name file, otherwise the default file name \fB$HOME/.arlogdat\fR is used. 
See also CAVEATS above.

If the environment variable EXPECT_PROMPT exists, it is taken as a regular
expression which matches the end of your login prompt (but does not otherwise
occur while logging in). See also CAVEATS above.

If a value for the environment variable TERM is obtained from the user during
the login process, it will be set.

If TERM is not obtained from the user, and/or if the environment variable
DISPLAY is defined in the environment where \fBarlog\fR is started, then TERM
and/or DISPLAY will be set in the remote computer to the same values as they 
have locally.
\fBarlog\fR does not change the shell on the remote computer, but will
use the syntax appropriate for the remote shell to set TERM and/or DISPLAY. If
TERM and/or DISPLAY are not defined locally, the remote defaults (if any) will
be kept. Equally, if the local TERM type is not known on the remote system
(e.g. a terminal type "hpterm" for a SUN), the remote default will be kept.
.SH SEE ALSO
.BR crypt (1),
.BR expect (1),
.BR rlogin (1),
.BR vi (1),
.BR Tcl (3)
.SH AUTHOR
R. Wilcke, ESRF (wilcke@esrf.fr)

\fBexpect(1)\fR was developed by Don Libes, National Institut of Standards
and Technology, USA.

--------------------------------------------------------------------------------
End of the manual page
--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
                                            | 
Rainer Wilcke                               |      /\  _  /\       ----- 
Experiments Division Programming Group      |      ^       ^      / / \ \ 
ESRF                                        |      (|)   (|)     / /   \ \ 
BP 220                                      |                   / /     \ \ 
F-38043 Grenoble Cedex                      |        = 0 =      \/       | |    
FRANCE                                      |          ~          ___   / /
                                            |        /     ---  /     \/ /
tel: (+33) 76 88 20 61                      |                  /  
fax: (+33) 76 88 21 60                      |       (      )   |       / 
e-mail: wilcke@esrf.fr                      |        \    /     \     /
                                            |          ||     ____  /
European Synchrotron Radiation Facility     |        (")(")  (_____ )
                                            | 
--------------------------------------------------------------------------------