mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-24 12:30:56 +03:00
Merge 229c2d793d964bb48f45ea17623b498d798f5dcb into fd77009f8da6f3aa1511596ff92300564b40fed3
This commit is contained in:
commit
1672890174
24
auth.go
24
auth.go
@ -44,6 +44,7 @@ type Auth struct {
|
||||
banned *set.Set
|
||||
whitelist *set.Set
|
||||
ops *set.Set
|
||||
masters *set.Set
|
||||
}
|
||||
|
||||
// NewAuth creates a new empty Auth.
|
||||
@ -53,6 +54,7 @@ func NewAuth() *Auth {
|
||||
banned: set.New(),
|
||||
whitelist: set.New(),
|
||||
ops: set.New(),
|
||||
masters: set.New(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +101,20 @@ func (a *Auth) Op(key ssh.PublicKey, d time.Duration) {
|
||||
logger.Debugf("Added to ops: %s (for %s)", authItem.Key(), d)
|
||||
}
|
||||
|
||||
// Master sets a public key as a known admin master.
|
||||
func (a *Auth) Master(key ssh.PublicKey, d time.Duration) {
|
||||
if key == nil {
|
||||
return
|
||||
}
|
||||
authItem := newAuthItem(key)
|
||||
if d != 0 {
|
||||
a.masters.Add(set.Expire(authItem, d))
|
||||
} else {
|
||||
a.masters.Add(authItem)
|
||||
}
|
||||
logger.Debugf("Added to masters: %s (for %s)", authItem.Key(), d)
|
||||
}
|
||||
|
||||
// IsOp checks if a public key is an op.
|
||||
func (a *Auth) IsOp(key ssh.PublicKey) bool {
|
||||
if key == nil {
|
||||
@ -108,6 +124,14 @@ func (a *Auth) IsOp(key ssh.PublicKey) bool {
|
||||
return a.ops.In(authkey)
|
||||
}
|
||||
|
||||
func (a *Auth) IsMaster(key ssh.PublicKey) bool {
|
||||
if key == nil {
|
||||
return false
|
||||
}
|
||||
authkey := newAuthKey(key)
|
||||
return a.masters.In(authkey)
|
||||
}
|
||||
|
||||
// Whitelist will set a public key as a whitelisted user.
|
||||
func (a *Auth) Whitelist(key ssh.PublicKey, d time.Duration) {
|
||||
if key == nil {
|
||||
|
@ -36,6 +36,8 @@ type Command struct {
|
||||
Handler func(*Room, message.CommandMsg) error
|
||||
// Command requires Op permissions
|
||||
Op bool
|
||||
// command requires Admin permissions
|
||||
Admin bool
|
||||
}
|
||||
|
||||
// Commands is a registry of available commands.
|
||||
@ -68,7 +70,7 @@ func (c Commands) Run(room *Room, msg message.CommandMsg) error {
|
||||
return ErrNoOwner
|
||||
}
|
||||
|
||||
cmd, ok := c[msg.Command()]
|
||||
cmd, ok := c[msg.Command()[1:]]
|
||||
if !ok {
|
||||
return ErrInvalidCommand
|
||||
}
|
||||
@ -77,14 +79,19 @@ func (c Commands) Run(room *Room, msg message.CommandMsg) error {
|
||||
}
|
||||
|
||||
// Help will return collated help text as one string.
|
||||
func (c Commands) Help(showOp bool) string {
|
||||
func (c Commands) Help(showOp, showAdmin bool) string {
|
||||
// Filter by op
|
||||
op := []*Command{}
|
||||
admin := []*Command{}
|
||||
normal := []*Command{}
|
||||
for _, cmd := range c {
|
||||
if cmd.Op {
|
||||
op = append(op, cmd)
|
||||
} else {
|
||||
}
|
||||
if cmd.Admin {
|
||||
admin = append(admin, cmd)
|
||||
}
|
||||
if !cmd.Op && !cmd.Admin {
|
||||
normal = append(normal, cmd)
|
||||
}
|
||||
}
|
||||
@ -92,6 +99,9 @@ func (c Commands) Help(showOp bool) string {
|
||||
if showOp {
|
||||
help += message.Newline + "-> Operator commands:" + message.Newline + NewCommandsHelp(op).String()
|
||||
}
|
||||
if showAdmin {
|
||||
help += message.Newline + "-> Admin commands:" + message.Newline + NewCommandsHelp(admin).String()
|
||||
}
|
||||
return help
|
||||
}
|
||||
|
||||
@ -105,16 +115,17 @@ func init() {
|
||||
// InitCommands injects default commands into a Commands registry.
|
||||
func InitCommands(c *Commands) {
|
||||
c.Add(Command{
|
||||
Prefix: "/help",
|
||||
Prefix: "help",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
op := room.IsOp(msg.From())
|
||||
room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
|
||||
admin := room.IsMaster(msg.From())
|
||||
room.Send(message.NewSystemMsg(room.commands.Help(op, admin), msg.From()))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/me",
|
||||
Prefix: "me",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
me := strings.TrimLeft(msg.Body(), "/me")
|
||||
if me == "" {
|
||||
@ -129,17 +140,18 @@ func InitCommands(c *Commands) {
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/exit",
|
||||
Prefix: "exit",
|
||||
Help: "Exit the chat.",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
msg.From().Close()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
c.Alias("/exit", "/quit")
|
||||
c.Alias("exit", "quit")
|
||||
c.Alias("q", "quit")
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/nick",
|
||||
Prefix: "nick",
|
||||
PrefixHelp: "NAME",
|
||||
Help: "Rename yourself.",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
@ -170,7 +182,38 @@ func InitCommands(c *Commands) {
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/names",
|
||||
Admin: true,
|
||||
Prefix: "setnick",
|
||||
PrefixHelp: "NAME ANOTHER",
|
||||
Help: "Rename NAME to ANOTHER username",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
args := msg.Args()
|
||||
if len(args) < 2 {
|
||||
return errors.New("invalid arguments")
|
||||
}
|
||||
|
||||
member, ok := room.MemberByID(args[0])
|
||||
if !ok {
|
||||
return errors.New("failed to find member")
|
||||
}
|
||||
|
||||
oldID := member.ID()
|
||||
newID := SanitizeName(args[1])
|
||||
if newID == oldID {
|
||||
return errors.New("new name is the same as the original")
|
||||
}
|
||||
member.SetID(newID)
|
||||
err := room.Rename(oldID, member)
|
||||
if err != nil {
|
||||
member.SetID(oldID)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "names",
|
||||
Help: "List users who are connected.",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
theme := msg.From().Config().Theme
|
||||
@ -196,10 +239,10 @@ func InitCommands(c *Commands) {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
c.Alias("/names", "/list")
|
||||
c.Alias("names", "list")
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/theme",
|
||||
Prefix: "theme",
|
||||
PrefixHelp: "[colors|...]",
|
||||
Help: "Set your color theme.",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
@ -239,7 +282,7 @@ func InitCommands(c *Commands) {
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/quiet",
|
||||
Prefix: "quiet",
|
||||
Help: "Silence room announcements.",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
u := msg.From()
|
||||
@ -259,7 +302,7 @@ func InitCommands(c *Commands) {
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/slap",
|
||||
Prefix: "slap",
|
||||
PrefixHelp: "NAME",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
var me string
|
||||
@ -276,7 +319,7 @@ func InitCommands(c *Commands) {
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/ignore",
|
||||
Prefix: "ignore",
|
||||
PrefixHelp: "[USER]",
|
||||
Help: "Hide messages from USER, /unignore USER to stop hiding.",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
@ -321,7 +364,7 @@ func InitCommands(c *Commands) {
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/unignore",
|
||||
Prefix: "unignore",
|
||||
PrefixHelp: "USER",
|
||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||
id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/unignore"))
|
||||
|
@ -5,6 +5,8 @@ type Identifier interface {
|
||||
ID() string
|
||||
SetID(string)
|
||||
Name() string
|
||||
Chat() string
|
||||
SetChat(string)
|
||||
}
|
||||
|
||||
// SimpleID is a simple Identifier implementation used for testing.
|
||||
@ -24,3 +26,12 @@ func (i SimpleID) SetID(s string) {
|
||||
func (i SimpleID) Name() string {
|
||||
return i.ID()
|
||||
}
|
||||
|
||||
// Chat returns the chat name
|
||||
func (i SimpleID) Chat() string {
|
||||
return i.Chat()
|
||||
}
|
||||
|
||||
func (i SimpleID) SetChat(s string) {
|
||||
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (m PublicMsg) From() *User {
|
||||
|
||||
func (m PublicMsg) ParseCommand() (*CommandMsg, bool) {
|
||||
// Check if the message is a command
|
||||
if !strings.HasPrefix(m.body, "/") {
|
||||
if !strings.HasPrefix(m.body, "/") && !strings.HasPrefix(m.body, ":") {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
12
chat/room.go
12
chat/room.go
@ -10,8 +10,8 @@ import (
|
||||
"github.com/shazow/ssh-chat/set"
|
||||
)
|
||||
|
||||
const historyLen = 20
|
||||
const roomBuffer = 10
|
||||
const historyLen = 5
|
||||
const roomBuffer = 250
|
||||
|
||||
// The error returned when a message is sent to a room that is already
|
||||
// closed.
|
||||
@ -37,6 +37,7 @@ type Room struct {
|
||||
|
||||
Members *set.Set
|
||||
Ops *set.Set
|
||||
Masters *set.Set
|
||||
}
|
||||
|
||||
// NewRoom creates a new room.
|
||||
@ -50,6 +51,7 @@ func NewRoom() *Room {
|
||||
|
||||
Members: set.New(),
|
||||
Ops: set.New(),
|
||||
Masters: set.New(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,6 +167,7 @@ func (r *Room) Leave(u message.Identifier) error {
|
||||
return err
|
||||
}
|
||||
r.Ops.Remove(u.ID())
|
||||
r.Masters.Remove(u.ID())
|
||||
s := fmt.Sprintf("%s left.", u.Name())
|
||||
r.Send(message.NewAnnounceMsg(s))
|
||||
return nil
|
||||
@ -212,6 +215,11 @@ func (r *Room) IsOp(u *message.User) bool {
|
||||
return r.Ops.In(u.ID())
|
||||
}
|
||||
|
||||
// IsMaster returns whether a user is an admin master in this room.
|
||||
func (r *Room) IsMaster(u *message.User) bool {
|
||||
return r.Masters.In(u.ID())
|
||||
}
|
||||
|
||||
// Topic of the room.
|
||||
func (r *Room) Topic() string {
|
||||
return r.topic
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/alexcesaro/log"
|
||||
"github.com/alexcesaro/log/golog"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
@ -31,7 +32,8 @@ type Options struct {
|
||||
Version bool `long:"version" description:"Print version and exit."`
|
||||
Identity string `short:"i" long:"identity" description:"Private key to identify server with." default:"~/.ssh/id_rsa"`
|
||||
Bind string `long:"bind" description:"Host and port to listen on." default:"0.0.0.0:2022"`
|
||||
Admin string `long:"admin" description:"File of public keys who are admins."`
|
||||
Mods string `long:"moderators" description:"File of public keys who are moderators."`
|
||||
Admins string `long:"admins" description:"File of public keys who are admins."`
|
||||
Whitelist string `long:"whitelist" description:"Optional file of public keys who are allowed to connect."`
|
||||
Motd string `long:"motd" description:"Optional Message of the Day file."`
|
||||
Log string `long:"log" description:"Write chat log to this file."`
|
||||
@ -123,7 +125,18 @@ func main() {
|
||||
host.SetTheme(message.Themes[0])
|
||||
host.Version = Version
|
||||
|
||||
err = fromFile(options.Admin, func(line []byte) error {
|
||||
err = fromFile(options.Admins, func(line []byte) error {
|
||||
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth.Master(key, 0)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fail(5, "Failed to load admins: %v\n", err)
|
||||
}
|
||||
err = fromFile(options.Mods, func(line []byte) error {
|
||||
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -132,7 +145,7 @@ func main() {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fail(5, "Failed to load admins: %v\n", err)
|
||||
fail(5, "Failed to load mods: %v\n", err)
|
||||
}
|
||||
|
||||
err = fromFile(options.Whitelist, func(line []byte) error {
|
||||
@ -169,7 +182,45 @@ func main() {
|
||||
host.SetLogging(fp)
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
go host.Serve()
|
||||
go func() {
|
||||
for event := range watcher.Events {
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
if event.Name == options.Admins {
|
||||
err = fromFile(options.Admins, func(line []byte) error {
|
||||
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth.Master(key, 0)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if event.Name == options.Mods {
|
||||
err = fromFile(options.Mods, func(line []byte) error {
|
||||
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth.Op(key, 0)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if len(options.Admins) > 0 {
|
||||
watcher.Add(options.Admins)
|
||||
}
|
||||
if len(options.Mods) > 0 {
|
||||
watcher.Add(options.Mods)
|
||||
}
|
||||
|
||||
// Construct interrupt handler
|
||||
sig := make(chan os.Signal, 1)
|
||||
@ -177,7 +228,6 @@ func main() {
|
||||
|
||||
<-sig // Wait for ^C signal
|
||||
fmt.Fprintln(os.Stderr, "Interrupt signal detected, shutting down.")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func fromFile(path string, handler func(line []byte) error) error {
|
||||
|
198
host.go
198
host.go
@ -25,7 +25,7 @@ func GetPrompt(user *message.User) string {
|
||||
if cfg.Theme != nil {
|
||||
name = cfg.Theme.ColorName(user)
|
||||
}
|
||||
return fmt.Sprintf("[%s] ", name)
|
||||
return fmt.Sprintf("[%s:%s] ", name, user.Chat())
|
||||
}
|
||||
|
||||
// Host is the bridge between sshd and chat modules
|
||||
@ -42,8 +42,11 @@ type Host struct {
|
||||
// Default theme
|
||||
theme message.Theme
|
||||
|
||||
mu sync.Mutex
|
||||
motd string
|
||||
mu sync.Mutex
|
||||
motd string
|
||||
// start private mode
|
||||
private map[string]*message.User
|
||||
|
||||
count int
|
||||
}
|
||||
|
||||
@ -88,6 +91,14 @@ func (h *Host) isOp(conn sshd.Connection) bool {
|
||||
return h.auth.IsOp(key)
|
||||
}
|
||||
|
||||
func (h *Host) isMaster(conn sshd.Connection) bool {
|
||||
key := conn.PublicKey()
|
||||
if key == nil {
|
||||
return false
|
||||
}
|
||||
return h.auth.IsMaster(key)
|
||||
}
|
||||
|
||||
// Connect a specific Terminal to this host and its room.
|
||||
func (h *Host) Connect(term *sshd.Terminal) {
|
||||
id := NewIdentity(term.Conn)
|
||||
@ -124,6 +135,7 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
||||
}
|
||||
|
||||
// Successfully joined.
|
||||
user.SetChat("general")
|
||||
term.SetPrompt(GetPrompt(user))
|
||||
term.AutoCompleteCallback = h.AutoCompleteFunction(user)
|
||||
user.SetHighlight(user.Name())
|
||||
@ -132,10 +144,15 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
||||
if h.isOp(term.Conn) {
|
||||
h.Room.Ops.Add(set.Itemize(member.ID(), member))
|
||||
}
|
||||
if h.isMaster(term.Conn) {
|
||||
h.Room.Masters.Add(set.Itemize(member.ID(), member))
|
||||
}
|
||||
ratelimit := rateio.NewSimpleLimiter(3, time.Second*3)
|
||||
|
||||
logger.Debugf("[%s] Joined: %s", term.Conn.RemoteAddr(), user.Name())
|
||||
|
||||
var args []string
|
||||
h.private = make(map[string]*message.User)
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err == io.EOF {
|
||||
@ -162,19 +179,35 @@ func (h *Host) Connect(term *sshd.Terminal) {
|
||||
|
||||
m := message.ParseInput(line, user)
|
||||
|
||||
// FIXME: Any reason to use h.room.Send(m) instead?
|
||||
h.HandleMsg(m)
|
||||
switch c := m.(type) {
|
||||
case *message.CommandMsg:
|
||||
args = c.Args()
|
||||
h.HandleMsg(m)
|
||||
default:
|
||||
to, ok := h.private[user.Name()]
|
||||
if ok {
|
||||
m = message.NewPrivateMsg(
|
||||
m.String(), user, to,
|
||||
)
|
||||
}
|
||||
h.HandleMsg(m)
|
||||
}
|
||||
|
||||
cmd := m.Command()
|
||||
if cmd == "/nick" || cmd == "/theme" {
|
||||
// Hijack /nick command to update terminal synchronously. Wouldn't
|
||||
// work if we use h.room.Send(m) above.
|
||||
//
|
||||
// FIXME: This is hacky, how do we improve the API to allow for
|
||||
// this? Chat module shouldn't know about terminals.
|
||||
if cmd := m.Command(); len(cmd) > 0 {
|
||||
switch cmd[1:] {
|
||||
case "private":
|
||||
if len(args) > 0 {
|
||||
user.SetChat(args[0])
|
||||
}
|
||||
case "endprivate":
|
||||
user.SetChat("general")
|
||||
case "nick":
|
||||
case "theme":
|
||||
}
|
||||
term.SetPrompt(GetPrompt(user))
|
||||
user.SetHighlight(user.Name())
|
||||
}
|
||||
args = nil
|
||||
}
|
||||
|
||||
err = h.Leave(user)
|
||||
@ -280,7 +313,7 @@ func (h *Host) GetUser(name string) (*message.User, bool) {
|
||||
// override any existing commands.
|
||||
func (h *Host) InitCommands(c *chat.Commands) {
|
||||
c.Add(chat.Command{
|
||||
Prefix: "/msg",
|
||||
Prefix: "msg",
|
||||
PrefixHelp: "USER MESSAGE",
|
||||
Help: "Send MESSAGE to USER.",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
@ -309,7 +342,59 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Prefix: "/reply",
|
||||
Prefix: "private",
|
||||
PrefixHelp: "USER",
|
||||
Help: "Start private chat with USER",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
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")
|
||||
}
|
||||
|
||||
h.private[msg.From().Name()] = target
|
||||
|
||||
txt := fmt.Sprintf("[Private mode started with %s]", target.Name())
|
||||
ms := message.NewSystemMsg(txt, msg.From())
|
||||
room.Send(ms)
|
||||
target.SetReplyTo(msg.From())
|
||||
return nil
|
||||
},
|
||||
})
|
||||
c.Alias("p", "private")
|
||||
|
||||
c.Add(chat.Command{
|
||||
Admin: true,
|
||||
Prefix: "welcome",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
if !room.IsMaster(msg.From()) {
|
||||
return errors.New("must be admin")
|
||||
}
|
||||
|
||||
room.Send(message.NewMsg(h.motd))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Prefix: "endprivate",
|
||||
Help: "Stop private chat",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
delete(h.private, msg.From().Name())
|
||||
|
||||
txt := "[Private mode ended]"
|
||||
ms := message.NewSystemMsg(txt, msg.From())
|
||||
room.Send(ms)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Prefix: "reply",
|
||||
PrefixHelp: "MESSAGE",
|
||||
Help: "Reply with MESSAGE to the previous private message.",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
@ -341,7 +426,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Prefix: "/whois",
|
||||
Prefix: "whois",
|
||||
PrefixHelp: "USER",
|
||||
Help: "Information about USER.",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
@ -357,11 +442,15 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
|
||||
id := target.Identifier.(*Identity)
|
||||
var whois string
|
||||
switch room.IsOp(msg.From()) {
|
||||
switch room.IsMaster(msg.From()) {
|
||||
case true:
|
||||
whois = id.WhoisAdmin()
|
||||
whois = id.WhoisMaster()
|
||||
case false:
|
||||
whois = id.Whois()
|
||||
if room.IsOp(msg.From()) {
|
||||
whois = id.WhoisAdmin()
|
||||
} else {
|
||||
whois = id.Whois()
|
||||
}
|
||||
}
|
||||
room.Send(message.NewSystemMsg(whois, msg.From()))
|
||||
|
||||
@ -371,7 +460,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
|
||||
// Hidden commands
|
||||
c.Add(chat.Command{
|
||||
Prefix: "/version",
|
||||
Prefix: "version",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
room.Send(message.NewSystemMsg(h.Version, msg.From()))
|
||||
return nil
|
||||
@ -380,7 +469,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
|
||||
timeStarted := time.Now()
|
||||
c.Add(chat.Command{
|
||||
Prefix: "/uptime",
|
||||
Prefix: "uptime",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
room.Send(message.NewSystemMsg(humanize.Time(timeStarted), msg.From()))
|
||||
return nil
|
||||
@ -390,11 +479,12 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
// Op commands
|
||||
c.Add(chat.Command{
|
||||
Op: true,
|
||||
Prefix: "/kick",
|
||||
Admin: true,
|
||||
Prefix: "kick",
|
||||
PrefixHelp: "USER",
|
||||
Help: "Kick USER from the server.",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
if !room.IsOp(msg.From()) {
|
||||
if !room.IsOp(msg.From()) && !room.IsMaster(msg.From()) {
|
||||
return errors.New("must be op")
|
||||
}
|
||||
|
||||
@ -408,6 +498,10 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
if room.IsMaster(target) {
|
||||
return errors.New("you cannot kick master")
|
||||
}
|
||||
|
||||
body := fmt.Sprintf("%s was kicked by %s.", target.Name(), msg.From().Name())
|
||||
room.Send(message.NewAnnounceMsg(body))
|
||||
target.Close()
|
||||
@ -416,13 +510,14 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Admin: true,
|
||||
Op: true,
|
||||
Prefix: "/ban",
|
||||
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()) {
|
||||
if !room.IsMaster(msg.From()) {
|
||||
return errors.New("must be op")
|
||||
}
|
||||
|
||||
@ -436,6 +531,10 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
if room.IsMaster(target) {
|
||||
return errors.New("you cannot ban master.")
|
||||
}
|
||||
|
||||
var until time.Duration = 0
|
||||
if len(args) > 1 {
|
||||
until, _ = time.ParseDuration(args[1])
|
||||
@ -456,13 +555,18 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Admin: true,
|
||||
Op: true,
|
||||
Prefix: "/motd",
|
||||
Prefix: "motd",
|
||||
PrefixHelp: "[MESSAGE]",
|
||||
Help: "Set a new MESSAGE of the day, print the current motd without parameters.",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
args := msg.Args()
|
||||
user := msg.From()
|
||||
if !room.IsMaster(user) && !room.IsOp(user) {
|
||||
return errors.New("must be OP to modify the MOTD")
|
||||
}
|
||||
|
||||
args := msg.Args()
|
||||
|
||||
h.mu.Lock()
|
||||
motd := h.motd
|
||||
@ -472,9 +576,6 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
room.Send(message.NewSystemMsg(motd, user))
|
||||
return nil
|
||||
}
|
||||
if !room.IsOp(user) {
|
||||
return errors.New("must be OP to modify the MOTD")
|
||||
}
|
||||
|
||||
motd = strings.Join(args, " ")
|
||||
h.SetMotd(motd)
|
||||
@ -486,13 +587,13 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Op: true,
|
||||
Prefix: "/op",
|
||||
Admin: 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")
|
||||
if !room.IsMaster(msg.From()) {
|
||||
return errors.New("must be admin")
|
||||
}
|
||||
|
||||
args := msg.Args()
|
||||
@ -520,4 +621,35 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(chat.Command{
|
||||
Admin: true,
|
||||
Prefix: "delop",
|
||||
PrefixHelp: "USER",
|
||||
Help: "Remove USER as admin.",
|
||||
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||
if !room.IsMaster(msg.From()) {
|
||||
return errors.New("must be master")
|
||||
}
|
||||
|
||||
args := msg.Args()
|
||||
if len(args) == 0 {
|
||||
return errors.New("must specify user")
|
||||
}
|
||||
|
||||
member, ok := room.MemberByID(args[0])
|
||||
if !ok {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
err := room.Ops.Remove(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := fmt.Sprintf("Deleted op by %s.", msg.From().Name())
|
||||
room.Send(message.NewSystemMsg(body, member.User))
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
18
identity.go
18
identity.go
@ -14,6 +14,7 @@ import (
|
||||
type Identity struct {
|
||||
sshd.Connection
|
||||
id string
|
||||
chat string
|
||||
created time.Time
|
||||
}
|
||||
|
||||
@ -42,8 +43,22 @@ func (i Identity) Name() string {
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i Identity) Chat() string {
|
||||
return i.chat
|
||||
}
|
||||
|
||||
func (i *Identity) SetChat(c string) {
|
||||
i.chat = c
|
||||
}
|
||||
|
||||
// Whois returns a whois description for non-admin users.
|
||||
func (i Identity) Whois() string {
|
||||
return "name: " + i.Name() + message.Newline +
|
||||
" > joined: " + humanize.Time(i.created)
|
||||
}
|
||||
|
||||
// WhoisAdmin returns a whois description for admin users.
|
||||
func (i Identity) WhoisAdmin() string {
|
||||
fingerprint := "(no public key)"
|
||||
if i.PublicKey() != nil {
|
||||
fingerprint = sshd.Fingerprint(i.PublicKey())
|
||||
@ -54,8 +69,7 @@ func (i Identity) Whois() string {
|
||||
" > joined: " + humanize.Time(i.created)
|
||||
}
|
||||
|
||||
// WhoisAdmin returns a whois description for admin users.
|
||||
func (i Identity) WhoisAdmin() string {
|
||||
func (i Identity) WhoisMaster() string {
|
||||
ip, _, _ := net.SplitHostPort(i.RemoteAddr().String())
|
||||
fingerprint := "(no public key)"
|
||||
if i.PublicKey() != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user