Don't use database for auth tokens

This commit is contained in:
mkwcat
2023-12-14 11:24:45 -05:00
parent e86d751428
commit ad79cb286a
15 changed files with 287 additions and 185 deletions

View File

@@ -31,7 +31,7 @@ func HandleGroups(w http.ResponseWriter, r *http.Request) {
filtered := map[string]string{}
filtered["pid"] = player["dwc_pid"]
filtered["name"] = player["name"]
filtered["name"] = player["+ingamesn"]
if player["gamename"] == "mariokartwii" {
filtered["ev"] = player["ev"]

158
common/auth_token.go Normal file
View File

@@ -0,0 +1,158 @@
package common
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"errors"
"strings"
"time"
)
func generateRandom(n int) []byte {
key := make([]byte, n)
read, err := rand.Read(key)
if err != nil {
panic(err)
}
if read != n {
panic("short rand.Read()")
}
return key
}
var (
authTokenKey = generateRandom(16)
authTokenIV = generateRandom(16)
authTokenMagic = generateRandom(16)
loginTicketKey = generateRandom(16)
loginTicketIV = generateRandom(16)
loginTicketMagic = generateRandom(4)
)
func appendString(blob []byte, value string, maxlen int) []byte {
if len([]byte(value)) < maxlen {
blob = append(blob, append([]byte(value), make([]byte, maxlen-len(value))...)...)
} else {
blob = append(blob, []byte(value)[:maxlen]...)
}
return blob
}
func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string) (string, string) {
blob := binary.LittleEndian.AppendUint64([]byte{}, uint64(time.Now().Unix()))
blob = appendString(blob, gamecd, 4)
blob = append(blob, binary.LittleEndian.AppendUint64([]byte{}, userid)[:6]...)
blob = append(blob, byte(min(len([]byte(gsbrcd)), 16)))
blob = appendString(blob, gsbrcd, 16)
blob = append(blob, binary.LittleEndian.AppendUint64([]byte{}, cfc)[:7]...)
blob = append(blob, region, lang)
blob = append(blob, byte(min(len([]byte(ingamesn)), 75)))
blob = appendString(blob, ingamesn, 75)
challenge := RandomString(8)
blob = append(blob, []byte(challenge)...)
blob = append(blob, authTokenMagic...)
block, err := aes.NewCipher(authTokenKey)
if err != nil {
panic(err)
}
cipher.NewCBCEncrypter(block, authTokenIV).CryptBlocks(blob, blob)
return "NDS" + Base64DwcEncoding.EncodeToString(blob), challenge
}
func UnmarshalNASAuthToken(token string) (err error, gamecd string, issuetime time.Time, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, challenge string) {
if !strings.HasPrefix(token, "NDS") {
err = errors.New("invalid auth token prefix")
return
}
blob, err := Base64DwcEncoding.DecodeString(token[3:])
if err != nil {
return
}
if len(blob) != 0x90 {
err = errors.New("invalid auth token length")
return
}
block, err := aes.NewCipher(authTokenKey)
if err != nil {
panic(err)
}
cipher.NewCBCDecrypter(block, authTokenIV).CryptBlocks(blob, blob)
if !bytes.Equal(blob[0x80:0x90], authTokenMagic) {
err = errors.New("invalid auth token magic")
return
}
issuetime = time.Unix(int64(binary.LittleEndian.Uint64(blob[0x0:0x8])), 0)
gamecd = string(blob[0x8:0xC])
userid = binary.LittleEndian.Uint64(append(bytes.Clone(blob[0xC:0x12]), 0, 0))
gsbrcd = string(blob[0x13 : 0x13+min(blob[0x12], 16)])
cfc = binary.LittleEndian.Uint64(append(bytes.Clone(blob[0x23:0x2A]), 0))
region = blob[0x2A]
lang = blob[0x2B]
ingamesn = string(blob[0x2D : 0x2D+min(blob[0x2C], 75)])
challenge = string(blob[0x78:0x80])
return
}
func MarshalGPCMLoginTicket(profileId uint32) string {
blob := binary.LittleEndian.AppendUint64([]byte{}, uint64(time.Now().Unix()))
blob = binary.LittleEndian.AppendUint32(blob, profileId)
blob = append(blob, loginTicketMagic...)
block, err := aes.NewCipher(loginTicketKey)
if err != nil {
panic(err)
}
cipher.NewCBCEncrypter(block, loginTicketIV).CryptBlocks(blob, blob)
return Base64DwcEncoding.EncodeToString(blob)
}
func UnmarshalGPCMLoginTicket(ticket string) (err error, profileId uint32, issuetime time.Time) {
blob, err := Base64DwcEncoding.DecodeString(ticket)
if err != nil {
return
}
if len(blob) != 0x10 {
err = errors.New("invalid login ticket length")
return
}
block, err := aes.NewCipher(loginTicketKey)
if err != nil {
panic(err)
}
cipher.NewCBCDecrypter(block, loginTicketIV).CryptBlocks(blob, blob)
if !bytes.Equal(blob[0xC:0x10], loginTicketMagic) {
err = errors.New("invalid login ticket magic")
return
}
issuetime = time.Unix(int64(binary.LittleEndian.Uint64(blob[0x0:0x8])), 0)
profileId = binary.LittleEndian.Uint32(blob[0x8:0xC])
return
}

View File

@@ -6,7 +6,7 @@ import (
var Base64DwcEncoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-").WithPadding('*')
func Base32Encode(value int64) string {
func Base32Encode(value uint64) string {
alpha := "0123456789abcdefghijklmnopqrstuv"
encoded := ""

View File

@@ -30,6 +30,10 @@ func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) {
for len(msg) > 0 && string(msg[0]) == `\` {
keyEnd := strings.Index(msg[1:], `\`) + 1
if keyEnd < 2 {
return nil, InvalidGameSpyCommand
}
key := msg[1:keyEnd]
value := ""
msg = msg[keyEnd+1:]

View File

@@ -2,101 +2,13 @@ package database
import (
"context"
"crypto/sha512"
"encoding/hex"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/logrusorgru/aurora/v3"
"os"
"strconv"
"wwfc/common"
"wwfc/logging"
)
const (
DoesAuthTokenExist = `SELECT EXISTS(SELECT 1 FROM logins WHERE auth_token = $1)`
DoesNASUserExist = `SELECT EXISTS(SELECT 1 FROM logins WHERE user_id = $1 AND gsbrcd = $2)`
UpdateUserLogin = `UPDATE logins SET auth_token = $1, challenge = $2 WHERE user_id = $3 AND gsbrcd = $4`
InsertUserLogin = `INSERT INTO logins (auth_token, user_id, gsbrcd, challenge) VALUES ($1, $2, $3, $4)`
GetNASUserLogin = `SELECT user_id, gsbrcd FROM logins WHERE auth_token = $1 LIMIT 1`
GetNASChallenge = `SELECT challenge FROM logins WHERE auth_token = $1`
)
var salt []byte
// GenerateAuthToken generates and stores the auth token for this user as well as a challenge.
func GenerateAuthToken(pool *pgxpool.Pool, ctx context.Context, userId int64, gsbrcd string) (string, string) {
var userExists bool
err := pool.QueryRow(ctx, DoesNASUserExist, userId, gsbrcd).Scan(&userExists)
if err != nil {
panic(err)
}
authToken := "NDS" + common.RandomString(80)
for {
// We must make sure that the auth token doesn't exist before attempting to insert it into the database.
var tokenExists bool
err := pool.QueryRow(ctx, DoesAuthTokenExist, authToken).Scan(&tokenExists)
if err != nil {
panic(err)
}
if !tokenExists {
break
}
authToken = "NDS" + common.RandomString(80)
}
challenge := common.RandomString(8)
if userExists {
// UPDATE rather than INSERT
_, err = pool.Exec(ctx, UpdateUserLogin, authToken, challenge, userId, gsbrcd)
if err != nil {
panic(err)
}
} else {
_, err = pool.Exec(ctx, InsertUserLogin, authToken, userId, gsbrcd, challenge)
if err != nil {
panic(err)
}
}
return authToken, challenge
}
func GetNASLogin(pool *pgxpool.Pool, ctx context.Context, authToken string) (int64, string) {
var userId int64
var gsbrcd string
err := pool.QueryRow(ctx, GetNASUserLogin, authToken).Scan(&userId, &gsbrcd)
if err != nil {
if err == pgx.ErrNoRows {
return 0, ""
} else {
panic(err)
}
}
return userId, gsbrcd
}
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string) (User, bool) {
// Make sure salt is loaded
if salt == nil {
var err error
salt, err = os.ReadFile("salt.bin")
if err != nil {
panic(err)
}
}
// Query login table with the auth token.
userId, gsbrcd := GetNASLogin(pool, ctx, authToken)
if userId == 0 {
logging.Error("DATABASE", "Invalid auth token:", aurora.Cyan(authToken))
return User{}, false
}
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string) (User, bool) {
var exists bool
err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists)
if err != nil {
@@ -104,12 +16,10 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string)
}
uniqueNickname := common.Base32Encode(userId) + gsbrcd
password := sha512.Sum512(append(salt, []byte(gsbrcd)...))
user := User{
UserId: userId,
GsbrCode: gsbrcd,
Password: hex.EncodeToString(password[:]),
Email: uniqueNickname + "@nds",
UniqueNick: uniqueNickname,
}
@@ -118,30 +28,15 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string)
// Create the GPCM account
user.CreateUser(pool, ctx)
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(strconv.FormatInt(int64(user.ProfileId), 10)))
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), "-", aurora.Cyan(user.ProfileId))
} else {
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId)
if err != nil {
panic(err)
}
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(strconv.FormatInt(int64(user.ProfileId), 10)))
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(userId), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(user.ProfileId))
}
return user, true
}
func GetChallenge(pool *pgxpool.Pool, ctx context.Context, authToken string) string {
var challenge string
err := pool.QueryRow(ctx, GetNASChallenge, authToken).Scan(&challenge)
if err != nil {
if err == pgx.ErrNoRows {
// Invalid auth token
return ""
} else {
panic(err)
}
}
return challenge
}

View File

@@ -2,17 +2,14 @@ package database
import (
"context"
"errors"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
"math/rand"
"wwfc/common"
)
const (
InsertUser = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5) RETURNING profile_id`
UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END WHERE profile_id = $1 RETURNING user_id, gsbrcd, password, email, unique_nick, firstname, lastname`
GetUser = `SELECT user_id, gsbrcd, password, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1`
UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END WHERE profile_id = $1 RETURNING user_id, gsbrcd, email, unique_nick, firstname, lastname`
GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1`
CreateUserSession = `INSERT INTO sessions (session_key, profile_id, login_ticket) VALUES ($1, $2, $3)`
GetTicketSession = `SELECT session_key, profile_id FROM sessions WHERE login_ticket = $1`
DoesUserExist = `SELECT EXISTS(SELECT 1 FROM users WHERE user_id = $1 AND gsbrcd = $2)`
@@ -25,9 +22,8 @@ const (
type User struct {
ProfileId uint32
UserId int64
UserId uint64
GsbrCode string
Password string
Email string
UniqueNick string
FirstName string
@@ -35,15 +31,15 @@ type User struct {
}
func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) {
err := pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, user.Password, user.Email, user.UniqueNick).Scan(&user.ProfileId)
err := pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, "", user.Email, user.UniqueNick).Scan(&user.ProfileId)
if err != nil {
panic(err)
}
}
func GetUniqueUserID() int64 {
func GetUniqueUserID() uint64 {
// Not guaranteed unique but doesn't matter in practice if multiple people have the same user ID.
return rand.Int63n(0x80000000000)
return uint64(rand.Int63n(0x80000000000))
}
func UpdateProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32, data map[string]string) User {
@@ -52,7 +48,7 @@ func UpdateProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32, da
user := User{}
row := pool.QueryRow(ctx, UpdateUserTable, profileId, firstName, firstNameExists, lastName, lastNameExists)
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Password, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
if err != nil {
panic(err)
}
@@ -61,38 +57,10 @@ func UpdateProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32, da
return user
}
func CreateSession(pool *pgxpool.Pool, ctx context.Context, profileId uint32, loginTicket string) string {
sessionKey := common.RandomString(8)
_, err := pool.Exec(ctx, CreateUserSession, sessionKey, profileId, loginTicket)
if err != nil {
panic(err)
}
return sessionKey
}
func GetSession(pool *pgxpool.Pool, ctx context.Context, loginTicket string) (string, uint32) {
var sessionKey string
var profileId uint32
err := pool.QueryRow(ctx, GetTicketSession, loginTicket).Scan(&sessionKey, &profileId)
if err != nil {
panic(err)
}
return sessionKey, profileId
}
func deleteSession(pool *pgxpool.Pool, ctx context.Context, profileId uint32) {
_, err := pool.Exec(ctx, DeleteUserSession, profileId)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
panic(err)
}
}
func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User, bool) {
user := User{}
row := pool.QueryRow(ctx, GetUser, profileId)
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Password, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
if err != nil {
return User{}, false
}

2
go.mod
View File

@@ -1,6 +1,6 @@
module wwfc
go 1.20
go 1.21
require (
github.com/jackc/pgx/v4 v4.18.1

View File

@@ -3,12 +3,13 @@ package gpcm
import (
"crypto/md5"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/logrusorgru/aurora/v3"
"math/rand"
"strconv"
"strings"
"time"
"wwfc/common"
"wwfc/database"
"wwfc/logging"
@@ -96,7 +97,7 @@ func verifySignature(authToken string, signature string) bool {
logging.Error("GPCM", "Auth token signature failed")
return false
}
logging.Notice("GPCM", "Auth token signature verified")
logging.Notice("GPCM", "Auth token signature verified; NG ID:", aurora.Cyan(fmt.Sprintf("%08x", ngId)))
return true
}
@@ -124,24 +125,27 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
}
signature, exists := command.OtherValues["wwfc_sig"]
if exists {
// TODO: This is still in testing so it's only checked if it exists
if !verifySignature(authToken, signature) {
g.replyError(GPError{
ErrorCode: ErrLogin.ErrorCode,
ErrorString: "The authentication signature is invalid.",
Fatal: true,
})
return
}
if !exists || !verifySignature(authToken, signature) {
g.replyError(GPError{
ErrorCode: ErrLogin.ErrorCode,
ErrorString: "The authentication signature is invalid.",
Fatal: true,
})
return
}
challenge := database.GetChallenge(pool, ctx, authToken)
if challenge == "" {
err, _, issueTime, userId, gsbrcd, cfc, _, _, ingamesn, challenge := common.UnmarshalNASAuthToken(authToken)
if err != nil {
g.replyError(ErrLogin)
return
}
currentTime := time.Now()
if issueTime.Before(currentTime.Add(-10*time.Minute)) || issueTime.After(currentTime) {
g.replyError(ErrLoginLoginTicketExpired)
return
}
response := generateResponse(g.Challenge, challenge, authToken, command.OtherValues["challenge"])
if response != command.OtherValues["response"] {
g.replyError(ErrLogin)
@@ -151,7 +155,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
proof := generateProof(g.Challenge, challenge, command.OtherValues["authtoken"], command.OtherValues["challenge"])
// Perform the login with the database.
user, ok := database.LoginUserToGPCM(pool, ctx, authToken)
user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd)
if !ok {
// There was an error logging in to the GP backend.
g.replyError(ErrLogin)
@@ -175,10 +179,9 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
sessions[g.User.ProfileId] = g
mutex.Unlock()
g.LoginTicket = strings.Replace(base64.StdEncoding.EncodeToString([]byte(common.RandomString(16))), "=", "_", -1)
// Now initiate the session
_ = database.CreateSession(pool, ctx, g.User.ProfileId, g.LoginTicket)
g.LoginTicket = common.MarshalGPCMLoginTicket(g.User.ProfileId)
g.SessionKey = rand.Int31n(290000000) + 10000000
g.InGameName = ingamesn
g.LoggedIn = true
g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10)
@@ -186,7 +189,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
// Notify QR2 of the login
// TODO: Get ingamesn and cfc from NAS
qr2.Login(g.User.ProfileId, "", "", g.Conn.RemoteAddr().String())
qr2.Login(g.User.ProfileId, ingamesn, cfc, g.Conn.RemoteAddr().String())
payload := common.CreateGameSpyMessage(common.GameSpyCommand{
Command: "lc",
@@ -194,8 +197,8 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
OtherValues: map[string]string{
"sesskey": strconv.FormatInt(int64(g.SessionKey), 10),
"proof": proof,
"userid": strconv.FormatInt(g.User.UserId, 10),
"profileid": strconv.FormatInt(int64(g.User.ProfileId), 10),
"userid": strconv.FormatUint(g.User.UserId, 10),
"profileid": strconv.FormatUint(uint64(g.User.ProfileId), 10),
"uniquenick": g.User.UniqueNick,
"lt": g.LoginTicket,
"id": command.OtherValues["id"],

View File

@@ -24,6 +24,7 @@ type GameSpySession struct {
Challenge string
LoginTicket string
SessionKey int32
InGameName string
Status string
LocString string
FriendList []uint32

View File

@@ -43,7 +43,7 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) {
OtherValues: map[string]string{
"profileid": command.OtherValues["profileid"],
"nick": user.UniqueNick,
"userid": strconv.FormatInt(user.UserId, 10),
"userid": strconv.FormatUint(uint64(user.UserId), 10),
"email": user.Email,
"sig": common.RandomHexString(32),
"uniquenick": user.UniqueNick,

View File

@@ -1,6 +1,8 @@
package nas
import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/logrusorgru/aurora/v3"
"net/http"
@@ -10,6 +12,7 @@ import (
"strconv"
"strings"
"time"
"unicode/utf16"
"wwfc/common"
"wwfc/database"
"wwfc/logging"
@@ -30,7 +33,7 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
fields := map[string]string{}
for key, values := range r.PostForm {
if len(values) != 1 {
logging.Warn(moduleName, "Ignoring multiple POST form values:", aurora.Cyan(key).String()+":", aurora.Cyan(values))
logging.Warn(moduleName, "Ignoring none or multiple POST form values:", aurora.Cyan(key).String()+":", aurora.Cyan(values))
continue
}
@@ -40,8 +43,20 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
replyHTTPError(w, 400, "400 Bad Request")
return
}
logging.Info(moduleName, aurora.Cyan(key).String()+":", aurora.Cyan(string(parsed)))
fields[key] = string(parsed)
value := string(parsed)
if key == "ingamesn" {
// Special handling required for the UTF-16 string
var utf16String []uint16
for i := 0; i < len(parsed)/2; i++ {
utf16String = append(utf16String, binary.BigEndian.Uint16(parsed[i*2:i*2+2]))
}
value = string(utf16.Decode(utf16String))
}
logging.Info(moduleName, aurora.Cyan(key).String()+":", aurora.Cyan(value))
fields[key] = value
}
reply := map[string]string{}
@@ -139,7 +154,7 @@ func acctcreate() map[string]string {
"retry": "0",
"datetime": getDateTime(),
"returncd": "002",
"userid": strconv.FormatInt(database.GetUniqueUserID(), 10),
"userid": strconv.FormatUint(database.GetUniqueUserID(), 10),
}
}
@@ -150,6 +165,13 @@ func login(moduleName string, fields map[string]string) map[string]string {
"locator": "gamespy.com",
}
gamecd, ok := fields["gamecd"]
if !ok {
logging.Error(moduleName, "No gamecd in form")
param["returncd"] = "103"
return param
}
strUserId, ok := fields["userid"]
if !ok {
logging.Error(moduleName, "No userid in form")
@@ -157,8 +179,8 @@ func login(moduleName string, fields map[string]string) map[string]string {
return param
}
userId, err := strconv.ParseInt(strUserId, 10, 64)
if err != nil {
userId, err := strconv.ParseUint(strUserId, 10, 64)
if err != nil || userId >= 0x80000000000 {
logging.Error(moduleName, "Invalid userid string in form")
param["returncd"] = "103"
return param
@@ -171,7 +193,45 @@ func login(moduleName string, fields map[string]string) map[string]string {
return param
}
authToken, challenge := database.GenerateAuthToken(pool, ctx, userId, gsbrcd)
cfc, ok := fields["cfc"]
if !ok {
logging.Error(moduleName, "No cfc in form")
param["returncd"] = "103"
return param
}
cfcInt, err := strconv.ParseUint(cfc, 10, 64)
if err != nil || cfcInt > 9999999999999999 {
logging.Error(moduleName, "Invalid cfc string in form")
param["returncd"] = "103"
return param
}
region, ok := fields["region"]
if !ok {
region = "ff"
}
regionByte, err := hex.DecodeString(region)
if err != nil || len(regionByte) != 1 {
logging.Error(moduleName, "Invalid region byte in form")
param["returncd"] = "103"
return param
}
lang, ok := fields["lang"]
if !ok {
lang = "ff"
}
langByte, err := hex.DecodeString(lang)
if err != nil || len(langByte) != 1 {
logging.Error(moduleName, "Invalid lang byte in form")
param["returncd"] = "103"
return param
}
authToken, challenge := common.MarshalNASAuthToken(gamecd, userId, gsbrcd, cfcInt, regionByte[0], langByte[0], fields["ingamesn"])
param["returncd"] = "001"
param["challenge"] = challenge

View File

@@ -187,6 +187,9 @@ func GetGroups(gameName string) []GroupInfo {
for k, v := range session.Data {
mapData[k] = v
}
mapData["+ingamesn"] = session.Login.InGameName
groupInfo.Players = append(groupInfo.Players, mapData)
if mapData["dwc_hoststate"] == "2" && mapData["dwc_suspend"] == "0" {

View File

@@ -3,13 +3,13 @@ package qr2
type LoginInfo struct {
ProfileID uint32
InGameName string
ConsoleFriendCode string
ConsoleFriendCode uint64
GPPublicIP string
}
var logins = map[uint32]LoginInfo{}
func Login(profileID uint32, inGameName, consoleFriendCode, publicIP string) {
func Login(profileID uint32, inGameName string, consoleFriendCode uint64, publicIP string) {
mutex.Lock()
logins[profileID] = LoginInfo{
ProfileID: profileID,

View File

@@ -24,6 +24,7 @@ type Session struct {
Addr net.Addr
Challenge string
Authenticated bool
Login LoginInfo
LastKeepAlive int64
Endianness byte // Some fields depend on the client's endianness
Data map[string]string
@@ -155,7 +156,9 @@ func (session *Session) setProfileID(moduleName string, newPID string) bool {
// Check if the public IP matches the one used for the GPCM session
var gpPublicIP string
if loginInfo, ok := logins[uint32(profileID)]; ok {
var loginInfo LoginInfo
var ok bool
if loginInfo, ok = logins[uint32(profileID)]; ok {
gpPublicIP = loginInfo.GPPublicIP
} else {
logging.Error(moduleName, "Provided dwc_pid is not logged in:", aurora.Cyan(newPID))
@@ -167,6 +170,8 @@ func (session *Session) setProfileID(moduleName string, newPID string) bool {
return false
}
session.Login = loginInfo
// Constraint: only one session can exist with a profile ID
var outdated []uint64
for sessionAddr, otherSession := range sessions {

View File

@@ -191,8 +191,13 @@ func getRequestIdentity(moduleName string, request StorageRequestData) (uint32,
panic("Invalid secret key")
}
// GetSession panics if it can't find the session for the login ticket
_, profileId := database.GetSession(pool, ctx, request.LoginTicket)
logging.Notice(moduleName, request.LoginTicket)
err, profileId, _ := common.UnmarshalGPCMLoginTicket(request.LoginTicket)
if err != nil {
panic(err)
}
logging.Info(moduleName, "Profile ID:", aurora.BrightCyan(profileId))
logging.Info(moduleName, "Game:", aurora.Cyan(request.GameID), "-", aurora.BrightCyan(gameInfo.Name))
logging.Info(moduleName, "Table ID:", aurora.Cyan(request.TableID))