admin.go 4.4 KB

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