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