1// Copyright 2010 Jonas mg
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7package user
8
9import (
10	"errors"
11	"fmt"
12	"os"
13	"reflect"
14	"strconv"
15	"strings"
16)
17
18type groupField int
19
20// Field names for group database.
21const (
22	G_NAME groupField = 1 << iota
23	G_PASSWD
24	G_GID
25	G_MEMBER
26
27	G_ALL
28)
29
30func (f groupField) String() string {
31	switch f {
32	case G_NAME:
33		return "Name"
34	case G_PASSWD:
35		return "Passwd"
36	case G_GID:
37		return "GID"
38	case G_MEMBER:
39		return "Member"
40	}
41	return "ALL"
42}
43
44// A Group represents the format of a group on the system.
45type Group struct {
46	// Group name. (Unique)
47	Name string
48
49	// Hashed password
50	//
51	// The (hashed) group password. If this field is empty, no password is needed.
52	password string
53
54	// The numeric group ID. (Unique)
55	GID int
56
57	// User list
58	//
59	// A list of the usernames that are members of this group, separated by commas.
60	UserList []string
61
62	addSystemGroup bool
63}
64
65// AddGroup returns a new Group.
66func NewGroup(name string, members ...string) *Group {
67	return &Group{
68		Name:     name,
69		password: "",
70		GID:      -1,
71		UserList: members,
72	}
73}
74
75// NewSystemGroup adds a system group.
76func NewSystemGroup(name string, members ...string) *Group {
77	return &Group{
78		Name:     name,
79		password: "",
80		GID:      -1,
81		UserList: members,
82
83		addSystemGroup: true,
84	}
85}
86
87func (g *Group) filename() string { return _GROUP_FILE }
88
89// IsOfSystem indicates whether it is a system group.
90func (g *Group) IsOfSystem() bool {
91	//loadConfig()
92
93	if g.GID > config.login.SYS_GID_MIN && g.GID < config.login.SYS_GID_MAX {
94		return true
95	}
96	return false
97}
98
99func (g *Group) String() string {
100	return fmt.Sprintf("%s:%s:%d:%s\n",
101		g.Name, g.password, g.GID, strings.Join(g.UserList, ","))
102}
103
104// parseGroup parses the row of a group.
105func parseGroup(row string) (*Group, error) {
106	fields := strings.Split(row, ":")
107	if len(fields) != 4 {
108		return nil, rowError{_GROUP_FILE, row}
109	}
110
111	gid, err := strconv.Atoi(fields[2])
112	if err != nil {
113		return nil, atoiError{_GROUP_FILE, row, "GID"}
114	}
115
116	return &Group{
117		Name:     fields[0],
118		password: fields[1],
119		GID:      gid,
120		UserList: strings.Split(fields[3], ","),
121	}, nil
122}
123
124// == Lookup
125//
126
127// lookUp parses the group line searching a value into the field.
128// Returns nil if it is not found.
129func (*Group) lookUp(line string, f field, value interface{}) interface{} {
130	_field := f.(groupField)
131	allField := strings.Split(line, ":")
132	arrayField := make(map[int][]string)
133	intField := make(map[int]int)
134
135	arrayField[3] = strings.Split(allField[3], ",")
136
137	// Check integers
138	var err error
139	if intField[2], err = strconv.Atoi(allField[2]); err != nil {
140		panic(atoiError{_GROUP_FILE, line, "GID"})
141	}
142
143	// Check fields
144	var isField bool
145	if G_NAME&_field != 0 && allField[0] == value.(string) {
146		isField = true
147	} else if G_PASSWD&_field != 0 && allField[1] == value.(string) {
148		isField = true
149	} else if G_GID&_field != 0 && intField[2] == value.(int) {
150		isField = true
151	} else if G_MEMBER&_field != 0 && checkGroup(arrayField[3], value.(string)) {
152		isField = true
153	} else if G_ALL&_field != 0 {
154		isField = true
155	}
156
157	if isField {
158		return &Group{
159			Name:     allField[0],
160			password: allField[1],
161			GID:      intField[2],
162			UserList: arrayField[3],
163		}
164	}
165	return nil
166}
167
168// LookupGID looks up a group by group ID.
169func LookupGID(gid int) (*Group, error) {
170	entries, err := LookupInGroup(G_GID, gid, 1)
171	if err != nil {
172		return nil, err
173	}
174
175	return entries[0], err
176}
177
178// LookupGroup looks up a group by name.
179func LookupGroup(name string) (*Group, error) {
180	entries, err := LookupInGroup(G_NAME, name, 1)
181	if err != nil {
182		return nil, err
183	}
184
185	return entries[0], err
186}
187
188// LookupInGroup looks up a group by the given values.
189//
190// The count determines the number of fields to return:
191//   n > 0: at most n fields
192//   n == 0: the result is nil (zero fields)
193//   n < 0: all fields
194func LookupInGroup(field groupField, value interface{}, n int) ([]*Group, error) {
195	iEntries, err := lookUp(&Group{}, field, value, n)
196	if err != nil {
197		return nil, err
198	}
199
200	// == Convert to type group
201	valueSlice := reflect.ValueOf(iEntries)
202	entries := make([]*Group, valueSlice.Len())
203
204	for i := 0; i < valueSlice.Len(); i++ {
205		entries[i] = valueSlice.Index(i).Interface().(*Group)
206	}
207
208	return entries, err
209}
210
211// Getgroups returns a list of the numeric ids of groups that the caller
212// belongs to.
213func Getgroups() []int {
214	user := GetUsername()
215	list := make([]int, 0)
216
217	// The user could have its own group.
218	if g, err := LookupGroup(user); err == nil {
219		list = append(list, g.GID)
220	}
221
222	groups, err := LookupInGroup(G_MEMBER, user, -1)
223	if err != nil {
224		if _, ok := err.(NoFoundError); !ok {
225			panic(err)
226		}
227	}
228
229	for _, v := range groups {
230		list = append(list, v.GID)
231	}
232	return list
233}
234
235// GetgroupsName returns a list of the groups that the caller belongs to.
236func GetgroupsName() []string {
237	user := GetUsername()
238	list := make([]string, 0)
239
240	// The user could have its own group.
241	if _, err := LookupGroup(user); err == nil {
242		list = append(list, user)
243	}
244
245	groups, err := LookupInGroup(G_MEMBER, user, -1)
246	if err != nil {
247		if _, ok := err.(NoFoundError); !ok {
248			panic(err)
249		}
250	}
251	for _, v := range groups {
252		list = append(list, v.Name)
253	}
254
255	return list
256}
257
258// == Editing
259//
260
261// AddGroup adds a group.
262func AddGroup(name string, members ...string) (gid int, err error) {
263	s := NewGShadow(name, members...)
264	if err = s.Add(nil); err != nil {
265		return
266	}
267
268	return NewGroup(name, members...).Add()
269}
270
271// AddSystemGroup adds a system group.
272func AddSystemGroup(name string, members ...string) (gid int, err error) {
273	s := NewGShadow(name, members...)
274	if err = s.Add(nil); err != nil {
275		return
276	}
277
278	return NewSystemGroup(name, members...).Add()
279}
280
281// Add adds a new group.
282// Whether GID is < 0, it will choose the first id available in the range set
283// in the system configuration.
284func (g *Group) Add() (gid int, err error) {
285	loadConfig()
286
287	group, err := LookupGroup(g.Name)
288	if err != nil {
289		if _, ok := err.(NoFoundError); !ok {
290			return 0, err
291		}
292	}
293	if group != nil {
294		return 0, ErrGroupExist
295	}
296
297	if g.Name == "" {
298		return 0, RequiredError("Name")
299	}
300
301	var db *dbfile
302	if g.GID < 0 {
303		db, gid, err = nextGUID(g.addSystemGroup)
304		if err != nil {
305			db.close()
306			return 0, err
307		}
308		g.GID = gid
309	} else {
310		db, err = openDBFile(_GROUP_FILE, os.O_WRONLY|os.O_APPEND)
311		if err != nil {
312			return
313		}
314
315		// Check if Id is unique.
316		_, err = LookupGID(g.GID)
317		if err == nil {
318			return 0, IdUsedError(g.GID)
319		} else if _, ok := err.(NoFoundError); !ok {
320			return 0, err
321		}
322	}
323
324	g.password = "x"
325
326	_, err = db.file.WriteString(g.String())
327	err2 := db.close()
328	if err2 != nil && err == nil {
329		err = err2
330	}
331	return
332}
333
334// DelGroup removes a group from the system.
335func DelGroup(name string) (err error) {
336	err = del(name, &Group{})
337	if err == nil {
338		err = del(name, &GShadow{})
339	}
340	return
341}
342
343// AddUsersToGroup adds the members to a group.
344func AddUsersToGroup(name string, members ...string) error {
345	if len(members) == 0 {
346		return fmt.Errorf("no members to add")
347	}
348	for i, v := range members {
349		if v == "" {
350			return EmptyMemberError(fmt.Sprintf("members[%s]", strconv.Itoa(i)))
351		}
352	}
353
354	// Group
355	gr, err := LookupGroup(name)
356	if err != nil {
357		return err
358	}
359	if err = _addMembers(&gr.UserList, members...); err != nil {
360		return err
361	}
362
363	// Shadow group
364	sg, err := LookupGShadow(name)
365	if err != nil {
366		return err
367	}
368	if err = _addMembers(&sg.UserList, members...); err != nil {
369		return err
370	}
371
372	// Editing
373	if err = edit(name, gr); err != nil {
374		return err
375	}
376	if err = edit(name, sg); err != nil {
377		return err
378	}
379
380	return nil
381}
382
383func _addMembers(userList *[]string, members ...string) error {
384	// Check if some member is already in the file.
385	for _, u := range *userList {
386		for _, m := range members {
387			if u == m {
388				return fmt.Errorf("user %q is already set", u)
389			}
390		}
391	}
392
393	if len(*userList) == 1 && (*userList)[0] == "" {
394		*userList = members
395	} else {
396		*userList = append(*userList, members...)
397	}
398
399	return nil
400}
401
402// DelUsersInGroup removes the specific members from a group.
403func DelUsersInGroup(name string, members ...string) error {
404	if len(members) == 0 {
405		return ErrNoMembers
406	}
407	for i, v := range members {
408		if v == "" {
409			return EmptyMemberError(fmt.Sprintf("members[%s]", strconv.Itoa(i)))
410		}
411	}
412
413	// Group
414	gr, err := LookupGroup(name)
415	if err != nil {
416		return err
417	}
418	if err = _delMembers(&gr.UserList, members...); err != nil {
419		return err
420	}
421
422	// Shadow group
423	sg, err := LookupGShadow(name)
424	if err != nil {
425		return err
426	}
427	if err = _delMembers(&sg.UserList, members...); err != nil {
428		return err
429	}
430
431	// Editing
432	if err = edit(name, gr); err != nil {
433		return err
434	}
435	if err = edit(name, sg); err != nil {
436		return err
437	}
438
439	return nil
440}
441
442func _delMembers(userList *[]string, members ...string) error {
443	if len(*userList) == 1 && (*userList)[0] == "" {
444		return ErrNoMembers
445	}
446
447	newUserList := make([]string, 0)
448
449	for _, u := range *userList {
450		found := false
451		for _, m := range members {
452			if u == m {
453				found = true
454				break
455			}
456		}
457		if !found {
458			newUserList = append(newUserList, u)
459		}
460	}
461
462	if len(newUserList) == len(*userList) {
463		return ErrNoMembers
464	}
465
466	*userList = make([]string, len(newUserList))
467	for i, v := range newUserList {
468		(*userList)[i] = v
469	}
470	return nil
471}
472
473// == Utility
474//
475
476// checkGroup indicates if a value is into a group.
477func checkGroup(group []string, value string) bool {
478	for _, v := range group {
479		if v == value {
480			return true
481		}
482	}
483	return false
484}
485
486// == Errors
487//
488
489var ErrNoMembers = errors.New("no members to remove")
490
491// EmptyMemberError reports an empty member.
492type EmptyMemberError string
493
494func (e EmptyMemberError) Error() string { return "empty field: " + string(e) }
495