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)}) }