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