1 package user
2
3 import (
4 "bufio"
5 "bytes"
6 "errors"
7 "fmt"
8 "io"
9 "os"
10 "strconv"
11 "strings"
12 )
13
14 const (
15 minID = 0
16 maxID = 1<<31 - 1
17 )
18
19 var (
20
21 ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
22
23 ErrNoGroupEntries = errors.New("no matching entries in group file")
24
25 ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minID, maxID)
26 )
27
28 type User struct {
29 Name string
30 Pass string
31 Uid int
32 Gid int
33 Gecos string
34 Home string
35 Shell string
36 }
37
38 type Group struct {
39 Name string
40 Pass string
41 Gid int
42 List []string
43 }
44
45
46 type SubID struct {
47 Name string
48 SubID int64
49 Count int64
50 }
51
52
53 type IDMap struct {
54 ID int64
55 ParentID int64
56 Count int64
57 }
58
59 func parseLine(line []byte, v ...interface{}) {
60 parseParts(bytes.Split(line, []byte(":")), v...)
61 }
62
63 func parseParts(parts [][]byte, v ...interface{}) {
64 if len(parts) == 0 {
65 return
66 }
67
68 for i, p := range parts {
69
70
71 if len(v) <= i {
72 break
73 }
74
75
76
77 switch e := v[i].(type) {
78 case *string:
79 *e = string(p)
80 case *int:
81
82 *e, _ = strconv.Atoi(string(p))
83 case *int64:
84 *e, _ = strconv.ParseInt(string(p), 10, 64)
85 case *[]string:
86
87 if len(p) != 0 {
88 *e = strings.Split(string(p), ",")
89 } else {
90 *e = []string{}
91 }
92 default:
93
94 panic(fmt.Sprintf("parseLine only accepts {*string, *int, *int64, *[]string} as arguments! %#v is not a pointer!", e))
95 }
96 }
97 }
98
99 func ParsePasswdFile(path string) ([]User, error) {
100 passwd, err := os.Open(path)
101 if err != nil {
102 return nil, err
103 }
104 defer passwd.Close()
105 return ParsePasswd(passwd)
106 }
107
108 func ParsePasswd(passwd io.Reader) ([]User, error) {
109 return ParsePasswdFilter(passwd, nil)
110 }
111
112 func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
113 passwd, err := os.Open(path)
114 if err != nil {
115 return nil, err
116 }
117 defer passwd.Close()
118 return ParsePasswdFilter(passwd, filter)
119 }
120
121 func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
122 if r == nil {
123 return nil, errors.New("nil source for passwd-formatted data")
124 }
125
126 var (
127 s = bufio.NewScanner(r)
128 out = []User{}
129 )
130
131 for s.Scan() {
132 line := bytes.TrimSpace(s.Bytes())
133 if len(line) == 0 {
134 continue
135 }
136
137
138
139
140
141
142 p := User{}
143 parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
144
145 if filter == nil || filter(p) {
146 out = append(out, p)
147 }
148 }
149 if err := s.Err(); err != nil {
150 return nil, err
151 }
152
153 return out, nil
154 }
155
156 func ParseGroupFile(path string) ([]Group, error) {
157 group, err := os.Open(path)
158 if err != nil {
159 return nil, err
160 }
161
162 defer group.Close()
163 return ParseGroup(group)
164 }
165
166 func ParseGroup(group io.Reader) ([]Group, error) {
167 return ParseGroupFilter(group, nil)
168 }
169
170 func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
171 group, err := os.Open(path)
172 if err != nil {
173 return nil, err
174 }
175 defer group.Close()
176 return ParseGroupFilter(group, filter)
177 }
178
179 func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
180 if r == nil {
181 return nil, errors.New("nil source for group-formatted data")
182 }
183 rd := bufio.NewReader(r)
184 out := []Group{}
185
186
187 for {
188 var (
189 isPrefix bool
190 wholeLine []byte
191 err error
192 )
193
194
195
196
197 for {
198 var line []byte
199 line, isPrefix, err = rd.ReadLine()
200
201 if err != nil {
202
203
204 if err == io.EOF {
205 err = nil
206 }
207 return out, err
208 }
209
210
211
212 if !isPrefix && len(wholeLine) == 0 {
213 wholeLine = line
214 break
215 }
216
217 wholeLine = append(wholeLine, line...)
218
219
220 if !isPrefix {
221 break
222 }
223 }
224
225
226
227
228 wholeLine = bytes.TrimSpace(wholeLine)
229 if len(wholeLine) == 0 || wholeLine[0] == '#' {
230 continue
231 }
232
233
234
235
236
237
238 p := Group{}
239 parseLine(wholeLine, &p.Name, &p.Pass, &p.Gid, &p.List)
240
241 if filter == nil || filter(p) {
242 out = append(out, p)
243 }
244 }
245 }
246
247 type ExecUser struct {
248 Uid int
249 Gid int
250 Sgids []int
251 Home string
252 }
253
254
255
256
257
258 func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
259 var passwd, group io.Reader
260
261 if passwdFile, err := os.Open(passwdPath); err == nil {
262 passwd = passwdFile
263 defer passwdFile.Close()
264 }
265
266 if groupFile, err := os.Open(groupPath); err == nil {
267 group = groupFile
268 defer groupFile.Close()
269 }
270
271 return GetExecUser(userSpec, defaults, passwd, group)
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
296 if defaults == nil {
297 defaults = new(ExecUser)
298 }
299
300
301 user := &ExecUser{
302 Uid: defaults.Uid,
303 Gid: defaults.Gid,
304 Sgids: defaults.Sgids,
305 Home: defaults.Home,
306 }
307
308
309 if user.Sgids == nil {
310 user.Sgids = []int{}
311 }
312
313
314 var userArg, groupArg string
315 parseLine([]byte(userSpec), &userArg, &groupArg)
316
317
318
319 uidArg, uidErr := strconv.Atoi(userArg)
320 gidArg, gidErr := strconv.Atoi(groupArg)
321
322
323 users, err := ParsePasswdFilter(passwd, func(u User) bool {
324 if userArg == "" {
325
326 return u.Uid == user.Uid
327 }
328
329 if uidErr == nil {
330
331 return uidArg == u.Uid
332 }
333
334 return u.Name == userArg
335 })
336
337
338 if err != nil && passwd != nil {
339 if userArg == "" {
340 userArg = strconv.Itoa(user.Uid)
341 }
342 return nil, fmt.Errorf("unable to find user %s: %w", userArg, err)
343 }
344
345 var matchedUserName string
346 if len(users) > 0 {
347
348 matchedUserName = users[0].Name
349 user.Uid = users[0].Uid
350 user.Gid = users[0].Gid
351 user.Home = users[0].Home
352 } else if userArg != "" {
353
354
355
356 if uidErr != nil {
357
358 return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries)
359 }
360 user.Uid = uidArg
361
362
363 if user.Uid < minID || user.Uid > maxID {
364 return nil, ErrRange
365 }
366
367
368 }
369
370
371
372 if groupArg != "" || matchedUserName != "" {
373 groups, err := ParseGroupFilter(group, func(g Group) bool {
374
375 if groupArg == "" {
376
377 for _, u := range g.List {
378 if u == matchedUserName {
379 return true
380 }
381 }
382 return false
383 }
384
385 if gidErr == nil {
386
387 return gidArg == g.Gid
388 }
389
390 return g.Name == groupArg
391 })
392 if err != nil && group != nil {
393 return nil, fmt.Errorf("unable to find groups for spec %v: %w", matchedUserName, err)
394 }
395
396
397 if groupArg != "" {
398 if len(groups) > 0 {
399
400 user.Gid = groups[0].Gid
401 } else {
402
403
404
405 if gidErr != nil {
406
407 return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries)
408 }
409 user.Gid = gidArg
410
411
412 if user.Gid < minID || user.Gid > maxID {
413 return nil, ErrRange
414 }
415
416
417 }
418 } else if len(groups) > 0 {
419
420 user.Sgids = make([]int, len(groups))
421 for i, group := range groups {
422 user.Sgids[i] = group.Gid
423 }
424 }
425 }
426
427 return user, nil
428 }
429
430
431
432
433
434
435 func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
436 groups := []Group{}
437 if group != nil {
438 var err error
439 groups, err = ParseGroupFilter(group, func(g Group) bool {
440 for _, ag := range additionalGroups {
441 if g.Name == ag || strconv.Itoa(g.Gid) == ag {
442 return true
443 }
444 }
445 return false
446 })
447 if err != nil {
448 return nil, fmt.Errorf("Unable to find additional groups %v: %w", additionalGroups, err)
449 }
450 }
451
452 gidMap := make(map[int]struct{})
453 for _, ag := range additionalGroups {
454 var found bool
455 for _, g := range groups {
456
457
458 if g.Name == ag || strconv.Itoa(g.Gid) == ag {
459 if _, ok := gidMap[g.Gid]; !ok {
460 gidMap[g.Gid] = struct{}{}
461 found = true
462 break
463 }
464 }
465 }
466
467
468 if !found {
469 gid, err := strconv.ParseInt(ag, 10, 64)
470 if err != nil {
471
472 return nil, fmt.Errorf("Unable to find group %s: %w", ag, ErrNoGroupEntries)
473 }
474
475 if gid < minID || gid > maxID {
476 return nil, ErrRange
477 }
478 gidMap[int(gid)] = struct{}{}
479 }
480 }
481 gids := []int{}
482 for gid := range gidMap {
483 gids = append(gids, gid)
484 }
485 return gids, nil
486 }
487
488
489
490
491 func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
492 var group io.Reader
493
494 if groupFile, err := os.Open(groupPath); err == nil {
495 group = groupFile
496 defer groupFile.Close()
497 }
498 return GetAdditionalGroups(additionalGroups, group)
499 }
500
501 func ParseSubIDFile(path string) ([]SubID, error) {
502 subid, err := os.Open(path)
503 if err != nil {
504 return nil, err
505 }
506 defer subid.Close()
507 return ParseSubID(subid)
508 }
509
510 func ParseSubID(subid io.Reader) ([]SubID, error) {
511 return ParseSubIDFilter(subid, nil)
512 }
513
514 func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
515 subid, err := os.Open(path)
516 if err != nil {
517 return nil, err
518 }
519 defer subid.Close()
520 return ParseSubIDFilter(subid, filter)
521 }
522
523 func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
524 if r == nil {
525 return nil, errors.New("nil source for subid-formatted data")
526 }
527
528 var (
529 s = bufio.NewScanner(r)
530 out = []SubID{}
531 )
532
533 for s.Scan() {
534 line := bytes.TrimSpace(s.Bytes())
535 if len(line) == 0 {
536 continue
537 }
538
539
540 p := SubID{}
541 parseLine(line, &p.Name, &p.SubID, &p.Count)
542
543 if filter == nil || filter(p) {
544 out = append(out, p)
545 }
546 }
547 if err := s.Err(); err != nil {
548 return nil, err
549 }
550
551 return out, nil
552 }
553
554 func ParseIDMapFile(path string) ([]IDMap, error) {
555 r, err := os.Open(path)
556 if err != nil {
557 return nil, err
558 }
559 defer r.Close()
560 return ParseIDMap(r)
561 }
562
563 func ParseIDMap(r io.Reader) ([]IDMap, error) {
564 return ParseIDMapFilter(r, nil)
565 }
566
567 func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) {
568 r, err := os.Open(path)
569 if err != nil {
570 return nil, err
571 }
572 defer r.Close()
573 return ParseIDMapFilter(r, filter)
574 }
575
576 func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
577 if r == nil {
578 return nil, errors.New("nil source for idmap-formatted data")
579 }
580
581 var (
582 s = bufio.NewScanner(r)
583 out = []IDMap{}
584 )
585
586 for s.Scan() {
587 line := bytes.TrimSpace(s.Bytes())
588 if len(line) == 0 {
589 continue
590 }
591
592
593 p := IDMap{}
594 parseParts(bytes.Fields(line), &p.ID, &p.ParentID, &p.Count)
595
596 if filter == nil || filter(p) {
597 out = append(out, p)
598 }
599 }
600 if err := s.Err(); err != nil {
601 return nil, err
602 }
603
604 return out, nil
605 }
606
View as plain text