package server import ( "bytes" "crypto/rand" "crypto/rsa" "crypto/x509" _ "embed" "encoding/json" "fmt" "io" gonet "net" "os" "os/exec" "strings" "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" "github.com/google/uuid" "github.com/rs/zerolog/log" ) //go:embed favicon.png var favicon []byte type Server struct { privateKey *rsa.PrivateKey publicKey []byte favicon []byte opts Options } type Options struct { CommunityURL string PingMessage string FaviconPath string Hooks []string } func New(opts Options) (*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() icon := favicon if opts.FaviconPath != "" { fi, err := os.Open(opts.FaviconPath) if err != nil { return nil, err } defer fi.Close() icon, err = io.ReadAll(fi) if err != nil { return nil, err } } return &Server{ privateKey: private, publicKey: public, favicon: icon, opts: opts, }, nil } func (s *Server) Start(port int) error { 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 { log.Err(err).Msg("accept error") } go s.acceptConn(conn) } } func (s *Server) acceptConn(conn net.Conn) { defer conn.Close() // handshake _, intention, err := s.handshake(conn) if err != nil { log.Err(err).Msg("handshake error") return } switch intention { default: log.Err(err).Msg("unknown handshake 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 { log.Err(err).Msg("login failed") return } verify, err := s.encryptionRequest(conn) if err != nil { log.Err(err).Msg("could not send encryption request") return } profile, secret, err := s.encryptionResponse(conn, info.Name, verify) if err != nil { log.Err(err).Msg("could not get encryption response") return } profile.IP, _, err = gonet.SplitHostPort(conn.Socket.RemoteAddr().String()) if err != nil { log.Err(err).Msg("could not get user IP") return } payload, err := json.Marshal(profile) if err != nil { log.Err(err).Msg("could not marshal payload") return } for _, hook := range s.opts.Hooks { go func(h string) { s := strings.Split(h, " ") var args []string if len(s) > 1 { args = s[1:] } cmd := exec.Command(s[0], args...) cmd.Stdin = bytes.NewReader(payload) out, err := cmd.CombinedOutput() if err != nil { log.Err(err).Str("hook", h).Str("output", string(out)).Msg("could not run hook") } }(hook) } econn, err := encryptedConn(conn, secret) if err != nil { log.Err(err).Msg("could not create encrypted connection") return } msg := fmt.Sprintf("Thanks for authenticating, %s!", profile.Name) if s.opts.CommunityURL != "" { msg += fmt.Sprintf("\n\nJoin the community\n%s", s.opts.CommunityURL) } packet := pk.Marshal(0x00, chat.Message{Text: msg}, ) if err := packet.Pack(econn, 0); err != nil { log.Err(err).Msg("could not disconnect player") } } 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 }