user.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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. passwd, err := os.Open(passwdPath)
  172. if err != nil {
  173. passwd = nil
  174. } else {
  175. defer passwd.Close()
  176. }
  177. group, err := os.Open(groupPath)
  178. if err != nil {
  179. group = nil
  180. } else {
  181. defer group.Close()
  182. }
  183. return GetExecUser(userSpec, defaults, passwd, group)
  184. }
  185. // GetExecUser parses a user specification string (using the passwd and group
  186. // readers as sources for /etc/passwd and /etc/group data, respectively). In
  187. // the case of blank fields or missing data from the sources, the values in
  188. // defaults is used.
  189. //
  190. // GetExecUser will return an error if a user or group literal could not be
  191. // found in any entry in passwd and group respectively.
  192. //
  193. // Examples of valid user specifications are:
  194. // * ""
  195. // * "user"
  196. // * "uid"
  197. // * "user:group"
  198. // * "uid:gid
  199. // * "user:gid"
  200. // * "uid:group"
  201. //
  202. // It should be noted that if you specify a numeric user or group id, they will
  203. // not be evaluated as usernames (only the metadata will be filled). So attempting
  204. // to parse a user with user.Name = "1337" will produce the user with a UID of
  205. // 1337.
  206. func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
  207. if defaults == nil {
  208. defaults = new(ExecUser)
  209. }
  210. // Copy over defaults.
  211. user := &ExecUser{
  212. Uid: defaults.Uid,
  213. Gid: defaults.Gid,
  214. Sgids: defaults.Sgids,
  215. Home: defaults.Home,
  216. }
  217. // Sgids slice *cannot* be nil.
  218. if user.Sgids == nil {
  219. user.Sgids = []int{}
  220. }
  221. // Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
  222. var userArg, groupArg string
  223. parseLine(userSpec, &userArg, &groupArg)
  224. // Convert userArg and groupArg to be numeric, so we don't have to execute
  225. // Atoi *twice* for each iteration over lines.
  226. uidArg, uidErr := strconv.Atoi(userArg)
  227. gidArg, gidErr := strconv.Atoi(groupArg)
  228. // Find the matching user.
  229. users, err := ParsePasswdFilter(passwd, func(u User) bool {
  230. if userArg == "" {
  231. // Default to current state of the user.
  232. return u.Uid == user.Uid
  233. }
  234. if uidErr == nil {
  235. // If the userArg is numeric, always treat it as a UID.
  236. return uidArg == u.Uid
  237. }
  238. return u.Name == userArg
  239. })
  240. // If we can't find the user, we have to bail.
  241. if err != nil && passwd != nil {
  242. if userArg == "" {
  243. userArg = strconv.Itoa(user.Uid)
  244. }
  245. return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
  246. }
  247. var matchedUserName string
  248. if len(users) > 0 {
  249. // First match wins, even if there's more than one matching entry.
  250. matchedUserName = users[0].Name
  251. user.Uid = users[0].Uid
  252. user.Gid = users[0].Gid
  253. user.Home = users[0].Home
  254. } else if userArg != "" {
  255. // If we can't find a user with the given username, the only other valid
  256. // option is if it's a numeric username with no associated entry in passwd.
  257. if uidErr != nil {
  258. // Not numeric.
  259. return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
  260. }
  261. user.Uid = uidArg
  262. // Must be inside valid uid range.
  263. if user.Uid < minId || user.Uid > maxId {
  264. return nil, ErrRange
  265. }
  266. // Okay, so it's numeric. We can just roll with this.
  267. }
  268. // On to the groups. If we matched a username, we need to do this because of
  269. // the supplementary group IDs.
  270. if groupArg != "" || matchedUserName != "" {
  271. groups, err := ParseGroupFilter(group, func(g Group) bool {
  272. // If the group argument isn't explicit, we'll just search for it.
  273. if groupArg == "" {
  274. // Check if user is a member of this group.
  275. for _, u := range g.List {
  276. if u == matchedUserName {
  277. return true
  278. }
  279. }
  280. return false
  281. }
  282. if gidErr == nil {
  283. // If the groupArg is numeric, always treat it as a GID.
  284. return gidArg == g.Gid
  285. }
  286. return g.Name == groupArg
  287. })
  288. if err != nil && group != nil {
  289. return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
  290. }
  291. // Only start modifying user.Gid if it is in explicit form.
  292. if groupArg != "" {
  293. if len(groups) > 0 {
  294. // First match wins, even if there's more than one matching entry.
  295. user.Gid = groups[0].Gid
  296. } else if groupArg != "" {
  297. // If we can't find a group with the given name, the only other valid
  298. // option is if it's a numeric group name with no associated entry in group.
  299. if gidErr != nil {
  300. // Not numeric.
  301. return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
  302. }
  303. user.Gid = gidArg
  304. // Must be inside valid gid range.
  305. if user.Gid < minId || user.Gid > maxId {
  306. return nil, ErrRange
  307. }
  308. // Okay, so it's numeric. We can just roll with this.
  309. }
  310. } else if len(groups) > 0 {
  311. // Supplementary group ids only make sense if in the implicit form.
  312. user.Sgids = make([]int, len(groups))
  313. for i, group := range groups {
  314. user.Sgids[i] = group.Gid
  315. }
  316. }
  317. }
  318. return user, nil
  319. }
  320. // GetAdditionalGroups looks up a list of groups by name or group id
  321. // against the given /etc/group formatted data. If a group name cannot
  322. // be found, an error will be returned. If a group id cannot be found,
  323. // or the given group data is nil, the id will be returned as-is
  324. // provided it is in the legal range.
  325. func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
  326. var groups = []Group{}
  327. if group != nil {
  328. var err error
  329. groups, err = ParseGroupFilter(group, func(g Group) bool {
  330. for _, ag := range additionalGroups {
  331. if g.Name == ag || strconv.Itoa(g.Gid) == ag {
  332. return true
  333. }
  334. }
  335. return false
  336. })
  337. if err != nil {
  338. return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
  339. }
  340. }
  341. gidMap := make(map[int]struct{})
  342. for _, ag := range additionalGroups {
  343. var found bool
  344. for _, g := range groups {
  345. // if we found a matched group either by name or gid, take the
  346. // first matched as correct
  347. if g.Name == ag || strconv.Itoa(g.Gid) == ag {
  348. if _, ok := gidMap[g.Gid]; !ok {
  349. gidMap[g.Gid] = struct{}{}
  350. found = true
  351. break
  352. }
  353. }
  354. }
  355. // we asked for a group but didn't find it. let's check to see
  356. // if we wanted a numeric group
  357. if !found {
  358. gid, err := strconv.Atoi(ag)
  359. if err != nil {
  360. return nil, fmt.Errorf("Unable to find group %s", ag)
  361. }
  362. // Ensure gid is inside gid range.
  363. if gid < minId || gid > maxId {
  364. return nil, ErrRange
  365. }
  366. gidMap[gid] = struct{}{}
  367. }
  368. }
  369. gids := []int{}
  370. for gid := range gidMap {
  371. gids = append(gids, gid)
  372. }
  373. return gids, nil
  374. }
  375. // GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
  376. // that opens the groupPath given and gives it as an argument to
  377. // GetAdditionalGroups.
  378. func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
  379. group, err := os.Open(groupPath)
  380. if err == nil {
  381. defer group.Close()
  382. }
  383. return GetAdditionalGroups(additionalGroups, group)
  384. }