557 lines
12 KiB
Go
557 lines
12 KiB
Go
package proxy
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/HimbeerserverDE/srp"
|
|
"github.com/anon55555/mt"
|
|
"github.com/anon55555/mt/rudp"
|
|
)
|
|
|
|
// A ServerConn is a connection to a minetest server.
|
|
type ServerConn struct {
|
|
mt.Peer
|
|
clt *ClientConn
|
|
mu sync.RWMutex
|
|
|
|
logger *log.Logger
|
|
|
|
cstate clientState
|
|
cstateMu sync.RWMutex
|
|
name string
|
|
initCh chan struct{}
|
|
|
|
auth struct {
|
|
method mt.AuthMethods
|
|
salt, srpA, a, srpK []byte
|
|
}
|
|
|
|
inv mt.Inv
|
|
detachedInvs []string
|
|
|
|
aos map[mt.AOID]struct{}
|
|
particleSpawners map[mt.ParticleSpawnerID]struct{}
|
|
|
|
sounds map[mt.SoundID]struct{}
|
|
|
|
huds map[mt.HUDID]mt.HUDType
|
|
|
|
playerList map[string]struct{}
|
|
}
|
|
|
|
func (sc *ServerConn) client() *ClientConn {
|
|
sc.mu.RLock()
|
|
defer sc.mu.RUnlock()
|
|
|
|
return sc.clt
|
|
}
|
|
|
|
func (sc *ServerConn) state() clientState {
|
|
sc.cstateMu.RLock()
|
|
defer sc.cstateMu.RUnlock()
|
|
|
|
return sc.cstate
|
|
}
|
|
|
|
func (sc *ServerConn) setState(state clientState) {
|
|
sc.cstateMu.Lock()
|
|
defer sc.cstateMu.Unlock()
|
|
|
|
sc.cstate = state
|
|
}
|
|
|
|
// Init returns a channel that is closed
|
|
// when the ServerConn enters the csActive state.
|
|
func (sc *ServerConn) Init() <-chan struct{} { return sc.initCh }
|
|
|
|
// Log logs an interaction with the ServerConn.
|
|
// dir indicates the direction of the interaction.
|
|
func (sc *ServerConn) Log(dir string, v ...interface{}) {
|
|
sc.logger.Println(append([]interface{}{dir}, v...)...)
|
|
}
|
|
|
|
func handleSrv(sc *ServerConn) {
|
|
go func() {
|
|
init := make(chan struct{})
|
|
defer close(init)
|
|
|
|
go func(init <-chan struct{}) {
|
|
select {
|
|
case <-init:
|
|
case <-time.After(10 * time.Second):
|
|
sc.Log("->", "timeout")
|
|
sc.Close()
|
|
}
|
|
}(init)
|
|
|
|
for sc.state() == csCreated && sc.client() != nil {
|
|
sc.SendCmd(&mt.ToSrvInit{
|
|
SerializeVer: latestSerializeVer,
|
|
MinProtoVer: latestProtoVer,
|
|
MaxProtoVer: latestProtoVer,
|
|
PlayerName: sc.client().Name(),
|
|
})
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
pkt, err := sc.Recv()
|
|
if err != nil {
|
|
if errors.Is(err, net.ErrClosed) {
|
|
if errors.Is(sc.WhyClosed(), rudp.ErrTimedOut) {
|
|
sc.Log("<->", "timeout")
|
|
} else {
|
|
sc.Log("<->", "disconnect")
|
|
}
|
|
|
|
if sc.client() != nil {
|
|
ack, _ := sc.client().SendCmd(&mt.ToCltDisco{
|
|
Reason: mt.Custom,
|
|
Custom: "Server connection closed unexpectedly.",
|
|
})
|
|
|
|
select {
|
|
case <-sc.client().Closed():
|
|
case <-ack:
|
|
sc.client().Close()
|
|
|
|
sc.client().mu.Lock()
|
|
sc.client().srv = nil
|
|
sc.client().mu.Unlock()
|
|
|
|
sc.mu.Lock()
|
|
sc.clt = nil
|
|
sc.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
sc.Log("<-", err)
|
|
continue
|
|
}
|
|
|
|
clt := sc.client()
|
|
if clt == nil {
|
|
sc.Log("<-", "no client")
|
|
continue
|
|
}
|
|
|
|
switch cmd := pkt.Cmd.(type) {
|
|
case *mt.ToCltHello:
|
|
if sc.auth.method != 0 {
|
|
sc.Log("<-", "unexpected authentication")
|
|
sc.Close()
|
|
break
|
|
}
|
|
|
|
sc.setState(sc.state() + 1)
|
|
if cmd.AuthMethods&mt.FirstSRP != 0 {
|
|
sc.auth.method = mt.FirstSRP
|
|
} else {
|
|
sc.auth.method = mt.SRP
|
|
}
|
|
|
|
if cmd.SerializeVer != latestSerializeVer {
|
|
sc.Log("<-", "invalid serializeVer")
|
|
break
|
|
}
|
|
|
|
switch sc.auth.method {
|
|
case mt.SRP:
|
|
sc.auth.srpA, sc.auth.a, err = srp.InitiateHandshake()
|
|
if err != nil {
|
|
sc.Log("->", err)
|
|
break
|
|
}
|
|
|
|
sc.SendCmd(&mt.ToSrvSRPBytesA{
|
|
A: sc.auth.srpA,
|
|
NoSHA1: true,
|
|
})
|
|
case mt.FirstSRP:
|
|
salt, verifier, err := srp.NewClient([]byte(clt.name), []byte{})
|
|
if err != nil {
|
|
sc.Log("->", err)
|
|
break
|
|
}
|
|
|
|
sc.SendCmd(&mt.ToSrvFirstSRP{
|
|
Salt: salt,
|
|
Verifier: verifier,
|
|
EmptyPasswd: true,
|
|
})
|
|
default:
|
|
sc.Log("<->", "invalid auth method")
|
|
sc.Close()
|
|
}
|
|
case *mt.ToCltSRPBytesSaltB:
|
|
if sc.auth.method != mt.SRP {
|
|
sc.Log("<-", "multiple authentication attempts")
|
|
break
|
|
}
|
|
|
|
sc.auth.srpK, err = srp.CompleteHandshake(sc.auth.srpA, sc.auth.a, []byte(clt.name), []byte{}, cmd.Salt, cmd.B)
|
|
if err != nil {
|
|
sc.Log("->", err)
|
|
break
|
|
}
|
|
|
|
M := srp.ClientProof([]byte(clt.name), cmd.Salt, sc.auth.srpA, cmd.B, sc.auth.srpK)
|
|
if M == nil {
|
|
sc.Log("<-", "SRP safety check fail")
|
|
break
|
|
}
|
|
|
|
sc.SendCmd(&mt.ToSrvSRPBytesM{
|
|
M: M,
|
|
})
|
|
case *mt.ToCltDisco:
|
|
sc.Log("<-", "deny access", cmd)
|
|
ack, _ := clt.SendCmd(cmd)
|
|
|
|
select {
|
|
case <-clt.Closed():
|
|
case <-ack:
|
|
clt.Close()
|
|
|
|
sc.mu.Lock()
|
|
sc.clt = nil
|
|
sc.mu.Unlock()
|
|
}
|
|
case *mt.ToCltAcceptAuth:
|
|
sc.auth = struct {
|
|
method mt.AuthMethods
|
|
salt, srpA, a, srpK []byte
|
|
}{}
|
|
sc.SendCmd(&mt.ToSrvInit2{Lang: clt.lang})
|
|
case *mt.ToCltDenySudoMode:
|
|
sc.Log("<-", "deny sudo")
|
|
case *mt.ToCltAcceptSudoMode:
|
|
sc.Log("<-", "accept sudo")
|
|
sc.setState(sc.state() + 1)
|
|
case *mt.ToCltAnnounceMedia:
|
|
sc.SendCmd(&mt.ToSrvReqMedia{})
|
|
|
|
sc.SendCmd(&mt.ToSrvCltReady{
|
|
Major: clt.major,
|
|
Minor: clt.minor,
|
|
Patch: clt.patch,
|
|
Reserved: clt.reservedVer,
|
|
Version: clt.versionStr,
|
|
Formspec: clt.formspecVer,
|
|
})
|
|
|
|
sc.Log("<->", "handshake completed")
|
|
sc.setState(sc.state() + 1)
|
|
close(sc.initCh)
|
|
case *mt.ToCltInv:
|
|
var oldInv mt.Inv
|
|
copy(oldInv, sc.inv)
|
|
sc.inv.Deserialize(strings.NewReader(cmd.Inv))
|
|
sc.prependInv(sc.inv)
|
|
|
|
handStack := mt.Stack{
|
|
Item: mt.Item{
|
|
Name: sc.name + "_hand",
|
|
},
|
|
Count: 1,
|
|
}
|
|
|
|
hand := sc.inv.List("hand")
|
|
if hand == nil {
|
|
sc.inv = append(sc.inv, mt.NamedInvList{
|
|
Name: "hand",
|
|
InvList: mt.InvList{
|
|
Width: 0,
|
|
Stacks: []mt.Stack{handStack},
|
|
},
|
|
})
|
|
} else if len(hand.Stacks) == 0 {
|
|
hand.Width = 0
|
|
hand.Stacks = []mt.Stack{handStack}
|
|
}
|
|
|
|
b := &strings.Builder{}
|
|
sc.inv.SerializeKeep(b, oldInv)
|
|
|
|
clt.SendCmd(&mt.ToCltInv{Inv: b.String()})
|
|
case *mt.ToCltAOMsgs:
|
|
for k := range cmd.Msgs {
|
|
sc.swapAOID(&cmd.Msgs[k].ID)
|
|
sc.handleAOMsg(cmd.Msgs[k].Msg)
|
|
}
|
|
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltAORmAdd:
|
|
resp := &mt.ToCltAORmAdd{}
|
|
|
|
for _, ao := range cmd.Remove {
|
|
delete(sc.aos, ao)
|
|
resp.Remove = append(resp.Remove, ao)
|
|
}
|
|
|
|
for _, ao := range cmd.Add {
|
|
if ao.InitData.Name == clt.name {
|
|
clt.currentCAO = ao.ID
|
|
|
|
if clt.playerCAO == 0 {
|
|
clt.playerCAO = ao.ID
|
|
for _, msg := range ao.InitData.Msgs {
|
|
sc.handleAOMsg(msg)
|
|
}
|
|
|
|
resp.Add = append(resp.Add, ao)
|
|
} else {
|
|
var msgs []mt.IDAOMsg
|
|
for _, msg := range ao.InitData.Msgs {
|
|
msgs = append(msgs, mt.IDAOMsg{
|
|
ID: ao.ID,
|
|
Msg: msg,
|
|
})
|
|
}
|
|
|
|
clt.SendCmd(&mt.ToCltAOMsgs{Msgs: msgs})
|
|
}
|
|
} else {
|
|
sc.swapAOID(&ao.ID)
|
|
for _, msg := range ao.InitData.Msgs {
|
|
sc.handleAOMsg(msg)
|
|
}
|
|
|
|
resp.Add = append(resp.Add, ao)
|
|
sc.aos[ao.ID] = struct{}{}
|
|
}
|
|
}
|
|
|
|
clt.SendCmd(resp)
|
|
case *mt.ToCltCSMRestrictionFlags:
|
|
if !Conf().DropCSMRF {
|
|
cmd.Flags &= ^mt.NoCSMs
|
|
clt.SendCmd(cmd)
|
|
}
|
|
case *mt.ToCltDetachedInv:
|
|
var inv mt.Inv
|
|
inv.Deserialize(strings.NewReader(cmd.Inv))
|
|
sc.prependInv(inv)
|
|
|
|
b := &strings.Builder{}
|
|
inv.Serialize(b)
|
|
|
|
if cmd.Keep {
|
|
sc.detachedInvs = append(sc.detachedInvs, cmd.Name)
|
|
} else {
|
|
for i, name := range sc.detachedInvs {
|
|
if name == cmd.Name {
|
|
sc.detachedInvs = append(sc.detachedInvs[:i], sc.detachedInvs[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
clt.SendCmd(&mt.ToCltDetachedInv{
|
|
Name: cmd.Name,
|
|
Keep: cmd.Keep,
|
|
Len: cmd.Len,
|
|
Inv: b.String(),
|
|
})
|
|
case *mt.ToCltMediaPush:
|
|
var exit bool
|
|
for _, f := range clt.media {
|
|
if f.name == cmd.Filename {
|
|
exit = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if exit {
|
|
break
|
|
}
|
|
|
|
prepend(sc.name, &cmd.Filename)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltSkyParams:
|
|
for i := range cmd.Textures {
|
|
prependTexture(sc.name, &cmd.Textures[i])
|
|
}
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltSunParams:
|
|
prependTexture(sc.name, &cmd.Texture)
|
|
prependTexture(sc.name, &cmd.ToneMap)
|
|
prependTexture(sc.name, &cmd.Rise)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltMoonParams:
|
|
prependTexture(sc.name, &cmd.Texture)
|
|
prependTexture(sc.name, &cmd.ToneMap)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltSetHotbarParam:
|
|
prependTexture(sc.name, &cmd.Img)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltUpdatePlayerList:
|
|
if !clt.playerListInit {
|
|
clt.playerListInit = true
|
|
} else if cmd.Type == mt.InitPlayers {
|
|
cmd.Type = mt.AddPlayers
|
|
}
|
|
|
|
if cmd.Type <= mt.AddPlayers {
|
|
for _, player := range cmd.Players {
|
|
sc.playerList[player] = struct{}{}
|
|
}
|
|
} else if cmd.Type == mt.RemovePlayers {
|
|
for _, player := range cmd.Players {
|
|
delete(sc.playerList, player)
|
|
}
|
|
}
|
|
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltSpawnParticle:
|
|
prependTexture(sc.name, &cmd.Texture)
|
|
sc.globalParam0(&cmd.NodeParam0)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltBlkData:
|
|
for i := range cmd.Blk.Param0 {
|
|
sc.globalParam0(&cmd.Blk.Param0[i])
|
|
}
|
|
|
|
for k := range cmd.Blk.NodeMetas {
|
|
for j, field := range cmd.Blk.NodeMetas[k].Fields {
|
|
if field.Name == "formspec" {
|
|
sc.prependFormspec(&cmd.Blk.NodeMetas[k].Fields[j].Value)
|
|
break
|
|
}
|
|
}
|
|
sc.prependInv(cmd.Blk.NodeMetas[k].Inv)
|
|
}
|
|
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltAddNode:
|
|
sc.globalParam0(&cmd.Node.Param0)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltAddParticleSpawner:
|
|
prependTexture(sc.name, &cmd.Texture)
|
|
sc.swapAOID(&cmd.AttachedAOID)
|
|
sc.globalParam0(&cmd.NodeParam0)
|
|
sc.particleSpawners[cmd.ID] = struct{}{}
|
|
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltDelParticleSpawner:
|
|
delete(sc.particleSpawners, cmd.ID)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltPlaySound:
|
|
prepend(sc.name, &cmd.Name)
|
|
sc.swapAOID(&cmd.SrcAOID)
|
|
if cmd.Loop {
|
|
sc.sounds[cmd.ID] = struct{}{}
|
|
}
|
|
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltFadeSound:
|
|
delete(sc.sounds, cmd.ID)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltStopSound:
|
|
delete(sc.sounds, cmd.ID)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltAddHUD:
|
|
sc.prependHUD(cmd.Type, cmd)
|
|
|
|
sc.huds[cmd.ID] = cmd.Type
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltChangeHUD:
|
|
sc.prependHUD(sc.huds[cmd.ID], cmd)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltRmHUD:
|
|
delete(sc.huds, cmd.ID)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltShowFormspec:
|
|
sc.prependFormspec(&cmd.Formspec)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltFormspecPrepend:
|
|
sc.prependFormspec(&cmd.Prepend)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltInvFormspec:
|
|
sc.prependFormspec(&cmd.Formspec)
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltMinimapModes:
|
|
for i := range cmd.Modes {
|
|
prependTexture(sc.name, &cmd.Modes[i].Texture)
|
|
}
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltNodeMetasChanged:
|
|
for k := range cmd.Changed {
|
|
for i, field := range cmd.Changed[k].Fields {
|
|
if field.Name == "formspec" {
|
|
sc.prependFormspec(&cmd.Changed[k].Fields[i].Value)
|
|
break
|
|
}
|
|
}
|
|
sc.prependInv(cmd.Changed[k].Inv)
|
|
}
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltAddPlayerVel:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltBreath:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltChatMsg:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltCloudParams:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltDeathScreen:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltEyeOffset:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltFOV:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltHP:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltHUDFlags:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltLocalPlayerAnim:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltModChanMsg:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltModChanSig:
|
|
var exit bool
|
|
switch cmd.Signal {
|
|
case mt.JoinOK:
|
|
if _, ok := clt.modChs[cmd.Channel]; ok {
|
|
exit = true
|
|
break
|
|
}
|
|
clt.modChs[cmd.Channel] = struct{}{}
|
|
case mt.JoinFail:
|
|
fallthrough
|
|
case mt.LeaveOK:
|
|
delete(clt.modChs, cmd.Channel)
|
|
}
|
|
|
|
if exit {
|
|
break
|
|
}
|
|
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltMovePlayer:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltMovement:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltOverrideDayNightRatio:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltPrivs:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltRemoveNode:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltStarParams:
|
|
clt.SendCmd(cmd)
|
|
case *mt.ToCltTimeOfDay:
|
|
clt.SendCmd(cmd)
|
|
}
|
|
}
|
|
}
|