mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-12 23:27:17 +03:00
progress/broken: multiuser support hardcoded, by multiplexing terminals
/whois breaks, empty message breaks, prompt replication is fragile
This commit is contained in:
parent
7b3818acc1
commit
76849c0d3d
2
Makefile
2
Makefile
@ -25,7 +25,7 @@ $(KEY):
|
|||||||
ssh-keygen -f $(KEY) -P ''
|
ssh-keygen -f $(KEY) -P ''
|
||||||
|
|
||||||
run: $(BINARY) $(KEY)
|
run: $(BINARY) $(KEY)
|
||||||
./$(BINARY) -i $(KEY) --bind ":$(PORT)" -vv
|
./$(BINARY) -i $(KEY) --bind "127.0.0.1:$(PORT)" -vv
|
||||||
|
|
||||||
debug: $(BINARY) $(KEY)
|
debug: $(BINARY) $(KEY)
|
||||||
./$(BINARY) --pprof 6060 -i $(KEY) --bind ":$(PORT)" -vv
|
./$(BINARY) --pprof 6060 -i $(KEY) --bind ":$(PORT)" -vv
|
||||||
|
22
client.go
22
client.go
@ -1,6 +1,7 @@
|
|||||||
package sshchat
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -9,22 +10,17 @@ import (
|
|||||||
"github.com/shazow/ssh-chat/sshd"
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type multiTerm interface {
|
||||||
|
Connections() []sshd.Connection
|
||||||
|
Add(*sshd.Terminal)
|
||||||
|
ReadLine() (string, error)
|
||||||
|
io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
Member
|
Member
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
conns []sshd.Connection
|
multiTerm
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Connections() []sshd.Connection {
|
|
||||||
return cl.conns
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Close() error {
|
|
||||||
// TODO: Stack errors?
|
|
||||||
for _, conn := range cl.conns {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Member interface {
|
type Member interface {
|
||||||
|
57
host.go
57
host.go
@ -32,10 +32,9 @@ type Host struct {
|
|||||||
// Default theme
|
// Default theme
|
||||||
theme message.Theme
|
theme message.Theme
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
motd string
|
motd string
|
||||||
count int
|
count int
|
||||||
clients map[chat.Member][]client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHost creates a Host on top of an existing listener.
|
// NewHost creates a Host on top of an existing listener.
|
||||||
@ -46,7 +45,6 @@ func NewHost(listener *sshd.SSHListener, auth *Auth) *Host {
|
|||||||
listener: listener,
|
listener: listener,
|
||||||
commands: chat.Commands{},
|
commands: chat.Commands{},
|
||||||
auth: auth,
|
auth: auth,
|
||||||
clients: map[chat.Member][]client{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make our own commands registry instance.
|
// Make our own commands registry instance.
|
||||||
@ -72,15 +70,30 @@ func (h *Host) SetMotd(motd string) {
|
|||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var globalUser *client
|
||||||
|
|
||||||
// Connect a specific Terminal to this host and its room.
|
// Connect a specific Terminal to this host and its room.
|
||||||
func (h *Host) Connect(term *sshd.Terminal) {
|
func (h *Host) Connect(t *sshd.Terminal) {
|
||||||
requestedName := term.Conn.Name()
|
// XXX: Hack to test multiple users per key
|
||||||
screen := message.BufferedScreen(requestedName, term)
|
if globalUser != nil {
|
||||||
user := &client{
|
globalUser.Add(t)
|
||||||
Member: screen,
|
return
|
||||||
conns: []sshd.Connection{term.Conn},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn := t.Conn
|
||||||
|
remoteAddr := conn.RemoteAddr()
|
||||||
|
requestedName := conn.Name()
|
||||||
|
term := sshd.MultiTerm(t)
|
||||||
|
screen := message.BufferedScreen(requestedName, term)
|
||||||
|
|
||||||
|
user := &client{
|
||||||
|
Member: screen,
|
||||||
|
multiTerm: term,
|
||||||
|
}
|
||||||
|
defer user.Close()
|
||||||
|
// XXX: Hack to test multiple users per key
|
||||||
|
globalUser = user
|
||||||
|
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
motd := h.motd
|
motd := h.motd
|
||||||
count := h.count
|
count := h.count
|
||||||
@ -91,10 +104,6 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
cfg.Theme = &h.theme
|
cfg.Theme = &h.theme
|
||||||
user.SetConfig(cfg)
|
user.SetConfig(cfg)
|
||||||
|
|
||||||
// Close term once user is closed.
|
|
||||||
defer screen.Close()
|
|
||||||
defer term.Close()
|
|
||||||
|
|
||||||
go screen.Consume()
|
go screen.Consume()
|
||||||
|
|
||||||
// Send MOTD
|
// Send MOTD
|
||||||
@ -109,7 +118,7 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
member, err = h.Join(user)
|
member, err = h.Join(user)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("[%s] Failed to join: %s", term.Conn.RemoteAddr(), err)
|
logger.Errorf("[%s] Failed to join: %s", conn.RemoteAddr(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,27 +127,29 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
term.AutoCompleteCallback = h.AutoCompleteFunction(user)
|
term.AutoCompleteCallback = h.AutoCompleteFunction(user)
|
||||||
user.SetHighlight(user.Name())
|
user.SetHighlight(user.Name())
|
||||||
|
|
||||||
|
// XXX: Mark multiterm as ready?
|
||||||
|
|
||||||
// Should the user be op'd on join?
|
// Should the user be op'd on join?
|
||||||
if key := term.Conn.PublicKey(); key != nil {
|
if key := conn.PublicKey(); key != nil {
|
||||||
authItem, err := h.auth.ops.Get(newAuthKey(key))
|
authItem, err := h.auth.ops.Get(newAuthKey(key))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = h.Room.Ops.Add(set.Rename(authItem, member.ID()))
|
err = h.Room.Ops.Add(set.Rename(authItem, member.ID()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warningf("[%s] Failed to op: %s", term.Conn.RemoteAddr(), err)
|
logger.Warningf("[%s] Failed to op: %s", remoteAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ratelimit := rateio.NewSimpleLimiter(3, time.Second*3)
|
ratelimit := rateio.NewSimpleLimiter(3, time.Second*3)
|
||||||
logger.Debugf("[%s] Joined: %s", term.Conn.RemoteAddr(), user.Name())
|
logger.Debugf("[%s] Joined: %s", remoteAddr, user.Name())
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line, err := term.ReadLine()
|
line, err := user.ReadLine()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
// Closed
|
// Closed
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
logger.Errorf("[%s] Terminal reading error: %s", term.Conn.RemoteAddr(), err)
|
logger.Errorf("[%s] Terminal reading error: %s", remoteAddr, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,10 +186,10 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
|
|
||||||
err = h.Leave(user)
|
err = h.Leave(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("[%s] Failed to leave: %s", term.Conn.RemoteAddr(), err)
|
logger.Errorf("[%s] Failed to leave: %s", remoteAddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debugf("[%s] Leaving: %s", term.Conn.RemoteAddr(), user.Name())
|
logger.Debugf("[%s] Leaving: %s", remoteAddr, user.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve our chat room onto the listener
|
// Serve our chat room onto the listener
|
||||||
|
131
sshd/multiterm.go
Normal file
131
sshd/multiterm.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package sshd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type termLine struct {
|
||||||
|
Term *Terminal
|
||||||
|
Line string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func MultiTerm(terms ...*Terminal) *multiTerm {
|
||||||
|
mt := &multiTerm{
|
||||||
|
lines: make(chan termLine),
|
||||||
|
}
|
||||||
|
for _, t := range terms {
|
||||||
|
mt.Add(t)
|
||||||
|
}
|
||||||
|
return mt
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiTerm struct {
|
||||||
|
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
terms []*Terminal
|
||||||
|
add chan *Terminal
|
||||||
|
lines chan termLine
|
||||||
|
prompt string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) SetPrompt(prompt string) {
|
||||||
|
mt.mu.Lock()
|
||||||
|
mt.prompt = prompt
|
||||||
|
mt.mu.Unlock()
|
||||||
|
for _, t := range mt.Terminals() {
|
||||||
|
t.SetPrompt(prompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) Connections() []Connection {
|
||||||
|
terms := mt.Terminals()
|
||||||
|
conns := make([]Connection, len(terms))
|
||||||
|
for _, term := range terms {
|
||||||
|
conns = append(conns, term.Conn)
|
||||||
|
}
|
||||||
|
return conns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) Terminals() []*Terminal {
|
||||||
|
mt.mu.Lock()
|
||||||
|
terms := mt.terms
|
||||||
|
mt.mu.Unlock()
|
||||||
|
return terms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) Add(t *Terminal) {
|
||||||
|
mt.mu.Lock()
|
||||||
|
mt.terms = append(mt.terms, t)
|
||||||
|
prompt := mt.prompt
|
||||||
|
mt.mu.Unlock()
|
||||||
|
t.AutoCompleteCallback = mt.AutoCompleteCallback
|
||||||
|
t.SetPrompt(prompt)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var line termLine
|
||||||
|
for {
|
||||||
|
line.Line, line.Err = t.ReadLine()
|
||||||
|
line.Term = t
|
||||||
|
mt.lines <- line
|
||||||
|
if line.Err != nil {
|
||||||
|
// FIXME: Should we not abort on all errors?
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) ReadLine() (string, error) {
|
||||||
|
line := <-mt.lines
|
||||||
|
mt.mu.Lock()
|
||||||
|
prompt := mt.prompt
|
||||||
|
mt.mu.Unlock()
|
||||||
|
if line.Err == nil {
|
||||||
|
// Write the line to all the other terminals
|
||||||
|
for _, w := range mt.Terminals() {
|
||||||
|
if w == line.Term {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// XXX: This is super hacky and frankly wrong.
|
||||||
|
w.Write([]byte(prompt + line.Line + "\n\r"))
|
||||||
|
// TODO: Remove terminal if it fails to write?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line.Line, line.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) Write(p []byte) (n int, err error) {
|
||||||
|
for _, w := range mt.Terminals() {
|
||||||
|
n, err = w.Write(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != len(p) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *multiTerm) Close() error {
|
||||||
|
mt.mu.Lock()
|
||||||
|
var errs []error
|
||||||
|
for _, t := range mt.terms {
|
||||||
|
if err := t.Close(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mt.terms = nil
|
||||||
|
mt.mu.Unlock()
|
||||||
|
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%d errors: %q", len(errs), errs)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user