mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-06-07 10:53:07 +03:00
progress: broken: Multi-client support, tests fail, Whois factored
This commit is contained in:
parent
810ef13bea
commit
5f2a230ecc
36
client.go
36
client.go
@ -1,45 +1,15 @@
|
|||||||
package sshchat
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
"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 {
|
||||||
sshd.Connection
|
user *message.User
|
||||||
message.User
|
conn sshd.Connection
|
||||||
|
|
||||||
connected time.Time
|
timestamp time.Time
|
||||||
}
|
|
||||||
|
|
||||||
// Whois returns a whois description for non-admin users.
|
|
||||||
func (client Client) Whois() string {
|
|
||||||
conn, u := client.Connection, client.User
|
|
||||||
fingerprint := "(no public key)"
|
|
||||||
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.Joined())
|
|
||||||
}
|
|
||||||
|
|
||||||
// WhoisAdmin returns a whois description for admin users.
|
|
||||||
func (client Client) WhoisAdmin() string {
|
|
||||||
conn, u := client.Connection, client.User
|
|
||||||
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
|
||||||
fingerprint := "(no public key)"
|
|
||||||
if conn.PublicKey() != nil {
|
|
||||||
fingerprint = sshd.Fingerprint(conn.PublicKey())
|
|
||||||
}
|
|
||||||
return "name: " + u.Name() + message.Newline +
|
|
||||||
" > ip: " + ip + message.Newline +
|
|
||||||
" > fingerprint: " + fingerprint + message.Newline +
|
|
||||||
" > client: " + SanitizeData(string(conn.ClientVersion())) + message.Newline +
|
|
||||||
" > joined: " + humanize.Time(u.Joined())
|
|
||||||
}
|
}
|
||||||
|
269
host.go
269
host.go
@ -32,9 +32,10 @@ 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[*message.User][]Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHost creates a Host on top of an existing listener.
|
// NewHost creates a Host on top of an existing listener.
|
||||||
@ -45,6 +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[*message.User][]Client{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make our own commands registry instance.
|
// Make our own commands registry instance.
|
||||||
@ -70,26 +72,13 @@ func (h *Host) SetMotd(motd string) {
|
|||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) isOp(conn sshd.Connection) bool {
|
|
||||||
key := conn.PublicKey()
|
|
||||||
if key == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return h.auth.IsOp(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.NewUserScreen(requestedName, term)
|
user := message.NewUserScreen(requestedName, term)
|
||||||
cfg := user.Config()
|
|
||||||
cfg.Theme = &h.theme
|
|
||||||
user.SetConfig(cfg)
|
|
||||||
go user.Consume()
|
|
||||||
|
|
||||||
// Close term once user is closed.
|
client := h.addClient(user, term.Conn)
|
||||||
defer user.Close()
|
defer h.removeClient(user, client)
|
||||||
defer term.Close()
|
|
||||||
|
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
motd := h.motd
|
motd := h.motd
|
||||||
@ -97,6 +86,16 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
h.count++
|
h.count++
|
||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
|
|
||||||
|
cfg := user.Config()
|
||||||
|
cfg.Theme = &h.theme
|
||||||
|
user.SetConfig(cfg)
|
||||||
|
|
||||||
|
// Close term once user is closed.
|
||||||
|
defer user.Close()
|
||||||
|
defer term.Close()
|
||||||
|
|
||||||
|
go user.Consume()
|
||||||
|
|
||||||
// Send MOTD
|
// Send MOTD
|
||||||
if motd != "" {
|
if motd != "" {
|
||||||
user.Send(message.NewAnnounceMsg(motd))
|
user.Send(message.NewAnnounceMsg(motd))
|
||||||
@ -119,11 +118,17 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
user.SetHighlight(user.Name())
|
user.SetHighlight(user.Name())
|
||||||
|
|
||||||
// Should the user be op'd on join?
|
// Should the user be op'd on join?
|
||||||
if h.isOp(term.Conn) {
|
if key := term.Conn.PublicKey(); key != nil {
|
||||||
h.Room.Ops.Add(set.Keyize(member.ID()))
|
authItem, err := h.auth.ops.Get(newAuthKey(key))
|
||||||
|
if err != nil {
|
||||||
|
err = h.Room.Ops.Add(set.Rename(authItem, member.ID()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("[%s] Failed to op: %s", term.Conn.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", term.Conn.RemoteAddr(), user.Name())
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -175,6 +180,41 @@ 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 *message.User, conn sshd.Connection) *Client {
|
||||||
|
client := Client{
|
||||||
|
user: user,
|
||||||
|
conn: conn,
|
||||||
|
timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
h.clients[user] = append(h.clients[user], client)
|
||||||
|
h.mu.Unlock()
|
||||||
|
return &client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Host) removeClient(user *message.User, 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 *message.User) []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
|
||||||
@ -331,37 +371,35 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// XXX: Temporarily disable whois
|
c.Add(chat.Command{
|
||||||
/*
|
Prefix: "/whois",
|
||||||
c.Add(chat.Command{
|
PrefixHelp: "USER",
|
||||||
Prefix: "/whois",
|
Help: "Information about USER.",
|
||||||
PrefixHelp: "USER",
|
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||||
Help: "Information about USER.",
|
args := msg.Args()
|
||||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
if len(args) == 0 {
|
||||||
args := msg.Args()
|
return errors.New("must specify user")
|
||||||
if len(args) == 0 {
|
}
|
||||||
return errors.New("must specify user")
|
|
||||||
}
|
|
||||||
|
|
||||||
target, ok := h.GetUser(args[0])
|
target, ok := h.GetUser(args[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("user not found")
|
return errors.New("user not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
id := target.Identifier.(*identity)
|
// FIXME: Handle many clients
|
||||||
var whois string
|
clients := h.findClients(target)
|
||||||
switch room.IsOp(msg.From()) {
|
var whois string
|
||||||
case true:
|
switch room.IsOp(msg.From()) {
|
||||||
whois = id.WhoisAdmin()
|
case true:
|
||||||
case false:
|
whois = whoisAdmin(clients)
|
||||||
whois = id.Whois()
|
case false:
|
||||||
}
|
whois = whoisPublic(clients)
|
||||||
room.Send(message.NewSystemMsg(whois, msg.From()))
|
}
|
||||||
|
room.Send(message.NewSystemMsg(whois, msg.From()))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
|
|
||||||
// Hidden commands
|
// Hidden commands
|
||||||
c.Add(chat.Command{
|
c.Add(chat.Command{
|
||||||
@ -381,85 +419,90 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// XXX: Temporarily disable op and ban
|
c.Add(chat.Command{
|
||||||
/*
|
Op: true,
|
||||||
|
Prefix: "/op",
|
||||||
|
PrefixHelp: "USER [DURATION]",
|
||||||
|
Help: "Set USER as admin.",
|
||||||
|
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||||
|
if !room.IsOp(msg.From()) {
|
||||||
|
return errors.New("must be op")
|
||||||
|
}
|
||||||
|
|
||||||
c.Add(chat.Command{
|
args := msg.Args()
|
||||||
Op: true,
|
if len(args) == 0 {
|
||||||
Prefix: "/op",
|
return errors.New("must specify user")
|
||||||
PrefixHelp: "USER [DURATION]",
|
}
|
||||||
Help: "Set USER as admin.",
|
|
||||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
|
||||||
if !room.IsOp(msg.From()) {
|
|
||||||
return errors.New("must be op")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := msg.Args()
|
var until time.Duration = 0
|
||||||
if len(args) == 0 {
|
if len(args) > 1 {
|
||||||
return errors.New("must specify user")
|
until, _ = time.ParseDuration(args[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
var until time.Duration = 0
|
user, ok := h.GetUser(args[0])
|
||||||
if len(args) > 1 {
|
if !ok {
|
||||||
until, _ = time.ParseDuration(args[1])
|
return errors.New("user not found")
|
||||||
}
|
}
|
||||||
|
if until != 0 {
|
||||||
user, ok := h.GetUser(args[0])
|
room.Ops.Add(set.Expire(set.Keyize(user.ID()), until))
|
||||||
if !ok {
|
} else {
|
||||||
return errors.New("user not found")
|
|
||||||
}
|
|
||||||
room.Ops.Add(set.Keyize(user.ID()))
|
room.Ops.Add(set.Keyize(user.ID()))
|
||||||
|
}
|
||||||
|
|
||||||
h.auth.Op(user.Identifier.(*identity).PublicKey(), until)
|
for _, client := range h.findClients(user) {
|
||||||
|
h.auth.Op(client.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))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Op commands
|
// Op commands
|
||||||
c.Add(chat.Command{
|
c.Add(chat.Command{
|
||||||
Op: true,
|
Op: true,
|
||||||
Prefix: "/ban",
|
Prefix: "/ban",
|
||||||
PrefixHelp: "USER [DURATION]",
|
PrefixHelp: "USER [DURATION]",
|
||||||
Help: "Ban USER from the server.",
|
Help: "Ban USER from the server.",
|
||||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||||
// TODO: Would be nice to specify what to ban. Key? Ip? etc.
|
// TODO: Would be nice to specify what to ban. Key? Ip? etc.
|
||||||
if !room.IsOp(msg.From()) {
|
if !room.IsOp(msg.From()) {
|
||||||
return errors.New("must be op")
|
return errors.New("must be op")
|
||||||
}
|
}
|
||||||
|
|
||||||
args := msg.Args()
|
args := msg.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("must specify user")
|
return errors.New("must specify user")
|
||||||
}
|
}
|
||||||
|
|
||||||
target, ok := h.GetUser(args[0])
|
target, ok := h.GetUser(args[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("user not found")
|
return errors.New("user not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
var until time.Duration = 0
|
var until time.Duration = 0
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
until, _ = time.ParseDuration(args[1])
|
until, _ = time.ParseDuration(args[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
id := target.Identifier.(*identity)
|
clients := h.findClients(target)
|
||||||
h.auth.Ban(id.PublicKey(), until)
|
for _, client := range clients {
|
||||||
h.auth.BanAddr(id.RemoteAddr(), until)
|
h.auth.Ban(client.conn.PublicKey(), 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.Close()
|
target.Close()
|
||||||
|
|
||||||
logger.Debugf("Banned: \n-> %s", id.Whois())
|
logger.Debugf("Banned: \n-> %s", whoisAdmin(clients))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
c.Add(chat.Command{
|
c.Add(chat.Command{
|
||||||
Op: true,
|
Op: true,
|
||||||
Prefix: "/kick",
|
Prefix: "/kick",
|
||||||
|
41
whois.go
Normal file
41
whois.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package sshchat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
humanize "github.com/dustin/go-humanize"
|
||||||
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helpers for printing whois messages
|
||||||
|
|
||||||
|
func whoisPublic(clients []Client) string {
|
||||||
|
// FIXME: Handle many clients
|
||||||
|
conn, u := clients[0].conn, clients[0].user
|
||||||
|
|
||||||
|
fingerprint := "(no public key)"
|
||||||
|
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.Joined())
|
||||||
|
}
|
||||||
|
|
||||||
|
func whoisAdmin(clients []Client) string {
|
||||||
|
// FIXME: Handle many clients
|
||||||
|
conn, u := clients[0].conn, clients[0].user
|
||||||
|
|
||||||
|
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
fingerprint := "(no public key)"
|
||||||
|
if conn.PublicKey() != nil {
|
||||||
|
fingerprint = sshd.Fingerprint(conn.PublicKey())
|
||||||
|
}
|
||||||
|
return "name: " + u.Name() + message.Newline +
|
||||||
|
" > ip: " + ip + message.Newline +
|
||||||
|
" > fingerprint: " + fingerprint + message.Newline +
|
||||||
|
" > client: " + SanitizeData(string(conn.ClientVersion())) + message.Newline +
|
||||||
|
" > joined: " + humanize.Time(u.Joined())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user