package ussher import ( "fmt" "log" "net" "time" "golang.org/x/crypto/ssh" ) func RunSSHServer(config *Config) error { sshConfig := &ssh.ServerConfig{ PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { return config.Keystore.CheckPublicKey(conn.User(), key) }, } private, err := config.GetPrivateKey() if err != nil { return err } sshConfig.AddHostKey(private) // Once a ServerConfig has been configured, connections can be accepted. listenAddr := fmt.Sprintf("%s:%d", config.BindAddress, config.Port) listener, err := net.Listen("tcp", listenAddr) if err != nil { log.Fatalf("Failed to listen on %s (%s)", listenAddr, err) } // Accept all connections log.Printf("Listening on %s...", listenAddr) for { tcpConn, err := listener.Accept() if err != nil { log.Printf("Failed to accept incoming connection (%s)", err) continue } // Before use, a handshake must be performed on the incoming net.Conn. sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, sshConfig) if err != nil { log.Printf("Failed to handshake (%s)", err) continue } log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion()) // Discard all global out-of-band Requests go ssh.DiscardRequests(reqs) // Accept all channels go handleChannels(chans, sshConn, config) } } func handleChannels(chans <-chan ssh.NewChannel, conn *ssh.ServerConn, config *Config) { for newChannel := range chans { go handleChannel(newChannel, conn, config) } } func handleChannel(newChannel ssh.NewChannel, conn *ssh.ServerConn, config *Config) { // We're emulating a shell command; so this is a "session" if t := newChannel.ChannelType(); t != "session" { newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) return } connection, requests, err := newChannel.Accept() if err != nil { log.Printf("Could not accept channel (%s)", err) return } go func() { var exit bool for { if exit { break } select { case req := <-requests: switch req.Type { case "shell": log.Printf("Requested shell; none can be provided") connection.Close() exit = true case "exec": appname := string(req.Payload[4:]) // skip the first four bytes, which are length of string for k, v := range config.SSHApps { fmt.Println(k, " ", []byte(k)) fmt.Printf("%#v\n", v) } fmt.Println(appname, req.Payload) v, ok := config.SSHApps[appname] if ok { v.HandleExec(conn, connection) } else { log.Printf("Can't find app %s", appname) } connection.Close() exit = true default: // Ignore things like 'pty' and 'env' variables. continue } case <-time.After(5 * time.Second): fmt.Println("connection timeout") connection.Close() } } }() }