Merge #14984: On POSIX, enforce permissions when reading default .netrc.
This commit is contained in:
commit
4750fa8369
|
@ -22,6 +22,14 @@ the Unix :program:`ftp` program and other FTP clients.
|
||||||
no argument is given, the file :file:`.netrc` in the user's home directory will
|
no argument is given, the file :file:`.netrc` in the user's home directory will
|
||||||
be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic
|
be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic
|
||||||
information including the file name, line number, and terminating token.
|
information including the file name, line number, and terminating token.
|
||||||
|
If no argument is specified on a POSIX system, the presence of passwords in
|
||||||
|
the :file:`.netrc` file will raise a :exc:`NetrcParseError` if the file
|
||||||
|
ownership or permissions are insecure (owned by a user other than the user
|
||||||
|
running the process, or accessible for read or write by any other user).
|
||||||
|
This implements security behavior equivalent to that of ftp and other
|
||||||
|
programs that use :file:`.netrc`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.4 Added the POSIX permission check.
|
||||||
|
|
||||||
|
|
||||||
.. exception:: NetrcParseError
|
.. exception:: NetrcParseError
|
||||||
|
|
27
Lib/netrc.py
27
Lib/netrc.py
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Module and documentation by Eric S. Raymond, 21 Dec 1998
|
# Module and documentation by Eric S. Raymond, 21 Dec 1998
|
||||||
|
|
||||||
import io, os, shlex
|
import io, os, shlex, stat, pwd
|
||||||
|
|
||||||
__all__ = ["netrc", "NetrcParseError"]
|
__all__ = ["netrc", "NetrcParseError"]
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ class NetrcParseError(Exception):
|
||||||
|
|
||||||
class netrc:
|
class netrc:
|
||||||
def __init__(self, file=None):
|
def __init__(self, file=None):
|
||||||
|
default_netrc = file is None
|
||||||
if file is None:
|
if file is None:
|
||||||
try:
|
try:
|
||||||
file = os.path.join(os.environ['HOME'], ".netrc")
|
file = os.path.join(os.environ['HOME'], ".netrc")
|
||||||
|
@ -29,9 +30,9 @@ class netrc:
|
||||||
self.hosts = {}
|
self.hosts = {}
|
||||||
self.macros = {}
|
self.macros = {}
|
||||||
with open(file) as fp:
|
with open(file) as fp:
|
||||||
self._parse(file, fp)
|
self._parse(file, fp, default_netrc)
|
||||||
|
|
||||||
def _parse(self, file, fp):
|
def _parse(self, file, fp, default_netrc):
|
||||||
lexer = shlex.shlex(fp)
|
lexer = shlex.shlex(fp)
|
||||||
lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
|
lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
|
||||||
lexer.commenters = lexer.commenters.replace('#', '')
|
lexer.commenters = lexer.commenters.replace('#', '')
|
||||||
|
@ -86,6 +87,26 @@ class netrc:
|
||||||
elif tt == 'account':
|
elif tt == 'account':
|
||||||
account = lexer.get_token()
|
account = lexer.get_token()
|
||||||
elif tt == 'password':
|
elif tt == 'password':
|
||||||
|
if os.name == 'posix' and default_netrc:
|
||||||
|
prop = os.fstat(fp.fileno())
|
||||||
|
if prop.st_uid != os.getuid():
|
||||||
|
try:
|
||||||
|
fowner = pwd.getpwuid(prop.st_uid)[0]
|
||||||
|
except KeyError:
|
||||||
|
fowner = 'uid %s' % prop.st_uid
|
||||||
|
try:
|
||||||
|
user = pwd.getpwuid(os.getuid())[0]
|
||||||
|
except KeyError:
|
||||||
|
user = 'uid %s' % os.getuid()
|
||||||
|
raise NetrcParseError(
|
||||||
|
("~/.netrc file owner (%s) does not match"
|
||||||
|
" current user (%s)") % (fowner, user),
|
||||||
|
file, lexer.lineno)
|
||||||
|
if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
|
||||||
|
raise NetrcParseError(
|
||||||
|
"~/.netrc access too permissive: access"
|
||||||
|
" permissions must restrict access to only"
|
||||||
|
" the owner", file, lexer.lineno)
|
||||||
password = lexer.get_token()
|
password = lexer.get_token()
|
||||||
else:
|
else:
|
||||||
raise NetrcParseError("bad follower token %r" % tt,
|
raise NetrcParseError("bad follower token %r" % tt,
|
||||||
|
|
|
@ -5,9 +5,6 @@ temp_filename = support.TESTFN
|
||||||
|
|
||||||
class NetrcTestCase(unittest.TestCase):
|
class NetrcTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
os.unlink(temp_filename)
|
|
||||||
|
|
||||||
def make_nrc(self, test_data):
|
def make_nrc(self, test_data):
|
||||||
test_data = textwrap.dedent(test_data)
|
test_data = textwrap.dedent(test_data)
|
||||||
mode = 'w'
|
mode = 'w'
|
||||||
|
@ -15,6 +12,7 @@ class NetrcTestCase(unittest.TestCase):
|
||||||
mode += 't'
|
mode += 't'
|
||||||
with open(temp_filename, mode) as fp:
|
with open(temp_filename, mode) as fp:
|
||||||
fp.write(test_data)
|
fp.write(test_data)
|
||||||
|
self.addCleanup(os.unlink, temp_filename)
|
||||||
return netrc.netrc(temp_filename)
|
return netrc.netrc(temp_filename)
|
||||||
|
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
|
@ -103,6 +101,28 @@ class NetrcTestCase(unittest.TestCase):
|
||||||
""", '#pass')
|
""", '#pass')
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(os.name == 'posix', 'POSIX only test')
|
||||||
|
def test_security(self):
|
||||||
|
# This test is incomplete since we are normally not run as root and
|
||||||
|
# therefore can't test the file ownership being wrong.
|
||||||
|
d = support.TESTFN
|
||||||
|
os.mkdir(d)
|
||||||
|
self.addCleanup(support.rmtree, d)
|
||||||
|
fn = os.path.join(d, '.netrc')
|
||||||
|
with open(fn, 'wt') as f:
|
||||||
|
f.write("""\
|
||||||
|
machine foo.domain.com login bar password pass
|
||||||
|
default login foo password pass
|
||||||
|
""")
|
||||||
|
with support.EnvironmentVarGuard() as environ:
|
||||||
|
environ.set('HOME', d)
|
||||||
|
os.chmod(fn, 0o600)
|
||||||
|
nrc = netrc.netrc()
|
||||||
|
self.assertEqual(nrc.hosts['foo.domain.com'],
|
||||||
|
('bar', None, 'pass'))
|
||||||
|
os.chmod(fn, 0o622)
|
||||||
|
self.assertRaises(netrc.NetrcParseError, netrc.netrc)
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(NetrcTestCase)
|
support.run_unittest(NetrcTestCase)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,12 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #14984: On POSIX systems, when netrc is called without a filename
|
||||||
|
argument (and therefore is reading the user's $HOME/.netrc file), it now
|
||||||
|
enforces the same security rules as typical ftp clients: the .netrc file must
|
||||||
|
be owned by the user that owns the process and must not be readable by any
|
||||||
|
other user.
|
||||||
|
|
||||||
- Issue #18873: The tokenize module now detects Python source code encoding
|
- Issue #18873: The tokenize module now detects Python source code encoding
|
||||||
only in comment lines.
|
only in comment lines.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue