go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/users/dscl.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package users
     5  
     6  import (
     7  	"io"
     8  	"regexp"
     9  	"strconv"
    10  
    11  	"github.com/rs/zerolog/log"
    12  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    13  )
    14  
    15  var USER_OSX_DSCL_REGEX = regexp.MustCompile(`(?m)^(\S*)\s*(.*)$`)
    16  
    17  func ParseDsclListResult(input io.Reader) (map[string]string, error) {
    18  	content, err := io.ReadAll(input)
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  
    23  	userMap := make(map[string]string)
    24  	m := USER_OSX_DSCL_REGEX.FindAllStringSubmatch(string(content), -1)
    25  	for i := range m {
    26  		key := m[i][1]
    27  		value := m[i][2]
    28  
    29  		if len(key) > 0 {
    30  			userMap[key] = value
    31  		}
    32  	}
    33  	return userMap, nil
    34  }
    35  
    36  type OSXUserManager struct {
    37  	conn shared.Connection
    38  }
    39  
    40  func (s *OSXUserManager) Name() string {
    41  	return "macOS User Manager"
    42  }
    43  
    44  func (s *OSXUserManager) User(id string) (*User, error) {
    45  	users, err := s.List()
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	return findUser(users, id)
    51  }
    52  
    53  // To retrieve all user information, we have two options:
    54  //
    55  //  1. fetch all users via `dscl . list /Users`
    56  //  2. iterate over each user and fetch the data via
    57  //     dscl -q . -read /Users/nobody NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell
    58  //
    59  // This approach is not very effective since it requires O(n), there we use the option to fetch one
    60  // value per list, which requires us to do 5 calls to fetch all information:
    61  // dscl . -list /Users UserShell
    62  // dscl . -list /Users UniqueID
    63  // dscl . -list /Users NFSHomeDirectory
    64  // dscl . -list /Users RecordName
    65  // dscl . -list /Users RealName
    66  func (s *OSXUserManager) List() ([]*User, error) {
    67  	users := make(map[string]*User)
    68  
    69  	// fetch all uids first
    70  	f, err := s.conn.RunCommand("dscl . -list /Users UniqueID")
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	m, err := ParseDsclListResult(f.Stdout)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	for k := range m {
    80  		uid, err := strconv.ParseInt(m[k], 10, 0)
    81  		if err != nil {
    82  			log.Error().Err(err).Str("user", k).Msg("could not parse uid")
    83  		}
    84  
    85  		users[k] = &User{
    86  			ID:   m[k],
    87  			Name: k,
    88  			Uid:  uid,
    89  		}
    90  	}
    91  
    92  	// fetch shells
    93  	f, err = s.conn.RunCommand("dscl . -list /Users UserShell")
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	m, err = ParseDsclListResult(f.Stdout)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	for k := range m {
   103  		users[k].Shell = m[k]
   104  	}
   105  
   106  	// fetch home
   107  	f, err = s.conn.RunCommand("dscl . -list /Users NFSHomeDirectory")
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	m, err = ParseDsclListResult(f.Stdout)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	for k := range m {
   117  		users[k].Home = m[k]
   118  	}
   119  
   120  	// fetch usernames
   121  	f, err = s.conn.RunCommand("dscl . -list /Users RealName")
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	m, err = ParseDsclListResult(f.Stdout)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	for k := range m {
   131  		users[k].Description = m[k]
   132  	}
   133  
   134  	// fetch gid
   135  	f, err = s.conn.RunCommand("dscl . -list /Users PrimaryGroupID")
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	m, err = ParseDsclListResult(f.Stdout)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	for k := range m {
   145  		gid, err := strconv.ParseInt(m[k], 10, 0)
   146  		if err != nil {
   147  			log.Error().Err(err).Str("user", k).Msg("could not parse gid")
   148  		}
   149  		users[k].Gid = gid
   150  	}
   151  
   152  	// convert map to slice
   153  	res := make([]*User, len(users))
   154  
   155  	i := 0
   156  	for k := range users {
   157  		res[i] = users[k]
   158  		i++
   159  	}
   160  
   161  	return res, nil
   162  }