Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions chat/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"sync"

"github.com/shazow/ssh-chat/chat/message"
"github.com/shazow/ssh-chat/internal/humantime"
//"github.com/shazow/ssh-chat/internal/humantime"
"github.com/shazow/ssh-chat/set"
)

Expand All @@ -34,6 +34,14 @@ type Member struct {
isMuted bool // When true, messages should not be broadcasted.
}

func (m *Member) Key() string {
return m.ID()
}

func (m *Member) Value() interface{} {
return m
}

func (m *Member) IsMuted() bool {
m.mu.Lock()
defer m.mu.Unlock()
Expand All @@ -54,7 +62,9 @@ type Room struct {
commands Commands
closed bool
closeOnce sync.Once

OpHubris bool
AnnounceJoin string
AnnounceLeave string
Members *set.Set
}

Expand Down Expand Up @@ -183,22 +193,39 @@ func (r *Room) Join(u *message.User) (*Member, error) {
}
// TODO: Remove user ID from sets, probably referring to a prior user.
r.History(u)
s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.Members.Len())
var s string
if r.OpHubris && member.IsOp {
s = fmt.Sprintf(" [OP] " + r.AnnounceJoin, u.Name())
} else {
s = fmt.Sprintf(r.AnnounceJoin, u.Name())
}
s += fmt.Sprintf(" (Connected: %d)", r.Members.Len())
r.Send(message.NewAnnounceMsg(s))
return member, nil
}

// Leave the room as a user, will announce. Mostly used during setup.
func (r *Room) Leave(u *message.User) error {
err := r.Members.Remove(u.ID())
if err != nil {
return err
}
s := fmt.Sprintf("%s left. (After %s)", u.Name(), humantime.Since(u.Joined()))
r.Send(message.NewAnnounceMsg(s))
return nil
}
item, err := r.Members.Get(u.ID())
if err != nil {
return nil
}
member := item.Value().(*Member)

r.Members.Remove(u.ID())

var s string
if r.OpHubris && member.IsOp {
s = fmt.Sprintf("⚡ [OP] " + r.AnnounceLeave, u.Name())
} else {
s = fmt.Sprintf(r.AnnounceLeave, u.Name())
}

s += fmt.Sprintf(" (Connected: %d)", r.Members.Len())
r.Send(message.NewAnnounceMsg(s))

return nil
}
// Rename member with a new identity. This will not call rename on the member.
func (r *Room) Rename(oldID string, u message.Identifier) error {
if u.ID() == "" {
Expand Down
11 changes: 10 additions & 1 deletion cmd/ssh-chat/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type Options struct {
Allowlist string `long:"allowlist" description:"Optional file of public keys who are allowed to connect."`
Whitelist string `long:"whitelist" dexcription:"Old name for allowlist option"`
Passphrase string `long:"unsafe-passphrase" description:"Require an interactive passphrase to connect. Allowlist feature is more secure."`
OpHubris bool `long:"op-hubris" description:"Enable arrogant join/leave messages for operators."`
AnnounceJoin string `long:"announce-join" description:"Custom join message. Use %s for username." default:"%s joined."`
AnnounceLeave string `long:"announce-leave" description:"Custom leave message. Use %s for username." default:"%s left."`
}

const extraHelp = `There are hidden options and easter eggs in ssh-chat. The source code is a good
Expand Down Expand Up @@ -134,7 +137,13 @@ func main() {

fmt.Printf("Listening for connections on %v\n", s.Addr().String())

host := sshchat.NewHost(s, auth)
host := sshchat.NewHost(
s,
auth,
options.OpHubris,
options.AnnounceJoin,
options.AnnounceLeave,
)
host.SetTheme(message.Themes[0])
host.Version = Version

Expand Down
93 changes: 77 additions & 16 deletions host.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,38 @@ type Host struct {
GetMOTD func() (string, error)
// OnUserJoined is used to notify when a user joins a host
OnUserJoined func(*message.User)

AnnounceJoin string
AnnounceLeave string
OpHubris bool
}

// NewHost creates a Host on top of an existing listener.
func NewHost(listener *sshd.SSHListener, auth *Auth) *Host {
room := chat.NewRoom()
h := Host{
Room: room,
listener: listener,
commands: chat.Commands{},
auth: auth,
}

// Make our own commands registry instance.
chat.InitCommands(&h.commands)
h.InitCommands(&h.commands)
room.SetCommands(h.commands)

go room.Serve()
return &h
func NewHost(listener *sshd.SSHListener, auth *Auth, opHubris bool, joinTmpl string, leaveTmpl string) *Host {
room := chat.NewRoom()

// Inject the config into the room immediately
room.OpHubris = opHubris
room.AnnounceJoin = joinTmpl
room.AnnounceLeave = leaveTmpl

h := Host{
Room: room,
listener: listener,
commands: chat.Commands{},
auth: auth,
// Store them in the host struct
OpHubris: opHubris,
AnnounceJoin: joinTmpl,
AnnounceLeave: leaveTmpl,
}

chat.InitCommands(&h.commands)
h.InitCommands(&h.commands)
room.SetCommands(h.commands)

go room.Serve()
return &h
}

// SetTheme sets the default theme for the host.
Expand Down Expand Up @@ -100,6 +113,23 @@ func (h *Host) isOp(conn sshd.Connection) bool {

// Connect a specific Terminal to this host and its room.
func (h *Host) Connect(term *sshd.Terminal) {
// SUPREME OVERLORD FIREWALL
if term.Conn.Name() == "brianfinch" {
remoteIP := term.Conn.RemoteAddr().String()

// Check if the connection is coming from laggy (localhost or Tailscale IP)
isLocal := len(remoteIP) >= 9 && remoteIP[:9] == "127.0.0.1"
isIPv6Local := len(remoteIP) >= 5 && remoteIP[:5] == "[::1]"
isTailscale := len(remoteIP) >= 14 && remoteIP[:14] == "100.126.247.84"

if !isLocal && !isIPv6Local && !isTailscale {
// Impostor detected! Blast them with red text and kill the connection.
term.Write([]byte("\r\n\x1b[31;1m ACCESS DENIED: You are not the true brianfinch. Impostor connection terminated.\x1b[0m\r\n"))
term.Close()
return
}
}

id := NewIdentity(term.Conn)
user := message.NewUserScreen(id, term)
user.OnChange = func() {
Expand Down Expand Up @@ -355,6 +385,37 @@ func (h *Host) InitCommands(c *chat.Commands) {
target.SetReplyTo(from)
return nil
}
c.Add(chat.Command{
Prefix: "/smite",
PrefixHelp: "USER",
Help: "Admin only: Smite a mortal from the server.",
Handler: func(room *chat.Room, msg message.CommandMsg) error {
// 1. Verify the user is the Overlord
if msg.From().Name() != "brianfinch" {
return errors.New("You lack the divine authority to smite.")
}

// 2. Check if a target was provided
args := msg.Args()
if len(args) < 1 {
return errors.New("Usage: /smite <username>")
}
targetName := args[0]

// 3. Find the target user
target, ok := h.GetUser(targetName)
if !ok {
return errors.New("Mortal not found.")
}

// 4. The Hubris Broadcast (Bold Red)
room.Send(message.NewAnnounceMsg("\x1b[31;1m The Supreme Overlord has SMITTEN " + targetName + " from existence!\x1b[0m"))

// 5. Execute the smite (closes their SSH connection instantly)
target.Close()
return nil
},
})

c.Add(chat.Command{
Prefix: "/msg",
Expand Down