mirror of
https://github.com/Luzifer/password.git
synced 2024-11-14 20:22:47 +00:00
237 lines
5.5 KiB
Go
237 lines
5.5 KiB
Go
// Copyright 2010 Jonas mg
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package user
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type gshadowField int
|
|
|
|
// Field names for shadowed group database.
|
|
const (
|
|
GS_NAME gshadowField = 1 << iota
|
|
GS_PASSWD
|
|
GS_ADMIN
|
|
GS_MEMBER
|
|
|
|
GS_ALL
|
|
)
|
|
|
|
func (f gshadowField) String() string {
|
|
switch f {
|
|
case GS_NAME:
|
|
return "Name"
|
|
case GS_PASSWD:
|
|
return "Passwd"
|
|
case GS_ADMIN:
|
|
return "Admin"
|
|
case GS_MEMBER:
|
|
return "Member"
|
|
}
|
|
return "ALL"
|
|
}
|
|
|
|
// A GShadow represents the format of the shadowed information for a group account.
|
|
type GShadow struct {
|
|
// Group name. (Unique)
|
|
//
|
|
// It must be a valid group name, which exist on the system.
|
|
Name string
|
|
|
|
// Hashed password
|
|
//
|
|
// If the password field contains some string that is not a valid result of
|
|
// crypt, for instance "!" or "*", users will not be able to use a unix
|
|
// password to access the group (but group members do not need the password).
|
|
//
|
|
// The password is used when an user who is not a member of the group wants
|
|
// to gain the permissions of this group (see "newgrp(1)").
|
|
//
|
|
// This field may be empty, in which case only the group members can gain
|
|
// the group permissions.
|
|
//
|
|
// A password field which starts with a exclamation mark means that the
|
|
// password is locked. The remaining characters on the line represent the
|
|
// password field before the password was locked.
|
|
//
|
|
// This password supersedes any password specified in '/etc/group'.
|
|
password string
|
|
|
|
// Group administrator list
|
|
//
|
|
// It must be a comma-separated list of user names.
|
|
//
|
|
// Administrators can change the password or the members of the group.
|
|
// Administrators also have the same permissions as the members (see below).
|
|
AdminList []string
|
|
|
|
// Group member list
|
|
//
|
|
// It must be a comma-separated list of user names.
|
|
//
|
|
// Members can access the group without being prompted for a password.
|
|
// You should use the same list of users as in /etc/group.
|
|
UserList []string
|
|
}
|
|
|
|
// NewGShadow returns a new GShadow.
|
|
func NewGShadow(username string, members ...string) *GShadow {
|
|
return &GShadow{
|
|
Name: username,
|
|
UserList: members,
|
|
}
|
|
}
|
|
|
|
func (gs *GShadow) filename() string { return _GSHADOW_FILE }
|
|
|
|
func (gs *GShadow) String() string {
|
|
return fmt.Sprintf("%s:%s:%s:%s\n",
|
|
gs.Name, gs.password, strings.Join(gs.AdminList, ","), strings.Join(gs.UserList, ","))
|
|
}
|
|
|
|
// parseGShadow parses the row of a group shadow.
|
|
func parseGShadow(row string) (*GShadow, error) {
|
|
fields := strings.Split(row, ":")
|
|
if len(fields) != 4 {
|
|
return nil, rowError{_GSHADOW_FILE, row}
|
|
}
|
|
|
|
return &GShadow{
|
|
fields[0],
|
|
fields[1],
|
|
strings.Split(fields[2], ","),
|
|
strings.Split(fields[3], ","),
|
|
}, nil
|
|
}
|
|
|
|
// == Lookup
|
|
//
|
|
|
|
// lookUp parses the shadowed group line searching a value into the field.
|
|
// Returns nil if it isn't found.
|
|
func (*GShadow) lookUp(line string, f field, value interface{}) interface{} {
|
|
_field := f.(gshadowField)
|
|
_value := value.(string)
|
|
allField := strings.Split(line, ":")
|
|
arrayField := make(map[int][]string)
|
|
|
|
arrayField[2] = strings.Split(allField[2], ",")
|
|
arrayField[3] = strings.Split(allField[3], ",")
|
|
|
|
// Check fields
|
|
var isField bool
|
|
if GS_NAME&_field != 0 && allField[0] == _value {
|
|
isField = true
|
|
} else if GS_PASSWD&_field != 0 && allField[1] == _value {
|
|
isField = true
|
|
} else if GS_ADMIN&_field != 0 && checkGroup(arrayField[2], _value) {
|
|
isField = true
|
|
} else if GS_MEMBER&_field != 0 && checkGroup(arrayField[3], _value) {
|
|
isField = true
|
|
} else if GS_ALL&_field != 0 {
|
|
isField = true
|
|
}
|
|
|
|
if isField {
|
|
return &GShadow{
|
|
allField[0],
|
|
allField[1],
|
|
arrayField[2],
|
|
arrayField[3],
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LookupGShadow looks up a shadowed group by name.
|
|
func LookupGShadow(name string) (*GShadow, error) {
|
|
entries, err := LookupInGShadow(GS_NAME, name, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return entries[0], err
|
|
}
|
|
|
|
// LookupInGShadow looks up a shadowed group by the given values.
|
|
//
|
|
// The count determines the number of fields to return:
|
|
// n > 0: at most n fields
|
|
// n == 0: the result is nil (zero fields)
|
|
// n < 0: all fields
|
|
func LookupInGShadow(field gshadowField, value string, n int) ([]*GShadow, error) {
|
|
checkRoot()
|
|
|
|
iEntries, err := lookUp(&GShadow{}, field, value, n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// == Convert to type GShadow
|
|
valueSlice := reflect.ValueOf(iEntries)
|
|
entries := make([]*GShadow, valueSlice.Len())
|
|
|
|
for i := 0; i < valueSlice.Len(); i++ {
|
|
entries[i] = valueSlice.Index(i).Interface().(*GShadow)
|
|
}
|
|
|
|
return entries, err
|
|
}
|
|
|
|
// == Editing
|
|
//
|
|
|
|
// Add adds a new shadowed group.
|
|
// If the key is not nil, generates a hashed password.
|
|
//
|
|
// It is created a backup before of modify the original file.
|
|
func (gs *GShadow) Add(key []byte) (err error) {
|
|
loadConfig()
|
|
|
|
gshadow, err := LookupGShadow(gs.Name)
|
|
if err != nil {
|
|
if _, ok := err.(NoFoundError); !ok {
|
|
return
|
|
}
|
|
}
|
|
if gshadow != nil {
|
|
return ErrGroupExist
|
|
}
|
|
|
|
if gs.Name == "" {
|
|
return RequiredError("Name")
|
|
}
|
|
|
|
// Backup
|
|
if err = backup(_GSHADOW_FILE); err != nil {
|
|
return
|
|
}
|
|
|
|
db, err := openDBFile(_GSHADOW_FILE, os.O_WRONLY|os.O_APPEND)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
e := db.close()
|
|
if e != nil && err == nil {
|
|
err = e
|
|
}
|
|
}()
|
|
|
|
if key != nil {
|
|
gs.password, _ = config.crypter.Generate(key, nil)
|
|
} else {
|
|
gs.password = "*" // Password disabled.
|
|
}
|
|
|
|
_, err = db.file.WriteString(gs.String())
|
|
return
|
|
}
|