partially implement tokened sessions

This commit is contained in:
wish
2022-07-16 15:46:38 +10:00
parent f28c9fa636
commit 5f833f93a0
10 changed files with 124 additions and 63 deletions

View File

@@ -87,6 +87,10 @@ func main() {
} }
logger.Info("Connected to database") logger.Info("Connected to database")
// Clear existing tokens
_ = db.MustExec("DELETE FROM sign_sessions")
_ = db.MustExec("DELETE FROM servers")
// Clean the DB if the option is on. // Clean the DB if the option is on.
if erupeConfig.DevMode && erupeConfig.DevModeOptions.CleanDB { if erupeConfig.DevMode && erupeConfig.DevModeOptions.CleanDB {
logger.Info("Cleaning DB") logger.Info("Cleaning DB")
@@ -137,26 +141,37 @@ func main() {
logger.Info("Started sign server") logger.Info("Started sign server")
var channels []channelserver.Server var channels []channelserver.Server
channelQuery := ""
si := 0
ci := 0
count := 1 count := 1
for _, ee := range erupeConfig.Entrance.Entries { for _, ee := range erupeConfig.Entrance.Entries {
for _, ce := range ee.Channels { for _, ce := range ee.Channels {
sid := (4096 + si * 256) + (16 + ci)
c := *channelserver.NewServer(&channelserver.Config{ c := *channelserver.NewServer(&channelserver.Config{
Logger: logger.Named("channel-"+fmt.Sprint(count)), ID: uint16(sid),
ErupeConfig: erupeConfig, Logger: logger.Named("channel-"+fmt.Sprint(count)),
DB: db, ErupeConfig: erupeConfig,
DiscordBot: discordBot, DB: db,
DiscordBot: discordBot,
}) })
err = c.Start(int(ce.Port)) err = c.Start(int(ce.Port))
if err != nil { if err != nil {
logger.Fatal("Failed to start channel", zap.Error(err)) logger.Fatal("Failed to start channel", zap.Error(err))
} else { } else {
channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, 0, 0);", sid)
channels = append(channels, c)
logger.Info(fmt.Sprintf("Started channel server %d on port %d", count, ce.Port)) logger.Info(fmt.Sprintf("Started channel server %d on port %d", count, ce.Port))
ci++
count++
} }
channels = append(channels, c)
count++
} }
ci = 0
si++
} }
_ = db.MustExec(channelQuery)
// Wait for exit or interrupt with ctrl+C. // Wait for exit or interrupt with ctrl+C.
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)

View File

@@ -36,8 +36,7 @@ func (m *MsgSysLogin) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContex
m.CharID1 = bf.ReadUint32() m.CharID1 = bf.ReadUint32()
m.HardcodedZero1 = bf.ReadUint16() m.HardcodedZero1 = bf.ReadUint16()
m.LoginTokenStringLength = bf.ReadUint16() m.LoginTokenStringLength = bf.ReadUint16()
m.LoginTokenString = string(bf.ReadBytes(17)) // TODO(Andoryuuta): What encoding is this string? m.LoginTokenString = string(bf.ReadNullTerminatedBytes())
return nil return nil
} }

View File

@@ -155,15 +155,19 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.Name = name s.Name = name
s.charID = pkt.CharID0 s.charID = pkt.CharID0
s.rights = rights s.rights = rights
s.token = pkt.LoginTokenString
s.Unlock() s.Unlock()
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
if s.server.erupeConfig.DevModeOptions.ServerName != "" { _, err = s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
_, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_name=$2", uint32(len(s.server.sessions)), s.server.erupeConfig.DevModeOptions.ServerName) if err != nil {
if err != nil { panic(err)
panic(err) }
}
_, err = s.server.db.Exec("UPDATE sign_sessions SET server_id=$1 WHERE token=$2", s.server.ID, s.token)
if err != nil {
panic(err)
} }
_, err = s.server.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", Time_Current().Unix(), s.charID) _, err = s.server.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", Time_Current().Unix(), s.charID)
@@ -194,11 +198,14 @@ func logoutPlayer(s *Session) {
delete(s.server.sessions, s.rawConn) delete(s.server.sessions, s.rawConn)
s.rawConn.Close() s.rawConn.Close()
if s.server.erupeConfig.DevModeOptions.ServerName != "" { _, err := s.server.db.Exec("UPDATE sign_sessions SET server_id=NULL WHERE token=$1", s.token)
_, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_name=$2", uint32(len(s.server.sessions)), s.server.erupeConfig.DevModeOptions.ServerName) if err != nil {
if err != nil { panic(err)
panic(err) }
}
_, err = s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
if err != nil {
panic(err)
} }
s.server.Lock() s.server.Lock()
@@ -213,7 +220,7 @@ func logoutPlayer(s *Session) {
removeSessionFromStage(s) removeSessionFromStage(s)
var timePlayed int var timePlayed int
err := s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed) _ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed)
timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed

View File

@@ -33,6 +33,7 @@ const (
// Config struct allows configuring the server. // Config struct allows configuring the server.
type Config struct { type Config struct {
ID uint16
Logger *zap.Logger Logger *zap.Logger
DB *sqlx.DB DB *sqlx.DB
DiscordBot *discordbot.DiscordBot DiscordBot *discordbot.DiscordBot
@@ -50,6 +51,7 @@ type userBinaryPartID struct {
// Server is a MHF channel server. // Server is a MHF channel server.
type Server struct { type Server struct {
sync.Mutex sync.Mutex
ID uint16
logger *zap.Logger logger *zap.Logger
db *sqlx.DB db *sqlx.DB
erupeConfig *config.Config erupeConfig *config.Config
@@ -137,6 +139,7 @@ func NewRaviente() *Raviente {
// NewServer creates a new Server type. // NewServer creates a new Server type.
func NewServer(config *Config) *Server { func NewServer(config *Config) *Server {
s := &Server { s := &Server {
ID: config.ID,
logger: config.Logger, logger: config.Logger,
db: config.DB, db: config.DB,
erupeConfig: config.ErupeConfig, erupeConfig: config.ErupeConfig,

View File

@@ -34,6 +34,7 @@ type Session struct {
logKey []byte logKey []byte
sessionStart int64 sessionStart int64
rights uint32 rights uint32
token string
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet. semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.

View File

@@ -11,17 +11,7 @@ import (
"erupe-ce/server/channelserver" "erupe-ce/server/channelserver"
) )
func paddedString(x string, size uint) []byte {
out := make([]byte, size)
copy(out, x)
// Null terminate it.
out[len(out)-1] = 0
return out
}
// Server Entries // Server Entries
var name string
var season uint8 var season uint8
// Server Channels // Server Channels
@@ -31,17 +21,10 @@ func encodeServerInfo(serverInfos []config.EntranceServerInfo, s *Server) []byte
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
for serverIdx, si := range serverInfos { for serverIdx, si := range serverInfos {
err := s.db.QueryRow("SELECT server_name FROM servers WHERE server_name=$1", si.Name).Scan(&name) sid := (4096 + serverIdx * 256) + 16
err := s.db.QueryRow("SELECT season FROM servers WHERE server_id=$1", sid).Scan(&season)
if err != nil { if err != nil {
_, err := s.db.Exec("INSERT INTO servers (server_name, season, current_players, event_id, event_expiration) VALUES ($1, $2, 0, 0, 0)", si.Name, si.Season) panic(err)
if err != nil {
panic(err)
}
} else {
err := s.db.QueryRow("SELECT season FROM servers WHERE server_name=$1", si.Name).Scan(&season)
if err != nil {
panic(err)
}
} }
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4())) bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
bf.WriteUint16(16 + uint16(serverIdx)) bf.WriteUint16(16 + uint16(serverIdx))
@@ -50,25 +33,18 @@ func encodeServerInfo(serverInfos []config.EntranceServerInfo, s *Server) []byte
bf.WriteUint8(si.Type) bf.WriteUint8(si.Type)
bf.WriteUint8(season) bf.WriteUint8(season)
bf.WriteUint8(si.Recommended) bf.WriteUint8(si.Recommended)
sjisName, err := stringsupport.ConvertUTF8ToShiftJIS(si.Name) combined := append([]byte{0x00}, stringsupport.UTF8ToSJIS(si.Name)...)
if err != nil {
panic(err)
}
sjisDesc, err := stringsupport.ConvertUTF8ToShiftJIS(si.Description)
if err != nil {
panic(err)
}
combined := append([]byte{0x00}, sjisName...)
combined = append(combined, []byte{0x00}...) combined = append(combined, []byte{0x00}...)
combined = append(combined, sjisDesc...) combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
bf.WriteBytes(paddedString(string(combined), 66)) bf.WriteBytes(stringsupport.PaddedString(string(combined), 66, false))
bf.WriteUint32(si.AllowedClientFlags) bf.WriteUint32(si.AllowedClientFlags)
for channelIdx, ci := range si.Channels { for channelIdx, ci := range si.Channels {
sid = (4096 + serverIdx * 256) + (16 + channelIdx)
bf.WriteUint16(ci.Port) bf.WriteUint16(ci.Port)
bf.WriteUint16(16 + uint16(channelIdx)) bf.WriteUint16(16 + uint16(channelIdx))
bf.WriteUint16(ci.MaxPlayers) bf.WriteUint16(ci.MaxPlayers)
err := s.db.QueryRow("SELECT current_players FROM servers WHERE server_name=$1", si.Name).Scan(&currentplayers) err := s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(&currentplayers)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -87,9 +87,43 @@ type character struct {
func (s *Server) getCharactersForUser(uid int) ([]character, error) { func (s *Server) getCharactersForUser(uid int) ([]character, error) {
characters := []character{} characters := []character{}
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", 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", uid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return characters, nil return characters, nil
} }
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
}
_, err = s.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", cid)
if err != nil {
return err
}
return nil
}
// Unused
func (s *Server) checkToken(uid int) (bool, error) {
var exists int
err := s.db.QueryRow("SELECT count(*) FROM sign_sessions WHERE user_id = $1", uid).Scan(&exists)
if err != nil {
return false, err
}
if exists > 0 {
return true, nil
}
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
}

View File

@@ -36,7 +36,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
token := randSeq(16) token := randSeq(16)
// TODO: register token to db, users table s.server.registerToken(uid, token)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
@@ -45,7 +45,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint8(1) // entrance server count bf.WriteUint8(1) // entrance server count
bf.WriteUint8(uint8(len(chars))) // character count bf.WriteUint8(uint8(len(chars))) // character count
bf.WriteUint32(0xFFFFFFFF) // login_token_number bf.WriteUint32(0xFFFFFFFF) // login_token_number
bf.WriteBytes(stringsupport.PaddedString(token, 16, false)) // login_token (16 byte padded string) bf.WriteBytes([]byte(token)) // login_token
bf.WriteUint32(uint32(time.Now().Unix())) // unk timestamp bf.WriteUint32(uint32(time.Now().Unix())) // unk timestamp
ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.HostIP, s.server.erupeConfig.Entrance.Port), false) ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.HostIP, s.server.erupeConfig.Entrance.Port), false)

View File

@@ -60,11 +60,9 @@ func (s *Session) handlePacket(pkt []byte) error {
} }
case "DELETE:100": case "DELETE:100":
loginTokenString := string(bf.ReadNullTerminatedBytes()) loginTokenString := string(bf.ReadNullTerminatedBytes())
_ = loginTokenString characterID := int(bf.ReadUint32())
characterID := bf.ReadUint32() s.server.deleteCharacter(characterID, loginTokenString)
sugar.Infof("Deleted character ID: %v\n", characterID)
sugar.Infof("Got delete request for character ID: %v\n", characterID)
sugar.Infof("remaining unknown data:\n%s\n", hex.Dump(bf.DataFromCurrent()))
default: default:
sugar.Infof("Got unknown request type %s, data:\n%s\n", reqType, hex.Dump(bf.DataFromCurrent())) sugar.Infof("Got unknown request type %s, data:\n%s\n", reqType, hex.Dump(bf.DataFromCurrent()))
} }
@@ -92,7 +90,6 @@ func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error {
newCharaReq = true newCharaReq = true
} }
// TODO(Andoryuuta): remove plaintext password storage if this ever becomes more than a toy project.
var ( var (
id int id int
password string password string
@@ -138,6 +135,13 @@ func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error {
break break
} }
} }
// 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
// }
serverRespBytes = s.makeSignInResp(id) serverRespBytes = s.makeSignInResp(id)
} else { } else {
s.logger.Info("Passwords don't match!") s.logger.Info("Passwords don't match!")

22
Erupe/tokensessions.sql Normal file
View File

@@ -0,0 +1,22 @@
BEGIN;
DROP TABLE IF EXISTS public.sign_sessions;
CREATE TABLE IF NOT EXISTS public.sign_sessions
(
user_id int NOT NULL,
token varchar(16) NOT NULL,
server_id integer
);
DROP TABLE IF EXISTS public.servers;
CREATE TABLE IF NOT EXISTS public.servers
(
server_id int NOT NULL,
season int NOT NULL,
current_players int NOT NULL
);
ALTER TABLE IF EXISTS public.characters
ADD COLUMN deleted boolean NOT NULL DEFAULT false;
END;