chat_nav.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package oscar
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "log/slog"
  7. "net"
  8. "os"
  9. "github.com/mk6i/retro-aim-server/config"
  10. "github.com/mk6i/retro-aim-server/state"
  11. "github.com/mk6i/retro-aim-server/wire"
  12. )
  13. // ChatNavServer provides client connection lifecycle management for the
  14. // ChatNav service. This service is only used by AIM 4.x clients that make a
  15. // separate ChatNav TCP connection. AIM 5.x clients call the ChatNav food group
  16. // provided by BOS without creating an additional TCP connection.
  17. type ChatNavServer struct {
  18. AuthService
  19. Handler
  20. Logger *slog.Logger
  21. OnlineNotifier
  22. config.Config
  23. }
  24. // Start starts a TCP server and listens for ChatNav connections.
  25. func (rt ChatNavServer) Start() {
  26. addr := config.Address("", rt.Config.ChatNavPort)
  27. listener, err := net.Listen("tcp", addr)
  28. if err != nil {
  29. rt.Logger.Error("unable to bind chat nav server address", "err", err.Error())
  30. os.Exit(1)
  31. }
  32. defer listener.Close()
  33. rt.Logger.Info("starting chat nav service", "addr", addr)
  34. for {
  35. conn, err := listener.Accept()
  36. if err != nil {
  37. rt.Logger.Error(err.Error())
  38. continue
  39. }
  40. ctx := context.Background()
  41. ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  42. rt.Logger.DebugContext(ctx, "accepted connection")
  43. go func() {
  44. if err := rt.handleNewConnection(ctx, conn); err != nil {
  45. rt.Logger.Info("user session failed", "err", err.Error())
  46. }
  47. }()
  48. }
  49. }
  50. func (rt ChatNavServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
  51. flapc := &flapClient{
  52. r: rwc,
  53. sequence: 100,
  54. w: rwc,
  55. }
  56. flap, err := flapc.SignonHandshake()
  57. if err != nil {
  58. return err
  59. }
  60. var ok bool
  61. sessionID, ok := flap.Slice(wire.OServiceTLVTagsLoginCookie)
  62. if !ok {
  63. return errors.New("unable to get session id from payload")
  64. }
  65. bosSess, err := rt.RetrieveBOSSession(string(sessionID))
  66. if err != nil {
  67. return err
  68. }
  69. if bosSess == nil {
  70. return errors.New("session not found")
  71. }
  72. defer func() {
  73. rwc.Close()
  74. }()
  75. ctx = context.WithValue(ctx, "screenName", bosSess.ScreenName())
  76. msg := rt.OnlineNotifier.HostOnline()
  77. if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
  78. return err
  79. }
  80. // We copy the session object here to make sure that
  81. // dispatchIncomingMessages does not consume relayed messages produced by
  82. // the BOS server. Without this hack, message consumption would be split
  83. // between the BOS server and ChatNav server, which would result in
  84. // incorrect sequence number generation, because each server has its own
  85. // sequence counter. This hack can be removed by decoupling FLAP routing
  86. // and message relaying, which are both performed in
  87. // dispatchIncomingMessages.
  88. sessCopy := state.NewSession()
  89. sessCopy.SetScreenName(bosSess.ScreenName())
  90. sessCopy.SetID(bosSess.ID())
  91. return dispatchIncomingMessages(ctx, sessCopy, flapc, rwc, rt.Logger, rt.Handler, rt.Config)
  92. }