X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/a251acd43adcd88388a00b7ebd7d8df0486adeba..b4a15075b0c85d571e7427963ff784c1e9e5acd1:/hotline/server.go diff --git a/hotline/server.go b/hotline/server.go index b17102e..bfc1c11 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -11,7 +11,6 @@ import ( "gopkg.in/yaml.v3" "io" "io/fs" - "io/ioutil" "math/big" "math/rand" "net" @@ -28,8 +27,6 @@ var contextKeyReq = contextKey("req") type requestCtx struct { remoteAddr string - login string - name string } type Server struct { @@ -106,7 +103,6 @@ func (s *Server) ListenAndServe(ctx context.Context, cancelRoot context.CancelFu ln, err := net.Listen("tcp", fmt.Sprintf("%s:%v", "", s.Port+1)) if err != nil { s.Logger.Fatal(err) - } s.Logger.Fatal(s.ServeFileTransfers(ctx, ln)) @@ -210,7 +206,7 @@ const ( ) // NewServer constructs a new Server from a config dir -func NewServer(configDir string, netPort int, logger *zap.SugaredLogger, FS FileStore) (*Server, error) { +func NewServer(configDir string, netPort int, logger *zap.SugaredLogger, fs FileStore) (*Server, error) { server := Server{ Port: netPort, Accounts: make(map[string]*Account), @@ -224,7 +220,7 @@ func NewServer(configDir string, netPort int, logger *zap.SugaredLogger, FS File outbox: make(chan Transaction), Stats: &Stats{Since: time.Now()}, ThreadedNews: &ThreadedNews{}, - FS: FS, + FS: fs, banList: make(map[string]*time.Time), } @@ -315,15 +311,15 @@ func (s *Server) keepaliveHandler() { c.Idle = true flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(c.Flags))) - flagBitmap.SetBit(flagBitmap, userFlagAway, 1) + flagBitmap.SetBit(flagBitmap, UserFlagAway, 1) binary.BigEndian.PutUint16(c.Flags, uint16(flagBitmap.Int64())) c.sendAll( - tranNotifyChangeUser, - NewField(fieldUserID, *c.ID), - NewField(fieldUserFlags, c.Flags), - NewField(fieldUserName, c.UserName), - NewField(fieldUserIconID, c.Icon), + TranNotifyChangeUser, + NewField(FieldUserID, *c.ID), + NewField(FieldUserFlags, c.Flags), + NewField(FieldUserName, c.UserName), + NewField(FieldUserIconID, c.Icon), ) } } @@ -339,7 +335,7 @@ func (s *Server) writeBanList() error { if err != nil { return err } - err = ioutil.WriteFile( + err = os.WriteFile( filepath.Join(s.ConfigDir, "Banlist.yaml"), out, 0666, @@ -469,7 +465,7 @@ func (s *Server) connectedUsers() []Field { Flags: c.Flags, Name: string(c.UserName), } - connectedUsers = append(connectedUsers, NewField(fieldUsernameWithInfo, user.Payload())) + connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, user.Payload())) } return connectedUsers } @@ -567,37 +563,60 @@ func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser return err } - c := s.NewClientConn(rwc, remoteAddr) - // check if remoteAddr is present in the ban list if banUntil, ok := s.banList[strings.Split(remoteAddr, ":")[0]]; ok { // permaban if banUntil == nil { - s.outbox <- *NewTransaction( - tranServerMsg, - c.ID, - NewField(fieldData, []byte("You are permanently banned on this server")), - NewField(fieldChatOptions, []byte{0, 0}), + t := NewTransaction( + TranServerMsg, + &[]byte{0, 0}, + NewField(FieldData, []byte("You are permanently banned on this server")), + NewField(FieldChatOptions, []byte{0, 0}), ) + + b, err := t.MarshalBinary() + if err != nil { + return err + } + + _, err = rwc.Write(b) + if err != nil { + return err + } + time.Sleep(1 * time.Second) return nil - } else if time.Now().Before(*banUntil) { - s.outbox <- *NewTransaction( - tranServerMsg, - c.ID, - NewField(fieldData, []byte("You are temporarily banned on this server")), - NewField(fieldChatOptions, []byte{0, 0}), + } + + // temporary ban + if time.Now().Before(*banUntil) { + t := NewTransaction( + TranServerMsg, + &[]byte{0, 0}, + NewField(FieldData, []byte("You are temporarily banned on this server")), + NewField(FieldChatOptions, []byte{0, 0}), ) + b, err := t.MarshalBinary() + if err != nil { + return err + } + + _, err = rwc.Write(b) + if err != nil { + return err + } + time.Sleep(1 * time.Second) return nil } - } + + c := s.NewClientConn(rwc, remoteAddr) defer c.Disconnect() - encodedLogin := clientLogin.GetField(fieldUserLogin).Data - encodedPassword := clientLogin.GetField(fieldUserPassword).Data - c.Version = clientLogin.GetField(fieldVersion).Data + encodedLogin := clientLogin.GetField(FieldUserLogin).Data + encodedPassword := clientLogin.GetField(FieldUserPassword).Data + c.Version = clientLogin.GetField(FieldVersion).Data var login string for _, char := range encodedLogin { @@ -625,15 +644,15 @@ func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser return nil } - if clientLogin.GetField(fieldUserIconID).Data != nil { - c.Icon = clientLogin.GetField(fieldUserIconID).Data + if clientLogin.GetField(FieldUserIconID).Data != nil { + c.Icon = clientLogin.GetField(FieldUserIconID).Data } c.Account = c.Server.Accounts[login] - if clientLogin.GetField(fieldUserName).Data != nil { + if clientLogin.GetField(FieldUserName).Data != nil { if c.Authorize(accessAnyName) { - c.UserName = clientLogin.GetField(fieldUserName).Data + c.UserName = clientLogin.GetField(FieldUserName).Data } else { c.UserName = []byte(c.Account.Name) } @@ -644,41 +663,48 @@ func (s *Server) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser } s.outbox <- c.NewReply(&clientLogin, - NewField(fieldVersion, []byte{0x00, 0xbe}), - NewField(fieldCommunityBannerID, []byte{0, 0}), - NewField(fieldServerName, []byte(s.Config.Name)), + NewField(FieldVersion, []byte{0x00, 0xbe}), + NewField(FieldCommunityBannerID, []byte{0, 0}), + NewField(FieldServerName, []byte(s.Config.Name)), ) // Send user access privs so client UI knows how to behave - c.Server.outbox <- *NewTransaction(tranUserAccess, c.ID, NewField(fieldUserAccess, c.Account.Access[:])) + c.Server.outbox <- *NewTransaction(TranUserAccess, c.ID, NewField(FieldUserAccess, c.Account.Access[:])) // Accounts with accessNoAgreement do not receive the server agreement on login. The behavior is different between - // client versions. For 1.2.3 client, we do not send tranShowAgreement. For other client versions, we send - // tranShowAgreement but with the NoServerAgreement field set to 1. + // client versions. For 1.2.3 client, we do not send TranShowAgreement. For other client versions, we send + // TranShowAgreement but with the NoServerAgreement field set to 1. if c.Authorize(accessNoAgreement) { // If client version is nil, then the client uses the 1.2.3 login behavior if c.Version != nil { - c.Server.outbox <- *NewTransaction(tranShowAgreement, c.ID, NewField(fieldNoServerAgreement, []byte{1})) + c.Server.outbox <- *NewTransaction(TranShowAgreement, c.ID, NewField(FieldNoServerAgreement, []byte{1})) } } else { - c.Server.outbox <- *NewTransaction(tranShowAgreement, c.ID, NewField(fieldData, s.Agreement)) + c.Server.outbox <- *NewTransaction(TranShowAgreement, c.ID, NewField(FieldData, s.Agreement)) } - // Used simplified hotline v1.2.3 login flow for clients that do not send login info in tranAgreed - // TODO: figure out a generalized solution that doesn't require playing whack-a-mole for specific client versions - c.logger = c.logger.With("name", string(c.UserName)) - c.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%v", func() int { i, _ := byteToInt(c.Version); return i }())) - - for _, t := range c.notifyOthers( - *NewTransaction( - tranNotifyChangeUser, nil, - NewField(fieldUserName, c.UserName), - NewField(fieldUserID, *c.ID), - NewField(fieldUserIconID, c.Icon), - NewField(fieldUserFlags, c.Flags), - ), - ) { - c.Server.outbox <- t + // If the client has provided a username as part of the login, we can infer that it is using the 1.2.3 login + // flow and not the 1.5+ flow. + if len(c.UserName) != 0 { + // Add the client username to the logger. For 1.5+ clients, we don't have this information yet as it comes as + // part of TranAgreed + c.logger = c.logger.With("name", string(c.UserName)) + + c.logger.Infow("Login successful", "clientVersion", "Not sent (probably 1.2.3)") + + // Notify other clients on the server that the new user has logged in. For 1.5+ clients we don't have this + // information yet, so we do it in TranAgreed instead + for _, t := range c.notifyOthers( + *NewTransaction( + TranNotifyChangeUser, nil, + NewField(FieldUserName, c.UserName), + NewField(FieldUserID, *c.ID), + NewField(FieldUserIconID, c.Icon), + NewField(FieldUserFlags, c.Flags), + ), + ) { + c.Server.outbox <- t + } } c.Server.Stats.ConnectionCounter += 1 @@ -711,7 +737,7 @@ func (s *Server) NewPrivateChat(cc *ClientConn) []byte { randID := make([]byte, 4) rand.Read(randID) - data := binary.BigEndian.Uint32(randID[:]) + data := binary.BigEndian.Uint32(randID) s.PrivateChats[data] = &PrivateChat{ ClientConn: make(map[uint16]*ClientConn), @@ -1180,7 +1206,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro return err } - if err := receiveFile(rwc, file, ioutil.Discard, ioutil.Discard, fileTransfer.bytesSentCounter); err != nil { + if err := receiveFile(rwc, file, io.Discard, io.Discard, fileTransfer.bytesSentCounter); err != nil { s.Logger.Error(err) }