From fcb806c3445ce56f2e57e208de773b4dac957c8d Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 15 Sep 2026 20:35:23 -0400 Subject: fix: owner re-entrancy deadlock, telnet write queue, recovery/refund money safety, admin path traversal --- internal/net/server.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'internal/net/server.go') diff --git a/internal/net/server.go b/internal/net/server.go index b0407fc..42077a1 100644 --- a/internal/net/server.go +++ b/internal/net/server.go @@ -174,7 +174,15 @@ func (sess *Session) StartWriter() { // enqueue appends msg to the outbound queue. When the client's queue is full // the session is disconnected instead of blocking the caller: the world must // never wait on a single slow client. +// enqueue appends msg to the outbound queue. When the client's queue is +// full the session is disconnected instead of blocking the caller: the world +// must never wait on a single slow client. func (sess *Session) enqueue(msg string) { + // A broadcast can race Close(): the queue may already be (or become) + // closed mid-send. Such late writes are dropped, never panicked on — + // Close() closes the queue so the writer goroutine drains whatever was + // queued before the close and then flushes the transport. + defer func() { _ = recover() }() select { case sess.outbox <- msg: default: @@ -490,6 +498,10 @@ func (s *Server) serveTelnet() { Conn: newTCPConn(conn), State: StateAccountName, } + // Queued writes like the websocket transport: a dedicated writer + // goroutine performs the I/O with per-message deadlines, so a slow + // or stalled telnet client can never block game code. + sess.StartWriter() s.hub.Add(sess) go s.handleSession(sess, s.handler) } @@ -505,6 +517,7 @@ func (s *Server) serveTelnetTLS() { Conn: newTCPConn(conn), State: StateAccountName, } + sess.StartWriter() s.hub.Add(sess) go s.handleSession(sess, s.handler) } -- cgit v1.2.3