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 using the IPMI Serial-Over-LAN protocol.
12# IPMItool is available from <http://ipmitool.sourceforge.net/>.
13#
14# This script can be specified in "conman.conf" in the following manner:
15#
16#   console name="zot" dev="/path/to/ipmitool.exp HOST USER PSWD [KEYG]"
17#
18# HOST is the hostname of the remote server.
19# USER is the username being authenticated.
20# PSWD is the corresponding password.
21# KEYG is the optional key-generation key "Kg" for IPMIv2 authentication.
22#
23# Since this command-line will persist in the process listing for the duration
24# of the connection, passing sensitive information like PSWD or KEYG in this
25# manner is not recommended.  Instead, consider using either a command-line
26# argument default or the password database (see below).
27#
28# WARNING:
29# As of version 1.8.9, ipmitool does not support prompting for the "Kg" key.
30# Consequently, this key will be exposed on the ipmitool command-line.
31###############################################################################
32
33##
34# Set "exp_internal" to 1 to print diagnostics describing internal operations.
35#   This is helpful in diagnosing pattern-match failures.
36##
37  exp_internal 0
38
39##
40# Set "log_user" to 1 to show the underlying dialogue establishing a connection
41#   to the console.
42##
43  log_user 0
44
45##
46# The "timeout" specifies the number of seconds before the connection attempt
47#   times-out and terminates the connection.
48##
49  set timeout 5
50
51##
52# The "password_db" specifies the location of the password database.
53#   This avoids exposing sensitive information on the command-line without
54#   needing to modify this script.
55# Whitespace and lines beginning with '#' are ignored.  The file format is:
56#   <host-regex> : <user> : <pswd> [ : <keyg> ]
57##
58  set password_db "/etc/conman.pswd"
59
60##
61# Command-line argument defaults can be specified here.  This avoids exposing
62#   sensitive information on the command-line.
63##
64# set user "foo"
65# set pswd "bar"
66# set keyg "qux"
67
68###############################################################################
69
70set env(PATH) "/usr/bin:/bin"
71
72proc get_password {host user index} {
73  global password_db
74  set db_pswd {}
75  if {! [info exists password_db]} {
76    return
77  }
78  if {[catch {open $password_db} input]} {
79    return
80  }
81  while {[gets $input line] != -1} {
82    if {[regexp {^[ \t]*#} $line]} {
83      continue
84    }
85    set record [split $line ":"]
86    set db_host [string trim [lindex $record 0]]
87    if {[catch {regexp "^$db_host$" $host} got_host_match]} {
88      continue
89    }
90    if {! $got_host_match && [string length $db_host]} {
91      continue
92    }
93    set db_user [string trim [lindex $record 1]]
94    if {[string compare $db_user $user]} {
95      continue
96    }
97    set db_pswd [string trim [lindex $record $index]]
98    break
99  }
100  close $input
101  return $db_pswd
102}
103
104if {! $argc} {
105  set prog [lindex [split $argv0 "/"] end]
106  send_user "Usage: $prog <host> <user> <pswd> \[<keyg>]\r\n"
107  exit 1
108}
109if {$argc > 0} {
110  set host [lindex $argv 0]
111}
112if {$argc > 1} {
113  set user [lindex $argv 1]
114}
115if {$argc > 2} {
116  set pswd [lindex $argv 2]
117}
118if {$argc > 3} {
119  set keyg [lindex $argv 3]
120}
121set authenticated 0
122if {! [info exists host]} {
123  send_user "Error: Unspecified hostname.\r\n"
124  exit 1
125}
126if {! [info exists user]} {
127  send_user "Error: Unspecified username.\r\n"
128  exit 1
129}
130if {! [info exists pswd]} {
131  set pswd [get_password $host $user 2]
132  if {! [string length $pswd]} {
133    send_user "Error: Unspecified password.\r\n"
134    exit 1
135  }
136  if {! [info exists keyg]} {
137    set keyg [get_password $host $user 3]
138    if {! [string length $keyg]} {
139      unset keyg
140    }
141  }
142}
143set cmd "ipmitool -e \& -I lanplus -H $host -U $user -a"
144if {[info exists keyg]} {
145  append cmd " -k $keyg"
146}
147if {[catch "spawn $cmd sol deactivate" spawn_result]} {
148  send_user "Error: $spawn_result.\r\n"
149  exit 1
150}
151expect {
152  -nocase -gl "Password:" {
153    if {$authenticated == 0} {
154      send "$pswd\r"
155      incr authenticated
156    }
157    exp_continue -continue_timer
158  }
159  eof {
160    ;
161  }
162  timeout {
163    send_user "Error: Timed-out.\r\n"
164    exit 1
165  }
166}
167wait
168
169if {[catch "spawn $cmd sol activate" spawn_result]} {
170  send_user "Error: $spawn_result.\r\n"
171  exit 1
172}
173set authenticated 0
174expect {
175  -nocase -re "\\\[SOL Session operational" {
176    ;
177  }
178  -nocase -re "^Error: (\[^\r]*)\r+\n" {
179    send_user "Error: $expect_out(1,string)\r\n"
180    exit 1
181  }
182  -nocase -gl "SOL payload already active on another session" {
183    send_user "Error: Console session already in use.\r\n"
184    exit 1
185  }
186  eof {
187    send_user "Error: Connection closed by remote host.\r\n"
188    exit 1
189  }
190  timeout {
191    send_user "Error: Timed-out.\r\n"
192    exit 1
193  }
194  -nocase -gl "Password:" {
195    if {$authenticated == 0} {
196      send "$pswd\r"
197      incr authenticated
198    }
199    exp_continue -continue_timer
200  }
201  -re "\[^\r]*\r+\n" {
202    exp_continue -continue_timer
203  }
204}
205send_user "Connection established via ipmitool (pid $spawn_result).\r\n"
206
207interact
208