mineauth/server.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
}