diff --git a/foodgroup/auth.go b/foodgroup/auth.go index ed430df9..fd259d26 100644 --- a/foodgroup/auth.go +++ b/foodgroup/auth.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net" + "strings" "github.com/mk6i/retro-aim-server/config" "github.com/mk6i/retro-aim-server/state" @@ -75,14 +76,24 @@ func (s AuthService) RegisterChatSession(authCookie []byte) (*state.Session, err return s.chatSessionRegistry.AddSession(c.ChatCookie, c.ScreenName), nil } +type bosCookie struct { + ICQ uint8 `oscar:"len_prefix=uint8"` + ScreenName state.DisplayScreenName `oscar:"len_prefix=uint8"` +} + // RegisterBOSSession creates and returns a user's session. func (s AuthService) RegisterBOSSession(authCookie []byte) (*state.Session, error) { - screenName, err := s.cookieBaker.Crack(authCookie) + buf, err := s.cookieBaker.Crack(authCookie) if err != nil { return nil, err } - u, err := s.userManager.User(state.NewIdentScreenName(string(screenName))) + c := bosCookie{} + if err := wire.UnmarshalBE(&c, bytes.NewBuffer(buf)); err != nil { + return nil, err + } + + u, err := s.userManager.User(c.ScreenName.IdentScreenName()) if err != nil { return nil, fmt.Errorf("failed to retrieve user: %w", err) } @@ -140,10 +151,8 @@ func (s AuthService) SignoutChat(ctx context.Context, sess *state.Session) { // BUCPChallenge processes a BUCP authentication challenge request. It // retrieves the user's auth key based on the screen name provided in the // request. The client uses the auth key to salt the MD5 password hash provided -// in the subsequent login request. If the account is invalid, an error code is -// set in TLV wire.LoginTLVTagsErrorSubcode. If login credentials are invalid and app -// config DisableAuth is true, a stub auth key is generated and a successful -// challenge response is returned. +// in the subsequent login request. If the account is valid, return +// SNAC(0x17,0x07), otherwise return SNAC(0x17,0x03). func (s AuthService) BUCPChallenge( bodyIn wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUIDFn func() uuid.UUID, @@ -250,56 +259,86 @@ func (s AuthService) login( return wire.TLVRestBlock{}, errors.New("screen name doesn't exist in tlv") } + isICQ := false + if clientName, hasclientName := TLVList.String(wire.LoginTLVTagsClientIdentity); hasclientName { + isICQ = strings.HasPrefix(clientName, "ICQ ") + } + user, err := s.userManager.User(state.NewIdentScreenName(screenName)) if err != nil { return wire.TLVRestBlock{}, err } - var loginOK bool - if user != nil { - if md5Hash, hasMD5 := TLVList.Slice(wire.LoginTLVTagsPasswordHash); hasMD5 { - loginOK = user.ValidateHash(md5Hash) - } else if roastedPass, hasRoasted := TLVList.Slice(wire.LoginTLVTagsRoastedPassword); hasRoasted { - loginOK = user.ValidateRoastedPass(roastedPass) - } else { - return wire.TLVRestBlock{}, errors.New("password hash doesn't exist in tlv") - } - } - - if loginOK || s.config.DisableAuth { - if !loginOK { - // make login succeed anyway. create new user if the account - // doesn't already exist. + if user == nil { + if s.config.DisableAuth { newUser, err := newUserFn(state.DisplayScreenName(screenName)) if err != nil { return wire.TLVRestBlock{}, err } if err := s.userManager.InsertUser(newUser); err != nil { - if !errors.Is(err, state.ErrDupUser) { - return wire.TLVRestBlock{}, err - } + return wire.TLVRestBlock{}, err } + return s.loginSuccessResponse(screenName, isICQ, err) } - cookie, err := s.cookieBaker.Issue([]byte(screenName)) - if err != nil { - return wire.TLVRestBlock{}, fmt.Errorf("failed to make auth cookie: %w", err) + loginErr := wire.LoginErrInvalidUsernameOrPassword + if isICQ { + loginErr = wire.LoginErrICQUserErr } - // auth success - return wire.TLVRestBlock{ - TLVList: []wire.TLV{ - wire.NewTLV(wire.LoginTLVTagsScreenName, screenName), - wire.NewTLV(wire.LoginTLVTagsReconnectHere, net.JoinHostPort(s.config.OSCARHost, s.config.BOSPort)), - wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, cookie), - }, - }, nil + return loginFailureResponse(screenName, loginErr), nil + } + + if s.config.DisableAuth { + return s.loginSuccessResponse(screenName, isICQ, err) + } + + var loginOK bool + // get the password from the appropriate TLV. older clients have a + // roasted password, newer clients have a hashed password. ICQ may omit + // the password TLV when logging in without saved password. + if md5Hash, hasMD5 := TLVList.Slice(wire.LoginTLVTagsPasswordHash); hasMD5 { + loginOK = user.ValidateHash(md5Hash) + } else if roastedPass, hasRoasted := TLVList.Slice(wire.LoginTLVTagsRoastedPassword); hasRoasted { + loginOK = user.ValidateRoastedPass(roastedPass) + } + if !loginOK { + return loginFailureResponse(screenName, wire.LoginErrInvalidPassword), nil + } + + return s.loginSuccessResponse(screenName, isICQ, err) +} + +func (s AuthService) loginSuccessResponse(screenName string, isICQ bool, err error) (wire.TLVRestBlock, error) { + loginCookie := bosCookie{ + ScreenName: state.DisplayScreenName(screenName), + } + if isICQ { + loginCookie.ICQ = 1 + } + + buf := &bytes.Buffer{} + if err := wire.MarshalBE(loginCookie, buf); err != nil { + return wire.TLVRestBlock{}, err + } + cookie, err := s.cookieBaker.Issue(buf.Bytes()) + if err != nil { + return wire.TLVRestBlock{}, fmt.Errorf("failed to make auth cookie: %w", err) } - // auth failure return wire.TLVRestBlock{ TLVList: []wire.TLV{ wire.NewTLV(wire.LoginTLVTagsScreenName, screenName), - wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword), + wire.NewTLV(wire.LoginTLVTagsReconnectHere, net.JoinHostPort(s.config.OSCARHost, s.config.BOSPort)), + wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, cookie), }, }, nil } + +func loginFailureResponse(screenName string, code uint16) wire.TLVRestBlock { + return wire.TLVRestBlock{ + TLVList: []wire.TLV{ + wire.NewTLV(wire.LoginTLVTagsScreenName, screenName), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, code), + }, + } +} diff --git a/foodgroup/auth_test.go b/foodgroup/auth_test.go index 215c4861..1a51e6b5 100644 --- a/foodgroup/auth_test.go +++ b/foodgroup/auth_test.go @@ -16,8 +16,9 @@ import ( func TestAuthService_BUCPLoginRequest(t *testing.T) { user := state.User{ - IdentScreenName: state.NewIdentScreenName("screen_name"), - AuthKey: "auth_key", + IdentScreenName: state.NewIdentScreenName("screen_name"), + DisplayScreenName: "screen_name", + AuthKey: "auth_key", } assert.NoError(t, user.HashPassword("the_password")) @@ -39,7 +40,7 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { wantErr error }{ { - name: "user provides valid credentials and logs in successfully", + name: "AIM account exists, correct password, login OK", cfg: config.Config{ OSCARHost: "127.0.0.1", BOSPort: "1234", @@ -63,7 +64,14 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { }, cookieIssuerParams: cookieIssuerParams{ { - data: []byte(user.IdentScreenName.String()), + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), cookie: []byte("the-cookie"), }, }, @@ -85,15 +93,15 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { }, }, { - name: "user logs in with non-existent screen name--account is created and logged in successfully", + name: "ICQ account exists, correct password, login OK", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ + wire.NewTLV(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"), wire.NewTLV(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, @@ -104,25 +112,25 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { getUserParams: getUserParams{ { screenName: user.IdentScreenName, - result: nil, - }, - }, - insertUserParams: insertUserParams{ - { - user: user, + result: &user, }, }, }, cookieIssuerParams: cookieIssuerParams{ { - data: []byte(user.IdentScreenName.String()), + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + ICQ: 1, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), cookie: []byte("the-cookie"), }, }, }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, nil - }, expectOutput: wire.SNACMessage{ Frame: wire.SNACFrame{ FoodGroup: wire.BUCP, @@ -140,16 +148,15 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { }, }, { - name: "user logs in with invalid password--account is created and logged in successfully", + name: "AIM account exists, incorrect password, login fails", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-password-hash")), + wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad_password")), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -162,22 +169,8 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { result: &user, }, }, - insertUserParams: insertUserParams{ - { - user: user, - }, - }, - }, - cookieIssuerParams: cookieIssuerParams{ - { - data: []byte(user.IdentScreenName.String()), - cookie: []byte("the-cookie"), - }, }, }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, nil - }, expectOutput: wire.SNACMessage{ Frame: wire.SNACFrame{ FoodGroup: wire.BUCP, @@ -185,27 +178,25 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { }, Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{ TLVRestBlock: wire.TLVRestBlock{ - TLVList: wire.TLVList{ + TLVList: []wire.TLV{ wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), - wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), - wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword), }, }, }, }, }, { - name: "user logs in with invalid password--account already exists and logged in successfully", + name: "AIM account doesn't exist, login fails", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-password-hash")), - wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), + wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("password")), + wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("non_existent_screen_name")), }, }, }, @@ -213,26 +204,11 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { userManagerParams: userManagerParams{ getUserParams: getUserParams{ { - screenName: user.IdentScreenName, - result: &user, - }, - }, - insertUserParams: insertUserParams{ - { - user: user, - err: state.ErrDupUser, + screenName: state.NewIdentScreenName("non_existent_screen_name"), + result: nil, }, }, }, - cookieIssuerParams: cookieIssuerParams{ - { - data: []byte(user.IdentScreenName.String()), - cookie: []byte("the-cookie"), - }, - }, - }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, nil }, expectOutput: wire.SNACMessage{ Frame: wire.SNACFrame{ @@ -241,25 +217,26 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { }, Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{ TLVRestBlock: wire.TLVRestBlock{ - TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), - wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), - wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), + TLVList: []wire.TLV{ + wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_screen_name")), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword), }, }, }, }, }, { - name: "user provides invalid password--account creation fails due to user creation runtime error", + name: "ICQ account doesn't exist, login fails", cfg: config.Config{ - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-password-hash")), - wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), + wire.NewTLV(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"), + wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("password")), + wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("non_existent_uin")), }, }, }, @@ -267,26 +244,38 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { userManagerParams: userManagerParams{ getUserParams: getUserParams{ { - screenName: user.IdentScreenName, - result: &user, + screenName: state.NewIdentScreenName("non_existent_uin"), + result: nil, }, }, }, }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, io.EOF + expectOutput: wire.SNACMessage{ + Frame: wire.SNACFrame{ + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPLoginResponse, + }, + Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{ + TLVRestBlock: wire.TLVRestBlock{ + TLVList: []wire.TLV{ + wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_uin")), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr), + }, + }, + }, }, - wantErr: io.EOF, }, { - name: "user provides invalid password--account creation fails due to user upsert runtime error", + name: "account doesn't exist, authentication is disabled, account is created, login succeeds", cfg: config.Config{ + OSCARHost: "127.0.0.1", + BOSPort: "1234", DisableAuth: true, }, inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-password-hash")), + wire.NewTLV(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -296,32 +285,59 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { getUserParams: getUserParams{ { screenName: user.IdentScreenName, - result: &user, + result: nil, }, }, insertUserParams: insertUserParams{ { user: user, - err: io.EOF, }, }, }, + cookieIssuerParams: cookieIssuerParams{ + { + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), + cookie: []byte("the-cookie"), + }, + }, }, newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { return user, nil }, - wantErr: io.EOF, + expectOutput: wire.SNACMessage{ + Frame: wire.SNACFrame{ + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPLoginResponse, + }, + Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{ + TLVRestBlock: wire.TLVRestBlock{ + TLVList: wire.TLVList{ + wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), + wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), + wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), + }, + }, + }, + }, }, { - name: "user provides invalid password and receives invalid login response", + name: "account exists, password is invalid, authentication is disabled, login succeeds", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", + OSCARHost: "127.0.0.1", + BOSPort: "1234", + DisableAuth: true, }, inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad_password")), + wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-password-hash")), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -331,10 +347,26 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { getUserParams: getUserParams{ { screenName: user.IdentScreenName, - result: nil, + result: &user, }, }, }, + cookieIssuerParams: cookieIssuerParams{ + { + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), + cookie: []byte("the-cookie"), + }, + }, + }, + newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { + return user, nil }, expectOutput: wire.SNACMessage{ Frame: wire.SNACFrame{ @@ -345,7 +377,8 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), - wire.NewTLV(wire.LoginTLVTagsErrorSubcode, uint16(0x01)), + wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), + wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), }, }, }, @@ -411,12 +444,13 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { func TestAuthService_FLAPLoginResponse(t *testing.T) { user := state.User{ - IdentScreenName: state.NewIdentScreenName("screen_name"), - AuthKey: "auth_key", + AuthKey: "auth_key", + DisplayScreenName: "screen_name", + IdentScreenName: state.NewIdentScreenName("screen_name"), } assert.NoError(t, user.HashPassword("the_password")) - // obfuscated password value: "the_password" + // roastedPassword the roasted form of "the_password" roastedPassword := []byte{0x87, 0x4E, 0xE4, 0x9B, 0x49, 0xE7, 0xA8, 0xE1, 0x06, 0xCC, 0xCB, 0x82} cases := []struct { @@ -437,7 +471,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { wantErr error }{ { - name: "user provides valid credentials and logs in successfully", + name: "AIM account exists, correct password, login OK", cfg: config.Config{ OSCARHost: "127.0.0.1", BOSPort: "1234", @@ -461,7 +495,14 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { }, cookieIssuerParams: cookieIssuerParams{ { - data: []byte(user.IdentScreenName.String()), + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), cookie: []byte("the-cookie"), }, }, @@ -475,15 +516,15 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { }, }, { - name: "user logs in with non-existent screen name--account is created and logged in successfully", + name: "ICQ account exists, correct password, login OK", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ + wire.NewTLV(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"), wire.NewTLV(wire.LoginTLVTagsRoastedPassword, roastedPassword), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, @@ -494,25 +535,25 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { getUserParams: getUserParams{ { screenName: user.IdentScreenName, - result: nil, - }, - }, - insertUserParams: insertUserParams{ - { - user: user, + result: &user, }, }, }, cookieIssuerParams: cookieIssuerParams{ { - data: []byte(user.IdentScreenName.String()), + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + ICQ: 1, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), cookie: []byte("the-cookie"), }, }, }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, nil - }, expectOutput: wire.TLVRestBlock{ TLVList: wire.TLVList{ wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), @@ -522,16 +563,15 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { }, }, { - name: "user logs in with invalid password--account is created and logged in successfully", + name: "AIM account exists, incorrect password, login fails", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-roasted-password")), + wire.NewTLV(wire.LoginTLVTagsRoastedPassword, []byte("bad_roasted_password")), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -544,42 +584,26 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { result: &user, }, }, - insertUserParams: insertUserParams{ - { - user: user, - }, - }, - }, - cookieIssuerParams: cookieIssuerParams{ - { - data: []byte(user.IdentScreenName.String()), - cookie: []byte("the-cookie"), - }, }, }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, nil - }, expectOutput: wire.TLVRestBlock{ - TLVList: wire.TLVList{ + TLVList: []wire.TLV{ wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), - wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), - wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword), }, }, }, { - name: "user logs in with invalid password--account already exists and logged in successfully", + name: "AIM account doesn't exist, login fails", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-roasted-password")), - wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), + wire.NewTLV(wire.LoginTLVTagsRoastedPassword, roastedPassword), + wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("non_existent_screen_name")), }, }, }, @@ -587,45 +611,31 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { userManagerParams: userManagerParams{ getUserParams: getUserParams{ { - screenName: user.IdentScreenName, - result: &user, - }, - }, - insertUserParams: insertUserParams{ - { - user: user, - err: state.ErrDupUser, + screenName: state.NewIdentScreenName("non_existent_screen_name"), + result: nil, }, }, }, - cookieIssuerParams: cookieIssuerParams{ - { - data: []byte(user.IdentScreenName.String()), - cookie: []byte("the-cookie"), - }, - }, - }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, nil }, expectOutput: wire.TLVRestBlock{ - TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), - wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), - wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), + TLVList: []wire.TLV{ + wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_screen_name")), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword), }, }, }, { - name: "user provides invalid password--account creation fails due to user creation runtime error", + name: "ICQ account doesn't exist, login fails", cfg: config.Config{ - DisableAuth: true, + OSCARHost: "127.0.0.1", + BOSPort: "1234", }, inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-roasted-password")), - wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), + wire.NewTLV(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"), + wire.NewTLV(wire.LoginTLVTagsRoastedPassword, roastedPassword), + wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("non_existent_uin")), }, }, }, @@ -633,26 +643,30 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { userManagerParams: userManagerParams{ getUserParams: getUserParams{ { - screenName: user.IdentScreenName, - result: &user, + screenName: state.NewIdentScreenName("non_existent_uin"), + result: nil, }, }, }, }, - newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { - return user, io.EOF + expectOutput: wire.TLVRestBlock{ + TLVList: []wire.TLV{ + wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_uin")), + wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr), + }, }, - wantErr: io.EOF, }, { - name: "user provides invalid password--account creation fails due to user upsert runtime error", + name: "account doesn't exist, authentication is disabled, account is created, login succeeds", cfg: config.Config{ + OSCARHost: "127.0.0.1", + BOSPort: "1234", DisableAuth: true, }, inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-roasted-password")), + wire.NewTLV(wire.LoginTLVTagsRoastedPassword, roastedPassword), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -662,32 +676,51 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { getUserParams: getUserParams{ { screenName: user.IdentScreenName, - result: &user, + result: nil, }, }, insertUserParams: insertUserParams{ { user: user, - err: io.EOF, }, }, }, + cookieIssuerParams: cookieIssuerParams{ + { + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), + cookie: []byte("the-cookie"), + }, + }, }, newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { return user, nil }, - wantErr: io.EOF, + expectOutput: wire.TLVRestBlock{ + TLVList: wire.TLVList{ + wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), + wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), + wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), + }, + }, }, { - name: "user provides invalid password and receives invalid login response", + name: "account exists, password is invalid, authentication is disabled, login succeeds", cfg: config.Config{ - OSCARHost: "127.0.0.1", - BOSPort: "1234", + OSCARHost: "127.0.0.1", + BOSPort: "1234", + DisableAuth: true, }, inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("bad-roasted-password")), + wire.NewTLV(wire.LoginTLVTagsRoastedPassword, "bad-roasted-password"), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -697,15 +730,32 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { getUserParams: getUserParams{ { screenName: user.IdentScreenName, - result: nil, + result: &user, }, }, }, + cookieIssuerParams: cookieIssuerParams{ + { + data: func() []byte { + loginCookie := bosCookie{ + ScreenName: user.DisplayScreenName, + } + buf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(loginCookie, buf)) + return buf.Bytes() + }(), + cookie: []byte("the-cookie"), + }, + }, + }, + newUserFn: func(screenName state.DisplayScreenName) (state.User, error) { + return user, nil }, expectOutput: wire.TLVRestBlock{ TLVList: wire.TLVList{ wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), - wire.NewTLV(wire.LoginTLVTagsErrorSubcode, uint16(0x01)), + wire.NewTLV(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"), + wire.NewTLV(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")), }, }, }, @@ -714,7 +764,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { inputSNAC: wire.FLAPSignonFrame{ TLVRestBlock: wire.TLVRestBlock{ TLVList: wire.TLVList{ - wire.NewTLV(wire.LoginTLVTagsPasswordHash, user.StrongMD5Pass), + wire.NewTLV(wire.LoginTLVTagsRoastedPassword, roastedPassword), wire.NewTLV(wire.LoginTLVTagsScreenName, user.IdentScreenName), }, }, @@ -972,12 +1022,16 @@ func TestAuthService_RegisterBOSSession_HappyPath(t *testing.T) { AddSession(sess.DisplayScreenName()). Return(sess) - authCookie := []byte(`the-auth-cookie`) + authCookie := bosCookie{ + ScreenName: sess.DisplayScreenName(), + } + cookieBuf := &bytes.Buffer{} + assert.NoError(t, wire.MarshalBE(authCookie, cookieBuf)) cookieBaker := newMockCookieBaker(t) cookieBaker.EXPECT(). - Crack(authCookie). - Return([]byte("screen-name"), nil) + Crack(cookieBuf.Bytes()). + Return(cookieBuf.Bytes(), nil) userManager := newMockUserManager(t) userManager.EXPECT(). @@ -991,7 +1045,7 @@ func TestAuthService_RegisterBOSSession_HappyPath(t *testing.T) { svc := NewAuthService(config.Config{}, sessionManager, nil, userManager, nil, cookieBaker, nil, nil, nil, accountManager) - have, err := svc.RegisterBOSSession(authCookie) + have, err := svc.RegisterBOSSession(cookieBuf.Bytes()) assert.NoError(t, err) assert.Equal(t, sess, have) } diff --git a/server/oscar/auth.go b/server/oscar/auth.go index df524864..66865f8c 100644 --- a/server/oscar/auth.go +++ b/server/oscar/auth.go @@ -101,6 +101,12 @@ func (rt AuthServer) processBUCPAuth(flapc *wire.FlapClient, err error) error { return err } + if outSNAC.Frame.SubGroup == wire.BUCPLoginResponse { + screenName, _ := challengeRequest.String(wire.LoginTLVTagsScreenName) + rt.Logger.Debug("failed BUCP challenge: user does not exist", "screen_name", screenName) + return nil // account does not exist + } + loginRequest := wire.SNAC_0x17_0x02_BUCPLoginRequest{} if err := flapc.ReceiveSNAC(&wire.SNACFrame{}, &loginRequest); err != nil { return err diff --git a/wire/snacs.go b/wire/snacs.go index 73119fc9..ad5555c2 100644 --- a/wire/snacs.go +++ b/wire/snacs.go @@ -83,6 +83,7 @@ const ( const ( LoginTLVTagsScreenName uint16 = 0x01 LoginTLVTagsRoastedPassword uint16 = 0x02 + LoginTLVTagsClientIdentity uint16 = 0x03 LoginTLVTagsReconnectHere uint16 = 0x05 LoginTLVTagsAuthorizationCookie uint16 = 0x06 LoginTLVTagsErrorSubcode uint16 = 0x08 @@ -90,7 +91,9 @@ const ( ) const ( - LoginErrInvalidUsernameOrPassword uint16 = 0x01 + LoginErrInvalidUsernameOrPassword uint16 = 0x0001 + LoginErrInvalidPassword uint16 = 0x0005 // invalid password + LoginErrICQUserErr uint16 = 0x0008 // ICQ user doesn't exist ) //