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  }