github.com/avfs/avfs@v0.33.1-0.20240303173310-c6ba67c33eb7/idm/osidm/osidm_linux.go (about)

     1  //
     2  //  Copyright 2020 The AVFS authors
     3  //
     4  //  Licensed under the Apache License, Version 2.0 (the "License");
     5  //  you may not use this file except in compliance with the License.
     6  //  You may obtain a copy of the License at
     7  //
     8  //  	http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  //  Unless required by applicable law or agreed to in writing, software
    11  //  distributed under the License is distributed on an "AS IS" BASIS,
    12  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  //  See the License for the specific language governing permissions and
    14  //  limitations under the License.
    15  //
    16  
    17  //go:build linux
    18  
    19  package osidm
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"os"
    25  	"os/exec"
    26  	"runtime"
    27  	"strconv"
    28  	"strings"
    29  	"syscall"
    30  
    31  	"github.com/avfs/avfs"
    32  )
    33  
    34  // To avoid flaky tests when executing commands or making system calls as root,
    35  // the current goroutine is locked to the operating system thread just before calling the function.
    36  // For details see https://github.com/golang/go/issues/1435
    37  
    38  // GroupAdd adds a new group.
    39  func (idm *OsIdm) GroupAdd(name string) (avfs.GroupReader, error) {
    40  	if idm.HasFeature(avfs.FeatReadOnlyIdm) {
    41  		return nil, avfs.ErrPermDenied
    42  	}
    43  
    44  	runtime.LockOSThread()
    45  	defer runtime.UnlockOSThread()
    46  
    47  	cmd := exec.Command("groupadd", name)
    48  
    49  	var stderr bytes.Buffer
    50  	cmd.Stderr = &stderr
    51  
    52  	if err := cmd.Run(); err != nil {
    53  		errStr := strings.TrimSpace(stderr.String())
    54  
    55  		switch {
    56  		case errStr == "groupadd: group '"+name+"' already exists":
    57  			return nil, avfs.AlreadyExistsGroupError(name)
    58  		default:
    59  			return nil, avfs.UnknownError(err.Error() + errStr)
    60  		}
    61  	}
    62  
    63  	g, err := idm.LookupGroup(name)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	return g, nil
    69  }
    70  
    71  // GroupDel deletes an existing group.
    72  func (idm *OsIdm) GroupDel(name string) error {
    73  	if idm.HasFeature(avfs.FeatReadOnlyIdm) {
    74  		return avfs.ErrPermDenied
    75  	}
    76  
    77  	runtime.LockOSThread()
    78  	defer runtime.UnlockOSThread()
    79  
    80  	cmd := exec.Command("groupdel", name)
    81  
    82  	var stderr bytes.Buffer
    83  	cmd.Stderr = &stderr
    84  
    85  	if err := cmd.Run(); err != nil {
    86  		errStr := strings.TrimSpace(stderr.String())
    87  
    88  		switch {
    89  		case errStr == "groupdel: group '"+name+"' does not exist":
    90  			return avfs.UnknownGroupError(name)
    91  		default:
    92  			return avfs.UnknownError(err.Error() + errStr)
    93  		}
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  // LookupGroup looks up a group by name. If the group cannot be found, the
   100  // returned error is of type UnknownGroupError.
   101  func (idm *OsIdm) LookupGroup(name string) (avfs.GroupReader, error) {
   102  	return getGroup(name, avfs.UnknownGroupError(name))
   103  }
   104  
   105  // LookupGroupId looks up a group by groupid. If the group cannot be found, the
   106  // returned error is of type UnknownGroupIdError.
   107  func (idm *OsIdm) LookupGroupId(gid int) (avfs.GroupReader, error) {
   108  	sGid := strconv.Itoa(gid)
   109  
   110  	return getGroup(sGid, avfs.UnknownGroupIdError(gid))
   111  }
   112  
   113  func getGroup(nameOrId string, notFoundErr error) (*OsGroup, error) {
   114  	line, err := getent("group", nameOrId, notFoundErr)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	cols := strings.Split(line, ":")
   120  	gid, _ := strconv.Atoi(cols[2])
   121  
   122  	g := &OsGroup{
   123  		name: cols[0],
   124  		gid:  gid,
   125  	}
   126  
   127  	return g, nil
   128  }
   129  
   130  // LookupUser looks up a user by username. If the user cannot be found, the
   131  // returned error is of type UnknownUserError.
   132  func (idm *OsIdm) LookupUser(name string) (avfs.UserReader, error) {
   133  	return lookupUser(name)
   134  }
   135  
   136  func lookupUser(name string) (avfs.UserReader, error) {
   137  	return getUser(name, avfs.UnknownUserError(name))
   138  }
   139  
   140  // LookupUserId looks up a user by userid. If the user cannot be found, the
   141  // returned error is of type UnknownUserIdError.
   142  func (idm *OsIdm) LookupUserId(uid int) (avfs.UserReader, error) {
   143  	return lookupUserId(uid)
   144  }
   145  
   146  func lookupUserId(uid int) (avfs.UserReader, error) {
   147  	sUid := strconv.Itoa(uid)
   148  
   149  	return getUser(sUid, avfs.UnknownUserIdError(uid))
   150  }
   151  
   152  func getUser(nameOrId string, notFoundErr error) (*OsUser, error) {
   153  	line, err := getent("passwd", nameOrId, notFoundErr)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	cols := strings.Split(line, ":")
   159  	uid, _ := strconv.Atoi(cols[2])
   160  	gid, _ := strconv.Atoi(cols[3])
   161  
   162  	u := &OsUser{
   163  		name: cols[0],
   164  		uid:  uid,
   165  		gid:  gid,
   166  	}
   167  
   168  	return u, nil
   169  }
   170  
   171  // SetUser sets the current user.
   172  // If the user can't be changed an error is returned.
   173  func SetUser(user avfs.UserReader) error {
   174  	const op = "user"
   175  
   176  	runtime.LockOSThread()
   177  	defer runtime.UnlockOSThread()
   178  
   179  	// If the current user is the target user there is nothing to do.
   180  	curUid := syscall.Geteuid()
   181  	if curUid == user.Uid() {
   182  		return nil
   183  	}
   184  
   185  	runtime.LockOSThread()
   186  
   187  	curGid := syscall.Getegid()
   188  
   189  	// If the current user is not root, root privileges must be restored
   190  	// before setting the new uid and gid.
   191  	if curGid != 0 {
   192  		runtime.LockOSThread()
   193  
   194  		if err := syscall.Setresgid(0, 0, 0); err != nil {
   195  			return avfs.UnknownError(fmt.Sprintf("%s : can't change gid to %d : %v", op, 0, err))
   196  		}
   197  	}
   198  
   199  	if curUid != 0 {
   200  		runtime.LockOSThread()
   201  
   202  		if err := syscall.Setresuid(0, 0, 0); err != nil {
   203  			return avfs.UnknownError(fmt.Sprintf("%s : can't change uid to %d : %v", op, 0, err))
   204  		}
   205  	}
   206  
   207  	if user.Uid() == 0 {
   208  		return nil
   209  	}
   210  
   211  	runtime.LockOSThread()
   212  
   213  	if err := syscall.Setresgid(user.Gid(), user.Gid(), 0); err != nil {
   214  		return avfs.UnknownError(fmt.Sprintf("%s : can't change gid to %d : %v", op, user.Gid(), err))
   215  	}
   216  
   217  	runtime.LockOSThread()
   218  
   219  	if err := syscall.Setresuid(user.Uid(), user.Uid(), 0); err != nil {
   220  		return avfs.UnknownError(fmt.Sprintf("%s : can't change uid to %d : %v", op, user.Uid(), err))
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  // SetUserByName sets the current user by name.
   227  // If the user is not found, the returned error is of type UnknownUserError.
   228  func SetUserByName(name string) error {
   229  	u, err := lookupUser(name)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	return SetUser(u)
   235  }
   236  
   237  // User returns the current user of the OS.
   238  func User() avfs.UserReader {
   239  	runtime.LockOSThread()
   240  	defer runtime.UnlockOSThread()
   241  
   242  	uid := syscall.Geteuid()
   243  
   244  	user, err := lookupUserId(uid)
   245  	if err != nil {
   246  		return nil
   247  	}
   248  
   249  	return user
   250  }
   251  
   252  // UserAdd adds a new user.
   253  func (idm *OsIdm) UserAdd(name, groupName string) (avfs.UserReader, error) {
   254  	if idm.HasFeature(avfs.FeatReadOnlyIdm) {
   255  		return nil, avfs.ErrPermDenied
   256  	}
   257  
   258  	runtime.LockOSThread()
   259  	defer runtime.UnlockOSThread()
   260  
   261  	cmd := exec.Command("useradd", "-M", "-g", groupName, name)
   262  
   263  	var stderr bytes.Buffer
   264  	cmd.Stderr = &stderr
   265  
   266  	if err := cmd.Run(); err != nil {
   267  		errStr := strings.TrimSpace(stderr.String())
   268  
   269  		switch {
   270  		case errStr == "useradd: user '"+name+"' already exists":
   271  			return nil, avfs.AlreadyExistsUserError(name)
   272  		case errStr == "useradd: group '"+groupName+"' does not exist":
   273  			return nil, avfs.UnknownGroupError(groupName)
   274  		default:
   275  			return nil, avfs.UnknownError(err.Error() + errStr)
   276  		}
   277  	}
   278  
   279  	u, err := idm.LookupUser(name)
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	return u, nil
   285  }
   286  
   287  // UserDel deletes an existing user.
   288  func (idm *OsIdm) UserDel(name string) error {
   289  	if idm.HasFeature(avfs.FeatReadOnlyIdm) {
   290  		return avfs.ErrPermDenied
   291  	}
   292  
   293  	runtime.LockOSThread()
   294  	defer runtime.UnlockOSThread()
   295  
   296  	cmd := exec.Command("userdel", name)
   297  
   298  	var stderr bytes.Buffer
   299  	cmd.Stderr = &stderr
   300  
   301  	if err := cmd.Run(); err != nil {
   302  		errStr := strings.TrimSpace(stderr.String())
   303  
   304  		switch {
   305  		case errStr == "userdel: user '"+name+"' does not exist":
   306  			return avfs.UnknownUserError(name)
   307  		default:
   308  			return avfs.UnknownError(err.Error() + errStr)
   309  		}
   310  	}
   311  
   312  	return nil
   313  }
   314  
   315  func getent(database, key string, notFoundErr error) (string, error) {
   316  	cmd := exec.Command("getent", database, key)
   317  
   318  	buf, err := cmd.Output()
   319  	if err != nil {
   320  		if e, ok := err.(*exec.ExitError); ok {
   321  			switch e.ExitCode() {
   322  			case 1:
   323  				return "", avfs.UnknownError("Missing arguments, or database unknown.")
   324  			case 2:
   325  				return "", notFoundErr
   326  			case 3:
   327  				return "", avfs.UnknownError("Enumeration not supported on this database.")
   328  			}
   329  		}
   330  
   331  		return "", err
   332  	}
   333  
   334  	return string(buf), nil
   335  }
   336  
   337  // IsUserAdmin returns true if the current user has admin privileges.
   338  func isUserAdmin() bool {
   339  	return os.Geteuid() == 0
   340  }