Only read .hg/hgrc files from trusted users/groups
The list of trusted users and groups is specified in the [trusted]
section of a hgrc; the current user is always trusted; "*" can be
used to trust all users/groups.
Global hgrc files are always read.
On Windows (and other systems that don't have the pwd and grp modules),
all .hg/hgrc files are read.
--- a/doc/hgrc.5.txt Wed Aug 23 00:19:24 2006 +0200
+++ b/doc/hgrc.5.txt Tue Aug 22 20:45:03 2006 -0300
@@ -50,6 +50,8 @@
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.
SYNTAX
------
@@ -349,6 +351,16 @@
6Mbps), uncompressed streaming is slower, because of the extra
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. To trust everybody,
+ list a user or a group with name "*".
+ users;;
+ Comma-separated list of trusted users.
+ groups;;
+ Comma-separated list of trusted groups.
+
ui::
User interface controls.
debug;;
--- a/mercurial/ui.py Wed Aug 23 00:19:24 2006 +0200
+++ b/mercurial/ui.py Tue Aug 22 20:45:03 2006 -0300
@@ -19,6 +19,8 @@
# this is the parent of all ui children
self.parentui = None
self.readhooks = list(readhooks)
+ self.trusted_users = {}
+ self.trusted_groups = {}
self.cdata = ConfigParser.SafeConfigParser()
self.readconfig(util.rcpath())
@@ -37,6 +39,8 @@
# parentui may point to an ui object which is already a child
self.parentui = parentui.parentui or parentui
self.readhooks = list(parentui.readhooks or readhooks)
+ self.trusted_users = parentui.trusted_users.copy()
+ self.trusted_groups = parentui.trusted_groups.copy()
parent_cdata = self.parentui.cdata
self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
# make interpolation work
@@ -72,7 +76,22 @@
fn = [fn]
for f in fn:
try:
- self.cdata.read(f)
+ fp = open(f)
+ except IOError:
+ continue
+ if ((self.trusted_users or self.trusted_groups) and
+ '*' not in self.trusted_users and
+ '*' not in self.trusted_groups):
+ st = util.fstat(fp)
+ user = util.username(st.st_uid)
+ group = util.groupname(st.st_gid)
+ if (user not in self.trusted_users and
+ group not in self.trusted_groups):
+ self.warn(_('not reading file %s from untrusted '
+ 'user %s, group %s\n') % (f, user, group))
+ continue
+ try:
+ self.cdata.readfp(fp, f)
except ConfigParser.ParsingError, inst:
raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
# translate paths relative to root (or home) into absolute paths
@@ -81,6 +100,13 @@
for name, path in self.configitems("paths"):
if path and "://" not in path and not os.path.isabs(path):
self.cdata.set("paths", name, os.path.join(root, path))
+ user = util.username()
+ if user is not None:
+ self.trusted_users[user] = 1
+ for user in self.configlist('trusted', 'users'):
+ self.trusted_users[user] = 1
+ for group in self.configlist('trusted', 'groups'):
+ self.trusted_groups[group] = 1
for hook in self.readhooks:
hook(self)
--- a/mercurial/util.py Wed Aug 23 00:19:24 2006 +0200
+++ b/mercurial/util.py Tue Aug 22 20:45:03 2006 -0300
@@ -15,7 +15,7 @@
from i18n import gettext as _
from demandload import *
demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
-demandload(globals(), "os threading time")
+demandload(globals(), "os threading time pwd grp")
# used by parsedate
defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
@@ -509,6 +509,38 @@
raise Abort(_('user name not available - set USERNAME '
'environment variable'))
+def username(uid=None):
+ """Return the name of the user with the given uid.
+
+ If uid is None, return the name of the current user."""
+ try:
+ # force an ImportError if there's no module pwd
+ getpwuid = pwd.getpwuid
+ if uid is None:
+ uid = os.getuid()
+ try:
+ return getpwuid(uid)[0]
+ except KeyError:
+ return str(uid)
+ except ImportError:
+ return None
+
+def groupname(gid=None):
+ """Return the name of the group with the given gid.
+
+ If gid is None, return the name of the current group."""
+ try:
+ # force an ImportError if there's no module grp
+ getgrgid = grp.getgrgid
+ if gid is None:
+ gid = os.getgid()
+ try:
+ return getgrgid(gid)[0]
+ except KeyError:
+ return str(gid)
+ except ImportError:
+ return None
+
# Platform specific variants
if os.name == 'nt':
demandload(globals(), "msvcrt")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-trusted.py Tue Aug 22 20:45:03 2006 -0300
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# Since it's not easy to write a test that portably deals
+# with files from different users/groups, we cheat a bit by
+# monkey-patching some functions in the util module
+
+import os
+from mercurial import ui, util
+
+hgrc = os.environ['HGRCPATH']
+
+def testui(user='foo', group='bar', tusers=(), tgroups=(),
+ cuser='foo', cgroup='bar'):
+ # user, group => owners of the file
+ # tusers, tgroups => trusted users/groups
+ # cuser, cgroup => user/group of the current process
+
+ # write a global hgrc with the list of trusted users/groups and
+ # some setting so that we can be sure it was read
+ f = open(hgrc, 'w')
+ f.write('[paths]\n')
+ f.write('global = /some/path\n\n')
+
+ if tusers or tgroups:
+ f.write('[trusted]\n')
+ if tusers:
+ f.write('users = %s\n' % ', '.join(tusers))
+ if tgroups:
+ f.write('groups = %s\n' % ', '.join(tgroups))
+ f.close()
+
+ # override the functions that give names to uids and gids
+ def username(uid=None):
+ if uid is None:
+ return cuser
+ return user
+ util.username = username
+
+ def groupname(gid=None):
+ if gid is None:
+ return 'bar'
+ return group
+ util.groupname = groupname
+
+ # try to read everything
+ #print '# File belongs to user %s, group %s' % (user, group)
+ #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
+ kind = ('different', 'same')
+ who = ('', 'user', 'group', 'user and the group')
+ trusted = who[(user in tusers) + 2*(group in tgroups)]
+ if trusted:
+ trusted = ', but we trust the ' + trusted
+ print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
+ trusted)
+
+ parentui = ui.ui()
+ u = ui.ui(parentui=parentui)
+ u.readconfig('.hg/hgrc')
+ for name, path in u.configitems('paths'):
+ print name, '=', path
+ print
+
+ return u
+
+os.mkdir('repo')
+os.chdir('repo')
+os.mkdir('.hg')
+f = open('.hg/hgrc', 'w')
+f.write('[paths]\n')
+f.write('local = /another/path\n\n')
+f.close()
+
+#print '# Everything is run by user foo, group bar\n'
+
+# same user, same group
+testui()
+# same user, different group
+testui(group='def')
+# different user, same group
+testui(user='abc')
+# ... but we trust the group
+testui(user='abc', tgroups=['bar'])
+# different user, different group
+testui(user='abc', group='def')
+# ... but we trust the user
+testui(user='abc', group='def', tusers=['abc'])
+# ... but we trust the group
+testui(user='abc', group='def', tgroups=['def'])
+# ... but we trust the user and the group
+testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
+# ... but we trust all users
+print '# we trust all users'
+testui(user='abc', group='def', tusers=['*'])
+# ... but we trust all groups
+print '# we trust all groups'
+testui(user='abc', group='def', tgroups=['*'])
+# ... but we trust the whole universe
+print '# we trust all users and groups'
+testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
+# ... check that users and groups are in different namespaces
+print "# we don't get confused by users and groups with the same name"
+testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
+# ... lists of user names work
+print "# list of user names"
+testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
+ tgroups=['bar', 'baz', 'qux'])
+# ... lists of group names work
+print "# list of group names"
+testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
+ tgroups=['bar', 'def', 'baz', 'qux'])
+
+print "# Can't figure out the name of the user running this process"
+testui(user='abc', group='def', cuser=None)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-trusted.py.out Tue Aug 22 20:45:03 2006 -0300
@@ -0,0 +1,67 @@
+# same user, same group
+global = /some/path
+local = /another/path
+
+# same user, different group
+global = /some/path
+local = /another/path
+
+# different user, same group
+not reading file .hg/hgrc from untrusted user abc, group bar
+global = /some/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
+
+# different user, different group, but we trust the user
+global = /some/path
+local = /another/path
+
+# different user, different group, but we trust the group
+global = /some/path
+local = /another/path
+
+# different user, different group, but we trust the user and the group
+global = /some/path
+local = /another/path
+
+# we trust all users
+# different user, different group
+global = /some/path
+local = /another/path
+
+# we trust all groups
+# different user, different group
+global = /some/path
+local = /another/path
+
+# we trust all users and groups
+# different user, different group
+global = /some/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
+
+# list of user names
+# different user, different group, but we trust the user
+global = /some/path
+local = /another/path
+
+# list of group names
+# different user, different group, but we trust the group
+global = /some/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
+