mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-06-07 19:03:17 +03:00
progress: refactor: Remove Identifier interface, reverse ownership of sshchat identity and message.User
This commit is contained in:
parent
f64b2a3607
commit
91718a511b
@ -1,26 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
// Identifier is an interface that can uniquely identify itself.
|
|
||||||
type Identifier interface {
|
|
||||||
ID() string
|
|
||||||
Name() string
|
|
||||||
SetName(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleID is a simple Identifier implementation used for testing.
|
|
||||||
type SimpleID string
|
|
||||||
|
|
||||||
// ID returns the ID as a string.
|
|
||||||
func (i SimpleID) ID() string {
|
|
||||||
return string(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the ID
|
|
||||||
func (i SimpleID) Name() string {
|
|
||||||
return i.ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetName is a no-op
|
|
||||||
func (i SimpleID) SetName(s string) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
@ -11,7 +11,7 @@ func TestMessage(t *testing.T) {
|
|||||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
u := NewUser(SimpleID("foo"))
|
u := NewUser("foo")
|
||||||
expected = "foo: hello"
|
expected = "foo: hello"
|
||||||
actual = NewPublicMsg("hello", u).String()
|
actual = NewPublicMsg("hello", u).String()
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package chat
|
package message
|
||||||
|
|
||||||
import "regexp"
|
import "regexp"
|
||||||
|
|
||||||
var reStripName = regexp.MustCompile("[^\\w.-]")
|
var reStripName = regexp.MustCompile("[^\\w.-]")
|
||||||
|
|
||||||
const maxLength = 16
|
const maxLength = 16
|
||||||
|
|
||||||
// SanitizeName returns a name with only allowed characters and a reasonable length
|
// SanitizeName returns a name with only allowed characters and a reasonable length
|
||||||
@ -15,10 +16,3 @@ func SanitizeName(s string) string {
|
|||||||
s = s[:nameLength]
|
s = s[:nameLength]
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
var reStripData = regexp.MustCompile("[^[:ascii:]]")
|
|
||||||
|
|
||||||
// SanitizeData returns a string with only allowed characters for client-provided metadata inputs.
|
|
||||||
func SanitizeData(s string) string {
|
|
||||||
return reStripData.ReplaceAllString(s, "")
|
|
||||||
}
|
|
@ -51,7 +51,7 @@ func TestTheme(t *testing.T) {
|
|||||||
t.Errorf("Got: %q; Expected: %q", actual, expected)
|
t.Errorf("Got: %q; Expected: %q", actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
u := NewUser(SimpleID("foo"))
|
u := NewUser("foo")
|
||||||
u.colorIdx = 4
|
u.colorIdx = 4
|
||||||
actual = colorTheme.ColorName(u)
|
actual = colorTheme.ColorName(u)
|
||||||
expected = "\033[38;05;5mfoo\033[0m"
|
expected = "\033[38;05;5mfoo\033[0m"
|
||||||
|
@ -18,7 +18,6 @@ var ErrUserClosed = errors.New("user closed")
|
|||||||
|
|
||||||
// User definition, implemented set Item interface and io.Writer
|
// User definition, implemented set Item interface and io.Writer
|
||||||
type User struct {
|
type User struct {
|
||||||
Identifier
|
|
||||||
colorIdx int
|
colorIdx int
|
||||||
joined time.Time
|
joined time.Time
|
||||||
msg chan Message
|
msg chan Message
|
||||||
@ -28,25 +27,26 @@ type User struct {
|
|||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
name string
|
||||||
config UserConfig
|
config UserConfig
|
||||||
replyTo *User // Set when user gets a /msg, for replying.
|
replyTo *User // Set when user gets a /msg, for replying.
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(identity Identifier) *User {
|
func NewUser(name string) *User {
|
||||||
u := User{
|
u := User{
|
||||||
Identifier: identity,
|
name: name,
|
||||||
config: DefaultUserConfig,
|
config: DefaultUserConfig,
|
||||||
joined: time.Now(),
|
joined: time.Now(),
|
||||||
msg: make(chan Message, messageBuffer),
|
msg: make(chan Message, messageBuffer),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
u.setColorIdx(rand.Int())
|
u.setColorIdx(rand.Int())
|
||||||
|
|
||||||
return &u
|
return &u
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
|
func NewUserScreen(name string, screen io.WriteCloser) *User {
|
||||||
u := NewUser(identity)
|
u := NewUser(name)
|
||||||
u.screen = screen
|
u.screen = screen
|
||||||
|
|
||||||
return u
|
return u
|
||||||
@ -64,10 +64,28 @@ func (u *User) SetConfig(cfg UserConfig) {
|
|||||||
u.mu.Unlock()
|
u.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) ID() string {
|
||||||
|
u.mu.Lock()
|
||||||
|
defer u.mu.Unlock()
|
||||||
|
return SanitizeName(u.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Name() string {
|
||||||
|
u.mu.Lock()
|
||||||
|
defer u.mu.Unlock()
|
||||||
|
return u.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Joined() time.Time {
|
||||||
|
return u.joined
|
||||||
|
}
|
||||||
|
|
||||||
// Rename the user with a new Identifier.
|
// Rename the user with a new Identifier.
|
||||||
func (u *User) SetName(name string) {
|
func (u *User) SetName(name string) {
|
||||||
u.Identifier.SetName(name)
|
u.mu.Lock()
|
||||||
|
u.name = name
|
||||||
u.setColorIdx(rand.Int())
|
u.setColorIdx(rand.Int())
|
||||||
|
u.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyTo returns the last user that messaged this user.
|
// ReplyTo returns the last user that messaged this user.
|
||||||
|
@ -9,7 +9,7 @@ func TestMakeUser(t *testing.T) {
|
|||||||
var actual, expected []byte
|
var actual, expected []byte
|
||||||
|
|
||||||
s := &MockScreen{}
|
s := &MockScreen{}
|
||||||
u := NewUserScreen(SimpleID("foo"), s)
|
u := NewUserScreen("foo", s)
|
||||||
m := NewAnnounceMsg("hello")
|
m := NewAnnounceMsg("hello")
|
||||||
|
|
||||||
defer u.Close()
|
defer u.Close()
|
||||||
|
@ -154,7 +154,7 @@ func (r *Room) Join(m Member) (*roomMember, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Leave the room as a user, will announce. Mostly used during setup.
|
// Leave the room as a user, will announce. Mostly used during setup.
|
||||||
func (r *Room) Leave(u message.Identifier) error {
|
func (r *Room) Leave(u Member) error {
|
||||||
err := r.Members.Remove(u.ID())
|
err := r.Members.Remove(u.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -166,7 +166,7 @@ func (r *Room) Leave(u message.Identifier) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename member with a new identity. This will not call rename on the member.
|
// Rename member with a new identity. This will not call rename on the member.
|
||||||
func (r *Room) Rename(oldID string, u message.Identifier) error {
|
func (r *Room) Rename(oldID string, u Member) error {
|
||||||
if u.ID() == "" {
|
if u.ID() == "" {
|
||||||
return ErrInvalidName
|
return ErrInvalidName
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@ func (r *Room) Rename(oldID string, u message.Identifier) error {
|
|||||||
|
|
||||||
// Member returns a corresponding Member object to a User if the Member is
|
// Member returns a corresponding Member object to a User if the Member is
|
||||||
// present in this room.
|
// present in this room.
|
||||||
func (r *Room) Member(u message.Identifier) (*roomMember, bool) {
|
func (r *Room) Member(u Member) (*roomMember, bool) {
|
||||||
m, ok := r.MemberByID(u.ID())
|
m, ok := r.MemberByID(u.ID())
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
@ -203,7 +203,7 @@ func (r *Room) MemberByID(id string) (*roomMember, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsOp returns whether a user is an operator in this room.
|
// IsOp returns whether a user is an operator in this room.
|
||||||
func (r *Room) IsOp(u message.Identifier) bool {
|
func (r *Room) IsOp(u Member) bool {
|
||||||
return r.Ops.In(u.ID())
|
return r.Ops.In(u.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ func TestIgnore(t *testing.T) {
|
|||||||
users := make([]ScreenedUser, 3)
|
users := make([]ScreenedUser, 3)
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
screen := &MockScreen{}
|
screen := &MockScreen{}
|
||||||
user := message.NewUserScreen(message.SimpleID(fmt.Sprintf("user%d", i)), screen)
|
user := message.NewUserScreen(fmt.Sprintf("user%d", i), screen)
|
||||||
users[i] = ScreenedUser{
|
users[i] = ScreenedUser{
|
||||||
user: user,
|
user: user,
|
||||||
screen: screen,
|
screen: screen,
|
||||||
@ -176,7 +176,7 @@ func TestRoomJoin(t *testing.T) {
|
|||||||
var expected, actual []byte
|
var expected, actual []byte
|
||||||
|
|
||||||
s := &MockScreen{}
|
s := &MockScreen{}
|
||||||
u := message.NewUserScreen(message.SimpleID("foo"), s)
|
u := message.NewUserScreen("foo", s)
|
||||||
|
|
||||||
ch := NewRoom()
|
ch := NewRoom()
|
||||||
go ch.Serve()
|
go ch.Serve()
|
||||||
@ -212,7 +212,7 @@ func TestRoomJoin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) {
|
func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) {
|
||||||
u := message.NewUser(message.SimpleID("foo"))
|
u := message.NewUser("foo")
|
||||||
u.SetConfig(message.UserConfig{
|
u.SetConfig(message.UserConfig{
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
})
|
})
|
||||||
@ -251,7 +251,7 @@ func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRoomQuietToggleBroadcasts(t *testing.T) {
|
func TestRoomQuietToggleBroadcasts(t *testing.T) {
|
||||||
u := message.NewUser(message.SimpleID("foo"))
|
u := message.NewUser("foo")
|
||||||
u.SetConfig(message.UserConfig{
|
u.SetConfig(message.UserConfig{
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
})
|
})
|
||||||
@ -294,7 +294,7 @@ func TestQuietToggleDisplayState(t *testing.T) {
|
|||||||
var expected, actual []byte
|
var expected, actual []byte
|
||||||
|
|
||||||
s := &MockScreen{}
|
s := &MockScreen{}
|
||||||
u := message.NewUserScreen(message.SimpleID("foo"), s)
|
u := message.NewUserScreen("foo", s)
|
||||||
|
|
||||||
ch := NewRoom()
|
ch := NewRoom()
|
||||||
go ch.Serve()
|
go ch.Serve()
|
||||||
@ -335,7 +335,7 @@ func TestRoomNames(t *testing.T) {
|
|||||||
var expected, actual []byte
|
var expected, actual []byte
|
||||||
|
|
||||||
s := &MockScreen{}
|
s := &MockScreen{}
|
||||||
u := message.NewUserScreen(message.SimpleID("foo"), s)
|
u := message.NewUserScreen("foo", s)
|
||||||
|
|
||||||
ch := NewRoom()
|
ch := NewRoom()
|
||||||
go ch.Serve()
|
go ch.Serve()
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
func TestSet(t *testing.T) {
|
func TestSet(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
s := set.New()
|
s := set.New()
|
||||||
u := message.NewUser(message.SimpleID("foo"))
|
u := message.NewUser("foo")
|
||||||
|
|
||||||
if s.In(u.ID()) {
|
if s.In(u.ID()) {
|
||||||
t.Errorf("Set should be empty.")
|
t.Errorf("Set should be empty.")
|
||||||
@ -25,7 +25,7 @@ func TestSet(t *testing.T) {
|
|||||||
t.Errorf("Set should contain user.")
|
t.Errorf("Set should contain user.")
|
||||||
}
|
}
|
||||||
|
|
||||||
u2 := message.NewUser(message.SimpleID("bar"))
|
u2 := message.NewUser("bar")
|
||||||
err = s.Add(set.Itemize(u2.ID(), u2))
|
err = s.Add(set.Itemize(u2.ID(), u2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
45
client.go
Normal file
45
client.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package sshchat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
humanize "github.com/dustin/go-humanize"
|
||||||
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
sshd.Connection
|
||||||
|
message.User
|
||||||
|
|
||||||
|
connected 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())
|
||||||
|
}
|
213
host.go
213
host.go
@ -80,8 +80,8 @@ func (h *Host) isOp(conn sshd.Connection) bool {
|
|||||||
|
|
||||||
// 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) {
|
||||||
ident := toIdentity(term.Conn)
|
requestedName := term.Conn.Name()
|
||||||
user := message.NewUserScreen(ident, term)
|
user := message.NewUserScreen(requestedName, term)
|
||||||
cfg := user.Config()
|
cfg := user.Config()
|
||||||
cfg.Theme = &h.theme
|
cfg.Theme = &h.theme
|
||||||
user.SetConfig(cfg)
|
user.SetConfig(cfg)
|
||||||
@ -105,7 +105,7 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
|||||||
member, err := h.Join(user)
|
member, err := h.Join(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try again...
|
// Try again...
|
||||||
ident.SetName(fmt.Sprintf("Guest%d", count))
|
user.SetName(fmt.Sprintf("Guest%d", count))
|
||||||
member, err = h.Join(user)
|
member, err = h.Join(user)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -331,34 +331,37 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Add(chat.Command{
|
// XXX: Temporarily disable whois
|
||||||
Prefix: "/whois",
|
/*
|
||||||
PrefixHelp: "USER",
|
c.Add(chat.Command{
|
||||||
Help: "Information about USER.",
|
Prefix: "/whois",
|
||||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
PrefixHelp: "USER",
|
||||||
args := msg.Args()
|
Help: "Information about USER.",
|
||||||
if len(args) == 0 {
|
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||||
return errors.New("must specify user")
|
args := msg.Args()
|
||||||
}
|
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)
|
id := target.Identifier.(*identity)
|
||||||
var whois string
|
var whois string
|
||||||
switch room.IsOp(msg.From()) {
|
switch room.IsOp(msg.From()) {
|
||||||
case true:
|
case true:
|
||||||
whois = id.WhoisAdmin()
|
whois = id.WhoisAdmin()
|
||||||
case false:
|
case false:
|
||||||
whois = id.Whois()
|
whois = id.Whois()
|
||||||
}
|
}
|
||||||
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{
|
||||||
@ -378,7 +381,85 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Op 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := msg.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("must specify user")
|
||||||
|
}
|
||||||
|
|
||||||
|
var until time.Duration = 0
|
||||||
|
if len(args) > 1 {
|
||||||
|
until, _ = time.ParseDuration(args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := h.GetUser(args[0])
|
||||||
|
if !ok {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
room.Ops.Add(set.Keyize(user.ID()))
|
||||||
|
|
||||||
|
h.auth.Op(user.Identifier.(*identity).PublicKey(), until)
|
||||||
|
|
||||||
|
body := fmt.Sprintf("Made op by %s.", msg.From().Name())
|
||||||
|
room.Send(message.NewSystemMsg(body, user))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Op commands
|
||||||
|
c.Add(chat.Command{
|
||||||
|
Op: true,
|
||||||
|
Prefix: "/ban",
|
||||||
|
PrefixHelp: "USER [DURATION]",
|
||||||
|
Help: "Ban USER from the server.",
|
||||||
|
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||||
|
// TODO: Would be nice to specify what to ban. Key? Ip? etc.
|
||||||
|
if !room.IsOp(msg.From()) {
|
||||||
|
return errors.New("must be op")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := msg.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("must specify user")
|
||||||
|
}
|
||||||
|
|
||||||
|
target, ok := h.GetUser(args[0])
|
||||||
|
if !ok {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var until time.Duration = 0
|
||||||
|
if len(args) > 1 {
|
||||||
|
until, _ = time.ParseDuration(args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
id := target.Identifier.(*identity)
|
||||||
|
h.auth.Ban(id.PublicKey(), until)
|
||||||
|
h.auth.BanAddr(id.RemoteAddr(), until)
|
||||||
|
|
||||||
|
body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
|
||||||
|
room.Send(message.NewAnnounceMsg(body))
|
||||||
|
target.Close()
|
||||||
|
|
||||||
|
logger.Debugf("Banned: \n-> %s", id.Whois())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
*/
|
||||||
c.Add(chat.Command{
|
c.Add(chat.Command{
|
||||||
Op: true,
|
Op: true,
|
||||||
Prefix: "/kick",
|
Prefix: "/kick",
|
||||||
@ -406,46 +487,6 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Add(chat.Command{
|
|
||||||
Op: true,
|
|
||||||
Prefix: "/ban",
|
|
||||||
PrefixHelp: "USER [DURATION]",
|
|
||||||
Help: "Ban USER from the server.",
|
|
||||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
|
||||||
// TODO: Would be nice to specify what to ban. Key? Ip? etc.
|
|
||||||
if !room.IsOp(msg.From()) {
|
|
||||||
return errors.New("must be op")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := msg.Args()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return errors.New("must specify user")
|
|
||||||
}
|
|
||||||
|
|
||||||
target, ok := h.GetUser(args[0])
|
|
||||||
if !ok {
|
|
||||||
return errors.New("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var until time.Duration = 0
|
|
||||||
if len(args) > 1 {
|
|
||||||
until, _ = time.ParseDuration(args[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
id := target.Identifier.(*identity)
|
|
||||||
h.auth.Ban(id.PublicKey(), until)
|
|
||||||
h.auth.BanAddr(id.RemoteAddr(), until)
|
|
||||||
|
|
||||||
body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
|
|
||||||
room.Send(message.NewAnnounceMsg(body))
|
|
||||||
target.Close()
|
|
||||||
|
|
||||||
logger.Debugf("Banned: \n-> %s", id.Whois())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
c.Add(chat.Command{
|
c.Add(chat.Command{
|
||||||
Op: true,
|
Op: true,
|
||||||
Prefix: "/motd",
|
Prefix: "/motd",
|
||||||
@ -476,38 +517,4 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := msg.Args()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return errors.New("must specify user")
|
|
||||||
}
|
|
||||||
|
|
||||||
var until time.Duration = 0
|
|
||||||
if len(args) > 1 {
|
|
||||||
until, _ = time.ParseDuration(args[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
user, ok := h.GetUser(args[0])
|
|
||||||
if !ok {
|
|
||||||
return errors.New("user not found")
|
|
||||||
}
|
|
||||||
room.Ops.Add(set.Keyize(user.ID()))
|
|
||||||
|
|
||||||
h.auth.Op(user.Identifier.(*identity).PublicKey(), until)
|
|
||||||
|
|
||||||
body := fmt.Sprintf("Made op by %s.", msg.From().Name())
|
|
||||||
room.Send(message.NewSystemMsg(body, user))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func stripPrompt(s string) string {
|
|||||||
func TestHostGetPrompt(t *testing.T) {
|
func TestHostGetPrompt(t *testing.T) {
|
||||||
var expected, actual string
|
var expected, actual string
|
||||||
|
|
||||||
u := message.NewUser(&identity{id: "foo"})
|
u := message.NewUser("foo")
|
||||||
|
|
||||||
actual = u.Prompt()
|
actual = u.Prompt()
|
||||||
expected = "[foo] "
|
expected = "[foo] "
|
||||||
|
65
identity.go
65
identity.go
@ -1,65 +0,0 @@
|
|||||||
package sshchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"github.com/shazow/ssh-chat/chat"
|
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
|
||||||
"github.com/shazow/ssh-chat/sshd"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Identity is a container for everything that identifies a client.
|
|
||||||
type identity struct {
|
|
||||||
sshd.Connection
|
|
||||||
id string
|
|
||||||
created time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts an sshd.Connection to an identity.
|
|
||||||
func toIdentity(conn sshd.Connection) *identity {
|
|
||||||
return &identity{
|
|
||||||
Connection: conn,
|
|
||||||
id: chat.SanitizeName(conn.Name()),
|
|
||||||
created: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i identity) ID() string {
|
|
||||||
return i.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *identity) SetName(name string) {
|
|
||||||
i.id = chat.SanitizeName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i identity) Name() string {
|
|
||||||
return i.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whois returns a whois description for non-admin users.
|
|
||||||
func (i identity) Whois() string {
|
|
||||||
fingerprint := "(no public key)"
|
|
||||||
if i.PublicKey() != nil {
|
|
||||||
fingerprint = sshd.Fingerprint(i.PublicKey())
|
|
||||||
}
|
|
||||||
return "name: " + i.Name() + message.Newline +
|
|
||||||
" > fingerprint: " + fingerprint + message.Newline +
|
|
||||||
" > client: " + chat.SanitizeData(string(i.ClientVersion())) + message.Newline +
|
|
||||||
" > joined: " + humanize.Time(i.created)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WhoisAdmin returns a whois description for admin users.
|
|
||||||
func (i identity) WhoisAdmin() string {
|
|
||||||
ip, _, _ := net.SplitHostPort(i.RemoteAddr().String())
|
|
||||||
fingerprint := "(no public key)"
|
|
||||||
if i.PublicKey() != nil {
|
|
||||||
fingerprint = sshd.Fingerprint(i.PublicKey())
|
|
||||||
}
|
|
||||||
return "name: " + i.Name() + message.Newline +
|
|
||||||
" > ip: " + ip + message.Newline +
|
|
||||||
" > fingerprint: " + fingerprint + message.Newline +
|
|
||||||
" > client: " + chat.SanitizeData(string(i.ClientVersion())) + message.Newline +
|
|
||||||
" > joined: " + humanize.Time(i.created)
|
|
||||||
}
|
|
10
sanitize.go
Normal file
10
sanitize.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package sshchat
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
var reStripData = regexp.MustCompile("[^[:ascii:]]")
|
||||||
|
|
||||||
|
// SanitizeData returns a string with only allowed characters for client-provided metadata inputs.
|
||||||
|
func SanitizeData(s string) string {
|
||||||
|
return reStripData.ReplaceAllString(s, "")
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user