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 console on a given blade in an IBM BladeCenter
12# via Serial Over LAN (SOL) using the telnet protocol.
13#
14# This script can be specified in "conman.conf" in the following manner:
15#
16#   console name="zot" dev="/path/to/ibm-bc.exp HOST BLADE USER PSWD"
17#
18# HOST  is the hostname of the blade server.
19# BLADE is the blade number associated with the console.
20# USER  is the username being authenticated.
21# PSWD  is the corresponding password.
22#
23# Since this command-line will persist in the process listing for the duration
24# of the connection, passing sensitive information like PSWD in this manner is
25# not recommended.  Instead, consider using either a command-line argument
26# default or the password database (see below).
27###############################################################################
28
29##
30# Set "exp_internal" to 1 to print diagnostics describing internal operations.
31#   This is helpful in diagnosing pattern-match failures.
32##
33  exp_internal 0
34
35##
36# Set "log_user" to 1 to show the underlying dialogue establishing a connection
37#   to the console.
38##
39  log_user 0
40
41##
42# The "timeout" specifies the number of seconds before the connection attempt
43#   times-out and terminates the connection.
44##
45  set timeout 10
46
47##
48# If "bcmm_timeout" is set greater than or equal to 0, the telnet inactivity
49#   timeout of the BladeCenter management module will be set to this value.
50# This setting specifies the number of seconds of inactivity before the
51#   interface session times-out; setting it to 0 disables the timeout.
52##
53  set bcmm_timeout 0
54
55##
56# If "idle_timeout" is set greater than 0, a "space-backspace" sequence will
57#   be sent every ${idle_timeout} seconds after no output activity has been
58#   detected in order to keep the connection alive.
59# This setting should ideally be the same value as "bcmm_timeout".
60##
61  set idle_timeout 0
62
63##
64# If "session_override" is enabled, an existing connection to this console
65#   session will be terminated and the new connection will be established.
66#   Subsequent attempts to steal this console session will be thwarted.
67# Otherwise, an existing connection to this console session will cause the new
68#   connection to fail with "console session already in use".  If the console
69#   session is not in use and the connection succeeds, the console session may
70#   subsequently be stolen thereby causing this connection to terminate with
71#   "console session stolen".
72# Beware of dueling banjos if a given console session is overridden by more
73#   than one process, as these processes will continuously steal the console
74#   session from each other.
75##
76  set session_override 1
77
78##
79# The "password_db" specifies the location of the password database.
80#   This avoids exposing sensitive information on the command-line without
81#   needing to modify this script.
82# Whitespace and lines beginning with '#' are ignored.  The file format is:
83#   <host-regex> : <user> : <pswd>
84##
85  set password_db "/etc/conman.pswd"
86
87##
88# Command-line argument defaults can be specified here.  This avoids exposing
89#   sensitive information on the command-line.
90##
91# set user "USERID"
92# set pswd "PASSW0RD"
93
94###############################################################################
95
96set env(PATH) "/usr/bin:/bin"
97
98proc get_password {host user index} {
99  global password_db
100  set db_pswd {}
101  if {! [info exists password_db]} {
102    return
103  }
104  if {[catch {open $password_db} input]} {
105    return
106  }
107  while {[gets $input line] != -1} {
108    if {[regexp {^[ \t]*#} $line]} {
109      continue
110    }
111    set record [split $line ":"]
112    set db_host [string trim [lindex $record 0]]
113    if {[catch {regexp "^$db_host$" $host} got_host_match]} {
114      continue
115    }
116    if {! $got_host_match && [string length $db_host]} {
117      continue
118    }
119    set db_user [string trim [lindex $record 1]]
120    if {[string compare $db_user $user]} {
121      continue
122    }
123    set db_pswd [string trim [lindex $record $index]]
124    break
125  }
126  close $input
127  return $db_pswd
128}
129
130if {! $argc} {
131  set prog [lindex [split $argv0 "/"] end]
132  send_user "Usage: $prog <host> <blade> <user> <pswd>\r\n"
133  exit 1
134}
135if {$argc > 0} {
136  set host [lindex $argv 0]
137}
138if {$argc > 1} {
139  set blade [lindex $argv 1]
140}
141if {$argc > 2} {
142  set user [lindex $argv 2]
143}
144if {$argc > 3} {
145  set pswd [lindex $argv 3]
146}
147set bcmm_bay 1
148set cmd_sent 0
149set connected 0
150if {! [info exists host]} {
151  send_user "Error: Unspecified hostname.\r\n"
152  exit 1
153}
154if {! [info exists blade]} {
155  send_user "Error: Unspecified blade number.\r\n"
156  exit 1
157}
158if {! [info exists user]} {
159  send_user "Error: Unspecified username.\r\n"
160  exit 1
161}
162if {! [info exists pswd]} {
163  set pswd [get_password $host $user 2]
164  if {! [string length $pswd]} {
165    send_user "Error: Unspecified password.\r\n"
166    exit 1
167  }
168}
169if {! [info exists bcmm_timeout] || ($bcmm_timeout < 0)} {
170  set bcmm_timeout -1
171}
172if {! [info exists idle_timeout] || ($idle_timeout <= 0)} {
173  set idle_timeout -1
174}
175set cmd "console -T system:blade\[$blade]"
176if {[info exists session_override] && ($session_override > 0)} {
177  append cmd " -o"
178}
179if {[catch "spawn telnet $host" spawn_result]} {
180  send_user "Error: $spawn_result.\r\n"
181  exit 1
182}
183expect {
184  -gl "\u001b\\\[2J" {
185    if {$connected == 0} {
186      exp_continue -continue_timer
187    }
188  }
189  -gl "Invalid login" {
190    send_user "Error: Permission denied.\r\n"
191    exit 1
192  }
193  -gl "Command not recognized" {
194    send_user "Error: Command not recognized.\r\n"
195    exit 1
196  }
197  -gl "Invalid target path" {
198    send_user "Error: Invalid blade name.\r\n"
199    exit 1
200  }
201  -gl "The target bay is out of range" {
202    send_user "Error: Invalid blade number.\r\n"
203    exit 1
204  }
205  -gl "SOL session is already active" {
206    send_user "Error: Console session already in use.\r\n"
207    exit 1
208  }
209  eof {
210    send_user "Error: Connection closed by remote host.\r\n"
211    exit 1
212  }
213  timeout {
214    if {$connected == 0} {
215      send_user "Error: Timed-out.\r\n"
216      exit 1
217    }
218  }
219  -nocase -gl "username: \$" {
220    if {$connected == 0} {
221      send "$user\r"
222    }
223    exp_continue -continue_timer
224  }
225  -nocase -gl "password: \$" {
226    if {$connected == 0} {
227      send "$pswd\r"
228    }
229    exp_continue -continue_timer
230  }
231  -nocase -gl "OK\r\n" {
232    if {$cmd_sent != 0} {
233      incr bcmm_bay
234      set cmd_sent 0
235    }
236    exp_continue -continue_timer
237  }
238  -nocase -gl "The target bay is empty.\r\n" {
239    if {$bcmm_bay == 0} {
240      send_user "Error: No blade in specified slot.\r\n"
241      exit 1
242    } elseif {$cmd_sent != 0} {
243      set bcmm_bay 0
244      set bcmm_timeout -1
245      set cmd_sent 0
246    }
247    exp_continue -continue_timer
248  }
249  -nocase -gl "Command cannot be issued to this target." {
250    if {$cmd_sent != 0} {
251      set bcmm_timeout -1
252      set cmd_sent 0
253    }
254    exp_continue -continue_timer
255  }
256  -nocase -gl "User does not have the authority to issue this command.\r\n" {
257    if {$cmd_sent != 0} {
258      set bcmm_timeout -1
259      set cmd_sent 0
260    }
261    exp_continue -continue_timer
262  }
263  -nocase -gl "system> \$" {
264    if {($bcmm_timeout >= 0) && ($cmd_sent == 0)} {
265      send "telnetcfg -T system:mm\[$bcmm_bay] -t $bcmm_timeout\r"
266      incr cmd_sent
267    } elseif {($connected == 0) && ($cmd_sent == 0)} {
268      send "$cmd\r"
269      incr connected
270    } else {
271      send_user "Error: Unrecognized response.\r\n"
272      exit 1
273    }
274    exp_continue -continue_timer
275  }
276  -re "\[^\r]*\r+\n" {
277    exp_continue -continue_timer
278  }
279}
280send_user "Connection established via telnet (pid $spawn_result).\r\n"
281
282set timeout 2
283interact {
284  # Replace "&B" with serial-break.
285  "&B" {
286    send "\035send brk\r\n"
287    expect "telnet> send brk"
288  }
289  # Match subsequent patterns against spawned process, not user's keystrokes.
290  -o
291  # Send "space-backspace" sequence after ${idle_timeout} seconds of inactivity
292  #   in order to keep the connection alive.
293  timeout $idle_timeout {
294    send " \177"
295  }
296  # Disable "ESC (" sequence for stopping the console session and returning to
297  #   the BladeCenter management module prompt.  If "session_override" is set,
298  #   this will also prevent the console session from being stolen.
299  -re "\r\nsystem> \$" {
300    send "$cmd\r"
301    expect {
302      -gl "\r\nSOL session is already active\r\nsystem> \$" {
303        send_user "\r\nConsole session stolen.\r\n"
304        exit 1
305      }
306      -gl "\u001b\\\[2J"
307    }
308  }
309}
310