Adding current work on easyssh
This commit is contained in:
parent
bf216fa4fd
commit
7308727a1c
146
client.go
Normal file
146
client.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package easyssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client wraps an SSH Client
|
||||||
|
type Client struct {
|
||||||
|
*ssh.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial starts an ssh connection to the provided server
|
||||||
|
func Dial(network, addr string, config *ssh.ClientConfig) (*Client, error) {
|
||||||
|
c, err := ssh.Dial(network, addr, config)
|
||||||
|
return &Client{c}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new SSH Client.
|
||||||
|
func NewClient(c ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) *Client {
|
||||||
|
|
||||||
|
client := ssh.NewClient(c, chans, reqs)
|
||||||
|
return &Client{client}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalForward performs a port forwarding over the ssh connection - ssh -L. Client will bind to the local address, and will tunnel those requests to host addr
|
||||||
|
func (c *Client) LocalForward(laddr, raddr *net.TCPAddr) error {
|
||||||
|
|
||||||
|
ln, err := net.ListenTCP("tcp", laddr) //tie to the client connection
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("Listening on address: ", ln.Addr().String())
|
||||||
|
|
||||||
|
quit := make(chan bool)
|
||||||
|
|
||||||
|
go func() { // Handle incoming connections on this new listener
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil { // Unable to accept new connection - listener likely closed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func(conn net.Conn) {
|
||||||
|
conn2, err := c.DialTCP("tcp", laddr, raddr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func(conn, conn2 net.Conn) {
|
||||||
|
|
||||||
|
close := func() {
|
||||||
|
conn.Close()
|
||||||
|
conn2.Close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
go CopyReadWriters(conn, conn2, close)
|
||||||
|
|
||||||
|
}(conn, conn2)
|
||||||
|
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Wait()
|
||||||
|
|
||||||
|
ln.Close()
|
||||||
|
quit <- true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteForward forwards a remote port - ssh -R
|
||||||
|
func (c *Client) RemoteForward(remote, local string) error {
|
||||||
|
ln, err := c.Listen("tcp", remote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
quit := make(chan bool)
|
||||||
|
|
||||||
|
go func() { // Handle incoming connections on this new listener
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil { // Unable to accept new connection - listener likely closed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
conn2, err := net.Dial("tcp", local)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
close := func() {
|
||||||
|
conn.Close()
|
||||||
|
conn2.Close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
go CopyReadWriters(conn, conn2, close)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Wait()
|
||||||
|
ln.Close()
|
||||||
|
quit <- true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleOpenChannel requests that the remote end accept a channel request and if accepted,
|
||||||
|
// passes the newly opened channel and requests to the provided handler
|
||||||
|
func (c *Client) HandleOpenChannel(channelName string, handler ChannelMultipleRequestsHandler, data ...byte) error {
|
||||||
|
ch, reqs, err := c.OpenChannel(channelName, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handler.HandleMultipleRequests(reqs, c.Conn, channelName, ch)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleOpenChannelFunc requests that the remote end accept a channel request and if accepted,
|
||||||
|
// passes the newly opened channel and requests to the provided handler function
|
||||||
|
func (c *Client) HandleOpenChannelFunc(channelName string, handler ChannelMultipleRequestsHandlerFunc, data ...byte) error {
|
||||||
|
|
||||||
|
return c.HandleOpenChannel(channelName, ChannelMultipleRequestsHandlerFunc(handler), data...)
|
||||||
|
}
|
292
common.go
Normal file
292
common.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package easyssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ConnHandler is a top level SSH Manager. Objects implementing the ConnHandler are responsible for managing incoming Channels and Global Requests
|
||||||
|
type ConnHandler interface {
|
||||||
|
HandleSSHConn(ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnHandlerFunc is an adapter that allows regular functions to act as SSH Connection Handlers
|
||||||
|
type ConnHandlerFunc func(ssh.Conn, <-chan ssh.NewChannel, <-chan *ssh.Request)
|
||||||
|
|
||||||
|
// HandleSSHConn calls f(sshConn, chans, reqs)
|
||||||
|
func (f ConnHandlerFunc) HandleSSHConn(sshConn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
||||||
|
f(sshConn, chans, reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelHandler handles channel requests for a given channel type
|
||||||
|
type ChannelHandler interface {
|
||||||
|
HandleChannel(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelHandlerFunc is an adapter that allows regular functions to act as SSH Channel Handlers
|
||||||
|
type ChannelHandlerFunc func(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn)
|
||||||
|
|
||||||
|
// HandleChannel calls f(channelType, channel, reqs, sshConn)
|
||||||
|
func (f ChannelHandlerFunc) HandleChannel(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
f(newChannel, channel, reqs, sshConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultipleChannelsHandler handles a chan of all SSH channel requests for a connection
|
||||||
|
type MultipleChannelsHandler interface {
|
||||||
|
HandleChannels(chans <-chan ssh.NewChannel, sshConn ssh.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultipleChannelsHandlerFunc is an adapter that allows regular functions to act as SSH Multiple Channels Handlers
|
||||||
|
type MultipleChannelsHandlerFunc func(chans <-chan ssh.NewChannel, sshConn ssh.Conn)
|
||||||
|
|
||||||
|
// HandleChannels calls f(chans, sshConn)
|
||||||
|
func (f MultipleChannelsHandlerFunc) HandleChannels(chans <-chan ssh.NewChannel, sshConn ssh.Conn) {
|
||||||
|
f(chans, sshConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalMultipleRequestsHandler handles global (not tied to a channel) out-of-band SSH Requests
|
||||||
|
type GlobalMultipleRequestsHandler interface {
|
||||||
|
HandleRequests(<-chan *ssh.Request, ssh.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalMultipleRequestsHandlerFunc is an adaper to allow regular functions to act as a Global Requests Handler
|
||||||
|
type GlobalMultipleRequestsHandlerFunc func(<-chan *ssh.Request, ssh.Conn)
|
||||||
|
|
||||||
|
// HandleRequests calls f(reqs, sshConn)
|
||||||
|
func (f GlobalMultipleRequestsHandlerFunc) HandleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
f(reqs, sshConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscardGlobalMultipleRequests is a wrapper around ssh.DiscardRequests. Ignores ssh ServerConn
|
||||||
|
func DiscardGlobalMultipleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
ssh.DiscardRequests(reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscardRequest appropriately discards SSH Requests, returning responses to those that expect it
|
||||||
|
func DiscardRequest(req *ssh.Request) {
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalRequestHandler handles global (not tied to a channel) out-of-band SSH Requests
|
||||||
|
type GlobalRequestHandler interface {
|
||||||
|
HandleRequest(*ssh.Request, ssh.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalRequestHandlerFunc is an adaper to allow regular functions to act as a Global Request Handler
|
||||||
|
type GlobalRequestHandlerFunc func(*ssh.Request, ssh.Conn)
|
||||||
|
|
||||||
|
// HandleRequest calls f(reqs, sshConn)
|
||||||
|
func (f GlobalRequestHandlerFunc) HandleRequest(req *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
f(req, sshConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelMultipleRequestsHandler handles tied to a channel out-of-band SSH Requests
|
||||||
|
type ChannelMultipleRequestsHandler interface {
|
||||||
|
HandleMultipleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelMultipleRequestsHandlerFunc is an adaper to allow regular functions to act as a Channel Requests Handler
|
||||||
|
type ChannelMultipleRequestsHandlerFunc func(reqs <-chan *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel)
|
||||||
|
|
||||||
|
// HandleMultipleRequests calls f(reqs, sshConn, channelType, channel)
|
||||||
|
func (f ChannelMultipleRequestsHandlerFunc) HandleMultipleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel) {
|
||||||
|
f(reqs, sshConn, channelType, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscardChannelMultipleRequests is a wrapper around ssh.DiscardRequests. Ignores ssh ServerConn
|
||||||
|
func DiscardChannelMultipleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel) {
|
||||||
|
ssh.DiscardRequests(reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelRequestHandler handles tied to a channel out-of-band SSH Requests
|
||||||
|
type ChannelRequestHandler interface {
|
||||||
|
HandleRequest(req *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelRequestHandlerFunc is an adaper to allow regular functions to act as a Global Request Handler
|
||||||
|
type ChannelRequestHandlerFunc func(req *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel)
|
||||||
|
|
||||||
|
// HandleRequest calls f(reqs, sshConn, channelType, channel)
|
||||||
|
func (f ChannelRequestHandlerFunc) HandleRequest(req *ssh.Request, sshConn ssh.Conn, channelType string, channel ssh.Channel) {
|
||||||
|
f(req, sshConn, channelType, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHConnHandler is an SSH Channel multiplexer. It matches Channel types and calls the handler for the corresponding type
|
||||||
|
type SSHConnHandler struct {
|
||||||
|
MultipleChannelsHandler
|
||||||
|
GlobalMultipleRequestsHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalMultipleRequestsMux is an SSH Global Requests multiplexer. It matches Channel types and calls the handler for the corresponding type - can be used as GlobalMultipleRequestsHandler
|
||||||
|
type GlobalMultipleRequestsMux struct {
|
||||||
|
requestMutex sync.RWMutex
|
||||||
|
requests map[string]GlobalRequestHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGlobalMultipleRequestsMux creates and returns a GlobalMultipleRequestsHandler that performs multiplexing of request types with dispatching to GlobalRequestHandlers
|
||||||
|
func NewGlobalMultipleRequestsMux() *GlobalMultipleRequestsMux {
|
||||||
|
return &GlobalMultipleRequestsMux{requests: map[string]GlobalRequestHandler{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelsMux is an SSH Channel multiplexer. It matches Channel types and calls the handler for the corresponding type - Can be used as ChannelsHandler
|
||||||
|
type ChannelsMux struct {
|
||||||
|
channelMutex sync.RWMutex
|
||||||
|
channels map[string]ChannelHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChannelsMux creates and returns a MultipleChannelsHandler that performs multiplexing of request types with dispatching to ChannelHandlers
|
||||||
|
func NewChannelsMux() *ChannelsMux {
|
||||||
|
return &ChannelsMux{channels: map[string]ChannelHandler{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSSHConnHandler creates and returns a basic working ConnHandler to provide a minimal "working" SSH server
|
||||||
|
func NewSSHConnHandler() *SSHConnHandler {
|
||||||
|
return &SSHConnHandler{MultipleChannelsHandler: NewChannelsMux(), GlobalMultipleRequestsHandler: NewGlobalMultipleRequestsMux()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleSSHConn manages incoming channel and out-of-band requests. It discards out-of-band requests and dispatches channel requests if a ChannelHandler is registered for a given Channel Type
|
||||||
|
func (s *SSHConnHandler) HandleSSHConn(sshConn ssh.Conn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
||||||
|
go globalRequestsHandler{s}.HandleRequests(reqs, sshConn)
|
||||||
|
go channelsHandler{s}.HandleChannels(chans, sshConn)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRequest registers the GlobalRequestHandler for the given Channel Type. If a GlobalRequestHandler was already registered for the type, HandleRequest panics
|
||||||
|
func (s *GlobalMultipleRequestsMux) HandleRequest(requestType string, handler GlobalRequestHandler) {
|
||||||
|
s.requestMutex.Lock()
|
||||||
|
defer s.requestMutex.Unlock()
|
||||||
|
_, ok := s.requests[requestType]
|
||||||
|
if ok {
|
||||||
|
panic("easyssh: GlobalRequestHandler already registered for " + requestType)
|
||||||
|
}
|
||||||
|
s.requests[requestType] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRequestFunc registers the Channel Handler function for the provided Request Type
|
||||||
|
func (s *GlobalMultipleRequestsMux) HandleRequestFunc(requestType string, f GlobalRequestHandlerFunc) {
|
||||||
|
s.HandleRequest(requestType, GlobalRequestHandlerFunc(f))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannel registers the ChannelHandler for the given Channel Type. If a ChannelHandler was already registered for the type, HandleChannel panics
|
||||||
|
func (s *ChannelsMux) HandleChannel(channelType string, handler ChannelHandler) {
|
||||||
|
s.channelMutex.Lock()
|
||||||
|
defer s.channelMutex.Unlock()
|
||||||
|
_, ok := s.channels[channelType]
|
||||||
|
if ok {
|
||||||
|
panic("easyssh: ChannelHandler already registered for " + channelType)
|
||||||
|
}
|
||||||
|
s.channels[channelType] = handler
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannelFunc registers the Channel Handler function for the provided Channel Type
|
||||||
|
func (s *ChannelsMux) HandleChannelFunc(channelType string, f ChannelHandlerFunc) {
|
||||||
|
s.HandleChannel(channelType, ChannelHandlerFunc(f))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRequests handles global out-of-band SSH Requests -
|
||||||
|
func (s *GlobalMultipleRequestsMux) HandleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
for req := range reqs {
|
||||||
|
t := req.Type
|
||||||
|
s.requestMutex.RLock()
|
||||||
|
handler, ok := s.requests[t]
|
||||||
|
if !ok {
|
||||||
|
DiscardRequest(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.requestMutex.RUnlock()
|
||||||
|
|
||||||
|
go handler.HandleRequest(req, sshConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannels acts a a mux for incoming channel requests
|
||||||
|
func (s *ChannelsMux) HandleChannels(chans <-chan ssh.NewChannel, sshConn ssh.Conn) {
|
||||||
|
for newChannel := range chans {
|
||||||
|
|
||||||
|
log.Printf("Received channel: %v", newChannel.ChannelType())
|
||||||
|
|
||||||
|
// Check the type of channel
|
||||||
|
t := newChannel.ChannelType()
|
||||||
|
s.channelMutex.RLock()
|
||||||
|
handler, ok := s.channels[t]
|
||||||
|
|
||||||
|
s.channelMutex.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Unknown channel type: %s", t)
|
||||||
|
|
||||||
|
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
channel, requests, err := newChannel.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not accept channel (%s)", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handler.HandleChannel(newChannel, channel, requests, sshConn)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSSHConnHandler is an SSH Server Handler
|
||||||
|
var DefaultSSHConnHandler = &SSHConnHandler{}
|
||||||
|
|
||||||
|
// DefaultMultipleChannelsHandler is the ChannelMux and is used to handle all incoming channel requests
|
||||||
|
var DefaultMultipleChannelsHandler = NewChannelsMux()
|
||||||
|
|
||||||
|
// DefaultGlobalMultipleRequestsHandler is a GlobalMultipleRequestsHandler that by default discards all incoming global requests
|
||||||
|
var DefaultGlobalMultipleRequestsHandler = NewGlobalMultipleRequestsMux()
|
||||||
|
|
||||||
|
// HandleRequest registers the given handler with the DefaultGlobalMultipleRequestsHandler
|
||||||
|
func HandleRequest(requestType string, handler GlobalRequestHandler) {
|
||||||
|
DefaultGlobalMultipleRequestsHandler.HandleRequest(requestType, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRequestFunc registers the given handler function with the DefaultGlobalMultipleRequestsHandler
|
||||||
|
func HandleRequestFunc(requestType string, handler GlobalRequestHandlerFunc) {
|
||||||
|
DefaultGlobalMultipleRequestsHandler.HandleRequestFunc(requestType, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannel registers the given handler under the channelType with the DefaultMultipleChannelsHandler
|
||||||
|
func HandleChannel(channelType string, handler ChannelHandler) {
|
||||||
|
DefaultMultipleChannelsHandler.HandleChannel(channelType, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannelFunc registers the given handler function under the channelType with the DefaultMultipleChannelsHandler
|
||||||
|
func HandleChannelFunc(channelType string, handler ChannelHandlerFunc) {
|
||||||
|
DefaultMultipleChannelsHandler.HandleChannelFunc(channelType, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelsHandler struct {
|
||||||
|
s *SSHConnHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleChannels is a wrapper, tests if the SSHConnHandler has a MultipleChannelsHandler, and if not uses the default one
|
||||||
|
func (s channelsHandler) HandleChannels(chans <-chan ssh.NewChannel, sshConn ssh.Conn) {
|
||||||
|
handler := s.s.MultipleChannelsHandler
|
||||||
|
if handler == nil {
|
||||||
|
handler = DefaultMultipleChannelsHandler
|
||||||
|
}
|
||||||
|
handler.HandleChannels(chans, sshConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type globalRequestsHandler struct {
|
||||||
|
s *SSHConnHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRequests is a wrapper, tests if the SSHConnHandler has a GlobalMultipleRequestsHandler, and if not uses the default one
|
||||||
|
func (s globalRequestsHandler) HandleRequests(reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
handler := s.s.GlobalMultipleRequestsHandler
|
||||||
|
if handler == nil {
|
||||||
|
handler = DefaultGlobalMultipleRequestsHandler
|
||||||
|
}
|
||||||
|
handler.HandleRequests(reqs, sshConn)
|
||||||
|
}
|
56
doc.go
Normal file
56
doc.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Package easyssh provides a simple wrapper around the standard SSH library. Designed to be like net/http but for ssh.
|
||||||
|
Both server and client implementations are provided.
|
||||||
|
|
||||||
|
Creating a client is similar to creating a normal ssh client
|
||||||
|
|
||||||
|
client, err := easyssh.Dial("tcp", "localhost:2022", config)
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
Once a client is created, you can do a number of things with it:
|
||||||
|
Local Port Forwarding
|
||||||
|
|
||||||
|
err = client.LocalForward("localhost:8000", "localhost:6060")
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
Remote Port Forwarding
|
||||||
|
|
||||||
|
err = client.RemoteForward("localhost:8000", "localhost:6060")
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
Create a session - used for executing remote commands or getting a remote shell
|
||||||
|
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
out, err := session.Output("whoami")
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Getting started with an SSH server is easy with easyssh
|
||||||
|
|
||||||
|
easyssh.HandleChannel(easyssh.SessionRequest, easyssh.SessionHandler())
|
||||||
|
easyssh.HandleChannel(easyssh.DirectForwardRequest, easyssh.DirectPortForwardHandler())
|
||||||
|
easyssh.HandleRequestFunc(easyssh.RemoteForwardRequest, easyssh.TCPIPForwardRequest)
|
||||||
|
|
||||||
|
easyssh.ListenAndServe(":2022", sshServerConfig, nil)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
There are a lot of layers of ssh communication, and easyssh makes it easy to control at the level desired.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package easyssh
|
63
example_client_test.go
Normal file
63
example_client_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package easyssh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"dev.justinjudd.org/justin/easyssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleDial() {
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: "test",
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.Password("test"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
conn, err := easyssh.Dial("tcp", "localhost:2022", config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect: %s", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleClient_LocalForward() {
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: "test",
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.Password("test"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
conn, err := easyssh.Dial("tcp", "localhost:2022", config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect: %s", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
err = conn.LocalForward("localhost:8000", "localhost:6060")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to forward local port: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleClient_RemoteForward() {
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: "test",
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.Password("test"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
conn, err := easyssh.Dial("tcp", "localhost:2022", config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect: %s", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
err = conn.RemoteForward("localhost:8000", "localhost:6060")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to forward local port: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
example_handler_test.go
Normal file
29
example_handler_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package easyssh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dev.justinjudd.org/justin/easyssh"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testHandler struct{}
|
||||||
|
|
||||||
|
func (testHandler) HandleChannel(nCh ssh.NewChannel, ch ssh.Channel, reqs <-chan *ssh.Request, conn *ssh.ServerConn) {
|
||||||
|
defer ch.Close()
|
||||||
|
// Do something
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleChannelsMux_HandleChannel() {
|
||||||
|
handler := easyssh.NewChannelsMux()
|
||||||
|
|
||||||
|
handler.HandleChannel("test", testHandler{})
|
||||||
|
|
||||||
|
test2Handler := func(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn *ssh.ServerConn) {
|
||||||
|
defer channel.Close()
|
||||||
|
ssh.DiscardRequests(reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.HandleChannelFunc("test2", test2Handler)
|
||||||
|
|
||||||
|
handler.HandleChannel("anotherTest2", easyssh.ChannelHandlerFunc(test2Handler))
|
||||||
|
}
|
46
example_tcpip_test.go
Normal file
46
example_tcpip_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package easyssh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"dev.justinjudd.org/justin/easyssh"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleDirectPortForwardChannel() {
|
||||||
|
s := easyssh.Server{Addr: ":2022"}
|
||||||
|
|
||||||
|
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||||
|
if err != nil {
|
||||||
|
// Failed to load private key (./id_rsa)
|
||||||
|
}
|
||||||
|
|
||||||
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||||
|
if err != nil {
|
||||||
|
// Failed to parse private key
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &ssh.ServerConfig{
|
||||||
|
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||||
|
if c.User() == "test" && string(pass) == "test" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("password rejected for %s", c.User())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.AddHostKey(private)
|
||||||
|
|
||||||
|
s.Config = config
|
||||||
|
|
||||||
|
handler := easyssh.NewServerHandler()
|
||||||
|
channelHandler := easyssh.NewChannelsMux()
|
||||||
|
|
||||||
|
channelHandler.HandleChannel(easyssh.DirectForwardRequest, easyssh.DirectPortForwardHandler())
|
||||||
|
handler.MultipleChannelsHandler = channelHandler
|
||||||
|
|
||||||
|
s.Handler = handler
|
||||||
|
|
||||||
|
s.ListenAndServe()
|
||||||
|
}
|
66
example_test.go
Normal file
66
example_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package easyssh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"dev.justinjudd.org/justin/easyssh"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleServer_ListenAndServe() {
|
||||||
|
s := easyssh.Server{Addr: ":2022"}
|
||||||
|
|
||||||
|
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||||
|
if err != nil {
|
||||||
|
// Failed to load private key (./id_rsa)
|
||||||
|
}
|
||||||
|
|
||||||
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||||
|
if err != nil {
|
||||||
|
// Failed to parse private key
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &ssh.ServerConfig{
|
||||||
|
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||||
|
if c.User() == "test" && string(pass) == "test" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("password rejected for %s", c.User())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.AddHostKey(private)
|
||||||
|
|
||||||
|
s.Config = config
|
||||||
|
|
||||||
|
handler := easyssh.NewServerHandler()
|
||||||
|
channelHandler := easyssh.NewChannelsMux()
|
||||||
|
|
||||||
|
channelHandler.HandleChannel(easyssh.SessionRequest, easyssh.SessionHandler())
|
||||||
|
handler.MultipleChannelsHandler = channelHandler
|
||||||
|
|
||||||
|
s.Handler = handler
|
||||||
|
|
||||||
|
s.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleListenAndServe() {
|
||||||
|
easyssh.HandleChannel(easyssh.SessionRequest, easyssh.SessionHandler())
|
||||||
|
easyssh.HandleChannel(easyssh.DirectForwardRequest, easyssh.DirectPortForwardHandler())
|
||||||
|
easyssh.HandleRequestFunc(easyssh.RemoteForwardRequest, easyssh.TCPIPForwardRequest)
|
||||||
|
|
||||||
|
easyssh.ListenAndServe(":2022", config, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleChannelsMux_HandleChannelFunc() {
|
||||||
|
handler := easyssh.NewChannelsMux()
|
||||||
|
|
||||||
|
testHandler := func(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn *ssh.ServerConn) {
|
||||||
|
defer channel.Close()
|
||||||
|
ssh.DiscardRequests(reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.HandleChannelFunc("test", testHandler)
|
||||||
|
|
||||||
|
}
|
157
server.go
Normal file
157
server.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package easyssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server represents an SSH Server. The SSH ServerConfig must be provided
|
||||||
|
type Server struct {
|
||||||
|
Addr string
|
||||||
|
Config *ssh.ServerConfig
|
||||||
|
Handler ConnHandler
|
||||||
|
*ssh.ServerConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on the TCP address s.Addr and then calls Serve to handle requests on incoming connections. If s.Addr is blank, ":ssh" is used
|
||||||
|
func (s *Server) ListenAndServe() error {
|
||||||
|
addr := s.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":ssh"
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.Serve(ln.(*net.TCPListener))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve accepts incoming connections on the provided listener.and reads global SSH Channel and Out-of-band requests and calls s,ConnHandler to handle them
|
||||||
|
func (s *Server) Serve(l net.Listener) error {
|
||||||
|
defer l.Close()
|
||||||
|
log.Print("SSH Server started listening on: ", l.Addr())
|
||||||
|
for {
|
||||||
|
tcpConn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.newConn(tcpConn)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go c.serve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleOpenChannel requests that the remote end accept a channel request and if accepted,
|
||||||
|
// passes the newly opened channel and requests to the provided handler
|
||||||
|
func (s *Server) HandleOpenChannel(channelName string, handler ChannelMultipleRequestsHandler, data ...byte) error {
|
||||||
|
ch, reqs, err := s.OpenChannel(channelName, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handler.HandleMultipleRequests(reqs, s.ServerConn, channelName, ch)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleOpenChannelFunc requests that the remote end accept a channel request and if accepted,
|
||||||
|
// passes the newly opened channel and requests to the provided handler function
|
||||||
|
func (s *Server) HandleOpenChannelFunc(channelName string, handler ChannelMultipleRequestsHandlerFunc, data ...byte) error {
|
||||||
|
|
||||||
|
return s.HandleOpenChannel(channelName, ChannelMultipleRequestsHandlerFunc(handler), data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
server *Server
|
||||||
|
remoteAddr string
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) serve() {
|
||||||
|
sshConn, chans, reqs, err := ssh.NewServerConn(c.conn, c.server.Config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.server.ServerConn = sshConn
|
||||||
|
log.Print("New ssh connection from: ", c.conn.RemoteAddr())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sshConn.Wait()
|
||||||
|
log.Print("Closing ssh connection from: ", c.conn.RemoteAddr())
|
||||||
|
if c.conn != nil {
|
||||||
|
c.conn.Close()
|
||||||
|
//c.conn = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Use default ConnHandler if one isn't provided
|
||||||
|
serverHandler{c.server}.HandleSSHConn(sshConn, chans, reqs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) newConn(netConn net.Conn) (*conn, error) {
|
||||||
|
c := new(conn)
|
||||||
|
c.remoteAddr = netConn.RemoteAddr().String()
|
||||||
|
c.server = s
|
||||||
|
c.conn = netConn
|
||||||
|
return c, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSessionServerHandler creates a ConnHandler to provide a more standard SSH server providing sessions
|
||||||
|
func NewSessionServerHandler() *SSHConnHandler {
|
||||||
|
s := SSHConnHandler{}
|
||||||
|
channelHandler := NewChannelsMux()
|
||||||
|
|
||||||
|
channelHandler.HandleChannel(SessionRequest, SessionHandler())
|
||||||
|
s.MultipleChannelsHandler = channelHandler
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStandardSSHServerHandler returns a server handler that can deal with ssh sessions and both local and remote port forwarding
|
||||||
|
func NewStandardSSHServerHandler() *SSHConnHandler {
|
||||||
|
s := NewSSHConnHandler()
|
||||||
|
|
||||||
|
chHandler := NewChannelsMux()
|
||||||
|
chHandler.HandleChannel(SessionRequest, SessionHandler())
|
||||||
|
chHandler.HandleChannel(DirectForwardRequest, DirectPortForwardHandler())
|
||||||
|
|
||||||
|
s.MultipleChannelsHandler = chHandler
|
||||||
|
|
||||||
|
globalHandler := NewGlobalMultipleRequestsMux()
|
||||||
|
globalHandler.HandleRequest(RemoteForwardRequest, TCPIPForwardRequestHandler())
|
||||||
|
|
||||||
|
s.GlobalMultipleRequestsHandler = globalHandler
|
||||||
|
return s
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on the given tcp address addr and then calls Serve with handler.
|
||||||
|
// If handler is nil, the DefaultServerHandler is used.
|
||||||
|
func ListenAndServe(addr string, conf *ssh.ServerConfig, handler ConnHandler) error {
|
||||||
|
s := &Server{addr, conf, handler, nil}
|
||||||
|
return s.ListenAndServe()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve accepts incoming SSH connections on the listener l.
|
||||||
|
// If handler is nil, the DefaultServerHandler is used.
|
||||||
|
func Serve(l net.Listener, conf *ssh.ServerConfig, handler ConnHandler) error {
|
||||||
|
s := &Server{Config: conf, Handler: handler}
|
||||||
|
return s.Serve(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverHandler struct {
|
||||||
|
s *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeSSH is a wrapper, tests if the server has a ServerHandler, and if not uses the default one
|
||||||
|
func (s serverHandler) HandleSSHConn(conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
|
||||||
|
handler := s.s.Handler
|
||||||
|
if handler == nil {
|
||||||
|
handler = DefaultSSHConnHandler
|
||||||
|
}
|
||||||
|
handler.HandleSSHConn(conn, chans, reqs)
|
||||||
|
}
|
313
session.go
Normal file
313
session.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
package easyssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/kr/pty"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request types used in sessions - RFC 4254 6.X
|
||||||
|
const (
|
||||||
|
SessionRequest = "session" // RFC 4254 6.1
|
||||||
|
PTYRequest = "pty-req" // RFC 4254 6.2
|
||||||
|
X11Request = "x11-req" // RFC 4254 6.3.1
|
||||||
|
X11ChannelRequest = "x11" // RFC 4254 6.3.2
|
||||||
|
EnvironmentRequest = "env" // RFC 4254 6.4
|
||||||
|
ShellRequest = "shell" // RFC 4254 6.5
|
||||||
|
ExecRequest = "exec" // RFC 4254 6.5
|
||||||
|
SubsystemRequest = "subsystem" // RFC 4254 6.5
|
||||||
|
WindowDimensionChangeRequest = "window-change" // RFC 4254 6.7
|
||||||
|
FlowControlRequest = "xon-off" // RFC 4254 6.8
|
||||||
|
SignalRequest = "signal" // RFC 4254 6.9
|
||||||
|
ExitStatusRequest = "exit-status" // RFC 4254 6.10
|
||||||
|
ExitSignalRequest = "exit-signal" // RFC 4254 6.10
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionHandler returns a ChannelHandler that implements standard SSH Sessions for PTY, shell, and exec capabilities
|
||||||
|
func SessionHandler() ChannelHandler { return ChannelHandlerFunc(SessionChannel) }
|
||||||
|
|
||||||
|
// SessionChannel acts as an SSH Session ChannelHandler
|
||||||
|
func SessionChannel(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
// Get system shell
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
c := exec.Command(shell)
|
||||||
|
f, err := pty.Start(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to start shell: %s", shell)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
terminalModes := ssh.TerminalModes{}
|
||||||
|
// TODO: Find out if I should do anything with the terminal modes. Do any of the ptys/ttys know what to do with them
|
||||||
|
|
||||||
|
close := func() {
|
||||||
|
channel.Close()
|
||||||
|
err := c.Wait()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to exit bash (%s)", err)
|
||||||
|
}
|
||||||
|
log.Printf("session closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(in <-chan *ssh.Request) {
|
||||||
|
|
||||||
|
env := []string{}
|
||||||
|
var command *exec.Cmd
|
||||||
|
|
||||||
|
for req := range in {
|
||||||
|
ok := false
|
||||||
|
|
||||||
|
switch req.Type {
|
||||||
|
case PTYRequest:
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
pty := ptyReq{}
|
||||||
|
err = ssh.Unmarshal(req.Payload, &pty)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to decode pty request: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
setWinsize(f.Fd(), pty.Width, pty.Height)
|
||||||
|
log.Printf("pty-req '%s'", pty.Term)
|
||||||
|
|
||||||
|
type termModeStruct struct {
|
||||||
|
//Key byte
|
||||||
|
Key uint8
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
termModes := []termModeStruct{}
|
||||||
|
working := []byte(pty.TermModes)
|
||||||
|
for {
|
||||||
|
if len(working) < 5 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tm := termModeStruct{}
|
||||||
|
|
||||||
|
tm.Key = working[0]
|
||||||
|
tm.Val = binary.BigEndian.Uint32(working[1:5])
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
err = ssh.Unmarshal(working, &tm)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
termModes = append(termModes, tm)
|
||||||
|
terminalModes[tm.Key] = tm.Val
|
||||||
|
working = working[5:]
|
||||||
|
|
||||||
|
}
|
||||||
|
go CopyReadWriters(channel, f, close)
|
||||||
|
|
||||||
|
case WindowDimensionChangeRequest:
|
||||||
|
win := windowDimensionReq{}
|
||||||
|
err = ssh.Unmarshal(req.Payload, &win)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading window dimension change request: %s", err.Error())
|
||||||
|
}
|
||||||
|
setWinsize(f.Fd(), win.Width, win.Height)
|
||||||
|
continue //no response according to RFC 4254 6.7
|
||||||
|
case ShellRequest: // Shell requests should not have a payload - RFC 4254 6.7
|
||||||
|
ok = true
|
||||||
|
go CopyReadWriters(channel, f, close)
|
||||||
|
|
||||||
|
case ExecRequest:
|
||||||
|
ok = true
|
||||||
|
var cmd execRequest
|
||||||
|
err = ssh.Unmarshal(req.Payload, &cmd)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
command = exec.Command("sh", "-c", cmd.Command) // Let shell do the parsing
|
||||||
|
log.Printf("exec starting: %s", cmd.Command)
|
||||||
|
//c.Env = append(c.Env, env...)
|
||||||
|
|
||||||
|
exitStatus := exitStatusReq{}
|
||||||
|
|
||||||
|
fd, err := pty.Start(command)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to wrap exec command in pty\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
execClose := func() {
|
||||||
|
channel.Close()
|
||||||
|
log.Printf("exec finished: %s", cmd.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fd.Close()
|
||||||
|
go CopyReadWriters(channel, fd, execClose)
|
||||||
|
err = command.Wait()
|
||||||
|
|
||||||
|
/*
|
||||||
|
command.Stdout = channel
|
||||||
|
command.Stderr = channel
|
||||||
|
*/
|
||||||
|
|
||||||
|
//command.Stdin = channel // TODO: test how stdin works on exec on openssh server
|
||||||
|
//err = command.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error running exec : %s", err.Error())
|
||||||
|
e, ok := err.(*exec.ExitError)
|
||||||
|
errVal := 1
|
||||||
|
if ok {
|
||||||
|
status := e.Sys().(syscall.WaitStatus)
|
||||||
|
if status.Exited() {
|
||||||
|
errVal = status.ExitStatus()
|
||||||
|
exitStatus.ExitStatus = uint32(errVal)
|
||||||
|
channel.SendRequest(ExitStatusRequest, false, ssh.Marshal(exitStatus))
|
||||||
|
} else if status.Signaled() { // What is the difference between Siglnal and StopSignal
|
||||||
|
e := exitSignalReq{}
|
||||||
|
e.SignalName = status.Signal().String()
|
||||||
|
e.CoreDumped = status.CoreDump()
|
||||||
|
// TODO: Figure out other two fields
|
||||||
|
channel.SendRequest(ExitSignalRequest, false, ssh.Marshal(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
channel.SendRequest(ExitStatusRequest, false, ssh.Marshal(exitStatus))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Reply(ok, nil)
|
||||||
|
close()
|
||||||
|
return
|
||||||
|
|
||||||
|
case EnvironmentRequest:
|
||||||
|
ok = true
|
||||||
|
e := envReq{}
|
||||||
|
err = ssh.Unmarshal(req.Payload, &e)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
env = append(env, fmt.Sprintf("%s=%s", e.Name, e.Value))
|
||||||
|
|
||||||
|
case SignalRequest:
|
||||||
|
ok = true
|
||||||
|
sig := signalRequest{}
|
||||||
|
ssh.Unmarshal(req.Payload, &sig)
|
||||||
|
log.Println("Received Signal: ", sig.Signal)
|
||||||
|
|
||||||
|
s := signalsMap[sig.Signal]
|
||||||
|
if command != nil {
|
||||||
|
|
||||||
|
command.Process.Signal(s)
|
||||||
|
} else {
|
||||||
|
c.Process.Signal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.Reply(ok, nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}(reqs)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var signalsMap = map[ssh.Signal]os.Signal{
|
||||||
|
ssh.SIGABRT: syscall.SIGABRT,
|
||||||
|
ssh.SIGALRM: syscall.SIGALRM,
|
||||||
|
ssh.SIGFPE: syscall.SIGFPE,
|
||||||
|
ssh.SIGHUP: syscall.SIGHUP,
|
||||||
|
ssh.SIGILL: syscall.SIGILL,
|
||||||
|
ssh.SIGINT: syscall.SIGINT,
|
||||||
|
ssh.SIGKILL: syscall.SIGKILL,
|
||||||
|
ssh.SIGPIPE: syscall.SIGPIPE,
|
||||||
|
ssh.SIGQUIT: syscall.SIGQUIT,
|
||||||
|
ssh.SIGSEGV: syscall.SIGSEGV,
|
||||||
|
ssh.SIGTERM: syscall.SIGTERM,
|
||||||
|
ssh.SIGUSR1: syscall.SIGUSR1,
|
||||||
|
ssh.SIGUSR2: syscall.SIGUSR2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyReadWriters copies biderectionally - output from a to b, and output of b into a. Calls the close function when unable to copy in either direction
|
||||||
|
func CopyReadWriters(a, b io.ReadWriter, close func()) {
|
||||||
|
var once sync.Once
|
||||||
|
go func() {
|
||||||
|
io.Copy(a, b)
|
||||||
|
once.Do(close)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
io.Copy(b, a)
|
||||||
|
once.Do(close)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// windowDimension represents channel request for window dimension change - RFC 4254 6.7
|
||||||
|
type windowDimensionReq struct {
|
||||||
|
Width uint32
|
||||||
|
Height uint32
|
||||||
|
WidthPixel uint32
|
||||||
|
HeightPixel uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptyReq represents the channel request for a PTY. RFC 4254 6.2
|
||||||
|
type ptyReq struct {
|
||||||
|
Term string
|
||||||
|
Width uint32
|
||||||
|
Height uint32
|
||||||
|
WidthPixel uint32
|
||||||
|
HeightPixel uint32
|
||||||
|
TermModes string
|
||||||
|
}
|
||||||
|
|
||||||
|
// envReq represents an "env" channel request - RFC 4254 6.4
|
||||||
|
type envReq struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// execRequest represents an "exec" channel request - RFC 4254 6.5
|
||||||
|
type execRequest struct {
|
||||||
|
Command string
|
||||||
|
}
|
||||||
|
|
||||||
|
// signalRequest represents a "signal" session channel request - RFC 4254 6.9
|
||||||
|
type signalRequest struct {
|
||||||
|
Signal ssh.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitStatusReq represents an exit status for "exec" requests - RFC 4254 6.10
|
||||||
|
type exitStatusReq struct {
|
||||||
|
ExitStatus uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitSignalReq represents an exit signal for "exec" requests - RFC 4254 6.10
|
||||||
|
type exitSignalReq struct {
|
||||||
|
SignalName string
|
||||||
|
CoreDumped bool
|
||||||
|
ErrorMessage string
|
||||||
|
LanguageTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// winsize stores the Height and Width of a terminal in rows/columns and pixels - for syscall - http://linux.die.net/man/4/tty_ioctl
|
||||||
|
type winsize struct {
|
||||||
|
Row uint16
|
||||||
|
Col uint16
|
||||||
|
XPixel uint16 // unused
|
||||||
|
YPixel uint16 // unused
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWinsize uses syscall to set pty window size
|
||||||
|
func setWinsize(fd uintptr, w, h uint32) {
|
||||||
|
log.Printf("Resize Window to %dx%d", w, h)
|
||||||
|
ws := &winsize{Col: uint16(w), Row: uint16(h)}
|
||||||
|
syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
}
|
173
tcpip.go
Normal file
173
tcpip.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package easyssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
|
||||||
|
const (
|
||||||
|
DirectForwardRequest = "direct-tcpip" // RFC 4254 7.2
|
||||||
|
RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1
|
||||||
|
ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2
|
||||||
|
CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1
|
||||||
|
)
|
||||||
|
|
||||||
|
// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request
|
||||||
|
type tcpipForward struct {
|
||||||
|
Host string
|
||||||
|
Port uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// directForward is struxture for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip"
|
||||||
|
type directForward struct {
|
||||||
|
Host1 string
|
||||||
|
Port1 uint32
|
||||||
|
Host2 string
|
||||||
|
Port2 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p directForward) String() string {
|
||||||
|
return fmt.Sprintf("CONNECT: %s:%d FROM: %s:%d", p.Host1, p.Port1, p.Host2, p.Port2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPIPForwardRequestHandler returns a GlobalRequestHandler that implements remote port forwarding - ssh -R
|
||||||
|
func TCPIPForwardRequestHandler() GlobalRequestHandler {
|
||||||
|
return GlobalRequestHandlerFunc(TCPIPForwardRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPIPForwardRequest fulfills RFC 4254 7.1 "tcpip-forward" request
|
||||||
|
//
|
||||||
|
// TODO: Need to add state to handle "cancel-tcpip-forward"
|
||||||
|
func TCPIPForwardRequest(req *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
|
||||||
|
t := tcpipForward{}
|
||||||
|
reply := (t.Port == 0) && req.WantReply
|
||||||
|
ssh.Unmarshal(req.Payload, &t)
|
||||||
|
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
|
||||||
|
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to listen on address: ", addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("Listening on address: ", ln.Addr().String())
|
||||||
|
|
||||||
|
quit := make(chan bool)
|
||||||
|
|
||||||
|
if reply { // Client sent port 0. let them know which port is actually being used
|
||||||
|
|
||||||
|
_, port, err := getHostPortFromAddr(ln.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, uint32(port))
|
||||||
|
t.Port = uint32(port)
|
||||||
|
req.Reply(true, b)
|
||||||
|
} else {
|
||||||
|
req.Reply(true, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() { // Handle incoming connections on this new listener
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil { // Unable to accept new connection - listener likely closed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func(conn net.Conn) {
|
||||||
|
p := directForward{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var portnum int
|
||||||
|
p.Host1 = t.Host
|
||||||
|
p.Port1 = t.Port
|
||||||
|
p.Host2, portnum, err = getHostPortFromAddr(conn.RemoteAddr())
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Port2 = uint32(portnum)
|
||||||
|
ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Open forwarded Channel: ", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ssh.DiscardRequests(reqs)
|
||||||
|
go func(ch ssh.Channel, conn net.Conn) {
|
||||||
|
|
||||||
|
close := func() {
|
||||||
|
ch.Close()
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
// log.Printf("forwarding closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
go CopyReadWriters(conn, ch, close)
|
||||||
|
|
||||||
|
}(ch, conn)
|
||||||
|
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
sshConn.Wait()
|
||||||
|
log.Println("Stop forwarding/listening on ", ln.Addr())
|
||||||
|
ln.Close()
|
||||||
|
quit <- true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
|
||||||
|
host, portString, err := net.SplitHostPort(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
port, err = strconv.Atoi(portString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectPortForwardHandler returns a ChannelHandler that implements standard SSH direct portforwarding
|
||||||
|
func DirectPortForwardHandler() ChannelHandler { return ChannelHandlerFunc(DirectPortForwardChannel) }
|
||||||
|
|
||||||
|
// DirectPortForwardChannel acts as an SSH Direct Port Forwarder - ssh -L
|
||||||
|
//
|
||||||
|
// Should be to channel type - "direct-tcpip" - RFC 4254 7.2
|
||||||
|
func DirectPortForwardChannel(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn) {
|
||||||
|
|
||||||
|
p := directForward{}
|
||||||
|
ssh.Unmarshal(newChannel.ExtraData(), &p)
|
||||||
|
log.Println(p)
|
||||||
|
|
||||||
|
go func(ch ssh.Channel, sshConn ssh.Conn) {
|
||||||
|
addr := fmt.Sprintf("%s:%d", p.Host1, p.Port1)
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
close := func() {
|
||||||
|
ch.Close()
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
//log.Printf("forwarding closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
go CopyReadWriters(conn, ch, close)
|
||||||
|
|
||||||
|
}(channel, sshConn)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user