1package main
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/davecgh/go-spew/spew"
8	"github.com/keybase/client/go/kbfs/idutil"
9	"github.com/keybase/client/go/kbfs/kbfscodec"
10	"github.com/keybase/client/go/kbfs/kbfscrypto"
11	"github.com/keybase/client/go/kbfs/kbfsmd"
12	"github.com/keybase/client/go/kbfs/libkbfs"
13	"github.com/keybase/client/go/protocol/keybase1"
14	"golang.org/x/net/context"
15)
16
17// replacementMap is a map from a string to its replacement, intended
18// to provide human-readable strings to UIDs and KIDs. It is usually
19// created once per invocation and plumbed through everything that
20// uses it, filling it on-demand so as to avoid needless calls to the
21// service.
22type replacementMap map[string]string
23
24func mdDumpGetDeviceStringForCryptPublicKey(k kbfscrypto.CryptPublicKey, ui idutil.UserInfo) (
25	string, bool) {
26	deviceName, ok := ui.KIDNames[k.KID()]
27	if !ok {
28		return "", false
29	}
30
31	if revokedInfo, ok := ui.RevokedCryptPublicKeys[k]; ok {
32		return fmt.Sprintf("%s (revoked %s) (kid:%s)",
33			deviceName, revokedInfo.Time.Time(), k), true
34	}
35
36	return fmt.Sprintf("%s (kid:%s)", deviceName, k), true
37}
38
39func mdDumpGetDeviceStringForVerifyingKey(k kbfscrypto.VerifyingKey, ui idutil.UserInfo) (
40	string, bool) {
41	deviceName, ok := ui.KIDNames[k.KID()]
42	if !ok {
43		return "", false
44	}
45
46	if revokedInfo, ok := ui.RevokedVerifyingKeys[k]; ok {
47		return fmt.Sprintf("%s (revoked %s) (kid:%s)",
48			deviceName, revokedInfo.Time.Time(), k), true
49	}
50
51	return fmt.Sprintf("%s (kid:%s)", deviceName, k), true
52}
53
54func mdDumpFillReplacements(ctx context.Context, codec kbfscodec.Codec,
55	service libkbfs.KeybaseService, osg idutil.OfflineStatusGetter,
56	prefix string, rmd kbfsmd.RootMetadata, extra kbfsmd.ExtraMetadata,
57	replacements replacementMap) error {
58	writers, readers, err := rmd.GetUserDevicePublicKeys(extra)
59	if err != nil {
60		return err
61	}
62
63	offline := osg.OfflineAvailabilityForID(rmd.TlfID())
64
65	for _, userKeys := range []kbfsmd.UserDevicePublicKeys{writers, readers} {
66		for u := range userKeys {
67			// Make sure to only make one Resolve and one
68			// LoadUserPlusKeys call per user for a single
69			// replacements map.
70			if _, ok := replacements[u.String()]; ok {
71				continue
72			}
73
74			username, _, err := service.Resolve(
75				ctx, fmt.Sprintf("uid:%s", u),
76				keybase1.OfflineAvailability_NONE)
77			if err == nil {
78				replacements[u.String()] = fmt.Sprintf(
79					"%s (uid:%s)", username, u)
80			} else {
81				replacements[u.String()] = fmt.Sprintf(
82					"<unknown username> (uid:%s)", u)
83				printError(prefix, err)
84			}
85
86			ui, err := service.LoadUserPlusKeys(ctx, u, "", offline)
87			if err != nil {
88				printError(prefix, err)
89				continue
90			}
91
92			for _, k := range ui.CryptPublicKeys {
93				if _, ok := replacements[k.String()]; ok {
94					continue
95				}
96
97				if deviceStr, ok := mdDumpGetDeviceStringForCryptPublicKey(k, ui); ok {
98					replacements[k.String()] = deviceStr
99				}
100			}
101
102			for k := range ui.RevokedCryptPublicKeys {
103				if _, ok := replacements[k.String()]; ok {
104					continue
105				}
106
107				if deviceStr, ok := mdDumpGetDeviceStringForCryptPublicKey(k, ui); ok {
108					replacements[k.String()] = deviceStr
109				}
110			}
111
112			for _, k := range ui.VerifyingKeys {
113				if _, ok := replacements[k.String()]; ok {
114					continue
115				}
116
117				if deviceStr, ok := mdDumpGetDeviceStringForVerifyingKey(k, ui); ok {
118					replacements[k.String()] = deviceStr
119				}
120			}
121
122			for k := range ui.RevokedVerifyingKeys {
123				if _, ok := replacements[k.String()]; ok {
124					continue
125				}
126
127				if deviceStr, ok := mdDumpGetDeviceStringForVerifyingKey(k, ui); ok {
128					replacements[k.String()] = deviceStr
129				}
130			}
131		}
132	}
133
134	return nil
135}
136
137func mdDumpReplaceAll(s string, replacements replacementMap) string {
138	for old, new := range replacements {
139		s = strings.Replace(s, old, new, -1)
140	}
141	return s
142}
143
144func mdDumpReadOnlyRMDWithReplacements(
145	ctx context.Context, codec kbfscodec.Codec,
146	replacements replacementMap, rmd libkbfs.ReadOnlyRootMetadata) error {
147	c := spew.NewDefaultConfig()
148	c.Indent = "  "
149	c.DisablePointerAddresses = true
150	c.DisableCapacities = true
151	c.SortKeys = true
152
153	fmt.Print("Root metadata\n")
154	fmt.Print("-------------\n")
155
156	brmdDump, err := kbfsmd.DumpRootMetadata(codec, rmd.GetBareRootMetadata())
157	if err != nil {
158		return err
159	}
160
161	fmt.Printf("%s\n", mdDumpReplaceAll(brmdDump, replacements))
162
163	fmt.Print("Extra metadata\n")
164	fmt.Print("--------------\n")
165	extraDump, err := kbfsmd.DumpExtraMetadata(codec, rmd.Extra())
166	if err != nil {
167		return err
168	}
169	fmt.Printf("%s\n", mdDumpReplaceAll(extraDump, replacements))
170
171	fmt.Print("Private metadata\n")
172	fmt.Print("----------------\n")
173	pmdDump, err := libkbfs.DumpPrivateMetadata(
174		codec, len(rmd.GetSerializedPrivateMetadata()), *rmd.Data())
175	if err != nil {
176		return err
177	}
178	// Let the caller provide a trailing newline (if desired).
179	fmt.Printf("%s", mdDumpReplaceAll(pmdDump, replacements))
180
181	return nil
182}
183
184func mdDumpReadOnlyRMD(ctx context.Context, config libkbfs.Config,
185	prefix string, replacements replacementMap,
186	rmd libkbfs.ReadOnlyRootMetadata) error {
187	err := mdDumpFillReplacements(
188		ctx, config.Codec(), config.KeybaseService(), config,
189		prefix, rmd.GetBareRootMetadata(), rmd.Extra(), replacements)
190	if err != nil {
191		printError(prefix, err)
192	}
193
194	return mdDumpReadOnlyRMDWithReplacements(
195		ctx, config.Codec(), replacements, rmd)
196}
197