github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/util.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 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 "math" 18 "math/big" 19 "net/url" 20 "os" 21 "os/exec" 22 "os/user" 23 "path/filepath" 24 "regexp" 25 "runtime" 26 "strconv" 27 "strings" 28 "sync" 29 "syscall" 30 "time" 31 "unicode" 32 "unicode/utf8" 33 34 "github.com/keybase/client/go/kbcrypto" 35 "github.com/keybase/client/go/kbun" 36 "github.com/keybase/client/go/logger" 37 "github.com/keybase/client/go/profiling" 38 keybase1 "github.com/keybase/client/go/protocol/keybase1" 39 "github.com/keybase/clockwork" 40 "github.com/keybase/go-codec/codec" 41 jsonw "github.com/keybase/go-jsonw" 42 "golang.org/x/net/context" 43 ) 44 45 // PrereleaseBuild can be set at compile time for prerelease builds. 46 // CAUTION: Don't change the name of this variable without grepping for 47 // occurrences in shell scripts! 48 var PrereleaseBuild string 49 50 // VersionString returns semantic version string 51 func VersionString() string { 52 if PrereleaseBuild != "" { 53 return fmt.Sprintf("%s-%s", Version, PrereleaseBuild) 54 } 55 return Version 56 } 57 58 func ErrToOkPtr(err *error) string { 59 if err == nil { 60 return "ok" 61 } 62 return ErrToOk(*err) 63 } 64 65 func ErrToOk(err error) string { 66 if err == nil { 67 return "ok" 68 } 69 return fmt.Sprintf("ERROR: %v", err) 70 } 71 72 // exists returns whether the given file or directory exists or not 73 func FileExists(path string) (bool, error) { 74 _, err := os.Stat(path) 75 if err == nil { 76 return true, nil 77 } 78 if os.IsNotExist(err) { 79 return false, nil 80 } 81 return false, err 82 } 83 84 func MakeParentDirs(log SkinnyLogger, filename string) error { 85 dir := filepath.Dir(filename) 86 exists, err := FileExists(dir) 87 if err != nil { 88 log.Errorf("Can't see if parent dir %s exists", dir) 89 return err 90 } 91 92 if !exists { 93 err = os.MkdirAll(dir, PermDir) 94 if err != nil { 95 log.Errorf("Can't make parent dir %s", dir) 96 return err 97 } 98 log.Debug("Created parent directory %s", dir) 99 } 100 return nil 101 } 102 103 func FastByteArrayEq(a, b []byte) bool { 104 return kbcrypto.FastByteArrayEq(a, b) 105 } 106 107 func SecureByteArrayEq(a, b []byte) bool { 108 return kbcrypto.SecureByteArrayEq(a, b) 109 } 110 111 func FormatTime(tm time.Time) string { 112 layout := "2006-01-02 15:04:05 MST" 113 return tm.Format(layout) 114 } 115 116 func Cicmp(s1, s2 string) bool { 117 return strings.EqualFold(s1, s2) 118 } 119 120 func TrimCicmp(s1, s2 string) bool { 121 return Cicmp(strings.TrimSpace(s1), strings.TrimSpace(s2)) 122 } 123 124 func NameTrim(s string) string { 125 s = strings.ToLower(strings.TrimSpace(s)) 126 strip := func(r rune) rune { 127 switch { 128 case r == '_', r == '-', r == '+', r == '\'': 129 return -1 130 case unicode.IsSpace(r): 131 return -1 132 } 133 return r 134 135 } 136 return strings.Map(strip, s) 137 } 138 139 // NameCmp removes whitespace and underscores, compares tolower. 140 func NameCmp(n1, n2 string) bool { 141 return NameTrim(n1) == NameTrim(n2) 142 } 143 144 func IsLowercase(s string) bool { 145 return strings.ToLower(s) == s 146 } 147 148 func PickFirstError(errors ...error) error { 149 for _, e := range errors { 150 if e != nil { 151 return e 152 } 153 } 154 return nil 155 } 156 157 type FirstErrorPicker struct { 158 e error 159 count int 160 } 161 162 func (p *FirstErrorPicker) Push(e error) { 163 if e != nil { 164 p.count++ 165 if p.e == nil { 166 p.e = e 167 } 168 } 169 } 170 171 func (p *FirstErrorPicker) Count() int { 172 return p.count 173 } 174 175 func (p *FirstErrorPicker) Error() error { 176 return p.e 177 } 178 179 func GiveMeAnS(i int) string { 180 if i != 1 { 181 return "s" 182 } 183 return "" 184 } 185 186 func KeybaseEmailAddress(s string) string { 187 return s + "@keybase.io" 188 } 189 190 func DrainPipe(rc io.Reader, sink func(string)) error { 191 scanner := bufio.NewScanner(rc) 192 for scanner.Scan() { 193 sink(scanner.Text()) 194 } 195 return scanner.Err() 196 } 197 198 type SafeWriter interface { 199 GetFilename() string 200 WriteTo(io.Writer) (int64, error) 201 } 202 203 type SafeWriteLogger interface { 204 Debug(format string, args ...interface{}) 205 Errorf(format string, args ...interface{}) 206 } 207 208 // SafeWriteToFile to safely write to a file. Use mode=0 for default permissions. 209 func safeWriteToFileOnce(g SafeWriteLogger, t SafeWriter, mode os.FileMode) (err error) { 210 fn := t.GetFilename() 211 g.Debug("+ SafeWriteToFile(%q)", fn) 212 defer func() { 213 g.Debug("- SafeWriteToFile(%q) -> %s", fn, ErrToOk(err)) 214 }() 215 216 tmpfn, tmp, err := OpenTempFile(fn, "", mode) 217 if err != nil { 218 return err 219 } 220 g.Debug("| Temporary file generated: %s", tmpfn) 221 defer tmp.Close() 222 defer func() { _ = ShredFile(tmpfn) }() 223 224 g.Debug("| WriteTo %s", tmpfn) 225 n, err := t.WriteTo(tmp) 226 if err != nil { 227 g.Errorf("| Error writing temporary file %s: %s", tmpfn, err) 228 return err 229 } 230 if n != 0 { 231 // unfortunately, some implementations always return 0 for the number 232 // of bytes written, so not much info there, but will log it when 233 // it isn't 0. 234 g.Debug("| bytes written to temporary file %s: %d", tmpfn, n) 235 } 236 237 if err := tmp.Sync(); err != nil { 238 g.Errorf("| Error syncing temporary file %s: %s", tmpfn, err) 239 return err 240 } 241 242 if err := tmp.Close(); err != nil { 243 g.Errorf("| Error closing temporary file %s: %s", tmpfn, err) 244 return err 245 } 246 247 g.Debug("| Renaming temporary file %s -> permanent file %s", tmpfn, fn) 248 if err := os.Rename(tmpfn, fn); err != nil { 249 g.Errorf("| Error renaming temporary file %s -> permanent file %s: %s", tmpfn, fn, err) 250 return err 251 } 252 253 if runtime.GOOS == "android" { 254 g.Debug("| Android extra checks in safeWriteToFile") 255 info, err := os.Stat(fn) 256 if err != nil { 257 g.Errorf("| Error os.Stat(%s): %s", fn, err) 258 return err 259 } 260 g.Debug("| File info: name = %s", info.Name()) 261 g.Debug("| File info: size = %d", info.Size()) 262 g.Debug("| File info: mode = %s", info.Mode()) 263 g.Debug("| File info: mod time = %s", info.ModTime()) 264 265 g.Debug("| Android extra checks done") 266 } 267 268 g.Debug("| Done writing to file %s", fn) 269 270 return nil 271 } 272 273 // Pluralize returns pluralized string with value. 274 // For example, 275 // 276 // Pluralize(1, "zebra", "zebras", true) => "1 zebra" 277 // Pluralize(2, "zebra", "zebras", true) => "2 zebras" 278 // Pluralize(2, "zebra", "zebras", false) => "zebras" 279 func 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 293 func 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. 298 func 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 308 var hostnameRE = regexp.MustCompile("^(?i:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$") 309 310 func 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 325 var 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 329 func IsPossiblePhoneNumberAssertion(s string) bool { 330 return phoneAssertionRE.MatchString(s) 331 } 332 333 var phoneRE = regexp.MustCompile(`^\+[1-9]\d{1,14}$`) 334 335 // IsPossiblePhoneNumber checks if s is string of digits in phone number format 336 func 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 343 type 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. 350 type SecureRandom struct{} 351 352 func (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 364 var _ Random = (*SecureRandom)(nil) 365 366 func 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 380 func 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 389 func 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. 405 func 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 413 func Unquote(data []byte) string { return keybase1.Unquote(data) } 414 415 func HexDecodeQuoted(data []byte) ([]byte, error) { 416 return hex.DecodeString(Unquote(data)) 417 } 418 419 func IsArmored(buf []byte) bool { 420 return bytes.HasPrefix(bytes.TrimSpace(buf), []byte("-----")) 421 } 422 423 func 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 432 func RandInt() (int, error) { 433 x, err := RandInt64() 434 if err != nil { 435 return 0, err 436 } 437 return int(x), nil 438 } 439 440 func 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. 450 func 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. 461 func 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. 472 func 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. 488 func 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. 503 func 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 515 func 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 523 func 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 532 func 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 539 func 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 552 func (g *GlobalContext) Trace(msg string, err *error) func() { 553 return Trace(g.Log.CloneWithAddedDepth(1), msg, err) 554 } 555 func (g *GlobalContext) CTrace(ctx context.Context, msg string, err *error) func() { 556 return CTrace(ctx, g.Log.CloneWithAddedDepth(1), msg, err, g.Clock()) 557 } 558 func (g *GlobalContext) CPerfTrace(ctx context.Context, msg string, err *error) func() { 559 return CTrace(ctx, g.PerfLog, msg, err, g.Clock()) 560 } 561 562 func (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 571 func (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 578 func (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 583 func 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"} 596 func 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. 602 func 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 617 func 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 627 func 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 // 638 // defer TimeLog("MyFunc", time.Now(), e.G().Log.Warning) 639 func TimeLog(name string, start time.Time, out func(string, ...interface{})) { 640 out("time> %s: %s", name, time.Since(start)) 641 } 642 643 // CTimeLog calls out with the time since start. Use like this: 644 // 645 // defer CTimeLog(ctx, "MyFunc", time.Now(), e.G().Log.Warning) 646 func CTimeLog(ctx context.Context, name string, start time.Time, out func(context.Context, string, ...interface{})) { 647 out(ctx, "time> %s: %s", name, time.Since(start)) 648 } 649 650 var wsRE = regexp.MustCompile(`\s+`) 651 652 func WhitespaceNormalize(s string) string { 653 v := wsRE.Split(s, -1) 654 if len(v) > 0 && len(v[0]) == 0 { 655 v = v[1:] 656 } 657 if len(v) > 0 && len(v[len(v)-1]) == 0 { 658 v = v[0 : len(v)-1] 659 } 660 return strings.Join(v, " ") 661 } 662 663 // JoinPredicate joins strings with predicate 664 func JoinPredicate(arr []string, delimeter string, f func(s string) bool) string { 665 arrNew := make([]string, 0, len(arr)) 666 for _, s := range arr { 667 if f(s) { 668 arrNew = append(arrNew, s) 669 } 670 } 671 return strings.Join(arrNew, delimeter) 672 } 673 674 // LogTagsFromContext is a wrapper around logger.LogTagsFromContext 675 // that simply casts the result to the type expected by 676 // rpc.Connection. 677 func LogTagsFromContext(ctx context.Context) (map[interface{}]string, bool) { 678 tags, ok := logger.LogTagsFromContext(ctx) 679 return map[interface{}]string(tags), ok 680 } 681 682 func MakeByte24(a []byte) [24]byte { 683 const n = 24 684 if len(a) != n { 685 panic(fmt.Sprintf("MakeByte expected len %v but got %v slice", n, len(a))) 686 } 687 var b [n]byte 688 copy(b[:], a) 689 return b 690 } 691 692 func MakeByte32(a []byte) [32]byte { 693 const n = 32 694 if len(a) != n { 695 panic(fmt.Sprintf("MakeByte expected len %v but got %v slice", n, len(a))) 696 } 697 var b [n]byte 698 copy(b[:], a) 699 return b 700 } 701 702 func MakeByte32Soft(a []byte) ([32]byte, error) { 703 const n = 32 704 var b [n]byte 705 if len(a) != n { 706 return b, fmt.Errorf("MakeByte expected len %v but got %v slice", n, len(a)) 707 } 708 copy(b[:], a) 709 return b, nil 710 } 711 712 // Sleep until `deadline` or until `ctx` is canceled, whichever occurs first. 713 // Returns an error BUT the error is not really an error. 714 // It is nil if the sleep finished, and the non-nil result of Context.Err() 715 func SleepUntilWithContext(ctx context.Context, clock clockwork.Clock, deadline time.Time) error { 716 if ctx == nil { 717 // should not happen 718 clock.AfterTime(deadline) 719 return nil 720 } 721 select { 722 case <-clock.AfterTime(deadline): 723 return nil 724 case <-ctx.Done(): 725 return ctx.Err() 726 } 727 } 728 729 func Sleep(ctx context.Context, duration time.Duration) error { 730 timer := time.NewTimer(duration) 731 select { 732 case <-ctx.Done(): 733 timer.Stop() 734 return ctx.Err() 735 case <-timer.C: 736 return nil 737 } 738 } 739 740 func UseCITime(g *GlobalContext) bool { 741 return g.GetEnv().RunningInCI() || g.GetEnv().GetSlowGregorConn() 742 } 743 744 func CITimeMultiplier(g *GlobalContext) time.Duration { 745 if UseCITime(g) { 746 return time.Duration(3) 747 } 748 return time.Duration(1) 749 } 750 751 func CanExec(p string) error { 752 return canExec(p) 753 } 754 755 func CurrentBinaryRealpath() (string, error) { 756 if IsMobilePlatform() { 757 return "mobile-binary-location-unknown", nil 758 } 759 760 executable, err := os.Executable() 761 if err != nil { 762 return "", err 763 } 764 return filepath.EvalSymlinks(executable) 765 } 766 767 var adminFeatureList = map[keybase1.UID]bool{ 768 "23260c2ce19420f97b58d7d95b68ca00": true, // | chris | 769 "dbb165b7879fe7b1174df73bed0b9500": true, // | max | 770 "1563ec26dc20fd162a4f783551141200": true, // | patrick | 771 "d73af57c418a917ba6665575eba13500": true, // | adamjspooner | 772 "95e88f2087e480cae28f08d81554bc00": true, // | mikem | 773 "d1b3a5fa977ce53da2c2142a4511bc00": true, // | joshblum | 774 "08abe80bd2da8984534b2d8f7b12c700": true, // | songgao | 775 "237e85db5d939fbd4b84999331638200": true, // | cjb | 776 "46fa8104092d0a680ad854bfc8507700": true, // | xgess | 777 "69da56f622a2ac750b8e590c3658a700": true, // | jzila | 778 "ef2e49961eddaa77094b45ed635cfc00": true, // | strib | 779 "9403ede05906b942fd7361f40a679500": true, // | jinyang | 780 "e0b4166c9c839275cf5633ff65c3e819": true, // | chrisnojima | 781 "5f72055750c37c02a630122781508219": true, // | jakob223 | 782 "d95f137b3b4a3600bc9e39350adba819": true, // | cecileb | 783 "eb08cb06e608ea41bd893946445d7919": true, // | mlsteele | 784 "4a2c5d27346497ad64e3b7d457a1f919": true, // | pzduniak | 785 "743338e8d5987e0e5077f0fddc763f19": true, // | taruti | 786 "ee71dbc8e4e3e671e29a94caef5e1b19": true, // | zapu | 787 "8c7c57995cd14780e351fc90ca7dc819": true, // | ayoubd | 788 "b848bce3d54a76e4da323aad2957e819": true, // | modalduality | 789 } 790 791 // IsKeybaseAdmin returns true if uid is a keybase admin. 792 func IsKeybaseAdmin(uid keybase1.UID) bool { 793 return adminFeatureList[uid] 794 } 795 796 // MobilePermissionDeniedCheck panics if err is a permission denied error 797 // and if app is a mobile app. This has caused issues opening config.json 798 // and secretkeys files, where it seems to be stuck in a permission 799 // denied state and force-killing the app is the only option. 800 func MobilePermissionDeniedCheck(g *GlobalContext, err error, msg string) { 801 if !os.IsPermission(err) { 802 return 803 } 804 if g.GetAppType() != MobileAppType { 805 return 806 } 807 g.Log.Warning("file open permission denied on mobile (%s): %s", msg, err) 808 os.Exit(4) 809 } 810 811 // IsNoSpaceOnDeviceError will return true if err is an `os` error 812 // for "no space left on device". 813 func IsNoSpaceOnDeviceError(err error) bool { 814 if err == nil { 815 return false 816 } 817 switch err := err.(type) { 818 case NoSpaceOnDeviceError: 819 return true 820 case *os.PathError: 821 return err.Err == syscall.ENOSPC 822 case *os.LinkError: 823 return err.Err == syscall.ENOSPC 824 case *os.SyscallError: 825 return err.Err == syscall.ENOSPC 826 } 827 828 return false 829 } 830 831 func ShredFile(filename string) error { 832 stat, err := os.Stat(filename) 833 if err != nil { 834 return err 835 } 836 if stat.IsDir() { 837 return errors.New("cannot shred a directory") 838 } 839 size := int(stat.Size()) 840 841 defer os.Remove(filename) 842 843 for i := 0; i < 3; i++ { 844 noise, err := RandBytes(size) 845 if err != nil { 846 return err 847 } 848 if err := os.WriteFile(filename, noise, stat.Mode().Perm()); err != nil { 849 return err 850 } 851 } 852 853 return os.Remove(filename) 854 } 855 856 func MPackEncode(input interface{}) ([]byte, error) { 857 mh := codec.MsgpackHandle{WriteExt: true} 858 var data []byte 859 enc := codec.NewEncoderBytes(&data, &mh) 860 if err := enc.Encode(input); err != nil { 861 return nil, err 862 } 863 return data, nil 864 } 865 866 func MPackDecode(data []byte, res interface{}) error { 867 mh := codec.MsgpackHandle{WriteExt: true} 868 dec := codec.NewDecoderBytes(data, &mh) 869 err := dec.Decode(res) 870 return err 871 } 872 873 type NoiseBytes [noiseFileLen]byte 874 875 func MakeNoise() (nb NoiseBytes, err error) { 876 noise, err := RandBytes(noiseFileLen) 877 if err != nil { 878 return nb, err 879 } 880 copy(nb[:], noise) 881 return nb, nil 882 } 883 884 func NoiseXOR(secret [32]byte, noise NoiseBytes) ([]byte, error) { 885 sum := sha256.Sum256(noise[:]) 886 if len(sum) != len(secret) { 887 return nil, errors.New("secret or sha256.Size is no longer 32") 888 } 889 890 xor := make([]byte, len(sum)) 891 for i := 0; i < len(sum); i++ { 892 xor[i] = sum[i] ^ secret[i] 893 } 894 895 return xor, nil 896 } 897 898 // ForceWallClock takes a multi-personality Go time and converts it to 899 // a regular old WallClock time. 900 func ForceWallClock(t time.Time) time.Time { 901 return t.Round(0) 902 } 903 904 // Decode decodes src into dst. 905 // Errors unless all of: 906 // - src is valid hex 907 // - src decodes into exactly len(dst) bytes 908 func DecodeHexFixed(dst, src []byte) error { 909 // hex.Decode is wrapped because it does not error on short reads and panics on long reads. 910 if len(src)%2 == 1 { 911 return hex.ErrLength 912 } 913 if len(dst) != hex.DecodedLen(len(src)) { 914 return NewHexWrongLengthError(fmt.Sprintf( 915 "error decoding fixed-length hex: expected %v bytes but got %v", len(dst), hex.DecodedLen(len(src)))) 916 } 917 n, err := hex.Decode(dst, src) 918 if err != nil { 919 return err 920 } 921 if n != len(dst) { 922 return NewHexWrongLengthError(fmt.Sprintf( 923 "error decoding fixed-length hex: expected %v bytes but got %v", len(dst), n)) 924 } 925 return nil 926 } 927 928 func IsIOS() bool { 929 return isIOS 930 } 931 932 // AcquireWithContext attempts to acquire a lock with a context. 933 // Returns nil if the lock was acquired. 934 // Returns an error if it was not. The error is from ctx.Err(). 935 func AcquireWithContext(ctx context.Context, lock sync.Locker) (err error) { 936 if err = ctx.Err(); err != nil { 937 return err 938 } 939 acquiredCh := make(chan struct{}) 940 shouldReleaseCh := make(chan bool, 1) 941 go func() { 942 lock.Lock() 943 close(acquiredCh) 944 shouldRelease := <-shouldReleaseCh 945 if shouldRelease { 946 lock.Unlock() 947 } 948 }() 949 select { 950 case <-acquiredCh: 951 err = nil 952 case <-ctx.Done(): 953 err = ctx.Err() 954 } 955 shouldReleaseCh <- err != nil 956 return err 957 } 958 959 // AcquireWithTimeout attempts to acquire a lock with a timeout. 960 // Convenience wrapper around AcquireWithContext. 961 // Returns nil if the lock was acquired. 962 // Returns context.DeadlineExceeded if it was not. 963 func AcquireWithTimeout(lock sync.Locker, timeout time.Duration) (err error) { 964 ctx2, cancel := context.WithTimeout(context.Background(), timeout) 965 defer cancel() 966 return AcquireWithContext(ctx2, lock) 967 } 968 969 // AcquireWithContextAndTimeout attempts to acquire a lock with a context and a timeout. 970 // Convenience wrapper around AcquireWithContext. 971 // Returns nil if the lock was acquired. 972 // Returns context.DeadlineExceeded or the error from ctx.Err() if it was not. 973 func AcquireWithContextAndTimeout(ctx context.Context, lock sync.Locker, timeout time.Duration) (err error) { 974 ctx2, cancel := context.WithTimeout(ctx, timeout) 975 defer cancel() 976 return AcquireWithContext(ctx2, lock) 977 } 978 979 func Once(f func()) func() { 980 var once sync.Once 981 return func() { 982 once.Do(f) 983 } 984 } 985 986 func RuntimeGroup() keybase1.RuntimeGroup { 987 return runtimeGroup(runtime.GOOS) 988 } 989 990 func runtimeGroup(osname string) keybase1.RuntimeGroup { 991 switch osname { 992 case "linux", "dragonfly", "freebsd", "netbsd", "openbsd", "android": 993 return keybase1.RuntimeGroup_LINUXLIKE 994 case "darwin", "ios": 995 return keybase1.RuntimeGroup_DARWINLIKE 996 case "windows": 997 return keybase1.RuntimeGroup_WINDOWSLIKE 998 default: 999 return keybase1.RuntimeGroup_UNKNOWN 1000 } 1001 } 1002 1003 // execToString returns the space-trimmed output of a command or an error. 1004 func execToString(bin string, args []string) (string, error) { 1005 result, err := exec.Command(bin, args...).Output() 1006 if err != nil { 1007 return "", err 1008 } 1009 if result == nil { 1010 return "", fmt.Errorf("Nil result") 1011 } 1012 return strings.TrimSpace(string(result)), nil 1013 } 1014 1015 var preferredKBFSMountDirs = func() []string { 1016 switch RuntimeGroup() { 1017 case keybase1.RuntimeGroup_LINUXLIKE: 1018 return []string{"/keybase"} 1019 case keybase1.RuntimeGroup_DARWINLIKE: 1020 return []string{"/keybase", "/Volumes/Keybase"} 1021 default: 1022 return []string{} 1023 } 1024 }() 1025 1026 func FindPreferredKBFSMountDirs() (mountDirs []string) { 1027 for _, mountDir := range preferredKBFSMountDirs { 1028 fi, err := os.Lstat(filepath.Join(mountDir, "private")) 1029 if err != nil { 1030 continue 1031 } 1032 if fi.Mode()&os.ModeSymlink != 0 { 1033 mountDirs = append(mountDirs, mountDir) 1034 } 1035 } 1036 return mountDirs 1037 } 1038 1039 var kbfsPathInnerRegExp = func() *regexp.Regexp { 1040 // e.g. alice@twitter 1041 const regularAssertion = `[-_a-zA-Z0-9.+]+@[a-zA-Z.]+` 1042 // e.g. [bob@keybase.io]@email 1043 const emailAssertion = `\[[-_+a-zA-Z0-9.]+@[-_a-zA-Z0-9.]+\]@[a-zA-Z.]+` 1044 const socialAssertion = `(?:` + regularAssertion + `)|(?:` + emailAssertion + `)` 1045 const user = `(?:(?:` + kbun.UsernameRE + `)|(?:` + socialAssertion + `))` 1046 const usernames = user + `(?:,` + user + `)*` 1047 const teamName = kbun.UsernameRE + `(?:\.` + kbun.UsernameRE + `)*` 1048 const tlfType = "/(?:private|public|team)$" 1049 const suffix = `(?: \([-_a-zA-Z0-9 #]+\))?` 1050 const tlf = "/(?:(?:private|public)/" + usernames + "(?:#" + usernames + ")?|team/" + teamName + ")" + suffix + "(?:/|$)" 1051 const specialFiles = "/(?:.kbfs_.+)" 1052 return regexp.MustCompile(`^(?:(?:` + tlf + `)|(?:` + tlfType + `)|(?:` + specialFiles + `))`) 1053 }() 1054 1055 // IsKBFSAfterKeybasePath returns true if afterKeybase, after prefixed by 1056 // /keybase, is a valid KBFS path. 1057 func IsKBFSAfterKeybasePath(afterKeybase string) bool { 1058 return len(afterKeybase) == 0 || kbfsPathInnerRegExp.MatchString(afterKeybase) 1059 } 1060 1061 func getKBFSAfterMountPath(afterKeybase string, isWindows bool) string { 1062 afterMount := afterKeybase 1063 if len(afterMount) == 0 { 1064 afterMount = "/" 1065 } 1066 1067 if !isWindows { 1068 return afterMount 1069 } 1070 1071 // Encode path names for Windows 1072 elems := strings.Split(afterMount, "/") 1073 for i, elem := range elems { 1074 elems[i] = EncodeKbfsNameForWindows(elem) 1075 } 1076 return strings.Join(elems, "\\") 1077 } 1078 1079 func getKBFSDeeplinkPath(afterKeybase string) string { 1080 if len(afterKeybase) == 0 { 1081 return "" 1082 } 1083 var segments []string 1084 for _, segment := range strings.Split(afterKeybase, "/") { 1085 segments = append(segments, url.PathEscape(segment)) 1086 } 1087 return "keybase:/" + strings.Join(segments, "/") 1088 } 1089 1090 func GetKBFSPathInfo(standardPath string) (pathInfo keybase1.KBFSPathInfo, err error) { 1091 const slashKeybase = "/keybase" 1092 if !strings.HasPrefix(standardPath, slashKeybase) { 1093 return keybase1.KBFSPathInfo{}, errors.New("not a KBFS path") 1094 } 1095 1096 afterKeybase := standardPath[len(slashKeybase):] 1097 1098 if !IsKBFSAfterKeybasePath(afterKeybase) { 1099 return keybase1.KBFSPathInfo{}, errors.New("not a KBFS path") 1100 } 1101 1102 return keybase1.KBFSPathInfo{ 1103 StandardPath: standardPath, 1104 DeeplinkPath: getKBFSDeeplinkPath(afterKeybase), 1105 PlatformAfterMountPath: getKBFSAfterMountPath(afterKeybase, RuntimeGroup() == keybase1.RuntimeGroup_WINDOWSLIKE), 1106 }, nil 1107 } 1108 1109 func GetSafeFilename(filename string) (safeFilename string) { 1110 filename = filepath.Base(filename) 1111 if !utf8.ValidString(filename) { 1112 return url.PathEscape(filename) 1113 } 1114 for _, r := range filename { 1115 if unicode.Is(unicode.C, r) { 1116 safeFilename += url.PathEscape(string(r)) 1117 } else { 1118 safeFilename += string(r) 1119 } 1120 } 1121 return safeFilename 1122 } 1123 1124 func GetSafePath(path string) (safePath string) { 1125 dir, file := filepath.Split(path) 1126 return filepath.Join(dir, GetSafeFilename(file)) 1127 } 1128 1129 func FindFilePathWithNumberSuffix(parentDir string, basename string, useArbitraryName bool) (filePath string, err error) { 1130 ext := filepath.Ext(basename) 1131 if useArbitraryName { 1132 return filepath.Join(parentDir, strconv.FormatInt(time.Now().UnixNano(), 16)+ext), nil 1133 } 1134 destPath := filepath.Join(parentDir, basename) 1135 basename = basename[:len(basename)-len(ext)] 1136 // keep a sane limit on the loop. 1137 for suffix := 1; suffix < 100000; suffix++ { 1138 _, err := os.Stat(destPath) 1139 if os.IsNotExist(err) { 1140 break 1141 } 1142 if err != nil { 1143 return "", err 1144 } 1145 destPath = filepath.Join(parentDir, fmt.Sprintf("%s (%d)%s", basename, suffix, ext)) 1146 } 1147 // Could race but it should be rare enough so fine. 1148 return destPath, nil 1149 } 1150 1151 func JsonwStringArray(a []string) *jsonw.Wrapper { 1152 aj := jsonw.NewArray(len(a)) 1153 for i, s := range a { 1154 _ = aj.SetIndex(i, jsonw.NewString(s)) 1155 } 1156 return aj 1157 } 1158 1159 var throttleBatchClock = clockwork.NewRealClock() 1160 1161 type throttleBatchEmpty struct{} 1162 1163 func isEmptyThrottleData(arg interface{}) bool { 1164 _, ok := arg.(throttleBatchEmpty) 1165 return ok 1166 } 1167 1168 func ThrottleBatch(f func(interface{}), batcher func(interface{}, interface{}) interface{}, 1169 reset func() interface{}, delay time.Duration, leadingFire bool) (func(interface{}), func()) { 1170 var lock sync.Mutex 1171 var closeLock sync.Mutex 1172 var lastCalled time.Time 1173 var creation func(interface{}) 1174 hasStored := false 1175 scheduled := false 1176 stored := reset() 1177 cancelCh := make(chan struct{}) 1178 closed := false 1179 creation = func(arg interface{}) { 1180 lock.Lock() 1181 defer lock.Unlock() 1182 elapsed := throttleBatchClock.Since(lastCalled) 1183 isEmpty := isEmptyThrottleData(arg) 1184 leading := leadingFire || hasStored 1185 if !isEmpty { 1186 stored = batcher(stored, arg) 1187 hasStored = true 1188 } 1189 if elapsed > delay && (!isEmpty || hasStored) && leading { 1190 f(stored) 1191 stored = reset() 1192 hasStored = false 1193 lastCalled = throttleBatchClock.Now() 1194 } else if !scheduled && !isEmpty { 1195 scheduled = true 1196 go func() { 1197 select { 1198 case <-throttleBatchClock.After(delay - elapsed): 1199 lock.Lock() 1200 scheduled = false 1201 lock.Unlock() 1202 creation(throttleBatchEmpty{}) 1203 case <-cancelCh: 1204 return 1205 } 1206 }() 1207 } 1208 } 1209 return creation, func() { 1210 closeLock.Lock() 1211 defer closeLock.Unlock() 1212 if closed { 1213 return 1214 } 1215 closed = true 1216 close(cancelCh) 1217 } 1218 } 1219 1220 // Format a proof for web-of-trust. Does not support all proof types. 1221 func NewWotProof(proofType keybase1.ProofType, key, value string) (res keybase1.WotProof, err error) { 1222 switch proofType { 1223 case keybase1.ProofType_TWITTER, keybase1.ProofType_GITHUB, keybase1.ProofType_REDDIT, 1224 keybase1.ProofType_COINBASE, keybase1.ProofType_HACKERNEWS, keybase1.ProofType_FACEBOOK, 1225 keybase1.ProofType_GENERIC_SOCIAL, keybase1.ProofType_ROOTER: 1226 return keybase1.WotProof{ 1227 ProofType: proofType, 1228 Name: key, 1229 Username: value, 1230 }, nil 1231 case keybase1.ProofType_GENERIC_WEB_SITE: 1232 return keybase1.WotProof{ 1233 ProofType: proofType, 1234 Protocol: key, 1235 Hostname: value, 1236 }, nil 1237 case keybase1.ProofType_DNS: 1238 return keybase1.WotProof{ 1239 ProofType: proofType, 1240 Protocol: key, 1241 Domain: value, 1242 }, nil 1243 default: 1244 return res, fmt.Errorf("unexpected proof type: %v", proofType) 1245 } 1246 } 1247 1248 // Format a web-of-trust proof for gui display. 1249 func NewWotProofUI(mctx MetaContext, proof keybase1.WotProof) (res keybase1.WotProofUI, err error) { 1250 iconKey := ProofIconKey(mctx, proof.ProofType, proof.Name) 1251 res = keybase1.WotProofUI{ 1252 SiteIcon: MakeProofIcons(mctx, iconKey, ProofIconTypeSmall, 16), 1253 SiteIconDarkmode: MakeProofIcons(mctx, iconKey, ProofIconTypeSmallDarkmode, 16), 1254 } 1255 switch proof.ProofType { 1256 case keybase1.ProofType_TWITTER, keybase1.ProofType_GITHUB, keybase1.ProofType_REDDIT, 1257 keybase1.ProofType_COINBASE, keybase1.ProofType_HACKERNEWS, keybase1.ProofType_FACEBOOK, 1258 keybase1.ProofType_GENERIC_SOCIAL, keybase1.ProofType_ROOTER: 1259 res.Type = proof.Name 1260 res.Value = proof.Username 1261 case keybase1.ProofType_GENERIC_WEB_SITE: 1262 res.Type = proof.Protocol 1263 res.Value = proof.Hostname 1264 case keybase1.ProofType_DNS: 1265 res.Type = "dns" 1266 res.Value = proof.Domain 1267 default: 1268 return res, fmt.Errorf("unexpected proof type: %v", proof.ProofType) 1269 } 1270 return res, nil 1271 } 1272 1273 func ProofIconKey(mctx MetaContext, proofType keybase1.ProofType, genericKeyAndFallback string) (iconKey string) { 1274 switch proofType { 1275 case keybase1.ProofType_TWITTER: 1276 return "twitter" 1277 case keybase1.ProofType_GITHUB: 1278 return "github" 1279 case keybase1.ProofType_REDDIT: 1280 return "reddit" 1281 case keybase1.ProofType_HACKERNEWS: 1282 return "hackernews" 1283 case keybase1.ProofType_FACEBOOK: 1284 return "facebook" 1285 case keybase1.ProofType_GENERIC_SOCIAL: 1286 serviceType := mctx.G().GetProofServices().GetServiceType(mctx.Ctx(), genericKeyAndFallback) 1287 if serviceType != nil { 1288 return serviceType.GetLogoKey() 1289 } 1290 return genericKeyAndFallback 1291 case keybase1.ProofType_GENERIC_WEB_SITE, keybase1.ProofType_DNS: 1292 return "web" 1293 default: 1294 return genericKeyAndFallback 1295 } 1296 }