Replace getstatusoutput with unicode safe Popen.
[portage.git] / pym / portage / data.py
1 # data.py -- Calculated/Discovered Data Values
2 # Copyright 1998-2012 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._unicode_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                 try:
90                         portage_uid = pwd.getpwnam(_get_global('_portage_username')).pw_uid
91                         _portage_grpname = _get_global('_portage_grpname')
92                         if platform.python_implementation() == 'PyPy':
93                                 # Somehow this prevents "TypeError: expected string" errors
94                                 # from grp.getgrnam() with PyPy 1.7
95                                 _portage_grpname = str(_portage_grpname)
96                         portage_gid = grp.getgrnam(_portage_grpname).gr_gid
97                         if secpass < 1 and portage_gid in os.getgroups():
98                                 secpass = 1
99                 except KeyError:
100                         portage_uid = 0
101                         portage_gid = 0
102                         writemsg(colorize("BAD",
103                                 _("portage: 'portage' user or group missing.")) + "\n", noiselevel=-1)
104                         writemsg(_(
105                                 "         For the defaults, line 1 goes into passwd, "
106                                 "and 2 into group.\n"), noiselevel=-1)
107                         writemsg(colorize("GOOD",
108                                 "         portage:x:250:250:portage:/var/tmp/portage:/bin/false") \
109                                 + "\n", noiselevel=-1)
110                         writemsg(colorize("GOOD", "         portage::250:portage") + "\n",
111                                 noiselevel=-1)
112                         portage_group_warning()
113
114                 _initialized_globals.add('portage_gid')
115                 _initialized_globals.add('portage_uid')
116                 _initialized_globals.add('secpass')
117
118                 if k == 'portage_gid':
119                         return portage_gid
120                 elif k == 'portage_uid':
121                         return portage_uid
122                 elif k == 'secpass':
123                         return secpass
124                 else:
125                         raise AssertionError('unknown name: %s' % k)
126
127         elif k == 'userpriv_groups':
128                 v = [portage_gid]
129                 if secpass >= 2:
130                         # Get a list of group IDs for the portage user. Do not use
131                         # grp.getgrall() since it is known to trigger spurious
132                         # SIGPIPE problems with nss_ldap.
133                         cmd = ["id", "-G", _portage_username]
134                         encoding = portage._encodings['content']
135                         if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
136                                 # Python 3.1 does not support bytes in Popen args.
137                                 cmd = [portage._unicode_encode(x,
138                                         encoding=encoding, errors='strict')
139                                         for x in cmd]
140                         proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
141                                 stderr=subprocess.STDOUT)
142                         myoutput = proc.communicate()[0]
143                         status = proc.wait()
144                         if os.WIFEXITED(status) and os.WEXITSTATUS(status) == os.EX_OK:
145                                 for x in portage._unicode_decode(myoutput,
146                                         encoding=encoding, errors='strict').split():
147                                         try:
148                                                 v.append(int(x))
149                                         except ValueError:
150                                                 pass
151                                 v = sorted(set(v))
152
153         # Avoid instantiating portage.settings when the desired
154         # variable is set in os.environ.
155         elif k in ('_portage_grpname', '_portage_username'):
156                 v = None
157                 if k == '_portage_grpname':
158                         env_key = 'PORTAGE_GRPNAME'
159                 else:
160                         env_key = 'PORTAGE_USERNAME'
161
162                 if env_key in os.environ:
163                         v = os.environ[env_key]
164                 elif hasattr(portage, 'settings'):
165                         v = portage.settings.get(env_key)
166                 elif portage.const.EPREFIX:
167                         # For prefix environments, default to the UID and GID of
168                         # the top-level EROOT directory. The config class has
169                         # equivalent code, but we also need to do it here if
170                         # _disable_legacy_globals() has been called.
171                         eroot = os.path.join(os.environ.get('ROOT', os.sep),
172                                 portage.const.EPREFIX.lstrip(os.sep))
173                         try:
174                                 eroot_st = os.stat(eroot)
175                         except OSError:
176                                 pass
177                         else:
178                                 if k == '_portage_grpname':
179                                         try:
180                                                 grp_struct = grp.getgrgid(eroot_st.st_gid)
181                                         except KeyError:
182                                                 pass
183                                         else:
184                                                 v = grp_struct.gr_name
185                                 else:
186                                         try:
187                                                 pwd_struct = pwd.getpwuid(eroot_st.st_uid)
188                                         except KeyError:
189                                                 pass
190                                         else:
191                                                 v = pwd_struct.pw_name
192
193                 if v is None:
194                         v = 'portage'
195         else:
196                 raise AssertionError('unknown name: %s' % k)
197
198         globals()[k] = v
199         _initialized_globals.add(k)
200         return v
201
202 class _GlobalProxy(portage.proxy.objectproxy.ObjectProxy):
203
204         __slots__ = ('_name',)
205
206         def __init__(self, name):
207                 portage.proxy.objectproxy.ObjectProxy.__init__(self)
208                 object.__setattr__(self, '_name', name)
209
210         def _get_target(self):
211                 return _get_global(object.__getattribute__(self, '_name'))
212
213 for k in ('portage_gid', 'portage_uid', 'secpass', 'userpriv_groups',
214         '_portage_grpname', '_portage_username'):
215         globals()[k] = _GlobalProxy(k)
216 del k
217
218 def _init(settings):
219         """
220         Use config variables like PORTAGE_GRPNAME and PORTAGE_USERNAME to
221         initialize global variables. This allows settings to come from make.conf
222         instead of requiring them to be set in the calling environment.
223         """
224         if '_portage_grpname' not in _initialized_globals and \
225                 '_portage_username' not in _initialized_globals:
226
227                 v = settings.get('PORTAGE_GRPNAME', 'portage')
228                 globals()['_portage_grpname'] = v
229                 _initialized_globals.add('_portage_grpname')
230
231                 v = settings.get('PORTAGE_USERNAME', 'portage')
232                 globals()['_portage_username'] = v
233                 _initialized_globals.add('_portage_username')