1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"errors"
8	"fmt"
9	"time"
10
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12	jsonw "github.com/keybase/go-jsonw"
13)
14
15var ErrTrackingExpired = errors.New("Local track expired")
16
17// Can be a ProofLinkWithState, one of the identities listed in a
18// tracking statement, or a PGP Fingerprint!
19type TrackIDComponent interface {
20	ToIDString() string
21	ToKeyValuePair() (string, string)
22	GetProofState() keybase1.ProofState
23	LastWriterWins() bool
24	GetProofType() keybase1.ProofType
25}
26
27type TrackSet struct {
28	ids      map[string]TrackIDComponent
29	services map[string]bool
30}
31
32func NewTrackSet() *TrackSet {
33	return &TrackSet{
34		ids:      make(map[string]TrackIDComponent),
35		services: make(map[string]bool),
36	}
37}
38
39func (ts TrackSet) Add(t TrackIDComponent) {
40	ts.ids[t.ToIDString()] = t
41	if t.LastWriterWins() {
42		k, _ := t.ToKeyValuePair()
43		ts.services[k] = true
44	}
45}
46
47func (ts TrackSet) GetProofState(id string) keybase1.ProofState {
48	ret := keybase1.ProofState_NONE
49	if obj := ts.ids[id]; obj != nil {
50		ret = obj.GetProofState()
51	}
52	return ret
53}
54
55func (ts TrackSet) Subtract(b TrackSet) (out []TrackIDComponent) {
56	for _, c := range ts.ids {
57		if !b.HasMember(c) {
58			out = append(out, c)
59		}
60	}
61	return
62}
63
64func (ts TrackSet) HasMember(t TrackIDComponent) bool {
65	var found bool
66
67	// For LastWriterWins like social networks, then it just matters
68	// that there is some proof for the service.  For non-last-writer-wins,
69	// like HTTPS and DNS, then the full proof needs to show up in A.
70	if t.LastWriterWins() {
71		k, _ := t.ToKeyValuePair()
72		_, found = ts.services[k]
73	} else {
74		_, found = ts.ids[t.ToIDString()]
75	}
76	return found
77}
78
79func (ts TrackSet) LenEq(b TrackSet) bool {
80	return len(ts.ids) == len(b.ids)
81}
82
83//=====================================================================
84
85type TrackInstructions struct {
86	Local  bool
87	Remote bool
88}
89
90//=====================================================================
91
92type TrackSummary struct {
93	time     time.Time
94	isRemote bool
95	username string
96}
97
98func (s TrackSummary) IsRemote() bool      { return s.isRemote }
99func (s TrackSummary) GetCTime() time.Time { return s.time }
100func (s TrackSummary) Username() string    { return s.username }
101
102//=====================================================================
103
104type TrackLookup struct {
105	Contextified
106	link         *TrackChainLink     // The original chain link that I signed
107	set          *TrackSet           // The total set of tracked identities
108	ids          map[string][]string // A http -> [foo.com, boo.com] lookup
109	trackerSeqno keybase1.Seqno      // The seqno in the tracker's sighcain
110}
111
112func (l TrackLookup) ToSummary() TrackSummary {
113	ret := TrackSummary{
114		time:     l.GetCTime(),
115		isRemote: l.IsRemote(),
116	}
117	return ret
118}
119
120func (l TrackLookup) GetProofState(id string) keybase1.ProofState {
121	return l.set.GetProofState(id)
122}
123
124func (l TrackLookup) GetTrackerSeqno() keybase1.Seqno {
125	return l.trackerSeqno
126}
127
128func (l TrackLookup) GetTrackedKeys() []TrackedKey {
129	ret, err := l.link.GetTrackedKeys()
130	if err != nil {
131		l.G().Log.Warning("Error in lookup of tracked PGP fingerprints: %s", err)
132	}
133	return ret
134}
135
136func (l TrackLookup) GetEldestKID() keybase1.KID {
137	ret, err := l.link.GetEldestKID()
138	if err != nil {
139		l.G().Log.Warning("Error in lookup of eldest KID: %s", err)
140	}
141	return ret
142}
143
144func (l TrackLookup) GetTrackedLinkSeqno() keybase1.Seqno {
145	ret, err := l.link.GetTrackedLinkSeqno()
146	if err != nil {
147		l.G().Log.Warning("Error in lookup of tracked link's seqno: %s", err)
148	}
149	return ret
150}
151
152func (l TrackLookup) GetTmpExpireTime() (ret time.Time) {
153	return l.link.GetTmpExpireTime()
154}
155
156func (l TrackLookup) IsRemote() bool {
157	return l.link.IsRemote()
158}
159
160type TrackDiff interface {
161	BreaksTracking() bool
162	ToDisplayString() string
163	ToDisplayMarkup() *Markup
164	IsSameAsTracked() bool
165	GetTrackDiffType() keybase1.TrackDiffType
166}
167
168type TrackDiffUpgraded struct {
169	prev, curr string
170}
171
172func (t TrackDiffUpgraded) IsSameAsTracked() bool {
173	return false
174}
175
176func (t TrackDiffUpgraded) BreaksTracking() bool {
177	return false
178}
179func (t TrackDiffUpgraded) ToDisplayString() string {
180	return "Upgraded from " + t.prev + " to " + t.curr
181}
182func (t TrackDiffUpgraded) GetPrev() string { return t.prev }
183func (t TrackDiffUpgraded) GetCurr() string { return t.curr }
184func (t TrackDiffUpgraded) ToDisplayMarkup() *Markup {
185	return NewMarkup(t.ToDisplayString())
186}
187func (t TrackDiffUpgraded) GetTrackDiffType() keybase1.TrackDiffType {
188	return keybase1.TrackDiffType_UPGRADED
189}
190
191type TrackDiffNone struct{}
192
193func (t TrackDiffNone) BreaksTracking() bool {
194	return false
195}
196func (t TrackDiffNone) IsSameAsTracked() bool {
197	return true
198}
199
200func (t TrackDiffNone) ToDisplayString() string {
201	return "followed"
202}
203func (t TrackDiffNone) ToDisplayMarkup() *Markup {
204	return NewMarkup(t.ToDisplayString())
205}
206func (t TrackDiffNone) GetTrackDiffType() keybase1.TrackDiffType {
207	return keybase1.TrackDiffType_NONE
208}
209
210type TrackDiffNoneViaTemporary struct{}
211
212func (t TrackDiffNoneViaTemporary) BreaksTracking() bool     { return false }
213func (t TrackDiffNoneViaTemporary) IsSameAsTracked() bool    { return true }
214func (t TrackDiffNoneViaTemporary) ToDisplayString() string  { return "snoozed" }
215func (t TrackDiffNoneViaTemporary) ToDisplayMarkup() *Markup { return NewMarkup(t.ToDisplayString()) }
216func (t TrackDiffNoneViaTemporary) GetTrackDiffType() keybase1.TrackDiffType {
217	return keybase1.TrackDiffType_NONE_VIA_TEMPORARY
218}
219
220type TrackDiffNew struct{}
221
222func (t TrackDiffNew) BreaksTracking() bool {
223	return false
224}
225func (t TrackDiffNew) IsSameAsTracked() bool {
226	return false
227}
228
229type TrackDiffClash struct {
230	observed, expected string
231}
232
233func (t TrackDiffNew) ToDisplayString() string {
234	return "new"
235}
236func (t TrackDiffNew) ToDisplayMarkup() *Markup {
237	return NewMarkup(t.ToDisplayString())
238}
239func (t TrackDiffNew) GetTrackDiffType() keybase1.TrackDiffType {
240	return keybase1.TrackDiffType_NEW
241}
242
243func (t TrackDiffClash) BreaksTracking() bool {
244	return true
245}
246
247func (t TrackDiffClash) ToDisplayString() string {
248	return "CHANGED from \"" + t.expected + "\""
249}
250func (t TrackDiffClash) IsSameAsTracked() bool {
251	return false
252}
253func (t TrackDiffClash) ToDisplayMarkup() *Markup {
254	return NewMarkup(t.ToDisplayString())
255}
256func (t TrackDiffClash) GetTrackDiffType() keybase1.TrackDiffType {
257	return keybase1.TrackDiffType_CLASH
258}
259
260type TrackDiffRevoked struct {
261	idc TrackIDComponent
262}
263
264func (t TrackDiffRevoked) BreaksTracking() bool {
265	return true
266}
267func (t TrackDiffRevoked) ToDisplayString() string {
268	return "Deleted proof: " + t.idc.ToIDString()
269}
270func (t TrackDiffRevoked) IsSameAsTracked() bool {
271	return false
272}
273func (t TrackDiffRevoked) ToDisplayMarkup() *Markup {
274	return NewMarkup(t.ToDisplayString())
275}
276func (t TrackDiffRevoked) GetTrackDiffType() keybase1.TrackDiffType {
277	return keybase1.TrackDiffType_REVOKED
278}
279
280type TrackDiffSnoozedRevoked struct {
281	idc TrackIDComponent
282}
283
284func (t TrackDiffSnoozedRevoked) BreaksTracking() bool {
285	return false
286}
287func (t TrackDiffSnoozedRevoked) ToDisplayString() string {
288	return "Deleted proof: " + t.idc.ToIDString() + " (snoozed)"
289}
290func (t TrackDiffSnoozedRevoked) IsSameAsTracked() bool {
291	return true
292}
293func (t TrackDiffSnoozedRevoked) ToDisplayMarkup() *Markup {
294	return NewMarkup(t.ToDisplayString())
295}
296func (t TrackDiffSnoozedRevoked) GetTrackDiffType() keybase1.TrackDiffType {
297	return keybase1.TrackDiffType_NONE_VIA_TEMPORARY
298}
299
300type TrackDiffRemoteFail struct {
301	observed keybase1.ProofState
302}
303
304func (t TrackDiffRemoteFail) BreaksTracking() bool {
305	return true
306}
307func (t TrackDiffRemoteFail) ToDisplayString() string {
308	return "remote failed"
309}
310func (t TrackDiffRemoteFail) ToDisplayMarkup() *Markup {
311	return NewMarkup(t.ToDisplayString())
312}
313func (t TrackDiffRemoteFail) GetTrackDiffType() keybase1.TrackDiffType {
314	return keybase1.TrackDiffType_REMOTE_FAIL
315}
316func (t TrackDiffRemoteFail) IsSameAsTracked() bool {
317	return false
318}
319
320type TrackDiffRemoteWorking struct {
321	tracked keybase1.ProofState
322}
323
324func (t TrackDiffRemoteWorking) BreaksTracking() bool {
325	return false
326}
327func (t TrackDiffRemoteWorking) ToDisplayString() string {
328	return "newly working"
329}
330func (t TrackDiffRemoteWorking) ToDisplayMarkup() *Markup {
331	return NewMarkup(t.ToDisplayString())
332}
333func (t TrackDiffRemoteWorking) GetTrackDiffType() keybase1.TrackDiffType {
334	return keybase1.TrackDiffType_REMOTE_WORKING
335}
336func (t TrackDiffRemoteWorking) IsSameAsTracked() bool {
337	return false
338}
339
340type TrackDiffRemoteChanged struct {
341	tracked, observed keybase1.ProofState
342}
343
344func (t TrackDiffRemoteChanged) BreaksTracking() bool {
345	return false
346}
347func (t TrackDiffRemoteChanged) ToDisplayString() string {
348	return "changed"
349}
350func (t TrackDiffRemoteChanged) ToDisplayMarkup() *Markup {
351	return NewMarkup(t.ToDisplayString())
352}
353func (t TrackDiffRemoteChanged) GetTrackDiffType() keybase1.TrackDiffType {
354	return keybase1.TrackDiffType_REMOTE_CHANGED
355}
356func (t TrackDiffRemoteChanged) IsSameAsTracked() bool {
357	return false
358}
359
360type TrackDiffNewEldest struct {
361	tracked  keybase1.KID
362	observed keybase1.KID
363}
364
365func (t TrackDiffNewEldest) BreaksTracking() bool {
366	return true
367}
368func (t TrackDiffNewEldest) IsSameAsTracked() bool {
369	return false
370}
371func (t TrackDiffNewEldest) GetTrackDiffType() keybase1.TrackDiffType {
372	return keybase1.TrackDiffType_NEW_ELDEST
373}
374func (t TrackDiffNewEldest) ToDisplayString() string {
375	if t.tracked.IsNil() {
376		return fmt.Sprintf("No key when followed; established new eldest key %s", t.observed)
377	}
378	if t.tracked.Equal(t.observed) {
379		return fmt.Sprintf("Account reset! Old key was %s; new key is the same", t.tracked)
380	}
381	return fmt.Sprintf("Account reset! Old key was %s; new key is %s", t.tracked, t.observed)
382}
383func (t TrackDiffNewEldest) ToDisplayMarkup() *Markup {
384	return NewMarkup(t.ToDisplayString())
385}
386
387func NewTrackLookup(g *GlobalContext, link *TrackChainLink) *TrackLookup {
388	sbs := link.ToServiceBlocks()
389	set := NewTrackSet()
390	ids := make(map[string][]string)
391	for _, sb := range sbs {
392		set.Add(sb)
393		k, v := sb.ToKeyValuePair()
394		ids[k] = append(ids[k], v)
395	}
396	ret := &TrackLookup{Contextified: NewContextified(g), link: link, set: set, ids: ids, trackerSeqno: link.GetSeqno()}
397	return ret
398}
399
400func (l *TrackLookup) GetCTime() time.Time {
401	return l.link.GetCTime()
402}
403
404//=====================================================================
405
406func LocalTrackDBKey(tracker, trackee keybase1.UID, expireLocal bool) DbKey {
407	key := fmt.Sprintf("%s-%s", tracker, trackee)
408	if expireLocal {
409		key += "-expires"
410	}
411	return DbKey{Typ: DBLocalTrack, Key: key}
412}
413
414//=====================================================================
415
416func localTrackChainLinkFor(m MetaContext, tracker, trackee keybase1.UID, localExpires bool) (ret *TrackChainLink, err error) {
417	data, _, err := m.G().LocalDb.GetRaw(LocalTrackDBKey(tracker, trackee, localExpires))
418	if err != nil {
419		m.Debug("| DB lookup failed")
420		return nil, err
421	}
422	if len(data) == 0 {
423		m.Debug("| No local track found")
424		return nil, nil
425	}
426
427	cl := &ChainLink{Contextified: NewContextified(m.G())}
428	if err = cl.UnpackLocal(data); err != nil {
429		m.Debug("| unpack local failed -> %s", err)
430		return nil, err
431	}
432
433	var linkETime time.Time
434
435	if localExpires {
436		linkETime = cl.GetCTime().Add(m.G().Env.GetLocalTrackMaxAge())
437
438		m.Debug("| local track created %s, expires: %s, it is now %s", cl.GetCTime(), linkETime.String(), m.G().Clock().Now())
439
440		if linkETime.Before(m.G().Clock().Now()) {
441			m.Debug("| expired local track, deleting")
442			_ = removeLocalTrack(m, tracker, trackee, true)
443			return nil, ErrTrackingExpired
444		}
445	}
446
447	base := GenericChainLink{cl}
448	ret, err = ParseTrackChainLink(base)
449	if ret != nil && err == nil {
450		ret.local = true
451		ret.tmpExpireTime = linkETime
452	}
453
454	return ret, err
455}
456
457func LocalTrackChainLinkFor(m MetaContext, tracker, trackee keybase1.UID) (ret *TrackChainLink, err error) {
458	return localTrackChainLinkFor(m, tracker, trackee, false)
459}
460
461func LocalTmpTrackChainLinkFor(m MetaContext, tracker, trackee keybase1.UID) (ret *TrackChainLink, err error) {
462	return localTrackChainLinkFor(m, tracker, trackee, true)
463}
464
465func StoreLocalTrack(m MetaContext, tracker keybase1.UID, trackee keybase1.UID, expiringLocal bool, statement *jsonw.Wrapper) error {
466	m.Debug("| StoreLocalTrack, expiring = %v", expiringLocal)
467	err := m.G().LocalDb.Put(LocalTrackDBKey(tracker, trackee, expiringLocal), nil, statement)
468	if err == nil {
469		m.G().IdentifyDispatch.NotifyTrackingSuccess(m, trackee)
470	}
471	return err
472}
473
474func removeLocalTrack(m MetaContext, tracker keybase1.UID, trackee keybase1.UID, expiringLocal bool) error {
475	m.Debug("| RemoveLocalTrack, expiring = %v", expiringLocal)
476	return m.G().LocalDb.Delete(LocalTrackDBKey(tracker, trackee, expiringLocal))
477}
478
479func RemoveLocalTracks(m MetaContext, tracker keybase1.UID, trackee keybase1.UID) error {
480	e1 := removeLocalTrack(m, tracker, trackee, false)
481	e2 := removeLocalTrack(m, tracker, trackee, true)
482	return PickFirstError(e1, e2)
483}
484