# HG changeset patch # User Alexis S. L. Carvalho # Date 1161883544 -7200 # Node ID 3b07e223534b3de2d92e1d952e5dab88c981d333 # Parent ef80b13df85ab0aa8008d1bb629a86e2162333c8 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. This is essentially the same patch that was previously applied as revision 494521a3f142. diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -50,6 +50,8 @@ 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. SYNTAX ------ @@ -364,6 +366,17 @@ server:: 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. 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;; + Comma-separated list of trusted groups. + ui:: User interface controls. debug;; diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -39,6 +39,8 @@ class ui(object): self.debugflag = debug self.interactive = interactive self.traceback = traceback + self.trusted_users = {} + self.trusted_groups = {} self.cdata = util.configparser() self.readconfig(util.rcpath()) self.updateopts(verbose, debug, quiet, interactive) @@ -46,6 +48,8 @@ class ui(object): # parentui may point to an ui object which is already a child self.parentui = parentui.parentui or parentui self.readhooks = self.parentui.readhooks[:] + self.trusted_users = parentui.trusted_users.copy() + self.trusted_groups = parentui.trusted_groups.copy() self.cdata = dupconfig(self.parentui.cdata) if self.parentui.overlay: self.overlay = dupconfig(self.parentui.overlay) @@ -82,12 +86,32 @@ class ui(object): elif self.verbose and self.quiet: self.quiet = self.verbose = False + def _is_trusted(self, fp, f, warn=True): + tusers = self.trusted_users + tgroups = self.trusted_groups + if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups: + st = util.fstat(fp) + 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 ' + 'user %s, group %s\n') % (f, user, group)) + return False + return True + def readconfig(self, fn, root=None): if isinstance(fn, basestring): fn = [fn] for f in fn: try: - self.cdata.read(f) + fp = open(f) + except IOError: + continue + if not self._is_trusted(fp, f): + continue + try: + self.cdata.readfp(fp, f) except ConfigParser.ParsingError, inst: raise util.Abort(_("Failed to parse %s\n%s") % (f, inst)) # override data from config files with data set with ui.setconfig @@ -144,6 +168,16 @@ class ui(object): if name is None or name == 'interactive': self.interactive = self.configbool("ui", "interactive", True) + # update trust information + if section is None or section == 'trusted': + 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 + def setconfig(self, section, name, value): if not self.overlay: self.overlay = util.configparser() diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -519,6 +519,36 @@ def is_win_9x(): except AttributeError: return os.name == 'nt' and 'command' in os.environ.get('comspec', '') +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: + import pwd + if uid is None: + uid = os.getuid() + try: + return pwd.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: + import grp + if gid is None: + gid = os.getgid() + try: + return grp.getgrgid(gid)[0] + except KeyError: + return str(gid) + except ImportError: + return None + # Platform specific variants if os.name == 'nt': demandload(globals(), "msvcrt") diff --git a/tests/test-trusted.py b/tests/test-trusted.py new file mode 100644 --- /dev/null +++ b/tests/test-trusted.py @@ -0,0 +1,113 @@ +#!/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', debug=False): + # 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() + parentui.updateopts(debug=debug) + 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) diff --git a/tests/test-trusted.py.out b/tests/test-trusted.py.out new file mode 100644 --- /dev/null +++ b/tests/test-trusted.py.out @@ -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 +