|
@@ -0,0 +1,241 @@
|
|
|
|
+package user
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "bufio"
|
|
|
|
+ "fmt"
|
|
|
|
+ "io"
|
|
|
|
+ "os"
|
|
|
|
+ "strconv"
|
|
|
|
+ "strings"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+type User struct {
|
|
|
|
+ Name string
|
|
|
|
+ Pass string
|
|
|
|
+ Uid int
|
|
|
|
+ Gid int
|
|
|
|
+ Gecos string
|
|
|
|
+ Home string
|
|
|
|
+ Shell string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Group struct {
|
|
|
|
+ Name string
|
|
|
|
+ Pass string
|
|
|
|
+ Gid int
|
|
|
|
+ List []string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func parseLine(line string, v ...interface{}) {
|
|
|
|
+ if line == "" {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ parts := strings.Split(line, ":")
|
|
|
|
+ for i, p := range parts {
|
|
|
|
+ if len(v) <= i {
|
|
|
|
+ // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch e := v[i].(type) {
|
|
|
|
+ case *string:
|
|
|
|
+ // "root", "adm", "/bin/bash"
|
|
|
|
+ *e = p
|
|
|
|
+ case *int:
|
|
|
|
+ // "0", "4", "1000"
|
|
|
|
+ // ignore string to int conversion errors, for great "tolerance" of naughty configuration files
|
|
|
|
+ *e, _ = strconv.Atoi(p)
|
|
|
|
+ case *[]string:
|
|
|
|
+ // "", "root", "root,adm,daemon"
|
|
|
|
+ if p != "" {
|
|
|
|
+ *e = strings.Split(p, ",")
|
|
|
|
+ } else {
|
|
|
|
+ *e = []string{}
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ // panic, because this is a programming/logic error, not a runtime one
|
|
|
|
+ panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ParsePasswd() ([]*User, error) {
|
|
|
|
+ return ParsePasswdFilter(nil)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
|
|
|
|
+ f, err := os.Open("/etc/passwd")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ defer f.Close()
|
|
|
|
+ return parsePasswdFile(f, filter)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
|
|
|
|
+ var (
|
|
|
|
+ s = bufio.NewScanner(r)
|
|
|
|
+ out = []*User{}
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ for s.Scan() {
|
|
|
|
+ if err := s.Err(); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ text := strings.TrimSpace(s.Text())
|
|
|
|
+ if text == "" {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // see: man 5 passwd
|
|
|
|
+ // name:password:UID:GID:GECOS:directory:shell
|
|
|
|
+ // Name:Pass:Uid:Gid:Gecos:Home:Shell
|
|
|
|
+ // root:x:0:0:root:/root:/bin/bash
|
|
|
|
+ // adm:x:3:4:adm:/var/adm:/bin/false
|
|
|
|
+ p := &User{}
|
|
|
|
+ parseLine(
|
|
|
|
+ text,
|
|
|
|
+ &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ if filter == nil || filter(p) {
|
|
|
|
+ out = append(out, p)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return out, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ParseGroup() ([]*Group, error) {
|
|
|
|
+ return ParseGroupFilter(nil)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
|
|
|
|
+ f, err := os.Open("/etc/group")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ defer f.Close()
|
|
|
|
+ return parseGroupFile(f, filter)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
|
|
|
|
+ var (
|
|
|
|
+ s = bufio.NewScanner(r)
|
|
|
|
+ out = []*Group{}
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ for s.Scan() {
|
|
|
|
+ if err := s.Err(); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ text := s.Text()
|
|
|
|
+ if text == "" {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // see: man 5 group
|
|
|
|
+ // group_name:password:GID:user_list
|
|
|
|
+ // Name:Pass:Gid:List
|
|
|
|
+ // root:x:0:root
|
|
|
|
+ // adm:x:4:root,adm,daemon
|
|
|
|
+ p := &Group{}
|
|
|
|
+ parseLine(
|
|
|
|
+ text,
|
|
|
|
+ &p.Name, &p.Pass, &p.Gid, &p.List,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ if filter == nil || filter(p) {
|
|
|
|
+ out = append(out, p)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return out, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, and list of supplementary group IDs, if possible.
|
|
|
|
+func GetUserGroupSupplementary(userSpec string, defaultUid int, defaultGid int) (int, int, []int, error) {
|
|
|
|
+ var (
|
|
|
|
+ uid = defaultUid
|
|
|
|
+ gid = defaultGid
|
|
|
|
+ suppGids = []int{}
|
|
|
|
+
|
|
|
|
+ userArg, groupArg string
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ // allow for userArg to have either "user" syntax, or optionally "user:group" syntax
|
|
|
|
+ parseLine(userSpec, &userArg, &groupArg)
|
|
|
|
+
|
|
|
|
+ users, err := ParsePasswdFilter(func(u *User) bool {
|
|
|
|
+ if userArg == "" {
|
|
|
|
+ return u.Uid == uid
|
|
|
|
+ }
|
|
|
|
+ return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
|
|
|
|
+ })
|
|
|
|
+ if err != nil && !os.IsNotExist(err) {
|
|
|
|
+ if userArg == "" {
|
|
|
|
+ userArg = strconv.Itoa(uid)
|
|
|
|
+ }
|
|
|
|
+ return 0, 0, nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ haveUser := users != nil && len(users) > 0
|
|
|
|
+ if haveUser {
|
|
|
|
+ // if we found any user entries that matched our filter, let's take the first one as "correct"
|
|
|
|
+ uid = users[0].Uid
|
|
|
|
+ gid = users[0].Gid
|
|
|
|
+ } else if userArg != "" {
|
|
|
|
+ // we asked for a user but didn't find them... let's check to see if we wanted a numeric user
|
|
|
|
+ uid, err = strconv.Atoi(userArg)
|
|
|
|
+ if err != nil {
|
|
|
|
+ // not numeric - we have to bail
|
|
|
|
+ return 0, 0, nil, fmt.Errorf("Unable to find user %v", userArg)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if groupArg != "" || (haveUser && users[0].Name != "") {
|
|
|
|
+ groups, err := ParseGroupFilter(func(g *Group) bool {
|
|
|
|
+ if groupArg != "" {
|
|
|
|
+ return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
|
|
|
|
+ }
|
|
|
|
+ for _, u := range g.List {
|
|
|
|
+ if u == users[0].Name {
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false
|
|
|
|
+ })
|
|
|
|
+ if err != nil && !os.IsNotExist(err) {
|
|
|
|
+ return 0, 0, nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ haveGroup := groups != nil && len(groups) > 0
|
|
|
|
+ if groupArg != "" {
|
|
|
|
+ if haveGroup {
|
|
|
|
+ // if we found any group entries that matched our filter, let's take the first one as "correct"
|
|
|
|
+ gid = groups[0].Gid
|
|
|
|
+ } else {
|
|
|
|
+ // we asked for a group but didn't find id... let's check to see if we wanted a numeric group
|
|
|
|
+ gid, err = strconv.Atoi(groupArg)
|
|
|
|
+ if err != nil {
|
|
|
|
+ // not numeric - we have to bail
|
|
|
|
+ return 0, 0, nil, fmt.Errorf("Unable to find group %v", groupArg)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
|
|
|
|
+ }
|
|
|
|
+ } else if haveGroup {
|
|
|
|
+ suppGids = make([]int, len(groups))
|
|
|
|
+ for i, group := range groups {
|
|
|
|
+ suppGids[i] = group.Gid
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return uid, gid, suppGids, nil
|
|
|
|
+}
|