From 6601285218c1724d5ece633a196ee68e51084a1a Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 30 Apr 2023 01:32:38 +1000 Subject: [PATCH 1/8] unfinished proof of concept --- patch-schema/psn-link.sql | 11 +++ server/signserver/dbutils.go | 91 +++++++++++++++--------- server/signserver/dsgn_resp.go | 13 ++-- server/signserver/session.go | 122 ++++++++++++++++++++++----------- 4 files changed, 158 insertions(+), 79 deletions(-) create mode 100644 patch-schema/psn-link.sql diff --git a/patch-schema/psn-link.sql b/patch-schema/psn-link.sql new file mode 100644 index 000000000..fc22e9046 --- /dev/null +++ b/patch-schema/psn-link.sql @@ -0,0 +1,11 @@ +BEGIN; + +ALTER TABLE public.sign_sessions ADD COLUMN id SERIAL; + +ALTER TABLE public.sign_sessions ADD CONSTRAINT sign_sessions_pkey PRIMARY KEY (id); + +ALTER TABLE public.sign_sessions ALTER COLUMN user_id DROP NOT NULL; + +ALTER TABLE public.sign_sessions ADD COLUMN psn_id TEXT; + +END; \ No newline at end of file diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index ee4dcc493..d66d85731 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -1,10 +1,12 @@ package signserver import ( + "errors" "erupe-ce/common/mhfcourse" "strings" "time" + "go.uber.org/zap" "golang.org/x/crypto/bcrypt" ) @@ -41,22 +43,19 @@ func (s *Server) newUserChara(username string) error { return nil } -func (s *Server) registerDBAccount(username string, password string) error { +func (s *Server) registerDBAccount(username string, password string) (uint32, error) { + var uid uint32 + s.logger.Info("Creating user", zap.String("User", username)) + // Create salted hash of user password passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { - return err + return 0, err } - _, err = s.db.Exec("INSERT INTO users (username, password, return_expires) VALUES ($1, $2, $3)", username, string(passwordHash), time.Now().Add(time.Hour*24*30)) + err = s.db.QueryRow("INSERT INTO users (username, password, return_expires) VALUES ($1, $2, $3) RETURNING id", username, string(passwordHash), time.Now().Add(time.Hour*24*30)).Scan(&uid) if err != nil { - return err - } - - var id int - err = s.db.QueryRow("SELECT id FROM users WHERE username = $1", username).Scan(&id) - if err != nil { - return err + return 0, err } // Create a base new character. @@ -65,14 +64,14 @@ func (s *Server) registerDBAccount(username string, password string) error { user_id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login) VALUES($1, False, True, '', '', 0, 0, 0, $2)`, - id, + uid, uint32(time.Now().Unix()), ) if err != nil { - return err + return 0, err } - return nil + return uid, nil } type character struct { @@ -87,7 +86,7 @@ type character struct { LastLogin uint32 `db:"last_login"` } -func (s *Server) getCharactersForUser(uid int) ([]character, error) { +func (s *Server) getCharactersForUser(uid uint32) ([]character, error) { characters := make([]character, 0) err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false ORDER BY id ASC", uid) if err != nil { @@ -96,7 +95,7 @@ func (s *Server) getCharactersForUser(uid int) ([]character, error) { return characters, nil } -func (s *Server) getReturnExpiry(uid int) time.Time { +func (s *Server) getReturnExpiry(uid uint32) time.Time { var returnExpiry, lastLogin time.Time s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid) if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) { @@ -113,16 +112,18 @@ func (s *Server) getReturnExpiry(uid int) time.Time { return returnExpiry } -func (s *Server) getLastCID(uid int) uint32 { +func (s *Server) getLastCID(uid uint32) uint32 { var lastPlayed uint32 _ = s.db.QueryRow("SELECT last_character FROM users WHERE id=$1", uid).Scan(&lastPlayed) return lastPlayed } -func (s *Server) getUserRights(uid int) uint32 { - rights := uint32(2) - _ = s.db.QueryRow("SELECT rights FROM users WHERE id=$1", uid).Scan(&rights) - _, rights = mhfcourse.GetCourseStruct(rights) +func (s *Server) getUserRights(uid uint32) uint32 { + var rights uint32 + if uid != 0 { + _ = s.db.QueryRow("SELECT rights FROM users WHERE id=$1", uid).Scan(&rights) + _, rights = mhfcourse.GetCourseStruct(rights) + } return rights } @@ -189,14 +190,12 @@ func (s *Server) getGuildmatesForCharacters(chars []character) []members { return guildmates } -func (s *Server) deleteCharacter(cid int, token string) error { - var verify int - err := s.db.QueryRow("SELECT count(*) FROM sign_sessions WHERE token = $1", token).Scan(&verify) - if err != nil { - return err // Invalid token +func (s *Server) deleteCharacter(cid int, token string, tokenID uint32) error { + if !s.validateToken(token, tokenID) { + return errors.New("invalid token") } var isNew bool - err = s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", cid).Scan(&isNew) + err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", cid).Scan(&isNew) if isNew { _, err = s.db.Exec("DELETE FROM characters WHERE id = $1", cid) } else { @@ -209,7 +208,7 @@ func (s *Server) deleteCharacter(cid int, token string) error { } // Unused -func (s *Server) checkToken(uid int) (bool, error) { +func (s *Server) checkToken(uid uint32) (bool, error) { var exists int err := s.db.QueryRow("SELECT count(*) FROM sign_sessions WHERE user_id = $1", uid).Scan(&exists) if err != nil { @@ -221,10 +220,36 @@ func (s *Server) checkToken(uid int) (bool, error) { return false, nil } -func (s *Server) registerToken(uid int, token string) error { - _, err := s.db.Exec("INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, token) - if err != nil { - return err - } - return nil +func (s *Server) registerToken(uid uint32, token string) (uint32, error) { + var id uint32 + var err error + err = s.db.QueryRow("INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, token).Scan(&id) + return id, err +} + +func (s *Server) validateToken(token string, tokenID uint32) bool { + query := `SELECT count(*) FROM sign_sessions WHERE token = $1` + if tokenID > 0 { + query += ` AND id = $2` + } + var exists int + err := s.db.QueryRow(query, token, tokenID).Scan(&exists) + if err != nil || exists == 0 { + return false + } + return true +} + +func (s *Server) validateLogin(user string, pass string) (uint32, error) { + var uid uint32 + var passDB string + err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB) + if err != nil { + return 0, err + } else { + if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil { + return uid, nil + } + return 0, nil + } } diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index b39a2901c..f07161e7e 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -11,17 +11,20 @@ import ( "strings" ) -func (s *Session) makeSignResponse(uid int) []byte { +func (s *Session) makeSignResponse(uid uint32) []byte { // Get the characters from the DB. chars, err := s.server.getCharactersForUser(uid) if err != nil { s.logger.Warn("Error getting characters from DB", zap.Error(err)) } - sessToken := token.Generate(16) - _ = s.server.registerToken(uid, sessToken) - bf := byteframe.NewByteFrame() + sessToken := token.Generate(16) + tokenID, err := s.server.registerToken(uid, sessToken) + if err != nil { + bf.WriteUint8(uint8(SIGN_EABORT)) + return bf.Data() + } bf.WriteUint8(1) // resp_code if (s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "") || s.client == PS3 { @@ -31,7 +34,7 @@ func (s *Session) makeSignResponse(uid int) []byte { } bf.WriteUint8(1) // entrance server count bf.WriteUint8(uint8(len(chars))) - bf.WriteUint32(0xFFFFFFFF) // login_token_number + bf.WriteUint32(tokenID) bf.WriteBytes([]byte(sessToken)) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) if s.client == PS3 { diff --git a/server/signserver/session.go b/server/signserver/session.go index 3eb914c93..3ade1426a 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -3,20 +3,21 @@ package signserver import ( "database/sql" "encoding/hex" + "erupe-ce/common/stringsupport" "fmt" "net" + "strings" "sync" "erupe-ce/common/byteframe" "erupe-ce/network" "go.uber.org/zap" - "golang.org/x/crypto/bcrypt" ) -type Client int +type client int const ( - PC100 Client = iota + PC100 client = iota VITA PS3 WIIU @@ -29,7 +30,7 @@ type Session struct { server *Server rawConn net.Conn cryptConn *network.CryptConn - client Client + client client } func (s *Session) work() { @@ -63,11 +64,14 @@ func (s *Session) handlePacket(pkt []byte) error { case "WIIUSGN:100": s.client = WIIU s.handleWIIUSGN(bf) + case "VITACOGLNK:100": + s.client = VITA + s.handlePSNLink(bf) case "DELETE:100": - loginTokenString := string(bf.ReadNullTerminatedBytes()) + token := string(bf.ReadNullTerminatedBytes()) characterID := int(bf.ReadUint32()) - _ = int(bf.ReadUint32()) // login_token_number - err := s.server.deleteCharacter(characterID, loginTokenString) + tokenID := bf.ReadUint32() + err := s.server.deleteCharacter(characterID, token, tokenID) if err == nil { s.logger.Info("Deleted character", zap.Int("CharacterID", characterID)) s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS @@ -89,45 +93,30 @@ func (s *Session) authenticate(username string, password string) { newCharaReq = true } - var id int - var hash string bf := byteframe.NewByteFrame() - err := s.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", username).Scan(&id, &hash) + uid, err := s.server.validateLogin(username, password) switch { case err == sql.ErrNoRows: s.logger.Info("User not found", zap.String("Username", username)) if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.AutoCreateAccount { - s.logger.Info("Creating user", zap.String("Username", username)) - err = s.server.registerDBAccount(username, password) - if err == nil { - bf.WriteBytes(s.makeSignResponse(id)) + uid, err = s.server.registerDBAccount(username, password) + if err == nil && uid > 0 { + bf.WriteBytes(s.makeSignResponse(uid)) } } else { bf.WriteUint8(uint8(SIGN_EAUTH)) } case err != nil: - bf.WriteUint8(uint8(SIGN_EABORT)) s.logger.Error("Error getting user details", zap.Error(err)) + bf.WriteUint8(uint8(SIGN_EABORT)) default: - if bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil || s.client == VITA || s.client == PS3 || s.client == WIIU { + if uid > 0 { s.logger.Debug("Passwords match!") if newCharaReq { - err = s.server.newUserChara(username) - if err != nil { - s.logger.Error("Error adding new character to user", zap.Error(err)) - bf.WriteUint8(uint8(SIGN_EABORT)) - break - } + _ = s.server.newUserChara(username) } - // TODO: Need to auto delete user tokens after inactivity - // exists, err := s.server.checkToken(id) - // if err != nil { - // s.logger.Info("Error checking for live tokens", zap.Error(err)) - // serverRespBytes = makeSignInFailureResp(SIGN_EABORT) - // break - // } - bf.WriteBytes(s.makeSignResponse(id)) + bf.WriteBytes(s.makeSignResponse(uid)) } else { s.logger.Warn("Incorrect password") bf.WriteUint8(uint8(SIGN_EPASS)) @@ -138,7 +127,7 @@ func (s *Session) authenticate(username string, password string) { fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data())) } - err = s.cryptConn.SendPacket(bf.Data()) + _ = s.cryptConn.SendPacket(bf.Data()) } func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) { @@ -160,20 +149,71 @@ func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { _ = bf.ReadBytes(2) // VITA = 1, PS3 = ! _ = bf.ReadBytes(82) psnUser := string(bf.ReadNullTerminatedBytes()) - var reqUsername string - err := s.server.db.QueryRow(`SELECT username FROM users WHERE psn_id = $1`, psnUser).Scan(&reqUsername) - if err == sql.ErrNoRows { - resp := byteframe.NewByteFrame() - resp.WriteUint8(uint8(SIGN_ECOGLINK)) - s.cryptConn.SendPacket(resp.Data()) + var uid uint32 + err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, psnUser).Scan(&uid) + if err != nil { + if err == sql.ErrNoRows { + s.cryptConn.SendPacket(s.makeSignResponse(0)) + return + } + s.sendCode(SIGN_EABORT) return } - s.authenticate(reqUsername, "") + s.cryptConn.SendPacket(s.makeSignResponse(uid)) +} + +func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) { + _ = bf.ReadNullTerminatedBytes() // Client ID + credentials := strings.Split(stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()), "\n") + token := string(bf.ReadNullTerminatedBytes()) + if s.server.erupeConfig.DevModeOptions.DisableTokenCheck || !s.server.validateToken(token, 0) { + uid, err := s.server.validateLogin(credentials[0], credentials[1]) + if err == nil && uid > 0 { + var psn string + err = s.server.db.QueryRow(`SELECT psn_id FROM sign_sessions WHERE token = $1`, token).Scan(&psn) + if err != nil { + s.sendCode(SIGN_ECOGLINK) + return + } + + var exists int + err = s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, psn).Scan(&exists) + if err != nil { + s.sendCode(SIGN_ECOGLINK) + return + } else if exists > 0 { + s.sendCode(SIGN_EPSI) + return + } + + var currentPSN string + err = s.server.db.QueryRow(`SELECT psn_id FROM users WHERE username = $1`, credentials[0]).Scan(¤tPSN) + if err != nil { + s.sendCode(SIGN_ECOGLINK) + return + } else if psn != currentPSN { + s.sendCode(SIGN_EMBID) + return + } + + _, err = s.server.db.Exec(`UPDATE users SET psn_id = $1 WHERE username = $2`, psn, credentials[0]) + if err == nil { + s.sendCode(SIGN_SUCCESS) + } + } else { + s.sendCode(SIGN_ECOGLINK) + } + + } } func (s *Session) handleDSGN(bf *byteframe.ByteFrame) { - reqUsername := string(bf.ReadNullTerminatedBytes()) - reqPassword := string(bf.ReadNullTerminatedBytes()) + user := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + pass := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) _ = string(bf.ReadNullTerminatedBytes()) // Unk - s.authenticate(reqUsername, reqPassword) + s.authenticate(user, pass) +} + +func (s *Session) sendCode(id RespID) { + s.cryptConn.SendPacket([]byte{byte(id)}) } From a260500bb56142f3f1ff31eb8740169017bd1ab5 Mon Sep 17 00:00:00 2001 From: wish Date: Sun, 30 Apr 2023 13:51:30 +1000 Subject: [PATCH 2/8] further sign server rewrite --- server/signserver/dbutils.go | 39 +++++++++--- server/signserver/dsgn_resp.go | 10 ++- server/signserver/session.go | 109 +++++++++++++-------------------- 3 files changed, 80 insertions(+), 78 deletions(-) diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index d66d85731..0410f351d 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -1,8 +1,10 @@ package signserver import ( + "database/sql" "errors" "erupe-ce/common/mhfcourse" + "erupe-ce/common/token" "strings" "time" @@ -220,11 +222,18 @@ func (s *Server) checkToken(uid uint32) (bool, error) { return false, nil } -func (s *Server) registerToken(uid uint32, token string) (uint32, error) { - var id uint32 - var err error - err = s.db.QueryRow("INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, token).Scan(&id) - return id, err +func (s *Server) registerUidToken(uid uint32) (uint32, string, error) { + token := token.Generate(16) + var tid uint32 + err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, token).Scan(&tid) + return tid, token, err +} + +func (s *Server) registerPsnToken(psn string) (uint32, string, error) { + token := token.Generate(16) + var tid uint32 + err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, token).Scan(&tid) + return tid, token, err } func (s *Server) validateToken(token string, tokenID uint32) bool { @@ -240,16 +249,28 @@ func (s *Server) validateToken(token string, tokenID uint32) bool { return true } -func (s *Server) validateLogin(user string, pass string) (uint32, error) { +func (s *Server) validateLogin(user string, pass string) (uint32, RespID) { var uid uint32 var passDB string err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB) if err != nil { - return 0, err + if err == sql.ErrNoRows { + s.logger.Info("User not found", zap.String("User", user)) + if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.AutoCreateAccount { + uid, err = s.registerDBAccount(user, pass) + if err == nil { + return uid, SIGN_SUCCESS + } else { + return 0, SIGN_EABORT + } + } + return 0, SIGN_EAUTH + } + return 0, SIGN_EABORT } else { if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil { - return uid, nil + return uid, SIGN_SUCCESS } - return 0, nil + return 0, SIGN_EPASS } } diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index f07161e7e..03f9b8aee 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -4,7 +4,6 @@ import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" - "erupe-ce/common/token" "erupe-ce/server/channelserver" "fmt" "go.uber.org/zap" @@ -19,8 +18,13 @@ func (s *Session) makeSignResponse(uid uint32) []byte { } bf := byteframe.NewByteFrame() - sessToken := token.Generate(16) - tokenID, err := s.server.registerToken(uid, sessToken) + var tokenID uint32 + var sessToken string + if uid == 0 && s.psn != "" { + tokenID, sessToken, err = s.server.registerPsnToken(s.psn) + } else { + tokenID, sessToken, err = s.server.registerUidToken(uid) + } if err != nil { bf.WriteUint8(uint8(SIGN_EABORT)) return bf.Data() diff --git a/server/signserver/session.go b/server/signserver/session.go index 3ade1426a..532998054 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -31,6 +31,7 @@ type Session struct { rawConn net.Conn cryptConn *network.CryptConn client client + psn string } func (s *Session) work() { @@ -87,46 +88,24 @@ func (s *Session) handlePacket(pkt []byte) error { func (s *Session) authenticate(username string, password string) { newCharaReq := false - if username[len(username)-1] == 43 { // '+' username = username[:len(username)-1] newCharaReq = true } - bf := byteframe.NewByteFrame() - - uid, err := s.server.validateLogin(username, password) - switch { - case err == sql.ErrNoRows: - s.logger.Info("User not found", zap.String("Username", username)) - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.AutoCreateAccount { - uid, err = s.server.registerDBAccount(username, password) - if err == nil && uid > 0 { - bf.WriteBytes(s.makeSignResponse(uid)) - } - } else { - bf.WriteUint8(uint8(SIGN_EAUTH)) + uid, resp := s.server.validateLogin(username, password) + switch resp { + case SIGN_SUCCESS: + if newCharaReq { + _ = s.server.newUserChara(username) } - case err != nil: - s.logger.Error("Error getting user details", zap.Error(err)) - bf.WriteUint8(uint8(SIGN_EABORT)) + bf.WriteBytes(s.makeSignResponse(uid)) default: - if uid > 0 { - s.logger.Debug("Passwords match!") - if newCharaReq { - _ = s.server.newUserChara(username) - } - bf.WriteBytes(s.makeSignResponse(uid)) - } else { - s.logger.Warn("Incorrect password") - bf.WriteUint8(uint8(SIGN_EPASS)) - } + bf.WriteUint8(uint8(resp)) } - if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages { fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data())) } - _ = s.cryptConn.SendPacket(bf.Data()) } @@ -148,9 +127,9 @@ func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { _ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255 _ = bf.ReadBytes(2) // VITA = 1, PS3 = ! _ = bf.ReadBytes(82) - psnUser := string(bf.ReadNullTerminatedBytes()) + s.psn = string(bf.ReadNullTerminatedBytes()) var uid uint32 - err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, psnUser).Scan(&uid) + err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, s.psn).Scan(&uid) if err != nil { if err == sql.ErrNoRows { s.cryptConn.SendPacket(s.makeSignResponse(0)) @@ -166,45 +145,43 @@ func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) { _ = bf.ReadNullTerminatedBytes() // Client ID credentials := strings.Split(stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()), "\n") token := string(bf.ReadNullTerminatedBytes()) - if s.server.erupeConfig.DevModeOptions.DisableTokenCheck || !s.server.validateToken(token, 0) { - uid, err := s.server.validateLogin(credentials[0], credentials[1]) - if err == nil && uid > 0 { - var psn string - err = s.server.db.QueryRow(`SELECT psn_id FROM sign_sessions WHERE token = $1`, token).Scan(&psn) - if err != nil { - s.sendCode(SIGN_ECOGLINK) - return - } - - var exists int - err = s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, psn).Scan(&exists) - if err != nil { - s.sendCode(SIGN_ECOGLINK) - return - } else if exists > 0 { - s.sendCode(SIGN_EPSI) - return - } - - var currentPSN string - err = s.server.db.QueryRow(`SELECT psn_id FROM users WHERE username = $1`, credentials[0]).Scan(¤tPSN) - if err != nil { - s.sendCode(SIGN_ECOGLINK) - return - } else if psn != currentPSN { - s.sendCode(SIGN_EMBID) - return - } - - _, err = s.server.db.Exec(`UPDATE users SET psn_id = $1 WHERE username = $2`, psn, credentials[0]) - if err == nil { - s.sendCode(SIGN_SUCCESS) - } - } else { + uid, resp := s.server.validateLogin(credentials[0], credentials[1]) + if resp == SIGN_SUCCESS && uid > 0 { + var psn string + err := s.server.db.QueryRow(`SELECT psn_id FROM sign_sessions WHERE token = $1`, token).Scan(&psn) + if err != nil { s.sendCode(SIGN_ECOGLINK) + return } + // Since we check for the psn_id, this will never run + var exists int + err = s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, psn).Scan(&exists) + if err != nil { + s.sendCode(SIGN_ECOGLINK) + return + } else if exists > 0 { + s.sendCode(SIGN_EPSI) + return + } + + var currentPSN string + err = s.server.db.QueryRow(`SELECT COALESCE(psn_id, '') FROM users WHERE username = $1`, credentials[0]).Scan(¤tPSN) + if err != nil { + s.sendCode(SIGN_ECOGLINK) + return + } else if currentPSN != "" { + s.sendCode(SIGN_EMBID) + return + } + + _, err = s.server.db.Exec(`UPDATE users SET psn_id = $1 WHERE username = $2`, psn, credentials[0]) + if err == nil { + s.sendCode(SIGN_SUCCESS) + return + } } + s.sendCode(SIGN_ECOGLINK) } func (s *Session) handleDSGN(bf *byteframe.ByteFrame) { From a3e1207dfd0428ccaf98d9c81b3604f0778a809f Mon Sep 17 00:00:00 2001 From: wish Date: Mon, 24 Jul 2023 23:42:51 +1000 Subject: [PATCH 3/8] fix conflicts --- server/signserver/dbutils.go | 2 +- server/signserver/session.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go index da1e37843..751862a49 100644 --- a/server/signserver/dbutils.go +++ b/server/signserver/dbutils.go @@ -12,7 +12,7 @@ import ( "golang.org/x/crypto/bcrypt" ) -func (s *Server) newUserChara(uid int) error { +func (s *Server) newUserChara(uid uint32) error { var numNewChars int err := s.db.QueryRow("SELECT COUNT(*) FROM characters WHERE user_id = $1 AND is_new_character = true", uid).Scan(&numNewChars) if err != nil { diff --git a/server/signserver/session.go b/server/signserver/session.go index 475203cc6..7a76fb23b 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -97,7 +97,7 @@ func (s *Session) authenticate(username string, password string) { switch resp { case SIGN_SUCCESS: if newCharaReq { - _ = s.server.newUserChara(username) + _ = s.server.newUserChara(uid) } bf.WriteBytes(s.makeSignResponse(uid)) default: From 7edf07d853ad41577fce4c6831470c98dcd6cf5f Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 25 Jul 2023 00:44:19 +1000 Subject: [PATCH 4/8] use universal Sign request names --- server/signserver/session.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/signserver/session.go b/server/signserver/session.go index 7a76fb23b..e2c2475c7 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -53,22 +53,22 @@ func (s *Session) work() { func (s *Session) handlePacket(pkt []byte) error { bf := byteframe.NewByteFrameFromBytes(pkt) reqType := string(bf.ReadNullTerminatedBytes()) - switch reqType { - case "DLTSKEYSIGN:100", "DSGN:100": + switch reqType[:len(reqType)-3] { + case "DLTSKEYSIGN:", "DSGN:": s.handleDSGN(bf) - case "PS3SGN:100": + case "PS3SGN:": s.client = PS3 s.handlePSSGN(bf) - case "VITASGN:100": + case "VITASGN:": s.client = VITA s.handlePSSGN(bf) - case "WIIUSGN:100", "WIIUSGN:000": + case "WIIUSGN:": s.client = WIIU s.handleWIIUSGN(bf) - case "VITACOGLNK:100": + case "VITACOGLNK:": s.client = VITA s.handlePSNLink(bf) - case "DELETE:100": + case "DELETE:": token := string(bf.ReadNullTerminatedBytes()) characterID := int(bf.ReadUint32()) tokenID := bf.ReadUint32() From 2eeab72c1c6d50d53e28ca72c1ee41f1d60664a3 Mon Sep 17 00:00:00 2001 From: wish Date: Tue, 25 Jul 2023 00:45:11 +1000 Subject: [PATCH 5/8] update legacy WIIU code --- server/signserver/session.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/server/signserver/session.go b/server/signserver/session.go index e2c2475c7..f2c99f22e 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -112,23 +112,24 @@ func (s *Session) authenticate(username string, password string) { func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) { _ = bf.ReadBytes(1) wiiuKey := string(bf.ReadBytes(64)) - var reqUsername string - err := s.server.db.QueryRow(`SELECT username FROM users WHERE wiiu_key = $1`, wiiuKey).Scan(&reqUsername) - if err == sql.ErrNoRows { - resp := byteframe.NewByteFrame() - resp.WriteUint8(uint8(SIGN_ECOGLINK)) - s.cryptConn.SendPacket(resp.Data()) + var uid uint32 + err := s.server.db.QueryRow(`SELECT id FROM users WHERE wiiu_key = $1`, wiiuKey).Scan(&uid) + if err != nil { + if err == sql.ErrNoRows { + s.logger.Info("Unlinked Wii U attempted to authenticate", zap.String("Key", wiiuKey)) + s.sendCode(SIGN_ECOGLINK) + return + } + s.sendCode(SIGN_EABORT) return } - s.authenticate(reqUsername, "") + s.cryptConn.SendPacket(s.makeSignResponse(uid)) } func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { // Prevent reading malformed request if len(bf.DataFromCurrent()) < 128 { - resp := byteframe.NewByteFrame() - resp.WriteUint8(uint8(SIGN_EABORT)) - s.cryptConn.SendPacket(resp.Data()) + s.sendCode(SIGN_EABORT) return } _ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255 From 165bdaf760e24234d176d2459030f8a37d925a18 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 2 Aug 2023 21:03:24 +1000 Subject: [PATCH 6/8] prevent link command from duplicating PSN connections --- server/channelserver/handlers_cast_binary.go | 12 +++++++++--- server/channelserver/sys_language.go | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 3e950657f..d93cf1a04 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -89,9 +89,15 @@ func parseChatCommand(s *Session, command string) { if err != nil || n != 1 { sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix)) } else { - _, err = s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, id, s.charID) - if err == nil { - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], id)) + var exists int + s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, id).Scan(&exists) + if exists == 0 { + _, err = s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, id, s.charID) + if err == nil { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], id)) + } + } else { + sendServerChatMessage(s, s.server.dict["commandPSNExists"]) } } } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index f393c5689..418e1cecb 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -22,6 +22,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandTeleportSuccess"] = "%d %dにテレポート" strings["commandPSNError"] = "PSN連携コマンドエラー 例:%s " strings["commandPSNSuccess"] = "PSN「%s」が連携されています" + strings["commandPSNExists"] = "PSNは既存のユーザに接続されています" strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" strings["commandRaviStartSuccess"] = "大討伐を開始します" @@ -72,6 +73,7 @@ func getLangStrings(s *Server) map[string]string { strings["commandTeleportSuccess"] = "Teleporting to %d %d" strings["commandPSNError"] = "Error in command. Format: %s " strings["commandPSNSuccess"] = "Connected PSN ID: %s" + strings["commandPSNExists"] = "PSN ID is connected to another account!" strings["commandRaviNoCommand"] = "No Raviente command specified!" strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" From 6c68b2f3ed3f020033b75feb00a15476f30546fe Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 2 Aug 2023 21:04:08 +1000 Subject: [PATCH 7/8] prevent PSN fake login from attempting to create a character --- server/signserver/dsgn_resp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index d93730c92..77ac6468d 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -14,7 +14,7 @@ import ( func (s *Session) makeSignResponse(uid uint32) []byte { // Get the characters from the DB. chars, err := s.server.getCharactersForUser(uid) - if len(chars) == 0 { + if len(chars) == 0 && uid != 0 { err = s.server.newUserChara(uid) if err == nil { chars, err = s.server.getCharactersForUser(uid) From 86f63d6a388055e6706e1be725b430666c9ac942 Mon Sep 17 00:00:00 2001 From: wish Date: Wed, 9 Aug 2023 22:04:15 +1000 Subject: [PATCH 8/8] properly support PS3 account linking --- server/signserver/session.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/signserver/session.go b/server/signserver/session.go index f2c99f22e..5806f1108 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -65,8 +65,7 @@ func (s *Session) handlePacket(pkt []byte) error { case "WIIUSGN:": s.client = WIIU s.handleWIIUSGN(bf) - case "VITACOGLNK:": - s.client = VITA + case "VITACOGLNK:", "COGLNK:": s.handlePSNLink(bf) case "DELETE:": token := string(bf.ReadNullTerminatedBytes())