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 }