Files
Erupe/server/signserver/session.go
Houmgaor f640cfee27 fix: log SJIS decoding errors instead of silently discarding them
Add SJISToUTF8Lossy() that wraps SJISToUTF8() and logs decode errors at
slog.Debug level. Replace all 31 call sites across 17 files that previously
discarded the error with `_, _ =`. This makes garbled text from malformed
SJIS client data debuggable without adding noise at default log levels.
2026-02-22 17:01:22 +01:00

207 lines
5.0 KiB
Go

package signserver
import (
"database/sql"
"encoding/hex"
"erupe-ce/common/stringsupport"
"net"
"strings"
"sync"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"go.uber.org/zap"
)
type client int
const (
PC100 client = iota
VITA
PS3
PS4
WIIU
)
// Session holds state for the sign server connection.
type Session struct {
sync.Mutex
logger *zap.Logger
server *Server
rawConn net.Conn
cryptConn *network.CryptConn
client client
psn string
}
func (s *Session) work() {
pkt, err := s.cryptConn.ReadPacket()
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
s.logger.Debug("Inbound packet", zap.Int("bytes", len(pkt)), zap.String("data", hex.Dump(pkt)))
}
if err != nil {
return
}
err = s.handlePacket(pkt)
if err != nil {
return
}
}
func (s *Session) handlePacket(pkt []byte) error {
bf := byteframe.NewByteFrameFromBytes(pkt)
reqType := string(bf.ReadNullTerminatedBytes())
switch reqType[:len(reqType)-3] {
case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
s.handleDSGN(bf)
case "PS4SGN:":
s.client = PS4
s.handlePSSGN(bf)
case "PS3SGN:":
s.client = PS3
s.handlePSSGN(bf)
case "VITASGN:":
s.client = VITA
s.handlePSSGN(bf)
case "WIIUSGN:":
s.client = WIIU
s.handleWIIUSGN(bf)
case "VITACOGLNK:", "COGLNK:":
s.handlePSNLink(bf)
case "DELETE:":
token := string(bf.ReadNullTerminatedBytes())
characterID := int(bf.ReadUint32())
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
}
default:
s.logger.Warn("Unknown request", zap.String("reqType", reqType))
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
s.logger.Debug("Unknown inbound packet", zap.Int("bytes", len(pkt)), zap.String("data", hex.Dump(pkt)))
}
}
return nil
}
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, resp := s.server.validateLogin(username, password)
switch resp {
case SIGN_SUCCESS:
if newCharaReq {
_ = s.server.newUserChara(uid)
}
bf.WriteBytes(s.makeSignResponse(uid))
default:
bf.WriteUint8(uint8(resp))
}
if s.server.erupeConfig.DebugOptions.LogOutboundMessages {
s.logger.Debug("Outbound packet", zap.Int("bytes", len(bf.Data())), zap.String("data", hex.Dump(bf.Data())))
}
_ = s.cryptConn.SendPacket(bf.Data())
}
func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) {
_ = bf.ReadBytes(1)
wiiuKey := string(bf.ReadBytes(64))
uid, err := s.server.userRepo.GetByWiiUKey(wiiuKey)
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.cryptConn.SendPacket(s.makeSignResponse(uid))
}
func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) {
// Prevent reading malformed request
if s.client != PS4 {
if len(bf.DataFromCurrent()) < 128 {
s.sendCode(SIGN_EABORT)
return
}
_ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255
_ = bf.ReadBytes(2) // VITA = 1, PS3 = !
_ = bf.ReadBytes(82)
}
s.psn = string(bf.ReadNullTerminatedBytes())
uid, err := s.server.userRepo.GetByPSNID(s.psn)
if err != nil {
if err == sql.ErrNoRows {
_ = s.cryptConn.SendPacket(s.makeSignResponse(0))
return
}
s.sendCode(SIGN_EABORT)
return
}
_ = s.cryptConn.SendPacket(s.makeSignResponse(uid))
}
func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) {
_ = bf.ReadNullTerminatedBytes() // Client ID
credStr := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
credentials := strings.Split(credStr, "\n")
tok := string(bf.ReadNullTerminatedBytes())
uid, resp := s.server.validateLogin(credentials[0], credentials[1])
if resp == SIGN_SUCCESS && uid > 0 {
psn, err := s.server.sessionRepo.GetPSNIDByToken(tok)
if err != nil {
s.sendCode(SIGN_ECOGLINK)
return
}
// Since we check for the psn_id, this will never run
exists, err := s.server.userRepo.CountByPSNID(psn)
if err != nil {
s.sendCode(SIGN_ECOGLINK)
return
} else if exists > 0 {
s.sendCode(SIGN_EPSI)
return
}
currentPSN, err := s.server.userRepo.GetPSNIDForUsername(credentials[0])
if err != nil {
s.sendCode(SIGN_ECOGLINK)
return
} else if currentPSN != "" {
s.sendCode(SIGN_EMBID)
return
}
err = s.server.userRepo.SetPSNID(credentials[0], psn)
if err == nil {
s.sendCode(SIGN_SUCCESS)
return
}
}
s.sendCode(SIGN_ECOGLINK)
}
func (s *Session) handleDSGN(bf *byteframe.ByteFrame) {
user := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
pass := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
_ = string(bf.ReadNullTerminatedBytes()) // Unk
s.authenticate(user, pass)
}
func (s *Session) sendCode(id RespID) {
_ = s.cryptConn.SendPacket([]byte{byte(id)})
}