Skip to content

Commit 58f68d7

Browse files
committed
return correct auth error codes for icq/aim
This commit updates the login handler to return the proper error codes based on whether the OSCAR client is AIM or ICQ. Additionally, set an ICQ flag in the BOS cookie. Also remove EOF error when attempting to login with nonexistent account.
1 parent fa33cdf commit 58f68d7

File tree

4 files changed

+320
-218
lines changed

4 files changed

+320
-218
lines changed

foodgroup/auth.go

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"net"
9+
"strings"
910

1011
"github.com/mk6i/retro-aim-server/config"
1112
"github.com/mk6i/retro-aim-server/state"
@@ -75,14 +76,24 @@ func (s AuthService) RegisterChatSession(authCookie []byte) (*state.Session, err
7576
return s.chatSessionRegistry.AddSession(c.ChatCookie, c.ScreenName), nil
7677
}
7778

79+
type bosCookie struct {
80+
ICQ uint8 `oscar:"len_prefix=uint8"`
81+
ScreenName state.DisplayScreenName `oscar:"len_prefix=uint8"`
82+
}
83+
7884
// RegisterBOSSession creates and returns a user's session.
7985
func (s AuthService) RegisterBOSSession(authCookie []byte) (*state.Session, error) {
80-
screenName, err := s.cookieBaker.Crack(authCookie)
86+
buf, err := s.cookieBaker.Crack(authCookie)
8187
if err != nil {
8288
return nil, err
8389
}
8490

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())
8697
if err != nil {
8798
return nil, fmt.Errorf("failed to retrieve user: %w", err)
8899
}
@@ -140,10 +151,8 @@ func (s AuthService) SignoutChat(ctx context.Context, sess *state.Session) {
140151
// BUCPChallenge processes a BUCP authentication challenge request. It
141152
// retrieves the user's auth key based on the screen name provided in the
142153
// 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).
147156
func (s AuthService) BUCPChallenge(
148157
bodyIn wire.SNAC_0x17_0x06_BUCPChallengeRequest,
149158
newUUIDFn func() uuid.UUID,
@@ -250,56 +259,86 @@ func (s AuthService) login(
250259
return wire.TLVRestBlock{}, errors.New("screen name doesn't exist in tlv")
251260
}
252261

262+
isICQ := false
263+
if clientName, hasclientName := TLVList.String(wire.LoginTLVTagsClientIdentity); hasclientName {
264+
isICQ = strings.HasPrefix(clientName, "ICQ ")
265+
}
266+
253267
user, err := s.userManager.User(state.NewIdentScreenName(screenName))
254268
if err != nil {
255269
return wire.TLVRestBlock{}, err
256270
}
257271

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 {
273274
newUser, err := newUserFn(state.DisplayScreenName(screenName))
274275
if err != nil {
275276
return wire.TLVRestBlock{}, err
276277
}
277278
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
281280
}
281+
return s.loginSuccessResponse(screenName, isICQ, err)
282282
}
283283

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
287287
}
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)
296326
}
297327

298-
// auth failure
299328
return wire.TLVRestBlock{
300329
TLVList: []wire.TLV{
301330
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),
303333
},
304334
}, nil
305335
}
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

Comments
 (0)