[PATCH 2 of 6] save settings from untrusted config files in a separate configparser
Alexis S. L. Carvalho
alexis at cecm.usp.br
Wed Oct 18 04:52:16 UTC 2006
# HG changeset patch
# User Alexis S. L. Carvalho <alexis at cecm.usp.br>
# Date 1161149391 10800
# Node ID b8a307f4d978897ff593ac3e8f5f5c62ba169b70
# Parent 811c58c066baff3d9ea1a71f21e1d725b98b5a16
save settings from untrusted config files in a separate configparser
This untrusted configparser is a superset of the trusted configparser,
so that interpolation still works.
Also add an "untrusted" argument to ui.config* to allow querying
ui.ucdata.
With --debug, we print a warning when we read an untrusted config
file, and when we try to access a trusted setting that has one value
in the trusted configparser and another in the untrusted configparser.
diff -r 811c58c066ba -r b8a307f4d978 doc/hgrc.5.txt
--- a/doc/hgrc.5.txt Tue Aug 22 20:45:03 2006 -0300
+++ b/doc/hgrc.5.txt Wed Oct 18 02:29:51 2006 -0300
@@ -50,8 +50,9 @@ installed.
particular repository. This file is not version-controlled, and
will not get transferred during a "clone" operation. Options in
this file override options in all other configuration files.
- On Unix, this file is only read if it belongs to a trusted user
- or to a trusted group.
+ On Unix, most of this file will be ignored if it doesn't belong
+ to a trusted user or to a trusted group. See the documentation
+ for the trusted section below for more details.
SYNTAX
------
@@ -367,11 +368,16 @@ server::
data transfer overhead. Default is False.
trusted::
- Mercurial will only read the .hg/hgrc file from a repository if
- it belongs to a trusted user or to a trusted group. This section
- specifies what users and groups are trusted. The current user is
- always trusted. To trust everybody, list a user or a group with
- name "*".
+ For security reasons, Mercurial will not use the settings in
+ the .hg/hgrc file from a repository if it doesn't belong to a
+ trusted user or to a trusted group. The main exception is the
+ web interface, which automatically uses some safe settings, since
+ it's common to serve repositories from different users.
+
+ This section specifies what users and groups are trusted. The
+ current user is always trusted. To trust everybody, list a user
+ or a group with name "*".
+
users;;
Comma-separated list of trusted users.
groups;;
diff -r 811c58c066ba -r b8a307f4d978 mercurial/ui.py
--- a/mercurial/ui.py Tue Aug 22 20:45:03 2006 -0300
+++ b/mercurial/ui.py Wed Oct 18 02:29:51 2006 -0300
@@ -41,7 +41,9 @@ class ui(object):
self.traceback = traceback
self.trusted_users = {}
self.trusted_groups = {}
+ # if ucdata is not None, its keys must be a superset of cdata's
self.cdata = util.configparser()
+ self.ucdata = None
self.readconfig(util.rcpath())
self.updateopts(verbose, debug, quiet, interactive)
else:
@@ -51,6 +53,8 @@ class ui(object):
self.trusted_users = parentui.trusted_users.copy()
self.trusted_groups = parentui.trusted_groups.copy()
self.cdata = dupconfig(self.parentui.cdata)
+ if self.parentui.ucdata:
+ self.ucdata = dupconfig(self.parentui.ucdata)
if self.parentui.overlay:
self.overlay = dupconfig(self.parentui.overlay)
@@ -94,8 +98,8 @@ class ui(object):
user = util.username(st.st_uid)
group = util.groupname(st.st_gid)
if user not in tusers and group not in tgroups:
- if warn:
- self.warn(_('not reading file %s from untrusted '
+ if warn and self.debugflag:
+ self.warn(_('Not trusting file %s from untrusted '
'user %s, group %s\n') % (f, user, group))
return False
return True
@@ -108,12 +112,29 @@ class ui(object):
fp = open(f)
except IOError:
continue
- if not self._is_trusted(fp, f):
- continue
- try:
- self.cdata.readfp(fp, f)
+ cdata = self.cdata
+ trusted = self._is_trusted(fp, f)
+ if not trusted:
+ if self.ucdata is None:
+ self.ucdata = dupconfig(self.cdata)
+ cdata = self.ucdata
+ elif self.ucdata is not None:
+ # use a separate configparser, so that we don't accidentally
+ # override ucdata settings later on.
+ cdata = util.configparser()
+
+ try:
+ cdata.readfp(fp, f)
except ConfigParser.ParsingError, inst:
- raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
+ msg = _("failed to parse %s\n%s") % (f, inst)
+ if trusted: raise util.Abort(msg)
+ self.warn(_("Ignored: "), msg, "\n")
+
+ if trusted:
+ if cdata != self.cdata:
+ updateconfig(cdata, self.cdata)
+ if self.ucdata is not None:
+ updateconfig(cdata, self.ucdata)
# override data from config files with data set with ui.setconfig
if self.overlay:
updateconfig(self.overlay, self.cdata)
@@ -127,7 +148,10 @@ class ui(object):
self.readhooks.append(hook)
def readsections(self, filename, *sections):
- "read filename and add only the specified sections to the config data"
+ """Read filename and add only the specified sections to the config data
+
+ The settings are added to the trusted config data.
+ """
if not sections:
return
@@ -142,6 +166,8 @@ class ui(object):
cdata.add_section(section)
updateconfig(cdata, self.cdata, sections)
+ if self.ucdata:
+ updateconfig(cdata, self.ucdata, sections)
def fixconfig(self, section=None, name=None, value=None, root=None):
# translate paths relative to root (or home) into absolute paths
@@ -149,7 +175,7 @@ class ui(object):
if root is None:
root = os.getcwd()
items = section and [(name, value)] or []
- for cdata in self.cdata, self.overlay:
+ for cdata in self.cdata, self.ucdata, self.overlay:
if not cdata: continue
if not items and cdata.has_section('paths'):
pathsitems = cdata.items('paths')
@@ -180,59 +206,95 @@ class ui(object):
def setconfig(self, section, name, value):
if not self.overlay:
self.overlay = util.configparser()
- for cdata in (self.overlay, self.cdata):
+ for cdata in (self.overlay, self.cdata, self.ucdata):
+ if not cdata: continue
if not cdata.has_section(section):
cdata.add_section(section)
cdata.set(section, name, value)
self.fixconfig(section, name, value)
- def _config(self, section, name, default, funcname):
- if self.cdata.has_option(section, name):
- try:
- func = getattr(self.cdata, funcname)
+ def _get_cdata(self, untrusted):
+ if untrusted and self.ucdata:
+ return self.ucdata
+ return self.cdata
+
+ def _config(self, section, name, default, funcname, untrusted, abort):
+ cdata = self._get_cdata(untrusted)
+ if cdata.has_option(section, name):
+ try:
+ func = getattr(cdata, funcname)
return func(section, name)
except ConfigParser.InterpolationError, inst:
- raise util.Abort(_("Error in configuration section [%s] "
- "parameter '%s':\n%s")
- % (section, name, inst))
+ msg = _("error in configuration section [%s] "
+ "parameter '%s':\n%s") % (section, name, inst)
+ if abort: raise util.Abort(msg)
+ self.warn(_("Ignoring "), msg, "\n")
return default
- def config(self, section, name, default=None):
- return self._config(section, name, default, 'get')
-
- def configbool(self, section, name, default=False):
- return self._config(section, name, default, 'getboolean')
-
- def configlist(self, section, name, default=None):
+ def _configcommon(self, section, name, default, funcname, untrusted):
+ value = self._config(section, name, default, funcname,
+ untrusted, abort=True)
+ if self.debugflag and not untrusted and self.ucdata:
+ uvalue = self._config(section, name, None, funcname,
+ untrusted=True, abort=False)
+ if uvalue is not None and uvalue != value:
+ self.warn(_("Ignoring untrusted configuration option "
+ "%s.%s = %s\n") % (section, name, uvalue))
+ return value
+
+ def config(self, section, name, default=None, untrusted=False):
+ return self._configcommon(section, name, default, 'get', untrusted)
+
+ def configbool(self, section, name, default=False, untrusted=False):
+ return self._configcommon(section, name, default, 'getboolean', untrusted)
+
+ def configlist(self, section, name, default=None, untrusted=False):
"""Return a list of comma/space separated strings"""
- result = self.config(section, name)
+ result = self.config(section, name, untrusted=untrusted)
if result is None:
result = default or []
if isinstance(result, basestring):
result = result.replace(",", " ").split()
return result
- def has_config(self, section):
+ def has_config(self, section, untrusted=False):
'''tell whether section exists in config.'''
- return self.cdata.has_section(section)
-
- def configitems(self, section):
+ cdata = self._get_cdata(untrusted)
+ return cdata.has_section(section)
+
+ def _configitems(self, section, untrusted, abort):
items = {}
- if self.cdata.has_section(section):
- try:
- items.update(dict(self.cdata.items(section)))
+ cdata = self._get_cdata(untrusted)
+ if cdata.has_section(section):
+ try:
+ items.update(dict(cdata.items(section)))
except ConfigParser.InterpolationError, inst:
- raise util.Abort(_("Error in configuration section [%s]:\n%s")
- % (section, inst))
+ msg = _("error in configuration section [%s]:\n"
+ "%s") % (section, inst)
+ if abort: raise util.Abort(msg)
+ self.warn(_("Ignoring "), msg, "\n")
+ return items
+
+ def configitems(self, section, untrusted=False):
+ items = self._configitems(section, untrusted=untrusted, abort=True)
+ if self.debugflag and not untrusted and self.ucdata:
+ uitems = self._configitems(section, untrusted=True, abort=False)
+ keys = uitems.keys()
+ keys.sort()
+ for k in keys:
+ if uitems[k] != items.get(k):
+ self.warn(_("Ignoring untrusted configuration option "
+ "%s.%s = %s\n") % (section, k, uitems[k]))
x = items.items()
x.sort()
return x
- def walkconfig(self):
- sections = self.cdata.sections()
+ def walkconfig(self, untrusted=False):
+ cdata = self._get_cdata(untrusted)
+ sections = cdata.sections()
sections.sort()
for section in sections:
- for name, value in self.configitems(section):
+ for name, value in self.configitems(section, untrusted):
yield section, name, value.replace('\n', '\\n')
def extensions(self):
diff -r 811c58c066ba -r b8a307f4d978 tests/test-hgrc.out
--- a/tests/test-hgrc.out Tue Aug 22 20:45:03 2006 -0300
+++ b/tests/test-hgrc.out Wed Oct 18 02:29:51 2006 -0300
@@ -1,4 +1,4 @@ abort: Failed to parse .../t/.hg/hgrc
-abort: Failed to parse .../t/.hg/hgrc
+abort: failed to parse .../t/.hg/hgrc
File contains no section headers.
file: .../t/.hg/hgrc, line: 1
'invalid\n'
diff -r 811c58c066ba -r b8a307f4d978 tests/test-trusted.py
--- a/tests/test-trusted.py Tue Aug 22 20:45:03 2006 -0300
+++ b/tests/test-trusted.py Wed Oct 18 02:29:51 2006 -0300
@@ -9,7 +9,7 @@ hgrc = os.environ['HGRCPATH']
hgrc = os.environ['HGRCPATH']
def testui(user='foo', group='bar', tusers=(), tgroups=(),
- cuser='foo', cgroup='bar', debug=False):
+ cuser='foo', cgroup='bar', debug=False, silent=False):
# user, group => owners of the file
# tusers, tgroups => trusted users/groups
# cuser, cgroup => user/group of the current process
@@ -56,8 +56,18 @@ def testui(user='foo', group='bar', tuse
parentui.updateopts(debug=debug)
u = ui.ui(parentui=parentui)
u.readconfig('.hg/hgrc')
+ if silent:
+ return u
+ print 'trusted'
for name, path in u.configitems('paths'):
print ' ', name, '=', path
+ print 'untrusted'
+ for name, path in u.configitems('paths', untrusted=True):
+ print '.',
+ u.config('paths', name) # warning with debug=True
+ print '.',
+ u.config('paths', name, untrusted=True) # no warnings
+ print name, '=', path
print
return u
@@ -68,6 +78,7 @@ f = open('.hg/hgrc', 'w')
f = open('.hg/hgrc', 'w')
f.write('[paths]\n')
f.write('local = /another/path\n\n')
+f.write('interpolated = %(global)s%(local)s\n\n')
f.close()
#print '# Everything is run by user foo, group bar\n'
@@ -111,3 +122,83 @@ testui(user='abc', group='def', tusers=[
print "# Can't figure out the name of the user running this process"
testui(user='abc', group='def', cuser=None)
+
+print "# prints debug warnings"
+u = testui(user='abc', group='def', cuser='foo', debug=True)
+
+print "# ui.readsections"
+filename = 'foobar'
+f = open(filename, 'w')
+f.write('[foobar]\n')
+f.write('baz = quux\n')
+f.close()
+u.readsections(filename, 'foobar')
+print u.config('foobar', 'baz')
+
+print
+print "# read trusted, untrusted, new ui, trusted"
+u = ui.ui()
+u.updateopts(debug=True)
+u.readconfig(filename)
+u2 = ui.ui(parentui=u)
+def username(uid=None):
+ return 'foo'
+util.username = username
+u2.readconfig('.hg/hgrc')
+print 'trusted:'
+print u2.config('foobar', 'baz')
+print u2.config('paths', 'interpolated')
+print 'untrusted:'
+print u2.config('foobar', 'baz', untrusted=True)
+print u2.config('paths', 'interpolated', untrusted=True)
+
+print
+print "# error handling"
+
+def assertraises(f, exc=util.Abort):
+ try:
+ f()
+ except exc, inst:
+ print 'raised', inst.__class__.__name__
+ else:
+ print 'no exception?!'
+
+print "# file doesn't exist"
+os.unlink('.hg/hgrc')
+assert not os.path.exists('.hg/hgrc')
+testui(debug=True, silent=True)
+testui(user='abc', group='def', debug=True, silent=True)
+
+print
+print "# parse error"
+f = open('.hg/hgrc', 'w')
+f.write('foo = bar')
+f.close()
+testui(user='abc', group='def', silent=True)
+assertraises(lambda: testui(debug=True, silent=True))
+
+print
+print "# interpolation error"
+f = open('.hg/hgrc', 'w')
+f.write('[foo]\n')
+f.write('bar = %(')
+f.close()
+u = testui(debug=True, silent=True)
+print '# regular config:'
+print ' trusted',
+assertraises(lambda: u.config('foo', 'bar'))
+print 'untrusted',
+assertraises(lambda: u.config('foo', 'bar', untrusted=True))
+
+u = testui(user='abc', group='def', debug=True, silent=True)
+print ' trusted',
+print u.config('foo', 'bar')
+print 'untrusted',
+assertraises(lambda: u.config('foo', 'bar', untrusted=True))
+
+print '# configitems:'
+print ' trusted',
+print u.configitems('foo')
+print 'untrusted',
+assertraises(lambda: u.configitems('foo', untrusted=True))
+
diff -r 811c58c066ba -r b8a307f4d978 tests/test-trusted.py.out
--- a/tests/test-trusted.py.out Tue Aug 22 20:45:03 2006 -0300
+++ b/tests/test-trusted.py.out Wed Oct 18 02:29:51 2006 -0300
@@ -1,67 +1,208 @@
# same user, same group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# same user, different group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# different user, same group
-not reading file .hg/hgrc from untrusted user abc, group bar
- global = /some/path
+trusted
+ global = /some/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# different user, same group, but we trust the group
- global = /some/path
- local = /another/path
-
-# different user, different group
-not reading file .hg/hgrc from untrusted user abc, group def
- global = /some/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, different group
+trusted
+ global = /some/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# different user, different group, but we trust the user
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# different user, different group, but we trust the group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# different user, different group, but we trust the user and the group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# we trust all users
# different user, different group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# we trust all groups
# different user, different group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# we trust all users and groups
# different user, different group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# we don't get confused by users and groups with the same name
# different user, different group
-not reading file .hg/hgrc from untrusted user abc, group def
- global = /some/path
+trusted
+ global = /some/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# list of user names
# different user, different group, but we trust the user
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# list of group names
# different user, different group, but we trust the group
- global = /some/path
- local = /another/path
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
# Can't figure out the name of the user running this process
# different user, different group
- global = /some/path
- local = /another/path
-
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# prints debug warnings
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+trusted
+Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
+Ignoring untrusted configuration option paths.local = /another/path
+ global = /some/path
+untrusted
+. . global = /some/path
+.Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
+ . interpolated = /some/path/another/path
+.Ignoring untrusted configuration option paths.local = /another/path
+ . local = /another/path
+
+# ui.readsections
+quux
+
+# read trusted, untrusted, new ui, trusted
+Not trusting file foobar from untrusted user abc, group def
+trusted:
+Ignoring untrusted configuration option foobar.baz = quux
+None
+/some/path/another/path
+untrusted:
+quux
+/some/path/another/path
+
+# error handling
+# file doesn't exist
+# same user, same group
+# different user, different group
+
+# parse error
+# different user, different group
+Ignored: failed to parse .hg/hgrc
+File contains no section headers.
+file: .hg/hgrc, line: 1
+'foo = bar'
+# same user, same group
+raised Abort
+
+# interpolation error
+# same user, same group
+# regular config:
+ trusted raised Abort
+untrusted raised Abort
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+ trustedIgnoring error in configuration section [foo] parameter 'bar':
+bad interpolation variable reference '%('
+ None
+untrusted raised Abort
+# configitems:
+ trustedIgnoring error in configuration section [foo]:
+bad interpolation variable reference '%('
+ []
+untrusted raised Abort
diff -r 811c58c066ba -r b8a307f4d978 tests/test-ui-config.out
--- a/tests/test-ui-config.out Tue Aug 22 20:45:03 2006 -0300
+++ b/tests/test-ui-config.out Wed Oct 18 02:29:51 2006 -0300
@@ -1,6 +1,6 @@
[('bool1', 'true'), ('bool2', 'false'), ('string', 'string value')]
[('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob')]
-Error in configuration section [interpolation]:
+error in configuration section [interpolation]:
'%' must be followed by '%' or '(', found: '%bad2'
---
'string value'
@@ -31,15 +31,15 @@ True
---
'hallo'
'hallo world'
-Error in configuration section [interpolation] parameter 'value3':
+error in configuration section [interpolation] parameter 'value3':
Bad value substitution:
section: [interpolation]
option : value3
key : novalue
rawval :
-Error in configuration section [interpolation] parameter 'value4':
+error in configuration section [interpolation] parameter 'value4':
bad interpolation variable reference '%(bad)1'
-Error in configuration section [interpolation] parameter 'value5':
+error in configuration section [interpolation] parameter 'value5':
'%' must be followed by '%' or '(', found: '%bad2'
---
More information about the Mercurial-devel
mailing list