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