QR2/GPCM: Add protection for restricted players joining public rooms

This commit is contained in:
mkwcat
2024-01-10 21:24:26 -05:00
parent 8137d96612
commit 809a258b7a
7 changed files with 132 additions and 10 deletions

View File

@@ -478,6 +478,26 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
return
}
if g.User.Restricted || toSession.User.Restricted {
// Check with QR2 if the room is public or private
resvError := qr2.CheckGPReservationAllowed(g.QR2IP, g.User.ProfileId, uint32(toProfileId), msgMatchData.Reservation.MatchType)
if resvError != "ok" {
if resvError == "restricted" || resvError == "restricted_join" {
logging.Error(g.ModuleName, "RESERVATION: Restricted user tried to connect to public room")
// Kick the player(s)
if g.User.Restricted {
kickPlayer(toSession.User.ProfileId, resvError)
}
if toSession.User.Restricted {
kickPlayer(g.User.ProfileId, resvError)
}
}
// Otherwise generic error?
return
}
}
if !sameAddress {
searchId := qr2.GetSearchID(g.QR2IP)
msgMatchData.Reservation.PublicIP = uint32(searchId & 0xffffffff)

View File

@@ -1,9 +1,6 @@
package gpcm
func KickPlayer(profileID uint32, reason string) {
mutex.Lock()
defer mutex.Unlock()
func kickPlayer(profileID uint32, reason string) {
if session, exists := sessions[profileID]; exists {
errorMessage := WWFCMsgKickedGeneric
@@ -33,3 +30,10 @@ func KickPlayer(profileID uint32, reason string) {
session.Conn.Close()
}
}
func KickPlayer(profileID uint32, reason string) {
mutex.Lock()
defer mutex.Unlock()
kickPlayer(profileID, reason)
}

View File

@@ -248,7 +248,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ")
// Notify QR2 of the login
qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated, g.User.Restricted)
qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated, g.User.Restricted, KickPlayer)
payload := common.CreateGameSpyMessage(common.GameSpyCommand{
Command: "lc",

View File

@@ -185,6 +185,73 @@ func ProcessGPStatusUpdate(profileID uint32, senderIP uint64, status string) {
}
}
func checkReservationAllowed(moduleName string, sender, destination *Session, joinType byte) string {
if sender.Login == nil || destination.Login == nil {
return ""
}
if !sender.Login.Restricted && !destination.Login.Restricted {
return "ok"
}
if joinType != 2 && joinType != 3 {
return "restricted_join"
}
// TODO: Once OpenHost is implemented, disallow joining public rooms
if destination.GroupPointer == nil {
// Destination is not in a group, check their dwc_mtype instead
if destination.Data["dwc_mtype"] != "2" && destination.Data["dwc_mtype"] != "3" {
return "restricted_join"
}
// This is fine
return "ok"
}
if destination.GroupPointer.MatchType != "private" {
return "restricted_join"
}
return "ok"
}
func CheckGPReservationAllowed(senderIP uint64, senderPid uint32, destPid uint32, joinType byte) string {
senderPidStr := strconv.FormatUint(uint64(senderPid), 10)
destPidStr := strconv.FormatUint(uint64(destPid), 10)
moduleName := "QR2:CheckReservation:" + senderPidStr + "->" + destPidStr
mutex.Lock()
defer mutex.Unlock()
from := sessions[senderIP]
if from == nil {
logging.Error(moduleName, "Sender IP does not exist:", aurora.Cyan(fmt.Sprintf("%012x", senderIP)))
return ""
}
toLogin := logins[destPid]
if toLogin == nil {
logging.Error(moduleName, "Destination profile ID does not exist:", aurora.Cyan(destPid))
return ""
}
to := toLogin.Session
if to == nil {
logging.Error(moduleName, "Destination profile ID does not have a session")
return ""
}
// Validate dwc_pid value
if !from.setProfileID(moduleName, senderPidStr) || from.Login == nil {
return ""
}
return checkReservationAllowed(moduleName, from, to, joinType)
}
// findNewServer attempts to find the new server/host in the group when the current server goes down.
// If no server is found, the group's server pointer is set to nil.
// Expects the mutex to be locked.

View File

@@ -12,8 +12,6 @@ import (
func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []byte) {
sessionId := binary.BigEndian.Uint32(buffer[1:5])
logging.Info(moduleName, "Received heartbeat; session ID:", aurora.BrightCyan(sessionId))
values := strings.Split(string(buffer[5:]), "\u0000")
payload := map[string]string{}
@@ -31,6 +29,14 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
payload[values[i]] = values[i+1]
}
if payload["dwc_mtype"] != "" {
logging.Info(moduleName, "Match type:", aurora.Cyan(payload["dwc_mtype"]))
}
if payload["dwc_hoststate"] != "" {
logging.Info(moduleName, "Host state:", aurora.Cyan(payload["dwc_hoststate"]))
}
realIP, realPort := common.IPFormatToString(addr.String())
if ip, ok := payload["publicip"]; !ok || ip == "0" {
@@ -94,6 +100,4 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
}
}
mutex.Unlock()
logging.Info(moduleName, "Heartbeat ok")
}

View File

@@ -9,12 +9,13 @@ type LoginInfo struct {
NeedsExploit bool
DeviceAuthenticated bool
Restricted bool
GPErrorCallback func(uint32, string)
Session *Session
}
var logins = map[uint32]*LoginInfo{}
func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool, restricted bool) {
func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool, restricted bool, gpErrorCallback func(uint32, string)) {
mutex.Lock()
defer mutex.Unlock()
@@ -27,6 +28,7 @@ func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCo
NeedsExploit: needsExploit,
DeviceAuthenticated: deviceAuthenticated,
Restricted: restricted,
GPErrorCallback: gpErrorCallback,
Session: nil,
}
}

View File

@@ -245,6 +245,31 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
common.LogMatchCommand(moduleName, destPid, cmd, matchData)
if cmd == common.MatchReservation {
resvError := checkReservationAllowed(moduleName, sender, receiver, matchData.Reservation.MatchType)
if resvError != "ok" {
if resvError == "restricted" || resvError == "restricted_join" {
logging.Error(moduleName, "RESERVATION: Restricted player attempted to join a public match")
if sender.Login != nil && sender.Login.Restricted {
callback := sender.Login.GPErrorCallback
profileId := sender.Login.ProfileID
mutex.Unlock()
callback(profileId, resvError)
mutex.Lock()
}
if receiver.Login != nil && receiver.Login.Restricted {
callback := receiver.Login.GPErrorCallback
profileId := receiver.Login.ProfileID
mutex.Unlock()
callback(profileId, resvError)
mutex.Lock()
}
}
return
}
sender.Reservation = matchData
sender.ReservationID = receiver.SearchID
} else if cmd == common.MatchResvOK || cmd == common.MatchResvDeny || cmd == common.MatchResvWait {