mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-06-13 13:52:08 +03:00
Adapted my repo to shazow. Changes...
- Added new rank: Administators (or masters). This rank can add or delete operators, but cannot add or remove another administrators. Administrators rank is designed by public key on server startup. - Added "vim mode". With this mode you can use commands as ':' instead of '/'. As you can see in the code, the Prefix parameter of Command structure was changed by a neutral parameter for future introductions of prefixes. - New Help function. This function add help for admin rank. - Added the following commands: - setnick: Administrators can change the nick of another user. - private: Users can stablish privates conversations. This conversations are permanent until they execute endprivate command. - endprivate: Finish private conversation. - welcome: Prints motd. Only admins can execute this. - whois: In whois only admins can see ip. - delop: Allow admins to delete an operator (or moderator as is mentioned in some parts of the code). - Changed historyLen and roomBuffer vars. - In cmd tool have been added fsnotify to update public key files (admin and moderator public key files). This is to prevent to restart the server everytime we want to add an administrator. - Added new prompt mode. In this mode you can see in which room you are talking. As default is 'general'. If you start private conversation the room will be the name of the another user. This part has been introduced for future implementations. - As I said before now exists private conversations (unlike msg command).
This commit is contained in:
parent
fd77009f8d
commit
229c2d793d
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