157 lines
3.3 KiB
Go
157 lines
3.3 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"crypto/rsa"
|
||
|
"crypto/x509"
|
||
|
_ "embed"
|
||
|
"fmt"
|
||
|
"github.com/Tnze/go-mc/chat"
|
||
|
"github.com/Tnze/go-mc/net"
|
||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||
|
"github.com/google/uuid"
|
||
|
"go.jolheiser.com/beaver"
|
||
|
)
|
||
|
|
||
|
//go:embed favicon.png
|
||
|
var favicon []byte
|
||
|
|
||
|
type Server struct {
|
||
|
privateKey *rsa.PrivateKey
|
||
|
publicKey []byte
|
||
|
discordInvite string
|
||
|
}
|
||
|
|
||
|
func NewServer(discordInvite string) (*Server, error) {
|
||
|
private, err := rsa.GenerateKey(rand.Reader, 1024)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error generate private key: %v", err)
|
||
|
}
|
||
|
|
||
|
public, err := x509.MarshalPKIXPublicKey(private.Public())
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error form public key to PKIX, ASN.1 DER: %v", err)
|
||
|
}
|
||
|
|
||
|
private.Precompute()
|
||
|
|
||
|
return &Server{
|
||
|
privateKey: private,
|
||
|
publicKey: public,
|
||
|
discordInvite: discordInvite,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (s *Server) Start(port int) error {
|
||
|
beaver.Infof("Listening on http://localhost:%d", port)
|
||
|
l, err := net.ListenMC(fmt.Sprintf(":%d", port))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("listen error: %v", err)
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
conn, err := l.Accept()
|
||
|
if err != nil {
|
||
|
beaver.Errorf("Accept error: %v", err)
|
||
|
}
|
||
|
go s.acceptConn(conn)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Server) acceptConn(conn net.Conn) {
|
||
|
defer conn.Close()
|
||
|
// handshake
|
||
|
_, intention, err := s.handshake(conn)
|
||
|
if err != nil {
|
||
|
beaver.Errorf("Handshake error: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch intention {
|
||
|
default:
|
||
|
beaver.Errorf("Unknown handshake intention: %v", intention)
|
||
|
case 1:
|
||
|
s.acceptListPing(conn)
|
||
|
case 2:
|
||
|
s.handlePlaying(conn)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Server) handlePlaying(conn net.Conn) {
|
||
|
info, err := s.acceptLogin(conn)
|
||
|
if err != nil {
|
||
|
beaver.Errorf("login failed: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
verify, err := s.encryptionRequest(conn)
|
||
|
if err != nil {
|
||
|
beaver.Errorf("could not send encryption request: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
profile, secret, err := s.encryptionResponse(conn, info.Name, verify)
|
||
|
if err != nil {
|
||
|
beaver.Errorf("could not get encryption response: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// TODO Register
|
||
|
|
||
|
econn, err := encryptedConn(conn, secret)
|
||
|
if err != nil {
|
||
|
beaver.Errorf("could not create encrypted connection: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
msg := fmt.Sprintf("Thanks for registering, %s!", profile.Name)
|
||
|
if s.discordInvite != "" {
|
||
|
msg += fmt.Sprintf("\n\nJoin the Discord\n%s", s.discordInvite)
|
||
|
}
|
||
|
packet := pk.Marshal(0x00,
|
||
|
chat.Message{Text: msg},
|
||
|
)
|
||
|
if err := packet.Pack(econn, 0); err != nil {
|
||
|
beaver.Errorf("could not disconnect player: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type PlayerInfo struct {
|
||
|
Name string
|
||
|
UUID uuid.UUID
|
||
|
OPLevel int
|
||
|
}
|
||
|
|
||
|
// acceptLogin check player's account
|
||
|
func (s *Server) acceptLogin(conn net.Conn) (info PlayerInfo, err error) {
|
||
|
//login start
|
||
|
var p pk.Packet
|
||
|
err = conn.ReadPacket(&p)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
err = p.Scan((*pk.String)(&info.Name)) //decode username as pk.String
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// handshake receive and parse Handshake packet
|
||
|
func (s *Server) handshake(conn net.Conn) (protocol, intention int32, err error) {
|
||
|
var (
|
||
|
p pk.Packet
|
||
|
Protocol, Intention pk.VarInt
|
||
|
ServerAddress pk.String // ignored
|
||
|
ServerPort pk.UnsignedShort // ignored
|
||
|
)
|
||
|
// receive handshake packet
|
||
|
if err = conn.ReadPacket(&p); err != nil {
|
||
|
return
|
||
|
}
|
||
|
err = p.Scan(&Protocol, &ServerAddress, &ServerPort, &Intention)
|
||
|
return int32(Protocol), int32(Intention), err
|
||
|
}
|