diff --git a/Doc/library/spwd.rst b/Doc/library/spwd.rst --- a/Doc/library/spwd.rst +++ b/Doc/library/spwd.rst @@ -49,16 +49,18 @@ The sp_namp and sp_pwdp items are string The following functions are defined: .. function:: getspnam(name) Return the shadow password database entry for the given user name. + .. versionchanged:: 3.6 + Raised a :exc:`PermissionError` if user doesn't have enough privileges. .. function:: getspall() Return a list of all available shadow password database entries, in arbitrary order. .. seealso:: diff --git a/Lib/test/test_spwd.py b/Lib/test/test_spwd.py --- a/Lib/test/test_spwd.py +++ b/Lib/test/test_spwd.py @@ -51,10 +51,20 @@ class TestSpwdRoot(unittest.TestCase): try: bytes_name = os.fsencode(random_name) except UnicodeEncodeError: pass else: self.assertRaises(TypeError, spwd.getspnam, bytes_name) +@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() != 0, + 'non-root user required') +class TestSpwdNonRoot(unittest.TestCase): + + def test_getspnam_exception(self): + with self.assertRaises(PermissionError) as cm: + spwd.getspnam('bin') + self.assertEqual(str(cm.exception), '[Errno 13] Permission denied') + + if __name__ == "__main__": unittest.main() diff --git a/Modules/spwdmodule.c b/Modules/spwdmodule.c --- a/Modules/spwdmodule.c +++ b/Modules/spwdmodule.c @@ -132,17 +132,20 @@ spwd_getspnam_impl(PyModuleDef *module, struct spwd *p; PyObject *bytes, *retval = NULL; if ((bytes = PyUnicode_EncodeFSDefault(arg)) == NULL) return NULL; if (PyBytes_AsStringAndSize(bytes, &name, NULL) == -1) goto out; if ((p = getspnam(name)) == NULL) { - PyErr_SetString(PyExc_KeyError, "getspnam(): name not found"); + if (errno != 0) + PyErr_SetFromErrno(PyExc_OSError); + else + PyErr_SetString(PyExc_KeyError, "getspnam(): name not found"); goto out; } retval = mkspent(p); out: Py_DECREF(bytes); return retval; }