user.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. package user
  2. import (
  3. "bufio"
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "strconv"
  10. "strings"
  11. )
  12. const (
  13. minID = 0
  14. maxID = 1<<31 - 1 // for 32-bit systems compatibility
  15. )
  16. var (
  17. // ErrNoPasswdEntries is returned if no matching entries were found in /etc/group.
  18. ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
  19. // ErrNoGroupEntries is returned if no matching entries were found in /etc/passwd.
  20. ErrNoGroupEntries = errors.New("no matching entries in group file")
  21. // ErrRange is returned if a UID or GID is outside of the valid range.
  22. ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minID, maxID)
  23. )
  24. type User struct {
  25. Name string
  26. Pass string
  27. Uid int
  28. Gid int
  29. Gecos string
  30. Home string
  31. Shell string
  32. }
  33. type Group struct {
  34. Name string
  35. Pass string
  36. Gid int
  37. List []string
  38. }
  39. // SubID represents an entry in /etc/sub{u,g}id
  40. type SubID struct {
  41. Name string
  42. SubID int64
  43. Count int64
  44. }
  45. // IDMap represents an entry in /proc/PID/{u,g}id_map
  46. type IDMap struct {
  47. ID int64
  48. ParentID int64
  49. Count int64
  50. }
  51. func parseLine(line []byte, v ...interface{}) {
  52. parseParts(bytes.Split(line, []byte(":")), v...)
  53. }
  54. func parseParts(parts [][]byte, v ...interface{}) {
  55. if len(parts) == 0 {
  56. return
  57. }
  58. for i, p := range parts {
  59. // Ignore cases where we don't have enough fields to populate the arguments.
  60. // Some configuration files like to misbehave.
  61. if len(v) <= i {
  62. break
  63. }
  64. // Use the type of the argument to figure out how to parse it, scanf() style.
  65. // This is legit.
  66. switch e := v[i].(type) {
  67. case *string:
  68. *e = string(p)
  69. case *int:
  70. // "numbers", with conversion errors ignored because of some misbehaving configuration files.
  71. *e, _ = strconv.Atoi(string(p))
  72. case *int64:
  73. *e, _ = strconv.ParseInt(string(p), 10, 64)
  74. case *[]string:
  75. // Comma-separated lists.
  76. if len(p) != 0 {
  77. *e = strings.Split(string(p), ",")
  78. } else {
  79. *e = []string{}
  80. }
  81. default:
  82. // Someone goof'd when writing code using this function. Scream so they can hear us.
  83. panic(fmt.Sprintf("parseLine only accepts {*string, *int, *int64, *[]string} as arguments! %#v is not a pointer!", e))
  84. }
  85. }
  86. }
  87. func ParsePasswdFile(path string) ([]User, error) {
  88. passwd, err := os.Open(path)
  89. if err != nil {
  90. return nil, err
  91. }
  92. defer passwd.Close()
  93. return ParsePasswd(passwd)
  94. }
  95. func ParsePasswd(passwd io.Reader) ([]User, error) {
  96. return ParsePasswdFilter(passwd, nil)
  97. }
  98. func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
  99. passwd, err := os.Open(path)
  100. if err != nil {
  101. return nil, err
  102. }
  103. defer passwd.Close()
  104. return ParsePasswdFilter(passwd, filter)
  105. }
  106. func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
  107. if r == nil {
  108. return nil, errors.New("nil source for passwd-formatted data")
  109. }
  110. var (
  111. s = bufio.NewScanner(r)
  112. out = []User{}
  113. )
  114. for s.Scan() {
  115. line := bytes.TrimSpace(s.Bytes())
  116. if len(line) == 0 {
  117. continue
  118. }
  119. // see: man 5 passwd
  120. // name:password:UID:GID:GECOS:directory:shell
  121. // Name:Pass:Uid:Gid:Gecos:Home:Shell
  122. // root:x:0:0:root:/root:/bin/bash
  123. // adm:x:3:4:adm:/var/adm:/bin/false
  124. p := User{}
  125. parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
  126. if filter == nil || filter(p) {
  127. out = append(out, p)
  128. }
  129. }
  130. if err := s.Err(); err != nil {
  131. return nil, err
  132. }
  133. return out, nil
  134. }
  135. func ParseGroupFile(path string) ([]Group, error) {
  136. group, err := os.Open(path)
  137. if err != nil {
  138. return nil, err
  139. }
  140. defer group.Close()
  141. return ParseGroup(group)
  142. }
  143. func ParseGroup(group io.Reader) ([]Group, error) {
  144. return ParseGroupFilter(group, nil)
  145. }
  146. func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
  147. group, err := os.Open(path)
  148. if err != nil {
  149. return nil, err
  150. }
  151. defer group.Close()
  152. return ParseGroupFilter(group, filter)
  153. }
  154. func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
  155. if r == nil {
  156. return nil, errors.New("nil source for group-formatted data")
  157. }
  158. rd := bufio.NewReader(r)
  159. out := []Group{}
  160. // Read the file line-by-line.
  161. for {
  162. var (
  163. isPrefix bool
  164. wholeLine []byte
  165. err error
  166. )
  167. // Read the next line. We do so in chunks (as much as reader's
  168. // buffer is able to keep), check if we read enough columns
  169. // already on each step and store final result in wholeLine.
  170. for {
  171. var line []byte
  172. line, isPrefix, err = rd.ReadLine()
  173. if err != nil {
  174. // We should return no error if EOF is reached
  175. // without a match.
  176. if err == io.EOF {
  177. err = nil
  178. }
  179. return out, err
  180. }
  181. // Simple common case: line is short enough to fit in a
  182. // single reader's buffer.
  183. if !isPrefix && len(wholeLine) == 0 {
  184. wholeLine = line
  185. break
  186. }
  187. wholeLine = append(wholeLine, line...)
  188. // Check if we read the whole line already.
  189. if !isPrefix {
  190. break
  191. }
  192. }
  193. // There's no spec for /etc/passwd or /etc/group, but we try to follow
  194. // the same rules as the glibc parser, which allows comments and blank
  195. // space at the beginning of a line.
  196. wholeLine = bytes.TrimSpace(wholeLine)
  197. if len(wholeLine) == 0 || wholeLine[0] == '#' {
  198. continue
  199. }
  200. // see: man 5 group
  201. // group_name:password:GID:user_list
  202. // Name:Pass:Gid:List
  203. // root:x:0:root
  204. // adm:x:4:root,adm,daemon
  205. p := Group{}
  206. parseLine(wholeLine, &p.Name, &p.Pass, &p.Gid, &p.List)
  207. if filter == nil || filter(p) {
  208. out = append(out, p)
  209. }
  210. }
  211. }
  212. type ExecUser struct {
  213. Uid int
  214. Gid int
  215. Sgids []int
  216. Home string
  217. }
  218. // GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
  219. // given file paths and uses that data as the arguments to GetExecUser. If the
  220. // files cannot be opened for any reason, the error is ignored and a nil
  221. // io.Reader is passed instead.
  222. func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
  223. var passwd, group io.Reader
  224. if passwdFile, err := os.Open(passwdPath); err == nil {
  225. passwd = passwdFile
  226. defer passwdFile.Close()
  227. }
  228. if groupFile, err := os.Open(groupPath); err == nil {
  229. group = groupFile
  230. defer groupFile.Close()
  231. }
  232. return GetExecUser(userSpec, defaults, passwd, group)
  233. }
  234. // GetExecUser parses a user specification string (using the passwd and group
  235. // readers as sources for /etc/passwd and /etc/group data, respectively). In
  236. // the case of blank fields or missing data from the sources, the values in
  237. // defaults is used.
  238. //
  239. // GetExecUser will return an error if a user or group literal could not be
  240. // found in any entry in passwd and group respectively.
  241. //
  242. // Examples of valid user specifications are:
  243. // - ""
  244. // - "user"
  245. // - "uid"
  246. // - "user:group"
  247. // - "uid:gid
  248. // - "user:gid"
  249. // - "uid:group"
  250. //
  251. // It should be noted that if you specify a numeric user or group id, they will
  252. // not be evaluated as usernames (only the metadata will be filled). So attempting
  253. // to parse a user with user.Name = "1337" will produce the user with a UID of
  254. // 1337.
  255. func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
  256. if defaults == nil {
  257. defaults = new(ExecUser)
  258. }
  259. // Copy over defaults.
  260. user := &ExecUser{
  261. Uid: defaults.Uid,
  262. Gid: defaults.Gid,
  263. Sgids: defaults.Sgids,
  264. Home: defaults.Home,
  265. }
  266. // Sgids slice *cannot* be nil.
  267. if user.Sgids == nil {
  268. user.Sgids = []int{}
  269. }
  270. // Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
  271. var userArg, groupArg string
  272. parseLine([]byte(userSpec), &userArg, &groupArg)
  273. // Convert userArg and groupArg to be numeric, so we don't have to execute
  274. // Atoi *twice* for each iteration over lines.
  275. uidArg, uidErr := strconv.Atoi(userArg)
  276. gidArg, gidErr := strconv.Atoi(groupArg)
  277. // Find the matching user.
  278. users, err := ParsePasswdFilter(passwd, func(u User) bool {
  279. if userArg == "" {
  280. // Default to current state of the user.
  281. return u.Uid == user.Uid
  282. }
  283. if uidErr == nil {
  284. // If the userArg is numeric, always treat it as a UID.
  285. return uidArg == u.Uid
  286. }
  287. return u.Name == userArg
  288. })
  289. // If we can't find the user, we have to bail.
  290. if err != nil && passwd != nil {
  291. if userArg == "" {
  292. userArg = strconv.Itoa(user.Uid)
  293. }
  294. return nil, fmt.Errorf("unable to find user %s: %w", userArg, err)
  295. }
  296. var matchedUserName string
  297. if len(users) > 0 {
  298. // First match wins, even if there's more than one matching entry.
  299. matchedUserName = users[0].Name
  300. user.Uid = users[0].Uid
  301. user.Gid = users[0].Gid
  302. user.Home = users[0].Home
  303. } else if userArg != "" {
  304. // If we can't find a user with the given username, the only other valid
  305. // option is if it's a numeric username with no associated entry in passwd.
  306. if uidErr != nil {
  307. // Not numeric.
  308. return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries)
  309. }
  310. user.Uid = uidArg
  311. // Must be inside valid uid range.
  312. if user.Uid < minID || user.Uid > maxID {
  313. return nil, ErrRange
  314. }
  315. // Okay, so it's numeric. We can just roll with this.
  316. }
  317. // On to the groups. If we matched a username, we need to do this because of
  318. // the supplementary group IDs.
  319. if groupArg != "" || matchedUserName != "" {
  320. groups, err := ParseGroupFilter(group, func(g Group) bool {
  321. // If the group argument isn't explicit, we'll just search for it.
  322. if groupArg == "" {
  323. // Check if user is a member of this group.
  324. for _, u := range g.List {
  325. if u == matchedUserName {
  326. return true
  327. }
  328. }
  329. return false
  330. }
  331. if gidErr == nil {
  332. // If the groupArg is numeric, always treat it as a GID.
  333. return gidArg == g.Gid
  334. }
  335. return g.Name == groupArg
  336. })
  337. if err != nil && group != nil {
  338. return nil, fmt.Errorf("unable to find groups for spec %v: %w", matchedUserName, err)
  339. }
  340. // Only start modifying user.Gid if it is in explicit form.
  341. if groupArg != "" {
  342. if len(groups) > 0 {
  343. // First match wins, even if there's more than one matching entry.
  344. user.Gid = groups[0].Gid
  345. } else {
  346. // If we can't find a group with the given name, the only other valid
  347. // option is if it's a numeric group name with no associated entry in group.
  348. if gidErr != nil {
  349. // Not numeric.
  350. return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries)
  351. }
  352. user.Gid = gidArg
  353. // Must be inside valid gid range.
  354. if user.Gid < minID || user.Gid > maxID {
  355. return nil, ErrRange
  356. }
  357. // Okay, so it's numeric. We can just roll with this.
  358. }
  359. } else if len(groups) > 0 {
  360. // Supplementary group ids only make sense if in the implicit form.
  361. user.Sgids = make([]int, len(groups))
  362. for i, group := range groups {
  363. user.Sgids[i] = group.Gid
  364. }
  365. }
  366. }
  367. return user, nil
  368. }
  369. // GetAdditionalGroups looks up a list of groups by name or group id
  370. // against the given /etc/group formatted data. If a group name cannot
  371. // be found, an error will be returned. If a group id cannot be found,
  372. // or the given group data is nil, the id will be returned as-is
  373. // provided it is in the legal range.
  374. func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
  375. groups := []Group{}
  376. if group != nil {
  377. var err error
  378. groups, err = ParseGroupFilter(group, func(g Group) bool {
  379. for _, ag := range additionalGroups {
  380. if g.Name == ag || strconv.Itoa(g.Gid) == ag {
  381. return true
  382. }
  383. }
  384. return false
  385. })
  386. if err != nil {
  387. return nil, fmt.Errorf("Unable to find additional groups %v: %w", additionalGroups, err)
  388. }
  389. }
  390. gidMap := make(map[int]struct{})
  391. for _, ag := range additionalGroups {
  392. var found bool
  393. for _, g := range groups {
  394. // if we found a matched group either by name or gid, take the
  395. // first matched as correct
  396. if g.Name == ag || strconv.Itoa(g.Gid) == ag {
  397. if _, ok := gidMap[g.Gid]; !ok {
  398. gidMap[g.Gid] = struct{}{}
  399. found = true
  400. break
  401. }
  402. }
  403. }
  404. // we asked for a group but didn't find it. let's check to see
  405. // if we wanted a numeric group
  406. if !found {
  407. gid, err := strconv.ParseInt(ag, 10, 64)
  408. if err != nil {
  409. // Not a numeric ID either.
  410. return nil, fmt.Errorf("Unable to find group %s: %w", ag, ErrNoGroupEntries)
  411. }
  412. // Ensure gid is inside gid range.
  413. if gid < minID || gid > maxID {
  414. return nil, ErrRange
  415. }
  416. gidMap[int(gid)] = struct{}{}
  417. }
  418. }
  419. gids := []int{}
  420. for gid := range gidMap {
  421. gids = append(gids, gid)
  422. }
  423. return gids, nil
  424. }
  425. // GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
  426. // that opens the groupPath given and gives it as an argument to
  427. // GetAdditionalGroups.
  428. func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
  429. var group io.Reader
  430. if groupFile, err := os.Open(groupPath); err == nil {
  431. group = groupFile
  432. defer groupFile.Close()
  433. }
  434. return GetAdditionalGroups(additionalGroups, group)
  435. }
  436. func ParseSubIDFile(path string) ([]SubID, error) {
  437. subid, err := os.Open(path)
  438. if err != nil {
  439. return nil, err
  440. }
  441. defer subid.Close()
  442. return ParseSubID(subid)
  443. }
  444. func ParseSubID(subid io.Reader) ([]SubID, error) {
  445. return ParseSubIDFilter(subid, nil)
  446. }
  447. func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
  448. subid, err := os.Open(path)
  449. if err != nil {
  450. return nil, err
  451. }
  452. defer subid.Close()
  453. return ParseSubIDFilter(subid, filter)
  454. }
  455. func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
  456. if r == nil {
  457. return nil, errors.New("nil source for subid-formatted data")
  458. }
  459. var (
  460. s = bufio.NewScanner(r)
  461. out = []SubID{}
  462. )
  463. for s.Scan() {
  464. line := bytes.TrimSpace(s.Bytes())
  465. if len(line) == 0 {
  466. continue
  467. }
  468. // see: man 5 subuid
  469. p := SubID{}
  470. parseLine(line, &p.Name, &p.SubID, &p.Count)
  471. if filter == nil || filter(p) {
  472. out = append(out, p)
  473. }
  474. }
  475. if err := s.Err(); err != nil {
  476. return nil, err
  477. }
  478. return out, nil
  479. }
  480. func ParseIDMapFile(path string) ([]IDMap, error) {
  481. r, err := os.Open(path)
  482. if err != nil {
  483. return nil, err
  484. }
  485. defer r.Close()
  486. return ParseIDMap(r)
  487. }
  488. func ParseIDMap(r io.Reader) ([]IDMap, error) {
  489. return ParseIDMapFilter(r, nil)
  490. }
  491. func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) {
  492. r, err := os.Open(path)
  493. if err != nil {
  494. return nil, err
  495. }
  496. defer r.Close()
  497. return ParseIDMapFilter(r, filter)
  498. }
  499. func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
  500. if r == nil {
  501. return nil, errors.New("nil source for idmap-formatted data")
  502. }
  503. var (
  504. s = bufio.NewScanner(r)
  505. out = []IDMap{}
  506. )
  507. for s.Scan() {
  508. line := bytes.TrimSpace(s.Bytes())
  509. if len(line) == 0 {
  510. continue
  511. }
  512. // see: man 7 user_namespaces
  513. p := IDMap{}
  514. parseParts(bytes.Fields(line), &p.ID, &p.ParentID, &p.Count)
  515. if filter == nil || filter(p) {
  516. out = append(out, p)
  517. }
  518. }
  519. if err := s.Err(); err != nil {
  520. return nil, err
  521. }
  522. return out, nil
  523. }