github.com/opensuse/umoci@v0.4.2/third_party/user/user.go (about)

     1  /*
     2   * Imported from opencontainers/runc/libcontainer/user.
     3   * Copyright (C) 2014 Docker, Inc.
     4   * Copyright (C) The Linux Foundation
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   *     http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package user
    20  
    21  import (
    22  	"bufio"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  )
    29  
    30  const (
    31  	minId = 0
    32  	maxId = 1<<31 - 1 //for 32-bit systems compatibility
    33  )
    34  
    35  var (
    36  	ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId)
    37  )
    38  
    39  type User struct {
    40  	Name  string
    41  	Pass  string
    42  	Uid   int
    43  	Gid   int
    44  	Gecos string
    45  	Home  string
    46  	Shell string
    47  }
    48  
    49  type Group struct {
    50  	Name string
    51  	Pass string
    52  	Gid  int
    53  	List []string
    54  }
    55  
    56  func parseLine(line string, v ...interface{}) {
    57  	if line == "" {
    58  		return
    59  	}
    60  
    61  	parts := strings.Split(line, ":")
    62  	for i, p := range parts {
    63  		// Ignore cases where we don't have enough fields to populate the arguments.
    64  		// Some configuration files like to misbehave.
    65  		if len(v) <= i {
    66  			break
    67  		}
    68  
    69  		// Use the type of the argument to figure out how to parse it, scanf() style.
    70  		// This is legit.
    71  		switch e := v[i].(type) {
    72  		case *string:
    73  			*e = p
    74  		case *int:
    75  			// "numbers", with conversion errors ignored because of some misbehaving configuration files.
    76  			*e, _ = strconv.Atoi(p)
    77  		case *[]string:
    78  			// Comma-separated lists.
    79  			if p != "" {
    80  				*e = strings.Split(p, ",")
    81  			} else {
    82  				*e = []string{}
    83  			}
    84  		default:
    85  			// Someone goof'd when writing code using this function. Scream so they can hear us.
    86  			panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
    87  		}
    88  	}
    89  }
    90  
    91  func ParsePasswdFile(path string) ([]User, error) {
    92  	passwd, err := os.Open(path)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	defer passwd.Close()
    97  	return ParsePasswd(passwd)
    98  }
    99  
   100  func ParsePasswd(passwd io.Reader) ([]User, error) {
   101  	return ParsePasswdFilter(passwd, nil)
   102  }
   103  
   104  func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
   105  	passwd, err := os.Open(path)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	defer passwd.Close()
   110  	return ParsePasswdFilter(passwd, filter)
   111  }
   112  
   113  func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
   114  	if r == nil {
   115  		return nil, fmt.Errorf("nil source for passwd-formatted data")
   116  	}
   117  
   118  	var (
   119  		s   = bufio.NewScanner(r)
   120  		out = []User{}
   121  	)
   122  
   123  	for s.Scan() {
   124  		if err := s.Err(); err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		line := strings.TrimSpace(s.Text())
   129  		if line == "" {
   130  			continue
   131  		}
   132  
   133  		// see: man 5 passwd
   134  		//  name:password:UID:GID:GECOS:directory:shell
   135  		// Name:Pass:Uid:Gid:Gecos:Home:Shell
   136  		//  root:x:0:0:root:/root:/bin/bash
   137  		//  adm:x:3:4:adm:/var/adm:/bin/false
   138  		p := User{}
   139  		parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
   140  
   141  		if filter == nil || filter(p) {
   142  			out = append(out, p)
   143  		}
   144  	}
   145  
   146  	return out, nil
   147  }
   148  
   149  func ParseGroupFile(path string) ([]Group, error) {
   150  	group, err := os.Open(path)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	defer group.Close()
   156  	return ParseGroup(group)
   157  }
   158  
   159  func ParseGroup(group io.Reader) ([]Group, error) {
   160  	return ParseGroupFilter(group, nil)
   161  }
   162  
   163  func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
   164  	group, err := os.Open(path)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	defer group.Close()
   169  	return ParseGroupFilter(group, filter)
   170  }
   171  
   172  func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
   173  	if r == nil {
   174  		return nil, fmt.Errorf("nil source for group-formatted data")
   175  	}
   176  
   177  	var (
   178  		s   = bufio.NewScanner(r)
   179  		out = []Group{}
   180  	)
   181  
   182  	for s.Scan() {
   183  		if err := s.Err(); err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		text := s.Text()
   188  		if text == "" {
   189  			continue
   190  		}
   191  
   192  		// see: man 5 group
   193  		//  group_name:password:GID:user_list
   194  		// Name:Pass:Gid:List
   195  		//  root:x:0:root
   196  		//  adm:x:4:root,adm,daemon
   197  		p := Group{}
   198  		parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List)
   199  
   200  		if filter == nil || filter(p) {
   201  			out = append(out, p)
   202  		}
   203  	}
   204  
   205  	return out, nil
   206  }
   207  
   208  type ExecUser struct {
   209  	Uid   int
   210  	Gid   int
   211  	Sgids []int
   212  	Home  string
   213  }
   214  
   215  // GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
   216  // given file paths and uses that data as the arguments to GetExecUser. If the
   217  // files cannot be opened for any reason, the error is ignored and a nil
   218  // io.Reader is passed instead.
   219  func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
   220  	passwd, err := os.Open(passwdPath)
   221  	if err != nil {
   222  		passwd = nil
   223  	} else {
   224  		defer passwd.Close()
   225  	}
   226  
   227  	group, err := os.Open(groupPath)
   228  	if err != nil {
   229  		group = nil
   230  	} else {
   231  		defer group.Close()
   232  	}
   233  
   234  	return GetExecUser(userSpec, defaults, passwd, group)
   235  }
   236  
   237  // GetExecUser parses a user specification string (using the passwd and group
   238  // readers as sources for /etc/passwd and /etc/group data, respectively). In
   239  // the case of blank fields or missing data from the sources, the values in
   240  // defaults is used.
   241  //
   242  // GetExecUser will return an error if a user or group literal could not be
   243  // found in any entry in passwd and group respectively.
   244  //
   245  // Examples of valid user specifications are:
   246  //     * ""
   247  //     * "user"
   248  //     * "uid"
   249  //     * "user:group"
   250  //     * "uid:gid
   251  //     * "user:gid"
   252  //     * "uid:group"
   253  //
   254  // It should be noted that if you specify a numeric user or group id, they will
   255  // not be evaluated as usernames (only the metadata will be filled). So attempting
   256  // to parse a user with user.Name = "1337" will produce the user with a UID of
   257  // 1337.
   258  func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
   259  	if defaults == nil {
   260  		defaults = new(ExecUser)
   261  	}
   262  
   263  	// Copy over defaults.
   264  	user := &ExecUser{
   265  		Uid:   defaults.Uid,
   266  		Gid:   defaults.Gid,
   267  		Sgids: defaults.Sgids,
   268  		Home:  defaults.Home,
   269  	}
   270  
   271  	// Sgids slice *cannot* be nil.
   272  	if user.Sgids == nil {
   273  		user.Sgids = []int{}
   274  	}
   275  
   276  	// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
   277  	var userArg, groupArg string
   278  	parseLine(userSpec, &userArg, &groupArg)
   279  
   280  	// Convert userArg and groupArg to be numeric, so we don't have to execute
   281  	// Atoi *twice* for each iteration over lines.
   282  	uidArg, uidErr := strconv.Atoi(userArg)
   283  	gidArg, gidErr := strconv.Atoi(groupArg)
   284  
   285  	// Find the matching user.
   286  	users, err := ParsePasswdFilter(passwd, func(u User) bool {
   287  		if userArg == "" {
   288  			// Default to current state of the user.
   289  			return u.Uid == user.Uid
   290  		}
   291  
   292  		if uidErr == nil {
   293  			// If the userArg is numeric, always treat it as a UID.
   294  			return uidArg == u.Uid
   295  		}
   296  
   297  		return u.Name == userArg
   298  	})
   299  
   300  	// If we can't find the user, we have to bail.
   301  	if err != nil && passwd != nil {
   302  		if userArg == "" {
   303  			userArg = strconv.Itoa(user.Uid)
   304  		}
   305  		return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
   306  	}
   307  
   308  	var matchedUserName string
   309  	if len(users) > 0 {
   310  		// First match wins, even if there's more than one matching entry.
   311  		matchedUserName = users[0].Name
   312  		user.Uid = users[0].Uid
   313  		user.Gid = users[0].Gid
   314  		user.Home = users[0].Home
   315  	} else if userArg != "" {
   316  		// If we can't find a user with the given username, the only other valid
   317  		// option is if it's a numeric username with no associated entry in passwd.
   318  
   319  		if uidErr != nil {
   320  			// Not numeric.
   321  			return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
   322  		}
   323  		user.Uid = uidArg
   324  
   325  		// Must be inside valid uid range.
   326  		if user.Uid < minId || user.Uid > maxId {
   327  			return nil, ErrRange
   328  		}
   329  
   330  		// Okay, so it's numeric. We can just roll with this.
   331  	}
   332  
   333  	// On to the groups. If we matched a username, we need to do this because of
   334  	// the supplementary group IDs.
   335  	if groupArg != "" || matchedUserName != "" {
   336  		groups, err := ParseGroupFilter(group, func(g Group) bool {
   337  			// If the group argument isn't explicit, we'll just search for it.
   338  			if groupArg == "" {
   339  				// Check if user is a member of this group.
   340  				for _, u := range g.List {
   341  					if u == matchedUserName {
   342  						return true
   343  					}
   344  				}
   345  				return false
   346  			}
   347  
   348  			if gidErr == nil {
   349  				// If the groupArg is numeric, always treat it as a GID.
   350  				return gidArg == g.Gid
   351  			}
   352  
   353  			return g.Name == groupArg
   354  		})
   355  		if err != nil && group != nil {
   356  			return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
   357  		}
   358  
   359  		// Only start modifying user.Gid if it is in explicit form.
   360  		if groupArg != "" {
   361  			if len(groups) > 0 {
   362  				// First match wins, even if there's more than one matching entry.
   363  				user.Gid = groups[0].Gid
   364  			} else if groupArg != "" {
   365  				// If we can't find a group with the given name, the only other valid
   366  				// option is if it's a numeric group name with no associated entry in group.
   367  
   368  				if gidErr != nil {
   369  					// Not numeric.
   370  					return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
   371  				}
   372  				user.Gid = gidArg
   373  
   374  				// Must be inside valid gid range.
   375  				if user.Gid < minId || user.Gid > maxId {
   376  					return nil, ErrRange
   377  				}
   378  
   379  				// Okay, so it's numeric. We can just roll with this.
   380  			}
   381  		} else if len(groups) > 0 && uidErr != nil {
   382  			// Supplementary group ids only make sense if in the implicit form.
   383  			user.Sgids = make([]int, len(groups))
   384  			for i, group := range groups {
   385  				user.Sgids[i] = group.Gid
   386  			}
   387  		}
   388  	}
   389  
   390  	return user, nil
   391  }
   392  
   393  // GetAdditionalGroups looks up a list of groups by name or group id
   394  // against the given /etc/group formatted data. If a group name cannot
   395  // be found, an error will be returned. If a group id cannot be found,
   396  // or the given group data is nil, the id will be returned as-is
   397  // provided it is in the legal range.
   398  func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
   399  	var groups = []Group{}
   400  	if group != nil {
   401  		var err error
   402  		groups, err = ParseGroupFilter(group, func(g Group) bool {
   403  			for _, ag := range additionalGroups {
   404  				if g.Name == ag || strconv.Itoa(g.Gid) == ag {
   405  					return true
   406  				}
   407  			}
   408  			return false
   409  		})
   410  		if err != nil {
   411  			return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
   412  		}
   413  	}
   414  
   415  	gidMap := make(map[int]struct{})
   416  	for _, ag := range additionalGroups {
   417  		var found bool
   418  		for _, g := range groups {
   419  			// if we found a matched group either by name or gid, take the
   420  			// first matched as correct
   421  			if g.Name == ag || strconv.Itoa(g.Gid) == ag {
   422  				if _, ok := gidMap[g.Gid]; !ok {
   423  					gidMap[g.Gid] = struct{}{}
   424  					found = true
   425  					break
   426  				}
   427  			}
   428  		}
   429  		// we asked for a group but didn't find it. let's check to see
   430  		// if we wanted a numeric group
   431  		if !found {
   432  			gid, err := strconv.Atoi(ag)
   433  			if err != nil {
   434  				return nil, fmt.Errorf("Unable to find group %s", ag)
   435  			}
   436  			// Ensure gid is inside gid range.
   437  			if gid < minId || gid > maxId {
   438  				return nil, ErrRange
   439  			}
   440  			gidMap[gid] = struct{}{}
   441  		}
   442  	}
   443  	gids := []int{}
   444  	for gid := range gidMap {
   445  		gids = append(gids, gid)
   446  	}
   447  	return gids, nil
   448  }
   449  
   450  // GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
   451  // that opens the groupPath given and gives it as an argument to
   452  // GetAdditionalGroups.
   453  func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
   454  	group, err := os.Open(groupPath)
   455  	if err == nil {
   456  		defer group.Close()
   457  	}
   458  	return GetAdditionalGroups(additionalGroups, group)
   459  }