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 managed by HP's "Lights Out 100" option
12# using the telnet protocol.  The LO100 is a traditional IPMI BMC and does
13# not share hardware or firmware with the HP Integrated Lights-Out (iLO)
14# management processor.
15#
16# This script can be specified in "conman.conf" in the following manner:
17#
18#   console name="zot" dev="/path/to/hp-lo100.exp HOST PORT USER PSWD"
19#
20# HOST is the hostname of the remote server.
21# PORT is the port number (typically 23).
22# USER is the username being authenticated.
23# PSWD is the corresponding password.
24#
25# Since this command-line will persist in the process listing for the duration
26# of the connection, passing sensitive information like PSWD in this manner is
27# not recommended.  Instead, consider using either a command-line argument
28# default or the password database (see below).
29###############################################################################
30
31##
32# Set "exp_internal" to 1 to print diagnostics describing internal operations.
33#   This is helpful in diagnosing pattern-match failures.
34##
35  exp_internal 0
36
37##
38# Set "log_user" to 1 to show the underlying dialogue establishing a connection
39#   to the console.
40##
41  log_user 0
42
43##
44# The "timeout" specifies the number of seconds before the connection attempt
45#   times-out and terminates the connection.
46##
47  set timeout 10
48
49##
50# The "password_db" specifies the location of the password database.
51#   This avoids exposing sensitive information on the command-line without
52#   needing to modify this script.
53# Whitespace and lines beginning with '#' are ignored.  The file format is:
54#   <host-regex> : <user> : <pswd>
55##
56  set password_db "/etc/conman.pswd"
57
58##
59# Command-line argument defaults can be specified here.  This avoids exposing
60#   sensitive information on the command-line.
61##
62# set port "23"
63# set user "foo"
64# set pswd "bar"
65
66###############################################################################
67
68set env(PATH) "/usr/bin:/bin"
69
70proc get_password {host user index} {
71  global password_db
72  set db_pswd {}
73  if {! [info exists password_db]} {
74    return
75  }
76  if {[catch {open $password_db} input]} {
77    return
78  }
79  while {[gets $input line] != -1} {
80    if {[regexp {^[ \t]*#} $line]} {
81      continue
82    }
83    set record [split $line ":"]
84    set db_host [string trim [lindex $record 0]]
85    if {[catch {regexp "^$db_host$" $host} got_host_match]} {
86      continue
87    }
88    if {! $got_host_match && [string length $db_host]} {
89      continue
90    }
91    set db_user [string trim [lindex $record 1]]
92    if {[string compare $db_user $user]} {
93      continue
94    }
95    set db_pswd [string trim [lindex $record $index]]
96    break
97  }
98  close $input
99  return $db_pswd
100}
101
102if {! $argc} {
103  set prog [lindex [split $argv0 "/"] end]
104  send_user "Usage: $prog <host> <port> <user> <pswd>\r\n"
105  exit 1
106}
107if {$argc > 0} {
108  set host [lindex $argv 0]
109}
110if {$argc > 1} {
111  set port [lindex $argv 1]
112}
113if {$argc > 2} {
114  set user [lindex $argv 2]
115}
116if {$argc > 3} {
117  set pswd [lindex $argv 3]
118}
119set state 0
120#
121# Valid States:
122#   0: initial
123#   1: connected to $host:$port
124#   2: sent $user
125#   3: sent $pswd
126#   4: sent ESC+Q
127#   5: connected to lo100
128#
129if {! [info exists host]} {
130  send_user "Error: Unspecified hostname.\r\n"
131  exit 1
132}
133if {! [info exists port]} {
134  send_user "Error: Unspecified port number.\r\n"
135  exit 1
136}
137if {! [info exists user]} {
138  send_user "Error: Unspecified username.\r\n"
139  exit 1
140}
141if {! [info exists pswd]} {
142  set pswd [get_password $host $user 2]
143  if {! [string length $pswd]} {
144    send_user "Error: Unspecified password.\r\n"
145    exit 1
146  }
147}
148if {[catch "spawn telnet $host $port" spawn_result]} {
149  send_user "Error: $spawn_result.\r\n"
150  exit 1
151}
152expect {
153  -re "^Connected to " {
154    if {$state == 0} {
155      incr state
156    }
157    exp_continue -continue_timer
158  }
159  -re "^telnet: (\[^\r]*)\r+\n" {
160    send_user "Error: $expect_out(1,string).\r\n"
161    exit 1
162  }
163  -gl "Login incorrect" {
164    send_user "Error: Permission denied.\r\n"
165    exit 1
166  }
167  -gl "CLI session stopped" {
168    send_user "Error: CLI session stopped.\r\n"
169    exit 1
170  }
171  -gl "Requested service is unavailable, it is already in use" {
172    send_user "Error: Console session already in use.\r\n"
173    exit 1
174  }
175  eof {
176    if {$state == 0} {
177      send_user "Error: Unable to connect to $host:$port.\r\n"
178      exit 1
179    }
180    send_user "Error: Connection closed by remote host.\r\n"
181    exit 1
182  }
183  timeout {
184    send_user "Error: Timed-out.\r\n"
185    exit 1
186  }
187  -nocase -re "login: +\$" {
188    if {$state != 1} {
189      send_user "Error: Permission denied.\r\n"
190      exit 1
191    }
192    send "$user\r"
193    incr state
194    exp_continue -continue_timer
195  }
196  -nocase -re "Password: +\$" {
197    if {$state != 2} {
198      send_user "Error: Permission denied.\r\n"
199      exit 1
200    }
201    send "$pswd\r"
202    incr state
203    exp_continue -continue_timer
204  }
205  -gl "/./-> \$" {
206    if {$state == 3} {
207      send "\033Q\r"
208      incr state
209    }
210    exp_continue -continue_timer
211  }
212  -re "\[^\r]*\r+\n" {
213    if {$state != 4} {
214      exp_continue -continue_timer
215    }
216    incr state
217    ; # success
218  }
219}
220send_user "Connection established via telnet (pid $spawn_result).\r\n"
221
222set timeout 5
223interact {
224  # Replace "&B" with serial-break.
225  "&B" {
226    send "\033\002"
227  }
228  # Match subsequent patterns against spawned process, not user's keystrokes.
229  -o
230  # Disable "ESC (" sequence for stopping console and returning to CLI prompt.
231  -re "/./-> \$" {
232    send "\033Q\r"
233  }
234}
235