Ctrl-Shift-v
I have a Go program that logs into a set of devices and runs a set of commands on each device. When trying to use a regex containing a question mark, though, the Cisco device always interprets the question mark as a help command. Using string literals in Go does not fix the problem nor does sending the command as a slice of bytes.
show boot | include (c|cat)[0-9]+[a-zA-Z]?
switch-1#show boot | include (c|cat)[0-9]+[a-zA-Z]?
LINE <cr>
switch-1#
instead of interpreting the question mark as a regex match of 0 or 1 for the [a-zA-Z] group.
ssh user@switch-1 'show boot | include (c|cat)[0-9]+[a-zA-Z]?'
Ctrl-Shift-v
My code as requested:
package main
import (
"golang.org/x/crypto/ssh"
"net"
"fmt"
"os"
"bufio"
"time"
"golang.org/x/crypto/ssh/terminal"
"io"
"io/ioutil"
"sync"
"strings"
)
// ReadLines reads a file line-by-line and returns a slice of the lines.
func ReadLines(filename string) ([]string, error) {
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer f.Close()
var lines []string
s := bufio.NewScanner(f)
for s.Scan() {
lines = append(lines, s.Text())
}
if err := s.Err(); err != nil {
return nil, err
}
return lines, nil
}
// Type Result represents the result of running the Configure method.
type Result struct {
Host string // Hostname of device
Output []byte // Remote shell's stdin and stderr output
Err error // Remote shell errors
}
// Configure logs into a device, starts a remote shell, runs the set of
// commands, and waits for the remote shell to return or timeout.
func Configure(host string, config *ssh.ClientConfig, cmds []string, results chan<- *Result, wg *sync.WaitGroup) {
defer wg.Done()
res := &Result{
Host: host,
Output: nil,
Err: nil,
}
// Create client connection
client, err := ssh.Dial("tcp", net.JoinHostPort(host, "22"), config)
if err != nil {
res.Err = fmt.Errorf("failed to dial: %v", err)
results <- res
return
}
defer client.Close()
// Create new session
session, err := client.NewSession()
if err != nil {
res.Err = fmt.Errorf("failed to create session: %v", err)
results <- res
return
}
defer session.Close()
// Set session IO
stdin, err := session.StdinPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stdin: %v", err)
results <- res
return
}
defer stdin.Close()
stdout, err := session.StdoutPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stdout: %v", err)
results <- res
return
}
stderr, err := session.StderrPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stderr: %v", err)
results <- res
return
}
// Start remote shell
if err := session.RequestPty("vt100", 0, 0, ssh.TerminalModes{
ssh.ECHO: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}); err != nil {
res.Err = fmt.Errorf("failed to request pseudoterminal: %v", err)
results <- res
return
}
if err := session.Shell(); err != nil {
res.Err = fmt.Errorf("failed to start remote shell: %v", err)
results <- res
return
}
// Run commands
for _, cmd := range cmds {
if _, err := io.WriteString(stdin, cmd+"
"); err != nil {
res.Err = fmt.Errorf("failed to run: %v", err)
results <- res
return
}
}
// Wait for remote commands to return or timeout
exit := make(chan error, 1)
go func(exit chan<- error) {
exit <- session.Wait()
}(exit)
timeout := time.After(1 * time.Minute)
select {
case <-exit:
output, err := ioutil.ReadAll(io.MultiReader(stdout, stderr))
if err != nil {
res.Err = fmt.Errorf("failed to read output: %v", err)
results <- res
return
}
res.Output = output
results <- res
return
case <-timeout:
res.Err = fmt.Errorf("session timed out")
results <- res
return
}
}
func main() {
hosts, err := ReadLines(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
cmds, err := ReadLines(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Fprint(os.Stderr, "Password: ")
secret, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read password: %v
", err)
os.Exit(1)
}
fmt.Fprintln(os.Stderr)
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{ssh.Password(string(secret))},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 30 * time.Second,
}
config.SetDefaults()
config.Ciphers = append(config.Ciphers, "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc")
results := make(chan *Result, len(hosts))
var wg sync.WaitGroup
wg.Add(len(hosts))
for _, host := range hosts {
go Configure(host, config, cmds, results, &wg)
}
wg.Wait()
close(results)
for res := range results {
if res.Err != nil {
fmt.Fprintf(os.Stderr, "Error %s: %v
", res.Host, res.Err)
continue
}
fmt.Printf("Host %s
%s
%s
", res.Host, res.Output, strings.Repeat("-", 50))
}
}