| ... | ... |
@@ -27,10 +27,10 @@ import ( |
| 27 | 27 |
"github.com/docker/docker/pkg/listenbuffer" |
| 28 | 28 |
"github.com/docker/docker/pkg/parsers" |
| 29 | 29 |
"github.com/docker/docker/pkg/systemd" |
| 30 |
- "github.com/docker/docker/pkg/user" |
|
| 31 | 30 |
"github.com/docker/docker/pkg/version" |
| 32 | 31 |
"github.com/docker/docker/registry" |
| 33 | 32 |
"github.com/docker/docker/utils" |
| 33 |
+ "github.com/docker/libcontainer/user" |
|
| 34 | 34 |
"github.com/gorilla/mux" |
| 35 | 35 |
) |
| 36 | 36 |
|
| ... | ... |
@@ -121,7 +121,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
| 121 | 121 |
b.config = image.Config |
| 122 | 122 |
} |
| 123 | 123 |
if b.config.Env == nil || len(b.config.Env) == 0 {
|
| 124 |
- b.config.Env = append(b.config.Env, "HOME=/", "PATH="+daemon.DefaultPathEnv) |
|
| 124 |
+ b.config.Env = append(b.config.Env, "PATH="+daemon.DefaultPathEnv) |
|
| 125 | 125 |
} |
| 126 | 126 |
// Process ONBUILD triggers if they exist |
| 127 | 127 |
if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
|
| ... | ... |
@@ -1043,9 +1043,12 @@ func (container *Container) setupLinkedContainers() ([]string, error) {
|
| 1043 | 1043 |
func (container *Container) createDaemonEnvironment(linkedEnv []string) []string {
|
| 1044 | 1044 |
// Setup environment |
| 1045 | 1045 |
env := []string{
|
| 1046 |
- "HOME=/", |
|
| 1047 | 1046 |
"PATH=" + DefaultPathEnv, |
| 1048 | 1047 |
"HOSTNAME=" + container.Config.Hostname, |
| 1048 |
+ // Note: we don't set HOME here because it'll get autoset intelligently |
|
| 1049 |
+ // based on the value of USER inside dockerinit, but only if it isn't |
|
| 1050 |
+ // set already (ie, that can be overridden by setting HOME via -e or ENV |
|
| 1051 |
+ // in a Dockerfile). |
|
| 1049 | 1052 |
} |
| 1050 | 1053 |
if container.Config.Tty {
|
| 1051 | 1054 |
env = append(env, "TERM=xterm") |
| ... | ... |
@@ -63,4 +63,4 @@ mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar |
| 63 | 63 |
|
| 64 | 64 |
clone git github.com/godbus/dbus v1 |
| 65 | 65 |
clone git github.com/coreos/go-systemd v2 |
| 66 |
-clone git github.com/docker/libcontainer e6a43c1c2b9f769deb96348a0a93417cd48a36d8 |
|
| 66 |
+clone git github.com/docker/libcontainer bc06326a5e7decdc4191d1367de8439b9d83c450 |
| ... | ... |
@@ -723,7 +723,7 @@ func TestBuildRelativeWorkdir(t *testing.T) {
|
| 723 | 723 |
|
| 724 | 724 |
func TestBuildEnv(t *testing.T) {
|
| 725 | 725 |
name := "testbuildenv" |
| 726 |
- expected := "[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" |
|
| 726 |
+ expected := "[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" |
|
| 727 | 727 |
defer deleteImages(name) |
| 728 | 728 |
_, err := buildImage(name, |
| 729 | 729 |
`FROM busybox |
| ... | ... |
@@ -657,7 +657,7 @@ func TestRunTwoConcurrentContainers(t *testing.T) {
|
| 657 | 657 |
} |
| 658 | 658 |
|
| 659 | 659 |
func TestEnvironment(t *testing.T) {
|
| 660 |
- cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") |
|
| 660 |
+ cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "-e=HOME=", "busybox", "env") |
|
| 661 | 661 |
cmd.Env = append(os.Environ(), |
| 662 | 662 |
"TRUE=false", |
| 663 | 663 |
"TRICKY=tri\ncky\n", |
| ... | ... |
@@ -676,13 +676,13 @@ func TestEnvironment(t *testing.T) {
|
| 676 | 676 |
|
| 677 | 677 |
goodEnv := []string{
|
| 678 | 678 |
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |
| 679 |
- "HOME=/", |
|
| 680 | 679 |
"HOSTNAME=testing", |
| 681 | 680 |
"FALSE=true", |
| 682 | 681 |
"TRUE=false", |
| 683 | 682 |
"TRICKY=tri", |
| 684 | 683 |
"cky", |
| 685 | 684 |
"", |
| 685 |
+ "HOME=/root", |
|
| 686 | 686 |
} |
| 687 | 687 |
sort.Strings(goodEnv) |
| 688 | 688 |
if len(goodEnv) != len(actualEnv) {
|
| 2 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,256 +0,0 @@ |
| 1 |
-package user |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "fmt" |
|
| 6 |
- "io" |
|
| 7 |
- "os" |
|
| 8 |
- "strconv" |
|
| 9 |
- "strings" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-const ( |
|
| 13 |
- minId = 0 |
|
| 14 |
- maxId = 1<<31 - 1 //for 32-bit systems compatibility |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-var ( |
|
| 18 |
- ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId)
|
|
| 19 |
-) |
|
| 20 |
- |
|
| 21 |
-type User struct {
|
|
| 22 |
- Name string |
|
| 23 |
- Pass string |
|
| 24 |
- Uid int |
|
| 25 |
- Gid int |
|
| 26 |
- Gecos string |
|
| 27 |
- Home string |
|
| 28 |
- Shell string |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-type Group struct {
|
|
| 32 |
- Name string |
|
| 33 |
- Pass string |
|
| 34 |
- Gid int |
|
| 35 |
- List []string |
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-func parseLine(line string, v ...interface{}) {
|
|
| 39 |
- if line == "" {
|
|
| 40 |
- return |
|
| 41 |
- } |
|
| 42 |
- |
|
| 43 |
- parts := strings.Split(line, ":") |
|
| 44 |
- for i, p := range parts {
|
|
| 45 |
- if len(v) <= i {
|
|
| 46 |
- // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files |
|
| 47 |
- break |
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- switch e := v[i].(type) {
|
|
| 51 |
- case *string: |
|
| 52 |
- // "root", "adm", "/bin/bash" |
|
| 53 |
- *e = p |
|
| 54 |
- case *int: |
|
| 55 |
- // "0", "4", "1000" |
|
| 56 |
- // ignore string to int conversion errors, for great "tolerance" of naughty configuration files |
|
| 57 |
- *e, _ = strconv.Atoi(p) |
|
| 58 |
- case *[]string: |
|
| 59 |
- // "", "root", "root,adm,daemon" |
|
| 60 |
- if p != "" {
|
|
| 61 |
- *e = strings.Split(p, ",") |
|
| 62 |
- } else {
|
|
| 63 |
- *e = []string{}
|
|
| 64 |
- } |
|
| 65 |
- default: |
|
| 66 |
- // panic, because this is a programming/logic error, not a runtime one |
|
| 67 |
- panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!")
|
|
| 68 |
- } |
|
| 69 |
- } |
|
| 70 |
-} |
|
| 71 |
- |
|
| 72 |
-func ParsePasswd() ([]*User, error) {
|
|
| 73 |
- return ParsePasswdFilter(nil) |
|
| 74 |
-} |
|
| 75 |
- |
|
| 76 |
-func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
|
|
| 77 |
- f, err := os.Open("/etc/passwd")
|
|
| 78 |
- if err != nil {
|
|
| 79 |
- return nil, err |
|
| 80 |
- } |
|
| 81 |
- defer f.Close() |
|
| 82 |
- return parsePasswdFile(f, filter) |
|
| 83 |
-} |
|
| 84 |
- |
|
| 85 |
-func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
|
|
| 86 |
- var ( |
|
| 87 |
- s = bufio.NewScanner(r) |
|
| 88 |
- out = []*User{}
|
|
| 89 |
- ) |
|
| 90 |
- |
|
| 91 |
- for s.Scan() {
|
|
| 92 |
- if err := s.Err(); err != nil {
|
|
| 93 |
- return nil, err |
|
| 94 |
- } |
|
| 95 |
- |
|
| 96 |
- text := strings.TrimSpace(s.Text()) |
|
| 97 |
- if text == "" {
|
|
| 98 |
- continue |
|
| 99 |
- } |
|
| 100 |
- |
|
| 101 |
- // see: man 5 passwd |
|
| 102 |
- // name:password:UID:GID:GECOS:directory:shell |
|
| 103 |
- // Name:Pass:Uid:Gid:Gecos:Home:Shell |
|
| 104 |
- // root:x:0:0:root:/root:/bin/bash |
|
| 105 |
- // adm:x:3:4:adm:/var/adm:/bin/false |
|
| 106 |
- p := &User{}
|
|
| 107 |
- parseLine( |
|
| 108 |
- text, |
|
| 109 |
- &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, |
|
| 110 |
- ) |
|
| 111 |
- |
|
| 112 |
- if filter == nil || filter(p) {
|
|
| 113 |
- out = append(out, p) |
|
| 114 |
- } |
|
| 115 |
- } |
|
| 116 |
- |
|
| 117 |
- return out, nil |
|
| 118 |
-} |
|
| 119 |
- |
|
| 120 |
-func ParseGroup() ([]*Group, error) {
|
|
| 121 |
- return ParseGroupFilter(nil) |
|
| 122 |
-} |
|
| 123 |
- |
|
| 124 |
-func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
|
|
| 125 |
- f, err := os.Open("/etc/group")
|
|
| 126 |
- if err != nil {
|
|
| 127 |
- return nil, err |
|
| 128 |
- } |
|
| 129 |
- defer f.Close() |
|
| 130 |
- return parseGroupFile(f, filter) |
|
| 131 |
-} |
|
| 132 |
- |
|
| 133 |
-func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
|
|
| 134 |
- var ( |
|
| 135 |
- s = bufio.NewScanner(r) |
|
| 136 |
- out = []*Group{}
|
|
| 137 |
- ) |
|
| 138 |
- |
|
| 139 |
- for s.Scan() {
|
|
| 140 |
- if err := s.Err(); err != nil {
|
|
| 141 |
- return nil, err |
|
| 142 |
- } |
|
| 143 |
- |
|
| 144 |
- text := s.Text() |
|
| 145 |
- if text == "" {
|
|
| 146 |
- continue |
|
| 147 |
- } |
|
| 148 |
- |
|
| 149 |
- // see: man 5 group |
|
| 150 |
- // group_name:password:GID:user_list |
|
| 151 |
- // Name:Pass:Gid:List |
|
| 152 |
- // root:x:0:root |
|
| 153 |
- // adm:x:4:root,adm,daemon |
|
| 154 |
- p := &Group{}
|
|
| 155 |
- parseLine( |
|
| 156 |
- text, |
|
| 157 |
- &p.Name, &p.Pass, &p.Gid, &p.List, |
|
| 158 |
- ) |
|
| 159 |
- |
|
| 160 |
- if filter == nil || filter(p) {
|
|
| 161 |
- out = append(out, p) |
|
| 162 |
- } |
|
| 163 |
- } |
|
| 164 |
- |
|
| 165 |
- return out, nil |
|
| 166 |
-} |
|
| 167 |
- |
|
| 168 |
-// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, and list of supplementary group IDs, if possible. |
|
| 169 |
-func GetUserGroupSupplementary(userSpec string, defaultUid int, defaultGid int) (int, int, []int, error) {
|
|
| 170 |
- var ( |
|
| 171 |
- uid = defaultUid |
|
| 172 |
- gid = defaultGid |
|
| 173 |
- suppGids = []int{}
|
|
| 174 |
- |
|
| 175 |
- userArg, groupArg string |
|
| 176 |
- ) |
|
| 177 |
- |
|
| 178 |
- // allow for userArg to have either "user" syntax, or optionally "user:group" syntax |
|
| 179 |
- parseLine(userSpec, &userArg, &groupArg) |
|
| 180 |
- |
|
| 181 |
- users, err := ParsePasswdFilter(func(u *User) bool {
|
|
| 182 |
- if userArg == "" {
|
|
| 183 |
- return u.Uid == uid |
|
| 184 |
- } |
|
| 185 |
- return u.Name == userArg || strconv.Itoa(u.Uid) == userArg |
|
| 186 |
- }) |
|
| 187 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 188 |
- if userArg == "" {
|
|
| 189 |
- userArg = strconv.Itoa(uid) |
|
| 190 |
- } |
|
| 191 |
- return 0, 0, nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
|
|
| 192 |
- } |
|
| 193 |
- |
|
| 194 |
- haveUser := users != nil && len(users) > 0 |
|
| 195 |
- if haveUser {
|
|
| 196 |
- // if we found any user entries that matched our filter, let's take the first one as "correct" |
|
| 197 |
- uid = users[0].Uid |
|
| 198 |
- gid = users[0].Gid |
|
| 199 |
- } else if userArg != "" {
|
|
| 200 |
- // we asked for a user but didn't find them... let's check to see if we wanted a numeric user |
|
| 201 |
- uid, err = strconv.Atoi(userArg) |
|
| 202 |
- if err != nil {
|
|
| 203 |
- // not numeric - we have to bail |
|
| 204 |
- return 0, 0, nil, fmt.Errorf("Unable to find user %v", userArg)
|
|
| 205 |
- } |
|
| 206 |
- if uid < minId || uid > maxId {
|
|
| 207 |
- return 0, 0, nil, ErrRange |
|
| 208 |
- } |
|
| 209 |
- |
|
| 210 |
- // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit |
|
| 211 |
- } |
|
| 212 |
- |
|
| 213 |
- if groupArg != "" || (haveUser && users[0].Name != "") {
|
|
| 214 |
- groups, err := ParseGroupFilter(func(g *Group) bool {
|
|
| 215 |
- if groupArg != "" {
|
|
| 216 |
- return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg |
|
| 217 |
- } |
|
| 218 |
- for _, u := range g.List {
|
|
| 219 |
- if u == users[0].Name {
|
|
| 220 |
- return true |
|
| 221 |
- } |
|
| 222 |
- } |
|
| 223 |
- return false |
|
| 224 |
- }) |
|
| 225 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 226 |
- return 0, 0, nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
|
|
| 227 |
- } |
|
| 228 |
- |
|
| 229 |
- haveGroup := groups != nil && len(groups) > 0 |
|
| 230 |
- if groupArg != "" {
|
|
| 231 |
- if haveGroup {
|
|
| 232 |
- // if we found any group entries that matched our filter, let's take the first one as "correct" |
|
| 233 |
- gid = groups[0].Gid |
|
| 234 |
- } else {
|
|
| 235 |
- // we asked for a group but didn't find id... let's check to see if we wanted a numeric group |
|
| 236 |
- gid, err = strconv.Atoi(groupArg) |
|
| 237 |
- if err != nil {
|
|
| 238 |
- // not numeric - we have to bail |
|
| 239 |
- return 0, 0, nil, fmt.Errorf("Unable to find group %v", groupArg)
|
|
| 240 |
- } |
|
| 241 |
- if gid < minId || gid > maxId {
|
|
| 242 |
- return 0, 0, nil, ErrRange |
|
| 243 |
- } |
|
| 244 |
- |
|
| 245 |
- // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit |
|
| 246 |
- } |
|
| 247 |
- } else if haveGroup {
|
|
| 248 |
- suppGids = make([]int, len(groups)) |
|
| 249 |
- for i, group := range groups {
|
|
| 250 |
- suppGids[i] = group.Gid |
|
| 251 |
- } |
|
| 252 |
- } |
|
| 253 |
- } |
|
| 254 |
- |
|
| 255 |
- return uid, gid, suppGids, nil |
|
| 256 |
-} |
| 257 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,94 +0,0 @@ |
| 1 |
-package user |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "strings" |
|
| 5 |
- "testing" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-func TestUserParseLine(t *testing.T) {
|
|
| 9 |
- var ( |
|
| 10 |
- a, b string |
|
| 11 |
- c []string |
|
| 12 |
- d int |
|
| 13 |
- ) |
|
| 14 |
- |
|
| 15 |
- parseLine("", &a, &b)
|
|
| 16 |
- if a != "" || b != "" {
|
|
| 17 |
- t.Fatalf("a and b should be empty ('%v', '%v')", a, b)
|
|
| 18 |
- } |
|
| 19 |
- |
|
| 20 |
- parseLine("a", &a, &b)
|
|
| 21 |
- if a != "a" || b != "" {
|
|
| 22 |
- t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b)
|
|
| 23 |
- } |
|
| 24 |
- |
|
| 25 |
- parseLine("bad boys:corny cows", &a, &b)
|
|
| 26 |
- if a != "bad boys" || b != "corny cows" {
|
|
| 27 |
- t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b)
|
|
| 28 |
- } |
|
| 29 |
- |
|
| 30 |
- parseLine("", &c)
|
|
| 31 |
- if len(c) != 0 {
|
|
| 32 |
- t.Fatalf("c should be empty (%#v)", c)
|
|
| 33 |
- } |
|
| 34 |
- |
|
| 35 |
- parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c)
|
|
| 36 |
- if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" {
|
|
| 37 |
- t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c)
|
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- parseLine("::::::::::", &a, &b, &c)
|
|
| 41 |
- if a != "" || b != "" || len(c) != 0 {
|
|
| 42 |
- t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c)
|
|
| 43 |
- } |
|
| 44 |
- |
|
| 45 |
- parseLine("not a number", &d)
|
|
| 46 |
- if d != 0 {
|
|
| 47 |
- t.Fatalf("d should be 0 (%v)", d)
|
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- parseLine("b:12:c", &a, &d, &b)
|
|
| 51 |
- if a != "b" || b != "c" || d != 12 {
|
|
| 52 |
- t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d)
|
|
| 53 |
- } |
|
| 54 |
-} |
|
| 55 |
- |
|
| 56 |
-func TestUserParsePasswd(t *testing.T) {
|
|
| 57 |
- users, err := parsePasswdFile(strings.NewReader(` |
|
| 58 |
-root:x:0:0:root:/root:/bin/bash |
|
| 59 |
-adm:x:3:4:adm:/var/adm:/bin/false |
|
| 60 |
-this is just some garbage data |
|
| 61 |
-`), nil) |
|
| 62 |
- if err != nil {
|
|
| 63 |
- t.Fatalf("Unexpected error: %v", err)
|
|
| 64 |
- } |
|
| 65 |
- if len(users) != 3 {
|
|
| 66 |
- t.Fatalf("Expected 3 users, got %v", len(users))
|
|
| 67 |
- } |
|
| 68 |
- if users[0].Uid != 0 || users[0].Name != "root" {
|
|
| 69 |
- t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name)
|
|
| 70 |
- } |
|
| 71 |
- if users[1].Uid != 3 || users[1].Name != "adm" {
|
|
| 72 |
- t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name)
|
|
| 73 |
- } |
|
| 74 |
-} |
|
| 75 |
- |
|
| 76 |
-func TestUserParseGroup(t *testing.T) {
|
|
| 77 |
- groups, err := parseGroupFile(strings.NewReader(` |
|
| 78 |
-root:x:0:root |
|
| 79 |
-adm:x:4:root,adm,daemon |
|
| 80 |
-this is just some garbage data |
|
| 81 |
-`), nil) |
|
| 82 |
- if err != nil {
|
|
| 83 |
- t.Fatalf("Unexpected error: %v", err)
|
|
| 84 |
- } |
|
| 85 |
- if len(groups) != 3 {
|
|
| 86 |
- t.Fatalf("Expected 3 groups, got %v", len(groups))
|
|
| 87 |
- } |
|
| 88 |
- if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 {
|
|
| 89 |
- t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List))
|
|
| 90 |
- } |
|
| 91 |
- if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 {
|
|
| 92 |
- t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
|
|
| 93 |
- } |
|
| 94 |
-} |
| ... | ... |
@@ -21,10 +21,10 @@ install: |
| 21 | 21 |
- if [ -z "$TRAVIS_GLOBAL_WTF" ]; then go env; fi |
| 22 | 22 |
- go get -d -v ./... |
| 23 | 23 |
- if [ "$TRAVIS_GLOBAL_WTF" ]; then |
| 24 |
- export DOCKER_PATH="${GOPATH%%:*}/src/github.com/dotcloud/docker";
|
|
| 24 |
+ export DOCKER_PATH="${GOPATH%%:*}/src/github.com/docker/docker";
|
|
| 25 | 25 |
mkdir -p "$DOCKER_PATH/hack/make"; |
| 26 |
- ( cd "$DOCKER_PATH/hack/make" && wget -c 'https://raw.githubusercontent.com/dotcloud/docker/master/hack/make/'{.validate,validate-dco,validate-gofmt} );
|
|
| 27 |
- sed -i 's!dotcloud/docker!docker/libcontainer!' "$DOCKER_PATH/hack/make/.validate"; |
|
| 26 |
+ ( cd "$DOCKER_PATH/hack/make" && wget -c 'https://raw.githubusercontent.com/docker/docker/master/hack/make/'{.validate,validate-dco,validate-gofmt} );
|
|
| 27 |
+ sed -i 's!docker/docker!docker/libcontainer!' "$DOCKER_PATH/hack/make/.validate"; |
|
| 28 | 28 |
fi |
| 29 | 29 |
|
| 30 | 30 |
script: |
| ... | ... |
@@ -176,7 +176,7 @@ One way to automate this, is customise your get ``commit.template`` by adding |
| 176 | 176 |
a ``prepare-commit-msg`` hook to your libcontainer checkout: |
| 177 | 177 |
|
| 178 | 178 |
``` |
| 179 |
-curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg |
|
| 179 |
+curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/docker/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg |
|
| 180 | 180 |
``` |
| 181 | 181 |
|
| 182 | 182 |
* Note: the above script expects to find your GitHub user name in ``git config --get github.user`` |
| ... | ... |
@@ -150,6 +150,10 @@ func (raw *data) parent(subsystem string) (string, error) {
|
| 150 | 150 |
} |
| 151 | 151 |
|
| 152 | 152 |
func (raw *data) path(subsystem string) (string, error) {
|
| 153 |
+ // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. |
|
| 154 |
+ if filepath.IsAbs(raw.cgroup) {
|
|
| 155 |
+ return filepath.Join(raw.root, subsystem, raw.cgroup), nil |
|
| 156 |
+ } |
|
| 153 | 157 |
parent, err := raw.parent(subsystem) |
| 154 | 158 |
if err != nil {
|
| 155 | 159 |
return "", err |
| ... | ... |
@@ -5,6 +5,8 @@ import ( |
| 5 | 5 |
"os" |
| 6 | 6 |
"path/filepath" |
| 7 | 7 |
"testing" |
| 8 |
+ |
|
| 9 |
+ "github.com/docker/libcontainer/cgroups" |
|
| 8 | 10 |
) |
| 9 | 11 |
|
| 10 | 12 |
const ( |
| ... | ... |
@@ -66,3 +68,20 @@ func TestGetCgroupParamsInt(t *testing.T) {
|
| 66 | 66 |
t.Fatal("Expecting error, got none")
|
| 67 | 67 |
} |
| 68 | 68 |
} |
| 69 |
+ |
|
| 70 |
+func TestAbsolutePathHandling(t *testing.T) {
|
|
| 71 |
+ testCgroup := cgroups.Cgroup{
|
|
| 72 |
+ Name: "bar", |
|
| 73 |
+ Parent: "/foo", |
|
| 74 |
+ } |
|
| 75 |
+ cgroupData := data{
|
|
| 76 |
+ root: "/sys/fs/cgroup", |
|
| 77 |
+ cgroup: "/foo/bar", |
|
| 78 |
+ c: &testCgroup, |
|
| 79 |
+ pid: 1, |
|
| 80 |
+ } |
|
| 81 |
+ expectedPath := filepath.Join(cgroupData.root, "cpu", testCgroup.Parent, testCgroup.Name) |
|
| 82 |
+ if path, err := cgroupData.path("cpu"); path != expectedPath || err != nil {
|
|
| 83 |
+ t.Fatalf("expected path %s but got %s %s", expectedPath, path, err)
|
|
| 84 |
+ } |
|
| 85 |
+} |
| ... | ... |
@@ -2,6 +2,13 @@ |
| 2 | 2 |
|
| 3 | 3 |
package label |
| 4 | 4 |
|
| 5 |
+// InitLabels returns the process label and file labels to be used within |
|
| 6 |
+// the container. A list of options can be passed into this function to alter |
|
| 7 |
+// the labels. |
|
| 8 |
+func InitLabels(options []string) (string, string, error) {
|
|
| 9 |
+ return "", "", nil |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 5 | 12 |
func GenLabels(options string) (string, string, error) {
|
| 6 | 13 |
return "", "", nil |
| 7 | 14 |
} |
| ... | ... |
@@ -22,7 +29,7 @@ func Relabel(path string, fileLabel string, relabel string) error {
|
| 22 | 22 |
return nil |
| 23 | 23 |
} |
| 24 | 24 |
|
| 25 |
-func GetPidCon(pid int) (string, error) {
|
|
| 25 |
+func GetPidLabel(pid int) (string, error) {
|
|
| 26 | 26 |
return "", nil |
| 27 | 27 |
} |
| 28 | 28 |
|
| ... | ... |
@@ -9,30 +9,49 @@ import ( |
| 9 | 9 |
"github.com/docker/libcontainer/selinux" |
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 |
-func GenLabels(options string) (string, string, error) {
|
|
| 12 |
+// InitLabels returns the process label and file labels to be used within |
|
| 13 |
+// the container. A list of options can be passed into this function to alter |
|
| 14 |
+// the labels. The labels returned will include a random MCS String, that is |
|
| 15 |
+// guaranteed to be unique. |
|
| 16 |
+func InitLabels(options []string) (string, string, error) {
|
|
| 13 | 17 |
if !selinux.SelinuxEnabled() {
|
| 14 | 18 |
return "", "", nil |
| 15 | 19 |
} |
| 16 | 20 |
var err error |
| 17 | 21 |
processLabel, mountLabel := selinux.GetLxcContexts() |
| 18 | 22 |
if processLabel != "" {
|
| 19 |
- var ( |
|
| 20 |
- s = strings.Fields(options) |
|
| 21 |
- l = len(s) |
|
| 22 |
- ) |
|
| 23 |
- if l > 0 {
|
|
| 24 |
- pcon := selinux.NewContext(processLabel) |
|
| 25 |
- for i := 0; i < l; i++ {
|
|
| 26 |
- o := strings.Split(s[i], "=") |
|
| 27 |
- pcon[o[0]] = o[1] |
|
| 23 |
+ pcon := selinux.NewContext(processLabel) |
|
| 24 |
+ mcon := selinux.NewContext(mountLabel) |
|
| 25 |
+ for _, opt := range options {
|
|
| 26 |
+ if opt == "disable" {
|
|
| 27 |
+ return "", "", nil |
|
| 28 |
+ } |
|
| 29 |
+ if i := strings.Index(opt, ":"); i == -1 {
|
|
| 30 |
+ return "", "", fmt.Errorf("Bad SELinux Option")
|
|
| 31 |
+ } |
|
| 32 |
+ con := strings.SplitN(opt, ":", 2) |
|
| 33 |
+ pcon[con[0]] = con[1] |
|
| 34 |
+ if con[0] == "level" || con[0] == "user" {
|
|
| 35 |
+ mcon[con[0]] = con[1] |
|
| 28 | 36 |
} |
| 29 |
- processLabel = pcon.Get() |
|
| 30 |
- mountLabel, err = selinux.CopyLevel(processLabel, mountLabel) |
|
| 31 | 37 |
} |
| 38 |
+ processLabel = pcon.Get() |
|
| 39 |
+ mountLabel = mcon.Get() |
|
| 32 | 40 |
} |
| 33 | 41 |
return processLabel, mountLabel, err |
| 34 | 42 |
} |
| 35 | 43 |
|
| 44 |
+// DEPRECATED: The GenLabels function is only to be used during the transition to the official API. |
|
| 45 |
+func GenLabels(options string) (string, string, error) {
|
|
| 46 |
+ return InitLabels(strings.Fields(options)) |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+// FormatMountLabel returns a string to be used by the mount command. |
|
| 50 |
+// The format of this string will be used to alter the labeling of the mountpoint. |
|
| 51 |
+// The string returned is suitable to be used as the options field of the mount command. |
|
| 52 |
+// If you need to have additional mount point options, you can pass them in as |
|
| 53 |
+// the first parameter. Second parameter is the label that you wish to apply |
|
| 54 |
+// to all content in the mount point. |
|
| 36 | 55 |
func FormatMountLabel(src, mountLabel string) string {
|
| 37 | 56 |
if mountLabel != "" {
|
| 38 | 57 |
switch src {
|
| ... | ... |
@@ -45,6 +64,8 @@ func FormatMountLabel(src, mountLabel string) string {
|
| 45 | 45 |
return src |
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 |
+// SetProcessLabel takes a process label and tells the kernel to assign the |
|
| 49 |
+// label to the next program executed by the current process. |
|
| 48 | 50 |
func SetProcessLabel(processLabel string) error {
|
| 49 | 51 |
if selinux.SelinuxEnabled() {
|
| 50 | 52 |
return selinux.Setexeccon(processLabel) |
| ... | ... |
@@ -52,6 +73,9 @@ func SetProcessLabel(processLabel string) error {
|
| 52 | 52 |
return nil |
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 |
+// GetProcessLabel returns the process label that the kernel will assign |
|
| 56 |
+// to the next program executed by the current process. If "" is returned |
|
| 57 |
+// this indicates that the default labeling will happen for the process. |
|
| 55 | 58 |
func GetProcessLabel() (string, error) {
|
| 56 | 59 |
if selinux.SelinuxEnabled() {
|
| 57 | 60 |
return selinux.Getexeccon() |
| ... | ... |
@@ -59,6 +83,7 @@ func GetProcessLabel() (string, error) {
|
| 59 | 59 |
return "", nil |
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
+// SetFileLabel modifies the "path" label to the specified file label |
|
| 62 | 63 |
func SetFileLabel(path string, fileLabel string) error {
|
| 63 | 64 |
if selinux.SelinuxEnabled() && fileLabel != "" {
|
| 64 | 65 |
return selinux.Setfilecon(path, fileLabel) |
| ... | ... |
@@ -83,17 +108,22 @@ func Relabel(path string, fileLabel string, relabel string) error {
|
| 83 | 83 |
return selinux.Chcon(path, fileLabel, true) |
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 |
-func GetPidCon(pid int) (string, error) {
|
|
| 86 |
+// GetPidLabel will return the label of the process running with the specified pid |
|
| 87 |
+func GetPidLabel(pid int) (string, error) {
|
|
| 87 | 88 |
if !selinux.SelinuxEnabled() {
|
| 88 | 89 |
return "", nil |
| 89 | 90 |
} |
| 90 | 91 |
return selinux.Getpidcon(pid) |
| 91 | 92 |
} |
| 92 | 93 |
|
| 94 |
+// Init initialises the labeling system |
|
| 93 | 95 |
func Init() {
|
| 94 | 96 |
selinux.SelinuxEnabled() |
| 95 | 97 |
} |
| 96 | 98 |
|
| 99 |
+// ReserveLabel will record the fact that the MCS label has already been used. |
|
| 100 |
+// This will prevent InitLabels from using the MCS label in a newly created |
|
| 101 |
+// container |
|
| 97 | 102 |
func ReserveLabel(label string) error {
|
| 98 | 103 |
selinux.ReserveLabel(label) |
| 99 | 104 |
return nil |
| 100 | 105 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,48 @@ |
| 0 |
+// +build selinux,linux |
|
| 1 |
+ |
|
| 2 |
+package label |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "testing" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/libcontainer/selinux" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestInit(t *testing.T) {
|
|
| 11 |
+ if selinux.SelinuxEnabled() {
|
|
| 12 |
+ var testNull []string |
|
| 13 |
+ plabel, mlabel, err := InitLabels(testNull) |
|
| 14 |
+ if err != nil {
|
|
| 15 |
+ t.Log("InitLabels Failed")
|
|
| 16 |
+ t.Fatal(err) |
|
| 17 |
+ } |
|
| 18 |
+ testDisabled := []string{"disable"}
|
|
| 19 |
+ plabel, mlabel, err = InitLabels(testDisabled) |
|
| 20 |
+ if err != nil {
|
|
| 21 |
+ t.Log("InitLabels Disabled Failed")
|
|
| 22 |
+ t.Fatal(err) |
|
| 23 |
+ } |
|
| 24 |
+ if plabel != "" {
|
|
| 25 |
+ t.Log("InitLabels Disabled Failed")
|
|
| 26 |
+ t.Fatal() |
|
| 27 |
+ } |
|
| 28 |
+ testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"}
|
|
| 29 |
+ plabel, mlabel, err = InitLabels(testUser) |
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ t.Log("InitLabels User Failed")
|
|
| 32 |
+ t.Fatal(err) |
|
| 33 |
+ } |
|
| 34 |
+ if plabel != "user_u:user_r:user_t:s0:c1,c15" || mlabel != "user_u:object_r:svirt_sandbox_file_t:s0:c1,c15" {
|
|
| 35 |
+ t.Log("InitLabels User Failed")
|
|
| 36 |
+ t.Log(plabel, mlabel) |
|
| 37 |
+ t.Fatal(err) |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ testBadData := []string{"user", "role:user_r", "type:user_t", "level:s0:c1,c15"}
|
|
| 41 |
+ plabel, mlabel, err = InitLabels(testBadData) |
|
| 42 |
+ if err == nil {
|
|
| 43 |
+ t.Log("InitLabels Bad Failed")
|
|
| 44 |
+ t.Fatal(err) |
|
| 45 |
+ } |
|
| 46 |
+ } |
|
| 47 |
+} |
| ... | ... |
@@ -9,7 +9,6 @@ import ( |
| 9 | 9 |
"strings" |
| 10 | 10 |
"syscall" |
| 11 | 11 |
|
| 12 |
- "github.com/docker/docker/pkg/user" |
|
| 13 | 12 |
"github.com/docker/libcontainer" |
| 14 | 13 |
"github.com/docker/libcontainer/apparmor" |
| 15 | 14 |
"github.com/docker/libcontainer/console" |
| ... | ... |
@@ -21,6 +20,7 @@ import ( |
| 21 | 21 |
"github.com/docker/libcontainer/security/restrict" |
| 22 | 22 |
"github.com/docker/libcontainer/syncpipe" |
| 23 | 23 |
"github.com/docker/libcontainer/system" |
| 24 |
+ "github.com/docker/libcontainer/user" |
|
| 24 | 25 |
"github.com/docker/libcontainer/utils" |
| 25 | 26 |
) |
| 26 | 27 |
|
| ... | ... |
@@ -119,7 +119,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn |
| 119 | 119 |
return fmt.Errorf("restore parent death signal %s", err)
|
| 120 | 120 |
} |
| 121 | 121 |
|
| 122 |
- return system.Execv(args[0], args[0:], container.Env) |
|
| 122 |
+ return system.Execv(args[0], args[0:], os.Environ()) |
|
| 123 | 123 |
} |
| 124 | 124 |
|
| 125 | 125 |
// RestoreParentDeathSignal sets the parent death signal to old. |
| ... | ... |
@@ -152,7 +152,7 @@ func RestoreParentDeathSignal(old int) error {
|
| 152 | 152 |
|
| 153 | 153 |
// SetupUser changes the groups, gid, and uid for the user inside the container |
| 154 | 154 |
func SetupUser(u string) error {
|
| 155 |
- uid, gid, suppGids, err := user.GetUserGroupSupplementary(u, syscall.Getuid(), syscall.Getgid()) |
|
| 155 |
+ uid, gid, suppGids, home, err := user.GetUserGroupSupplementaryHome(u, syscall.Getuid(), syscall.Getgid(), "/") |
|
| 156 | 156 |
if err != nil {
|
| 157 | 157 |
return fmt.Errorf("get supplementary groups %s", err)
|
| 158 | 158 |
} |
| ... | ... |
@@ -169,6 +169,13 @@ func SetupUser(u string) error {
|
| 169 | 169 |
return fmt.Errorf("setuid %s", err)
|
| 170 | 170 |
} |
| 171 | 171 |
|
| 172 |
+ // if we didn't get HOME already, set it based on the user's HOME |
|
| 173 |
+ if envHome := os.Getenv("HOME"); envHome == "" {
|
|
| 174 |
+ if err := os.Setenv("HOME", home); err != nil {
|
|
| 175 |
+ return fmt.Errorf("set HOME %s", err)
|
|
| 176 |
+ } |
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 172 | 179 |
return nil |
| 173 | 180 |
} |
| 174 | 181 |
|
| ... | ... |
@@ -3,7 +3,6 @@ |
| 3 | 3 |
package namespaces |
| 4 | 4 |
|
| 5 | 5 |
/* |
| 6 |
-#include <dirent.h> |
|
| 7 | 6 |
#include <errno.h> |
| 8 | 7 |
#include <fcntl.h> |
| 9 | 8 |
#include <linux/limits.h> |
| ... | ... |
@@ -12,7 +11,6 @@ package namespaces |
| 12 | 12 |
#include <stdio.h> |
| 13 | 13 |
#include <stdlib.h> |
| 14 | 14 |
#include <string.h> |
| 15 |
-#include <sys/stat.h> |
|
| 16 | 15 |
#include <sys/types.h> |
| 17 | 16 |
#include <unistd.h> |
| 18 | 17 |
#include <getopt.h> |
| ... | ... |
@@ -145,36 +143,31 @@ void nsenter() {
|
| 145 | 145 |
char ns_dir[PATH_MAX]; |
| 146 | 146 |
memset(ns_dir, 0, PATH_MAX); |
| 147 | 147 |
snprintf(ns_dir, PATH_MAX - 1, "/proc/%d/ns/", init_pid); |
| 148 |
- struct dirent *dent; |
|
| 149 |
- DIR *dir = opendir(ns_dir); |
|
| 150 |
- if (dir == NULL) {
|
|
| 151 |
- fprintf(stderr, "nsenter: Failed to open directory \"%s\" with error: \"%s\"\n", ns_dir, strerror(errno)); |
|
| 152 |
- exit(1); |
|
| 153 |
- } |
|
| 154 | 148 |
|
| 155 |
- while((dent = readdir(dir)) != NULL) {
|
|
| 156 |
- if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || strcmp(dent->d_name, "user") == 0) {
|
|
| 157 |
- continue; |
|
| 158 |
- } |
|
| 159 |
- |
|
| 160 |
- // Get and open the namespace for the init we are joining.. |
|
| 149 |
+ char* namespaces[] = {"ipc", "uts", "net", "pid", "mnt"};
|
|
| 150 |
+ const int num = sizeof(namespaces) / sizeof(char*); |
|
| 151 |
+ int i; |
|
| 152 |
+ for (i = 0; i < num; i++) {
|
|
| 161 | 153 |
char buf[PATH_MAX]; |
| 162 | 154 |
memset(buf, 0, PATH_MAX); |
| 163 |
- snprintf(buf, PATH_MAX - 1, "%s%s", ns_dir, dent->d_name); |
|
| 155 |
+ snprintf(buf, PATH_MAX - 1, "%s%s", ns_dir, namespaces[i]); |
|
| 164 | 156 |
int fd = open(buf, O_RDONLY); |
| 165 | 157 |
if (fd == -1) {
|
| 166 |
- fprintf(stderr, "nsenter: Failed to open ns file \"%s\" for ns \"%s\" with error: \"%s\"\n", buf, dent->d_name, strerror(errno)); |
|
| 158 |
+ // Ignore nonexistent namespaces. |
|
| 159 |
+ if (errno == ENOENT) |
|
| 160 |
+ continue; |
|
| 161 |
+ |
|
| 162 |
+ fprintf(stderr, "nsenter: Failed to open ns file \"%s\" for ns \"%s\" with error: \"%s\"\n", buf, namespaces[i], strerror(errno)); |
|
| 167 | 163 |
exit(1); |
| 168 | 164 |
} |
| 169 | 165 |
|
| 170 | 166 |
// Set the namespace. |
| 171 | 167 |
if (setns(fd, 0) == -1) {
|
| 172 |
- fprintf(stderr, "nsenter: Failed to setns for \"%s\" with error: \"%s\"\n", dent->d_name, strerror(errno)); |
|
| 168 |
+ fprintf(stderr, "nsenter: Failed to setns for \"%s\" with error: \"%s\"\n", namespaces[i], strerror(errno)); |
|
| 173 | 169 |
exit(1); |
| 174 | 170 |
} |
| 175 | 171 |
close(fd); |
| 176 | 172 |
} |
| 177 |
- closedir(dir); |
|
| 178 | 173 |
|
| 179 | 174 |
// We must fork to actually enter the PID namespace. |
| 180 | 175 |
int child = fork(); |
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,258 @@ |
| 0 |
+package user |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "os" |
|
| 7 |
+ "strconv" |
|
| 8 |
+ "strings" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+const ( |
|
| 12 |
+ minId = 0 |
|
| 13 |
+ maxId = 1<<31 - 1 //for 32-bit systems compatibility |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+var ( |
|
| 17 |
+ ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId)
|
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+type User struct {
|
|
| 21 |
+ Name string |
|
| 22 |
+ Pass string |
|
| 23 |
+ Uid int |
|
| 24 |
+ Gid int |
|
| 25 |
+ Gecos string |
|
| 26 |
+ Home string |
|
| 27 |
+ Shell string |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+type Group struct {
|
|
| 31 |
+ Name string |
|
| 32 |
+ Pass string |
|
| 33 |
+ Gid int |
|
| 34 |
+ List []string |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func parseLine(line string, v ...interface{}) {
|
|
| 38 |
+ if line == "" {
|
|
| 39 |
+ return |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ parts := strings.Split(line, ":") |
|
| 43 |
+ for i, p := range parts {
|
|
| 44 |
+ if len(v) <= i {
|
|
| 45 |
+ // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files |
|
| 46 |
+ break |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ switch e := v[i].(type) {
|
|
| 50 |
+ case *string: |
|
| 51 |
+ // "root", "adm", "/bin/bash" |
|
| 52 |
+ *e = p |
|
| 53 |
+ case *int: |
|
| 54 |
+ // "0", "4", "1000" |
|
| 55 |
+ // ignore string to int conversion errors, for great "tolerance" of naughty configuration files |
|
| 56 |
+ *e, _ = strconv.Atoi(p) |
|
| 57 |
+ case *[]string: |
|
| 58 |
+ // "", "root", "root,adm,daemon" |
|
| 59 |
+ if p != "" {
|
|
| 60 |
+ *e = strings.Split(p, ",") |
|
| 61 |
+ } else {
|
|
| 62 |
+ *e = []string{}
|
|
| 63 |
+ } |
|
| 64 |
+ default: |
|
| 65 |
+ // panic, because this is a programming/logic error, not a runtime one |
|
| 66 |
+ panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!")
|
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+func ParsePasswd() ([]*User, error) {
|
|
| 72 |
+ return ParsePasswdFilter(nil) |
|
| 73 |
+} |
|
| 74 |
+ |
|
| 75 |
+func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
|
|
| 76 |
+ f, err := os.Open("/etc/passwd")
|
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ return nil, err |
|
| 79 |
+ } |
|
| 80 |
+ defer f.Close() |
|
| 81 |
+ return parsePasswdFile(f, filter) |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
|
|
| 85 |
+ var ( |
|
| 86 |
+ s = bufio.NewScanner(r) |
|
| 87 |
+ out = []*User{}
|
|
| 88 |
+ ) |
|
| 89 |
+ |
|
| 90 |
+ for s.Scan() {
|
|
| 91 |
+ if err := s.Err(); err != nil {
|
|
| 92 |
+ return nil, err |
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ text := strings.TrimSpace(s.Text()) |
|
| 96 |
+ if text == "" {
|
|
| 97 |
+ continue |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ // see: man 5 passwd |
|
| 101 |
+ // name:password:UID:GID:GECOS:directory:shell |
|
| 102 |
+ // Name:Pass:Uid:Gid:Gecos:Home:Shell |
|
| 103 |
+ // root:x:0:0:root:/root:/bin/bash |
|
| 104 |
+ // adm:x:3:4:adm:/var/adm:/bin/false |
|
| 105 |
+ p := &User{}
|
|
| 106 |
+ parseLine( |
|
| 107 |
+ text, |
|
| 108 |
+ &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, |
|
| 109 |
+ ) |
|
| 110 |
+ |
|
| 111 |
+ if filter == nil || filter(p) {
|
|
| 112 |
+ out = append(out, p) |
|
| 113 |
+ } |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ return out, nil |
|
| 117 |
+} |
|
| 118 |
+ |
|
| 119 |
+func ParseGroup() ([]*Group, error) {
|
|
| 120 |
+ return ParseGroupFilter(nil) |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
|
|
| 124 |
+ f, err := os.Open("/etc/group")
|
|
| 125 |
+ if err != nil {
|
|
| 126 |
+ return nil, err |
|
| 127 |
+ } |
|
| 128 |
+ defer f.Close() |
|
| 129 |
+ return parseGroupFile(f, filter) |
|
| 130 |
+} |
|
| 131 |
+ |
|
| 132 |
+func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
|
|
| 133 |
+ var ( |
|
| 134 |
+ s = bufio.NewScanner(r) |
|
| 135 |
+ out = []*Group{}
|
|
| 136 |
+ ) |
|
| 137 |
+ |
|
| 138 |
+ for s.Scan() {
|
|
| 139 |
+ if err := s.Err(); err != nil {
|
|
| 140 |
+ return nil, err |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ text := s.Text() |
|
| 144 |
+ if text == "" {
|
|
| 145 |
+ continue |
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 148 |
+ // see: man 5 group |
|
| 149 |
+ // group_name:password:GID:user_list |
|
| 150 |
+ // Name:Pass:Gid:List |
|
| 151 |
+ // root:x:0:root |
|
| 152 |
+ // adm:x:4:root,adm,daemon |
|
| 153 |
+ p := &Group{}
|
|
| 154 |
+ parseLine( |
|
| 155 |
+ text, |
|
| 156 |
+ &p.Name, &p.Pass, &p.Gid, &p.List, |
|
| 157 |
+ ) |
|
| 158 |
+ |
|
| 159 |
+ if filter == nil || filter(p) {
|
|
| 160 |
+ out = append(out, p) |
|
| 161 |
+ } |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ return out, nil |
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, list of supplementary group IDs, and home directory, if available and/or applicable. |
|
| 168 |
+func GetUserGroupSupplementaryHome(userSpec string, defaultUid, defaultGid int, defaultHome string) (int, int, []int, string, error) {
|
|
| 169 |
+ var ( |
|
| 170 |
+ uid = defaultUid |
|
| 171 |
+ gid = defaultGid |
|
| 172 |
+ suppGids = []int{}
|
|
| 173 |
+ home = defaultHome |
|
| 174 |
+ |
|
| 175 |
+ userArg, groupArg string |
|
| 176 |
+ ) |
|
| 177 |
+ |
|
| 178 |
+ // allow for userArg to have either "user" syntax, or optionally "user:group" syntax |
|
| 179 |
+ parseLine(userSpec, &userArg, &groupArg) |
|
| 180 |
+ |
|
| 181 |
+ users, err := ParsePasswdFilter(func(u *User) bool {
|
|
| 182 |
+ if userArg == "" {
|
|
| 183 |
+ return u.Uid == uid |
|
| 184 |
+ } |
|
| 185 |
+ return u.Name == userArg || strconv.Itoa(u.Uid) == userArg |
|
| 186 |
+ }) |
|
| 187 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 188 |
+ if userArg == "" {
|
|
| 189 |
+ userArg = strconv.Itoa(uid) |
|
| 190 |
+ } |
|
| 191 |
+ return 0, 0, nil, "", fmt.Errorf("Unable to find user %v: %v", userArg, err)
|
|
| 192 |
+ } |
|
| 193 |
+ |
|
| 194 |
+ haveUser := users != nil && len(users) > 0 |
|
| 195 |
+ if haveUser {
|
|
| 196 |
+ // if we found any user entries that matched our filter, let's take the first one as "correct" |
|
| 197 |
+ uid = users[0].Uid |
|
| 198 |
+ gid = users[0].Gid |
|
| 199 |
+ home = users[0].Home |
|
| 200 |
+ } else if userArg != "" {
|
|
| 201 |
+ // we asked for a user but didn't find them... let's check to see if we wanted a numeric user |
|
| 202 |
+ uid, err = strconv.Atoi(userArg) |
|
| 203 |
+ if err != nil {
|
|
| 204 |
+ // not numeric - we have to bail |
|
| 205 |
+ return 0, 0, nil, "", fmt.Errorf("Unable to find user %v", userArg)
|
|
| 206 |
+ } |
|
| 207 |
+ if uid < minId || uid > maxId {
|
|
| 208 |
+ return 0, 0, nil, "", ErrRange |
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit |
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ if groupArg != "" || (haveUser && users[0].Name != "") {
|
|
| 215 |
+ groups, err := ParseGroupFilter(func(g *Group) bool {
|
|
| 216 |
+ if groupArg != "" {
|
|
| 217 |
+ return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg |
|
| 218 |
+ } |
|
| 219 |
+ for _, u := range g.List {
|
|
| 220 |
+ if u == users[0].Name {
|
|
| 221 |
+ return true |
|
| 222 |
+ } |
|
| 223 |
+ } |
|
| 224 |
+ return false |
|
| 225 |
+ }) |
|
| 226 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 227 |
+ return 0, 0, nil, "", fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ haveGroup := groups != nil && len(groups) > 0 |
|
| 231 |
+ if groupArg != "" {
|
|
| 232 |
+ if haveGroup {
|
|
| 233 |
+ // if we found any group entries that matched our filter, let's take the first one as "correct" |
|
| 234 |
+ gid = groups[0].Gid |
|
| 235 |
+ } else {
|
|
| 236 |
+ // we asked for a group but didn't find id... let's check to see if we wanted a numeric group |
|
| 237 |
+ gid, err = strconv.Atoi(groupArg) |
|
| 238 |
+ if err != nil {
|
|
| 239 |
+ // not numeric - we have to bail |
|
| 240 |
+ return 0, 0, nil, "", fmt.Errorf("Unable to find group %v", groupArg)
|
|
| 241 |
+ } |
|
| 242 |
+ if gid < minId || gid > maxId {
|
|
| 243 |
+ return 0, 0, nil, "", ErrRange |
|
| 244 |
+ } |
|
| 245 |
+ |
|
| 246 |
+ // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit |
|
| 247 |
+ } |
|
| 248 |
+ } else if haveGroup {
|
|
| 249 |
+ suppGids = make([]int, len(groups)) |
|
| 250 |
+ for i, group := range groups {
|
|
| 251 |
+ suppGids[i] = group.Gid |
|
| 252 |
+ } |
|
| 253 |
+ } |
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ return uid, gid, suppGids, home, nil |
|
| 257 |
+} |
| 0 | 258 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,94 @@ |
| 0 |
+package user |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "strings" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func TestUserParseLine(t *testing.T) {
|
|
| 8 |
+ var ( |
|
| 9 |
+ a, b string |
|
| 10 |
+ c []string |
|
| 11 |
+ d int |
|
| 12 |
+ ) |
|
| 13 |
+ |
|
| 14 |
+ parseLine("", &a, &b)
|
|
| 15 |
+ if a != "" || b != "" {
|
|
| 16 |
+ t.Fatalf("a and b should be empty ('%v', '%v')", a, b)
|
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ parseLine("a", &a, &b)
|
|
| 20 |
+ if a != "a" || b != "" {
|
|
| 21 |
+ t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b)
|
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+ parseLine("bad boys:corny cows", &a, &b)
|
|
| 25 |
+ if a != "bad boys" || b != "corny cows" {
|
|
| 26 |
+ t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b)
|
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ parseLine("", &c)
|
|
| 30 |
+ if len(c) != 0 {
|
|
| 31 |
+ t.Fatalf("c should be empty (%#v)", c)
|
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c)
|
|
| 35 |
+ if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" {
|
|
| 36 |
+ t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c)
|
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ parseLine("::::::::::", &a, &b, &c)
|
|
| 40 |
+ if a != "" || b != "" || len(c) != 0 {
|
|
| 41 |
+ t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c)
|
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ parseLine("not a number", &d)
|
|
| 45 |
+ if d != 0 {
|
|
| 46 |
+ t.Fatalf("d should be 0 (%v)", d)
|
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ parseLine("b:12:c", &a, &d, &b)
|
|
| 50 |
+ if a != "b" || b != "c" || d != 12 {
|
|
| 51 |
+ t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d)
|
|
| 52 |
+ } |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+func TestUserParsePasswd(t *testing.T) {
|
|
| 56 |
+ users, err := parsePasswdFile(strings.NewReader(` |
|
| 57 |
+root:x:0:0:root:/root:/bin/bash |
|
| 58 |
+adm:x:3:4:adm:/var/adm:/bin/false |
|
| 59 |
+this is just some garbage data |
|
| 60 |
+`), nil) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ t.Fatalf("Unexpected error: %v", err)
|
|
| 63 |
+ } |
|
| 64 |
+ if len(users) != 3 {
|
|
| 65 |
+ t.Fatalf("Expected 3 users, got %v", len(users))
|
|
| 66 |
+ } |
|
| 67 |
+ if users[0].Uid != 0 || users[0].Name != "root" {
|
|
| 68 |
+ t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name)
|
|
| 69 |
+ } |
|
| 70 |
+ if users[1].Uid != 3 || users[1].Name != "adm" {
|
|
| 71 |
+ t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name)
|
|
| 72 |
+ } |
|
| 73 |
+} |
|
| 74 |
+ |
|
| 75 |
+func TestUserParseGroup(t *testing.T) {
|
|
| 76 |
+ groups, err := parseGroupFile(strings.NewReader(` |
|
| 77 |
+root:x:0:root |
|
| 78 |
+adm:x:4:root,adm,daemon |
|
| 79 |
+this is just some garbage data |
|
| 80 |
+`), nil) |
|
| 81 |
+ if err != nil {
|
|
| 82 |
+ t.Fatalf("Unexpected error: %v", err)
|
|
| 83 |
+ } |
|
| 84 |
+ if len(groups) != 3 {
|
|
| 85 |
+ t.Fatalf("Expected 3 groups, got %v", len(groups))
|
|
| 86 |
+ } |
|
| 87 |
+ if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 {
|
|
| 88 |
+ t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List))
|
|
| 89 |
+ } |
|
| 90 |
+ if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 {
|
|
| 91 |
+ t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
|
|
| 92 |
+ } |
|
| 93 |
+} |