forked from External/grumble
Add internal SSH server as a replacement for ctl.
This commit is contained in:
parent
1c5325cba1
commit
244027d41b
5 changed files with 221 additions and 41 deletions
5
Makefile
5
Makefile
|
|
@ -54,9 +54,8 @@ GOFILES = \
|
||||||
freeze.go \
|
freeze.go \
|
||||||
gencert.go \
|
gencert.go \
|
||||||
register.go \
|
register.go \
|
||||||
ctlrpc.go \
|
ssh.go \
|
||||||
ctl.go \
|
args.go \
|
||||||
args.go
|
|
||||||
|
|
||||||
ifeq ($(SQLITE),1)
|
ifeq ($(SQLITE),1)
|
||||||
GOFILES += murmurdb.go
|
GOFILES += murmurdb.go
|
||||||
|
|
|
||||||
20
args.go
20
args.go
|
|
@ -11,8 +11,7 @@ import (
|
||||||
type args struct {
|
type args struct {
|
||||||
ShowHelp bool
|
ShowHelp bool
|
||||||
DataDir string
|
DataDir string
|
||||||
CtlNet string
|
SshAddr string
|
||||||
CtlAddr string
|
|
||||||
RegenKeys bool
|
RegenKeys bool
|
||||||
SQLiteDB string
|
SQLiteDB string
|
||||||
CleanUp bool
|
CleanUp bool
|
||||||
|
|
@ -26,20 +25,6 @@ func defaultDataDir() string {
|
||||||
return filepath.Join(os.Getenv("HOME"), dirname)
|
return filepath.Join(os.Getenv("HOME"), dirname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultCtlNet() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return "tcp"
|
|
||||||
}
|
|
||||||
return "unix"
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultCtlAddr() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return "localhost:5454"
|
|
||||||
}
|
|
||||||
return filepath.Join(defaultDataDir(), ".ctl")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Usage() {
|
func Usage() {
|
||||||
fmt.Fprintf(os.Stderr, "usage: grumble [options]\n")
|
fmt.Fprintf(os.Stderr, "usage: grumble [options]\n")
|
||||||
fmt.Fprintf(os.Stderr, "remote control: grumble [options] ctl [ctlopts]\n")
|
fmt.Fprintf(os.Stderr, "remote control: grumble [options] ctl [ctlopts]\n")
|
||||||
|
|
@ -51,8 +36,7 @@ var Args args
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&Args.ShowHelp, "help", false, "Show this help")
|
flag.BoolVar(&Args.ShowHelp, "help", false, "Show this help")
|
||||||
flag.StringVar(&Args.DataDir, "datadir", defaultDataDir(), "Directory to use for server storage")
|
flag.StringVar(&Args.DataDir, "datadir", defaultDataDir(), "Directory to use for server storage")
|
||||||
flag.StringVar(&Args.CtlNet, "ctlnet", defaultCtlNet(), "Network to use for ctl socket")
|
flag.StringVar(&Args.SshAddr, "ssh", "localhost:46545", "Address to use for SSH admin prompt")
|
||||||
flag.StringVar(&Args.CtlAddr, "ctladdr", defaultCtlAddr(), "Address to use for ctl socket")
|
|
||||||
flag.BoolVar(&Args.RegenKeys, "regenkeys", false, "Force Grumble to regenerate its global RSA keypair and certificate")
|
flag.BoolVar(&Args.RegenKeys, "regenkeys", false, "Force Grumble to regenerate its global RSA keypair and certificate")
|
||||||
|
|
||||||
// SQLite related
|
// SQLite related
|
||||||
|
|
|
||||||
21
grumble.go
21
grumble.go
|
|
@ -9,8 +9,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"grumble/blobstore"
|
"grumble/blobstore"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/rpc"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -27,13 +25,6 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, str := range os.Args {
|
|
||||||
if str == "ctl" {
|
|
||||||
GrumbleCtl(os.Args[i+1:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetPrefix("[G] ")
|
log.SetPrefix("[G] ")
|
||||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||||
log.Printf("Grumble")
|
log.Printf("Grumble")
|
||||||
|
|
@ -167,17 +158,7 @@ func main() {
|
||||||
go s.ListenAndMurmur()
|
go s.ListenAndMurmur()
|
||||||
}
|
}
|
||||||
|
|
||||||
if Args.CtlNet == "unix" {
|
go RunSSH()
|
||||||
os.Remove(Args.CtlAddr)
|
|
||||||
}
|
|
||||||
lis, err := net.Listen(Args.CtlNet, Args.CtlAddr)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Unable to listen on ctl socket: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl := &ControlRPC{}
|
|
||||||
rpc.RegisterName("ctl", ctl)
|
|
||||||
go rpc.Accept(lis)
|
|
||||||
|
|
||||||
if len(servers) > 0 {
|
if len(servers) > 0 {
|
||||||
go SignalHandler()
|
go SignalHandler()
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,11 @@ const (
|
||||||
StateClientDead
|
StateClientDead
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type KeyValuePair struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
// A Murmur server instance
|
// A Murmur server instance
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Id int64
|
Id int64
|
||||||
|
|
|
||||||
211
ssh.go
Normal file
211
ssh.go
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"exp/ssh"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func passwordAuth(username, password string) bool {
|
||||||
|
if username == "admin" && password == "admin" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type SshCmdReply interface {
|
||||||
|
WriteString(s string) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SshCmdFunc func(reply SshCmdReply, args []string) error
|
||||||
|
|
||||||
|
type SshCmd struct {
|
||||||
|
CmdFunc SshCmdFunc
|
||||||
|
Args string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c SshCmd) Call(reply SshCmdReply, args []string) error {
|
||||||
|
return c.CmdFunc(reply, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdMap = map[string]SshCmd{}
|
||||||
|
|
||||||
|
func RegisterSSHCmd(name string, cmdFunc SshCmdFunc, args string, desc string) {
|
||||||
|
cmdMap[name] = SshCmd{
|
||||||
|
CmdFunc: cmdFunc,
|
||||||
|
Args: args,
|
||||||
|
Description: desc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunSSH() {
|
||||||
|
RegisterSSHCmd("help",
|
||||||
|
HelpCmd,
|
||||||
|
"[cmd]",
|
||||||
|
"Shows this help (or help for a given command)")
|
||||||
|
RegisterSSHCmd("start",
|
||||||
|
StartServerCmd,
|
||||||
|
"<id>",
|
||||||
|
"Starts the server with the given id")
|
||||||
|
RegisterSSHCmd("stop",
|
||||||
|
StopServerCmd,
|
||||||
|
"<id>",
|
||||||
|
"Stops the server with the given id")
|
||||||
|
RegisterSSHCmd("supw",
|
||||||
|
SetSuperUserPasswordCmd,
|
||||||
|
"<id> <password>",
|
||||||
|
"Set the SuperUser password for server with the given id")
|
||||||
|
RegisterSSHCmd("setconf",
|
||||||
|
SetConfCmd,
|
||||||
|
"<id> <key> <value>",
|
||||||
|
"Set a config value for the server with the given id")
|
||||||
|
RegisterSSHCmd("getconf",
|
||||||
|
GetConfCmd,
|
||||||
|
"<id> <key> <value>",
|
||||||
|
"Get a config value for the server with the given id")
|
||||||
|
|
||||||
|
pemBytes, err := ioutil.ReadFile(filepath.Join(Args.DataDir, "key"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := new(ssh.ServerConfig)
|
||||||
|
cfg.Rand = rand.Reader
|
||||||
|
cfg.PasswordCallback = passwordAuth
|
||||||
|
cfg.SetRSAPrivateKey(pemBytes)
|
||||||
|
|
||||||
|
listener, err := ssh.Listen("tcp", Args.SshAddr, cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Listening for SSH connections on '%v'", Args.SshAddr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("ssh: unable to accept incoming connection: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.Handshake()
|
||||||
|
if err == io.EOF {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatalf("ssh: unable to perform handshake: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
channel, err := conn.Accept()
|
||||||
|
if err == io.EOF {
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatalf("ssh: unable to accept channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go handleChannel(channel)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleChannel(channel ssh.Channel) {
|
||||||
|
if channel.ChannelType() == "session" {
|
||||||
|
channel.Accept()
|
||||||
|
shell := ssh.NewServerShell(channel, "G> ")
|
||||||
|
go func() {
|
||||||
|
defer channel.Close()
|
||||||
|
for {
|
||||||
|
line, err := shell.ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
log.Printf("ssh: error in reading from channel: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
args := strings.Split(line, " ")
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[0] == "exit" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd, ok := cmdMap[args[0]]; ok {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = cmd.Call(buf, args)
|
||||||
|
if err != nil {
|
||||||
|
_, err = shell.Write([]byte(fmt.Sprintf("error: %v\r\n", err.Error())))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = shell.Write(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = shell.Write([]byte("error: unknown command\r\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func HelpCmd(reply SshCmdReply, args []string) error {
|
||||||
|
if len(args) == 1 {
|
||||||
|
for cmdName, cmd := range cmdMap {
|
||||||
|
reply.WriteString("\r\n")
|
||||||
|
reply.WriteString(" " + cmdName + " " + cmd.Args + "\r\n")
|
||||||
|
reply.WriteString(" " + cmd.Description + "\r\n")
|
||||||
|
}
|
||||||
|
} else if len(args) > 1 {
|
||||||
|
cmdName := args[1]
|
||||||
|
if cmd, ok := cmdMap[cmdName]; ok {
|
||||||
|
reply.WriteString("\r\n")
|
||||||
|
reply.WriteString(" " + cmdName + " " + cmd.Args + "\r\n")
|
||||||
|
reply.WriteString(" " + cmd.Description + "\r\n")
|
||||||
|
} else {
|
||||||
|
return errors.New("no such command name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reply.WriteString("\r\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartServerCmd(reply SshCmdReply, args []string) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopServerCmd(reply SshCmdReply, args []string) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetSuperUserPasswordCmd(reply SshCmdReply, args []string) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetConfCmd(reply SshCmdReply, args []string) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfCmd(reply SshCmdReply, args []string) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue