Просмотр исходного кода

don't allow IMs or profile lookups between blocked users

Mike 3 лет назад
Родитель
Сommit
5ff0ea4167
5 измененных файлов с 248 добавлено и 87 удалено
  1. 57 0
      oscar/feedbag_store.go
  2. 119 48
      oscar/feedbag_store_test.go
  3. 18 0
      oscar/icbm.go
  4. 22 7
      oscar/locate.go
  5. 32 32
      oscar/protocol.go

+ 57 - 0
oscar/feedbag_store.go

@@ -185,6 +185,8 @@ func (f *FeedbagStore) InterestedUsers(screenName string) ([]string, error) {
 	return items, nil
 }
 
+// Buddies returns all user's buddies. Don't return a buddy if screenName
+// blocked them.
 func (f *FeedbagStore) Buddies(screenName string) ([]string, error) {
 	q := `
 		SELECT f.name
@@ -214,6 +216,61 @@ func (f *FeedbagStore) Buddies(screenName string) ([]string, error) {
 	return items, nil
 }
 
+type BlockedState int
+
+const (
+	BlockedNo BlockedState = iota
+	BlockedA
+	BlockedB
+)
+
+// Blocked informs whether there is a blocking relationship between sn1 and
+// sn2. Return BlockedA if sn1 blocked sn2, BlockedB if sn2 blocked sn1, or
+// BlockedNo if neither screen name blocked the other.
+func (f *FeedbagStore) Blocked(sn1, sn2 string) (BlockedState, error) {
+	q := `
+		SELECT EXISTS(SELECT 1
+					  FROM feedbag f
+					  WHERE f.classID = 3
+						AND f.ScreenName = ?
+						AND f.name = ?)
+		UNION ALL
+		SELECT EXISTS(SELECT 1
+					  FROM feedbag f
+					  WHERE f.classID = 3
+						AND f.ScreenName = ?
+						AND f.name = ?)
+	`
+	var blockedA bool
+	row, err := f.db.Query(q, sn1, sn2, sn2, sn1)
+	if err != nil {
+		return BlockedNo, err
+	}
+	defer row.Close()
+
+	row.Next()
+	err = row.Scan(&blockedA)
+	if err != nil {
+		return BlockedNo, err
+	}
+
+	row.Next()
+	var blockedB bool
+	err = row.Scan(&blockedB)
+	if err != nil {
+		return BlockedNo, err
+	}
+
+	switch {
+	case blockedA:
+		return BlockedA, nil
+	case blockedB:
+		return BlockedB, nil
+	default:
+		return BlockedNo, nil
+	}
+}
+
 // RetrieveProfile fetches a user profile. Return empty string if the user
 // exists but has no profile. Return errUserNotExist if the user does not
 // exist.

+ 119 - 48
oscar/feedbag_store_test.go

@@ -52,53 +52,6 @@ func TestFeedbagStore(t *testing.T) {
 	}
 }
 
-func TestFeedbagStoreBlockedUser(t *testing.T) {
-
-	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
-	const screenName = "sn2day"
-
-	defer func() {
-		err := os.Remove(testFile)
-		if err != nil {
-			t.Error("unable to clean up test file")
-		}
-	}()
-
-	f, err := NewFeedbagStore(testFile)
-	if err != nil {
-		t.Fatalf("failed to create new feedbag store: %s", err.Error())
-	}
-
-	itemsIn := []*feedbagItem{
-		{
-			groupID:    0,
-			itemID:     1805,
-			classID:    0,
-			name:       "spimmer1234",
-			TLVPayload: TLVPayload{},
-		},
-		{
-			groupID:    0,
-			itemID:     1807,
-			classID:    3,
-			name:       "spimmer1234",
-			TLVPayload: TLVPayload{},
-		},
-	}
-	if err := f.Upsert(screenName, itemsIn); err != nil {
-		t.Fatalf("failed to upsert: %s", err.Error())
-	}
-
-	itemsOut, err := f.Buddies(screenName)
-	if err != nil {
-		t.Fatalf("failed to retrieve: %s", err.Error())
-	}
-
-	if len(itemsOut) != 0 {
-		t.Fatalf("got unexpected blocked buddy %v", itemsOut[0])
-	}
-}
-
 func TestFeedbagDelete(t *testing.T) {
 
 	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
@@ -308,7 +261,6 @@ func TestProfileNonExistent(t *testing.T) {
 }
 
 func TestInterestedUsers(t *testing.T) {
-
 	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
 
 	defer func() {
@@ -337,3 +289,122 @@ func TestInterestedUsers(t *testing.T) {
 		t.Fatalf("expected no interested users, got %v", users)
 	}
 }
+
+func TestFeedbagStoreBuddiesBlockedUser(t *testing.T) {
+	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
+
+	defer func() {
+		err := os.Remove(testFile)
+		if err != nil {
+			t.Error("unable to clean up test file")
+		}
+	}()
+
+	f, err := NewFeedbagStore(testFile)
+	if err != nil {
+		t.Fatalf("failed to create new feedbag store: %s", err.Error())
+	}
+
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userA',0,13852,3,'userB',NULL,1691286176)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userA',27631,4016,0,'userB',NULL,1690508233)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userB',28330,8120,0,'userA',NULL,1691180328)`)
+
+	users, err := f.Buddies("userA")
+	if len(users) != 0 {
+		t.Fatalf("expected no buddies, got %v", users)
+	}
+
+	users, err = f.Buddies("userB")
+	if len(users) != 0 {
+		t.Fatalf("expected no buddies, got %v", users)
+	}
+}
+
+func TestFeedbagStoreBlockedA(t *testing.T) {
+	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
+
+	defer func() {
+		err := os.Remove(testFile)
+		if err != nil {
+			t.Error("unable to clean up test file")
+		}
+	}()
+
+	f, err := NewFeedbagStore(testFile)
+	if err != nil {
+		t.Fatalf("failed to create new feedbag store: %s", err.Error())
+	}
+
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userA',0,13852,3,'userB',NULL,1691286176)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userA',27631,4016,0,'userB',NULL,1690508233)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userB',28330,8120,0,'userA',NULL,1691180328)`)
+
+	sn1 := "userA"
+	sn2 := "userB"
+	blocked, err := f.Blocked(sn1, sn2)
+	if err != nil {
+		t.Fatalf("db err: %s", err.Error())
+	}
+	if blocked != BlockedA {
+		t.Fatalf("expected A to be blocker")
+	}
+}
+
+func TestFeedbagStoreBlockedB(t *testing.T) {
+	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
+
+	defer func() {
+		err := os.Remove(testFile)
+		if err != nil {
+			t.Error("unable to clean up test file")
+		}
+	}()
+
+	f, err := NewFeedbagStore(testFile)
+	if err != nil {
+		t.Fatalf("failed to create new feedbag store: %s", err.Error())
+	}
+
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userB',0,13852,3,'userA',NULL,1691286176)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userA',27631,4016,0,'userB',NULL,1690508233)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userB',28330,8120,0,'userA',NULL,1691180328)`)
+
+	sn1 := "userA"
+	sn2 := "userB"
+	blocked, err := f.Blocked(sn1, sn2)
+	if err != nil {
+		t.Fatalf("db err: %s", err.Error())
+	}
+	if blocked != BlockedB {
+		t.Fatalf("expected B to be blocker")
+	}
+}
+
+func TestFeedbagStoreBlockedNoBlocked(t *testing.T) {
+	const testFile string = "/Users/mike/dev/goaim/aim_test.db"
+
+	defer func() {
+		err := os.Remove(testFile)
+		if err != nil {
+			t.Error("unable to clean up test file")
+		}
+	}()
+
+	f, err := NewFeedbagStore(testFile)
+	if err != nil {
+		t.Fatalf("failed to create new feedbag store: %s", err.Error())
+	}
+
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userA',27631,4016,0,'userB',NULL,1690508233)`)
+	f.db.Exec(`INSERT INTO "feedbag" VALUES('userB',28330,8120,0,'userA',NULL,1691180328)`)
+
+	sn1 := "userA"
+	sn2 := "userB"
+	blocked, err := f.Blocked(sn1, sn2)
+	if err != nil {
+		t.Fatalf("db err: %s", err.Error())
+	}
+	if blocked != BlockedNo {
+		t.Fatalf("expected no blocker")
+	}
+}

+ 18 - 0
oscar/icbm.go

@@ -221,6 +221,24 @@ func SendAndReceiveChannelMsgTohost(sm *SessionManager, fm *FeedbagStore, sess *
 		return err
 	}
 
+	blocked, err := fm.Blocked(sess.ScreenName, snacPayloadIn.screenName)
+	if err != nil {
+		return err
+	}
+	if blocked != BlockedNo {
+		snacFrameOut := snacFrame{
+			foodGroup: ICBM,
+			subGroup:  ICBMErr,
+		}
+		snacPayloadOut := &snacError{
+			code: ErrorCodeNotLoggedOn,
+		}
+		if blocked == BlockedA {
+			snacPayloadOut.code = ErrorCodeInLocalPermitDeny
+		}
+		return writeOutSNAC(snac, flap, snacFrameOut, snacPayloadOut, sequence, w)
+	}
+
 	session, err := sm.RetrieveByScreenName(snacPayloadIn.screenName)
 	if err != nil {
 		if errors.Is(err, errSessNotFound) {

+ 22 - 7
oscar/locate.go

@@ -69,7 +69,7 @@ func routeLocate(sess *Session, sm *SessionManager, fm *FeedbagStore, flap *flap
 	case LocateFindListReply:
 		panic("not implemented")
 	case LocateUserInfoQuery2:
-		return SendAndReceiveUserInfoQuery2(sm, fm, flap, snac, r, w, sequence)
+		return SendAndReceiveUserInfoQuery2(sess, sm, fm, flap, snac, r, w, sequence)
 	}
 
 	return nil
@@ -261,7 +261,7 @@ func (f *snacUserInfoReply) write(w io.Writer) error {
 	return f.awayMessage.write(w)
 }
 
-func SendAndReceiveUserInfoQuery2(sm *SessionManager, fm *FeedbagStore, flap *flapFrame, snac *snacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func SendAndReceiveUserInfoQuery2(sess *Session, sm *SessionManager, fm *FeedbagStore, flap *flapFrame, snac *snacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	fmt.Printf("SendAndReceiveUserInfoQuery2 read SNAC frame: %+v\n", snac)
 
 	snacPayloadIn := &snacUserInfoQuery2{}
@@ -269,7 +269,22 @@ func SendAndReceiveUserInfoQuery2(sm *SessionManager, fm *FeedbagStore, flap *fl
 		return err
 	}
 
-	sess, err := sm.RetrieveByScreenName(snacPayloadIn.screenName)
+	blocked, err := fm.Blocked(sess.ScreenName, snacPayloadIn.screenName)
+	if err != nil {
+		return err
+	}
+	if blocked != BlockedNo {
+		snacFrameOut := snacFrame{
+			foodGroup: LOCATE,
+			subGroup:  LocateErr,
+		}
+		snacPayloadOut := &snacError{
+			code: ErrorCodeNotLoggedOn,
+		}
+		return writeOutSNAC(snac, flap, snacFrameOut, snacPayloadOut, sequence, w)
+	}
+
+	buddySess, err := sm.RetrieveByScreenName(snacPayloadIn.screenName)
 	if err != nil {
 		if errors.Is(err, errSessNotFound) {
 			snacFrameOut := snacFrame{
@@ -289,9 +304,9 @@ func SendAndReceiveUserInfoQuery2(sm *SessionManager, fm *FeedbagStore, flap *fl
 	}
 	snacPayloadOut := &snacUserInfoReply{
 		screenName:   snacPayloadIn.screenName,
-		warningLevel: sess.GetWarning(),
+		warningLevel: buddySess.GetWarning(),
 		userInfo: TLVPayload{
-			TLVs: sess.GetUserInfo(),
+			TLVs: buddySess.GetUserInfo(),
 		},
 		clientProfile: TLVPayload{},
 		awayMessage:   TLVPayload{},
@@ -331,7 +346,7 @@ func SendAndReceiveUserInfoQuery2(sm *SessionManager, fm *FeedbagStore, flap *fl
 			},
 			{
 				tType: 0x04,
-				val:   sess.GetAwayMessage(),
+				val:   buddySess.GetAwayMessage(),
 			},
 		}
 	default:
@@ -370,7 +385,7 @@ func (s *snacSetDirInfoReply) write(w io.Writer) error {
 }
 
 func SendAndReceiveSetDirInfo(flap *flapFrame, snac *snacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
-	fmt.Printf("SendAndReceiveUserInfoQuery2 read SNAC frame: %+v\n", snac)
+	fmt.Printf("SendAndReceiveSetDirInfo read SNAC frame: %+v\n", snac)
 
 	snacPayloadIn := &snacSetDirInfo{}
 	if err := snacPayloadIn.read(r); err != nil {

+ 32 - 32
oscar/protocol.go

@@ -10,38 +10,38 @@ import (
 )
 
 const (
-	ErrorCodeInvalidSnac          = 0x01
-	ErrorCodeRateToHost           = 0x02
-	ErrorCodeRateToClient         = 0x03
-	ErrorCodeNotLoggedOn          = 0x04
-	ErrorCodeServiceUnavailable   = 0x05
-	ErrorCodeServiceNotDefined    = 0x06
-	ErrorCodeObsoleteSnac         = 0x07
-	ErrorCodeNotSupportedByHost   = 0x08
-	ErrorCodeNotSupportedByClient = 0x09
-	ErrorCodeRefusedByClient      = 0x0A
-	ErrorCodeReplyTooBig          = 0x0B
-	ErrorCodeResponsesLost        = 0x0C
-	ErrorCodeRequestDenied        = 0x0D
-	ErrorCodeBustedSnacPayload    = 0x0E
-	ErrorCodeInsufficientRights   = 0x0F
-	ErrorCodeInLocalPermitDeny    = 0x10
-	ErrorCodeTooEvilSender        = 0x11
-	ErrorCodeTooEvilReceiver      = 0x12
-	ErrorCodeUserTempUnavail      = 0x13
-	ErrorCodeNoMatch              = 0x14
-	ErrorCodeListOverflow         = 0x15
-	ErrorCodeRequestAmbigous      = 0x16
-	ErrorCodeQueueFull            = 0x17
-	ErrorCodeNotWhileOnAol        = 0x18
-	ErrorCodeQueryFail            = 0x19
-	ErrorCodeTimeout              = 0x1A
-	ErrorCodeErrorText            = 0x1B
-	ErrorCodeGeneralFailure       = 0x1C
-	ErrorCodeProgress             = 0x1D
-	ErrorCodeInFreeArea           = 0x1E
-	ErrorCodeRestrictedByPc       = 0x1F
-	ErrorCodeRemoteRestrictedByPc = 0x20
+	ErrorCodeInvalidSnac          uint16 = 0x01
+	ErrorCodeRateToHost           uint16 = 0x02
+	ErrorCodeRateToClient         uint16 = 0x03
+	ErrorCodeNotLoggedOn          uint16 = 0x04
+	ErrorCodeServiceUnavailable   uint16 = 0x05
+	ErrorCodeServiceNotDefined    uint16 = 0x06
+	ErrorCodeObsoleteSnac         uint16 = 0x07
+	ErrorCodeNotSupportedByHost   uint16 = 0x08
+	ErrorCodeNotSupportedByClient uint16 = 0x09
+	ErrorCodeRefusedByClient      uint16 = 0x0A
+	ErrorCodeReplyTooBig          uint16 = 0x0B
+	ErrorCodeResponsesLost        uint16 = 0x0C
+	ErrorCodeRequestDenied        uint16 = 0x0D
+	ErrorCodeBustedSnacPayload    uint16 = 0x0E
+	ErrorCodeInsufficientRights   uint16 = 0x0F
+	ErrorCodeInLocalPermitDeny    uint16 = 0x10
+	ErrorCodeTooEvilSender        uint16 = 0x11
+	ErrorCodeTooEvilReceiver      uint16 = 0x12
+	ErrorCodeUserTempUnavail      uint16 = 0x13
+	ErrorCodeNoMatch              uint16 = 0x14
+	ErrorCodeListOverflow         uint16 = 0x15
+	ErrorCodeRequestAmbigous      uint16 = 0x16
+	ErrorCodeQueueFull            uint16 = 0x17
+	ErrorCodeNotWhileOnAol        uint16 = 0x18
+	ErrorCodeQueryFail            uint16 = 0x19
+	ErrorCodeTimeout              uint16 = 0x1A
+	ErrorCodeErrorText            uint16 = 0x1B
+	ErrorCodeGeneralFailure       uint16 = 0x1C
+	ErrorCodeProgress             uint16 = 0x1D
+	ErrorCodeInFreeArea           uint16 = 0x1E
+	ErrorCodeRestrictedByPc       uint16 = 0x1F
+	ErrorCodeRemoteRestrictedByPc uint16 = 0x20
 )
 
 const (