From 229c2d793d964bb48f45ea17623b498d798f5dcb Mon Sep 17 00:00:00 2001 From: themester Date: Fri, 12 Jan 2018 20:04:39 +0100 Subject: [PATCH] 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). --- auth.go | 24 +++++ chat/command.go | 75 +++++++++++---- chat/message/identity.go | 11 +++ chat/message/message.go | 2 +- chat/room.go | 12 ++- cmd/ssh-chat/cmd.go | 58 +++++++++++- host.go | 198 ++++++++++++++++++++++++++++++++------- identity.go | 18 +++- 8 files changed, 340 insertions(+), 58 deletions(-) diff --git a/auth.go b/auth.go index ced008b..7b22a01 100644 --- a/auth.go +++ b/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 { diff --git a/chat/command.go b/chat/command.go index e84350b..afeec11 100644 --- a/chat/command.go +++ b/chat/command.go @@ -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")) diff --git a/chat/message/identity.go b/chat/message/identity.go index 8edef77..ca0ece3 100644 --- a/chat/message/identity.go +++ b/chat/message/identity.go @@ -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) { + +} diff --git a/chat/message/message.go b/chat/message/message.go index 5b4f76a..832979d 100644 --- a/chat/message/message.go +++ b/chat/message/message.go @@ -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 } diff --git a/chat/room.go b/chat/room.go index 7ce6de1..3186c42 100644 --- a/chat/room.go +++ b/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 diff --git a/cmd/ssh-chat/cmd.go b/cmd/ssh-chat/cmd.go index 7c7d000..c3eeb83 100644 --- a/cmd/ssh-chat/cmd.go +++ b/cmd/ssh-chat/cmd.go @@ -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 { diff --git a/host.go b/host.go index 752cf2f..e9430e2 100644 --- a/host.go +++ b/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 + }, + }) } diff --git a/identity.go b/identity.go index 50cee7e..e947e31 100644 --- a/identity.go +++ b/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 {