1package steam
2
3import (
4	"crypto/sha1"
5	"sync/atomic"
6	"time"
7
8	. "github.com/Philipp15b/go-steam/protocol"
9	. "github.com/Philipp15b/go-steam/protocol/protobuf"
10	. "github.com/Philipp15b/go-steam/protocol/steamlang"
11	. "github.com/Philipp15b/go-steam/steamid"
12	"github.com/golang/protobuf/proto"
13)
14
15type Auth struct {
16	client  *Client
17	details *LogOnDetails
18}
19
20type SentryHash []byte
21
22type LogOnDetails struct {
23	Username string
24
25	// If logging into an account without a login key, the account's password.
26	Password string
27
28	// If you have a Steam Guard email code, you can provide it here.
29	AuthCode string
30
31	// If you have a Steam Guard mobile two-factor authentication code, you can provide it here.
32	TwoFactorCode  string
33	SentryFileHash SentryHash
34	LoginKey       string
35
36	// true if you want to get a login key which can be used in lieu of
37	// a password for subsequent logins. false or omitted otherwise.
38	ShouldRememberPassword bool
39}
40
41// Log on with the given details. You must always specify username and
42// password OR username and loginkey. For the first login, don't set an authcode or a hash and you'll
43//  receive an error (EResult_AccountLogonDenied)
44// and Steam will send you an authcode. Then you have to login again, this time with the authcode.
45// Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows
46// you to login without using an authcode in the future.
47//
48// If you don't use Steam Guard, username and password are enough.
49//
50// After the event EMsg_ClientNewLoginKey is received you can use the LoginKey
51// to login instead of using the password.
52func (a *Auth) LogOn(details *LogOnDetails) {
53	if details.Username == "" {
54		panic("Username must be set!")
55	}
56	if details.Password == "" && details.LoginKey == "" {
57		panic("Password or LoginKey must be set!")
58	}
59
60	logon := new(CMsgClientLogon)
61	logon.AccountName = &details.Username
62	logon.Password = &details.Password
63	if details.AuthCode != "" {
64		logon.AuthCode = proto.String(details.AuthCode)
65	}
66	if details.TwoFactorCode != "" {
67		logon.TwoFactorCode = proto.String(details.TwoFactorCode)
68	}
69	logon.ClientLanguage = proto.String("english")
70	logon.ProtocolVersion = proto.Uint32(MsgClientLogon_CurrentProtocol)
71	logon.ShaSentryfile = details.SentryFileHash
72	if details.LoginKey != "" {
73		logon.LoginKey = proto.String(details.LoginKey)
74	}
75	if details.ShouldRememberPassword {
76		logon.ShouldRememberPassword = proto.Bool(details.ShouldRememberPassword)
77	}
78
79	atomic.StoreUint64(&a.client.steamId, uint64(NewIdAdv(0, 1, int32(EUniverse_Public), int32(EAccountType_Individual))))
80
81	a.client.Write(NewClientMsgProtobuf(EMsg_ClientLogon, logon))
82}
83
84func (a *Auth) HandlePacket(packet *Packet) {
85	switch packet.EMsg {
86	case EMsg_ClientLogOnResponse:
87		a.handleLogOnResponse(packet)
88	case EMsg_ClientNewLoginKey:
89		a.handleLoginKey(packet)
90	case EMsg_ClientSessionToken:
91	case EMsg_ClientLoggedOff:
92		a.handleLoggedOff(packet)
93	case EMsg_ClientUpdateMachineAuth:
94		a.handleUpdateMachineAuth(packet)
95	case EMsg_ClientAccountInfo:
96		a.handleAccountInfo(packet)
97	}
98}
99
100func (a *Auth) handleLogOnResponse(packet *Packet) {
101	if !packet.IsProto {
102		a.client.Fatalf("Got non-proto logon response!")
103		return
104	}
105
106	body := new(CMsgClientLogonResponse)
107	msg := packet.ReadProtoMsg(body)
108
109	result := EResult(body.GetEresult())
110	if result == EResult_OK {
111		atomic.StoreInt32(&a.client.sessionId, msg.Header.Proto.GetClientSessionid())
112		atomic.StoreUint64(&a.client.steamId, msg.Header.Proto.GetSteamid())
113		a.client.Web.webLoginKey = *body.WebapiAuthenticateUserNonce
114
115		go a.client.heartbeatLoop(time.Duration(body.GetOutOfGameHeartbeatSeconds()))
116
117		a.client.Emit(&LoggedOnEvent{
118			Result:                    EResult(body.GetEresult()),
119			ExtendedResult:            EResult(body.GetEresultExtended()),
120			OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(),
121			InGameSecsPerHeartbeat:    body.GetInGameHeartbeatSeconds(),
122			PublicIp:                  body.GetDeprecatedPublicIp(),
123			ServerTime:                body.GetRtime32ServerTime(),
124			AccountFlags:              EAccountFlags(body.GetAccountFlags()),
125			ClientSteamId:             SteamId(body.GetClientSuppliedSteamid()),
126			EmailDomain:               body.GetEmailDomain(),
127			CellId:                    body.GetCellId(),
128			CellIdPingThreshold:       body.GetCellIdPingThreshold(),
129			Steam2Ticket:              body.GetSteam2Ticket(),
130			UsePics:                   body.GetDeprecatedUsePics(),
131			WebApiUserNonce:           body.GetWebapiAuthenticateUserNonce(),
132			IpCountryCode:             body.GetIpCountryCode(),
133			VanityUrl:                 body.GetVanityUrl(),
134			NumLoginFailuresToMigrate: body.GetCountLoginfailuresToMigrate(),
135			NumDisconnectsToMigrate:   body.GetCountDisconnectsToMigrate(),
136		})
137	} else if result == EResult_Fail || result == EResult_ServiceUnavailable || result == EResult_TryAnotherCM {
138		// some error on Steam's side, we'll get an EOF later
139	} else {
140		a.client.Emit(&LogOnFailedEvent{
141			Result: EResult(body.GetEresult()),
142		})
143		a.client.Disconnect()
144	}
145}
146
147func (a *Auth) handleLoginKey(packet *Packet) {
148	body := new(CMsgClientNewLoginKey)
149	packet.ReadProtoMsg(body)
150	a.client.Write(NewClientMsgProtobuf(EMsg_ClientNewLoginKeyAccepted, &CMsgClientNewLoginKeyAccepted{
151		UniqueId: proto.Uint32(body.GetUniqueId()),
152	}))
153	a.client.Emit(&LoginKeyEvent{
154		UniqueId: body.GetUniqueId(),
155		LoginKey: body.GetLoginKey(),
156	})
157}
158
159func (a *Auth) handleLoggedOff(packet *Packet) {
160	result := EResult_Invalid
161	if packet.IsProto {
162		body := new(CMsgClientLoggedOff)
163		packet.ReadProtoMsg(body)
164		result = EResult(body.GetEresult())
165	} else {
166		body := new(MsgClientLoggedOff)
167		packet.ReadClientMsg(body)
168		result = body.Result
169	}
170	a.client.Emit(&LoggedOffEvent{Result: result})
171}
172
173func (a *Auth) handleUpdateMachineAuth(packet *Packet) {
174	body := new(CMsgClientUpdateMachineAuth)
175	packet.ReadProtoMsg(body)
176	hash := sha1.New()
177	hash.Write(packet.Data)
178	sha := hash.Sum(nil)
179
180	msg := NewClientMsgProtobuf(EMsg_ClientUpdateMachineAuthResponse, &CMsgClientUpdateMachineAuthResponse{
181		ShaFile: sha,
182	})
183	msg.SetTargetJobId(packet.SourceJobId)
184	a.client.Write(msg)
185
186	a.client.Emit(&MachineAuthUpdateEvent{sha})
187}
188
189func (a *Auth) handleAccountInfo(packet *Packet) {
190	body := new(CMsgClientAccountInfo)
191	packet.ReadProtoMsg(body)
192	a.client.Emit(&AccountInfoEvent{
193		PersonaName:          body.GetPersonaName(),
194		Country:              body.GetIpCountry(),
195		CountAuthedComputers: body.GetCountAuthedComputers(),
196		AccountFlags:         EAccountFlags(body.GetAccountFlags()),
197		FacebookId:           body.GetFacebookId(),
198		FacebookName:         body.GetFacebookName(),
199	})
200}
201