aboutsummaryrefslogtreecommitdiff
blob: 2c99548cbd064dc67e63fffddc443bf4ccb08bec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# data.py -- Calculated/Discovered Data Values
# Copyright 1998-2014 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import os, pwd, grp, platform, sys

import portage
portage.proxy.lazyimport.lazyimport(globals(),
	'portage.output:colorize',
	'portage.util:writemsg',
	'portage.util.path:first_existing',
	'subprocess'
)
from portage.localization import _

ostype = platform.system()
userland = None
if ostype == "DragonFly" or ostype.endswith("BSD"):
	userland = "BSD"
else:
	userland = "GNU"

lchown = getattr(os, "lchown", None)

if not lchown:
	if ostype == "Darwin":
		def lchown(*_args, **_kwargs):
			pass
	else:
		def lchown(*_args, **_kwargs):
			writemsg(colorize("BAD", "!!!") + _(
				" It seems that os.lchown does not"
				" exist.  Please rebuild python.\n"), noiselevel=-1)
		lchown()

lchown = portage._unicode_func_wrapper(lchown)

def _target_eprefix():
	"""
	Calculate the target EPREFIX, which may be different from
	portage.const.EPREFIX due to cross-prefix support. The result
	is equivalent to portage.settings["EPREFIX"], but the calculation
	is done without the expense of instantiating portage.settings.
	@rtype: str
	@return: the target EPREFIX
	"""
	eprefix = os.environ.get("EPREFIX", portage.const.EPREFIX)
	if eprefix:
		eprefix = portage.util.normalize_path(eprefix)
	return eprefix

def _target_root():
	"""
	Calculate the target ROOT. The result is equivalent to
	portage.settings["ROOT"], but the calculation
	is done without the expense of instantiating portage.settings.
	@rtype: str
	@return: the target ROOT (always ends with a slash)
	"""
	root = os.environ.get("ROOT")
	if not root:
		# Handle either empty or unset ROOT.
		root = os.sep
	root = portage.util.normalize_path(root)
	return root.rstrip(os.sep) + os.sep

def portage_group_warning():
	warn_prefix = colorize("BAD", "*** WARNING ***  ")
	mylines = [
		"For security reasons, only system administrators should be",
		"allowed in the portage group.  Untrusted users or processes",
		"can potentially exploit the portage group for attacks such as",
		"local privilege escalation."
	]
	for x in mylines:
		writemsg(warn_prefix, noiselevel=-1)
		writemsg(x, noiselevel=-1)
		writemsg("\n", noiselevel=-1)
	writemsg("\n", noiselevel=-1)

# Portage has 3 security levels that depend on the uid and gid of the main
# process and are assigned according to the following table:
#
# Privileges  secpass  uid    gid
# normal      0        any    any
# group       1        any    portage_gid
# super       2        0      any
#
# If the "wheel" group does not exist then wheelgid falls back to 0.
# If the "portage" group does not exist then portage_uid falls back to wheelgid.

# If the current user is not root, but has write access to the
# EROOT directory (not due to the 0002 bit), then use "unprivileged"
# mode which sets secpass = 2 and uses the UID and GID of the EROOT
# directory to generate default PORTAGE_INST_GID, PORTAGE_INST_UID,
# PORTAGE_USERNAME, and PORTAGE_GRPNAME settings.
def _unprivileged_mode(eroot, eroot_st):
	return os.getuid() != 0 and os.access(eroot, os.W_OK) and \
		not eroot_st.st_mode & 0o0002

uid = os.getuid()
wheelgid = 0
try:
	wheelgid = grp.getgrnam("wheel")[2]
except KeyError:
	pass

# The portage_uid and portage_gid global constants, and others that
# depend on them are initialized lazily, in order to allow configuration
# via make.conf. Eventually, these constants may be deprecated in favor
# of config attributes, since it's conceivable that multiple
# configurations with different constants could be used simultaneously.
_initialized_globals = set()

def _get_global(k):
	if k in _initialized_globals:
		return globals()[k]

	if k == 'secpass':

		unprivileged = False
		if hasattr(portage, 'settings'):
			unprivileged = "unprivileged" in portage.settings.features
		else:
			# The config class has equivalent code, but we also need to
			# do it here if _disable_legacy_globals() has been called.
			eroot_or_parent = first_existing(os.path.join(
				_target_root(), _target_eprefix().lstrip(os.sep)))
			try:
				eroot_st = os.stat(eroot_or_parent)
			except OSError:
				pass
			else:
				unprivileged = _unprivileged_mode(
					eroot_or_parent, eroot_st)

		v = 0
		if uid == 0:
			v = 2
		elif unprivileged:
			v = 2
		elif _get_global('portage_gid') in os.getgroups():
			v = 1

	elif k in ('portage_gid', 'portage_uid'):

		#Discover the uid and gid of the portage user/group
		keyerror = False
		try:
			portage_uid = pwd.getpwnam(_get_global('_portage_username')).pw_uid
		except KeyError:
			keyerror = True
			portage_uid = 0

		try:
			portage_gid = grp.getgrnam(_get_global('_portage_grpname')).gr_gid
		except KeyError:
			keyerror = True
			portage_gid = 0

		# Suppress this error message if both PORTAGE_GRPNAME and
		# PORTAGE_USERNAME are set to "root", for things like
		# Android (see bug #454060).
		if keyerror and not (_get_global('_portage_username') == "root" and
			_get_global('_portage_grpname') == "root"):
			writemsg(colorize("BAD",
				_("portage: 'portage' user or group missing.")) + "\n", noiselevel=-1)
			writemsg(_(
				"         For the defaults, line 1 goes into passwd, "
				"and 2 into group.\n"), noiselevel=-1)
			writemsg(colorize("GOOD",
				"         portage:x:250:250:portage:/var/tmp/portage:/bin/false") \
				+ "\n", noiselevel=-1)
			writemsg(colorize("GOOD", "         portage::250:portage") + "\n",
				noiselevel=-1)
			portage_group_warning()

		globals()['portage_gid'] = portage_gid
		_initialized_globals.add('portage_gid')
		globals()['portage_uid'] = portage_uid
		_initialized_globals.add('portage_uid')

		if k == 'portage_gid':
			return portage_gid
		elif k == 'portage_uid':
			return portage_uid
		else:
			raise AssertionError('unknown name: %s' % k)

	elif k == 'userpriv_groups':
		v = [_get_global('portage_gid')]
		if secpass >= 2:
			# Get a list of group IDs for the portage user. Do not use
			# grp.getgrall() since it is known to trigger spurious
			# SIGPIPE problems with nss_ldap.
			cmd = ["id", "-G", _portage_username]

			if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000:
				# Python 3.1 _execvp throws TypeError for non-absolute executable
				# path passed as bytes (see http://bugs.python.org/issue8513).
				fullname = portage.process.find_binary(cmd[0])
				if fullname is None:
					globals()[k] = v
					_initialized_globals.add(k)
					return v
				cmd[0] = fullname

			encoding = portage._encodings['content']
			cmd = [portage._unicode_encode(x,
				encoding=encoding, errors='strict') for x in cmd]
			proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
				stderr=subprocess.STDOUT)
			myoutput = proc.communicate()[0]
			status = proc.wait()
			if os.WIFEXITED(status) and os.WEXITSTATUS(status) == os.EX_OK:
				for x in portage._unicode_decode(myoutput,
					encoding=encoding, errors='strict').split():
					try:
						v.append(int(x))
					except ValueError:
						pass
				v = sorted(set(v))

	# Avoid instantiating portage.settings when the desired
	# variable is set in os.environ.
	elif k in ('_portage_grpname', '_portage_username'):
		v = None
		if k == '_portage_grpname':
			env_key = 'PORTAGE_GRPNAME'
		else:
			env_key = 'PORTAGE_USERNAME'

		if env_key in os.environ:
			v = os.environ[env_key]
		elif hasattr(portage, 'settings'):
			v = portage.settings.get(env_key)
		else:
			# The config class has equivalent code, but we also need to
			# do it here if _disable_legacy_globals() has been called.
			eroot_or_parent = first_existing(os.path.join(
				_target_root(), _target_eprefix().lstrip(os.sep)))
			try:
				eroot_st = os.stat(eroot_or_parent)
			except OSError:
				pass
			else:
				if _unprivileged_mode(eroot_or_parent, eroot_st):
					if k == '_portage_grpname':
						try:
							grp_struct = grp.getgrgid(eroot_st.st_gid)
						except KeyError:
							pass
						else:
							v = grp_struct.gr_name
					else:
						try:
							pwd_struct = pwd.getpwuid(eroot_st.st_uid)
						except KeyError:
							pass
						else:
							v = pwd_struct.pw_name

		if v is None:
			v = 'portage'
	else:
		raise AssertionError('unknown name: %s' % k)

	globals()[k] = v
	_initialized_globals.add(k)
	return v

class _GlobalProxy(portage.proxy.objectproxy.ObjectProxy):

	__slots__ = ('_name',)

	def __init__(self, name):
		portage.proxy.objectproxy.ObjectProxy.__init__(self)
		object.__setattr__(self, '_name', name)

	def _get_target(self):
		return _get_global(object.__getattribute__(self, '_name'))

for k in ('portage_gid', 'portage_uid', 'secpass', 'userpriv_groups',
	'_portage_grpname', '_portage_username'):
	globals()[k] = _GlobalProxy(k)
del k

def _init(settings):
	"""
	Use config variables like PORTAGE_GRPNAME and PORTAGE_USERNAME to
	initialize global variables. This allows settings to come from make.conf
	instead of requiring them to be set in the calling environment.
	"""
	if '_portage_grpname' not in _initialized_globals and \
		'_portage_username' not in _initialized_globals:

		# Prevents "TypeError: expected string" errors
		# from grp.getgrnam() with PyPy
		native_string = platform.python_implementation() == 'PyPy'

		v = settings.get('PORTAGE_GRPNAME', 'portage')
		if native_string:
			v = portage._native_string(v)
		globals()['_portage_grpname'] = v
		_initialized_globals.add('_portage_grpname')

		v = settings.get('PORTAGE_USERNAME', 'portage')
		if native_string:
			v = portage._native_string(v)
		globals()['_portage_username'] = v
		_initialized_globals.add('_portage_username')

	if 'secpass' not in _initialized_globals:
		v = 0
		if uid == 0:
			v = 2
		elif "unprivileged" in settings.features:
			v = 2
		elif portage_gid in os.getgroups():
			v = 1
		globals()['secpass'] = v
		_initialized_globals.add('secpass')