package ssh
import (
"bytes"
"errors"
"fmt"
"net"
"sync"
"github.com/anmitsu/go-shlex"
gossh "golang.org/x/crypto/ssh"
)
type Session interface {
gossh .Channel
User () string
RemoteAddr () net .Addr
LocalAddr () net .Addr
Environ () []string
Exit (code int ) error
Command () []string
RawCommand () string
Subsystem () string
PublicKey () PublicKey
Context () Context
Permissions () Permissions
Pty () (Pty , <-chan Window , bool )
Signals (c chan <- Signal )
Break (c chan <- bool )
}
const maxSigBufSize = 128
func DefaultSessionHandler (srv *Server , conn *gossh .ServerConn , newChan gossh .NewChannel , ctx Context ) {
ch , reqs , err := newChan .Accept ()
if err != nil {
return
}
sess := &session {
Channel : ch ,
conn : conn ,
handler : srv .Handler ,
ptyCb : srv .PtyCallback ,
sessReqCb : srv .SessionRequestCallback ,
subsystemHandlers : srv .SubsystemHandlers ,
ctx : ctx ,
}
sess .handleRequests (reqs )
}
type session struct {
sync .Mutex
gossh .Channel
conn *gossh .ServerConn
handler Handler
subsystemHandlers map [string ]SubsystemHandler
handled bool
exited bool
pty *Pty
winch chan Window
env []string
ptyCb PtyCallback
sessReqCb SessionRequestCallback
rawCmd string
subsystem string
ctx Context
sigCh chan <- Signal
sigBuf []Signal
breakCh chan <- bool
}
func (sess *session ) Write (p []byte ) (n int , err error ) {
if sess .pty != nil {
m := len (p )
p = bytes .Replace (p , []byte {'\n' }, []byte {'\r' , '\n' }, -1 )
p = bytes .Replace (p , []byte {'\r' , '\r' , '\n' }, []byte {'\r' , '\n' }, -1 )
n , err = sess .Channel .Write (p )
if n > m {
n = m
}
return
}
return sess .Channel .Write (p )
}
func (sess *session ) PublicKey () PublicKey {
sessionkey := sess .ctx .Value (ContextKeyPublicKey )
if sessionkey == nil {
return nil
}
return sessionkey .(PublicKey )
}
func (sess *session ) Permissions () Permissions {
perms := sess .ctx .Value (ContextKeyPermissions ).(*Permissions )
return *perms
}
func (sess *session ) Context () Context {
return sess .ctx
}
func (sess *session ) Exit (code int ) error {
sess .Lock ()
defer sess .Unlock ()
if sess .exited {
return errors .New ("Session.Exit called multiple times" )
}
sess .exited = true
status := struct { Status uint32 }{uint32 (code )}
_ , err := sess .SendRequest ("exit-status" , false , gossh .Marshal (&status ))
if err != nil {
return err
}
return sess .Close ()
}
func (sess *session ) User () string {
return sess .conn .User ()
}
func (sess *session ) RemoteAddr () net .Addr {
return sess .conn .RemoteAddr ()
}
func (sess *session ) LocalAddr () net .Addr {
return sess .conn .LocalAddr ()
}
func (sess *session ) Environ () []string {
return append ([]string (nil ), sess .env ...)
}
func (sess *session ) RawCommand () string {
return sess .rawCmd
}
func (sess *session ) Command () []string {
cmd , _ := shlex .Split (sess .rawCmd , true )
return append ([]string (nil ), cmd ...)
}
func (sess *session ) Subsystem () string {
return sess .subsystem
}
func (sess *session ) Pty () (Pty , <-chan Window , bool ) {
if sess .pty != nil {
return *sess .pty , sess .winch , true
}
return Pty {}, sess .winch , false
}
func (sess *session ) Signals (c chan <- Signal ) {
sess .Lock ()
defer sess .Unlock ()
sess .sigCh = c
if len (sess .sigBuf ) > 0 {
go func () {
for _ , sig := range sess .sigBuf {
sess .sigCh <- sig
}
}()
}
}
func (sess *session ) Break (c chan <- bool ) {
sess .Lock ()
defer sess .Unlock ()
sess .breakCh = c
}
func (sess *session ) handleRequests (reqs <-chan *gossh .Request ) {
for req := range reqs {
switch req .Type {
case "shell" , "exec" :
if sess .handled {
req .Reply (false , nil )
continue
}
var payload = struct { Value string }{}
gossh .Unmarshal (req .Payload , &payload )
sess .rawCmd = payload .Value
if sess .sessReqCb != nil && !sess .sessReqCb (sess , req .Type ) {
sess .rawCmd = ""
req .Reply (false , nil )
continue
}
sess .handled = true
req .Reply (true , nil )
go func () {
sess .handler (sess )
sess .Exit (0 )
}()
case "subsystem" :
if sess .handled {
req .Reply (false , nil )
continue
}
var payload = struct { Value string }{}
gossh .Unmarshal (req .Payload , &payload )
sess .subsystem = payload .Value
if sess .sessReqCb != nil && !sess .sessReqCb (sess , req .Type ) {
sess .rawCmd = ""
req .Reply (false , nil )
continue
}
handler := sess .subsystemHandlers [payload .Value ]
if handler == nil {
handler = sess .subsystemHandlers ["default" ]
}
if handler == nil {
req .Reply (false , nil )
continue
}
sess .handled = true
req .Reply (true , nil )
go func () {
handler (sess )
sess .Exit (0 )
}()
case "env" :
if sess .handled {
req .Reply (false , nil )
continue
}
var kv struct { Key , Value string }
gossh .Unmarshal (req .Payload , &kv )
sess .env = append (sess .env , fmt .Sprintf ("%s=%s" , kv .Key , kv .Value ))
req .Reply (true , nil )
case "signal" :
var payload struct { Signal string }
gossh .Unmarshal (req .Payload , &payload )
sess .Lock ()
if sess .sigCh != nil {
sess .sigCh <- Signal (payload .Signal )
} else {
if len (sess .sigBuf ) < maxSigBufSize {
sess .sigBuf = append (sess .sigBuf , Signal (payload .Signal ))
}
}
sess .Unlock ()
case "pty-req" :
if sess .handled || sess .pty != nil {
req .Reply (false , nil )
continue
}
ptyReq , ok := parsePtyRequest (req .Payload )
if !ok {
req .Reply (false , nil )
continue
}
if sess .ptyCb != nil {
ok := sess .ptyCb (sess .ctx , ptyReq )
if !ok {
req .Reply (false , nil )
continue
}
}
sess .pty = &ptyReq
sess .winch = make (chan Window , 1 )
sess .winch <- ptyReq .Window
defer func () {
close (sess .winch )
}()
req .Reply (ok , nil )
case "window-change" :
if sess .pty == nil {
req .Reply (false , nil )
continue
}
win , ok := parseWinchRequest (req .Payload )
if ok {
sess .pty .Window = win
sess .winch <- win
}
req .Reply (ok , nil )
case agentRequestType :
SetAgentRequested (sess .ctx )
req .Reply (true , nil )
case "break" :
ok := false
sess .Lock ()
if sess .breakCh != nil {
sess .breakCh <- true
ok = true
}
req .Reply (ok , nil )
sess .Unlock ()
default :
req .Reply (false , nil )
}
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .