bos.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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/wire"
  11. )
  12. // OnlineNotifier returns a OServiceHostOnline SNAC that is sent to the client
  13. // at the beginning of the protocol sequence which lists all food groups
  14. // managed by the server.
  15. type OnlineNotifier interface {
  16. HostOnline() wire.SNACMessage
  17. }
  18. // BOSServer provides client connection lifecycle management for the BOS
  19. // service.
  20. type BOSServer struct {
  21. AuthService
  22. CookieCracker
  23. Handler
  24. ListenAddr string
  25. Logger *slog.Logger
  26. OnlineNotifier
  27. config.Config
  28. }
  29. // Start starts a TCP server and listens for connections. The initial
  30. // authentication handshake sequences are handled by this method. The remaining
  31. // requests are relayed to BOSRouter.
  32. func (rt BOSServer) Start() {
  33. listener, err := net.Listen("tcp", rt.ListenAddr)
  34. if err != nil {
  35. rt.Logger.Error("unable to bind server address", "host", rt.ListenAddr, "err", err.Error())
  36. os.Exit(1)
  37. }
  38. defer listener.Close()
  39. rt.Logger.Info("starting server", "listen_host", rt.ListenAddr, "oscar_host", rt.Config.OSCARHost)
  40. for {
  41. conn, err := listener.Accept()
  42. if err != nil {
  43. rt.Logger.Error(err.Error())
  44. continue
  45. }
  46. ctx := context.Background()
  47. ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  48. rt.Logger.DebugContext(ctx, "accepted connection")
  49. go func() {
  50. if err := rt.handleNewConnection(ctx, conn); err != nil {
  51. rt.Logger.Info("user session failed", "err", err.Error())
  52. }
  53. }()
  54. }
  55. }
  56. func (rt BOSServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
  57. flapc := wire.NewFlapClient(100, rwc, rwc)
  58. if err := flapc.SendSignonFrame(nil); err != nil {
  59. return err
  60. }
  61. flap, err := flapc.ReceiveSignonFrame()
  62. if err != nil {
  63. return err
  64. }
  65. authCookie, ok := flap.Slice(wire.OServiceTLVTagsLoginCookie)
  66. if !ok {
  67. return errors.New("unable to get session id from payload")
  68. }
  69. token, err := rt.CookieCracker.Crack(authCookie)
  70. if err != nil {
  71. return err
  72. }
  73. sess, err := rt.RegisterBOSSession(string(token))
  74. if err != nil {
  75. return err
  76. }
  77. if sess == nil {
  78. return errors.New("session not found")
  79. }
  80. defer func() {
  81. sess.Close()
  82. rwc.Close()
  83. if err := rt.Signout(ctx, sess); err != nil {
  84. rt.Logger.ErrorContext(ctx, "error notifying departure", "err", err.Error())
  85. }
  86. }()
  87. ctx = context.WithValue(ctx, "screenName", sess.ScreenName())
  88. msg := rt.OnlineNotifier.HostOnline()
  89. if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
  90. return err
  91. }
  92. return dispatchIncomingMessages(ctx, sess, flapc, rwc, rt.Logger, rt.Handler, rt.Config)
  93. }