user.go 11 KB

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