mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-13 07:37:17 +03:00
progress: Host User interface, and more interfaces in general, tests pass
This commit is contained in:
parent
d16b1f5829
commit
e86996e6b5
@ -1,6 +1,8 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shazow/ssh-chat/chat/message"
|
||||
"github.com/shazow/ssh-chat/set"
|
||||
)
|
||||
@ -21,4 +23,10 @@ type Member interface {
|
||||
SetConfig(message.UserConfig)
|
||||
|
||||
Send(message.Message) error
|
||||
|
||||
Joined() time.Time
|
||||
ReplyTo() message.Author
|
||||
SetReplyTo(message.Author)
|
||||
Prompt() string
|
||||
SetHighlight(string) error
|
||||
}
|
||||
|
@ -38,3 +38,7 @@ func (u *User) Color() int {
|
||||
func (u *User) ID() string {
|
||||
return SanitizeName(u.name)
|
||||
}
|
||||
|
||||
func (u *User) Joined() time.Time {
|
||||
return u.joined
|
||||
}
|
||||
|
@ -199,7 +199,8 @@ func (r *Room) MemberByID(id string) (*roomMember, bool) {
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return m.Value().(*roomMember), true
|
||||
rm, ok := m.Value().(*roomMember)
|
||||
return rm, ok
|
||||
}
|
||||
|
||||
// IsOp returns whether a user is an operator in this room.
|
||||
|
32
client.go
32
client.go
@ -1,21 +1,33 @@
|
||||
package sshchat
|
||||
|
||||
import (
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/shazow/ssh-chat/chat"
|
||||
"github.com/shazow/ssh-chat/chat/message"
|
||||
"github.com/shazow/ssh-chat/sshd"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
user chat.Member
|
||||
conn sshd.Connection
|
||||
|
||||
timestamp time.Time
|
||||
type client struct {
|
||||
chat.Member
|
||||
sync.Mutex
|
||||
conns []sshd.Connection
|
||||
}
|
||||
|
||||
type Replier interface {
|
||||
ReplyTo() message.Author
|
||||
SetReplyTo(message.Author)
|
||||
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 User interface {
|
||||
chat.Member
|
||||
|
||||
Connections() []sshd.Connection
|
||||
Close() error
|
||||
}
|
||||
|
92
host.go
92
host.go
@ -35,7 +35,7 @@ type Host struct {
|
||||
mu sync.Mutex
|
||||
motd string
|
||||
count int
|
||||
clients map[chat.Member][]Client
|
||||
clients map[chat.Member][]client
|
||||
}
|
||||
|
||||
// NewHost creates a Host on top of an existing listener.
|
||||
@ -46,7 +46,7 @@ func NewHost(listener *sshd.SSHListener, auth *Auth) *Host {
|
||||
listener: listener,
|
||||
commands: chat.Commands{},
|
||||
auth: auth,
|
||||
clients: map[chat.Member][]Client{},
|
||||
clients: map[chat.Member][]client{},
|
||||
}
|
||||
|
||||
// Make our own commands registry instance.
|
||||
@ -75,10 +75,11 @@ func (h *Host) SetMotd(motd string) {
|
||||
// Connect a specific Terminal to this host and its room.
|
||||
func (h *Host) Connect(term *sshd.Terminal) {
|
||||
requestedName := term.Conn.Name()
|
||||
user := message.BufferedScreen(requestedName, term)
|
||||
|
||||
client := h.addClient(user, term.Conn)
|
||||
defer h.removeClient(user, client)
|
||||
screen := message.BufferedScreen(requestedName, term)
|
||||
user := &client{
|
||||
Member: screen,
|
||||
conns: []sshd.Connection{term.Conn},
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
motd := h.motd
|
||||
@ -91,10 +92,10 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
||||
user.SetConfig(cfg)
|
||||
|
||||
// Close term once user is closed.
|
||||
defer user.Close()
|
||||
defer screen.Close()
|
||||
defer term.Close()
|
||||
|
||||
go user.Consume()
|
||||
go screen.Consume()
|
||||
|
||||
// Send MOTD
|
||||
if motd != "" {
|
||||
@ -180,44 +181,6 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
||||
logger.Debugf("[%s] Leaving: %s", term.Conn.RemoteAddr(), user.Name())
|
||||
}
|
||||
|
||||
func (h *Host) addClient(user chat.Member, conn sshd.Connection) *Client {
|
||||
client := Client{
|
||||
user: user,
|
||||
conn: conn,
|
||||
timestamp: time.Now(),
|
||||
}
|
||||
h.mu.Lock()
|
||||
if _, ok := h.clients[user]; ok {
|
||||
logger.Warningf("user collision: %q", user)
|
||||
}
|
||||
h.clients[user] = append(h.clients[user], client)
|
||||
h.mu.Unlock()
|
||||
return &client
|
||||
}
|
||||
|
||||
func (h *Host) removeClient(user chat.Member, client *Client) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
clients := h.clients[user]
|
||||
for i, c := range clients {
|
||||
// Find the user
|
||||
if &c != client {
|
||||
continue
|
||||
}
|
||||
// Delete corresponding client
|
||||
clients[i] = clients[len(clients)-1]
|
||||
clients = clients[:len(clients)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) findClients(user chat.Member) []Client {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
return h.clients[user]
|
||||
}
|
||||
|
||||
// Serve our chat room onto the listener
|
||||
func (h *Host) Serve() {
|
||||
h.listener.HandlerFunc = h.Connect
|
||||
@ -244,7 +207,7 @@ func (h *Host) completeCommand(partial string) string {
|
||||
}
|
||||
|
||||
// AutoCompleteFunction returns a callback for terminal autocompletion
|
||||
func (h *Host) AutoCompleteFunction(u Replier) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||
func (h *Host) AutoCompleteFunction(u User) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||
return func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||
if key != 9 {
|
||||
return
|
||||
@ -301,12 +264,13 @@ func (h *Host) AutoCompleteFunction(u Replier) func(line string, pos int, key ru
|
||||
}
|
||||
|
||||
// GetUser returns a message.User based on a name.
|
||||
func (h *Host) GetUser(name string) (chat.Member, bool) {
|
||||
func (h *Host) GetUser(name string) (User, bool) {
|
||||
m, ok := h.MemberByID(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return m.Member, true
|
||||
u, ok := m.Member.(User)
|
||||
return u, ok
|
||||
}
|
||||
|
||||
// InitCommands adds host-specific commands to a Commands container. These will
|
||||
@ -336,7 +300,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
txt := fmt.Sprintf("[Sent PM to %s]", target.Name())
|
||||
ms := message.NewSystemMsg(txt, msg.From())
|
||||
room.Send(ms)
|
||||
target.(Replier).SetReplyTo(msg.From())
|
||||
target.SetReplyTo(msg.From())
|
||||
return nil
|
||||
},
|
||||
})
|
||||
@ -352,7 +316,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
return errors.New("must specify message")
|
||||
}
|
||||
|
||||
target := msg.From().(Replier).ReplyTo()
|
||||
target := msg.From().(chat.Member).ReplyTo()
|
||||
if target == nil {
|
||||
return errors.New("no message to reply to")
|
||||
}
|
||||
@ -388,14 +352,12 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// FIXME: Handle many clients
|
||||
clients := h.findClients(target)
|
||||
var whois string
|
||||
switch room.IsOp(msg.From().(chat.Member)) {
|
||||
case true:
|
||||
whois = whoisAdmin(clients)
|
||||
whois = whoisAdmin(target)
|
||||
case false:
|
||||
whois = whoisPublic(clients)
|
||||
whois = whoisPublic(target)
|
||||
}
|
||||
room.Send(message.NewSystemMsg(whois, msg.From()))
|
||||
|
||||
@ -451,9 +413,12 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
room.Ops.Add(set.Keyize(user.ID()))
|
||||
}
|
||||
|
||||
for _, client := range h.findClients(user) {
|
||||
h.auth.Op(client.conn.PublicKey(), until)
|
||||
}
|
||||
// TODO: Add pubkeys to op
|
||||
/*
|
||||
for _, conn := range user.Connections() {
|
||||
h.auth.Op(conn.PublicKey(), until)
|
||||
}
|
||||
*/
|
||||
|
||||
body := fmt.Sprintf("Made op by %s.", msg.From().Name())
|
||||
room.Send(message.NewSystemMsg(body, user))
|
||||
@ -489,17 +454,16 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
until, _ = time.ParseDuration(args[1])
|
||||
}
|
||||
|
||||
clients := h.findClients(target)
|
||||
for _, client := range clients {
|
||||
h.auth.Ban(client.conn.PublicKey(), until)
|
||||
h.auth.BanAddr(client.conn.RemoteAddr(), until)
|
||||
for _, conn := range target.Connections() {
|
||||
h.auth.Ban(conn.PublicKey(), until)
|
||||
h.auth.BanAddr(conn.RemoteAddr(), until)
|
||||
}
|
||||
|
||||
body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
|
||||
room.Send(message.NewAnnounceMsg(body))
|
||||
target.(io.Closer).Close()
|
||||
target.Close()
|
||||
|
||||
logger.Debugf("Banned: \n-> %s", whoisAdmin(clients))
|
||||
logger.Debugf("Banned: \n-> %s", whoisAdmin(target))
|
||||
|
||||
return nil
|
||||
},
|
||||
|
23
whois.go
23
whois.go
@ -2,7 +2,6 @@ package sshchat
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/shazow/ssh-chat/chat/message"
|
||||
@ -11,28 +10,22 @@ import (
|
||||
|
||||
// Helpers for printing whois messages
|
||||
|
||||
type joinTimestamped interface {
|
||||
Joined() time.Time
|
||||
}
|
||||
|
||||
func whoisPublic(clients []Client) string {
|
||||
// FIXME: Handle many clients
|
||||
conn, u := clients[0].conn, clients[0].user
|
||||
|
||||
func whoisPublic(u User) string {
|
||||
fingerprint := "(no public key)"
|
||||
// FIXME: Use all connections?
|
||||
conn := u.Connections()[0]
|
||||
if conn.PublicKey() != nil {
|
||||
fingerprint = sshd.Fingerprint(conn.PublicKey())
|
||||
}
|
||||
return "name: " + u.Name() + message.Newline +
|
||||
" > fingerprint: " + fingerprint + message.Newline +
|
||||
" > client: " + SanitizeData(string(conn.ClientVersion())) + message.Newline +
|
||||
" > joined: " + humanize.Time(u.(joinTimestamped).Joined())
|
||||
" > joined: " + humanize.Time(u.Joined())
|
||||
}
|
||||
|
||||
func whoisAdmin(clients []Client) string {
|
||||
// FIXME: Handle many clients
|
||||
conn, u := clients[0].conn, clients[0].user
|
||||
|
||||
func whoisAdmin(u User) string {
|
||||
// FIXME: Use all connections?
|
||||
conn := u.Connections()[0]
|
||||
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
fingerprint := "(no public key)"
|
||||
if conn.PublicKey() != nil {
|
||||
@ -42,5 +35,5 @@ func whoisAdmin(clients []Client) string {
|
||||
" > ip: " + ip + message.Newline +
|
||||
" > fingerprint: " + fingerprint + message.Newline +
|
||||
" > client: " + SanitizeData(string(conn.ClientVersion())) + message.Newline +
|
||||
" > joined: " + humanize.Time(u.(joinTimestamped).Joined())
|
||||
" > joined: " + humanize.Time(u.Joined())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user