package client import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "errors" "fmt" "net" "strconv" "github.com/admin-else/strom/api" "github.com/admin-else/strom/crypto" "github.com/admin-else/strom/event" "github.com/admin-else/strom/proto" "github.com/admin-else/strom/proto_base" "github.com/admin-else/strom/text" "github.com/admin-else/strom/proto_generated/v1_21_8" ) type LoginClient struct { *proto.Conn Account *api.Account ServerHost string ServerPort uint16 GivenAccount *v1_21_8.LoginToClientPacketSuccess } type KickedDuringLoginErr struct { text.Component } func (k KickedDuringLoginErr) Error() string { return k.String() } func (s *LoginClient) Default(event event.Unhandled) (err error) { err = fmt.Errorf("unexpected event: %#v", event) return } func (s *LoginClient) OnCycle(_ event.Tick) (err error) { return } func (s *LoginClient) OnStart(_ event.OnStart) (err error) { if s.ServerHost == "" && s.ServerPort == 0 { var portStr string s.ServerHost, portStr, err = net.SplitHostPort(s.RemoteAddr().String()) if err != nil { return } var portUint uint64 portUint, err = strconv.ParseUint(portStr, 10, 16) if err != nil { return } s.ServerPort = uint16(portUint) } err = s.Send(&v1_21_8.HandshakingToServerPacketSetProtocol{ ProtocolVersion: s.ProtocolVersion, ServerHost: s.ServerHost, ServerPort: s.ServerPort, NextState: int32(proto_base.Login), }) if err != nil { return } s.SetState(proto_base.Login) err = s.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid}) return } func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) { s.CompressionThreshold = compress.Threshold return } func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) { sharedSecret := make([]byte, 16) _, _ = rand.Read(sharedSecret) //never fails if packet.ShouldAuthenticate { if s.Account.Ygg == "" { err = fmt.Errorf("the account has no token so we cant join online servers") return } serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey) err = s.Account.JoinServer(serverId) if err != nil { return } } pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey) if err != nil { return } pub, ok := pubAny.(*rsa.PublicKey) if !ok { err = fmt.Errorf("public key is not rsa") return } verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken) if err != nil { return } sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret) if err != nil { return } err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc}) if err != nil { return } err = s.SetSecret(sharedSecret) return } func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) { var reason text.Component err = json.Unmarshal([]byte(packet.Reason), &reason) if err != nil { return } err = KickedDuringLoginErr{reason} return } func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) { s.GivenAccount = success err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{}) if err != nil { return } s.SetState(proto_base.Configuration) err = event.HandlerDoneErr{} return } func Login(c *proto.Conn, account *api.Account) (err error) { err = c.Start(&LoginClient{ Conn: c, Account: account, }) if err != nil { err = errors.Join(err, errors.New("failed to login")) } return } func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) { c, err = Connect(connectTo) if err != nil { return } err = c.Start(&LoginClient{ Conn: c, Account: account, }) if err != nil { err = errors.Join(errors.New("failed to login"), err) } return }