auth.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package oscar
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log/slog"
  9. "net"
  10. "sync"
  11. "github.com/mk6i/retro-aim-server/config"
  12. "github.com/mk6i/retro-aim-server/state"
  13. "github.com/mk6i/retro-aim-server/wire"
  14. "github.com/google/uuid"
  15. )
  16. type AuthService interface {
  17. BUCPChallenge(bodyIn wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)
  18. BUCPLogin(bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error)) (wire.SNACMessage, error)
  19. FLAPLogin(frame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error)) (wire.TLVRestBlock, error)
  20. RegisterBOSSession(ctx context.Context, authCookie []byte) (*state.Session, error)
  21. RetrieveBOSSession(authCookie []byte) (*state.Session, error)
  22. RegisterChatSession(ctx context.Context, authCookie []byte) (*state.Session, error)
  23. Signout(ctx context.Context, sess *state.Session)
  24. SignoutChat(ctx context.Context, sess *state.Session)
  25. }
  26. // AuthServer is an authentication server for both FLAP (AIM v1.0-3.0) and BUCP
  27. // (AIM v3.5-5.9) authentication flows.
  28. type AuthServer struct {
  29. AuthService
  30. config.Config
  31. Logger *slog.Logger
  32. }
  33. // Start starts the authentication server and listens for new connections.
  34. func (rt AuthServer) Start(ctx context.Context) error {
  35. addr := net.JoinHostPort("", rt.Config.AuthPort)
  36. listener, err := net.Listen("tcp", addr)
  37. if err != nil {
  38. return fmt.Errorf("unable to start auth server: %w", err)
  39. }
  40. go func() {
  41. <-ctx.Done()
  42. listener.Close()
  43. }()
  44. rt.Logger.Info("starting server", "listen_host", addr, "oscar_host", rt.Config.OSCARHost)
  45. wg := sync.WaitGroup{}
  46. for {
  47. conn, err := listener.Accept()
  48. if err != nil {
  49. if errors.Is(err, net.ErrClosed) {
  50. break
  51. }
  52. rt.Logger.Error("accept failed", "err", err.Error())
  53. continue
  54. }
  55. wg.Add(1)
  56. go func() {
  57. defer wg.Done()
  58. connCtx := context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  59. rt.Logger.DebugContext(connCtx, "accepted connection")
  60. if err := rt.handleNewConnection(conn); err != nil {
  61. rt.Logger.Info("user session failed", "err", err.Error())
  62. }
  63. }()
  64. }
  65. if !waitForShutdown(&wg) {
  66. rt.Logger.Error("shutdown complete, but connections didn't close cleanly")
  67. } else {
  68. rt.Logger.Info("shutdown complete")
  69. }
  70. return nil
  71. }
  72. func (rt AuthServer) handleNewConnection(rwc io.ReadWriteCloser) error {
  73. defer rwc.Close()
  74. flapc := wire.NewFlapClient(100, rwc, rwc)
  75. if err := flapc.SendSignonFrame(nil); err != nil {
  76. return err
  77. }
  78. signonFrame, err := flapc.ReceiveSignonFrame()
  79. if err != nil {
  80. return err
  81. }
  82. // decide whether the client is using BUCP or FLAP authentication based on
  83. // the presence of the screen name TLV. this block used to check for the
  84. // presence of the roasted password TLV, however that proved an unreliable
  85. // indicator of FLAP-auth because older ICQ clients appear to omit the
  86. // roasted password TLV when the password is not stored client-side.
  87. if _, hasScreenName := signonFrame.Uint16BE(wire.LoginTLVTagsScreenName); hasScreenName {
  88. return rt.processFLAPAuth(signonFrame, flapc)
  89. }
  90. return rt.processBUCPAuth(flapc)
  91. }
  92. func (rt AuthServer) processFLAPAuth(signonFrame wire.FLAPSignonFrame, flapc *wire.FlapClient) error {
  93. tlv, err := rt.AuthService.FLAPLogin(signonFrame, state.NewStubUser)
  94. if err != nil {
  95. return err
  96. }
  97. return flapc.SendSignoffFrame(tlv)
  98. }
  99. func (rt AuthServer) processBUCPAuth(flapc *wire.FlapClient) error {
  100. for {
  101. frame, err := flapc.ReceiveFLAP()
  102. if err != nil {
  103. return err
  104. }
  105. switch frame.FrameType {
  106. case wire.FLAPFrameSignoff:
  107. rt.Logger.Debug("signed off mid-login")
  108. return io.EOF // client disconnected
  109. case wire.FLAPFrameKeepAlive:
  110. rt.Logger.Debug("received flap keepalive frame")
  111. case wire.FLAPFrameData:
  112. buf := bytes.NewReader(frame.Payload)
  113. fr := wire.SNACFrame{}
  114. if err := wire.UnmarshalBE(&fr, buf); err != nil {
  115. return err
  116. }
  117. switch {
  118. case fr.FoodGroup == wire.BUCP && fr.SubGroup == wire.BUCPChallengeRequest:
  119. challengeRequest := wire.SNAC_0x17_0x06_BUCPChallengeRequest{}
  120. if err := wire.UnmarshalBE(&challengeRequest, buf); err != nil {
  121. return err
  122. }
  123. outSNAC, err := rt.BUCPChallenge(challengeRequest, uuid.New)
  124. if err != nil {
  125. return err
  126. }
  127. if err := flapc.SendSNAC(outSNAC.Frame, outSNAC.Body); err != nil {
  128. return err
  129. }
  130. if outSNAC.Frame.SubGroup == wire.BUCPLoginResponse {
  131. screenName, _ := challengeRequest.String(wire.LoginTLVTagsScreenName)
  132. rt.Logger.Debug("failed BUCP challenge: user does not exist", "screen_name", screenName)
  133. return nil // account does not exist
  134. }
  135. case fr.FoodGroup == wire.BUCP && fr.SubGroup == wire.BUCPLoginRequest:
  136. loginRequest := wire.SNAC_0x17_0x02_BUCPLoginRequest{}
  137. if err := wire.UnmarshalBE(&loginRequest, buf); err != nil {
  138. return err
  139. }
  140. outSNAC, err := rt.BUCPLogin(loginRequest, state.NewStubUser)
  141. if err != nil {
  142. return err
  143. }
  144. return flapc.SendSNAC(outSNAC.Frame, outSNAC.Body)
  145. default:
  146. rt.Logger.Debug("unexpected SNAC received during login",
  147. "foodgroup", wire.FoodGroupName(fr.FoodGroup),
  148. "subgroup", wire.SubGroupName(fr.FoodGroup, fr.SubGroup))
  149. return io.EOF
  150. }
  151. default:
  152. rt.Logger.Debug("unexpected frame type received during login", "type", frame.FrameType)
  153. return io.EOF
  154. }
  155. }
  156. }