From d2aec8cc44f36992dd03e246f35d550da37d434d Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Sat, 6 Dec 2014 23:31:23 -0800 Subject: [PATCH] Progress: Echo working. --- cmd.go | 75 ++++++++++++++++++++++++++++ logger.go | 7 +++ server.go | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 cmd.go create mode 100644 logger.go create mode 100644 server.go diff --git a/cmd.go b/cmd.go new file mode 100644 index 0000000..e8c4610 --- /dev/null +++ b/cmd.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/signal" + + "github.com/alexcesaro/log" + "github.com/alexcesaro/log/golog" + "github.com/jessevdk/go-flags" +) + +type Options struct { + Verbose []bool `short:"v" long:"verbose" description:"Show verbose logging."` + Bind string `short:"b" long:"bind" description:"Host and port to listen on." default:"0.0.0.0:22"` + Identity string `short:"i" long:"identity" description:"Private key to identify server with." default:"~/.ssh/id_rsa"` +} + +var logLevels = []log.Level{ + log.Warning, + log.Info, + log.Debug, +} + +func main() { + options := Options{} + parser := flags.NewParser(&options, flags.Default) + + p, err := parser.Parse() + if err != nil { + if p == nil { + fmt.Print(err) + } + return + } + + // Figure out the log level + numVerbose := len(options.Verbose) + if numVerbose > len(logLevels) { + numVerbose = len(logLevels) + } + + logLevel := logLevels[numVerbose] + logger = golog.New(os.Stderr, logLevel) + + privateKey, err := ioutil.ReadFile(options.Identity) + if err != nil { + logger.Errorf("Failed to load identity: %v", err) + return + } + + server, err := NewServer(privateKey) + if err != nil { + logger.Errorf("Failed to create server: %v", err) + return + } + + // Construct interrupt handler + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt) + go func() { + <-sig // Wait for ^C signal + logger.Warningf("Interrupt signal detected, shutting down.") + server.Stop() + }() + + done, err := server.Start(options.Bind) + if err != nil { + logger.Errorf("Failed to start server: %v", err) + return + } + + <-done +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..8fe9842 --- /dev/null +++ b/logger.go @@ -0,0 +1,7 @@ +package main + +import ( + "github.com/alexcesaro/log/golog" +) + +var logger *golog.Logger diff --git a/server.go b/server.go new file mode 100644 index 0000000..7fb02e2 --- /dev/null +++ b/server.go @@ -0,0 +1,142 @@ +// TODO: NoClientAuth + +package main + +import ( + "fmt" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" + "net" +) + +type Server struct { + sshConfig *ssh.ServerConfig + sshSigner *ssh.Signer + socket *net.Listener + done chan struct{} +} + +func NewServer(privateKey []byte) (*Server, error) { + signer, err := ssh.ParsePrivateKey(privateKey) + if err != nil { + return nil, err + } + + config := ssh.ServerConfig{ + NoClientAuth: true, + } + config.AddHostKey(signer) + + server := Server{ + sshConfig: &config, + sshSigner: &signer, + } + + return &server, nil +} + +func (s *Server) handleShell(channel ssh.Channel) { + defer channel.Close() + + term := terminal.NewTerminal(channel, "") + + for { + line, err := term.ReadLine() + if err != nil { + break + } + + switch line { + case "exit": + channel.Close() + } + + term.Write([]byte("you wrote: " + string(line) + "\r\n")) + } +} + +func (s *Server) handleChannels(channels <-chan ssh.NewChannel) { + for ch := range channels { + if t := ch.ChannelType(); t != "session" { + ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) + continue + } + + channel, requests, err := ch.Accept() + if err != nil { + logger.Errorf("Could not accept channel: %v", err) + continue + } + + go func(in <-chan *ssh.Request) { + defer channel.Close() + for req := range in { + logger.Infof("Request: ", req.Type, string(req.Payload)) + + ok := false + switch req.Type { + case "shell": + // We don't accept any commands (Payload), + // only the default shell. + if len(req.Payload) == 0 { + ok = true + } + case "pty-req": + // Responding 'ok' here will let the client + // know we have a pty ready for input + ok = true + case "window-change": + continue //no response + } + req.Reply(ok, nil) + } + }(requests) + + go s.handleShell(channel) + + channel.Write([]byte("Hello")) + } +} + +func (s *Server) Start(laddr string) (<-chan struct{}, error) { + // Once a ServerConfig has been configured, connections can be + // accepted. + socket, err := net.Listen("tcp", laddr) + if err != nil { + return nil, err + } + + s.socket = &socket + logger.Infof("Listening on %s", laddr) + + go func() { + for { + conn, err := socket.Accept() + if err != nil { + // TODO: Handle shutdown more gracefully. + logger.Errorf("Failed to accept connection, aborting loop: %v", err) + return + } + + // From a standard TCP connection to an encrypted SSH connection + sshConn, channels, requests, err := ssh.NewServerConn(conn, s.sshConfig) + if err != nil { + logger.Errorf("Failed to handshake: %v", err) + continue + } + + logger.Infof("Connection from: %s, %s, %s", sshConn.RemoteAddr(), sshConn.User(), sshConn.ClientVersion()) + + go ssh.DiscardRequests(requests) + go s.handleChannels(channels) + } + }() + + return s.done, nil +} + +func (s *Server) Stop() error { + err := (*s.socket).Close() + s.done <- struct{}{} + return err +}