blob: 78324db2a6c5ba4e1d5dbba90312c68f076d2809 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
The shadow package implements marshaling and unmarshaling of files in the
style of /etc/passwd.
Each of the files used by passwd-related tools (/etc/passwd, /etc/group,
/etc/shadow, and /etc/gshadow) are essentially the same format: one record per
line, with each record having its fields separated by colons. This package
makes some assumptions about the types of these fields to make manipulation
easier.
Valid record fields are string (user names, group names, gecos, homedir, etc),
uint32 (uid and gid), *uint64 (for empty integer fields, like inactive/expire),
and []string for comma-separated lists (group members).
Each record must have a nonempty string Name, which is the name of the user or
group.
*/
package shadow
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
)
// PasswdEntry encapsulates an entry in /etc/passwd. See passwd(5).
type PasswdEntry struct {
Name string
Password string
Uid uint32
Gid uint32
Gecos string
Homedir string
Shell string
}
// ShadowEntry encapsulates an entry in /etc/shadow. See shadow(5).
type ShadowEntry struct {
Name string
Password string
LastChange *uint64
Min *uint64
Max *uint64
Warn *uint64
Inactive *uint64
Expire *uint64
Reserved string
}
// GroupEntry encapsulates an entry in /etc/group. See group(5).
type GroupEntry struct {
Name string
Password string
Gid uint32
UserList []string
}
// GroupShadowEntry encapsulates an entry in /etc/gshadow. See gshadow(5).
type GroupShadowEntry struct {
Name string
Password string
Admins []string
Members []string
}
// PasswdFile contains a list of PasswdEntry entries.
type PasswdFile struct {
Entries []PasswdEntry
}
// ShadowFile contains a list of ShadowEntry entries.
type ShadowFile struct {
Entries []ShadowEntry
}
// GroupFile contains a list of GroupEntry entries.
type GroupFile struct {
Entries []GroupEntry
}
// GroupShadowFile contains a list of GroupShadowEntry entries.
type GroupShadowFile struct {
Entries []GroupShadowEntry
}
// MarshalText marshals the PasswdFile to the on-disk format as specified by
// passwd(5).
func (f PasswdFile) MarshalText() (text []byte, err error) {
b := &bytes.Buffer{}
if err := marshal(b, &f.Entries); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// UnmarshalText unmarshals from the on-disk format specified in passwd(5).
func (f *PasswdFile) UnmarshalText(text []byte) error {
if err := unmarshal(bytes.NewReader(text), &f.Entries); err != nil {
return err
}
return nil
}
// MarshalText marshals the ShadowFile to the on-disk format as specified by
// shadow(5).
func (f ShadowFile) MarshalText() (text []byte, err error) {
b := &bytes.Buffer{}
if err := marshal(b, &f.Entries); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// UnmarshalText unmarshals from the on-disk format specified in shadow(5).
func (f *ShadowFile) UnmarshalText(text []byte) error {
if err := unmarshal(bytes.NewReader(text), &f.Entries); err != nil {
return err
}
return nil
}
// MarshalText marshals the GroupFile to the on-disk format as specified by
// group(5).
func (f GroupFile) MarshalText() (text []byte, err error) {
b := &bytes.Buffer{}
if err := marshal(b, &f.Entries); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// UnmarshalText unmarshals from the on-disk format specified in group(5).
func (f *GroupFile) UnmarshalText(text []byte) error {
if err := unmarshal(bytes.NewReader(text), &f.Entries); err != nil {
return err
}
return nil
}
// MarshalText marshals the GroupShadowFile to the on-disk format as specified
// by gshadow(5).
func (f GroupShadowFile) MarshalText() (text []byte, err error) {
b := &bytes.Buffer{}
if err := marshal(b, &f.Entries); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// UnmarshalText unmarshals from the on-disk format specified in gshadow(5).
func (f *GroupShadowFile) UnmarshalText(text []byte) error {
if err := unmarshal(bytes.NewReader(text), &f.Entries); err != nil {
return err
}
return nil
}
// NewUint64 returns a pointer to a uint64. This is useful for initializing
// *uint64 fields in structs.
func NewUint64(x uint64) *uint64 {
return &x
}
// unmarshal unmarshals from reader to v. v must be a pointer to a slice of
// structs.
func unmarshal(reader io.Reader, v interface{}) error {
value := reflect.ValueOf(v)
// Verify that v is a pointer to a slice.
if value.Kind() != reflect.Ptr || value.Type().Elem().Kind() != reflect.Slice {
return errors.New("unmarshal target must be a pointer to a slice")
}
// sliceValue is the underlying slice that is pointed to. This will allow
// appending to the slice later.
sliceValue := value.Elem()
// structType is the underlying struct that represents a line in the file.
// If v is a pointer to a slice of PasswdEntry, then structType is PasswdEntry.
structType := value.Type().Elem().Elem()
if structType.Kind() != reflect.Struct {
return errors.New("unmarshal slice must contain structs")
}
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
fields := strings.Split(line, ":")
// Ignore blank lines.
if len(fields) == 1 && len(fields[0]) == 0 {
continue
}
// The number of fields in the unmarshal target struct must match the
// number of fields on the line.
if len(fields) != structType.NumField() {
return fmt.Errorf("expected %d fields, got %d: %q", structType.NumField(), len(fields), line)
}
// Instantiate a new *Entry for this line.
linePtr := reflect.New(structType)
lineValue := linePtr.Elem()
// Fill in the fields for the *Entry.
for i := 0; i < structType.NumField(); i++ {
structField := structType.Field(i)
if structField.Name == "Name" && len(fields[i]) == 0 {
return errors.New("Name field must be nonempty")
}
switch k := structField.Type.Kind(); k {
case reflect.Uint32:
u, err := strconv.ParseUint(fields[i], 10, 32)
if err != nil {
return fmt.Errorf("failed to parse uint32 field: %v", err)
}
lineValue.Field(i).SetUint(u)
case reflect.String:
lineValue.Field(i).SetString(fields[i])
case reflect.Slice:
if structField.Type.Elem().Kind() != reflect.String {
return errors.New("slices must only have strings as their fields")
}
sliceFields := strings.Split(fields[i], ",")
if len(sliceFields) > 1 {
for _, v := range sliceFields {
if len(v) == 0 {
return errors.New("slice field must be nonempty")
}
}
} else {
// A slice of length 1 can be an empty string - remove it.
if len(sliceFields[0]) == 0 {
sliceFields = []string{}
}
}
lineValue.Field(i).Set(reflect.ValueOf(sliceFields))
case reflect.Ptr:
if structField.Type.Elem().Kind() != reflect.Uint64 {
return errors.New("only pointers to uint64 are allowed as fields")
}
if len(fields[i]) != 0 {
u, err := strconv.ParseUint(fields[i], 10, 64)
if err != nil {
return fmt.Errorf("failed to parse uint64 field: %v", err)
}
lineValue.Field(i).Set(reflect.ValueOf(&u))
} else {
lineValue.Field(i).Set(reflect.Zero(structField.Type))
}
default:
return fmt.Errorf("invalid field kind: %v", k)
}
}
// Append this *Entry to the user-supplied slice.
sliceValue.Set(reflect.Append(sliceValue, lineValue))
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("failed to read lines from reader: %v", err)
}
return nil
}
// marshal marshals v to the given writer. v must be a pointer to a slice of
// structs, or simply a slice of structs.
func marshal(writer io.Writer, v interface{}) error {
value := reflect.ValueOf(v)
valueType := value.Type()
var sliceValue reflect.Value
// Grab sliceValue, which may be behind a pointer.
switch valueType.Kind() {
case reflect.Ptr:
if valueType.Elem().Kind() != reflect.Slice {
return errors.New("pointer to marshal must point to a slice")
}
sliceValue = value.Elem()
case reflect.Slice:
sliceValue = value
default:
return errors.New("marshal value must be a slice or pointer to a slice")
}
// structType is the underlying Entry type, e.g. PasswdEntry.
structType := sliceValue.Type().Elem()
if structType.Kind() != reflect.Struct {
return errors.New("marshal slice must contain structs")
}
for i := 0; i < sliceValue.Len(); i++ {
entryValue := sliceValue.Index(i)
// Marshal the individual Entry fields.
for j := 0; j < entryValue.NumField(); j++ {
entryFieldValue := entryValue.Field(j)
switch k := entryFieldValue.Type().Kind(); k {
case reflect.Uint32:
s := strconv.FormatUint(entryFieldValue.Uint(), 10)
_, err := io.WriteString(writer, s)
if err != nil {
return fmt.Errorf("failed to output uint32 field: %v", err)
}
case reflect.String:
_, err := io.WriteString(writer, entryFieldValue.String())
if err != nil {
return fmt.Errorf("failed to output string field: %v", err)
}
case reflect.Slice:
for l := 0; l < entryFieldValue.Len(); l++ {
_, err := io.WriteString(writer, entryFieldValue.Index(l).String())
if err != nil {
return fmt.Errorf("failed to output slice field: %v", err)
}
if l != (entryFieldValue.Len() - 1) {
_, err = io.WriteString(writer, ",")
if err != nil {
return fmt.Errorf("failed to output slice string separator: %v", err)
}
}
}
case reflect.Ptr:
if !entryFieldValue.IsNil() {
if entryFieldValue.Elem().Kind() != reflect.Uint64 {
return errors.New("only pointers to uint64 are allowed as fields")
}
s := strconv.FormatUint(entryFieldValue.Elem().Uint(), 10)
_, err := io.WriteString(writer, s)
if err != nil {
return fmt.Errorf("failed to output *uint64 field: %v", err)
}
}
default:
return fmt.Errorf("invalid field kind: %v", k)
}
// Write a ":" field separator on all but the last field.
if j != (entryValue.NumField() - 1) {
_, err := io.WriteString(writer, ":")
if err != nil {
return fmt.Errorf("failed to write field separator: %v", err)
}
}
}
_, err := io.WriteString(writer, "\n")
if err != nil {
return fmt.Errorf("failed to write newline: %v", err)
}
}
return nil
}