github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/pkg/idtools/usergroupadd_linux.go (about)

     1  package idtools
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  	"path/filepath"
     7  	"strings"
     8  	"syscall"
     9  )
    10  
    11  // add a user and/or group to Linux /etc/passwd, /etc/group using standard
    12  // Linux distribution commands:
    13  // adduser --uid <id> --shell /bin/login --no-create-home --disabled-login --ingroup <groupname> <username>
    14  // useradd -M -u <id> -s /bin/nologin -N -g <groupname> <username>
    15  // addgroup --gid <id> <groupname>
    16  // groupadd -g <id> <groupname>
    17  
    18  const baseUID int = 10000
    19  const baseGID int = 10000
    20  const idMAX int = 65534
    21  
    22  var (
    23  	userCommand  string
    24  	groupCommand string
    25  
    26  	cmdTemplates = map[string]string{
    27  		"adduser":  "--uid %d --shell /bin/false --no-create-home --disabled-login --ingroup %s %s",
    28  		"useradd":  "-M -u %d -s /bin/false -N -g %s %s",
    29  		"addgroup": "--gid %d %s",
    30  		"groupadd": "-g %d %s",
    31  	}
    32  )
    33  
    34  func init() {
    35  	// set up which commands are used for adding users/groups dependent on distro
    36  	if _, err := resolveBinary("adduser"); err == nil {
    37  		userCommand = "adduser"
    38  	} else if _, err := resolveBinary("useradd"); err == nil {
    39  		userCommand = "useradd"
    40  	}
    41  	if _, err := resolveBinary("addgroup"); err == nil {
    42  		groupCommand = "addgroup"
    43  	} else if _, err := resolveBinary("groupadd"); err == nil {
    44  		groupCommand = "groupadd"
    45  	}
    46  }
    47  
    48  func resolveBinary(binname string) (string, error) {
    49  	binaryPath, err := exec.LookPath(binname)
    50  	if err != nil {
    51  		return "", err
    52  	}
    53  	resolvedPath, err := filepath.EvalSymlinks(binaryPath)
    54  	if err != nil {
    55  		return "", err
    56  	}
    57  	//only return no error if the final resolved binary basename
    58  	//matches what was searched for
    59  	if filepath.Base(resolvedPath) == binname {
    60  		return resolvedPath, nil
    61  	}
    62  	return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
    63  }
    64  
    65  // AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
    66  // and calls the appropriate helper function to add the group and then
    67  // the user to the group in /etc/group and /etc/passwd respectively.
    68  // This new user's /etc/sub{uid,gid} ranges will be used for user namespace
    69  // mapping ranges in containers.
    70  func AddNamespaceRangesUser(name string) (int, int, error) {
    71  	// Find unused uid, gid pair
    72  	uid, err := findUnusedUID(baseUID)
    73  	if err != nil {
    74  		return -1, -1, fmt.Errorf("Unable to find unused UID: %v", err)
    75  	}
    76  	gid, err := findUnusedGID(baseGID)
    77  	if err != nil {
    78  		return -1, -1, fmt.Errorf("Unable to find unused GID: %v", err)
    79  	}
    80  
    81  	// First add the group that we will use
    82  	if err := addGroup(name, gid); err != nil {
    83  		return -1, -1, fmt.Errorf("Error adding group %q: %v", name, err)
    84  	}
    85  	// Add the user as a member of the group
    86  	if err := addUser(name, uid, name); err != nil {
    87  		return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
    88  	}
    89  	return uid, gid, nil
    90  }
    91  
    92  func addUser(userName string, uid int, groupName string) error {
    93  
    94  	if userCommand == "" {
    95  		return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
    96  	}
    97  	args := fmt.Sprintf(cmdTemplates[userCommand], uid, groupName, userName)
    98  	return execAddCmd(userCommand, args)
    99  }
   100  
   101  func addGroup(groupName string, gid int) error {
   102  
   103  	if groupCommand == "" {
   104  		return fmt.Errorf("Cannot add group; no groupadd/addgroup binary found")
   105  	}
   106  	args := fmt.Sprintf(cmdTemplates[groupCommand], gid, groupName)
   107  	// only error out if the error isn't that the group already exists
   108  	// if the group exists then our needs are already met
   109  	if err := execAddCmd(groupCommand, args); err != nil && !strings.Contains(err.Error(), "already exists") {
   110  		return err
   111  	}
   112  	return nil
   113  }
   114  
   115  func execAddCmd(cmd, args string) error {
   116  	execCmd := exec.Command(cmd, strings.Split(args, " ")...)
   117  	out, err := execCmd.CombinedOutput()
   118  	if err != nil {
   119  		return fmt.Errorf("Failed to add user/group with error: %v; output: %q", err, string(out))
   120  	}
   121  	return nil
   122  }
   123  
   124  func findUnusedUID(startUID int) (int, error) {
   125  	return findUnused("passwd", startUID)
   126  }
   127  
   128  func findUnusedGID(startGID int) (int, error) {
   129  	return findUnused("group", startGID)
   130  }
   131  
   132  func findUnused(file string, id int) (int, error) {
   133  	for {
   134  		cmdStr := fmt.Sprintf("cat /etc/%s | cut -d: -f3 | grep '^%d$'", file, id)
   135  		cmd := exec.Command("sh", "-c", cmdStr)
   136  		if err := cmd.Run(); err != nil {
   137  			// if a non-zero return code occurs, then we know the ID was not found
   138  			// and is usable
   139  			if exiterr, ok := err.(*exec.ExitError); ok {
   140  				// The program has exited with an exit code != 0
   141  				if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   142  					if status.ExitStatus() == 1 {
   143  						//no match, we can use this ID
   144  						return id, nil
   145  					}
   146  				}
   147  			}
   148  			return -1, fmt.Errorf("Error looking in /etc/%s for unused ID: %v", file, err)
   149  		}
   150  		id++
   151  		if id > idMAX {
   152  			return -1, fmt.Errorf("Maximum id in %q reached with finding unused numeric ID", file)
   153  		}
   154  	}
   155  }