auth.go 4.3 KB

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