diff --git a/db/login_hardware_ids.go b/db/login_hardware_ids.go new file mode 100644 index 0000000..a71b79d --- /dev/null +++ b/db/login_hardware_ids.go @@ -0,0 +1,57 @@ +package db + +import "time" + +type LoginHardwareId struct { + Id int `db:"id"` + UserId int `db:"user_id"` + CpuId string `db:"cpu_id"` + DiskId string `db:"disk_id"` + CpuDiskId string `db:"cpu_disk_id"` + QuaverDll string `db:"quaver_dll"` + QuaverAPIDll string `db:"quaver_api_dll"` + QuaverServerClientDll string `db:"quaver_server_client_dll"` + QuaverServerCommonDll string `db:"quaver_server_common_dll"` + QuaverSharedDll string `db:"quaver_shared_dll"` + Occurrences int `db:"occurrences"` + Timestamp int64 `db:"timestamp"` +} + +// InsertLoginHardwareId Logs the hardware ids and client build signatures used by a user during login. +func InsertLoginHardwareId(userId int, cpuId string, diskId string, cpuDiskId string, build GameBuild) error { + timestamp := time.Now().UnixMilli() + + updateQuery := "UPDATE login_hardware_ids SET " + + "quaver_dll = ?, quaver_api_dll = ?, quaver_server_client_dll = ?, quaver_server_common_dll = ?, quaver_shared_dll = ?, occurrences = occurrences + 1, timestamp = ? " + + "WHERE user_id = ? AND cpu_id = ? AND disk_id = ? AND cpu_disk_id = ?" + + result, err := SQL.Exec(updateQuery, build.QuaverDll, build.QuaverAPIDll, build.QuaverServerClientDll, + build.QuaverServerCommonDll, build.QuaverSharedDll, timestamp, userId, cpuId, diskId, cpuDiskId) + + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + + if err != nil { + return err + } + + if rowsAffected > 0 { + return nil + } + + query := "INSERT INTO login_hardware_ids " + + "(user_id, cpu_id, disk_id, cpu_disk_id, quaver_dll, quaver_api_dll, quaver_server_client_dll, quaver_server_common_dll, quaver_shared_dll, occurrences, timestamp) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + + _, err = SQL.Exec(query, userId, cpuId, diskId, cpuDiskId, build.QuaverDll, build.QuaverAPIDll, + build.QuaverServerClientDll, build.QuaverServerCommonDll, build.QuaverSharedDll, 1, timestamp) + + if err != nil { + return err + } + + return nil +} diff --git a/handlers/login.go b/handlers/login.go index ac024a0..4826be5 100644 --- a/handlers/login.go +++ b/handlers/login.go @@ -4,6 +4,13 @@ import ( "database/sql" "encoding/base64" "encoding/json" + "fmt" + "log" + "net" + "net/http" + "strconv" + "strings" + "example.com/Quaver/Z/chat" "example.com/Quaver/Z/common" "example.com/Quaver/Z/config" @@ -12,13 +19,7 @@ import ( "example.com/Quaver/Z/sessions" "example.com/Quaver/Z/utils" "example.com/Quaver/Z/webhooks" - "fmt" "github.com/go-resty/resty/v2" - "log" - "net" - "net/http" - "strconv" - "strings" ) // LoginData The data that the user sends to log in @@ -34,6 +35,15 @@ type LoginData struct { // Game Client file signatures Client string `json:"client"` + + // Hardware string +Hardware string `json:"hw,omitempty"` +} + +type HardwareIds struct { + CpuId string + DiskId string + CpuDiskId string } var ( @@ -75,7 +85,13 @@ func HandleLogin(conn net.Conn, r *http.Request) error { return nil } - err = verifyGameBuild(data) + build, err := parseGameBuild(data.Client) + + if err != nil { + return err + } + + err = verifyGameBuild(build) if err != nil { if err == sql.ErrNoRows { @@ -87,6 +103,14 @@ func HandleLogin(conn net.Conn, r *http.Request) error { } } + hardware := parseHardwareIds(data.Hardware) + + err = db.InsertLoginHardwareId(user.Id, hardware.CpuId, hardware.DiskId, hardware.CpuDiskId, build) + + if err != nil { + return err + } + ip := conn.RemoteAddr().String() ipHeader := r.Header.Get("X-Forwarded-For") @@ -280,20 +304,49 @@ func checkSteamAppOwnership(steamId string) error { return nil } -// Checks the client signatures to see if the build they are using is valid -func verifyGameBuild(data *LoginData) error { - split := strings.Split(data.Client, "|") +func parseGameBuild(client string) (db.GameBuild, error) { + split := strings.Split(client, "|") if len(split) != 5 { - return fmt.Errorf("user provided an incorrect amount of client signatures - %v", data.Client) + return db.GameBuild{}, fmt.Errorf("user provided an incorrect amount of client signatures - %v", client) } - err := db.VerifyGameBuild(db.GameBuild{ + return db.GameBuild{ + QuaverDll: split[0], QuaverAPIDll: split[1], QuaverServerClientDll: split[2], QuaverServerCommonDll: split[3], QuaverSharedDll: split[4], - }) + }, nil +} + +func parseHardwareIds(hardware string) HardwareIds { + split := strings.Split(hardware, "|") + + ids := HardwareIds{ + CpuId: "none", + DiskId: "none", + CpuDiskId: "none", + } + + if len(split) > 0 && split[0] != "" { + ids.CpuId = split[0] + } + + if len(split) > 1 && split[1] != "" { + ids.DiskId = split[1] + } + + if len(split) > 2 && split[2] != "" { + ids.CpuDiskId = split[2] + } + + return ids +} + +// Checks the client signatures to see if the build they are using is valid +func verifyGameBuild(build db.GameBuild) error { + err := db.VerifyGameBuild(build) if err != nil { return err @@ -344,9 +397,10 @@ func handleCustomGameBuildUsage(conn net.Conn, user *db.User, client string) boo } clientStr := fmt.Sprintf("```json\n%v```", formatCustomGameBuild(client)) - webhooks.SendAntiCheat(user.Username, user.Id, user.GetProfileUrl(), user.AvatarUrl.String, "Invalid Game Build", clientStr) if !canUserUseCustomGameBuild(user) { + webhooks.SendAntiCheat(user.Username, user.Id, user.GetProfileUrl(), user.AvatarUrl.String, "Invalid Game Build", clientStr) + sessions.SendPacketToConnection(packets.NewServerNotificationError("Please update your client before attempting to login."), conn) utils.CloseConnectionDelayed(conn) return false diff --git a/handlers/login_test.go b/handlers/login_test.go new file mode 100644 index 0000000..00194b3 --- /dev/null +++ b/handlers/login_test.go @@ -0,0 +1,67 @@ +package handlers + +import "testing" + +func TestParseHardwareIds(t *testing.T) { + tests := []struct { + name string + hardware string + expectedCpu string + expectedDisk string + expectedCombo string + }{ + { + name: "all values", + hardware: "cpu|disk|combo", + expectedCpu: "cpu", + expectedDisk: "disk", + expectedCombo: "combo", + }, + { + name: "missing cpu", + hardware: "|disk|combo", + expectedCpu: "none", + expectedDisk: "disk", + expectedCombo: "combo", + }, + { + name: "missing disk", + hardware: "cpu||combo", + expectedCpu: "cpu", + expectedDisk: "none", + expectedCombo: "combo", + }, + { + name: "missing combo", + hardware: "cpu|disk", + expectedCpu: "cpu", + expectedDisk: "disk", + expectedCombo: "none", + }, + { + name: "empty", + hardware: "", + expectedCpu: "none", + expectedDisk: "none", + expectedCombo: "none", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + hardware := parseHardwareIds(test.hardware) + + if hardware.CpuId != test.expectedCpu { + t.Fatalf("expected cpu id %q, got %q", test.expectedCpu, hardware.CpuId) + } + + if hardware.DiskId != test.expectedDisk { + t.Fatalf("expected disk id %q, got %q", test.expectedDisk, hardware.DiskId) + } + + if hardware.CpuDiskId != test.expectedCombo { + t.Fatalf("expected cpu disk id %q, got %q", test.expectedCombo, hardware.CpuDiskId) + } + }) + } +}