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 port on a Cyclades terminal server using the
12# telnet protocol (protocol socket_server or socket_server_ssh).  It supports
13# consoles using no authentication (authtype none) and local authentication
14# (authtype local).  However, if console authentication is not needed, better
15# scalability will be achieved using the native telnet support within ConMan
16# (console name="zot" dev="HOST:PORT").
17#
18# This script can be specified in "conman.conf" in the following manner:
19#
20#   console name="zot" dev="/path/to/cyclades-telnet.exp HOST PORT [USER PSWD]"
21#
22# HOST is the hostname of the terminal server.
23# PORT is the port number associated with the console.
24# USER is the username being authenticated.
25# PSWD is the corresponding password.
26#
27# Both USER and PSWD must be specified if the console requires authentication.
28#
29# Since this command-line will persist in the process listing for the duration
30# of the connection, passing sensitive information like PSWD in this manner is
31# not recommended.  Instead, consider using either a command-line argument
32# default or the password database (see below).
33###############################################################################
34
35##
36# Set "exp_internal" to 1 to print diagnostics describing internal operations.
37#   This is helpful in diagnosing pattern-match failures.
38##
39  exp_internal 0
40
41##
42# Set "log_user" to 1 to show the underlying dialogue establishing a connection
43#   to the console.
44##
45  log_user 0
46
47##
48# The "timeout" specifies the number of seconds before the connection attempt
49#   times-out and terminates the connection.
50##
51  set timeout 10
52
53##
54# The "password_db" specifies the location of the password database.
55#   This avoids exposing sensitive information on the command-line without
56#   needing to modify this script.
57# Whitespace and lines beginning with '#' are ignored.  The file format is:
58#   <host-regex> : <user> : <pswd>
59##
60  set password_db "/etc/conman.pswd"
61
62##
63# Command-line argument defaults can be specified here.  This avoids exposing
64#   sensitive information on the command-line.
65##
66# set user "root"
67# set pswd "tslinux"
68
69###############################################################################
70
71set env(PATH) "/usr/bin:/bin"
72
73proc get_password {host user index} {
74  global password_db
75  set db_pswd {}
76  if {! [info exists password_db]} {
77    return
78  }
79  if {[catch {open $password_db} input]} {
80    return
81  }
82  while {[gets $input line] != -1} {
83    if {[regexp {^[ \t]*#} $line]} {
84      continue
85    }
86    set record [split $line ":"]
87    set db_host [string trim [lindex $record 0]]
88    if {[catch {regexp "^$db_host$" $host} got_host_match]} {
89      continue
90    }
91    if {! $got_host_match && [string length $db_host]} {
92      continue
93    }
94    set db_user [string trim [lindex $record 1]]
95    if {[string compare $db_user $user]} {
96      continue
97    }
98    set db_pswd [string trim [lindex $record $index]]
99    break
100  }
101  close $input
102  return $db_pswd
103}
104
105if {! $argc} {
106  set prog [lindex [split $argv0 "/"] end]
107  send_user "Usage: $prog <host> <port> \[<user> <pswd>]\r\n"
108  exit 1
109}
110if {$argc > 0} {
111  set host [lindex $argv 0]
112}
113if {$argc > 1} {
114  set port [lindex $argv 1]
115}
116if {$argc > 2} {
117  set user [lindex $argv 2]
118}
119if {$argc > 3} {
120  set pswd [lindex $argv 3]
121}
122set authenticated 0
123if {! [info exists host]} {
124  send_user "Error: Unspecified hostname.\r\n"
125  exit 1
126}
127if {! [info exists port]} {
128  send_user "Error: Unspecified port number.\r\n"
129  exit 1
130}
131if {! [info exists user]} {
132  set authenticated 1
133  set user {}
134} elseif {! [info exists pswd]} {
135  set pswd [get_password $host $user 2]
136  if {! [string length $pswd]} {
137    send_user "Error: Unspecified password.\r\n"
138    exit 1
139  }
140}
141if {[catch "spawn telnet $host $port" spawn_result]} {
142  send_user "Error: $spawn_result.\r\n"
143  exit 1
144}
145expect {
146  -gl "Authentication failure" {
147    send_user "Error: Permission denied.\r\n"
148    exit 1
149  }
150  -gl "Connection refused" {
151    send_user "Error: Connection refused.\r\n"
152    exit 1
153  }
154  -re "(ttyS\[0-9]*) is being used by \\((.*)\\)" {
155    send_user "Error: Console session for $expect_out(1,string) in use by $expect_out(2,string).\r\n"
156    exit 1
157  }
158  -gl "is being used by" {
159    send_user "Error: Console session already in use.\r\n"
160    exit 1
161  }
162  -gl "closed by remote host" {
163    send_user "Error: Connection closed by remote host.\r\n"
164    exit 1
165  }
166  -re "^telnet: (\[^\r]*)\r+\n" {
167    send_user "Error: $expect_out(1,string).\r\n"
168    exit 1
169  }
170  eof {
171    send_user "Error: Connection closed by remote host.\r\n"
172    exit 1
173  }
174  timeout {
175    if {$authenticated == 0} {
176      send_user "Error: Timed-out waiting to authenticate.\r\n"
177      exit 1
178    }
179  }
180  -nocase -gl "login: \$" {
181    if {$authenticated == 0} {
182      send "$user\r"
183      exp_continue -continue_timer
184    } else {
185      send_user "Error: Permission denied.\r\n"
186      exit 1
187    }
188  }
189  -nocase -gl "password: \$" {
190    if {$authenticated == 0} {
191      send "$pswd\r"
192      incr authenticated
193      exp_continue -continue_timer
194    } else {
195      send_user "Error: Permission denied.\r\n"
196      exit 1
197    }
198  }
199  -re "\[^\r]*\r+\n" {
200    exp_continue -continue_timer
201  }
202}
203send_user "Connection established via telnet (pid $spawn_result).\r\n"
204
205interact {
206  # Replace "&B" with serial-break.
207  "&B" {
208    set timeout 2
209    send "\035send brk\r\n"
210    expect "telnet> send brk"
211  }
212}
213