Skip to content

Commit bcaa847

Browse files
committed
fix java AIM client login
The java AIM client sends a different roasting string than the other clients.
1 parent a7c55ec commit bcaa847

File tree

4 files changed

+240
-45
lines changed

4 files changed

+240
-45
lines changed

foodgroup/auth.go

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"net"
99
"strconv"
10+
"strings"
1011
"time"
1112

1213
"github.com/mk6i/retro-aim-server/config"
@@ -279,12 +280,14 @@ func (s AuthService) FLAPLogin(
279280

280281
// loginProperties represents the properties sent by the client at login.
281282
type loginProperties struct {
282-
screenName state.DisplayScreenName
283-
clientID string
284-
isBUCPAuth bool
285-
isTOCAuth bool
286-
passwordHash []byte
287-
roastedPass []byte
283+
clientID string
284+
isBUCPAuth bool
285+
isFLAPJavaAuth bool
286+
isTOCAuth bool
287+
isFLAPAuth bool
288+
passwordHash []byte
289+
roastedPass []byte
290+
screenName state.DisplayScreenName
288291
}
289292

290293
// fromTLV creates an instance of loginProperties from a TLV list.
@@ -304,22 +307,26 @@ func (l *loginProperties) fromTLV(list wire.TLVList) error {
304307
// get the password from the appropriate TLV. older clients have a
305308
// roasted password, newer clients have a hashed password. ICQ may omit
306309
// the password TLV when logging in without saved password.
307-
308-
// extract password hash for BUCP login
309-
if passwordHash, found := list.Bytes(wire.LoginTLVTagsPasswordHash); found {
310-
l.passwordHash = passwordHash
310+
switch {
311+
case list.HasTag(wire.LoginTLVTagsPasswordHash):
312+
// extract password hash for BUCP login
313+
l.passwordHash, _ = list.Bytes(wire.LoginTLVTagsPasswordHash)
311314
l.isBUCPAuth = true
312-
}
313-
314-
// extract roasted password for FLAP login
315-
if roastedPass, found := list.Bytes(wire.LoginTLVTagsRoastedPassword); found {
316-
l.roastedPass = roastedPass
317-
}
318-
319-
// extract roasted password for TOC FLAP login
320-
if roastedPass, found := list.Bytes(wire.LoginTLVTagsRoastedTOCPassword); found {
321-
l.roastedPass = roastedPass
315+
case list.HasTag(wire.LoginTLVTagsRoastedPassword):
316+
// extract roasted password for FLAP login
317+
l.roastedPass, _ = list.Bytes(wire.LoginTLVTagsRoastedPassword)
318+
if strings.HasPrefix(l.clientID, "AOL Instant Messenger (TM) version") &&
319+
strings.Contains(l.clientID, "for Java") {
320+
l.isFLAPJavaAuth = true
321+
} else {
322+
l.isFLAPAuth = true
323+
}
324+
case list.HasTag(wire.LoginTLVTagsRoastedTOCPassword):
325+
// extract roasted password for TOC FLAP login
326+
l.roastedPass, _ = list.Bytes(wire.LoginTLVTagsRoastedTOCPassword)
322327
l.isTOCAuth = true
328+
default:
329+
l.isFLAPAuth = true
323330
}
324331

325332
return nil
@@ -370,10 +377,12 @@ func (s AuthService) login(
370377
switch {
371378
case props.isBUCPAuth:
372379
loginOK = user.ValidateHash(props.passwordHash)
380+
case props.isFLAPAuth:
381+
loginOK = user.ValidateRoastedPass(props.roastedPass)
382+
case props.isFLAPJavaAuth:
383+
loginOK = user.ValidateRoastedJavaPass(props.roastedPass)
373384
case props.isTOCAuth:
374385
loginOK = user.ValidateRoastedTOCPass(props.roastedPass)
375-
default:
376-
loginOK = user.ValidateRoastedPass(props.roastedPass)
377386
}
378387

379388
if !loginOK {

foodgroup/auth_test.go

Lines changed: 181 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,100 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
536536
},
537537
wantErr: io.EOF,
538538
},
539+
{
540+
name: "login with TOC client - success",
541+
cfg: config.Config{
542+
OSCARHost: "127.0.0.1",
543+
BOSPort: "1234",
544+
},
545+
inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
546+
TLVRestBlock: wire.TLVRestBlock{
547+
TLVList: wire.TLVList{
548+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
549+
wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, wire.RoastTOCPassword([]byte("the_password"))),
550+
},
551+
},
552+
},
553+
mockParams: mockParams{
554+
userManagerParams: userManagerParams{
555+
getUserParams: getUserParams{
556+
{
557+
screenName: user.IdentScreenName,
558+
result: &user,
559+
},
560+
},
561+
},
562+
cookieBakerParams: cookieBakerParams{
563+
cookieIssueParams: cookieIssueParams{
564+
{
565+
dataIn: func() []byte {
566+
loginCookie := bosCookie{
567+
ScreenName: user.DisplayScreenName,
568+
}
569+
buf := &bytes.Buffer{}
570+
assert.NoError(t, wire.MarshalBE(loginCookie, buf))
571+
return buf.Bytes()
572+
}(),
573+
cookieOut: []byte("the-cookie"),
574+
},
575+
},
576+
},
577+
},
578+
expectOutput: wire.SNACMessage{
579+
Frame: wire.SNACFrame{
580+
FoodGroup: wire.BUCP,
581+
SubGroup: wire.BUCPLoginResponse,
582+
},
583+
Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
584+
TLVRestBlock: wire.TLVRestBlock{
585+
TLVList: wire.TLVList{
586+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
587+
wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"),
588+
wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
589+
},
590+
},
591+
},
592+
},
593+
},
594+
{
595+
name: "login with TOC client - failed",
596+
cfg: config.Config{
597+
OSCARHost: "127.0.0.1",
598+
BOSPort: "1234",
599+
},
600+
inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
601+
TLVRestBlock: wire.TLVRestBlock{
602+
TLVList: wire.TLVList{
603+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
604+
wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, wire.RoastTOCPassword([]byte("the_wrong_password"))),
605+
},
606+
},
607+
},
608+
mockParams: mockParams{
609+
userManagerParams: userManagerParams{
610+
getUserParams: getUserParams{
611+
{
612+
screenName: user.IdentScreenName,
613+
result: &user,
614+
},
615+
},
616+
},
617+
},
618+
expectOutput: wire.SNACMessage{
619+
Frame: wire.SNACFrame{
620+
FoodGroup: wire.BUCP,
621+
SubGroup: wire.BUCPLoginResponse,
622+
},
623+
Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
624+
TLVRestBlock: wire.TLVRestBlock{
625+
TLVList: wire.TLVList{
626+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, "screenName"),
627+
wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
628+
},
629+
},
630+
},
631+
},
632+
},
539633
}
540634

541635
for _, tc := range cases {
@@ -578,9 +672,6 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
578672
}
579673
assert.NoError(t, user.HashPassword("the_password"))
580674

581-
// roastedPassword the roasted form of "the_password"
582-
roastedPassword := []byte{0x87, 0x4E, 0xE4, 0x9B, 0x49, 0xE7, 0xA8, 0xE1, 0x06, 0xCC, 0xCB, 0x82}
583-
584675
cases := []struct {
585676
// name is the unit test name
586677
name string
@@ -607,7 +698,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
607698
inputSNAC: wire.FLAPSignonFrame{
608699
TLVRestBlock: wire.TLVRestBlock{
609700
TLVList: wire.TLVList{
610-
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
701+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
611702
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
612703
},
613704
},
@@ -655,7 +746,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
655746
TLVRestBlock: wire.TLVRestBlock{
656747
TLVList: wire.TLVList{
657748
wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
658-
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
749+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
659750
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
660751
},
661752
},
@@ -734,7 +825,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
734825
inputSNAC: wire.FLAPSignonFrame{
735826
TLVRestBlock: wire.TLVRestBlock{
736827
TLVList: wire.TLVList{
737-
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
828+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
738829
wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("non_existent_screen_name")),
739830
},
740831
},
@@ -766,7 +857,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
766857
TLVRestBlock: wire.TLVRestBlock{
767858
TLVList: wire.TLVList{
768859
wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
769-
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
860+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
770861
wire.NewTLVBE(wire.LoginTLVTagsScreenName, []byte("100003")),
771862
},
772863
},
@@ -798,7 +889,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
798889
inputSNAC: wire.FLAPSignonFrame{
799890
TLVRestBlock: wire.TLVRestBlock{
800891
TLVList: wire.TLVList{
801-
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
892+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
802893
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
803894
},
804895
},
@@ -900,7 +991,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
900991
inputSNAC: wire.FLAPSignonFrame{
901992
TLVRestBlock: wire.TLVRestBlock{
902993
TLVList: wire.TLVList{
903-
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, roastedPassword),
994+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARPassword([]byte("the_password"))),
904995
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
905996
},
906997
},
@@ -917,6 +1008,87 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
9171008
},
9181009
wantErr: io.EOF,
9191010
},
1011+
{
1012+
name: "login with AIM 1.1.19 for Java - success",
1013+
cfg: config.Config{
1014+
OSCARHost: "127.0.0.1",
1015+
BOSPort: "1234",
1016+
},
1017+
inputSNAC: wire.FLAPSignonFrame{
1018+
TLVRestBlock: wire.TLVRestBlock{
1019+
TLVList: wire.TLVList{
1020+
wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "AOL Instant Messenger (TM) version 1.1.19 for Java"),
1021+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
1022+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARJavaPassword([]byte("the_password"))),
1023+
},
1024+
},
1025+
},
1026+
mockParams: mockParams{
1027+
userManagerParams: userManagerParams{
1028+
getUserParams: getUserParams{
1029+
{
1030+
screenName: user.IdentScreenName,
1031+
result: &user,
1032+
},
1033+
},
1034+
},
1035+
cookieBakerParams: cookieBakerParams{
1036+
cookieIssueParams: cookieIssueParams{
1037+
{
1038+
dataIn: func() []byte {
1039+
loginCookie := bosCookie{
1040+
ScreenName: user.DisplayScreenName,
1041+
ClientID: "AOL Instant Messenger (TM) version 1.1.19 for Java",
1042+
}
1043+
buf := &bytes.Buffer{}
1044+
assert.NoError(t, wire.MarshalBE(loginCookie, buf))
1045+
return buf.Bytes()
1046+
}(),
1047+
cookieOut: []byte("the-cookie"),
1048+
},
1049+
},
1050+
},
1051+
},
1052+
expectOutput: wire.TLVRestBlock{
1053+
TLVList: wire.TLVList{
1054+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
1055+
wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, "127.0.0.1:1234"),
1056+
wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, []byte("the-cookie")),
1057+
},
1058+
},
1059+
},
1060+
{
1061+
name: "login with AIM 1.1.19 for Java - failed",
1062+
cfg: config.Config{
1063+
OSCARHost: "127.0.0.1",
1064+
BOSPort: "1234",
1065+
},
1066+
inputSNAC: wire.FLAPSignonFrame{
1067+
TLVRestBlock: wire.TLVRestBlock{
1068+
TLVList: wire.TLVList{
1069+
wire.NewTLVBE(wire.LoginTLVTagsClientIdentity, "AOL Instant Messenger (TM) version 1.1.19 for Java"),
1070+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, user.DisplayScreenName),
1071+
wire.NewTLVBE(wire.LoginTLVTagsRoastedPassword, wire.RoastOSCARJavaPassword([]byte("the_wrong_password"))),
1072+
},
1073+
},
1074+
},
1075+
mockParams: mockParams{
1076+
userManagerParams: userManagerParams{
1077+
getUserParams: getUserParams{
1078+
{
1079+
screenName: user.IdentScreenName,
1080+
result: &user,
1081+
},
1082+
},
1083+
},
1084+
},
1085+
expectOutput: wire.TLVRestBlock{
1086+
TLVList: wire.TLVList{
1087+
wire.NewTLVBE(wire.LoginTLVTagsScreenName, "screenName"),
1088+
wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidPassword),
1089+
},
1090+
},
1091+
},
9201092
}
9211093

9221094
for _, tc := range cases {

state/user.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,16 @@ func (u *User) ValidateHash(md5Hash []byte) bool {
403403
// hash of the user's actual password. A roasted password is a XOR-obfuscated
404404
// form of the real password, intended to add a simple layer of security.
405405
func (u *User) ValidateRoastedPass(roastedPass []byte) bool {
406-
clearPass := wire.RoastPassword(roastedPass)
406+
clearPass := wire.RoastOSCARPassword(roastedPass)
407+
md5Hash := wire.WeakMD5PasswordHash(string(clearPass), u.AuthKey) // todo remove string conversion
408+
return bytes.Equal(u.WeakMD5Pass, md5Hash)
409+
}
410+
411+
// ValidateRoastedJavaPass checks if the provided roasted password matches the MD5
412+
// hash of the user's actual password. A roasted password is a XOR-obfuscated
413+
// form of the real password, intended to add a simple layer of security. // todo toc description
414+
func (u *User) ValidateRoastedJavaPass(roastedPass []byte) bool {
415+
clearPass := wire.RoastOSCARJavaPassword(roastedPass)
407416
md5Hash := wire.WeakMD5PasswordHash(string(clearPass), u.AuthKey) // todo remove string conversion
408417
return bytes.Equal(u.WeakMD5Pass, md5Hash)
409418
}

0 commit comments

Comments
 (0)