github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/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 }