passwd.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package utils
  3. import (
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "os/user"
  8. "runtime"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "howett.net/plist"
  13. )
  14. var _ = fmt.Print
  15. type PasswdEntry struct {
  16. Username, Pass, Uid, Gid, Gecos, Home, Shell string
  17. }
  18. func ParsePasswdLine(line string) (PasswdEntry, error) {
  19. parts := strings.Split(line, ":")
  20. if len(parts) == 7 {
  21. return PasswdEntry{parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6]}, nil
  22. }
  23. return PasswdEntry{}, fmt.Errorf("passwd line has %d colon delimited fields instead of 7", len(parts))
  24. }
  25. func ParsePasswdDatabase(raw string) (ans map[string]PasswdEntry) {
  26. scanner := NewLineScanner(raw)
  27. ans = make(map[string]PasswdEntry)
  28. for scanner.Scan() {
  29. line := scanner.Text()
  30. if entry, e := ParsePasswdLine(line); e == nil {
  31. ans[entry.Uid] = entry
  32. }
  33. }
  34. return ans
  35. }
  36. func ParsePasswdFile(path string) (ans map[string]PasswdEntry, err error) {
  37. raw, err := os.ReadFile(path)
  38. if err != nil {
  39. return nil, err
  40. }
  41. return ParsePasswdDatabase(UnsafeBytesToString(raw)), nil
  42. }
  43. var passwd_err error
  44. var passwd_database = sync.OnceValue(func() (ans map[string]PasswdEntry) {
  45. ans, passwd_err = ParsePasswdFile("/etc/passwd")
  46. return
  47. })
  48. func PwdEntryForUid(uid string) (ans PasswdEntry, err error) {
  49. pwd := passwd_database()
  50. if passwd_err != nil {
  51. return ans, passwd_err
  52. }
  53. ans, found := pwd[uid]
  54. if !found {
  55. return ans, fmt.Errorf("No user matching the UID: %#v found", uid)
  56. }
  57. return ans, nil
  58. }
  59. func parse_dscl_data(raw []byte) (ans map[string]PasswdEntry, err error) {
  60. var pd []any
  61. _, err = plist.Unmarshal(raw, &pd)
  62. if err != nil {
  63. return
  64. }
  65. ans = make(map[string]PasswdEntry, 256)
  66. for _, entry := range pd {
  67. if e, ok := entry.(map[string]any); ok {
  68. item := PasswdEntry{}
  69. for key, a := range e {
  70. array, ok := a.([]any)
  71. if !ok || len(array) == 0 || !strings.HasPrefix(key, "dsAttrTypeNative:") {
  72. continue
  73. }
  74. _, key, _ = strings.Cut(key, ":")
  75. if val, ok := array[0].(string); ok {
  76. switch key {
  77. case "uid":
  78. item.Uid = val
  79. case "gid":
  80. item.Gid = val
  81. case "home":
  82. item.Home = val
  83. case "name":
  84. item.Username = val
  85. case "realname":
  86. item.Gecos = val
  87. case "shell":
  88. item.Shell = val
  89. }
  90. }
  91. }
  92. ans[item.Uid] = item
  93. }
  94. }
  95. return
  96. }
  97. var dscl_error error
  98. var dscl_user_database = sync.OnceValue(func() map[string]PasswdEntry {
  99. c := exec.Command("/usr/bin/dscl", "-plist", ".", "-readall", "/Users", "uid", "gid", "name", "realname", "home", "shell")
  100. raw, err := c.Output()
  101. if err != nil {
  102. dscl_error = err
  103. return nil
  104. }
  105. ans, err := parse_dscl_data(raw)
  106. if err != nil {
  107. dscl_error = err
  108. return nil
  109. }
  110. return ans
  111. })
  112. func LoginShellForUser(u *user.User) (ans string, err error) {
  113. var db map[string]PasswdEntry
  114. switch runtime.GOOS {
  115. case "darwin":
  116. db = dscl_user_database()
  117. err = dscl_error
  118. default:
  119. db = passwd_database()
  120. err = passwd_err
  121. }
  122. if err != nil {
  123. return
  124. }
  125. if rec, found := db[u.Uid]; found {
  126. return rec.Shell, nil
  127. }
  128. return ans, fmt.Errorf("No user record available for user with UID: %#v", u.Uid)
  129. }
  130. func CurrentUser() (ans *user.User, err error) {
  131. ans, err = user.Current()
  132. if err != nil && runtime.GOOS == "darwin" {
  133. uid := strconv.Itoa(os.Geteuid())
  134. db := dscl_user_database()
  135. if dscl_error != nil {
  136. err = dscl_error
  137. return
  138. }
  139. if rec, found := db[uid]; found {
  140. u := user.User{Uid: uid, Gid: rec.Gid, Username: rec.Username, Name: rec.Gecos, HomeDir: rec.Home}
  141. ans = &u
  142. err = nil
  143. } else {
  144. err = fmt.Errorf("Could not find the current uid: %d in the DSCL user database", os.Geteuid())
  145. }
  146. }
  147. return
  148. }
  149. func LoginShellForCurrentUser() (ans string, err error) {
  150. u, err := CurrentUser()
  151. if err != nil {
  152. return ans, err
  153. }
  154. return LoginShellForUser(u)
  155. }