@@ -69,23 +69,36 @@ func parseLine(line string, v ...interface{}) {
6969 }
7070}
7171
72- func ParsePasswd () ([]* User , error ) {
73- return ParsePasswdFilter (nil )
72+ func ParsePasswdFile (path string ) ([]User , error ) {
73+ passwd , err := os .Open (path )
74+ if err != nil {
75+ return nil , err
76+ }
77+ defer passwd .Close ()
78+ return ParsePasswd (passwd )
79+ }
80+
81+ func ParsePasswd (passwd io.Reader ) ([]User , error ) {
82+ return ParsePasswdFilter (passwd , nil )
7483}
7584
76- func ParsePasswdFilter ( filter func (* User ) bool ) ([]* User , error ) {
77- f , err := os .Open ("/etc/passwd" )
85+ func ParsePasswdFileFilter ( path string , filter func (User ) bool ) ([]User , error ) {
86+ passwd , err := os .Open (path )
7887 if err != nil {
7988 return nil , err
8089 }
81- defer f .Close ()
82- return parsePasswdFile ( f , filter )
90+ defer passwd .Close ()
91+ return ParsePasswdFilter ( passwd , filter )
8392}
8493
85- func parsePasswdFile (r io.Reader , filter func (* User ) bool ) ([]* User , error ) {
94+ func ParsePasswdFilter (r io.Reader , filter func (User ) bool ) ([]User , error ) {
95+ if r == nil {
96+ return nil , fmt .Errorf ("nil source for passwd-formatted data" )
97+ }
98+
8699 var (
87100 s = bufio .NewScanner (r )
88- out = []* User {}
101+ out = []User {}
89102 )
90103
91104 for s .Scan () {
@@ -103,7 +116,7 @@ func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
103116 // Name:Pass:Uid:Gid:Gecos:Home:Shell
104117 // root:x:0:0:root:/root:/bin/bash
105118 // adm:x:3:4:adm:/var/adm:/bin/false
106- p := & User {}
119+ p := User {}
107120 parseLine (
108121 text ,
109122 & p .Name , & p .Pass , & p .Uid , & p .Gid , & p .Gecos , & p .Home , & p .Shell ,
@@ -117,23 +130,36 @@ func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
117130 return out , nil
118131}
119132
120- func ParseGroup () ([]* Group , error ) {
121- return ParseGroupFilter (nil )
133+ func ParseGroupFile (path string ) ([]Group , error ) {
134+ group , err := os .Open (path )
135+ if err != nil {
136+ return nil , err
137+ }
138+ defer group .Close ()
139+ return ParseGroup (group )
140+ }
141+
142+ func ParseGroup (group io.Reader ) ([]Group , error ) {
143+ return ParseGroupFilter (group , nil )
122144}
123145
124- func ParseGroupFilter ( filter func (* Group ) bool ) ([]* Group , error ) {
125- f , err := os .Open ("/etc/group" )
146+ func ParseGroupFileFilter ( path string , filter func (Group ) bool ) ([]Group , error ) {
147+ group , err := os .Open (path )
126148 if err != nil {
127149 return nil , err
128150 }
129- defer f .Close ()
130- return parseGroupFile ( f , filter )
151+ defer group .Close ()
152+ return ParseGroupFilter ( group , filter )
131153}
132154
133- func parseGroupFile (r io.Reader , filter func (* Group ) bool ) ([]* Group , error ) {
155+ func ParseGroupFilter (r io.Reader , filter func (Group ) bool ) ([]Group , error ) {
156+ if r == nil {
157+ return nil , fmt .Errorf ("nil source for group-formatted data" )
158+ }
159+
134160 var (
135161 s = bufio .NewScanner (r )
136- out = []* Group {}
162+ out = []Group {}
137163 )
138164
139165 for s .Scan () {
@@ -151,7 +177,7 @@ func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
151177 // Name:Pass:Gid:List
152178 // root:x:0:root
153179 // adm:x:4:root,adm,daemon
154- p := & Group {}
180+ p := Group {}
155181 parseLine (
156182 text ,
157183 & p .Name , & p .Pass , & p .Gid , & p .List ,
@@ -165,94 +191,160 @@ func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
165191 return out , nil
166192}
167193
168- // Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, list of supplementary group IDs, and home directory, if available and/or applicable.
169- func GetUserGroupSupplementaryHome (userSpec string , defaultUid , defaultGid int , defaultHome string ) (int , int , []int , string , error ) {
170- var (
171- uid = defaultUid
172- gid = defaultGid
173- suppGids = []int {}
174- home = defaultHome
194+ type ExecUser struct {
195+ Uid , Gid int
196+ Sgids []int
197+ Home string
198+ }
175199
200+ // GetExecUserFile is a wrapper for GetExecUser. It reads data from each of the
201+ // given file paths and uses that data as the arguments to GetExecUser. If the
202+ // files cannot be opened for any reason, the error is ignored and a nil
203+ // io.Reader is passed instead.
204+ func GetExecUserFile (userSpec string , defaults * ExecUser , passwdPath , groupPath string ) (* ExecUser , error ) {
205+ passwd , err := os .Open (passwdPath )
206+ if err != nil {
207+ passwd = nil
208+ } else {
209+ defer passwd .Close ()
210+ }
211+
212+ group , err := os .Open (groupPath )
213+ if err != nil {
214+ group = nil
215+ } else {
216+ defer group .Close ()
217+ }
218+
219+ return GetExecUser (userSpec , defaults , passwd , group )
220+ }
221+
222+ // GetExecUser parses a user specification string (using the passwd and group
223+ // readers as sources for /etc/passwd and /etc/group data, respectively). In
224+ // the case of blank fields or missing data from the sources, the values in
225+ // defaults is used.
226+ //
227+ // GetExecUser will return an error if a user or group literal could not be
228+ // found in any entry in passwd and group respectively.
229+ //
230+ // Examples of valid user specifications are:
231+ // * ""
232+ // * "user"
233+ // * "uid"
234+ // * "user:group"
235+ // * "uid:gid
236+ // * "user:gid"
237+ // * "uid:group"
238+ func GetExecUser (userSpec string , defaults * ExecUser , passwd , group io.Reader ) (* ExecUser , error ) {
239+ var (
176240 userArg , groupArg string
241+ name string
177242 )
178243
244+ if defaults == nil {
245+ defaults = new (ExecUser )
246+ }
247+
248+ // Copy over defaults.
249+ user := & ExecUser {
250+ Uid : defaults .Uid ,
251+ Gid : defaults .Gid ,
252+ Sgids : defaults .Sgids ,
253+ Home : defaults .Home ,
254+ }
255+
256+ // Sgids slice *cannot* be nil.
257+ if user .Sgids == nil {
258+ user .Sgids = []int {}
259+ }
260+
179261 // allow for userArg to have either "user" syntax, or optionally "user:group" syntax
180262 parseLine (userSpec , & userArg , & groupArg )
181263
182- users , err := ParsePasswdFilter (func (u * User ) bool {
264+ users , err := ParsePasswdFilter (passwd , func (u User ) bool {
183265 if userArg == "" {
184- return u .Uid == uid
266+ return u .Uid == user . Uid
185267 }
186268 return u .Name == userArg || strconv .Itoa (u .Uid ) == userArg
187269 })
188- if err != nil && ! os . IsNotExist ( err ) {
270+ if err != nil && passwd != nil {
189271 if userArg == "" {
190- userArg = strconv .Itoa (uid )
272+ userArg = strconv .Itoa (user . Uid )
191273 }
192- return 0 , 0 , nil , "" , fmt .Errorf ("Unable to find user %v: %v" , userArg , err )
274+ return nil , fmt .Errorf ("Unable to find user %v: %v" , userArg , err )
193275 }
194276
195277 haveUser := users != nil && len (users ) > 0
196278 if haveUser {
197279 // if we found any user entries that matched our filter, let's take the first one as "correct"
198- uid = users [0 ].Uid
199- gid = users [0 ].Gid
200- home = users [0 ].Home
280+ name = users [0 ].Name
281+ user .Uid = users [0 ].Uid
282+ user .Gid = users [0 ].Gid
283+ user .Home = users [0 ].Home
201284 } else if userArg != "" {
202285 // we asked for a user but didn't find them... let's check to see if we wanted a numeric user
203- uid , err = strconv .Atoi (userArg )
286+ user . Uid , err = strconv .Atoi (userArg )
204287 if err != nil {
205288 // not numeric - we have to bail
206- return 0 , 0 , nil , "" , fmt .Errorf ("Unable to find user %v" , userArg )
289+ return nil , fmt .Errorf ("Unable to find user %v" , userArg )
207290 }
208- if uid < minId || uid > maxId {
209- return 0 , 0 , nil , "" , ErrRange
291+
292+ // Must be inside valid uid range.
293+ if user .Uid < minId || user .Uid > maxId {
294+ return nil , ErrRange
210295 }
211296
212297 // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
213298 }
214299
215- if groupArg != "" || (haveUser && users [0 ].Name != "" ) {
216- groups , err := ParseGroupFilter (func (g * Group ) bool {
300+ if groupArg != "" || name != "" {
301+ groups , err := ParseGroupFilter (group , func (g Group ) bool {
302+ // Explicit group format takes precedence.
217303 if groupArg != "" {
218304 return g .Name == groupArg || strconv .Itoa (g .Gid ) == groupArg
219305 }
306+
307+ // Check if user is a member.
220308 for _ , u := range g .List {
221- if u == users [ 0 ]. Name {
309+ if u == name {
222310 return true
223311 }
224312 }
313+
225314 return false
226315 })
227- if err != nil && ! os . IsNotExist ( err ) {
228- return 0 , 0 , nil , "" , fmt .Errorf ("Unable to find groups for user %v: %v" , users [0 ].Name , err )
316+ if err != nil && group != nil {
317+ return nil , fmt .Errorf ("Unable to find groups for user %v: %v" , users [0 ].Name , err )
229318 }
230319
231320 haveGroup := groups != nil && len (groups ) > 0
232321 if groupArg != "" {
233322 if haveGroup {
234323 // if we found any group entries that matched our filter, let's take the first one as "correct"
235- gid = groups [0 ].Gid
324+ user . Gid = groups [0 ].Gid
236325 } else {
237326 // we asked for a group but didn't find id... let's check to see if we wanted a numeric group
238- gid , err = strconv .Atoi (groupArg )
327+ user . Gid , err = strconv .Atoi (groupArg )
239328 if err != nil {
240329 // not numeric - we have to bail
241- return 0 , 0 , nil , "" , fmt .Errorf ("Unable to find group %v" , groupArg )
330+ return nil , fmt .Errorf ("Unable to find group %v" , groupArg )
242331 }
243- if gid < minId || gid > maxId {
244- return 0 , 0 , nil , "" , ErrRange
332+
333+ // Ensure gid is inside gid range.
334+ if user .Gid < minId || user .Gid > maxId {
335+ return nil , ErrRange
245336 }
246337
247338 // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
248339 }
249340 } else if haveGroup {
250- suppGids = make ([]int , len (groups ))
341+ // If implicit group format, fill supplementary gids.
342+ user .Sgids = make ([]int , len (groups ))
251343 for i , group := range groups {
252- suppGids [i ] = group .Gid
344+ user . Sgids [i ] = group .Gid
253345 }
254346 }
255347 }
256348
257- return uid , gid , suppGids , home , nil
349+ return user , nil
258350}
0 commit comments