unfinished proof of concept

This commit is contained in:
wish
2023-04-30 01:32:38 +10:00
parent cc979dbb41
commit 6601285218
4 changed files with 158 additions and 79 deletions

11
patch-schema/psn-link.sql Normal file
View File

@@ -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;

View File

@@ -1,10 +1,12 @@
package signserver package signserver
import ( import (
"errors"
"erupe-ce/common/mhfcourse" "erupe-ce/common/mhfcourse"
"strings" "strings"
"time" "time"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -41,22 +43,19 @@ func (s *Server) newUserChara(username string) error {
return nil 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 // Create salted hash of user password
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil { 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 { if err != nil {
return err return 0, err
}
var id int
err = s.db.QueryRow("SELECT id FROM users WHERE username = $1", username).Scan(&id)
if err != nil {
return err
} }
// Create a base new character. // 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, user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login) hrp, gr, weapon_type, last_login)
VALUES($1, False, True, '', '', 0, 0, 0, $2)`, VALUES($1, False, True, '', '', 0, 0, 0, $2)`,
id, uid,
uint32(time.Now().Unix()), uint32(time.Now().Unix()),
) )
if err != nil { if err != nil {
return err return 0, err
} }
return nil return uid, nil
} }
type character struct { type character struct {
@@ -87,7 +86,7 @@ type character struct {
LastLogin uint32 `db:"last_login"` 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) 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) 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 { if err != nil {
@@ -96,7 +95,7 @@ func (s *Server) getCharactersForUser(uid int) ([]character, error) {
return characters, nil return characters, nil
} }
func (s *Server) getReturnExpiry(uid int) time.Time { func (s *Server) getReturnExpiry(uid uint32) time.Time {
var returnExpiry, lastLogin time.Time var returnExpiry, lastLogin time.Time
s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid) 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) { if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
@@ -113,16 +112,18 @@ func (s *Server) getReturnExpiry(uid int) time.Time {
return returnExpiry return returnExpiry
} }
func (s *Server) getLastCID(uid int) uint32 { func (s *Server) getLastCID(uid uint32) uint32 {
var lastPlayed uint32 var lastPlayed uint32
_ = s.db.QueryRow("SELECT last_character FROM users WHERE id=$1", uid).Scan(&lastPlayed) _ = s.db.QueryRow("SELECT last_character FROM users WHERE id=$1", uid).Scan(&lastPlayed)
return lastPlayed return lastPlayed
} }
func (s *Server) getUserRights(uid int) uint32 { func (s *Server) getUserRights(uid uint32) uint32 {
rights := uint32(2) var rights uint32
if uid != 0 {
_ = s.db.QueryRow("SELECT rights FROM users WHERE id=$1", uid).Scan(&rights) _ = s.db.QueryRow("SELECT rights FROM users WHERE id=$1", uid).Scan(&rights)
_, rights = mhfcourse.GetCourseStruct(rights) _, rights = mhfcourse.GetCourseStruct(rights)
}
return rights return rights
} }
@@ -189,14 +190,12 @@ func (s *Server) getGuildmatesForCharacters(chars []character) []members {
return guildmates return guildmates
} }
func (s *Server) deleteCharacter(cid int, token string) error { func (s *Server) deleteCharacter(cid int, token string, tokenID uint32) error {
var verify int if !s.validateToken(token, tokenID) {
err := s.db.QueryRow("SELECT count(*) FROM sign_sessions WHERE token = $1", token).Scan(&verify) return errors.New("invalid token")
if err != nil {
return err // Invalid token
} }
var isNew bool 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 { if isNew {
_, err = s.db.Exec("DELETE FROM characters WHERE id = $1", cid) _, err = s.db.Exec("DELETE FROM characters WHERE id = $1", cid)
} else { } else {
@@ -209,7 +208,7 @@ func (s *Server) deleteCharacter(cid int, token string) error {
} }
// Unused // Unused
func (s *Server) checkToken(uid int) (bool, error) { func (s *Server) checkToken(uid uint32) (bool, error) {
var exists int var exists int
err := s.db.QueryRow("SELECT count(*) FROM sign_sessions WHERE user_id = $1", uid).Scan(&exists) err := s.db.QueryRow("SELECT count(*) FROM sign_sessions WHERE user_id = $1", uid).Scan(&exists)
if err != nil { if err != nil {
@@ -221,10 +220,36 @@ func (s *Server) checkToken(uid int) (bool, error) {
return false, nil return false, nil
} }
func (s *Server) registerToken(uid int, token string) error { func (s *Server) registerToken(uid uint32, token string) (uint32, error) {
_, err := s.db.Exec("INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, token) 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 { if err != nil {
return err return 0, err
} else {
if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil {
return uid, nil
}
return 0, nil
} }
return nil
} }

View File

@@ -11,17 +11,20 @@ import (
"strings" "strings"
) )
func (s *Session) makeSignResponse(uid int) []byte { func (s *Session) makeSignResponse(uid uint32) []byte {
// Get the characters from the DB. // Get the characters from the DB.
chars, err := s.server.getCharactersForUser(uid) chars, err := s.server.getCharactersForUser(uid)
if err != nil { if err != nil {
s.logger.Warn("Error getting characters from DB", zap.Error(err)) s.logger.Warn("Error getting characters from DB", zap.Error(err))
} }
sessToken := token.Generate(16)
_ = s.server.registerToken(uid, sessToken)
bf := byteframe.NewByteFrame() 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 bf.WriteUint8(1) // resp_code
if (s.server.erupeConfig.PatchServerManifest != "" && s.server.erupeConfig.PatchServerFile != "") || s.client == PS3 { 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(1) // entrance server count
bf.WriteUint8(uint8(len(chars))) bf.WriteUint8(uint8(len(chars)))
bf.WriteUint32(0xFFFFFFFF) // login_token_number bf.WriteUint32(tokenID)
bf.WriteBytes([]byte(sessToken)) bf.WriteBytes([]byte(sessToken))
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
if s.client == PS3 { if s.client == PS3 {

View File

@@ -3,20 +3,21 @@ package signserver
import ( import (
"database/sql" "database/sql"
"encoding/hex" "encoding/hex"
"erupe-ce/common/stringsupport"
"fmt" "fmt"
"net" "net"
"strings"
"sync" "sync"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
) )
type Client int type client int
const ( const (
PC100 Client = iota PC100 client = iota
VITA VITA
PS3 PS3
WIIU WIIU
@@ -29,7 +30,7 @@ type Session struct {
server *Server server *Server
rawConn net.Conn rawConn net.Conn
cryptConn *network.CryptConn cryptConn *network.CryptConn
client Client client client
} }
func (s *Session) work() { func (s *Session) work() {
@@ -63,11 +64,14 @@ func (s *Session) handlePacket(pkt []byte) error {
case "WIIUSGN:100": case "WIIUSGN:100":
s.client = WIIU s.client = WIIU
s.handleWIIUSGN(bf) s.handleWIIUSGN(bf)
case "VITACOGLNK:100":
s.client = VITA
s.handlePSNLink(bf)
case "DELETE:100": case "DELETE:100":
loginTokenString := string(bf.ReadNullTerminatedBytes()) token := string(bf.ReadNullTerminatedBytes())
characterID := int(bf.ReadUint32()) characterID := int(bf.ReadUint32())
_ = int(bf.ReadUint32()) // login_token_number tokenID := bf.ReadUint32()
err := s.server.deleteCharacter(characterID, loginTokenString) err := s.server.deleteCharacter(characterID, token, tokenID)
if err == nil { if err == nil {
s.logger.Info("Deleted character", zap.Int("CharacterID", characterID)) s.logger.Info("Deleted character", zap.Int("CharacterID", characterID))
s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS
@@ -89,45 +93,30 @@ func (s *Session) authenticate(username string, password string) {
newCharaReq = true newCharaReq = true
} }
var id int
var hash string
bf := byteframe.NewByteFrame() 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 { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
s.logger.Info("User not found", zap.String("Username", username)) s.logger.Info("User not found", zap.String("Username", username))
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.AutoCreateAccount { if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.AutoCreateAccount {
s.logger.Info("Creating user", zap.String("Username", username)) uid, err = s.server.registerDBAccount(username, password)
err = s.server.registerDBAccount(username, password) if err == nil && uid > 0 {
if err == nil { bf.WriteBytes(s.makeSignResponse(uid))
bf.WriteBytes(s.makeSignResponse(id))
} }
} else { } else {
bf.WriteUint8(uint8(SIGN_EAUTH)) bf.WriteUint8(uint8(SIGN_EAUTH))
} }
case err != nil: case err != nil:
bf.WriteUint8(uint8(SIGN_EABORT))
s.logger.Error("Error getting user details", zap.Error(err)) s.logger.Error("Error getting user details", zap.Error(err))
bf.WriteUint8(uint8(SIGN_EABORT))
default: 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!") s.logger.Debug("Passwords match!")
if newCharaReq { if newCharaReq {
err = s.server.newUserChara(username) _ = 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
} }
} bf.WriteBytes(s.makeSignResponse(uid))
// 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))
} else { } else {
s.logger.Warn("Incorrect password") s.logger.Warn("Incorrect password")
bf.WriteUint8(uint8(SIGN_EPASS)) 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())) 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) { 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(2) // VITA = 1, PS3 = !
_ = bf.ReadBytes(82) _ = bf.ReadBytes(82)
psnUser := string(bf.ReadNullTerminatedBytes()) psnUser := string(bf.ReadNullTerminatedBytes())
var reqUsername string var uid uint32
err := s.server.db.QueryRow(`SELECT username FROM users WHERE psn_id = $1`, psnUser).Scan(&reqUsername) err := s.server.db.QueryRow(`SELECT id FROM users WHERE psn_id = $1`, psnUser).Scan(&uid)
if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
resp := byteframe.NewByteFrame() s.cryptConn.SendPacket(s.makeSignResponse(0))
resp.WriteUint8(uint8(SIGN_ECOGLINK))
s.cryptConn.SendPacket(resp.Data())
return return
} }
s.authenticate(reqUsername, "") s.sendCode(SIGN_EABORT)
return
}
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(&currentPSN)
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) { func (s *Session) handleDSGN(bf *byteframe.ByteFrame) {
reqUsername := string(bf.ReadNullTerminatedBytes()) user := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
reqPassword := string(bf.ReadNullTerminatedBytes()) pass := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
_ = string(bf.ReadNullTerminatedBytes()) // Unk _ = string(bf.ReadNullTerminatedBytes()) // Unk
s.authenticate(reqUsername, reqPassword) s.authenticate(user, pass)
}
func (s *Session) sendCode(id RespID) {
s.cryptConn.SendPacket([]byte{byte(id)})
} }