Skip to content

Instantly share code, notes, and snippets.

@vito
Created March 28, 2015 22:17
Show Gist options
  • Save vito/dbf30553d158bd7a67bd to your computer and use it in GitHub Desktop.
Save vito/dbf30553d158bd7a67bd to your computer and use it in GitHub Desktop.
golang ssh+http listener
package main
import (
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"syscall"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
type SSHTTPListener interface {
SSHListener() net.Listener
HTTPListener() net.Listener
}
type listener struct {
net.Listener
sshConns chan net.Conn
sshErrs chan error
httpConns chan net.Conn
httpErrs chan error
}
type fileConn interface {
File() (*os.File, error)
}
func (l *listener) Dispatch() error {
for {
c, err := l.Listener.Accept()
if err != nil {
l.sshErrs <- err
l.httpErrs <- err
continue
}
var connFile *os.File
if fileConn, ok := c.(fileConn); ok {
var err error
connFile, err = fileConn.File()
if err != nil {
l.sshErrs <- err
l.httpErrs <- err
continue
}
} else {
return fmt.Errorf("connection type cannot provide file: %T", c)
}
buf := make([]byte, 4)
n, addr, err := syscall.Recvfrom(int(connFile.Fd()), buf, syscall.MSG_PEEK)
if err != nil {
log.Println("peek conn type failed:", err)
continue
}
log.Println("RECVD", n, addr, string(buf[:n]))
if string(buf[:n]) == "SSH-" {
l.sshConns <- c
} else {
l.httpConns <- c
}
}
}
func (l *listener) HTTPListener() net.Listener {
return &chanListener{
Listener: l.Listener,
conns: l.httpConns,
errs: l.httpErrs,
}
}
func (l *listener) SSHListener() net.Listener {
return &chanListener{
Listener: l.Listener,
conns: l.sshConns,
errs: l.sshErrs,
}
}
type chanListener struct {
net.Listener
conns <-chan net.Conn
errs <-chan error
}
func (l *chanListener) Accept() (net.Conn, error) {
select {
case c := <-l.conns:
return c, nil
case e := <-l.errs:
return nil, e
}
}
func main() {
l, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln("failed to listen:", err)
}
sshttpListener := &listener{
Listener: l,
sshConns: make(chan net.Conn),
sshErrs: make(chan error),
httpConns: make(chan net.Conn),
httpErrs: make(chan error),
}
go http.Serve(sshttpListener.HTTPListener(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello from http!")
}))
sshL := sshttpListener.SSHListener()
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
// Should use constant-time compare (or better, salt+hash) in
// a production setting.
if c.User() == "testuser" && string(pass) == "tiger" {
return nil, nil
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
}
privateBytes, err := ioutil.ReadFile("/home/alex/.ssh/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)
go func() {
for {
c, err := sshL.Accept()
if err != nil {
log.Println("SSH ACCEPT ERR", err)
break
}
_, chans, reqs, err := ssh.NewServerConn(c, config)
if err != nil {
log.Println("failed to handshake:", err)
continue
}
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 {
panic("could not accept channel.")
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "shell" request.
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
switch req.Type {
case "shell":
ok = true
if len(req.Payload) > 0 {
// We don't accept any
// commands, only the
// default shell.
ok = false
}
}
req.Reply(ok, nil)
}
}(requests)
term := terminal.NewTerminal(channel, "> ")
go func() {
defer channel.Close()
for {
line, err := term.ReadLine()
if err != nil {
break
}
fmt.Println(line)
}
}()
}
}
}()
err = sshttpListener.Dispatch()
if err != nil {
log.Fatalln("failed to dispatch:", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment