admin.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package oscar
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "net"
  9. "os"
  10. "github.com/mk6i/retro-aim-server/config"
  11. "github.com/mk6i/retro-aim-server/server/oscar/middleware"
  12. "github.com/mk6i/retro-aim-server/state"
  13. "github.com/mk6i/retro-aim-server/wire"
  14. )
  15. // AdminServer provides client connection lifecycle management for the BOS
  16. // service.
  17. type AdminServer struct {
  18. AuthService
  19. Handler
  20. ListenAddr string
  21. Logger *slog.Logger
  22. OnlineNotifier
  23. config.Config
  24. }
  25. // Start starts a TCP server and listens for connections. The initial
  26. // authentication handshake sequences are handled by this method. The remaining
  27. // requests are relayed to BOSRouter.
  28. func (rt AdminServer) Start() {
  29. listener, err := net.Listen("tcp", rt.ListenAddr)
  30. if err != nil {
  31. rt.Logger.Error("unable to bind server address", "host", rt.ListenAddr, "err", err.Error())
  32. os.Exit(1)
  33. }
  34. defer listener.Close()
  35. rt.Logger.Info("starting server", "listen_host", rt.ListenAddr, "oscar_host", rt.Config.OSCARHost)
  36. for {
  37. conn, err := listener.Accept()
  38. if err != nil {
  39. rt.Logger.Error(err.Error())
  40. continue
  41. }
  42. ctx := context.Background()
  43. ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  44. rt.Logger.DebugContext(ctx, "accepted connection")
  45. go func() {
  46. if err := rt.handleNewConnection(ctx, conn); err != nil {
  47. rt.Logger.Info("user session failed", "err", err.Error())
  48. }
  49. }()
  50. }
  51. }
  52. func (rt AdminServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
  53. flapc := wire.NewFlapClient(100, rwc, rwc)
  54. if err := flapc.SendSignonFrame(nil); err != nil {
  55. return err
  56. }
  57. flap, err := flapc.ReceiveSignonFrame()
  58. if err != nil {
  59. return err
  60. }
  61. authCookie, ok := flap.Slice(wire.OServiceTLVTagsLoginCookie)
  62. if !ok {
  63. return errors.New("unable to get session id from payload")
  64. }
  65. sess, err := rt.RetrieveBOSSession(authCookie)
  66. if sess == nil {
  67. return errors.New("session not found")
  68. }
  69. defer func() {
  70. sess.Close()
  71. rwc.Close()
  72. if err := rt.Signout(ctx, sess); err != nil {
  73. rt.Logger.ErrorContext(ctx, "error notifying departure", "err", err.Error())
  74. }
  75. }()
  76. ctx = context.WithValue(ctx, "screenName", sess.IdentScreenName())
  77. msg := rt.OnlineNotifier.HostOnline()
  78. if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
  79. return err
  80. }
  81. return dispatchIncomingMessagesSimple(ctx, sess, flapc, rwc, rt.Logger, rt.Handler, rt.Config)
  82. }
  83. func dispatchIncomingMessagesSimple(ctx context.Context, sess *state.Session, flapc *wire.FlapClient, r io.Reader, logger *slog.Logger, router Handler, config config.Config) error {
  84. // buffered so that the go routine has room to exit
  85. msgCh := make(chan incomingMessage, 1)
  86. readErrCh := make(chan error, 1)
  87. go consumeFLAPFrames(r, msgCh, readErrCh)
  88. defer func() {
  89. logger.InfoContext(ctx, "user disconnected")
  90. }()
  91. for {
  92. select {
  93. case m, ok := <-msgCh:
  94. if !ok {
  95. return nil
  96. }
  97. switch m.flap.FrameType {
  98. case wire.FLAPFrameData:
  99. inFrame := wire.SNACFrame{}
  100. if err := wire.Unmarshal(&inFrame, m.payload); err != nil {
  101. return err
  102. }
  103. // route a client request to the appropriate service handler. the
  104. // handler may write a response to the client connection.
  105. if err := router.Handle(ctx, sess, inFrame, m.payload, flapc); err != nil {
  106. middleware.LogRequestError(ctx, logger, inFrame, err)
  107. if errors.Is(err, ErrRouteNotFound) {
  108. if err1 := sendInvalidSNACErr(inFrame, flapc); err1 != nil {
  109. return errors.Join(err1, err)
  110. }
  111. if config.FailFast {
  112. panic(err.Error())
  113. }
  114. break
  115. }
  116. return err
  117. }
  118. case wire.FLAPFrameSignon:
  119. return fmt.Errorf("shouldn't get FLAPFrameSignon. flap: %v", m.flap)
  120. case wire.FLAPFrameError:
  121. return fmt.Errorf("got FLAPFrameError. flap: %v", m.flap)
  122. case wire.FLAPFrameSignoff:
  123. logger.InfoContext(ctx, "got FLAPFrameSignoff", "flap", m.flap)
  124. return nil
  125. case wire.FLAPFrameKeepAlive:
  126. logger.DebugContext(ctx, "keepalive heartbeat")
  127. default:
  128. return fmt.Errorf("got unknown FLAP frame type. flap: %v", m.flap)
  129. }
  130. case err := <-readErrCh:
  131. if !errors.Is(io.EOF, err) {
  132. logger.ErrorContext(ctx, "client disconnected with error", "err", err)
  133. }
  134. return nil
  135. }
  136. }
  137. }