|
6 | 6 | "errors"
|
7 | 7 | "fmt"
|
8 | 8 | "net"
|
| 9 | + "strings" |
9 | 10 |
|
10 | 11 | "github.com/mk6i/retro-aim-server/config"
|
11 | 12 | "github.com/mk6i/retro-aim-server/state"
|
@@ -75,14 +76,24 @@ func (s AuthService) RegisterChatSession(authCookie []byte) (*state.Session, err
|
75 | 76 | return s.chatSessionRegistry.AddSession(c.ChatCookie, c.ScreenName), nil
|
76 | 77 | }
|
77 | 78 |
|
| 79 | +type bosCookie struct { |
| 80 | + ICQ uint8 `oscar:"len_prefix=uint8"` |
| 81 | + ScreenName state.DisplayScreenName `oscar:"len_prefix=uint8"` |
| 82 | +} |
| 83 | + |
78 | 84 | // RegisterBOSSession creates and returns a user's session.
|
79 | 85 | func (s AuthService) RegisterBOSSession(authCookie []byte) (*state.Session, error) {
|
80 |
| - screenName, err := s.cookieBaker.Crack(authCookie) |
| 86 | + buf, err := s.cookieBaker.Crack(authCookie) |
81 | 87 | if err != nil {
|
82 | 88 | return nil, err
|
83 | 89 | }
|
84 | 90 |
|
85 |
| - u, err := s.userManager.User(state.NewIdentScreenName(string(screenName))) |
| 91 | + c := bosCookie{} |
| 92 | + if err := wire.UnmarshalBE(&c, bytes.NewBuffer(buf)); err != nil { |
| 93 | + return nil, err |
| 94 | + } |
| 95 | + |
| 96 | + u, err := s.userManager.User(c.ScreenName.IdentScreenName()) |
86 | 97 | if err != nil {
|
87 | 98 | return nil, fmt.Errorf("failed to retrieve user: %w", err)
|
88 | 99 | }
|
@@ -140,10 +151,8 @@ func (s AuthService) SignoutChat(ctx context.Context, sess *state.Session) {
|
140 | 151 | // BUCPChallenge processes a BUCP authentication challenge request. It
|
141 | 152 | // retrieves the user's auth key based on the screen name provided in the
|
142 | 153 | // request. The client uses the auth key to salt the MD5 password hash provided
|
143 |
| -// in the subsequent login request. If the account is invalid, an error code is |
144 |
| -// set in TLV wire.LoginTLVTagsErrorSubcode. If login credentials are invalid and app |
145 |
| -// config DisableAuth is true, a stub auth key is generated and a successful |
146 |
| -// challenge response is returned. |
| 154 | +// in the subsequent login request. If the account is valid, return |
| 155 | +// SNAC(0x17,0x07), otherwise return SNAC(0x17,0x03). |
147 | 156 | func (s AuthService) BUCPChallenge(
|
148 | 157 | bodyIn wire.SNAC_0x17_0x06_BUCPChallengeRequest,
|
149 | 158 | newUUIDFn func() uuid.UUID,
|
@@ -250,56 +259,86 @@ func (s AuthService) login(
|
250 | 259 | return wire.TLVRestBlock{}, errors.New("screen name doesn't exist in tlv")
|
251 | 260 | }
|
252 | 261 |
|
| 262 | + isICQ := false |
| 263 | + if clientName, hasclientName := TLVList.String(wire.LoginTLVTagsClientIdentity); hasclientName { |
| 264 | + isICQ = strings.HasPrefix(clientName, "ICQ ") |
| 265 | + } |
| 266 | + |
253 | 267 | user, err := s.userManager.User(state.NewIdentScreenName(screenName))
|
254 | 268 | if err != nil {
|
255 | 269 | return wire.TLVRestBlock{}, err
|
256 | 270 | }
|
257 | 271 |
|
258 |
| - var loginOK bool |
259 |
| - if user != nil { |
260 |
| - if md5Hash, hasMD5 := TLVList.Slice(wire.LoginTLVTagsPasswordHash); hasMD5 { |
261 |
| - loginOK = user.ValidateHash(md5Hash) |
262 |
| - } else if roastedPass, hasRoasted := TLVList.Slice(wire.LoginTLVTagsRoastedPassword); hasRoasted { |
263 |
| - loginOK = user.ValidateRoastedPass(roastedPass) |
264 |
| - } else { |
265 |
| - return wire.TLVRestBlock{}, errors.New("password hash doesn't exist in tlv") |
266 |
| - } |
267 |
| - } |
268 |
| - |
269 |
| - if loginOK || s.config.DisableAuth { |
270 |
| - if !loginOK { |
271 |
| - // make login succeed anyway. create new user if the account |
272 |
| - // doesn't already exist. |
| 272 | + if user == nil { |
| 273 | + if s.config.DisableAuth { |
273 | 274 | newUser, err := newUserFn(state.DisplayScreenName(screenName))
|
274 | 275 | if err != nil {
|
275 | 276 | return wire.TLVRestBlock{}, err
|
276 | 277 | }
|
277 | 278 | if err := s.userManager.InsertUser(newUser); err != nil {
|
278 |
| - if !errors.Is(err, state.ErrDupUser) { |
279 |
| - return wire.TLVRestBlock{}, err |
280 |
| - } |
| 279 | + return wire.TLVRestBlock{}, err |
281 | 280 | }
|
| 281 | + return s.loginSuccessResponse(screenName, isICQ, err) |
282 | 282 | }
|
283 | 283 |
|
284 |
| - cookie, err := s.cookieBaker.Issue([]byte(screenName)) |
285 |
| - if err != nil { |
286 |
| - return wire.TLVRestBlock{}, fmt.Errorf("failed to make auth cookie: %w", err) |
| 284 | + loginErr := wire.LoginErrInvalidUsernameOrPassword |
| 285 | + if isICQ { |
| 286 | + loginErr = wire.LoginErrICQUserErr |
287 | 287 | }
|
288 |
| - // auth success |
289 |
| - return wire.TLVRestBlock{ |
290 |
| - TLVList: []wire.TLV{ |
291 |
| - wire.NewTLV(wire.LoginTLVTagsScreenName, screenName), |
292 |
| - wire.NewTLV(wire.LoginTLVTagsReconnectHere, net.JoinHostPort(s.config.OSCARHost, s.config.BOSPort)), |
293 |
| - wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, cookie), |
294 |
| - }, |
295 |
| - }, nil |
| 288 | + return loginFailureResponse(screenName, loginErr), nil |
| 289 | + } |
| 290 | + |
| 291 | + if s.config.DisableAuth { |
| 292 | + return s.loginSuccessResponse(screenName, isICQ, err) |
| 293 | + } |
| 294 | + |
| 295 | + var loginOK bool |
| 296 | + // get the password from the appropriate TLV. older clients have a |
| 297 | + // roasted password, newer clients have a hashed password. ICQ may omit |
| 298 | + // the password TLV when logging in without saved password. |
| 299 | + if md5Hash, hasMD5 := TLVList.Slice(wire.LoginTLVTagsPasswordHash); hasMD5 { |
| 300 | + loginOK = user.ValidateHash(md5Hash) |
| 301 | + } else if roastedPass, hasRoasted := TLVList.Slice(wire.LoginTLVTagsRoastedPassword); hasRoasted { |
| 302 | + loginOK = user.ValidateRoastedPass(roastedPass) |
| 303 | + } |
| 304 | + if !loginOK { |
| 305 | + return loginFailureResponse(screenName, wire.LoginErrInvalidPassword), nil |
| 306 | + } |
| 307 | + |
| 308 | + return s.loginSuccessResponse(screenName, isICQ, err) |
| 309 | +} |
| 310 | + |
| 311 | +func (s AuthService) loginSuccessResponse(screenName string, isICQ bool, err error) (wire.TLVRestBlock, error) { |
| 312 | + loginCookie := bosCookie{ |
| 313 | + ScreenName: state.DisplayScreenName(screenName), |
| 314 | + } |
| 315 | + if isICQ { |
| 316 | + loginCookie.ICQ = 1 |
| 317 | + } |
| 318 | + |
| 319 | + buf := &bytes.Buffer{} |
| 320 | + if err := wire.MarshalBE(loginCookie, buf); err != nil { |
| 321 | + return wire.TLVRestBlock{}, err |
| 322 | + } |
| 323 | + cookie, err := s.cookieBaker.Issue(buf.Bytes()) |
| 324 | + if err != nil { |
| 325 | + return wire.TLVRestBlock{}, fmt.Errorf("failed to make auth cookie: %w", err) |
296 | 326 | }
|
297 | 327 |
|
298 |
| - // auth failure |
299 | 328 | return wire.TLVRestBlock{
|
300 | 329 | TLVList: []wire.TLV{
|
301 | 330 | wire.NewTLV(wire.LoginTLVTagsScreenName, screenName),
|
302 |
| - wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword), |
| 331 | + wire.NewTLV(wire.LoginTLVTagsReconnectHere, net.JoinHostPort(s.config.OSCARHost, s.config.BOSPort)), |
| 332 | + wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, cookie), |
303 | 333 | },
|
304 | 334 | }, nil
|
305 | 335 | }
|
| 336 | + |
| 337 | +func loginFailureResponse(screenName string, code uint16) wire.TLVRestBlock { |
| 338 | + return wire.TLVRestBlock{ |
| 339 | + TLVList: []wire.TLV{ |
| 340 | + wire.NewTLV(wire.LoginTLVTagsScreenName, screenName), |
| 341 | + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, code), |
| 342 | + }, |
| 343 | + } |
| 344 | +} |
0 commit comments