alert.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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. // AlertServer provides client connection lifecycle management for the Alert
  14. // service. This server, whose handlers are all no-op, exists solely to satisfy
  15. // AIM 4.x, which throws an error when it can't connect to the alert service.
  16. type AlertServer struct {
  17. AuthService
  18. Handler
  19. Logger *slog.Logger
  20. OnlineNotifier
  21. config.Config
  22. }
  23. // Start starts a TCP server and listens for connections. The initial
  24. // authentication handshake sequences are handled by this method. The remaining
  25. // requests are relayed to Handler.
  26. func (rt AlertServer) Start() {
  27. addr := config.Address("", rt.Config.AlertPort)
  28. listener, err := net.Listen("tcp", addr)
  29. if err != nil {
  30. rt.Logger.Error("unable to bind ALERT server address", "err", err.Error())
  31. os.Exit(1)
  32. }
  33. defer listener.Close()
  34. rt.Logger.Info("starting ALERT service", "addr", addr)
  35. for {
  36. conn, err := listener.Accept()
  37. if err != nil {
  38. rt.Logger.Error(err.Error())
  39. continue
  40. }
  41. ctx := context.Background()
  42. ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  43. rt.Logger.DebugContext(ctx, "accepted connection")
  44. go func() {
  45. if err := rt.handleNewConnection(ctx, conn); err != nil {
  46. rt.Logger.Info("user session failed", "err", err.Error())
  47. }
  48. }()
  49. }
  50. }
  51. func (rt AlertServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
  52. flapc := &flapClient{
  53. r: rwc,
  54. sequence: 100,
  55. w: rwc,
  56. }
  57. flap, err := flapc.SignonHandshake()
  58. if err != nil {
  59. return err
  60. }
  61. var ok bool
  62. sessionID, ok := flap.Slice(wire.OServiceTLVTagsLoginCookie)
  63. if !ok {
  64. return errors.New("unable to get session id from payload")
  65. }
  66. bosSess, err := rt.RetrieveBOSSession(string(sessionID))
  67. if err != nil {
  68. return err
  69. }
  70. if bosSess == nil {
  71. return errors.New("session not found")
  72. }
  73. defer func() {
  74. bosSess.Close()
  75. rwc.Close()
  76. if err := rt.Signout(ctx, bosSess); err != nil {
  77. rt.Logger.ErrorContext(ctx, "error notifying departure", "err", err.Error())
  78. }
  79. }()
  80. ctx = context.WithValue(ctx, "screenName", bosSess.ScreenName())
  81. msg := rt.OnlineNotifier.HostOnline()
  82. if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
  83. return err
  84. }
  85. // We copy the session object here to make sure that
  86. // dispatchIncomingMessages does not consume relayed messages produced by
  87. // the BOS server. Without this hack, message consumption would be split
  88. // between the BOS server and Alert server, which would result in
  89. // incorrect sequence number generation, because each server has its own
  90. // sequence counter. This hack can be removed by decoupling FLAP routing
  91. // and message relaying, which are both performed in
  92. // dispatchIncomingMessages.
  93. sessCopy := state.NewSession()
  94. sessCopy.SetScreenName(bosSess.ScreenName())
  95. sessCopy.SetID(bosSess.ID())
  96. return dispatchIncomingMessages(ctx, sessCopy, flapc, rwc, rt.Logger, rt.Handler, rt.Config)
  97. }