Skip to content

Commit

Permalink
Intial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
42wim committed Mar 11, 2015
1 parent eb7daa1 commit 69498ba
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 0 deletions.
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,106 @@
# cssh
Tool to connect to (recent) Cisco switches and accesspoints and execute commands.

Tested on:
- Cisco Nexus devices (n2k/n5k/n7k/n9k)
- A lot of Cisco Catalyst types (2960, 3750, ..)
- A lot of Cisco Aironet APs

## use cases
* configure a lot of devices easy and at the same time
* get realtime output from a specified list of switches (e.g searching a mac address)
* run simple commands without an interactive shell

## building
set your GOPATH correct (https://golang.org/doc/code.html#GOPATH)

```
git clone https://github.com/42wim/cssh.git
go get
go build
```

## usage

```
$ ./cssh
-cmd="": single command to execute
-cmd-from="": file containing commands to execute
-host="": host
-host-from="": file containing hosts
```


## config
cssh looks for cssh.conf in current directory.
This config file is necessary because it contains the credentials.

Format is
```
[device "yourswitch"] #yourswitch can be a FQDN or an IP or an regular expression
username=admin
password=abc
enable=abc
```

E.g.
```
[device "switch-building-x-*"]
username=admin
password=abc
enable=abc
```

Also see cssh.conf.dist

## examples
### 1 host and 1 command
```
$ cssh -host lab-switch-1 -cmd "sh clock"
sh clock
23:41:27.572 CET Wed Mar 11 2015
lab-switch-1#
```

### 1 host and multiple commands
see examples/cmd-configexample for the contents

```
$ cssh -host lab-switch-1 -cmd-from cmd-configexample
conf t
Enter configuration commands, one per line. End with CNTL/Z.
lab-switch-1(config)#interface GigabitEthernet0/2
lab-switch-1(config-if)#switchport trunk allowed vlan 234,336,337,356,445,488
lab-switch-1(config-if)#switchport mode trunk
lab-switch-1(config-if)#switchport nonegotiate
lab-switch-1(config-if)#switchport protected
lab-switch-1(config-if)#storm-control broadcast level 50.00 30.00
lab-switch-1(config-if)#storm-control action trap
lab-switch-1(config-if)#no cdp enable
lab-switch-1(config-if)#spanning-tree portfast
lab-switch-1(config-if)#spanning-tree bpduguard enable
lab-switch-1(config-if)#ip dhcp snooping limit rate 30
lab-switch-1(config-if)#exit
lab-switch-1(config)#exit
lab-switch-1#
```

### multiple hosts and 1 command
labswitches contains
```
lab-switch-1
lab-switch-2
```

```
$ cssh -host-from labswitches -cmd "sh clock"
sh clock
*22:45:16.946 UTC Wed Mar 11 2015
lab-switch-1#
sh clock
23:45:18.604 CET Wed Mar 11 2015
lab-switch-2#
```

### multiple hosts and multiple commands
left as an exercise for the reader
49 changes: 49 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"code.google.com/p/gcfg"
"io/ioutil"
"log"
"regexp"
)

type Credentials struct {
Username string
Password string
Enable string
}

type config struct {
Device map[string]*struct {
Username string
Password string
Enable string
}
}

func readConfig(cfgfile string) config {
var cfg config
content, err := ioutil.ReadFile(cfgfile)
if err != nil {
log.Fatal(err)
}
err = gcfg.ReadStringInto(&cfg, string(content))
if err != nil {
log.Fatal("Failed to parse "+cfgfile+":", err)
}
return cfg
}

func getCredentials(host string) Credentials {
var cred Credentials
for device, _ := range cfg.Device {
re := regexp.MustCompile(device)
if re.MatchString(host) {
cred.Username = cfg.Device[device].Username
cred.Password = cfg.Device[device].Password
cred.Enable = cfg.Device[device].Enable
break
}
}
return cred
}
19 changes: 19 additions & 0 deletions cssh.conf.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[device "switch-building-x-*"]
username=admin
password=abc
enable=abc

[device "lwap-*"]
username=admin
password=abc
enable=abc

[device "customer1-switch-*"]
username=admin
password=abc
enable=abc

[device "lab-*"]
username=cisco
password=Cisco
enable=Cisco
99 changes: 99 additions & 0 deletions cssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"flag"
"fmt"
"github.com/42wim/cssh/device"
"io/ioutil"
"log"
"strings"
"time"
)

var host string
var cmd string
var cmdfrom string
var hostfrom string
var cfg config

func init() {
flag.StringVar(&host, "host", "", "host")
flag.StringVar(&cmd, "cmd", "", "single command to execute")
flag.StringVar(&cmdfrom, "cmd-from", "", "file containing commands to execute")
flag.StringVar(&hostfrom, "host-from", "", "file containing hosts")
flag.Parse()
cfg = readConfig("cssh.conf")
}

func doCmd(d *device.CiscoDevice) {
d.Cmd("terminal length 0")
fmt.Print(d.Cmd(cmd))
fmt.Println()
}

func doCmdFrom(d *device.CiscoDevice) {
dat, _ := ioutil.ReadFile(cmdfrom)
lines := strings.Split(string(dat), "\n")
d.Cmd("terminal length 0")
for _, line := range lines {
if line != "" {
fmt.Print(d.Cmd(line))
}
}
fmt.Println()
}

func doHost(host string, c chan int) {
if host != "" {
//fmt.Println("connecting to " + host)
cred := getCredentials(host)
if cred.Username == "" {
fmt.Println("couldn't get credentials for " + host)
c <- 2
return
}
d := &device.CiscoDevice{Hostname: host, Username: cred.Username, Password: cred.Password, Enable: cred.Enable}
err := d.Connect()
if err != nil {
log.Println("couldn't connect", err.Error())
c <- 1
return
} else {
defer d.Close()
if cmdfrom != "" {
doCmdFrom(d)
}
if cmd != "" {
doCmd(d)
}
c <- 1
}
}
}

func main() {
if host == "" && hostfrom == "" {
flag.PrintDefaults()
return
}
if hostfrom != "" {
dat, _ := ioutil.ReadFile(hostfrom)
lines := strings.Split(string(dat), "\n")
// make buffers
c := make(chan int, len(lines)-1)
for _, host := range lines[0 : len(lines)-1] {
time.Sleep(time.Millisecond * 50)
go doHost(host, c)
}
// wait for all goroutines
for i := 0; i < len(lines)-1; i++ {
<-c
}
return
}
if (cmd != "" || cmdfrom != "") && host != "" {
c := make(chan int, 1)
go doHost(host, c)
<-c
}
}
105 changes: 105 additions & 0 deletions device/cisco.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package device

import (
"bufio"
"github.com/ScriptRock/crypto/ssh"
"io"
"log"
"regexp"
"strings"
"time"
)

type CiscoDevice struct {
Username string
Password string
Enable string
name string
Hostname string
stdin io.WriteCloser
stdout io.Reader
session *ssh.Session
}

func (d *CiscoDevice) Connect() error {
config := &ssh.ClientConfig{
User: d.Username,
Auth: []ssh.AuthMethod{
ssh.Password(d.Password),
},
Config: ssh.Config{
Ciphers: ssh.AllSupportedCiphers(),
},
}
client, err := ssh.Dial("tcp", d.Hostname+":22", config)
if err != nil {
return err
}
session, err := client.NewSession()
if err != nil {
return err
}
d.stdin, _ = session.StdinPipe()
d.stdout, _ = session.StdoutPipe()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.OCRNL: 0,
ssh.TTY_OP_ISPEED: 38400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 38400, // output speed = 14.4kbaud
}
session.RequestPty("vt100", 0, 2000, modes)
session.Shell()
d.init()
d.session = session
return nil
}

func (d *CiscoDevice) Close() {
d.session.Close()
}

func (d *CiscoDevice) Cmd(cmd string) string {
bufstdout := bufio.NewReader(d.stdout)
io.WriteString(d.stdin, cmd+"\n")
time.Sleep(time.Millisecond * 100)
return strings.Replace(d.readln(bufstdout), "\r", "", -1)
}

func (d *CiscoDevice) init() {
bufstdout := bufio.NewReader(d.stdout)
io.WriteString(d.stdin, "enable\n")
time.Sleep(time.Millisecond * 100)
re := regexp.MustCompile("assword:")
buf := make([]byte, 1000)
loadStr := ""
for {
n, err := bufstdout.Read(buf)
if err != nil {
log.Fatal(err)
}
loadStr += string(buf[:n])
if re.MatchString(loadStr) {
io.WriteString(d.stdin, d.Enable+"\n")
break
} else {
break
}
}
}

func (d *CiscoDevice) readln(r *bufio.Reader) string {
re := regexp.MustCompile(".*?#.?$")
buf := make([]byte, 1000)
loadStr := ""
for {
n, err := r.Read(buf)
if err != nil {
log.Fatal(err)
}
loadStr += string(buf[:n])
if re.MatchString(loadStr) {
break
}
}
return loadStr
}
14 changes: 14 additions & 0 deletions examples/cmd-configexample
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
conf t
interface GigabitEthernet0/2
switchport trunk allowed vlan 234,336,337,356,445,488
switchport mode trunk
switchport nonegotiate
switchport protected
storm-control broadcast level 50.00 30.00
storm-control action trap
no cdp enable
spanning-tree portfast
spanning-tree bpduguard enable
ip dhcp snooping limit rate 30
exit
exit
Loading

0 comments on commit 69498ba

Please sign in to comment.