Browse code

vendor: update vendor'd libcontainer version

This patch updates the vendor'd libcontainer version, so that Docker can
take advantage of the updates to the `user` API.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> (github: cyphar)

Aleksa Sarai authored on 2014/11/08 07:57:20
Showing 10 changed files
... ...
@@ -66,7 +66,7 @@ if [ "$1" = '--go' ]; then
66 66
 	mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
67 67
 fi
68 68
 
69
-clone git github.com/docker/libcontainer fd6df76562137aa3b18e44b790cb484fe2b6fa0b
69
+clone git github.com/docker/libcontainer 4ae31b6ceb2c2557c9f05f42da61b0b808faa5a4
70 70
 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
71 71
 rm -rf src/github.com/docker/libcontainer/vendor
72 72
 eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
... ...
@@ -167,26 +167,43 @@ func RestoreParentDeathSignal(old int) error {
167 167
 
168 168
 // SetupUser changes the groups, gid, and uid for the user inside the container
169 169
 func SetupUser(u string) error {
170
-	uid, gid, suppGids, home, err := user.GetUserGroupSupplementaryHome(u, syscall.Getuid(), syscall.Getgid(), "/")
170
+	// Set up defaults.
171
+	defaultExecUser := user.ExecUser{
172
+		Uid:  syscall.Getuid(),
173
+		Gid:  syscall.Getgid(),
174
+		Home: "/",
175
+	}
176
+
177
+	passwdFile, err := user.GetPasswdFile()
178
+	if err != nil {
179
+		return err
180
+	}
181
+
182
+	groupFile, err := user.GetGroupFile()
183
+	if err != nil {
184
+		return err
185
+	}
186
+
187
+	execUser, err := user.GetExecUserFile(u, &defaultExecUser, passwdFile, groupFile)
171 188
 	if err != nil {
172 189
 		return fmt.Errorf("get supplementary groups %s", err)
173 190
 	}
174 191
 
175
-	if err := syscall.Setgroups(suppGids); err != nil {
192
+	if err := syscall.Setgroups(execUser.Sgids); err != nil {
176 193
 		return fmt.Errorf("setgroups %s", err)
177 194
 	}
178 195
 
179
-	if err := system.Setgid(gid); err != nil {
196
+	if err := system.Setgid(execUser.Gid); err != nil {
180 197
 		return fmt.Errorf("setgid %s", err)
181 198
 	}
182 199
 
183
-	if err := system.Setuid(uid); err != nil {
200
+	if err := system.Setuid(execUser.Uid); err != nil {
184 201
 		return fmt.Errorf("setuid %s", err)
185 202
 	}
186 203
 
187 204
 	// if we didn't get HOME already, set it based on the user's HOME
188 205
 	if envHome := os.Getenv("HOME"); envHome == "" {
189
-		if err := os.Setenv("HOME", home); err != nil {
206
+		if err := os.Setenv("HOME", execUser.Home); err != nil {
190 207
 			return fmt.Errorf("set HOME %s", err)
191 208
 		}
192 209
 	}
... ...
@@ -1003,28 +1003,23 @@ func AddRoute(destination, source, gateway, device string) error {
1003 1003
 	}
1004 1004
 
1005 1005
 	if source != "" {
1006
-		srcIP, srcNet, err := net.ParseCIDR(source)
1006
+		srcIP := net.ParseIP(source)
1007 1007
 		if err != nil {
1008
-			return fmt.Errorf("source CIDR %s couldn't be parsed", source)
1008
+			return fmt.Errorf("source IP %s couldn't be parsed", source)
1009 1009
 		}
1010 1010
 		srcFamily := getIpFamily(srcIP)
1011 1011
 		if currentFamily != -1 && currentFamily != srcFamily {
1012 1012
 			return fmt.Errorf("source and destination ip were not the same IP family")
1013 1013
 		}
1014 1014
 		currentFamily = srcFamily
1015
-		srcLen, bits := srcNet.Mask.Size()
1016
-		if srcLen == 0 && bits == 0 {
1017
-			return fmt.Errorf("source CIDR %s generated a non-canonical Mask", source)
1018
-		}
1019 1015
 		msg.Family = uint8(srcFamily)
1020
-		msg.Src_len = uint8(srcLen)
1021 1016
 		var srcData []byte
1022 1017
 		if srcFamily == syscall.AF_INET {
1023 1018
 			srcData = srcIP.To4()
1024 1019
 		} else {
1025 1020
 			srcData = srcIP.To16()
1026 1021
 		}
1027
-		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_SRC, srcData))
1022
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_PREFSRC, srcData))
1028 1023
 	}
1029 1024
 
1030 1025
 	if gateway != "" {
... ...
@@ -280,6 +280,34 @@ func TestAddDelNetworkIp(t *testing.T) {
280 280
 	}
281 281
 }
282 282
 
283
+func TestAddRouteSourceSelection(t *testing.T) {
284
+	tstIp := "127.1.1.1"
285
+	tl := testLink{name: "tstEth", linkType: "dummy"}
286
+
287
+	addLink(t, tl.name, tl.linkType)
288
+	defer deleteLink(t, tl.name)
289
+
290
+	ip := net.ParseIP(tstIp)
291
+	mask := net.IPv4Mask(255, 255, 255, 255)
292
+	ipNet := &net.IPNet{IP: ip, Mask: mask}
293
+
294
+	iface, err := net.InterfaceByName(tl.name)
295
+	if err != nil {
296
+		t.Fatalf("Lost created link %#v", tl)
297
+	}
298
+
299
+	if err := NetworkLinkAddIp(iface, ip, ipNet); err != nil {
300
+		t.Fatalf("Could not add IP address %s to interface %#v: %s", ip.String(), iface, err)
301
+	}
302
+
303
+	upLink(t, tl.name)
304
+	defer downLink(t, tl.name)
305
+
306
+	if err := AddRoute("127.0.0.0/8", tstIp, "", tl.name); err != nil {
307
+		t.Fatalf("Failed to add route with source address")
308
+	}
309
+}
310
+
283 311
 func TestCreateVethPair(t *testing.T) {
284 312
 	if testing.Short() {
285 313
 		return
286 314
new file mode 100644
... ...
@@ -0,0 +1,209 @@
0
+{
1
+    "capabilities": [
2
+        "CHOWN",
3
+        "DAC_OVERRIDE",
4
+        "FOWNER",
5
+        "MKNOD",
6
+        "NET_RAW",
7
+        "SETGID",
8
+        "SETUID",
9
+        "SETFCAP",
10
+        "SETPCAP",
11
+        "NET_BIND_SERVICE",
12
+        "SYS_CHROOT",
13
+        "KILL"
14
+    ],
15
+    "cgroups": {
16
+        "allowed_devices": [
17
+            {
18
+                "cgroup_permissions": "m",
19
+                "major_number": -1,
20
+                "minor_number": -1,
21
+                "type": 99
22
+            },
23
+            {
24
+                "cgroup_permissions": "m",
25
+                "major_number": -1,
26
+                "minor_number": -1,
27
+                "type": 98
28
+            },
29
+            {
30
+                "cgroup_permissions": "rwm",
31
+                "major_number": 5,
32
+                "minor_number": 1,
33
+                "path": "/dev/console",
34
+                "type": 99
35
+            },
36
+            {
37
+                "cgroup_permissions": "rwm",
38
+                "major_number": 4,
39
+                "path": "/dev/tty0",
40
+                "type": 99
41
+            },
42
+            {
43
+                "cgroup_permissions": "rwm",
44
+                "major_number": 4,
45
+                "minor_number": 1,
46
+                "path": "/dev/tty1",
47
+                "type": 99
48
+            },
49
+            {
50
+                "cgroup_permissions": "rwm",
51
+                "major_number": 136,
52
+                "minor_number": -1,
53
+                "type": 99
54
+            },
55
+            {
56
+                "cgroup_permissions": "rwm",
57
+                "major_number": 5,
58
+                "minor_number": 2,
59
+                "type": 99
60
+            },
61
+            {
62
+                "cgroup_permissions": "rwm",
63
+                "major_number": 10,
64
+                "minor_number": 200,
65
+                "type": 99
66
+            },
67
+            {
68
+                "cgroup_permissions": "rwm",
69
+                "file_mode": 438,
70
+                "major_number": 1,
71
+                "minor_number": 3,
72
+                "path": "/dev/null",
73
+                "type": 99
74
+            },
75
+            {
76
+                "cgroup_permissions": "rwm",
77
+                "file_mode": 438,
78
+                "major_number": 1,
79
+                "minor_number": 5,
80
+                "path": "/dev/zero",
81
+                "type": 99
82
+            },
83
+            {
84
+                "cgroup_permissions": "rwm",
85
+                "file_mode": 438,
86
+                "major_number": 1,
87
+                "minor_number": 7,
88
+                "path": "/dev/full",
89
+                "type": 99
90
+            },
91
+            {
92
+                "cgroup_permissions": "rwm",
93
+                "file_mode": 438,
94
+                "major_number": 5,
95
+                "path": "/dev/tty",
96
+                "type": 99
97
+            },
98
+            {
99
+                "cgroup_permissions": "rwm",
100
+                "file_mode": 438,
101
+                "major_number": 1,
102
+                "minor_number": 9,
103
+                "path": "/dev/urandom",
104
+                "type": 99
105
+            },
106
+            {
107
+                "cgroup_permissions": "rwm",
108
+                "file_mode": 438,
109
+                "major_number": 1,
110
+                "minor_number": 8,
111
+                "path": "/dev/random",
112
+                "type": 99
113
+            }
114
+        ],
115
+        "name": "docker-koye",
116
+        "parent": "docker"
117
+    },
118
+    "restrict_sys": true,
119
+    "mount_config": {
120
+        "device_nodes": [
121
+            {
122
+                "cgroup_permissions": "rwm",
123
+                "file_mode": 438,
124
+                "major_number": 1,
125
+                "minor_number": 3,
126
+                "path": "/dev/null",
127
+                "type": 99
128
+            },
129
+            {
130
+                "cgroup_permissions": "rwm",
131
+                "file_mode": 438,
132
+                "major_number": 1,
133
+                "minor_number": 5,
134
+                "path": "/dev/zero",
135
+                "type": 99
136
+            },
137
+            {
138
+                "cgroup_permissions": "rwm",
139
+                "file_mode": 438,
140
+                "major_number": 1,
141
+                "minor_number": 7,
142
+                "path": "/dev/full",
143
+                "type": 99
144
+            },
145
+            {
146
+                "cgroup_permissions": "rwm",
147
+                "file_mode": 438,
148
+                "major_number": 5,
149
+                "path": "/dev/tty",
150
+                "type": 99
151
+            },
152
+            {
153
+                "cgroup_permissions": "rwm",
154
+                "file_mode": 438,
155
+                "major_number": 1,
156
+                "minor_number": 9,
157
+                "path": "/dev/urandom",
158
+                "type": 99
159
+            },
160
+            {
161
+                "cgroup_permissions": "rwm",
162
+                "file_mode": 438,
163
+                "major_number": 1,
164
+                "minor_number": 8,
165
+                "path": "/dev/random",
166
+                "type": 99
167
+            }
168
+        ]
169
+    },
170
+    "environment": [
171
+        "HOME=/",
172
+        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
173
+        "HOSTNAME=koye",
174
+        "TERM=xterm"
175
+    ],
176
+    "hostname": "koye",
177
+    "namespaces": {
178
+        "NEWIPC": true,
179
+        "NEWNET": true,
180
+        "NEWNS": true,
181
+        "NEWPID": true,
182
+        "NEWUTS": true
183
+    },
184
+    "networks": [
185
+        {
186
+            "address": "127.0.0.1/0",
187
+            "gateway": "localhost",
188
+            "mtu": 1500,
189
+            "type": "loopback"
190
+        },
191
+        {
192
+            "address": "172.17.0.101/16",
193
+            "bridge": "docker0",
194
+            "veth_prefix": "veth",
195
+            "mtu": 1500,
196
+            "type": "veth"
197
+        }
198
+    ],
199
+    "routes": [
200
+        {
201
+            "destination": "0.0.0.0/0",
202
+            "source": "172.17.0.101",
203
+            "gateway": "172.17.42.1",
204
+            "interface_name": "eth0"
205
+        }
206
+    ],
207
+    "tty": true
208
+}
0 209
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package user
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"syscall"
6
+)
7
+
8
+var (
9
+	// The current operating system does not provide the required data for user lookups.
10
+	ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
11
+)
12
+
13
+func lookupUser(filter func(u User) bool) (User, error) {
14
+	// Get operating system-specific passwd reader-closer.
15
+	passwd, err := GetPasswd()
16
+	if err != nil {
17
+		return User{}, err
18
+	}
19
+	defer passwd.Close()
20
+
21
+	// Get the users.
22
+	users, err := ParsePasswdFilter(passwd, filter)
23
+	if err != nil {
24
+		return User{}, err
25
+	}
26
+
27
+	// No user entries found.
28
+	if len(users) == 0 {
29
+		return User{}, fmt.Errorf("no matching entries in passwd file")
30
+	}
31
+
32
+	// Assume the first entry is the "correct" one.
33
+	return users[0], nil
34
+}
35
+
36
+// CurrentUser looks up the current user by their user id in /etc/passwd. If the
37
+// user cannot be found (or there is no /etc/passwd file on the filesystem),
38
+// then CurrentUser returns an error.
39
+func CurrentUser() (User, error) {
40
+	return LookupUid(syscall.Getuid())
41
+}
42
+
43
+// LookupUser looks up a user by their username in /etc/passwd. If the user
44
+// cannot be found (or there is no /etc/passwd file on the filesystem), then
45
+// LookupUser returns an error.
46
+func LookupUser(username string) (User, error) {
47
+	return lookupUser(func(u User) bool {
48
+		return u.Name == username
49
+	})
50
+}
51
+
52
+// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
53
+// be found (or there is no /etc/passwd file on the filesystem), then LookupId
54
+// returns an error.
55
+func LookupUid(uid int) (User, error) {
56
+	return lookupUser(func(u User) bool {
57
+		return u.Uid == uid
58
+	})
59
+}
60
+
61
+func lookupGroup(filter func(g Group) bool) (Group, error) {
62
+	// Get operating system-specific group reader-closer.
63
+	group, err := GetGroup()
64
+	if err != nil {
65
+		return Group{}, err
66
+	}
67
+	defer group.Close()
68
+
69
+	// Get the users.
70
+	groups, err := ParseGroupFilter(group, filter)
71
+	if err != nil {
72
+		return Group{}, err
73
+	}
74
+
75
+	// No user entries found.
76
+	if len(groups) == 0 {
77
+		return Group{}, fmt.Errorf("no matching entries in group file")
78
+	}
79
+
80
+	// Assume the first entry is the "correct" one.
81
+	return groups[0], nil
82
+}
83
+
84
+// CurrentGroup looks up the current user's group by their primary group id's
85
+// entry in /etc/passwd. If the group cannot be found (or there is no
86
+// /etc/group file on the filesystem), then CurrentGroup returns an error.
87
+func CurrentGroup() (Group, error) {
88
+	return LookupGid(syscall.Getgid())
89
+}
90
+
91
+// LookupGroup looks up a group by its name in /etc/group. If the group cannot
92
+// be found (or there is no /etc/group file on the filesystem), then LookupGroup
93
+// returns an error.
94
+func LookupGroup(groupname string) (Group, error) {
95
+	return lookupGroup(func(g Group) bool {
96
+		return g.Name == groupname
97
+	})
98
+}
99
+
100
+// LookupGid looks up a group by its group id in /etc/group. If the group cannot
101
+// be found (or there is no /etc/group file on the filesystem), then LookupGid
102
+// returns an error.
103
+func LookupGid(gid int) (Group, error) {
104
+	return lookupGroup(func(g Group) bool {
105
+		return g.Gid == gid
106
+	})
107
+}
0 108
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
1
+
2
+package user
3
+
4
+import (
5
+	"io"
6
+	"os"
7
+)
8
+
9
+// Unix-specific path to the passwd and group formatted files.
10
+const (
11
+	unixPasswdFile = "/etc/passwd"
12
+	unixGroupFile  = "/etc/group"
13
+)
14
+
15
+func GetPasswdFile() (string, error) {
16
+	return unixPasswdFile, nil
17
+}
18
+
19
+func GetPasswd() (io.ReadCloser, error) {
20
+	return os.Open(unixPasswdFile)
21
+}
22
+
23
+func GetGroupFile() (string, error) {
24
+	return unixGroupFile, nil
25
+}
26
+
27
+func GetGroup() (io.ReadCloser, error) {
28
+	return os.Open(unixGroupFile)
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
1
+
2
+package user
3
+
4
+import "io"
5
+
6
+func GetPasswdFile() (string, error) {
7
+	return "", ErrUnsupported
8
+}
9
+
10
+func GetPasswd() (io.ReadCloser, error) {
11
+	return nil, ErrUnsupported
12
+}
13
+
14
+func GetGroupFile() (string, error) {
15
+	return "", ErrUnsupported
16
+}
17
+
18
+func GetGroup() (io.ReadCloser, error) {
19
+	return nil, ErrUnsupported
20
+}
... ...
@@ -69,23 +69,36 @@ func parseLine(line string, v ...interface{}) {
69 69
 	}
70 70
 }
71 71
 
72
-func ParsePasswd() ([]*User, error) {
73
-	return ParsePasswdFilter(nil)
72
+func ParsePasswdFile(path string) ([]User, error) {
73
+	passwd, err := os.Open(path)
74
+	if err != nil {
75
+		return nil, err
76
+	}
77
+	defer passwd.Close()
78
+	return ParsePasswd(passwd)
79
+}
80
+
81
+func ParsePasswd(passwd io.Reader) ([]User, error) {
82
+	return ParsePasswdFilter(passwd, nil)
74 83
 }
75 84
 
76
-func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
77
-	f, err := os.Open("/etc/passwd")
85
+func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
86
+	passwd, err := os.Open(path)
78 87
 	if err != nil {
79 88
 		return nil, err
80 89
 	}
81
-	defer f.Close()
82
-	return parsePasswdFile(f, filter)
90
+	defer passwd.Close()
91
+	return ParsePasswdFilter(passwd, filter)
83 92
 }
84 93
 
85
-func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
94
+func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
95
+	if r == nil {
96
+		return nil, fmt.Errorf("nil source for passwd-formatted data")
97
+	}
98
+
86 99
 	var (
87 100
 		s   = bufio.NewScanner(r)
88
-		out = []*User{}
101
+		out = []User{}
89 102
 	)
90 103
 
91 104
 	for s.Scan() {
... ...
@@ -103,7 +116,7 @@ func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
103 103
 		// Name:Pass:Uid:Gid:Gecos:Home:Shell
104 104
 		//  root:x:0:0:root:/root:/bin/bash
105 105
 		//  adm:x:3:4:adm:/var/adm:/bin/false
106
-		p := &User{}
106
+		p := User{}
107 107
 		parseLine(
108 108
 			text,
109 109
 			&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
... ...
@@ -117,23 +130,36 @@ func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
117 117
 	return out, nil
118 118
 }
119 119
 
120
-func ParseGroup() ([]*Group, error) {
121
-	return ParseGroupFilter(nil)
120
+func ParseGroupFile(path string) ([]Group, error) {
121
+	group, err := os.Open(path)
122
+	if err != nil {
123
+		return nil, err
124
+	}
125
+	defer group.Close()
126
+	return ParseGroup(group)
127
+}
128
+
129
+func ParseGroup(group io.Reader) ([]Group, error) {
130
+	return ParseGroupFilter(group, nil)
122 131
 }
123 132
 
124
-func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
125
-	f, err := os.Open("/etc/group")
133
+func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
134
+	group, err := os.Open(path)
126 135
 	if err != nil {
127 136
 		return nil, err
128 137
 	}
129
-	defer f.Close()
130
-	return parseGroupFile(f, filter)
138
+	defer group.Close()
139
+	return ParseGroupFilter(group, filter)
131 140
 }
132 141
 
133
-func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
142
+func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
143
+	if r == nil {
144
+		return nil, fmt.Errorf("nil source for group-formatted data")
145
+	}
146
+
134 147
 	var (
135 148
 		s   = bufio.NewScanner(r)
136
-		out = []*Group{}
149
+		out = []Group{}
137 150
 	)
138 151
 
139 152
 	for s.Scan() {
... ...
@@ -151,7 +177,7 @@ func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
151 151
 		// Name:Pass:Gid:List
152 152
 		//  root:x:0:root
153 153
 		//  adm:x:4:root,adm,daemon
154
-		p := &Group{}
154
+		p := Group{}
155 155
 		parseLine(
156 156
 			text,
157 157
 			&p.Name, &p.Pass, &p.Gid, &p.List,
... ...
@@ -165,94 +191,160 @@ func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
165 165
 	return out, nil
166 166
 }
167 167
 
168
-// 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.
169
-func GetUserGroupSupplementaryHome(userSpec string, defaultUid, defaultGid int, defaultHome string) (int, int, []int, string, error) {
170
-	var (
171
-		uid      = defaultUid
172
-		gid      = defaultGid
173
-		suppGids = []int{}
174
-		home     = defaultHome
168
+type ExecUser struct {
169
+	Uid, Gid int
170
+	Sgids    []int
171
+	Home     string
172
+}
175 173
 
174
+// GetExecUserFile is a wrapper for GetExecUser. It reads data from each of the
175
+// given file paths and uses that data as the arguments to GetExecUser. If the
176
+// files cannot be opened for any reason, the error is ignored and a nil
177
+// io.Reader is passed instead.
178
+func GetExecUserFile(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
179
+	passwd, err := os.Open(passwdPath)
180
+	if err != nil {
181
+		passwd = nil
182
+	} else {
183
+		defer passwd.Close()
184
+	}
185
+
186
+	group, err := os.Open(groupPath)
187
+	if err != nil {
188
+		group = nil
189
+	} else {
190
+		defer group.Close()
191
+	}
192
+
193
+	return GetExecUser(userSpec, defaults, passwd, group)
194
+}
195
+
196
+// GetExecUser parses a user specification string (using the passwd and group
197
+// readers as sources for /etc/passwd and /etc/group data, respectively). In
198
+// the case of blank fields or missing data from the sources, the values in
199
+// defaults is used.
200
+//
201
+// GetExecUser will return an error if a user or group literal could not be
202
+// found in any entry in passwd and group respectively.
203
+//
204
+// Examples of valid user specifications are:
205
+//     * ""
206
+//     * "user"
207
+//     * "uid"
208
+//     * "user:group"
209
+//     * "uid:gid
210
+//     * "user:gid"
211
+//     * "uid:group"
212
+func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
213
+	var (
176 214
 		userArg, groupArg string
215
+		name              string
177 216
 	)
178 217
 
218
+	if defaults == nil {
219
+		defaults = new(ExecUser)
220
+	}
221
+
222
+	// Copy over defaults.
223
+	user := &ExecUser{
224
+		Uid:   defaults.Uid,
225
+		Gid:   defaults.Gid,
226
+		Sgids: defaults.Sgids,
227
+		Home:  defaults.Home,
228
+	}
229
+
230
+	// Sgids slice *cannot* be nil.
231
+	if user.Sgids == nil {
232
+		user.Sgids = []int{}
233
+	}
234
+
179 235
 	// allow for userArg to have either "user" syntax, or optionally "user:group" syntax
180 236
 	parseLine(userSpec, &userArg, &groupArg)
181 237
 
182
-	users, err := ParsePasswdFilter(func(u *User) bool {
238
+	users, err := ParsePasswdFilter(passwd, func(u User) bool {
183 239
 		if userArg == "" {
184
-			return u.Uid == uid
240
+			return u.Uid == user.Uid
185 241
 		}
186 242
 		return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
187 243
 	})
188
-	if err != nil && !os.IsNotExist(err) {
244
+	if err != nil && passwd != nil {
189 245
 		if userArg == "" {
190
-			userArg = strconv.Itoa(uid)
246
+			userArg = strconv.Itoa(user.Uid)
191 247
 		}
192
-		return 0, 0, nil, "", fmt.Errorf("Unable to find user %v: %v", userArg, err)
248
+		return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
193 249
 	}
194 250
 
195 251
 	haveUser := users != nil && len(users) > 0
196 252
 	if haveUser {
197 253
 		// if we found any user entries that matched our filter, let's take the first one as "correct"
198
-		uid = users[0].Uid
199
-		gid = users[0].Gid
200
-		home = users[0].Home
254
+		name = users[0].Name
255
+		user.Uid = users[0].Uid
256
+		user.Gid = users[0].Gid
257
+		user.Home = users[0].Home
201 258
 	} else if userArg != "" {
202 259
 		// we asked for a user but didn't find them...  let's check to see if we wanted a numeric user
203
-		uid, err = strconv.Atoi(userArg)
260
+		user.Uid, err = strconv.Atoi(userArg)
204 261
 		if err != nil {
205 262
 			// not numeric - we have to bail
206
-			return 0, 0, nil, "", fmt.Errorf("Unable to find user %v", userArg)
263
+			return nil, fmt.Errorf("Unable to find user %v", userArg)
207 264
 		}
208
-		if uid < minId || uid > maxId {
209
-			return 0, 0, nil, "", ErrRange
265
+
266
+		// Must be inside valid uid range.
267
+		if user.Uid < minId || user.Uid > maxId {
268
+			return nil, ErrRange
210 269
 		}
211 270
 
212 271
 		// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
213 272
 	}
214 273
 
215
-	if groupArg != "" || (haveUser && users[0].Name != "") {
216
-		groups, err := ParseGroupFilter(func(g *Group) bool {
274
+	if groupArg != "" || name != "" {
275
+		groups, err := ParseGroupFilter(group, func(g Group) bool {
276
+			// Explicit group format takes precedence.
217 277
 			if groupArg != "" {
218 278
 				return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
219 279
 			}
280
+
281
+			// Check if user is a member.
220 282
 			for _, u := range g.List {
221
-				if u == users[0].Name {
283
+				if u == name {
222 284
 					return true
223 285
 				}
224 286
 			}
287
+
225 288
 			return false
226 289
 		})
227
-		if err != nil && !os.IsNotExist(err) {
228
-			return 0, 0, nil, "", fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
290
+		if err != nil && group != nil {
291
+			return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
229 292
 		}
230 293
 
231 294
 		haveGroup := groups != nil && len(groups) > 0
232 295
 		if groupArg != "" {
233 296
 			if haveGroup {
234 297
 				// if we found any group entries that matched our filter, let's take the first one as "correct"
235
-				gid = groups[0].Gid
298
+				user.Gid = groups[0].Gid
236 299
 			} else {
237 300
 				// we asked for a group but didn't find id...  let's check to see if we wanted a numeric group
238
-				gid, err = strconv.Atoi(groupArg)
301
+				user.Gid, err = strconv.Atoi(groupArg)
239 302
 				if err != nil {
240 303
 					// not numeric - we have to bail
241
-					return 0, 0, nil, "", fmt.Errorf("Unable to find group %v", groupArg)
304
+					return nil, fmt.Errorf("Unable to find group %v", groupArg)
242 305
 				}
243
-				if gid < minId || gid > maxId {
244
-					return 0, 0, nil, "", ErrRange
306
+
307
+				// Ensure gid is inside gid range.
308
+				if user.Gid < minId || user.Gid > maxId {
309
+					return nil, ErrRange
245 310
 				}
246 311
 
247 312
 				// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
248 313
 			}
249 314
 		} else if haveGroup {
250
-			suppGids = make([]int, len(groups))
315
+			// If implicit group format, fill supplementary gids.
316
+			user.Sgids = make([]int, len(groups))
251 317
 			for i, group := range groups {
252
-				suppGids[i] = group.Gid
318
+				user.Sgids[i] = group.Gid
253 319
 			}
254 320
 		}
255 321
 	}
256 322
 
257
-	return uid, gid, suppGids, home, nil
323
+	return user, nil
258 324
 }
... ...
@@ -1,6 +1,8 @@
1 1
 package user
2 2
 
3 3
 import (
4
+	"io"
5
+	"reflect"
4 6
 	"strings"
5 7
 	"testing"
6 8
 )
... ...
@@ -54,7 +56,7 @@ func TestUserParseLine(t *testing.T) {
54 54
 }
55 55
 
56 56
 func TestUserParsePasswd(t *testing.T) {
57
-	users, err := parsePasswdFile(strings.NewReader(`
57
+	users, err := ParsePasswdFilter(strings.NewReader(`
58 58
 root:x:0:0:root:/root:/bin/bash
59 59
 adm:x:3:4:adm:/var/adm:/bin/false
60 60
 this is just some garbage data
... ...
@@ -74,7 +76,7 @@ this is just some garbage data
74 74
 }
75 75
 
76 76
 func TestUserParseGroup(t *testing.T) {
77
-	groups, err := parseGroupFile(strings.NewReader(`
77
+	groups, err := ParseGroupFilter(strings.NewReader(`
78 78
 root:x:0:root
79 79
 adm:x:4:root,adm,daemon
80 80
 this is just some garbage data
... ...
@@ -92,3 +94,259 @@ this is just some garbage data
92 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 93
 	}
94 94
 }
95
+
96
+func TestValidGetExecUser(t *testing.T) {
97
+	const passwdContent = `
98
+root:x:0:0:root user:/root:/bin/bash
99
+adm:x:42:43:adm:/var/adm:/bin/false
100
+this is just some garbage data
101
+`
102
+	const groupContent = `
103
+root:x:0:root
104
+adm:x:43:
105
+grp:x:1234:root,adm
106
+this is just some garbage data
107
+`
108
+	defaultExecUser := ExecUser{
109
+		Uid:   8888,
110
+		Gid:   8888,
111
+		Sgids: []int{8888},
112
+		Home:  "/8888",
113
+	}
114
+
115
+	tests := []struct {
116
+		ref      string
117
+		expected ExecUser
118
+	}{
119
+		{
120
+			ref: "root",
121
+			expected: ExecUser{
122
+				Uid:   0,
123
+				Gid:   0,
124
+				Sgids: []int{0, 1234},
125
+				Home:  "/root",
126
+			},
127
+		},
128
+		{
129
+			ref: "adm",
130
+			expected: ExecUser{
131
+				Uid:   42,
132
+				Gid:   43,
133
+				Sgids: []int{1234},
134
+				Home:  "/var/adm",
135
+			},
136
+		},
137
+		{
138
+			ref: "root:adm",
139
+			expected: ExecUser{
140
+				Uid:   0,
141
+				Gid:   43,
142
+				Sgids: defaultExecUser.Sgids,
143
+				Home:  "/root",
144
+			},
145
+		},
146
+		{
147
+			ref: "adm:1234",
148
+			expected: ExecUser{
149
+				Uid:   42,
150
+				Gid:   1234,
151
+				Sgids: defaultExecUser.Sgids,
152
+				Home:  "/var/adm",
153
+			},
154
+		},
155
+		{
156
+			ref: "42:1234",
157
+			expected: ExecUser{
158
+				Uid:   42,
159
+				Gid:   1234,
160
+				Sgids: defaultExecUser.Sgids,
161
+				Home:  "/var/adm",
162
+			},
163
+		},
164
+		{
165
+			ref: "1337:1234",
166
+			expected: ExecUser{
167
+				Uid:   1337,
168
+				Gid:   1234,
169
+				Sgids: defaultExecUser.Sgids,
170
+				Home:  defaultExecUser.Home,
171
+			},
172
+		},
173
+		{
174
+			ref: "1337",
175
+			expected: ExecUser{
176
+				Uid:   1337,
177
+				Gid:   defaultExecUser.Gid,
178
+				Sgids: defaultExecUser.Sgids,
179
+				Home:  defaultExecUser.Home,
180
+			},
181
+		},
182
+		{
183
+			ref: "",
184
+			expected: ExecUser{
185
+				Uid:   defaultExecUser.Uid,
186
+				Gid:   defaultExecUser.Gid,
187
+				Sgids: defaultExecUser.Sgids,
188
+				Home:  defaultExecUser.Home,
189
+			},
190
+		},
191
+	}
192
+
193
+	for _, test := range tests {
194
+		passwd := strings.NewReader(passwdContent)
195
+		group := strings.NewReader(groupContent)
196
+
197
+		execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
198
+		if err != nil {
199
+			t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
200
+			t.Fail()
201
+			continue
202
+		}
203
+
204
+		if !reflect.DeepEqual(test.expected, *execUser) {
205
+			t.Logf("got:      %#v", execUser)
206
+			t.Logf("expected: %#v", test.expected)
207
+			t.Fail()
208
+			continue
209
+		}
210
+	}
211
+}
212
+
213
+func TestInvalidGetExecUser(t *testing.T) {
214
+	const passwdContent = `
215
+root:x:0:0:root user:/root:/bin/bash
216
+adm:x:42:43:adm:/var/adm:/bin/false
217
+this is just some garbage data
218
+`
219
+	const groupContent = `
220
+root:x:0:root
221
+adm:x:43:
222
+grp:x:1234:root,adm
223
+this is just some garbage data
224
+`
225
+
226
+	tests := []string{
227
+		// No such user/group.
228
+		"notuser",
229
+		"notuser:notgroup",
230
+		"root:notgroup",
231
+		"notuser:adm",
232
+		"8888:notgroup",
233
+		"notuser:8888",
234
+
235
+		// Invalid user/group values.
236
+		"-1:0",
237
+		"0:-3",
238
+		"-5:-2",
239
+	}
240
+
241
+	for _, test := range tests {
242
+		passwd := strings.NewReader(passwdContent)
243
+		group := strings.NewReader(groupContent)
244
+
245
+		execUser, err := GetExecUser(test, nil, passwd, group)
246
+		if err == nil {
247
+			t.Logf("got unexpected success when parsing '%s': %#v", test, execUser)
248
+			t.Fail()
249
+			continue
250
+		}
251
+	}
252
+}
253
+
254
+func TestGetExecUserNilSources(t *testing.T) {
255
+	const passwdContent = `
256
+root:x:0:0:root user:/root:/bin/bash
257
+adm:x:42:43:adm:/var/adm:/bin/false
258
+this is just some garbage data
259
+`
260
+	const groupContent = `
261
+root:x:0:root
262
+adm:x:43:
263
+grp:x:1234:root,adm
264
+this is just some garbage data
265
+`
266
+
267
+	defaultExecUser := ExecUser{
268
+		Uid:   8888,
269
+		Gid:   8888,
270
+		Sgids: []int{8888},
271
+		Home:  "/8888",
272
+	}
273
+
274
+	tests := []struct {
275
+		ref           string
276
+		passwd, group bool
277
+		expected      ExecUser
278
+	}{
279
+		{
280
+			ref:    "",
281
+			passwd: false,
282
+			group:  false,
283
+			expected: ExecUser{
284
+				Uid:   8888,
285
+				Gid:   8888,
286
+				Sgids: []int{8888},
287
+				Home:  "/8888",
288
+			},
289
+		},
290
+		{
291
+			ref:    "root",
292
+			passwd: true,
293
+			group:  false,
294
+			expected: ExecUser{
295
+				Uid:   0,
296
+				Gid:   0,
297
+				Sgids: []int{8888},
298
+				Home:  "/root",
299
+			},
300
+		},
301
+		{
302
+			ref:    "0",
303
+			passwd: false,
304
+			group:  false,
305
+			expected: ExecUser{
306
+				Uid:   0,
307
+				Gid:   8888,
308
+				Sgids: []int{8888},
309
+				Home:  "/8888",
310
+			},
311
+		},
312
+		{
313
+			ref:    "0:0",
314
+			passwd: false,
315
+			group:  false,
316
+			expected: ExecUser{
317
+				Uid:   0,
318
+				Gid:   0,
319
+				Sgids: []int{8888},
320
+				Home:  "/8888",
321
+			},
322
+		},
323
+	}
324
+
325
+	for _, test := range tests {
326
+		var passwd, group io.Reader
327
+
328
+		if test.passwd {
329
+			passwd = strings.NewReader(passwdContent)
330
+		}
331
+
332
+		if test.group {
333
+			group = strings.NewReader(groupContent)
334
+		}
335
+
336
+		execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
337
+		if err != nil {
338
+			t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
339
+			t.Fail()
340
+			continue
341
+		}
342
+
343
+		if !reflect.DeepEqual(test.expected, *execUser) {
344
+			t.Logf("got:      %#v", execUser)
345
+			t.Logf("expected: %#v", test.expected)
346
+			t.Fail()
347
+			continue
348
+		}
349
+	}
350
+}