aboutsummaryrefslogtreecommitdiff
blob: 44104c273d49dee002656fa50bfd3b52495034c9 (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
# data.py -- Calculated/Discovered Data Values
# Copyright 1998-2013 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',
	'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(*pos_args, **key_args):
			pass
	else:
		def lchown(*pargs, **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 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.

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 in ('portage_gid', 'portage_uid', 'secpass'):
		global portage_gid, portage_uid, secpass
		secpass = 0
		if uid == 0:
			secpass = 2
		elif portage.const.EPREFIX:
			secpass = 2
		#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

		if secpass < 1 and portage_gid in os.getgroups():
			secpass = 1

		# 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()

		_initialized_globals.add('portage_gid')
		_initialized_globals.add('portage_uid')
		_initialized_globals.add('secpass')

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

	elif k == 'userpriv_groups':
		v = [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)
		elif portage.const.EPREFIX:
			# For prefix environments, default to the UID and GID of
			# the top-level EROOT directory. The config class has
			# equivalent code, but we also need to do it here if
			# _disable_legacy_globals() has been called.
			eroot = os.path.join(os.environ.get('ROOT', os.sep),
				portage.const.EPREFIX.lstrip(os.sep))
			try:
				eroot_st = os.stat(eroot)
			except OSError:
				pass
			else:
				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')