diff --git a/host.go b/host.go index a5d12d9d..f5752298 100644 --- a/host.go +++ b/host.go @@ -96,6 +96,28 @@ func (h *Host) Connect(term *sshd.Terminal) { cfg := user.Config() cfg.Theme = &h.theme user.SetConfig(cfg) + + env := term.Env() + for _, e := range env { + switch e.Key { + case "SSHCHAT_TIMESTAMP": + if e.Value != "" && e.Value != "0" { + cmd := "/timestamp" + if e.Value != "1" { + cmd += " " + e.Value + } + if msg, ok := message.NewPublicMsg(cmd, user).ParseCommand(); ok { + h.Room.HandleMsg(msg) + } + } + case "SSHCHAT_THEME": + cmd := "/theme " + e.Value + if msg, ok := message.NewPublicMsg(cmd, user).ParseCommand(); ok { + h.Room.HandleMsg(msg) + } + } + } + go user.Consume() // Close term once user is closed. diff --git a/sshd/pty.go b/sshd/pty.go index 4443fd33..d3ed9ca8 100644 --- a/sshd/pty.go +++ b/sshd/pty.go @@ -6,8 +6,8 @@ import "encoding/binary" // parsePtyRequest parses the payload of the pty-req message and extracts the // dimensions of the terminal. See RFC 4254, section 6.2. -func parsePtyRequest(s []byte) (width, height int, ok bool) { - _, s, ok = parseString(s) +func parsePtyRequest(s []byte) (term string, width, height int, ok bool) { + term, s, ok = parseString(s) if !ok { return } diff --git a/sshd/terminal.go b/sshd/terminal.go index 472b5c15..480fa59c 100644 --- a/sshd/terminal.go +++ b/sshd/terminal.go @@ -55,6 +55,29 @@ func (c sshConn) Name() string { return c.User() } +// EnvVar is an environment variable key-value pair +type EnvVar struct { + Key string + Value string +} + +func (v EnvVar) String() string { + return v.Key + "=" + v.Value +} + +// Env is a wrapper type around []EnvVar with some helper methods +type Env []EnvVar + +// Get returns the latest value for a given key, or empty string if not found +func (e Env) Get(key string) string { + for i := len(e) - 1; i >= 0; i-- { + if e[i].Key == key { + return e[i].Value + } + } + return "" +} + // Terminal extends ssh/terminal to include a close method type Terminal struct { terminal.Terminal @@ -63,6 +86,10 @@ type Terminal struct { done chan struct{} closeOnce sync.Once + + mu sync.Mutex + env []EnvVar + term string } // Make new terminal from a session channel @@ -82,7 +109,8 @@ func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) { done: make(chan struct{}), } - go term.listen(requests) + ready := make(chan struct{}) + go term.listen(requests, ready) go func() { // Keep-Alive Ticker @@ -103,6 +131,14 @@ func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) { } }() + select { + case <-ready: // ok... + case <-term.done: + return nil, errors.New("terminal aborted") + case <-time.NewTimer(time.Minute).C: + return nil, errors.New("timed out starting terminal") + } + return &term, nil } @@ -134,7 +170,8 @@ func (t *Terminal) Close() error { } // Negotiate terminal type and settings -func (t *Terminal) listen(requests <-chan *ssh.Request) { +// ready is closed when the terminal is ready. +func (t *Terminal) listen(requests <-chan *ssh.Request, ready chan<- struct{}) { hasShell := false for req := range requests { @@ -146,13 +183,19 @@ func (t *Terminal) listen(requests <-chan *ssh.Request) { if !hasShell { ok = true hasShell = true + close(ready) } case "pty-req": - width, height, ok = parsePtyRequest(req.Payload) + var term string + term, width, height, ok = parsePtyRequest(req.Payload) if ok { // TODO: Hardcode width to 100000? err := t.SetSize(width, height) ok = err == nil + // Save the term: + t.mu.Lock() + t.term = term + t.mu.Unlock() } case "window-change": width, height, ok = parseWinchRequest(req.Payload) @@ -161,6 +204,14 @@ func (t *Terminal) listen(requests <-chan *ssh.Request) { err := t.SetSize(width, height) ok = err == nil } + case "env": + var v EnvVar + if err := ssh.Unmarshal(req.Payload, &v); err == nil { + t.mu.Lock() + t.env = append(t.env, v) + t.mu.Unlock() + ok = true + } } if req.WantReply { @@ -168,3 +219,20 @@ func (t *Terminal) listen(requests <-chan *ssh.Request) { } } } + +// Env returns a list of environment key-values that have been set. They are +// returned in the order that they have been set, there is no deduplication or +// other pre-processing applied. +func (t *Terminal) Env() Env { + t.mu.Lock() + defer t.mu.Unlock() + return Env(t.env) +} + +// Term returns the terminal string value as set by the pty. +// If there was no pty request, this is empty. +func (t *Terminal) Term() string { + t.mu.Lock() + defer t.mu.Unlock() + return t.term +}