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	"bufio"
8	"bytes"
9	"crypto/rand"
10	"crypto/sha256"
11	"encoding/base32"
12	"encoding/base64"
13	"encoding/hex"
14	"errors"
15	"fmt"
16	"io"
17	"io/ioutil"
18	"math"
19	"math/big"
20	"net/url"
21	"os"
22	"os/exec"
23	"os/user"
24	"path/filepath"
25	"regexp"
26	"runtime"
27	"strconv"
28	"strings"
29	"sync"
30	"syscall"
31	"time"
32	"unicode"
33	"unicode/utf8"
34
35	"github.com/keybase/client/go/kbcrypto"
36	"github.com/keybase/client/go/kbun"
37	"github.com/keybase/client/go/logger"
38	"github.com/keybase/client/go/profiling"
39	keybase1 "github.com/keybase/client/go/protocol/keybase1"
40	"github.com/keybase/clockwork"
41	"github.com/keybase/go-codec/codec"
42	jsonw "github.com/keybase/go-jsonw"
43	"golang.org/x/net/context"
44)
45
46// PrereleaseBuild can be set at compile time for prerelease builds.
47// CAUTION: Don't change the name of this variable without grepping for
48// occurrences in shell scripts!
49var PrereleaseBuild string
50
51// VersionString returns semantic version string
52func VersionString() string {
53	if PrereleaseBuild != "" {
54		return fmt.Sprintf("%s-%s", Version, PrereleaseBuild)
55	}
56	return Version
57}
58
59func ErrToOkPtr(err *error) string {
60	if err == nil {
61		return "ok"
62	}
63	return ErrToOk(*err)
64}
65
66func ErrToOk(err error) string {
67	if err == nil {
68		return "ok"
69	}
70	return fmt.Sprintf("ERROR: %v", err)
71}
72
73// exists returns whether the given file or directory exists or not
74func FileExists(path string) (bool, error) {
75	_, err := os.Stat(path)
76	if err == nil {
77		return true, nil
78	}
79	if os.IsNotExist(err) {
80		return false, nil
81	}
82	return false, err
83}
84
85func MakeParentDirs(log SkinnyLogger, filename string) error {
86	dir := filepath.Dir(filename)
87	exists, err := FileExists(dir)
88	if err != nil {
89		log.Errorf("Can't see if parent dir %s exists", dir)
90		return err
91	}
92
93	if !exists {
94		err = os.MkdirAll(dir, PermDir)
95		if err != nil {
96			log.Errorf("Can't make parent dir %s", dir)
97			return err
98		}
99		log.Debug("Created parent directory %s", dir)
100	}
101	return nil
102}
103
104func FastByteArrayEq(a, b []byte) bool {
105	return kbcrypto.FastByteArrayEq(a, b)
106}
107
108func SecureByteArrayEq(a, b []byte) bool {
109	return kbcrypto.SecureByteArrayEq(a, b)
110}
111
112func FormatTime(tm time.Time) string {
113	layout := "2006-01-02 15:04:05 MST"
114	return tm.Format(layout)
115}
116
117func Cicmp(s1, s2 string) bool {
118	return strings.EqualFold(s1, s2)
119}
120
121func TrimCicmp(s1, s2 string) bool {
122	return Cicmp(strings.TrimSpace(s1), strings.TrimSpace(s2))
123}
124
125func NameTrim(s string) string {
126	s = strings.ToLower(strings.TrimSpace(s))
127	strip := func(r rune) rune {
128		switch {
129		case r == '_', r == '-', r == '+', r == '\'':
130			return -1
131		case unicode.IsSpace(r):
132			return -1
133		}
134		return r
135
136	}
137	return strings.Map(strip, s)
138}
139
140// NameCmp removes whitespace and underscores, compares tolower.
141func NameCmp(n1, n2 string) bool {
142	return NameTrim(n1) == NameTrim(n2)
143}
144
145func IsLowercase(s string) bool {
146	return strings.ToLower(s) == s
147}
148
149func PickFirstError(errors ...error) error {
150	for _, e := range errors {
151		if e != nil {
152			return e
153		}
154	}
155	return nil
156}
157
158type FirstErrorPicker struct {
159	e     error
160	count int
161}
162
163func (p *FirstErrorPicker) Push(e error) {
164	if e != nil {
165		p.count++
166		if p.e == nil {
167			p.e = e
168		}
169	}
170}
171
172func (p *FirstErrorPicker) Count() int {
173	return p.count
174}
175
176func (p *FirstErrorPicker) Error() error {
177	return p.e
178}
179
180func GiveMeAnS(i int) string {
181	if i != 1 {
182		return "s"
183	}
184	return ""
185}
186
187func KeybaseEmailAddress(s string) string {
188	return s + "@keybase.io"
189}
190
191func DrainPipe(rc io.Reader, sink func(string)) error {
192	scanner := bufio.NewScanner(rc)
193	for scanner.Scan() {
194		sink(scanner.Text())
195	}
196	return scanner.Err()
197}
198
199type SafeWriter interface {
200	GetFilename() string
201	WriteTo(io.Writer) (int64, error)
202}
203
204type SafeWriteLogger interface {
205	Debug(format string, args ...interface{})
206	Errorf(format string, args ...interface{})
207}
208
209// SafeWriteToFile to safely write to a file. Use mode=0 for default permissions.
210func safeWriteToFileOnce(g SafeWriteLogger, t SafeWriter, mode os.FileMode) (err error) {
211	fn := t.GetFilename()
212	g.Debug("+ SafeWriteToFile(%q)", fn)
213	defer func() {
214		g.Debug("- SafeWriteToFile(%q) -> %s", fn, ErrToOk(err))
215	}()
216
217	tmpfn, tmp, err := OpenTempFile(fn, "", mode)
218	if err != nil {
219		return err
220	}
221	g.Debug("| Temporary file generated: %s", tmpfn)
222	defer tmp.Close()
223	defer func() { _ = ShredFile(tmpfn) }()
224
225	g.Debug("| WriteTo %s", tmpfn)
226	n, err := t.WriteTo(tmp)
227	if err != nil {
228		g.Errorf("| Error writing temporary file %s: %s", tmpfn, err)
229		return err
230	}
231	if n != 0 {
232		// unfortunately, some implementations always return 0 for the number
233		// of bytes written, so not much info there, but will log it when
234		// it isn't 0.
235		g.Debug("| bytes written to temporary file %s: %d", tmpfn, n)
236	}
237
238	if err := tmp.Sync(); err != nil {
239		g.Errorf("| Error syncing temporary file %s: %s", tmpfn, err)
240		return err
241	}
242
243	if err := tmp.Close(); err != nil {
244		g.Errorf("| Error closing temporary file %s: %s", tmpfn, err)
245		return err
246	}
247
248	g.Debug("| Renaming temporary file %s -> permanent file %s", tmpfn, fn)
249	if err := os.Rename(tmpfn, fn); err != nil {
250		g.Errorf("| Error renaming temporary file %s -> permanent file %s: %s", tmpfn, fn, err)
251		return err
252	}
253
254	if runtime.GOOS == "android" {
255		g.Debug("| Android extra checks in safeWriteToFile")
256		info, err := os.Stat(fn)
257		if err != nil {
258			g.Errorf("| Error os.Stat(%s): %s", fn, err)
259			return err
260		}
261		g.Debug("| File info: name = %s", info.Name())
262		g.Debug("| File info: size = %d", info.Size())
263		g.Debug("| File info: mode = %s", info.Mode())
264		g.Debug("| File info: mod time = %s", info.ModTime())
265
266		g.Debug("| Android extra checks done")
267	}
268
269	g.Debug("| Done writing to file %s", fn)
270
271	return nil
272}
273
274// Pluralize returns pluralized string with value.
275// For example,
276//   Pluralize(1, "zebra", "zebras", true) => "1 zebra"
277//   Pluralize(2, "zebra", "zebras", true) => "2 zebras"
278//   Pluralize(2, "zebra", "zebras", false) => "zebras"
279func Pluralize(n int, singular string, plural string, nshow bool) string {
280	if n == 1 {
281		if nshow {
282			return fmt.Sprintf("%d %s", n, singular)
283		}
284		return singular
285	}
286	if nshow {
287		return fmt.Sprintf("%d %s", n, plural)
288	}
289	return plural
290}
291
292// Contains returns true if string is contained in string slice
293func Contains(s string, list []string) bool {
294	return IsIn(s, list, false)
295}
296
297// IsIn checks for needle in haystack, ci means case-insensitive.
298func IsIn(needle string, haystack []string, ci bool) bool {
299	for _, h := range haystack {
300		if (ci && Cicmp(h, needle)) || (!ci && h == needle) {
301			return true
302		}
303	}
304	return false
305}
306
307// Found regex here: http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
308var hostnameRE = regexp.MustCompile("^(?i:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$")
309
310func IsValidHostname(s string) bool {
311	parts := strings.Split(s, ".")
312	// Found regex here: http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
313	if len(parts) < 2 {
314		return false
315	}
316	for _, p := range parts {
317		if !hostnameRE.MatchString(p) {
318			return false
319		}
320	}
321	// TLDs must be >=2 chars
322	return len(parts[len(parts)-1]) >= 2
323}
324
325var phoneAssertionRE = regexp.MustCompile(`^[1-9]\d{1,14}$`)
326
327// IsPossiblePhoneNumberAssertion checks if s is string of digits without a `+`
328// prefix for SBS assertions
329func IsPossiblePhoneNumberAssertion(s string) bool {
330	return phoneAssertionRE.MatchString(s)
331}
332
333var phoneRE = regexp.MustCompile(`^\+[1-9]\d{1,14}$`)
334
335// IsPossiblePhoneNumber checks if s is string of digits in phone number format
336func IsPossiblePhoneNumber(phone keybase1.PhoneNumber) error {
337	if !phoneRE.MatchString(string(phone)) {
338		return fmt.Errorf("Invalid phone number, expected +11234567890 format")
339	}
340	return nil
341}
342
343type Random interface {
344	// RndRange returns a uniformly random integer between low and high inclusive
345	RndRange(low, high int64) (res int64, err error)
346}
347
348// SecureRandom internally uses the cryptographically secure crypto/rand as a
349// source of randomness.
350type SecureRandom struct{}
351
352func (r *SecureRandom) RndRange(low, high int64) (res int64, err error) {
353	if low > high {
354		return 0, fmt.Errorf("SecureRandom error: [%v,%v] is not a valid range", low, high)
355	}
356	rangeBig := big.NewInt(high - low + 1)
357	big, err := rand.Int(rand.Reader, rangeBig)
358	if err != nil {
359		return 0, err
360	}
361	return low + big.Int64(), nil
362}
363
364var _ Random = (*SecureRandom)(nil)
365
366func RandBytes(length int) ([]byte, error) {
367	var n int
368	var err error
369	buf := make([]byte, length)
370	if n, err = rand.Read(buf); err != nil {
371		return nil, err
372	}
373	// rand.Read uses io.ReadFull internally, so this check should never fail.
374	if n != length {
375		return nil, fmt.Errorf("RandBytes got too few bytes, %d < %d", n, length)
376	}
377	return buf, nil
378}
379
380func RandBytesWithSuffix(length int, suffix byte) ([]byte, error) {
381	buf, err := RandBytes(length)
382	if err != nil {
383		return nil, err
384	}
385	buf[len(buf)-1] = suffix
386	return buf, nil
387}
388
389func XORBytes(dst, a, b []byte) int {
390	n := len(a)
391	if len(b) < n {
392		n = len(b)
393	}
394	for i := 0; i < n; i++ {
395		dst[i] = a[i] ^ b[i]
396	}
397	return n
398}
399
400// The standard time.Unix() converter interprets 0 as the Unix epoch (1970).
401// But in PGP, an expiry time of zero indicates that a key never expires, and
402// it would be nice to be able to check for that case with Time.IsZero(). This
403// conversion special-cases 0 to be time.Time's zero-value (1 AD), so that we
404// get that nice property.
405func UnixToTimeMappingZero(unixTime int64) time.Time {
406	if unixTime == 0 {
407		var zeroTime time.Time
408		return zeroTime
409	}
410	return time.Unix(unixTime, 0)
411}
412
413func Unquote(data []byte) string { return keybase1.Unquote(data) }
414
415func HexDecodeQuoted(data []byte) ([]byte, error) {
416	return hex.DecodeString(Unquote(data))
417}
418
419func IsArmored(buf []byte) bool {
420	return bytes.HasPrefix(bytes.TrimSpace(buf), []byte("-----"))
421}
422
423func RandInt64() (int64, error) {
424	max := big.NewInt(math.MaxInt64)
425	x, err := rand.Int(rand.Reader, max)
426	if err != nil {
427		return 0, err
428	}
429	return x.Int64(), nil
430}
431
432func RandInt() (int, error) {
433	x, err := RandInt64()
434	if err != nil {
435		return 0, err
436	}
437	return int(x), nil
438}
439
440func RandIntn(n int) int {
441	x, err := RandInt()
442	if err != nil {
443		panic(fmt.Sprintf("RandInt error: %s", err))
444	}
445	return x % n
446}
447
448// MakeURI makes a URI string out of the given protocol and
449// host strings, adding necessary punctuation in between.
450func MakeURI(prot string, host string) string {
451	if prot == "" {
452		return host
453	}
454	if prot[len(prot)-1] != ':' {
455		prot += ":"
456	}
457	return prot + "//" + host
458}
459
460// RemoveNilErrors returns error slice with ni errors removed.
461func RemoveNilErrors(errs []error) []error {
462	var r []error
463	for _, err := range errs {
464		if err != nil {
465			r = append(r, err)
466		}
467	}
468	return r
469}
470
471// CombineErrors returns a single error for multiple errors, or nil if none.
472func CombineErrors(errs ...error) error {
473	errs = RemoveNilErrors(errs)
474	if len(errs) == 0 {
475		return nil
476	} else if len(errs) == 1 {
477		return errs[0]
478	}
479
480	msgs := []string{}
481	for _, err := range errs {
482		msgs = append(msgs, err.Error())
483	}
484	return fmt.Errorf("There were multiple errors: %s", strings.Join(msgs, "; "))
485}
486
487// IsDirEmpty returns whether directory has any files.
488func IsDirEmpty(dir string) (bool, error) {
489	f, err := os.Open(dir)
490	if err != nil {
491		return false, err
492	}
493	defer f.Close()
494
495	_, err = f.Readdir(1)
496	if err == io.EOF {
497		return true, nil
498	}
499	return false, err // Either not empty or error, suits both cases
500}
501
502// RandString returns random (base32) string with prefix.
503func RandString(prefix string, numbytes int) (string, error) {
504	buf, err := RandBytes(numbytes)
505	if err != nil {
506		return "", err
507	}
508	str := base32.StdEncoding.EncodeToString(buf)
509	if prefix != "" {
510		str = strings.Join([]string{prefix, str}, "")
511	}
512	return str, nil
513}
514
515func RandStringB64(numTriads int) string {
516	buf, err := RandBytes(numTriads * 3)
517	if err != nil {
518		return ""
519	}
520	return base64.URLEncoding.EncodeToString(buf)
521}
522
523func RandHexString(prefix string, numbytes int) (string, error) {
524	buf, err := RandBytes(numbytes)
525	if err != nil {
526		return "", err
527	}
528	str := hex.EncodeToString(buf)
529	return prefix + str, nil
530}
531
532func Trace(log logger.Logger, msg string, err *error) func() {
533	log = log.CloneWithAddedDepth(1)
534	log.Debug("+ %s", msg)
535	start := time.Now()
536	return func() { log.Debug("- %s -> %s [time=%s]", msg, ErrToOkPtr(err), time.Since(start)) }
537}
538
539func CTrace(ctx context.Context, log logger.Logger, msg string, err *error, cl clockwork.Clock) func() {
540	log = log.CloneWithAddedDepth(1)
541	log.CDebugf(ctx, "+ %s", msg)
542	start := cl.Now()
543	return func() {
544		if err != nil && *err != nil {
545			log.CDebugf(ctx, "- %s -> %v %T [time=%s]", msg, *err, err, cl.Since(start))
546		} else {
547			log.CDebugf(ctx, "- %s -> ok [time=%s]", msg, cl.Since(start))
548		}
549	}
550}
551
552func (g *GlobalContext) Trace(msg string, err *error) func() {
553	return Trace(g.Log.CloneWithAddedDepth(1), msg, err)
554}
555func (g *GlobalContext) CTrace(ctx context.Context, msg string, err *error) func() {
556	return CTrace(ctx, g.Log.CloneWithAddedDepth(1), msg, err, g.Clock())
557}
558func (g *GlobalContext) CPerfTrace(ctx context.Context, msg string, err *error) func() {
559	return CTrace(ctx, g.PerfLog, msg, err, g.Clock())
560}
561
562func (g *GlobalContext) CVTrace(ctx context.Context, lev VDebugLevel, msg string, err *error) func() {
563	cl := g.Clock()
564	g.VDL.CLogf(ctx, lev, "+ %s", msg)
565	start := cl.Now()
566	return func() {
567		g.VDL.CLogf(ctx, lev, "- %s -> %v [time=%s]", msg, ErrToOkPtr(err), cl.Since(start))
568	}
569}
570
571func (g *GlobalContext) CTimeTracer(ctx context.Context, label string, enabled bool) profiling.TimeTracer {
572	if enabled {
573		return profiling.NewTimeTracer(ctx, g.Log.CloneWithAddedDepth(1), g.Clock(), label)
574	}
575	return profiling.NewSilentTimeTracer()
576}
577
578func (g *GlobalContext) CTimeBuckets(ctx context.Context) (context.Context, *profiling.TimeBuckets) {
579	return profiling.WithTimeBuckets(ctx, g.Clock(), g.Log)
580}
581
582// SplitByRunes splits string by runes
583func SplitByRunes(s string, separators []rune) []string {
584	f := func(r rune) bool {
585		for _, s := range separators {
586			if r == s {
587				return true
588			}
589		}
590		return false
591	}
592	return strings.FieldsFunc(s, f)
593}
594
595// SplitPath return string split by path separator: SplitPath("/a/b/c") => []string{"a", "b", "c"}
596func SplitPath(s string) []string {
597	return SplitByRunes(s, []rune{filepath.Separator})
598}
599
600// IsSystemAdminUser returns true if current user is root or admin (system user, not Keybase user).
601// WARNING: You shouldn't rely on this for security purposes.
602func IsSystemAdminUser() (isAdminUser bool, match string, err error) {
603	u, err := user.Current()
604	if err != nil {
605		return
606	}
607
608	if u.Uid == "0" {
609		match = "Uid: 0"
610		isAdminUser = true
611		return
612	}
613	return
614}
615
616// DigestForFileAtPath returns a SHA256 digest for file at specified path
617func DigestForFileAtPath(path string) (string, error) {
618	f, err := os.Open(path)
619	if err != nil {
620		return "", err
621	}
622	defer f.Close()
623	return Digest(f)
624}
625
626// Digest returns a SHA256 digest
627func Digest(r io.Reader) (string, error) {
628	hasher := sha256.New()
629	if _, err := io.Copy(hasher, r); err != nil {
630		return "", err
631	}
632	digest := hex.EncodeToString(hasher.Sum(nil))
633	return digest, nil
634}
635
636// TimeLog calls out with the time since start.  Use like this:
637//    defer TimeLog("MyFunc", time.Now(), e.G().Log.Warning)
638func TimeLog(name string, start time.Time, out func(string, ...interface{})) {
639	out("time> %s: %s", name, time.Since(start))
640}
641
642// CTimeLog calls out with the time since start.  Use like this:
643//    defer CTimeLog(ctx, "MyFunc", time.Now(), e.G().Log.Warning)
644func CTimeLog(ctx context.Context, name string, start time.Time, out func(context.Context, string, ...interface{})) {
645	out(ctx, "time> %s: %s", name, time.Since(start))
646}
647
648var wsRE = regexp.MustCompile(`\s+`)
649
650func WhitespaceNormalize(s string) string {
651	v := wsRE.Split(s, -1)
652	if len(v) > 0 && len(v[0]) == 0 {
653		v = v[1:]
654	}
655	if len(v) > 0 && len(v[len(v)-1]) == 0 {
656		v = v[0 : len(v)-1]
657	}
658	return strings.Join(v, " ")
659}
660
661// JoinPredicate joins strings with predicate
662func JoinPredicate(arr []string, delimeter string, f func(s string) bool) string {
663	arrNew := make([]string, 0, len(arr))
664	for _, s := range arr {
665		if f(s) {
666			arrNew = append(arrNew, s)
667		}
668	}
669	return strings.Join(arrNew, delimeter)
670}
671
672// LogTagsFromContext is a wrapper around logger.LogTagsFromContext
673// that simply casts the result to the type expected by
674// rpc.Connection.
675func LogTagsFromContext(ctx context.Context) (map[interface{}]string, bool) {
676	tags, ok := logger.LogTagsFromContext(ctx)
677	return map[interface{}]string(tags), ok
678}
679
680func MakeByte24(a []byte) [24]byte {
681	const n = 24
682	if len(a) != n {
683		panic(fmt.Sprintf("MakeByte expected len %v but got %v slice", n, len(a)))
684	}
685	var b [n]byte
686	copy(b[:], a)
687	return b
688}
689
690func MakeByte32(a []byte) [32]byte {
691	const n = 32
692	if len(a) != n {
693		panic(fmt.Sprintf("MakeByte expected len %v but got %v slice", n, len(a)))
694	}
695	var b [n]byte
696	copy(b[:], a)
697	return b
698}
699
700func MakeByte32Soft(a []byte) ([32]byte, error) {
701	const n = 32
702	var b [n]byte
703	if len(a) != n {
704		return b, fmt.Errorf("MakeByte expected len %v but got %v slice", n, len(a))
705	}
706	copy(b[:], a)
707	return b, nil
708}
709
710// Sleep until `deadline` or until `ctx` is canceled, whichever occurs first.
711// Returns an error BUT the error is not really an error.
712// It is nil if the sleep finished, and the non-nil result of Context.Err()
713func SleepUntilWithContext(ctx context.Context, clock clockwork.Clock, deadline time.Time) error {
714	if ctx == nil {
715		// should not happen
716		clock.AfterTime(deadline)
717		return nil
718	}
719	select {
720	case <-clock.AfterTime(deadline):
721		return nil
722	case <-ctx.Done():
723		return ctx.Err()
724	}
725}
726
727func Sleep(ctx context.Context, duration time.Duration) error {
728	timer := time.NewTimer(duration)
729	select {
730	case <-ctx.Done():
731		timer.Stop()
732		return ctx.Err()
733	case <-timer.C:
734		return nil
735	}
736}
737
738func UseCITime(g *GlobalContext) bool {
739	return g.GetEnv().RunningInCI() || g.GetEnv().GetSlowGregorConn()
740}
741
742func CITimeMultiplier(g *GlobalContext) time.Duration {
743	if UseCITime(g) {
744		return time.Duration(3)
745	}
746	return time.Duration(1)
747}
748
749func CanExec(p string) error {
750	return canExec(p)
751}
752
753func CurrentBinaryRealpath() (string, error) {
754	if IsMobilePlatform() {
755		return "mobile-binary-location-unknown", nil
756	}
757
758	executable, err := os.Executable()
759	if err != nil {
760		return "", err
761	}
762	return filepath.EvalSymlinks(executable)
763}
764
765var adminFeatureList = map[keybase1.UID]bool{
766	"23260c2ce19420f97b58d7d95b68ca00": true, // | chris        |
767	"dbb165b7879fe7b1174df73bed0b9500": true, // | max          |
768	"1563ec26dc20fd162a4f783551141200": true, // | patrick      |
769	"d73af57c418a917ba6665575eba13500": true, // | adamjspooner |
770	"95e88f2087e480cae28f08d81554bc00": true, // | mikem        |
771	"d1b3a5fa977ce53da2c2142a4511bc00": true, // | joshblum     |
772	"08abe80bd2da8984534b2d8f7b12c700": true, // | songgao      |
773	"237e85db5d939fbd4b84999331638200": true, // | cjb          |
774	"46fa8104092d0a680ad854bfc8507700": true, // | xgess        |
775	"69da56f622a2ac750b8e590c3658a700": true, // | jzila        |
776	"ef2e49961eddaa77094b45ed635cfc00": true, // | strib        |
777	"9403ede05906b942fd7361f40a679500": true, // | jinyang      |
778	"e0b4166c9c839275cf5633ff65c3e819": true, // | chrisnojima  |
779	"5f72055750c37c02a630122781508219": true, // | jakob223     |
780	"d95f137b3b4a3600bc9e39350adba819": true, // | cecileb      |
781	"eb08cb06e608ea41bd893946445d7919": true, // | mlsteele     |
782	"4a2c5d27346497ad64e3b7d457a1f919": true, // | pzduniak     |
783	"743338e8d5987e0e5077f0fddc763f19": true, // | taruti       |
784	"ee71dbc8e4e3e671e29a94caef5e1b19": true, // | zapu         |
785	"8c7c57995cd14780e351fc90ca7dc819": true, // | ayoubd       |
786	"b848bce3d54a76e4da323aad2957e819": true, // | modalduality |
787}
788
789// IsKeybaseAdmin returns true if uid is a keybase admin.
790func IsKeybaseAdmin(uid keybase1.UID) bool {
791	return adminFeatureList[uid]
792}
793
794// MobilePermissionDeniedCheck panics if err is a permission denied error
795// and if app is a mobile app. This has caused issues opening config.json
796// and secretkeys files, where it seems to be stuck in a permission
797// denied state and force-killing the app is the only option.
798func MobilePermissionDeniedCheck(g *GlobalContext, err error, msg string) {
799	if !os.IsPermission(err) {
800		return
801	}
802	if g.GetAppType() != MobileAppType {
803		return
804	}
805	g.Log.Warning("file open permission denied on mobile (%s): %s", msg, err)
806	os.Exit(4)
807}
808
809// IsNoSpaceOnDeviceError will return true if err is an `os` error
810// for "no space left on device".
811func IsNoSpaceOnDeviceError(err error) bool {
812	if err == nil {
813		return false
814	}
815	switch err := err.(type) {
816	case NoSpaceOnDeviceError:
817		return true
818	case *os.PathError:
819		return err.Err == syscall.ENOSPC
820	case *os.LinkError:
821		return err.Err == syscall.ENOSPC
822	case *os.SyscallError:
823		return err.Err == syscall.ENOSPC
824	}
825
826	return false
827}
828
829func ShredFile(filename string) error {
830	stat, err := os.Stat(filename)
831	if err != nil {
832		return err
833	}
834	if stat.IsDir() {
835		return errors.New("cannot shred a directory")
836	}
837	size := int(stat.Size())
838
839	defer os.Remove(filename)
840
841	for i := 0; i < 3; i++ {
842		noise, err := RandBytes(size)
843		if err != nil {
844			return err
845		}
846		if err := ioutil.WriteFile(filename, noise, stat.Mode().Perm()); err != nil {
847			return err
848		}
849	}
850
851	return os.Remove(filename)
852}
853
854func MPackEncode(input interface{}) ([]byte, error) {
855	mh := codec.MsgpackHandle{WriteExt: true}
856	var data []byte
857	enc := codec.NewEncoderBytes(&data, &mh)
858	if err := enc.Encode(input); err != nil {
859		return nil, err
860	}
861	return data, nil
862}
863
864func MPackDecode(data []byte, res interface{}) error {
865	mh := codec.MsgpackHandle{WriteExt: true}
866	dec := codec.NewDecoderBytes(data, &mh)
867	err := dec.Decode(res)
868	return err
869}
870
871type NoiseBytes [noiseFileLen]byte
872
873func MakeNoise() (nb NoiseBytes, err error) {
874	noise, err := RandBytes(noiseFileLen)
875	if err != nil {
876		return nb, err
877	}
878	copy(nb[:], noise)
879	return nb, nil
880}
881
882func NoiseXOR(secret [32]byte, noise NoiseBytes) ([]byte, error) {
883	sum := sha256.Sum256(noise[:])
884	if len(sum) != len(secret) {
885		return nil, errors.New("secret or sha256.Size is no longer 32")
886	}
887
888	xor := make([]byte, len(sum))
889	for i := 0; i < len(sum); i++ {
890		xor[i] = sum[i] ^ secret[i]
891	}
892
893	return xor, nil
894}
895
896// ForceWallClock takes a multi-personality Go time and converts it to
897// a regular old WallClock time.
898func ForceWallClock(t time.Time) time.Time {
899	return t.Round(0)
900}
901
902// Decode decodes src into dst.
903// Errors unless all of:
904// - src is valid hex
905// - src decodes into exactly len(dst) bytes
906func DecodeHexFixed(dst, src []byte) error {
907	// hex.Decode is wrapped because it does not error on short reads and panics on long reads.
908	if len(src)%2 == 1 {
909		return hex.ErrLength
910	}
911	if len(dst) != hex.DecodedLen(len(src)) {
912		return NewHexWrongLengthError(fmt.Sprintf(
913			"error decoding fixed-length hex: expected %v bytes but got %v", len(dst), hex.DecodedLen(len(src))))
914	}
915	n, err := hex.Decode(dst, src)
916	if err != nil {
917		return err
918	}
919	if n != len(dst) {
920		return NewHexWrongLengthError(fmt.Sprintf(
921			"error decoding fixed-length hex: expected %v bytes but got %v", len(dst), n))
922	}
923	return nil
924}
925
926func IsIOS() bool {
927	return isIOS
928}
929
930// AcquireWithContext attempts to acquire a lock with a context.
931// Returns nil if the lock was acquired.
932// Returns an error if it was not. The error is from ctx.Err().
933func AcquireWithContext(ctx context.Context, lock sync.Locker) (err error) {
934	if err = ctx.Err(); err != nil {
935		return err
936	}
937	acquiredCh := make(chan struct{})
938	shouldReleaseCh := make(chan bool, 1)
939	go func() {
940		lock.Lock()
941		close(acquiredCh)
942		shouldRelease := <-shouldReleaseCh
943		if shouldRelease {
944			lock.Unlock()
945		}
946	}()
947	select {
948	case <-acquiredCh:
949		err = nil
950	case <-ctx.Done():
951		err = ctx.Err()
952	}
953	shouldReleaseCh <- err != nil
954	return err
955}
956
957// AcquireWithTimeout attempts to acquire a lock with a timeout.
958// Convenience wrapper around AcquireWithContext.
959// Returns nil if the lock was acquired.
960// Returns context.DeadlineExceeded if it was not.
961func AcquireWithTimeout(lock sync.Locker, timeout time.Duration) (err error) {
962	ctx2, cancel := context.WithTimeout(context.Background(), timeout)
963	defer cancel()
964	return AcquireWithContext(ctx2, lock)
965}
966
967// AcquireWithContextAndTimeout attempts to acquire a lock with a context and a timeout.
968// Convenience wrapper around AcquireWithContext.
969// Returns nil if the lock was acquired.
970// Returns context.DeadlineExceeded or the error from ctx.Err() if it was not.
971func AcquireWithContextAndTimeout(ctx context.Context, lock sync.Locker, timeout time.Duration) (err error) {
972	ctx2, cancel := context.WithTimeout(ctx, timeout)
973	defer cancel()
974	return AcquireWithContext(ctx2, lock)
975}
976
977func Once(f func()) func() {
978	var once sync.Once
979	return func() {
980		once.Do(f)
981	}
982}
983
984func RuntimeGroup() keybase1.RuntimeGroup {
985	switch runtime.GOOS {
986	case "linux", "dragonfly", "freebsd", "netbsd", "openbsd":
987		return keybase1.RuntimeGroup_LINUXLIKE
988	case "darwin":
989		return keybase1.RuntimeGroup_DARWINLIKE
990	case "windows":
991		return keybase1.RuntimeGroup_WINDOWSLIKE
992	default:
993		return keybase1.RuntimeGroup_UNKNOWN
994	}
995}
996
997// execToString returns the space-trimmed output of a command or an error.
998func execToString(bin string, args []string) (string, error) {
999	result, err := exec.Command(bin, args...).Output()
1000	if err != nil {
1001		return "", err
1002	}
1003	if result == nil {
1004		return "", fmt.Errorf("Nil result")
1005	}
1006	return strings.TrimSpace(string(result)), nil
1007}
1008
1009var preferredKBFSMountDirs = func() []string {
1010	switch RuntimeGroup() {
1011	case keybase1.RuntimeGroup_LINUXLIKE:
1012		return []string{"/keybase"}
1013	case keybase1.RuntimeGroup_DARWINLIKE:
1014		return []string{"/keybase", "/Volumes/Keybase"}
1015	default:
1016		return []string{}
1017	}
1018}()
1019
1020func FindPreferredKBFSMountDirs() (mountDirs []string) {
1021	for _, mountDir := range preferredKBFSMountDirs {
1022		fi, err := os.Lstat(filepath.Join(mountDir, "private"))
1023		if err != nil {
1024			continue
1025		}
1026		if fi.Mode()&os.ModeSymlink != 0 {
1027			mountDirs = append(mountDirs, mountDir)
1028		}
1029	}
1030	return mountDirs
1031}
1032
1033var kbfsPathInnerRegExp = func() *regexp.Regexp {
1034	// e.g. alice@twitter
1035	const regularAssertion = `[-_a-zA-Z0-9.+]+@[a-zA-Z.]+`
1036	// e.g. [bob@keybase.io]@email
1037	const emailAssertion = `\[[-_+a-zA-Z0-9.]+@[-_a-zA-Z0-9.]+\]@[a-zA-Z.]+`
1038	const socialAssertion = `(?:` + regularAssertion + `)|(?:` + emailAssertion + `)`
1039	const user = `(?:(?:` + kbun.UsernameRE + `)|(?:` + socialAssertion + `))`
1040	const usernames = user + `(?:,` + user + `)*`
1041	const teamName = kbun.UsernameRE + `(?:\.` + kbun.UsernameRE + `)*`
1042	const tlfType = "/(?:private|public|team)$"
1043	const suffix = `(?: \([-_a-zA-Z0-9 #]+\))?`
1044	const tlf = "/(?:(?:private|public)/" + usernames + "(?:#" + usernames + ")?|team/" + teamName + ")" + suffix + "(?:/|$)"
1045	const specialFiles = "/(?:.kbfs_.+)"
1046	return regexp.MustCompile(`^(?:(?:` + tlf + `)|(?:` + tlfType + `)|(?:` + specialFiles + `))`)
1047}()
1048
1049// IsKBFSAfterKeybasePath returns true if afterKeybase, after prefixed by
1050// /keybase, is a valid KBFS path.
1051func IsKBFSAfterKeybasePath(afterKeybase string) bool {
1052	return len(afterKeybase) == 0 || kbfsPathInnerRegExp.MatchString(afterKeybase)
1053}
1054
1055func getKBFSAfterMountPath(afterKeybase string, isWindows bool) string {
1056	afterMount := afterKeybase
1057	if len(afterMount) == 0 {
1058		afterMount = "/"
1059	}
1060
1061	if !isWindows {
1062		return afterMount
1063	}
1064
1065	// Encode path names for Windows
1066	elems := strings.Split(afterMount, "/")
1067	for i, elem := range elems {
1068		elems[i] = EncodeKbfsNameForWindows(elem)
1069	}
1070	return strings.Join(elems, "\\")
1071}
1072
1073func getKBFSDeeplinkPath(afterKeybase string) string {
1074	if len(afterKeybase) == 0 {
1075		return ""
1076	}
1077	var segments []string
1078	for _, segment := range strings.Split(afterKeybase, "/") {
1079		segments = append(segments, url.PathEscape(segment))
1080	}
1081	return "keybase:/" + strings.Join(segments, "/")
1082}
1083
1084func GetKBFSPathInfo(standardPath string) (pathInfo keybase1.KBFSPathInfo, err error) {
1085	const slashKeybase = "/keybase"
1086	if !strings.HasPrefix(standardPath, slashKeybase) {
1087		return keybase1.KBFSPathInfo{}, errors.New("not a KBFS path")
1088	}
1089
1090	afterKeybase := standardPath[len(slashKeybase):]
1091
1092	if !IsKBFSAfterKeybasePath(afterKeybase) {
1093		return keybase1.KBFSPathInfo{}, errors.New("not a KBFS path")
1094	}
1095
1096	return keybase1.KBFSPathInfo{
1097		StandardPath:           standardPath,
1098		DeeplinkPath:           getKBFSDeeplinkPath(afterKeybase),
1099		PlatformAfterMountPath: getKBFSAfterMountPath(afterKeybase, RuntimeGroup() == keybase1.RuntimeGroup_WINDOWSLIKE),
1100	}, nil
1101}
1102
1103func GetSafeFilename(filename string) (safeFilename string) {
1104	filename = filepath.Base(filename)
1105	if !utf8.ValidString(filename) {
1106		return url.PathEscape(filename)
1107	}
1108	for _, r := range filename {
1109		if unicode.Is(unicode.C, r) {
1110			safeFilename += url.PathEscape(string(r))
1111		} else {
1112			safeFilename += string(r)
1113		}
1114	}
1115	return safeFilename
1116}
1117
1118func GetSafePath(path string) (safePath string) {
1119	dir, file := filepath.Split(path)
1120	return filepath.Join(dir, GetSafeFilename(file))
1121}
1122
1123func FindFilePathWithNumberSuffix(parentDir string, basename string, useArbitraryName bool) (filePath string, err error) {
1124	ext := filepath.Ext(basename)
1125	if useArbitraryName {
1126		return filepath.Join(parentDir, strconv.FormatInt(time.Now().UnixNano(), 16)+ext), nil
1127	}
1128	destPath := filepath.Join(parentDir, basename)
1129	basename = basename[:len(basename)-len(ext)]
1130	// keep a sane limit on the loop.
1131	for suffix := 1; suffix < 100000; suffix++ {
1132		_, err := os.Stat(destPath)
1133		if os.IsNotExist(err) {
1134			break
1135		}
1136		if err != nil {
1137			return "", err
1138		}
1139		destPath = filepath.Join(parentDir, fmt.Sprintf("%s (%d)%s", basename, suffix, ext))
1140	}
1141	// Could race but it should be rare enough so fine.
1142	return destPath, nil
1143}
1144
1145func JsonwStringArray(a []string) *jsonw.Wrapper {
1146	aj := jsonw.NewArray(len(a))
1147	for i, s := range a {
1148		_ = aj.SetIndex(i, jsonw.NewString(s))
1149	}
1150	return aj
1151}
1152
1153var throttleBatchClock = clockwork.NewRealClock()
1154
1155type throttleBatchEmpty struct{}
1156
1157func isEmptyThrottleData(arg interface{}) bool {
1158	_, ok := arg.(throttleBatchEmpty)
1159	return ok
1160}
1161
1162func ThrottleBatch(f func(interface{}), batcher func(interface{}, interface{}) interface{},
1163	reset func() interface{}, delay time.Duration, leadingFire bool) (func(interface{}), func()) {
1164	var lock sync.Mutex
1165	var closeLock sync.Mutex
1166	var lastCalled time.Time
1167	var creation func(interface{})
1168	hasStored := false
1169	scheduled := false
1170	stored := reset()
1171	cancelCh := make(chan struct{})
1172	closed := false
1173	creation = func(arg interface{}) {
1174		lock.Lock()
1175		defer lock.Unlock()
1176		elapsed := throttleBatchClock.Since(lastCalled)
1177		isEmpty := isEmptyThrottleData(arg)
1178		leading := leadingFire || hasStored
1179		if !isEmpty {
1180			stored = batcher(stored, arg)
1181			hasStored = true
1182		}
1183		if elapsed > delay && (!isEmpty || hasStored) && leading {
1184			f(stored)
1185			stored = reset()
1186			hasStored = false
1187			lastCalled = throttleBatchClock.Now()
1188		} else if !scheduled && !isEmpty {
1189			scheduled = true
1190			go func() {
1191				select {
1192				case <-throttleBatchClock.After(delay - elapsed):
1193					lock.Lock()
1194					scheduled = false
1195					lock.Unlock()
1196					creation(throttleBatchEmpty{})
1197				case <-cancelCh:
1198					return
1199				}
1200			}()
1201		}
1202	}
1203	return creation, func() {
1204		closeLock.Lock()
1205		defer closeLock.Unlock()
1206		if closed {
1207			return
1208		}
1209		closed = true
1210		close(cancelCh)
1211	}
1212}
1213
1214// Format a proof for web-of-trust. Does not support all proof types.
1215func NewWotProof(proofType keybase1.ProofType, key, value string) (res keybase1.WotProof, err error) {
1216	switch proofType {
1217	case keybase1.ProofType_TWITTER, keybase1.ProofType_GITHUB, keybase1.ProofType_REDDIT,
1218		keybase1.ProofType_COINBASE, keybase1.ProofType_HACKERNEWS, keybase1.ProofType_FACEBOOK,
1219		keybase1.ProofType_GENERIC_SOCIAL, keybase1.ProofType_ROOTER:
1220		return keybase1.WotProof{
1221			ProofType: proofType,
1222			Name:      key,
1223			Username:  value,
1224		}, nil
1225	case keybase1.ProofType_GENERIC_WEB_SITE:
1226		return keybase1.WotProof{
1227			ProofType: proofType,
1228			Protocol:  key,
1229			Hostname:  value,
1230		}, nil
1231	case keybase1.ProofType_DNS:
1232		return keybase1.WotProof{
1233			ProofType: proofType,
1234			Protocol:  key,
1235			Domain:    value,
1236		}, nil
1237	default:
1238		return res, fmt.Errorf("unexpected proof type: %v", proofType)
1239	}
1240}
1241
1242// Format a web-of-trust proof for gui display.
1243func NewWotProofUI(mctx MetaContext, proof keybase1.WotProof) (res keybase1.WotProofUI, err error) {
1244	iconKey := ProofIconKey(mctx, proof.ProofType, proof.Name)
1245	res = keybase1.WotProofUI{
1246		SiteIcon:         MakeProofIcons(mctx, iconKey, ProofIconTypeSmall, 16),
1247		SiteIconDarkmode: MakeProofIcons(mctx, iconKey, ProofIconTypeSmallDarkmode, 16),
1248	}
1249	switch proof.ProofType {
1250	case keybase1.ProofType_TWITTER, keybase1.ProofType_GITHUB, keybase1.ProofType_REDDIT,
1251		keybase1.ProofType_COINBASE, keybase1.ProofType_HACKERNEWS, keybase1.ProofType_FACEBOOK,
1252		keybase1.ProofType_GENERIC_SOCIAL, keybase1.ProofType_ROOTER:
1253		res.Type = proof.Name
1254		res.Value = proof.Username
1255	case keybase1.ProofType_GENERIC_WEB_SITE:
1256		res.Type = proof.Protocol
1257		res.Value = proof.Hostname
1258	case keybase1.ProofType_DNS:
1259		res.Type = "dns"
1260		res.Value = proof.Domain
1261	default:
1262		return res, fmt.Errorf("unexpected proof type: %v", proof.ProofType)
1263	}
1264	return res, nil
1265}
1266
1267func ProofIconKey(mctx MetaContext, proofType keybase1.ProofType, genericKeyAndFallback string) (iconKey string) {
1268	switch proofType {
1269	case keybase1.ProofType_TWITTER:
1270		return "twitter"
1271	case keybase1.ProofType_GITHUB:
1272		return "github"
1273	case keybase1.ProofType_REDDIT:
1274		return "reddit"
1275	case keybase1.ProofType_HACKERNEWS:
1276		return "hackernews"
1277	case keybase1.ProofType_FACEBOOK:
1278		return "facebook"
1279	case keybase1.ProofType_GENERIC_SOCIAL:
1280		serviceType := mctx.G().GetProofServices().GetServiceType(mctx.Ctx(), genericKeyAndFallback)
1281		if serviceType != nil {
1282			return serviceType.GetLogoKey()
1283		}
1284		return genericKeyAndFallback
1285	case keybase1.ProofType_GENERIC_WEB_SITE, keybase1.ProofType_DNS:
1286		return "web"
1287	default:
1288		return genericKeyAndFallback
1289	}
1290}
1291