Python 3.1: pass bytes to Popen
[portage.git] / pym / portage / data.py
1 # data.py -- Calculated/Discovered Data Values
2 # Copyright 1998-2013 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4
5 import os, pwd, grp, platform, sys
6
7 import portage
8 portage.proxy.lazyimport.lazyimport(globals(),
9         'portage.output:colorize',
10         'portage.util:writemsg',
11         'subprocess'
12 )
13 from portage.localization import _
14
15 ostype=platform.system()
16 userland = None
17 if ostype == "DragonFly" or ostype.endswith("BSD"):
18         userland = "BSD"
19 else:
20         userland = "GNU"
21
22 lchown = getattr(os, "lchown", None)
23
24 if not lchown:
25         if ostype == "Darwin":
26                 def lchown(*pos_args, **key_args):
27                         pass
28         else:
29                 def lchown(*pargs, **kwargs):
30                         writemsg(colorize("BAD", "!!!") + _(
31                                 " It seems that os.lchown does not"
32                                 " exist.  Please rebuild python.\n"), noiselevel=-1)
33                 lchown()
34
35 lchown = portage._chown_func_wrapper(lchown)
36
37 def portage_group_warning():
38         warn_prefix = colorize("BAD", "*** WARNING ***  ")
39         mylines = [
40                 "For security reasons, only system administrators should be",
41                 "allowed in the portage group.  Untrusted users or processes",
42                 "can potentially exploit the portage group for attacks such as",
43                 "local privilege escalation."
44         ]
45         for x in mylines:
46                 writemsg(warn_prefix, noiselevel=-1)
47                 writemsg(x, noiselevel=-1)
48                 writemsg("\n", noiselevel=-1)
49         writemsg("\n", noiselevel=-1)
50
51 # Portage has 3 security levels that depend on the uid and gid of the main
52 # process and are assigned according to the following table:
53 #
54 # Privileges  secpass  uid    gid
55 # normal      0        any    any
56 # group       1        any    portage_gid
57 # super       2        0      any
58 #
59 # If the "wheel" group does not exist then wheelgid falls back to 0.
60 # If the "portage" group does not exist then portage_uid falls back to wheelgid.
61
62 uid=os.getuid()
63 wheelgid=0
64
65 try:
66         wheelgid=grp.getgrnam("wheel")[2]
67 except KeyError:
68         pass
69
70 # The portage_uid and portage_gid global constants, and others that
71 # depend on them are initialized lazily, in order to allow configuration
72 # via make.conf. Eventually, these constants may be deprecated in favor
73 # of config attributes, since it's conceivable that multiple
74 # configurations with different constants could be used simultaneously.
75 _initialized_globals = set()
76
77 def _get_global(k):
78         if k in _initialized_globals:
79                 return globals()[k]
80
81         if k in ('portage_gid', 'portage_uid', 'secpass'):
82                 global portage_gid, portage_uid, secpass
83                 secpass = 0
84                 if uid == 0:
85                         secpass = 2
86                 elif portage.const.EPREFIX:
87                         secpass = 2
88                 #Discover the uid and gid of the portage user/group
89                 keyerror = False
90                 try:
91                         portage_uid = pwd.getpwnam(_get_global('_portage_username')).pw_uid
92                 except KeyError:
93                         keyerror = True
94                         portage_uid = 0
95
96                 try:
97                         portage_gid = grp.getgrnam(_get_global('_portage_grpname')).gr_gid
98                 except KeyError:
99                         keyerror = True
100                         portage_gid = 0
101
102                 if secpass < 1 and portage_gid in os.getgroups():
103                         secpass = 1
104
105                 # Suppress this error message if both PORTAGE_GRPNAME and
106                 # PORTAGE_USERNAME are set to "root", for things like
107                 # Android (see bug #454060).
108                 if keyerror and not (_get_global('_portage_username') == "root" and
109                         _get_global('_portage_grpname') == "root"):
110                         writemsg(colorize("BAD",
111                                 _("portage: 'portage' user or group missing.")) + "\n", noiselevel=-1)
112                         writemsg(_(
113                                 "         For the defaults, line 1 goes into passwd, "
114                                 "and 2 into group.\n"), noiselevel=-1)
115                         writemsg(colorize("GOOD",
116                                 "         portage:x:250:250:portage:/var/tmp/portage:/bin/false") \
117                                 + "\n", noiselevel=-1)
118                         writemsg(colorize("GOOD", "         portage::250:portage") + "\n",
119                                 noiselevel=-1)
120                         portage_group_warning()
121
122                 _initialized_globals.add('portage_gid')
123                 _initialized_globals.add('portage_uid')
124                 _initialized_globals.add('secpass')
125
126                 if k == 'portage_gid':
127                         return portage_gid
128                 elif k == 'portage_uid':
129                         return portage_uid
130                 elif k == 'secpass':
131                         return secpass
132                 else:
133                         raise AssertionError('unknown name: %s' % k)
134
135         elif k == 'userpriv_groups':
136                 v = [portage_gid]
137                 if secpass >= 2:
138                         # Get a list of group IDs for the portage user. Do not use
139                         # grp.getgrall() since it is known to trigger spurious
140                         # SIGPIPE problems with nss_ldap.
141                         cmd = ["id", "-G", _portage_username]
142
143                         if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000:
144                                 # Python 3.1 _execvp throws TypeError for non-absolute executable
145                                 # path passed as bytes (see http://bugs.python.org/issue8513).
146                                 fullname = portage.process.find_binary(cmd[0])
147                                 if fullname is None:
148                                         globals()[k] = v
149                                         _initialized_globals.add(k)
150                                         return v
151                                 cmd[0] = fullname
152
153                         encoding = portage._encodings['content']
154                         cmd = [portage._unicode_encode(x,
155                                 encoding=encoding, errors='strict') for x in cmd]
156                         proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
157                                 stderr=subprocess.STDOUT)
158                         myoutput = proc.communicate()[0]
159                         status = proc.wait()
160                         if os.WIFEXITED(status) and os.WEXITSTATUS(status) == os.EX_OK:
161                                 for x in portage._unicode_decode(myoutput,
162                                         encoding=encoding, errors='strict').split():
163                                         try:
164                                                 v.append(int(x))
165                                         except ValueError:
166                                                 pass
167                                 v = sorted(set(v))
168
169         # Avoid instantiating portage.settings when the desired
170         # variable is set in os.environ.
171         elif k in ('_portage_grpname', '_portage_username'):
172                 v = None
173                 if k == '_portage_grpname':
174                         env_key = 'PORTAGE_GRPNAME'
175                 else:
176                         env_key = 'PORTAGE_USERNAME'
177
178                 if env_key in os.environ:
179                         v = os.environ[env_key]
180                 elif hasattr(portage, 'settings'):
181                         v = portage.settings.get(env_key)
182                 elif portage.const.EPREFIX:
183                         # For prefix environments, default to the UID and GID of
184                         # the top-level EROOT directory. The config class has
185                         # equivalent code, but we also need to do it here if
186                         # _disable_legacy_globals() has been called.
187                         eroot = os.path.join(os.environ.get('ROOT', os.sep),
188                                 portage.const.EPREFIX.lstrip(os.sep))
189                         try:
190                                 eroot_st = os.stat(eroot)
191                         except OSError:
192                                 pass
193                         else:
194                                 if k == '_portage_grpname':
195                                         try:
196                                                 grp_struct = grp.getgrgid(eroot_st.st_gid)
197                                         except KeyError:
198                                                 pass
199                                         else:
200                                                 v = grp_struct.gr_name
201                                 else:
202                                         try:
203                                                 pwd_struct = pwd.getpwuid(eroot_st.st_uid)
204                                         except KeyError:
205                                                 pass
206                                         else:
207                                                 v = pwd_struct.pw_name
208
209                 if v is None:
210                         v = 'portage'
211         else:
212                 raise AssertionError('unknown name: %s' % k)
213
214         globals()[k] = v
215         _initialized_globals.add(k)
216         return v
217
218 class _GlobalProxy(portage.proxy.objectproxy.ObjectProxy):
219
220         __slots__ = ('_name',)
221
222         def __init__(self, name):
223                 portage.proxy.objectproxy.ObjectProxy.__init__(self)
224                 object.__setattr__(self, '_name', name)
225
226         def _get_target(self):
227                 return _get_global(object.__getattribute__(self, '_name'))
228
229 for k in ('portage_gid', 'portage_uid', 'secpass', 'userpriv_groups',
230         '_portage_grpname', '_portage_username'):
231         globals()[k] = _GlobalProxy(k)
232 del k
233
234 def _init(settings):
235         """
236         Use config variables like PORTAGE_GRPNAME and PORTAGE_USERNAME to
237         initialize global variables. This allows settings to come from make.conf
238         instead of requiring them to be set in the calling environment.
239         """
240         if '_portage_grpname' not in _initialized_globals and \
241                 '_portage_username' not in _initialized_globals:
242
243                 # Prevents "TypeError: expected string" errors
244                 # from grp.getgrnam() with PyPy
245                 native_string = platform.python_implementation() == 'PyPy'
246
247                 v = settings.get('PORTAGE_GRPNAME', 'portage')
248                 if native_string:
249                         v = portage._native_string(v)
250                 globals()['_portage_grpname'] = v
251                 _initialized_globals.add('_portage_grpname')
252
253                 v = settings.get('PORTAGE_USERNAME', 'portage')
254                 if native_string:
255                         v = portage._native_string(v)
256                 globals()['_portage_username'] = v
257                 _initialized_globals.add('_portage_username')