From: Rainer Wilcke 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 (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 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 | (")(") (_____ ) | --------------------------------------------------------------------------------