1#!/usr/local/bin/expect -f
2###############################################################################
3# Written by Chris Dunlap <cdunlap@llnl.gov>.
4# Copyright (C) 2007-2018 Lawrence Livermore National Security, LLC.
5# Copyright (C) 2001-2007 The Regents of the University of California.
6# UCRL-CODE-2002-009.
7#
8# This file is part of ConMan: The Console Manager.
9# For details, see <https://dun.github.io/conman/>.
10###############################################################################
11# This script connects to a host using the SSH protocol.
12#
13# This script can be specified in "conman.conf" in the following manner:
14#
15#   console name="zot" dev="/path/to/ssh.exp HOST PORT USER PSWD"
16#
17# HOST is the hostname of the remote server.
18# PORT is the port number (typically 22).
19# USER is the username being authenticated.
20# PSWD is the corresponding password.
21#
22# Since this command-line will persist in the process listing for the duration
23# of the connection, passing sensitive information like PSWD in this manner is
24# not recommended.  Instead, consider using either a command-line argument
25# default or the password database (see below).
26###############################################################################
27
28##
29# Set "exp_internal" to 1 to print diagnostics describing internal operations.
30#   This is helpful in diagnosing pattern-match failures.
31##
32  exp_internal 0
33
34##
35# Set "log_user" to 1 to show the underlying dialogue establishing a connection
36#   to the console.
37##
38  log_user 0
39
40##
41# The "timeout" specifies the number of seconds before the connection attempt
42#   times-out and terminates the connection.
43##
44  set timeout 10
45
46##
47# The "password_db" specifies the location of the password database.
48#   This avoids exposing sensitive information on the command-line without
49#   needing to modify this script.
50# Whitespace and lines beginning with '#' are ignored.  The file format is:
51#   <host-regex> : <user> : <pswd>
52##
53  set password_db "/etc/conman.pswd"
54
55##
56# Command-line argument defaults can be specified here.  This avoids exposing
57#   sensitive information on the command-line.
58##
59# set port "22"
60# set user "foo"
61# set pswd "bar"
62
63###############################################################################
64
65set env(PATH) "/usr/bin:/bin"
66
67proc get_password {host user index} {
68  global password_db
69  set db_pswd {}
70  if {! [info exists password_db]} {
71    return
72  }
73  if {[catch {open $password_db} input]} {
74    return
75  }
76  while {[gets $input line] != -1} {
77    if {[regexp {^[ \t]*#} $line]} {
78      continue
79    }
80    set record [split $line ":"]
81    set db_host [string trim [lindex $record 0]]
82    if {[catch {regexp "^$db_host$" $host} got_host_match]} {
83      continue
84    }
85    if {! $got_host_match && [string length $db_host]} {
86      continue
87    }
88    set db_user [string trim [lindex $record 1]]
89    if {[string compare $db_user $user]} {
90      continue
91    }
92    set db_pswd [string trim [lindex $record $index]]
93    break
94  }
95  close $input
96  return $db_pswd
97}
98
99if {! $argc} {
100  set prog [lindex [split $argv0 "/"] end]
101  send_user "Usage: $prog <host> <port> <user> <pswd>\r\n"
102  exit 1
103}
104if {$argc > 0} {
105  set host [lindex $argv 0]
106}
107if {$argc > 1} {
108  set port [lindex $argv 1]
109}
110if {$argc > 2} {
111  set user [lindex $argv 2]
112}
113if {$argc > 3} {
114  set pswd [lindex $argv 3]
115}
116set authenticated 0
117if {! [info exists host]} {
118  send_user "Error: Unspecified hostname.\r\n"
119  exit 1
120}
121if {! [info exists port]} {
122  send_user "Error: Unspecified port number.\r\n"
123  exit 1
124}
125if {! [info exists user]} {
126  send_user "Error: Unspecified username.\r\n"
127  exit 1
128}
129if {! [info exists pswd]} {
130  set pswd [get_password $host $user 2]
131  if {! [string length $pswd]} {
132    send_user "Error: Unspecified password.\r\n"
133    exit 1
134  }
135}
136if {[catch "spawn ssh -a -e \& -l $user -p $port -x $host" spawn_result]} {
137  send_user "Error: $spawn_result.\r\n"
138  exit 1
139}
140expect {
141  -gl "Permission denied" {
142    send_user "Error: Permission denied.\r\n"
143    exit 1
144  }
145  -re "^ssh: (\[^\r]*)\r+\n" {
146    send_user "Error: $expect_out(1,string).\r\n"
147    exit 1
148  }
149  eof {
150    send_user "Error: Connection closed by remote host.\r\n"
151    exit 1
152  }
153  timeout {
154    if {$authenticated == 0} {
155      send_user "Error: Timed-out.\r\n"
156      exit 1
157    }
158  }
159  -nocase -gl "Are you sure you want to continue connecting (yes/no)? \$" {
160    send "yes\r"
161    exp_continue -continue_timer
162  }
163  -re "Enter passphrase for key .*: \$" {
164    if {$authenticated == 0} {
165      send "\r"
166    }
167    exp_continue -continue_timer
168  }
169  -re "^$user@.* password: \$" {
170    if {$authenticated == 0} {
171      send "$pswd\r"
172      incr authenticated
173      exp_continue -continue_timer
174    } else {
175      send_user "Error: Permission denied.\r\n"
176      exit 1
177    }
178  }
179  -re "(:|#|%|\\\$) \$" {
180    ;
181  }
182  -re "\[^\r]*\r+\n" {
183    exp_continue -continue_timer
184  }
185}
186send_user "Connection established via ssh (pid $spawn_result).\r\n"
187
188interact
189# Since the default ssh escape character was changed from "~" to "&",
190#   "&B" will generate a serial-break.
191