489 lines
12 KiB
Go
489 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"io"
|
|
"log"
|
|
|
|
"github.com/HimbeerserverDE/srp"
|
|
"github.com/anon55555/mt/rudp"
|
|
)
|
|
|
|
const (
|
|
ToClientHello = 0x02
|
|
ToClientAuthAccept = 0x03
|
|
ToClientAcceptSudoMode = 0x04
|
|
ToClientDenySudoMode = 0x05
|
|
ToClientAccessDenied = 0x0A
|
|
ToClientBlockdata = 0x20
|
|
ToClientAddNode = 0x21
|
|
ToClientRemoveNode = 0x22
|
|
ToClientInventory = 0x27
|
|
ToClientTimeOfDay = 0x29
|
|
ToClientCSMRestrictionFlags = 0x2A
|
|
ToClientPlayerSpeed = 0x2B
|
|
ToClientMediaPush = 0x2C
|
|
ToClientChatMessage = 0x2F
|
|
ToClientActiveObjectRemoveAdd = 0x31
|
|
ToClientActiveObjectMessages = 0x32
|
|
ToClientHP = 0x33
|
|
ToClientMovePlayer = 0x34
|
|
ToClientFOV = 0x36
|
|
ToClientDeathscreen = 0x37
|
|
ToClientMedia = 0x38
|
|
ToClientToolDef = 0x39
|
|
ToClientNodeDef = 0x3A
|
|
ToClientCraftItemDef = 0x3B
|
|
ToClientAnnounceMedia = 0x3C
|
|
ToClientItemDef = 0x3D
|
|
ToClientPlaySound = 0x3F
|
|
ToClientStopSound = 0x40
|
|
ToClientPrivileges = 0x41
|
|
ToClientInventoryFormspec = 0x42
|
|
ToClientDetachedInventory = 0x43
|
|
ToClientShowFormspec = 0x44
|
|
ToClientMovement = 0x45
|
|
ToClientSpawnParticle = 0x46
|
|
ToClientAddParticleSpawner = 0x47
|
|
ToClientHudAdd = 0x49
|
|
ToClientHudRM = 0x4A
|
|
ToClientHudChange = 0x4B
|
|
ToClientHudSetFlags = 0x4C
|
|
ToClientHudSetParam = 0x4D
|
|
ToClientBreath = 0x4E
|
|
ToClientSetSky = 0x4F
|
|
ToClientOverrideDayNightRatio = 0x50
|
|
ToClientLocalPlayerAnimations = 0x51
|
|
ToClientEyeOffset = 0x52
|
|
ToClientDeleteParticleSpawner = 0x53
|
|
ToClientCloudParams = 0x54
|
|
ToClientFadeSound = 0x55
|
|
ToClientUpdatePlayerList = 0x56
|
|
ToClientModChannelMSG = 0x57
|
|
ToClientModChannelSignal = 0x58
|
|
ToClientNodeMetaChanged = 0x59
|
|
ToClientSetSun = 0x5A
|
|
ToClientSetMoon = 0x5B
|
|
ToClientSetStars = 0x5C
|
|
ToClientSrpBytesSB = 0x60
|
|
ToClientFormspecPrepend = 0x61
|
|
ToClientMinimapModes = 0x62
|
|
)
|
|
|
|
const (
|
|
ToServerInit = 0x02
|
|
ToServerInit2 = 0x11
|
|
ToServerModChannelJoin = 0x17
|
|
ToServerModChannelLeave = 0x18
|
|
ToServerModChannelMsg = 0x19
|
|
ToServerPlayerPos = 0x23
|
|
ToServerGotBlocks = 0x24
|
|
ToServerDeletedBlocks = 0x25
|
|
ToServerInventoryAction = 0x31
|
|
ToServerChatMessage = 0x32
|
|
ToServerDamage = 0x35
|
|
ToServerPlayerItem = 0x37
|
|
ToServerRespawn = 0x38
|
|
ToServerInteract = 0x39
|
|
ToServerRemovedSounds = 0x3A
|
|
ToServerNodeMetaFields = 0x3B
|
|
ToServerInventoryFields = 0x3C
|
|
ToServerRequestMedia = 0x40
|
|
ToServerClientReady = 0x43
|
|
ToServerFirstSRP = 0x50
|
|
ToServerSRPBytesA = 0x51
|
|
ToServerSRPBytesM = 0x52
|
|
)
|
|
|
|
const (
|
|
AccessDeniedWrongPassword = iota
|
|
AccessDeniedUnexpectedData
|
|
AccessDeniedSingleplayer
|
|
AccessDeniedWrongVersion
|
|
AccessDeniedWrongCharsInName
|
|
AccessDeniedWrongName
|
|
AccessDeniedTooManyUsers
|
|
AccessDeniedEmptyPassword
|
|
AccessDeniedAlreadyConnected
|
|
AccessDeniedServerFail
|
|
AccessDeniedCustomString
|
|
AccessDeniedShutdown
|
|
AccessDeniedCrash
|
|
)
|
|
|
|
func processPktCommand(src, dst *Conn, pkt *rudp.Pkt) bool {
|
|
r := ByteReader(*pkt)
|
|
|
|
origReader := *r
|
|
pkt.Reader = &origReader
|
|
|
|
cmdBytes := make([]byte, 2)
|
|
r.Read(cmdBytes)
|
|
|
|
if src.IsSrv() {
|
|
switch cmd := binary.BigEndian.Uint16(cmdBytes); cmd {
|
|
case ToClientActiveObjectRemoveAdd:
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, processAoRmAdd(dst, r)...))
|
|
return false
|
|
case ToClientActiveObjectMessages:
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, processAoMsgs(dst, r)...))
|
|
return false
|
|
case ToClientChatMessage:
|
|
r.Seek(2, io.SeekCurrent)
|
|
|
|
ReadBytes16(r)
|
|
msg := ReadBytes16(r)
|
|
|
|
w := bytes.NewBuffer([]byte{0x00, ToServerChatMessage})
|
|
WriteBytes16(w, msg)
|
|
|
|
return processServerChatMessage(dst, rudp.Pkt{
|
|
Reader: w,
|
|
PktInfo: rudp.PktInfo{
|
|
Channel: pkt.Channel,
|
|
},
|
|
})
|
|
case ToClientModChannelSignal:
|
|
r.Seek(1, io.SeekCurrent)
|
|
|
|
ch := string(ReadBytes16(r))
|
|
|
|
state := ReadUint8(r)
|
|
|
|
r.Seek(2, io.SeekStart)
|
|
|
|
if ch == rpcCh {
|
|
switch sig := ReadUint8(r); sig {
|
|
case ModChSigJoinOk:
|
|
src.SetUseRpc(true)
|
|
case ModChSigSetState:
|
|
if state == ModChStateRO {
|
|
src.SetUseRpc(false)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
return false
|
|
case ToClientModChannelMSG:
|
|
return processRpc(src, r)
|
|
case ToClientBlockdata:
|
|
data, drop := processBlockdata(dst, r)
|
|
if drop {
|
|
return true
|
|
}
|
|
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, data...))
|
|
return false
|
|
case ToClientAddNode:
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, processAddnode(dst, r)...))
|
|
return false
|
|
case ToClientHudAdd:
|
|
id := ReadUint32(r)
|
|
dst.huds[id] = true
|
|
return false
|
|
case ToClientHudRM:
|
|
id := ReadUint32(r)
|
|
dst.huds[id] = false
|
|
return false
|
|
case ToClientPlaySound:
|
|
id := int32(ReadUint32(r))
|
|
namelen := ReadUint16(r)
|
|
r.Seek(int64(17+namelen), io.SeekStart)
|
|
objID := ReadUint16(r)
|
|
|
|
if objID == dst.currentPlayerCao {
|
|
objID = dst.localPlayerCao
|
|
} else if objID == dst.localPlayerCao {
|
|
objID = dst.currentPlayerCao
|
|
}
|
|
|
|
r.Seek(2, io.SeekStart)
|
|
|
|
data := make([]byte, r.Len())
|
|
r.Read(data)
|
|
|
|
binary.BigEndian.PutUint16(data[17+namelen:19+namelen], objID)
|
|
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, data...))
|
|
|
|
if loop := ReadUint8(r); loop > 0 {
|
|
dst.sounds[id] = true
|
|
}
|
|
case ToClientStopSound:
|
|
id := int32(ReadUint32(r))
|
|
dst.sounds[id] = false
|
|
case ToClientAddParticleSpawner:
|
|
r.Seek(97, io.SeekStart)
|
|
texturelen := ReadUint32(r)
|
|
r.Seek(int64(6+texturelen), io.SeekCurrent)
|
|
id := ReadUint16(r)
|
|
|
|
if id == dst.currentPlayerCao {
|
|
id = dst.localPlayerCao
|
|
} else if id == dst.localPlayerCao {
|
|
id = dst.currentPlayerCao
|
|
}
|
|
|
|
r.Seek(2, io.SeekStart)
|
|
|
|
data := make([]byte, r.Len())
|
|
r.Read(data)
|
|
|
|
binary.BigEndian.PutUint16(data[107+texturelen:109+texturelen], id)
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, data...))
|
|
case ToClientInventory:
|
|
old := *dst.Inv()
|
|
|
|
if err := dst.Inv().Deserialize(r); err != nil {
|
|
return true
|
|
}
|
|
|
|
dst.UpdateHandCapabs()
|
|
|
|
buf := &bytes.Buffer{}
|
|
dst.Inv().SerializeKeep(buf, old)
|
|
|
|
pkt.Reader = bytes.NewReader(append(cmdBytes, buf.Bytes()...))
|
|
|
|
return false
|
|
case ToClientAccessDenied:
|
|
doFallback, ok := ConfKey("do_fallback").(bool)
|
|
if ok && !doFallback {
|
|
return false
|
|
}
|
|
|
|
reason := ReadUint8(r)
|
|
|
|
if reason != uint8(AccessDeniedShutdown) && reason != uint8(AccessDeniedCrash) {
|
|
return false
|
|
}
|
|
|
|
msg := "shut down"
|
|
if reason == uint8(AccessDeniedCrash) {
|
|
msg = "crashed"
|
|
}
|
|
|
|
defsrv, ok := ConfKey("default_server").(string)
|
|
if !ok {
|
|
log.Print("Default server name not set or not a string")
|
|
return false
|
|
}
|
|
|
|
if dst.ServerName() == defsrv {
|
|
return false
|
|
}
|
|
|
|
dst.SendChatMsg("The minetest server has " + msg + ", connecting you to the default server...")
|
|
|
|
go dst.Redirect(defsrv)
|
|
|
|
for src.Forward() {
|
|
}
|
|
|
|
return true
|
|
case ToClientMediaPush:
|
|
digest := ReadBytes16(r)
|
|
name := string(ReadBytes16(r))
|
|
cacheByte := ReadUint8(r)
|
|
cache := cacheByte == uint8(1)
|
|
|
|
r.Seek(5, io.SeekCurrent)
|
|
data := make([]byte, r.Len())
|
|
r.Read(data)
|
|
|
|
media[string(name)] = &mediaFile{
|
|
digest: digest,
|
|
data: data,
|
|
noCache: !cache,
|
|
}
|
|
|
|
for _, conn := range Conns() {
|
|
ack, err := conn.Send(*pkt)
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
<-ack
|
|
}
|
|
|
|
updateMediaCache()
|
|
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
} else {
|
|
switch cmd := binary.BigEndian.Uint16(cmdBytes); cmd {
|
|
case ToServerChatMessage:
|
|
return processChatMessage(src, r)
|
|
case ToServerFirstSRP:
|
|
if src.sudoMode {
|
|
src.sudoMode = false
|
|
|
|
// This is a password change, save verifier and salt
|
|
s := ReadBytes16(r)
|
|
v := ReadBytes16(r)
|
|
|
|
SetPassword(src.Username(), v, s)
|
|
} else {
|
|
log.Print("User " + src.Username() + " at " + src.Addr().String() + " did not enter sudo mode before attempting to change the password")
|
|
}
|
|
|
|
return true
|
|
case ToServerSRPBytesA:
|
|
if !src.sudoMode {
|
|
A := ReadBytes16(r)
|
|
|
|
v, s, err := Password(src.Username())
|
|
if err != nil {
|
|
log.Print(err)
|
|
return true
|
|
}
|
|
|
|
B, _, K, err := srp.Handshake(A, v)
|
|
if err != nil {
|
|
log.Print(err)
|
|
return true
|
|
}
|
|
|
|
src.srp_s = s
|
|
src.srp_A = A
|
|
src.srp_B = B
|
|
src.srp_K = K
|
|
|
|
// Send SRP_BYTES_S_B
|
|
data := make([]byte, 6+len(s)+len(B))
|
|
data[0] = uint8(0x00)
|
|
data[1] = uint8(ToClientSrpBytesSB)
|
|
binary.BigEndian.PutUint16(data[2:4], uint16(len(s)))
|
|
copy(data[4:4+len(s)], s)
|
|
binary.BigEndian.PutUint16(data[4+len(s):6+len(s)], uint16(len(B)))
|
|
copy(data[6+len(s):6+len(s)+len(B)], B)
|
|
|
|
w := bytes.NewBuffer([]byte{0x00, ToClientSrpBytesSB})
|
|
WriteBytes16(w, s)
|
|
WriteBytes16(w, B)
|
|
|
|
ack, err := src.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
|
|
if err != nil {
|
|
log.Print(err)
|
|
return true
|
|
}
|
|
<-ack
|
|
}
|
|
return true
|
|
case ToServerSRPBytesM:
|
|
if !src.sudoMode {
|
|
M := ReadBytes16(r)
|
|
M2 := srp.ClientProof([]byte(src.Username()), src.srp_s, src.srp_A, src.srp_B, src.srp_K)
|
|
|
|
if subtle.ConstantTimeCompare(M, M2) == 1 {
|
|
// Password is correct
|
|
// Enter sudo mode
|
|
src.sudoMode = true
|
|
|
|
// Send ACCEPT_SUDO_MODE
|
|
data := []byte{0, ToClientAcceptSudoMode}
|
|
|
|
ack, err := src.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
|
|
if err != nil {
|
|
log.Print(err)
|
|
return true
|
|
}
|
|
<-ack
|
|
} else {
|
|
// Client supplied wrong password
|
|
log.Print("User " + src.Username() + " at " + src.Addr().String() + " supplied wrong password for sudo mode")
|
|
|
|
// Send DENY_SUDO_MODE
|
|
data := []byte{0, ToClientDenySudoMode}
|
|
|
|
ack, err := src.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
|
|
if err != nil {
|
|
log.Print(err)
|
|
return true
|
|
}
|
|
<-ack
|
|
}
|
|
}
|
|
return true
|
|
case ToServerModChannelJoin:
|
|
deny := func() {
|
|
data := make([]byte, 5+len(rpcCh))
|
|
data[0] = uint8(0x00)
|
|
data[1] = uint8(ToClientModChannelSignal)
|
|
data[2] = uint8(ModChSigJoinFail)
|
|
binary.BigEndian.PutUint16(data[3:5], uint16(len(rpcCh)))
|
|
copy(data[5:], []byte(rpcCh))
|
|
|
|
ack, err := src.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
<-ack
|
|
}
|
|
|
|
chAllowed, ok := ConfKey("modchannels").(bool)
|
|
if ok && !chAllowed {
|
|
deny()
|
|
return true
|
|
}
|
|
|
|
ch := string(ReadBytes16(r))
|
|
if ch == rpcCh {
|
|
deny()
|
|
return true
|
|
}
|
|
|
|
src.modChs[ch] = true
|
|
return false
|
|
case ToServerModChannelLeave:
|
|
deny := func() {
|
|
data := make([]byte, 5+len(rpcCh))
|
|
data[0] = uint8(0x00)
|
|
data[1] = uint8(ToClientModChannelSignal)
|
|
data[2] = uint8(ModChSigLeaveFail)
|
|
binary.BigEndian.PutUint16(data[3:5], uint16(len(rpcCh)))
|
|
copy(data[5:], []byte(rpcCh))
|
|
|
|
ack, err := src.Send(rudp.Pkt{Reader: bytes.NewReader(data)})
|
|
if err != nil {
|
|
log.Print(err)
|
|
}
|
|
<-ack
|
|
}
|
|
|
|
chAllowed, ok := ConfKey("modchannels").(bool)
|
|
if ok && !chAllowed {
|
|
deny()
|
|
return true
|
|
}
|
|
|
|
ch := string(ReadBytes16(r))
|
|
if ch == rpcCh {
|
|
deny()
|
|
return true
|
|
}
|
|
|
|
src.modChs[ch] = false
|
|
return false
|
|
case ToServerModChannelMsg:
|
|
chAllowed, ok := ConfKey("modchannels").(bool)
|
|
if ok && !chAllowed {
|
|
return true
|
|
}
|
|
|
|
ch := string(ReadBytes16(r))
|
|
if ch == rpcCh {
|
|
return true
|
|
}
|
|
return false
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
}
|