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