user.go 14 KB

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