go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/users/dscl.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package users 5 6 import ( 7 "io" 8 "regexp" 9 "strconv" 10 11 "github.com/rs/zerolog/log" 12 "go.mondoo.com/cnquery/providers/os/connection/shared" 13 ) 14 15 var USER_OSX_DSCL_REGEX = regexp.MustCompile(`(?m)^(\S*)\s*(.*)$`) 16 17 func ParseDsclListResult(input io.Reader) (map[string]string, error) { 18 content, err := io.ReadAll(input) 19 if err != nil { 20 return nil, err 21 } 22 23 userMap := make(map[string]string) 24 m := USER_OSX_DSCL_REGEX.FindAllStringSubmatch(string(content), -1) 25 for i := range m { 26 key := m[i][1] 27 value := m[i][2] 28 29 if len(key) > 0 { 30 userMap[key] = value 31 } 32 } 33 return userMap, nil 34 } 35 36 type OSXUserManager struct { 37 conn shared.Connection 38 } 39 40 func (s *OSXUserManager) Name() string { 41 return "macOS User Manager" 42 } 43 44 func (s *OSXUserManager) User(id string) (*User, error) { 45 users, err := s.List() 46 if err != nil { 47 return nil, err 48 } 49 50 return findUser(users, id) 51 } 52 53 // To retrieve all user information, we have two options: 54 // 55 // 1. fetch all users via `dscl . list /Users` 56 // 2. iterate over each user and fetch the data via 57 // dscl -q . -read /Users/nobody NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell 58 // 59 // This approach is not very effective since it requires O(n), there we use the option to fetch one 60 // value per list, which requires us to do 5 calls to fetch all information: 61 // dscl . -list /Users UserShell 62 // dscl . -list /Users UniqueID 63 // dscl . -list /Users NFSHomeDirectory 64 // dscl . -list /Users RecordName 65 // dscl . -list /Users RealName 66 func (s *OSXUserManager) List() ([]*User, error) { 67 users := make(map[string]*User) 68 69 // fetch all uids first 70 f, err := s.conn.RunCommand("dscl . -list /Users UniqueID") 71 if err != nil { 72 return nil, err 73 } 74 75 m, err := ParseDsclListResult(f.Stdout) 76 if err != nil { 77 return nil, err 78 } 79 for k := range m { 80 uid, err := strconv.ParseInt(m[k], 10, 0) 81 if err != nil { 82 log.Error().Err(err).Str("user", k).Msg("could not parse uid") 83 } 84 85 users[k] = &User{ 86 ID: m[k], 87 Name: k, 88 Uid: uid, 89 } 90 } 91 92 // fetch shells 93 f, err = s.conn.RunCommand("dscl . -list /Users UserShell") 94 if err != nil { 95 return nil, err 96 } 97 98 m, err = ParseDsclListResult(f.Stdout) 99 if err != nil { 100 return nil, err 101 } 102 for k := range m { 103 users[k].Shell = m[k] 104 } 105 106 // fetch home 107 f, err = s.conn.RunCommand("dscl . -list /Users NFSHomeDirectory") 108 if err != nil { 109 return nil, err 110 } 111 112 m, err = ParseDsclListResult(f.Stdout) 113 if err != nil { 114 return nil, err 115 } 116 for k := range m { 117 users[k].Home = m[k] 118 } 119 120 // fetch usernames 121 f, err = s.conn.RunCommand("dscl . -list /Users RealName") 122 if err != nil { 123 return nil, err 124 } 125 126 m, err = ParseDsclListResult(f.Stdout) 127 if err != nil { 128 return nil, err 129 } 130 for k := range m { 131 users[k].Description = m[k] 132 } 133 134 // fetch gid 135 f, err = s.conn.RunCommand("dscl . -list /Users PrimaryGroupID") 136 if err != nil { 137 return nil, err 138 } 139 140 m, err = ParseDsclListResult(f.Stdout) 141 if err != nil { 142 return nil, err 143 } 144 for k := range m { 145 gid, err := strconv.ParseInt(m[k], 10, 0) 146 if err != nil { 147 log.Error().Err(err).Str("user", k).Msg("could not parse gid") 148 } 149 users[k].Gid = gid 150 } 151 152 // convert map to slice 153 res := make([]*User, len(users)) 154 155 i := 0 156 for k := range users { 157 res[i] = users[k] 158 i++ 159 } 160 161 return res, nil 162 }