blob: 2e89ca8f73d4309924014ee6943b0e4cb403f50c [file] [log] [blame]
Serge Bazanski848db462021-10-07 18:46:14 +00001package cli
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +01002
3import (
4 "context"
5 "fmt"
6 "strings"
7
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +01008 "github.com/ziutek/telnet"
9 "golang.org/x/net/trace"
10)
11
Serge Bazanski848db462021-10-07 18:46:14 +000012type Client struct {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010013 conn *telnet.Conn
14
15 username string
16 password string
17
18 loggedIn bool
19 promptHostname string
20}
21
Serge Bazanski848db462021-10-07 18:46:14 +000022func NewClient(c *telnet.Conn, username, password string) *Client {
23 return &Client{
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010024 conn: c,
25 username: username,
26 password: password,
27 }
28}
29
Serge Bazanski848db462021-10-07 18:46:14 +000030func (c *Client) readUntil(ctx context.Context, delims ...string) (string, error) {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010031 chStr := make(chan string, 1)
32 chErr := make(chan error, 1)
33 go func() {
34 s, err := c.conn.ReadUntil(delims...)
35 if err != nil {
36 chErr <- err
37 return
38 }
39 chStr <- string(s)
40 }()
41
42 select {
43 case <-ctx.Done():
44 return "", fmt.Errorf("context done")
45 case err := <-chErr:
46 c.trace(ctx, "readUntil failed: %v", err)
47 return "", err
48 case s := <-chStr:
49 c.trace(ctx, "readUntil <- %q", s)
50 return s, nil
51
52 }
53}
54
Serge Bazanski848db462021-10-07 18:46:14 +000055func (c *Client) readString(ctx context.Context, delim byte) (string, error) {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010056 chStr := make(chan string, 1)
57 chErr := make(chan error, 1)
58 go func() {
59 s, err := c.conn.ReadString(delim)
60 if err != nil {
61 chErr <- err
62 return
63 }
64 chStr <- s
65 }()
66
67 select {
68 case <-ctx.Done():
69 return "", fmt.Errorf("context done")
70 case err := <-chErr:
71 c.trace(ctx, "readString failed: %v", err)
72 return "", err
73 case s := <-chStr:
74 c.trace(ctx, "readString <- %q", s)
75 return s, nil
76
77 }
78}
79
Serge Bazanski848db462021-10-07 18:46:14 +000080func (c *Client) writeLine(ctx context.Context, s string) error {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010081 n, err := c.conn.Write([]byte(s + "\n"))
82 if got, want := n, len(s)+1; got != want {
83 err = fmt.Errorf("wrote %d bytes out of %d", got, want)
84 }
85 if err != nil {
86 c.trace(ctx, "writeLine failed: %v", err)
87 return err
88 }
89 c.trace(ctx, "writeLine -> %q", s)
90 return nil
91}
92
Serge Bazanski848db462021-10-07 18:46:14 +000093func (c *Client) trace(ctx context.Context, f string, parts ...interface{}) {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010094 tr, ok := trace.FromContext(ctx)
95 if !ok {
Serge Bazanski848db462021-10-07 18:46:14 +000096 //fmted := fmt.Sprintf(f, parts...)
97 //glog.Infof("[no trace] %s", fmted)
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010098 return
99 }
100 tr.LazyPrintf(f, parts...)
101}
102
Serge Bazanski848db462021-10-07 18:46:14 +0000103func (c *Client) logIn(ctx context.Context) error {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +0100104 if c.loggedIn {
105 return nil
106 }
107
108 // Provide username.
109 prompt, err := c.readString(ctx, ':')
110 if err != nil {
111 return fmt.Errorf("could not read username prompt: %v", err)
112 }
113 if !strings.HasSuffix(prompt, "User:") {
114 return fmt.Errorf("invalid username prompt: %v", err)
115 }
116 if err := c.writeLine(ctx, c.username); err != nil {
117 return fmt.Errorf("could not write username: %v")
118 }
119
120 // Provide password.
121 prompt, err = c.readString(ctx, ':')
122 if err != nil {
123 return fmt.Errorf("could not read password prompt: %v", err)
124 }
125 if !strings.HasSuffix(prompt, "Password:") {
126 return fmt.Errorf("invalid password prompt: %v", err)
127 }
128 if err := c.writeLine(ctx, c.password); err != nil {
129 return fmt.Errorf("could not write password: %v")
130 }
131
132 // Get unprivileged prompt.
133 prompt, err = c.readString(ctx, '>')
134 if err != nil {
135 return fmt.Errorf("could not read unprivileged prompt: %v", err)
136 }
137
138 parts := strings.Split(prompt, "\r\n")
139 c.promptHostname = strings.TrimSuffix(parts[len(parts)-1], ">")
140
141 // Enable privileged mode.
142
143 if err := c.writeLine(ctx, "enable"); err != nil {
144 return fmt.Errorf("could not write enable: %v")
145 }
146
147 // Provide password (again)
148 prompt, err = c.readString(ctx, ':')
149 if err != nil {
150 return fmt.Errorf("could not read password prompt: %v", err)
151 }
152 if !strings.HasSuffix(prompt, "Password:") {
153 return fmt.Errorf("invalid password prompt: %v", err)
154 }
155 if err := c.writeLine(ctx, c.password); err != nil {
156 return fmt.Errorf("could not write password: %v")
157 }
158
159 // Get privileged prompt.
160 prompt, err = c.readString(ctx, '#')
161 if err != nil {
162 return fmt.Errorf("could not read privileged prompt: %v", err)
163 }
164
165 if !strings.HasSuffix(prompt, c.promptHostname+"#") {
166 return fmt.Errorf("unexpected privileged prompt: %v", prompt)
167 }
168
169 // Disable pager.
170 if err := c.writeLine(ctx, "terminal length 0"); err != nil {
171 return fmt.Errorf("could not diable pager: %v", err)
172 }
173 prompt, err = c.readString(ctx, '#')
174 if err != nil {
175 return fmt.Errorf("could not disable pager: %v", err)
176 }
177 if !strings.HasSuffix(prompt, c.promptHostname+"#") {
178 return fmt.Errorf("unexpected privileged prompt: %v", prompt)
179 }
180
181 // Success!
182 c.loggedIn = true
183 c.trace(ctx, "logged into %v", c.promptHostname)
184 return nil
185}
186
Serge Bazanski848db462021-10-07 18:46:14 +0000187func (c *Client) RunCommand(ctx context.Context, command string) ([]string, string, error) {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +0100188 if err := c.logIn(ctx); err != nil {
189 return nil, "", fmt.Errorf("could not log in: %v", err)
190 }
191
192 // First, synchronize to prompt.
193 attempts := 3
194 for {
195 c.writeLine(ctx, "")
196 line, err := c.readString(ctx, '\n')
197 if err != nil {
198 return nil, "", fmt.Errorf("while synchronizing to prompt: %v", err)
199 }
200 line = strings.Trim(line, "\r\n")
201 if strings.HasSuffix(line, c.promptHostname+"#") {
202 break
203 }
204
205 attempts -= 1
206 if attempts == 0 {
207 return nil, "", fmt.Errorf("could not find prompt, last result %q", line)
208 }
209 }
210
211 // Send comand.
212 c.writeLine(ctx, command)
213
214 // First, read until prompt again.
215 if _, err := c.readUntil(ctx, c.promptHostname+"#"); err != nil {
216 return nil, "", fmt.Errorf("could not get command hostname echo: %v", err)
217 }
218
219 loopback, err := c.readUntil(ctx, "\r\n")
220 if err != nil {
221 return nil, "", fmt.Errorf("could not get command loopback: %v", err)
222 }
223 loopback = strings.Trim(loopback, "\r\n")
224 c.trace(ctx, "effective command: %q", loopback)
225
226 // Read until we have a standalone prompt with no newline afterwards.
227 data, err := c.readUntil(ctx, c.promptHostname+"#")
228 if err != nil {
229 return nil, "", fmt.Errorf("could not get command results: %v", err)
230 }
231
232 lines := []string{}
233 for _, line := range strings.Split(data, "\r\n") {
234 if line == c.promptHostname+"#" {
235 break
236 }
237 lines = append(lines, line)
238 }
239 c.trace(ctx, "command %q returned lines: %v", command, lines)
240
241 return lines, loopback, nil
242}