mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-15 00:20:37 +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
|
package chat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
"github.com/shazow/ssh-chat/set"
|
"github.com/shazow/ssh-chat/set"
|
||||||
)
|
)
|
||||||
@ -21,4 +23,10 @@ type Member interface {
|
|||||||
SetConfig(message.UserConfig)
|
SetConfig(message.UserConfig)
|
||||||
|
|
||||||
Send(message.Message) error
|
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 {
|
func (u *User) ID() string {
|
||||||
return SanitizeName(u.name)
|
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 {
|
if err != nil {
|
||||||
return nil, false
|
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.
|
// IsOp returns whether a user is an operator in this room.
|
||||||
|
32
client.go
32
client.go
@ -1,21 +1,33 @@
|
|||||||
package sshchat
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"sync"
|
||||||
|
|
||||||
"github.com/shazow/ssh-chat/chat"
|
"github.com/shazow/ssh-chat/chat"
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
|
||||||
"github.com/shazow/ssh-chat/sshd"
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type client struct {
|
||||||
user chat.Member
|
chat.Member
|
||||||
conn sshd.Connection
|
sync.Mutex
|
||||||
|
conns []sshd.Connection
|
||||||
timestamp time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Replier interface {
|
func (cl *client) Connections() []sshd.Connection {
|
||||||
ReplyTo() message.Author
|
return cl.conns
|
||||||
SetReplyTo(message.Author)
|
}
|
||||||
|
|
||||||
|
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
|
mu sync.Mutex
|
||||||
motd string
|
motd string
|
||||||
count int
|
count int
|
||||||
clients map[chat.Member][]Client
|
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 +46,7 @@ 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{},
|
clients: map[chat.Member][]client{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make our own commands registry instance.
|
// 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.
|
// Connect a specific Terminal to this host and its room.
|
||||||
func (h *Host) Connect(term *sshd.Terminal) {
|
func (h *Host) Connect(term *sshd.Terminal) {
|
||||||
requestedName := term.Conn.Name()
|
requestedName := term.Conn.Name()
|
||||||
user := message.BufferedScreen(requestedName, term)
|
screen := message.BufferedScreen(requestedName, term)
|
||||||
|
user := &client{
|
||||||
client := h.addClient(user, term.Conn)
|
Member: screen,
|
||||||
defer h.removeClient(user, client)
|
conns: []sshd.Connection{term.Conn},
|
||||||
|
}
|
||||||
|
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
motd := h.motd
|
motd := h.motd
|
||||||
@ -91,10 +92,10 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
user.SetConfig(cfg)
|
user.SetConfig(cfg)
|
||||||
|
|
||||||
// Close term once user is closed.
|
// Close term once user is closed.
|
||||||
defer user.Close()
|
defer screen.Close()
|
||||||
defer term.Close()
|
defer term.Close()
|
||||||
|
|
||||||
go user.Consume()
|
go screen.Consume()
|
||||||
|
|
||||||
// Send MOTD
|
// Send MOTD
|
||||||
if motd != "" {
|
if motd != "" {
|
||||||
@ -180,44 +181,6 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
logger.Debugf("[%s] Leaving: %s", term.Conn.RemoteAddr(), user.Name())
|
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
|
// Serve our chat room onto the listener
|
||||||
func (h *Host) Serve() {
|
func (h *Host) Serve() {
|
||||||
h.listener.HandlerFunc = h.Connect
|
h.listener.HandlerFunc = h.Connect
|
||||||
@ -244,7 +207,7 @@ func (h *Host) completeCommand(partial string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AutoCompleteFunction returns a callback for terminal autocompletion
|
// 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) {
|
return func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||||
if key != 9 {
|
if key != 9 {
|
||||||
return
|
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.
|
// 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)
|
m, ok := h.MemberByID(name)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
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
|
// 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())
|
txt := fmt.Sprintf("[Sent PM to %s]", target.Name())
|
||||||
ms := message.NewSystemMsg(txt, msg.From())
|
ms := message.NewSystemMsg(txt, msg.From())
|
||||||
room.Send(ms)
|
room.Send(ms)
|
||||||
target.(Replier).SetReplyTo(msg.From())
|
target.SetReplyTo(msg.From())
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -352,7 +316,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
return errors.New("must specify message")
|
return errors.New("must specify message")
|
||||||
}
|
}
|
||||||
|
|
||||||
target := msg.From().(Replier).ReplyTo()
|
target := msg.From().(chat.Member).ReplyTo()
|
||||||
if target == nil {
|
if target == nil {
|
||||||
return errors.New("no message to reply to")
|
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")
|
return errors.New("user not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Handle many clients
|
|
||||||
clients := h.findClients(target)
|
|
||||||
var whois string
|
var whois string
|
||||||
switch room.IsOp(msg.From().(chat.Member)) {
|
switch room.IsOp(msg.From().(chat.Member)) {
|
||||||
case true:
|
case true:
|
||||||
whois = whoisAdmin(clients)
|
whois = whoisAdmin(target)
|
||||||
case false:
|
case false:
|
||||||
whois = whoisPublic(clients)
|
whois = whoisPublic(target)
|
||||||
}
|
}
|
||||||
room.Send(message.NewSystemMsg(whois, msg.From()))
|
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()))
|
room.Ops.Add(set.Keyize(user.ID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, client := range h.findClients(user) {
|
// TODO: Add pubkeys to op
|
||||||
h.auth.Op(client.conn.PublicKey(), until)
|
/*
|
||||||
}
|
for _, conn := range user.Connections() {
|
||||||
|
h.auth.Op(conn.PublicKey(), until)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
body := fmt.Sprintf("Made op by %s.", msg.From().Name())
|
body := fmt.Sprintf("Made op by %s.", msg.From().Name())
|
||||||
room.Send(message.NewSystemMsg(body, user))
|
room.Send(message.NewSystemMsg(body, user))
|
||||||
@ -489,17 +454,16 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
until, _ = time.ParseDuration(args[1])
|
until, _ = time.ParseDuration(args[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
clients := h.findClients(target)
|
for _, conn := range target.Connections() {
|
||||||
for _, client := range clients {
|
h.auth.Ban(conn.PublicKey(), until)
|
||||||
h.auth.Ban(client.conn.PublicKey(), until)
|
h.auth.BanAddr(conn.RemoteAddr(), until)
|
||||||
h.auth.BanAddr(client.conn.RemoteAddr(), until)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
|
body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
|
||||||
room.Send(message.NewAnnounceMsg(body))
|
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
|
return nil
|
||||||
},
|
},
|
||||||
|
23
whois.go
23
whois.go
@ -2,7 +2,6 @@ package sshchat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
@ -11,28 +10,22 @@ import (
|
|||||||
|
|
||||||
// Helpers for printing whois messages
|
// Helpers for printing whois messages
|
||||||
|
|
||||||
type joinTimestamped interface {
|
func whoisPublic(u User) string {
|
||||||
Joined() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func whoisPublic(clients []Client) string {
|
|
||||||
// FIXME: Handle many clients
|
|
||||||
conn, u := clients[0].conn, clients[0].user
|
|
||||||
|
|
||||||
fingerprint := "(no public key)"
|
fingerprint := "(no public key)"
|
||||||
|
// FIXME: Use all connections?
|
||||||
|
conn := u.Connections()[0]
|
||||||
if conn.PublicKey() != nil {
|
if conn.PublicKey() != nil {
|
||||||
fingerprint = sshd.Fingerprint(conn.PublicKey())
|
fingerprint = sshd.Fingerprint(conn.PublicKey())
|
||||||
}
|
}
|
||||||
return "name: " + u.Name() + message.Newline +
|
return "name: " + u.Name() + message.Newline +
|
||||||
" > fingerprint: " + fingerprint + message.Newline +
|
" > fingerprint: " + fingerprint + message.Newline +
|
||||||
" > client: " + SanitizeData(string(conn.ClientVersion())) + 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 {
|
func whoisAdmin(u User) string {
|
||||||
// FIXME: Handle many clients
|
// FIXME: Use all connections?
|
||||||
conn, u := clients[0].conn, clients[0].user
|
conn := u.Connections()[0]
|
||||||
|
|
||||||
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
fingerprint := "(no public key)"
|
fingerprint := "(no public key)"
|
||||||
if conn.PublicKey() != nil {
|
if conn.PublicKey() != nil {
|
||||||
@ -42,5 +35,5 @@ func whoisAdmin(clients []Client) string {
|
|||||||
" > ip: " + ip + message.Newline +
|
" > ip: " + ip + message.Newline +
|
||||||
" > fingerprint: " + fingerprint + message.Newline +
|
" > fingerprint: " + fingerprint + message.Newline +
|
||||||
" > client: " + SanitizeData(string(conn.ClientVersion())) + 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