aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJeff Halter <868228+jhalter@users.noreply.github.com>2026-07-09 18:28:58 -0700
committerJeff Halter <868228+jhalter@users.noreply.github.com>2026-07-09 18:28:58 -0700
commitfff87ed2d3791d6853529457cc37689e8869b270 (patch)
tree2a32d47fd192ddccb9029ca6cd13605325daaa1b /internal
parent631b7f1f99c2ff6295152210640a2f9b2c4f5a88 (diff)
Publish ClientConn only after login and guard Account with the state mutex
NewClientConn added the connection to the ClientManager before Account and Version were assigned, so any goroutine iterating ClientMgr.List() during the login handshake could dereference a nil Account (e.g. HandleSetUser reading c.Account.Login) and panic. Account was also read and written across goroutines with no synchronization: HandleSetUser wrote c.Account.Access on another client's connection while that client's own transaction loop read it in Authorize. The connection is now added to the manager in handleNewConnection only once Account, Version, UserName, and Flags are initialized, so a published client is always fully formed. Failed logins never publish the connection at all; Disconnect's manager delete is a no-op for them. Account joins the mutex-guarded session state with accessors in the established style: SetAccount/GetAccount, SetAccountAccess for the one post-login mutation, AccessBytes for building transaction fields, and Authorize now takes the read lock. All cross-goroutine call sites go through the accessors. HandleSetUser previously recomputed the admin flag from the client's stale access level and only converged on the following edit; the access update now happens before the recompute.
Diffstat (limited to 'internal')
-rw-r--r--internal/mobius/api.go4
-rw-r--r--internal/mobius/handlers_accounts.go10
-rw-r--r--internal/mobius/handlers_chat.go2
-rw-r--r--internal/mobius/handlers_session.go8
4 files changed, 12 insertions, 12 deletions
diff --git a/internal/mobius/api.go b/internal/mobius/api.go
index 3620cf4..c14c8bf 100644
--- a/internal/mobius/api.go
+++ b/internal/mobius/api.go
@@ -110,7 +110,7 @@ func (srv *APIServer) OnlineHandler(w http.ResponseWriter, r *http.Request) {
} else {
for _, c := range srv.hlServer.ClientMgr.List() {
users = append(users, map[string]string{
- "login": string(c.Account.Login),
+ "login": c.GetAccount().Login,
"nickname": string(c.GetUserName()),
"ip": c.RemoteAddr,
})
@@ -164,7 +164,7 @@ func (srv *APIServer) BanHandler(w http.ResponseWriter, r *http.Request) {
// Disconnect user if online
for _, c := range srv.hlServer.ClientMgr.List() {
- if (req.Username != "" && c.Account.Login == req.Username) ||
+ if (req.Username != "" && c.GetAccount().Login == req.Username) ||
(req.Nickname != "" && string(c.GetUserName()) == req.Nickname) ||
(req.IP != "" && c.IP() == req.IP) {
c.Disconnect()
diff --git a/internal/mobius/handlers_accounts.go b/internal/mobius/handlers_accounts.go
index 02b7c77..5014d84 100644
--- a/internal/mobius/handlers_accounts.go
+++ b/internal/mobius/handlers_accounts.go
@@ -55,18 +55,18 @@ func HandleSetUser(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotlin
// Notify connected clients logged in as the user of the new access level
for _, c := range cc.Server.ClientMgr.List() {
- if c.Account.Login == login {
+ if c.GetAccount().Login == login {
newT := hotline.NewTransaction(hotline.TranUserAccess, c.ID, hotline.NewField(hotline.FieldUserAccess, newAccessLvl))
res = append(res, newT)
+ c.SetAccountAccess(account.Access)
+
if c.Authorize(hotline.AccessDisconUser) {
c.SetFlag(hotline.UserFlagAdmin, 1)
} else {
c.SetFlag(hotline.UserFlagAdmin, 0)
}
- c.Account.Access = account.Access
-
cc.SendAll(
hotline.TranNotifyChangeUser,
hotline.NewField(hotline.FieldUserID, c.ID[:]),
@@ -200,7 +200,7 @@ func HandleUpdateUser(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot
}
for _, client := range cc.Server.ClientMgr.List() {
- if client.Account.Login == login {
+ if client.GetAccount().Login == login {
// "You are logged in with an account which was deleted."
res = append(res,
@@ -374,7 +374,7 @@ func HandleDeleteUser(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot
}
for _, client := range cc.Server.ClientMgr.List() {
- if client.Account.Login == login {
+ if client.GetAccount().Login == login {
res = append(res,
hotline.NewTransaction(hotline.TranServerMsg, client.ID,
hotline.NewField(hotline.FieldData, []byte(ErrMsgAccountDeleted)),
diff --git a/internal/mobius/handlers_chat.go b/internal/mobius/handlers_chat.go
index b81cfa5..ec92a7c 100644
--- a/internal/mobius/handlers_chat.go
+++ b/internal/mobius/handlers_chat.go
@@ -62,7 +62,7 @@ func HandleChatSend(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotli
//cc.Server.mux.Lock()
for _, c := range cc.Server.ClientMgr.List() {
- if c == nil || cc.Account == nil {
+ if c == nil || cc.GetAccount() == nil {
continue
}
// Skip clients that do not have the read chat permission.
diff --git a/internal/mobius/handlers_session.go b/internal/mobius/handlers_session.go
index 443fed0..836c608 100644
--- a/internal/mobius/handlers_session.go
+++ b/internal/mobius/handlers_session.go
@@ -102,11 +102,11 @@ func HandleTranAgreed(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot
if cc.Authorize(hotline.AccessAnyName) {
cc.SetUserName(t.GetField(hotline.FieldUserName).Data)
} else {
- cc.SetUserName([]byte(cc.Account.Name))
+ cc.SetUserName([]byte(cc.GetAccount().Name))
}
}
- login := cc.Account.Login
+ login := cc.GetAccount().Login
ip := cc.IP()
if cc.Server.Presence != nil {
@@ -191,7 +191,7 @@ func HandleDisconnectUser(cc *hotline.ClientConn, t *hotline.Transaction) (res [
}
if clientConn.Authorize(hotline.AccessCannotBeDiscon) {
- return cc.NewErrReply(t, clientConn.Account.Login+" is not allowed to be disconnected.")
+ return cc.NewErrReply(t, clientConn.GetAccount().Login+" is not allowed to be disconnected.")
}
// If FieldOptions is set, then the client IP is banned in addition to disconnected.
@@ -266,7 +266,7 @@ func HandleSetClientUserInfo(cc *hotline.ClientConn, t *hotline.Transaction) (re
newNickname := string(t.GetField(hotline.FieldUserName).Data)
cc.SetUserName(t.GetField(hotline.FieldUserName).Data)
- login := cc.Account.Login
+ login := cc.GetAccount().Login
ip := cc.IP()
if cc.Server.Presence != nil {