package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"io/ioutil"
"net"
"net/http"
"os"
)
const (
sshPortEnv = "SSH_PORT"
httpPortEnv = "PORT"
defaultSshPort = "2022"
defaultHttpPort = "3000"
)
func handler(conn net.Conn, gm *GameManager, config *ssh.ServerConfig) {
// Before use, a handshake must be performed on the incoming
// net.Conn.
sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
fmt.Println("Failed to handshake with new client")
return
}
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of a shell, the type is
// "session" and ServerShell may be used to present a simple
// terminal interface.
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
fmt.Println("could not accept channel.")
return
}
// TODO: Remove this -- only temporary while we launch on HN
//
// To see how many concurrent users are online
fmt.Printf("Player joined. Current stats: %d users, %d games\n",
gm.SessionCount(), gm.GameCount())
// Reject all out of band requests accept for the unix defaults, pty-req and
// shell.
go func(in <-chan *ssh.Request) {
for req := range in {
switch req.Type {
case "pty-req":
req.Reply(true, nil)
continue
case "shell":
req.Reply(true, nil)
continue
}
req.Reply(false, nil)
}
}(requests)
gm.HandleNewChannel(channel, sshConn.User())
}
}
func port(env, def string) string {
port := os.Getenv(env)
if port == "" {
port = def
}
return fmt.Sprintf(":%s", port)
}
func main() {
sshPort := port(sshPortEnv, defaultSshPort)
httpPort := port(httpPortEnv, defaultHttpPort)
// Everyone can login!
config := &ssh.ServerConfig{
NoClientAuth: true,
}
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
panic("Failed to load private key")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
panic("Failed to parse private key")
}
config.AddHostKey(private)
// Create the GameManager
gm := NewGameManager()
fmt.Printf(
"Listening on port %s for SSH and port %s for HTTP...\n",
sshPort,
httpPort,
)
go func() {
panic(http.ListenAndServe(httpPort, http.FileServer(http.Dir("./static/"))))
}()
// Once a ServerConfig has been configured, connections can be
// accepted.
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0%s", sshPort))
if err != nil {
panic("failed to listen for connection")
}
for {
nConn, err := listener.Accept()
if err != nil {
panic("failed to accept incoming connection")
}
go handler(nConn, gm, config)
}
}