diff --git a/auth.go b/auth.go index 4d6de86..d218366 100644 --- a/auth.go +++ b/auth.go @@ -81,7 +81,16 @@ func (a *Auth) Op(key ssh.PublicKey) { a.Unlock() } -// Whitelist will set a fingerprint as a whitelisted user. +// IsOp checks if a public key is an op. +func (a Auth) IsOp(key ssh.PublicKey) bool { + authkey := NewAuthKey(key) + a.RLock() + _, ok := a.ops[authkey] + a.RUnlock() + return ok +} + +// Whitelist will set a public key as a whitelisted user. func (a *Auth) Whitelist(key ssh.PublicKey) { authkey := NewAuthKey(key) a.Lock() @@ -89,6 +98,15 @@ func (a *Auth) Whitelist(key ssh.PublicKey) { a.Unlock() } +// IsWhitelisted checks if a public key is whitelisted. +func (a Auth) IsWhitelisted(key ssh.PublicKey) bool { + authkey := NewAuthKey(key) + a.RLock() + _, ok := a.whitelist[authkey] + a.RUnlock() + return ok +} + // Ban will set a fingerprint as banned. func (a *Auth) Ban(key ssh.PublicKey) { authkey := NewAuthKey(key) @@ -96,3 +114,12 @@ func (a *Auth) Ban(key ssh.PublicKey) { a.banned[authkey] = struct{}{} a.Unlock() } + +// IsBanned will set a fingerprint as banned. +func (a Auth) IsBanned(key ssh.PublicKey) bool { + authkey := NewAuthKey(key) + a.RLock() + _, ok := a.whitelist[authkey] + a.RUnlock() + return ok +} diff --git a/auth_test.go b/auth_test.go index cb7e521..3b11212 100644 --- a/auth_test.go +++ b/auth_test.go @@ -35,16 +35,16 @@ func TestAuthWhitelist(t *testing.T) { auth.Whitelist(key) - key_clone, err := ClonePublicKey(key) + keyClone, err := ClonePublicKey(key) if err != nil { t.Fatal(err) } - if string(key_clone.Marshal()) != string(key.Marshal()) { + if string(keyClone.Marshal()) != string(key.Marshal()) { t.Error("Clone key does not match.") } - ok, err = auth.Check(key_clone) + ok, err = auth.Check(keyClone) if !ok || err != nil { t.Error("Failed to permit whitelisted:", err) } diff --git a/chat/channel.go b/chat/channel.go index 4dadbae..34d981f 100644 --- a/chat/channel.go +++ b/chat/channel.go @@ -114,17 +114,18 @@ func (ch *Channel) Send(m Message) { } // Join the channel as a user, will announce. -func (ch *Channel) Join(u *User) error { +func (ch *Channel) Join(u *User) (*Member, error) { if ch.closed { - return ErrChannelClosed + return nil, ErrChannelClosed } - err := ch.members.Add(&Member{u, false}) + member := Member{u, false} + err := ch.members.Add(&member) if err != nil { - return err + return nil, err } s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), ch.members.Len()) ch.Send(NewAnnounceMsg(s)) - return nil + return &member, nil } // Leave the channel as a user, will announce. Mostly used during setup. diff --git a/chat/channel_test.go b/chat/channel_test.go index 9ba73d8..13c9bdc 100644 --- a/chat/channel_test.go +++ b/chat/channel_test.go @@ -28,7 +28,7 @@ func TestChannelJoin(t *testing.T) { go ch.Serve() defer ch.Close() - err := ch.Join(u) + _, err := ch.Join(u) if err != nil { t.Fatal(err) } @@ -66,7 +66,7 @@ func TestChannelDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { ch := NewChannel() defer ch.Close() - err := ch.Join(u) + _, err := ch.Join(u) if err != nil { t.Fatal(err) } @@ -101,7 +101,7 @@ func TestChannelQuietToggleBroadcasts(t *testing.T) { ch := NewChannel() defer ch.Close() - err := ch.Join(u) + _, err := ch.Join(u) if err != nil { t.Fatal(err) } @@ -138,7 +138,7 @@ func TestQuietToggleDisplayState(t *testing.T) { go ch.Serve() defer ch.Close() - err := ch.Join(u) + _, err := ch.Join(u) if err != nil { t.Fatal(err) } @@ -174,7 +174,7 @@ func TestChannelNames(t *testing.T) { go ch.Serve() defer ch.Close() - err := ch.Join(u) + _, err := ch.Join(u) if err != nil { t.Fatal(err) } diff --git a/host.go b/host.go index 376c966..51a1fb5 100644 --- a/host.go +++ b/host.go @@ -46,9 +46,17 @@ func (h *Host) SetMotd(motd string) { h.motd = motd } +func (h Host) isOp(conn sshd.Connection) bool { + key, ok := conn.PublicKey() + if !ok { + return false + } + return h.auth.IsOp(key) +} + // Connect a specific Terminal to this host and its channel. func (h *Host) Connect(term *sshd.Terminal) { - name := term.Conn.User() + name := term.Conn.Name() term.AutoCompleteCallback = h.AutoCompleteFunction user := chat.NewUserScreen(name, term) @@ -60,11 +68,11 @@ func (h *Host) Connect(term *sshd.Terminal) { }() defer user.Close() - err := h.channel.Join(user) + member, err := h.channel.Join(user) if err == chat.ErrIdTaken { // Try again... user.SetName(fmt.Sprintf("Guest%d", h.count)) - err = h.channel.Join(user) + member, err = h.channel.Join(user) } if err != nil { logger.Errorf("Failed to join: %s", err) @@ -75,6 +83,9 @@ func (h *Host) Connect(term *sshd.Terminal) { term.SetPrompt(GetPrompt(user)) h.count++ + // Should the user be op'd? + member.Op = h.isOp(term.Conn) + for { line, err := term.ReadLine() if err == io.EOF { diff --git a/sshd/client_test.go b/sshd/client_test.go index 2fd109f..b115e8f 100644 --- a/sshd/client_test.go +++ b/sshd/client_test.go @@ -19,7 +19,8 @@ func (a RejectAuth) Check(ssh.PublicKey) (bool, error) { } func consume(ch <-chan *Terminal) { - for range ch {} + for _ = range ch { + } } func TestClientReject(t *testing.T) { diff --git a/sshd/terminal.go b/sshd/terminal.go index 196b9be..bfd16a0 100644 --- a/sshd/terminal.go +++ b/sshd/terminal.go @@ -8,10 +8,43 @@ import ( "golang.org/x/crypto/ssh/terminal" ) +// Connection is an interface with fields necessary to operate an sshd host. +type Connection interface { + PublicKey() (ssh.PublicKey, bool) + Name() string + Close() error +} + +type sshConn struct { + *ssh.ServerConn +} + +func (c sshConn) PublicKey() (ssh.PublicKey, bool) { + if c.Permissions == nil { + return nil, false + } + + s, ok := c.Permissions.Extensions["pubkey"] + if !ok { + return nil, false + } + + key, err := ssh.ParsePublicKey([]byte(s)) + if err != nil { + return nil, false + } + + return key, true +} + +func (c sshConn) Name() string { + return c.User() +} + // Extending ssh/terminal to include a closer interface type Terminal struct { terminal.Terminal - Conn *ssh.ServerConn + Conn Connection Channel ssh.Channel } @@ -26,7 +59,7 @@ func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) { } term := Terminal{ *terminal.NewTerminal(channel, "Connecting..."), - conn, + sshConn{conn}, channel, }