package ssh

import (
	
	
	
	
	

	
	gossh 
)

// Session provides access to information about an SSH session and methods
// to read and write to the SSH channel with an embedded Channel interface from
// crypto/ssh.
//
// When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments.
//
// TODO: Signals
type Session interface {
	gossh.Channel

	// User returns the username used when establishing the SSH connection.
	User() string

	// RemoteAddr returns the net.Addr of the client side of the connection.
	RemoteAddr() net.Addr

	// LocalAddr returns the net.Addr of the server side of the connection.
	LocalAddr() net.Addr

	// Environ returns a copy of strings representing the environment set by the
	// user for this session, in the form "key=value".
	Environ() []string

	// Exit sends an exit status and then closes the session.
	Exit(code int) error

	// Command returns a shell parsed slice of arguments that were provided by the
	// user. Shell parsing splits the command string according to POSIX shell rules,
	// which considers quoting not just whitespace.
	Command() []string

	// RawCommand returns the exact command that was provided by the user.
	RawCommand() string

	// Subsystem returns the subsystem requested by the user.
	Subsystem() string

	// PublicKey returns the PublicKey used to authenticate. If a public key was not
	// used it will return nil.
	PublicKey() PublicKey

	// Context returns the connection's context. The returned context is always
	// non-nil and holds the same data as the Context passed into auth
	// handlers and callbacks.
	//
	// The context is canceled when the client's connection closes or I/O
	// operation fails.
	Context() Context

	// Permissions returns a copy of the Permissions object that was available for
	// setup in the auth handlers via the Context.
	Permissions() Permissions

	// Pty returns PTY information, a channel of window size changes, and a boolean
	// of whether or not a PTY was accepted for this session.
	Pty() (Pty, <-chan Window, bool)

	// Signals registers a channel to receive signals sent from the client. The
	// channel must handle signal sends or it will block the SSH request loop.
	// Registering nil will unregister the channel from signal sends. During the
	// time no channel is registered signals are buffered up to a reasonable amount.
	// If there are buffered signals when a channel is registered, they will be
	// sent in order on the channel immediately after registering.
	Signals(c chan<- Signal)

	// Break regisers a channel to receive notifications of break requests sent
	// from the client. The channel must handle break requests, or it will block
	// the request handling loop. Registering nil will unregister the channel.
	// During the time that no channel is registered, breaks are ignored.
	Break(c chan<- bool)
}

// maxSigBufSize is how many signals will be buffered
// when there is no signal channel specified
const maxSigBufSize = 128

func ( *Server,  *gossh.ServerConn,  gossh.NewChannel,  Context) {
	, ,  := .Accept()
	if  != nil {
		// TODO: trigger event callback
		return
	}
	 := &session{
		Channel:           ,
		conn:              ,
		handler:           .Handler,
		ptyCb:             .PtyCallback,
		sessReqCb:         .SessionRequestCallback,
		subsystemHandlers: .SubsystemHandlers,
		ctx:               ,
	}
	.handleRequests()
}

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 ( *session) ( []byte) ( int,  error) {
	if .pty != nil {
		 := len()
		// normalize \n to \r\n when pty is accepted.
		// this is a hardcoded shortcut since we don't support terminal modes.
		 = bytes.Replace(, []byte{'\n'}, []byte{'\r', '\n'}, -1)
		 = bytes.Replace(, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
		,  = .Channel.Write()
		if  >  {
			 = 
		}
		return
	}
	return .Channel.Write()
}

func ( *session) () PublicKey {
	 := .ctx.Value(ContextKeyPublicKey)
	if  == nil {
		return nil
	}
	return .(PublicKey)
}

func ( *session) () Permissions {
	// use context permissions because its properly
	// wrapped and easier to dereference
	 := .ctx.Value(ContextKeyPermissions).(*Permissions)
	return *
}

func ( *session) () Context {
	return .ctx
}

func ( *session) ( int) error {
	.Lock()
	defer .Unlock()
	if .exited {
		return errors.New("Session.Exit called multiple times")
	}
	.exited = true

	 := struct{  uint32 }{uint32()}
	,  := .SendRequest("exit-status", false, gossh.Marshal(&))
	if  != nil {
		return 
	}
	return .Close()
}

func ( *session) () string {
	return .conn.User()
}

func ( *session) () net.Addr {
	return .conn.RemoteAddr()
}

func ( *session) () net.Addr {
	return .conn.LocalAddr()
}

func ( *session) () []string {
	return append([]string(nil), .env...)
}

func ( *session) () string {
	return .rawCmd
}

func ( *session) () []string {
	,  := shlex.Split(.rawCmd, true)
	return append([]string(nil), ...)
}

func ( *session) () string {
	return .subsystem
}

func ( *session) () (Pty, <-chan Window, bool) {
	if .pty != nil {
		return *.pty, .winch, true
	}
	return Pty{}, .winch, false
}

func ( *session) ( chan<- Signal) {
	.Lock()
	defer .Unlock()
	.sigCh = 
	if len(.sigBuf) > 0 {
		go func() {
			for ,  := range .sigBuf {
				.sigCh <- 
			}
		}()
	}
}

func ( *session) ( chan<- bool) {
	.Lock()
	defer .Unlock()
	.breakCh = 
}

func ( *session) ( <-chan *gossh.Request) {
	for  := range  {
		switch .Type {
		case "shell", "exec":
			if .handled {
				.Reply(false, nil)
				continue
			}

			var  = struct{  string }{}
			gossh.Unmarshal(.Payload, &)
			.rawCmd = .

			// If there's a session policy callback, we need to confirm before
			// accepting the session.
			if .sessReqCb != nil && !.sessReqCb(, .Type) {
				.rawCmd = ""
				.Reply(false, nil)
				continue
			}

			.handled = true
			.Reply(true, nil)

			go func() {
				.handler()
				.Exit(0)
			}()
		case "subsystem":
			if .handled {
				.Reply(false, nil)
				continue
			}

			var  = struct{  string }{}
			gossh.Unmarshal(.Payload, &)
			.subsystem = .

			// If there's a session policy callback, we need to confirm before
			// accepting the session.
			if .sessReqCb != nil && !.sessReqCb(, .Type) {
				.rawCmd = ""
				.Reply(false, nil)
				continue
			}

			 := .subsystemHandlers[.]
			if  == nil {
				 = .subsystemHandlers["default"]
			}
			if  == nil {
				.Reply(false, nil)
				continue
			}

			.handled = true
			.Reply(true, nil)

			go func() {
				()
				.Exit(0)
			}()
		case "env":
			if .handled {
				.Reply(false, nil)
				continue
			}
			var  struct{ ,  string }
			gossh.Unmarshal(.Payload, &)
			.env = append(.env, fmt.Sprintf("%s=%s", ., .))
			.Reply(true, nil)
		case "signal":
			var  struct{  string }
			gossh.Unmarshal(.Payload, &)
			.Lock()
			if .sigCh != nil {
				.sigCh <- Signal(.)
			} else {
				if len(.sigBuf) < maxSigBufSize {
					.sigBuf = append(.sigBuf, Signal(.))
				}
			}
			.Unlock()
		case "pty-req":
			if .handled || .pty != nil {
				.Reply(false, nil)
				continue
			}
			,  := parsePtyRequest(.Payload)
			if ! {
				.Reply(false, nil)
				continue
			}
			if .ptyCb != nil {
				 := .ptyCb(.ctx, )
				if ! {
					.Reply(false, nil)
					continue
				}
			}
			.pty = &
			.winch = make(chan Window, 1)
			.winch <- .Window
			defer func() {
				// when reqs is closed
				close(.winch)
			}()
			.Reply(, nil)
		case "window-change":
			if .pty == nil {
				.Reply(false, nil)
				continue
			}
			,  := parseWinchRequest(.Payload)
			if  {
				.pty.Window = 
				.winch <- 
			}
			.Reply(, nil)
		case agentRequestType:
			// TODO: option/callback to allow agent forwarding
			SetAgentRequested(.ctx)
			.Reply(true, nil)
		case "break":
			 := false
			.Lock()
			if .breakCh != nil {
				.breakCh <- true
				 = true
			}
			.Reply(, nil)
			.Unlock()
		default:
			// TODO: debug log
			.Reply(false, nil)
		}
	}
}