From 244027d41b1e576fac00fefeb45c5af4519b1cc7 Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Wed, 9 Nov 2011 18:10:55 +0100 Subject: [PATCH] Add internal SSH server as a replacement for ctl. --- Makefile | 5 +- args.go | 20 +---- grumble.go | 21 +----- server.go | 5 ++ ssh.go | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 221 insertions(+), 41 deletions(-) create mode 100644 ssh.go diff --git a/Makefile b/Makefile index 468aee1..f60b162 100644 --- a/Makefile +++ b/Makefile @@ -54,9 +54,8 @@ GOFILES = \ freeze.go \ gencert.go \ register.go \ - ctlrpc.go \ - ctl.go \ - args.go + ssh.go \ + args.go \ ifeq ($(SQLITE),1) GOFILES += murmurdb.go diff --git a/args.go b/args.go index 577f027..ddd9534 100644 --- a/args.go +++ b/args.go @@ -11,8 +11,7 @@ import ( type args struct { ShowHelp bool DataDir string - CtlNet string - CtlAddr string + SshAddr string RegenKeys bool SQLiteDB string CleanUp bool @@ -26,20 +25,6 @@ func defaultDataDir() string { 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() { fmt.Fprintf(os.Stderr, "usage: grumble [options]\n") fmt.Fprintf(os.Stderr, "remote control: grumble [options] ctl [ctlopts]\n") @@ -51,8 +36,7 @@ var Args args func init() { flag.BoolVar(&Args.ShowHelp, "help", false, "Show this help") 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.CtlAddr, "ctladdr", defaultCtlAddr(), "Address to use for ctl socket") + flag.StringVar(&Args.SshAddr, "ssh", "localhost:46545", "Address to use for SSH admin prompt") flag.BoolVar(&Args.RegenKeys, "regenkeys", false, "Force Grumble to regenerate its global RSA keypair and certificate") // SQLite related diff --git a/grumble.go b/grumble.go index 7177929..5040e9f 100644 --- a/grumble.go +++ b/grumble.go @@ -9,8 +9,6 @@ import ( "fmt" "grumble/blobstore" "log" - "net" - "net/rpc" "os" "path/filepath" "regexp" @@ -27,13 +25,6 @@ func main() { return } - for i, str := range os.Args { - if str == "ctl" { - GrumbleCtl(os.Args[i+1:]) - return - } - } - log.SetPrefix("[G] ") log.SetFlags(log.LstdFlags | log.Lmicroseconds) log.Printf("Grumble") @@ -167,17 +158,7 @@ func main() { go s.ListenAndMurmur() } - if Args.CtlNet == "unix" { - 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) + go RunSSH() if len(servers) > 0 { go SignalHandler() diff --git a/server.go b/server.go index e8851e8..56dd843 100644 --- a/server.go +++ b/server.go @@ -48,6 +48,11 @@ const ( StateClientDead ) +type KeyValuePair struct { + Key string + Value string +} + // A Murmur server instance type Server struct { Id int64 diff --git a/ssh.go b/ssh.go new file mode 100644 index 0000000..fc7ca7f --- /dev/null +++ b/ssh.go @@ -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, + "", + "Starts the server with the given id") + RegisterSSHCmd("stop", + StopServerCmd, + "", + "Stops the server with the given id") + RegisterSSHCmd("supw", + SetSuperUserPasswordCmd, + " ", + "Set the SuperUser password for server with the given id") + RegisterSSHCmd("setconf", + SetConfCmd, + " ", + "Set a config value for the server with the given id") + RegisterSSHCmd("getconf", + GetConfCmd, + " ", + "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") +} \ No newline at end of file