gopkg.in/essentialkaos/ek.v3@v3.5.1/system/user.go (about) 1 // +build linux, darwin, !windows 2 3 package system 4 5 // ////////////////////////////////////////////////////////////////////////////////// // 6 // // 7 // Copyright (c) 2009-2016 Essential Kaos // 8 // Essential Kaos Open Source License <http://essentialkaos.com/ekol?en> // 9 // // 10 // ////////////////////////////////////////////////////////////////////////////////// // 11 12 import ( 13 "errors" 14 "fmt" 15 "os" 16 "os/exec" 17 "sort" 18 "strconv" 19 "strings" 20 "syscall" 21 "time" 22 23 "pkg.re/essentialkaos/ek.v3/env" 24 ) 25 26 // ////////////////////////////////////////////////////////////////////////////////// // 27 28 const _PTS_DIR = "/dev/pts" 29 30 // ////////////////////////////////////////////////////////////////////////////////// // 31 32 // User contains information about user 33 type User struct { 34 UID int `json:"uid"` 35 GID int `json:"gid"` 36 Name string `json:"name"` 37 Groups []*Group `json:"groups"` 38 Comment string `json:"comment"` 39 Shell string `json:"shell"` 40 HomeDir string `json:"home_dir"` 41 RealUID int `json:"real_uid"` 42 RealGID int `json:"real_gid"` 43 RealName string `json:"real_name"` 44 } 45 46 // Group contains information about group 47 type Group struct { 48 Name string `json:"name"` 49 GID int `json:"gid"` 50 } 51 52 // SessionInfo contains information about all sessions 53 type SessionInfo struct { 54 User *User `json:"user"` 55 LoginTime time.Time `json:"login_time"` 56 LastActivityTime time.Time `json:"last_activity_time"` 57 } 58 59 // sessionsInfo is slice with SessionInfo 60 type sessionsInfo []*SessionInfo 61 62 // ////////////////////////////////////////////////////////////////////////////////// // 63 64 func (s sessionsInfo) Len() int { 65 return len(s) 66 } 67 68 func (s sessionsInfo) Less(i, j int) bool { 69 return s[i].LoginTime.Unix() < s[j].LoginTime.Unix() 70 } 71 72 func (s sessionsInfo) Swap(i, j int) { 73 s[i], s[j] = s[j], s[i] 74 } 75 76 // ////////////////////////////////////////////////////////////////////////////////// // 77 78 // Current user info cache 79 var curUser *User 80 81 // ////////////////////////////////////////////////////////////////////////////////// // 82 83 // Who return info about all active sessions sorted by login time 84 func Who() ([]*SessionInfo, error) { 85 var result []*SessionInfo 86 87 ptsList := readDir(_PTS_DIR) 88 89 if len(ptsList) == 0 { 90 return result, nil 91 } 92 93 for _, file := range ptsList { 94 if file == "ptmx" { 95 continue 96 } 97 98 info, err := getSessionInfo(file) 99 100 if err != nil { 101 continue 102 } 103 104 result = append(result, info) 105 } 106 107 if len(result) != 0 { 108 sort.Sort(sessionsInfo(result)) 109 } 110 111 return result, nil 112 } 113 114 // CurrentUser return struct with info about current user 115 func CurrentUser(avoidCache ...bool) (*User, error) { 116 if len(avoidCache) == 0 && curUser != nil { 117 return curUser, nil 118 } 119 120 user, err := LookupUser(getCurrentUserName()) 121 122 if err != nil { 123 return user, err 124 } 125 126 if user.Name == "root" { 127 appendRealUserInfo(user) 128 } 129 130 curUser = user 131 132 return user, nil 133 } 134 135 // LookupUser search user info by given name 136 func LookupUser(nameOrID string) (*User, error) { 137 if nameOrID == "" { 138 return nil, errors.New("User name/id can't be blank") 139 } 140 141 user, err := getUserInfo(nameOrID) 142 143 if err != nil { 144 return nil, err 145 } 146 147 appendGroupInfo(user) 148 149 return user, nil 150 } 151 152 // LookupGroup search group info by given name 153 func LookupGroup(nameOrID string) (*Group, error) { 154 if nameOrID == "" { 155 return nil, errors.New("Group name/id can't be blank") 156 } 157 158 name, gid, err := getGroupInfo(nameOrID) 159 160 return &Group{Name: name, GID: gid}, err 161 } 162 163 // IsUserExist check if user exist on system or not 164 func IsUserExist(name string) bool { 165 cmd := exec.Command("getent", "passwd", name) 166 167 err := cmd.Run() 168 169 if err == nil { 170 return true 171 } 172 173 return false 174 } 175 176 // IsGroupExist check if group exist on system or not 177 func IsGroupExist(name string) bool { 178 cmd := exec.Command("getent", "group", name) 179 180 err := cmd.Run() 181 182 if err == nil { 183 return true 184 } 185 186 return false 187 } 188 189 // ////////////////////////////////////////////////////////////////////////////////// // 190 191 // IsRoot check if current user is root 192 func (u *User) IsRoot() bool { 193 return u.UID == 0 && u.GID == 0 194 } 195 196 // IsSudo check if it user over sudo command 197 func (u *User) IsSudo() bool { 198 return u.IsRoot() && u.RealUID != 0 && u.RealGID != 0 199 } 200 201 // GroupList return slice with user groups names 202 func (u *User) GroupList() []string { 203 var result []string 204 205 for _, group := range u.Groups { 206 result = append(result, group.Name) 207 } 208 209 return result 210 } 211 212 // ////////////////////////////////////////////////////////////////////////////////// // 213 214 func getCurrentUserName() string { 215 cmd := exec.Command("id", "-un") 216 217 out, err := cmd.Output() 218 219 if err != nil { 220 return "" 221 } 222 223 sOut := string(out[:]) 224 sOut = strings.Trim(sOut, "\n") 225 226 return sOut 227 } 228 229 // appendGroupInfo append info about groups 230 func appendGroupInfo(user *User) { 231 cmd := exec.Command("id", user.Name) 232 233 out, err := cmd.Output() 234 235 if err != nil { 236 return 237 } 238 239 sOut := string(out[:]) 240 sOut = strings.Trim(sOut, "\n") 241 aOut := strings.Split(sOut, "=") 242 243 if len(aOut) < 4 { 244 return 245 } 246 247 for _, info := range strings.Split(aOut[3], ",") { 248 user.Groups = append(user.Groups, parseGroupInfo(info)) 249 } 250 } 251 252 // appendRealUserInfo append real user info when user under sudo 253 func appendRealUserInfo(user *User) { 254 username, uid, gid := getRealUserByPTY() 255 256 if username == "" { 257 username, uid, gid = getRealUserFromEnv() 258 } 259 260 user.RealName = username 261 user.RealUID = uid 262 user.RealGID = gid 263 } 264 265 // getUserInfo return uid associated with current tty 266 func getTDOwnerID() (int, bool) { 267 sPid := strconv.Itoa(os.Getpid()) 268 269 fdLink, err := os.Readlink("/proc/" + sPid + "/fd/0") 270 271 if err != nil { 272 return -1, false 273 } 274 275 ownerID, err := getOwner(fdLink) 276 277 return ownerID, err == nil 278 } 279 280 // getRealUserByPTY try to find info about real user from real user PTY 281 func getRealUserByPTY() (string, int, int) { 282 ownerID, ok := getTDOwnerID() 283 284 if !ok { 285 return "", -1, -1 286 } 287 288 realUser, err := getUserInfo(strconv.Itoa(ownerID)) 289 290 if err != nil { 291 return "", -1, -1 292 } 293 294 return realUser.Name, realUser.UID, realUser.GID 295 } 296 297 // getRealUserFromEnv try to find info about real user in environment variables 298 func getRealUserFromEnv() (string, int, int) { 299 e := env.Get() 300 301 if e["SUDO_USER"] == "" || e["SUDO_UID"] == "" || e["SUDO_GID"] == "" { 302 return "", -1, -1 303 } 304 305 user := e["SUDO_USER"] 306 uid, _ := strconv.Atoi(e["SUDO_UID"]) 307 gid, _ := strconv.Atoi(e["SUDO_GID"]) 308 309 return user, uid, gid 310 } 311 312 // getGroupInfo return group info by name or id 313 func getGroupInfo(nameOrID string) (string, int, error) { 314 cmd := exec.Command("getent", "group", nameOrID) 315 316 out, err := cmd.Output() 317 318 if err != nil { 319 return "", -1, fmt.Errorf("Group with this name/id %s is not exist", nameOrID) 320 } 321 322 sOut := string(out[:]) 323 sOut = strings.Trim(sOut, "\n") 324 aOut := strings.Split(sOut, ":") 325 326 gid, _ := strconv.Atoi(aOut[1]) 327 328 return aOut[0], gid, nil 329 } 330 331 // parseGroupInfo remove bracket symbols, parse value as number and return result 332 func parseGroupInfo(info string) *Group { 333 ai := strings.Split(info, "(") 334 335 gid, _ := strconv.Atoi(ai[0]) 336 name := strings.TrimRight(ai[1], ")") 337 338 return &Group{name, gid} 339 } 340 341 // getOwner return file or dir owner uid 342 func getOwner(path string) (int, error) { 343 if path == "" { 344 return -1, errors.New("Path is empty") 345 } 346 347 var stat = &syscall.Stat_t{} 348 349 err := syscall.Stat(path, stat) 350 351 if err != nil { 352 return -1, err 353 } 354 355 return int(stat.Uid), nil 356 } 357 358 func readDir(dir string) []string { 359 fd, err := syscall.Open(dir, syscall.O_CLOEXEC, 0644) 360 361 if err != nil { 362 return []string{} 363 } 364 365 var size = 100 366 var n = -1 367 368 var nbuf int 369 var bufp int 370 371 var buf = make([]byte, 4096) 372 var names = make([]string, 0, size) 373 374 for n != 0 { 375 if bufp >= nbuf { 376 bufp = 0 377 378 var errno error 379 380 nbuf, errno = fixCount(syscall.ReadDirent(fd, buf)) 381 382 if errno != nil { 383 return names 384 } 385 386 if nbuf <= 0 { 387 break 388 } 389 } 390 391 var nb, nc int 392 nb, nc, names = syscall.ParseDirent(buf[bufp:nbuf], n, names) 393 bufp += nb 394 n -= nc 395 } 396 397 return names 398 } 399 400 func fixCount(n int, err error) (int, error) { 401 if n < 0 { 402 n = 0 403 } 404 return n, err 405 } 406 407 func getSessionInfo(pts string) (*SessionInfo, error) { 408 ptsFile := _PTS_DIR + "/" + pts 409 uid, err := getOwner(ptsFile) 410 411 if err != nil { 412 return nil, err 413 } 414 415 user, err := getUserInfo(strconv.Itoa(uid)) 416 417 if err != nil { 418 return nil, err 419 } 420 421 _, mtime, ctime, err := getTimes(ptsFile) 422 423 if err != nil { 424 return nil, err 425 } 426 427 return &SessionInfo{ 428 User: user, 429 LoginTime: ctime, 430 LastActivityTime: mtime, 431 }, nil 432 }